From be5aa4fd1a175a942a8b31d36bca045e871f1e83 Mon Sep 17 00:00:00 2001 From: Kai <33246768+KayzzzZ@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:59:16 +0800 Subject: [PATCH] Delete the previous code related to the eBPF observer. (#1836) Signed-off-by: qianlu.kk --- .github/workflows/build-core-ut.yaml | 2 +- core/CMakeLists.txt | 4 - core/common/common.cmake | 6 - .../protocol/picohttpparser/picohttpparser.c | 694 --------------- .../protocol/picohttpparser/picohttpparser.h | 102 --- core/config/PipelineConfig.cpp | 23 +- core/monitor/LogtailAlarm.cpp | 4 - core/monitor/LogtailAlarm.h | 4 - core/monitor/Monitor.cpp | 6 - core/observer/ObserverManager.h | 51 -- core/observer/docs/schema.md | 108 --- core/observer/interface/global.cpp | 57 -- core/observer/interface/global.h | 61 -- core/observer/interface/helper.h | 225 ----- core/observer/interface/layerfour.cpp | 61 -- core/observer/interface/layerfour.h | 120 --- core/observer/interface/network.h | 126 --- core/observer/interface/protocol.h | 98 --- core/observer/interface/statistics.h | 395 --------- core/observer/interface/type.h | 210 ----- core/observer/metas/CGroupPathResolver.cpp | 232 ----- core/observer/metas/CGroupPathResolver.h | 68 -- core/observer/metas/ConnectionMetaManager.cpp | 524 ------------ core/observer/metas/ConnectionMetaManager.h | 201 ----- core/observer/metas/ContainerProcessGroup.cpp | 324 ------- core/observer/metas/ContainerProcessGroup.h | 186 ---- core/observer/metas/K8sMeta.h | 56 -- core/observer/metas/ProcessMeta.h | 178 ---- core/observer/metas/ServiceMetaCache.cpp | 141 --- core/observer/metas/ServiceMetaCache.h | 151 ---- core/observer/network/ConnectionObserver.h | 188 ---- core/observer/network/NetworkConfig.cpp | 391 --------- core/observer/network/NetworkConfig.h | 165 ---- core/observer/network/NetworkObserver.cpp | 576 ------------- core/observer/network/NetworkObserver.h | 155 ---- core/observer/network/ProcessObserver.cpp | 49 -- core/observer/network/ProcessObserver.h | 94 -- .../protocols/ProtocolEventAggregators.cpp | 63 -- .../protocols/ProtocolEventAggregators.h | 123 --- core/observer/network/protocols/category.h | 231 ----- core/observer/network/protocols/common.h | 437 ---------- core/observer/network/protocols/dns/README.md | 11 - .../network/protocols/dns/inner_parser.cpp | 185 ---- .../network/protocols/dns/inner_parser.h | 138 --- .../observer/network/protocols/dns/parser.cpp | 162 ---- core/observer/network/protocols/dns/parser.h | 93 -- core/observer/network/protocols/dns/type.h | 31 - .../network/protocols/http/inner_parser.h | 148 ---- .../network/protocols/http/parser.cpp | 140 --- core/observer/network/protocols/http/parser.h | 117 --- core/observer/network/protocols/http/type.h | 36 - core/observer/network/protocols/infer.h | 315 ------- .../network/protocols/mysql/README.md | 4 - .../network/protocols/mysql/inner_parser.cpp | 338 -------- .../network/protocols/mysql/inner_parser.h | 163 ---- .../network/protocols/mysql/parser.cpp | 109 --- .../observer/network/protocols/mysql/parser.h | 94 -- core/observer/network/protocols/mysql/type.h | 30 - .../network/protocols/pgsql/inner_parser.cpp | 140 --- .../network/protocols/pgsql/inner_parser.h | 63 -- .../network/protocols/pgsql/parser.cpp | 109 --- .../observer/network/protocols/pgsql/parser.h | 111 --- core/observer/network/protocols/pgsql/type.h | 29 - .../network/protocols/redis/inner_parser.cpp | 100 --- .../network/protocols/redis/inner_parser.h | 61 -- .../network/protocols/redis/parser.cpp | 93 -- .../observer/network/protocols/redis/parser.h | 107 --- core/observer/network/protocols/redis/type.h | 30 - core/observer/network/protocols/utils.h | 279 ------ .../network/sources/ebpf/EBPFWrapper.cpp | 806 ------------------ .../network/sources/ebpf/EBPFWrapper.h | 132 --- .../network/sources/ebpf/include/net.h | 248 ------ .../network/sources/ebpf/include/syscall.h | 159 ---- .../network/sources/pcap/PCAPWrapper.cpp | 474 ---------- .../network/sources/pcap/PCAPWrapper.h | 93 -- core/pipeline/Pipeline.h | 2 +- core/pipeline/plugin/PluginRegistry.cpp | 2 - core/plugin/flusher/sls/FlusherSLS.h | 2 +- core/plugin/input/InputObserverNetwork.cpp | 40 - core/plugin/input/InputObserverNetwork.h | 38 - core/plugin/input/input.cmake | 7 +- core/unittest/CMakeLists.txt | 3 - core/unittest/config/ConfigUpdateUnittest.cpp | 6 +- .../config/PipelineConfigUnittest.cpp | 20 - core/unittest/observer/CGoupMetaUnittest.cpp | 399 --------- core/unittest/observer/CMakeLists.txt | 58 -- .../observer/HostnameMetaUnittest.cpp | 94 -- core/unittest/observer/JsonNetPacketReader.h | 195 ----- core/unittest/observer/NetLinkUnittest.cpp | 121 --- .../observer/NetworkObserverUnittest.cpp | 258 ------ .../observer/ObserverConfigUnittest.cpp | 148 ---- .../unittest/observer/ProtocolDnsUnittest.cpp | 429 ---------- .../observer/ProtocolHttpUnittest.cpp | 520 ----------- .../observer/ProtocolInferUnittest.cpp | 58 -- .../observer/ProtocolMySqlUnittest.cpp | 328 ------- .../observer/ProtocolPgsqlUnittest.cpp | 261 ------ .../observer/ProtocolRedisUnittest.cpp | 224 ----- .../observer/ProtocolUtilUnittest.cpp | 349 -------- core/unittest/observer/RawNetPacketReader.h | 222 ----- core/unittest/observer/cgroup.tar | Bin 14665728 -> 0 bytes docker/Dockerfile_coverage | 2 +- .../LICENSE_OF_ILOGTAIL_CPP_DEPENDENCIES.md | 1 - .../input_observer_dns/Dockerfile_client | 26 - .../input_observer_dns/Dockerfile_server | 26 - .../input_observer_dns/case.feature | 28 - .../input_observer_dns/docker-compose.yaml | 39 - .../test_cases/input_observer_dns/mock/dns.go | 123 --- .../test_cases/input_observer_dns/mock/go.mod | 11 - .../test_cases/input_observer_dns/mock/go.sum | 42 - .../input_observer_dns/mock/main.go | 84 -- .../input_observer_http/Dockerfile_client | 26 - .../input_observer_http/Dockerfile_server | 26 - .../input_observer_http/case.feature | 27 - .../input_observer_http/docker-compose.yaml | 39 - .../input_observer_http/mock/go.mod | 9 - .../input_observer_http/mock/go.sum | 6 - .../input_observer_http/mock/main.go | 149 ---- 117 files changed, 12 insertions(+), 16474 deletions(-) delete mode 100644 core/common/protocol/picohttpparser/picohttpparser.c delete mode 100644 core/common/protocol/picohttpparser/picohttpparser.h delete mode 100644 core/observer/ObserverManager.h delete mode 100644 core/observer/docs/schema.md delete mode 100644 core/observer/interface/global.cpp delete mode 100644 core/observer/interface/global.h delete mode 100644 core/observer/interface/helper.h delete mode 100644 core/observer/interface/layerfour.cpp delete mode 100644 core/observer/interface/layerfour.h delete mode 100644 core/observer/interface/network.h delete mode 100644 core/observer/interface/protocol.h delete mode 100644 core/observer/interface/statistics.h delete mode 100644 core/observer/interface/type.h delete mode 100644 core/observer/metas/CGroupPathResolver.cpp delete mode 100644 core/observer/metas/CGroupPathResolver.h delete mode 100644 core/observer/metas/ConnectionMetaManager.cpp delete mode 100644 core/observer/metas/ConnectionMetaManager.h delete mode 100644 core/observer/metas/ContainerProcessGroup.cpp delete mode 100644 core/observer/metas/ContainerProcessGroup.h delete mode 100644 core/observer/metas/K8sMeta.h delete mode 100644 core/observer/metas/ProcessMeta.h delete mode 100644 core/observer/metas/ServiceMetaCache.cpp delete mode 100644 core/observer/metas/ServiceMetaCache.h delete mode 100644 core/observer/network/ConnectionObserver.h delete mode 100644 core/observer/network/NetworkConfig.cpp delete mode 100644 core/observer/network/NetworkConfig.h delete mode 100644 core/observer/network/NetworkObserver.cpp delete mode 100644 core/observer/network/NetworkObserver.h delete mode 100644 core/observer/network/ProcessObserver.cpp delete mode 100644 core/observer/network/ProcessObserver.h delete mode 100644 core/observer/network/protocols/ProtocolEventAggregators.cpp delete mode 100644 core/observer/network/protocols/ProtocolEventAggregators.h delete mode 100644 core/observer/network/protocols/category.h delete mode 100644 core/observer/network/protocols/common.h delete mode 100644 core/observer/network/protocols/dns/README.md delete mode 100644 core/observer/network/protocols/dns/inner_parser.cpp delete mode 100644 core/observer/network/protocols/dns/inner_parser.h delete mode 100644 core/observer/network/protocols/dns/parser.cpp delete mode 100644 core/observer/network/protocols/dns/parser.h delete mode 100644 core/observer/network/protocols/dns/type.h delete mode 100644 core/observer/network/protocols/http/inner_parser.h delete mode 100644 core/observer/network/protocols/http/parser.cpp delete mode 100644 core/observer/network/protocols/http/parser.h delete mode 100644 core/observer/network/protocols/http/type.h delete mode 100644 core/observer/network/protocols/infer.h delete mode 100644 core/observer/network/protocols/mysql/README.md delete mode 100644 core/observer/network/protocols/mysql/inner_parser.cpp delete mode 100644 core/observer/network/protocols/mysql/inner_parser.h delete mode 100644 core/observer/network/protocols/mysql/parser.cpp delete mode 100644 core/observer/network/protocols/mysql/parser.h delete mode 100644 core/observer/network/protocols/mysql/type.h delete mode 100644 core/observer/network/protocols/pgsql/inner_parser.cpp delete mode 100644 core/observer/network/protocols/pgsql/inner_parser.h delete mode 100644 core/observer/network/protocols/pgsql/parser.cpp delete mode 100644 core/observer/network/protocols/pgsql/parser.h delete mode 100644 core/observer/network/protocols/pgsql/type.h delete mode 100644 core/observer/network/protocols/redis/inner_parser.cpp delete mode 100644 core/observer/network/protocols/redis/inner_parser.h delete mode 100644 core/observer/network/protocols/redis/parser.cpp delete mode 100644 core/observer/network/protocols/redis/parser.h delete mode 100644 core/observer/network/protocols/redis/type.h delete mode 100644 core/observer/network/protocols/utils.h delete mode 100644 core/observer/network/sources/ebpf/EBPFWrapper.cpp delete mode 100644 core/observer/network/sources/ebpf/EBPFWrapper.h delete mode 100644 core/observer/network/sources/ebpf/include/net.h delete mode 100644 core/observer/network/sources/ebpf/include/syscall.h delete mode 100644 core/observer/network/sources/pcap/PCAPWrapper.cpp delete mode 100644 core/observer/network/sources/pcap/PCAPWrapper.h delete mode 100644 core/plugin/input/InputObserverNetwork.cpp delete mode 100644 core/plugin/input/InputObserverNetwork.h delete mode 100644 core/unittest/observer/CGoupMetaUnittest.cpp delete mode 100644 core/unittest/observer/CMakeLists.txt delete mode 100644 core/unittest/observer/HostnameMetaUnittest.cpp delete mode 100644 core/unittest/observer/JsonNetPacketReader.h delete mode 100644 core/unittest/observer/NetLinkUnittest.cpp delete mode 100644 core/unittest/observer/NetworkObserverUnittest.cpp delete mode 100644 core/unittest/observer/ObserverConfigUnittest.cpp delete mode 100644 core/unittest/observer/ProtocolDnsUnittest.cpp delete mode 100644 core/unittest/observer/ProtocolHttpUnittest.cpp delete mode 100644 core/unittest/observer/ProtocolInferUnittest.cpp delete mode 100644 core/unittest/observer/ProtocolMySqlUnittest.cpp delete mode 100644 core/unittest/observer/ProtocolPgsqlUnittest.cpp delete mode 100644 core/unittest/observer/ProtocolRedisUnittest.cpp delete mode 100644 core/unittest/observer/ProtocolUtilUnittest.cpp delete mode 100644 core/unittest/observer/RawNetPacketReader.h delete mode 100644 core/unittest/observer/cgroup.tar delete mode 100644 test/e2e/test_cases/input_observer_dns/Dockerfile_client delete mode 100644 test/e2e/test_cases/input_observer_dns/Dockerfile_server delete mode 100644 test/e2e/test_cases/input_observer_dns/case.feature delete mode 100644 test/e2e/test_cases/input_observer_dns/docker-compose.yaml delete mode 100644 test/e2e/test_cases/input_observer_dns/mock/dns.go delete mode 100644 test/e2e/test_cases/input_observer_dns/mock/go.mod delete mode 100644 test/e2e/test_cases/input_observer_dns/mock/go.sum delete mode 100644 test/e2e/test_cases/input_observer_dns/mock/main.go delete mode 100644 test/e2e/test_cases/input_observer_http/Dockerfile_client delete mode 100644 test/e2e/test_cases/input_observer_http/Dockerfile_server delete mode 100644 test/e2e/test_cases/input_observer_http/case.feature delete mode 100644 test/e2e/test_cases/input_observer_http/docker-compose.yaml delete mode 100644 test/e2e/test_cases/input_observer_http/mock/go.mod delete mode 100644 test/e2e/test_cases/input_observer_http/mock/go.sum delete mode 100644 test/e2e/test_cases/input_observer_http/mock/main.go diff --git a/.github/workflows/build-core-ut.yaml b/.github/workflows/build-core-ut.yaml index e9d77f4e80..c0db279b1f 100644 --- a/.github/workflows/build-core-ut.yaml +++ b/.github/workflows/build-core-ut.yaml @@ -82,7 +82,7 @@ jobs: run: make unittest_core - name: Unit Test Coverage - run: docker build -t unittest_coverage -f ./docker/Dockerfile_coverage . && docker run -v $(pwd):$(pwd) unittest_coverage bash -c "cd $(pwd)/core && gcovr --gcov-ignore-errors=no_working_dir_found --root . --json coverage.json --json-summary-pretty --json-summary summary.json -e \".*sdk.*\" -e \".*observer.*\" -e \".*logger.*\" -e \".*unittest.*\" -e \".*config_server.*\" -e \".*go_pipeline.*\" -e \".*application.*\" -e \".*protobuf.*\" -e \".*runner.*\"" + run: docker build -t unittest_coverage -f ./docker/Dockerfile_coverage . && docker run -v $(pwd):$(pwd) unittest_coverage bash -c "cd $(pwd)/core && gcovr --gcov-ignore-errors=no_working_dir_found --root . --json coverage.json --json-summary-pretty --json-summary summary.json -e \".*sdk.*\" -e \".*logger.*\" -e \".*unittest.*\" -e \".*config_server.*\" -e \".*go_pipeline.*\" -e \".*application.*\" -e \".*protobuf.*\" -e \".*runner.*\"" - name: Setup Python3.10 uses: actions/setup-python@v5 diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index fbe3e45714..b48a538935 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -141,7 +141,6 @@ endif() include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories("/opt/logtail_spl/include") if (LINUX) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/observer) if (WITHSPL) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/spl) endif() @@ -177,9 +176,6 @@ elseif(UNIX) file(GLOB REMOVE_EVENT_LISTENER_SOURCES file_server/event_listener/*_Windows.cpp file_server/event_listener/*_Windows.h) list(REMOVE_ITEM FRAMEWORK_SOURCE_FILES ${REMOVE_EVENT_LISTENER_SOURCES}) if (LINUX) - # observer - file(GLOB_RECURSE APPEND_OBSERVER_SOURCES observer/*) - list(APPEND FRAMEWORK_SOURCE_FILES ${APPEND_OBSERVER_SOURCES}) if (WITHSPL) set(SRC_FILES ${PLUGIN_SOURCE_FILES_SPL}) endif() diff --git a/core/common/common.cmake b/core/common/common.cmake index caf8e45b98..d26b5acfe9 100644 --- a/core/common/common.cmake +++ b/core/common/common.cmake @@ -39,12 +39,6 @@ if(MSVC) if (ENABLE_ENTERPRISE) list(REMOVE_ITEM THIS_SOURCE_FILES_LIST ${CMAKE_SOURCE_DIR}/common/LinuxDaemonUtil.h ${CMAKE_SOURCE_DIR}/common/LinuxDaemonUtil.cpp) endif() -elseif(UNIX) - if (LINUX) - # needed by observer in common - file(GLOB PICOHTTPPARSER_SOURCE_FILES ${CMAKE_SOURCE_DIR}/common/protocol/picohttpparser/*.c ${CMAKE_SOURCE_DIR}/common/protocol/picohttpparser/*.h) - list(APPEND THIS_SOURCE_FILES_LIST ${PICOHTTPPARSER_SOURCE_FILES}) - endif() endif() # Set source files to parent diff --git a/core/common/protocol/picohttpparser/picohttpparser.c b/core/common/protocol/picohttpparser/picohttpparser.c deleted file mode 100644 index c120924ce7..0000000000 --- a/core/common/protocol/picohttpparser/picohttpparser.c +++ /dev/null @@ -1,694 +0,0 @@ -/* - * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, - * Shigeo Mitsunari - * - * The software is licensed under either the MIT License (below) or the Perl - * license. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include -#include -#include -#ifdef __SSE4_2__ -#ifdef _MSC_VER -#include -#else -#include -#endif -#endif -#include "picohttpparser.h" - -#if __GNUC__ >= 3 -#define likely(x) __builtin_expect(!!(x), 1) -#define unlikely(x) __builtin_expect(!!(x), 0) -#else -#define likely(x) (x) -#define unlikely(x) (x) -#endif - -#ifdef _MSC_VER -#define ALIGNED(n) _declspec(align(n)) -#else -#define ALIGNED(n) __attribute__((aligned(n))) -#endif - -#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u) - -#define CHECK_EOF() \ - if (buf == buf_end) { \ - *ret = -2; \ - return NULL; \ - } - -#define EXPECT_CHAR_NO_CHECK(ch) \ - if (*buf++ != ch) { \ - *ret = -1; \ - return NULL; \ - } - -#define EXPECT_CHAR(ch) \ - CHECK_EOF(); \ - EXPECT_CHAR_NO_CHECK(ch); - -#define ADVANCE_TOKEN(tok, toklen) \ - do { \ - const char* tok_start = buf; \ - static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \ - int found2; \ - buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ - if (!found2) { \ - CHECK_EOF(); \ - } \ - while (1) { \ - if (*buf == ' ') { \ - break; \ - } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ - if ((unsigned char)*buf < '\040' || *buf == '\177') { \ - *ret = -1; \ - return NULL; \ - } \ - } \ - ++buf; \ - CHECK_EOF(); \ - } \ - tok = tok_start; \ - toklen = buf - tok_start; \ - } while (0) - -static const char* token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" - "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" - "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; - -static const char* -findchar_fast(const char* buf, const char* buf_end, const char* ranges, size_t ranges_size, int* found) { - *found = 0; -#if __SSE4_2__ - if (likely(buf_end - buf >= 16)) { - __m128i ranges16 = _mm_loadu_si128((const __m128i*)ranges); - - size_t left = (buf_end - buf) & ~15; - do { - __m128i b16 = _mm_loadu_si128((const __m128i*)buf); - int r = _mm_cmpestri( - ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); - if (unlikely(r != 16)) { - buf += r; - *found = 1; - break; - } - buf += 16; - left -= 16; - } while (likely(left != 0)); - } -#else - /* suppress unused parameter warning */ - (void)buf_end; - (void)ranges; - (void)ranges_size; -#endif - return buf; -} - -static const char* -get_token_to_eol(const char* buf, const char* buf_end, const char** token, size_t* token_len, int* ret) { - const char* token_start = buf; - -#ifdef __SSE4_2__ - static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */ - "\012\037" /* allow SP and up to but not including DEL */ - "\177\177"; /* allow chars w. MSB set */ - int found; - buf = findchar_fast(buf, buf_end, ranges1, 6, &found); - if (found) - goto FOUND_CTL; -#else - /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ - while (likely(buf_end - buf >= 8)) { -#define DOIT() \ - do { \ - if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ - goto NonPrintable; \ - ++buf; \ - } while (0) - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); -#undef DOIT - continue; - NonPrintable: - if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { - goto FOUND_CTL; - } - ++buf; - } -#endif - for (;; ++buf) { - CHECK_EOF(); - if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { - if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { - goto FOUND_CTL; - } - } - } -FOUND_CTL: - if (likely(*buf == '\015')) { - ++buf; - EXPECT_CHAR('\012'); - *token_len = buf - 2 - token_start; - } else if (*buf == '\012') { - *token_len = buf - token_start; - ++buf; - } else { - *ret = -1; - return NULL; - } - *token = token_start; - - return buf; -} - -static const char* is_complete(const char* buf, const char* buf_end, size_t last_len, int* ret) { - int ret_cnt = 0; - buf = last_len < 3 ? buf : buf + last_len - 3; - - while (1) { - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - CHECK_EOF(); - EXPECT_CHAR('\012'); - ++ret_cnt; - } else if (*buf == '\012') { - ++buf; - ++ret_cnt; - } else { - ++buf; - ret_cnt = 0; - } - if (ret_cnt == 2) { - return buf; - } - } - - *ret = -2; - return NULL; -} - -#define PARSE_INT(valp_, mul_) \ - if (*buf < '0' || '9' < *buf) { \ - buf++; \ - *ret = -1; \ - return NULL; \ - } \ - *(valp_) = (mul_) * (*buf++ - '0'); - -#define PARSE_INT_3(valp_) \ - do { \ - int res_ = 0; \ - PARSE_INT(&res_, 100) \ - *valp_ = res_; \ - PARSE_INT(&res_, 10) \ - *valp_ += res_; \ - PARSE_INT(&res_, 1) \ - *valp_ += res_; \ - } while (0) - -/* returned pointer is always within [buf, buf_end), or null */ -static const char* -parse_token(const char* buf, const char* buf_end, const char** token, size_t* token_len, char next_char, int* ret) { - /* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128 - * bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */ - static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */ - "\"\"" /* 0x22 */ - "()" /* 0x28,0x29 */ - ",," /* 0x2c */ - "//" /* 0x2f */ - ":@" /* 0x3a-0x40 */ - "[]" /* 0x5b-0x5d */ - "{\xff"; /* 0x7b-0xff */ - const char* buf_start = buf; - int found; - buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); - if (!found) { - CHECK_EOF(); - } - while (1) { - if (*buf == next_char) { - break; - } else if (!token_char_map[(unsigned char)*buf]) { - *ret = -1; - return NULL; - } - ++buf; - CHECK_EOF(); - } - *token = buf_start; - *token_len = buf - buf_start; - return buf; -} - -/* returned pointer is always within [buf, buf_end), or null */ -static const char* parse_http_version(const char* buf, const char* buf_end, int* minor_version, int* ret) { - /* we want at least [HTTP/1.] to try to parse */ - if (buf_end - buf < 9) { - *ret = -2; - return NULL; - } - EXPECT_CHAR_NO_CHECK('H'); - EXPECT_CHAR_NO_CHECK('T'); - EXPECT_CHAR_NO_CHECK('T'); - EXPECT_CHAR_NO_CHECK('P'); - EXPECT_CHAR_NO_CHECK('/'); - EXPECT_CHAR_NO_CHECK('1'); - EXPECT_CHAR_NO_CHECK('.'); - PARSE_INT(minor_version, 1); - return buf; -} - -static const char* parse_headers(const char* buf, - const char* buf_end, - struct phr_header* headers, - size_t* num_headers, - size_t max_headers, - int* ret) { - for (;; ++*num_headers) { - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); - break; - } else if (*buf == '\012') { - ++buf; - break; - } - if (*num_headers == max_headers) { - *ret = -1; - return NULL; - } - if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { - /* parsing name, but do not discard SP before colon, see - * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ - if ((buf - = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) - == NULL) { - return NULL; - } - if (headers[*num_headers].name_len == 0) { - *ret = -1; - return NULL; - } - ++buf; - for (;; ++buf) { - CHECK_EOF(); - if (!(*buf == ' ' || *buf == '\t')) { - break; - } - } - } else { - headers[*num_headers].name = NULL; - headers[*num_headers].name_len = 0; - } - const char* value; - size_t value_len; - if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) { - return NULL; - } - /* remove trailing SPs and HTABs */ - const char* value_end = value + value_len; - for (; value_end != value; --value_end) { - const char c = *(value_end - 1); - if (!(c == ' ' || c == '\t')) { - break; - } - } - headers[*num_headers].value = value; - headers[*num_headers].value_len = value_end - value; - } - return buf; -} - -static const char* parse_request(const char* buf, - const char* buf_end, - const char** method, - size_t* method_len, - const char** path, - size_t* path_len, - int* minor_version, - struct phr_header* headers, - size_t* num_headers, - size_t max_headers, - int* ret) { - /* skip first empty line (some clients add CRLF after POST content) */ - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); - } else if (*buf == '\012') { - ++buf; - } - - /* parse request line */ - if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) { - return NULL; - } - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - ADVANCE_TOKEN(*path, *path_len); - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - if (*method_len == 0 || *path_len == 0) { - *ret = -1; - return NULL; - } - if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { - return NULL; - } - if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); - } else if (*buf == '\012') { - ++buf; - } else { - *ret = -1; - return NULL; - } - - return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); -} - -int phr_parse_request(const char* buf_start, - size_t len, - const char** method, - size_t* method_len, - const char** path, - size_t* path_len, - int* minor_version, - struct phr_header* headers, - size_t* num_headers, - size_t last_len) { - const char *buf = buf_start, *buf_end = buf_start + len; - size_t max_headers = *num_headers; - int r; - - *method = NULL; - *method_len = 0; - *path = NULL; - *path_len = 0; - *minor_version = -1; - *num_headers = 0; - - /* if last_len != 0, check if the request is complete (a fast countermeasure - againt slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_request( - buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers, &r)) - == NULL) { - return r; - } - - return (int)(buf - buf_start); -} - -static const char* parse_response(const char* buf, - const char* buf_end, - int* minor_version, - int* status, - const char** msg, - size_t* msg_len, - struct phr_header* headers, - size_t* num_headers, - size_t max_headers, - int* ret) { - /* parse "HTTP/1.x" */ - if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { - return NULL; - } - /* skip space */ - if (*buf != ' ') { - *ret = -1; - return NULL; - } - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - /* parse status code, we want at least [:digit:][:digit:][:digit:] to try to parse */ - if (buf_end - buf < 4) { - *ret = -2; - return NULL; - } - PARSE_INT_3(status); - - /* get message including preceding space */ - if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { - return NULL; - } - if (*msg_len == 0) { - /* ok */ - } else if (**msg == ' ') { - /* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP - * before running past the end of the given buffer. */ - do { - ++*msg; - --*msg_len; - } while (**msg == ' '); - } else { - /* garbage found after status code */ - *ret = -1; - return NULL; - } - - return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); -} - -int phr_parse_response(const char* buf_start, - size_t len, - int* minor_version, - int* status, - const char** msg, - size_t* msg_len, - struct phr_header* headers, - size_t* num_headers, - size_t last_len) { - const char *buf = buf_start, *buf_end = buf + len; - size_t max_headers = *num_headers; - int r; - - *minor_version = -1; - *status = 0; - *msg = NULL; - *msg_len = 0; - *num_headers = 0; - - /* if last_len != 0, check if the response is complete (a fast countermeasure - against slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) - == NULL) { - return r; - } - - return (int)(buf - buf_start); -} - -int phr_parse_headers( - const char* buf_start, size_t len, struct phr_header* headers, size_t* num_headers, size_t last_len) { - const char *buf = buf_start, *buf_end = buf + len; - size_t max_headers = *num_headers; - int r; - - *num_headers = 0; - - /* if last_len != 0, check if the response is complete (a fast countermeasure - against slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) { - return r; - } - - return (int)(buf - buf_start); -} - -enum { - CHUNKED_IN_CHUNK_SIZE, - CHUNKED_IN_CHUNK_EXT, - CHUNKED_IN_CHUNK_DATA, - CHUNKED_IN_CHUNK_CRLF, - CHUNKED_IN_TRAILERS_LINE_HEAD, - CHUNKED_IN_TRAILERS_LINE_MIDDLE -}; - -static int decode_hex(int ch) { - if ('0' <= ch && ch <= '9') { - return ch - '0'; - } else if ('A' <= ch && ch <= 'F') { - return ch - 'A' + 0xa; - } else if ('a' <= ch && ch <= 'f') { - return ch - 'a' + 0xa; - } else { - return -1; - } -} - -ssize_t phr_decode_chunked(struct phr_chunked_decoder* decoder, char* buf, size_t* _bufsz) { - size_t dst = 0, src = 0, bufsz = *_bufsz; - ssize_t ret = -2; /* incomplete */ - - while (1) { - switch (decoder->_state) { - case CHUNKED_IN_CHUNK_SIZE: - for (;; ++src) { - int v; - if (src == bufsz) - goto Exit; - if ((v = decode_hex(buf[src])) == -1) { - if (decoder->_hex_count == 0) { - ret = -1; - goto Exit; - } - break; - } - if (decoder->_hex_count == sizeof(size_t) * 2) { - ret = -1; - goto Exit; - } - decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; - ++decoder->_hex_count; - } - decoder->_hex_count = 0; - decoder->_state = CHUNKED_IN_CHUNK_EXT; - /* fallthru */ - case CHUNKED_IN_CHUNK_EXT: - /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] == '\012') - break; - } - ++src; - if (decoder->bytes_left_in_chunk == 0) { - if (decoder->consume_trailer) { - decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; - break; - } else { - goto Complete; - } - } - decoder->_state = CHUNKED_IN_CHUNK_DATA; - /* fallthru */ - case CHUNKED_IN_CHUNK_DATA: { - size_t avail = bufsz - src; - if (avail < decoder->bytes_left_in_chunk) { - if (dst != src) - memmove(buf + dst, buf + src, avail); - src += avail; - dst += avail; - decoder->bytes_left_in_chunk -= avail; - goto Exit; - } - if (dst != src) - memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); - src += decoder->bytes_left_in_chunk; - dst += decoder->bytes_left_in_chunk; - decoder->bytes_left_in_chunk = 0; - decoder->_state = CHUNKED_IN_CHUNK_CRLF; - } - /* fallthru */ - case CHUNKED_IN_CHUNK_CRLF: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] != '\015') - break; - } - if (buf[src] != '\012') { - ret = -1; - goto Exit; - } - ++src; - decoder->_state = CHUNKED_IN_CHUNK_SIZE; - break; - case CHUNKED_IN_TRAILERS_LINE_HEAD: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] != '\015') - break; - } - if (buf[src++] == '\012') - goto Complete; - decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; - /* fallthru */ - case CHUNKED_IN_TRAILERS_LINE_MIDDLE: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] == '\012') - break; - } - ++src; - decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; - break; - default: - assert(!"decoder is corrupt"); - } - } - -Complete: - ret = bufsz - src; -Exit: - if (dst != src) - memmove(buf + dst, buf + src, bufsz - src); - *_bufsz = dst; - return ret; -} - -int phr_decode_chunked_is_in_data(struct phr_chunked_decoder* decoder) { - return decoder->_state == CHUNKED_IN_CHUNK_DATA; -} - -#undef CHECK_EOF -#undef EXPECT_CHAR -#undef ADVANCE_TOKEN \ No newline at end of file diff --git a/core/common/protocol/picohttpparser/picohttpparser.h b/core/common/protocol/picohttpparser/picohttpparser.h deleted file mode 100644 index b38996bd1a..0000000000 --- a/core/common/protocol/picohttpparser/picohttpparser.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, - * Shigeo Mitsunari - * - * The software is licensed under either the MIT License (below) or the Perl - * license. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef picohttpparser_h -#define picohttpparser_h - -#include - -#ifdef _MSC_VER -#define ssize_t intptr_t -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* contains name and value of a header (name == NULL if is a continuing line - * of a multiline header */ -struct phr_header { - const char* name; - size_t name_len; - const char* value; - size_t value_len; -}; - -/* returns number of bytes consumed if successful, -2 if request is partial, - * -1 if failed */ -int phr_parse_request(const char* buf, - size_t len, - const char** method, - size_t* method_len, - const char** path, - size_t* path_len, - int* minor_version, - struct phr_header* headers, - size_t* num_headers, - size_t last_len); - -/* ditto */ -int phr_parse_response(const char* _buf, - size_t len, - int* minor_version, - int* status, - const char** msg, - size_t* msg_len, - struct phr_header* headers, - size_t* num_headers, - size_t last_len); - -/* ditto */ -int phr_parse_headers(const char* buf, size_t len, struct phr_header* headers, size_t* num_headers, size_t last_len); - -/* should be zero-filled before start */ -struct phr_chunked_decoder { - size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ - char consume_trailer; /* if trailing headers should be consumed */ - char _hex_count; - char _state; -}; - -/* the function rewrites the buffer given as (buf, bufsz) removing the chunked- - * encoding headers. When the function returns without an error, bufsz is - * updated to the length of the decoded data available. Applications should - * repeatedly call the function while it returns -2 (incomplete) every time - * supplying newly arrived data. If the end of the chunked-encoded data is - * found, the function returns a non-negative number indicating the number of - * octets left undecoded, that starts from the offset returned by `*bufsz`. - * Returns -1 on error. - */ -ssize_t phr_decode_chunked(struct phr_chunked_decoder* decoder, char* buf, size_t* bufsz); - -/* returns if the chunked decoder is in middle of chunked data */ -int phr_decode_chunked_is_in_data(struct phr_chunked_decoder* decoder); - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/core/config/PipelineConfig.cpp b/core/config/PipelineConfig.cpp index 993b5be2fe..febe01986f 100644 --- a/core/config/PipelineConfig.cpp +++ b/core/config/PipelineConfig.cpp @@ -138,7 +138,6 @@ bool PipelineConfig::Parse() { // inputs, processors and flushers module must be parsed first and parsed by order, since aggregators and // extensions module parsing will rely on their results. - bool hasObserverInput = false; bool hasFileInput = false; key = "inputs"; itr = mDetail->find(key.c_str(), key.c_str() + key.size()); @@ -237,15 +236,12 @@ bool PipelineConfig::Parse() { } mInputs.push_back(&plugin); // TODO: remove these special restrictions - if (pluginType == "input_observer_network") { - hasObserverInput = true; - } else if (pluginType == "input_file" || pluginType == "input_container_stdio") { + if (pluginType == "input_file" || pluginType == "input_container_stdio") { hasFileInput = true; } } // TODO: remove these special restrictions - bool hasSpecialInput = hasObserverInput || hasFileInput; - if (hasSpecialInput && (*mDetail)["inputs"].size() > 1) { + if (hasFileInput && (*mDetail)["inputs"].size() > 1) { PARAM_ERROR_RETURN(sLogger, alarm, "more than 1 input_file or input_container_stdio plugin is given", @@ -331,7 +327,7 @@ bool PipelineConfig::Parse() { if (isCurrentPluginNative) { if (PluginRegistry::GetInstance()->IsValidGoPlugin(pluginType)) { // TODO: remove these special restrictions - if (!hasObserverInput && !hasFileInput) { + if (!hasFileInput) { PARAM_ERROR_RETURN(sLogger, alarm, "extended processor plugins coexist with native input plugins other " @@ -365,17 +361,6 @@ bool PipelineConfig::Parse() { mRegion); } } else { - // TODO: remove these special restrictions - if (hasObserverInput) { - PARAM_ERROR_RETURN(sLogger, - alarm, - "native processor plugins coexist with input_observer_network", - noModule, - mName, - mProject, - mLogstore, - mRegion); - } mHasNativeProcessor = true; } } else { @@ -470,7 +455,7 @@ bool PipelineConfig::Parse() { const string pluginType = it->asString(); if (PluginRegistry::GetInstance()->IsValidGoPlugin(pluginType)) { // TODO: remove these special restrictions - if (mHasNativeInput && !hasFileInput && !hasObserverInput) { + if (mHasNativeInput && !hasFileInput) { PARAM_ERROR_RETURN(sLogger, alarm, "extended flusher plugins coexist with native input plugins other than " diff --git a/core/monitor/LogtailAlarm.cpp b/core/monitor/LogtailAlarm.cpp index 267c0518fd..45648ee305 100644 --- a/core/monitor/LogtailAlarm.cpp +++ b/core/monitor/LogtailAlarm.cpp @@ -98,10 +98,6 @@ LogtailAlarm::LogtailAlarm() { mMessageType[CHECKPOINT_V2_ALARM] = "CHECKPOINT_V2_ALARM"; mMessageType[EXACTLY_ONCE_ALARM] = "EXACTLY_ONCE_ALARM"; mMessageType[READ_STOPPED_CONTAINER_ALARM] = "READ_STOPPED_CONTAINER_ALARM"; - mMessageType[MULTI_OBSERVER_ALARM] = "MULTI_OBSERVER_ALARM"; - mMessageType[OBSERVER_INIT_ALARM] = "OBSERVER_INIT_ALARM"; - mMessageType[OBSERVER_RUNTIME_ALARM] = "OBSERVER_RUNTIME_ALARM"; - mMessageType[OBSERVER_STOP_ALARM] = "OBSERVER_STOP_ALARM"; mMessageType[INVALID_CONTAINER_PATH_ALARM] = "INVALID_CONTAINER_PATH_ALARM"; mMessageType[COMPRESS_FAIL_ALARM] = "COMPRESS_FAIL_ALARM"; mMessageType[SERIALIZE_FAIL_ALARM] = "SERIALIZE_FAIL_ALARM"; diff --git a/core/monitor/LogtailAlarm.h b/core/monitor/LogtailAlarm.h index 4dc88a8b5d..bd375c8f1c 100644 --- a/core/monitor/LogtailAlarm.h +++ b/core/monitor/LogtailAlarm.h @@ -91,10 +91,6 @@ enum LogtailAlarmType { CHECKPOINT_V2_ALARM = 57, EXACTLY_ONCE_ALARM = 58, READ_STOPPED_CONTAINER_ALARM = 59, - MULTI_OBSERVER_ALARM = 60, - OBSERVER_INIT_ALARM = 61, - OBSERVER_RUNTIME_ALARM = 62, - OBSERVER_STOP_ALARM = 63, INVALID_CONTAINER_PATH_ALARM = 64, COMPRESS_FAIL_ALARM = 65, SERIALIZE_FAIL_ALARM = 66, diff --git a/core/monitor/Monitor.cpp b/core/monitor/Monitor.cpp index 774ba97352..74bbf70aa4 100644 --- a/core/monitor/Monitor.cpp +++ b/core/monitor/Monitor.cpp @@ -41,9 +41,6 @@ #include "plugin/flusher/sls/FlusherSLS.h" #include "protobuf/sls/sls_logs.pb.h" #include "runner/FlusherRunner.h" -#if defined(__linux__) && !defined(__ANDROID__) -#include "ObserverManager.h" -#endif #include "application/Application.h" #include "sdk/Common.h" #ifdef __ENTERPRISE__ @@ -304,9 +301,6 @@ bool LogtailMonitor::SendStatusProfile(bool suicide) { #endif UpdateMetric("config_prefer_real_ip", BOOL_FLAG(send_prefer_real_ip)); UpdateMetric("plugin_enabled", LogtailPlugin::GetInstance()->IsPluginOpened()); -#if defined(__linux__) && !defined(__ANDROID__) - UpdateMetric("observer_enabled", ObserverManager::GetInstance()->Status()); -#endif const std::vector& envTags = AppConfig::GetInstance()->GetEnvTags(); if (!envTags.empty()) { UpdateMetric("env_config_count", envTags.size()); diff --git a/core/observer/ObserverManager.h b/core/observer/ObserverManager.h deleted file mode 100644 index 31df04243c..0000000000 --- a/core/observer/ObserverManager.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#pragma once -#include "network/NetworkObserver.h" - - -namespace logtail { - -enum LOGTAIL_OBSERVER_CATEGORY { - LOGTAILOBSERVERCATEGORY_NETWORK_OBSERVER, - LOGTAILOBSERVERCATEGORY_NUMBER, -}; - -class ObserverManager { -public: - static ObserverManager* GetInstance() { - static auto sInstance = new ObserverManager; - return sInstance; - } - // lock - void HoldOn(bool exitFlag = false) { NetworkObserver::GetInstance()->HoldOn(exitFlag); } - - // unlock - void Resume() { NetworkObserver::GetInstance()->Resume(); } - - // processing under locked - void Reload() { NetworkObserver::GetInstance()->Reload(); } - - int Status() { - int flag = 0; - flag |= NetworkConfig::GetInstance()->mEnabled ? 1 << LOGTAILOBSERVERCATEGORY_NETWORK_OBSERVER : 0; - return flag; - } - -private: - ObserverManager() = default; - ~ObserverManager() = default; -}; -} // namespace logtail \ No newline at end of file diff --git a/core/observer/docs/schema.md b/core/observer/docs/schema.md deleted file mode 100644 index 9706ea40f6..0000000000 --- a/core/observer/docs/schema.md +++ /dev/null @@ -1,108 +0,0 @@ -# Data structure - -The doc shows the observer data structure. Each metrics data structure contains 4 parts, which are connection labels, -process labels, timestamp and metrics values. - -## Common structure - -Common structure means which are referenced by the most other structures. - -### Local info - -| Column | Type | Meaning | Required column | -|--------------------|--------|-----------------------------------------------------|-----------------| -| `_namespace_` | string | local kubernetes namespace | false | -| `_container_name_` | string | local container name | false | -| `_pod_name_` | string | local kubernetes pod name | false | -| `_workload_name_` | string | local kubernetes workload name | false | -| `_running_mode_` | string | currently running mode, such as kubernetes and host | false | -| `__process_cmd_"_` | string | process command line | false | -| `__process_pid_"_` | string | process id | false | - -### Remote info - -| Column | Type | Meaning | Required column | -|-----------------|--------|---------------------|-----------------| -| `_remote_host_` | string | remote dns hostname | true | - -## Layer 7 data structure - -### DB metric data structure - -| Column | Type | Meaning | Required column | -|-----------------|--------------------|---------------------------------------------------------------------------------|-----------------| -| `__time__` | int | timestamp for aggregation | true | -| `_local_addr`_ | string | local address | true | -| `_local_port`_ | int | local port | true | -| `_remote_addr`_ | string | remote address | true | -| `_remote_port`_ | int | remote port | true | -| type | string | l7_db means L7 DB metrics, l7_req means L7 RPC metrics, and l4 means L4 metrics | true | -| local_info | `Local Info` Json | local information | true | -| remote_info | `Remote Info` Json | remote information | true | -| conn_id | int | each aggregation belongs to one unique connection id | true | -| interval | int | unit is seconds, aggregation interval | true | -| role | string | c means client, s means server | true | -| version | string | protocol version | true | -| protocol | string | L7 detect protocol | true | -| query_cmd | string | DB query command,such as select, insert and etc. | true | -| query | string | DB query statement. | true | -| extra | json | extra message for feature. | true | -| status | int | the query result, 0 means failure, 1 means success. | true | -| latency_ns | int | the total invoke cost ns | true | -| tdigest_latency | string | base64 tdigest data for compute Pxx | true | -| count | int | the total invoke cost | true | -| req_bytes | int | the total requst bytes | true | -| resp_bytes | int | the total response bytes | true | - -### Request metric data structure - -Request metric works in RPC, DNS or MQ transfer. - -| Column | Type | Meaning | Required column | -|-----------------|--------------------|---------------------------------------------------------------------------------|-----------------| -| `__time__` | int | timestamp for aggregation | true | -| `_local_addr`_ | string | local address | true | -| `_local_port`_ | int | local port | true | -| `_remote_addr`_ | string | remote address | true | -| `_remote_port`_ | int | remote port | true | -| type | string | l7_db means L7 DB metrics, l7_req means L7 RPC metrics, and l4 means L4 metrics | true | -| local_info | `Local Info` Json | local information | true | -| remote_info | `Remote Info` Json | remote information | true | -| conn_id | int | each aggregation belongs to one unique connection id | true | -| interval | int | unit is seconds, aggregation interval | true | -| role | string | c means client, s means server | true | -| version | string | protocol version | true | -| protocol | string | L7 detect protocol | true | -| req_type | string | request type, such as POST in HTTP. | true | -| req_domain | string | request dommain, such as domain in HTTP. | true | -| req_resource | string | request specific resource, such HTTP path or RPC method. | true | -| resp_code | int | response code, currently only works in HTTP. | true | -| resp_status | int | response status, 0 means success, non-zero means failure. | true | -| extra | json | extra message for feature. | true | -| latency_ns | int | the total invoke cost ns | true | -| tdigest_latency | string | base64 tdigest data for compute Pxx | true | -| count | int | the total invoke cost | true | -| req_bytes | int | the total requst bytes | true | -| resp_bytes | int | the total response bytes | true | - -## Layer 4 metrics data structure - -| Column | Type | Meaning | Required column | -|-----------------|--------------------|---------------------------------------------------------------------------------|-----------------| -| `__time__` | int | timestamp for aggregation | true | -| `_local_addr`_ | string | local address | true | -| `_local_port`_ | int | local port | true | -| `_remote_addr`_ | string | remote address | true | -| `_remote_port`_ | int | remote port | true | -| type | string | l7_db means L7 DB metrics, l7_req means L7 RPC metrics, and l4 means L4 metrics | true | -| local_info | `Local Info` Json | local information | true | -| remote_info | `Remote Info` Json | remote information | true | -| conn_id | int | each aggregation belongs to one unique connection id | true | -| interval | int | unit is seconds, aggregation interval | true | -| role | string | c means client, s means server | true | -| conn_type | int | 0 means TCP, and 1 means UDP. | true | -| extra | json | extra message for feature. | true | -| recv_bytes | int | the total receive bytes | true | -| send_bytes | int | the total send bytes | true | -| recv_packets | int | the total receive packets | true | -| send_packets | int | the total send packets | true | diff --git a/core/observer/interface/global.cpp b/core/observer/interface/global.cpp deleted file mode 100644 index 7d948c4b57..0000000000 --- a/core/observer/interface/global.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2023 iLogtail 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. - -// -// Created by evan on 2022/10/19. -// - -#include "global.h" - -namespace logtail { -namespace observer { - std::string kLocalInfo = "local_info"; - std::string kRemoteInfo = "remote_info"; - std::string kRole = "role"; - std::string kType = "type"; - std::string kConnId = "conn_id"; - std::string kConnType = "conn_type"; - std::string kLocalAddr = "_local_addr_"; - std::string kLocalPort = "_local_port_"; - std::string kRemoteAddr = "_remote_addr_"; - std::string kRemotePort = "_remote_port_"; - std::string kInterval = "interval"; - std::string kSendBytes = "send_bytes"; - std::string kRecvBytes = "recv_bytes"; - std::string kSendpackets = "send_packets"; - std::string kRecvPackets = "recv_packets"; - std::string kQueryCmd = "query_cmd"; - std::string kQuery = "query"; - std::string kStatus = "status"; - std::string kReqType = "req_type"; - std::string kReqDomain = "req_domain"; - std::string kReqResource = "req_resource"; - std::string kRespStatus = "resp_status"; - std::string kRespCode = "resp_code"; - std::string kLatencyNs = "latency_ns"; - std::string kReqBytes = "req_bytes"; - std::string kRespBytes = "resp_bytes"; - std::string kCount = "count"; - std::string kProtocol = "protocol"; - std::string kVersion = "version"; - std::string kTdigestLatency = "tdigest_latency"; - -} // namespace observer - - -} // namespace logtail \ No newline at end of file diff --git a/core/observer/interface/global.h b/core/observer/interface/global.h deleted file mode 100644 index 6ba0e41173..0000000000 --- a/core/observer/interface/global.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2023 iLogtail 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. - */ - -#pragma once -#include - -namespace logtail { -namespace observer { - // process label keys - extern std::string kLocalInfo; - extern std::string kRemoteInfo; - extern std::string kRole; - extern std::string kType; - - // connection label keys - extern std::string kConnId; - extern std::string kConnType; - extern std::string kLocalAddr; - extern std::string kLocalPort; - extern std::string kRemoteAddr; - extern std::string kRemotePort; - extern std::string kInterval; - - // value names - extern std::string kSendBytes; - extern std::string kRecvBytes; - extern std::string kSendpackets; - extern std::string kRecvPackets; - - // metric names - extern std::string kQueryCmd; - extern std::string kQuery; - extern std::string kStatus; - extern std::string kReqType; - extern std::string kReqDomain; - extern std::string kReqResource; - extern std::string kRespStatus; - extern std::string kRespCode; - extern std::string kLatencyNs; - extern std::string kReqBytes; - extern std::string kRespBytes; - extern std::string kCount; - extern std::string kProtocol; - extern std::string kVersion; - extern std::string kTdigestLatency; - -} // namespace observer -} // namespace logtail diff --git a/core/observer/interface/helper.h b/core/observer/interface/helper.h deleted file mode 100644 index a230f88994..0000000000 --- a/core/observer/interface/helper.h +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "network.h" -#include -#include -#include -#include -#include -#include - -#include "metas/ServiceMetaCache.h" -#include "common/xxhash/xxhash.h" -#include "layerfour.h" -#include "MachineInfoUtil.h" -#include "global.h" - - -namespace logtail { - -template -inline void AddAnyLogContent(sls_logs::Log* log, const std::string& key, const T& value) { - auto content = log->add_contents(); - content->set_key(key); - content->set_value(std::to_string(value)); -} -inline void AddAnyLogContent(sls_logs::Log* log, const std::string& key, const std::string& value) { - auto content = log->add_contents(); - content->set_key(key); - content->set_value(value); -} -inline void AddAnyLogContent(sls_logs::Log* log, const std::string& key, std::string&& value) { - auto content = log->add_contents(); - content->set_key(key); - content->set_value(std::move(value)); -} - -// GenUniqueConnectionID use connid,add and pid to generate a mostly unique id. -inline uint64_t GenConnectionID(uint32_t pid, uint32_t connid) { - static std::string sHostname = GetHostName(); - uint32_t sHash = XXH32(sHostname.c_str(), sHostname.size(), 0); - return static_cast(sHash) << 32 | static_cast(connid); -} -static const std::string kRemoteInfoPrefix = R"({"_remote_host_":")"; -static const std::string kRemoteInfoSuffix = R"("})"; - -} // namespace logtail - -inline std::string SockAddressToString(const SockAddress& address) { - char addr[INET6_ADDRSTRLEN + 1] = {'\0'}; - - if (address.Type == SockAddressType_IPV4) { - inet_ntop(AF_INET, &address.Addr.IPV4, addr, INET6_ADDRSTRLEN); - } else { - inet_ntop(AF_INET6, address.Addr.IPV6, addr, INET6_ADDRSTRLEN); - } - return {addr}; -} - -inline SockAddress SockAddressFromString(const std::string& ipV4V6) { - SockAddress addr; - if (ipV4V6.find('.') != std::string::npos) { - addr.Type = SockAddressType_IPV4; - inet_pton(AF_INET, ipV4V6.c_str(), &addr.Addr.IPV4); - } else { - addr.Type = SockAddressType_IPV6; - inet_pton(AF_INET6, ipV4V6.c_str(), addr.Addr.IPV6); - } - return addr; -} -inline std::string PacketEventHeaderToString(PacketEventHeader* header) { - std::string str; - str.append("EventType : ").append(PacketEventTypeToString(header->EventType)).append("\n"); - str.append("PID : ").append(std::to_string(header->PID)).append("\n"); - str.append("SocketHash : ").append(std::to_string(header->SockHash)).append("\n"); - str.append("Time : ").append(std::to_string(header->TimeNano)).append("\n"); - str.append("SrcAddress : ").append(SockAddressToString(header->SrcAddr)).append("\n"); - str.append("SrcPort : ").append(std::to_string(header->SrcPort)).append("\n"); - str.append("DstAddress : ").append(SockAddressToString(header->DstAddr)).append("\n"); - str.append("DstPort : ").append(std::to_string(header->DstPort)).append("\n"); - return str; -} - -inline std::string PacketEventDataToString(PacketEventData* data) { - std::string str; - str.append("PacketType : ").append(PacketTypeToString(data->PktType)).append("\n"); - str.append("ProtocolType : ").append(ProtocolTypeToString(data->PtlType)).append("\n"); - str.append("MessageType : ").append(MessageTypeToString(data->MsgType)).append("\n"); - str.append("RealLen : ").append(std::to_string(int(data->RealLen))).append("\n"); - str.append("BufferLen : ").append(std::to_string(int(data->BufferLen))).append("\n"); - std::stringstream ss; - char const hex_chars[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - ss << "###############################" << std::endl; - for (int i = 0; i < data->BufferLen; ++i) { - if (i % 32 == 0) { - char buf[64] = {0}; - snprintf(buf, 63, "%06d - %06d : ", i, i + 32); - ss << buf; - } - if (i % 4 == 0) { - ss << "0x"; - } - ss << hex_chars[uint8_t(data->Buffer[i]) >> 4] << hex_chars[uint8_t(data->Buffer[i]) & 0x0F]; - if (i % 4 == 3) { - ss << " "; - } - if (i % 32 == 31) { - ss << std::endl; - } - } - ss << std::endl << "###############################" << std::endl; - str.append("Data : ").append(ss.str()).append("\n"); - str.append("Data String : \n").append(std::string(data->Buffer, data->BufferLen)).append("\n"); - - return str; -} - -inline std::string PacketEventToString(void* event, int32_t len) { - if ((uint32_t)len < sizeof(PacketEventHeader)) { - return "ErrorLength"; - } - PacketEventHeader* header = static_cast(event); - switch (header->EventType) { - case PacketEventType_Data: { - PacketEventData* data = reinterpret_cast((char*)event + sizeof(PacketEventHeader)); - return PacketEventHeaderToString(header).append("\n").append(PacketEventDataToString(data)); - } - default: - return PacketEventHeaderToString(header); - } -} - -inline void BufferToPacketEvent(char* buffer, int32_t buffer_len, void*& event, int32_t& len) { - event = NULL; - len = buffer_len; - if ((uint32_t)buffer_len < sizeof(PacketEventHeader)) { - return; - } - event = buffer; - if (buffer_len == sizeof(PacketEventHeader)) { - return; - } - PacketEventData* data = (PacketEventData*)((uint8_t*)event + sizeof(PacketEventHeader)); - data->Buffer = (char*)event + sizeof(PacketEventHeader) + sizeof(PacketEventData); - return; -} - -inline void PacketEventToBuffer(void* event, int32_t len, std::string& buffer) { - buffer.clear(); - if ((uint32_t)len < sizeof(PacketEventHeader)) { - return; - } - PacketEventHeader* header = static_cast(event); - - switch (header->EventType) { - case PacketEventType_Data: { - PacketEventData* data = reinterpret_cast((char*)event + sizeof(PacketEventHeader)); - buffer.resize(4 + sizeof(PacketEventHeader) + sizeof(PacketEventData) + data->BufferLen); - *(uint32_t*)&buffer.at(0) = buffer.size() - 4; - memcpy(&buffer.at(4), event, sizeof(PacketEventHeader) + sizeof(PacketEventData)); - memcpy(&buffer.at(4) + sizeof(PacketEventHeader) + sizeof(PacketEventData), data->Buffer, data->BufferLen); - return; - } - default: - buffer.resize(4 + sizeof(PacketEventHeader)); - *(uint32_t*)&buffer.at(0) = buffer.size() - 4; - memcpy(&buffer.at(4), event, sizeof(PacketEventHeader)); - return; - } -} - -// true means request and false means response. -inline MessageType InferRequestOrResponse(PacketType pktType, PacketEventHeader* header) { - // 根据端口大小粗略判断 MessageType_Response or MessageType_Request - if (pktType == PacketType_In) { - if (header->SrcPort > header->DstPort || header->DstPort < 30000) { - return MessageType_Response; - } else { - return MessageType_Request; - } - } else if (pktType == PacketType_Out) { - if (header->SrcPort > header->DstPort || header->DstPort < 30000) { - return MessageType_Request; - } else { - return MessageType_Response; - } - } - return MessageType_None; -} - -inline PacketRoleType InferServerOrClient(PacketType pktType, MessageType messageType) { - if (pktType == PacketType_None || messageType == MessageType_None) { - return PacketRoleType::Unknown; - } - if (pktType == PacketType_In) { - return messageType == MessageType_Request ? PacketRoleType::Server : PacketRoleType::Client; - } else if (pktType == PacketType_Out) { - return messageType == MessageType_Request ? PacketRoleType::Client : PacketRoleType::Server; - } - return PacketRoleType::Unknown; -} - - -inline void ConnectionAddrInfoToPB(const ConnectionAddrInfo& info, sls_logs::Log* log) { - logtail::AddAnyLogContent(log, logtail::observer::kLocalAddr, SockAddressToString(info.LocalAddr)); - logtail::AddAnyLogContent(log, logtail::observer::kLocalPort, info.LocalPort); - logtail::AddAnyLogContent(log, logtail::observer::kRemoteAddr, SockAddressToString(info.RemoteAddr)); - logtail::AddAnyLogContent(log, logtail::observer::kRemotePort, info.RemotePort); -} diff --git a/core/observer/interface/layerfour.cpp b/core/observer/interface/layerfour.cpp deleted file mode 100644 index 0635b15b72..0000000000 --- a/core/observer/interface/layerfour.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2023 iLogtail 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. - -// -// Created by evan on 2022/10/17. -// - -#include "layerfour.h" -#include "helper.h" -#include "MachineInfoUtil.h" - -namespace logtail { - -void NetStatisticsTCP::ToPB(sls_logs::Log* log) const { - this->Base.ToPB(log); -} - -void NetStatisticsBase::ToPB(sls_logs::Log* log) const { - AddAnyLogContent(log, logtail::observer::kSendBytes, this->SendBytes); - AddAnyLogContent(log, logtail::observer::kRecvBytes, this->RecvBytes); - AddAnyLogContent(log, logtail::observer::kSendpackets, this->SendPackets); - AddAnyLogContent(log, logtail::observer::kRecvPackets, this->RecvPackets); -} - - -void NetStatisticsKey::ToPB(sls_logs::Log* log) const { - static ServiceMetaManager* sHostnameManager = logtail::ServiceMetaManager::GetInstance(); - ConnectionAddrInfoToPB(this->AddrInfo, log); - const std::string& remoteAddr = SockAddressToString(this->AddrInfo.RemoteAddr); - const ServiceMeta& meta = sHostnameManager->GetServiceMeta(this->PID, remoteAddr); - auto remoteInfo - = std::string(kRemoteInfoPrefix).append(meta.Empty() ? remoteAddr : meta.Host).append(kRemoteInfoSuffix); - AddAnyLogContent(log, logtail::observer::kRemoteInfo, std::move(remoteInfo)); - AddAnyLogContent(log, logtail::observer::kRole, PacketRoleTypeToString(this->RoleType)); - AddAnyLogContent(log, logtail::observer::kConnId, GenConnectionID(this->PID, this->SockHash)); - AddAnyLogContent(log, logtail::observer::kConnType, std::string("tcp")); // todo replace with real type - AddAnyLogContent(log, observer::kType, ObserverMetricsTypeToString(ObserverMetricsType::L4_METRICS)); -} - -logtail::NetStatisticsTCP& NetStaticticsMap::GetStatisticsItem(const logtail::NetStatisticsKey& key) { - auto findRst = mHashMap.find(key); - if (findRst != mHashMap.end()) { - return findRst->second; - } - static NetStatisticsTCP sNewTcp; - auto insertIter = mHashMap.insert(std::make_pair(key, sNewTcp)); - return insertIter.first->second; -} - -} // namespace logtail diff --git a/core/observer/interface/layerfour.h b/core/observer/interface/layerfour.h deleted file mode 100644 index 31298c7280..0000000000 --- a/core/observer/interface/layerfour.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2023 iLogtail 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. - */ - -#pragma once - -#include -#include "network.h" -#include "xxhash/xxhash.h" -#include "metas/ServiceMetaCache.h" -#include "helper.h" -namespace logtail { -struct NetStatisticsKey { - uint32_t PID; - uint32_t SockHash; - ConnectionAddrInfo AddrInfo; - SocketCategory SockCategory; - PacketRoleType RoleType; - - void ToPB(sls_logs::Log* log) const; -}; - -struct NetStatisticsBase { - int64_t SendBytes{0}; - int64_t RecvBytes{0}; - int64_t SendPackets{0}; - int64_t RecvPackets{0}; - - inline void Merge(const struct NetStatisticsBase& o) { - SendBytes += o.SendBytes; - RecvBytes += o.RecvBytes; - SendPackets += o.SendPackets; - RecvPackets += o.RecvPackets; - } - void ToPB(sls_logs::Log* log) const; -}; - -struct NetStatisticsTCP { - struct NetStatisticsBase Base {}; - int64_t SendTotalLatency{0}; - int64_t RecvTotalLatency{0}; - int64_t SendRetranCount{0}; - int64_t RecvRetranCount{0}; - int64_t SendZeroWinCount{0}; - int64_t RecvZeroWinCount{0}; - - inline void Merge(const struct NetStatisticsTCP& o){ - this->Base.Merge(o.Base); - SendTotalLatency += o.SendTotalLatency; - RecvTotalLatency += o.RecvTotalLatency; - SendRetranCount += o.SendRetranCount; - RecvRetranCount += o.RecvRetranCount; - SendZeroWinCount += o.SendZeroWinCount; - RecvZeroWinCount += o.RecvZeroWinCount; - } - void ToPB(sls_logs::Log* log) const; -}; - - -struct NetStatisticsKeyHash { - size_t operator()(const NetStatisticsKey& key) const { - return size_t(((uint64_t)key.PID << 32) | (uint64_t)key.SockHash); - } -}; - -struct NetStatisticsKeyEqual { - bool operator()(const NetStatisticsKey& a, const NetStatisticsKey& b) const { - return a.PID == b.PID && a.SockHash == b.SockHash; - } -}; - -// hash by connection -typedef std::unordered_map - NetStatisticsHashMap; - - -struct NetStaticticsMap { - NetStatisticsHashMap mHashMap; - - NetStatisticsTCP& GetStatisticsItem(const NetStatisticsKey& key); - inline void Clear() { mHashMap.clear(); } -}; - - -struct MergedNetStatisticsKeyHash { - size_t operator()(const logtail::NetStatisticsKey& key) const { - uint32_t hash = XXH32(&key.AddrInfo.RemoteAddr, sizeof(key.AddrInfo.RemoteAddr), 0); - hash = XXH32(&key.AddrInfo.RemotePort, sizeof(key.AddrInfo.RemotePort), hash); - hash = XXH32(&key.PID, sizeof(key.PID), hash); - return hash; - } -}; -struct MergedNetStatisticsKeyEqual { - bool operator()(const logtail::NetStatisticsKey& a, const logtail::NetStatisticsKey& b) const { - return a.PID == b.PID && a.AddrInfo.RemotePort == b.AddrInfo.RemotePort && a.RoleType == b.RoleType - && a.AddrInfo.RemoteAddr == b.AddrInfo.RemoteAddr; - } -}; - -// hash by process and remote addr -typedef std::unordered_map - MergedNetStatisticsHashMap; - - -} // namespace logtail \ No newline at end of file diff --git a/core/observer/interface/network.h b/core/observer/interface/network.h deleted file mode 100644 index 221b5b6db7..0000000000 --- a/core/observer/interface/network.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "interface/type.h" -#include -#include - -enum class SocketCategory { - UnknownSocket, - InetSocket, - UnixSocket, -}; - -inline std::string SocketCategoryToString(SocketCategory type) { - switch (type) { - case SocketCategory::InetSocket: - return "inet_socket"; - case SocketCategory::UnixSocket: - return "unix_socket"; - default: - return "unknown_socket"; - } -} - -// TCP状态维护 -// map[sock-key] -> tcp_conn_state -// sock-key : tgid << 32 | skc_hash -// uint32_t tgid = bpf_get_current_pid_tgid() >> 32 -// https://github.com/y123456yz/Reading-and-comprehense-linux-Kernel-network-protocol-stack/blob/master/linux-net-kernel/include/net/sock.h#L189 - -enum SockAddressType { SockAddressType_IPV4, SockAddressType_IPV6 }; - -// If T is a union type, the object's first non-static named data member is -// zero-initialized and padding is initialized to zero bits. -union SockAddressDetail { - uint64_t IPV6[2]; // IPV6最长,因此需要放在最前面 - uint32_t IPV4; -}; - -struct SockAddress { - SockAddressType Type; - SockAddressDetail Addr; - - bool operator==(const SockAddress& rhs) const { - return Type == rhs.Type && std::memcmp(Addr.IPV6, rhs.Addr.IPV6, sizeof(SockAddressDetail)) == 0; - } - - bool operator!=(const SockAddress& rhs) const { return !(rhs == *this); } -}; - -enum PacketEventType { - PacketEventType_None, // 用于一些空循环 - PacketEventType_Data, - PacketEventType_Connected, - PacketEventType_Accepted, - PacketEventType_Closed -}; - -inline std::string PacketEventTypeToString(enum PacketEventType type) { - switch (type) { - case PacketEventType_None: - return "None"; - case PacketEventType_Data: - return "Data"; - case PacketEventType_Connected: - return "Connected"; - case PacketEventType_Accepted: - return "Accepted"; - case PacketEventType_Closed: - return "Closed"; - } - return "None"; -} - -// 如果是控制消息(建连、断连),只需要output -// header;如果是数据类型,传PacketEventData -struct PacketEventHeader { - uint32_t PID; - uint32_t SockHash; // hashed by local addr + local port + remote addrr + - // remote port - - PacketEventType EventType; - PacketRoleType RoleType; - - uint64_t TimeNano; - SockAddress SrcAddr; // 永远是本机地址,通过PacketType来计算方向 - uint16_t SrcPort; - SockAddress DstAddr; // 永远是对端地址 - uint16_t DstPort; -}; - -// 当EventType为Data类型时的payload表示; header + data -struct PacketEventData { - ProtocolType PtlType; - MessageType MsgType; - PacketType PktType; - - int32_t RealLen; // 原始的数据包大小 - int32_t BufferLen; // 实际拷贝的数据包大小,即Buffer的Size - - char* Buffer; // 一般设置为 buffer + sizeof(PacketEventHeader) + - // sizeof(PacketEventData) -}; - - -struct ConnectionAddrInfo { - SockAddress LocalAddr{}; - SockAddress RemoteAddr{}; - uint16_t LocalPort{0}; - uint16_t RemotePort{0}; -}; \ No newline at end of file diff --git a/core/observer/interface/protocol.h b/core/observer/interface/protocol.h deleted file mode 100644 index c2e78d9fb5..0000000000 --- a/core/observer/interface/protocol.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "interface/type.h" -#include -#include - -// 解析结果 -enum ParseResult { - ParseResult_OK, - ParseResult_Fail, - ParseResult_Drop, - ParseResult_Partial, -}; - -// ProtocolEventKey 需要提供一个 uint64_t Hash() 函数 - -// 协议的事件定义,例如一次完整的HTTP请求和返回,一次DNS请求和返回 -// 例如HTTP请求,记录:Host、URL、Method、Status、Latency、RequestSize、ResponseSize -template -struct ProtocolEvent { - // GetKey 获取聚合的Key(不需要做URL、Query的聚类) - void GetKey(ProtocolEventKey& key) {} -}; - -// 数据包的聚合项,例如请求某一个HTTP协议,Key是Host、URL(聚类后)、Method、Status;ProtocolEventAggItem就记录 -// 请求次数、总延迟、RequestSizeTotal、ResponseSizeTotal、PXX延迟的中间结果(待定) -// @TODO(yuanyi) : 这个可以设计成通用的 -template -struct ProtocolEventAggItem { - ProtocolEventKey& GetKey() { return ProtocolEventKey(); } - // AddEvent 更新聚合结果 - void AddEvent(ProtocolEvent* event) {} - // Clear 清理聚合结果 - void Clear() {} -}; - -// ProtocolPatternGenerator 协议分类器,只提供一个接口 ProtocolEventKey & -// GetPattern(ProtocolEventKey & key) - -// 协议的聚类器 -template -class ProtocolEventAggregator { -public: - ProtocolEventAggregator(ProtocolPatternGenerator& patternGenerator) : mPatternGenerator(patternGenerator) {} - - // AddEvent 增加一个事件 - void AddEvent(ProtocolEvent* event) {} - - // 获取AGG的结果,获取完毕后需要Clear ProtocolEventAggItem - size_t GetAggResult(uint64_t timeNs, std::vector& aggList) { return size_t(0); } - -protected: - ProtocolPatternGenerator& mPatternGenerator; - std::unordered_map mProtocolEventAggMap; -}; - -// 协议解析器,流式解析,解析到某个协议后,自动放到aggregator中聚合 -template -class ProtocolParser { -public: - ProtocolParser(ProtocolEventAggregator* aggregator) {} - - // 创建一个实例 - static ProtocolParser* Create(ProtocolEventAggregator* aggregator) { return new ProtocolParser(aggregator); } - - // Packet到达的时候会Call,所有参数都会告诉类型(PacketType,MessageType) - ParseResult OnPacket(PacketType pktType, - MessageType msgType, - uint64_t timeNs, - const char* pkt, - int32_t pktSize, - int32_t pktRealSize) { - return ParseResult_OK; - } - - // GC,把内部没有完成Event匹配的消息按照SizeLimit和TimeOut进行清理 - // 返回值,如果是true代表还有数据,false代表无数据 - bool GarbageCollection(size_t size_limit_bytes, uint64_t expireTimeNs) { return false; } -}; diff --git a/core/observer/interface/statistics.h b/core/observer/interface/statistics.h deleted file mode 100644 index ca2e845eb9..0000000000 --- a/core/observer/interface/statistics.h +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "Monitor.h" -#include "iostream" -#include "monitor/LogtailAlarm.h" -#include -#include - -namespace logtail { - -// Global statistics for container meta -struct ProcessMetaStatistic { - uint32_t mCgroupPathTotalCount{0}; - uint32_t mCgroupPathParseFailCount{0}; - uint32_t mWatchProcessCount{0}; - uint32_t mFetchContainerMetaCount{0}; - uint32_t mFetchContainerMetaFailCount{0}; - - static ProcessMetaStatistic* GetInstance() { - static auto ptr = new ProcessMetaStatistic(); - return ptr; - } - - static void Clear() { - static auto sInstance = GetInstance(); - sInstance->doClear(); - } - - void FlushMetrics() { - static auto sMonitor = LogtailMonitor::GetInstance(); - sMonitor->UpdateMetric("observer_processmeta_cgroup_count", mCgroupPathTotalCount); - sMonitor->UpdateMetric("observer_processmeta_cgroup_invalid_count", mCgroupPathParseFailCount); - sMonitor->UpdateMetric("observer_processmeta_watch_count", mWatchProcessCount); - sMonitor->UpdateMetric("observer_processmeta_fetch_container_count", mFetchContainerMetaCount); - sMonitor->UpdateMetric("observer_processmeta_fetch_container_fail_count", mFetchContainerMetaFailCount); - doClear(); - } - - friend std::ostream& operator<<(std::ostream& os, const ProcessMetaStatistic& statistic) { - os << "mCgroupPathTotalCount: " << statistic.mCgroupPathTotalCount - << " mCgroupPathParseFailCount: " << statistic.mCgroupPathParseFailCount - << " mWatchProcessCount: " << statistic.mWatchProcessCount - << " mFetchContainerMetaCount: " << statistic.mFetchContainerMetaCount - << " mFetchContainerMetaFailCount: " << statistic.mFetchContainerMetaFailCount; - return os; - } - - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - -private: - ProcessMetaStatistic() = default; - - void doClear() { - mCgroupPathTotalCount = 0; - mCgroupPathParseFailCount = 0; - mWatchProcessCount = 0; - mFetchContainerMetaCount = 0; - mFetchContainerMetaFailCount = 0; - } -}; - -// Global statistics for netlink connection meta. -struct ConnectionMetaStatistic { - uint16_t mGetSocketInfoCount{0}; - uint16_t mGetSocketInfoFailCount{0}; - uint16_t mGetNetlinkProberCount{0}; - uint16_t mGetNetlinkProberFailCount{0}; - uint16_t mFetchNetlinkCount{0}; - - static ConnectionMetaStatistic* GetInstance() { - static auto ptr = new ConnectionMetaStatistic(); - return ptr; - } - - static void Clear() { - static auto sInstance = GetInstance(); - sInstance->doClear(); - } - - void FlushMetrics() { - static auto sMonitor = LogtailMonitor::GetInstance(); - sMonitor->UpdateMetric("observer_connmeta_socket_get_count", mGetSocketInfoCount); - sMonitor->UpdateMetric("observer_connmeta_socket_get_fail_count", mGetSocketInfoFailCount); - sMonitor->UpdateMetric("observer_connmeta_socket_create_prober_count", mGetNetlinkProberCount); - sMonitor->UpdateMetric("observer_connmeta_socket_create_prober_fail_count", mGetNetlinkProberFailCount); - sMonitor->UpdateMetric("observer_connmeta_socket_fetch_netlink_count", mFetchNetlinkCount); - doClear(); - } - - friend std::ostream& operator<<(std::ostream& os, const ConnectionMetaStatistic& statistic) { - os << "mGetSocketInfoCount: " << statistic.mGetSocketInfoCount - << " mGetSocketInfoFailCount: " << statistic.mGetSocketInfoFailCount - << " mGetNetlinkProberCount: " << statistic.mGetNetlinkProberCount - << " mGetNetlinkProberFailCount: " << statistic.mGetNetlinkProberFailCount - << " mFetchNetlinkCount: " << statistic.mFetchNetlinkCount; - return os; - } - - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - -private: - ConnectionMetaStatistic() = default; - - void doClear() { - mGetSocketInfoCount = 0; - mGetSocketInfoFailCount = 0; - mGetNetlinkProberCount = 0; - mGetNetlinkProberFailCount = 0; - mFetchNetlinkCount = 0; - } -}; - -// Global statistics for network stat -struct NetworkStatistic { - uint32_t mOutputEvents{0}; - uint32_t mOutputBytes{0}; - uint32_t mInputEvents{0}; - uint32_t mInputBytes{0}; - uint32_t mProtocolMatched{0}; - uint32_t mProtocolUnMatched{0}; - uint32_t mGCCount{0}; - uint32_t mGCReleaseConnCount{0}; - uint32_t mGCReleaseProcessCount{0}; - uint32_t mEbpfLostCount{0}; - uint32_t mEbpfGCCount{0}; - uint32_t mEbpfGCReleaseFDCount{0}; - uint32_t mEbpfDisableProcesses{0}; - uint32_t mEbpfUsingConnections{0}; - - void FlushMetrics() { - static auto sMonitor = LogtailMonitor::GetInstance(); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_RUNTIME_ALARM, - "ebpf lost event count: " + std::to_string(mEbpfLostCount)); - sMonitor->UpdateMetric("observer_input_events", mInputEvents); - sMonitor->UpdateMetric("observer_input_bytes", mInputBytes); - sMonitor->UpdateMetric("observer_output_events", mOutputEvents); - sMonitor->UpdateMetric("observer_output_raw_bytes", mOutputBytes); - sMonitor->UpdateMetric("observer_unmatched_protocol_event", mProtocolUnMatched); - sMonitor->UpdateMetric("observer_matched_protocol_event", mProtocolMatched); - sMonitor->UpdateMetric("observer_gc_count", mGCCount); - sMonitor->UpdateMetric("observer_gc_release_conn_count", mGCReleaseConnCount); - sMonitor->UpdateMetric("observer_gc_release_process_count", mGCReleaseProcessCount); - sMonitor->UpdateMetric("observer_gc_ebpf_count", mEbpfGCCount); - sMonitor->UpdateMetric("observer_gc_ebpf_release_fd_count", mEbpfGCReleaseFDCount); - sMonitor->UpdateMetric("observer_ebpf_disable_processes", mEbpfDisableProcesses); - sMonitor->UpdateMetric("observer_ebpf_holding_connections", mEbpfUsingConnections); - sMonitor->UpdateMetric("observer_ebpf_lost_count", mEbpfLostCount); - doClear(); - } - - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - - friend std::ostream& operator<<(std::ostream& os, const NetworkStatistic& statistic) { - os << "mOutputEvents: " << statistic.mOutputEvents << " mOutputBytes: " << statistic.mOutputBytes - << " mInputEvents: " << statistic.mInputEvents << " mInputBytes: " << statistic.mInputBytes - << " mProtocolMatched: " << statistic.mProtocolMatched - << " mProtocolUnMatched: " << statistic.mProtocolUnMatched << " mGCCount: " << statistic.mGCCount - << " mGCReleaseConnCount: " << statistic.mGCReleaseConnCount - << " mGCReleaseProcessCount: " << statistic.mGCReleaseProcessCount - << " mEbpfLostCount: " << statistic.mEbpfLostCount << " mEbpfGCCount: " << statistic.mEbpfGCCount - << " mEbpfGCReleaseFDCount: " << statistic.mEbpfGCReleaseFDCount - << " mEbpfDisableProcesses: " << statistic.mEbpfDisableProcesses - << " mEbpfUsingConnections: " << statistic.mEbpfUsingConnections; - return os; - } - - static NetworkStatistic* GetInstance() { - static auto ptr = new NetworkStatistic(); - return ptr; - } - - static void Clear() { - static auto sInstance = GetInstance(); - sInstance->doClear(); - } - -private: - NetworkStatistic() = default; - - void doClear() { - mOutputEvents = 0; - mOutputBytes = 0; - mInputEvents = 0; - mInputBytes = 0; - mProtocolMatched = 0; - mProtocolUnMatched = 0; - mGCCount = 0; - mGCReleaseConnCount = 0; - mGCReleaseProcessCount = 0; - mEbpfGCCount = 0; - mEbpfGCReleaseFDCount = 0; - mEbpfDisableProcesses = 0; - mEbpfUsingConnections = 0; - mEbpfLostCount = 0; - } -}; - -// Global statistics for protocol -struct ProtocolStatistic { - uint32_t mHTTPParseFailCount{0}; - uint32_t mRedisParseFailCount{0}; - uint32_t mMySQLParseFailCount{0}; - uint32_t mPgSQLParseFailCount{0}; - uint32_t mDNSParseFailCount{0}; - uint32_t mHTTPDropCount{0}; - uint32_t mRedisDropCount{0}; - uint32_t mMySQLDropCount{0}; - uint32_t mPgSQLDropCount{0}; - uint32_t mDNSDropCount{0}; - uint32_t mHTTPCount{0}; - uint32_t mRedisCount{0}; - uint32_t mMySQLCount{0}; - uint32_t mPgSQLCount{0}; - uint32_t mDNSCount{0}; - - static ProtocolStatistic* GetInstance() { - static auto ptr = new ProtocolStatistic(); - return ptr; - } - - static void Clear() { - static auto sInstance = GetInstance(); - sInstance->doClear(); - } - - void FlushMetrics() { - static auto sMonitor = LogtailMonitor::GetInstance(); - - sMonitor->UpdateMetric("observer_protocol_http_drop_count", mHTTPDropCount); - sMonitor->UpdateMetric("observer_protocol_dns_drop_count", mDNSDropCount); - sMonitor->UpdateMetric("observer_protocol_mysql_drop_count", mMySQLDropCount); - sMonitor->UpdateMetric("observer_protocol_pgsql_drop_count", mPgSQLDropCount); - sMonitor->UpdateMetric("observer_protocol_redis_drop_count", mRedisDropCount); - sMonitor->UpdateMetric("observer_protocol_http_count", mHTTPCount); - sMonitor->UpdateMetric("observer_protocol_dns_count", mDNSCount); - sMonitor->UpdateMetric("observer_protocol_mysql_count", mMySQLCount); - sMonitor->UpdateMetric("observer_protocol_pgsql_count", mPgSQLCount); - sMonitor->UpdateMetric("observer_protocol_redis_count", mRedisCount); - sMonitor->UpdateMetric("observer_protocol_http_parse_fail_count", mHTTPParseFailCount); - sMonitor->UpdateMetric("observer_protocol_dns_parse_fail_count", mDNSParseFailCount); - sMonitor->UpdateMetric("observer_protocol_mysql_parse_fail_count", mMySQLParseFailCount); - sMonitor->UpdateMetric("observer_protocol_pgsql_parse_fail_count", mPgSQLParseFailCount); - sMonitor->UpdateMetric("observer_protocol_redis_parse_fail_count", mRedisParseFailCount); - doClear(); - } - - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - - friend std::ostream& operator<<(std::ostream& os, const ProtocolStatistic& statistic) { - os << "mHTTPParseFailCount: " << statistic.mHTTPParseFailCount - << " mRedisParseFailCount: " << statistic.mRedisParseFailCount - << " mMySQLParseFailCount: " << statistic.mMySQLParseFailCount - << " mPgSQLParseFailCount: " << statistic.mPgSQLParseFailCount - << " mDNSParseFailCount: " << statistic.mDNSParseFailCount << " mHTTPDropCount: " << statistic.mHTTPDropCount - << " mRedisDropCount: " << statistic.mRedisDropCount << " mMySQLDropCount: " << statistic.mMySQLDropCount - << " mPgSQLDropCount: " << statistic.mPgSQLDropCount << " mDNSDropCount: " << statistic.mDNSDropCount - << " mHTTPCount: " << statistic.mHTTPCount << " mRedisCount: " << statistic.mRedisCount - << " mMySQLCount: " << statistic.mMySQLCount << " mPgSQLCount: " << statistic.mPgSQLCount - << " mDNSCount: " << statistic.mDNSCount; - return os; - } - -private: - ProtocolStatistic() = default; - - void doClear() { - mHTTPParseFailCount = 0; - mRedisParseFailCount = 0; - mMySQLParseFailCount = 0; - mPgSQLParseFailCount = 0; - mDNSParseFailCount = 0; - mHTTPDropCount = 0; - mRedisDropCount = 0; - mMySQLDropCount = 0; - mPgSQLDropCount = 0; - mDNSDropCount = 0; - mHTTPCount = 0; - mRedisCount = 0; - mMySQLCount = 0; - mPgSQLCount = 0; - mDNSCount = 0; - } -}; - -// Global statistics for analyse the memory usage of different protocol parsers. -struct ProtocolDebugStatistic { - uint32_t mHTTPConnectionNum{0}; - uint32_t mHTTPConnectionCachedSize{0}; - uint32_t mDNSConnectionNum{0}; - uint32_t mDNSConnectionCachedSize{0}; - uint32_t mRedisConnectionNum{0}; - uint32_t mRedisConnectionCachedSize{0}; - uint32_t mMySQLConnectionNum{0}; - uint32_t mMySQLConnectionCachedSize{0}; - uint32_t mPgSQLConnectionNum{0}; - uint32_t mPgSQLConnectionCachedSize{0}; - - static ProtocolDebugStatistic* GetInstance() { - static auto ptr = new ProtocolDebugStatistic(); - return ptr; - } - - static void Clear() { - static auto sInstance = GetInstance(); - sInstance->doClear(); - } - - void FlushMetrics(bool flush) { - static auto sMonitor = LogtailMonitor::GetInstance(); - - if (flush) { - sMonitor->UpdateMetric("observer_protocolstat_http_conn", mHTTPConnectionNum); - sMonitor->UpdateMetric("observer_protocolstat_dns_conn", mDNSConnectionNum); - sMonitor->UpdateMetric("observer_protocolstat_mysql_conn", mMySQLConnectionNum); - sMonitor->UpdateMetric("observer_protocolstat_pgsql_conn", mPgSQLConnectionNum); - sMonitor->UpdateMetric("observer_protocolstat_redis_conn", mRedisConnectionNum); - sMonitor->UpdateMetric("observer_protocolstat_http_cached", mHTTPConnectionCachedSize); - sMonitor->UpdateMetric("observer_protocolstat_dns_cached", mDNSConnectionCachedSize); - sMonitor->UpdateMetric("observer_protocolstat_mysql_cached", mMySQLConnectionCachedSize); - sMonitor->UpdateMetric("observer_protocolstat_pgsql_cached", mPgSQLConnectionCachedSize); - sMonitor->UpdateMetric("observer_protocolstat_redis_cached", mRedisConnectionCachedSize); - } - doClear(); - } - - friend std::ostream& operator<<(std::ostream& os, const ProtocolDebugStatistic& statistic) { - os << "mHTTPConnectionNum: " << statistic.mHTTPConnectionNum - << " mHTTPConnectionCachedSize: " << statistic.mHTTPConnectionCachedSize - << " mDNSConnectionNum: " << statistic.mDNSConnectionNum - << " mDNSConnectionCachedSize: " << statistic.mDNSConnectionCachedSize - << " mRedisConnectionNum: " << statistic.mRedisConnectionNum - << " mRedisConnectionCachedSize: " << statistic.mRedisConnectionCachedSize - << " mMySQLConnectionNum: " << statistic.mMySQLConnectionNum - << " mMySQLConnectionCachedSize: " << statistic.mMySQLConnectionCachedSize - << " mPgSQLConnectionNum: " << statistic.mPgSQLConnectionNum - << " mPgSQLConnectionCachedSize: " << statistic.mPgSQLConnectionCachedSize; - return os; - } - - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - -private: - ProtocolDebugStatistic() = default; - - void doClear() { - mHTTPConnectionNum = 0; - mHTTPConnectionCachedSize = 0; - mDNSConnectionNum = 0; - mDNSConnectionCachedSize = 0; - mRedisConnectionNum = 0; - mRedisConnectionCachedSize = 0; - mMySQLConnectionNum = 0; - mMySQLConnectionCachedSize = 0; - mPgSQLConnectionNum = 0; - mPgSQLConnectionCachedSize = 0; - } -}; - -} // namespace logtail \ No newline at end of file diff --git a/core/observer/interface/type.h b/core/observer/interface/type.h deleted file mode 100644 index 02114a40d8..0000000000 --- a/core/observer/interface/type.h +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include - - -enum class ObserverMetricsType : uint8_t { - L4_METRICS = 0, - L7_DB_METRICS = 1, - L7_REQ_METRICS = 2, -}; - -inline std::string ObserverMetricsTypeToString(ObserverMetricsType type) { - switch (type) { - case ObserverMetricsType::L4_METRICS: - return "l4"; - case ObserverMetricsType::L7_DB_METRICS: - return "l7_db"; - case ObserverMetricsType::L7_REQ_METRICS: - return "l7_req"; - default: - break; - } - return "unknown"; -} - - -// 协议的消息类型,包括Request、Response。 TODO : Produce Consume -enum MessageType { MessageType_None, MessageType_Request, MessageType_Response }; - -inline std::string MessageTypeToString(MessageType type) { - switch (type) { - case MessageType_Request: - return "Request"; - case MessageType_Response: - return "Response"; - default: - break; - } - return "None"; -} - -enum class ServiceCategory { - Unknown, - DB, - MQ, - Server, - DNS, -}; - -inline std::string ServiceCategoryToString(ServiceCategory type) { - switch (type) { - case ServiceCategory::DB: - return "database"; - case ServiceCategory::MQ: - return "mq"; - case ServiceCategory::Server: - return "server"; - case ServiceCategory::DNS: - return "dns"; - default: - return "unknown"; - } -} - -// 数据包的类型,In代表接收,例如 Recv/Read接收到的数据;Out代表发送,例如 -// Send/Write发送出去的数据 -enum PacketType { PacketType_None, PacketType_In, PacketType_Out }; - -enum class PacketRoleType : uint8_t { - Unknown = 0, - Client = 1, - Server = 2, -}; - -inline std::string PacketRoleTypeToString(PacketRoleType type) { - switch (type) { - case PacketRoleType::Unknown: - return "u"; - case PacketRoleType::Client: - return "c"; - case PacketRoleType::Server: - return "s"; - default: - return "u"; - } -} - -inline std::string PacketTypeToString(PacketType type) { - switch (type) { - case PacketType_In: - return "In"; - case PacketType_Out: - return "Out"; - default: - break; - } - return "None"; -} - -// 连接的类型,被动Accept的代表是Server,主动connect的代表是Client -enum ConnectionType { ConnectionType_None = 0x01, ConnectionType_Client = 0x02, ConnectionType_Server = 0x04 }; - -inline std::string ConnectionTypeToString(ConnectionType type) { - switch (type) { - case ConnectionType_Client: - return "Client"; - case ConnectionType_Server: - return "Server"; - default: - break; - } - return "None"; -} - -// 应用层协议类型, -// Don't change the protocol order, which is the same as the order in the -// ebpflib. -enum ProtocolType { - ProtocolType_None, - ProtocolType_HTTP, - ProtocolType_MySQL, - ProtocolType_DNS, - ProtocolType_Redis, - ProtocolType_Kafka, - ProtocolType_PgSQL, - ProtocolType_Mongo, - ProtocolType_Dubbo, - ProtocolType_HSF, - ProtocolType_NumProto, -}; - -inline bool IsRemoteInvokeProtocolType(ProtocolType type) { - switch (type) { - case ProtocolType_HTTP: - case ProtocolType_Dubbo: - case ProtocolType_HSF: { - return true; - } - default: - return false; - } -} - -inline std::string ProtocolTypeToString(ProtocolType type) { - switch (type) { - case ProtocolType_HTTP: - return "http"; - case ProtocolType_DNS: - return "dns"; - case ProtocolType_MySQL: - return "mysql"; - case ProtocolType_Redis: - return "redis"; - case ProtocolType_Kafka: - return "kafka"; - case ProtocolType_PgSQL: - return "pgsql"; - case ProtocolType_Mongo: - return "mongo"; - case ProtocolType_Dubbo: - return "dubbo"; - case ProtocolType_HSF: - return "hsf"; - default: - break; - } - return "none"; -} - -inline ServiceCategory DetectRemoteServiceCategory(ProtocolType protocolType) { - switch (protocolType) { - case ProtocolType_MySQL: - case ProtocolType_Redis: - case ProtocolType_PgSQL: - case ProtocolType_Mongo: { - return ServiceCategory::DB; - } - case ProtocolType_Kafka: { - return ServiceCategory::MQ; - } - case ProtocolType_HTTP: - case ProtocolType_Dubbo: - case ProtocolType_HSF: { - return ServiceCategory::Server; - } - case ProtocolType_DNS: { - return ServiceCategory::DNS; - } - default: { - return ServiceCategory::Unknown; - } - } -} diff --git a/core/observer/metas/CGroupPathResolver.cpp b/core/observer/metas/CGroupPathResolver.cpp deleted file mode 100644 index 306154ffef..0000000000 --- a/core/observer/metas/CGroupPathResolver.cpp +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "CGroupPathResolver.h" -#include "common/FileSystemUtil.h" -#include "logger/Logger.h" -#include "common/ExceptionBase.h" -#include -#include "StringTools.h" - -namespace logtail { -static const char* containerTypeStr[] = {"docker", "cri-containerd", "crio", "kubepods", "unknow"}; -static const char* containerTypeRegexStr[] = {"docker", "cri-containerd", "crio", "\\S+", "unknow"}; - -CONTAINER_TYPE ExtractContainerType(const std::string& path) { - if (path.find(containerTypeStr[CONTAINER_TYPE_DOCKER]) != std::string::npos) { - return CONTAINER_TYPE_DOCKER; - } else if (path.find(containerTypeStr[CONTAINER_TYPE_CRI_CONTAINERD]) != std::string::npos) { - return CONTAINER_TYPE_CRI_CONTAINERD; - } else if (path.find(containerTypeStr[CONTAINER_TYPE_CRIO]) != std::string::npos) { - return CONTAINER_TYPE_CRIO; - } else if (path.find(containerTypeStr[CONTAINER_TYPE_K8S_OTHERS]) != std::string::npos) { - // "/sys/fs/cgroup/cpu,cpuacct/kubepods.slice/kubepods-burstable.slice/" - return CONTAINER_TYPE_K8S_OTHERS; - } - return CONTAINER_TYPE_UNKNOWN; -} - -KubernetesCGroupPathMatcher* GetCGroupMatcher(const std::string& path, CONTAINER_TYPE type) { - const std::string podRegex = "([0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12})"; - const std::string containerRegex = "([0-9a-f]{64})"; - auto* matcher = new KubernetesCGroupPathMatcher(); - // STANDARD: - // kubepods.slice/kubepods-pod2b801b7a_5266_4386_864e_45ed71136371.slice/cri-containerd-20e061fc708d3b66dfe257b19552b34a1307a7347ed6b5bd0d8c5e76afb1a870.scope/cgroup.procs - matcher->setGuaranteedPathRegex(boost::regex("^kubepods.slice\\/kubepods-pod" + podRegex + ".slice\\/" - + containerTypeRegexStr[type] + "-" + containerRegex - + "\\.scope\\/cgroup\\.procs$")); - // STANDARD: - // kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod0d206349_0faf_445c_8c3f_2d2153784f15.slice/cri-containerd-efd08a78ad94af4408bcdb097fbcb603a31a40e4d74907f72ff14c3264ee7e85.scope/cgroup.procs - matcher->setBesteffortPathRegex(boost::regex("^kubepods.slice\\/kubepods-besteffort.slice\\/kubepods-besteffort-pod" - + podRegex + ".slice\\/" + containerTypeRegexStr[type] + "-" - + containerRegex + "\\.scope\\/cgroup\\.procs$")); - // STANDARD: - // kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podee10fb7d_d989_47b3_bc2a_e9ffbe767849.slice/cri-containerd-4591321a5d841ce6a60a777223cf7fe872d1af0ca721e76a5cf20985056771f7.scope/cgroup.procs - matcher->setBurstablePathRegex(boost::regex("^kubepods.slice\\/kubepods-burstable.slice\\/kubepods-burstable-pod" - + podRegex + ".slice\\/" + containerTypeRegexStr[type] + "-" - + containerRegex + "\\.scope\\/cgroup\\.procs$")); - if (matcher->IsMatch(path)) { - return matcher; - } - // GKE: - // kubepods/pod8dbc5577-d0e2-4706-8787-57d52c03ddf2/14011c7d92a9e513dfd69211da0413dbf319a5e45a02b354ba6e98e10272542d/cgroup.procs - matcher->setGuaranteedPathRegex( - boost::regex("^kubepods\\/pod" + podRegex + "\\/" + containerRegex + "\\/cgroup\\.procs$")); - // GKE: - // kubepods/besteffort/pod8dbc5577-d0e2-4706-8787-57d52c03ddf2/14011c7d92a9e513dfd69211da0413dbf319a5e45a02b354ba6e98e10272542d/cgroup.procs - matcher->setBesteffortPathRegex( - boost::regex("^kubepods\\/besteffort\\/pod" + podRegex + "\\/" + containerRegex + "\\/cgroup\\.procs$")); - // GKE: - // kubepods/burstable/pod8dbc5577-d0e2-4706-8787-57d52c03ddf2/14011c7d92a9e513dfd69211da0413dbf319a5e45a02b354ba6e98e10272542d/cgroup.procs - matcher->setBurstablePathRegex( - boost::regex("^kubepods\\/burstable\\/pod" + podRegex + "\\/" + containerRegex + "\\/cgroup\\.procs$")); - if (matcher->IsMatch(path)) { - return matcher; - } - // pure docker : - // /sys/fs/cgroup/cpu/docker/1ad2ce5889acb209e1576339741b1e504480db77d3771365e95b3bbd6fe91120/cgroup.procs - matcher->setGuaranteedPathRegex(boost::regex("^docker\\/" + containerRegex + "\\/cgroup\\.procs$")); - if (matcher->IsMatch(path)) { - return matcher; - } - delete matcher; - return nullptr; -} - -std::string CGroupBasePath(const std::string& sysfsPath) { - const std::vector cgroup_dirs = {"cpu,cpuacct", "cpu", "pids"}; - std::string basePath = sysfsPath; - basePath.append("/cgroup/"); - for (auto& dir : cgroup_dirs) { - std::string innerPath = basePath; - innerPath.append(dir); - if (logtail::IsAccessibleDirectory(innerPath)) { - return innerPath; - } - } - throw ExceptionBase(std::string("cannot find the base cgroup path'")); -} - -void resolveAllCGroupProcsPaths(const std::string& basePath, std::vector& allPaths) { - fsutil::Dir dir(basePath); - if (!dir.Open()) { - LOG_DEBUG(sLogger, ("Open dir fail", basePath)("errno", GetErrno())); - return; - } - while (auto ent = dir.ReadNext(false)) { - if (ent.IsRegFile()) { - static std::string sCgroupProcesFileName = "cgroup.procs"; - if (ent.Name() == sCgroupProcesFileName) { - std::string fullPath = basePath + "/cgroup.procs"; - allPaths.push_back(fullPath); - } - continue; - } - if (ent.IsDir()) { - resolveAllCGroupProcsPaths(basePath + "/" + ent.Name(), allPaths); - } - } -} - -void ResolveAllCGroupProcsPaths(const std::string& basePath, std::vector& allPaths) { - // resolve standard or OpenShift kubernetes cgroup procs paths - resolveAllCGroupProcsPaths(basePath + "/kubepods.slice", allPaths); - if (!allPaths.empty()) - return; - // resolve GKE kubernetes cgroup procs paths - resolveAllCGroupProcsPaths(basePath + "/kubepods", allPaths); - if (!allPaths.empty()) - return; - // resolve docker cgroup procs paths - resolveAllCGroupProcsPaths(basePath + "/docker", allPaths); - if (!allPaths.empty()) - return; - resolveAllCGroupProcsPaths(basePath, allPaths); -} - - -bool KubernetesCGroupPathMatcher::IsMatch(const std::string& path) { - std::string exception; - if (path.find("burstable") != std::string::npos) { - return BoostRegexMatch(path.c_str(), this->burstablePathRegex, exception); - } else if (path.find("besteffort") != std::string::npos) { - return BoostRegexMatch(path.c_str(), this->besteffortPathRegex, exception); - } else { - return BoostRegexMatch(path.c_str(), this->guaranteedPathRegex, exception); - } -} - -void KubernetesCGroupPathMatcher::ExtractProcessMeta(const std::string& path, - std::string& containerID, - std::string& podID) { - std::string exception; - boost::match_results what; - bool res; - if (path.find("burstable") != std::string::npos) { - res = BoostRegexSearch(path.c_str(), this->burstablePathRegex, exception, what); - } else if (path.find("besteffort") != std::string::npos) { - res = BoostRegexSearch(path.c_str(), this->besteffortPathRegex, exception, what); - } else { - res = BoostRegexSearch(path.c_str(), this->guaranteedPathRegex, exception, what); - } - if (!res) { - LOG_DEBUG(sLogger, ("extract contianer meta error", exception)("path", path)); - return; - } - if (what.size() == 3) { - podID = what[1].str(); - containerID = what[2].str(); - } else if (what.size() == 2) { - containerID = what[1].str(); - } -} - -std::string ExtractPodWorkloadName(const std::string& podName) { - if (podName.empty()) { - return podName; - } - static boost::regex deploymentPodReg(R"(^([\w\-]+)\-([0-9a-z]{9,10})\-([0-9a-z]{5})$)"); - static boost::regex setPodReg(R"(^([\w\-]+)\-([0-9a-z]{5})$)"); - static boost::regex statefulSetPodReg(R"(^([\w\-]+)\-(\d+)$)"); - std::string exception; - boost::match_results what; - std::string pod; - if (BoostRegexSearch(podName.c_str(), deploymentPodReg, exception, what)) { - if (what.size() > 1) { - return what[1].str(); - } - } else if (BoostRegexSearch(podName.c_str(), setPodReg, exception, what)) { - if (what.size() > 1) { - return what[1].str(); - } - } else if (BoostRegexSearch(podName.c_str(), statefulSetPodReg, exception, what)) { - if (what.size() > 1) { - return what[1].str(); - } - } - LOG_DEBUG(sLogger, ("extract workload name error", exception)("podName", podName)); - return podName; -} - -std::string ReadPidCgroupPath(uint32_t pid, const std::string& procPath) { - std::string cgroupPath = std::string(procPath).append(std::to_string(pid)).append("/cgroup"); - std::string content; - LOG_DEBUG(sLogger, ("read pid cgroup path", cgroupPath)); - if (!ReadFileContent(cgroupPath, content) || content.empty()) { - return ""; - } - std::vector pidStrs = SplitString(content, "\n"); - for (const auto& item : pidStrs) { - if (item.empty()) { - continue; - } - std::vector parts = SplitString(item, ":"); - if (parts.size() != 3) { - continue; - } - if ((parts[1] == "cpu,cpuacct" || parts[1] == "pids") && !parts[2].empty()) { - parts[2].erase(parts[2].begin()); - return parts[2] + "/cgroup.procs"; - } - } - LOG_DEBUG(sLogger, ("read pid cgroup path error, pid", pid)("procPath", procPath)); - return ""; -} - - -std::string ContainerTypeToString(CONTAINER_TYPE ct) { - const static std::string sContainerTypeValue[] = {"docker", "cri_contianerd", "crio", "k8s_others", "unknown"}; - return sContainerTypeValue[ct]; -} -} // namespace logtail \ No newline at end of file diff --git a/core/observer/metas/CGroupPathResolver.h b/core/observer/metas/CGroupPathResolver.h deleted file mode 100644 index 2d46722b28..0000000000 --- a/core/observer/metas/CGroupPathResolver.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include - -#include "ProcessMeta.h" - -namespace logtail { - -enum CONTAINER_TYPE { - CONTAINER_TYPE_DOCKER, - CONTAINER_TYPE_CRI_CONTAINERD, - CONTAINER_TYPE_CRIO, - CONTAINER_TYPE_K8S_OTHERS, - CONTAINER_TYPE_UNKNOWN -}; - - -class KubernetesCGroupPathMatcher { -private: - boost::regex guaranteedPathRegex; - boost::regex besteffortPathRegex; - boost::regex burstablePathRegex; - -public: - void setGuaranteedPathRegex(const boost::regex& regex) { KubernetesCGroupPathMatcher::guaranteedPathRegex = regex; } - - void setBesteffortPathRegex(const boost::regex& regex) { KubernetesCGroupPathMatcher::besteffortPathRegex = regex; } - - void setBurstablePathRegex(const boost::regex& regex) { KubernetesCGroupPathMatcher::burstablePathRegex = regex; } - - bool IsMatch(const std::string& path); - - void ExtractProcessMeta(const std::string& path, std::string& containerID, std::string& podID); -}; - -CONTAINER_TYPE ExtractContainerType(const std::string& path); - -std::string CGroupBasePath(const std::string& sysfs_path); - -void ResolveAllCGroupProcsPaths(const std::string& bashPath, std::vector& allPaths); - -KubernetesCGroupPathMatcher* GetCGroupMatcher(const std::string& path, CONTAINER_TYPE type); - -std::string ExtractPodWorkloadName(const std::string& podName); - -std::string ReadPidCgroupPath(uint32_t pid, const std::string& procPath = "/proc/"); - -std::string ContainerTypeToString(CONTAINER_TYPE ct); - -} // namespace logtail \ No newline at end of file diff --git a/core/observer/metas/ConnectionMetaManager.cpp b/core/observer/metas/ConnectionMetaManager.cpp deleted file mode 100644 index 5def0db777..0000000000 --- a/core/observer/metas/ConnectionMetaManager.cpp +++ /dev/null @@ -1,524 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include -#include -#include -#include -#include -#include "ConnectionMetaManager.h" -#include "FileSystemUtil.h" -#include "Logger.h" -#include -#include "LogtailAlarm.h" -#include "MachineInfoUtil.h" -#include "DynamicLibHelper.h" - -namespace logtail { - -uint32_t ReadInodeNum(const std::string& path, const std::string& prefix, int8_t& errorCode) { - size_t pathLen = path.size(), prefixLen = prefix.size(); - if (pathLen - prefixLen < 3 || path.compare(0, prefixLen, prefix)) { - errorCode = -1; - return 0; - } - if (path[prefixLen] != '[' || path[pathLen - 1] != ']') { - errorCode = -2; - return 0; - } - errorCode = 0; - return (uint32_t)std::strtol(path.c_str() + prefixLen + 1, NULL, 10); -} - -uint32_t ReadNetworkNsInodeNum(const std::string& path, int8_t& errorCode) { - const static std::string prefix = "net:"; - return ReadInodeNum(path, prefix, errorCode); -} - -uint32_t ReadSocketInodeNum(const std::string& path, int8_t& errorCode) { - const static std::string prefix = "socket:"; - return ReadInodeNum(path, prefix, errorCode); -} - - -bool ConnectionMetaManager::Init(const std::string& procBashPath) { - if (!this->mBashProcPath.empty()) { - return true; - } - std::string bashPath = procBashPath; - if (procBashPath[procBashPath.size() - 1] != '/') { - bashPath.append("/"); - } - - if (!IsAccessibleDirectory(bashPath)) { - LOG_ERROR(sLogger, ("init observer connection manager", "fail")("proc path", bashPath)); - LogtailAlarm::GetInstance()->SendAlarm( - OBSERVER_INIT_ALARM, - "init observer connection manager fails because the proc path cannot be accessed: " + bashPath); - return false; - } - this->mBashProcPath = bashPath; - LOG_INFO(sLogger, ("init observer connection manager", "success")("proc path", bashPath)); - return true; -} - -ConnectionInfoPtr ConnectionMetaManager::GetConnectionInfo(uint32_t pid, uint32_t fd) { - ++mConnMetaStatistic->mGetSocketInfoCount; - std::string fdPath = this->mBashProcPath; - fdPath.append(std::to_string(pid)).append("/fd/").append(std::to_string(fd)); - std::string fdLinkPath; - ReadFdLink(fdPath, fdLinkPath); - if (fdLinkPath.empty()) { - LOG_DEBUG(sLogger, ("fetch connection meta", "fail")("path", fdPath)); - ++mConnMetaStatistic->mGetSocketInfoFailCount; - return nullptr; - } - int8_t errorCode; - uint32_t inode = ReadSocketInodeNum(fdLinkPath, errorCode); - if (errorCode < 0) { - LOG_DEBUG(sLogger, ("fetch connection meta", "fail")("fdLink", fdLinkPath)); - ++mConnMetaStatistic->mGetSocketInfoFailCount; - return nullptr; - } - auto meta = mConnectionMeta.find(inode); - if (meta != mConnectionMeta.end()) { - return meta->second; - } - static auto sProberManger = NamespacedProberManger::GetInstance(this->mBashProcPath); - auto prober = sProberManger->GetOrCreateProber(pid); - ++mConnMetaStatistic->mGetNetlinkProberCount; - if (prober == nullptr) { - LOG_DEBUG(sLogger, ("fetch connection meta", "fail")("prober create fail", pid)); - ++mConnMetaStatistic->mGetSocketInfoFailCount; - ++mConnMetaStatistic->mGetNetlinkProberFailCount; - return nullptr; - } - if (mProberFetchLog.find(prober->Inode()) != mProberFetchLog.end()) { - ++mConnMetaStatistic->mGetSocketInfoFailCount; - return nullptr; - } - mProberFetchLog.insert(prober->Inode()); - ++mConnMetaStatistic->mFetchNetlinkCount; - prober->FetchInetConnections(this->mConnectionMeta); - prober->FetchUnixConnections(this->mConnectionMeta); - - meta = mConnectionMeta.find(inode); - if (meta != mConnectionMeta.end()) { - LOG_DEBUG( - sLogger, - ("ConnectionManager find info", "success")("pid", pid)("inode", inode)("meta", meta->second->ToString())); - return meta->second; - } - LOG_DEBUG(sLogger, ("ConnectionManager find info", "fail")("pid", pid)("inode", inode)); - ++mConnMetaStatistic->mGetSocketInfoFailCount; - return nullptr; -} - -bool ConnectionMetaManager::GarbageCollection() { - mConnectionMeta.erase(this->mConnectionMeta.begin(), this->mConnectionMeta.end()); - mProberFetchLog.erase(this->mProberFetchLog.begin(), this->mProberFetchLog.end()); - static auto sProberManger = NamespacedProberManger::GetInstance(this->mBashProcPath); - sProberManger->GarbageCollection(); - return true; -} - -void ConnectionMetaManager::Print() { - for (const auto& item : this->mConnectionMeta) { - std::cout << "inode:" << item.first << " info: " << item.second->ToString() << std::endl; - } -} - -NetLinkProber::NetLinkProber(uint32_t pid, uint32_t inode, const std::string& procPath) { - this->mInode = inode; - NetLinkBinder binder(pid, procPath); - // only create netlink socket when bind success, otherwise would get wrong connections. - if (!binder.Status()) { - this->mStatus = -1; - LOG_DEBUG(sLogger, ("bind network ns", "fail")); - return; - } - this->mFd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_SOCK_DIAG); - if (mFd < 0) { - LOG_DEBUG(sLogger, ("open netlink socket", "fail")); - this->mStatus = -2; - return; - } - LOG_DEBUG(sLogger, ("open netlink socket", "success")); -} - -template -bool NetLinkProber::SendMsg(const msgType& realMsg, std::string& errorMsg) { - struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; - struct { - struct nlmsghdr nlh; - msgType msg; - } req{ - .nlh={ - .nlmsg_len = sizeof(req), - .nlmsg_type=SOCK_DIAG_BY_FAMILY, - .nlmsg_flags= NLM_F_REQUEST | NLM_F_DUMP, - }, - .msg=realMsg, - }; - struct iovec iov { - .iov_base = &req, .iov_len = sizeof(req), - }; - struct msghdr msg = { - .msg_name = &nladdr, - .msg_namelen = sizeof(nladdr), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - - ssize_t sentTotal = 0; - - while (sentTotal < (ssize_t)iov.iov_len) { - ssize_t sendBytes = sendmsg(this->mFd, &msg, 0); - if (sendBytes < 0) { - errorMsg = "cannot send msg to netlink, fd:" + std::to_string(this->mFd); - return false; - } - LOG_DEBUG(sLogger, ("send length", sendBytes)); - sentTotal += sendBytes; - } - return true; -} - -template -bool NetLinkProber::ReceiveMsg(std::unordered_map& infos, std::string& errorMsg) { - static int bufSize = 8192; - long buffer[bufSize / sizeof(long)]; - - struct sockaddr_nl nladdr = {}; - struct iovec iov = {.iov_base = buffer, .iov_len = sizeof(buffer)}; - - while (true) { - msghdr msg = {.msg_name = &nladdr, .msg_namelen = sizeof(nladdr), .msg_iov = &iov, .msg_iovlen = 1}; - - ssize_t ret = recvmsg(this->mFd, &msg, 0); - if (ret < 0) { - errorMsg = "cannot read msg from netlink" + std::to_string(this->mFd); - return false; - } - if (ret == 0) { - errorMsg = "read zero bytes msg from netlink" + std::to_string(this->mFd); - return false; - } - if (nladdr.nl_family != AF_NETLINK) { - errorMsg = "read the wrong msg because nl_family is not AF_NETLINK, which is " - + std::to_string(nladdr.nl_family); - return false; - } - LOG_DEBUG(sLogger, ("receive length", ret)); - const struct nlmsghdr* header = (struct nlmsghdr*)buffer; - - for (; NLMSG_OK(header, ret); header = NLMSG_NEXT(header, ret)) { - if (header->nlmsg_type == NLMSG_DONE) - return true; - - if (header->nlmsg_type == NLMSG_ERROR) { - errorMsg = "netlink error"; - return false; - } - if (header->nlmsg_type != SOCK_DIAG_BY_FAMILY) { - errorMsg = "not SOCK_DIAG_BY_FAMILY msg"; - return false; - } - auto* data = reinterpret_cast(NLMSG_DATA(header)); - if (!ExtractDiagMsg(*data, header->nlmsg_len, infos, errorMsg)) { - return false; - } - } - } - return true; -} - -void NetLinkProber::FetchInetConnections(std::unordered_map& infos, int connStat) { - inet_diag_req_v2 req = {}; - req.sdiag_protocol = IPPROTO_TCP; - req.idiag_states = connStat; - std::string errorMsg; - - req.sdiag_family = AF_INET; - this->SendMsg(req, errorMsg); - if (!errorMsg.empty()) { - LOG_DEBUG(sLogger, ("fetch inet connection error", errorMsg)); - return; - } - LOG_DEBUG(sLogger, ("send inet msg", "success")); - this->ReceiveMsg(infos, errorMsg); - if (!errorMsg.empty()) { - LOG_DEBUG(sLogger, ("fetch inet connection error", errorMsg)); - return; - } - LOG_DEBUG(sLogger, ("receive inet msg", "success")("size", infos.size())); - req.sdiag_family = AF_INET6; - this->SendMsg(req, errorMsg); - if (!errorMsg.empty()) { - LOG_DEBUG(sLogger, ("fetch inet connection error", errorMsg)); - return; - } - this->ReceiveMsg(infos, errorMsg); - if (!errorMsg.empty()) { - LOG_DEBUG(sLogger, ("fetch inet connection error", errorMsg)); - return; - } - - std::unordered_set connSet; - for (const auto& item : infos) { - if (item.second->stat == TCPConnectionStat::Listening) { - connSet.insert(item.second); - } - } - ConnectionInfoPtr ip = std::make_shared(); - ip->localAddr.Addr = {}; - for (const auto& item : infos) { - ip->localPort = item.second->localPort; - ip->localAddr.Type = item.second->localAddr.Type; - if (connSet.find(ip) != connSet.end() || connSet.find(item.second) != connSet.end()) { - item.second->role = PacketRoleType::Server; - } else { - item.second->role = PacketRoleType::Client; - } - } -} - -void NetLinkProber::FetchUnixConnections(std::unordered_map& infos, int connStat) { - unix_diag_req req = {}; - std::string errorMsg; - req.sdiag_family = AF_UNIX; - req.udiag_states = connStat; - req.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER; - - this->SendMsg(req, errorMsg); - if (!errorMsg.empty()) { - LOG_DEBUG(sLogger, ("fetch unix connection error", errorMsg)); - return; - } - this->ReceiveMsg(infos, errorMsg); - if (!errorMsg.empty()) { - LOG_DEBUG(sLogger, ("fetch unix connection error", errorMsg)); - return; - } -} - -NetLinkProber::~NetLinkProber() { - if (mFd >= 0) { - close(mFd); - LOG_DEBUG(sLogger, ("close netlink socket ", "success")); - } -} - -bool ExtractDiagMsg(const inet_diag_msg& msg, - uint32_t len, - std::unordered_map& infos, - std::string& errorMsg) { - if (len < sizeof(msg)) { - errorMsg = "no enough netlink data"; - return false; - } - if (msg.idiag_family != AF_INET && msg.idiag_family != AF_INET6) { - errorMsg = "unsupported idiag family " + std::to_string(msg.idiag_family); - return false; - } - - uint32_t inode = msg.idiag_inode; - if (inode == 0) { - return true; - } - auto iter = infos.find(inode); - if (iter != infos.end()) { - errorMsg = "duplicate inode msg " + std::to_string(inode); - return false; - } - auto info = std::make_shared(); - info->family = msg.idiag_family; - info->localPort = ntohs(msg.id.idiag_sport); - info->remotePort = ntohs(msg.id.idiag_dport); - info->stat = static_cast(msg.idiag_state); - - if (msg.idiag_family == AF_INET) { - info->localAddr = SockAddress{.Type = SockAddressType_IPV4, - .Addr = SockAddressDetail{ - .IPV4 = msg.id.idiag_src[0], - }}; - info->remoteAddr = SockAddress{.Type = SockAddressType_IPV4, - .Addr = SockAddressDetail{ - .IPV4 = msg.id.idiag_dst[0], - }}; - } else if (msg.idiag_family == AF_INET6) { -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstrict-aliasing" -#endif // __GNUC__ - info->localAddr = SockAddress{.Type = SockAddressType_IPV6, - .Addr = SockAddressDetail{ - .IPV6 = {((uint64_t*)msg.id.idiag_src)[0], ((uint64_t*)msg.id.idiag_src)[1]}, - }}; - info->remoteAddr = SockAddress{.Type = SockAddressType_IPV6, - .Addr = SockAddressDetail{ - .IPV6 = {((uint64_t*)msg.id.idiag_dst)[0], ((uint64_t*)msg.id.idiag_dst)[1]}, - }}; -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif // __GNUC__ - } - infos.insert(std::make_pair(inode, info)); - return true; -} - -bool ExtractDiagMsg(const unix_diag_msg& msg, - uint32_t len, - std::unordered_map& infos, - std::string& errorMsg) { - if (len < sizeof(msg)) { - errorMsg = "no enough netlink data"; - return false; - } - if (msg.udiag_family != AF_UNIX) { - errorMsg = "unsupported idiag family " + std::to_string(msg.udiag_family); - return false; - } - unsigned int rta_len = len - NLMSG_LENGTH(sizeof(msg)); - unsigned int peer = 0; - - for (auto* attr = (struct rtattr*)(&msg + 1); RTA_OK(attr, rta_len); attr = RTA_NEXT(attr, rta_len)) { - switch (attr->rta_type) { - case UNIX_DIAG_NAME: - break; - case UNIX_DIAG_PEER: - if (RTA_PAYLOAD(attr) >= sizeof(peer)) - peer = *(unsigned int*)RTA_DATA(attr); - break; - } - } - auto iter = infos.find(msg.udiag_ino); - if (iter != infos.end()) { - errorMsg = "duplicate inode msg " + std::to_string(msg.udiag_ino); - return false; - } - auto info = std::make_shared(); - info->family = msg.udiag_family; - info->localPort = msg.udiag_ino; - info->remotePort = peer; - info->stat = static_cast(msg.udiag_state); - info->localAddr = SockAddress{.Type = SockAddressType_IPV4, - .Addr = SockAddressDetail{ - .IPV4 = 0, - }}; - info->remoteAddr = info->localAddr; - infos.insert(std::make_pair(msg.udiag_ino, info)); - return true; -} - - -NetLinkBinder::NetLinkBinder(uint32_t pid, const std::string& procPath) { - static const std::string selfPath = "self/ns/net"; - std::string bashPath = procPath; - if (procPath[procPath.size() - 1] != '/') { - bashPath.append("/"); - } - std::string selfNsPath = bashPath + selfPath; - this->mOrgFd = open((selfNsPath).c_str(), O_RDONLY); - if (this->mOrgFd < 0) { - LOG_DEBUG(sLogger, ("netlink status", "fail")("reason", "open self net ns fail")("path", selfNsPath)); - this->Close(); - return; - } else { - LOG_DEBUG(sLogger, ("open self net ns", "success")("path", selfNsPath)); - } - std::string pidNsPath = bashPath; - pidNsPath.append(std::to_string(pid)).append("/ns/net"); - this->mFd = open(pidNsPath.c_str(), O_RDONLY); - if (this->mFd < 0) { - LOG_DEBUG(sLogger, ("netlink status", "fail")("reason", "open pid net ns fail")("path", pidNsPath)); - this->Close(); - return; - } else { - LOG_DEBUG(sLogger, ("open pid net ns", "success")("path", pidNsPath)); - } - int res = glibc::g_setns_func(this->mFd, 0); - if (res != 0) { - LOG_DEBUG(sLogger, ("netlink status", "fail")("reason", "set ns fail")("path", pidNsPath)); - this->Close(); - return; - } else { - LOG_DEBUG(sLogger, ("set ns status", "success")); - } - this->success = true; -} - - -void NetLinkBinder::Close() { - if (this->success && mOrgFd >= 0) { - int res = glibc::g_setns_func(this->mOrgFd, 0); - if (res != 0) { - LOG_ERROR(sLogger, ("netlink status", "unknown")("reason", "recover netlink net ns fail")); - } else { - LOG_DEBUG(sLogger, ("recover netlink net ns", "success")); - } - } - if (this->mFd >= 0) { - close(this->mFd); - this->mFd = -1; - LOG_DEBUG(sLogger, ("close pid net ns fd", "success")); - } - if (this->mOrgFd >= 0) { - close(this->mOrgFd); - this->mOrgFd = -1; - LOG_DEBUG(sLogger, ("close self net ns fd", "success")); - } -} - -std::shared_ptr NamespacedProberManger::GetOrCreateProber(uint32_t pid) { - std::string nsPath = this->mBaseProcPath; - nsPath.append(std::to_string(pid)).append("/ns/net"); - std::string fdLink; - ReadFdLink(nsPath, fdLink); - if (fdLink.empty()) { - LOG_DEBUG(sLogger, ("get netlink prober", "fail")("cannot read fdlink path", fdLink)); - return nullptr; - } - int8_t errorCode; - uint32_t inode = ReadNetworkNsInodeNum(fdLink, errorCode); - if (errorCode < 0) { - LOG_DEBUG(sLogger, ("get netlink prober", "fail")("cannot read net inode", nsPath)("error", errorCode)); - return nullptr; - } - auto item = this->mProbers.find(inode); - if (item != this->mProbers.end()) { - return item->second; - } - auto prober = std::make_shared(pid, inode, this->mBaseProcPath); - if (prober->Status() < 0) { - LOG_DEBUG(sLogger, ("get netlink prober", "fail")("prober create fail", prober->Status())); - return nullptr; - } - this->mProbers.insert(std::make_pair(inode, prober)); - return prober; -} - -// Clear all namespaced probers and close their Fd. -void NamespacedProberManger::GarbageCollection() { - this->mProbers.erase(this->mProbers.begin(), this->mProbers.end()); -} - -void ReadFdLink(std::string& fdPath, std::string& fdLinkPath) { - boost::system::error_code ec; - auto path = boost::filesystem::read_symlink(fdPath, ec); - if (!ec) { - fdLinkPath = path.string(); - } -} -} // namespace logtail \ No newline at end of file diff --git a/core/observer/metas/ConnectionMetaManager.h b/core/observer/metas/ConnectionMetaManager.h deleted file mode 100644 index dbd418ddf7..0000000000 --- a/core/observer/metas/ConnectionMetaManager.h +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include "Logger.h" -#include "interface/network.h" -#include "linux/rtnetlink.h" -#include "interface/helper.h" -#include "interface/statistics.h" - - -namespace logtail { - -enum class TCPConnectionStat { - Unknown = 0, - Established = 1, - TimeWait = 6, - CloseWait = 8, - Listening = 10, -}; - -struct ConnectionInfo { - sa_family_t family; - SockAddress localAddr; - SockAddress remoteAddr; - // local port and remote port save inode num in unix socket, so use uint32. - uint32_t localPort; - uint32_t remotePort; - TCPConnectionStat stat = TCPConnectionStat::Unknown; - PacketRoleType role; - - friend std::ostream& operator<<(std::ostream& os, const ConnectionInfo& info) { - os << "family: " << info.family << " localAddr: " << SockAddressToString(info.localAddr) - << " remoteAddr: " << SockAddressToString(info.remoteAddr) << " localPort: " << info.localPort - << " remotePort: " << info.remotePort << " stat: " << ((int)info.stat) - << " role: " << PacketRoleTypeToString(info.role); - return os; - } - - std::string ToString() { - std::stringstream ss; - ss << *this; - return ss.str(); - } - - void Print() { std::cout << *this << std::endl; } -}; - -typedef std::shared_ptr ConnectionInfoPtr; - -struct ConnectionInfoPtrHashFn { - size_t operator()(const ConnectionInfoPtr& ptr) const { - size_t hash = XXH32(reinterpret_cast(ptr->localAddr.Addr.IPV6), sizeof(union SockAddressDetail), 0); - hash = XXH32(&ptr->localPort, sizeof(ptr->localPort), hash); - return hash; - } -}; - -struct ConnectionInfoPtrEqFn { - size_t operator()(const ConnectionInfoPtr& a, const ConnectionInfoPtr& b) const { - return a->localPort == b->localPort && a->localAddr == b->localAddr; - } -}; - - -// NetLinkBinder bind the specific ns fd to the global network ns. -class NetLinkBinder { -public: - explicit NetLinkBinder(uint32_t pid, const std::string& procPath = "/proc/"); - - ~NetLinkBinder() { this->Close(); } - - bool Status() const { return this->success; } - -private: - void Close(); - -private: - int mFd = -1; - int mOrgFd = -1; - bool success = false; -}; - -// NetLinkProber fetch all connections in global network ns. -class NetLinkProber { -public: - explicit NetLinkProber(uint32_t pid, uint32_t inode, const std::string& procPath = "/proc/"); - - void FetchInetConnections(std::unordered_map& infos, - int connStat - = (1 << (int)TCPConnectionStat::Established) | (1 << (int)TCPConnectionStat::Listening)); - - void FetchUnixConnections(std::unordered_map& infos, - int connStat - = (1 << (int)TCPConnectionStat::Established) | (1 << (int)TCPConnectionStat::Listening)); - - int8_t Status() const { return this->mStatus; } - - uint32_t Inode() const { return this->mInode; } - - ~NetLinkProber(); - -private: - /** - * Send dump connections request with netlink - * reference: - * 1.-0 https://man7.org/linux/man-pages/man7/sock_diag.7.html - * 2. pixie - */ - template - bool SendMsg(const msgType& msg, std::string& errorMsg); - - /** - * Receive dump connections response with netlink - * reference: - * 1. https://man7.org/linux/man-pages/man7/sock_diag.7.html - * 2. pixie - */ - template - bool ReceiveMsg(std::unordered_map& infos, std::string& errorMsg); - - int mFd = -1; - int8_t mStatus = 0; - uint32_t mInode = 0; -}; - - -class NamespacedProberManger { -public: - static NamespacedProberManger* GetInstance(std::string& baseProcPath) { - static auto* instance = new NamespacedProberManger(baseProcPath); - return instance; - } - - std::shared_ptr GetOrCreateProber(uint32_t pid); - - void GarbageCollection(); - -private: - explicit NamespacedProberManger(std::string& baseProcPath) : mBaseProcPath(baseProcPath) {} - - std::unordered_map> mProbers{}; - std::string mBaseProcPath; -}; - - -class ConnectionMetaManager { -public: - static ConnectionMetaManager* GetInstance() { - static auto* instance = new ConnectionMetaManager; - return instance; - } - bool Init(const std::string& procBashPath = "/proc/"); - - ConnectionInfoPtr GetConnectionInfo(uint32_t pid, uint32_t fd); - - bool GarbageCollection(); - - void Print(); - -private: - ConnectionMetaManager() { mConnMetaStatistic = ConnectionMetaStatistic::GetInstance(); } - -private: - ConnectionMetaStatistic* mConnMetaStatistic; - std::string mBashProcPath; - std::unordered_map mConnectionMeta{}; - std::unordered_set mProberFetchLog{}; -}; - - -uint32_t ReadInodeNum(const std::string& path, const std::string& prefix, int8_t& errorCode); - -uint32_t ReadNetworkNsInodeNum(const std::string& path, int8_t& errorCode); - -uint32_t ReadSocketInodeNum(const std::string& path, int8_t& errorCode); - -void ReadFdLink(std::string& fdPath, std::string& fdLinkPath); -} // namespace logtail diff --git a/core/observer/metas/ContainerProcessGroup.cpp b/core/observer/metas/ContainerProcessGroup.cpp deleted file mode 100644 index 34fd6f9a52..0000000000 --- a/core/observer/metas/ContainerProcessGroup.cpp +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "ContainerProcessGroup.h" -#include "common/TimeUtil.h" -#include "logger/Logger.h" -#include "CGroupPathResolver.h" -#include "monitor/LogtailAlarm.h" -#include "FileSystemUtil.h" -#include "StringTools.h" -#include "go_pipeline/LogtailPlugin.h" -#include "interface/statistics.h" -#include - -DEFINE_FLAG_INT32(sls_observer_process_update_interval, "SLS Observer Process Update Interval", 300); - - -namespace logtail { - -void ContainerProcessGroup::FlushOutMetrics(uint64_t timeNano, - std::vector& allData, - std::vector>& tags, - uint64_t interval) { - auto& metaTags = mMetaPtr->GetFormattedMeta(); - mAggregator.FlushOutMetrics(timeNano, allData, metaTags, tags, interval); -} - -void ContainerProcessGroupManager::FlushOutMetrics(std::vector& allData, - std::vector>& tags, - uint64_t interval) { - uint64_t timeNano = GetCurrentTimeInNanoSeconds(); - for (auto& iter : mPureProcessGroupMap) { - iter.second->FlushOutMetrics(timeNano, allData, tags, interval); - } - for (auto& iter : mContainerProcessGroupMap) { - iter.second->FlushOutMetrics(timeNano, allData, tags, interval); - } -} - - -bool ContainerProcessGroupManager::Init(const std::string& cgroupPath) { - if (!this->mGgoupBasePath.empty()) { - return true; - } - LOG_INFO(sLogger, ("try to find a valid cgroup path", "begin")); - try { - this->mGgoupBasePath = CGroupBasePath(cgroupPath); - } catch (const std::exception& e) { - LOG_ERROR(sLogger, ("fail to find cgroup path", e.what())); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, - "fail to find cgroup path:" + std::string(e.what())); - return false; - } - LOG_INFO(sLogger, ("find a valid cgroup path", "success")("path", this->mGgoupBasePath)); - return true; -} - - -int32_t ContainerProcessGroupManager::ParseCgroupPath(const std::string& path, - std::vector& pids, - std::string& containerID, - std::string& podID) { - std::string content; - if (!ReadFileContent(path, content)) { - return -2; - } - if (content.empty()) { - return -1; - } - std::vector pidStrs = SplitString(content, "\n"); - if (pidStrs.empty() || pidStrs[0].empty()) { - return -1; - } - for (size_t i = 0; i < pidStrs.size(); ++i) { - pids.push_back(strtol(pidStrs[i].c_str(), NULL, 10)); - } - if (path.size() <= mGgoupBasePath.size()) { - return -3; - } - mMatcher->ExtractProcessMeta(path.substr(mGgoupBasePath.size() + 1), containerID, podID); - if (containerID.empty()) { - return -4; - } - return 0; -} - -int32_t ContainerProcessGroupManager::DetectContainerType(const std::string& procsPath) { - mContainerType = ExtractContainerType(procsPath); - if (mContainerType == CONTAINER_TYPE_UNKNOWN) { - return -1; - } - if (procsPath.size() <= mGgoupBasePath.size()) { - mContainerType = CONTAINER_TYPE_UNKNOWN; - return -2; - } - mMatcher = GetCGroupMatcher(procsPath.substr(mGgoupBasePath.size() + 1), mContainerType); - if (mMatcher == NULL) { - mContainerType = CONTAINER_TYPE_UNKNOWN; - return -3; - } - LOG_INFO(sLogger, ("detect container type by cgroup path success, type", mContainerType)("path", procsPath)); - return 0; -} - -void ContainerProcessGroupManager::FlushMetas() { - // ProcessMetaStatistic is the gauge value, so must clear history data before fetching meta. - ProcessMetaStatistic::Clear(); - std::vector paths; - std::unordered_set pidSet; - if (!this->mGgoupBasePath.empty()) { - ResolveAllCGroupProcsPaths(this->mGgoupBasePath, paths); - } - if (paths.empty()) { - LOG_WARNING(sLogger, ("try to find process meta because of no valid cgroup proc paths", "")); - FlushPids(pidSet); - return; - } - if (mContainerType == CONTAINER_TYPE_UNKNOWN) { - // paths may have : - // 1. /sys/fs/cgroup/cpu,cpuacct/kubepods.slice/cgroup.procs - // 2. - // /sys/fs/cgroup/cpu,cpuacct/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pode4cd4327_698e_42e0_b6bb_54d32685bcf5.slice/docker-d1eefc8c91baea08621aa21b07abf73b2a565016326732793113f0ba31cc41a1.scope/cgroup.procs - // we should use max length's path to detect exact container type - size_t maxLenIndex = 0; - size_t maxLen = 0; - for (size_t i = 0; i < paths.size(); ++i) { - if (paths[i].size() > maxLen) { - maxLen = paths[i].size(); - maxLenIndex = i; - } - } - int32_t detectRst = DetectContainerType(paths[maxLenIndex]); - if (detectRst != 0) { - LOG_ERROR(sLogger, ("unknown container type from path", paths[maxLenIndex])("result", detectRst)); - FlushPids(pidSet); - return; - } - } - size_t ignoredPathCount = 0; - for (auto& p : paths) { - std::vector pids; - std::string containerID; - std::string podID; - int32_t rst = ParseCgroupPath(p, pids, containerID, podID); - if (rst == -1) { - ignoredPathCount++; - LOG_DEBUG(sLogger, ("parse cgroup path ignored, rst", rst)("path", p)); - continue; - } - if (rst < -1) { - mProcessMetaStatistic->mCgroupPathParseFailCount++; - LOG_DEBUG(sLogger, ("parse cgroup path failed, rst", rst)("path", p)); - continue; - } - bool containerMetaFetched = false; - K8sContainerMeta containerMeta; - for (const auto& pid : pids) { - if (pid == 0) { - continue; - } - pidSet.insert(pid); - ProcessMetaPtr& metaPtr = mProcessMetaMap[pid]; - if (metaPtr.get() == nullptr) { - metaPtr.reset(new ProcessMeta); - } - ProcessMeta* meta = metaPtr.get(); - if (meta->Pod.PodUUID != podID || meta->Container.ContainerID != containerID - || meta->Container.ContainerName.empty()) { - meta->Clear(); - meta->PID = pid; - meta->Container.ContainerID = containerID; - meta->Pod.PodUUID = podID; - if (!containerMetaFetched) { - containerMetaFetched = true; - containerMeta = LogtailPlugin::GetInstance()->GetContainerMeta(containerID); - mProcessMetaStatistic->mFetchContainerMetaCount++; - if (containerMeta.ContainerName.empty()) { - mProcessMetaStatistic->mFetchContainerMetaFailCount++; - } - LOG_DEBUG(sLogger, - ("flushMeta get container meta for pid", - pid)("id", containerID)("meta", containerMeta.ToString())); - } - meta->Pod.NameSpace = containerMeta.K8sNamespace; - meta->Pod.PodName = containerMeta.PodName; - meta->Pod.WorkloadName = ExtractPodWorkloadName(containerMeta.PodName); - meta->Container.ContainerName = containerMeta.ContainerName; - meta->Container.Image = containerMeta.Image; - meta->Pod.Labels = containerMeta.k8sLabels; - meta->Container.Labels = containerMeta.containerLabels; - meta->Container.Envs = containerMeta.envs; - } - } - } - mProcessMetaStatistic->mCgroupPathTotalCount = paths.size() - ignoredPathCount; - mProcessMetaStatistic->mWatchProcessCount = pidSet.size(); - LOG_INFO(sLogger, ("flush meta success", mProcessMetaStatistic->ToString())); - FlushPids(pidSet); -} - -void ContainerProcessGroupManager::FlushPids(const std::unordered_set& existedPids) { - uint32_t nowTime = time(NULL); - if (nowTime - mLastNormalProcessMetaUpdateTime >= (uint32_t)INT32_FLAG(sls_observer_process_update_interval)) { - mLastNormalProcessMetaUpdateTime = nowTime; - for (auto iter = mProcessMetaMap.begin(); iter != mProcessMetaMap.end();) { - // do not delete pid 0 - if (iter->first == 0) { - ++iter; - continue; - } - if (existedPids.find(iter->first) != existedPids.end()) { - // LOG_INFO(sLogger, (std::to_string(iter->first), iter->second->ToString())); - ++iter; - continue; - } - // check pid exists. if exists, update it; else delete it - std::string cmdLine; - if (!readCmdline(iter->first, cmdLine)) { - iter = mProcessMetaMap.erase(iter); - } else { - auto& metaPtr = iter->second; - if (metaPtr->ProcessCMD != cmdLine) { - // clear k8s info - metaPtr->Clear(); - metaPtr->ProcessCMD = cmdLine; - } - ++iter; - } - } - } -} - -ProcessMetaPtr ContainerProcessGroupManager::GetProcessMeta(uint32_t pid) { - auto findIter = mProcessMetaMap.find(pid); - if (findIter != mProcessMetaMap.end()) { - // todo 还需要检查插入时间,如果超过几分钟,还需要刷新一次PID列表 - return findIter->second; - } - auto path = ReadPidCgroupPath(pid); - if (!path.empty()) { - std::string containerID, podID; - std::vector pids; - auto res = this->ParseCgroupPath(path, pids, containerID, podID); - K8sContainerMeta containerMeta; - if (res == 0 && std::find(pids.begin(), pids.end(), pid) != pids.end()) { - ++mProcessMetaStatistic->mCgroupPathTotalCount; - mProcessMetaStatistic->mWatchProcessCount += pids.size(); - for (size_t i = 0; i < pids.size(); ++i) { - if (i == 0) { - containerMeta = LogtailPlugin::GetInstance()->GetContainerMeta(containerID); - ++mProcessMetaStatistic->mFetchContainerMetaCount; - if (containerMeta.ContainerName.empty()) { - ++mProcessMetaStatistic->mFetchContainerMetaFailCount; - } - LOG_DEBUG(sLogger, - ("getMeta get container meta for pid", pid)("id", containerID)("meta", - containerMeta.ToString())); - } - ProcessMetaPtr meta = mProcessMetaMap.find(pids[i]) == mProcessMetaMap.end() - ? std::make_shared() - : mProcessMetaMap[pids[i]]; - meta->Clear(); - meta->PID = pids[i]; - meta->Container.ContainerID = containerID; - meta->Pod.PodUUID = podID; - meta->Pod.NameSpace = containerMeta.K8sNamespace; - meta->Pod.PodName = containerMeta.PodName; - meta->Pod.WorkloadName = ExtractPodWorkloadName(containerMeta.PodName); - meta->Container.ContainerName = containerMeta.ContainerName; - meta->Container.Image = containerMeta.Image; - meta->Pod.Labels = containerMeta.k8sLabels; - meta->Container.Labels = containerMeta.containerLabels; - meta->Container.Envs = containerMeta.envs; - LOG_DEBUG(sLogger, ("getMeta insert process meta with container meta", pid)); - mProcessMetaMap.insert(std::make_pair(pids[i], meta)); - } - return mProcessMetaMap[pid]; - } else if (res < -1) { - ++mProcessMetaStatistic->mCgroupPathTotalCount; - ++mProcessMetaStatistic->mCgroupPathParseFailCount; - } - } - ProcessMetaPtr meta(new ProcessMeta); - meta->PID = pid; - std::string cmdLine; - if (readCmdline(pid, cmdLine)) { - meta->ProcessCMD = cmdLine; - } - ++mProcessMetaStatistic->mWatchProcessCount; - LOG_DEBUG(sLogger, ("getMeta insert process meta with cmd", pid)); - mProcessMetaMap.insert(std::make_pair(pid, meta)); - return meta; -} - -bool ContainerProcessGroupManager::readCmdline(uint32_t pid, std::string& cmdLine) { - std::string cmdLinePath = std::string("/proc/").append(std::to_string(pid)).append("/cmdline"); - if (!ReadFileContent(cmdLinePath, cmdLine, 1024)) { - return false; - } - for (char& i : cmdLine) { - if (i == '\0') - *(&i) = ' '; - } - auto position = cmdLine.find(' '); - if (position != std::string::npos) { - auto sub = cmdLine.substr(0, position); - if (sub.find("java") == std::string::npos && sub.find("python") == std::string::npos) { - cmdLine = std::move(sub); - } - } - return true; -} -} // namespace logtail diff --git a/core/observer/metas/ContainerProcessGroup.h b/core/observer/metas/ContainerProcessGroup.h deleted file mode 100644 index 517746a7f0..0000000000 --- a/core/observer/metas/ContainerProcessGroup.h +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "ProcessMeta.h" -#include "common/Thread.h" -#include "common/Lock.h" -#include "CGroupPathResolver.h" -#include "interface/statistics.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace logtail { - - -struct ContainerProcessGroup { - explicit ContainerProcessGroup(const ProcessMetaPtr& meta) : mMetaPtr(meta) { mAggregator.SetProcessMeta(meta); } - - void AddProcess(uint32_t pid) { mAllProcesses.insert(pid); } - - /** - * @brief RemoveProcess - * @param pid - * @return all processes empty flag - */ - bool RemoveProcess(uint32_t pid) { - mAllProcesses.erase(pid); - return mAllProcesses.empty(); - } - - void FlushOutMetrics(uint64_t timeNano, - std::vector& allData, - std::vector>& tags, - uint64_t interval); - - std::unordered_set mAllProcesses; - ProcessMetaPtr mMetaPtr; - ProtocolEventAggregators mAggregator; -}; - -typedef std::shared_ptr ContainerProcessGroupPtr; - -/** - * @brief The ContainerProcessGroupManager class - * 用来管理所有的容器进程组。内部管理所有pid对应的container映射关系,并支持动态/按需更新。工作主要如下: - * * 新的PID创建时,判断其所属的ContainerProcessGroup,返回ContainerProcessGroup实例(不存在创建) - * * 当PID不属于某个cgroup时,返回一个新的ContainerProcessGroup - * * 当Container所有的进程销毁时,销毁对应的ContainerProcessGroup实例 - */ -class ContainerProcessGroupManager { -public: - static ContainerProcessGroupManager* GetInstance() { - static ContainerProcessGroupManager* sManager = new ContainerProcessGroupManager; - return sManager; - } - - bool Init(const std::string& cgroupPath = "/sys/fs"); - - void ResetFilterProcessMeta() { - for (const auto& item : this->mProcessMetaMap) { - item.second->ResetFilter(); - } - } - - - /** - * @brief GetProcessMeta - * @param pid - * @return - */ - ProcessMetaPtr GetProcessMeta(uint32_t pid); - - - /** - * @brief GetContainerProcessGroupPtr 判断其所属的ContainerProcessGroup,返回ContainerProcessGroup实例(不存在创建) - * @note 当PID不属于某个cgroup时,返回一个新的ContainerProcessGroup - * @param pid - * @return - */ - ContainerProcessGroupPtr GetContainerProcessGroupPtr(const ProcessMetaPtr& meta, uint32_t pid) { - const std::string& containerID = meta->Container.ContainerID; - if (containerID.empty()) { - auto findIter = mPureProcessGroupMap.find(meta->PID); - if (findIter != mPureProcessGroupMap.end()) { - return findIter->second; - } - ContainerProcessGroupPtr newPtr(new ContainerProcessGroup(meta)); - mPureProcessGroupMap.insert(std::make_pair(meta->PID, newPtr)); - return newPtr; - } - auto findIter = mContainerProcessGroupMap.find(containerID); - if (findIter != mContainerProcessGroupMap.end()) { - findIter->second->AddProcess(pid); - return findIter->second; - } - ContainerProcessGroupPtr newPtr(new ContainerProcessGroup(meta)); - newPtr->AddProcess(pid); - mContainerProcessGroupMap.insert(std::make_pair(containerID, newPtr)); - return newPtr; - } - - /** - * @brief OnProcessDestroy 当进程销毁(一段时间没有数据流动也算销毁)的时候需要调用 - * @note 不能使用meta中的PID,meta可能属于容器内的其他进程 - * @param pid - */ - void OnProcessDestroy(ProcessMeta* meta, uint32_t pid) { - const std::string& containerID = meta->Container.ContainerID; - if (containerID.empty()) { - // only delete pid without container - mProcessMetaMap.erase(pid); - mPureProcessGroupMap.erase(pid); - } else { - auto findRst = mContainerProcessGroupMap.find(containerID); - if (findRst != mContainerProcessGroupMap.end()) { - bool needDelete = findRst->second->RemoveProcess(pid); - if (needDelete) { - mContainerProcessGroupMap.erase(findRst); - } - } - } - } - - void FlushOutMetrics(std::vector& allData, - std::vector>& tags, - uint64_t interval); - - - void FlushMetas(); - - std::string GetContainerType() { return ContainerTypeToString(this->mContainerType); }; - -protected: - void FlushPids(const std::unordered_set& existedPids); - - // 0 means success, -1 means ignored path, - int32_t - ParseCgroupPath(const std::string& path, std::vector& pids, std::string& containerID, std::string& podID); - - int32_t DetectContainerType(const std::string& path); - - bool readCmdline(uint32_t pid, std::string& cmdLine); - -private: - ContainerProcessGroupManager() { mProcessMetaStatistic = ProcessMetaStatistic::GetInstance(); } - - ProcessMetaStatistic* mProcessMetaStatistic; - // 从ContainerCenter同步过来的所有容器对应PID列表 - std::unordered_map mProcessMetaMap; - uint32_t mLastNormalProcessMetaUpdateTime = 0; - - // 保存所有已经创建的GroupPtr - // 这两个Map只有在实际有数据到的时候才会初始化,否则是空的 - // ContainerProcessGroupPtr 中除了引用ProcessMeta外,还有最关键的Aggregator信息 - // 需要注意,这两个Map的GC尤为重要 - std::unordered_map mPureProcessGroupMap; - std::unordered_map mContainerProcessGroupMap; - - - std::string mGgoupBasePath; - // matcher and containerType would be kept in the whole life cycle; - KubernetesCGroupPathMatcher* mMatcher = NULL; - CONTAINER_TYPE mContainerType = CONTAINER_TYPE_UNKNOWN; -}; - -} // namespace logtail diff --git a/core/observer/metas/K8sMeta.h b/core/observer/metas/K8sMeta.h deleted file mode 100644 index 3e72484a97..0000000000 --- a/core/observer/metas/K8sMeta.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include - -namespace logtail { - -struct K8sPodMeta { - std::string NameSpace; - std::string WorkloadName; // deploy/daemonset/statefulset - std::string PodName; - std::string PodUUID; - std::unordered_map Labels; - - void Clear() { - NameSpace.clear(); - WorkloadName.clear(); - PodName.clear(); - PodUUID.clear(); - Labels.erase(Labels.begin(), Labels.end()); - } -}; - -struct ContainerMeta { - std::string Image; - std::string ContainerName; - std::string ContainerID; - std::unordered_map Labels; - std::unordered_map Envs; - - void Clear() { - Image.clear(); - ContainerName.clear(); - ContainerID.clear(); - Labels.erase(Labels.begin(), Labels.end()); - Envs.erase(Envs.begin(), Envs.end()); - } -}; - -} // namespace logtail diff --git a/core/observer/metas/ProcessMeta.h b/core/observer/metas/ProcessMeta.h deleted file mode 100644 index 092c976fd7..0000000000 --- a/core/observer/metas/ProcessMeta.h +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include - -#include "K8sMeta.h" -#include "logger/Logger.h" -#include "network/NetworkConfig.h" -#include "network/sources/ebpf/EBPFWrapper.h" - -namespace logtail { - -struct ProcessMeta { - uint32_t PID; - std::string ProcessCMD; - - K8sPodMeta Pod; - ContainerMeta Container; - - void Clear() { - ProcessCMD.clear(); - Pod.Clear(); - Container.Clear(); - mMetaInfo.clear(); - mPassFilterRules = 0; - } - - std::string ToString() { - std::string rst; - std::vector >& meta = GetFormattedMeta(); - for (size_t i = 0; i < meta.size(); ++i) { - rst.append("[").append(meta[i].first).append(", ").append(meta[i].second).append("]"); - } - return rst; - } - - std::vector >& GetFormattedMeta() { - if (!mMetaInfo.empty()) { - return mMetaInfo; - } - - if (!Pod.PodName.empty()) { - // k8s pod - mMetaInfo.reserve(5); - mMetaInfo.emplace_back(std::string("_pod_name_"), Pod.PodName); - mMetaInfo.emplace_back(std::string("_namespace_"), Pod.NameSpace); - mMetaInfo.emplace_back(std::string("_container_name_"), Container.ContainerName); - mMetaInfo.emplace_back(std::string("_workload_name_"), Pod.WorkloadName); - mMetaInfo.emplace_back(std::string("_running_mode_"), "kubernetes"); - } else if (!Container.ContainerName.empty()) { - // container - mMetaInfo.reserve(2); - mMetaInfo.emplace_back(std::string("_container_name_"), Container.ContainerName); - mMetaInfo.emplace_back(std::string("_running_mode_"), "container"); - } else { - // process - mMetaInfo.reserve(3); - mMetaInfo.emplace_back(std::string("_process_pid_"), std::to_string(PID)); - mMetaInfo.emplace_back(std::string("_process_cmd_"), ProcessCMD); - mMetaInfo.emplace_back(std::string("_running_mode_"), "host"); - } - return mMetaInfo; - } - - static bool isMapLabelsMatch(std::unordered_map& includeLabels, - std::unordered_map& excludeLabels, - std::unordered_map& labels) { - std::string exception; - if (!includeLabels.empty()) { - bool matchedFlag = false; - for (const auto& item : includeLabels) { - auto res = labels.find(item.first); - if (res != labels.end()) { - if (res->second.empty() || BoostRegexMatch(res->second.c_str(), item.second, exception)) { - matchedFlag = true; - break; - } - } - } - if (!matchedFlag) { - return false; - } - } - if (!excludeLabels.empty()) { - for (const auto& item : excludeLabels) { - auto res = labels.find(item.first); - if (res != labels.end()) { - if (res->second.empty() || BoostRegexMatch(res->second.c_str(), item.second, exception)) { - return false; - } - } - } - } - return true; - } - - bool PassFilterRules() { - if (this->mPassFilterRules != 0) { - return mPassFilterRules > 0; - } - auto instance = NetworkConfig::GetInstance(); - std::string exception; - if (!Pod.PodName.empty()) { - if ((!instance->mIncludeNamespaceNameRegex.empty() - && !BoostRegexMatch(Pod.NameSpace.c_str(), instance->mIncludeNamespaceNameRegex, exception)) - || (!instance->mIncludePodNameRegex.empty() - && !BoostRegexMatch(Pod.PodName.c_str(), instance->mIncludePodNameRegex, exception)) - || (!instance->mExcludeNamespaceNameRegex.empty() - && BoostRegexMatch(Pod.NameSpace.c_str(), instance->mExcludeNamespaceNameRegex, exception)) - || (!instance->mExcludePodNameRegex.empty() - && BoostRegexMatch(Pod.PodName.c_str(), instance->mExcludePodNameRegex, exception)) - || (!isMapLabelsMatch(instance->mIncludeK8sLabels, instance->mExcludeK8sLabels, Pod.Labels)) - || (!instance->mIncludeContainerNameRegex.empty() - && !BoostRegexMatch( - Container.ContainerName.c_str(), instance->mIncludeContainerNameRegex, exception)) - || (!instance->mExcludeContainerNameRegex.empty() - && BoostRegexMatch( - Container.ContainerName.c_str(), instance->mExcludeContainerNameRegex, exception)) - || (!isMapLabelsMatch( - instance->mIncludeContainerLabels, instance->mExcludeContainerLabels, Container.Labels)) - || (!isMapLabelsMatch(instance->mIncludeEnvs, instance->mExcludeEnvs, Container.Envs))) { - mPassFilterRules = -1; - return false; - } - } else if (!Container.ContainerName.empty()) { - if ((!instance->mIncludeContainerNameRegex.empty() - && !BoostRegexMatch(Container.ContainerName.c_str(), instance->mIncludeContainerNameRegex, exception)) - || (!instance->mExcludeContainerNameRegex.empty() - && BoostRegexMatch( - Container.ContainerName.c_str(), instance->mExcludeContainerNameRegex, exception)) - || (!isMapLabelsMatch( - instance->mIncludeContainerLabels, instance->mExcludeContainerLabels, Container.Labels)) - || (!isMapLabelsMatch(instance->mIncludeEnvs, instance->mExcludeEnvs, Container.Envs))) { - mPassFilterRules = -1; - return false; - } - } else { - if ((!instance->mIncludeCmdRegex.empty() - && !BoostRegexMatch(ProcessCMD.c_str(), instance->mIncludeCmdRegex, exception)) - || (!instance->mExcludeCmdRegex.empty() - && BoostRegexMatch(ProcessCMD.c_str(), instance->mExcludeCmdRegex, exception))) { - mPassFilterRules = -1; - return false; - } - } - mPassFilterRules = 1; - return true; - } - - void ResetFilter() { this->mPassFilterRules = 0; } - -private: - std::vector > mMetaInfo; - int8_t mPassFilterRules = 0; - friend class CGroupPathResolverUnittest; -}; - -typedef std::shared_ptr ProcessMetaPtr; - - -} // namespace logtail diff --git a/core/observer/metas/ServiceMetaCache.cpp b/core/observer/metas/ServiceMetaCache.cpp deleted file mode 100644 index 6dd69d3a5e..0000000000 --- a/core/observer/metas/ServiceMetaCache.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "ServiceMetaCache.h" -#include "Logger.h" - -namespace logtail { - -static const ServiceMeta& GetEmptyHost() { - static const ServiceMeta sEmptyHost; - return sEmptyHost; -} - -const ServiceMeta& ServiceMetaCache::Get(const std::string& remoteIP, ProtocolType protocolType) { - if (mIndexMap.find(remoteIP) == mIndexMap.end()) { - return GetEmptyHost(); - } - mData.splice(mData.begin(), mData, mIndexMap[remoteIP]); - mData.begin()->second.Category = DetectRemoteServiceCategory(protocolType); - mData.front().second.time = time(nullptr); - return mData.front().second; -} - -const ServiceMeta& ServiceMetaCache::Get(const std::string& remoteIP) { - if (mIndexMap.find(remoteIP) == mIndexMap.end()) { - return GetEmptyHost(); - } - mData.splice(mData.begin(), mData, mIndexMap[remoteIP]); - mData.front().second.time = time(nullptr); - return mData.front().second; -} - - -void ServiceMetaCache::Put(const std::string& remoteIP, const std::string& host, ProtocolType protocolType) { - if (!Get(remoteIP, ProtocolType_NumProto).Host.empty()) { - mData.begin()->second.Host = host; - mData.begin()->second.Category = DetectRemoteServiceCategory(protocolType); - mData.begin()->second.time = time(nullptr); - } else { - if (mData.size() == cap) { - std::string delKey = std::move(mData.back().first); - mData.pop_back(); - mIndexMap.erase(delKey); - } - mData.emplace_front(remoteIP, ServiceMeta{host, DetectRemoteServiceCategory(protocolType), time(nullptr)}); - mIndexMap[remoteIP] = mData.begin(); - } -} - - -void ServiceMetaManager::AddHostName(uint32_t pid, const std::string& hostname, const std::string& ip) { - auto meta = mHostnameMetas.find(pid); - if (meta == mHostnameMetas.end()) { - meta = mHostnameMetas.insert(std::make_pair(pid, new ServiceMetaCache(200))).first; - } - meta->second->Put(ip, hostname, ProtocolType_HTTP); - LOG_TRACE(sLogger, ("ServiceMeta ADD hostname, ip", ip)("data", meta->second->mData.begin()->second.ToString())); -} - -const ServiceMeta& -ServiceMetaManager::GetOrPutServiceMeta(uint32_t pid, const std::string& ip, ProtocolType protocolType) { - auto& meta = doGetOrPutServiceMeta(pid, ip, protocolType); - LOG_TRACE(sLogger, ("ServiceMeta GET or PUT, pid", pid)("ip", ip)("data", meta.ToString())); - return meta; -} - -const ServiceMeta& ServiceMetaManager::GetServiceMeta(uint32_t pid, const std::string& ip) { - auto& meta = doGetServiceMeta(pid, ip); - LOG_TRACE(sLogger, ("ServiceMeta GET, pid", pid)("ip", ip)("data", meta.ToString())); - return meta; -} - -void ServiceMetaManager::OnProcessDestroy(uint32_t pid) { - auto meta = mHostnameMetas.find(pid); - if (meta == mHostnameMetas.end()) { - return; - } - delete meta->second; - mHostnameMetas.erase(meta); - LOG_TRACE(sLogger, ("destroy host name, pid", pid)); -} - -void ServiceMetaManager::GarbageTimeoutHostname(long currentTime) { - long timeoutTime = currentTime - INT64_FLAG(sls_observer_network_hostname_timeout); - for (auto iter = mHostnameMetas.begin(); iter != mHostnameMetas.end();) { - while (!iter->second->mData.empty()) { - if (iter->second->mData.back().second.time > timeoutTime) { - break; - } - LOG_TRACE(sLogger, ("destroy host name remove history meta", iter->second->mData.back().second.ToString())); - std::string delKey = std::move(iter->second->mData.back().first); - iter->second->mIndexMap.erase(delKey); - iter->second->mData.pop_back(); - } - if (iter->second->mData.empty()) { - delete iter->second; - iter = mHostnameMetas.erase(iter); - } else { - ++iter; - } - } -} - -inline const ServiceMeta& -ServiceMetaManager::doGetOrPutServiceMeta(uint32_t pid, const std::string& ip, ProtocolType protocolType) { - auto meta = mHostnameMetas.find(pid); - if (meta == mHostnameMetas.end()) { - if (IsRemoteInvokeProtocolType(protocolType)) { - return GetEmptyHost(); - } - meta = mHostnameMetas.insert(std::make_pair(pid, new ServiceMetaCache(200))).first; - meta->second->Put(ip, "", protocolType); - return meta->second->mData.begin()->second; - } - auto& data = meta->second->Get(ip, protocolType); - if (!data.Empty()) { - return data; - } - meta->second->Put(ip, "", protocolType); - return meta->second->mData.begin()->second; -} - -inline const ServiceMeta& ServiceMetaManager::doGetServiceMeta(uint32_t pid, const std::string& ip) { - auto meta = mHostnameMetas.find(pid); - if (meta == mHostnameMetas.end()) { - return GetEmptyHost(); - } - return meta->second->Get(ip); -} -} // namespace logtail \ No newline at end of file diff --git a/core/observer/metas/ServiceMetaCache.h b/core/observer/metas/ServiceMetaCache.h deleted file mode 100644 index 00e5592062..0000000000 --- a/core/observer/metas/ServiceMetaCache.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - - -#include -#include -#include -#include -#include "interface/type.h" -#include "network/NetworkConfig.h" - -namespace logtail { - - -struct ServiceMeta { - ServiceMeta(const std::string& host, ServiceCategory category, long time) - : Host(host), Category(category), time(time) {} - ServiceMeta() = default; - - // Only configured when found unknown dns addr. - std::string Host; - ServiceCategory Category{ServiceCategory::Unknown}; - long time{0}; - - friend std::ostream& operator<<(std::ostream& os, const ServiceMeta& hostname) { - os << "Host: " << hostname.Host << " Category: " << ServiceCategoryToString(hostname.Category) - << " time: " << hostname.time; - return os; - } - - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - - bool Empty() const { return time == 0; } -}; - - -template -class LRUCache { -public: - LRUCache() = delete; - explicit LRUCache(uint32_t capacity) : cap(capacity) {} - - const V* Get(const K& key, std::function wrapper) { - if (mIndexMap.find(key) == mIndexMap.end()) { - return nullptr; - } - mData.splice(mData.begin(), mData, mIndexMap[key]); - if (wrapper != nullptr) { - wrapper(&(mData.begin()->second)); - } - return &mData.front().second; - } - - void Put(const K& k,const V&& v, std::function wrapper) { - if (Get(k, wrapper) != nullptr) { - mData.begin()->second = v; - wrapper(&mData.begin()->second); - } else { - if (mData.size() == cap) { - K delKey = std::move(mData.back().first); - mData.pop_back(); - mIndexMap.erase(delKey); - } - mData.emplace_front(k, v); - mIndexMap[k] = mData.begin(); - } - } - -private: - uint32_t cap = 0; - std::list> mData; - std::unordered_map>::iterator> mIndexMap; -}; - -// todo: Use LRUCache to refine ServiceMetaCache -class ServiceMetaCache { -public: - ServiceMetaCache() = delete; - -private: - uint32_t cap = 0; - std::list> mData; - std::unordered_map>::iterator> mIndexMap; - -private: - explicit ServiceMetaCache(uint32_t capacity) { cap = capacity; } - - const ServiceMeta& Get(const std::string& remoteIP, ProtocolType protocolType); - - const ServiceMeta& Get(const std::string& remoteIP); - - void Put(const std::string& remoteIP, const std::string& host, ProtocolType protocolType); - - friend class ServiceMetaManager; - friend class HostnameMetaUnittest; -}; - -class ServiceMetaManager { -public: - static ServiceMetaManager* GetInstance() { - static auto sManager = new ServiceMetaManager(); - return sManager; - } - - // AddHostName method only called by dns protocol parser to store recently used dns hostname and ip mapping. - void AddHostName(uint32_t pid, const std::string& hostname, const std::string& ip); - - // GetHostName called by other protocol parser to get remote hostname and wrapper hostname category. - const ServiceMeta& GetOrPutServiceMeta(uint32_t pid, const std::string& ip, ProtocolType protocolType); - - // GetHostName called by statistics to get remote hostname and hostname category. - const ServiceMeta& GetServiceMeta(uint32_t pid, const std::string& ip); - - // OnProcessDestroy delete cache metas. - void OnProcessDestroy(uint32_t pid); - - // GarbageTimeoutHostname delete the unused metas. - void GarbageTimeoutHostname(long currentTime); - -private: - ServiceMetaManager() = default; - inline const ServiceMeta& doGetOrPutServiceMeta(uint32_t pid, const std::string& ip, ProtocolType protocolType); - inline const ServiceMeta& doGetServiceMeta(uint32_t pid, const std::string& ip); - - -private: - std::unordered_map mHostnameMetas; - friend class HostnameMetaUnittest; -}; - - -} // namespace logtail diff --git a/core/observer/network/ConnectionObserver.h b/core/observer/network/ConnectionObserver.h deleted file mode 100644 index 26178f6496..0000000000 --- a/core/observer/network/ConnectionObserver.h +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "interface/network.h" -#include -#include -#include -#include -#include -#include -#include "NetworkConfig.h" -#include "network/protocols/pgsql/parser.h" -#include "interface/statistics.h" - -#define OBSERVER_PROTOCOL_GARBAGE(protocolType) \ - { \ - protocolType##ProtocolParser* parser = (protocolType##ProtocolParser*)mProtocolParser; \ - auto success = parser->GarbageCollection(size_limit_bytes, nowTimeNs); \ - if (!success && BOOL_FLAG(sls_observer_network_protocol_stat)) { \ - ++sStatistic->m##protocolType##ConnectionNum; \ - sStatistic->m##protocolType##ConnectionCachedSize += parser->GetCacheSize(); \ - } \ - return success; \ - } -#define OBSERVER_PROTOCOL_ON_DATA(protocolType) \ - { \ - if (mProtocolParser == NULL) { \ - mProtocolParser \ - = protocolType##ProtocolParser::Create(mAllAggregators.Get##protocolType##Aggregator(), header); \ - } \ - auto parser = (protocolType##ProtocolParser*)mProtocolParser; \ - ParseResult rst \ - = parser->OnPacket(data->PktType, data->MsgType, header, data->Buffer, data->BufferLen, data->RealLen); \ - if (rst == ParseResult_Fail) { \ - ++sStatistic->m##protocolType##ParseFailCount; \ - } \ - ++sStatistic->m##protocolType##Count; \ - if (rst == ParseResult_Drop) { \ - ++sStatistic->m##protocolType##DropCount; \ - } \ - } -namespace logtail { - -class ConnectionObserver { -public: - ConnectionObserver(PacketEventHeader* header, ProtocolEventAggregators& allAggregators) - : mCreateReason(*header), mAllAggregators(allAggregators) { - mLastDataTimeNs = header->TimeNano; - } - - ~ConnectionObserver() { ClearParser(); } - - - void ClearParser() { - if (mProtocolParser == NULL) { - return; - } - switch (mLastProtocolType) { - case ProtocolType_None: - break; - case ProtocolType_HTTP: - HTTPProtocolParser::Delete((HTTPProtocolParser*)mProtocolParser); - break; - case ProtocolType_DNS: - DNSProtocolParser::Delete((DNSProtocolParser*)mProtocolParser); - break; - case ProtocolType_MySQL: - MySQLProtocolParser::Delete((MySQLProtocolParser*)mProtocolParser); - break; - case ProtocolType_Redis: - RedisProtocolParser::Delete((RedisProtocolParser*)mProtocolParser); - break; - case ProtocolType_PgSQL: - PgSQLProtocolParser::Delete((PgSQLProtocolParser*)mProtocolParser); - break; - default: - break; - } - mProtocolParser = NULL; - } - - void OnData(PacketEventHeader* header, PacketEventData* data) { - static auto sStatistic = ProtocolStatistic::GetInstance(); - mLastDataTimeNs = header->TimeNano; - if (mLastProtocolType != ProtocolType_None && mLastProtocolType != data->PtlType) { - ClearParser(); - ++mProtocolSwitchCount; - if (mProtocolSwitchCount % 10 == 0) { - // log error - } - } - mLastProtocolType = data->PtlType; - - switch (data->PtlType) { - case ProtocolType_None: - break; - case ProtocolType_HTTP: - OBSERVER_PROTOCOL_ON_DATA(HTTP); - break; - case ProtocolType_DNS: - OBSERVER_PROTOCOL_ON_DATA(DNS); - break; - case ProtocolType_MySQL: - OBSERVER_PROTOCOL_ON_DATA(MySQL); - break; - case ProtocolType_Redis: - OBSERVER_PROTOCOL_ON_DATA(Redis); - break; - case ProtocolType_PgSQL: - OBSERVER_PROTOCOL_ON_DATA(PgSQL); - break; - default: - break; - } - } - void MarkDeleted() { mMarkDeleted = true; } - - - bool GarbageCollection(size_t size_limit_bytes, uint64_t nowTimeNs) { - auto sStatistic = ProtocolDebugStatistic::GetInstance(); - if (mMarkDeleted - && nowTimeNs - mLastDataTimeNs - > (uint64_t)INT64_FLAG(sls_observer_network_connection_closed_timeout) * 1000LL * 1000LL * 1000LL) { - return true; - } - if (nowTimeNs - mLastDataTimeNs - > (uint64_t)INT64_FLAG(sls_observer_network_connection_timeout) * 1000LL * 1000LL * 1000LL) { - return true; - } - if (mProtocolParser == NULL) { - return false; - } - switch (mLastProtocolType) { - case ProtocolType_None: - break; - case ProtocolType_HTTP: - OBSERVER_PROTOCOL_GARBAGE(HTTP); - break; - case ProtocolType_DNS: - OBSERVER_PROTOCOL_GARBAGE(DNS); - break; - case ProtocolType_MySQL: - OBSERVER_PROTOCOL_GARBAGE(MySQL); - break; - case ProtocolType_Redis: - OBSERVER_PROTOCOL_GARBAGE(Redis); - break; - case ProtocolType_PgSQL: - OBSERVER_PROTOCOL_GARBAGE(PgSQL); - break; - default: - break; - } - return false; - } - -protected: - PacketEventHeader mCreateReason; - ProtocolEventAggregators& mAllAggregators; - bool mMarkDeleted = false; - ProtocolType mLastProtocolType = ProtocolType_None; - int32_t mProtocolSwitchCount = 0; - void* mProtocolParser = NULL; - uint64_t mLastDataTimeNs = 0; - - friend class ProtocolDnsUnittest; - friend class ProtocolHttpUnittest; - friend class ProtocolMySqlUnittest; - friend class ProtocolRedisUnittest; - friend class ProtocolPgSqlUnittest; -}; - -} // namespace logtail diff --git a/core/observer/network/NetworkConfig.cpp b/core/observer/network/NetworkConfig.cpp deleted file mode 100644 index 6b454ed118..0000000000 --- a/core/observer/network/NetworkConfig.cpp +++ /dev/null @@ -1,391 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "NetworkConfig.h" -#include -#include "LogtailAlarm.h" -#include "logger/Logger.h" -#include -#include "common/JsonUtil.h" -#include "ExceptionBase.h" -#include "plugin/input/InputObserverNetwork.h" - -DEFINE_FLAG_INT64(sls_observer_network_gc_interval, "SLS Observer NetWork GC interval seconds", 30); -DEFINE_FLAG_INT64(sls_observer_network_probe_disable_process_interval, - "SLS Observer NetWork probe disable processes interval seconds", - 3); -DEFINE_FLAG_INT64(sls_observer_network_cleanall_disable_process_interval, - "SLS Observer NetWork clean all disable processes interval seconds", - 1800); -DEFINE_FLAG_INT64(sls_observer_network_process_timeout, "SLS Observer NetWork normal process timeout seconds", 600); -DEFINE_FLAG_INT64(sls_observer_network_hostname_timeout, "SLS Observer NetWork dns hostname timeout seconds", 3600); -DEFINE_FLAG_INT64(sls_observer_network_process_no_connection_timeout, - "SLS Observer NetWork no connection process timeout seconds", - 180); -DEFINE_FLAG_INT64(sls_observer_network_process_destroyed_timeout, - "SLS Observer NetWork destroyed process timeout seconds", - 180); -DEFINE_FLAG_INT64(sls_observer_network_connection_timeout, - "SLS Observer NetWork normal connection timeout seconds", - 300); -DEFINE_FLAG_INT64(sls_observer_network_connection_closed_timeout, - "SLS Observer NetWork closed connection timeout seconds", - 5); -DEFINE_FLAG_INT32(sls_observer_network_no_data_sleep_interval_ms, "SLS Observer NetWork no data sleep interval ms", 10); -DEFINE_FLAG_INT32(sls_observer_network_pcap_loop_count, "SLS Observer NetWork PCAP loop count", 100); -DEFINE_FLAG_BOOL(sls_observer_network_protocol_stat, "SLS Observer NetWork protocol stat output", false); - -#define OBSERVER_CONFIG_EXTRACT_REGEXP(jsonvalue, param) \ - do { \ - std::string str##param = GetStringValue(jsonvalue, #param, ""); \ - if (!str##param.empty()) { \ - this->m##param = boost::regex(str##param, boost::regex_constants::no_except); \ - if (this->m##param.status() != 0) { \ - throw ExceptionBase(std::string("regex illegal:") + #param); \ - } \ - } else { \ - this->m##param = boost::regex(); \ - } \ - } while (0) - -#define OBSERVER_CONFIG_EXTRACT_REGEXP_MAP(jsonvalue, param) \ - do { \ - this->m##param.erase(this->m##param.begin(), this->m##param.end()); \ - if ((jsonvalue).isMember(#param) && commonValue[#param].isObject()) { \ - Json::Value& labelsVal = (jsonvalue)[#param]; \ - for (auto item = labelsVal.begin(); item != labelsVal.end(); item++) { \ - std::string key = item.name(); \ - std::string val = labelsVal[item.name()].asString(); \ - if (!val.empty()) { \ - auto reg = boost::regex(val, boost::regex_constants::no_except); \ - if (reg.status() != 0) { \ - throw ExceptionBase(std::string("regex illegal:") + #param); \ - } \ - this->m##param.insert(std::make_pair(key, reg)); \ - } \ - } \ - } \ - } while (0) -#define OBSERVER_CONFIG_EXTRACT_INT(jsonvalue, param, defaultVal, prefix) \ - do { \ - this->m##prefix##param = GetIntValue(jsonvalue, #param, defaultVal); \ - } while (0) - -#define OBSERVER_CONFIG_EXTRACT_BOOL(jsonvalue, param, defaultVal, prefix) \ - do { \ - this->m##prefix##param = GetBoolValue(jsonvalue, #param, defaultVal); \ - } while (0) - -#define OBSERVER_CONFIG_EXTRACT_STRING(jsonvalue, param, defaultVal, prefix) \ - do { \ - this->m##prefix##param = GetStringValue(jsonvalue, #param, defaultVal); \ - } while (0) - - -namespace logtail { - - -NetworkConfig::NetworkConfig() { -} - - -void NetworkConfig::ReportAlarm() { - if (mAllNetworkConfigs.size() < (size_t)2) { - return; - } - std::stringstream ss; - ss << "Load multi observer config, only one config is enabled, others not work. Enabled config is, project : " - << mLastApplyedConfig->GetContext().GetProjectName() - << ", store : " << mLastApplyedConfig->GetContext().GetLogstoreName() << ".\n"; - ss << "Disabled configs : "; - for (auto& config : mAllNetworkConfigs) { - if (config.second == mLastApplyedConfig) { - continue; - } - ss << "project : " << mLastApplyedConfig->GetContext().GetProjectName() - << ", store : " << mLastApplyedConfig->GetContext().GetLogstoreName() << ".\n"; - } - std::string alarmStr = ss.str(); - for (auto& config : mAllNetworkConfigs) { - LogtailAlarm::GetInstance()->SendAlarm(MULTI_OBSERVER_ALARM, - alarmStr, - config.second->GetContext().GetProjectName(), - config.second->GetContext().GetLogstoreName(), - config.second->GetContext().GetRegion()); - } - LOG_WARNING(sLogger, (alarmStr, "")); -} - - -void NetworkConfig::LoadConfig(const Pipeline* config) { - if (mLastApplyedConfig == NULL) { - mLastApplyedConfig = config; - } - if (mOldestConfigCreateTime > config->GetContext().GetCreateTime()) { - mOldestConfigCreateTime = config->GetContext().GetCreateTime(); - mLastApplyedConfig = config; - } - // mAllNetworkConfigs.push_back(config); -} - -void NetworkConfig::EndLoadConfig() { - if (mLastApplyedConfig == NULL) { - mLastApplyedConfigDetail.clear(); - return; - } - ReportAlarm(); - const InputObserverNetwork* plugin - = static_cast(mLastApplyedConfig->GetInputs()[0]->GetPlugin()); - if (mLastApplyedConfigDetail != plugin->mDetail) { - LOG_INFO(sLogger, ("reload observer config, last", mLastApplyedConfigDetail)("new", plugin->mDetail)); - mLastApplyedConfigDetail = plugin->mDetail; - std::string parseResult = SetFromJsonString(); - if (parseResult.empty()) { - LOG_INFO(sLogger, ("new loaded observer config", ToString())); - } else { - LOG_WARNING(sLogger, ("parse observer config, result", parseResult)); - } - mNeedReload = true; - } -} - -std::string NetworkConfig::CheckValid() { - if (!mEBPFEnabled && !mPCAPEnabled) { - return "both ebpf and pcap are not enabled"; - } - // todo, check params - return ""; -} - -std::string NetworkConfig::SetFromJsonString() { - Clear(); - mEnabled = true; - - Json::Value jsonRoot; - Json::CharReaderBuilder builder; - builder["collectComments"] = false; - std::unique_ptr jsonReader(builder.newCharReader()); - std::string jsonParseErrs; - if (!jsonReader->parse(mLastApplyedConfigDetail.data(), - mLastApplyedConfigDetail.data() + mLastApplyedConfigDetail.size(), - &jsonRoot, - &jsonParseErrs)) { - return std::string("invalid config format : ") + jsonParseErrs; - } - if (jsonRoot.isArray() && jsonRoot.size() == 1) { - jsonRoot = jsonRoot[0]; - } else if (!jsonRoot.isObject()) { - jsonRoot = {}; - } - if (jsonRoot.empty()) { - return std::string("invalid config format : ") + jsonParseErrs; - } - - try { - if (jsonRoot.isMember("EBPF") && jsonRoot["EBPF"].isObject()) { - Json::Value& ebpfValue = jsonRoot["EBPF"]; - OBSERVER_CONFIG_EXTRACT_BOOL(ebpfValue, Enabled, false, EBPF); - OBSERVER_CONFIG_EXTRACT_INT(ebpfValue, Pid, -1, EBPF); - } - if (jsonRoot.isMember("PCAP") && jsonRoot["PCAP"].isObject()) { - Json::Value& pcapValue = jsonRoot["PCAP"]; - OBSERVER_CONFIG_EXTRACT_BOOL(pcapValue, Enabled, false, PCAP); - OBSERVER_CONFIG_EXTRACT_BOOL(pcapValue, Promiscuous, true, PCAP); - OBSERVER_CONFIG_EXTRACT_INT(pcapValue, TimeoutMs, 0, PCAP); - OBSERVER_CONFIG_EXTRACT_STRING(pcapValue, Filter, "", PCAP); - OBSERVER_CONFIG_EXTRACT_STRING(pcapValue, Interface, "", PCAP); - } - - if (jsonRoot.isMember("Common") && jsonRoot["Common"].isObject()) { - Json::Value& commonValue = jsonRoot["Common"]; - OBSERVER_CONFIG_EXTRACT_INT(commonValue, FlushOutL4Interval, 60, ); - OBSERVER_CONFIG_EXTRACT_INT(commonValue, FlushOutL7Interval, 15, ); - OBSERVER_CONFIG_EXTRACT_INT(commonValue, FlushMetaInterval, 30, ); - OBSERVER_CONFIG_EXTRACT_INT(commonValue, FlushNetlinkInterval, 10, ); - OBSERVER_CONFIG_EXTRACT_INT(commonValue, Sampling, 100, ); - OBSERVER_CONFIG_EXTRACT_BOOL(commonValue, SaveToDisk, false, ); - OBSERVER_CONFIG_EXTRACT_BOOL(commonValue, DropUnixSocket, true, ); - OBSERVER_CONFIG_EXTRACT_BOOL(commonValue, DropLocalConnections, true, ); - OBSERVER_CONFIG_EXTRACT_BOOL(commonValue, DropUnknownSocket, true, ); - OBSERVER_CONFIG_EXTRACT_REGEXP_MAP(commonValue, IncludeContainerLabels); - OBSERVER_CONFIG_EXTRACT_REGEXP_MAP(commonValue, ExcludeContainerLabels); - OBSERVER_CONFIG_EXTRACT_REGEXP_MAP(commonValue, IncludeK8sLabels); - OBSERVER_CONFIG_EXTRACT_REGEXP_MAP(commonValue, ExcludeK8sLabels); - OBSERVER_CONFIG_EXTRACT_REGEXP_MAP(commonValue, IncludeEnvs); - OBSERVER_CONFIG_EXTRACT_REGEXP_MAP(commonValue, ExcludeEnvs); - OBSERVER_CONFIG_EXTRACT_REGEXP(commonValue, IncludeCmdRegex); - OBSERVER_CONFIG_EXTRACT_REGEXP(commonValue, ExcludeCmdRegex); - OBSERVER_CONFIG_EXTRACT_REGEXP(commonValue, IncludeContainerNameRegex); - OBSERVER_CONFIG_EXTRACT_REGEXP(commonValue, ExcludeContainerNameRegex); - OBSERVER_CONFIG_EXTRACT_REGEXP(commonValue, IncludePodNameRegex); - OBSERVER_CONFIG_EXTRACT_REGEXP(commonValue, ExcludePodNameRegex); - OBSERVER_CONFIG_EXTRACT_REGEXP(commonValue, IncludeNamespaceNameRegex); - OBSERVER_CONFIG_EXTRACT_REGEXP(commonValue, ExcludeNamespaceNameRegex); - // partial open protocol processing. - mProtocolProcessFlag = GetBoolValue(commonValue, "ProtocolProcess", true) ? -1 : 0; - if (mProtocolProcessFlag != 0) { - std::vector includeProtocolVec; - if (commonValue.isMember("IncludeProtocols") && commonValue["IncludeProtocols"].isArray()) { - Json::Value& includeProtocols = commonValue["IncludeProtocols"]; - if (commonValue["IncludeProtocols"].size() >= 0) { - mProtocolProcessFlag = 0; // reset flag to open selected protocol. - for (const auto& includeProtocol : includeProtocols) { - for (int i = 1; i < ProtocolType_NumProto; ++i) { - if (strcasecmp(ProtocolTypeToString((ProtocolType)i).c_str(), - includeProtocol.asString().c_str()) - == 0) { - mProtocolProcessFlag |= (1 << (i - 1)); - break; - } - } - } - } - } - } - // config protocol advanced config. - if (commonValue.isMember("ProtocolAggCfg") && commonValue["ProtocolAggCfg"].isObject()) { - Json::Value& protocolAggCfg = commonValue["ProtocolAggCfg"]; - for (const auto& item : protocolAggCfg.getMemberNames()) { - for (int i = 1; i < ProtocolType_NumProto; ++i) { - if (strcasecmp(ProtocolTypeToString((ProtocolType)i).c_str(), item.c_str()) == 0 - && protocolAggCfg[item].isObject() && (mProtocolProcessFlag & 1 << (i - 1))) { - Json::Value& itemCfg = protocolAggCfg[item]; - this->mProtocolAggCfg[i] = std::make_pair(GetIntValue(itemCfg, "ClientSize"), - GetIntValue(itemCfg, "ServerSize")); - } - } - } - } - std::string cluster = GetStringValue(commonValue, "Cluster", ""); - if (!cluster.empty()) { - mTags.emplace_back("__tag__:cluster", cluster); - } - // append global tags. - if (commonValue.isMember("Tags") && commonValue["Tags"].isObject()) { - Json::Value& tags = commonValue["Tags"]; - for (const auto& item : tags.getMemberNames()) { - if (item.empty()) { - continue; - } - mTags.emplace_back("__tag__:" + item, GetStringValue(tags, item)); - } - } - } - } catch (...) { - return "invalid config pattern"; - } - return CheckValid(); -} - -bool NetworkConfig::isOpenPartialSelectDump() { - return -1 != this->localPickPID || -1 != this->localPickConnectionHashId || -1 != this->localPickSrcPort - || -1 != this->localPickDstPort; -} - - -std::string NetworkConfig::ToString() const { - std::string rst; - rst.append("EBPF : ").append(mEBPFEnabled ? "true" : "false").append("\t"); - if (mEBPFEnabled) { - rst.append("EBPFFilter pid : ").append(std::to_string(mEBPFPid)).append("\t"); - } - rst.append("PCAP : ").append(mPCAPEnabled ? "true" : "false").append("\t"); - if (mPCAPEnabled) { - rst.append("PCAPFilter : ").append(mPCAPFilter).append("\t"); - rst.append("PCAPInterface : ").append(mPCAPInterface).append("\t"); - rst.append("PCAPTimeoutMs : ").append(std::to_string(mPCAPTimeoutMs)).append("\t"); - rst.append("PCAPPromiscuous : ").append(std::to_string(mPCAPPromiscuous)).append("\t"); - } - rst.append("Sampling : ").append(std::to_string(mSampling)).append("\t"); - rst.append("FlushOutL4Interval : ").append(std::to_string(mFlushOutL4Interval)).append("\t"); - rst.append("FlushOutL7Interval : ").append(std::to_string(mFlushOutL7Interval)).append("\t"); - rst.append("FlushMetaInterval : ").append(std::to_string(mFlushMetaInterval)).append("\t"); - rst.append("FlushNetlinkInterval : ").append(std::to_string(mFlushNetlinkInterval)).append("\t"); - rst.append("IncludeCmdRegex : ").append(mIncludeCmdRegex.str()).append("\t"); - rst.append("ExcludeCmdRegex : ").append(mExcludeCmdRegex.str()).append("\t"); - rst.append("IncludeContainerNameRegex : ").append(mIncludeContainerNameRegex.str()).append("\t"); - rst.append("ExcludeContainerNameRegex : ").append(mExcludeContainerNameRegex.str()).append("\t"); - rst.append("IncludeContainerLabels : ").append(label2String(mIncludeContainerLabels)).append("\t"); - rst.append("ExcludeContainerLabels : ").append(label2String(mExcludeContainerLabels)).append("\t"); - rst.append("IncludePodNameRegex : ").append(mIncludePodNameRegex.str()).append("\t"); - rst.append("ExcludePodNameRegex : ").append(mExcludePodNameRegex.str()).append("\t"); - rst.append("IncludeNamespaceNameRegex : ").append(mIncludeNamespaceNameRegex.str()).append("\t"); - rst.append("ExcludeNamespaceNameRegex : ").append(mExcludeNamespaceNameRegex.str()).append("\t"); - rst.append("IncludeK8sLabels : ").append(label2String(mIncludeK8sLabels)).append("\t"); - rst.append("ExcludeK8sLabels : ").append(label2String(mExcludeK8sLabels)).append("\t"); - rst.append("IncludeEnvs : ").append(label2String(mIncludeEnvs)).append("\t"); - rst.append("ExcludeEnvs : ").append(label2String(mExcludeEnvs)).append("\t"); - rst.append("DropUnixSocket : ").append(mDropUnixSocket ? "true" : "false").append("\t"); - rst.append("DropLocalConnections : ").append(mDropLocalConnections ? "true" : "false").append("\t"); - rst.append("DropUnknownSocket : ").append(mDropUnknownSocket ? "true" : "false").append("\t"); - rst.append("ProtocolProcess : {"); - for (int i = 1; i < ProtocolType_NumProto; ++i) { - if (this->IsLegalProtocol(static_cast(i))) { - auto pair = GetProtocolAggSize(static_cast(i)); - rst.append(ProtocolTypeToString(static_cast(i))) - .append(" ClientSize: ") - .append(std::to_string(pair.first)) - .append(" ServerSize: ") - .append(std::to_string(pair.second)) - .append("\t"); - } - } - rst.append("} Tags : "); - for (const auto& item : mTags) { - rst.append(item.first).append("@").append(item.second).append(","); - } - rst.append("\t"); - return rst; -} -void NetworkConfig::Clear() { - mEnabled = false; - mEBPFEnabled = false; - mSampling = 100; - mEBPFPid = -1; - mPCAPEnabled = false; - mPCAPFilter.clear(); - mPCAPInterface.clear(); - mPCAPPromiscuous = true; - mPCAPTimeoutMs = 0; - mFlushOutL4Interval = 60; - mFlushOutL7Interval = 15; - mFlushMetaInterval = 30; - mFlushNetlinkInterval = 10; - mSaveToDisk = false; - mDropUnixSocket = true; - boost::regex emptyRegex; - mIncludeCmdRegex = emptyRegex; - mExcludeCmdRegex = emptyRegex; - mIncludeContainerNameRegex = emptyRegex; - mExcludeContainerNameRegex = emptyRegex; - mIncludePodNameRegex = emptyRegex; - mExcludePodNameRegex = emptyRegex; - mIncludeNamespaceNameRegex = emptyRegex; - mExcludeNamespaceNameRegex = emptyRegex; - mIncludeContainerLabels.clear(); - mExcludeContainerLabels.clear(); - mIncludeK8sLabels.clear(); - mExcludeK8sLabels.clear(); - mIncludeEnvs.clear(); - mExcludeEnvs.clear(); - mTags.clear(); - mProtocolAggCfg.clear(); - mDropUnixSocket = true; - mDropLocalConnections = true; - mDropUnknownSocket = true; - mProtocolProcessFlag = -1; -} - - -} // namespace logtail diff --git a/core/observer/network/NetworkConfig.h b/core/observer/network/NetworkConfig.h deleted file mode 100644 index d33e89c5eb..0000000000 --- a/core/observer/network/NetworkConfig.h +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include - -#include "boost/regex.hpp" - -#include "common/Flags.h" -#include "interface/type.h" -#include "bits/stl_numeric.h" -#include "pipeline/Pipeline.h" - -DECLARE_FLAG_INT64(sls_observer_network_gc_interval); -DECLARE_FLAG_INT64(sls_observer_network_probe_disable_process_interval); -DECLARE_FLAG_INT64(sls_observer_network_cleanall_disable_process_interval); -DECLARE_FLAG_INT64(sls_observer_network_process_timeout); -DECLARE_FLAG_INT64(sls_observer_network_hostname_timeout); -DECLARE_FLAG_INT64(sls_observer_network_process_no_connection_timeout); -DECLARE_FLAG_INT64(sls_observer_network_process_destroyed_timeout); -DECLARE_FLAG_INT64(sls_observer_network_connection_timeout); -DECLARE_FLAG_INT64(sls_observer_network_connection_closed_timeout); -DECLARE_FLAG_INT32(sls_observer_network_no_data_sleep_interval_ms); -DECLARE_FLAG_INT32(sls_observer_network_pcap_loop_count); -DECLARE_FLAG_BOOL(sls_observer_network_protocol_stat); - - -namespace logtail { -struct NetworkConfig { - NetworkConfig(); - - static NetworkConfig* GetInstance() { - static auto sConfig = new NetworkConfig; - return sConfig; - } - - void ReportAlarm(); - - void BeginLoadConfig() { - mOldestConfigCreateTime = 0; - mEnabled = false; - mLastApplyedConfig = nullptr; - // mAllNetworkConfigs.clear(); - } - - void LoadConfig(const Pipeline* config); - - void EndLoadConfig(); - - bool Enabled() const { return mEnabled; } - - bool NeedReload() const { return mNeedReload; } - - void Reload() { mNeedReload = false; } - - std::string SetFromJsonString(); - - /** - * @brief - * - * @return std::string empty str is OK, otherwise return error msg - */ - std::string CheckValid(); - - bool isOpenPartialSelectDump(); - - void Clear(); - - std::string ToString() const; - - - static std::pair GetProtocolAggSize(ProtocolType protocolType) { - static auto sInstance = GetInstance(); - auto iter = sInstance->mProtocolAggCfg.find(protocolType); - if (iter == sInstance->mProtocolAggCfg.end()) { - return std::make_pair(500, 5000); - } - return iter->second; - } - - static std::string label2String(const std::unordered_map& map) { - return std::accumulate(map.begin(), - map.end(), - std::string(), - [](const std::string& s, const std::pair& p) { - return s + p.first + "=" + p.second.str() + ","; - }); - } - - bool IsLegalProtocol(ProtocolType type) const { - if (type == ProtocolType_None || type == ProtocolType_NumProto) { - return false; - } - return this->mProtocolProcessFlag & (1 << (type - 1)); - } - - - uint32_t mOldestConfigCreateTime = 0; // used to check which config is best - volatile bool mEnabled = false; - std::string mLastApplyedConfigDetail; - const Pipeline* mLastApplyedConfig = nullptr; - std::unordered_map mAllNetworkConfigs; - bool mNeedReload = false; - - // enable ebpf - bool mEBPFEnabled = false; - int mEBPFPid = -1; - // for PCAP(PCAP would be removed in the feature.) - bool mPCAPEnabled = false; - std::string mPCAPFilter; - std::string mPCAPInterface; - bool mPCAPPromiscuous = true; - int mPCAPTimeoutMs = 0; - uint32_t mPCAPCacheConnSize = 2000; - // collect config - int mSampling = 100; - uint64_t mFlushOutL4Interval = 60; - uint64_t mFlushOutL7Interval = 15; - uint64_t mFlushMetaInterval = 30; - uint64_t mFlushNetlinkInterval = 10; - boost::regex mIncludeCmdRegex; - boost::regex mExcludeCmdRegex; - boost::regex mIncludeContainerNameRegex; - boost::regex mExcludeContainerNameRegex; - std::unordered_map mIncludeContainerLabels; - std::unordered_map mExcludeContainerLabels; - boost::regex mIncludePodNameRegex; - boost::regex mExcludePodNameRegex; - boost::regex mIncludeNamespaceNameRegex; - boost::regex mExcludeNamespaceNameRegex; - std::unordered_map mIncludeK8sLabels; - std::unordered_map mExcludeK8sLabels; - std::unordered_map mIncludeEnvs; - std::unordered_map mExcludeEnvs; - bool mDropUnixSocket = true; - bool mDropLocalConnections = true; - bool mDropUnknownSocket = true; - uint32_t mProtocolProcessFlag = -1; - std::vector> mTags; - std::unordered_map> mProtocolAggCfg; - - // for local test - bool mSaveToDisk = false; - bool mLocalFileEnabled = false; - int64_t localPickConnectionHashId = -1; - int64_t localPickPID = -1; - int64_t localPickDstPort = -1; - int64_t localPickSrcPort = -1; -}; - -} // namespace logtail diff --git a/core/observer/network/NetworkObserver.cpp b/core/observer/network/NetworkObserver.cpp deleted file mode 100644 index 003a4471c4..0000000000 --- a/core/observer/network/NetworkObserver.cpp +++ /dev/null @@ -1,576 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "NetworkObserver.h" - -#include "Constants.h" -#include "FileSystemUtil.h" -#include "LogFileProfiler.h" -#include "MachineInfoUtil.h" -#include "Monitor.h" -#include "ProcessObserver.h" -#include "common/LogtailCommonFlags.h" -#include "go_pipeline/LogtailPlugin.h" -#include "logger/Logger.h" -#include "metas/ConnectionMetaManager.h" -#include "metas/ContainerProcessGroup.h" -#include "network/protocols/ProtocolEventAggregators.h" -#include "sources/ebpf/EBPFWrapper.h" -#include "sources/pcap/PCAPWrapper.h" -#ifdef __ENTERPRISE__ -#include "config/provider/EnterpriseConfigProvider.h" -#endif -#include "common/HashUtil.h" -#include "plugin/flusher/sls/FlusherSLS.h" - -DEFINE_FLAG_INT64(sls_observer_network_ebpf_connection_gc_interval, - "SLS Observer NetWork connection gc interval seconds", - 300); -DEFINE_FLAG_INT64(sls_observer_network_max_save_size, - "SLS Observer NetWork max save file size", - 1024LL * 1024LL * 1024LL); -DEFINE_FLAG_STRING(sls_observer_network_save_filename, "SLS Observer NetWork save disk's file name", "ebpf.dump"); - -DECLARE_FLAG_INT32(merge_log_count_limit); - -namespace logtail { - -NetworkObserver::~NetworkObserver() { - for (auto& mAllProcess : mAllProcesses) { - delete mAllProcess.second; - } -} -void NetworkObserver::HoldOn(bool exitFlag) { - if (mEBPFWrapper != nullptr) { - mEBPFWrapper->HoldOn(exitFlag); - } - mEventLoopThreadRWL.lock(); - LOG_INFO(sLogger, ("hold on", "observer")); -} - -void NetworkObserver::Resume() { - if (mEBPFWrapper != nullptr) { - mEBPFWrapper->Resume(); - } - Reload(); - mEventLoopThreadRWL.unlock(); - LOG_INFO(sLogger, ("resume on", "observer")); -} - -uint32_t NetworkObserver::EBPFConnectionGC(uint64_t nowTimeNs) { - std::vector connIds, toDeleteConnIds; - mEBPFWrapper->GetAllConnections(connIds); - uint32_t originConnSize = connIds.size(); - if (originConnSize < (size_t)64) { - return originConnSize; - } - std::unordered_set pids; - GetAllPids(pids); - for (auto& connId : connIds) { - auto findIter = mAllProcesses.find(connId.tgid); - if (findIter != mAllProcesses.end()) { - if (findIter->second->HasConnection(EBPFWrapper::ConvertConnIdToSockHash(&connId))) { - continue; - } - } - // check pid exists - if (pids.find(connId.tgid) == pids.end()) { - // @debug - LOG_DEBUG(sLogger, ("delete conn because pid not exist, pid", connId.tgid)("fd", connId.fd)); - toDeleteConnIds.push_back(connId); - continue; - } - // check fd exists - std::string fdPath = std::string("/proc/") - .append(std::to_string(connId.tgid)) - .append("/fd/") - .append(std::to_string(connId.fd)); - if (!CheckExistance(fdPath)) { - LOG_DEBUG(sLogger, ("delete conn because fd not exist, pid", connId.tgid)("fd", connId.fd)); - toDeleteConnIds.push_back(connId); - } - } - uint32_t todoDeleteConnCount = toDeleteConnIds.size(); - ++mNetworkStatistic->mEbpfGCCount; - mNetworkStatistic->mEbpfGCReleaseFDCount += todoDeleteConnCount; - LOG_INFO(sLogger, - ("ebpf connection gc, count", connIds.size())("to delete", todoDeleteConnCount)("pids", pids.size())); - mEBPFWrapper->DeleteInvalidConnections(toDeleteConnIds); - return originConnSize - todoDeleteConnCount; -} - -void NetworkObserver::GarbageCollection(uint64_t nowTimeNs) { - size_t maxSizeLimit = 1024 * 1024; - ++mNetworkStatistic->mGCCount; - ProtocolDebugStatistic::Clear(); - for (auto iter = mAllProcesses.begin(); iter != mAllProcesses.end();) { - ProcessObserver* observer = iter->second; - if (observer->GarbageCollection(maxSizeLimit, nowTimeNs)) { - LOG_DEBUG(sLogger, - ("delete processor observer when gc, meta", observer->GetProcessMeta()->ToString())("pid", - iter->first)); - static ContainerProcessGroupManager* containerProcessGroupManager - = ContainerProcessGroupManager::GetInstance(); - // @note we must us iter->first as pid (not processMeta->Pid), because processMeta may belong to other pid - // in the same container - containerProcessGroupManager->OnProcessDestroy(observer->GetProcessMeta().get(), iter->first); - mServiceMetaManager->OnProcessDestroy(iter->first); - delete observer; - iter = mAllProcesses.erase(iter); - ++mNetworkStatistic->mGCReleaseProcessCount; - } else { - ++iter; - } - mServiceMetaManager->GarbageTimeoutHostname(nowTimeNs / 1000000); - } -} - -void NetworkObserver::FlushOutMetrics(std::vector& allData) { - static ContainerProcessGroupManager* containerProcessGroupManager = ContainerProcessGroupManager::GetInstance(); - containerProcessGroupManager->FlushOutMetrics(allData, mConfig->mTags, mConfig->mFlushOutL7Interval); -} - -void NetworkObserver::FlushStatistics(logtail::NetStaticticsMap& statisticsMap, std::vector& allData) { - static ContainerProcessGroupManager* cpgManager = ContainerProcessGroupManager::GetInstance(); - MergedNetStatisticsHashMap mergedMap; - for (auto& item : statisticsMap.mHashMap) { - auto iter = mergedMap.find(item.first); - if (iter == mergedMap.end()) { - mergedMap.insert(std::make_pair(item.first, item.second)); - } else { - iter->second.Merge(item.second); - } - } - size_t lastSize = allData.size(); - allData.resize(mergedMap.size() + lastSize); - - ::google::protobuf::RepeatedPtrField gTags; - gTags.Reserve(mConfig->mTags.size()); - for (const auto& tag : mConfig->mTags) { - sls_logs::Log_Content* content = gTags.Add(); - content->set_key(tag.first); - content->set_value(tag.second); - } - - for (auto iter = mergedMap.begin(); iter != mergedMap.end() && lastSize < allData.size(); ++iter) { - sls_logs::Log* log = &allData[lastSize]; - log->mutable_contents()->Reserve(16); - log->mutable_contents()->CopyFrom(gTags); - Json::Value root; - Json::StreamWriterBuilder builder; - builder["indentation"] = ""; // If you want whitespace-less output - if (iter->first.PID == 0) { - root["_process_pid_"] = "0"; - } else { - const ProcessMetaPtr& ptr = cpgManager->GetProcessMeta(iter->first.PID); - if (!ptr->PassFilterRules()) { - if (this->mEBPFWrapper != nullptr) { - this->mEBPFWrapper->DisableProcess(iter->first.PID); - } - continue; - } - for (const auto& item : ptr->GetFormattedMeta()) { - root[item.first] = item.second; - } - } - AddAnyLogContent(log, observer::kLocalInfo, Json::writeString(builder, root)); - AddAnyLogContent(log, observer::kInterval, this->mConfig->mFlushOutL4Interval); - iter->first.ToPB(log); - iter->second.ToPB(log); - mNetworkStatistic->mInputBytes += iter->second.Base.RecvBytes; - mNetworkStatistic->mInputBytes += iter->second.Base.SendBytes; - mNetworkStatistic->mInputEvents += iter->second.Base.RecvPackets; - mNetworkStatistic->mInputEvents += iter->second.Base.SendPackets; - ++lastSize; - } -} - -void NetworkObserver::FlushOutStatistics(std::vector& allData) { - // pcap wrapper, do not need to add meta - if (mPCAPWrapper != nullptr) { - NetStaticticsMap& statisticsMap = mPCAPWrapper->GetStatistics(); - FlushStatistics(statisticsMap, allData); - statisticsMap.Clear(); - } - - if (mEBPFWrapper != nullptr) { - NetStaticticsMap& statisticsMap = mEBPFWrapper->GetStatistics(); - FlushStatistics(statisticsMap, allData); - statisticsMap.Clear(); - } -} - -ProcessObserver* NetworkObserver::GetProcess(PacketEventHeader* header, bool create) { - auto findIter = mAllProcesses.find(header->PID); - if (findIter != mAllProcesses.end()) { - return findIter->second; - } - if (!create) { - return nullptr; - } - auto newProc = new ProcessObserver(header->TimeNano); - static ContainerProcessGroupManager* containerProcessGroupManager = ContainerProcessGroupManager::GetInstance(); - ProcessMetaPtr processMeta = containerProcessGroupManager->GetProcessMeta(header->PID); - ContainerProcessGroupPtr groupPtr - = containerProcessGroupManager->GetContainerProcessGroupPtr(processMeta, header->PID); - newProc->SetProcessGroup(groupPtr); - mAllProcesses.insert(std::make_pair(header->PID, newProc)); - return newProc; -} - -int NetworkObserver::OnPacketEvent(void* event, size_t len) { - if (len < sizeof(PacketEventHeader)) { - LOG_ERROR(sLogger, ("invalid packet len", len)); - return -1; - } - auto header = static_cast(event); - static bool openPartialSelect = false; - if (mConfig->mSaveToDisk) { - if (mDumpFilePtr == nullptr) { - std::string fileName = STRING_FLAG(sls_observer_network_save_filename); - if (mConfig->mLocalFileEnabled) { - fileName += ".new"; - } - mDumpFilePtr = fopen64(fileName.c_str(), "wb+"); - openPartialSelect = mConfig->isOpenPartialSelectDump(); - } - if (mDumpFilePtr != nullptr && mDumpSize < INT64_FLAG(sls_observer_network_max_save_size)) { - if (!openPartialSelect - || (header->PID == mConfig->localPickPID || header->SockHash == mConfig->localPickConnectionHashId - || header->SrcPort == mConfig->localPickSrcPort || header->DstPort == mConfig->localPickDstPort)) { - std::string dumpContent; - PacketEventToBuffer(event, len, dumpContent); - mDumpSize += dumpContent.size(); - fwrite(dumpContent.data(), 1, dumpContent.size(), mDumpFilePtr); - } - } - } - switch (header->EventType) { - case PacketEventType_None: - break; - case PacketEventType_Data: { - auto data = reinterpret_cast((char*)event + sizeof(PacketEventHeader)); - - if (data->PtlType == ProtocolType_None) { - break; - } - ProcessObserver* proc = GetProcess(header); - if (!proc->GetProcessMeta()->PassFilterRules()) { - if (this->mEBPFWrapper != nullptr) { - this->mEBPFWrapper->DisableProcess(header->PID); - } - break; - } - proc->OnData(header, data); - } break; - case PacketEventType_Connected: - case PacketEventType_Accepted: - // create process - GetProcess(header); - break; - case PacketEventType_Closed: { - ProcessObserver* proc = GetProcess(header, false); - if (proc == nullptr) { - break; - } - proc->ConnectionMarkDeleted(header); - } break; - } - return 0; -} -void NetworkObserver::OnProcessDestroyed(uint32_t pid, const char* command, size_t len) { - auto findIter = mAllProcesses.find(pid); - if (findIter != mAllProcesses.end()) { - auto& meta = findIter->second->GetProcessMeta(); - if (meta && meta->ProcessCMD.size() == len && memcmp(meta->ProcessCMD.c_str(), command, len) == 0) { - findIter->second->MarkDeleted(); - LOG_DEBUG(sLogger, ("process destroyed, mark deleted, command", command)("pid", pid)); - } else { - LOG_INFO(sLogger, - ("find pid on process destroyed, but command not match, destroyed command", - command)("pid", pid)("real command", meta ? meta->ProcessCMD : "")); - } - } -} -void NetworkObserver::ReloadSource() { - LOG_INFO(sLogger, ("reload observer", "begin")); - bool success = true; - if (mConfig->mPCAPEnabled) { - LOG_INFO(sLogger, ("reload module", "pcap")); - mPCAPWrapper = PCAPWrapper::GetInstance(); - success = mPCAPWrapper->Stop() - && mPCAPWrapper->Init(std::bind(&NetworkObserver::OnPacketEventStringPiece, this, std::placeholders::_1)) - && mPCAPWrapper->Start(); - } else { - if (mPCAPWrapper != nullptr) { - LOG_INFO(sLogger, ("disable module", "pcap")); - success = mPCAPWrapper->Stop(); - } - mPCAPWrapper = nullptr; - } - if (mConfig->mEBPFEnabled) { - LOG_INFO(sLogger, ("reload module", "ebpf")); - mEBPFWrapper = EBPFWrapper::GetInstance(); - success = mEBPFWrapper->Stop() - && mEBPFWrapper->Init(std::bind(&NetworkObserver::OnPacketEventStringPiece, this, std::placeholders::_1)) - && mEBPFWrapper->Start(); - } else { - if (mEBPFWrapper != nullptr) { - LOG_INFO(sLogger, ("disable module", "ebpf")); - success = mEBPFWrapper->Stop(); - } - mEBPFWrapper = nullptr; - } - if (success) { - ContainerProcessGroupManager::GetInstance()->ResetFilterProcessMeta(); - } - LOG_INFO(sLogger, ("reload observer result", success ? "success" : "fail")); -} - -void NetworkObserver::Reload() { - if (!glibc::LoadGlibcFunc()) { - LOG_ERROR(sLogger, ("observer depends on glibc1.14", "load glibc func fail")); - return; - } - mConfig->BeginLoadConfig(); - for (auto config : mConfig->mAllNetworkConfigs) { - mConfig->LoadConfig(config.second); - } - mConfig->EndLoadConfig(); - if (!mConfig->NeedReload()) { - return; - } - mConfig->Reload(); - ReloadSource(); - StartEventLoop(); - BindSender(); -} - -void NetworkObserver::EventLoop() { - LOG_INFO(sLogger, ("start observer network event loop", "success")); - ContainerProcessGroupManager::GetInstance()->Init(); - if (mConfig->mLocalFileEnabled) { - mReplayFilePtr = fopen64(STRING_FLAG(sls_observer_network_save_filename).c_str(), "rb+"); - } - uint64_t lastProfilingTime = GetCurrentTimeInNanoSeconds(); - while (true) { - bool hasMoreData = false; - ReadLock lock(mEventLoopThreadRWL); - if (mPCAPWrapper == nullptr && mEBPFWrapper == nullptr) { - static int sErrorCount = 0; - static int sErrorPintCount = 60000 / INT32_FLAG(sls_observer_network_no_data_sleep_interval_ms); - if (++sErrorCount % sErrorPintCount == 0) { - LOG_WARNING(sLogger, ("no observer datasource working", "")); - sErrorCount = 0; - } - usleep(1000 * INT32_FLAG(sls_observer_network_no_data_sleep_interval_ms)); - continue; - } - uint64_t nowTimeNs = GetCurrentTimeInNanoSeconds(); - // fetching and processing packets - if (mPCAPWrapper != nullptr) { - int32_t rst = mPCAPWrapper->ProcessPackets(100, 100); - if (rst >= 100) { - hasMoreData = true; - } - if (rst != 0) { - auto deltaTime = GetCurrentTimeInNanoSeconds() - nowTimeNs; - LOG_DEBUG(sLogger, ("process pcap packets, rst", rst)("time", deltaTime / 1000LL / 1000LL)); - } - } - - if (mEBPFWrapper != nullptr) { - if (nowTimeNs - mLastCleanAllDisableProcessNs - >= INT64_FLAG(sls_observer_network_cleanall_disable_process_interval) * 1000ULL * 1000ULL * 1000ULL) { - mLastCleanAllDisableProcessNs = nowTimeNs; - mEBPFWrapper->CleanAllDisableProcesses(); - } - mNetworkStatistic->mEbpfDisableProcesses = mEBPFWrapper->GetDisablesProcessCount(); - if (nowTimeNs - mLastProbeDisableProcessNs - >= INT64_FLAG(sls_observer_network_probe_disable_process_interval) * 1000ULL * 1000ULL * 1000ULL) { - mLastProbeDisableProcessNs = nowTimeNs; - mEBPFWrapper->ProbeProcessStat(); - } - int32_t rst = mEBPFWrapper->ProcessPackets(100, 100); - if (rst >= 100) { - hasMoreData = true; - } - if (rst != 0) { - LOG_TRACE(sLogger, - ("process ebpf packets, rst", - rst)("time", GetCurrentTimeInNanoSeconds() - nowTimeNs / 1000LL / 1000LL)); - } - } - if (mReplayFilePtr != nullptr) { - uint32_t dataSize = 0; - fread(&dataSize, 1, 4, mReplayFilePtr); - if (dataSize != 0 && dataSize < 1024 * 1024) { - char* buf = (char*)malloc(dataSize); - if (dataSize != fread(buf, 1, dataSize, mReplayFilePtr)) { - LOG_ERROR(sLogger, ("read ebpf replay file failed", errno)); - } else { - void* event = nullptr; - int32_t len = 0; - BufferToPacketEvent(buf, static_cast(dataSize), event, len); - if (event != nullptr) { - OnPacketEvent(event, len); - hasMoreData = true; - } - } - free(buf); - } - } - - // fetching metas - if (nowTimeNs - mLastFlushMetaTimeNs >= mConfig->mFlushMetaInterval * 1000ULL * 1000ULL * 1000ULL) { - mLastFlushMetaTimeNs = nowTimeNs; - ContainerProcessGroupManager::GetInstance()->Init(); - ContainerProcessGroupManager::GetInstance()->FlushMetas(); - } - - // GC - if (nowTimeNs - mLastGCTimeNs >= INT64_FLAG(sls_observer_network_gc_interval) * 1000ULL * 1000ULL * 1000ULL) { - mLastGCTimeNs = nowTimeNs; - GarbageCollection(nowTimeNs); - } - if (mEBPFWrapper != nullptr - && nowTimeNs - mLastEbpfGCTimeNs - > INT64_FLAG(sls_observer_network_ebpf_connection_gc_interval) * 1000ULL * 1000ULL * 1000ULL) { - mLastEbpfGCTimeNs = nowTimeNs; - mNetworkStatistic->mEbpfUsingConnections = EBPFConnectionGC(nowTimeNs); - } - if (nowTimeNs - mLastFlushNetlinkTimeNs >= mConfig->mFlushNetlinkInterval * 1000ULL * 1000ULL * 1000ULL) { - mLastFlushNetlinkTimeNs = nowTimeNs; - ConnectionMetaManager::GetInstance()->Init(); - ConnectionMetaManager::GetInstance()->GarbageCollection(); - } - - // flush observer metrics - if (nowTimeNs - mLastL4FlushTimeNs >= mConfig->mFlushOutL4Interval * 1000ULL * 1000ULL * 1000ULL) { - mLastL4FlushTimeNs = nowTimeNs; - std::vector allLogs; - FlushOutStatistics(allLogs); - if (mSenderFunc) { - mSenderFunc(allLogs, mConfig->mLastApplyedConfig); - } - mNetworkStatistic->mOutputEvents += allLogs.size(); - for (const auto& item : allLogs) { - mNetworkStatistic->mOutputBytes += item.GetCachedSize(); - } - } - - // flush observer metrics - if (nowTimeNs - mLastL7FlushTimeNs >= mConfig->mFlushOutL7Interval * 1000ULL * 1000ULL * 1000ULL) { - mLastL7FlushTimeNs = nowTimeNs; - std::vector allLogs; - FlushOutMetrics(allLogs); - if (mSenderFunc) { - mSenderFunc(allLogs, mConfig->mLastApplyedConfig); - } - mNetworkStatistic->mOutputEvents += allLogs.size(); - for (const auto& item : allLogs) { - mNetworkStatistic->mOutputBytes += item.GetCachedSize(); - } - } - // flush profile metrics - if ((nowTimeNs - lastProfilingTime) >= INT32_FLAG(monitor_interval) * 1000ULL * 1000ULL * 1000ULL) { - static auto sPMStat = ProcessMetaStatistic::GetInstance(); - static auto sCMStat = ConnectionMetaStatistic::GetInstance(); - static auto sPStat = ProtocolStatistic::GetInstance(); - static auto sPDStat = ProtocolDebugStatistic::GetInstance(); - LOG_DEBUG(sLogger, ("observer_process_meta_statistic", sPMStat->ToString())); - LOG_DEBUG(sLogger, ("observer_connection_meta_statistic", sCMStat->ToString())); - LOG_DEBUG(sLogger, ("observer_protocol_statistic", sPStat->ToString())); - LOG_DEBUG(sLogger, ("observer_protocol_mem_statistic", sPDStat->ToString())); - LOG_DEBUG(sLogger, ("observer_network_statistic", mNetworkStatistic->ToString())); - - sPMStat->FlushMetrics(); - sCMStat->FlushMetrics(); - sPStat->FlushMetrics(); - sPDStat->FlushMetrics(BOOL_FLAG(sls_observer_network_protocol_stat)); - mNetworkStatistic->FlushMetrics(); - LogtailMonitor::GetInstance()->UpdateMetric( - "observer_container_category", ContainerProcessGroupManager::GetInstance()->GetContainerType()); - lastProfilingTime = nowTimeNs; - } - if (!hasMoreData) { - usleep(1000 * INT32_FLAG(sls_observer_network_no_data_sleep_interval_ms)); - } - } -} - -void NetworkObserver::BindSender() { - mSenderFunc - = this->mConfig->mLastApplyedConfig->IsFlushingThroughGoPipeline() ? OutputPluginProcess : OutputDirectly; -} - -inline void NetworkObserver::StartEventLoop() { - if (!mEventLoopThread) { - mEventLoopThread = CreateThread([this]() { EventLoop(); }); - } -} -int NetworkObserver::OutputPluginProcess(std::vector& logs, const Pipeline* config) { - static auto sPlugin = LogtailPlugin::GetInstance(); - auto now = GetCurrentLogtailTime(); - for (auto& item : logs) { - // nanosecond of observer will not be discard after processors, so here is default to no nanosecond - SetLogTime(&item, now.tv_sec); - if (config->GetContext().GetGlobalConfig().mTopicType == GlobalConfig::TopicType::MACHINE_GROUP_TOPIC) { - sPlugin->ProcessLog(config->Name(), item, "", config->GetContext().GetGlobalConfig().mTopicFormat, ""); - } else { - sPlugin->ProcessLog(config->Name(), item, "", "", ""); - } - } - return 0; -} - -int NetworkObserver::OutputDirectly(std::vector& logs, const Pipeline* config) { - const FlusherSLS* plugin = static_cast(config->GetFlushers()[0]->GetPlugin()); - const size_t maxCount = INT32_FLAG(merge_log_count_limit) / 4; - for (size_t beginIndex = 0; beginIndex < logs.size(); beginIndex += maxCount) { - size_t endIndex = beginIndex + maxCount; - if (endIndex > logs.size()) { - endIndex = logs.size(); - } - sls_logs::LogGroup logGroup; - sls_logs::LogTag* logTagPtr = logGroup.add_logtags(); - logTagPtr->set_key(LOG_RESERVED_KEY_HOSTNAME); - logTagPtr->set_value(LogFileProfiler::mHostname.substr(0, 99)); -#ifdef __ENTERPRISE__ - std::string userDefinedId = EnterpriseConfigProvider::GetInstance()->GetUserDefinedIdSet(); - if (!userDefinedId.empty()) { - logTagPtr = logGroup.add_logtags(); - logTagPtr->set_key(LOG_RESERVED_KEY_USER_DEFINED_ID); - logTagPtr->set_value(userDefinedId.substr(0, 99)); - } -#endif - logGroup.set_category(plugin->mLogstore); - logGroup.set_source(LogFileProfiler::mIpAddr); - if (config->GetContext().GetGlobalConfig().mTopicType == GlobalConfig::TopicType::MACHINE_GROUP_TOPIC - && !config->GetContext().GetGlobalConfig().mTopicFormat.empty()) { - logGroup.set_topic(config->GetContext().GetGlobalConfig().mTopicFormat); - } - auto now = GetCurrentLogtailTime(); - for (size_t i = beginIndex; i < endIndex; ++i) { - sls_logs::Log* log = logGroup.add_logs(); - log->mutable_contents()->CopyFrom(*(logs[i].mutable_contents())); - SetLogTime(log, now.tv_sec); - } - if (!const_cast(plugin)->Send(logGroup.SerializeAsString(), "")) { - return -1; - } - } - return 0; -} -} // namespace logtail \ No newline at end of file diff --git a/core/observer/network/NetworkObserver.h b/core/observer/network/NetworkObserver.h deleted file mode 100644 index 71dd25397d..0000000000 --- a/core/observer/network/NetworkObserver.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "interface/network.h" -#include "interface/helper.h" -#include "NetworkConfig.h" -#include -#include -#include "protobuf/sls/sls_logs.pb.h" -#include "common/Thread.h" -#include "common/Lock.h" -#include "common/TimeUtil.h" -#include "common/StringPiece.h" -#include "metas/ContainerProcessGroup.h" -#include "ConnectionObserver.h" -#include "metas/ConnectionMetaManager.h" -#include "interface/layerfour.h" - -namespace logtail { -class ProcessObserver; -class PCAPWrapper; -class EBPFWrapper; - - -class NetworkObserver { -public: - static NetworkObserver* GetInstance() { - static auto* sObserver = new NetworkObserver(); - return sObserver; - } - - void Stop() { - if (mEventLoopThread) { - mEventLoopThread->Wait(100); - } - } - - void HoldOn(bool exitFlag = false); - - void Resume(); - - void Reload(); - -private: - NetworkObserver() { - mLastGCTimeNs = GetCurrentTimeInNanoSeconds(); - mLastL4FlushTimeNs = GetCurrentTimeInNanoSeconds(); - mLastL7FlushTimeNs = GetCurrentTimeInNanoSeconds(); - mConfig = NetworkConfig::GetInstance(); - mNetworkStatistic = NetworkStatistic::GetInstance(); - mServiceMetaManager = ServiceMetaManager::GetInstance(); - } - ~NetworkObserver(); - - void EventLoop(); - - - void GarbageCollection(uint64_t nowTimeNs); - - uint32_t EBPFConnectionGC(uint64_t nowTimeNs); - - /** - * @brief Get or create a new process observer to process network packet. - * @param header the network packet meta data. - * @param create whether create new process obj when not found - * @return ProcessObserver allocated in heap. - */ - ProcessObserver* GetProcess(PacketEventHeader* header, bool create = true); - - /** - * @brief BindSender bind different output ways, such as sls or plugins output ways. - */ - void BindSender(); - static int OutputPluginProcess(std::vector& logs, const Pipeline* cfg); - static int OutputDirectly(std::vector& logs, const Pipeline* cfg); - - /** - * @brief Process bytes by different protocol processors. - * @param sp network transfer bytes. - * @return -1 means illegal arguments and 0 means success. - */ - int OnPacketEventStringPiece(StringPiece sp) { return OnPacketEvent((void*)sp.c_str(), sp.size()); } - - int OnPacketEvent(void* event, size_t len); - - void OnProcessDestroyed(uint32_t pid, const char* command, size_t len); - - /** - * @brief Output layer 4 statistics - * @param allData allData stores all observer logs - */ - void FlushOutStatistics(std::vector& allData); - /** - * @brief Read logs from ContainerProcessGroupManager - * @param allData allData stores all observer protocol logs - */ - void FlushOutMetrics(std::vector& allData); - - void FlushStatistics(logtail::NetStaticticsMap& map, std::vector& logs); - - void ReloadSource(); - - // create a still running thread to process observer data. - void StartEventLoop(); - - std::unordered_map mAllProcesses; - std::function&, const Pipeline*)> mSenderFunc; - ThreadPtr mEventLoopThread; - ReadWriteLock mEventLoopThreadRWL; - uint64_t mLastGCTimeNs = 0; - uint64_t mLastL4FlushTimeNs = 0; - uint64_t mLastL7FlushTimeNs = 0; - uint64_t mLastEbpfGCTimeNs = 0; - uint64_t mLastFlushMetaTimeNs = 0; - uint64_t mLastFlushNetlinkTimeNs = 0; - uint64_t mLastProbeDisableProcessNs = 0; - uint64_t mLastCleanAllDisableProcessNs = 0; - FILE* mDumpFilePtr = nullptr; - FILE* mReplayFilePtr = nullptr; - int64_t mDumpSize = 0; - - // don't delete following pointer, the lifecycles of them may be over current instance. - NetworkStatistic* mNetworkStatistic; - ServiceMetaManager* mServiceMetaManager; - PCAPWrapper* mPCAPWrapper = nullptr; - EBPFWrapper* mEBPFWrapper = nullptr; - NetworkConfig* mConfig; - - friend class NetworkObserverUnittest; - friend class PCAPWrapperUnittest; - friend class EBPFWrapperUnittest; - friend class LocalFileWrapperUnittest; - friend class ProtocolDnsUnittest; - friend class ProtocolHttpUnittest; - friend class ProtocolMySqlUnittest; - friend class ProtocolRedisUnittest; - friend class ProtocolPgSqlUnittest; -}; - -} // namespace logtail diff --git a/core/observer/network/ProcessObserver.cpp b/core/observer/network/ProcessObserver.cpp deleted file mode 100644 index c739eec755..0000000000 --- a/core/observer/network/ProcessObserver.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "ProcessObserver.h" -#include "logger/Logger.h" - -namespace logtail { - -ProcessObserver::ProcessObserver(uint64_t time) : mLastDataTimeNs(time) { -} - - -bool ProcessObserver::GarbageCollection(size_t size_limit_bytes, uint64_t nowTimeNs) { - static auto sNetStatistic = NetworkStatistic::GetInstance(); - if (nowTimeNs - mLastDataTimeNs - > (uint64_t)INT64_FLAG(sls_observer_network_process_timeout) * 1000LL * 1000LL * 1000LL) { - return true; - } - for (auto iter = mAllConnections.begin(); iter != mAllConnections.end();) { - ConnectionObserver* conn = iter->second; - if (conn->GarbageCollection(size_limit_bytes, nowTimeNs)) { - LOG_DEBUG(sLogger, - ("delete connection observer when gc, id", GetProcessMeta()->ToString())("conn id", iter->first)); - delete conn; - iter = mAllConnections.erase(iter); - ++sNetStatistic->mGCReleaseConnCount; - } else { - ++iter; - } - } - if (!mAllConnections.empty()) { - return false; - } - return nowTimeNs - mLastDataTimeNs - > (uint64_t)INT64_FLAG(sls_observer_network_process_no_connection_timeout) * 1000LL * 1000LL * 1000LL; -} - -} // namespace logtail diff --git a/core/observer/network/ProcessObserver.h b/core/observer/network/ProcessObserver.h deleted file mode 100644 index 3182967fe7..0000000000 --- a/core/observer/network/ProcessObserver.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "interface/network.h" -#include "ConnectionObserver.h" -#include -#include "network/protocols/ProtocolEventAggregators.h" -#include -#include "metas/ProcessMeta.h" -#include "metas/ContainerProcessGroup.h" -#include "NetworkConfig.h" -#include "NetworkObserver.h" - - -namespace logtail { -class ProcessObserver { -public: - explicit ProcessObserver(uint64_t time); - - ~ProcessObserver() { - for (auto iter = mAllConnections.begin(); iter != mAllConnections.end(); ++iter) { - delete iter->second; - } - } - - bool HasConnection(uint32_t sockHash) const { return mAllConnections.find(sockHash) != mAllConnections.end(); } - - ConnectionObserver* GetOrCreateConnection(PacketEventHeader* header) { - auto findIter = mAllConnections.find(header->SockHash); - if (findIter != mAllConnections.end()) { - return findIter->second; - } - ConnectionObserver* newConn = new ConnectionObserver(header, *mAllAggregator); - mAllConnections.insert(std::make_pair(header->SockHash, newConn)); - return newConn; - } - - void OnData(PacketEventHeader* header, PacketEventData* data) { - auto conn = GetOrCreateConnection(header); - mLastDataTimeNs = header->TimeNano; - conn->OnData(header, data); - } - - void ConnectionMarkDeleted(PacketEventHeader* header) { - auto findIter = mAllConnections.find(header->SockHash); - if (findIter != mAllConnections.end()) { - findIter->second->MarkDeleted(); - } - } - - void MarkDeleted() { mMarkDeleted = true; } - - const ProcessMetaPtr& GetProcessMeta() const { return mAllAggregator->GetProcessMeta(); } - - ProtocolEventAggregators* GetAggregator() { return mAllAggregator; } - - void SetProcessGroup(ContainerProcessGroupPtr& groupPtr) { - mProcessGroupPtr = groupPtr; - mAllAggregator = &mProcessGroupPtr->mAggregator; - } - - /** - * @brief GarbageCollection - * @param size_limit_bytes - * @param nowTimeNs - * @return if we need delete this ProcessObserver - */ - bool GarbageCollection(size_t size_limit_bytes, uint64_t nowTimeNs); - -protected: - std::unordered_map mAllConnections; - uint64_t mLastDataTimeNs = 0; - ContainerProcessGroupPtr mProcessGroupPtr; - ProtocolEventAggregators* mAllAggregator = NULL; - bool mMarkDeleted = false; - friend class NetworkObserverUnittest; -}; - -} // namespace logtail diff --git a/core/observer/network/protocols/ProtocolEventAggregators.cpp b/core/observer/network/protocols/ProtocolEventAggregators.cpp deleted file mode 100644 index 7af3ac984f..0000000000 --- a/core/observer/network/protocols/ProtocolEventAggregators.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "ProtocolEventAggregators.h" - -namespace logtail { - - -void ProtocolEventAggregators::FlushOutMetrics(uint64_t timeNano, - std::vector& allData, - std::vector>& processTags, - std::vector>& globalTags, - uint64_t interval) { - Json::Value root; - Json::StreamWriterBuilder builder; - builder["indentation"] = ""; // If you want whitespace-less output - for (auto& tag : processTags) { - root[tag.first] = tag.second; - } - const std::string& pTags = Json::writeString(builder, root); - - ::google::protobuf::RepeatedPtrField gTags; - gTags.Reserve(globalTags.size()); - for (const auto& tag : globalTags) { - sls_logs::Log_Content* content = gTags.Add(); - content->set_key(tag.first); - content->set_value(tag.second); - } - - - if (mDNSAggregators != nullptr) { - mDNSAggregators->FlushLogs(allData, pTags, gTags, interval); - } - - if (mHTTPAggregators != nullptr) { - mHTTPAggregators->FlushLogs(allData, pTags, gTags, interval); - } - - if (mMySQLAggregators != nullptr) { - mMySQLAggregators->FlushLogs(allData, pTags, gTags, interval); - } - - if (mRedisAggregators != nullptr) { - mRedisAggregators->FlushLogs(allData, pTags, gTags, interval); - } - - if (mPgSQLAggregators != nullptr) { - mPgSQLAggregators->FlushLogs(allData, pTags, gTags, interval); - } -} - -} // namespace logtail diff --git a/core/observer/network/protocols/ProtocolEventAggregators.h b/core/observer/network/protocols/ProtocolEventAggregators.h deleted file mode 100644 index d02e1b46a2..0000000000 --- a/core/observer/network/protocols/ProtocolEventAggregators.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "network/protocols/dns/type.h" -#include "network/protocols/http/type.h" -#include "network/protocols/mysql/type.h" -#include "network/protocols/redis/type.h" -#include "network/protocols/pgsql/type.h" -#include -#include -#include - - -namespace logtail { - -class ProtocolEventAggregators { -public: - ProtocolEventAggregators() = default; - - ~ProtocolEventAggregators() { - if (mDNSAggregators != NULL) { - delete mDNSAggregators; - mDNSAggregators = NULL; - } - if (mHTTPAggregators != NULL) { - delete mHTTPAggregators; - mHTTPAggregators = NULL; - } - if (mMySQLAggregators != NULL) { - delete mMySQLAggregators; - mMySQLAggregators = NULL; - } - if (mRedisAggregators != NULL) { - delete mRedisAggregators; - mRedisAggregators = NULL; - } - if (mPgSQLAggregators != NULL) { - delete mPgSQLAggregators; - mPgSQLAggregators = NULL; - } - } - - DNSProtocolEventAggregator* GetDNSAggregator() { - if (mDNSAggregators != NULL) { - return mDNSAggregators; - } - auto pair = NetworkConfig::GetProtocolAggSize(ProtocolType_DNS); - mDNSAggregators = new DNSProtocolEventAggregator(pair.first, pair.second); - return mDNSAggregators; - } - - HTTPProtocolEventAggregator* GetHTTPAggregator() { - if (mHTTPAggregators != NULL) { - return mHTTPAggregators; - } - auto pair = NetworkConfig::GetProtocolAggSize(ProtocolType_HTTP); - mHTTPAggregators = new HTTPProtocolEventAggregator(pair.first, pair.second); - return mHTTPAggregators; - } - - - MySQLProtocolEventAggregator* GetMySQLAggregator() { - if (mMySQLAggregators != NULL) { - return mMySQLAggregators; - } - auto pair = NetworkConfig::GetProtocolAggSize(ProtocolType_MySQL); - mMySQLAggregators = new MySQLProtocolEventAggregator(pair.first, pair.second); - return mMySQLAggregators; - } - - RedisProtocolEventAggregator* GetRedisAggregator() { - if (mRedisAggregators != NULL) { - return mRedisAggregators; - } - auto pair = NetworkConfig::GetProtocolAggSize(ProtocolType_Redis); - mRedisAggregators = new RedisProtocolEventAggregator(pair.first, pair.second); - return mRedisAggregators; - } - - PgSQLProtocolEventAggregator* GetPgSQLAggregator() { - if (mPgSQLAggregators != NULL) { - return mPgSQLAggregators; - } - auto pair = NetworkConfig::GetProtocolAggSize(ProtocolType_PgSQL); - mPgSQLAggregators = new PgSQLProtocolEventAggregator(pair.first, pair.second); - return mPgSQLAggregators; - } - - const ProcessMetaPtr& GetProcessMeta() const { return mMetaPtr; } - - void SetProcessMeta(const ProcessMetaPtr& metaPtr) { mMetaPtr = metaPtr; } - - void FlushOutMetrics(uint64_t timeNano, - std::vector& allData, - std::vector>& processTags, - std::vector>& globalTags, - uint64_t interval); - -protected: - DNSProtocolEventAggregator* mDNSAggregators = NULL; - HTTPProtocolEventAggregator* mHTTPAggregators = NULL; - MySQLProtocolEventAggregator* mMySQLAggregators = NULL; - RedisProtocolEventAggregator* mRedisAggregators = NULL; - PgSQLProtocolEventAggregator* mPgSQLAggregators = NULL; - ProcessMetaPtr mMetaPtr; -}; - -} // namespace logtail diff --git a/core/observer/network/protocols/category.h b/core/observer/network/protocols/category.h deleted file mode 100644 index 1b954a8eb6..0000000000 --- a/core/observer/network/protocols/category.h +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright 2023 iLogtail 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. - */ - -#pragma once -#include -#include "interface/network.h" -#include "interface/global.h" -#include "interface/helper.h" -#include "common.h" -#include "Logger.h" -#include "xxhash/xxhash.h" - -namespace logtail { - - -/** - * Common hash key for metrics aggregation - */ -struct CommonAggKey { - CommonAggKey() = default; - CommonAggKey(const CommonAggKey& other) = default; - CommonAggKey(CommonAggKey&& other) noexcept - : HashVal(other.HashVal), - ConnId(other.ConnId), - RemotePort(other.RemotePort), - LocalPort(other.LocalPort), - Role(other.Role), - RemoteIp(std::move(other.RemoteIp)), - LocalIp(std::move(other.LocalIp)) {} - CommonAggKey& operator=(const CommonAggKey& other) = default; - CommonAggKey& operator=(CommonAggKey&& other) noexcept { - this->HashVal = other.HashVal; - this->Role = other.Role; - this->RemotePort = other.RemotePort; - this->RemoteIp = std::move(other.RemoteIp); - this->LocalPort = other.LocalPort; - this->LocalIp = std::move(other.LocalIp); - this->ConnId = other.ConnId; - return *this; - } - explicit CommonAggKey(PacketEventHeader* header) - : HashVal(header->SockHash), - ConnId(GenConnectionID(header->PID, header->SockHash)), - RemotePort(header->DstPort), - LocalPort(header->SrcPort), - Pid(header->PID), - Role(header->RoleType), - RemoteIp(SockAddressToString(header->DstAddr)), - LocalIp(SockAddressToString(header->SrcAddr)) { - HashVal = XXH32(&this->Role, sizeof(Role), HashVal); - } - - friend std::ostream& operator<<(std::ostream& Os, const CommonAggKey& Key) { - Os << "HashVal: " << Key.HashVal << " ConnId: " << Key.ConnId << " RemotePort: " << Key.RemotePort - << " LocalPort: " << Key.LocalPort << " Role: " << PacketRoleTypeToString(Key.Role) - << " RemoteIp: " << Key.RemoteIp << " LocalIp: " << Key.LocalIp; - return Os; - } - - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - - void ToPB(sls_logs::Log* log) const { - static ServiceMetaManager* sHostnameManager = logtail::ServiceMetaManager::GetInstance(); - AddAnyLogContent(log, observer::kRole, PacketRoleTypeToString(this->Role)); - AddAnyLogContent(log, observer::kRemoteAddr, RemoteIp); - AddAnyLogContent(log, observer::kRemotePort, RemotePort); - AddAnyLogContent(log, observer::kLocalPort, LocalPort); - AddAnyLogContent(log, observer::kLocalAddr, LocalIp); - AddAnyLogContent(log, observer::kConnId, ConnId); - const ServiceMeta& meta = sHostnameManager->GetServiceMeta(this->Pid, this->RemoteIp); - auto remoteInfo = std::string(kRemoteInfoPrefix) - .append(meta.Empty() ? this->RemoteIp : meta.Host) - .append(kRemoteInfoSuffix); - AddAnyLogContent(log, observer::kRemoteInfo, std::move(remoteInfo)); - } - - uint64_t HashVal{0}; - uint64_t ConnId{0}; - uint16_t RemotePort{0}; - uint16_t LocalPort{0}; - uint16_t Pid{0}; - PacketRoleType Role{PacketRoleType::Unknown}; - std::string RemoteIp; - std::string LocalIp; -}; - -template -struct DBAggKey { - DBAggKey() = default; - DBAggKey(const DBAggKey& other) = default; - DBAggKey(DBAggKey&& other) noexcept - : ConnKey(std::move(other.ConnKey)), - QueryCmd(std::move(other.QueryCmd)), - Query(std::move(other.Query)), - Version(std::move(other.Version)), - Status(other.Status) {} - DBAggKey& operator=(const DBAggKey& other) = default; - DBAggKey& operator=(DBAggKey&& other) noexcept { - this->ConnKey = std::move(other.ConnKey); - this->QueryCmd = std::move(other.QueryCmd); - this->Query = std::move(other.Query); - this->Version = std::move(other.Version); - this->Status = other.Status; - return *this; - - } - - uint64_t Hash() const { - uint64_t hashValue = ConnKey.HashVal; - hashValue = XXH32(this->QueryCmd.c_str(), this->QueryCmd.size(), hashValue); - hashValue = XXH32(this->Query.c_str(), this->Query.size(), hashValue); - hashValue = XXH32(this->Version.c_str(), this->Version.size(), hashValue); - hashValue = XXH32(&this->Status, sizeof(this->Status), hashValue); - return hashValue; - } - void ToPB(sls_logs::Log* log) const { - AddAnyLogContent(log, observer::kVersion, Version); - AddAnyLogContent(log, observer::kQueryCmd, QueryCmd); - AddAnyLogContent(log, observer::kQuery, Query); - AddAnyLogContent(log, observer::kStatus, Status); - AddAnyLogContent(log, observer::kProtocol, ProtocolTypeToString(PT)); - AddAnyLogContent(log, observer::kType, ObserverMetricsTypeToString(ObserverMetricsType::L7_DB_METRICS)); - ConnKey.ToPB(log); - } - - std::string ProtocolType() { return ProtocolTypeToString(PT); } - - friend std::ostream& operator<<(std::ostream& Os, const DBAggKey& Key) { - Os << "ConnKey: " << Key.ConnKey << " QueryCmd: " << Key.QueryCmd << " Query: " << Key.Query - << " Version: " << Key.Version << " Status: " << Key.Status; - return Os; - } - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - - CommonAggKey ConnKey; - std::string QueryCmd; - std::string Query; - std::string Version; - int8_t Status{-1}; -}; - -template -struct RequestAggKey { - RequestAggKey() = default; - RequestAggKey(const RequestAggKey& other) = default; - RequestAggKey& operator=(const RequestAggKey& other) = default; - RequestAggKey(RequestAggKey&& other) noexcept - : ConnKey(std::move(other.ConnKey)), - ReqType(std::move(other.ReqType)), - ReqDomain(std::move(other.ReqDomain)), - ReqResource(std::move(other.ReqResource)), - Version(std::move(other.Version)), - RespCode(other.RespCode), - RespStatus(other.RespStatus) {} - RequestAggKey& operator=(RequestAggKey&& other) noexcept { - this->ConnKey = std::move(other.ConnKey); - this->ReqType = std::move(other.ReqType); - this->ReqDomain = std::move(other.ReqDomain); - this->ReqResource = std::move(other.ReqResource); - this->Version = std::move(other.Version); - this->RespCode = other.RespCode; - this->RespStatus = other.RespStatus; - return *this; - } - - uint64_t Hash() const { - uint64_t hashValue = ConnKey.HashVal; - hashValue = XXH32(this->ReqType.c_str(), this->ReqType.size(), hashValue); - hashValue = XXH32(this->ReqDomain.c_str(), this->ReqDomain.size(), hashValue); - hashValue = XXH32(this->ReqResource.c_str(), this->ReqResource.size(), hashValue); - hashValue = XXH32(this->Version.c_str(), this->Version.size(), hashValue); - hashValue = XXH32(&this->RespCode, sizeof(this->RespCode), hashValue); - hashValue = XXH32(&this->RespStatus, sizeof(this->RespStatus), hashValue); - return hashValue; - } - void ToPB(sls_logs::Log* log) const { - AddAnyLogContent(log, observer::kReqType, ReqType); - AddAnyLogContent(log, observer::kReqDomain, ReqDomain); - AddAnyLogContent(log, observer::kReqResource, ReqResource); - AddAnyLogContent(log, observer::kVersion, Version); - AddAnyLogContent(log, observer::kRespStatus, RespStatus); - AddAnyLogContent(log, observer::kRespCode, RespCode); - AddAnyLogContent(log, observer::kProtocol, ProtocolTypeToString(PT)); - AddAnyLogContent(log, observer::kType, ObserverMetricsTypeToString(ObserverMetricsType::L7_REQ_METRICS)); - ConnKey.ToPB(log); - } - - std::string ProtocolType() { return ProtocolTypeToString(PT); } - - friend std::ostream& operator<<(std::ostream& Os, const RequestAggKey& Key) { - Os << "ConnKey: " << Key.ConnKey << " ReqType: " << Key.ReqType << " ReqDomain: " << Key.ReqDomain - << " ReqResource: " << Key.ReqResource << " Version: " << Key.Version << " RespCode: " << Key.RespCode - << " RespStatus: " << Key.RespStatus; - return Os; - } - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - - CommonAggKey ConnKey; - std::string ReqType; - std::string ReqDomain; - std::string ReqResource; - std::string Version; - int16_t RespCode{-1}; - int8_t RespStatus{-1}; -}; -} // namespace logtail diff --git a/core/observer/network/protocols/common.h b/core/observer/network/protocols/common.h deleted file mode 100644 index 4cc68a6132..0000000000 --- a/core/observer/network/protocols/common.h +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "interface/protocol.h" -#include -#include "protobuf/sls/sls_logs.pb.h" -#include "interface/helper.h" -#include "LogtailAlarm.h" -#include "metas/ServiceMetaCache.h" -#include "Logger.h" -#include -#include - -namespace logtail { - - -/** - * Single protocol event metrics info. - */ -struct CommonProtocolEventInfo { - CommonProtocolEventInfo() = default; - CommonProtocolEventInfo(CommonProtocolEventInfo&& other) noexcept = default; - CommonProtocolEventInfo(const CommonProtocolEventInfo& other) = default; - CommonProtocolEventInfo& operator=(CommonProtocolEventInfo&& other) noexcept = default; - CommonProtocolEventInfo& operator=(const CommonProtocolEventInfo& other) = default; - - - friend std::ostream& operator<<(std::ostream& os, const CommonProtocolEventInfo& info) { - os << "LatencyNs: " << info.LatencyNs << " ReqBytes: " << info.ReqBytes << " RespBytes: " << info.RespBytes; - return os; - } - - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - - int64_t LatencyNs{0}; - int32_t ReqBytes{0}; - int32_t RespBytes{0}; -}; - -/** - * A pair of protocol keys and metrics info. - * @tparam ProtocolEventKey stores unique protocol keys - */ -template -struct CommonProtocolEvent { - CommonProtocolEvent() = default; - CommonProtocolEvent(CommonProtocolEvent&& other) noexcept = default; - CommonProtocolEvent(const CommonProtocolEvent& other) = default; - CommonProtocolEvent& operator=(CommonProtocolEvent&& other) noexcept = default; - CommonProtocolEvent& operator=(const CommonProtocolEvent& other) = default; - - ProtocolEventKey Key; - CommonProtocolEventInfo Info; -}; - -/** - * Aggregate single protocol event info. - */ -struct CommonProtocolAggResult { - CommonProtocolAggResult() = default; - void Clear() { - TotalCount = 0; - TotalLatencyNs = 0; - TotalReqBytes = 0; - TotalRespBytes = 0; - } - - bool IsEmpty() const { return TotalCount == 0; } - - void AddEventInfo(CommonProtocolEventInfo& info) { - ++TotalCount; - TotalLatencyNs += info.LatencyNs; - TotalReqBytes += info.ReqBytes; - TotalRespBytes += info.RespBytes; - } - - void Merge(CommonProtocolAggResult& aggResult) { - TotalCount += aggResult.TotalCount; - TotalLatencyNs += aggResult.TotalLatencyNs; - TotalReqBytes += aggResult.TotalReqBytes; - TotalRespBytes += aggResult.TotalRespBytes; - } - - void ToPB(sls_logs::Log* log) const { - AddAnyLogContent(log, observer::kCount, TotalCount); - AddAnyLogContent(log, observer::kLatencyNs, TotalLatencyNs); - AddAnyLogContent(log, observer::kReqBytes, TotalReqBytes); - AddAnyLogContent(log, observer::kRespBytes, TotalRespBytes); - AddAnyLogContent(log, observer::kTdigestLatency, std::string("xxxxx")); - } - - int64_t TotalCount{0}; - int64_t TotalLatencyNs{0}; - int64_t TotalReqBytes{0}; - int64_t TotalRespBytes{0}; -}; - - -/** - * A aggregation result for unique protocol event key. - * @tparam ProtocolEventKey stores unique protocol keys - * @tparam ProtocolEventAggResult stores the whole aggregation result for whole input events - */ -template -struct CommonProtocolEventAggItem { - CommonProtocolEventAggItem() = default; - void ToPB(sls_logs::Log* log) { - Key.ToPB(log); - AggResult.ToPB(log); - } - void Merge(CommonProtocolEventAggItem& aggItem) { - AggResult.Merge(aggItem.AggResult); - } - void AddEventInfo(CommonProtocolEventInfo& info) { AggResult.AddEventInfo(info); } - void Clear() { AggResult.Clear(); } - - ProtocolEventKey Key; - ProtocolEventAggResult AggResult; -}; - -/** - * Cache pool - * @tparam ProtocolEventAggItem reused object. - */ -template -class CommonProtocolEventAggItemManager { -public: - explicit CommonProtocolEventAggItemManager(size_t maxCount = 128) : mMaxCount(maxCount) {} - - ~CommonProtocolEventAggItemManager() { - for (auto iter = mUnUsed.begin(); iter != mUnUsed.end(); ++iter) { - delete *iter; - } - } - - /** - * Create an new object or reuse the cached object. - * @return an protocol metrics event aggregate node. - */ - template - ProtocolEventAggItem* Create(ProtocolEventKey&& event) { - ProtocolEventAggItem* item; - if (mUnUsed.empty()) { - item = new ProtocolEventAggItem(); - item->Key = std::move(event); - return item; - } - item = mUnUsed.back(); - item->Clear(); - item->Key = std::move(event); - mUnUsed.pop_back(); - return item; - } - - /** - * Delete object when cache is full, or else would cache it. - * @param item deleting object. - */ - void Delete(ProtocolEventAggItem* item) { - if (mUnUsed.size() < mMaxCount) { - mUnUsed.push_back(item); - return; - } - delete item; - } - -private: - size_t mMaxCount; - std::deque mUnUsed; -}; - -// 通用的协议的聚类器实现 -template -class CommonProtocolEventAggregator { -public: - CommonProtocolEventAggregator(uint32_t maxClientAggSize, uint32_t maxServerAggSize) - : mClientAggMaxSize(maxClientAggSize), mServerAggMaxSize(maxServerAggSize) {} - - ~CommonProtocolEventAggregator() { - for (auto iter = mProtocolEventAggMap.begin(); iter != mProtocolEventAggMap.end(); ++iter) { - mAggItemManager.Delete(iter->second); - } - } - bool AddEvent(ProtocolEvent&& event) { - auto key = event.Key; - auto hashVal = key.Hash(); - auto findRst = mProtocolEventAggMap.find(hashVal); - if (findRst == mProtocolEventAggMap.end()) { - if (isFull(event.Key.ConnKey.Role)) { - static uint32_t sLastDropTime{0}; - auto now = time(nullptr); - LOG_DEBUG(sLogger, ("aggregator is full, some events would be dropped", event.Key.ToString())); - if (now - sLastDropTime > 60) { - sLastDropTime = now; - LOG_ERROR(sLogger, ("aggregator is full, some events would be dropped", event.Key.ProtocolType())); - } - return false; - } - auto item = mAggItemManager.Create(std::move(event.Key)); - findRst = mProtocolEventAggMap.insert(std::make_pair(hashVal, item)).first; - } - findRst->second->AddEventInfo(event.Info); - return true; - } - - void FlushLogs(std::vector& allData, - const std::string& tags, - google::protobuf::RepeatedPtrField& globalTags, - uint64_t interval) { - for (auto iter = mProtocolEventAggMap.begin(); iter != mProtocolEventAggMap.end();) { - if (iter->second->AggResult.IsEmpty()) { - mAggItemManager.Delete(iter->second); - iter = mProtocolEventAggMap.erase(iter); - } else { - sls_logs::Log newLog; - newLog.mutable_contents()->CopyFrom(globalTags); - AddAnyLogContent(&newLog, observer::kLocalInfo, tags); - AddAnyLogContent(&newLog, observer::kInterval, interval); - iter->second->ToPB(&newLog); - iter->second->Clear(); // wait for next clear - allData.push_back(std::move(newLog)); - ++iter; - } - } - } - - -private: - bool isFull(PacketRoleType role) { - if (role == PacketRoleType::Client) { - return this->mProtocolEventAggMap.size() >= mClientAggMaxSize; - } - if (role == PacketRoleType::Server) { - return this->mProtocolEventAggMap.size() >= mServerAggMaxSize; - } - return true; - } - ProtocolEventAggItemManager mAggItemManager; - std::unordered_map mProtocolEventAggMap; - uint32_t mClientAggMaxSize; - uint32_t mServerAggMaxSize; -}; - -/** - * For many protocols, they don't have an ID to bind the request and the response, such as mysql. - * So we would get many false matches for persistent connection. - * The cache would remove "dirty" data according to the timestamp. - */ -template -class CommonCache { -public: - explicit CommonCache(aggregatorType* aggregators) : mAggregators(aggregators) { - for (long unsigned int i = 0; i < capacity; ++i) { - mRequests[i] = new reqType; - mResponses[i] = new respType; - } - } - - ~CommonCache() { - for (long unsigned int i = 0; i < capacity; ++i) { - delete mRequests[i]; - delete mResponses[i]; - } - } - - // Only add event fail returns false; - bool InsertReq(std::function configFunc) { - configFunc(GetReqPos()); - return TryStitcherByReq(); - } - - // Only add event fail returns false; - bool InsertResp(std::function configFunc) { - configFunc(GetRespPos()); - return TryStitcherByResp(); - } - - bool GarbageCollection(uint64_t expireTimeNs) { - while (mHeadRequestsIdx <= mTailRequestsIdx) { - if (this->GetReqFront()->TimeNano < expireTimeNs) { - ++mHeadRequestsIdx; - continue; - } - break; - } - while (mHeadResponsesIdx <= mTailResponsesIdx) { - if (this->GetRespFront()->TimeNano < expireTimeNs) { - ++mHeadResponsesIdx; - continue; - } - break; - } - return this->GetRequestsSize() == 0 && this->GetResponsesSize() == 0; - } - - size_t GetRequestsSize() { return this->mTailRequestsIdx - this->mHeadRequestsIdx + 1; } - - size_t GetResponsesSize() { return this->mTailResponsesIdx - this->mHeadResponsesIdx + 1; } - - reqType* GetReqByIndex(size_t index) { return this->mRequests[index & (capacity - 1)]; } - - respType* GetRespByIndex(size_t index) { return this->mResponses[index & (capacity - 1)]; } - - void BindConvertFunc(std::function func) { - this->mConvertEventFunc = func; - } - -private: - bool TryStitcherByReq() { - auto req = this->GetReqFront(); - auto resp = this->GetRespFront(); - if (req == nullptr || resp == nullptr) { - return true; - } - // pop illegal nodes with sequence when the timeNano of them is less than the front request node. - while (true) { - if (resp != nullptr && resp->TimeNano < req->TimeNano) { - ++this->mHeadResponsesIdx; - resp = this->GetRespFront(); - continue; - } - break; - } - if (resp == nullptr) { - return true; - } - eventType event; - bool success = true; - if (this->mConvertEventFunc != nullptr && this->mConvertEventFunc(req, resp, event)) { - success = this->mAggregators->AddEvent(std::move(event)); - } - ++this->mHeadRequestsIdx; - ++this->mHeadResponsesIdx; - return success; - } - - bool TryStitcherByResp() { - auto req = this->GetReqFront(); - auto resp = this->GetRespFront(); - if (req == nullptr || resp == nullptr) { - return true; - } - // pop the resp node when the timeNano before the first req node. - if (resp->TimeNano < req->TimeNano) { - ++this->mHeadResponsesIdx; - return true; - } - // try to find the most matching req node, that means they are having the most closing timeNano. - int idx = this->mHeadRequestsIdx; - while (idx <= this->mTailRequestsIdx) { - if (this->GetReqByIndex(idx)->TimeNano > resp->TimeNano) { - break; - } - ++idx; - } - this->mHeadRequestsIdx = idx - 1; - req = this->GetReqFront(); - if (req == nullptr) { - return true; - } - eventType event; - bool success = true; - if (this->mConvertEventFunc != nullptr && this->mConvertEventFunc(req, resp, event)) { - LOG_TRACE(sLogger, - ("head_req", this->mHeadRequestsIdx)("tail_req", this->mTailRequestsIdx)( - "head_resp", this->mHeadRequestsIdx)("tail_resp", this->mTailResponsesIdx)); - success = this->mAggregators->AddEvent(std::move(event)); - } - ++this->mHeadRequestsIdx; - ++this->mHeadResponsesIdx; - return success; - } - - reqType* GetReqPos() { - ++this->mTailRequestsIdx; - if (mTailRequestsIdx - mHeadRequestsIdx == capacity) { - ++mHeadRequestsIdx; - } - return this->mRequests[mTailRequestsIdx & (capacity - 1)]; - } - - reqType* GetReqFront() { - if (this->mHeadRequestsIdx > this->mTailRequestsIdx) { - return nullptr; - } - return this->mRequests[mHeadRequestsIdx & (capacity - 1)]; - } - - respType* GetRespPos() { - ++this->mTailResponsesIdx; - if (mTailResponsesIdx - mHeadResponsesIdx == capacity) { - ++mHeadResponsesIdx; - } - return this->mResponses[mTailResponsesIdx & (capacity - 1)]; - } - - respType* GetRespFront() { - if (this->mHeadResponsesIdx > this->mTailResponsesIdx) { - return nullptr; - } - return this->mResponses[mHeadResponsesIdx & (capacity - 1)]; - } - -private: - std::array mRequests; - std::array mResponses; - // idx keep increasing - int64_t mHeadRequestsIdx = 0; - int64_t mTailRequestsIdx = -1; - int64_t mHeadResponsesIdx = 0; - int64_t mTailResponsesIdx = -1; - aggregatorType* mAggregators; - - std::function mConvertEventFunc; - - friend class ProtocolUtilUnittest; -}; -} // namespace logtail diff --git a/core/observer/network/protocols/dns/README.md b/core/observer/network/protocols/dns/README.md deleted file mode 100644 index 90fb68d2fb..0000000000 --- a/core/observer/network/protocols/dns/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# refer - - - -## build and test - -```bash -mkdir build -cd build -cmake .. -DCMAKE_CXX_STANDARD=11 -``` diff --git a/core/observer/network/protocols/dns/inner_parser.cpp b/core/observer/network/protocols/dns/inner_parser.cpp deleted file mode 100644 index 6b6a7d6c67..0000000000 --- a/core/observer/network/protocols/dns/inner_parser.cpp +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include - -#include "inner_parser.h" -#include "interface/network.h" -#include "interface/helper.h" - -namespace logtail { - -void DNSParser::parseName(std::string& name, const bool advancePos) { - bool useReferPosition = false; - - size_t readPos = currPostion; - while (readPos < pktSize) { - int nameSize = (int)payload[readPos]; - - if (((nameSize >> 6) & 0x03) == 0x03) { - // dns name refer to anthoer position - readPos = ((payload[readPos] & 0x3f) << 8) + payload[readPos + 1]; - useReferPosition = true; - continue; - } - if (nameSize == 0) { - if (!name.empty()) { - name.pop_back(); - } - readPos += 1; - break; - } else if (nameSize < 0) { - setParseFail("unexcepted nameSize found"); - return; - } - name.append(payload + readPos + 1, nameSize).append("."); - readPos += nameSize + 1; - } - - if (advancePos) { - if (useReferPosition) { - currPostion += 2; - } else { - currPostion = readPos; - } - } -} - -void DNSParser::parseQuery() { - dnsRequest.requests.reserve(dnsHeader.numQueries); - for (int i = 0; i < dnsHeader.numQueries; i++) { - DNSRequest request; - parseName(request.queryHost, true); - - request.queryType = DNSQueryType(readUint16()); - positionCommit(2); // query class - dnsRequest.requests.push_back(request); - } -} - -void DNSParser::parseAnswer() { - dnsResponse.responses.reserve(dnsHeader.numAnswers); - dnsResponse.answerCount = dnsHeader.numAnswers; - for (int i = 0; i < dnsHeader.numAnswers; i++) { - DNSResponse response; - - // DNSRecord queryHost; - parseName(response.queryHost, true); - - // read dns answser type - response.queryType = DNSQueryType(readUint16()); - - // read dns answer class - positionCommit(2); // answer class - - // read ttl - response.ttlSeconds = readUint32(); - - // read data length - int answerDataLen = readUint16(); - - if (response.queryType == DNSQueryTypeA) { - uint8_t* ipaddr = (uint8_t*)(payload + currPostion); - positionCommit(answerDataLen); - if (!isParseFail) { - SockAddress addr; - addr.Type = SockAddressType_IPV4; - addr.Addr.IPV4 = *(uint32_t*)ipaddr; - response.value = SockAddressToString(addr); - } - } else if (response.queryType == DNSQueryTypeAAAA) { - uint8_t* ipaddr = (uint8_t*)(payload + currPostion); - positionCommit(answerDataLen); - if (!isParseFail) { - SockAddress addr; - addr.Type = SockAddressType_IPV6; - addr.Addr.IPV6[0] = *(uint64_t*)ipaddr; - addr.Addr.IPV6[1] = *(uint64_t*)(ipaddr + 8); - response.value = SockAddressToString(addr); - } - } else if (response.queryType == DNSQueryTypeCNAME) { - std::string data; - parseName(data, false); - positionCommit(answerDataLen); - response.value = std::move(data); - } else { - response.value = ToString(response.queryType); - } - dnsResponse.responses.push_back(response); - } -} - -void DNSParser::print() { - // print request - std::cout << "****** Query ******" << std::endl; - for (auto r : dnsRequest.requests) { - r.print(); - } - - // print response - std::cout << "****** Response ******" << std::endl; - for (auto r : dnsResponse.responses) { - r.print(); - } - - std::cout << std::endl; -} - -void DNSParser::parseHeader() { - if (pktSize < sizeof(dnsHeader)) { - isParseFail = true; - return; - } - - dnsHeader.txid = readUint16(); - dnsHeader.flags = readUint16(); - dnsHeader.numQueries = readUint16(); - dnsHeader.numAnswers = readUint16(); - - // skip auth rr(+2) and add rr(+2) - positionCommit(4); - - if (DNS_FLAG_OPCODE(dnsHeader.flags) != 0) { - setParseFail("not a stand query"); - return; - } - if (dnsHeader.numQueries == 0) { - setParseFail("dns numQueries is zero"); - return; - } - - if ((dnsHeader.flags & DNS_FLAG_RESPONSE) == DNS_FLAG_RESPONSE) { - packetType = DNSPacketResponse; - } else { - packetType = DNSPacketRequest; - } -} - -void DNSParser::parse() { - parseHeader(); - if (isParseFail) { - return; - } - - parseQuery(); - if (isParseFail) { - return; - } - - if (packetType == DNSPacketResponse) { - parseAnswer(); - } -} - -} // namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/dns/inner_parser.h b/core/observer/network/protocols/dns/inner_parser.h deleted file mode 100644 index abbf80cd96..0000000000 --- a/core/observer/network/protocols/dns/inner_parser.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once -#include -#include -#include - -#include "network/protocols/utils.h" - -#define DNS_FLAG_RESPONSE 0x8000 -#define DNS_FLAG_OPCODE(FLGS) ((FLGS >> 11) & 0x0F) - -namespace logtail { - -struct DNSHeader { - uint16_t txid; - uint16_t flags; - uint16_t numQueries; - uint16_t numAnswers; - uint16_t numAuth{0}; - uint16_t numAddl{0}; -}; - -enum DNSPacketType { - DNSPacketRequest, - DNSPacketResponse, -}; - -enum DNSQueryType { - DNSQueryTypeA = 1, // ipv4 address query - DNSQueryTypeCNAME = 5, // query cname - DNSQueryTypePTR = 12, // reverse dns parse - DNSQueryTypeHTTPS = 65, // query https address - DNSQueryTypeMX = 15, // query mx - DNSQueryTypeTXT = 16, // query txt - DNSQueryTypeAAAA = 28, // query ipv6 -}; - -inline const char* ToString(DNSQueryType v) { - switch (v) { - case DNSQueryTypeA: - return "A"; - case DNSQueryTypeCNAME: - return "CNAME"; - case DNSQueryTypeHTTPS: - return "HTTPS"; - case DNSQueryTypeMX: - return "MX"; - case DNSQueryTypeTXT: - return "TXT"; - case DNSQueryTypeAAAA: - return "AAAA"; - default: - return "UnKnown"; - } -} - -struct DNSRequest { - std::string queryHost; - DNSQueryType queryType; - -public: - void print() { - std::cout << "queryHost: " << queryHost; - std::cout << "queryType: " << queryType << std::endl; - } -}; - -struct DNSRequestPacket { - std::vector requests; -}; - -struct DNSResponse { - std::string queryHost; - DNSQueryType queryType; - uint32_t ttlSeconds; - std::string value; - -public: - void print() { - std::cout << "-------------" << std::endl; - std::cout << "queryHost: " << queryHost; - std::cout << "queryType: " << queryType << std::endl; - std::cout << "ttlSeconds: " << ttlSeconds << std::endl; - std::cout << "value: " << value << std::endl; - } -}; - -struct DNSResponsePacket { - std::vector responses; - uint32_t answerCount; -}; - -class DNSParser : public ProtoParser { -public: - DNSParser(const char* payload, const size_t pktSize) : ProtoParser(payload, pktSize, true) {} - - ~DNSParser() {} - - void parse(); - - void print(); - -private: - void parseName(std::string& name, const bool advancePos); - - void parseHeader(); - - void parseQuery(); - - void parseAnswer(); - -public: - // dns spec - DNSHeader dnsHeader; - - DNSPacketType packetType; - - DNSRequestPacket dnsRequest; - - DNSResponsePacket dnsResponse; -}; - -} // namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/dns/parser.cpp b/core/observer/network/protocols/dns/parser.cpp deleted file mode 100644 index 0af8d1c4d8..0000000000 --- a/core/observer/network/protocols/dns/parser.cpp +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include -#include -#include "parser.h" -#include "logger/Logger.h" -#include "interface/helper.h" -#include "StringTools.h" -#include "metas/ServiceMetaCache.h" -#include - -namespace logtail { - -ParseResult DNSProtocolParser::OnPacket(PacketType pktType, - MessageType msgType, - PacketEventHeader* header, - const char* pkt, - int32_t pktSize, - int32_t pktRealSize) { - DNSParser dns(pkt, pktSize); - try { - dns.parse(); - } catch (const std::runtime_error& re) { - LOG_DEBUG(sLogger, - ("dns_parse_fail", re.what())("data", charToHexString(pkt, pktSize, pktSize))( - "srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } catch (const std::exception& ex) { - LOG_DEBUG(sLogger, - ("dns_parse_fail", ex.what())("data", charToHexString(pkt, pktSize, pktSize))( - "srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } catch (...) { - LOG_DEBUG( - sLogger, - ("dns_parse_fail", "Unknown failure occurred when parse")("data", charToHexString(pkt, pktSize, pktSize))( - "srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } - if (dns.OK()) { - bool insertSuccess = true; - uint16_t txid = dns.dnsHeader.txid; - if (dns.packetType == DNSPacketRequest && !dns.dnsRequest.requests.empty()) { - auto resp = mRespCache.find(txid); - std::string queryHosts; - std::string queryTypes; - - if (dns.dnsRequest.requests.size() == 1) { // 大部分情况 - queryHosts = dns.dnsRequest.requests.at(0).queryHost; - queryTypes = ToString(dns.dnsRequest.requests.at(0).queryType); - } else { - for (auto iter = dns.dnsRequest.requests.begin(); iter < dns.dnsRequest.requests.end(); iter++) { - queryHosts.append(std::string(iter->queryHost)).append(","); - queryTypes.append(std::string(ToString(iter->queryType))).append(","); - } - // remove last , - if (!queryHosts.empty()) { - queryHosts.pop_back(); - } - if (!queryTypes.empty()) { - queryTypes.pop_back(); - } - } - - auto req = new DNSRequestInfo(header->TimeNano, std::move(queryHosts), std::move(queryTypes), pktRealSize); - - if (resp == mRespCache.end()) { - mReqCache.insert(std::make_pair(txid, req)); - } else { - insertSuccess = stitcher(req, resp->second); - delete req; - delete resp->second; - mRespCache.erase(resp); - } - } else if (dns.packetType == DNSPacketResponse) { - // currently, only collect the dns host outside the kubernetes cluster - const static std::string sKubernetesDnsFlag = "cluster.local"; - const static auto sHostManager = ServiceMetaManager::GetInstance(); - for (auto& response : dns.dnsResponse.responses) { - if (!EndWith(response.queryHost, sKubernetesDnsFlag)) { - sHostManager->AddHostName(header->PID, response.queryHost, response.value); - } - } - auto resp = new DNSResponseInfo(header->TimeNano, dns.dnsResponse.answerCount, pktRealSize); - auto req = mReqCache.find(txid); - if (req == mReqCache.end()) { - mRespCache.insert(std::make_pair(txid, resp)); - } else { - insertSuccess = stitcher(req->second, resp); - delete resp; - delete req->second; - mReqCache.erase(req); - } - } - return insertSuccess ? ParseResult_OK : ParseResult_Drop; - } - return ParseResult_Fail; -} - - -DNSResponseInfo::DNSResponseInfo(uint64_t timeNano, uint16_t answerCount, int32_t respBytes) - : TimeNano(timeNano), RespBytes(respBytes), AnswerCount(answerCount) { -} - -DNSRequestInfo::DNSRequestInfo(uint64_t timeNano, std::string&& queryRecord, std::string&& queryType, int32_t reqBytes) - : TimeNano(timeNano), ReqBytes(reqBytes), QueryRecord(queryRecord), QueryType(queryType) { -} - - -bool DNSProtocolParser::stitcher(DNSRequestInfo* requestInfo, DNSResponseInfo* responseInfo) { - DNSProtocolEvent dnsEvent; - dnsEvent.Key.ReqResource = std::move(requestInfo->QueryRecord); - dnsEvent.Key.ReqType = std::move(requestInfo->QueryType); - dnsEvent.Key.RespStatus = responseInfo->AnswerCount > 0 ? 1 : 0; - dnsEvent.Key.ConnKey = mKey; - dnsEvent.Info.LatencyNs = responseInfo->TimeNano - requestInfo->TimeNano; - if (dnsEvent.Info.LatencyNs < 0) { - dnsEvent.Info.LatencyNs = 0; - } - dnsEvent.Info.ReqBytes = requestInfo->ReqBytes; - dnsEvent.Info.RespBytes = responseInfo->RespBytes; - return mAggregator->AddEvent(std::move(dnsEvent)); -} - -bool DNSProtocolParser::GarbageCollection(size_t size_limit_bytes, uint64_t expireTimeNs) { - for (auto item = mReqCache.begin(); item != mReqCache.end();) { - if (item->second->TimeNano < expireTimeNs) { - delete item->second; - item = mReqCache.erase(item); - continue; - } - item++; - } - - for (auto item = mRespCache.begin(); item != mRespCache.end();) { - if (item->second->TimeNano < expireTimeNs) { - delete item->second; - item = mRespCache.erase(item); - continue; - } - item++; - } - return mRespCache.empty() && mReqCache.empty(); -} - -int32_t DNSProtocolParser::GetCacheSize() { - return this->mReqCache.size() + this->mRespCache.size(); -} - -} // end of namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/dns/parser.h b/core/observer/network/protocols/dns/parser.h deleted file mode 100644 index 989890caf0..0000000000 --- a/core/observer/network/protocols/dns/parser.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include "network/protocols/dns/type.h" -#include "observer/interface/network.h" -#include "inner_parser.h" - -namespace logtail { - - -struct DNSRequestInfo { - uint64_t TimeNano; - int32_t ReqBytes; - std::string QueryRecord; - std::string QueryType; - -public: - DNSRequestInfo(uint64_t timeNano, std::string&& queryRecord, std::string&& queryType, int32_t reqBytes); -}; - -struct DNSResponseInfo { - uint64_t TimeNano; - int32_t RespBytes; - uint16_t AnswerCount; - - DNSResponseInfo(uint64_t timeNano, uint16_t answerCount, int32_t respBytes); -}; - - -// 协议解析器,流式解析,解析到某个协议后,自动放到aggregator中聚合 -class DNSProtocolParser { -public: - DNSProtocolParser(DNSProtocolEventAggregator* aggregator, PacketEventHeader* header) - : mAggregator(aggregator), mKey(header) {} - - ~DNSProtocolParser() { - for (auto& p : mReqCache) { - delete p.second; - } - for (auto& p : mRespCache) { - delete p.second; - } - } - - static DNSProtocolParser* Create(DNSProtocolEventAggregator* aggregator, PacketEventHeader* header) { - return new DNSProtocolParser(aggregator, header); - } - - static void Delete(DNSProtocolParser* parser) { delete parser; } - - // Packet到达的时候会Call,所有参数都会告诉类型(PacketType,MessageType) - ParseResult OnPacket(PacketType pktType, - MessageType msgType, - PacketEventHeader* header, - const char* pkt, - int32_t pktSize, - int32_t pktRealSize); - - // GC,把内部没有完成Event匹配的消息按照SizeLimit和TimeOut进行清理 - // 返回值,如果是true代表还有数据,false代表无数据 - bool GarbageCollection(size_t size_limit_bytes, uint64_t expireTimeNs); - - int32_t GetCacheSize(); - -private: - bool stitcher(DNSRequestInfo* requestInfo, DNSResponseInfo* responseInfo); - - DNSProtocolEventAggregator* mAggregator = NULL; - std::unordered_map mReqCache; - std::unordered_map mRespCache; - CommonAggKey mKey; - - friend class ProtocolDnsUnittest; -}; - -} // namespace logtail diff --git a/core/observer/network/protocols/dns/type.h b/core/observer/network/protocols/dns/type.h deleted file mode 100644 index fcc19f6def..0000000000 --- a/core/observer/network/protocols/dns/type.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "network/protocols/category.h" -#include "network/protocols/common.h" - -namespace logtail { -using DNSProtocolEventKey = RequestAggKey; -using DNSProtocolEvent = CommonProtocolEvent; -using DNSProtocolEventAggItem = CommonProtocolEventAggItem; -using DNSProtocolEventAggItemManager = CommonProtocolEventAggItemManager; -using DNSProtocolEventAggregator = CommonProtocolEventAggregator; - -} // namespace logtail diff --git a/core/observer/network/protocols/http/inner_parser.h b/core/observer/network/protocols/http/inner_parser.h deleted file mode 100644 index e2d50e30c4..0000000000 --- a/core/observer/network/protocols/http/inner_parser.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include - -#include "common/protocol/picohttpparser/picohttpparser.h" -#include "network/protocols/utils.h" - -namespace logtail { - -enum class HTTPBodyPacketCategory { - Unkonwn, - Chunked, - MoreContent, - SingleContent, -}; - -struct HTTPRequestPacket { - SlsStringPiece method; - SlsStringPiece url; -}; - -struct HTTPResponsePacket { - SlsStringPiece msg; - int code; -}; - -struct HTTPCommonPacket { - int version; - size_t headersNum{50}; - struct phr_header headers[50]{}; -}; - -struct Packet { - union Msg { - struct HTTPRequestPacket req; - struct HTTPResponsePacket resp; - - Msg() {} - } msg; - - HTTPCommonPacket common; -}; - - -struct HTTPParser { - void ParseRequest(const char* buf, size_t size) { - packetLen = size; - status = phr_parse_request(buf, - size, - &packet.msg.req.method.mPtr, - &packet.msg.req.method.mLen, - &packet.msg.req.url.mPtr, - &packet.msg.req.url.mLen, - &packet.common.version, - packet.common.headers, - &packet.common.headersNum, - /*last_len*/ 0); - } - - void ParseResp(const char* buf, size_t size) { - packetLen = size; - status = phr_parse_response(buf, - size, - &packet.common.version, - &packet.msg.resp.code, - &packet.msg.resp.msg.mPtr, - &packet.msg.resp.msg.mLen, - packet.common.headers, - &packet.common.headersNum, - /*last_len*/ 0); - } - - void ParseBodyType() { - // Content-Length - static const std::string sContentName("Content-Length"); - auto val = ReadHeaderVal(sContentName); - if (val.Size() > 0) { - auto len = std::strtol(val.mPtr, NULL, 10); - if (len <= long(packetLen - status)) { - bodyPacketCategory = HTTPBodyPacketCategory::SingleContent; - } else { - bodySize = len; - bodyPacketCategory = HTTPBodyPacketCategory::MoreContent; - } - return; - } - - static const std::string sChunkedName("Transfer-Encoding"); - static const std::string sChunkedVal = "chunked"; - val = ReadHeaderVal(sChunkedName); - if (val.Size() > 0 && val == sChunkedVal) { - bodyPacketCategory = HTTPBodyPacketCategory::Chunked; - return; - } - } - - SlsStringPiece ReadHeaderVal(const std::string& name) { - for (size_t i = 0; i < this->packet.common.headersNum; ++i) { - if (std::strncmp(packet.common.headers[i].name, name.c_str(), packet.common.headers[i].name_len) == 0) { - return {packet.common.headers[i].value, packet.common.headers[i].value_len}; - } - } - return {}; - } - - static int isChunkedMsg(const char* buf, size_t size) { - if (size > 4) { - bool commonChunked = *(buf + size - 1) == '\n' && *(buf + size - 2) == '\r'; - bool endChunked = *(buf + size - 3) == '\n' && *(buf + size - 4) == '\r'; - if (commonChunked && endChunked) { - return 0; - } - if (commonChunked) { - return 1; - } - return -1; - } - return -1; - } - - Packet packet; - int status = 0; - int bodySize = 0; - size_t packetLen = 0; - HTTPBodyPacketCategory bodyPacketCategory; - -private: -}; - -} // end of namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/http/parser.cpp b/core/observer/network/protocols/http/parser.cpp deleted file mode 100644 index 3088bb777b..0000000000 --- a/core/observer/network/protocols/http/parser.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include -#include -#include "parser.h" -#include "inner_parser.h" -#include "observer/interface/helper.h" -#include "logger/Logger.h" - -namespace logtail { - -ParseResult HTTPProtocolParser::OnPacket(PacketType pktType, - MessageType msgType, - PacketEventHeader* header, - const char* pkt, - int32_t pktSize, - int32_t pktRealSize) { - // Currently, only accept packets with header. - // if (msgType == MessageType_Request) { - // if (reqCategory == HTTPBodyPacketCategory::Chunked) { - // int res = HTTPParser::isChunkedMsg(pkt, pktSize); - // if (res == 0) { - // reqCategory = HTTPBodyPacketCategory::Unkonwn; - // return ParseResult_Partial; - // } else if (res > 0) { - // return ParseResult_Partial; - // } else { - // reqCategory = HTTPBodyPacketCategory::Unkonwn; - // } - // } else if (reqCategory == HTTPBodyPacketCategory::MoreContent) { - // if (pktRealSize == mReqMoreContentCapacity) { - // reqCategory = HTTPBodyPacketCategory::Unkonwn; - // mReqMoreContentCapacity=0; - // return ParseResult_Partial; - // } else if (pktRealSize < mReqMoreContentCapacity) { - // mReqMoreContentCapacity -= pktRealSize; - // return ParseResult_Partial; - // } else { - // reqCategory = HTTPBodyPacketCategory::Unkonwn; - // } - // } - // }else if(msgType == MessageType_Response){ - // if (respCategory == HTTPBodyPacketCategory::Chunked) { - // int res = HTTPParser::isChunkedMsg(pkt, pktSize); - // if (res == 0) { - // mRespMoreContentCapacity=0; - // respCategory = HTTPBodyPacketCategory::Unkonwn; - // return ParseResult_Partial; - // } else if (res > 0) { - // return ParseResult_Partial; - // } else { - // respCategory = HTTPBodyPacketCategory::Unkonwn; - // } - // } else if (respCategory == HTTPBodyPacketCategory::MoreContent) { - // if (pktRealSize == mRespMoreContentCapacity) { - // respCategory = HTTPBodyPacketCategory::Unkonwn; - // return ParseResult_Partial; - // } else if (pktRealSize < mRespMoreContentCapacity) { - // mRespMoreContentCapacity -= pktRealSize; - // return ParseResult_Partial; - // } else { - // respCategory = HTTPBodyPacketCategory::Unkonwn; - // } - // } - // } - - HTTPParser parser; - if (msgType == MessageType_Request) { - parser.ParseRequest(pkt, pktSize); - // reqCategory = parser.bodyPacketCategory; - // mReqMoreContentCapacity = parser.bodySize; - } else if (msgType == MessageType_Response) { - parser.ParseResp(pkt, pktSize); - // this->respCategory = parser.bodyPacketCategory; - // this->mRespMoreContentCapacity = parser.bodySize; - } - LOG_TRACE(sLogger, - ("http got data", std::string(pkt, pktSize))("message_type", MessageTypeToString(msgType))( - "raw_data", charToHexString(pkt, pktSize, pktSize))("connection_id", header->SockHash)); - if (parser.status == -1) { - LOG_DEBUG(sLogger, - ("http_parse_fail", "")("data", charToHexString(pkt, pktSize, pktSize))("srcPort", header->SrcPort)( - "dstPort", header->DstPort)("message_type", MessageTypeToString(msgType))); - return ParseResult_Fail; - } - - - bool insertSuccess = true; - if (msgType == MessageType_Request) { - std::string host = parser.ReadHeaderVal("Host").ToString(); - if (host.empty()) { - if (pktType == PacketType_Out) { - host = SockAddressToString(header->DstAddr); - } else { - host = SockAddressToString(header->SrcAddr); - } - } - int pos = parser.packet.msg.req.url.Find('?'); - std::string url = std::string(parser.packet.msg.req.url.mPtr, pos == -1 ? parser.packet.msg.req.url.mLen : pos); - insertSuccess = mCache.InsertReq([&](HTTPRequestInfo* req) { - req->TimeNano = header->TimeNano; - req->Method = parser.packet.msg.req.method.ToString(); - req->URL = std::move(url); - req->Version = std::to_string(parser.packet.common.version); - req->Host = std::move(host); - req->ReqBytes = pktRealSize; - LOG_TRACE(sLogger, ("http insert req hash", header->SockHash)("data", req->ToString())); - }); - } else if (msgType == MessageType_Response) { - insertSuccess = mCache.InsertResp([&](HTTPResponseInfo* info) { - info->TimeNano = header->TimeNano; - info->RespCode = parser.packet.msg.resp.code; - info->RespBytes = pktRealSize; - LOG_TRACE(sLogger, ("http insert resp hash", header->SockHash)("data", info->ToString())); - }); - } - return insertSuccess ? ParseResult_OK : ParseResult_Drop; -} - -bool HTTPProtocolParser::GarbageCollection(size_t size_limit_bytes, uint64_t expireTimeNs) { - return mCache.GarbageCollection(expireTimeNs); -} - -int32_t HTTPProtocolParser::GetCacheSize() { - return mCache.GetRequestsSize() + mCache.GetResponsesSize(); -} - -} // end of namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/http/parser.h b/core/observer/network/protocols/http/parser.h deleted file mode 100644 index 175adddcea..0000000000 --- a/core/observer/network/protocols/http/parser.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include "network/protocols/http/type.h" -#include "observer/interface/network.h" -#include "inner_parser.h" - -namespace logtail { - -struct HTTPRequestInfo { - uint64_t TimeNano; - std::string Method; - std::string URL; - std::string Version; - std::string Host; - int32_t ReqBytes; - - friend std::ostream& operator<<(std::ostream& os, const HTTPRequestInfo& info) { - os << "TimeNano: " << info.TimeNano << " Method: " << info.Method << " URL: " << info.URL - << " Version: " << info.Version << " Host: " << info.Host << " ReqBytes: " << info.ReqBytes; - return os; - } - - std::string ToString() { - std::stringstream ss; - ss << *this; - return ss.str(); - } -}; -struct HTTPResponseInfo { - uint64_t TimeNano; - int16_t RespCode; - int32_t RespBytes; - - friend std::ostream& operator<<(std::ostream& os, const HTTPResponseInfo& info) { - os << "TimeNano: " << info.TimeNano << " RespCode: " << info.RespCode << " RespBytes: " << info.RespBytes; - return os; - } - std::string ToString() { - std::stringstream ss; - ss << *this; - return ss.str(); - } -}; - -typedef CommonCache HttpCache; - - -// 协议解析器,流式解析,解析到某个协议后,自动放到aggregator中聚合 -class HTTPProtocolParser { -public: - explicit HTTPProtocolParser(HTTPProtocolEventAggregator* aggregator, PacketEventHeader* header) - : mCache(HttpCache(aggregator)), mKey(header) { - mCache.BindConvertFunc( - [&](HTTPRequestInfo* requestInfo, HTTPResponseInfo* responseInfo, HTTPProtocolEvent& event) -> bool { - event.Info.LatencyNs = responseInfo->TimeNano - requestInfo->TimeNano; - if (event.Info.LatencyNs < 0) { - event.Info.LatencyNs = 0; - } - event.Info.ReqBytes = requestInfo->ReqBytes; - event.Info.RespBytes = responseInfo->RespBytes; - event.Key.ReqType = std::move(requestInfo->Method); - event.Key.ReqDomain = std::move(requestInfo->Host); - event.Key.ReqResource = std::move(requestInfo->URL); - event.Key.Version = std::move(requestInfo->Version); - event.Key.RespCode = responseInfo->RespCode; - event.Key.ConnKey = mKey; - return true; - }); - } - - static HTTPProtocolParser* Create(HTTPProtocolEventAggregator* aggregator, PacketEventHeader* header) { - return new HTTPProtocolParser(aggregator, header); - } - - static void Delete(HTTPProtocolParser* parser) { delete parser; } - - // Packet到达的时候会Call,所有参数都会告诉类型(PacketType,MessageType) - ParseResult OnPacket(PacketType pktType, - MessageType msgType, - PacketEventHeader* header, - const char* pkt, - int32_t pktSize, - int32_t pktRealSize); - - // GC,把内部没有完成Event匹配的消息按照SizeLimit和TimeOut进行清理 - // 返回值,如果是true代表还有数据,false代表无数据 - bool GarbageCollection(size_t size_limit_bytes, uint64_t expireTimeNs); - - int32_t GetCacheSize(); - -private: - HttpCache mCache; - CommonAggKey mKey; - - friend class ProtocolHttpUnittest; -}; - - -} // end of namespace logtail diff --git a/core/observer/network/protocols/http/type.h b/core/observer/network/protocols/http/type.h deleted file mode 100644 index 7709e88dfd..0000000000 --- a/core/observer/network/protocols/http/type.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "interface/protocol.h" -#include "network/protocols/common.h" -#include "network/protocols/category.h" -#include -#include -#include "common/xxhash/xxhash.h" - -namespace logtail { - -using HTTPProtocolEventKey = RequestAggKey; -using HTTPProtocolEvent = CommonProtocolEvent; -using HTTPProtocolEventAggItem = CommonProtocolEventAggItem; -using HTTPProtocolEventAggItemManager = CommonProtocolEventAggItemManager; -using HTTPProtocolEventAggregator = CommonProtocolEventAggregator; - -} // namespace logtail diff --git a/core/observer/network/protocols/infer.h b/core/observer/network/protocols/infer.h deleted file mode 100644 index 51b5bff380..0000000000 --- a/core/observer/network/protocols/infer.h +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include -#include -#include "observer/interface/type.h" -#include "observer/interface/protocol.h" -#include "interface/network.h" -#include "interface/helper.h" - - -namespace logtail { -enum class PostgreSqlTag { - // Frontend (F) & Backend (B). - CopyData = 'd', - CopyDone = 'c', - - // F. - Query = 'Q', - CopyFail = 'f', - Close = 'C', - Bind = 'B', - Passwd = 'p', - Parse = 'P', - Desc = 'D', - Sync = 'S', - Execute = 'E', - - // B. - DataRow = 'D', - ReadyForQuery = 'Z', - CopyOutResponse = 'H', - CopyInResponse = 'G', - ErrResp = 'E', - CmdComplete = 'C', - CloseComplete = '3', - BindComplete = '2', - EmptyQuery = 'I', - Key = 'K', - Auth = 'R', - ParseComplete = '1', - ParamDesc = 't', - RowDesc = 'T', - NoData = 'n', - - Unknown = '\0', -}; - -} - - -static __inline MessageType -infer_http_message(const char* buf, int32_t count, const uint16_t srcPort, const uint16_t dstPort) { - // Smallest HTTP response is 17 characters: - // HTTP/1.1 200 OK\r\n - // Smallest HTTP response is 16 characters: - // GET x HTTP/1.1\r\n - if (count < 16) { - return MessageType_None; - } - if (srcPort == 80) { - return MessageType_Response; - } else if (dstPort == 80) { - return MessageType_Request; - } - - if (buf[0] == 'H' && buf[1] == 'T' && buf[2] == 'T' && buf[3] == 'P') { - return MessageType_Response; - } - if (buf[0] == 'G' && buf[1] == 'E' && buf[2] == 'T') { - return MessageType_Request; - } - if (buf[0] == 'H' && buf[1] == 'E' && buf[2] == 'A' && buf[3] == 'D') { - return MessageType_Request; - } - if (buf[0] == 'P' && buf[1] == 'O' && buf[2] == 'S' && buf[3] == 'T') { - return MessageType_Request; - } - if (buf[0] == 'P' && buf[1] == 'U' && buf[2] == 'T') { - return MessageType_Request; - } - if (buf[0] == 'D' && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'E' && buf[4] == 'T' && buf[5] == 'E') { - return MessageType_Request; - } - return MessageType_None; -} - -static __inline MessageType infer_mysql_message( - const char* buf, int32_t count, int32_t pktRealSize, const uint16_t srcPort, const uint16_t dstPort) { - // MySQL packets start with a 3-byte packet length and a 1-byte packet number. - // The 5th byte on a request contains a command that tells the type. - if (count < 5) { - return MessageType_None; - } - if (srcPort == 3306) { - return MessageType_Response; - } else if (dstPort == 3306) { - return MessageType_Request; - } - - int32_t pktLen = (uint8_t)buf[0] + ((uint8_t)buf[1] << 8) + ((uint8_t)buf[2] << 16); - uint8_t pktNum = buf[3]; - uint8_t com = buf[4]; - - // MySQL报文头一共4个字节,pktLen+4个字节=Payload的长度 - if (pktLen != pktRealSize - 4) { // mysql报文一般pktLen和pktRealSize是相等的 - if (pktNum == 1 && pktLen == 1) { - return MessageType_Response; // result set response - } - } else { - if (pktNum == 0) { - if (com == 0x0a) { - // server greeting - return MessageType_Response; - } else { - return MessageType_Request; - } - } else if (pktNum == 1) { - if (pktLen > 36 && count > 40 && buf[18] == 0 && buf[25] == 0) { - // login request - return MessageType_Request; - } else { - return MessageType_Response; - } - } - } - return MessageType_None; -} - -static __inline bool is_pgsql_message(const char* buf, int32_t count, const uint16_t srcPort, const uint16_t dstPort) { - if (count < 5) { - // Regular Message: Tag(char), Length(int32) - // StartUp Message: Length(int32), Major(int16),Minor(int16) - return false; - } - - if (srcPort == 5432 || dstPort == 5432) { - return true; - } - - { - // check startup message - static char pgsql[] = "\x00\x03\x00\x00"; - auto tag = static_cast(buf[0]); - if (tag == logtail::PostgreSqlTag::Unknown) { - uint32_t len = ntohl(*(uint32_t*)buf); - if (len == (uint32_t)count && memcmp(buf + 4, pgsql, 4) == 0) { - return true; - } - return false; - } - } - - // check other message - uint32_t pos = 0; - while (true) { - auto tag = static_cast(buf[pos++]); - if (pos + 4 > (uint32_t)count) { - return false; - } - if (!(tag == logtail::PostgreSqlTag::CopyData || tag == logtail::PostgreSqlTag::CopyDone - || tag == logtail::PostgreSqlTag::Query || tag == logtail::PostgreSqlTag::CopyFail - || tag == logtail::PostgreSqlTag::Close || tag == logtail::PostgreSqlTag::Bind - || tag == logtail::PostgreSqlTag::Passwd || tag == logtail::PostgreSqlTag::Parse - || tag == logtail::PostgreSqlTag::Desc || tag == logtail::PostgreSqlTag::Sync - || tag == logtail::PostgreSqlTag::Execute || tag == logtail::PostgreSqlTag::DataRow - || tag == logtail::PostgreSqlTag::ReadyForQuery || tag == logtail::PostgreSqlTag::CopyOutResponse - || tag == logtail::PostgreSqlTag::CopyInResponse || tag == logtail::PostgreSqlTag::ErrResp - || tag == logtail::PostgreSqlTag::CmdComplete || tag == logtail::PostgreSqlTag::CloseComplete - || tag == logtail::PostgreSqlTag::BindComplete || tag == logtail::PostgreSqlTag::EmptyQuery - || tag == logtail::PostgreSqlTag::Key || tag == logtail::PostgreSqlTag::Auth - || tag == logtail::PostgreSqlTag::ParseComplete || tag == logtail::PostgreSqlTag::ParamDesc - || tag == logtail::PostgreSqlTag::RowDesc || tag == logtail::PostgreSqlTag::NoData)) { - return false; - } - uint32_t len = ntohl(*(uint32_t*)(buf + pos)); - pos += len; - if (pos == (uint32_t)count) { - return true; - } - if (pos > (uint32_t)count) { - return false; - } - } -} - - -static __inline MessageType -infer_dns_message(const char* buf, size_t count, const uint16_t srcPort, const uint16_t dstPort) { - if (srcPort == 53) { - return MessageType_Response; - } else if (dstPort == 53) { - return MessageType_Request; - } - - const int kDNSHeaderSize = 12; - - // Use the maximum *guaranteed* UDP packet size as the max DNS message size. - // UDP packets can be larger, but this is the typical maximum size for DNS. - const int kMaxDNSMessageSize = 512; - - // Maximum number of resource records. - // https://stackoverflow.com/questions/6794926/how-many-a-records-can-fit-in-a-single-dns-response - const int kMaxNumRR = 25; - - if (count < kDNSHeaderSize || count > kMaxDNSMessageSize) { - return MessageType_None; - } - - const uint8_t* ubuf = (const uint8_t*)buf; - - uint16_t flags = (ubuf[2] << 8) + ubuf[3]; - uint16_t num_questions = (ubuf[4] << 8) + ubuf[5]; - uint16_t num_answers = (ubuf[6] << 8) + ubuf[7]; - uint16_t num_auth = (ubuf[8] << 8) + ubuf[9]; - uint16_t num_addl = (ubuf[10] << 8) + ubuf[11]; - - bool qr = (flags >> 15) & 0x1; - uint8_t opcode = (flags >> 11) & 0xf; - uint8_t zero = (flags >> 6) & 0x1; - - if (zero != 0) { - return MessageType_None; - } - - if (opcode != 0) { - return MessageType_None; - } - - if (num_questions == 0 || num_questions > 10) { - return MessageType_None; - } - - uint32_t num_rr = num_questions + num_answers + num_auth + num_addl; - if (num_rr > kMaxNumRR) { - return MessageType_None; - } - - return (qr == 0) ? MessageType_Request : MessageType_Response; -} - -static __inline bool is_redis_message(const char* buf, int32_t count, const uint16_t srcPort, const uint16_t dstPort) { - // Redis messages start with an one-byte type marker, and end with \r\n terminal sequence. - if (count < 3) { - return false; - } - if (srcPort == 6379 || dstPort == 6379) { - return true; - } - - const char first_byte = buf[0]; - - if ( // Simple strings start with + - first_byte != '+' && - // Errors start with - - first_byte != '-' && - // Integers start with : - first_byte != ':' && - // Bulk strings start with $ - first_byte != '$' && - // Arrays start with * - first_byte != '*') { - return false; - } - - // The last two chars are \r\n, the terminal sequence of all Redis messages. - if (buf[count - 2] != '\r') { - return false; - } - if (buf[count - 1] != '\n') { - return false; - } - - return true; -} - -static __inline std::tuple -infer_protocol(PacketEventHeader* header, PacketType pktType, const char* pkt, int32_t pktSize, int32_t pktRealSize) { - std::tuple ret(ProtocolType_None, MessageType_None); - if ((std::get<1>(ret) = infer_http_message(pkt, pktSize, header->SrcPort, header->DstPort)) != MessageType_None) { - std::get<0>(ret) = ProtocolType_HTTP; - } else if ((std::get<1>(ret) = infer_dns_message(pkt, pktSize, header->SrcPort, header->DstPort)) - != MessageType_None) { - std::get<0>(ret) = ProtocolType_DNS; - } else if ((std::get<1>(ret) = infer_mysql_message(pkt, pktSize, pktRealSize, header->SrcPort, header->DstPort)) - != MessageType_None) { - std::get<0>(ret) = ProtocolType_MySQL; - } else if (is_redis_message(pkt, pktSize, header->SrcPort, header->DstPort)) { - // Redis协议 从data中无法区分MessageType,依赖tcp协议层面的判断 - std::get<0>(ret) = ProtocolType_Redis; - std::get<1>(ret) = InferRequestOrResponse(pktType, header); - } else if (is_pgsql_message(pkt, pktSize, header->SrcPort, header->DstPort)) { - std::get<0>(ret) = ProtocolType_PgSQL; - std::get<1>(ret) = InferRequestOrResponse(pktType, header); - } - - return ret; -} \ No newline at end of file diff --git a/core/observer/network/protocols/mysql/README.md b/core/observer/network/protocols/mysql/README.md deleted file mode 100644 index 48644eb6f5..0000000000 --- a/core/observer/network/protocols/mysql/README.md +++ /dev/null @@ -1,4 +0,0 @@ -## refer - -http://hutaow.com/blog/2013/11/06/mysql-protocol-analysis/ -https://github.com/PyMySQL/PyMySQL \ No newline at end of file diff --git a/core/observer/network/protocols/mysql/inner_parser.cpp b/core/observer/network/protocols/mysql/inner_parser.cpp deleted file mode 100644 index 07cabc2618..0000000000 --- a/core/observer/network/protocols/mysql/inner_parser.cpp +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include -#include - -#include "inner_parser.h" -#include "Logger.h" - -namespace logtail { - -uint64_t MySQLParser::readLengthCodedInt() { - uint8_t v = readUint8(); - - if (v < 251) { - return v; - } else if (v == 252) { - return readUint16(); - } else if (v == 253) { - // mysql报文为小端模式 - auto data = readChar(false); - positionCommit(3); - return data[0] + (data[1] << 8) + (data[2] << 16); - } else if (v == 254) { - return readUint64(); - } else { // 251 - return 0; - } -} - -SlsStringPiece MySQLParser::readLeft() { - uint32_t leftSize = getLeftSize(); - - const char* s = readChar(true); - - positionCommit(leftSize - 1); - SlsStringPiece value(s, leftSize); - return value; -} - -SlsStringPiece MySQLParser::readFixSizeString(int32_t size) { - const char* s = readChar(true); - positionCommit(size - 1); - SlsStringPiece value(s, size); - return value; -} - - -SlsStringPiece MySQLParser::readLengthCodedString() { - uint64_t counter = readLengthCodedInt(); - return readFixSizeString(counter); -} - -uint32_t MySQLParser::readPacketLength() { - auto data = readChar(false); - positionCommit(3); - return data[0] + (data[1] << 8) + (data[2] << 16); -} - -uint8_t MySQLParser::readPacketNum() { - return readUint8(); -} - - -void MySQLParser::parseGreetingPacket() { - // uint8_t protocol = readUint8(); - // SlsStringPiece version = readUntil('\0'); - // uint32_t threadId = readUint32(); - // const char * slatStart = readChar(true); - // this->positionCommit(8); - // SlsStringPiece salt(slatStart, 8); - // uint16_t cap = readUint16(); - // uint8_t chCoding = readUint8(); - // uint16_t serverStatus = readUint16(); - // uint16_t extCap = readUint16(); - // uint8_t authPluinLen = readUint8(); - // - // // 10个填充字符 - // this->positionCommit(10); - // SlsStringPiece salt2 = readUntil('\0'); - // SlsStringPiece autPluinName = readUntil('\0'); -} - -void MySQLParser::parseLoginPacket() { - // uint16_t clientCap = readUint16(); - // uint16_t clientCapExt = readUint16(); - // uint32_t maxPacketSize = readUint32(); - // uint8_t clientCharset = readUint8(); - // - // // skip 23个填充字符 - // positionCommit(23); - // do nothing for username, database because which are optional fields. -} - -void MySQLParser::parseQueryPacket(MySQLCommand command) { - // for com_query only collect command type to avoid plain sql influence aggregator performance - if (command == MySQLCommand::comQuery) { - SlsStringPiece val = readUntil(' ', true); - mysqlPacketQuery.sql = val; - } else { - SlsStringPiece val = readLeft(); - mysqlPacketQuery.sql = val; - } -} - -void MySQLParser::parseOKPacket() { - // uint8_t code = readUint8(); - // uint32_t affected = readLengthCodedInt(); - // uint32_t lastId = readLengthCodedInt(); - // uint32_t serverStatus = readUint16(); - // uint32_t warnings = readUint16(); -} - -void MySQLParser::parseErrPacket() { - // uint8_t code = readUint8(); - // uint8_t errCode = readUint16(); - // uint8_t serverStateFlag = readUint8(); - // SlsStringPiece serverState = readFixSizeString(5); - // SlsStringPiece errorMsg = readLeft(); -} - -void MySQLParser::parseEOFPacket() { - // uint8_t eofVal = readUint8(); - // uint16_t warnings = readUint16(); - // uint16_t serverStatus = readUint16(); -} - -bool MySQLParser::packetLengthCheckSkip(uint32_t startPosition, uint32_t packetLen) { - if ((currPostion - startPosition) != (packetLen)) { - uint32_t left = packetLen - (currPostion - startPosition); - if (left < 0) { - setParseFail("bad packet found"); - return false; - } - positionCommit(left); - } - return true; -} - -uint32_t MySQLParser::parseFieldHeaderPacket() { - uint32_t startPosition = currPostion; - uint32_t fieldCount = readLengthCodedInt(); - if (!packetLengthCheckSkip(startPosition, packet.packetLen)) { - return 0; - } - return fieldCount; -} - -void MySQLParser::parseFieldPacket() { - // uint32_t startPosition = currPostion; - // SlsStringPiece catalog = readLengthCodedString(); - // SlsStringPiece database = readLengthCodedString(); - // SlsStringPiece table = readLengthCodedString(); - // SlsStringPiece tableOriName = readLengthCodedString(); - // SlsStringPiece fieldName = readLengthCodedString(); - // SlsStringPiece fieldOriName = readLengthCodedString(); - // - // // 跳过一位填充值 - // positionCommit(1); - // uint16_t fieldCharset = readUint16(); - // uint32_t fieldLength = readUint32(); - // uint8_t fieldType = readUint8(); - // uint16_t fieldFlag = readUint16(); - // uint8_t decimal = readUint8(); - // packetLengthCheckSkip(startPosition, packet.packetLen); -} - -void MySQLParser::parseRowPacket(int fieldCount) { - // for(int i=0; i= 7) { - return true; - } - return false; -} - -bool MySQLParser::isErrPacket() { - const uint8_t v = readUint8(false); - if (v == 0xff && packet.packetLen >= 7) { - return true; - } - return false; -} - -void MySQLParser::parsePacketHeader() { - packet.packetLen = readPacketLength(); - packet.packetNum = readPacketNum(); -} - -void MySQLParser::parseResultSetPacket() { - // uint32_t fieldCount = parseFieldHeaderPacket(); - // for(int32_t i=0; ipacket.packetLen)("num", (int)this->packet.packetNum)); - if (packet.packetNum == 0) { - // Server Greeting | Query - uint8_t v = readUint8(false); - if (v == 0x0a && packet.packetLen > 60) { - LOG_TRACE(sLogger, ("[MYSQL TYPE] ", "Greeting")); - mysqlPacketType = MySQLPacketTypeServerGreeting; - parseGreetingPacket(); - } else { - auto com = static_cast(readUint8()); - LOG_TRACE(sLogger, ("[MYSQL TYPE] request comm", (int)com)); - switch (com) { - case MySQLCommand::comInitDB: - case MySQLCommand::comFieldList: - case MySQLCommand::comRefresh: - case MySQLCommand::comShutdown: - case MySQLCommand::comStatistics: - case MySQLCommand::comProcessInfo: - case MySQLCommand::comConnect: - case MySQLCommand::comProcessKill: - case MySQLCommand::comDebug: - case MySQLCommand::comPing: - case MySQLCommand::comTime: - case MySQLCommand::comDelayedInsert: - case MySQLCommand::comChangeUser: - case MySQLCommand::comBinlogDump: - case MySQLCommand::comTableDump: - case MySQLCommand::comConnectOut: - case MySQLCommand::comRegisterSlave: - case MySQLCommand::comStmtExecute: - case MySQLCommand::comStmtReset: - case MySQLCommand::comSetOption: - case MySQLCommand::comStmtFetch: { - mysqlPacketType = MySQLPacketIgnoreStatement; - break; - } - case MySQLCommand::comStmtSendLongData: - case MySQLCommand::comStmtClose: - case MySQLCommand::comQuit: { - mysqlPacketType = MySQLPacketNoResponseStatement; - break; - } - case MySQLCommand::comStmtPrepare: - case MySQLCommand::comCreateDB: - case MySQLCommand::comDropDB: - case MySQLCommand::comQuery: { - mysqlPacketType = MySQLPacketTypeCollectStatement; - parseQueryPacket(com); - break; - } - default: { - setParseFail("got unknown packet"); - } - } - } - } else { - if (packet.packetLen == 1 && packet.packetNum == 1 && pktSize > 5) { - LOG_TRACE(sLogger, ("[MYSQL TYPE]", "Result response")); - mysqlPacketType = MySQLPacketTypeResponse; - mysqlPacketResponse.ok = true; - // parseResultSetPacket(); - } else { - uint8_t v = readUint8(false); - if (packet.packetNum == 1 && v == 0x00) { - LOG_TRACE(sLogger, ("[MYSQL TYPE]", "ok response")); - mysqlPacketType = MySQLPacketTypeResponse; - mysqlPacketResponse.ok = true; - // parseOKPacket(); - } else if (packet.packetNum == 1 && v == 0xff) { - LOG_TRACE(sLogger, ("[MYSQL TYPE]", "error response")); - mysqlPacketType = MySQLPacketTypeResponse; - mysqlPacketResponse.ok = false; - // parseErrPacket(); - } else if (packet.packetLen > 31 && pktSize > 35 && payload[13] == '\0' - && payload[35] == '\0') { // 40 = 36 + 4, 通过抽样payload判断 - LOG_TRACE(sLogger, ("[MYSQL TYPE]", "login Request")); - mysqlPacketType = MySQLPacketTypeClientLogin; - // parseLoginPacket(); - } else { - LOG_TRACE(sLogger, ("[MYSQL TYPE]", "unknown packet")); - setParseFail("got unknown packet"); - } - } - } -} - -void MySQLParser::print() { - std::cout << "===================================================" << std::endl; - std::cout << "print" << std::endl; - std::cout << "type:" << std::endl; - std::cout << this->mysqlPacketType << std::endl; - std::cout << "resp:" << std::endl; - std::cout << this->mysqlPacketResponse.ok << std::endl; - std::cout << "sql:" + this->mysqlPacketQuery.sql.ToString() << std::endl; - std::cout << "===================================================" << std::endl; -} - -} // end of namespace logtail diff --git a/core/observer/network/protocols/mysql/inner_parser.h b/core/observer/network/protocols/mysql/inner_parser.h deleted file mode 100644 index d9db1eaff4..0000000000 --- a/core/observer/network/protocols/mysql/inner_parser.h +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "network/protocols/utils.h" - -namespace logtail { -struct MySQLPacket { - uint32_t packetLen; - uint8_t packetNum; -}; - -enum MySQLPacketType { - MySQLPacketTypeServerGreeting = 1, - MySQLPacketTypeClientLogin = 2, - MySQLPacketTypeResponse = 3, - MySQLPacketTypeCollectStatement = 4, - MySQLPacketIgnoreStatement = 5, - MySQLPacketNoResponseStatement = 6, -}; - -inline std::string MySQLPacketTypeToString(enum MySQLPacketType packetType) { - switch (packetType) { - case MySQLPacketTypeServerGreeting: - return "server_greeting"; - case MySQLPacketTypeClientLogin: - return "client_login"; - case MySQLPacketTypeResponse: - return "response"; - case MySQLPacketTypeCollectStatement: - return "collect_statment"; - case MySQLPacketIgnoreStatement: - return "ignore_statement"; - case MySQLPacketNoResponseStatement: - return "no_response_statement"; - default: - return "unknown"; - } -} - -// https://dev.mysql.com/doc/internals/en/command-phase.html -enum class MySQLCommand { - comSleep = 0, - comQuit = 1, - comInitDB = 2, - comQuery = 3, - comFieldList = 4, - comCreateDB = 5, - comDropDB = 6, - comRefresh = 7, - comShutdown = 8, - comStatistics = 9, - comProcessInfo = 10, - comConnect = 11, - comProcessKill = 12, - comDebug = 13, - comPing = 14, - comTime = 15, - comDelayedInsert = 16, - comChangeUser = 17, - comBinlogDump = 18, - comTableDump = 19, - comConnectOut = 20, - comRegisterSlave = 21, - comStmtPrepare = 22, - comStmtExecute = 23, - comStmtSendLongData = 24, - comStmtClose = 25, - comStmtReset = 26, - comSetOption = 27, - comStmtFetch = 28, -}; - -struct MySQLPacketQuery { - SlsStringPiece sql; -}; - -struct MySQLPacketResponse { - bool ok; -}; - -class MySQLParser : public ProtoParser { -public: - MySQLParser(const char* payload, const size_t pktSize) : ProtoParser(payload, pktSize, false) {} - - void parsePacketHeader(); - - void parseGreetingPacket(); - - void parseLoginPacket(); - - void parseQueryPacket(MySQLCommand command); - - void parseOKPacket(); - - void parseErrPacket(); - - void parseEOFPacket(); - - void parseRowPacket(int fieldCount); - - bool isEofPacket(); - - bool isOKPacket(); - - bool isErrPacket(); - - uint32_t parseFieldHeaderPacket(); // return field count - - void parseFieldPacket(); - - bool packetLengthCheckSkip(uint32_t startPosition, uint32_t packetLen); - - void parseResultSetPacket(); - - uint64_t readLengthCodedInt(); - - SlsStringPiece readLeft(); - - SlsStringPiece readFixSizeString(int32_t size); - - SlsStringPiece readLengthCodedString(); - - uint32_t readPacketLength(); - - uint8_t readPacketNum(); - - void readData(std::vector& data); - - void parse(); - - void print(); - -private: - MySQLPacket packet; - -public: - MySQLPacketType mysqlPacketType; - MySQLPacketQuery mysqlPacketQuery; - MySQLPacketResponse mysqlPacketResponse; -}; - -} // end of namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/mysql/parser.cpp b/core/observer/network/protocols/mysql/parser.cpp deleted file mode 100644 index 99c6aea137..0000000000 --- a/core/observer/network/protocols/mysql/parser.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include -#include "parser.h" -#include "inner_parser.h" -#include "logger/Logger.h" -#include "interface/helper.h" - -namespace logtail { - -ParseResult MySQLProtocolParser::OnPacket(PacketType pktType, - MessageType msgType, - PacketEventHeader* header, - const char* pkt, - int32_t pktSize, - int32_t pktRealSize) { - if (pktRealSize == 4) { - // skip 4 bytes packet because the packet would be appended to the next packet. - return ParseResult_OK; - } - LOG_TRACE(sLogger, ("req size", mCache.GetRequestsSize())("resp size", mCache.GetResponsesSize())); - MySQLParser mysql(pkt, pktSize); - try { - mysql.parse(); - } catch (const std::runtime_error& re) { - LOG_DEBUG(sLogger, - ("mysql_parse_fail", re.what())("data", charToHexString(pkt, pktSize, pktSize))("pid", header->PID)( - "msgType", MessageTypeToString(msgType))("srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } catch (const std::exception& ex) { - LOG_DEBUG(sLogger, - ("mysql_parse_fail", ex.what())("data", charToHexString(pkt, pktSize, pktSize))("pid", header->PID)( - "msgType", MessageTypeToString(msgType))("srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } catch (...) { - LOG_DEBUG(sLogger, - ("mysql_parse_fail", "Unknown failure occurred when parse")( - "data", charToHexString(pkt, pktSize, pktSize))("pid", header->PID)( - "msgType", MessageTypeToString(msgType))("srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } - if (mysql.OK()) { - bool insertSuccess = true; - LOG_TRACE( - sLogger, - ("hashID", header->SockHash)("packet_type", MySQLPacketTypeToString(mysql.mysqlPacketType))( - "pid", header->PID)("msgType", MessageTypeToString(msgType))( - "mysql_query", mysql.mysqlPacketQuery.sql.ToString())("mysql_resp", mysql.mysqlPacketResponse.ok)); - switch (mysql.mysqlPacketType) { - case MySQLPacketTypeServerGreeting: - case MySQLPacketNoResponseStatement: - // do nothing when receive single direction packet. - break; - case MySQLPacketTypeClientLogin: - case MySQLPacketIgnoreStatement: { - insertSuccess = mCache.InsertReq([&](MySQLRequestInfo* info) { - info->TimeNano = 0; - info->ReqBytes = MYSQL_REQUEST_INFO_IGNORE_FLAG; - info->SQL = ""; - }); - break; - } - case MySQLPacketTypeCollectStatement: { - insertSuccess = mCache.InsertReq([&](MySQLRequestInfo* info) { - info->TimeNano = header->TimeNano; - info->ReqBytes = pktRealSize; - info->SQL = mysql.mysqlPacketQuery.sql.ToString(); - }); - break; - } - case MySQLPacketTypeResponse: { - insertSuccess = mCache.InsertResp([&](MySQLResponseInfo* info) { - info->TimeNano = header->TimeNano; - info->RespBytes = pktRealSize; - info->OK = mysql.mysqlPacketResponse.ok; - }); - break; - } - } - return insertSuccess ? ParseResult_OK : ParseResult_Drop; - } - LOG_DEBUG( - sLogger, - ("mysql_parse_fail", "Unknown packets when parse")("pid", header->PID)("msgType", MessageTypeToString(msgType))( - "data", charToHexString(pkt, pktSize, pktSize))("srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; -} - -bool MySQLProtocolParser::GarbageCollection(size_t size_limit_bytes, uint64_t expireTimeNs) { - return mCache.GarbageCollection(expireTimeNs); -} - -int32_t MySQLProtocolParser::GetCacheSize() { - return this->mCache.GetRequestsSize() + this->mCache.GetResponsesSize(); -} - -} // end of namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/mysql/parser.h b/core/observer/network/protocols/mysql/parser.h deleted file mode 100644 index 7a6ee25820..0000000000 --- a/core/observer/network/protocols/mysql/parser.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include - -#include "common/StringTools.h" -#include "network/protocols/mysql/type.h" -#include "observer/interface/network.h" -#include "inner_parser.h" - -namespace logtail { - - -#define MYSQL_REQUEST_INFO_IGNORE_FLAG (-100) - -struct MySQLRequestInfo { - uint64_t TimeNano{0}; - int32_t ReqBytes{0}; - std::string SQL; -}; - -struct MySQLResponseInfo { - uint64_t TimeNano{0}; - int32_t RespBytes{0}; - bool OK{false}; -}; - -typedef CommonCache - MysqlCache; - -// 协议解析器,流式解析,解析到某个协议后,自动放到aggregator中聚合 -class MySQLProtocolParser { -public: - explicit MySQLProtocolParser(MySQLProtocolEventAggregator* aggregator, PacketEventHeader* header) - : mCache(aggregator), mKey(header) { - mCache.BindConvertFunc( - [&](MySQLRequestInfo* requestInfo, MySQLResponseInfo* responseInfo, MySQLProtocolEvent& event) -> bool { - if (requestInfo->ReqBytes == MYSQL_REQUEST_INFO_IGNORE_FLAG) { - return false; - } - event.Info.LatencyNs = int64_t(responseInfo->TimeNano - requestInfo->TimeNano); - event.Info.ReqBytes = requestInfo->ReqBytes; - event.Info.RespBytes = responseInfo->RespBytes; - event.Key.QueryCmd = ToLowerCaseString(requestInfo->SQL.substr(0, requestInfo->SQL.find_first_of(' '))); - event.Key.Query = std::move(requestInfo->SQL); - event.Key.Status = responseInfo->OK; - event.Key.ConnKey = mKey; - return true; - }); - } - - static MySQLProtocolParser* Create(MySQLProtocolEventAggregator* aggregator, PacketEventHeader* header) { - return new MySQLProtocolParser(aggregator, header); - } - - static void Delete(MySQLProtocolParser* parser) { delete parser; } - - // Packet到达的时候会Call,所有参数都会告诉类型(PacketType,MessageType) - ParseResult OnPacket(PacketType pktType, - MessageType msgType, - PacketEventHeader* header, - const char* pkt, - int32_t pktSize, - int32_t pktRealSize); - - // GC,把内部没有完成Event匹配的消息按照SizeLimit和TimeOut进行清理 - // 返回值,如果是true代表还有数据,false代表无数据 - bool GarbageCollection(size_t size_limit_bytes, uint64_t expireTimeNs); - - int32_t GetCacheSize(); - -private: - MysqlCache mCache; - CommonAggKey mKey; - friend class ProtocolMySqlUnittest; -}; - -} // namespace logtail diff --git a/core/observer/network/protocols/mysql/type.h b/core/observer/network/protocols/mysql/type.h deleted file mode 100644 index 98cf78f36b..0000000000 --- a/core/observer/network/protocols/mysql/type.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "network/protocols/category.h" - -namespace logtail { - -using MySQLProtocolEventKey = DBAggKey; -using MySQLProtocolEvent = CommonProtocolEvent; -using MySQLProtocolEventAggItem = CommonProtocolEventAggItem; -using MySQLProtocolEventAggItemManager = CommonProtocolEventAggItemManager; -using MySQLProtocolEventAggregator = CommonProtocolEventAggregator; -} // namespace logtail diff --git a/core/observer/network/protocols/pgsql/inner_parser.cpp b/core/observer/network/protocols/pgsql/inner_parser.cpp deleted file mode 100644 index 636089da4a..0000000000 --- a/core/observer/network/protocols/pgsql/inner_parser.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "inner_parser.h" -#include "network/protocols/infer.h" -#include "Logger.h" - -namespace logtail { - -// length[int32],major[int16],major[int16] -bool PgSQLParser::parseStartUpMsg() { - auto c = static_cast(*readChar(false)); - if (c == PostgreSqlTag::Unknown) { - this->type = PgSQLPacketType::IgnoreQuery; - return true; - } - return false; -} - -void PgSQLParser::parseRegularMsg(MessageType messageType) { - if (messageType == MessageType_Request) { - auto c = readChar(true); - auto t = static_cast(*c); - switch (t) { - case PostgreSqlTag::Query: { - uint32_t len = readUint32(); - if (this->isParseFail || pktSize - currPostion + 4 < len) { - setParseFail("parese pgsql Parse fail"); - return; - } - this->query.sql = readUntil(' '); - this->type = PgSQLPacketType::Query; - break; - } - case PostgreSqlTag::Parse: { - uint32_t len = readUint32(); - if (this->isParseFail || pktSize - currPostion + 4 < len) { - setParseFail("parese pgsql Parse fail"); - return; - } - readUntil('\0'); // statement name - this->query.sql = readUntil('\0'); - this->type = PgSQLPacketType::Query; - break; - } - default: { - this->type = PgSQLPacketType::IgnoreQuery; - LOG_DEBUG(sLogger, ("pgsql response tag has not been supported to parsed", *c)); - break; - } - } - } else if (messageType == MessageType_Response) { - bool foundRowDesc = false, foundCmdCmpl = false, foundParseCmpl = false, foundError = false; - bool res = parseRegularResponseMsg(foundRowDesc, foundCmdCmpl, foundParseCmpl, foundError); - if (!res) { - this->type = PgSQLPacketType::Unknown; - return; - } - // check simple query result - if (foundRowDesc) { - if (foundCmdCmpl) { - this->resp.ok = true; - this->type = PgSQLPacketType::Response; - return; - } else if (foundError) { - this->resp.ok = false; - this->type = PgSQLPacketType::Response; - return; - } - } - // check parse statement result - if (foundParseCmpl) { - this->resp.ok = true; - this->type = PgSQLPacketType::Response; - return; - } else if (foundError) { - this->resp.ok = false; - this->type = PgSQLPacketType::Response; - return; - } - this->type = PgSQLPacketType::Unknown; - } -} - -void PgSQLParser::parse(MessageType messageType) { - if (!parseStartUpMsg()) { - parseRegularMsg(messageType); - } -} - -bool PgSQLParser::parseRegularResponseMsg(bool& foundRowDesc, - bool& foundCmdCmpl, - bool& foundParseCmpl, - bool& foundError) { - auto c = readChar(true); - auto t = static_cast(*c); - uint32_t len = readUint32(false); - uint32_t nextPos = currPostion + len; - if (isParseFail || nextPos > pktSize) { - LOG_DEBUG(sLogger, ("pgsql request tag has not been supported to parsed", *c)); - return false; - } - switch (t) { - case PostgreSqlTag::CmdComplete: { - foundCmdCmpl = true; - break; - } - case PostgreSqlTag::RowDesc: { - foundRowDesc = true; - break; - } - case PostgreSqlTag::ParseComplete: { - foundParseCmpl = true; - break; - } - case PostgreSqlTag::ErrResp: { - foundError = true; - break; - } - default: { - } - } - if (nextPos == pktSize) { - return true; - } - positionCommit(len); - return parseRegularResponseMsg(foundRowDesc, foundCmdCmpl, foundParseCmpl, foundError); -} -} // namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/pgsql/inner_parser.h b/core/observer/network/protocols/pgsql/inner_parser.h deleted file mode 100644 index 8bdaeae922..0000000000 --- a/core/observer/network/protocols/pgsql/inner_parser.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "network/protocols/utils.h" -#include "interface/type.h" - -namespace logtail { -enum class PgSQLPacketType { - Unknown, - Query, - Response, - IgnoreQuery, -}; - -struct PgSQLPacketQuery { - SlsStringPiece sql; -}; - -struct PgSQLPacketResponse { - bool ok; -}; - -class PgSQLParser : public ProtoParser { -public: - PgSQLParser(const char* payload, const size_t pktSize) : ProtoParser(payload, pktSize, true) {} - - - void parse(MessageType messageType); - - PgSQLPacketQuery query; - PgSQLPacketResponse resp{}; - PgSQLPacketType type; - -private: - bool parseStartUpMsg(); - - void parseRegularMsg(MessageType messageType); - - bool parseRegularResponseMsg(bool& foundRowDesc, bool& foundCmdCmpl, bool& foundParseCmpl, bool& foundError); -}; - -} // namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/pgsql/parser.cpp b/core/observer/network/protocols/pgsql/parser.cpp deleted file mode 100644 index e1edf8fb6c..0000000000 --- a/core/observer/network/protocols/pgsql/parser.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2022 iLogtail 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. - - -#include "network/protocols/pgsql/parser.h" -#include "Logger.h" -#include "interface/helper.h" - -namespace logtail { - -ParseResult PgSQLProtocolParser::OnPacket(PacketType pktType, - MessageType msgType, - PacketEventHeader* header, - const char* pkt, - int32_t pktSize, - int32_t pktRealSize) { - PgSQLParser pgsql(pkt, pktSize); - LOG_TRACE(sLogger, - ("message_type", MessageTypeToString(msgType))("pgsql date", charToHexString(pkt, pktSize, pktSize))); - try { - pgsql.parse(msgType); - } catch (const std::runtime_error& re) { - LOG_DEBUG(sLogger, - ("pgsql_parse_fail", re.what())("data", charToHexString(pkt, pktSize, pktSize))( - "srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } catch (const std::exception& ex) { - LOG_DEBUG(sLogger, - ("pgsql_parse_fail", ex.what())("data", charToHexString(pkt, pktSize, pktSize))( - "srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } catch (...) { - LOG_DEBUG( - sLogger, - ("pgsql_parse_fail", "Unknown failure occurred when parse")("data", charToHexString(pkt, pktSize, pktSize))( - "srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } - - if (pgsql.OK()) { - bool insertSuccess = true; - switch (pgsql.type) { - case PgSQLPacketType::Query: { - insertSuccess = mCache.InsertReq([&](PgSQLRequestInfo* info) { - info->TimeNano = header->TimeNano; - info->ReqBytes = pktRealSize; - info->SQL = pgsql.query.sql.ToString(); - LOG_TRACE(sLogger, ("pgsql insert req", info->ToString())); - }); - break; - } - case PgSQLPacketType::IgnoreQuery: { - insertSuccess = mCache.InsertReq([&](PgSQLRequestInfo* info) { - info->TimeNano = 0; - info->ReqBytes = PGSQL_REQUEST_INFO_IGNORE_FLAG; - info->SQL = ""; - LOG_TRACE(sLogger, ("pgsql insert req", info->ToString())); - }); - break; - } - case PgSQLPacketType::Response: { - insertSuccess = mCache.InsertResp([&](PgSQLResponseInfo* info) { - info->TimeNano = header->TimeNano; - info->RespBytes = pktRealSize; - info->OK = pgsql.resp.ok; - LOG_TRACE(sLogger, ("pgsql insert resp", info->ToString())); - }); - break; - } - default: { - // do noting - } - } - return insertSuccess ? ParseResult_OK : ParseResult_Drop; - } - return ParseResult_Fail; -} - -bool PgSQLProtocolParser::GarbageCollection(size_t size_limit_bytes, uint64_t expireTimeNs) { - return mCache.GarbageCollection(expireTimeNs); -} - - -int32_t PgSQLProtocolParser::GetCacheSize() { - return this->mCache.GetRequestsSize() + this->mCache.GetResponsesSize(); -} - - -std::ostream& operator<<(std::ostream& os, const PgSQLRequestInfo& info) { - os << "TimeNano: " << info.TimeNano << " SQL: " << info.SQL << " ReqBytes: " << info.ReqBytes; - return os; -} - -std::ostream& operator<<(std::ostream& os, const PgSQLResponseInfo& info) { - os << "TimeNano: " << info.TimeNano << " OK: " << info.OK << " RespBytes: " << info.RespBytes; - return os; -} -} // namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/pgsql/parser.h b/core/observer/network/protocols/pgsql/parser.h deleted file mode 100644 index 7718ef268c..0000000000 --- a/core/observer/network/protocols/pgsql/parser.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include - -#include "common/StringTools.h" -#include "network/protocols/pgsql/type.h" - -#include "interface/network.h" -#include "inner_parser.h" - -namespace logtail { - -#define PGSQL_REQUEST_INFO_IGNORE_FLAG (-100) - -struct PgSQLRequestInfo { - uint64_t TimeNano; - std::string SQL; - int32_t ReqBytes; - - friend std::ostream& operator<<(std::ostream& os, const PgSQLRequestInfo& info); - - std::string ToString() { - std::stringstream ss; - ss << *this; - return ss.str(); - } -}; - -struct PgSQLResponseInfo { - uint64_t TimeNano; - bool OK; - int32_t RespBytes; - - friend std::ostream& operator<<(std::ostream& os, const PgSQLResponseInfo& info); - - std::string ToString() { - std::stringstream ss; - ss << *this; - return ss.str(); - } -}; - -typedef CommonCache - PgsqlCache; - - -class PgSQLProtocolParser { -public: - explicit PgSQLProtocolParser(PgSQLProtocolEventAggregator* aggregator, PacketEventHeader* header) - : mCache(aggregator), mKey(header) { - mCache.BindConvertFunc( - [&](PgSQLRequestInfo* requestInfo, PgSQLResponseInfo* responseInfo, PgSQLProtocolEvent& event) -> bool { - if (requestInfo->ReqBytes == PGSQL_REQUEST_INFO_IGNORE_FLAG) { - return false; - } - event.Info.LatencyNs = responseInfo->TimeNano - requestInfo->TimeNano; - if (event.Info.LatencyNs < 0) { - event.Info.LatencyNs = 0; - } - event.Info.ReqBytes = requestInfo->ReqBytes; - event.Info.RespBytes = responseInfo->RespBytes; - event.Key.ConnKey = mKey; - event.Key.QueryCmd = ToLowerCaseString(requestInfo->SQL.substr(0, requestInfo->SQL.find_first_of(' '))); - event.Key.Query = std::move(requestInfo->SQL); - event.Key.Status = responseInfo->OK; - return true; - }); - } - - static PgSQLProtocolParser* Create(PgSQLProtocolEventAggregator* aggregator, PacketEventHeader* header) { - return new PgSQLProtocolParser(aggregator, header); - } - - static void Delete(PgSQLProtocolParser* parser) { delete parser; } - - bool GarbageCollection(size_t size_limit_bytes, uint64_t expireTimeNs); - - int32_t GetCacheSize(); - - ParseResult OnPacket(PacketType pktType, - MessageType msgType, - PacketEventHeader* header, - const char* pkt, - int32_t pktSize, - int32_t pktRealSize); - - -private: - PgsqlCache mCache; - CommonAggKey mKey; - - friend class ProtocolPgSqlUnittest; -}; -} // namespace logtail diff --git a/core/observer/network/protocols/pgsql/type.h b/core/observer/network/protocols/pgsql/type.h deleted file mode 100644 index c35cd0ab17..0000000000 --- a/core/observer/network/protocols/pgsql/type.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "network/protocols/category.h" - -namespace logtail { - -using PgSQLProtocolEventKey = DBAggKey; -using PgSQLProtocolEvent = CommonProtocolEvent; -using PgSQLProtocolEventAggItem = CommonProtocolEventAggItem; -using PgSQLProtocolEventAggItemManager = CommonProtocolEventAggItemManager; -using PgSQLProtocolEventAggregator - = CommonProtocolEventAggregator; -} // namespace logtail diff --git a/core/observer/network/protocols/redis/inner_parser.cpp b/core/observer/network/protocols/redis/inner_parser.cpp deleted file mode 100644 index e2a3a0661e..0000000000 --- a/core/observer/network/protocols/redis/inner_parser.cpp +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include -#include - -#include "inner_parser.h" -namespace logtail { - -SlsStringPiece RedisParser::readUtilNewLine() { - const char* s = readChar(); - const char* ch = s; - const char* lastCh = s; - int counter = 1; - while (true) { - ch++; - counter++; - positionCommit(1); - if (isParseFail) { - SlsStringPiece empty; - return empty; - } - - if ((*lastCh) == '\r' && (*ch) == '\n') { - break; - } - lastCh = ch; - } - SlsStringPiece val(s, counter - 2); - return val; -} - -void RedisParser::readData(std::vector& data) { - const char* ch = readChar(); - switch (*ch) { - case '+': // + 字符串 \r\n - { - auto s = readUtilNewLine(); - // std::cout << s1.ToString() << std::endl; - data.push_back(s); - } break; - case '-': // - 错误前缀 错误信息 \r\n - { - auto s = readUtilNewLine(); - // std::cout << s1.ToString() << std::endl; - data.push_back(s); - redisData.isError = true; - } break; - case ':': // : 数字 \r\n - { - auto s = readUtilNewLine(); - // std::cout << s1.ToString() << std::endl; - data.push_back(s); - } break; - case '$': // $ 字符串的长度 \r\n 字符串 \r\n - { - readUtilNewLine(); - auto s2 = readUtilNewLine(); - data.push_back(s2); - break; - } - case '*': // * 数组元素个数 \r\n 其他所有类型 (结尾不需要\r\n) - { - auto s = readUtilNewLine(); - int cnt = std::stoi(s.ToString()); - int max = cnt > 1 ? 1 : cnt; - for (int i = 0; i < max; i++) { - readData(data); - } - break; - } - default: - break; - } -} - -void RedisParser::parse() { - readData(redisData.data); -} - -void RedisParser::print() { - for (auto v : redisData.data) { - std::cout << v.ToString() << " "; - } - - std::cout << std::endl; -} - -} // end of namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/redis/inner_parser.h b/core/observer/network/protocols/redis/inner_parser.h deleted file mode 100644 index 1fbcdd6cac..0000000000 --- a/core/observer/network/protocols/redis/inner_parser.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include - -#include "network/protocols/utils.h" - -namespace logtail { -struct RedisData { - std::vector data; - bool isError; - - std::string GetCommands() { - std::string cmd; - for (auto iter = data.begin(); iter < data.end(); iter++) { - if ((iter + 1) != data.end()) { - cmd.append(iter->mPtr, iter->mLen).append(" "); - } else { - cmd.append(iter->mPtr, iter->mLen); - } - } - return cmd; - } -}; - -class RedisParser : public ProtoParser { -public: - RedisParser(const char* payload, const size_t pktSize) : ProtoParser(payload, pktSize, true) { - redisData.isError = false; - } - - SlsStringPiece readUtilNewLine(); - - void readData(std::vector& data); - - void parse(); - - void print(); - -public: - RedisData redisData; -}; - -} // end of namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/redis/parser.cpp b/core/observer/network/protocols/redis/parser.cpp deleted file mode 100644 index e363588cd6..0000000000 --- a/core/observer/network/protocols/redis/parser.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include -#include -#include "parser.h" -#include "inner_parser.h" -#include "logger/Logger.h" -#include "interface/helper.h" - -namespace logtail { - - -ParseResult RedisProtocolParser::OnPacket(PacketType pktType, - MessageType msgType, - PacketEventHeader* header, - const char* pkt, - int32_t pktSize, - int32_t pktRealSize) { - RedisParser redis(pkt, pktSize); - LOG_TRACE(sLogger, - ("message_type", MessageTypeToString(msgType))("redis date", charToHexString(pkt, pktSize, pktSize))); - try { - redis.parse(); - } catch (const std::runtime_error& re) { - LOG_DEBUG(sLogger, - ("redis_parse_fail", re.what())("data", charToHexString(pkt, pktSize, pktSize))( - "srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } catch (const std::exception& ex) { - LOG_DEBUG(sLogger, - ("redis_parse_fail", ex.what())("data", charToHexString(pkt, pktSize, pktSize))( - "srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } catch (...) { - LOG_DEBUG( - sLogger, - ("redis_parse_fail", "Unknown failure occurred when parse")("data", charToHexString(pkt, pktSize, pktSize))( - "srcPort", header->SrcPort)("dstPort", header->DstPort)); - return ParseResult_Fail; - } - - if (redis.OK()) { - bool insertSuccess = true; - if (msgType == MessageType_Request) { - insertSuccess = mCache.InsertReq([&](RedisRequestInfo* info) { - info->TimeNano = header->TimeNano; - info->ReqBytes = pktRealSize; - info->CMD = redis.redisData.GetCommands(); - LOG_TRACE(sLogger, ("redis insert req", info->ToString())); - }); - } else if (msgType == MessageType_Response) { - insertSuccess = mCache.InsertResp([&](RedisResponseInfo* info) { - info->TimeNano = header->TimeNano; - info->RespBytes = pktRealSize; - info->isOK = !redis.redisData.isError; - LOG_TRACE(sLogger, ("redis insert resp", info->ToString())); - }); - } - return insertSuccess ? ParseResult_OK : ParseResult_Drop; - } - return ParseResult_Fail; -} - -bool RedisProtocolParser::GarbageCollection(size_t size_limit_bytes, uint64_t expireTimeNs) { - return mCache.GarbageCollection(expireTimeNs); -} - -int32_t RedisProtocolParser::GetCacheSize() { - return this->mCache.GetRequestsSize() + this->mCache.GetResponsesSize(); -} - -std::ostream& operator<<(std::ostream& os, const RedisRequestInfo& info) { - os << "TimeNano: " << info.TimeNano << " CMD: " << info.CMD << " ReqBytes: " << info.ReqBytes; - return os; -} - -std::ostream& operator<<(std::ostream& os, const RedisResponseInfo& info) { - os << "TimeNano: " << info.TimeNano << " isOK: " << info.isOK << " RespBytes: " << info.RespBytes; - return os; -} -} // end of namespace logtail \ No newline at end of file diff --git a/core/observer/network/protocols/redis/parser.h b/core/observer/network/protocols/redis/parser.h deleted file mode 100644 index d7f111e18b..0000000000 --- a/core/observer/network/protocols/redis/parser.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include -#include - -#include "common/StringTools.h" -#include "network/protocols/redis/type.h" -#include "observer/interface/network.h" -#include "inner_parser.h" - -namespace logtail { - -struct RedisRequestInfo { - uint64_t TimeNano; - std::string CMD; - int32_t ReqBytes; - - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - friend std::ostream& operator<<(std::ostream& os, const RedisRequestInfo& info); -}; - -struct RedisResponseInfo { - uint64_t TimeNano; - bool isOK; - int32_t RespBytes; - std::string ToString() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } - friend std::ostream& operator<<(std::ostream& os, const RedisResponseInfo& info); -}; - -typedef CommonCache - RedisCache; - -// 协议解析器,流式解析,解析到某个协议后,自动放到aggregator中聚合 -class RedisProtocolParser { -public: - RedisProtocolParser(RedisProtocolEventAggregator* aggregator, PacketEventHeader* header) - : mCache(aggregator), mKey(header) { - mCache.BindConvertFunc([&](RedisRequestInfo* req, RedisResponseInfo* resp, RedisProtocolEvent& event) { - event.Info.LatencyNs = resp->TimeNano - req->TimeNano; - if (event.Info.LatencyNs < 0) { - event.Info.LatencyNs = 0; - } - event.Info.ReqBytes = req->ReqBytes; - event.Info.RespBytes = resp->RespBytes; - event.Key.ConnKey = mKey; - event.Key.QueryCmd = ToLowerCaseString(req->CMD.substr(0, req->CMD.find_first_of(' '))); - event.Key.Query = std::move(req->CMD); - event.Key.Status = resp->isOK; - return true; - }); - } - - static RedisProtocolParser* Create(RedisProtocolEventAggregator* aggregator, PacketEventHeader* header) { - return new RedisProtocolParser(aggregator, header); - } - - static void Delete(RedisProtocolParser* parser) { delete parser; } - - // Packet到达的时候会Call,所有参数都会告诉类型(PacketType,MessageType) - ParseResult OnPacket(PacketType pktType, - MessageType msgType, - PacketEventHeader* header, - const char* pkt, - int32_t pktSize, - int32_t pktRealSize); - - // GC,把内部没有完成Event匹配的消息按照SizeLimit和TimeOut进行清理 - // 返回值,如果是true代表还有数据,false代表无数据 - bool GarbageCollection(size_t size_limit_bytes, uint64_t expireTimeNs); - - int32_t GetCacheSize(); - -private: - RedisCache mCache; - CommonAggKey mKey; - - friend class ProtocolRedisUnittest; -}; - - -} // namespace logtail diff --git a/core/observer/network/protocols/redis/type.h b/core/observer/network/protocols/redis/type.h deleted file mode 100644 index a944013b9e..0000000000 --- a/core/observer/network/protocols/redis/type.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - - -#include "network/protocols/category.h" - -namespace logtail { - -using RedisProtocolEventKey = DBAggKey; -using RedisProtocolEvent = CommonProtocolEvent; -using RedisProtocolEventAggItem = CommonProtocolEventAggItem; -using RedisProtocolEventAggItemManager = CommonProtocolEventAggItemManager; -using RedisProtocolEventAggregator - = CommonProtocolEventAggregator; -} // namespace logtail diff --git a/core/observer/network/protocols/utils.h b/core/observer/network/protocols/utils.h deleted file mode 100644 index c702765078..0000000000 --- a/core/observer/network/protocols/utils.h +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace logtail { -struct SlsStringPiece { - const char* mPtr; - size_t mLen; - - SlsStringPiece(const char* ptr, size_t len) : mPtr(ptr), mLen(len) {} - - explicit SlsStringPiece(const std::string& s) : mPtr(s.data()), mLen(s.size()) {} - - SlsStringPiece() : mPtr(nullptr), mLen(0) {} - - inline bool operator==(const std::string& targetStr) const { - return (this->mLen == targetStr.size()) && (memcmp(this->mPtr, targetStr.data(), this->mLen) == 0); - } - - inline bool operator==(const SlsStringPiece& targetStr) const { - return (this->mLen == targetStr.mLen) && (memcmp(this->mPtr, targetStr.mPtr, this->mLen) == 0); - } - - inline uint32_t Size() { return this->mLen; } - - inline char operator[](size_t pos) const { return this->mPtr[pos]; } - - - inline bool StartWith(const std::string& prefix) const { - return (prefix.length() <= mLen) && (memcmp(this->mPtr, prefix.data(), prefix.length()) == 0); - } - - std::string TrimToString() const { - if (mPtr == nullptr) { - return {}; - } - const char* start = mPtr; - int preEmptyCount = 0; - for (uint32_t i = 0; i < mLen; i++) { - if (start[i] != ' ' && start[i] != '\t') { - start = start + i; - break; - } - preEmptyCount++; - } - int postEmptyCount = 0; - for (uint32_t i = mLen - 1; i > 0; i--) { - if (mPtr[i] != ' ' && mPtr[i] != '\t') { - break; - } - postEmptyCount++; - } - return std::string(start, mLen - preEmptyCount - postEmptyCount); - } - - int Find(char c) const { - if (mPtr == nullptr) { - return -1; - } - for (int i = 0; i < (int)mLen; ++i) { - if (mPtr[i] == c) { - return i; - } - } - return -1; - } - - std::string ToString() const { - if (mPtr == nullptr) { - return {}; - } - return {mPtr, mLen}; - } - - bool operator<(const SlsStringPiece& other) const { - if (mLen == 0 || other.mLen == 0) { - return mLen < other.mLen; - } - uint32_t i = 0; - while (mPtr[i] == other.mPtr[i]) { - i++; - if (i == mLen || i == other.mLen) { - return mLen < other.mLen; - } - } - return mPtr[i] < other.mPtr[i]; - } -}; - -inline void hexstring_to_bin(std::string s, std::vector& dest) { - auto p = s.data(); - auto end = p + s.length(); - - while (p < end) { - const char hexbytestr[] = {*p, *(p + 1), 0}; - uint8_t val = strtol(hexbytestr, NULL, 16); - dest.push_back(val); - p += 2; - } -} - -inline std::string charToHexString(const char* payload, const int32_t len, const int32_t limit) { - std::stringstream ioss; - std::string hexstring; - for (int i = 0; i < len && i < limit; i++) { - ioss << std::hex << std::setfill('0') << std::setw(2) << (int)(unsigned char)payload[i]; - } - ioss >> hexstring; - return hexstring; -} - -class ProtoParser { -public: - ProtoParser(const char* payload, const size_t pktSize, bool bigByteOrder) - : currPostion(0), isParseFail(false), payload(payload), pktSize(pktSize), bigByteOrder(bigByteOrder) { - parserFailMsg = std::string(); - // needByteOrderCovert = isCPUBigByteOrder() != bigByteOrder; - } - - ~ProtoParser() {} - - bool OK() { return !isParseFail; } - - uint64_t readUint64(bool commit = true) { - if (this->currPostion + 8 > this->pktSize) { - this->setParseFail("unexcepted eof"); - return 0; - } - uint64_t data = *((uint64_t*)(payload + this->currPostion)); - if (commit) { - this->currPostion += 8; - } - - if (bigByteOrder) { - return be64toh(data); - } - return le64toh(data); - } - - uint32_t readUint32(bool commit = true) { - if (this->currPostion + 4 > this->pktSize) { - this->setParseFail("unexcepted eof"); - return 0; - } - uint32_t data = *((uint32_t*)(payload + this->currPostion)); - if (commit) { - this->currPostion += 4; - } - - if (bigByteOrder) { - return be32toh(data); - } - return le32toh(data); - } - - uint16_t readUint16(bool commit = true) { - if (this->currPostion + 2 > this->pktSize) { - this->setParseFail("unexcepted eof"); - return 0; - } - uint16_t data = *((uint16_t*)(payload + this->currPostion)); - if (commit) { - this->currPostion += 2; - } - - if (bigByteOrder) { - return be16toh(data); - } - return le16toh(data); - } - - uint8_t readUint8(bool commit = true) { - if (this->currPostion + 1 > this->pktSize) { - this->setParseFail("unexcepted eof"); - return 0; - } - uint8_t data = *((uint8_t*)(payload + this->currPostion)); - if (commit) { - this->currPostion += 1; - } - return data; - } - - const char* readChar(bool commit = true) { - if (this->currPostion + 1 > this->pktSize) { - this->setParseFail("unexcepted eof"); - return 0; - } - const char* data = payload + this->currPostion; - if (commit) { - this->currPostion += 1; - } - return data; - } - - SlsStringPiece readUntil(char flag, bool commit = true) { - if (this->currPostion + 1 > this->pktSize) { - this->setParseFail("unexcepted eof"); - return {}; - } - size_t len = 0; - uint32_t pos = currPostion; - while (pos + len + 1 <= this->pktSize && *(payload + pos + len) != flag) { - len++; - } - if (commit && pos + 1 <= this->pktSize) { - this->currPostion = pos + len + 1; - } - return {payload + pos, len}; - } - - - void positionCommit(uint64_t step) { - if (this->currPostion + step > this->pktSize) { - this->setParseFail("unexcepted eof"); - return; - } - this->currPostion += step; - } - - bool isNextEof() { - if (this->currPostion + 1 >= this->pktSize) { - return true; - } else { - return false; - } - } - - int32_t getLeftSize() { return pktSize - this->currPostion; } - -protected: - void setParseFail(const std::string& failMsg) { - this->isParseFail = true; - this->parserFailMsg = failMsg; - throw std::runtime_error("unexcepted eof"); - } - -protected: - // common fields - - uint32_t currPostion; - - std::string parserFailMsg; - - bool isParseFail; - - const char* payload; - - const size_t pktSize; - - // bool needByteOrderCovert; - bool bigByteOrder; -}; - -} // end of namespace logtail \ No newline at end of file diff --git a/core/observer/network/sources/ebpf/EBPFWrapper.cpp b/core/observer/network/sources/ebpf/EBPFWrapper.cpp deleted file mode 100644 index 63f9392310..0000000000 --- a/core/observer/network/sources/ebpf/EBPFWrapper.cpp +++ /dev/null @@ -1,806 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "app_config/AppConfig.h" -#include "logger/Logger.h" -#include "EBPFWrapper.h" -#include "RuntimeUtil.h" -#include "interface/network.h" -#include "TimeUtil.h" -#include -#include "MachineInfoUtil.h" -#include "FileSystemUtil.h" -#include "metas/ConnectionMetaManager.h" -#include "network/protocols/utils.h" -#include -#include "interface/layerfour.h" - -DECLARE_FLAG_STRING(default_container_host_path); -DEFINE_FLAG_INT64(sls_observer_ebpf_min_kernel_version, - "the minimum kernel version that supported eBPF normal running, 4.19.0.0 -> 4019000000", - 4019000000); -DEFINE_FLAG_INT64( - sls_observer_ebpf_nobtf_kernel_version, - "the minimum kernel version that supported eBPF normal running without self BTF file, 5.4.0.0 -> 5004000000", - 5004000000); - -static const std::string kLowkernelCentosName = "CentOS"; -static const uint16_t kLowkernelCentosMinVersion = 7006; -static const uint16_t kLowkernelSpecificVersion = 3010; - - -namespace logtail { -// copy from libbpf_print_level -enum sls_libbpf_print_level { - SLS_LIBBPF_WARN, - SLS_LIBBPF_INFO, - SLS_LIBBPF_DEBUG, -}; -#define LOAD_EBPF_FUNC(funcName) \ - { \ - void* funcPtr = mEBPFLib->LoadMethod(#funcName, loadErr); \ - if (funcPtr == NULL) { \ - LOG_ERROR(sLogger, ("load ebpf method", "failed")("method", #funcName)("error", loadErr)); \ - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, \ - std::string("load ebpf method failed: ") + #funcName); \ - return false; \ - } \ - g_##funcName##_func = funcName##_func(funcPtr); \ - } -#define LOAD_UPROBE_OFFSET_FAIL (-100) -#define LOAD_UPROBE_OFFSET(funcName) \ - ({ \ - Dl_info dlinfo; \ - int dlAddrErr = dladdr((const void*)(funcName), &dlinfo); \ - long res = 0; \ - if (dlAddrErr == 0) { \ - LOG_ERROR(sLogger, \ - ("load ebpf dl address", "failed")("error", dlAddrErr)("method", "g_ebpf_cleanup_dog_func")); \ - LogtailAlarm::GetInstance()->SendAlarm( \ - OBSERVER_INIT_ALARM, \ - "cannot load ebpf dl address, method g_ebpf_cleanup_dog_func, error:" + std::to_string(dlAddrErr)); \ - res = LOAD_UPROBE_OFFSET_FAIL; \ - } else { \ - res = (long)dlinfo.dli_saddr - (long)dlinfo.dli_fbase; \ - LOG_DEBUG(sLogger, ("func", #funcName)("offset", res)); \ - } \ - res; \ - }) - -typedef void (*ebpf_setup_net_data_process_func_func)(net_data_process_func_t func, void* custom_data); - -typedef void (*ebpf_setup_net_event_process_func_func)(net_ctrl_process_func_t func, void* custom_data); - -typedef void (*ebpf_setup_net_statistics_process_func_func)(net_statistics_process_func_t func, void* custom_data); - -typedef void (*ebpf_setup_net_lost_func_func)(net_lost_func_t func, void* custom_data); - -typedef void (*ebpf_setup_print_func_func)(net_print_fn_t func); - -typedef int32_t (*ebpf_config_func)( - int32_t opt1, int32_t opt2, int32_t params_count, void** params, int32_t* params_len); - -typedef int32_t (*ebpf_poll_events_func)(int32_t max_events, int32_t* stop_flag); - -typedef int32_t (*ebpf_init_func)(char* btf, - int32_t btf_size, - char* so, - int32_t so_size, - long uprobe_offset, - long upca_offset, - long upps_offset, - long upcr_offset); - -typedef int32_t (*ebpf_start_func)(); - -typedef int32_t (*ebpf_stop_func)(); - -typedef int32_t (*ebpf_get_fd_func)(void); - -typedef int32_t (*ebpf_get_next_key_func)(int32_t fd, const void* key, void* next_key); - -typedef void (*ebpf_delete_map_value_func)(void* key, int32_t size); - -typedef void (*ebpf_cleanup_dog_func)(void* key, int32_t size); -typedef void (*ebpf_update_conn_addr_func)(struct connect_id_t* conn_id, - union sockaddr_t* dest_addr, - uint16_t local_port, - bool drop); -typedef void (*ebpf_disable_process_func)(uint32_t pid, bool drop); -typedef void (*ebpf_update_conn_role_func)(struct connect_id_t* conn_id, enum support_role_e role_type); - - -ebpf_setup_net_data_process_func_func g_ebpf_setup_net_data_process_func_func = NULL; -ebpf_setup_net_event_process_func_func g_ebpf_setup_net_event_process_func_func = NULL; -ebpf_setup_net_statistics_process_func_func g_ebpf_setup_net_statistics_process_func_func = NULL; -ebpf_setup_net_lost_func_func g_ebpf_setup_net_lost_func_func = NULL; -ebpf_setup_print_func_func g_ebpf_setup_print_func_func = NULL; -ebpf_config_func g_ebpf_config_func = NULL; -ebpf_poll_events_func g_ebpf_poll_events_func = NULL; -ebpf_init_func g_ebpf_init_func = NULL; -ebpf_start_func g_ebpf_start_func = NULL; -ebpf_stop_func g_ebpf_stop_func = NULL; -ebpf_get_fd_func g_ebpf_get_fd_func = NULL; -ebpf_get_next_key_func g_ebpf_get_next_key_func = NULL; -ebpf_delete_map_value_func g_ebpf_delete_map_value_func = NULL; -ebpf_cleanup_dog_func g_ebpf_cleanup_dog_func = NULL; -ebpf_update_conn_addr_func g_ebpf_update_conn_addr_func = NULL; -ebpf_disable_process_func g_ebpf_disable_process_func = NULL; -ebpf_update_conn_role_func g_ebpf_update_conn_role_func = NULL; - - -static bool EBPFLoadSuccess() { - return g_ebpf_setup_net_data_process_func_func != NULL && g_ebpf_setup_net_event_process_func_func != NULL - && g_ebpf_setup_net_statistics_process_func_func != NULL && g_ebpf_setup_net_lost_func_func != NULL - && g_ebpf_setup_print_func_func != NULL && g_ebpf_config_func != NULL && g_ebpf_poll_events_func != NULL - && g_ebpf_init_func != NULL && g_ebpf_start_func != NULL && g_ebpf_stop_func != NULL - && g_ebpf_get_fd_func != NULL && g_ebpf_get_next_key_func != NULL && g_ebpf_delete_map_value_func != NULL - && g_ebpf_cleanup_dog_func != NULL && g_ebpf_update_conn_addr_func != NULL - && g_ebpf_disable_process_func != NULL; -} - -static void set_ebpf_int_config(int32_t opt, int32_t opt2, int32_t value) { - if (g_ebpf_config_func == NULL) { - return; - } - int32_t* params[] = {&value}; - int32_t paramsLen[] = {4}; - g_ebpf_config_func(opt, opt2, 1, (void**)params, paramsLen); -} - -static int ebpf_print_callback(int16_t level, const char* format, va_list args) { - sls_libbpf_print_level printLevel = (sls_libbpf_print_level)level; - char buffer[1024] = {0}; - vsnprintf(buffer, 1023, format, args); - switch (printLevel) { - case SLS_LIBBPF_WARN: - LOG_WARNING(sLogger, ("module", "ebpf")("msg", buffer)); - break; - case SLS_LIBBPF_INFO: - LOG_INFO(sLogger, ("module", "ebpf")("msg", buffer)); - break; - case SLS_LIBBPF_DEBUG: - LOG_DEBUG(sLogger, ("module", "ebpf")("msg", buffer)); - break; - default: - LOG_INFO(sLogger, ("module", "ebpf")("level", int(level))("msg", buffer)); - break; - } - return 0; -} - -// static void test_print(int16_t level, -// const char *format, ...) { -// va_list args; - -// va_start(args, format); -// ebpf_print_callback(level, format, args); -// va_end(args); -// } - -static void ebpf_data_process_callback(void* custom_data, struct conn_data_event_t* event_data) { - if (custom_data == NULL || event_data == NULL) { - return; - } - ((EBPFWrapper*)custom_data)->OnData(event_data); -} - -static void ebpf_ctrl_process_callback(void* custom_data, struct conn_ctrl_event_t* event_data) { - if (custom_data == NULL || event_data == NULL) { - return; - } - ((EBPFWrapper*)custom_data)->OnCtrl(event_data); -} - -static void ebpf_stat_process_callback(void* custom_data, struct conn_stats_event_t* event_data) { - if (custom_data == NULL || event_data == NULL) { - return; - } - ((EBPFWrapper*)custom_data)->OnStat(event_data); -} - -static void ebpf_lost_callback(void* custom_data, enum callback_type_e type, uint64_t lost_count) { - if (custom_data == NULL) { - return; - } - ((EBPFWrapper*)custom_data)->OnLost(type, lost_count); -} - -static std::string GetValidBTFPath(const int64_t& kernelVersion, const std::string& kernelRelease) { - char* configedBTFPath = getenv("ALIYUN_SLS_EBPF_BTF_PATH"); - if (configedBTFPath != nullptr) { - return {configedBTFPath}; - } - // ebpf lib load - std::string execDir = GetProcessExecutionDir(); - fsutil::Dir dir(execDir); - if (!dir.Open()) { - return ""; - } - std::string lastMatch; - fsutil::Entry entry; - while (true) { - entry = dir.ReadNext(); - if (!entry) { - break; - } - if (!entry.IsRegFile()) { - continue; - } - if (entry.Name().find(kernelRelease) != std::string::npos) { - return execDir + entry.Name(); - } - if (entry.Name().find("vmlinux-") == (size_t)0) { - lastMatch = entry.Name(); - } - } - if (!lastMatch.empty()) { - return execDir + lastMatch; - } - return ""; -} - -bool EBPFWrapper::isSupportedOS(int64_t& kernelVersion, std::string& kernelRelease) { - GetKernelInfo(kernelRelease, kernelVersion); - LOG_INFO(sLogger, ("kernel version", kernelRelease)); - if (kernelRelease.empty()) { - return false; - } - if (kernelVersion >= INT64_FLAG(sls_observer_ebpf_min_kernel_version)) { - return true; - } - if (kernelVersion / 1000000 != kLowkernelSpecificVersion) { - return false; - } - std::string os; - int64_t osVersion; - - if (GetRedHatReleaseInfo(os, osVersion, STRING_FLAG(default_container_host_path)) - || GetRedHatReleaseInfo(os, osVersion)) { - return os == kLowkernelCentosName && osVersion >= kLowkernelCentosMinVersion; - } - return false; -} - -bool EBPFWrapper::loadEbpfLib(int64_t kernelVersion, std::string& soPath) { - if (mEBPFLib != nullptr) { - if (!EBPFLoadSuccess()) { - LOG_INFO(sLogger, ("load ebpf dynamic library", "failed")("error", "last load failed")); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, "cannot load ebpf dynamic library"); - return false; - } - return true; - } - LOG_INFO(sLogger, ("load ebpf dynamic library", "begin")); - // load ebpf lib - std::string dlPrefix = GetProcessExecutionDir(); - soPath = dlPrefix + "libebpf.so"; - if (kernelVersion < INT64_FLAG(sls_observer_ebpf_min_kernel_version)) { - fsutil::PathStat buf; - // overlayfs has a reference bug in low kernel version, so copy docker inner file to host path to avoid using - // overlay fs. detail: https://lore.kernel.org/lkml/20180228004014.445-1-hmclauchlan@fb.com/ - if (fsutil::PathStat::stat(STRING_FLAG(default_container_host_path).c_str(), buf)) { - std::string cmd - = std::string("\\cp ").append(soPath).append(" ").append(GetObserverEbpfHostPath()); - LOG_INFO(sLogger, ("invoke cp cmd:", cmd)); - system(std::string("mkdir ").append(GetObserverEbpfHostPath()).c_str()); - system(cmd.c_str()); - dlPrefix = GetObserverEbpfHostPath(); - soPath = GetObserverEbpfHostPath() + "libebpf.so"; - } - } - LOG_INFO(sLogger, ("load ebpf, libebpf path", soPath)); - mEBPFLib = new DynamicLibLoader; - std::string loadErr; - if (!mEBPFLib->LoadDynLib("ebpf", loadErr, dlPrefix)) { - LOG_ERROR(sLogger, ("load ebpf dynamic library path", soPath)("error", loadErr)); - return false; - } - LOAD_EBPF_FUNC(ebpf_setup_net_data_process_func) - LOAD_EBPF_FUNC(ebpf_setup_net_event_process_func) - LOAD_EBPF_FUNC(ebpf_setup_net_statistics_process_func) - LOAD_EBPF_FUNC(ebpf_setup_net_lost_func) - LOAD_EBPF_FUNC(ebpf_setup_print_func) - LOAD_EBPF_FUNC(ebpf_config) - LOAD_EBPF_FUNC(ebpf_poll_events) - LOAD_EBPF_FUNC(ebpf_init) - LOAD_EBPF_FUNC(ebpf_start) - LOAD_EBPF_FUNC(ebpf_stop) - LOAD_EBPF_FUNC(ebpf_get_fd) - LOAD_EBPF_FUNC(ebpf_get_next_key) - LOAD_EBPF_FUNC(ebpf_delete_map_value) - LOAD_EBPF_FUNC(ebpf_cleanup_dog) - LOAD_EBPF_FUNC(ebpf_update_conn_addr) - LOAD_EBPF_FUNC(ebpf_disable_process) - LOAD_EBPF_FUNC(ebpf_update_conn_role) - LOG_INFO(sLogger, ("load ebpf dynamic library", "success")); - return true; -} - -bool EBPFWrapper::Init(std::function processor) { - if (mInitSuccess) { - return true; - } - LOG_INFO(sLogger, ("init ebpf source", "begin")); - int64_t kernelVersion; - std::string kernelRelease; - if (!isSupportedOS(kernelVersion, kernelRelease)) { - LOG_ERROR(sLogger, ("init ebpf source", "failed")("reason", "not supported kernel or OS")); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, "not supported kernel or OS:" + kernelRelease); - return false; - } - - std::string btfPath = "/sys/kernel/btf/vmlinux"; - if (kernelVersion < INT64_FLAG(sls_observer_ebpf_nobtf_kernel_version)) { - btfPath = GetValidBTFPath(kernelVersion, kernelRelease); - if (btfPath.empty()) { - LOG_ERROR(sLogger, ("not found any btf files", kernelRelease)); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, "not found any btf files:" + kernelRelease); - return false; - } - } - - - std::string soPath; - if (!loadEbpfLib(kernelVersion, soPath)) { - return false; - } - mPacketProcessor = processor; - - g_ebpf_setup_print_func_func(ebpf_print_callback); - g_ebpf_setup_net_event_process_func_func(ebpf_ctrl_process_callback, this); - g_ebpf_setup_net_data_process_func_func(ebpf_data_process_callback, this); - g_ebpf_setup_net_statistics_process_func_func(ebpf_stat_process_callback, this); - g_ebpf_setup_net_lost_func_func(ebpf_lost_callback, this); - - long cleanup_offset = LOAD_UPROBE_OFFSET(g_ebpf_cleanup_dog_func); - long update_conn_offset = LOAD_UPROBE_OFFSET(g_ebpf_update_conn_addr_func); - long disable_process_offset = LOAD_UPROBE_OFFSET(g_ebpf_disable_process_func); - long update_role_offset = LOAD_UPROBE_OFFSET(g_ebpf_update_conn_role_func); - if (cleanup_offset == LOAD_UPROBE_OFFSET_FAIL || update_conn_offset == LOAD_UPROBE_OFFSET_FAIL - || disable_process_offset == LOAD_UPROBE_OFFSET_FAIL || update_role_offset == LOAD_UPROBE_OFFSET_FAIL) { - return false; - } - - int err = g_ebpf_init_func(&btfPath.at(0), - static_cast(btfPath.size()), - &soPath.at(0), - static_cast(soPath.size()), - cleanup_offset, - update_conn_offset, - disable_process_offset, - update_role_offset); - if (err) { - LOG_ERROR(sLogger, ("init ebpf", "failed")("error", "ebpf_init func failed")); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, "ebpf_init func failed"); - return false; - } - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - uint64_t nowTime = GetCurrentTimeInNanoSeconds(); - mDeltaTimeNs = nowTime - (uint64_t)ts.tv_sec * 1000000000ULL - (uint64_t)ts.tv_nsec; - set_ebpf_int_config((int32_t)PROTOCOL_FILTER, 0, -1); - set_ebpf_int_config((int32_t)TGID_FILTER, 0, mConfig->mEBPFPid); - set_ebpf_int_config((int32_t)SELF_FILTER, 0, getpid()); - set_ebpf_int_config((int32_t)DATA_SAMPLING, 0, mConfig->mSampling); - set_ebpf_int_config((int32_t)PERF_BUFFER_PAGE, (int32_t)DATA_HAND, 512); - LOG_INFO(sLogger, ("init ebpf source", "success")); - mInitSuccess = true; - return true; -} - -bool EBPFWrapper::Start() { - if (!mInitSuccess) { - return false; - } - if (mStartSuccess) { - return true; - } - set_ebpf_int_config((int32_t)PROTOCOL_FILTER, 0, mConfig->mProtocolProcessFlag); - int err = g_ebpf_start_func == NULL ? -100 : g_ebpf_start_func(); - if (err) { - LOG_ERROR(sLogger, ("start ebpf", "failed")("error", err)); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_RUNTIME_ALARM, - "start ebpf failed, error: " + std::to_string(err)); - this->Stop(); - return false; - } - LOG_INFO(sLogger, ("start ebpf source", "success")); - mStartSuccess = true; - return true; -} - -bool EBPFWrapper::Stop() { - CleanAllDisableProcesses(); - mStartSuccess = false; - return true; -} - -int32_t EBPFWrapper::ProcessPackets(int32_t maxProcessPackets, int32_t maxProcessDurationMs) { - if (g_ebpf_poll_events_func == nullptr || !mStartSuccess) { - return -1; - } - auto res = g_ebpf_poll_events_func(maxProcessPackets, &this->holdOnFlag); - if (res < 0 && res != -100) { - LOG_ERROR(sLogger, ("pull ebpf events", "failed")("error", "unknown polling result")("result", res)); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_RUNTIME_ALARM, - "pull ebpf events, error: " + std::to_string(res)); - } - return res; -} - -uint32_t EBPFWrapper::ConvertConnIdToSockHash(struct connect_id_t* id) { - return XXH32(id, sizeof(struct connect_id_t), 0); -} - - -void EBPFWrapper::GetAllConnections(std::vector& connIds) { - if (!mStartSuccess || g_ebpf_get_fd_func == NULL) { - return; - } - int32_t fd = g_ebpf_get_fd_func(); - struct connect_id_t key = {}; - struct connect_id_t next_key; - while (g_ebpf_get_next_key_func(fd, &key, &next_key) == 0) { - connIds.push_back(key); - key = next_key; - } -} - -void EBPFWrapper::DeleteInvalidConnections(const std::vector& connIds) { - if (!mStartSuccess || g_ebpf_delete_map_value_func == NULL) { - return; - } - const size_t maxDeleteCount = 64; - for (size_t i = 0; i < connIds.size(); i += maxDeleteCount) { - size_t endIndex = std::min(connIds.size(), i + maxDeleteCount); - g_ebpf_delete_map_value_func((void*)&connIds[i], endIndex - i); - } -} - -SocketCategory EBPFWrapper::ConvertSockAddress(union sockaddr_t& from, - struct connect_id_t& connectId, - SockAddress& addr, - uint16_t& port) { - static auto sConnManager = ConnectionMetaManager::GetInstance(); - if (from.sa.sa_family == AF_INET) { - addr.Type = SockAddressType_IPV4; - addr.Addr.IPV4 = from.in4.sin_addr.s_addr; - port = ntohs(from.in4.sin_port); - return SocketCategory::InetSocket; - } else if (from.sa.sa_family == AF_INET6) { - addr.Type = SockAddressType_IPV6; - addr.Addr.IPV6[0] = ((uint64_t*)&from.in6.sin6_addr)[0]; - addr.Addr.IPV6[1] = ((uint64_t*)&from.in6.sin6_addr)[1]; - port = ntohs(from.in6.sin6_port); - return SocketCategory::InetSocket; - } else if (from.sa.sa_family == AF_UNIX) { - addr.Type = SockAddressType_IPV4; - addr.Addr.IPV4 = 0; - port = 0; - return SocketCategory::UnixSocket; - } else { - auto info = sConnManager->GetConnectionInfo(connectId.tgid, connectId.fd); - if (info != nullptr) { - if (info->family == AF_UNIX) { - addr.Type = SockAddressType_IPV4; - addr.Addr.IPV4 = 0; - port = 0; - union sockaddr_t socketAddr = {}; - socketAddr.sa.sa_family = AF_UNIX; - // update unix connection info family and update sample status to ignore unnecessary data processing. - g_ebpf_update_conn_addr_func(&connectId, &socketAddr, 0, this->mConfig->mDropUnixSocket); - return SocketCategory::UnixSocket; - } else if (info->family == AF_INET) { - addr.Type = SockAddressType_IPV4; - addr = info->remoteAddr; - port = info->remotePort; - - union sockaddr_t socketAddr = {}; - socketAddr.sa.sa_family = AF_INET; - socketAddr.in4.sin_port = htons(info->remotePort); - socketAddr.in4.sin_addr.s_addr = info->remoteAddr.Addr.IPV4; - g_ebpf_update_conn_addr_func(&connectId, &socketAddr, 0, false); - return SocketCategory::InetSocket; - } else if (info->family == AF_INET6) { - addr.Type = SockAddressType_IPV6; - addr = info->remoteAddr; - port = info->remotePort; - - union sockaddr_t socketAddr = {}; - socketAddr.in6.sin6_addr = in6addr_any; - socketAddr.sa.sa_family = AF_INET6; - socketAddr.in6.sin6_port = htons(info->remotePort); - ((uint64_t*)&socketAddr.in6.sin6_addr)[0] = info->remoteAddr.Addr.IPV6[0]; - ((uint64_t*)&socketAddr.in6.sin6_addr)[1] = info->remoteAddr.Addr.IPV6[1]; - g_ebpf_update_conn_addr_func(&connectId, &socketAddr, 0, false); - return SocketCategory::InetSocket; - } - } - addr.Type = SockAddressType_IPV4; - addr.Addr.IPV4 = 0; - port = 0; - return SocketCategory::UnknownSocket; - } -} - -SocketCategory EBPFWrapper::ConvertDataToPacketHeader(struct conn_data_event_t* event, PacketEventHeader* header) { - header->PID = event->attr.conn_id.tgid; - header->SockHash = ConvertConnIdToSockHash(&(event->attr.conn_id)); - header->EventType = PacketEventType_Data; - header->SrcAddr.Type = SockAddressType_IPV4; - header->SrcAddr.Addr.IPV4 = 0; - header->SrcPort = 0; - header->RoleType = DetectRole(event->attr.role, event->attr.conn_id, header->DstPort); - return ConvertSockAddress(event->attr.addr, event->attr.conn_id, header->DstAddr, header->DstPort); -} - -PacketRoleType EBPFWrapper::DetectRole(enum support_role_e& kernelDetectRole, - struct connect_id_t& connectId, - uint16_t& remotePort) const { - static auto sConnManager = ConnectionMetaManager::GetInstance(); - if (kernelDetectRole != IsUnknown) { - auto role = kernelDetectRole == IsClient ? PacketRoleType::Client : PacketRoleType::Server; - LOG_TRACE(sLogger, ("role_type_detected_by_kernel", PacketRoleTypeToString(role))); - return role; - } - auto info = sConnManager->GetConnectionInfo(connectId.tgid, connectId.fd); - if (info != nullptr && (info->family == AF_INET || info->family == AF_INET6)) { - g_ebpf_update_conn_role_func(&connectId, - info->role == PacketRoleType::Client ? IsClient - : info->role == PacketRoleType::Server ? IsServer - : IsUnknown); - LOG_TRACE(sLogger, ("role_type_detected_by_netlink", PacketRoleTypeToString(info->role))); - return info->role; - } - return PacketRoleType::Unknown; -} - - -bool EBPFWrapper::ConvertCtrlToPacketHeader(struct conn_ctrl_event_t* event, PacketEventHeader* header) { - header->PID = event->conn_id.tgid; - header->SockHash = ConvertConnIdToSockHash(&(event->conn_id)); - header->EventType = event->type == EventConnect ? PacketEventType_Connected : PacketEventType_Closed; - // local address always set to 0.0.0.0 - header->SrcAddr.Type = SockAddressType_IPV4; - header->SrcAddr.Addr.IPV4 = 0; - header->SrcPort = 0; - if (event->type == EventConnect) { - ConvertSockAddress(event->connect.addr, event->conn_id, header->DstAddr, header->DstPort); - } else { - header->DstAddr.Type = SockAddressType_IPV4; - header->DstAddr.Addr.IPV4 = 0; - header->DstPort = 0; - } - return true; -} - -void EBPFWrapper::OnData(struct conn_data_event_t* event) { - if (event->attr.msg_buf_size > CONN_DATA_MAX_SIZE) { - LOG_WARNING(sLogger, ("module", "ebpf")("error", "invalid ebpf data event")("size", event->attr.msg_buf_size)); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_RUNTIME_ALARM, - "invalid ebpf data event because the size is over " - + std::to_string(CONN_DATA_MAX_SIZE)); - return; - } - // convert data event to PacketEvent - auto* header = (PacketEventHeader*)(&mPacketDataBuffer.at(0)); - SocketCategory socketCategory = ConvertDataToPacketHeader(event, header); - EBPF_CONNECTION_FILTER(socketCategory, header->DstAddr, event->attr.conn_id, event->attr.addr); - header->TimeNano = event->attr.ts + mDeltaTimeNs; - PacketEventData* data = (PacketEventData*)(&mPacketDataBuffer.at(0) + sizeof(PacketEventHeader)); - data->PktType = (PacketType)event->attr.direction; - data->PtlType = (ProtocolType)event->attr.protocol; - data->BufferLen = event->attr.msg_buf_size; - data->RealLen = event->attr.org_msg_size; - if (header->RoleType == PacketRoleType::Unknown) { - data->MsgType = InferRequestOrResponse(data->PktType, header); - header->RoleType = InferServerOrClient(data->PktType, data->MsgType); - LOG_TRACE(sLogger, ("role_type_detected_by_guess", PacketRoleTypeToString(header->RoleType))); - LOG_TRACE(sLogger, ("message_type_detected_by_guess", MessageTypeToString(data->MsgType))); - g_ebpf_update_conn_role_func(&event->attr.conn_id, - header->RoleType == PacketRoleType::Client ? IsClient - : header->RoleType == PacketRoleType::Server ? IsServer - : IsUnknown); - } else if (event->attr.direction == DirIngress) { - data->MsgType = header->RoleType == PacketRoleType::Client ? MessageType_Response : MessageType_Request; - LOG_TRACE(sLogger, ("message_type_detected_success", MessageTypeToString(data->MsgType))); - } else { - data->MsgType = header->RoleType == PacketRoleType::Client ? MessageType_Request : MessageType_Response; - LOG_TRACE(sLogger, ("message_type_detected_success", MessageTypeToString(data->MsgType))); - } - LOG_TRACE( - sLogger, - ("data event, addr", SockAddressToString(header->DstAddr))("port", header->DstPort)( - "family", event->attr.addr.sa.sa_family)("pid", event->attr.conn_id.tgid)("fd", event->attr.conn_id.fd)( - "protocol", event->attr.protocol)("length_header", event->attr.length_header)( - "role", PacketRoleTypeToString(header->RoleType))("ori_role", std::to_string(event->attr.role))( - "ori_msg", MessageTypeToString((MessageType)event->attr.type))("msg", MessageTypeToString(data->MsgType))( - "data", charToHexString(event->msg, event->attr.msg_buf_size, event->attr.msg_buf_size))( - "hash", header->SockHash)("raw_data", std::string(event->msg, event->attr.msg_buf_size)) - - ); - // Kafka protocol would first read 4Byte data, so kernel use try_to_prepend to identify this condition. - // But this operation is an optional choose for Mysql protocol. So, MySQL protocol would also append 4Bytes - // when the length_header is positive number. - if (event->attr.try_to_prepend || isAppendMySQLMsg(event)) { - data->Buffer = &mPacketDataBuffer.at(0) + sizeof(PacketEventHeader) + sizeof(PacketEventData); - data->BufferLen = event->attr.msg_buf_size + 4; - data->RealLen = event->attr.org_msg_size + 4; - *(uint32_t*)data->Buffer = event->attr.length_header; - memcpy(data->Buffer + 4, event->msg, event->attr.msg_buf_size); - LOG_TRACE(sLogger, ("data event append data", charToHexString(data->Buffer, data->BufferLen, data->BufferLen))); - } else { - data->Buffer = event->msg; - data->BufferLen = event->attr.msg_buf_size; - data->RealLen = event->attr.org_msg_size; - } - if (mPacketProcessor) { - mPacketProcessor(StringPiece(mPacketDataBuffer.data(), sizeof(PacketEventHeader) + sizeof(PacketEventData))); - } -} - -void EBPFWrapper::OnCtrl(struct conn_ctrl_event_t* event) { - PacketEventHeader* header = (PacketEventHeader*)(&mPacketDataBuffer.at(0)); - header->TimeNano = event->ts + mDeltaTimeNs; - ConvertCtrlToPacketHeader(event, header); - if (mPacketProcessor) { - mPacketProcessor(StringPiece(mPacketDataBuffer.data(), sizeof(PacketEventHeader))); - } -} - -void EBPFWrapper::OnStat(struct conn_stats_event_t* event) { - logtail::NetStatisticsKey key; - key.SockCategory - = ConvertSockAddress(event->addr, event->conn_id, key.AddrInfo.RemoteAddr, key.AddrInfo.RemotePort); - EBPF_CONNECTION_FILTER(key.SockCategory, key.AddrInfo.RemoteAddr, event->conn_id, event->addr); - key.PID = event->conn_id.tgid; - key.SockHash = ConvertConnIdToSockHash(&(event->conn_id)); - key.AddrInfo.LocalPort = 0; - key.AddrInfo.LocalAddr.Type = SockAddressType_IPV4; - key.AddrInfo.LocalAddr.Addr.IPV4 = 0; - key.RoleType = DetectRole(event->role, event->conn_id, key.AddrInfo.RemotePort); - LOG_TRACE(sLogger, - ("receive stat event,addr", SockAddressToString(key.AddrInfo.RemoteAddr))( - "family", event->addr.sa.sa_family)("socket_type", SocketCategoryToString(key.SockCategory))( - "port", key.AddrInfo.RemotePort)("pid", key.PID)("fd", event->conn_id.fd)); - NetStatisticsTCP& item = mStatistics.GetStatisticsItem(key); - item.Base.SendBytes += event->wr_bytes - event->last_output_wr_bytes; - item.Base.RecvBytes += event->rd_bytes - event->last_output_rd_bytes; - item.Base.SendPackets += event->wr_pkts - event->last_output_wr_pkts; - item.Base.RecvPackets += event->wr_pkts - event->last_output_wr_pkts; -} - -void EBPFWrapper::OnLost(enum callback_type_e type, uint64_t count) { - LOG_DEBUG(sLogger, ("module", "ebpf")("lost event type", (int)type)("count", count)); - static auto sMetrics = NetworkStatistic::GetInstance(); - sMetrics->mEbpfLostCount += count; -} - -bool EBPFWrapper::isAppendMySQLMsg(conn_data_event_t* pEvent) { - if (pEvent->attr.protocol != ProtoMySQL) { - return false; - } - if ((pEvent->attr.length_header & 0x00ffffff) != pEvent->attr.org_msg_size) { - return false; - } - if (pEvent->attr.msg_buf_size > 4) { - uint32_t len = (*((uint8_t*)(pEvent->msg + 2)) << 16) + (*((uint8_t*)(pEvent->msg + 1)) << 8) - + (*((uint8_t*)(pEvent->msg))); - if (len == pEvent->attr.org_msg_size) { - return false; - } - } - return true; -} - -void EBPFWrapper::DisableProcess(uint32_t pid) { - if (!mStartSuccess) { - return; - } - if (this->mDisabledProcesses.find(pid) != this->mDisabledProcesses.end()) { - return; - } - uint64_t startTime = readStat(pid); - if (startTime == 0) { - return; - } - this->mDisabledProcesses[pid] = startTime; - LOG_DEBUG(sLogger, ("insert disable process", pid)); - g_ebpf_disable_process_func(pid, false); -} - -uint64_t EBPFWrapper::readStat(uint64_t pid) { - std::string statPath = std::string("/proc/").append(std::to_string(pid)).append("/stat"); - std::string content; - if (!ReadFileContent(statPath, content, 1024)) { - return 0; - } - if (content.empty()) { - return 0; - } - int count = 0; - int startIndex = 0, endIndex = 0; - for (size_t i = 0; i < content.length(); ++i) { - if (content[i] == ' ') { - count++; - } - if (count == 21) { - startIndex = i + 1; - continue; - } - if (count == 22) { - endIndex = i; - break; - } - } - if (startIndex == 0 || endIndex == 0) { - return 0; - } - return std::strtol(content.c_str() + startIndex, NULL, 10); -} - -void EBPFWrapper::ProbeProcessStat() { - if (!mStartSuccess) { - return; - } - std::stringstream ss; - for (auto item = this->mDisabledProcesses.begin(); item != this->mDisabledProcesses.end();) { - uint64_t startTime = readStat(item->first); - if (startTime == 0 || item->second != startTime) { - if (sLogger->should_log(spdlog::level::debug)) { - ss << item->first << ", "; - } - g_ebpf_disable_process_func(item->first, true); - item = this->mDisabledProcesses.erase(item); - } else { - item++; - } - } - LOG_DEBUG(sLogger, - ("ebpf disable processes size after probe", mDisabledProcesses.size())("recover processes", ss.str())); -} - -void EBPFWrapper::CleanAllDisableProcesses() { - if (!mStartSuccess) { - return; - } - std::stringstream ss; - for (auto item = this->mDisabledProcesses.begin(); item != this->mDisabledProcesses.end();) { - if (sLogger->should_log(spdlog::level::debug)) { - ss << item->first << ", "; - } - g_ebpf_disable_process_func(item->first, true); - item = this->mDisabledProcesses.erase(item); - } - LOG_DEBUG(sLogger, - ("ebpf disable processes size after clean", mDisabledProcesses.size())("recover processes", ss.str())); -} - -int32_t EBPFWrapper::GetDisablesProcessCount() { - if (!mStartSuccess) { - return 0; - } - return mDisabledProcesses.size(); -} -void EBPFWrapper::HoldOn(bool exitFlag) { - holdOnFlag = 1; - if (exitFlag) { - int err = g_ebpf_stop_func == NULL ? -100 : g_ebpf_stop_func(); - if (err) { - LOG_INFO(sLogger, ("stop ebpf", "failed")("error", err)); - } - } -} -} // namespace logtail diff --git a/core/observer/network/sources/ebpf/EBPFWrapper.h b/core/observer/network/sources/ebpf/EBPFWrapper.h deleted file mode 100644 index 8f7e5d278c..0000000000 --- a/core/observer/network/sources/ebpf/EBPFWrapper.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - - -#include "observer/network/sources/ebpf/include/syscall.h" -#include "DynamicLibHelper.h" -#include "network/NetworkConfig.h" -#include -#include "observer/interface/helper.h" -#include "metas/ConnectionMetaManager.h" -#include "common/StringPiece.h" - -// ::ffff:127.0.0.1 -const uint64_t local_addr_bind_inet6[2] = {0, 72058143793676288}; -#define EBPF_CONNECTION_FILTER(socketType, socketAddr, connectionId, connectionAddr) \ - { \ - if (this->mConfig->mDropUnixSocket && (socketType) == SocketCategory::UnixSocket) { \ - return; \ - } \ - if (this->mConfig->mDropLocalConnections && (socketType) == SocketCategory::InetSocket) { \ - if ((socketAddr).Type == SockAddressType_IPV4 \ - && ((socketAddr).Addr.IPV4 == INADDR_ANY || (socketAddr).Addr.IPV4 == htonl(INADDR_LOOPBACK))) { \ - g_ebpf_update_conn_addr_func(&(connectionId), &(connectionAddr), 0, true); \ - return; \ - } else if ((socketAddr).Type == SockAddressType_IPV6 && (socketAddr).Addr.IPV6[0] == 0 \ - && (memcmp((socketAddr).Addr.IPV6, &in6addr_any, 16) == 0 \ - || memcmp((socketAddr).Addr.IPV6, &in6addr_loopback, 16) == 0 \ - || memcmp((socketAddr).Addr.IPV6, local_addr_bind_inet6, 16) == 0)) { \ - g_ebpf_update_conn_addr_func(&(connectionId), &(connectionAddr), 0, true); \ - return; \ - } \ - } \ - if (this->mConfig->mDropUnknownSocket && (socketType) == SocketCategory::UnknownSocket) { \ - return; \ - } \ - } -namespace logtail { -class EBPFWrapper { -public: - explicit EBPFWrapper(NetworkConfig* config) : mConfig(config) { - mPacketDataBuffer.resize(CONN_DATA_MAX_SIZE + 4096); - } - - ~EBPFWrapper() { Stop(); } - - static EBPFWrapper* GetInstance() { - static auto* sWrapper = new EBPFWrapper(NetworkConfig::GetInstance()); - sWrapper->mConnectionMetaManager = ConnectionMetaManager::GetInstance(); - return sWrapper; - } - - bool Init(std::function processor); - - bool Start(); - - bool Stop(); - - void HoldOn(bool exitFlag); - - void Resume() { holdOnFlag = 0; } - - int32_t ProcessPackets(int32_t maxProcessPackets, int32_t maxProcessDurationMs); - - NetStaticticsMap& GetStatistics() { return mStatistics; } - - void OnData(struct conn_data_event_t* event); - void OnCtrl(struct conn_ctrl_event_t* event); - void OnStat(struct conn_stats_event_t* event); - void OnLost(enum callback_type_e type, uint64_t count); - - void GetAllConnections(std::vector& connIds); - void DeleteInvalidConnections(const std::vector& connIds); - - void DisableProcess(uint32_t pid); - static uint32_t ConvertConnIdToSockHash(struct connect_id_t* id); - void ProbeProcessStat(); - void CleanAllDisableProcesses(); - int32_t GetDisablesProcessCount(); - -protected: - SocketCategory ConvertDataToPacketHeader(struct conn_data_event_t* event, PacketEventHeader* header); - bool ConvertCtrlToPacketHeader(struct conn_ctrl_event_t* event, PacketEventHeader* header); - SocketCategory - ConvertSockAddress(union sockaddr_t& from, struct connect_id_t& connectId, SockAddress& addr, uint16_t& port); - PacketRoleType - DetectRole(enum support_role_e& kernelDetectRole, struct connect_id_t& connectId, uint16_t& remotePort) const; - -private: - static uint64_t readStat(uint64_t pid); - -private: - NetworkConfig* mConfig; - DynamicLibLoader* mEBPFLib = NULL; - std::function mPacketProcessor; - std::int32_t holdOnFlag{0}; - NetStaticticsMap mStatistics; - std::string mPacketDataBuffer; - bool mInitSuccess = false; - bool mStartSuccess = false; - uint64_t mDeltaTimeNs = 0; - std::unordered_map mDisabledProcesses; - - ConnectionMetaManager* mConnectionMetaManager; - - bool isAppendMySQLMsg(conn_data_event_t* pEvent); - /** - * @param kernelVersion - * @param kernelRelease - * @return true if kernel version >= sls_observer_ebpf_min_kernel_version or kernel version equals to 3.10.x in - * centos 7.6+. - */ - bool isSupportedOS(int64_t& kernelVersion, std::string& kernelRelease); - bool loadEbpfLib(int64_t kernelVersion, std::string& soPath); -}; - - -} // namespace logtail diff --git a/core/observer/network/sources/ebpf/include/net.h b/core/observer/network/sources/ebpf/include/net.h deleted file mode 100644 index eb72fba080..0000000000 --- a/core/observer/network/sources/ebpf/include/net.h +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#ifndef LCC_SYSCALL_NET_H -#define LCC_SYSCALL_NET_H - -#define CONN_DATA_MAX_SIZE 16384 -#define DATA_SAMPLE_ALL 100 - -enum support_proto_e { - ProtoUnknown = 0, - ProtoHTTP = 1, - ProtoMySQL = 2, - ProtoDNS = 3, - ProtoRedis = 4, - ProtoKafka = 5, - ProtoPGSQL = 6, - ProtoMongo = 7, - ProtoDubbo = 8, - ProtoHSF = 9, - NumProto, -}; -enum support_role_e { - IsUnknown = 0x01, - IsClient = 0x02, - IsServer = 0x04, -}; - -enum tgid_config_e { - TgidIndex = 0, - TgidNum, -}; - -enum support_conn_status_e { - StatusOpen, - StatusClose, -}; - -enum support_syscall_e { - FuncUnknown, - FuncWrite, - FuncRead, - FuncSend, - FuncRecv, - FuncSendTo, - FuncRecvFrom, - FuncSendMsg, - FuncRecvMsg, - FuncMmap, - FuncSockAlloc, - FuncAccept, - FuncAccept4, - FuncSecuritySendMsg, - FuncSecurityRecvMsg, -}; - -enum support_direction_e { - DirUnknown, - DirIngress, - DirEgress, -}; - -enum support_event_e { - EventConnect, - EventClose, -}; - -enum support_tgid_e { - TgidUndefine, - TgidAll, - TgidMatch, - TgidUnmatch, -}; - -enum support_type_e { TypeUnknown, TypeRequest, TypeResponse }; - -struct addr_pair_t { - uint32_t saddr; - uint32_t daddr; - uint16_t sport; - uint16_t dport; -}; - -struct map_syscall_t { - int funcid; - char* funcname; -}; - -struct mproto_t { - int protocol; - char* proto_name; -}; - -struct test_data { - struct addr_pair_t ap; - uint64_t size; - int fd; - char com[16]; - char func[16]; - int pid; - int family; - int funcid; - int ret_val; -}; - -union sockaddr_t { - struct sockaddr sa; - struct sockaddr_in in4; - struct sockaddr_in6 in6; -}; - -struct connect_id_t { - int32_t fd; - uint32_t tgid; - uint64_t start; -}; - -struct conn_event_t { - union sockaddr_t addr; - enum support_role_e role; -}; - -struct close_event_t { - int64_t wr_bytes; - int64_t rd_bytes; -}; - -struct conn_ctrl_event_t { - enum support_event_e type; - uint64_t ts; - struct connect_id_t conn_id; - union { - struct conn_event_t connect; - struct close_event_t close; - }; -}; - -struct conn_data_event_t { - struct attr_t { - uint64_t ts; - struct connect_id_t conn_id; - union sockaddr_t addr; - enum support_proto_e protocol; - enum support_role_e role; - enum support_type_e type; - enum support_direction_e direction; - enum support_syscall_e syscall_func; - uint64_t pos; - uint32_t org_msg_size; - uint32_t msg_buf_size; - bool try_to_prepend; - uint32_t length_header; - } attr; - char msg[CONN_DATA_MAX_SIZE]; -}; - -struct conn_stats_event_t { - uint64_t ts; - struct connect_id_t conn_id; - union sockaddr_t addr; - enum support_role_e role; - int64_t wr_bytes; - int64_t rd_bytes; - int32_t wr_pkts; - int32_t rd_pkts; - int64_t last_output_wr_bytes; - int64_t last_output_rd_bytes; - int32_t last_output_wr_pkts; - int32_t last_output_rd_pkts; - uint32_t conn_events; -}; - -struct connect_info_t { - struct connect_id_t conn_id; - union sockaddr_t addr; - enum support_proto_e protocol; - enum support_role_e role; - enum support_type_e type; - int64_t wr_bytes; - int64_t rd_bytes; - int32_t wr_pkts; - int32_t rd_pkts; - int64_t last_output_wr_bytes; - int64_t last_output_rd_bytes; - int32_t last_output_wr_pkts; - int32_t last_output_rd_pkts; - int32_t total_bytes_for_proto; - uint64_t last_output_time; - size_t prev_count; - char prev_buf[4]; - bool try_to_prepend; - bool is_sample; -}; - -struct protocol_type_t { - enum support_proto_e protocol; - enum support_type_e type; -}; - -struct tg_info_t { - uint32_t tgid; - int32_t fd; - enum support_role_e role; -}; - -struct conn_param_t { - const struct sockaddr* addr; - int32_t fd; -}; - -struct accept_param_t { - struct sockaddr* addr; - struct socket* accept_socket; -}; - -struct close_param_t { - int32_t fd; -}; - -struct data_param_t { - enum support_syscall_e syscall_func; - bool real_conn; - int32_t fd; - const char* buf; - const struct iovec* iov; - size_t iovlen; - unsigned int* msg_len; -}; - -struct config_info_t { - int32_t port; - int32_t self_pid; - int32_t data_sample; -}; -#endif diff --git a/core/observer/network/sources/ebpf/include/syscall.h b/core/observer/network/sources/ebpf/include/syscall.h deleted file mode 100644 index ab90790915..0000000000 --- a/core/observer/network/sources/ebpf/include/syscall.h +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -/* - * Author: Chen Tao - * Create: Sun Feb 20 20:32:45 2022 - */ - -#include -#include -#include -#include -#include "net.h" - -enum callback_type_e { - CTRL_HAND = 0, - DATA_HAND, - STAT_HAND, -#ifdef NET_TEST - TEST_HAND, -#endif - MAX_HAND, -}; - - -#define MAX_PROTOCOL_NUM 5 -#ifdef NET_TEST -typedef void (*net_test_process_func_t)(void* custom_data, struct test_data* event); -#endif -typedef void (*net_data_process_func_t)(void* custom_data, struct conn_data_event_t* event); -typedef void (*net_ctrl_process_func_t)(void* custom_data, struct conn_ctrl_event_t* event); -typedef void (*net_statistics_process_func_t)(void* custom_data, struct conn_stats_event_t* event); -typedef void (*net_lost_func_t)(void* custom_data, enum callback_type_e type, uint64_t lost_count); -typedef int (*net_print_fn_t)(int16_t level, const char* format, va_list args); - -#ifdef NET_TEST -void ebpf_setup_net_test_process_func(net_test_process_func_t func, void* custom_data); -#endif -void ebpf_setup_net_data_process_func(net_data_process_func_t func, void* custom_data); -void ebpf_setup_net_event_process_func(net_ctrl_process_func_t func, void* custom_data); -void ebpf_setup_net_statistics_process_func(net_statistics_process_func_t func, void* custom_data); -void ebpf_setup_net_lost_func(net_lost_func_t func, void* custom_data); -void ebpf_setup_print_func(net_print_fn_t func); - - -enum ebpf_config_primary_e { - PROTOCOL_FILTER = 0, // 默认值-1。协议类型过滤器,为-1时代表Trace所有协议,其他只允许某一协议 - TGID_FILTER, // 默认值-1。进程过滤器,为-1时代表Trace所有进程,其他只允许某一进程 - SELF_FILTER, // 默认值-1。是否Disable自身的Trace,为-1代表不Disable,其他情况会传入本进程的ID,这时需要过滤掉该进程所有的数据 - PORT_FILTER, // 默认值-1。端口过滤器,为-1时代表Trace所有端口,其他只允许某一端口 - DATA_SAMPLING, // 默认值100。采样策略,取值0 -> 100,代表采样的百分比(0全部丢弃,100全部上传) - // 采样的策略:tcp的包,连接建立的ns时间 % 100, - // 小于采样率即为需要上传,大于的话对该连接进行标记,不上传Data、Ctrl(统计数据还是要上传) - // udp的包,接收到数据包的ns时间 % 100, 小于采样率即为需要上传,大于的话不上传Data(统计数据还是要上传 - // @note 要注意统计数据Map的清理策略) - PERF_BUFFER_PAGE, // ring buffer page count, 默认128个页,也就是512KB, opt2 的类型是 callback_type_e -}; -// opt1 列表: -// AddProtocolFilter、RemoveProtocolFilter -// AddTGIDFilter、RemoveTGIDFilter -// AddConnFilter、RemoveConnFilter -// AddPortFilter、RemovePortFilter - -/** - * @brief 配置各类参数,例如监听的TGID、协议、端口等黑白名单,采集策略等 - * 每个参数由对应的配置类型来推导,例如 opt1是 AddPortFilter opt2 是 BlackList,params_count 是1,params = - * {&uint16_t(80)}, params_len = {2} - * @param opt1 配置主类型:ebpf_config_primary_e - * @param opt2 配置副类型,[暂未使用] - * @param params_count 参数个数 [目前均为1] - * @param params 参数列表 [int32] - * @param params_len 每个参数的长度,[均为4] - * - * int32_t disabledPort = 443; - * int32_t * params[] = {&disabledPort}; - * int32_t paramsLen[] = {4}; - * ebpf_config(PORT_FILTER, 0, 1, params, paramsLen); - * - * if ((ebpf_config_primary_e)opt1 == PORT_FILTER) { - * disabledPort = (int32_t *)(params[0])[0]; - * // update to bpf code - * } - */ -void ebpf_config(int32_t opt1, int32_t opt2, int32_t params_count, void** params, int32_t* params_len); - -/** - * @brief 由外层调用,每次调用poll数据,然后交给预先setup好的3个回调来处理,每次poll数据,需要检查stop_flag是否 >0,如果 - * > 0立即退出。 顺序:控制、统计、Data - * - * @param max_events 最多处理的事件数 - * @param stop_flag 是否需要立即退出 - * @return int32_t 正数,返回处理的事件数; -100,stop_flag触发;其他,错误码 - */ -int32_t ebpf_poll_events(int32_t max_events, int32_t* stop_flag); - -// 启动时,会调用init,然后调用start -/* - * @btf btf路径包括btf文件全名, 传NULL就默认在/usr/lib/vmlinux-** - * @so so文件路径包括so文件全名,uprobe解析用到 - * @return int32_t 0:success, others:failed - * @uprobe_offset cleanup_dog函数偏移 - * @update_conn_addr_offset update_conn_addr函数偏移 - * @upps_offset disable_process函数偏移 - * @upcr_offset ebpf_update_conn_role 函数偏移 - */ -int32_t ebpf_init(char* btf, - int32_t btf_size, - char* so, - int32_t so_size, - long uprobe_offset, - long upca_offset, - long upps_offset, - long upcr_offset); -/* - * @return int32_t 0:success, others:failed - */ -int32_t ebpf_start(void); - -// 是否支持运行期间动态的 start stop? 如果不支持,那只有程序退出的时候会调用/或者永远不调用,等进程销毁自动回收 -/* - * @return int32_t 0:success, others:failed - */ -int32_t ebpf_stop(void); - -/* - * @return 返回fd - */ -int32_t ebpf_get_fd(void); - -// 这里的key是u64类型,conn_info_map的key -int32_t ebpf_get_next_key(int32_t fd, const void* key, void* next_key); - -// 这里的key传connet_id_t结构体 -void ebpf_delete_map_value(void* key, int32_t size); - -// 用于uprobe, 没有实现内容 -void ebpf_cleanup_dog(void* key, int32_t size); - -// 更新conn对应的Remote address和local port, uprobe -void ebpf_update_conn_addr(struct connect_id_t* conn_id, union sockaddr_t* dest_addr, uint16_t local_port, bool drop); - -// 更新process 观察范围,动态增加pid,drop 为true 是进行删除操作。 -void ebpf_disable_process(uint32_t pid, bool drop); - -// 更新conn对应的角色,某些协议内核态无法判断角色 -void ebpf_update_conn_role(struct connect_id_t* conn_id, enum support_role_e role_type); \ No newline at end of file diff --git a/core/observer/network/sources/pcap/PCAPWrapper.cpp b/core/observer/network/sources/pcap/PCAPWrapper.cpp deleted file mode 100644 index 63adcff359..0000000000 --- a/core/observer/network/sources/pcap/PCAPWrapper.cpp +++ /dev/null @@ -1,474 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "PCAPWrapper.h" - -#include -#include "common/xxhash/xxhash.h" -#include "common/MachineInfoUtil.h" -#include "logger/Logger.h" -#include "network/protocols/infer.h" -#include "RuntimeUtil.h" -#include "network/protocols/utils.h" -#include "interface/layerfour.h" - -static void OnPCAPPacketsCallBack(u_char* user, const struct pcap_pkthdr* packet_header, const u_char* packet_content) { - logtail::PCAPWrapper* wrapper = (logtail::PCAPWrapper*)user; - wrapper->PCAPCallBack(packet_header, packet_content); -} - -typedef int (*pcap_compile_func)(pcap_t*, struct bpf_program*, const char*, int, bpf_u_int32); -typedef char* (*pcap_lookupdev_func)(char*); -typedef int (*pcap_lookupnet_func)(const char*, bpf_u_int32*, bpf_u_int32*, char*); -typedef pcap_t* (*pcap_open_live_func)(const char*, int, int, int, char*); -typedef int (*pcap_setfilter_func)(pcap_t*, struct bpf_program*); -typedef int (*pcap_dispatch_func)(pcap_t*, int, pcap_handler, u_char*); -typedef void (*pcap_close_func)(pcap_t*); -typedef char* (*pcap_geterr_func)(pcap_t*); - -pcap_compile_func g_pcap_compile_func = NULL; // pcap_compile -pcap_lookupdev_func g_pcap_lookupdev_func = NULL; // pcap_lookupdev -pcap_lookupnet_func g_pcap_lookupnet_func = NULL; // pcap_lookupnet -pcap_open_live_func g_pcap_open_live_func = NULL; // pcap_open_live -pcap_setfilter_func g_pcap_setfilter_func = NULL; // pcap_setfilter -pcap_dispatch_func g_pcap_dispatch_func = NULL; // pcap_dispatch -pcap_close_func g_pcap_close_func = NULL; // pcap_close -pcap_geterr_func g_pcap_geterr_func = NULL; // pcap_geterr - -static bool PCAPLoadSuccess() { - return g_pcap_compile_func != NULL && g_pcap_lookupdev_func != NULL && g_pcap_lookupnet_func != NULL - && g_pcap_open_live_func != NULL && g_pcap_setfilter_func != NULL && g_pcap_dispatch_func != NULL - && g_pcap_geterr_func != NULL && g_pcap_close_func != NULL; -} - -#define LOAD_PCAP_FUNC(funcName) \ - { \ - void* funcPtr = mPCAPLib->LoadMethod(#funcName, loadErr); \ - if (funcPtr == nullptr) { \ - LOG_ERROR(sLogger, ("load pcap method", "failed")("method", #funcName)("error", loadErr)); \ - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, \ - std::string("load pcap method failed: ") + #funcName); \ - return false; \ - } \ - g_##funcName##_func = funcName##_func(funcPtr); \ - } - -namespace logtail { -bool PCAPWrapper::Stop() { - if (mHandle != NULL && g_pcap_close_func != NULL) { - LOG_INFO(sLogger, ("pcap close", "begin")); - g_pcap_close_func(mHandle); - LOG_INFO(sLogger, ("pcap close", "success")); - mHandle = NULL; - memset(mErrBuf, 0, sizeof(mErrBuf)); - } - return true; -} -bool PCAPWrapper::Init(std::function processor) { - LOG_INFO(sLogger, ("init pcap", "begin")); - mHandle = NULL; - if (mPCAPLib == NULL) { - LOG_INFO(sLogger, ("load pcap dynamic library", "begin")); - mPCAPLib = new DynamicLibLoader; - std::string loadErr; - // pcap lib load - if (!mPCAPLib->LoadDynLib("pcap", loadErr, GetProcessExecutionDir())) { - if (!mPCAPLib->LoadDynLib("pcap", loadErr)) { - LOG_ERROR(sLogger, ("load pcap dynamic library", "failed")("error", loadErr)); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, "cannot load pcap dynamic library"); - return false; - } - } - LOAD_PCAP_FUNC(pcap_compile); - LOAD_PCAP_FUNC(pcap_lookupdev); - LOAD_PCAP_FUNC(pcap_lookupnet); - LOAD_PCAP_FUNC(pcap_open_live); - LOAD_PCAP_FUNC(pcap_setfilter); - LOAD_PCAP_FUNC(pcap_dispatch); - LOAD_PCAP_FUNC(pcap_close); - LOAD_PCAP_FUNC(pcap_geterr); - - LOG_INFO(sLogger, ("load pcap dynamic library", "success")); - } - if (!PCAPLoadSuccess()) { - LOG_ERROR(sLogger, ("load pcap dynamic library", "failed")("error", "last load failed")); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, "cannot load pcap dynamic library"); - return false; - } - mPacketProcessor = std::move(processor); - const char* netInterface; - if (!mConfig->mPCAPInterface.empty()) { - netInterface = mConfig->mPCAPInterface.c_str(); - } else { - netInterface = g_pcap_lookupdev_func(mErrBuf); - } - if (netInterface == nullptr) { - LOG_ERROR(sLogger, ("init pcap wrapper when get net interface error, err", mErrBuf)); - LogtailAlarm::GetInstance()->SendAlarm( - OBSERVER_INIT_ALARM, "cannot find any net interface in pcap source, err: " + std::string(mErrBuf)); - return false; - } - LOG_INFO(sLogger, ("init pcap with net interface", netInterface)); - bpf_u_int32 netp; - bpf_u_int32 maskp; - - // Get netmask - if (g_pcap_lookupnet_func(netInterface, &netp, &maskp, mErrBuf) == PCAP_ERROR) { - LOG_ERROR(sLogger, ("init pcap wrapper when loop up net error, err", mErrBuf)); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, - "cannot find any netmask in pcap source, err: " + std::string(mErrBuf)); - return false; - } - - // the device that we specified in the previous section - // snaplen is an integer which defines the maximum number of bytes to be captured by pcap - // promisc, when set to true, brings the interface into promiscuous mode (however, even if it is set to false, it is - // possible under specific cases for the interface to be in promiscuous mode, anyway) to_ms is the read time out in - // milliseconds (a value of 0 sniffs until an error occurs; -1 sniffs indefinitely) - mHandle = g_pcap_open_live_func(netInterface, BUFSIZ, mConfig->mPCAPPromiscuous, mConfig->mPCAPTimeoutMs, mErrBuf); - if (mHandle == NULL) { - LOG_ERROR(sLogger, ("init pcap wrapper when open pcap live error, err", mErrBuf)); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, - "cannot open pcap live, err: " + std::string(mErrBuf)); - return false; - } - // optimize controls whether optimization on the - // resulting code is performed. netmask specifies the IPv4 netmask - // of the network on which packets are being captured; it is used - // only when checking for IPv4 broadcast addresses in the filter - // program. - mLocalAddress = GetHostIpValueByInterface(std::string(netInterface)); - mLocalMaskAddress = netp; - - SockAddress addressLocal, addressNet, addressMask; - addressNet.Type = SockAddressType_IPV4; - addressNet.Addr.IPV4 = netp; - addressMask.Type = SockAddressType_IPV4; - addressMask.Addr.IPV4 = maskp; - addressLocal.Type = SockAddressType_IPV4; - addressLocal.Addr.IPV4 = mLocalAddress; - - LOG_INFO(sLogger, - ("local address", SockAddressToString(addressLocal))("pcap interface ip", SockAddressToString(addressNet))( - "mask", SockAddressToString(addressMask))); - - if (g_pcap_compile_func(mHandle, &mBPFFilter, mConfig->mPCAPFilter.c_str(), 0, netp) == PCAP_ERROR) { - LOG_ERROR(sLogger, - ("init pcap wrapper when compile bpf filter error, err", mErrBuf)("filter", mConfig->mPCAPFilter)); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, - "compile pcap bpf filter error, err: " + std::string(mErrBuf)); - return false; - } - LOG_INFO(sLogger, ("pcap compile bpf filter success, filter", mConfig->mPCAPFilter)); - if (g_pcap_setfilter_func(mHandle, &mBPFFilter) == PCAP_ERROR) { - LOG_ERROR(sLogger, ("init pcap wrapper when bind bpf filter error, err", mErrBuf)); - LogtailAlarm::GetInstance()->SendAlarm(OBSERVER_INIT_ALARM, - "bind pcap bpf filter error, err: " + std::string(mErrBuf)); - return false; - } - LOG_INFO(sLogger, ("init pcap", "success")); - return true; -} - -int32_t PCAPWrapper::ProcessPackets(int32_t maxProcessPackets, int32_t maxProcessDurationMs) { - if (g_pcap_dispatch_func == NULL || g_pcap_geterr_func == NULL || mHandle == NULL) { - return -2; - } - assert(mHandle != NULL); - int32_t processedPackets = 0; - for (; processedPackets < maxProcessPackets; processedPackets += INT32_FLAG(sls_observer_network_pcap_loop_count)) { - int dispathRst = g_pcap_dispatch_func( - mHandle, INT32_FLAG(sls_observer_network_pcap_loop_count), OnPCAPPacketsCallBack, (u_char*)this); - if (dispathRst >= int(INT32_FLAG(sls_observer_network_pcap_loop_count))) { - continue; - } - if (dispathRst >= 0) { - // reach end - LOG_DEBUG(sLogger, ("pcap dispath success, total", processedPackets + dispathRst)("last", dispathRst)); - return processedPackets + dispathRst; - } - LOG_WARNING(sLogger, ("pcap dispath error, code", dispathRst)("error", g_pcap_geterr_func(mHandle))); - return -1; - } - return maxProcessPackets; -} -void PCAPWrapper::PCAPCallBack(const struct pcap_pkthdr* header, const u_char* packet) { - assert(mPacketProcessor); - /* First, lets make sure we have an IP packet */ - struct ether_header* eth_header; - eth_header = (struct ether_header*)packet; - if (ntohs(eth_header->ether_type) != ETHERTYPE_IP) { - // printf("Not an IP packet. Skipping...\n\n"); - return; - } - - /* The total packet length, including all headers - and the data payload is stored in - header->len and header->caplen. Caplen is - the amount actually available, and len is the - total packet length even if it is larger - than what we currently have captured. If the snapshot - length set with pcap_open_live() is too small, you may - not have the whole packet. */ - // printf("Total packet available: %d bytes\n", header->caplen); - // printf("Expected packet size: %d bytes\n", header->len); - - /* Pointers to start point of various headers */ - const u_char* ip_header = NULL; - const u_char* payload = NULL; - - /* Header lengths in bytes */ - int ethernet_header_length = 14; /* Doesn't change */ - int ip_header_length = 0; - int payload_length = 0; // valid payload length, calc use pcap_pkthdr->caplen - int payload_raw_length = 0; // raw payload length, calc use pcap_pkthdr->len - - /* Find start of IP header */ - ip_header = packet + ethernet_header_length; - - struct iphdr* ipHeaderStruct = (struct iphdr*)ip_header; - - - /* The second-half of the first byte in ip_header - contains the IP header length (IHL). */ - ip_header_length = ipHeaderStruct->ihl; - /* The IHL is number of 32-bit segments. Multiply - by four to get a byte count for pointer arithmetic */ - ip_header_length = ip_header_length << 2; - // printf("IP header length (IHL) in bytes: %d\n", ip_header_length); - - /* Now that we know where the IP header is, we can - inspect the IP header for a protocol number to - make sure it is TCP before going any further. - Protocol is always the 10th byte of the IP header */ - u_char protocol = ipHeaderStruct->protocol; - uint16_t srcPort = 0; - uint16_t dstPort = 0; - - char packetBuffer[sizeof(PacketEventHeader) + sizeof(PacketEventData)] = {0}; - PacketEventHeader* eventHeader = (PacketEventHeader*)packetBuffer; - // @todo - bool retran = false; - bool zeroWindow = false; - if (protocol == IPPROTO_UDP) { - if (ethernet_header_length + ip_header_length + sizeof(udphdr) >= header->caplen) { - // invalid length - return; - } - const u_char* udp_header = packet + ethernet_header_length + ip_header_length; - struct udphdr* udpHeaderSturct = (struct udphdr*)udp_header; - srcPort = udpHeaderSturct->source; - dstPort = udpHeaderSturct->dest; - uint64_t udpLength = ntohs(udpHeaderSturct->len); // include udphdr - if (udpLength < sizeof(udphdr)) { - return; - } - payload_raw_length = payload_length = udpLength - sizeof(udphdr); - if (udpLength + ethernet_header_length + ip_header_length > header->caplen) { - payload_length = header->caplen - ethernet_header_length + ip_header_length - sizeof(udphdr); - } - payload = udp_header + sizeof(udphdr); - } else if (protocol == IPPROTO_TCP) { - const u_char* tcp_header = NULL; - int tcp_header_length = 0; - /* Add the ethernet and ip header length to the start of the packet - to find the beginning of the TCP header */ - tcp_header = packet + ethernet_header_length + ip_header_length; - struct tcphdr* tcpHeaderSturct = (struct tcphdr*)tcp_header; - if (ethernet_header_length + ip_header_length + sizeof(tcphdr) >= header->caplen) { - // invalid length - return; - } - /* TCP header length is stored in the first half - of the 12th byte in the TCP header. Because we only want - the value of the top half of the byte, we have to shift it - down to the bottom half otherwise it is using the most - significant bits instead of the least significant bits */ - tcp_header_length = tcpHeaderSturct->doff; - /* The TCP header length stored in those 4 bits represents - how many 32-bit words there are in the header, just like - the IP header length. We multiply by four again to get a - byte count. */ - tcp_header_length = tcp_header_length << 2; - srcPort = tcpHeaderSturct->source; - dstPort = tcpHeaderSturct->dest; - // printf("TCP header length in bytes: %d\n", tcp_header_length); - - /* Add up all the header sizes to find the payload offset */ - int total_headers_size = ethernet_header_length + ip_header_length + tcp_header_length; - // printf("Size of all headers combined: %d bytes\n", total_headers_size); - payload_length = header->caplen - total_headers_size; - payload_raw_length = header->len - total_headers_size; - // printf("Payload size: %d bytes\n", payload_length); - payload = packet + total_headers_size; - } else { - return; - } - if (payload_length <= 0 || payload_raw_length <= 0) { - return; - } - eventHeader->DstAddr.Type = SockAddressType_IPV4; - eventHeader->SrcAddr.Type = SockAddressType_IPV4; - PacketType packetType = PacketType_In; - if (ipHeaderStruct->saddr == mLocalAddress) { - packetType = PacketType_Out; - } else if (ipHeaderStruct->daddr == mLocalAddress) { - packetType = PacketType_In; - } else { - char* srcAddr = (char*)&ipHeaderStruct->saddr; - char* dstAddr = (char*)&ipHeaderStruct->daddr; - char* localMaskAddr = (char*)&mLocalMaskAddress; - for (int i = 0; i < 4; ++i) { - if (srcAddr[i] == localMaskAddr[i] && dstAddr[i] != localMaskAddr[i]) { - packetType = PacketType_Out; - break; - } - if (srcAddr[i] != localMaskAddr[i] && dstAddr[i] == localMaskAddr[i]) { - packetType = PacketType_In; - break; - } - } - } - - if (packetType == PacketType_Out) { - eventHeader->DstAddr.Addr.IPV4 = ipHeaderStruct->daddr; - eventHeader->SrcAddr.Addr.IPV4 = ipHeaderStruct->saddr; - eventHeader->SrcPort = ntohs(srcPort); - eventHeader->DstPort = ntohs(dstPort); - } else { - eventHeader->DstAddr.Addr.IPV4 = ipHeaderStruct->saddr; - eventHeader->SrcAddr.Addr.IPV4 = ipHeaderStruct->daddr; - eventHeader->SrcPort = ntohs(dstPort); - eventHeader->DstPort = ntohs(srcPort); - } - // filter host process connections - if (mConfig->mDropLocalConnections - && (eventHeader->DstAddr.Addr.IPV4 == htonl(INADDR_LOOPBACK) || eventHeader->DstAddr.Addr.IPV4 == INADDR_ANY) - && (eventHeader->SrcAddr.Addr.IPV4 == htonl(INADDR_LOOPBACK) || eventHeader->SrcAddr.Addr.IPV4 == INADDR_ANY)) { - return; - } - eventHeader->SockHash - = XXH32((void*)(&eventHeader->SrcAddr), (char*)(&eventHeader->DstPort) - (char*)(&eventHeader->SrcAddr) + 2, 0); - eventHeader->EventType = PacketEventType_Data; - eventHeader->PID = 0; - eventHeader->TimeNano = uint64_t(header->ts.tv_sec) * 1000000000LL + header->ts.tv_usec * 1000LL; - PacketEventData* eventData = (PacketEventData*)(packetBuffer + sizeof(PacketEventHeader)); - eventData->Buffer = (char*)payload; - eventData->BufferLen = payload_length; - eventData->RealLen = payload_raw_length; - eventData->PktType = packetType; - - auto res = caches.Get(eventHeader->SockHash, nullptr); - if (res == nullptr) { - std::tuple inferRst - = infer_protocol(eventHeader, packetType, (char*)payload, payload_length, payload_raw_length); - eventData->PtlType = std::get<0>(inferRst); - eventData->MsgType = std::get<1>(inferRst); - eventHeader->RoleType = InferServerOrClient(packetType, eventData->MsgType); - if (eventHeader->RoleType != PacketRoleType::Unknown) { - caches.Put( - eventHeader->SockHash, std::move(std::make_pair(eventHeader->RoleType, eventData->PtlType)), nullptr); - } - LOG_DEBUG(sLogger, - ("receive data event:new conn, addr", - SockAddressToString(eventHeader->DstAddr))("role", PacketRoleTypeToString(eventHeader->RoleType))( - "port", eventHeader->DstPort)("protocol", eventData->PtlType)( - "msg", MessageTypeToString(eventData->MsgType))("hash", eventHeader->SockHash)); - LOG_TRACE(sLogger, ("data", charToHexString(eventData->Buffer, eventData->RealLen, eventData->RealLen))); - } else { - eventData->PtlType = res->second; - eventHeader->RoleType = res->first; - if (eventData->PktType == PacketType_In) { - eventData->MsgType - = eventHeader->RoleType == PacketRoleType::Client ? MessageType_Response : MessageType_Request; - } else { - eventData->MsgType - = eventHeader->RoleType == PacketRoleType::Client ? MessageType_Request : MessageType_Response; - } - LOG_DEBUG(sLogger, - ("receive data event:history conn, addr", - SockAddressToString(eventHeader->DstAddr))("role", PacketRoleTypeToString(eventHeader->RoleType))( - "port", eventHeader->DstPort)("protocol", eventData->PtlType)( - "msg", MessageTypeToString(eventData->MsgType))("hash", eventHeader->SockHash)); - LOG_TRACE(sLogger, ("data", charToHexString(eventData->Buffer, eventData->RealLen, eventData->RealLen))); - } - LOG_DEBUG(sLogger, ("tag3", "===")); - - logtail::NetStatisticsKey key; - key.PID = eventHeader->PID; - key.SockHash = eventHeader->SockHash; - key.AddrInfo.RemoteAddr = eventHeader->DstAddr; - key.AddrInfo.RemotePort = eventHeader->DstPort; - key.RoleType = eventHeader->RoleType; - key.SockCategory = SocketCategory::InetSocket; - bool needRebuildHash = false; - if (eventHeader->RoleType == PacketRoleType::Server) { - key.AddrInfo.RemotePort = 0; - needRebuildHash = true; - } - key.AddrInfo.LocalAddr = eventHeader->SrcAddr; - key.AddrInfo.LocalPort = eventHeader->SrcPort; - if (eventHeader->RoleType == PacketRoleType::Client) { - key.AddrInfo.LocalPort = 0; - needRebuildHash = true; - } - if (needRebuildHash) { - key.SockHash = 0; - key.SockHash = XXH32(&key, sizeof(NetStatisticsKey), 0); - } - - NetStatisticsTCP& statisticsItem = mStatistics.GetStatisticsItem(key); - - if (packetType == PacketType_Out) { - ++statisticsItem.Base.SendPackets; - statisticsItem.Base.SendBytes += payload_raw_length; - if (retran) { - ++statisticsItem.SendRetranCount; - } - if (zeroWindow) { - ++statisticsItem.SendZeroWinCount; - } - } else { - ++statisticsItem.Base.RecvPackets; - statisticsItem.Base.RecvBytes += payload_raw_length; - if (retran) { - ++statisticsItem.RecvRetranCount; - } - if (zeroWindow) { - ++statisticsItem.RecvZeroWinCount; - } - } - if (mConfig->IsLegalProtocol(eventData->PtlType)) { - mPacketProcessor(StringPiece(packetBuffer, sizeof(PacketEventHeader) + sizeof(PacketEventData))); - } - - - // printf("Memory address where payload begins: %p\n\n", payload); - - /* Print payload in ASCII */ - /* - if (payload_length > 0) { - const u_char *temp_pointer = payload; - int byte_count = 0; - while (byte_count++ < payload_length) { - printf("%c", *temp_pointer); - temp_pointer++; - } - printf("\n"); - } - */ - - return; -} -}; // namespace logtail \ No newline at end of file diff --git a/core/observer/network/sources/pcap/PCAPWrapper.h b/core/observer/network/sources/pcap/PCAPWrapper.h deleted file mode 100644 index 0fe296631f..0000000000 --- a/core/observer/network/sources/pcap/PCAPWrapper.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "observer/network/NetworkConfig.h" -#include "observer/interface/network.h" -#include "observer/interface/helper.h" -#include "common/StringPiece.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "common/DynamicLibHelper.h" - - -namespace logtail { - -class PCAPWrapper { -public: - PCAPWrapper(NetworkConfig* config) : mConfig(config), caches(config->mPCAPCacheConnSize) {} - ~PCAPWrapper() { Stop(); } - - static PCAPWrapper* GetInstance() { - static PCAPWrapper* sWrapper = new PCAPWrapper(NetworkConfig::GetInstance()); - return sWrapper; - } - - bool Init(std::function processor); - - std::string GetErrorMessage() { - if (strlen(mErrBuf) > 0) { - return std::string(mErrBuf); - } - return ""; - } - - bool Start() { - // do nonthing - return true; - } - - bool Stop(); - - /** - * @brief - * - * @param maxProcessPackets - * @param maxProcessDurationMs - * @return int32_t packets processed, 0 no packets, < 0 error - */ - int32_t ProcessPackets(int32_t maxProcessPackets, int32_t maxProcessDurationMs); - - void PCAPCallBack(const struct pcap_pkthdr* packet_header, const u_char* packet_content); - - NetStaticticsMap& GetStatistics() { return mStatistics; } - - friend class PCAPWrapperUnittest; - -private: - NetworkConfig* mConfig; - std::function mPacketProcessor; - char mErrBuf[PCAP_ERRBUF_SIZE] = {'\0'}; - pcap_t* mHandle = NULL; - struct bpf_program mBPFFilter; - bpf_u_int32 mLocalAddress = 0; - bpf_u_int32 mLocalMaskAddress = 0; - DynamicLibLoader* mPCAPLib = NULL; - NetStaticticsMap mStatistics; - LRUCache> caches; -}; - -} // namespace logtail \ No newline at end of file diff --git a/core/pipeline/Pipeline.h b/core/pipeline/Pipeline.h index 8ccecfef7b..815b9907bf 100644 --- a/core/pipeline/Pipeline.h +++ b/core/pipeline/Pipeline.h @@ -67,7 +67,7 @@ class Pipeline { return mPluginCntMap; } - // only for input_observer_network for compatability + // only for input_file const std::vector>& GetInputs() const { return mInputs; } std::string GetNowPluginID(); diff --git a/core/pipeline/plugin/PluginRegistry.cpp b/core/pipeline/plugin/PluginRegistry.cpp index 2135a81a06..9e684c14a6 100644 --- a/core/pipeline/plugin/PluginRegistry.cpp +++ b/core/pipeline/plugin/PluginRegistry.cpp @@ -35,7 +35,6 @@ #include "plugin/input/InputNetworkObserver.h" #include "plugin/input/InputNetworkSecurity.h" #include "plugin/input/InputProcessSecurity.h" -#include "plugin/input/InputObserverNetwork.h" #endif #include "logger/Logger.h" #include "pipeline/plugin/creator/CProcessor.h" @@ -132,7 +131,6 @@ void PluginRegistry::LoadStaticPlugins() { RegisterInputCreator(new StaticInputCreator()); RegisterInputCreator(new StaticInputCreator()); RegisterInputCreator(new StaticInputCreator()); - RegisterInputCreator(new StaticInputCreator()); #endif RegisterProcessorCreator(new StaticProcessorCreator()); diff --git a/core/plugin/flusher/sls/FlusherSLS.h b/core/plugin/flusher/sls/FlusherSLS.h index cd1c227614..c9afca958a 100644 --- a/core/plugin/flusher/sls/FlusherSLS.h +++ b/core/plugin/flusher/sls/FlusherSLS.h @@ -70,7 +70,7 @@ class FlusherSLS : public HttpFlusher { CompressType GetCompressType() const { return mCompressor ? mCompressor->GetCompressType() : CompressType::NONE; } - // for use of Go pipeline, stream, observer and shennong + // for use of Go pipeline and shennong bool Send(std::string&& data, const std::string& shardHashKey, const std::string& logstore = ""); std::string mProject; diff --git a/core/plugin/input/InputObserverNetwork.cpp b/core/plugin/input/InputObserverNetwork.cpp deleted file mode 100644 index ab22c12848..0000000000 --- a/core/plugin/input/InputObserverNetwork.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2023 iLogtail 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. - -#include "plugin/input/InputObserverNetwork.h" - -#include "observer/network/NetworkConfig.h" - -using namespace std; - -namespace logtail { - -const std::string InputObserverNetwork::sName = "input_observer_network"; - -bool InputObserverNetwork::Init(const Json::Value& config, Json::Value& optionalGoPipeline) { - mDetail = config.toStyledString(); - return true; -} - -bool InputObserverNetwork::Start() { - NetworkConfig::GetInstance()->mAllNetworkConfigs[mContext->GetConfigName()] = &mContext->GetPipeline(); - return true; -} - -bool InputObserverNetwork::Stop(bool isPipelineRemoving) { - NetworkConfig::GetInstance()->mAllNetworkConfigs.erase(mContext->GetConfigName()); - return true; -} - -} // namespace logtail diff --git a/core/plugin/input/InputObserverNetwork.h b/core/plugin/input/InputObserverNetwork.h deleted file mode 100644 index dbf7300bff..0000000000 --- a/core/plugin/input/InputObserverNetwork.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2023 iLogtail 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. - */ - -#pragma once - -#include - -#include "pipeline/plugin/interface/Input.h" - -namespace logtail { - -class InputObserverNetwork : public Input { -public: - static const std::string sName; - - const std::string& Name() const override { return sName; } - bool Init(const Json::Value& config, Json::Value& optionalGoPipeline) override; - bool Start() override; - bool Stop(bool isPipelineRemoving) override; - bool SupportAck() const override { return true; } - - std::string mDetail; -}; - -} // namespace logtail diff --git a/core/plugin/input/input.cmake b/core/plugin/input/input.cmake index b986267eca..26901456c1 100644 --- a/core/plugin/input/input.cmake +++ b/core/plugin/input/input.cmake @@ -21,13 +21,8 @@ file(GLOB THIS_SOURCE_FILES ${CMAKE_SOURCE_DIR}/plugin/input/*.c ${CMAKE_SOURCE_ list(APPEND THIS_SOURCE_FILES_LIST ${THIS_SOURCE_FILES}) if(MSVC) - # remove observer related files in input - list(REMOVE_ITEM THIS_SOURCE_FILES_LIST ${CMAKE_SOURCE_DIR}/plugin/input/InputObserverNetwork.cpp ${CMAKE_SOURCE_DIR}/plugin/input/InputObserverNetwork.h) +# TODO: remote ebpf related source files elseif(UNIX) - if (NOT LINUX) - # remove observer related files in input - list(REMOVE_ITEM THIS_SOURCE_FILES_LIST ${CMAKE_SOURCE_DIR}/plugin/input/InputObserverNetwork.cpp ${CMAKE_SOURCE_DIR}/plugin/input/InputObserverNetwork.h) - endif() endif() # Set source files to parent diff --git a/core/unittest/CMakeLists.txt b/core/unittest/CMakeLists.txt index 1a55be7ee3..6b41223ab5 100644 --- a/core/unittest/CMakeLists.txt +++ b/core/unittest/CMakeLists.txt @@ -54,9 +54,6 @@ macro(add_core_subdir) add_subdirectory(serializer) add_subdirectory(prometheus) add_subdirectory(route) - if (LINUX) - add_subdirectory(observer) - endif () endmacro() macro(add_spl_subdir) diff --git a/core/unittest/config/ConfigUpdateUnittest.cpp b/core/unittest/config/ConfigUpdateUnittest.cpp index 8dba34cb77..45b6fc4c7f 100644 --- a/core/unittest/config/ConfigUpdateUnittest.cpp +++ b/core/unittest/config/ConfigUpdateUnittest.cpp @@ -153,7 +153,7 @@ class ConfigUpdateUnittest : public testing::Test { "valid": false, "inputs": [ { - "Type": "input_observer_network" + "Type": "input_container_stdio" } ], "flushers": [ @@ -168,7 +168,7 @@ class ConfigUpdateUnittest : public testing::Test { "valid": true, "inputs": [ { - "Type": "input_observer_network" + "Type": "input_container_stdio" } ], "flushers": [ @@ -184,7 +184,7 @@ class ConfigUpdateUnittest : public testing::Test { "enable": false, "inputs": [ { - "Type": "input_observer_network" + "Type": "input_container_stdio" } ], "flushers": [ diff --git a/core/unittest/config/PipelineConfigUnittest.cpp b/core/unittest/config/PipelineConfigUnittest.cpp index 577113ebc7..e3dcfc5d4f 100644 --- a/core/unittest/config/PipelineConfigUnittest.cpp +++ b/core/unittest/config/PipelineConfigUnittest.cpp @@ -1744,26 +1744,6 @@ void PipelineConfigUnittest::HandleInvalidProcessors() const { config.reset(new PipelineConfig(configName, std::move(configJson))); APSARA_TEST_FALSE(config->Parse()); - // native processor plugins coexist with input_observer_network - configStr = R"( - { - "inputs": [ - { - "Type": "input_observer_network" - } - ], - "processors": [ - { - "Type": "processor_parse_regex_native" - } - ] - } - )"; - configJson.reset(new Json::Value()); - APSARA_TEST_TRUE(ParseJsonTable(configStr, *configJson, errorMsg)); - config.reset(new PipelineConfig(configName, std::move(configJson))); - APSARA_TEST_FALSE(config->Parse()); - // native processor plugins coexist with processor_spl configStr = R"( { diff --git a/core/unittest/observer/CGoupMetaUnittest.cpp b/core/unittest/observer/CGoupMetaUnittest.cpp deleted file mode 100644 index cfa8d1697c..0000000000 --- a/core/unittest/observer/CGoupMetaUnittest.cpp +++ /dev/null @@ -1,399 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include - -#include "unittest/Unittest.h" -#include "metas/CGroupPathResolver.h" -#include "metas/ContainerProcessGroup.h" -#include "unittest/UnittestHelper.h" - -namespace logtail { - -static const std::string DOCKER_BESTEFFORT_PATH - = "kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-podd068a7ab_ddb1_4873_a0a4_a4e3408fe1e8.slice/" - "docker-32c0b6ca6cb7b94a1cfa605153ae920bf9b23335efd74f6e6e51ccc097f6e70b.scope/cgroup.procs"; -static const std::string DOCKER_BURSTABLE_PATH - = "kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod0dc61110_6b40_4a27_bd00_9edb8c454211.slice/" - "docker-7f0e7ba0b1678264767cff71448f0dd6c5490f31f296ef0c9bf4d24ef0e9914e.scope/cgroup.procs"; -static const std::string DOCKER_GUARANTEED_PATH - = "kubepods.slice/kubepods-pod3f5397a2_76aa_4d66_bd6b_83bfcb6d6dc6.slice/" - "docker-6587c323ce97dd3a91a131acd7d04cab88f23a5bcfeb21139ca5cb878bbae2c6.scope/cgroup.procs"; - - -static const std::string CONTAINERD_BESTEFFORT_PATH - = "kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod0d206349_0faf_445c_8c3f_2d2153784f15.slice/" - "cri-containerd-ba48928b25af68cebd77e605c5c3b2474bcd47b32143e4d9125f4a8270ac07d3.scope/cgroup.procs"; -static const std::string CONTAINERD_BURSTABLE_PATH - = "kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod6e10d863_beec_40a8_bb4c_007f7c893ef1.slice/" - "cri-containerd-5caf42bd82e5b4fa21fcde3322332c5d19ccbeadf52281cbb00f804b372e41d0.scope/cgroup.procs"; -static const std::string CONTAINERD_GUARANTEED_PATH - = "kubepods.slice/kubepods-pod2b801b7a_5266_4386_864e_45ed71136371.slice/" - "cri-containerd-20e061fc708d3b66dfe257b19552b34a1307a7347ed6b5bd0d8c5e76afb1a870.scope/cgroup.procs"; -static const std::string ERRORPATH = "12345"; - - -static const std::string GKE_BESTEFFORT_PATH - = "kubepods/besteffort/pod8dbc5577-d0e2-4706-8787-57d52c03ddf2/" - "14011c7d92a9e513dfd69211da0413dbf319a5e45a02b354ba6e98e10272542d/cgroup.procs"; -static const std::string GKE_BURSTABLE_PATH - = "kubepods/burstable/pod8dbc5577-d0e2-4706-8787-57d52c03ddf2/" - "14011c7d92a9e513dfd69211da0413dbf319a5e45a02b354ba6e98e10272542d/cgroup.procs"; -static const std::string GKE_GUARANTEED_PATH - = "kubepods/pod8dbc5577-d0e2-4706-8787-57d52c03ddf2/" - "14011c7d92a9e513dfd69211da0413dbf319a5e45a02b354ba6e98e10272542d/cgroup.procs"; - - -class CGroupPathResolverUnittest : public ::testing::Test { -public: - static void SetUpTestCase() { system("tar -xvf cgroup.tar"); } - - static void TearDownTestCase() { system("rm -rf cgroup"); } - - - void TestCGroupExtractContainerType() { - EXPECT_EQ(CONTAINER_TYPE::CONTAINER_TYPE_DOCKER, ExtractContainerType(DOCKER_BESTEFFORT_PATH)); - EXPECT_EQ(CONTAINER_TYPE::CONTAINER_TYPE_DOCKER, ExtractContainerType(DOCKER_BURSTABLE_PATH)); - EXPECT_EQ(CONTAINER_TYPE::CONTAINER_TYPE_DOCKER, ExtractContainerType(DOCKER_GUARANTEED_PATH)); - EXPECT_EQ(CONTAINER_TYPE::CONTAINER_TYPE_CRI_CONTAINERD, ExtractContainerType(CONTAINERD_BESTEFFORT_PATH)); - EXPECT_EQ(CONTAINER_TYPE::CONTAINER_TYPE_CRI_CONTAINERD, ExtractContainerType(CONTAINERD_BURSTABLE_PATH)); - EXPECT_EQ(CONTAINER_TYPE::CONTAINER_TYPE_CRI_CONTAINERD, ExtractContainerType(CONTAINERD_GUARANTEED_PATH)); - } - - void TestExtractDockerMeta() { - auto matcher = GetCGroupMatcher(DOCKER_BESTEFFORT_PATH, CONTAINER_TYPE::CONTAINER_TYPE_DOCKER); - EXPECT_TRUE(matcher != nullptr); - delete matcher; - matcher = GetCGroupMatcher(DOCKER_BURSTABLE_PATH, CONTAINER_TYPE::CONTAINER_TYPE_DOCKER); - EXPECT_TRUE(matcher != nullptr); - delete matcher; - matcher = GetCGroupMatcher(DOCKER_GUARANTEED_PATH, CONTAINER_TYPE::CONTAINER_TYPE_DOCKER); - EXPECT_TRUE(matcher != nullptr); - - std::string containerID, podID; - matcher->ExtractProcessMeta(DOCKER_BESTEFFORT_PATH, containerID, podID); - EXPECT_EQ("d068a7ab_ddb1_4873_a0a4_a4e3408fe1e8", podID); - EXPECT_EQ("32c0b6ca6cb7b94a1cfa605153ae920bf9b23335efd74f6e6e51ccc097f6e70b", containerID); - matcher->ExtractProcessMeta(DOCKER_BURSTABLE_PATH, containerID, podID); - EXPECT_EQ("0dc61110_6b40_4a27_bd00_9edb8c454211", podID); - EXPECT_EQ("7f0e7ba0b1678264767cff71448f0dd6c5490f31f296ef0c9bf4d24ef0e9914e", containerID); - matcher->ExtractProcessMeta(DOCKER_GUARANTEED_PATH, containerID, podID); - EXPECT_EQ("3f5397a2_76aa_4d66_bd6b_83bfcb6d6dc6", podID); - EXPECT_EQ("6587c323ce97dd3a91a131acd7d04cab88f23a5bcfeb21139ca5cb878bbae2c6", containerID); - podID.clear(); - containerID.clear(); - matcher->ExtractProcessMeta(ERRORPATH, containerID, podID); - EXPECT_EQ("", podID); - EXPECT_EQ("", containerID); - } - - void TestExtractContainerdMeta() { - auto matcher = GetCGroupMatcher(CONTAINERD_BESTEFFORT_PATH, CONTAINER_TYPE::CONTAINER_TYPE_CRI_CONTAINERD); - EXPECT_TRUE(matcher != nullptr); - delete matcher; - matcher = GetCGroupMatcher(CONTAINERD_BURSTABLE_PATH, CONTAINER_TYPE::CONTAINER_TYPE_CRI_CONTAINERD); - EXPECT_TRUE(matcher != nullptr); - delete matcher; - - matcher = GetCGroupMatcher(CONTAINERD_GUARANTEED_PATH, CONTAINER_TYPE::CONTAINER_TYPE_CRI_CONTAINERD); - EXPECT_TRUE(matcher != nullptr); - - - std::string containerID, podID; - matcher->ExtractProcessMeta(CONTAINERD_BESTEFFORT_PATH, containerID, podID); - EXPECT_EQ("0d206349_0faf_445c_8c3f_2d2153784f15", podID); - EXPECT_EQ("ba48928b25af68cebd77e605c5c3b2474bcd47b32143e4d9125f4a8270ac07d3", containerID); - matcher->ExtractProcessMeta(CONTAINERD_BURSTABLE_PATH, containerID, podID); - EXPECT_EQ("6e10d863_beec_40a8_bb4c_007f7c893ef1", podID); - EXPECT_EQ("5caf42bd82e5b4fa21fcde3322332c5d19ccbeadf52281cbb00f804b372e41d0", containerID); - matcher->ExtractProcessMeta(CONTAINERD_GUARANTEED_PATH, containerID, podID); - EXPECT_EQ("2b801b7a_5266_4386_864e_45ed71136371", podID); - EXPECT_EQ("20e061fc708d3b66dfe257b19552b34a1307a7347ed6b5bd0d8c5e76afb1a870", containerID); - podID.clear(); - containerID.clear(); - matcher->ExtractProcessMeta(ERRORPATH, containerID, podID); - EXPECT_EQ("", podID); - EXPECT_EQ("", containerID); - } - - - void TestExtractGKEMeta() { - auto matcher = GetCGroupMatcher(GKE_BESTEFFORT_PATH, CONTAINER_TYPE::CONTAINER_TYPE_CRI_CONTAINERD); - EXPECT_TRUE(matcher != nullptr); - delete matcher; - matcher = GetCGroupMatcher(GKE_BURSTABLE_PATH, CONTAINER_TYPE::CONTAINER_TYPE_CRI_CONTAINERD); - EXPECT_TRUE(matcher != nullptr); - delete matcher; - - matcher = GetCGroupMatcher(GKE_GUARANTEED_PATH, CONTAINER_TYPE::CONTAINER_TYPE_CRI_CONTAINERD); - EXPECT_TRUE(matcher != nullptr); - - - std::string containerID, podID; - matcher->ExtractProcessMeta(GKE_BESTEFFORT_PATH, containerID, podID); - EXPECT_EQ("8dbc5577-d0e2-4706-8787-57d52c03ddf2", podID); - EXPECT_EQ("14011c7d92a9e513dfd69211da0413dbf319a5e45a02b354ba6e98e10272542d", containerID); - matcher->ExtractProcessMeta(GKE_BURSTABLE_PATH, containerID, podID); - EXPECT_EQ("8dbc5577-d0e2-4706-8787-57d52c03ddf2", podID); - EXPECT_EQ("14011c7d92a9e513dfd69211da0413dbf319a5e45a02b354ba6e98e10272542d", containerID); - matcher->ExtractProcessMeta(GKE_GUARANTEED_PATH, containerID, podID); - EXPECT_EQ("8dbc5577-d0e2-4706-8787-57d52c03ddf2", podID); - EXPECT_EQ("14011c7d92a9e513dfd69211da0413dbf319a5e45a02b354ba6e98e10272542d", containerID); - podID.clear(); - containerID.clear(); - matcher->ExtractProcessMeta(ERRORPATH, containerID, podID); - EXPECT_EQ("", podID); - EXPECT_EQ("", containerID); - } - - - void TestExtractPodName() { - EXPECT_EQ("kube-state-metrics", ExtractPodWorkloadName("kube-state-metrics-86679c945-5rmck")); - EXPECT_EQ("kube-state-metrics", ExtractPodWorkloadName("kube-state-metrics-86679c9454-5rmck")); - EXPECT_EQ("kube-state-metrics", ExtractPodWorkloadName("kube-state-metrics-5rmck")); - EXPECT_EQ("kube-state-metrics", ExtractPodWorkloadName("kube-state-metrics")); - EXPECT_EQ("", ExtractPodWorkloadName("")); - } - - void TestExtractMetaPath() { - std::vector res; - ResolveAllCGroupProcsPaths(CGroupBasePath("."), res); - for (const auto& item : res) { - std::cout << item << std::endl; - } - auto iter = std::find( - res.begin(), - res.end(), - "./cgroup/cpu,cpuacct/kubepods.slice/kubepods-burstable.slice/" - "kubepods-burstable-pod28010fb2_7fe0_424c_a131_ad8a25a0c80a.slice/" - "cri-containerd-c89017ad4ef7fd2b029d8d21452d17d9d9ed7443f48f02ef94b4815df6aebe47.scope/cgroup.procs"); - EXPECT_TRUE(iter != res.end()); - } - - void TestOnlyProcessCGroupManager() { - EXPECT_FALSE(instance->Init("./notExist")); - instance->GetProcessMeta(1); - instance->GetProcessMeta(99999991); - auto data = instance->GetProcessMeta(1)->GetFormattedMeta(); - EXPECT_EQ(data.size(), 3); - EXPECT_EQ(data[0].first, "_process_pid_"); - EXPECT_EQ(data[0].second, "1"); - EXPECT_EQ(data[1].first, "_process_cmd_"); - EXPECT_TRUE(!data[1].second.empty()); - - data = instance->GetProcessMeta(99999991)->GetFormattedMeta(); - EXPECT_EQ(data[0].first, "_process_pid_"); - EXPECT_EQ(data[0].second, "99999991"); - EXPECT_EQ(data[1].first, "_process_cmd_"); - EXPECT_TRUE(data[1].second.empty()); - - instance->FlushMetas(); - - auto groupPtr1 = instance->GetContainerProcessGroupPtr(instance->GetProcessMeta(1), 1); - MySQLProtocolEvent MySQLEvent = getEvent(1); - groupPtr1->mAggregator.GetMySQLAggregator()->AddEvent(std::move(MySQLEvent)); - - std::vector logs; - auto tags = std::vector>{}; - instance->FlushOutMetrics(logs, tags, 1); - - APSARA_TEST_TRUE(logs.size() == 1); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched( - &logs[0], "local_info", "{\"_process_cmd_\":\"/usr/lib/systemd/systemd\",\"_process_pid_\":\"1\",\"_running_mode_\":\"host\"}")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&logs[0], "query", "select 1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&logs[0], "status", "0")); - } - - void TestContainerCGroupManager() { - EXPECT_TRUE(instance->Init(".")); - instance->FlushMetas(); - - auto meta = instance->GetProcessMeta(9999997); - EXPECT_EQ(meta->PID, 9999997); - EXPECT_EQ(meta->Pod.PodUUID, "28010fb2_7fe0_424c_a131_ad8a25a0c80a"); - EXPECT_EQ(meta->Container.ContainerID, "c89017ad4ef7fd2b029d8d21452d17d9d9ed7443f48f02ef94b4815df6aebe47"); - - - meta = instance->GetProcessMeta(9999999); - EXPECT_EQ(meta->PID, 9999999); - EXPECT_EQ(meta->Pod.PodUUID, "2b801b7a_5266_4386_864e_45ed71136371"); - EXPECT_EQ(meta->Container.ContainerID, "20e061fc708d3b66dfe257b19552b34a1307a7347ed6b5bd0d8c5e76afb1a870"); - - - meta = instance->GetProcessMeta(9999998); - EXPECT_EQ(meta->PID, 9999998); - EXPECT_EQ(meta->Pod.PodUUID, "2b801b7a_5266_4386_864e_45ed71136371"); - EXPECT_EQ(meta->Container.ContainerID, "20e061fc708d3b66dfe257b19552b34a1307a7347ed6b5bd0d8c5e76afb1a870"); - - // test flush logs by container group - - auto groupPtr2 = instance->GetContainerProcessGroupPtr(instance->GetProcessMeta(9999998), 9999998); - auto groupPtr3 = instance->GetContainerProcessGroupPtr(instance->GetProcessMeta(9999999), 9999999); - auto groupPtr4 = instance->GetContainerProcessGroupPtr(instance->GetProcessMeta(9999997), 9999997); - ASSERT_TRUE(groupPtr2 == groupPtr3); - - // mock k8s info - groupPtr2->mMetaPtr->Pod.PodName = "mock2-pod"; - groupPtr2->mMetaPtr->Pod.NameSpace = "mock2-ns"; - groupPtr2->mMetaPtr->Pod.WorkloadName = "mock2-wn"; - groupPtr2->mMetaPtr->Container.Image = "mock2-image"; - groupPtr2->mMetaPtr->Container.ContainerName = "mock2-container"; - - // mock only container info - groupPtr4->mMetaPtr->Container.Image = "mock4-image"; - groupPtr4->mMetaPtr->Container.ContainerName = "mock4-container"; - - - MySQLProtocolEvent MySQLEvent2 = getEvent(2); - groupPtr2->mAggregator.GetMySQLAggregator()->AddEvent(std::move(MySQLEvent2)); - - MySQLProtocolEvent MySQLEvent4 = getEvent(4); - groupPtr4->mAggregator.GetMySQLAggregator()->AddEvent(std::move(MySQLEvent4)); - - std::vector logs; - auto tags = std::vector>{}; - instance->FlushOutMetrics(logs, tags, 1); - APSARA_TEST_TRUE(logs.size() == 2); - - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&logs[0], "local_info", "{\"_container_name_\":\"mock4-container\",\"_running_mode_\":\"container\"}")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&logs[0], "query", "select 4")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&logs[0], "status", "0")); - - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&logs[1], "local_info", "{\"_container_name_\":\"mock2-container\",\"_namespace_\":\"mock2-ns\",\"_pod_name_\":\"mock2-pod\",\"_running_mode_\":\"kubernetes\",\"_workload_name_\":\"mock2-wn\"}")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&logs[1], "query", "select 2")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&logs[1], "status", "0")); - } - - static MySQLProtocolEvent getEvent(int32_t i) { - MySQLProtocolEvent MySQLEvent; - MySQLEvent.Info.LatencyNs = i; - MySQLEvent.Info.ReqBytes = i; - MySQLEvent.Info.RespBytes = i; - MySQLEvent.Key.Query = "select " + std::to_string(i); - MySQLEvent.Key.Status = 0; - MySQLEvent.Key.ConnKey.Role = PacketRoleType::Server; - return MySQLEvent; - } - - - void TestPassFilterRules() { - auto cfg = NetworkConfig::GetInstance(); - - cfg->mIncludeCmdRegex = boost::regex("^(test|abc)$"); - cfg->mExcludeCmdRegex = boost::regex("^abc"); - - - // valid cmd - ProcessMeta meta; - meta.PID = 1; - meta.ProcessCMD = "test"; - APSARA_TEST_TRUE(meta.PassFilterRules()); - meta.mPassFilterRules = 0; - - meta.ProcessCMD = "abc"; - APSARA_TEST_TRUE(!meta.PassFilterRules()); - meta.mPassFilterRules = 0; - - meta.ProcessCMD = "abc1"; - APSARA_TEST_TRUE(!meta.PassFilterRules()); - meta.mPassFilterRules = 0; - - // valid container - meta.Container.ContainerName = "container1"; - meta.Container.Labels.insert(std::make_pair("app", "container")); - - cfg->mIncludeContainerNameRegex = boost::regex("^(container1|container2)$"); - APSARA_TEST_TRUE(meta.PassFilterRules()); - meta.mPassFilterRules = 0; - cfg->mIncludeContainerNameRegex = boost::regex(); - - cfg->mExcludeContainerNameRegex = boost::regex("^(container1|container)$"); - APSARA_TEST_TRUE(!meta.PassFilterRules()); - meta.mPassFilterRules = 0; - cfg->mExcludeContainerNameRegex = boost::regex(); - - cfg->mIncludeContainerLabels.insert(std::make_pair("app", boost::regex("^(container|abc)$"))); - APSARA_TEST_TRUE(meta.PassFilterRules()); - meta.mPassFilterRules = 0; - cfg->mIncludeContainerLabels.clear(); - - cfg->mExcludeContainerLabels.insert(std::make_pair("app", boost::regex("^(container)$"))); - APSARA_TEST_TRUE(!meta.PassFilterRules()); - meta.mPassFilterRules = 0; - cfg->mExcludeContainerLabels.clear(); - - // valid pod - meta.Pod.PodName = "podname1"; - meta.Pod.NameSpace = "namespace1"; - meta.Pod.Labels.insert(std::make_pair("app", "pod")); - - cfg->mIncludePodNameRegex = boost::regex("^(podname1|namespace2)$"); - APSARA_TEST_TRUE(meta.PassFilterRules()); - meta.mPassFilterRules = 0; - cfg->mIncludePodNameRegex = boost::regex(); - - cfg->mExcludePodNameRegex = boost::regex("^(podname1|namespace2)$"); - APSARA_TEST_TRUE(!meta.PassFilterRules()); - meta.mPassFilterRules = 0; - cfg->mExcludePodNameRegex = boost::regex(); - - cfg->mIncludeNamespaceNameRegex = boost::regex("^(namespace1|namespace2)$"); - APSARA_TEST_TRUE(meta.PassFilterRules()); - meta.mPassFilterRules = 0; - cfg->mIncludeNamespaceNameRegex = boost::regex(); - - cfg->mExcludeNamespaceNameRegex = boost::regex("^(namespace1|namespace2)$"); - APSARA_TEST_TRUE(!meta.PassFilterRules()); - meta.mPassFilterRules = 0; - cfg->mExcludeNamespaceNameRegex = boost::regex(); - - cfg->mIncludeK8sLabels.insert(std::make_pair("app", boost::regex("^(pod|abc)$"))); - APSARA_TEST_TRUE(meta.PassFilterRules()); - meta.mPassFilterRules = 0; - cfg->mIncludeK8sLabels.clear(); - - cfg->mExcludeK8sLabels.insert(std::make_pair("app", boost::regex("^(pod)$"))); - APSARA_TEST_TRUE(!meta.PassFilterRules()); - meta.mPassFilterRules = 0; - cfg->mExcludeK8sLabels.clear(); - } - - ContainerProcessGroupManager* instance = ContainerProcessGroupManager::GetInstance(); -}; - -UNIT_TEST_CASE(CGroupPathResolverUnittest, TestPassFilterRules) - -UNIT_TEST_CASE(CGroupPathResolverUnittest, TestCGroupExtractContainerType) - -UNIT_TEST_CASE(CGroupPathResolverUnittest, TestExtractDockerMeta) - -UNIT_TEST_CASE(CGroupPathResolverUnittest, TestExtractContainerdMeta) - -UNIT_TEST_CASE(CGroupPathResolverUnittest, TestExtractPodName) - -UNIT_TEST_CASE(CGroupPathResolverUnittest, TestExtractGKEMeta) - -UNIT_TEST_CASE(CGroupPathResolverUnittest, TestExtractMetaPath) - -UNIT_TEST_CASE(CGroupPathResolverUnittest, TestOnlyProcessCGroupManager) - -UNIT_TEST_CASE(CGroupPathResolverUnittest, TestContainerCGroupManager) - -} // namespace logtail - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/core/unittest/observer/CMakeLists.txt b/core/unittest/observer/CMakeLists.txt deleted file mode 100644 index 6804a3e4a6..0000000000 --- a/core/unittest/observer/CMakeLists.txt +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2022 iLogtail 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. - -cmake_minimum_required(VERSION 3.22) -project(observer_unittest) - -add_executable(observer_config_unittest ObserverConfigUnittest.cpp) -target_link_libraries(observer_config_unittest ${UT_BASE_TARGET}) - -# protocol unittest -# add_executable(protocol_dns_unittest ProtocolDnsUnittest.cpp) -# add_executable(protocol_http_unittest ProtocolHttpUnittest.cpp) -# add_executable(protocol_redis_unittest ProtocolRedisUnittest.cpp) -# add_executable(protocol_pgsql_unittest ProtocolPgsqlUnittest.cpp) -# add_executable(protocol_mysql_unittest ProtocolMySqlUnittest.cpp) - -# target_link_libraries(protocol_dns_unittest ${UT_BASE_TARGET}) -# target_link_libraries(protocol_http_unittest ${UT_BASE_TARGET}) -# target_link_libraries(protocol_redis_unittest ${UT_BASE_TARGET}) -# target_link_libraries(protocol_pgsql_unittest ${UT_BASE_TARGET}) -# target_link_libraries(protocol_mysql_unittest ${UT_BASE_TARGET}) - -# cgoutp meta unittest -# add_executable(cgroup_meta_unittest CGoupMetaUnittest.cpp) -# target_link_libraries(cgroup_meta_unittest ${UT_BASE_TARGET}) -add_executable(netlink_meta_unittest NetLinkUnittest.cpp) -target_link_libraries(netlink_meta_unittest ${UT_BASE_TARGET}) - -add_executable(hostname_meta_unittest HostnameMetaUnittest.cpp) -target_link_libraries(hostname_meta_unittest ${UT_BASE_TARGET}) - -# framework unittest -add_executable(network_observer_unittest NetworkObserverUnittest.cpp) -add_executable(protocol_util_unittest ProtocolUtilUnittest.cpp) -add_executable(protocol_infer_unittest ProtocolInferUnittest.cpp) - -target_link_libraries(network_observer_unittest ${UT_BASE_TARGET}) -target_link_libraries(protocol_util_unittest ${UT_BASE_TARGET}) -target_link_libraries(protocol_infer_unittest ${UT_BASE_TARGET}) - -include(GoogleTest) -gtest_discover_tests(observer_config_unittest) -gtest_discover_tests(netlink_meta_unittest) -gtest_discover_tests(hostname_meta_unittest) -gtest_discover_tests(network_observer_unittest) -gtest_discover_tests(protocol_util_unittest) -gtest_discover_tests(protocol_infer_unittest) diff --git a/core/unittest/observer/HostnameMetaUnittest.cpp b/core/unittest/observer/HostnameMetaUnittest.cpp deleted file mode 100644 index f5c0723a60..0000000000 --- a/core/unittest/observer/HostnameMetaUnittest.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "Logger.h" -#include "unittest/Unittest.h" -#include "unittest/UnittestHelper.h" -#include "metas/ServiceMetaCache.h" - -namespace logtail { - -class HostnameMetaUnittest : public ::testing::Test { -public: - void testLRUCache() { - ServiceMetaCache cache(5); - cache.Put("1", "host", ProtocolType_HTTP); - cache.Put("2", "host", ProtocolType_HTTP); - cache.Put("3", "host", ProtocolType_HTTP); - cache.Put("4", "host", ProtocolType_HTTP); - cache.Put("5", "host", ProtocolType_HTTP); - cache.Put("6", "host", ProtocolType_HTTP); - APSARA_TEST_TRUE(cache.Get("1").Host.empty()); - for (int i = 2; i < 7; ++i) { - auto key = std::to_string(i); - APSARA_TEST_TRUE(!cache.Get(key).Host.empty()); - } - APSARA_TEST_EQUAL(cache.mData.begin()->first, "6"); - cache.Get("2"); - APSARA_TEST_EQUAL(cache.mData.begin()->first, "2"); - } - - void testHostnameMetaManager() { - auto instance = ServiceMetaManager::GetInstance(); - instance->AddHostName(1, "host", "ip"); - APSARA_TEST_EQUAL(instance->mHostnameMetas.size(), 1); - instance->OnProcessDestroy(1); - APSARA_TEST_EQUAL(instance->mHostnameMetas.size(), 0); - - instance->AddHostName(1, "host", "ip"); - instance->AddHostName(1, "host2", "ip2"); - instance->AddHostName(1, "host3", "ip3"); - - APSARA_TEST_EQUAL(instance->mHostnameMetas[1]->mData.size(), 3); - APSARA_TEST_EQUAL(instance->mHostnameMetas[1]->mIndexMap.size(), 3); - INT64_FLAG(sls_observer_network_hostname_timeout) = 1; - sleep(2); - instance->GarbageTimeoutHostname(time(NULL)); - - APSARA_TEST_EQUAL(instance->mHostnameMetas.size(), 0); - - instance->GetOrPutServiceMeta(1, "ip", ProtocolType_MySQL); - APSARA_TEST_EQUAL(instance->mHostnameMetas[1]->mData.size(), 1); - APSARA_TEST_EQUAL(instance->mHostnameMetas[1]->mIndexMap.size(), 1); - APSARA_TEST_EQUAL(instance->mHostnameMetas[1]->mData.front().first, "ip"); - APSARA_TEST_EQUAL(instance->mHostnameMetas[1]->mData.front().second.Host, ""); - APSARA_TEST_EQUAL(ServiceCategoryToString(instance->mHostnameMetas[1]->mData.front().second.Category), - ServiceCategoryToString(DetectRemoteServiceCategory(ProtocolType_MySQL))); - APSARA_TEST_TRUE(instance->mHostnameMetas[1]->mData.front().second.time != 0); - - instance->GetOrPutServiceMeta(1, "ip2", ProtocolType_DNS); - APSARA_TEST_EQUAL(instance->mHostnameMetas[1]->mData.size(), 2); - APSARA_TEST_EQUAL(instance->mHostnameMetas[1]->mIndexMap.size(), 2); - APSARA_TEST_EQUAL(instance->mHostnameMetas[1]->mData.front().first, "ip2"); - APSARA_TEST_EQUAL(instance->mHostnameMetas[1]->mData.front().second.Host, ""); - APSARA_TEST_EQUAL(ServiceCategoryToString(instance->mHostnameMetas[1]->mData.front().second.Category), - ServiceCategoryToString(DetectRemoteServiceCategory(ProtocolType_DNS))); - APSARA_TEST_TRUE(instance->mHostnameMetas[1]->mData.front().second.time != 0); - APSARA_TEST_TRUE(instance->GetServiceMeta(1, "ip3").Empty()); - APSARA_TEST_EQUAL(ServiceCategoryToString(instance->GetServiceMeta(1, "ip2").Category), - ServiceCategoryToString(DetectRemoteServiceCategory(ProtocolType_DNS))); - } -}; - -APSARA_UNIT_TEST_CASE(HostnameMetaUnittest, testLRUCache, 0); - -APSARA_UNIT_TEST_CASE(HostnameMetaUnittest, testHostnameMetaManager, 0); -} // namespace logtail - - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/core/unittest/observer/JsonNetPacketReader.h b/core/unittest/observer/JsonNetPacketReader.h deleted file mode 100644 index e3bda76232..0000000000 --- a/core/unittest/observer/JsonNetPacketReader.h +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#pragma once - -#include "rapidjson/document.h" -#include "rapidjson/rapidjson.h" -#include -#include "rapidjson/istreamwrapper.h" -#include "rapidjson/ostreamwrapper.h" -#include "rapidjson/error/en.h" -#include "observer/network/NetworkObserver.h" -#include "observer/interface/helper.h" -#include - -namespace logtail { - - -class JsonNetPacketReader { -public: - JsonNetPacketReader(const std::string& filePath, - const std::string& localAddress, - bool isServer, - ProtocolType type) { - std::ifstream ifs(filePath); - rapidjson::IStreamWrapper isw(ifs); - mParseRst = mDoc.ParseStream(isw); - mLocalAddress = localAddress; - mIsServer = isServer; - mProtocol = type; - } - - bool OK() { return !mDoc.HasParseError(); } - - std::string Error() { return rapidjson::GetParseError_En(mParseRst.Code()); } - - uint8_t hexToValue(const char val) { - if (val >= '0' && val <= '9') { - return val - '0'; - } - if (val >= 'A' && val <= 'F') { - return 10 + (val - 'A'); - } - if (val >= 'a' && val <= 'f') { - return 10 + (val - 'a'); - } - return 0; - } - - char hexToChar(const char* data) { return (hexToValue(data[0]) << 4) | hexToValue(data[1]); } - - /** - * @brief - *51:31:79:53:70:58:4a:44:4c:69:36:48:4a:48:30:6b:47:66:4d:50:77:57:47:71:51:3d:22:2c:22:72:73:61:5f:6b:65:79:5f:76:65:72:73:69:6f:6e:22:3a:22:33:22:2c:22:61:65:73:5f:6b:65:79:5f:76:65:72:73:69:6f:6e:22:3a:22:33:22:7d:7d" - * @param payload - * @param dataBuffer - * @return true - * @return false - */ - bool ParseData(const std::string& payload, char* dataBuffer) { - size_t lastIndex = 0; - size_t sep = 0; - size_t i = 0; - while (true) { - sep = payload.find(':', lastIndex); - if (sep == std::string::npos) { - dataBuffer[i++] = hexToChar(&payload.at(lastIndex)); - break; - } - - dataBuffer[i++] = hexToChar(&payload.at(lastIndex)); - lastIndex += 3; - } - if (i == (payload.size() / 3 + 1)) { - return true; - } - return false; - } - - void GetAllNetPackets(std::vector& packets) { - for (auto iter = mDoc.Begin(); iter != mDoc.End(); ++iter) { - rapidjson::Value& layersValue = iter->operator[]("_source")["layers"]; - if (!layersValue.HasMember("ip")) { - continue; - } - rapidjson::Value& ipValue = layersValue["ip"]; - std::string src(ipValue["ip.src"].GetString(), ipValue["ip.src"].GetStringLength()); - std::string dst(ipValue["ip.dst"].GetString(), ipValue["ip.dst"].GetStringLength()); - - if (layersValue.HasMember("tcp") && layersValue["tcp"].HasMember("tcp.payload")) { - rapidjson::Value& tcpValue = layersValue["tcp"]; - std::string payload(tcpValue["tcp.payload"].GetString(), tcpValue["tcp.payload"].GetStringLength()); - std::string srcPort(tcpValue["tcp.srcport"].GetString(), tcpValue["tcp.srcport"].GetStringLength()); - std::string dstPort(tcpValue["tcp.dstport"].GetString(), tcpValue["tcp.dstport"].GetStringLength()); - - int32_t dataLen = int32_t(payload.size() / 3 + 1); - - std::string packetData; - packetData.resize(dataLen + sizeof(PacketEventHeader) + sizeof(PacketEventData)); - char* beginData = &packetData.at(0); - - PacketEventHeader* header = (PacketEventHeader*)beginData; - PacketEventData* data = (PacketEventData*)(beginData + sizeof(PacketEventHeader)); - data->Buffer = beginData + sizeof(PacketEventHeader) + sizeof(PacketEventData); - char* dataBuffer = data->Buffer; - - - data->PtlType = mProtocol; - // check packet direction - if (src == mLocalAddress) { - data->PktType = PacketType_Out; - if (mIsServer) { - data->MsgType = MessageType_Response; - } else { - data->MsgType = MessageType_Request; - } - } else { - data->PktType = PacketType_In; - std::swap(src, dst); - std::swap(srcPort, dstPort); - if (mIsServer) { - data->MsgType = MessageType_Request; - } else { - data->MsgType = MessageType_Response; - } - } - - - header->EventType = PacketEventType_Data; - header->PID = 0; - header->SockHash = uint32_t(HashString(src + srcPort + dst + dstPort)); - - - header->SrcAddr = SockAddressFromString(src); - - - header->SrcPort = atoi(srcPort.c_str()); - - header->DstAddr = SockAddressFromString(dst); - header->DstPort = atoi(dstPort.c_str()); - - data->BufferLen = dataLen; - data->RealLen = dataLen; - - ParseData(payload, dataBuffer); - - header->TimeNano = GetCurrentTimeInNanoSeconds(); - - - // { - // const char * rData = packetData.c_str(); - // char * bData = &packetData.at(0); - // PacketEventData * pData = (PacketEventData *)(bData + sizeof(PacketEventHeader)); - // printf("%p %p %p %p %d %d \n", data->Buffer, rData, bData, pData->Buffer, pData->Buffer - bData, - // pData->Buffer - rData); - // } - - packets.push_back(packetData); - // fix data buffers - std::string& lastPacketData = packets.back(); - { - // const char * rData = lastPacketData.c_str(); - char* bData = &lastPacketData.at(0); - PacketEventData* pData = (PacketEventData*)(bData + sizeof(PacketEventHeader)); - pData->Buffer = bData + sizeof(PacketEventHeader) + sizeof(PacketEventData); - // printf("%p %p %p %p %d %d \n", data->Buffer, rData, bData, pData->Buffer, pData->Buffer - bData, - // pData->Buffer - rData); - } - } - } - } - -private: - rapidjson::Document mDoc; - rapidjson::ParseResult mParseRst; - std::string mLocalAddress; - ProtocolType mProtocol; - bool mIsServer; -}; - - -} // namespace logtail \ No newline at end of file diff --git a/core/unittest/observer/NetLinkUnittest.cpp b/core/unittest/observer/NetLinkUnittest.cpp deleted file mode 100644 index 45f795fa5c..0000000000 --- a/core/unittest/observer/NetLinkUnittest.cpp +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include - -#include "unittest/Unittest.h" -#include "metas/ConnectionMetaManager.h" -#include "DynamicLibHelper.h" - -namespace logtail { - -// ConnectionMetaUnitTest depends on some network namespace and connection env. -// Currently, only use log to valid it. -// TODO: Polish unittest. -class ConnectionMetaUnitTest : public ::testing::Test { -public: - void TestReadInode() { - int8_t errorCode = 0; - std::string str = "net:[4026531993]"; - uint32_t number = ReadNetworkNsInodeNum(str, errorCode); - ASSERT_TRUE(number == 4026531993); - - errorCode = 0; - str = "socket:[4026531993]"; - number = ReadSocketInodeNum(str, errorCode); - ASSERT_TRUE(number == 4026531993); - } - - /** - * [2022-04-27 15:04:30.859418] [debug] open self net ns:success path:/dev/proc/self/ns/net - * [2022-04-27 15:04:30.859437] [debug] open pid net ns:success path:/dev/proc/27534/ns/net - * [2022-04-27 15:04:30.859444] [debug] set ns status:success - * [2022-04-27 15:04:30.859448] [debug] recover netlink net ns:success - * [2022-04-27 15:04:30.859454] [debug] close pid net ns fd:success - * [2022-04-27 15:04:30.859457] [debug] close self net ns fd:success - */ - void TestBindNetNamespace() { NetLinkBinder binder(27534, "/dev/proc"); } - - /** - * inode221157938 - * family: 2 localAddr: 11.122.76.82 remoteAddr: 30.30.73.112 localPort: 2222 remotePort: 61552 stat: 1 - * inode44347245 - * family: 2 localAddr: 11.122.76.82 remoteAddr: 11.239.153.17 localPort: 20459 remotePort: 19888 stat: 1 - * inode220153303 - * family: 2 localAddr: 11.122.76.82 remoteAddr: 30.25.233.92 localPort: 2222 remotePort: 52762 stat: 1 - * inode221725976 - * family: 2 localAddr: 11.122.76.82 remoteAddr: 30.30.73.112 localPort: 2222 remotePort: 50089 stat: 1 - * inode221434065 - * family: 2 localAddr: 11.122.76.82 remoteAddr: 30.30.73.112 localPort: 22 remotePort: 63831 stat: 1 - * inode220153335 - * family: 2 localAddr: 11.122.76.82 remoteAddr: 30.25.233.92 localPort: 2222 remotePort: 52767 stat: 1 - * inode195705799 - * family: 2 localAddr: 11.122.76.82 remoteAddr: 100.82.131.45 localPort: 60430 remotePort: 8000 stat: 1 - */ - void TestFetchInetConnections() { - NetLinkProber prober(594114, 1, "/dev/proc/"); - ASSERT_TRUE(prober.Status() == 0); - std::unordered_map infos; - prober.FetchInetConnections(infos); - for (const auto& item : infos) { - std::cout << "inode:" << item.first << std::endl; - item.second->Print(); - } - } - - void TestFetchUnixConnections() { - NetLinkProber prober(594114, 1, "/dev/proc/"); - ASSERT_TRUE(prober.Status() == 0); - std::unordered_map infos; - prober.FetchUnixConnections(infos); - for (const auto& item : infos) { - std::cout << "inode:" << item.first << std::endl; - item.second->Print(); - } - } - - void TestReadFdLink() { - std::cout << sizeof(sockaddr) << std::endl; - std::cout << sizeof(sockaddr_in) << std::endl; - std::cout << sizeof(sockaddr_in6) << std::endl; - std::cout << "==============" << std::endl; - APSARA_TEST_TRUE(logtail::glibc::LoadGlibcFunc()); - auto instance = ConnectionMetaManager::GetInstance(); - APSARA_TEST_TRUE(instance->Init("/proc/")); - auto info = instance->GetConnectionInfo(48064, 6); - instance->Print(); - std::cout << "==============" << std::endl; - if (info != nullptr) { - info->Print(); - } - } -}; - - -// TODO add auto check -// APSARA_UNIT_TEST_CASE(ConnectionMetaUnitTest, TestBindNetNamespace, 0); -// APSARA_UNIT_TEST_CASE(ConnectionMetaUnitTest, TestReadInode, 0); -// APSARA_UNIT_TEST_CASE(ConnectionMetaUnitTest, TestFetchInetConnections, 0); -// APSARA_UNIT_TEST_CASE(ConnectionMetaUnitTest, TestFetchUnixConnections, 0); -APSARA_UNIT_TEST_CASE(ConnectionMetaUnitTest, TestReadFdLink, 0); -// APSARA_UNIT_TEST_CASE(ConnectionMetaUnitTest, TestIPV6, 0); - -} // namespace logtail - - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/core/unittest/observer/NetworkObserverUnittest.cpp b/core/unittest/observer/NetworkObserverUnittest.cpp deleted file mode 100644 index c132354533..0000000000 --- a/core/unittest/observer/NetworkObserverUnittest.cpp +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright 2022 iLogtail 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. - - -#include "unittest/Unittest.h" -#include "unittest/UnittestHelper.h" -#include "observer/network/NetworkObserver.h" -#include "JsonNetPacketReader.h" -#include "observer/interface/helper.h" -#include "observer/network/protocols/utils.h" -#include "RawNetPacketReader.h" -#include "observer/network/ConnectionObserver.h" -#include "observer/network/ProcessObserver.h" -#include "network/protocols/ProtocolEventAggregators.h" -#include "metas/ContainerProcessGroup.h" -#include "observer/network/protocols/infer.h" - -namespace logtail { - -class NetworkObserverUnittest : public ::testing::Test { -public: - void TestToPB() { - char packetType[sizeof(PacketEventHeader) + sizeof(PacketEventData)]; - PacketEventHeader* header = (PacketEventHeader*)packetType; - header->EventType = PacketEventType_Data; - header->PID = 8; - PacketEventData* data = (PacketEventData*)(packetType + sizeof(PacketEventHeader)); - data->BufferLen = 0; - data->RealLen = 1024; - data->PtlType = ProtocolType_HTTP; - mObserver->OnPacketEvent(packetType, sizeof(PacketEventHeader) + sizeof(PacketEventData)); - - APSARA_TEST_EQUAL_FATAL(mObserver->mAllProcesses.size(), size_t(1)); - APSARA_TEST_EQUAL_FATAL(mObserver->mAllProcesses.begin()->first, 8); - APSARA_TEST_EQUAL_FATAL(mObserver->mAllProcesses.begin()->second->mAllConnections.size(), size_t(1)); - ProtocolEventAggregators* agg = mObserver->mAllProcesses.begin()->second->GetAggregator(); - DNSProtocolEventAggregator* dnsAgg = agg->GetDNSAggregator(); - DNSProtocolEvent dnsEvent; - dnsEvent.Info.ReqBytes = 100; - dnsEvent.Info.RespBytes = 200; - dnsEvent.Info.LatencyNs = 300; - dnsEvent.Key.ReqResource = "cn-hangzhou.log.aliyuncs.com"; - dnsEvent.Key.RespStatus = 1; - dnsEvent.Key.ConnKey.Role = PacketRoleType::Server; - dnsAgg->AddEvent(std::move(dnsEvent)); - - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), size_t(1)); - - std::cout << allData[0].DebugString(); - sls_logs::Log* log = &allData[0]; - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched( - log, "local_info", "{\"_process_cmd_\":\"\",\"_process_pid_\":\"8\",\"_running_mode_\":\"host\"}")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "req_resource", "cn-hangzhou.log.aliyuncs.com")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "resp_status", "1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "protocol", "dns")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "role", "s")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "count", "1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "latency_ns", "300")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "req_bytes", "100")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "resp_bytes", "200")); - } - - - void TestJsonPacketToPB() { - JsonNetPacketReader reader("/tmp/wireshark.json", "30.43.121.41", false, ProtocolType_DNS); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - const char* data = packet.c_str(); - char* bData = &packet.at(0); - PacketEventData* pData = (PacketEventData*)(bData + sizeof(PacketEventHeader)); - printf("%p %p %p %ld %ld \n", data, bData, pData->Buffer, pData->Buffer - bData, pData->Buffer - data); - std::cout << PacketEventToString(&packet.at(0), packet.size()) << std::endl; - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), size_t(1)); - - std::cout << allData[0].DebugString(); - } - - - void TestJsonNetPacketReader() { - JsonNetPacketReader reader("/tmp/wireshark.json", "30.43.121.41", false, ProtocolType_HTTP); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - std::cout << PacketEventToString(&packet.at(0), packet.size()) << std::endl; - } - } - - - void TestRawPacketUDPReader() { - std::string rawHex1 - = "a07817a0852e00749c945d39080045000057518900007a111c121e1e1e1e1e2b78940035c09800430bab1dd68180000100020000" - "000005626169647503636f6d0000010001c00c00010001000001300004dcb52694c00c00010001000001300004dcb526fb"; - std::vector rawHexs{rawHex1}; - RawNetPacketReader reader("30.43.120.148", true, ProtocolType_DNS, rawHexs); - std::vector packets; - reader.GetAllNetPackets(packets); - if (!reader.OK()) { - std::cerr << reader.GetParseFailMsg() << std::endl; - } - } - - void TestRawPacketTCPReader() { - std::string rawHex1 = "00749c945d39a07817a0852e080045000071000040004006a0581e2b7853dcb526fbcd4700506b7401eca591" - "a5ce5018100037ad0000474554202f20485454502f312e310d0a486f73743a2062616964752e636f6d0d0a55" - "7365722d4167656e743a206375726c2f372e37372e300d0a4163636570743a202a2f2a0d0a0d0a"; - std::vector rawHexs{rawHex1}; - RawNetPacketReader reader("30.43.120.83", true, ProtocolType_HTTP, rawHexs); - std::vector packets; - reader.GetAllNetPackets(packets); - if (!reader.OK()) { - std::cerr << reader.GetParseFailMsg() << std::endl; - } - } - - - void inferRedis() { - std::string rawHex1 = "00749c945d39a07817a0852e08004500005b000040004006375a1e2b78d70b9f60a2e2ae18ebeec9ad5886a8" - "e17980180800c6e800000101080a4d49243fd112ef9f2a330d0a24330d0a7365740d0a24310d0a610d0a2431" - "320d0a6861686167617367667361660d0a"; - std::string rawHex2 = "a07817a0852e00749c945d39080045000039b7464000370689350b9f60a21e2b78d718ebe2ae86a8e179eec9" - "ad7f80180039a65a00000101080ad11409da4d49243f2b4f4b0d0a"; - std::vector rawHexs{rawHex1, rawHex2}; - RawNetPacketReader reader("30.43.120.215", false, ProtocolType_None, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - char* bData = &packet.at(0); - PacketEventData* pData = (PacketEventData*)(bData + sizeof(PacketEventHeader)); - - PacketEventHeader header; - auto ret = infer_protocol(&header, PacketType_In, pData->Buffer, pData->BufferLen, pData->BufferLen); - APSARA_TEST_TRUE(std::get<0>(ret) == ProtocolType_Redis); - } - } - - void inferDNS() { - std::string rawHex1 - = "a07817a0852e00749c945d39080045000057518900007a111c121e1e1e1e1e2b78940035c09800430bab1dd68180000100020000" - "000005626169647503636f6d0000010001c00c00010001000001300004dcb52694c00c00010001000001300004dcb526fb"; - std::vector rawHexs{rawHex1}; - RawNetPacketReader reader("30.43.120.215", false, ProtocolType_None, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - char* bData = &packet.at(0); - PacketEventData* pData = (PacketEventData*)(bData + sizeof(PacketEventHeader)); - - PacketEventHeader header; - auto ret = infer_protocol(&header, PacketType_In, pData->Buffer, pData->BufferLen, pData->BufferLen); - APSARA_TEST_TRUE(std::get<0>(ret) == ProtocolType_DNS); - } - } - - void inferHTTP() { - std::string rawHex1 = "00749c945d39a07817a0852e080045000071000040004006a0581e2b7853dcb526fbcd4700506b7401eca591" - "a5ce5018100037ad0000474554202f20485454502f312e310d0a486f73743a2062616964752e636f6d0d0a55" - "7365722d4167656e743a206375726c2f372e37372e300d0a4163636570743a202a2f2a0d0a0d0a"; - std::string rawHex2 - = "a07817a0852e00749c945d390800450001598c7240002a0628fedcb526fb1e2b78530050cd47a591a5ce6b74023550180304c265" - "0000485454502f312e3120323030204f4b0d0a446174653a205468752c203237204a616e20323032322030393a34363a31332047" - "4d540d0a5365727665723a204170616368650d0a4c6173742d4d6f6469666965643a205475652c203132204a616e203230313020" - "31333a34383a303020474d540d0a455461673a202235312d34376366376536656538343030220d0a4163636570742d52616e6765" - "733a2062797465730d0a436f6e74656e742d4c656e6774683a2038310d0a43616368652d436f6e74726f6c3a206d61782d616765" - "3d38363430300d0a457870697265733a204672692c203238204a616e20323032322030393a34363a313320474d540d0a436f6e6e" - "656374696f6e3a204b6565702d416c6976650d0a436f6e74656e742d547970653a20746578742f68746d6c0d0a0d0a"; - std::vector rawHexs{rawHex1, rawHex2}; - RawNetPacketReader reader("30.43.120.215", false, ProtocolType_None, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - char* bData = &packet.at(0); - PacketEventData* pData = (PacketEventData*)(bData + sizeof(PacketEventHeader)); - - PacketEventHeader header; - auto ret = infer_protocol(&header, PacketType_In, pData->Buffer, pData->BufferLen, pData->BufferLen); - APSARA_TEST_TRUE(std::get<0>(ret) == ProtocolType_HTTP); - } - } - - void inferMySQL() { - std::string rawHex1 - = "00749c945d39a07817a0852e08004500004c00004000400637031e2b793d0b9f60a2cb7b0cea933e3190a670a97080180801920c" - "00000101080a08d1f5fd7fc82492140000000373656c656374202a2066726f6d2068656c6c6f"; - std::string rawHex2 = "a07817a0852e00749c945d390800450000c330164000360610760b9f60a21e2b793d0ceacb7ba670a970933e" - "31a880180039dc6300000101080a7fc848ca08d1f5fd01000001022a00000203646566066d79746573740568" - "656c6c6f0568656c6c6f0263310263310c080020000000fd00000000002a00000303646566066d7974657374" - "0568656c6c6f0568656c6c6f0263320263320c080020000000fd000000000005000004fe000022000a000005" - "046161613104616161320a0000060462626231046262623205000007fe00002200"; - // login request - std::string rawHex3 = "00749c945d39a07817a0852e0800450000f800004000400692a01e2b78cc0b9f04cae5290cea916ada492b07" - "2bc9801808090f9100000101080abb07480dccbb2a2bc00000018da6ff0900000001ff000000000000000000" - "0000000000000000000000000000736c73000065746c5f6e67320063616368696e675f736861325f70617373" - "776f7264007c035f6f73086f737831302e3136095f706c6174666f726d067838365f36340f5f636c69656e74" - "5f76657273696f6e06382e302e32330c5f636c69656e745f6e616d65086c69626d7973716c045f7069640532" - "39373237076f735f757365720a73756e796f6e676875610c70726f6772616d5f6e616d65056d7973716c"; - std::vector rawHexs{rawHex1, rawHex2, rawHex3}; - RawNetPacketReader reader("30.43.120.215", false, ProtocolType_None, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - char* bData = &packet.at(0); - PacketEventData* pData = (PacketEventData*)(bData + sizeof(PacketEventHeader)); - PacketEventHeader header; - auto ret = infer_protocol(&header, PacketType_In, pData->Buffer, pData->BufferLen, pData->BufferLen); - APSARA_TEST_TRUE(std::get<0>(ret) == ProtocolType_MySQL); - } - } - - void TestInferProtocol() { - inferRedis(); - inferDNS(); - inferHTTP(); - inferMySQL(); - } - - NetworkObserver* mObserver = NetworkObserver::GetInstance(); -}; - - -APSARA_UNIT_TEST_CASE(NetworkObserverUnittest, TestToPB, 0); -// APSARA_UNIT_TEST_CASE(NetworkObserverUnittest, TestJsonNetPacketReader, 0); -// APSARA_UNIT_TEST_CASE(NetworkObserverUnittest, TestJsonPacketToPB, 0); -APSARA_UNIT_TEST_CASE(NetworkObserverUnittest, TestRawPacketUDPReader, 0); -APSARA_UNIT_TEST_CASE(NetworkObserverUnittest, TestRawPacketTCPReader, 0); -APSARA_UNIT_TEST_CASE(NetworkObserverUnittest, TestInferProtocol, 0); -} // namespace logtail - - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/core/unittest/observer/ObserverConfigUnittest.cpp b/core/unittest/observer/ObserverConfigUnittest.cpp deleted file mode 100644 index 35fd1baf1f..0000000000 --- a/core/unittest/observer/ObserverConfigUnittest.cpp +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include - -#include "unittest/Unittest.h" -#include "unittest/UnittestHelper.h" -#include "network/NetworkConfig.h" -#include "logger/Logger.h" - -namespace logtail { - -class ObserverConfigUnittest : public ::testing::Test { -public: - void TestparseConfig() { - auto cfg = NetworkConfig::GetInstance(); - - cfg->mLastApplyedConfigDetail = "[\n" - " {\n" - " \"Common\":{\n" - " \"Sampling\":50,\n" - " \"FlushOutL4Interval\":5,\n" - " \"FlushOutL7Interval\":55,\n" - " \"FlushMetaInterval\":6,\n" - " \"FlushNetlinkInterval\":7,\n" - " \"ProtocolProcess\":true,\n" - " \"DropUnixSocket\":false,\n" - " \"DropLocalConnections\":false,\n" - " \"DropUnknownSocket\":false,\n" - " \"IncludeProtocols\":[\n" - " \"MySQL\",\n" - " \"PgSQL\"\n" - " ],\n" - " \"Tags\":{\n" - " \"key\":\"val\"\n" - " },\n" - " \"ProtocolAggCfg\":{\n" - " \"mysql\":{\n" - " \"ClientSize\":1,\n" - " \"ServerSize\":2\n" - " },\n" - " \"pgsql\":{\n" - " \"ClientSize\":1,\n" - " \"ServerSize\":2\n" - " }\n" - " },\n" - " \"IncludeCmdRegex\":\"^in_cmd\",\n" - " \"ExcludeCmdRegex\":\"^ex_cmd$\",\n" - " \"IncludeContainerNameRegex\":\"^in_cname\",\n" - " \"ExcludeContainerNameRegex\":\"^ex_cname$\",\n" - " \"IncludePodNameRegex\":\"^in_pod\",\n" - " \"ExcludePodNameRegex\":\"^ex_pod$\",\n" - " \"IncludeNamespaceNameRegex\":\"^in_namespace\",\n" - " \"ExcludeNamespaceNameRegex\":\"^ex_namespace\",\n" - " \"IncludeContainerLabels\":{\n" - " \"app3\":\"^test3$\"\n" - " },\n" - " \"ExcludeContainerLabels\":{\n" - " \"app4\":\"^test4$\"\n" - " },\n" - " \"IncludeK8sLabels\":{\n" - " \"app2\":\"^test2$\"\n" - " },\n" - " \"ExcludeK8sLabels\":{\n" - " \"app1\":\"^test1$\"\n" - " },\n" - " \"IncludeEnvs\":{\n" - " \"env1\":\"^env1\"\n" - " },\n" - " \"ExcludeEnvs\":{\n" - " \"env2\":\"^env2\"\n" - " }\n" - " },\n" - " \"EBPF\":{\n" - " \"Enabled\":true\n" - " },\n" - " \"Type\":\"input_observer_network\"\n" - " }\n" - "]"; - - std::cout << cfg->mLastApplyedConfigDetail << std::endl; - cfg->SetFromJsonString(); - - APSARA_TEST_TRUE(cfg->mSampling == 50); - APSARA_TEST_TRUE(cfg->mFlushOutL4Interval == 5); - APSARA_TEST_TRUE(cfg->mFlushOutL7Interval == 55); - APSARA_TEST_TRUE(cfg->mFlushMetaInterval == 6); - APSARA_TEST_TRUE(cfg->mFlushNetlinkInterval == 7); - APSARA_TEST_TRUE(cfg->mProtocolProcessFlag > 0); - APSARA_TEST_TRUE(!cfg->mDropUnixSocket); - APSARA_TEST_TRUE(!cfg->mDropLocalConnections); - APSARA_TEST_TRUE(!cfg->mDropUnknownSocket); - APSARA_TEST_TRUE(!cfg->mDropUnknownSocket); - APSARA_TEST_TRUE(cfg->IsLegalProtocol(ProtocolType_MySQL)); - APSARA_TEST_TRUE(cfg->IsLegalProtocol(ProtocolType_PgSQL)); - APSARA_TEST_TRUE(!cfg->IsLegalProtocol(ProtocolType_DNS)); - APSARA_TEST_TRUE(!cfg->IsLegalProtocol(ProtocolType_Redis)); - APSARA_TEST_TRUE(cfg->mTags.size() == 1); - APSARA_TEST_EQUAL(cfg->mTags[0].first, "__tag__:key"); - APSARA_TEST_EQUAL(cfg->mTags[0].second, "val"); - APSARA_TEST_EQUAL(cfg->GetProtocolAggSize(ProtocolType_Redis).first, 500); - APSARA_TEST_EQUAL(cfg->GetProtocolAggSize(ProtocolType_Redis).second, 5000); - APSARA_TEST_EQUAL(cfg->GetProtocolAggSize(ProtocolType_MySQL).first, 1); - APSARA_TEST_EQUAL(cfg->GetProtocolAggSize(ProtocolType_MySQL).second, 2); - APSARA_TEST_EQUAL(cfg->GetProtocolAggSize(ProtocolType_PgSQL).first, 1); - APSARA_TEST_EQUAL(cfg->GetProtocolAggSize(ProtocolType_PgSQL).second, 2); - APSARA_TEST_TRUE(!cfg->mExcludeCmdRegex.empty() && cfg->mExcludeCmdRegex.str() == "^ex_cmd$"); - APSARA_TEST_TRUE(!cfg->mExcludeContainerNameRegex.empty() - && cfg->mExcludeContainerNameRegex.str() == "^ex_cname$"); - APSARA_TEST_TRUE(!cfg->mExcludePodNameRegex.empty() && cfg->mExcludePodNameRegex.str() == "^ex_pod$"); - APSARA_TEST_TRUE(!cfg->mExcludeNamespaceNameRegex.empty() - && cfg->mExcludeNamespaceNameRegex.str() == "^ex_namespace"); - APSARA_TEST_TRUE(!cfg->mIncludeCmdRegex.empty() && cfg->mIncludeCmdRegex.str() == "^in_cmd"); - APSARA_TEST_TRUE(!cfg->mIncludeContainerNameRegex.empty() - && cfg->mIncludeContainerNameRegex.str() == "^in_cname"); - APSARA_TEST_TRUE(!cfg->mIncludePodNameRegex.empty() && cfg->mIncludePodNameRegex.str() == "^in_pod"); - APSARA_TEST_TRUE(!cfg->mIncludeNamespaceNameRegex.empty() - && cfg->mIncludeNamespaceNameRegex.str() == "^in_namespace"); - APSARA_TEST_EQUAL(cfg->label2String(cfg->mIncludeContainerLabels), "app3=^test3$,"); - APSARA_TEST_EQUAL(cfg->label2String(cfg->mExcludeContainerLabels), "app4=^test4$,"); - APSARA_TEST_EQUAL(cfg->label2String(cfg->mIncludeK8sLabels), "app2=^test2$,"); - APSARA_TEST_EQUAL(cfg->label2String(cfg->mExcludeK8sLabels), "app1=^test1$,"); - APSARA_TEST_EQUAL(cfg->label2String(cfg->mIncludeEnvs), "env1=^env1,"); - APSARA_TEST_EQUAL(cfg->label2String(cfg->mExcludeEnvs), "env2=^env2,"); - - std::cout << cfg->ToString() << std::endl; - } -}; - -APSARA_UNIT_TEST_CASE(ObserverConfigUnittest, TestparseConfig, 0); -} // namespace logtail - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/core/unittest/observer/ProtocolDnsUnittest.cpp b/core/unittest/observer/ProtocolDnsUnittest.cpp deleted file mode 100644 index e25216e7f0..0000000000 --- a/core/unittest/observer/ProtocolDnsUnittest.cpp +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include - -#include "network/protocols/utils.h" -#include "observer/network/NetworkObserver.h" -#include "observer/network/ProcessObserver.h" -#include "network/protocols/dns/inner_parser.h" -#include "unittest/Unittest.h" -#include "RawNetPacketReader.h" -#include "unittest/UnittestHelper.h" - -namespace logtail { - -class ProtocolDnsUnittest : public ::testing::Test { -public: - // Domain Name System (query) - // Transaction ID: 0x8f62 - // Flags: 0x0100 Standard query - // Questions: 1 - // Answer RRs: 0 - // Authority RRs: 0 - // Additional RRs: 0 - // Queries - // sp1.baidu.com: type A, class IN - void TestCommonRequest() { - const std::string hexString = "8f62010000010000000000000373703105626169647503636f6d0000010001"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::DNSParser dns((const char*)data.data(), (size_t)data.size()); - dns.parse(); - APSARA_TEST_EQUAL(dns.dnsHeader.txid, 0x8f62); - APSARA_TEST_EQUAL(dns.dnsHeader.flags, 0x0100); - APSARA_TEST_EQUAL(dns.dnsHeader.numQueries, 1); - APSARA_TEST_EQUAL(dns.dnsHeader.numAnswers, 0); - APSARA_TEST_EQUAL(dns.dnsHeader.numAuth, 0); - APSARA_TEST_EQUAL(dns.dnsHeader.numAddl, 0); - APSARA_TEST_EQUAL(dns.packetType, DNSPacketType::DNSPacketRequest); - APSARA_TEST_EQUAL(dns.dnsRequest.requests.size(), 1); - APSARA_TEST_EQUAL(dns.dnsResponse.responses.size(), 0); - APSARA_TEST_EQUAL(dns.dnsRequest.requests[0].queryHost, "sp1.baidu.com"); - APSARA_TEST_EQUAL(dns.dnsRequest.requests[0].queryType, DNSQueryTypeA); - } - - // Domain Name System (response) - // Transaction ID: 0x661b - // Flags: 0x8180 Standard query response, No error - // Questions: 1 - // Answer RRs: 3 - // Authority RRs: 0 - // Additional RRs: 0 - // Queries - // www.baidu.com: type A, class IN - // Answers - // www.baidu.com: type CNAME, class IN, cname www.a.shifen.com - // Name: www.baidu.com - // Type: CNAME (Canonical NAME for an alias) (5) - // Class: IN (0x0001) - // Time to live: 1130 (18 minutes, 50 seconds) - // Data length: 15 - // CNAME: www.a.shifen.com - // www.a.shifen.com: type A, class IN, addr 110.242.68.3 - // Name: www.a.shifen.com - // Type: A (Host Address) (1) - // Class: IN (0x0001) - // Time to live: 51 (51 seconds) - // Data length: 4 - // Address: 110.242.68.3 - // www.a.shifen.com: type A, class IN, addr 110.242.68.4 - // Name: www.a.shifen.com - // Type: A (Host Address) (1) - // Class: IN (0x0001) - // Time to live: 51 (51 seconds) - // Data length: 4 - // Address: 110.242.68.4 - void TestCommonResponse() { - const std::string hexString - = "661b818000010003000000000377777705626169647503636f6d0000010001c00c000500010000046a000f037777770161067368" - "6966656ec016c02b000100010000003300046ef24403c02b000100010000003300046ef24404"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::DNSParser dns((const char*)data.data(), (size_t)data.size()); - dns.parse(); - APSARA_TEST_EQUAL(dns.dnsHeader.txid, 0x661b); - APSARA_TEST_EQUAL(dns.dnsHeader.flags, 0x8180); - APSARA_TEST_EQUAL(dns.dnsHeader.numQueries, 1); - APSARA_TEST_EQUAL(dns.dnsHeader.numAnswers, 3); - APSARA_TEST_EQUAL(dns.dnsHeader.numAuth, 0); - APSARA_TEST_EQUAL(dns.dnsHeader.numAddl, 0); - APSARA_TEST_EQUAL(dns.packetType, DNSPacketType::DNSPacketResponse); - APSARA_TEST_EQUAL(dns.dnsRequest.requests.size(), 1); - APSARA_TEST_EQUAL(dns.dnsResponse.responses.size(), 3); - APSARA_TEST_EQUAL(dns.dnsRequest.requests[0].queryHost, "www.baidu.com"); - APSARA_TEST_EQUAL(dns.dnsRequest.requests[0].queryType, DNSQueryTypeA); - APSARA_TEST_EQUAL(dns.dnsResponse.answerCount, 3); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[0].queryType, DNSQueryTypeCNAME); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[0].queryHost, "www.baidu.com"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[0].ttlSeconds, 1130); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[0].value, "www.a.shifen.com"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[1].queryType, DNSQueryTypeA); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[1].queryHost, "www.a.shifen.com"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[1].ttlSeconds, 51); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[1].value, "110.242.68.3"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[2].queryType, DNSQueryTypeA); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[2].queryHost, "www.a.shifen.com"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[2].ttlSeconds, 51); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[2].value, "110.242.68.4"); - } - - // Domain Name System (response) - // Transaction ID: 0x7eb0 - // Flags: 0x8180 Standard query response, No error - // Questions: 1 - // Answer RRs: 9 - // Authority RRs: 0 - // Additional RRs: 0 - // Queries - // teamapp-sf.teamapp.com: type AAAA, class IN - // Name: teamapp-sf.teamapp.com - // [Name Length: 22] - // [Label Count: 3] - // Type: AAAA (IPv6 Address) (28) - // Class: IN (0x0001) - // Answers - // teamapp-sf.teamapp.com: type CNAME, class IN, cname d363k6ozzqba28.cloudfront.net - // Name: teamapp-sf.teamapp.com - // Type: CNAME (Canonical NAME for an alias) (5) - // Class: IN (0x0001) - // Time to live: 194 (3 minutes, 14 seconds) - // Data length: 31 - // CNAME: d363k6ozzqba28.cloudfront.net - // d363k6ozzqba28.cloudfront.net: type AAAA, class IN, addr 2600:9000:21c4:aa00:2:307f:6c00:93a1 - // Name: d363k6ozzqba28.cloudfront.net - // Type: AAAA (IPv6 Address) (28) - // Class: IN (0x0001) - // Time to live: 54 (54 seconds) - // Data length: 16 - // AAAA Address: 2600:9000:21c4:aa00:2:307f:6c00:93a1 - // d363k6ozzqba28.cloudfront.net: type AAAA, class IN, addr 2600:9000:21c4:8200:2:307f:6c00:93a1 - // Name: d363k6ozzqba28.cloudfront.net - // Type: AAAA (IPv6 Address) (28) - // Class: IN (0x0001) - // Time to live: 54 (54 seconds) - // Data length: 16 - // AAAA Address: 2600:9000:21c4:8200:2:307f:6c00:93a1 - // d363k6ozzqba28.cloudfront.net: type AAAA, class IN, addr 2600:9000:21c4:5c00:2:307f:6c00:93a1 - // Name: d363k6ozzqba28.cloudfront.net - // Type: AAAA (IPv6 Address) (28) - // Class: IN (0x0001) - // Time to live: 54 (54 seconds) - // Data length: 16 - // AAAA Address: 2600:9000:21c4:5c00:2:307f:6c00:93a1 - // d363k6ozzqba28.cloudfront.net: type AAAA, class IN, addr 2600:9000:21c4:ac00:2:307f:6c00:93a1 - // Name: d363k6ozzqba28.cloudfront.net - // Type: AAAA (IPv6 Address) (28) - // Class: IN (0x0001) - // Time to live: 54 (54 seconds) - // Data length: 16 - // AAAA Address: 2600:9000:21c4:ac00:2:307f:6c00:93a1 - // d363k6ozzqba28.cloudfront.net: type AAAA, class IN, addr 2600:9000:21c4:a800:2:307f:6c00:93a1 - // Name: d363k6ozzqba28.cloudfront.net - // Type: AAAA (IPv6 Address) (28) - // Class: IN (0x0001) - // Time to live: 54 (54 seconds) - // Data length: 16 - // AAAA Address: 2600:9000:21c4:a800:2:307f:6c00:93a1 - // d363k6ozzqba28.cloudfront.net: type AAAA, class IN, addr 2600:9000:21c4:b600:2:307f:6c00:93a1 - // Name: d363k6ozzqba28.cloudfront.net - // Type: AAAA (IPv6 Address) (28) - // Class: IN (0x0001) - // Time to live: 54 (54 seconds) - // Data length: 16 - // AAAA Address: 2600:9000:21c4:b600:2:307f:6c00:93a1 - // d363k6ozzqba28.cloudfront.net: type AAAA, class IN, addr 2600:9000:21c4:800:2:307f:6c00:93a1 - // Name: d363k6ozzqba28.cloudfront.net - // Type: AAAA (IPv6 Address) (28) - // Class: IN (0x0001) - // Time to live: 54 (54 seconds) - // Data length: 16 - // AAAA Address: 2600:9000:21c4:800:2:307f:6c00:93a1 - // d363k6ozzqba28.cloudfront.net: type AAAA, class IN, addr 2600:9000:21c4:b200:2:307f:6c00:93a1 - // Name: d363k6ozzqba28.cloudfront.net - // Type: AAAA (IPv6 Address) (28) - // Class: IN (0x0001) - // Time to live: 54 (54 seconds) - // Data length: 16 - // AAAA Address: 2600:9000:21c4:b200:2:307f:6c00:93a1 - void TestTypeAAAAResponse() { - const std::string hexString - = "7eb0818000010009000000000a7465616d6170702d7366077465616d61707003636f6d00001c0001c00c00050001000000c2001f" - "0e643336336b366f7a7a71626132380a636c6f756466726f6e74036e657400c034001c00010000003600102600900021c4aa0000" - "02307f6c0093a1c034001c00010000003600102600900021c482000002307f6c0093a1c034001c00010000003600102600900021" - "c45c000002307f6c0093a1c034001c00010000003600102600900021c4ac000002307f6c0093a1c034001c000100000036001026" - "00900021c4a8000002307f6c0093a1c034001c00010000003600102600900021c4b6000002307f6c0093a1c034001c0001000000" - "3600102600900021c408000002307f6c0093a1c034001c00010000003600102600900021c4b2000002307f6c0093a1"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::DNSParser dns((const char*)data.data(), (size_t)data.size()); - dns.parse(); - APSARA_TEST_EQUAL(dns.dnsHeader.txid, 0x7eb0); - APSARA_TEST_EQUAL(dns.dnsHeader.flags, 0x8180); - APSARA_TEST_EQUAL(dns.dnsHeader.numQueries, 1); - APSARA_TEST_EQUAL(dns.dnsHeader.numAnswers, 9); - APSARA_TEST_EQUAL(dns.dnsHeader.numAuth, 0); - APSARA_TEST_EQUAL(dns.dnsHeader.numAddl, 0); - APSARA_TEST_EQUAL(dns.packetType, DNSPacketType::DNSPacketResponse); - APSARA_TEST_EQUAL(dns.dnsRequest.requests.size(), 1); - APSARA_TEST_EQUAL(dns.dnsResponse.responses.size(), 9); - APSARA_TEST_EQUAL(dns.dnsRequest.requests[0].queryHost, "teamapp-sf.teamapp.com"); - APSARA_TEST_EQUAL(dns.dnsRequest.requests[0].queryType, DNSQueryTypeAAAA); - APSARA_TEST_EQUAL(dns.dnsResponse.answerCount, 9); - - APSARA_TEST_EQUAL(dns.dnsResponse.responses[0].queryType, DNSQueryTypeCNAME); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[0].queryHost, "teamapp-sf.teamapp.com"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[0].ttlSeconds, 194); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[0].value, "d363k6ozzqba28.cloudfront.net"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[1].queryType, DNSQueryTypeAAAA); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[1].queryHost, "d363k6ozzqba28.cloudfront.net"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[1].ttlSeconds, 54); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[1].value, "2600:9000:21c4:aa00:2:307f:6c00:93a1"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[2].queryType, DNSQueryTypeAAAA); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[2].queryHost, "d363k6ozzqba28.cloudfront.net"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[2].ttlSeconds, 54); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[2].value, "2600:9000:21c4:8200:2:307f:6c00:93a1"); - } - - - // Domain Name System (response) - // Transaction ID: 0x588d - // Flags: 0x8180 Standard query response, No error - // Questions: 1 - // Answer RRs: 1 - // Authority RRs: 0 - // Additional RRs: 0 - // Queries - // 83.74.227.13.in-addr.arpa: type PTR, class IN - // Name: 83.74.227.13.in-addr.arpa - // [Name Length: 25] - // [Label Count: 6] - // Type: PTR (domain name PoinTeR) (12) - // Class: IN (0x0001) - // Answers - // 83.74.227.13.in-addr.arpa: type PTR, class IN, server-13-227-74-83.sfo20.r.cloudfront.net - // Name: 83.74.227.13.in-addr.arpa - // Type: PTR (domain name PoinTeR) (12) - // Class: IN (0x0001) - // Time to live: 66720 (18 hours, 32 minutes) - // Data length: 44 - // Domain Name: server-13-227-74-83.sfo20.r.cloudfront.net - void TestUnknownResponse() { - const std::string hexString - = "588d818000010001000000000238330237340332323702313307696e2d61646472046172706100000c0001c00c000c0001000104" - "a0002c137365727665722d31332d3232372d37342d38330573666f323001720a636c6f756466726f6e74036e657400"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::DNSParser dns((const char*)data.data(), (size_t)data.size()); - dns.parse(); - APSARA_TEST_EQUAL(dns.dnsHeader.txid, 0x588d); - APSARA_TEST_EQUAL(dns.dnsHeader.flags, 0x8180); - APSARA_TEST_EQUAL(dns.dnsHeader.numQueries, 1); - APSARA_TEST_EQUAL(dns.dnsHeader.numAnswers, 1); - APSARA_TEST_EQUAL(dns.dnsHeader.numAuth, 0); - APSARA_TEST_EQUAL(dns.dnsHeader.numAddl, 0); - APSARA_TEST_EQUAL(dns.packetType, DNSPacketType::DNSPacketResponse); - APSARA_TEST_EQUAL(dns.dnsRequest.requests.size(), 1); - APSARA_TEST_EQUAL(dns.dnsResponse.responses.size(), 1); - APSARA_TEST_EQUAL(dns.dnsRequest.requests[0].queryHost, "83.74.227.13.in-addr.arpa"); - APSARA_TEST_EQUAL(ToString(dns.dnsRequest.requests[0].queryType), "UnKnown"); - APSARA_TEST_EQUAL(dns.dnsResponse.answerCount, 1); - APSARA_TEST_EQUAL(ToString(dns.dnsResponse.responses[0].queryType), "UnKnown"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[0].queryHost, "83.74.227.13.in-addr.arpa"); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[0].ttlSeconds, 66720); - APSARA_TEST_EQUAL(dns.dnsResponse.responses[0].value, "UnKnown"); - } - - - void TestDNSPacketReader() { - std::vector rawHexs{rawHex1, rawHex2, rawHex3, rawHex4}; - RawNetPacketReader reader("30.43.120.83", false, ProtocolType_DNS, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - // std::cout << PacketEventToString(&packet.at(0), packet.size()) << std::endl; - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_resource", "xxxxaawefafadsfasfasf.com")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_type", "A")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "resp_status", "0")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "count", "1")); - APSARA_TEST_TRUE(UnitTestHelper::GetLogKey(&allData[0], "latency_ns").first != "0"); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_bytes", "54")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "resp_bytes", "127")); - - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "req_resource", "baidu.com")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "req_type", "A")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "resp_status", "1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "count", "1")); - APSARA_TEST_TRUE(UnitTestHelper::GetLogKey(&allData[0], "latency_ns").first != "0"); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "req_bytes", "38")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "resp_bytes", "70")); - } - - - void TestDNSPacketReaderUnorder() { - std::vector rawHexs{rawHex4, rawHex3, rawHex2, rawHex1}; - RawNetPacketReader reader("30.43.120.83", false, ProtocolType_DNS, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - // std::cout << PacketEventToString(&packet.at(0), packet.size()) << std::endl; - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_resource", "xxxxaawefafadsfasfasf.com")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_type", "A")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "resp_status", "0")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "latency_ns", "0")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_bytes", "54")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "resp_bytes", "127")); - - - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "req_resource", "baidu.com")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "req_type", "A")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "resp_status", "1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "count", "1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "latency_ns", "0")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "req_bytes", "38")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "resp_bytes", "70")); - } - - void TestDNSParserGC() { - INT64_FLAG(sls_observer_network_process_timeout) = 18446744073; - INT64_FLAG(sls_observer_network_connection_timeout) = 18446744073; - INT64_FLAG(sls_observer_network_process_no_connection_timeout) = 0; - BOOL_FLAG(sls_observer_network_protocol_stat) = true; - // clear history - NetworkStatistic* networkStatistic = NetworkStatistic::GetInstance(); - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - networkStatistic->Clear(); - - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - - std::vector rawHexs{rawHex1, rawHex4}; - RawNetPacketReader reader("30.30.30.30", false, ProtocolType_DNS, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), 0); - - // GC - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds() - 60000000000000ULL); - ProtocolDebugStatistic* statistic = ProtocolDebugStatistic::GetInstance(); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 1); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 0); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 0); - APSARA_TEST_EQUAL(statistic->mDNSConnectionNum, 2); - APSARA_TEST_EQUAL(statistic->mDNSConnectionCachedSize, 2); - statistic->Clear(); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 2); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 2); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 1); - APSARA_TEST_EQUAL(statistic->mDNSConnectionNum, 0); - APSARA_TEST_EQUAL(statistic->mDNSConnectionCachedSize, 0); - } - - NetworkObserver* mObserver = NetworkObserver::GetInstance(); - - const std::string rawHex1 = "00749c945d39a07817a0852e080045000042f72100004011b0cf1e2b78531e1e1e1ef7530035002ea4c43f" - "2e0120000100000000000105626169647503636f6d00000100010000291000000000000000"; - const std::string rawHex2 = "a07817a0852e00749c945d3908004500006286f500007a11e6db1e1e1e1e1e2b78530035f753004ec1f83f" - "2e8180000100020000000105626169647503636f6d0000010001c00c00010001000001210004dcb52694c0" - "0c00010001000001210004dcb526fb0000290fa0000000000000"; - const std::string rawHex3 - = "00749c945d39a07817a0852e080045000052ccbf00004011db211e2b78531e1e1e1ee0800035003e48f33e5701200001000000000001" - "1578787878616177656661666164736661736661736603636f6d00000100010000291000000000000000"; - const std::string rawHex4 = "a07817a0852e00749c945d3908004500009b09bd00007a1163db1e1e1e1e1e2b78530035e08000874c1e3e" - "57818300010000000100011578787878616177656661666164736661736661736603636f6d0000010001c0" - "220006000100000384003d01610c67746c642d73657276657273036e657400056e73746c640c7665726973" - "69676e2d677273c02261f25b8e000007080000038400093a80000151800000290fa0000000000000"; -}; - -APSARA_UNIT_TEST_CASE(ProtocolDnsUnittest, TestCommonRequest, 0); - -APSARA_UNIT_TEST_CASE(ProtocolDnsUnittest, TestCommonResponse, 0); - -APSARA_UNIT_TEST_CASE(ProtocolDnsUnittest, TestTypeAAAAResponse, 0); - -APSARA_UNIT_TEST_CASE(ProtocolDnsUnittest, TestUnknownResponse, 0); - -APSARA_UNIT_TEST_CASE(ProtocolDnsUnittest, TestDNSPacketReader, 0); - -APSARA_UNIT_TEST_CASE(ProtocolDnsUnittest, TestDNSPacketReaderUnorder, 0); - -APSARA_UNIT_TEST_CASE(ProtocolDnsUnittest, TestDNSParserGC, 0); -} // namespace logtail - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/core/unittest/observer/ProtocolHttpUnittest.cpp b/core/unittest/observer/ProtocolHttpUnittest.cpp deleted file mode 100644 index 54d46af5bc..0000000000 --- a/core/unittest/observer/ProtocolHttpUnittest.cpp +++ /dev/null @@ -1,520 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include - -#include "unittest/Unittest.h" -#include "network/protocols/utils.h" -#include "network/protocols/http/inner_parser.h" -#include "RawNetPacketReader.h" -#include "network/protocols/http/parser.h" -#include "observer/network/ProcessObserver.h" -#include "unittest/UnittestHelper.h" - -namespace logtail { - - -class ProtocolHttpUnittest : public ::testing::Test { -public: - std::string getHeaders(logtail::HTTPParser& http, std::string name) { - for (size_t i = 0; i < http.packet.common.headersNum; ++i) { - if (std::strncmp(http.packet.common.headers[i].name, name.c_str(), name.length()) == 0) { - return std::string(http.packet.common.headers[i].value, http.packet.common.headers[i].value_len); - } - } - return ""; - } - - // POST /a HTTP/1.1\r\n - // [Expert Info (Chat/Sequence): POST /a HTTP/1.1\r\n] - // Request Method: POST - // Request URI: /a - // Request Version: HTTP/1.1 - // Host: ocs-oneagent-server.alibaba.com\r\n - // Content-Type: application/json\r\n - // Connection: keep-alive\r\n - // Accept: */*\r\n - // User-Agent: CloudShell/5.0.42 (Mac OS X Version 12.2.1 (Build 21D62))\r\n - // Accept-Language: zh-Hans-CN;q=1\r\n - // Accept-Encoding: gzip, deflate\r\n - // Content-Length: 1475\r\n - // [Content length: 1475] - // \r\n - // [Full request URI: http://ocs-oneagent-server.alibaba.com/a] - // [HTTP request 3/24] - // [Prev request in frame: 2962] - // [Response in frame: 4580] - // [Next request in frame: 5847] - // File Data: 1475 bytes - void TestCommonRequest() { - const std::string hexString - = "504f5354202f6120485454502f312e310d0a486f73743a206f63732d6f6e656167656e742d7365727665722e616c69626162612e" - "636f6d0d0a436f6e74656e742d547970653a206170706c69636174696f6e2f6a736f6e0d0a436f6e6e656374696f6e3a206b6565" - "702d616c6976650d0a4163636570743a202a2f2a0d0a557365722d4167656e743a20436c6f75645368656c6c2f352e302e343220" - "284d6163204f5320582056657273696f6e2031322e322e3120284275696c6420323144363229290d0a4163636570742d4c616e67" - "756167653a207a682d48616e732d434e3b713d310d0a4163636570742d456e636f64696e673a20677a69702c206465666c617465" - "0d0a436f6e74656e742d4c656e6774683a20313437350d0a0d0a"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::HTTPParser http; - http.ParseRequest((const char*)data.data(), (size_t)data.size()); - APSARA_TEST_TRUE(http.status >= 0); - APSARA_TEST_TRUE(http.packet.msg.req.method == "POST"); - APSARA_TEST_TRUE(http.packet.msg.req.url == "/a"); - APSARA_TEST_TRUE(http.packet.common.version == 1); - APSARA_TEST_EQUAL(http.packet.common.headersNum, 8); - APSARA_TEST_EQUAL(getHeaders(http, "Host"), "ocs-oneagent-server.alibaba.com"); - APSARA_TEST_EQUAL(getHeaders(http, "Content-Type"), "application/json"); - APSARA_TEST_EQUAL(getHeaders(http, "Connection"), "keep-alive"); - APSARA_TEST_EQUAL(getHeaders(http, "Accept"), "*/*"); - APSARA_TEST_EQUAL(getHeaders(http, "User-Agent"), "CloudShell/5.0.42 (Mac OS X Version 12.2.1 (Build 21D62))"); - APSARA_TEST_EQUAL(getHeaders(http, "Accept-Language"), "zh-Hans-CN;q=1"); - APSARA_TEST_EQUAL(getHeaders(http, "Accept-Encoding"), "gzip, deflate"); - APSARA_TEST_EQUAL(getHeaders(http, "Content-Length"), "1475"); - } - - - void TestCommonRequest2() { - const std::string hexString - = "504f5354202f6c6f6773746f7265732f6c6f677461696c5f7374617475735f70726f66696c652f7368617264732f6c6220485454" - "502f312e310d0a486f73743a616c692d636e2d7368616e676861692d636f72702d736c732d61646d696e2e636e2d7368616e6768" - "61692d636f72702e736c732e616c6979756e63732e636f6d0d0a4163636570743a202a2f2a0d0a417574686f72697a6174696f6e" - "3a4c4f47203934746f337a3431387975706936696b61777171643337303a69544b46686c36736f6e6a6a715447306437356d5433" - "474d6438413d0d0a436f6e74656e742d4c656e6774683a313135340d0a436f6e74656e742d4d44353a3637463146384143333136" - "4137383238304342304131343037313937464344340d0a436f6e74656e742d547970653a6170706c69636174696f6e2f782d7072" - "6f746f6275660d0a446174653a5475652c203239204d617220323032322031313a32353a313620474d540d0a557365722d416765" - "6e743a616c692d6c6f672d6c6f677461696c0d0a782d6c6f672d61706976657273696f6e3a302e362e300d0a782d6c6f672d626f" - "647972617773697a653a313738300d0a782d6c6f672d636f6d7072657373747970653a6c7a340d0a782d6c6f672d6b657970726f" - "76696465723a6d64352d736861312d73616c740d0a782d6c6f672d7369676e61747572656d6574686f643a686d61632d73686131" - "0d0a4578706563743a203130302d636f6e74696e75650d0a0d0a"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::HTTPParser http; - http.ParseRequest((const char*)data.data(), (size_t)data.size()); - APSARA_TEST_TRUE(http.status >= 0); - APSARA_TEST_EQUAL(http.packet.msg.req.method.ToString(), "POST"); - APSARA_TEST_EQUAL(http.packet.msg.req.url.ToString(), "/logstores/logtail_status_profile/shards/lb"); - APSARA_TEST_EQUAL(http.packet.common.version, 1); - APSARA_TEST_EQUAL(http.packet.common.headersNum, 14); - - APSARA_TEST_EQUAL(getHeaders(http, "Date"), "Tue, 29 Mar 2022 11:25:16 GMT"); - APSARA_TEST_EQUAL(getHeaders(http, "Host"), "ali-cn-shanghai-corp-sls-admin.cn-shanghai-corp.sls.aliyuncs.com"); - APSARA_TEST_EQUAL(getHeaders(http, "Accept"), "*/*"); - APSARA_TEST_EQUAL(getHeaders(http, "Expect"), "100-continue"); - APSARA_TEST_EQUAL(getHeaders(http, "User-Agent"), "ali-log-logtail"); - APSARA_TEST_EQUAL(getHeaders(http, "Content-Type"), "application/x-protobuf"); - } - - - // HTTP/1.1 200 OK\r\n - // Server: Tengine\r\n - // Content-Length: 0\r\n - // Connection: close\r\n - // Access-Control-Allow-Origin: *\r\n - // Date: Sun, 19 Jun 2022 13:36:44 GMT\r\n - // x-log-append-meta: true\r\n - // x-log-time: 1655645804\r\n - // x-log-requestid: 62AF266CFF534E3CBFEB797C\r\n - // \r\n - // [HTTP response 2/2] - // [Time since request: 0.015786000 seconds] - // [Prev response in frame: 97700] - // [Request in frame: 97703] - // [Request URI: - // http://edr-project.cn-beijing.log.aliyuncs.com/logstores/bradar_performance_statistics_log/shards/lb] - void TestCommonResponse() { - const std::string hexString - = "485454502f312e3120323030204f4b0d0a5365727665723a2054656e67696e650d0a436f6e74656e742d4c656e6774683a20300d" - "0a436f6e6e656374696f6e3a20636c6f73650d0a4163636573732d436f6e74726f6c2d416c6c6f772d4f726967696e3a202a0d0a" - "446174653a2053756e2c203139204a756e20323032322031333a33363a343420474d540d0a782d6c6f672d617070656e642d6d65" - "74613a20747275650d0a782d6c6f672d74696d653a20313635353634353830340d0a782d6c6f672d7265717565737469643a2036" - "32414632363643464635333445334342464542373937430d0a0d0a"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::HTTPParser http; - http.ParseResp((const char*)data.data(), (size_t)data.size()); - APSARA_TEST_TRUE(http.status >= 0); - APSARA_TEST_TRUE(http.packet.common.version == 1); - APSARA_TEST_TRUE(http.packet.msg.resp.code == 200); - APSARA_TEST_TRUE(http.packet.msg.resp.msg == "OK"); - - APSARA_TEST_EQUAL(http.packet.common.headersNum, 8); - APSARA_TEST_EQUAL(getHeaders(http, "Server"), "Tengine"); - APSARA_TEST_EQUAL(getHeaders(http, "Content-Length"), "0"); - APSARA_TEST_EQUAL(getHeaders(http, "Connection"), "close"); - APSARA_TEST_EQUAL(getHeaders(http, "Access-Control-Allow-Origin"), "*"); - APSARA_TEST_EQUAL(getHeaders(http, "x-log-append-meta"), "true"); - APSARA_TEST_EQUAL(getHeaders(http, "x-log-time"), "1655645804"); - APSARA_TEST_EQUAL(getHeaders(http, "x-log-requestid"), "62AF266CFF534E3CBFEB797C"); - } - - void TestHTTPPacketReaderOrder() { - std::vector rawHexs{rawHex1, rawHex2}; - RawNetPacketReader reader("30.43.120.83", false, ProtocolType_HTTP, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - - std::vector allData; - mObserver->FlushOutMetrics(allData); - - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "version", "1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_domain", "baidu.com")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_type", "GET")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_resource", "/")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "resp_code", "200")); - } - - - void TestHTTPPacketReaderUnorder() { - std::vector rawHexs{rawHex1, rawHex2}; - RawNetPacketReader reader("30.43.120.83", false, ProtocolType_HTTP, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (int i = packets.size() - 1; i >= 0; --i) { - mObserver->OnPacketEvent(&packets[i].at(0), packets[i].size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "version", "1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_domain", "baidu.com")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_type", "GET")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_resource", "/")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "resp_code", "200")); - } - - void TestMoreContentResponse() { - std::vector rawHexs{ - // req - "00000000040088665a406acf08004500015100004000400640941ef065a6cb77a905f5430050f351495f1109db5d50181000343500" - "00504f5354202f6120485454502f312e310d0a486f73743a206f63732d6f6e656167656e742d7365727665722e616c69626162612e" - "636f6d0d0a436f6e74656e742d547970653a206170706c69636174696f6e2f6a736f6e0d0a436f6e6e656374696f6e3a206b656570" - "2d616c6976650d0a4163636570743a202a2f2a0d0a557365722d4167656e743a20436c6f75645368656c6c2f382e312e343220284d" - "6163204f5320582056657273696f6e2031322e3420284275696c6420323146373929290d0a4163636570742d4c616e67756167653a" - "20656e2d434e3b713d312c207a682d48616e732d434e3b713d302e390d0a4163636570742d456e636f64696e673a20677a69702c20" - "6465666c6174650d0a436f6e74656e742d4c656e6774683a20313536380d0a0d0a", - "00000000040088665a406acf0800450005c80000400040063c1d1ef065a6cb77a905f5430050f3514a881109db5d501010002eb900" - "007b2276657273696f6e223a332c2276616c7565223a7b2263736964223a2239433130463434362d313138462d343745342d413530" - "422d363131443932464134373936222c226b6579223a2250576e775a2b5946435070492b73377238412b4e49457077494154413758" - "713244363568376a67564c32396c384b4b5858666e49466b64445c2f4e7776447674544e67707048357a384e544370365963314545" - "45706467314758534c36415674484f772b763674684965377972454f6e7a6a6c6f6865434f64305247734f39626a38684a4a2b5858" - "4d4f35544a526f327a4b68413079684a777058315a2b466b6b53343661646558473730303d222c2276616c7565223a226575463576" - "4c425c2f4669334c67346967304b627544387578343554517164464d674b62357249426b3245744e6b343853416e594a636264374a" - "6a6469393548714f38365a6d5a494e65573074365268714f30656e554a70593242584738542b32657578787635524a37646a715a54" - "674269684c4e30624a70314b4a732b653751384a6b497a53593334524670556278384c6f5579745758437765703061576f45787173" - "6d565665656f2b6749632b7a334b543341766e5564314a7a4c5a334a375772596a6f57675456774b495644487456747a707a393333" - "763058594d52774f3673684e51657749634839684c7462507078534e575537483130466b71783269476f48475733644a4a6d527168" - "703161724d477974564961442b2b70666762387272584a486c48562b68396e64525c2f6e7349666d2b566c38714d72726338375459" - "7338714e79584a453333325334304d595c2f4c4e50647967336e576a67594e2b4432386f5c2f4b335a3032366f574a7a5c2f74457a" - "2b716558386b6f6b45706449587861526c754638706270486b41496268565435356d5a735c2f6d48444547526c786a31665678636a" - "2b4f72577a535432655554734d7731795849586d504e4172333170417a4e47594a536445744962746d72785879746569654c39732b" - "734842424c4255416a7241334e6f4a336c62376f626f696250526a51674c4d49615a4831687251654e497835476379583565364b67" - "32717a304f5777654a31694f6241786971725c2f776441434c5a522b444151315c2f3871537a694746727172724d6c4a785472542b" - "5a735045345442714f364e6d7a6d6b3350514e716e486c4d5736556d7951467275476265304b3558394e71417550706b7459427065" - "594b38424c6f317463556c38707253315765306534586a41594b74374f6931446c53425568796c704273487a2b48645a586e574766" - "4c494648434f615477762b45644938396675435163587762326157446c31474939515c2f44552b49313346457477706c3379716457" - "6c476f7850315a3658345751366b4478786b48576d42696e4e73596d6a367665366c5533437741576d676f6d534f70426f5c2f3866" - "57597a326d4f773939677155327a49526a787336352b53456e486b48736e4e5c2f714d57563341396e4c7974356835375c2f675732" - "664a4a30474a4d39335661636f756d6c556a7448704c4d6c724f3173346a656b3942476e635346757a6e5a4e3166426d6a72757255" - "4564637876566b765164746e2b324f417a684f6b5c2f57426a59614d7238373732363974624e6367615156374251376c4f54794774" - "4a4e55495334386675723158547535707938694a756d6d674655684f4c747876585662646465344c545c2f6e357263763738724c31" - "426d5274762b4347435930354859584a474468742b30514b6153465339415c2f36745c2f4d742b624145756a4f596f6e3442563741" - "79796f47435a574d744b566f414876614a6b4469636e4773422b5369566e744a6239646978436b4f706c5469487139466e36705841" - "456475516b6a6c695c2f6f4548396732426d4675695c2f665a4363345978687065395c2f4e702b5276587a474d5c2f6847734e596e" - "4a374e32687352683771", - "00000000040088665a406acf0800450000a8000040004006413d1ef065a6cb77a905f5430050f35150281109db5d5018100013a700" - "006f67735a6f355c2f6f34734851554c56776b4568674231554e5c2f436641645039306a682b626e3139465a70352b6e327a396c77" - "445559654e765a55666f684156414e5a6a433955446d30654261513d3d222c227273615f6b65795f76657273696f6e223a2233222c" - "226165735f6b65795f76657273696f6e223a2233227d7d", - // resp - "88665a406acf00000000040008004500014cf4b74000340657e1cb77a9051ef065a60050f5431109db5df35150a85018001e877700" - "00485454502f312e3120323030200d0a446174653a204d6f6e2c203230204a756e20323032322030383a32363a353720474d540d0a" - "436f6e74656e742d547970653a206170706c69636174696f6e2f6a736f6e3b636861727365743d5554462d380d0a436f6e74656e74" - "2d4c656e6774683a2035330d0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a582d4170706c69636174696f6e2d436f" - "6e746578743a206f63732d6f6e656167656e742d7365727665723a373030310d0a5365727665723a2054656e67696e652f41736572" - "7665720d0a4561676c654579652d547261636549643a20323132626463353131363535373133363137353439333436356531653465" - "0d0a54696d696e672d416c6c6f772d4f726967696e3a202a0d0a0d0a", - "88665a406acf00000000040008004500005df4b84000340658cfcb77a9051ef065a60050f5431109dc81f35150a85018001e13e600" - "007b22656e63727970746564223a747275652c2276616c7565223a22584f5865636476656c4c4471376d36422b57563149673d3d22" - "7d", - }; - - - std::vector expectRes = { - ParseResult_OK, - ParseResult_Partial, - ParseResult_Partial, - ParseResult_OK, - ParseResult_Partial, - }; - - RawNetPacketReader reader("30.240.101.166", false, ProtocolType_HTTP, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - auto aggregator = new HTTPProtocolEventAggregator(200, 1001); - static HTTPProtocolParser* parser = nullptr; - for (size_t i = 0; i < packets.size(); ++i) { - PacketEventHeader* header = static_cast((void*)&packets[i].at(0)); - PacketEventData* data - = reinterpret_cast((char*)&packets[i].at(0) + sizeof(PacketEventHeader)); - if (parser == nullptr) { - parser = HTTPProtocolParser::Create(aggregator, header); - } - auto parserRes - = parser->OnPacket(data->PktType, data->MsgType, header, data->Buffer, data->BufferLen, data->RealLen); - APSARA_TEST_EQUAL(parserRes, expectRes[i]); - } - std::vector allData; - google::protobuf::RepeatedPtrField tags; - aggregator->FlushLogs(allData, {}, tags, 1); - APSARA_TEST_TRUE(allData.size() == 1); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "version", "1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "host", "ocs-oneagent-server.alibaba.com")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "method", "POST")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "url", "/a")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "resp_code", "200")); - - for (size_t i = 0; i < packets.size(); ++i) { - PacketEventHeader* header = static_cast((void*)&packets[i].at(0)); - PacketEventData* data - = reinterpret_cast((char*)&packets[i].at(0) + sizeof(PacketEventHeader)); - if (parser == nullptr) { - parser = HTTPProtocolParser::Create(aggregator, header); - } - auto parserRes - = parser->OnPacket(data->PktType, data->MsgType, header, data->Buffer, data->BufferLen, data->RealLen); - APSARA_TEST_EQUAL(parserRes, expectRes[i]); - } - aggregator->FlushLogs(allData, {}, tags, 1); - APSARA_TEST_TRUE(allData.size() == 2); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "version", "1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "host", "ocs-oneagent-server.alibaba.com")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "method", "POST")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "url", "/a")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "resp_code", "200")); - } - - - void TestChunkedResponse() { - std::vector rawHexs{ - - // req - "00000000040088665a406acf08004500028e00004000400630701ef065a628705af4fa330050eae1b69266ee936780180804386900" - "000101080ac2fca775a2f7b3d5474554202f4368756e6b656420485454502f312e310d0a486f73743a20616e676c6573686172702e" - "617a75726577656273697465732e6e65740d0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a43616368652d436f6e74" - "726f6c3a206d61782d6167653d300d0a557067726164652d496e7365637572652d52657175657374733a20310d0a557365722d4167" - "656e743a204d6f7a696c6c612f352e3020284d6163696e746f73683b20496e74656c204d6163204f5320582031305f31355f372920" - "4170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3130322e302e30" - "2e30205361666172692f3533372e33360d0a4163636570743a20746578742f68746d6c2c6170706c69636174696f6e2f7868746d6c" - "2b786d6c2c6170706c69636174696f6e2f786d6c3b713d302e392c696d6167652f617669662c696d6167652f776562702c696d6167" - "652f61706e672c2a2f2a3b713d302e382c6170706c69636174696f6e2f7369676e65642d65786368616e67653b763d62333b713d30" - "2e390d0a526566657265723a2068747470733a2f2f7777772e676f6f676c652e636f6d2f0d0a4163636570742d456e636f64696e67" - "3a20677a69702c206465666c6174650d0a4163636570742d4c616e67756167653a207a682d434e2c7a683b713d302e390d0a436f6f" - "6b69653a20415252416666696e6974793d333837346637363566366132663137363766376339336435353237373764376461636636" - "643365333232376132363066356530623131303935313036383032350d0a0d0a", - // resp - "88665a406acf0000000004000800451402ba21d440006406ea5b28705af41ef065a60050fa3366ee9367eae1b8ec80180405da9a00" - "000101080aa2f7b556c2fca775485454502f312e3120323030204f4b0d0a436f6e74656e742d547970653a20746578742f68746d6c" - "0d0a446174653a204d6f6e2c203230204a756e20323032322030343a31373a333820474d540d0a5365727665723a204d6963726f73" - "6f66742d4949532f31302e300d0a43616368652d436f6e74726f6c3a20707269766174650d0a436f6e74656e742d456e636f64696e" - "673a20677a69700d0a5365742d436f6f6b69653a20415252416666696e6974793d3338373466373635663661326631373637663763" - "39336435353237373764376461636636643365333232376132363066356530623131303935313036383032353b506174683d2f3b48" - "7474704f6e6c793b446f6d61696e3d616e676c6573686172702e617a75726577656273697465732e6e65740d0a5472616e73666572" - "2d456e636f64696e673a206368756e6b65640d0a566172793a204163636570742d456e636f64696e670d0a582d4173704e65744d76" - "632d56657273696f6e3a20352e300d0a582d4173704e65742d56657273696f6e3a20342e302e33303331390d0a582d506f77657265" - "642d42793a204153502e4e45540d0a0d0a64340d0a1f8b0800000000000400edbd07601c499625262f6dca7b7f4af54ad7e074a108" - "80601324d8904010ecc188cde692ec1d69472329ab2a81ca6556655d661640cced9dbcf7de7befbdf7de7befbdf7ba3b9d4e27f7df" - "ff3f5c6664016cf6ce4adac99e2180aac81f3f7e7c1f3f221effae4fbf3c79f3fbbc3c4de7eda23cfa8d93c7f89996d9f2e2b37cc9" - "7fe7d90c3f17799ba5d379563779fbd9c7ebf67cfbe0637cde166d991f9dccd7cbb7f92c6deb6cd99ce7759a2fa7d5ac585ea46dde" - "b48fef4a2b6a7ed7c09b54b3eba3c7f3dd1b5f9def0d0a", - "88665a406acf00000000040008004514005e21d540006406ecb628705af41ef065a60050fa3366ee95edeae1b8ec80180405e53f00" - "000101080aa2f7b5c3c2fca77532340d0a1e3d9edf3f7a332f9a94fe0724e4853a6f56d5b2c9d3ecbca5d7767776d24533a617ee1f" - "0d0a", - "88665a406acf00000000040008004514008521d640006406ec8e28705af41ef065a60050fa3366ee9617eae1b8ec801804051ac200" - "000101080aa2f7b9bdc2fca8fd33650d0addee85b4c9a7d572364edfcc73fabdbea40f9b79b52e67e9b26ad3695951db16dfb5759e" - "2dd2497e5ed5f476590a48824d7f36f9b24ddb0afd9405fd2e180d0a380d0a3cbe2b03bc0b7a1e0d0a", - "88665a406acf00000000040008004514004321d740006406eccf28705af41ef065a60050fa3366ee9668eae1b8ec80180405565900" - "000101080aa2f7b9bdc2fca8fd610d0afd3f7a9bfb5d670100000d0a", - "88665a406acf00000000040008004514003921d840006406ecd828705af41ef065a60050fa3366ee9677eae1b8ec80180405c22e00" - "000101080aa2f7b9bdc2fca8fd300d0a0d0a", - }; - - std::vector expectRes = { - ParseResult_OK, - ParseResult_OK, - ParseResult_Partial, - ParseResult_Partial, - ParseResult_Partial, - ParseResult_Partial, - }; - RawNetPacketReader reader("30.240.101.166", false, ProtocolType_HTTP, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - auto aggregator = new HTTPProtocolEventAggregator(200, 1001); - static HTTPProtocolParser* parser = nullptr; - for (size_t i = 0; i < packets.size(); ++i) { - PacketEventHeader* header = static_cast((void*)&packets[i].at(0)); - PacketEventData* data - = reinterpret_cast((char*)&packets[i].at(0) + sizeof(PacketEventHeader)); - if (parser == nullptr) { - parser = HTTPProtocolParser::Create(aggregator, header); - } - auto parserRes - = parser->OnPacket(data->PktType, data->MsgType, header, data->Buffer, data->BufferLen, data->RealLen); - APSARA_TEST_EQUAL(parserRes, expectRes[i]); - } - std::vector allData; - ::google::protobuf::RepeatedPtrField tagsContent; - google::protobuf::RepeatedPtrField tags; - - aggregator->FlushLogs(allData, {}, tags, 1); - APSARA_TEST_TRUE(allData.size() == 1); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_version", "1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_domain", "anglesharp.azurewebsites.net")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_type", "GET")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "req_resource", "/Chunked")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "resp_code", "200")); - - for (size_t i = 0; i < packets.size(); ++i) { - PacketEventHeader* header = static_cast((void*)&packets[i].at(0)); - PacketEventData* data - = reinterpret_cast((char*)&packets[i].at(0) + sizeof(PacketEventHeader)); - auto parserRes - = parser->OnPacket(data->PktType, data->MsgType, header, data->Buffer, data->BufferLen, data->RealLen); - APSARA_TEST_EQUAL(parserRes, expectRes[i]); - } - aggregator->FlushLogs(allData, {}, tags, 1); - APSARA_TEST_TRUE(allData.size() == 2); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "req_version", "1")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "req_domain", "anglesharp.azurewebsites.net")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "req_type", "GET")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "req_resource", "/Chunked")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[1], "resp_code", "200")); - } - - - void TestHTTPParserGC() { - INT64_FLAG(sls_observer_network_process_timeout) = 18446744073; - INT64_FLAG(sls_observer_network_connection_timeout) = 18446744073; - INT64_FLAG(sls_observer_network_process_no_connection_timeout) = 0; - BOOL_FLAG(sls_observer_network_protocol_stat) = true; - - NetworkStatistic* networkStatistic = NetworkStatistic::GetInstance(); - ProtocolDebugStatistic* protocolDebugStatistic = ProtocolDebugStatistic::GetInstance(); - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - networkStatistic->Clear(); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - - { - std::vector rawHexs{rawHex1}; - RawNetPacketReader reader("30.43.120.83", false, ProtocolType_HTTP, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), 0); - - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds() - 60000000000000ULL); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 1); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 0); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mHTTPConnectionNum, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mHTTPConnectionCachedSize, 1); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 2); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mHTTPConnectionNum, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mHTTPConnectionCachedSize, 0); - } - networkStatistic->Clear(); - { - std::vector rawHexs{rawHex2}; - RawNetPacketReader reader("30.43.120.83", false, ProtocolType_HTTP, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), 0); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds() - 60000000000000ULL); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 1); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 0); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mHTTPConnectionNum, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mHTTPConnectionCachedSize, 1); - - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 2); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mHTTPConnectionNum, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mHTTPConnectionCachedSize, 0); - } - } - - - NetworkObserver* mObserver = NetworkObserver::GetInstance(); - const std::string rawHex1 = "00749c945d39a07817a0852e080045000071000040004006a0581e2b7853dcb526fbcd4700506b7401eca5" - "91a5ce5018100037ad0000474554202f20485454502f312e310d0a486f73743a2062616964752e636f6d0d" - "0a557365722d4167656e743a206375726c2f372e37372e300d0a4163636570743a202a2f2a0d0a0d0a"; - const std::string rawHex2 - = "a07817a0852e00749c945d390800450001598c7240002a0628fedcb526fb1e2b78530050cd47a591a5ce6b74023550180304c2650000" - "485454502f312e3120323030204f4b0d0a446174653a205468752c203237204a616e20323032322030393a34363a313320474d540d0a" - "5365727665723a204170616368650d0a4c6173742d4d6f6469666965643a205475652c203132204a616e20323031302031333a34383a" - "303020474d540d0a455461673a202235312d34376366376536656538343030220d0a4163636570742d52616e6765733a206279746573" - "0d0a436f6e74656e742d4c656e6774683a2038310d0a43616368652d436f6e74726f6c3a206d61782d6167653d38363430300d0a4578" - "70697265733a204672692c203238204a616e20323032322030393a34363a313320474d540d0a436f6e6e656374696f6e3a204b656570" - "2d416c6976650d0a436f6e74656e742d547970653a20746578742f68746d6c0d0a0d0a"; -}; - -APSARA_UNIT_TEST_CASE(ProtocolHttpUnittest, TestCommonRequest, 0); -APSARA_UNIT_TEST_CASE(ProtocolHttpUnittest, TestCommonResponse, 0); -APSARA_UNIT_TEST_CASE(ProtocolHttpUnittest, TestHTTPPacketReaderOrder, 0); -APSARA_UNIT_TEST_CASE(ProtocolHttpUnittest, TestHTTPPacketReaderUnorder, 0); -APSARA_UNIT_TEST_CASE(ProtocolHttpUnittest, TestHTTPParserGC, 0); -APSARA_UNIT_TEST_CASE(ProtocolHttpUnittest, TestCommonRequest2, 0); -// TODO : currently only accept the data starts with HTTP or special METHOD , such as GET, PUT and etc. -// APSARA_UNIT_TEST_CASE(ProtocolHttpUnittest, TestChunkedResponse, 0); -// APSARA_UNIT_TEST_CASE(ProtocolHttpUnittest, TestMoreContentResponse, 0); - -} // namespace logtail - - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/core/unittest/observer/ProtocolInferUnittest.cpp b/core/unittest/observer/ProtocolInferUnittest.cpp deleted file mode 100644 index 180c72a23a..0000000000 --- a/core/unittest/observer/ProtocolInferUnittest.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "unittest/Unittest.h" -#include "unittest/UnittestHelper.h" -#include "observer/network/protocols/infer.h" - - -namespace logtail { - -class ProtocolUtilUnittest : public ::testing::Test { -public: - void TestInferPgSql() { - // whole request - char s[] = "\x42\x00\x00\x00\x0f\x00\x53\x5f\x31\x00\x00\x00\x00\x00\x00\x00\x45\x00\x00\x00\x09\x00\x00\x00" - "\x00\x01\x53\x00\x00\x00\x04"; - APSARA_TEST_TRUE(is_pgsql_message(s, 31, 0, 0)); - char passwd[] = "\x70\x00\x00\x00\x28\x6d\x64\x35\x64\x39\x65\x63\x38\x62\x39\x34" - "\x33\x30\x38\x65\x39\x62\x34\x33\x32\x39\x31\x30\x33\x62\x39\x37" - "\x32\x37\x37\x66\x61\x64\x65\x32\x00"; - APSARA_TEST_TRUE(is_pgsql_message(passwd, 41, 0, 0)); - - // whole response - char resp[] = "\x32\x00\x00\x00\x04\x49\x00\x00\x00\x04\x5a\x00\x00\x00\x05\x49"; - APSARA_TEST_TRUE(is_pgsql_message(resp, 16, 0, 0)); - char auth[] = "\x52\x00\x00\x00\x0c\x00\x00\x00\x05\x13\xbd\x41\x85"; - APSARA_TEST_TRUE(is_pgsql_message(auth, 13, 0, 0)); - - char start_up[] = "\x00\x00\x00\x61\x00\x03\x00\x00\x64\x61\x74\x65\x73\x74\x79\x6c" - "\x65\x00\x49\x53\x4f\x2c\x20\x4d\x44\x59\x00\x63\x6c\x69\x65\x6e" - "\x74\x5f\x65\x6e\x63\x6f\x64\x69\x6e\x67\x00\x55\x54\x46\x38\x00" - "\x65\x78\x74\x72\x61\x5f\x66\x6c\x6f\x61\x74\x5f\x64\x69\x67\x69" - "\x74\x73\x00\x32\x00\x75\x73\x65\x72\x00\x6c\x6a\x70\x00\x64\x61" - "\x74\x61\x62\x61\x73\x65\x00\x70\x6f\x73\x74\x67\x72\x65\x73\x00\x00"; - APSARA_TEST_TRUE(is_pgsql_message(start_up, 97, 0, 0)); - } -}; - -APSARA_UNIT_TEST_CASE(ProtocolUtilUnittest, TestInferPgSql, 0); -} // namespace logtail - - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/core/unittest/observer/ProtocolMySqlUnittest.cpp b/core/unittest/observer/ProtocolMySqlUnittest.cpp deleted file mode 100644 index 88599c9596..0000000000 --- a/core/unittest/observer/ProtocolMySqlUnittest.cpp +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include - -#include "unittest/Unittest.h" -#include "network/protocols/utils.h" -#include "RawNetPacketReader.h" -#include "unittest/UnittestHelper.h" -#include "network/protocols/mysql/inner_parser.h" -#include "network/protocols/mysql/parser.h" -#include "observer/network/ProcessObserver.h" - -namespace logtail { - -class ProtocolMySqlUnittest : public ::testing::Test { -public: - // Packet Length: 74 - // Packet Number: 0 - // Server Greeting - // Protocol: 10 - // Version: 5.7.37 - // Thread ID: 16 - // Salt: !I\030\004\035u#' - // Server Capabilities: 0xffff - // Server Language: latin1 COLLATE latin1_swedish_ci (8) - // Server Status: 0x0002 - // Extended Server Capabilities: 0xc1ff - // Authentication Plugin Length: 21 - // Unused: 00000000000000000000 - // Salt: \vgbuc@\036\001\005T^} - // Authentication Plugin: mysql_native_password - void TestGreetingRequest() { - const std::string hexString = "4a0000000a352e372e33370010000000214918041d75232700ffff080200ffc11500000000000000" - "0000000b67627563401e0105545e7d006d7973716c5f6e61746976655f70617373776f726400"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::MySQLParser mysql((const char*)data.data(), (size_t)data.size()); - mysql.parse(); - APSARA_TEST_EQUAL(mysql.mysqlPacketType, MySQLPacketTypeServerGreeting); - APSARA_TEST_TRUE(mysql.mysqlPacketQuery.sql == ""); - APSARA_TEST_TRUE(mysql.mysqlPacketResponse.ok == false); - APSARA_TEST_TRUE(mysql.OK()); - } - - // Packet Length: 32 - // Packet Number: 1 - // Login Request - // Client Capabilities: 0xaa07 - // Extended Client Capabilities: 0x013e - // MAX Packet: 16777215 - // Charset: utf8 COLLATE utf8_general_ci (33) - // Unused: 0000000000000000000000000000000000000000000000 - // Username: - void TestLoginRequest() { - const std::string hexString = "2000000107aa3e01ffffff00210000000000000000000000000000000000000000000000"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::MySQLParser mysql((const char*)data.data(), (size_t)data.size()); - mysql.parse(); - APSARA_TEST_EQUAL(mysql.mysqlPacketType, MySQLPacketTypeClientLogin); - APSARA_TEST_TRUE(mysql.mysqlPacketQuery.sql == ""); - APSARA_TEST_TRUE(mysql.mysqlPacketResponse.ok == false); - APSARA_TEST_TRUE(mysql.OK()); - } - - - void TestQueryRequest() { - const std::string hexString = "210000000373656c65637420404076657273696f6e5f636f6d6d656e74206c696d69742031"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::MySQLParser mysql((const char*)data.data(), (size_t)data.size()); - mysql.parse(); - APSARA_TEST_EQUAL(mysql.mysqlPacketType, MySQLPacketTypeCollectStatement); - APSARA_TEST_TRUE(mysql.mysqlPacketQuery.sql == "select"); - APSARA_TEST_TRUE(mysql.mysqlPacketResponse.ok == false); - APSARA_TEST_TRUE(mysql.OK()); - } - - void TestOkResponse() { - const std::string hexString = "0700000100010102000000"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::MySQLParser mysql((const char*)data.data(), (size_t)data.size()); - mysql.parse(); - APSARA_TEST_EQUAL(mysql.mysqlPacketType, MySQLPacketTypeResponse); - APSARA_TEST_TRUE(mysql.mysqlPacketQuery.sql == ""); - APSARA_TEST_TRUE(mysql.mysqlPacketResponse.ok == true); - APSARA_TEST_TRUE(mysql.OK()); - } - - void TestErrorResponse() { - const std::string hexString - = "a1000001ff2804233432303030596f75206861766520616e206572726f7220696e20796f75722053514c2073796e7461783b2063" - "6865636b20746865206d616e75616c207468617420636f72726573706f6e647320746f20796f7572204d7953514c207365727665" - "722076657273696f6e20666f72207468652072696768742073796e74617820746f20757365206e65617220272261616122292720" - "6174206c696e652031"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::MySQLParser mysql((const char*)data.data(), (size_t)data.size()); - mysql.parse(); - APSARA_TEST_EQUAL(mysql.mysqlPacketType, MySQLPacketTypeResponse); - APSARA_TEST_TRUE(mysql.mysqlPacketQuery.sql == ""); - APSARA_TEST_TRUE(mysql.mysqlPacketResponse.ok == false); - APSARA_TEST_TRUE(mysql.OK()); - } - - void Test5_6ResultSet() { - const std::string hexString - = "01000001022a00000203646566066d79746573740568656c6c6f0568656c6c6f0263310263310c080020000000fd00000000002a" - "00000303646566066d79746573740568656c6c6f0568656c6c6f0263320263320c080020000000fd000000000005000004fe0000" - "22000a000005046161613104616161320a0000060462626231046262623205000007fe00002200"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::MySQLParser mysql((const char*)data.data(), (size_t)data.size()); - mysql.parse(); - APSARA_TEST_EQUAL(mysql.mysqlPacketType, MySQLPacketTypeResponse); - APSARA_TEST_TRUE(mysql.mysqlPacketQuery.sql == ""); - APSARA_TEST_TRUE(mysql.mysqlPacketResponse.ok == true); - APSARA_TEST_TRUE(mysql.OK()); - } - - void Test8_xResultSet() { - const std::string hexString - = "010000010a2d000002036465660765746c5f6e673206776f726b657206776f726b65720269640269640c3f001400000008034200" - "000039000003036465660765746c5f6e673206776f726b657206776f726b657208486f73746e616d6508486f73746e616d650cff" - "0000020000fd044000000037000004036465660765746c5f6e673206776f726b657206776f726b65720741646472657373074164" - "64726573730cff0000010000fd00000000003f000005036465660765746c5f6e673206776f726b657206776f726b65720b536f75" - "72636554797065730b536f7572636554797065730c3f00fffffffff590000000003b000006036465660765746c5f6e673206776f" - "726b657206776f726b65720953696e6b54797065730953696e6b54797065730c3f00fffffffff590000000003500000703646566" - "0765746c5f6e673206776f726b657206776f726b657206537461747573065374617475730c3f00080000000100000000003b0000" - "08036465660765746c5f6e673206776f726b657206776f726b65720947726f75704e616d650947726f75704e616d650cff008000" - "0000fd000000000031000009036465660765746c5f6e673206776f726b657206776f726b6572045461677304546167730c3f00ff" - "fffffff590000000003d00000a036465660765746c5f6e673206776f726b657206776f726b65720a43726561746554696d650a43" - "726561746554696d650c3f00140000000800000000004500000b036465660765746c5f6e673206776f726b657206776f726b6572" - "0e4c6173744d6f6469667954696d650e4c6173744d6f6469667954696d650c3f00140000000800000000000500000cfe00002200" - "0500000dfe00002200"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::MySQLParser mysql((const char*)data.data(), (size_t)data.size()); - mysql.parse(); - APSARA_TEST_EQUAL(mysql.mysqlPacketType, MySQLPacketTypeResponse); - APSARA_TEST_TRUE(mysql.mysqlPacketQuery.sql == ""); - APSARA_TEST_TRUE(mysql.mysqlPacketResponse.ok == true); - APSARA_TEST_TRUE(mysql.OK()); - } - - - void TestMySQLPacketReader() { - std::vector rawHexs{rawHex1, rawHex2}; - RawNetPacketReader reader("30.43.121.61", false, ProtocolType_MySQL, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - - std::vector allData; - mObserver->FlushOutMetrics(allData); - - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "query", "select")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "status", "1")); - } - - - void TestMySQLPacketReaderUnorder() { - std::vector rawHexs{rawHex1, rawHex2}; - RawNetPacketReader reader("30.43.121.61", false, ProtocolType_MySQL, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (int i = packets.size() - 1; i >= 0; --i) { - mObserver->OnPacketEvent(&packets[i].at(0), packets[i].size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "query", "select")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "status", "1")); - } - - // 0100000001 - void TestMySQLQuitPacket() { - // mysql quit command - const std::string pkt("0100000001"); - std::vector data; - logtail::hexstring_to_bin(pkt, data); - - logtail::MySQLParser mysql((const char*)data.data(), (size_t)data.size()); - mysql.parse(); - APSARA_TEST_EQUAL(mysql.mysqlPacketType, MySQLPacketNoResponseStatement); - } - - - void TestMysqlUnknownPacket() { - const std::string pkt("0700000100000022080000"); - std::vector data; - logtail::hexstring_to_bin(pkt, data); - logtail::MySQLParser mysql((const char*)data.data(), (size_t)data.size()); - mysql.parse(); - } - - void TestMysqlParserGC() { - INT64_FLAG(sls_observer_network_process_timeout) = 18446744073; - INT64_FLAG(sls_observer_network_connection_timeout) = 18446744073; - INT64_FLAG(sls_observer_network_process_no_connection_timeout) = 0; - BOOL_FLAG(sls_observer_network_protocol_stat) = true; - NetworkStatistic* networkStatistic = NetworkStatistic::GetInstance(); - ProtocolDebugStatistic* protocolDebugStatistic = ProtocolDebugStatistic::GetInstance(); - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - NetworkStatistic::Clear(); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - - { - std::vector rawHexs{rawHex1}; - RawNetPacketReader reader("30.43.121.61", false, ProtocolType_MySQL, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), 0); - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds() - 60000000000000ULL); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 1); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 0); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mMySQLConnectionNum, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mMySQLConnectionCachedSize, 1); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 2); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mMySQLConnectionNum, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mMySQLConnectionCachedSize, 0); - } - NetworkStatistic::Clear(); - { - std::vector rawHexs{rawHex2}; - RawNetPacketReader reader("30.43.121.61", false, ProtocolType_MySQL, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), 0); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds() - 60000000000000ULL); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 1); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 0); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mMySQLConnectionNum, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mMySQLConnectionCachedSize, 1); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 2); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mMySQLConnectionNum, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mMySQLConnectionCachedSize, 0); - } - } - - void TestMysqlCache() { MysqlCache cache(nullptr); } - - - NetworkObserver* mObserver = NetworkObserver::GetInstance(); - const std::string rawHex1 - = "00749c945d39a07817a0852e08004500004c00004000400637031e2b793d0b9f60a2cb7b0cea933e3190a670a97080180801920c0000" - "0101080a08d1f5fd7fc82492140000000373656c656374202a2066726f6d2068656c6c6f"; - const std::string rawHex2 - = "a07817a0852e00749c945d390800450000c330164000360610760b9f60a21e2b793d0ceacb7ba670a970933e31a880180039dc630000" - "0101080a7fc848ca08d1f5fd01000001022a00000203646566066d79746573740568656c6c6f0568656c6c6f0263310263310c080020" - "000000fd00000000002a00000303646566066d79746573740568656c6c6f0568656c6c6f0263320263320c080020000000fd00000000" - "0005000004fe000022000a000005046161613104616161320a0000060462626231046262623205000007fe00002200"; -}; - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, TestGreetingRequest, 0); - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, TestLoginRequest, 0); - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, TestQueryRequest, 0); - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, TestOkResponse, 0); - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, TestErrorResponse, 0); - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, Test5_6ResultSet, 0); - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, Test8_xResultSet, 0); - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, TestMySQLPacketReader, 0); - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, TestMySQLPacketReaderUnorder, 0); - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, TestMySQLQuitPacket, 0); - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, TestMysqlParserGC, 0); - -APSARA_UNIT_TEST_CASE(ProtocolMySqlUnittest, TestMysqlCache, 0); -} // namespace logtail - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/core/unittest/observer/ProtocolPgsqlUnittest.cpp b/core/unittest/observer/ProtocolPgsqlUnittest.cpp deleted file mode 100644 index f3cd3b476d..0000000000 --- a/core/unittest/observer/ProtocolPgsqlUnittest.cpp +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include - -#include "unittest/Unittest.h" -#include "network/protocols/utils.h" -#include "network/protocols/pgsql/inner_parser.h" -#include "RawNetPacketReader.h" -#include "unittest/UnittestHelper.h" -#include "network/protocols/pgsql/parser.h" -#include "observer/network/ProcessObserver.h" - -namespace logtail { - - -class ProtocolPgSqlUnittest : public ::testing::Test { -public: - void TestSimpleQueryRequest() { - const char* data = "Q\000\000\000\033select * from account;\000"; - logtail::PgSQLParser pgsql(data, 28); - pgsql.parse(MessageType::MessageType_Request); - APSARA_TEST_TRUE(pgsql.type == PgSQLPacketType::Query); - APSARA_TEST_TRUE(pgsql.query.sql == "select"); - APSARA_TEST_TRUE(pgsql.OK()); - } - - void TestExtendQueryRequest() { - const std::string hexString = "50000000280053484f57205452414e53414354494f4e2049534f4c4154494f4e204c4556454c0000" - "00420000000c000000000000000044000000065000450000000900000000005300000004"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::PgSQLParser pgsql((const char*)data.data(), (size_t)data.size()); - pgsql.parse(MessageType::MessageType_Request); - APSARA_TEST_TRUE(pgsql.type == PgSQLPacketType::Query); - APSARA_TEST_TRUE(pgsql.query.sql == "SHOW TRANSACTION ISOLATION LEVEL"); - APSARA_TEST_TRUE(pgsql.OK()); - } - - void TestSimpleQueryResponse() { - const std::string hexString - = "540000006e000475696400000040220001000000170004ffffffff0000757365726e616d650000004022000200000413ffff0000" - "006800006465706172746e616d650000004022000300000413ffff000001f8000063726561746564000000402200040000043a00" - "04ffffffff0000440000003c0004000000033331360000000d617374617869657570646174650000000ce7a094e58f91e983a8e9" - "97a80000000a323031322d31322d3039430000000d53454c4543542031005a0000000549"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::PgSQLParser pgsql((const char*)data.data(), (size_t)data.size()); - pgsql.parse(MessageType::MessageType_Response); - APSARA_TEST_TRUE(pgsql.type == PgSQLPacketType::Response); - APSARA_TEST_TRUE(pgsql.resp.ok); - APSARA_TEST_TRUE(pgsql.OK()); - } - - void TestExtendQueryResponse() { - const std::string hexString - = "31000000043200000004540000002e00017472616e73616374696f6e5f69736f6c6174696f6e0000000000000000000019ffffff" - "ffffff0000440000001800010000000e7265616420636f6d6d6974746564430000000953484f57005a0000000549"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::PgSQLParser pgsql((const char*)data.data(), (size_t)data.size()); - pgsql.parse(MessageType::MessageType_Response); - APSARA_TEST_TRUE(pgsql.OK()); - APSARA_TEST_TRUE(pgsql.type == PgSQLPacketType::Response); - APSARA_TEST_TRUE(pgsql.resp.ok); - } - - void TestStartUp() { - const std::string hexString - = "000000650003000075736572006c6a70006461746162617365007465737400636c69656e745f656e636f64696e67005554463800" - "446174655374796c650049534f0054696d655a6f6e65005554430065787472615f666c6f61745f64696769747300320000"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::PgSQLParser pgsql((const char*)data.data(), (size_t)data.size()); - pgsql.parse(MessageType::MessageType_Response); - APSARA_TEST_TRUE(pgsql.type == PgSQLPacketType::IgnoreQuery); - APSARA_TEST_TRUE(pgsql.OK()); - } - - void TestErrorResp() { - const std::string hexString - = "450000005b534552524f5200564552524f5200433432363031004d73796e746178206572726f7220617420656e64206f6620696e" - "7075740050343200467363616e2e6c004c3131373300527363616e6e65725f79796572726f720000"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::PgSQLParser pgsql((const char*)data.data(), (size_t)data.size()); - pgsql.parse(MessageType::MessageType_Response); - APSARA_TEST_TRUE(pgsql.type == PgSQLPacketType::Response); - APSARA_TEST_TRUE(!pgsql.resp.ok); - APSARA_TEST_TRUE(pgsql.OK()); - } - - void TestPgSqlPacketReaderOrder() { - std::vector rawHexs{rawHex1, rawHex2}; - RawNetPacketReader reader("30.240.99.13", false, ProtocolType_PgSQL, rawHexs); - std::vector packets; - reader.GetAllNetPackets(packets); - APSARA_TEST_TRUE(reader.OK()); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - - std::vector allData; - mObserver->FlushOutMetrics(allData); - - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "query", "SHOW TRANSACTION ISOLATION LEVEL")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "status", "1")); - } - - void TestPgSqlPacketReaderUnorder() { - std::vector rawHexs{rawHex1, rawHex2}; - RawNetPacketReader reader("30.240.99.13", false, ProtocolType_PgSQL, rawHexs); - std::vector packets; - reader.GetAllNetPackets(packets); - APSARA_TEST_TRUE(reader.OK()); - for (int i = packets.size() - 1; i >= 0; --i) { - mObserver->OnPacketEvent(&packets[i].at(0), packets[i].size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "query", "SHOW TRANSACTION ISOLATION LEVEL")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(&allData[0], "status", "1")); - } - - void TestPgSQLParserGC() { - INT64_FLAG(sls_observer_network_process_timeout) = 18446744073; - INT64_FLAG(sls_observer_network_connection_timeout) = 18446744073; - INT64_FLAG(sls_observer_network_process_no_connection_timeout) = 0; - BOOL_FLAG(sls_observer_network_protocol_stat) = true; - - NetworkStatistic* networkStatistic = NetworkStatistic::GetInstance(); - ProtocolDebugStatistic* protocolDebugStatistic = ProtocolDebugStatistic::GetInstance(); - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - networkStatistic->Clear(); - { - std::vector rawHexs{rawHex1}; - RawNetPacketReader reader("30.240.99.13", false, ProtocolType_PgSQL, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), 0); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds() - 60000000000000ULL); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 1); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 0); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mPgSQLConnectionNum, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mPgSQLConnectionCachedSize, 1); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 2); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mPgSQLConnectionNum, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mPgSQLConnectionCachedSize, 0); - } - NetworkStatistic::Clear(); - { - std::vector rawHexs{rawHex2}; - RawNetPacketReader reader("30.240.99.13", false, ProtocolType_PgSQL, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), 0); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds() - 60000000000000ULL); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 1); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 0); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mPgSQLConnectionNum, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mPgSQLConnectionCachedSize, 1); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 2); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mPgSQLConnectionNum, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mPgSQLConnectionCachedSize, 0); - } - } - - void TestSimplyQueryResponse() { - // const std::string hexString = - // "540000006e000475696400000040220001000000170004ffffffff0000757365726e616d650000004022000200000413ffff0000006800006465706172746e616d650000004022000300000413ffff000001f8000063726561746564000000402200040000043a0004ffffffff0000440000003c0004000000033330310000000d617374617869657570646174650000000ce7a094e58f91e983a8e997a80000000a323031322d31322d3039430000000d53454c4543542031005a0000000549"; - const std::string hexString = "31000000047400000012000300000413000004130000043a540000001c0001756964000000402200" - "01000000170004ffffffff00005a0000000549"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::PgSQLParser pgsql((const char*)data.data(), (size_t)data.size()); - pgsql.parse(MessageType_Response); - APSARA_TEST_EQUAL(pgsql.query.sql.ToString(), ""); - APSARA_TEST_EQUAL(pgsql.resp.ok, true); - APSARA_TEST_TRUE(pgsql.type == PgSQLPacketType::Response); - } - - - NetworkObserver* mObserver = NetworkObserver::GetInstance(); - - const std::string rawHex1 - = "00000000040088665a406acf080045000080000040004006c69b1ef0630d707c8163cbd31538ff44f48ea0c79df68018080058310000" - "0101080a110926ce00c93c1650000000280053484f57205452414e53414354494f4e2049534f4c4154494f4e204c4556454c00000042" - "0000000c000000000000000044000000065000450000000900000000005300000004"; - const std::string rawHex2 = "88665a406acf00000000040008004500009636e4400033069ca1707c81631ef0630d1538cbd3a0c79df6ff" - "44f4da8018004366df00000101080a00c93d1f1109277831000000043200000004540000002e0001747261" - "6e73616374696f6e5f69736f6c6174696f6e0000000000000000000019ffffffffffff0000440000001800" - "010000000e7265616420636f6d6d6974746564430000000953484f57005a0000000549"; -}; - -APSARA_UNIT_TEST_CASE(ProtocolPgSqlUnittest, TestStartUp, 0); - -APSARA_UNIT_TEST_CASE(ProtocolPgSqlUnittest, TestSimpleQueryRequest, 0); - -APSARA_UNIT_TEST_CASE(ProtocolPgSqlUnittest, TestSimpleQueryResponse, 0); - -APSARA_UNIT_TEST_CASE(ProtocolPgSqlUnittest, TestExtendQueryRequest, 0); - -APSARA_UNIT_TEST_CASE(ProtocolPgSqlUnittest, TestExtendQueryResponse, 0); - -APSARA_UNIT_TEST_CASE(ProtocolPgSqlUnittest, TestErrorResp, 0); - -APSARA_UNIT_TEST_CASE(ProtocolPgSqlUnittest, TestPgSqlPacketReaderOrder, 0); - -APSARA_UNIT_TEST_CASE(ProtocolPgSqlUnittest, TestPgSqlPacketReaderUnorder, 0); - -APSARA_UNIT_TEST_CASE(ProtocolPgSqlUnittest, TestPgSQLParserGC, 0); - -APSARA_UNIT_TEST_CASE(ProtocolPgSqlUnittest, TestSimplyQueryResponse, 0); -} // namespace logtail - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/core/unittest/observer/ProtocolRedisUnittest.cpp b/core/unittest/observer/ProtocolRedisUnittest.cpp deleted file mode 100644 index 49fa79e9b9..0000000000 --- a/core/unittest/observer/ProtocolRedisUnittest.cpp +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include - -#include "unittest/Unittest.h" -#include "network/protocols/utils.h" -#include "network/protocols/redis/inner_parser.h" -#include "RawNetPacketReader.h" -#include "unittest/UnittestHelper.h" -#include "observer/network/ProcessObserver.h" - -namespace logtail { - - -class ProtocolRedisUnittest : public ::testing::Test { -public: - void TestCommonRequest() { - // set aa 1; - const std::string hexString = "2a330d0a24330d0a7365740d0a24320d0a61610d0a24320d0a313b0d0a"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::RedisParser redis((const char*)data.data(), (size_t)data.size()); - redis.parse(); - APSARA_TEST_EQUAL(redis.redisData.isError, false); - APSARA_TEST_EQUAL(redis.redisData.GetCommands(), "set"); - } - - void TestCommonResponse() { - const std::string hexString = "2b4f4b0d0a"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::RedisParser redis((const char*)data.data(), (size_t)data.size()); - redis.parse(); - APSARA_TEST_EQUAL(redis.redisData.isError, false); - APSARA_TEST_EQUAL(redis.redisData.GetCommands(), "OK"); - } - - void TestErrResponse() { - const std::string hexString = "2d45525220756e6b6e6f776e20636f6d6d616e64202741414141270d0a"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::RedisParser redis((const char*)data.data(), (size_t)data.size()); - redis.parse(); - APSARA_TEST_EQUAL(redis.redisData.isError, true); - APSARA_TEST_EQUAL(redis.redisData.GetCommands(), "ERR unknown command 'AAAA'"); - } - - void TestRangeResponse() { - const std::string hexString = "2a330d0a24330d0a6f6e650d0a24330d0a74776f0d0a24350d0a74687265650d0a"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::RedisParser redis((const char*)data.data(), (size_t)data.size()); - redis.parse(); - APSARA_TEST_EQUAL(redis.redisData.isError, false); - APSARA_TEST_EQUAL(redis.redisData.GetCommands(), "one"); - } - - void TestIntegerResponse() { - const std::string hexString = "3a340d0a"; - std::vector data; - hexstring_to_bin(hexString, data); - logtail::RedisParser redis((const char*)data.data(), (size_t)data.size()); - redis.parse(); - APSARA_TEST_EQUAL(redis.redisData.isError, false); - APSARA_TEST_EQUAL(redis.redisData.GetCommands(), "4"); - } - - void TestRedisPacketReader() { - std::vector rawHexs{rawHex1, rawHex2}; - RawNetPacketReader reader("30.43.120.215", false, ProtocolType_Redis, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), size_t(1)); - - sls_logs::Log* log = &allData[0]; - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "query", "set")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "status", "1")); - } - - void TestRedisPacketReaderUnorder() { - // response first - std::vector rawHexs{rawHex1, rawHex2}; - RawNetPacketReader reader("30.43.120.215", false, ProtocolType_Redis, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (int i = packets.size() - 1; i >= 0; --i) { - mObserver->OnPacketEvent(&packets[i].at(0), packets[i].size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), size_t(1)); - - sls_logs::Log* log = &allData[0]; - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "query", "set")); - APSARA_TEST_TRUE(UnitTestHelper::LogKeyMatched(log, "status", "1")); - } - - void TestRedisParserGC() { - INT64_FLAG(sls_observer_network_process_timeout) = 18446744073; - INT64_FLAG(sls_observer_network_connection_timeout) = 18446744073; - INT64_FLAG(sls_observer_network_process_no_connection_timeout) = 0; - BOOL_FLAG(sls_observer_network_protocol_stat) = true; - - NetworkStatistic* networkStatistic = NetworkStatistic::GetInstance(); - ProtocolDebugStatistic* protocolDebugStatistic = ProtocolDebugStatistic::GetInstance(); - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - NetworkStatistic::Clear(); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - - { - std::vector rawHexs{rawHex1}; - RawNetPacketReader reader("30.43.120.215", false, ProtocolType_Redis, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), 0); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds() - 60000000000000ULL); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 1); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 0); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 0); - - APSARA_TEST_EQUAL(protocolDebugStatistic->mRedisConnectionNum, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mRedisConnectionCachedSize, 1); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 2); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mRedisConnectionNum, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mRedisConnectionCachedSize, 0); - } - NetworkStatistic::Clear(); - { - std::vector rawHexs{rawHex2}; - RawNetPacketReader reader("30.43.120.215", false, ProtocolType_Redis, rawHexs); - APSARA_TEST_TRUE(reader.OK()); - std::vector packets; - reader.GetAllNetPackets(packets); - for (auto& packet : packets) { - mObserver->OnPacketEvent(&packet.at(0), packet.size()); - } - std::vector allData; - mObserver->FlushOutMetrics(allData); - APSARA_TEST_EQUAL(allData.size(), 0); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds() - 60000000000000ULL); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 1); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 0); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mRedisConnectionNum, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mRedisConnectionCachedSize, 1); - - mObserver->GarbageCollection(GetCurrentTimeInNanoSeconds()); - APSARA_TEST_EQUAL(mObserver->mAllProcesses.size(), 0); - APSARA_TEST_EQUAL(networkStatistic->mGCCount, 2); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseConnCount, 1); - APSARA_TEST_EQUAL(networkStatistic->mGCReleaseProcessCount, 1); - APSARA_TEST_EQUAL(protocolDebugStatistic->mRedisConnectionNum, 0); - APSARA_TEST_EQUAL(protocolDebugStatistic->mRedisConnectionCachedSize, 0); - } - } - - - NetworkObserver* mObserver = NetworkObserver::GetInstance(); - const std::string rawHex1 - = "00749c945d39a07817a0852e08004500005b000040004006375a1e2b78d70b9f60a2e2ae18ebeec9ad5886a8e17980180800c6e80000" - "0101080a4d49243fd112ef9f2a330d0a24330d0a7365740d0a24310d0a610d0a2431320d0a6861686167617367667361660d0a"; - const std::string rawHex2 = "a07817a0852e00749c945d39080045000039b7464000370689350b9f60a21e2b78d718ebe2ae86a8e179ee" - "c9ad7f80180039a65a00000101080ad11409da4d49243f2b4f4b0d0a"; -}; - -APSARA_UNIT_TEST_CASE(ProtocolRedisUnittest, TestCommonRequest, 0); - -APSARA_UNIT_TEST_CASE(ProtocolRedisUnittest, TestCommonResponse, 0); - -APSARA_UNIT_TEST_CASE(ProtocolRedisUnittest, TestErrResponse, 0); - -APSARA_UNIT_TEST_CASE(ProtocolRedisUnittest, TestRangeResponse, 0); - -APSARA_UNIT_TEST_CASE(ProtocolRedisUnittest, TestIntegerResponse, 0); - -APSARA_UNIT_TEST_CASE(ProtocolRedisUnittest, TestRedisPacketReader, 0); - -APSARA_UNIT_TEST_CASE(ProtocolRedisUnittest, TestRedisPacketReaderUnorder, 0); - -APSARA_UNIT_TEST_CASE(ProtocolRedisUnittest, TestRedisParserGC, 0); - - -} // namespace logtail - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/core/unittest/observer/ProtocolUtilUnittest.cpp b/core/unittest/observer/ProtocolUtilUnittest.cpp deleted file mode 100644 index 9fbed0ec29..0000000000 --- a/core/unittest/observer/ProtocolUtilUnittest.cpp +++ /dev/null @@ -1,349 +0,0 @@ -// Copyright 2022 iLogtail 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. - -#include "unittest/Unittest.h" -#include "unittest/UnittestHelper.h" -#include "observer/interface/helper.h" -#include "observer/network/protocols/utils.h" -#include "network/protocols/mysql/parser.h" - - -namespace logtail { - -struct TestReq { - int num; - uint64_t TimeNano; -}; - -struct TestResp { - int num; - uint64_t TimeNano; -}; - - -typedef CommonCache TestCache; - -struct ExpectTestCacheMeta { - size_t headRequest = 0; - size_t headResponse = 0; - size_t tailRequest = -1; - size_t tailResponse = -1; - size_t reqSize = 0; - size_t respSize = 0; -}; - - -class ProtocolUtilUnittest : public ::testing::Test { -public: - void TestReadUntil() { - char s[] = "\x50\x00\x00\x00\x28\x00\x73\x65\x6c\x65\x63\x74\x20\x2a\x20\x66" - "\x72\x6f\x6d\x20\x70\x67\x5f\x61\x6d\x20\x77\x68\x65\x72\x65\x20" - "\x6f\x69\x64\x3d\x20\x32\x00\x00\x00"; - ProtoParser parser(&s[0], 41, true); - APSARA_TEST_TRUE((*parser.readChar()) == 'P'); - APSARA_TEST_EQUAL(parser.readUint32(), 40); - APSARA_TEST_TRUE(parser.readUntil('\0') == ""); - APSARA_TEST_TRUE(parser.readUntil('\0') == "select * from pg_am where oid= 2"); - APSARA_TEST_EQUAL(parser.readUint16(), 0); - } - - void TestSlsStringPiece() { - logtail::SlsStringPiece d1("abc", 3); - APSARA_TEST_TRUE(d1.TrimToString() == "abc"); - - logtail::SlsStringPiece d2("abc ", 5); - APSARA_TEST_TRUE(d2.TrimToString() == "abc"); - - logtail::SlsStringPiece d3(" abc", 4); - APSARA_TEST_TRUE(d3.TrimToString() == "abc"); - - - logtail::SlsStringPiece d4(" abc ", 5); - APSARA_TEST_TRUE(d4.TrimToString() == "abc"); - - logtail::SlsStringPiece d5(" \tabc \t", 8); - APSARA_TEST_TRUE(d5.TrimToString() == "abc"); - - logtail::SlsStringPiece v1("Ram-Sdk-Hostip", 14); - logtail::SlsStringPiece v2("Content-Length", 14); - - std::map header1; - - header1[v1] = 1; - header1[v1] = 1; - APSARA_TEST_TRUE(header1.size() == 1); - - std::map header2; - - header2[v1] = 1; - header2[v2] = 1; - - APSARA_TEST_TRUE(header2.size() == 2); - - static logtail::SlsStringPiece v3("Content-Length", 14); - - APSARA_TEST_TRUE(header2.find(v3) != header2.end()); - - logtail::SlsStringPiece v4("abc", 3); - logtail::SlsStringPiece v5("ab", 2); - logtail::SlsStringPiece v6("abcd", 4); - logtail::SlsStringPiece v7("aBc", 3); - logtail::SlsStringPiece v8("Ab", 2); - logtail::SlsStringPiece v9("abcD", 4); - logtail::SlsStringPiece v10("", 0); - logtail::SlsStringPiece v11("abc", 3); - logtail::SlsStringPiece v12("", 0); - - APSARA_TEST_EQUAL(v4 < v5, false); - APSARA_TEST_EQUAL(v5 < v4, true); - - APSARA_TEST_EQUAL(v4 < v6, true); - APSARA_TEST_EQUAL(v6 < v4, false); - - APSARA_TEST_EQUAL(v4 < v7, false); - APSARA_TEST_EQUAL(v7 < v4, true); - - APSARA_TEST_EQUAL(v4 < v8, false); - APSARA_TEST_EQUAL(v8 < v4, true); - - APSARA_TEST_EQUAL(v4 < v9, true); - APSARA_TEST_EQUAL(v9 < v4, false); - - APSARA_TEST_EQUAL(v4 < v10, false); - APSARA_TEST_EQUAL(v10 < v4, true); - - APSARA_TEST_EQUAL(v4 < v11, false); - APSARA_TEST_EQUAL(v11 < v4, false); - - APSARA_TEST_EQUAL(v10 < v12, false); - APSARA_TEST_EQUAL(v12 < v10, false); - } - - void PrintCache(TestCache& cache, ExpectTestCacheMeta meta) { - std::cout << "=============================\n" - << "req head: " << cache.mHeadRequestsIdx << "req tail:" << cache.mTailRequestsIdx - << "req size:" << cache.GetRequestsSize() << "resp head: " << cache.mHeadResponsesIdx - << "resp tail:" << cache.mTailResponsesIdx << "resp size:" << cache.GetResponsesSize() << std::endl; - - APSARA_TEST_EQUAL(cache.mHeadRequestsIdx, meta.headRequest); - APSARA_TEST_EQUAL(cache.mTailRequestsIdx, meta.tailRequest); - APSARA_TEST_EQUAL(cache.mHeadResponsesIdx, meta.headResponse); - APSARA_TEST_EQUAL(cache.mTailResponsesIdx, meta.tailResponse); - APSARA_TEST_EQUAL(cache.GetRequestsSize(), meta.reqSize); - APSARA_TEST_EQUAL(cache.GetResponsesSize(), meta.respSize); - } - - void TestCommonCacheContinueReq() { - TestCache cache(nullptr); - for (int i = 0; i < 4; ++i) { - cache.GetReqByIndex(i)->num = i; - cache.GetRespByIndex(i)->num = i; - } - for (int i = 0; i < 16; ++i) { - cache.GetReqPos()->TimeNano = GetCurrentTimeInNanoSeconds(); - ExpectTestCacheMeta meta; - meta.headRequest = i < 4 ? 0 : i - 3; - meta.tailRequest = meta.headRequest + (i < 4 ? i : 3); - meta.reqSize = meta.tailRequest - meta.headRequest + 1; - cache.TryStitcherByReq(); - PrintCache(cache, meta); - } - } - - void TestCommonCacheContinueResp() { - TestCache cache(nullptr); - for (int i = 0; i < 4; ++i) { - cache.GetReqByIndex(i)->num = i; - cache.GetRespByIndex(i)->num = i; - } - for (int i = 0; i < 16; ++i) { - cache.GetRespPos()->TimeNano = GetCurrentTimeInNanoSeconds(); - ExpectTestCacheMeta meta; - meta.headResponse = i < 4 ? 0 : i - 3; - meta.tailResponse = meta.headResponse + (i < 4 ? i : 3); - meta.respSize = meta.tailResponse - meta.headResponse + 1; - cache.TryStitcherByResp(); - PrintCache(cache, meta); - } - } - - void TestCommonCacheContinueOneByOneAndReqFirst() { - TestCache cache(nullptr); - for (int i = 0; i < 4; ++i) { - cache.GetReqByIndex(i)->num = i; - cache.GetRespByIndex(i)->num = i; - } - int count = 0; - cache.BindConvertFunc([&](TestReq* req, TestResp* resp, MySQLProtocolEvent& e) -> bool { - count++; - return false; - }); - for (int i = 0; i < 16; ++i) { - cache.GetReqPos()->TimeNano = GetCurrentTimeInNanoSeconds(); - cache.GetRespPos()->TimeNano = GetCurrentTimeInNanoSeconds(); - ExpectTestCacheMeta meta; - meta.headRequest = i; - meta.tailRequest = meta.headRequest; - meta.headResponse = i; - meta.tailResponse = meta.headResponse; - meta.reqSize = 1; - meta.respSize = 1; - PrintCache(cache, meta); - cache.TryStitcherByResp(); - ++meta.headRequest; - meta.tailRequest = meta.headRequest - 1; - meta.headResponse = meta.headRequest; - meta.tailResponse = meta.tailRequest; - meta.reqSize = 0; - meta.respSize = 0; - PrintCache(cache, meta); - APSARA_TEST_EQUAL(count, i + 1); - } - } - - void TestCommonCacheContinueOneByOneAndRespFirst() { - TestCache cache(nullptr); - for (int i = 0; i < 4; ++i) { - cache.GetReqByIndex(i)->num = i; - cache.GetRespByIndex(i)->num = i; - } - int count = 0; - cache.BindConvertFunc([&](TestReq* req, TestResp* resp, MySQLProtocolEvent& e) -> bool { - count++; - return false; - }); - for (int i = 0; i < 16; ++i) { - auto before = GetCurrentTimeInNanoSeconds(); - cache.GetRespPos()->TimeNano = GetCurrentTimeInNanoSeconds(); - cache.GetReqPos()->TimeNano = before; - ExpectTestCacheMeta meta; - meta.headRequest = i; - meta.tailRequest = meta.headRequest; - meta.headResponse = i; - meta.tailResponse = meta.headResponse; - meta.reqSize = 1; - meta.respSize = 1; - PrintCache(cache, meta); - cache.TryStitcherByReq(); - ++meta.headRequest; - meta.tailRequest = meta.headRequest - 1; - meta.headResponse = meta.headRequest; - meta.tailResponse = meta.tailRequest; - meta.reqSize = 0; - meta.respSize = 0; - PrintCache(cache, meta); - APSARA_TEST_EQUAL(count, i + 1); - } - } - - void TestCommonCacheInsertOldResp() { - TestCache cache(nullptr); - for (int i = 0; i < 4; ++i) { - cache.GetReqByIndex(i)->num = i; - cache.GetRespByIndex(i)->num = i; - } - int count = 0; - cache.BindConvertFunc([&](TestReq* req, TestResp* resp, MySQLProtocolEvent& e) -> bool { - count++; - return false; - }); - auto time = GetCurrentTimeInNanoSeconds(); - cache.GetReqPos()->TimeNano = time; - - for (int i = 0; i < 16; ++i) { - cache.GetRespPos()->TimeNano = time - 2; - cache.TryStitcherByResp(); - APSARA_TEST_EQUAL(count, 0); - ExpectTestCacheMeta meta; - meta.headRequest = 0; - meta.tailRequest = 0; - meta.headResponse = i + 1; - meta.tailResponse = i; - meta.reqSize = 1; - meta.respSize = 0; - PrintCache(cache, meta); - } - } - - void TestCommonCacheInsertNewReq() { - TestCache cache(nullptr); - for (int i = 0; i < 4; ++i) { - cache.GetReqByIndex(i)->num = i; - cache.GetRespByIndex(i)->num = i; - } - int count = 0; - cache.BindConvertFunc([&](TestReq* req, TestResp* resp, MySQLProtocolEvent& e) -> bool { - count++; - return false; - }); - auto time = GetCurrentTimeInNanoSeconds(); - cache.GetRespPos()->TimeNano = time - 3; - cache.GetRespPos()->TimeNano = time - 2; - cache.GetReqPos()->TimeNano = time; - cache.TryStitcherByReq(); - APSARA_TEST_EQUAL(cache.GetRequestsSize(), 1); - APSARA_TEST_EQUAL(cache.GetResponsesSize(), 0); - APSARA_TEST_EQUAL(count, 0); - ExpectTestCacheMeta meta; - meta.headRequest = 0; - meta.tailRequest = 0; - meta.headResponse = 2; - meta.tailResponse = 1; - meta.reqSize = 1; - meta.respSize = 0; - PrintCache(cache, meta); - } - - void TestCommonCacheTryMatchingReq() { - TestCache cache(nullptr); - for (int i = 0; i < 4; ++i) { - cache.GetReqByIndex(i)->num = i; - cache.GetRespByIndex(i)->num = i; - } - int count = 0; - cache.BindConvertFunc([&](TestReq* req, TestResp* resp, MySQLProtocolEvent& e) -> bool { - count++; - APSARA_TEST_EQUAL(resp->TimeNano - req->TimeNano, 2); - return false; - }); - auto time = GetCurrentTimeInNanoSeconds(); - cache.GetReqPos()->TimeNano = time - 3; - cache.GetReqPos()->TimeNano = time - 2; - cache.GetReqPos()->TimeNano = time + 1; - cache.GetRespPos()->TimeNano = time; - cache.TryStitcherByResp(); - APSARA_TEST_EQUAL(cache.GetRequestsSize(), 1); - APSARA_TEST_EQUAL(cache.GetResponsesSize(), 0); - APSARA_TEST_EQUAL(count, 1); - } -}; - - -APSARA_UNIT_TEST_CASE(ProtocolUtilUnittest, TestReadUntil, 0); -APSARA_UNIT_TEST_CASE(ProtocolUtilUnittest, TestSlsStringPiece, 0); -APSARA_UNIT_TEST_CASE(ProtocolUtilUnittest, TestCommonCacheContinueReq, 0); -APSARA_UNIT_TEST_CASE(ProtocolUtilUnittest, TestCommonCacheContinueResp, 0); -APSARA_UNIT_TEST_CASE(ProtocolUtilUnittest, TestCommonCacheContinueOneByOneAndReqFirst, 0); -APSARA_UNIT_TEST_CASE(ProtocolUtilUnittest, TestCommonCacheContinueOneByOneAndRespFirst, 0); -APSARA_UNIT_TEST_CASE(ProtocolUtilUnittest, TestCommonCacheInsertOldResp, 0); -APSARA_UNIT_TEST_CASE(ProtocolUtilUnittest, TestCommonCacheInsertNewReq, 0); -APSARA_UNIT_TEST_CASE(ProtocolUtilUnittest, TestCommonCacheTryMatchingReq, 0); -} // namespace logtail - - -int main(int argc, char** argv) { - logtail::Logger::Instance().InitGlobalLoggers(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/core/unittest/observer/RawNetPacketReader.h b/core/unittest/observer/RawNetPacketReader.h deleted file mode 100644 index c4ae552e33..0000000000 --- a/core/unittest/observer/RawNetPacketReader.h +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2022 iLogtail 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. - */ - -#include -#include -#include "observer/network/NetworkObserver.h" -#include "observer/interface/helper.h" -#include "observer/network/protocols/utils.h" -#include "HashUtil.h" -#include -#include - -namespace logtail { - -enum L4ProtoType { L4ProtoUDP, L4ProtoTCP }; - -class RawNetPacketReader { -private: - bool parseSuccess = true; - std::string parserFailMsg; - std::string mLocalAddress; - bool mIsServer; - ProtocolType mProtocolType; - std::vector rawHexs; - -public: - RawNetPacketReader(std::string mLocalAddress, - bool mIsServer, - ProtocolType mProtocolType, - std::vector rawHexs) - : mLocalAddress(mLocalAddress), mIsServer(mIsServer), mProtocolType(mProtocolType), rawHexs(rawHexs) {} - - std::string& GetParseFailMsg() { return parserFailMsg; } - - bool OK() { return parseSuccess; } - - void GetAllNetPackets(std::vector& packets) { - for (auto& rawHex : rawHexs) { - std::vector rawData; - hexstring_to_bin(rawHex, rawData); - const char* rawPkt = (const char*)rawData.data(); - size_t rawPktSize = rawData.size(); - parse(rawPkt, rawPktSize, packets); - } - } - - void assemble(const char* payload, - const int32_t dataLen, - uint16_t srcPort, - uint16_t dstPort, - SockAddress& srcIP, - SockAddress& dstIP, - std::vector& packets) { - // assemble data - std::string packetData; - // int32_t dataLen = udpPayloadLength; - packetData.resize(dataLen + sizeof(PacketEventHeader) + sizeof(PacketEventData)); - char* beginData = &packetData.at(0); - - PacketEventHeader* header = (PacketEventHeader*)beginData; - PacketEventData* data = (PacketEventData*)(beginData + sizeof(PacketEventHeader)); - std::string src = SockAddressToString(srcIP); - std::string dst = SockAddressToString(dstIP); - data->Buffer = beginData + sizeof(PacketEventHeader) + sizeof(PacketEventData); - - // check packet direction - if (src == mLocalAddress) { - data->PktType = PacketType_Out; - if (mIsServer) { - data->MsgType = MessageType_Response; - } else { - data->MsgType = MessageType_Request; - } - } else { - data->PktType = PacketType_In; - std::swap(src, dst); - std::swap(srcPort, dstPort); - if (mIsServer) { - data->MsgType = MessageType_Request; - } else { - data->MsgType = MessageType_Response; - } - } - - header->PID = 0; - header->EventType = PacketEventType_Data; - header->TimeNano = GetCurrentTimeInNanoSeconds(); - header->SockHash = uint32_t(HashString(src + std::to_string(srcPort) + dst + std::to_string(dstPort))); - - header->SrcAddr = SockAddressFromString(src); - header->DstAddr = SockAddressFromString(dst); - - header->DstPort = dstPort; - header->SrcPort = srcPort; - header->RoleType = PacketRoleType::Server; - - data->BufferLen = dataLen; - data->RealLen = dataLen; - char* dataBuffer = data->Buffer; - for (int i = 0; i < dataLen; i++) { - dataBuffer[i] = payload[i]; - } - - data->PtlType = mProtocolType; - packets.push_back(packetData); - // fix data buffers - std::string& lastPacketData = packets.back(); - char* bData = &lastPacketData.at(0); - PacketEventData* pData = (PacketEventData*)(bData + sizeof(PacketEventHeader)); - pData->Buffer = bData + sizeof(PacketEventHeader) + sizeof(PacketEventData); - } - - void parse(const char* rawPkt, const size_t rawPktSize, std::vector& packets) { - uint32_t position = 0; - // read ethernet layer - if (rawPktSize < 15) { // 以太网+ip的第一个字段 - parserFailMsg = "bad ethernet packet length"; - parseSuccess = false; - return; - } - position = 14; - - // read ip layer - uint8_t* ipheader = (uint8_t*)(rawPkt + position); - - if ((ipheader[0] >> 4) != 4) { - parserFailMsg = "not ipv4 packet"; - parseSuccess = false; - return; - } - - size_t ipheaderLen = (ipheader[0] & 0x0f) * 4; // 32bit 一个单位,即 4个字节一个计量 - if (ipheaderLen < 20 || (position + ipheaderLen) > rawPktSize) { - parserFailMsg = "bad ip packet length"; - parseSuccess = false; - return; - } - - uint8_t protocolCode = ipheader[9]; // protocol - SockAddress srcIP; - srcIP.Type = SockAddressType_IPV4; - srcIP.Addr.IPV4 = *((uint32_t*)(ipheader + 12)); - std::cout << SockAddressToString(srcIP) << std::endl; - - SockAddress dstIP; - dstIP.Type = SockAddressType_IPV4; - dstIP.Addr.IPV4 = *((uint32_t*)(ipheader + 16)); - std::cout << SockAddressToString(dstIP) << std::endl; - - if (protocolCode == 17) { - // udp packet - position += ipheaderLen; - if ((position + 8) > rawPktSize) { - parserFailMsg = "bad udp packet length"; - parseSuccess = false; - return; - } - uint16_t* udpHeader = (uint16_t*)(rawPkt + position); - - uint16_t srcPort = ntohs(udpHeader[0]); - uint16_t dstPort = ntohs(udpHeader[1]); - - uint16_t udpTotalLength = ntohs(udpHeader[2]); - - uint16_t udpPayloadLength = udpTotalLength - 8; // UDP 总长度减去 Header固定长度8 - // std::cout << udpPayloadLength << std::endl; - - position += 8; - - if ((position + udpPayloadLength) > rawPktSize) { - parserFailMsg = "corrupt udp payload"; - parseSuccess = false; - return; - } - - const char* udpPayload = (char*)(rawPkt + position); - - assemble(udpPayload, udpPayloadLength, srcPort, dstPort, srcIP, dstIP, packets); - } else if (protocolCode == 6) { - // TCP Packet - position += ipheaderLen; - if ((position + 20) > rawPktSize) { // tcp报文头至少20个字节 - parserFailMsg = "bad tcp packet length"; - parseSuccess = false; - return; - } - uint16_t* tcpHeader = (uint16_t*)(rawPkt + position); - - uint16_t srcPort = ntohs(tcpHeader[0]); - uint16_t dstPort = ntohs(tcpHeader[1]); - - uint8_t tcpHeaderLen = (ntohs(tcpHeader[6]) >> 12) * 4; - - position += tcpHeaderLen; - const char* payload = (char*)(rawPkt + position); - int32_t payloadLen = rawPktSize - position; - - // std::cout << "srcPort: " << std::to_string(srcPort) << " dstPort: " << std::to_string(dstPort)<< - // std::endl; - assemble(payload, payloadLen, srcPort, dstPort, srcIP, dstIP, packets); - - } else { - parserFailMsg = "bad tcp packet length"; - parseSuccess = false; - return; - } - } -}; -} // namespace logtail \ No newline at end of file diff --git a/core/unittest/observer/cgroup.tar b/core/unittest/observer/cgroup.tar deleted file mode 100644 index a3839ee4c8246e962284909aa3d58f1bb92bc3b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14665728 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�C8i{f_!S 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

wh5@f$)E@{w9?>wnSQFa06> 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%0D3acHWj?|1c83{GCiJ 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

qHdbrLH$4Fk3#%6_C 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

r#`QKz-PPH@s{R-5|M|4~{@Guw{+HYN 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�bfJ?N16XwQ09Lk|NB6E_|8T1e@_*W|A)A@ch>eli2wUm-;VJ7 z{-22dNWfNk{zu4ct}AQ&75=YQ0{iJ7#{FN)<@`SuVE)HE8bIiR`+xOS5gmY5p{(t{ z;s3s<<$+A`UmO36{4Wxe^M73bmnG@`-Wq>}|Ldin&AG_ZbN#)5@5B6$mj8wShyI^f z|G_=jnl~<*|NE+l{I5aZUH{*{sO5o7_TTcquzwN~T>p{9=;hu$|5uSfd4RN=0|35; z^&h$Z8;i>Nf4KhR1@gg57hV5HRz>sy1MJHiYy0oV{{yQRLz(Qq<^Nv%4+TIl{udJT zcV&&g692cg-^4eY@TW|!|3+zy`9DH{9woGb>HeA5^B+wT=w$q%{u^E8`rrKd|JL@e`2VsJgChYr|C7Y%<<=U1h5xHe zz(FHu6aJd>A1uK6pBKF4moEDJN2iL&{W9+Dy|w)}{NIVXeR1!n$^KjZ@6Z1Tp8x1c z(BGv>-^KBsI4%EX&i_)+e{;6V^B?oi{}z4z`=;tLdV(RBIOBg+)ZIYO^1q1xp(70L z|8(;6d7<$C;dITjdjkJEr>FPi?Og2Bb8?0b0GF&@&YAoxle|;2(9nVve?v6>y;D2m; zB*`ufSQ3WE-GLri6p};p0{_j8)k^Xh~Tibub z{{vA=1DWi<<$tmM4^;_p{YR3bpIblk?|Ly|^KmUQs2)2Re-6w4tL*=#S!kKIo-$cU ztnFX%|IqR~$%@PUS~SN$jrgnezZgsy*SP*S89wV@SakiDo+_gE*Q<)P{Wt!T(nHw&i*o+PMKVJWfxU~Kkk#*Mpo!|ejqyOhnm(dppYj(x| zk7&bW|M2IM{WCb0hU zTIu=yN$3*$e=4eols}?I*7mQ6S{=w_|1JLu`wwHX%JYA!?0i2F^gYZ3Df79D&-kNX-G|Bl(@0+bpu^48681w z&932j<)00O>`*MGQj{)14g|MWbF z7JqZm=RYb^YE=;#U#BM4_TR++15rx@ne4yif3g2d2w&y;za+RBZvCA9Xq2L<0tom% zJpZTQ&-qI6pMU+|+Wv+AZw~!r=i-tl*^krtaLq5J3Y+k!Tsi;Gu>Z>p#t%a*^ZdV7 z6)^zNsFbz+YehXA$YlR5|BL+}$V!Lxf1Ljf<>=#1(04zO`{VT=8H4jbnEy4$`rm@{ zzp6>eP?ymO3|YjQ|DT|Z>HJR_$MwGV@hOg{|*0lqSg##vi~;z7wdl^<9Plh)^Pl=RZ5~dA)Fjf9d!k^3QJ?4gmb0%m41;e-f1Ie;EIJg}!>ZEBvcetC1ta)A0v) zvC94r{_&PaQ1O4*+4K2Gyj5dPr3iho&fa01ND`rk0ia`+FL zj6Wk0uKx~Y=Sum&Mvyu-)d?!(f@}e!u)?y8`0H)hNi!lBh zz~t@575<;MJo&huitFhPCE$zq{s<pRV-$XJ2abT&wuIivOCx02+Z#$3I|X$?L!K zK>O4vD*msLUREiTi&+6r%k<2cYHOa)p2Xdt3vi;~$3Q`5)Z>u>|*tUw4In75@#(es`l! z$Di^T@$cm4^THMW_5T04_@(*S>G+4RUX1py5vB7-uJAAS|2HE^K-2LjVcGu|`QJI* z$_riLU)w*d_?+sRul<1l|B3rQkp7MH|Go&lUAw}+^8fnj-wX49LPF$!Zv^@IAA*e(;&e7FZW<;-slSdcJEg+#0&fn z(|=t5(TUReGwJ*XI|T88?C37tr6f){r{s7bA*Vdw?E*LevW= zW7hcZRsWwt=AUBvmC^wI6Ziii{U7)LUN^W2zv+tq>-B#~_D#~mKhpTq?Vm6_|1Am8 z)2%iBP5+ONYmo_$Km6F~_(PqDRr3E(ZgbpOFQ3l;Q2zn>e<(yx zcf+C<|IRBdketZ5OE+Ad=?3|w76L>1x22S_?oMZm?{}%rB{%^nQm-zna_(Mlv-2WE> z^mt|M|CRo4Y9}DT|6%?|*Z;ZWf2jWt`G3DX{=2(Zf2;cc-B-&{_(!Ci|F4h# z=imP$&~MLq|L5<*|5{x}U(h#WSNyLPbu*C3{#E|R{(pUpd|r?7zqXOziyvabpGHjP ze@bEhANk+^`A^8SUOfLxx{MB>XUC5DUlMgakjefv{Ez)Vdl>n=9OM5s6L7GQ`{IX~ z@Q3D+68}TH$W^ZY{DJtN2aD%_tuCV%sI1vB|7%5UvVWETvH$OjR6F_Cysp1C^&kP? zhwJ|e{v5V{SDF7cKmS|XzY;*yJAwUtFcbb1d{D?j==s*>|3Cx4Rj&U{Hec&sAe1cE z{|i+_Z$F|b*7o1*{|Hg51DWh!;eQ6lKXLzG6{LqJ*7z&@Uxt9gPSAkw!}`A}|FeLs za{bQ(z=l^Yn*W6=BL9!*fwlcN{4YeU4rH=_mH)B-yDCLLPZavD61g>pxGaV@;ZGwj z#s3kb0q+0Y(>eBk_;Ta_5nV=SAdJ}+|Br~e8pve-D*wZB5uX1RAbGsFf3qmi^n#g;KZoPrORoQ~ar2J4E=*P#W&RJ)-F|JH zE&!PDr%@!&|0A6LU!kGy9P|GL`~NXr#z>&!30MAqOw{E-Ci_?YKg9pA{Ph|ukF=YO&)BJX#&w-?s---PztkVB$CWh16u}oGP#eKgQKl3E}kzS#l zOA5`L-rrAe(AHa1*8AzPO!lvx|H1r!eg1!b{0Up~{BJ~;F%IZ#*OmW&L>ngi*UtY| zx&GI|GTVj1|J6qRJ^R=^TG5~Oc_#cd`9Dwq3i*GY^}nU(|ERi*^e^n!75_h?4U_$A z_@Bl&{}Uj2yi)kTUdVMv{_yQ4{DJJH{U2WS|G@Zvj{Tnnpa0V7GLrv*Od%KwCg82?LB^mFTV{|7Rd?(o@w@6`GquqCj@{Ezw9|Gna$ z|E@IP1^i>OO8cK5e zM%3LvCi{m!m+T+fzhnKEPJTWwtnpVINL>l&T>R=j+l0Ta|4VuPbA9W-oycapSakoF zP8E^$#}vxi{+s!KCu+?=Ci_?UKZJTwYn=aSgy;E@HU0|!mz97vP36z}<4yQOw|B|^ zv#{*{hy3pue0V(+T3BxU-%~~O0D4`rw*Q9zd!kkbGTFb%|0G;z{a;0N-XB=wuke2z z2|&vDsc60)@HNzbRQaE+a{b3U+{yrp=Kr25BL6GUR}ZZ1e-QuotzL|0vVR@_N2^@_ z7c!gc${K%#|Erb2e)@+w|BuW0e^3A#^FL-$0YVqt|EsTx=m50(WNrTq|Mx{L5A*{6 zlMvT`WHEZVx5i)L|9a_Xb1t&VdWW zH~ilhwLFl?{x$JGp{wlwBumc!y*2&{|5uSfdFZa21DO23TK|W|@ha=T`^Q@zVbS$} zWK~2TAjAKE*7o0z{|8nthBDc|%KtPd_y4f|n~o)i7z(cPoV~h)c=o{ z`Tw~8D`e>FiqHl3|7}$fd4E)WtnI(y|5ntxflT(V@_)z)p8wQ|()ly5=RcYx(8>5i z{kIU$|ICvAZ*BjI|1T>+{y62=^qT$ns`S-_zvlceTW9?r=jLsE;iAv~4pb3?0G%>e z+kfN#2cnh+GTFcC{|Sp%$^VkzX1KM+U*Z2M6R_X@-Ms(LVrBkUp8v%8pMM2d^!blY z6_NYLG|Afj8~*P^tr=)K|A(C7{=Y_&?jNc2T^#R;)ADa-{#Va`M={{PKK`Ho{6AD$ zS@8J}RhQ8d3^~LZ|Er?z2AaL=Q=+$+Lmzi52p32 z_}lCH^Fv)mPf!|i6TUx`)MSH~e!*n_@aK~K^N6pK|2w<;YyUY<|3_>8uQ(93b`;=i z$p2OQf0I@6zhpQw+%CHR_fQp)@HP5mZU5c+e+mtwPM2i=b`C|1HP=j8Q!Q;R9~vor~W89jGGGzXCoyu(tn( z{|BO$1~S>dhW{DE{a;Cnes2BDzw5<>&BwLKAQqU6KU9A$`G0->e+lzHX!)IF#pQl2 zn&Y2F{B`xeB0T@&$Ka*6F1r3pPZiPoOTag`uKa&b)XG4U`Ja&x^S=}^dU>Es-?}q% z!^IPu!2$or>wgiK)_)^75`_Cd*#EPP1CIv+=|DW+?ShOSwsCNz`FsAO2jje+C8M*BJi`T;{s6#$Py) zOL0FPzmnnt$phy+z2)t{!Q}rbSMU$8{!1%8zds3GV*gJ?6_N5s)X3WY6;Z1Lne1QX ze-f}&?*FT@^Zi87_b?Nr%;zpX?|&-)D=yFfV*VHAe_6%kNX-G|Bl(@0+b;Vj1U-(GQVUryM9>wmo~ zs)+300^j_!w*Qv@6;X?UO!lwge~#z>g#3J7DfHcKUWs%htrh`2T^Zjro5n|8qhx|Myk8o%DOw`0E|W?VRpUmt)-l5b%Ax z{==2?A5ao-jqCrDpmXw<7k&PtBBfRpk@0m3V{QLU{67%2G?2;uRsM%lshIyGNzu=( zpYtD$QZ!Wn0pEw`{}lWo?Qfm+zvhqst?gg<|K`w7b}lY?lKnWH57+!sYOM)>%9Zo~ z9Q(icv3L2c%RK+DRYeQ{WbonM+Wxho9u8!(e}(@Ui2{iK*LVJJD7QK81bz1dxj$b2 zk(c{_nEy3H{@0T0KUR~Hp)R8n7_y2r|35(+)A=8EgmL|E2$ZKgYy1@l0w+N?RV~5p zi%s}bP5pN~|J{kt>&2q$zjvyLtUsni*7o1@~#y#5o*`M)uX@cj2I{l5sDyAay0UTb7>lpyP!U#rXL3tGD@f4bs-t*D!UO!lwxe^~$)^MCp{`MMtC z|EzfbfW;dp4#0#zjr#eY75_uDhx|Ve|FgyOzog6P0D5HNnExeF*8`dCU&H^DVgK(Q zO8zd#_`j{0HrO+J@iR>L!{ohp{=4LVDF1}@|JJzw>n;8-YgrC-8NEPd&5rqBOB*Kp zSNWe;=YKH&=S9Mu@N3ffKezUm|I9DiZm|Go78G%n-6^_~BiV7QL{>Nl(Z?v{V*P3ck<(c6#ci!1*xM6C{FvVVpD8MJ@G z^M9%!Jv_0-UztFbA>ha9lHa;6(17pb{U3$@8TSA1fVJ+Gi{^i!ipc*u@Zp)Y{Wnpc z5Vbmx$^KRT$NnFx6#YC==({>I>#p`=`PC-;>7f5#wg1B?p8uX_{g3hG?*EMFGCBjn zC9e2?MAX$lCi_?UAC?O+|5t$I@ygHo-#xwk+lBu|{#WqlaQttT_5YW!{x__*QL3)V z{)hFyO8?JbzKH989?iac^*5{k<#zpVsEX+JJJiL6EB`+fwRnGdWwL*T|DpaD_WvEq z(AS+G|KBVM9MjV|yWA(^&m*?Z_22pZzcT*^=%D|b(**z%{xpiD{GTYo`Ttz~-=gKt z|HpJ0BZ1DHxbpvFqAmwA*}v-lA^uSG)?Ef%}4iLIsF8|A_h`g`pl(qdgp}s8Y{y@|DpOUiv%lh*F`cj+c+Uxx9{q$7* zf5rcE9<8(g(|4Hv6+g@F4e9kQPttpGdVfE?UDJDRp^hf|*Uta2{@+adze}F~jp#DQ z0i89w^8fE6qAmwA*}ua7<^B)W|L8FCd7(_-s*U`6_OW@iqCf46O!#Z^f1o7D8u$O^ z_y1(^`9G>IBmE2eb;bXWXv1Xx8vcg^Ksf&sDEYfm_`hDrbw~d2=_dT4+Ml%l!{f64 zffByL2EB^WKN&{ZNpYm1a|IClS624UnIiI%_w*R31 z^Zx6ei-SBbQjiIM8ii8)2j$;z|8J)Kzi_$pe~m6<6gV_uSMi@l)ZIWP`&axwqddm? zFP;2+URdL=IFPy$(7E{4eYOezVgKK%{udO0!})(FK#v!T?*G!MBC`IN3R&BKGym^I ztr^H<|0@57(K_otYJ}(ckv0Ab|Cg13HcjQv`r}Rb!(>m2|5+HUa{tdW*qYZvU#;cF z|2Ns&mH~`^FOGD|H&%P|MgkF1L50~B4b zw*P+oKd^c+l*#^8{-;5?|A*`ULWaJstnpX)zpecyzSx96Wpe#D*b&D3A0a@GSA+)3 z-T!S>5qW=9gRJep;r~|Dx`9mgukwG$*IEBdCq2K|gc&1QvkS$p4byX1KM+U*Z2M6R_X@-Ms(LVk!R@c7$;LHv{_r_kW8%|Iw);a{rhz zS=)cZ|DC8c15M}ukW<|M*GSU+BbC03<2`X&{>{w)>iO>|2K?8T|2zNu4=SxJcm40C z>N0wQA%{5Qe^u1oK-2l3#v$(ibn^3gq459Vbj`DS0{=Uwr}yOTT{An!Le~Y6s{{#7df6o8w}0(Fo3?bLe=zNP|Mt56{7{$C6O@MB zgzpa}HQC^$UohD}{JAv$=Mi5e|7#3wnmtL9H{V+Of5m~QwW9#vhxvcH{@-+!{BOp~ z_1{=@|L>tHBH?S4$J+k8_5bmTaVGm$`JY5(|Ht+1|7rm8{Ky)Ah5zeAx6t}6Ne};2 zUk~^i&VS4CKVuZne|Sc|e*L2Ne+R0F^siGWYx{5be;{gUAd~%T_@6P{|COZZ=hn~s zyIxG#d|ZnRs>e>opGRS|%JV-fN3>zGfB19B{uy0o{4a2s>&hB`;Xp3M{dD|FiVGwUob&Y7 zZAP2?KjjMk0oH$+M*|35V*gJ?6_N5s)XCcZ6;Z1Lne1QXe-f}&?*FT@^Zi87_b?Nr z%;zpX?|&-)D=yFfV*Ect{J(-hhHsM~=|_OHhOG>$R; zZ^h^Jr#1en14(};L-%3w|FGC6t^aWrxAeDl-V{#*W6 zL@fp~*}sPWIiCL$^7DD6(08|yqZ-ev-(kWZPJ2rHACqvE`M>ke{|MVHm;WE=GCG3V znqBe#15q3E|5X0xgkb*ft8_c*_pI^PJCNHs-JdSUx&t8K`*{6_E9XDpNZ=aR|0hA` zlDV?{+sxJAZlqKll`mwPiVZ#{C`PsGu-+)|IsK#Qw0$4eR%#) z!JqRuTIKrR{PDlF{R{u!9Qw)5#U)R&AE)!-nqNv4HsP;1|Ie}i%M8X3LoDFJsikn{|f&z5(TTQ|2~x49Cw1g`+?jaum8x){XfkAnj!yd$@L$rNy$)` z(FqJ$#hU-0ppEJL4?DuR{x<~5)15W`iUWa@pqr|eVE4r){Nb>lwEqJgA@TfoCqJJT zi?09PsUouem=0Onf5ZQss5Jwb>|c%l!z%v6`d=DhI)0?kw^Ruf;LYCb%Kw_ze_}cR zH)au@|GwSR8RCC9d%2eSzXM%HmryZ@EB=2(8`JrpbL{`?nCM4dXN|w&KG9`LoW=3>nC!z{kg$km$u{26BudGi#D^%}?Y2z^hz9@5=X;t!#Cq3!E8zPu|EtBH zK-M1#_;ODznh+SBoe<3HH{(LeYO?LYsSU+z%rS@$2{|CIlS zQ=bk02ORT1bdq%c%oYFF@&BOhbN8{+@ee7%{NGM~J}+G1UlKn=e)&zq0n_S?cz&*YFTa&@F_;>yvPJb+# z|7St=xj|I?%Ys7_U~blbhFNyQv7g0X*gqjS{~OBC*PSc;kLl^0U39B|=JOw*A65RB zz(2tJKVN`L?{$Sg&;XQ;(+2?4?Vp7auK!C&`MGt4|K}}FKCY+Ydb&di@Z!Dywa?<; z`F}Y75#st^UxePSRs3DWe@$QjjUXrE59QyYC9nT^Lv#9zRs3Iz|K5wA<)P2?`j#i@ zJvqI%y|DJtpG!ubm@fYzQRD{CypBK=a{wuEV zuj4-r`0zX%&MpZ^}$fM@X+^S^+H$p2QjKKskA@UP;(VcF;I6Q0Fi*gp-!CC~pf zWq#^ecPFyhE?nVX@Bg2RUz#s}7Jp&?A;I$>8bNw^-*1oQ&_tUUjL@xKSa zrB_Pz-|{Tp9r!*t1iM{+2r|gSq?`sBlnFlUSa^&NVM?+nI_^WZ&uAEJqZFQ#?Vjup z@T;3!R(#}t4+jDKW6u9m&I98ps(uE9)j#1``JdSTfxj4V!prr4K75UzyHAK)uJCF3 z&$KF5HZ}_Uwc~N@58-+9@92Kkb&>wAS3x_%A6;A>{ex-CaSuLF|3|Luzvh2f@jKNu zU;CqhXr8P8*X93(oZ$L@UxePSUEyEFfBNZvX%bK2FXF$DQ>_2u4fFnsUEyE5{>{07 zbD#b9m)C&*Q}h2`{09xd(ErPF^l|SB|8D(X!vWLnAG#7_{+|!Hm3O+rzuo&+27m|8 z;@`9X0P{a|l63z}#otjR+C;wK5q?d_AAV!W`@imCeEE$k{?jHLEzD*9&yQ1nO|RLH zFX?NZ#b3mKu>6DZza&LJx32K7)_?TJe_rB$?EmNoZ~3jx@Gp*V>i6`1g4~}=vj4j0 z;vmp;`=^9r{3k=@<-W<%lgc-}or`^X9-iWUefVeb7x5n_>#YCUtrPyW|LpaDe*QSd zWRP6h|H#%~^*w<9Q}h3x{|^_P|EYm_8T)^b?3<*AeU8`A8shrzP>7!HtnqIG2srYQWX0uvEtKWoSK@z;@t-HarPsQ`zgz#+ z1On6je|Z2H`Co#{%dIv3m*Rdp!jZ2_a?Q_qdaF)+3_o=`|3m!;3oGe{K1u zKR^Fl=l-8&VlZtvcCwaO+kc}#@B_~OYRDhH=mq|#A;$lcv3%thoZ+ATPW0$MzyBxZ z|1`k(?-ews|BfsCyZBF20^nKyFYt%-Pu%~R4(1ELA?1IjyF;AqgMA)FY`fnb4@b&( z`)zy(j$s_LZN~QzWOL=)D9pCmmTg12BjGV8yD~#G<@WiXq|X0jkpF}EpH2Ud|AF=2 z%KqC{>yjQm>;J|2e;8x^Pf3WLZe8*J$kuQ5)z9+3z&{9a|KAhf(rc~pci8_|_%HK|7QMI@xQzM|Bzz*=Lc}jtK|Rr_kRha?)yJ~7yj4kGWvqP8N1?t zt*D!UO!nXMzp#Jo|JTRJ=k*x>Ya98!_#r0zX~bmyk0MzASzrABC-|Q&p8q9XMhDQd zW5@h2iMk%hWd9oe$Nrx^jC@{>@qe2MI#|ek@k31b)0jy7ABQ2#|B(Ov=YJ3$ES~?h zx{O|+vS!EpuNAe){#E|R{=Y9$?c`tcy8hbK0|k7aI{(|v|2UMKT;=}H{PDlF{VM^4 z+6mZ%KLsBYLd&lXe+KOzR=NH+*?g^k0V=I6*Z&JuL~lQ$Dc1Ji?EeT+s{@(rU-kdk z|67%!pC{J%EBs%EfWuDUfbYZluR8wct6cx{gYl!cE}H*^DkA@nD1o*8H~cR|tqx?e ze;xnFnE$N`(!&#lzN zGKTX%kFa;ICUm>p`Jb$c$on1c?S-}dH=({P>h}HR&u95x*guIvtpC#&p|@+V^FL?@ z@)G`>@o1I!e~0;B@iR}dAL;ciPf}>+_5OZ(gSOt9vhGjoq{;r<`Jb@=D2$>1Y#yFs}U0449eMHnR@jqj@{@Wqs_d=PzRU7&D>|^t2MSt2infYJM|0(Z=fB1N52qpIew@mxZ;1EsM~=|_OIc8O0oW5D?YD3t??gm zAWiB=_3bA7DX;7Q3H}HF5BhD&zQ#c&{Nc2ZwEn}xvj6w`;=lRvCoEiU{*OkNF$x^ou&ek_ zBkFD-ll{Y=OZHF05c@xM^7DCNjlbeR>PkT8;#c?CCj8s}FP;B~{x31s|LWxD^J3Bc zUpiGp)*sU$Yx{5J|DC8c1DWi<<$q!SAt~d(K=Ur=k5=CQ$r^u!|I121o2K$-jg|?2 z%Io-F;7?iE|8ITqzjN(2zL3xb_kZcBB6<8=0p2o)iGP6!e+o4yrT%{b$NV25K#y01F1Y`1tBT0`qZ(vw{|*1QqSg&$vj3L<#r!`E zBG~`K^&g!y-9Gbr{-a3(os2)!e~VX{|2u#FzqS1<{=cjQ`Qwyd(`)wQt5Q}I{_6Q( z7Ob=Wk6YV*=532U|2t4c3<7leU~T`6{~w528pve-t^e=M{{n*kUy`DqTWkCk{;x6t zf1KWabRTTOUvvH=DC@uC`d=qPZx@R`|Iw);a{rhDS=)cZ|DC8c1DWi<<^TTtFUIp9 z8cDi;q|$eByeCe}znSyD)brn@%>P>-|Ia`FTlD$wo2tv`35FcvjQ>?pcLP1k|04dU z(3TR{|2hGByioZ6aJuH%J%Rt7)6;wMb}shmIXOcIfJ;^{=S=<@6aF-=>%R&AABScB z2lD?6^*`b4AKKi`r!4Po|Jr{xZRtk;;IEJ3z@s>eD6lA3Jr(l40oUyc7cDc673 zxBsItv}yJvN#1;G?f(@AqSlTAd=2@(YX5JJ^*_zvIrtlk?*BbhMI?NUzF6CTxBj0( z!>H3G*?-;h+i)P0{kQxt;(roGtK|Qo+~&Bm#$Vz8`p_-3eoNBBKh-}0@HL$Omg9dK z$9Vo@4*36X(fhvxRYdyNDU`MSH~c>kwKR~){#*VR^M3*n;~L|CNpLgV`k8;%iwT>L zYmq@LFd2W&!*G@R|4W$vLCfzXD=znI(H#FY;;*Uy#n;*YX9k0phFEm{m!2x3_m_Zg zZe98Ro~V_9Ci6d>|EK8x6*>BNpiJMoGjqem6PvLC|Htcpk<$M&!v4?AMvLe5U&0pL z|7S#((H977cE$fAqOJxq*+2ZbT>m}B`JceZ=an`7!hu|h`|0?V6cOi}_!e|8=JPaNLI{@9c*m+b8@Oz;0r? z5By}G?AhL@2jZVoav8)b&6* z`>*H!ZT=Uf`TpPb@_%cIE%(zHf7yY=zoNeT(D{D|_6YGm?h(xYT0{v5TXFrbH%S$d z{ROQuw*Q*{B~iBn>Fi&@{~n(IYvt$iN}}&(BS$u#x$mLFpN3-p?|^T!|KIZKKM0$w zF#cbW(ugjjBPgxe7XOckTATkT^FOrz+hqRdT!x+hJ!AaU4&-r-kC)rI>;MS(j>mtv zeE!4VWc`o%IL`g%s?UEEq*SXSGQLVjjP1YcYHLvo1L^F)=Ks$AZ%DBJhag2aw{FgV zR7z1*0R((U&;LpIgCgYpKeiYDTYmm;Z2ztQuMYhr*X))?$&btRbWd-&*6Q%5Tt5Gg z{a@VJyZF>~p8r>>B2EBA@ZsLr{*|Kc52UmIn*ZDVAL3Jp|8I}~`*K_2uBGqpMDCB* zfAqI`{%?8zzv}vr#rm(W%jg99tYXanFVMzx{--|0_+K9=M|Z~fOAZ81g6@i1g3TN2 z@K@A--zfh#B3tZY)%D*SRYcYwQYd5lulTY5nN&6p3^et2Z z1$dM1X61kN@t;ua{~mf|ll33}!2BQ1UT&rSZ%>!eB@|?0i~q-HV>X_+xma*`I|idA)@2{e%1NEvMM6$3o2!7|JD3o z7Iiz2&i>{3KlL#F-^$PBl`;Mj|KC2|;t0;JJ)74Ce8=;@SlFKb;QX7%sE_sE8}T{4 z_+;^)GzkwUe)LY>;gFuZB=mgf4(uiQ>Fxvb&*90BNfHF-qtA{BoD$myF?>$;M{+#D zSNrF`8&yQwA5tP?`>*)F5w&6LnfB_Ul4UU zkk0-U{159s|f@8?&JBtnPfZhYr^?IyY`pg-?Q zf9BJ?{vYOlwK8;dWsJYX|M?W~{c=kmO&4gucYOax!T)@d@jq864?lO+{NJh~^8bJe z7~6lv|E;Lyfpqq-;{Ooq|H^`N@QFm<#hF=qwIB0W*5OaP{r`&m4<2r_|I;6c|9!6h ze|G2p1G%YFJ`vd9hzvlne{}bZj`mZlUM|W=gf29=X_;O8d&&l{h_9xk7{h#IizvR9FI_Upq zH2|Q)p9X<&{wwft{lCoqFJZxY^FN1l8D|2GH?igaheVwZq_cn7|HJ$r_x~DFmE=|26otV59Nha(E^2e;LTNNB;2cI{YEqN7(=2lx*_+*PodGllAhyN|%xR zdu(Eh|5c){2h!QUg8wPS`hT_foc=V%f53rMsUO9=>+pvPp924b|Ht!RPI0dv{>l1( z+s*$gNvTprB>o!n?oVU;2N`Gg-@Bfuy92Q^xW6~bBcfZtI&W~@o)Bj^7=2E?EkX-`cIm^6@i?OM-1D4Q2+V(`!u*f>9?bu@SN~;s{6AU$U%T@^g)ZYPuy4e+=6?!NHv{SHU-JLZ|ARpP zC(Qq8MCk0o7=Ouultq`W*}u&@>+o0g|IN?;Zjb*Pku7$y>i#c{DkAF-DU`AOSL^>q z)QW+0_FwaVGyjK%J|6PFLX!4BGR9xx|GW~=uB!Z5zON2{%FFqGi$7&~|4-z9`*@2_ zBy`37UplIY9zaCCyf?Q0ivK&JmIl(f6$NfJ^jxIhh#$V$9awY&N@41Aoxc^sI7106oaIbHS?Z4vxuBhv0*FRt7|2F>{ z+JA3y|G$>lazBmnm-xR7{p_w;5?{-I2jD-N|55XQC;r2?S^sA_-0P86&Hr6hMEhqzvTb(N{~M;={>$DKYkbbs>5GC|I55>*8j0? z-r5JQ`uuNC6>$=vQU+uDul#>c)WSeI`>*|fXZ`09?EfK1(ao(f{u2KenSi}U&^r7T z=Rdpv*MDvR7oNK6^B;{WBKHfhH@C+2U-5q<>gLJqh0gwK{_n2;!cETqTbV6&CDV6y zenwWyzv=V8o+}_J5XN|E>D`_e0iY^aOoovBv+hsGEUa<^Oj6PeCm)|F;pK z!wZT3PnUa|JR|tOYkYZ&zF)Ire2uQq0pOOD;hf2Dqr;zuW&O9-|A&5_|AGAPzWxt^ zvzPDj^T&UZ@AvHE{hx9=*Z85`<9bQb=JxO7M^%=3@E3o-XD1$nNf_?W(a}G65esM% z?ZX3&f|wjhcnZS9VP8GKY5aLqm(dgChFneGM|dV$bK+tBr$y?28T)_9 zfylL^0AEf1FB9rNF`wi455bDu+^)L+cV88e@D(a#Z2wLCf4E|t&i-ruZ|DES$M}x| zEC(MM<1g`ldFU2ezeVxszr`B^zMAvj;{2b6dH>h#&Ho($ta;?B_kVk;i1e>g1!McK z_`fG=VIZCT*ZkkE{|U?(G5#Zn(aEiw^>-Oe*uCGg1ggirihtw(IfnvZTf|=xXqgsI znXDtm_AmK=X!#u_+3k7Hs^gzp{OkH(4gL&1LH|D)%Q;_Ib^Vu)Dx&vSYlpG@SN^{v zYH6U!{14$D%>R%ZmwKVe(w|Jm1N z^aZUo+v5K*+R)iQ{9dqs8g6s{r^RKdD`Wgy2Xf1vm-Fu^yFv26HH{x>{cq6uf67bz z+x0)CSpTJ#p4*>4!S18QV!|B|TXfpqp?^M7mqK5PJNG5#aV&h;lPefKj# z@_g>>gUzhG{moOK2G zYvMm*{a-M7%>P{^|BtXW&i^Y`Q|U6&e$R%D=l>(3t_RZDe?9;2?Eg`$|6WVa?N4L; zWd{=fiu&$D=l>ztBgFr>M>aYCcZ;>^>4dGg{@0tNipc(g5*XWm&Hs|9+ktfUui$?V z&;PaZb9p7vce9Zr8_(SL(BV%*vHv%ef`s$`82?!y{=*LIJ^weN%jgJ7YqrJzBcj&k z|H=Fh?fRYaxP1P@$NEo`aGd+ct3LlxkW#IR z$oMK9F}D9|{@)X|Fp$pvYyR)-|Au6f@qa;Z3*5Rn|4}JLRRs|69X; zt+D+pMcp4rXa6<-xBEZDr&#~LFGNRoEq!+VRjdl1d>c9K`CeQyhB3tZY)%D*S zRYcYwQYd5lulTY5nN&6p3^et2Z1$dM1X61kN@t?5F|84OP zJ*@v;720)N|AoNW%dOP^?ddYQgn~?L@&6cYOy_^@QRIL7WUqLlG5(SRDJ#J}lK7fN z=Qw$0cOCw9{%_0w9Q*$%uyXOys`GzkRYcYol*rirtNFhy>UJQV{mb)z>TPrXx0Tsa zS8DtZrz1Zn-Z5f+$|DxDLlp0Q8YO9Z+@E6tyM{s4z@ftb_VE@+uw(sHyg1-Hn*UYf zU+n*Q{2L^H2e|%I2-3kvw(##9wT8;i{dO_a>_5k!75%?F2mfB_ksqM6cUYpPKjR`< zLHdL01NgJ^dFYq$Is7Bxzi-XYa$j^}Kb4W^JNlJn{FChM9p-#}1pv8B$p0zuhXSBj z|HBdFy~kR{zb>`VTWU|opYv^=|L;Mw$c-)h)1T?>3ALV8{{j9l`TuhLSNMOQWBqTH zD6M~H3;%Nd-)sBayz317i~bMj{~HlHyRe0SPWe{AXP9>%YzzM) z)n?$x=&Sg*>p#XY|F4UZyK5Q$(aw$&0t3(Z@xPJ`Ud6xh|1@NI|DVa!qck=%4=l2k1vz|D}xo z5j_991o}hy|I_UsXaLH_sV4x_?H^hOL;Z)<^8X6$|KpKH@Ape~zdWG?c=p!S-dFK& z{6C{4!1!NRgwC#I{A$LnY`~53xQYhZ|DDv25Ywe75e{j{-*#R zbaFcWQ2rnD|7KY8^sm^m{}O-oDsVdfkd=n^KOM{&->`-MKW zKf7ZA{uA>*i63J8w<|(t*S7F4{J(nnH)H)zh==(<&M@yj*cSdJ_3y43ocrrOKfj*A z|In6t#qs|Y#{Zi5uYv@QC11tAGymt9|EH3q{m*3l zE${!rl5jfy9$WSPpM4l-KT*bi-u`c%|MBCJ-s5}nwop} zkC*5FUH;Ge|6u-?U`P&bZQ1`LJHx5Z%i9HVKW@?S_cLt|f=su68hSYYbAYz|$SO-q zDBtt_njPb7{}iwM-B&+(75~=$dB8Wh|F3Wi)7i@srv!wO-{db*?zZV9ZU&TaVy8nmtf87838u;I` z=j9AXes0k{y{7S_$oTKS`gHyW^GE*g3(?VCfBjdN@A>2Fzi^xTe_wa~SLS~!$RG4} zI{(u!!1#|Ni|;?y*8ER>^0yr8RsL_|{}c*9WBj*|l$$$S_&4)Eg(6JHpThzG@pprG z|EX5-uUjqmhj^L)+xRb`SpQEDqN7_|_z!IT6>t5j{kQmg9P>XN0WLh&7=PRSKa2V= z1nYkaIJvqN`hPS3z^NaE`_Kzchs5Iv@eV#uPeFV*g=s<$38Vhu?8V;U5T8hVJi$fC z4qgy4atL>iBzsT)>Ax-5f5`c_+z*67Q2d1cKIKpNEdQ^q|3m3FkCTAF{Ezn^%U_DXK(*M@NoX;M&E^}8uNb}{};MGwgZ8EPdxwOb3Fgw%FpGM!2iJV0X-6S z3ZOA?ay@cVZCU)VqP|5Aw2$wv}>k2Ug|`35@wPXk)u-^PCf;zRz=ubJab`r5|h zKZH^H{hvPz|0{JFeL>egZ1KNR)YU*b`>*-GwSVmY*Tu-?^$`Cn8~M$A5gqc)N0OJ41{|oRx_g2sUN?k@TP*}5N{#S}xXa6$)WB=coR6F@=PUEjtJy5`RH2yE) z5A5%6a{uQG=Kn|&h;k>en-8kPA9ni${}1WEn~eWWHecf}5K7kT|65f=Z$F?Zw)}r9 zYIz`?{Y(7MeC+=%i_ys^#`sJ8pHBh%oxlO#(fmJ||CvWN8UJ$v@#sTW&Ht?`BL5F) zfwBEp`~R(|<$-keFY`Z~|Ht~@vLGFNBGGq|$geoWX)v}9e`xks%>Uc`uK+dxG5+_A z7l{8czTWx&fG(pmXpPwx{||_|7)WRTHUGEv&luMKZZUFswm2w{wE&-)ZtHgng7wT{~+)Gy}kWEmzJ)6Hle|K{eNE-(d{c#z?T2-i@HCM z&i-ZpPXfNl{hz+vmbi1{|Er)t)eC4c{yd=mChPw%@BgLsKS1~RcV{&Kpu?X+Q3#*G znGZrjQk?$?;ChSxe|h`~Te1Hi(q)_pG_J*#{~r={K9J7-W&h6tx=H?TU|Hh3_V@_!Tm zW0V9~|Gg_hXV*^Gf6xvlfAPuqbI^oM;=la-Uvl4X+0Qgee#H0hX%s^pc0x%S8(UR;Mi4a)Q1E&jad|AX`YCGtO4eEv(N z%Siq`DzU}?DpA)1>Fi(Te;Q)_?^=3pe;VUI;6SR>kK*NZ_)}ii|7-c5(LDaQJ^pu# zwd(1Ft~md{B&A9fk@!{k8{0p~IJ^Je^+YWUq_cmS{|WPO{x3+;&8^e@pT{$P!fOM* zFV+8O_@DD4{=dETpCT~mxc`Gw{L^2B2E2-Yv;ULVf5Gz~AUpT#KWX|_1aj1UeCINd z4u7cjFXVsb8^Bwf|1EF-(B@~o`9BI>##vzBifzsR6ryeh(%C=!Ua)`22;3z9HzHf? z!We(afs~biuGzoMzd?t8-T$TW{}BFXxc+bC=kj9J{a+eYMAjeDA!GZm*8h#D6$9z) zzvlne{{0~D|F^yMpF((!KQhK&;{Uu7(5|ZdS-!jue<=1X=KuJ4|38fXItCvePoUrO zd;I+IpR$klKczW0>yR=nsk#08_;D|nrGvki7O(inY5bw1is%8Oac-pXYxf|GPlE|IAhMe@7LO|NFSt zcgFVLoBwyMz8*oJ|E1#pZJz&WWwz9nG5!+&7m>hj`iDOMkBj+#Pyia^KZ~dUp)2nH z)m24w07`u_w*QL%yP_5c(#`*B{%`jGq57+b^?yY%I=MH-U*i8V^s~EWNqj9|8Sov= z|DYBBxA8wlDAs>)g17k4Rr7yW6_Ni%=!<(}`>*)FD{65do&DGR-`YPR4C6nd7@gc3 z<1g`lF%!rSkTz=oz*iIh5$nIPaGU)foj^SL&{fy}5mga=fTRn?_TQcV_pH7hN@xEy z|99s9VF>YG-2ZK5=<3QCe~JI=+HdU}>+q*ggEFuG+4_H25n%kM6`;c_SZJ+x|F>2} zn7~6m4|9heq z2GZGo?f*OLKNe#D4?&7m?6!Z`@BgzAFy+%Lf1 z+#1_|#s7_{n?3zjFU?-2WGh$kFYp`+xUU5eZ+RL&o;s#Q$Sx z7|26-&^M4XhtpC&(qN6)w{3ZS`58XoRwiyrIDkA;&kuUFz?Z4vxo~VU^boO8Kf4lyN;~&WXf)w4{y2<}4iwW)C z?^yzKfywyuz$2TC|1BT?H@1Jt|3k~~D9LWmdsZF))Z(wI|FzBiA2&8W_S99^f9a?q zdVjSR7~6m4|2v|V2Aa(O`T2kJ|B@VCd?3+xb7rm|JTVy?@E?!=1-yv=w(Eab{%>;q zx4i#{nf7}3e+G0JeL-u@w)lTQ)WtwL`-k5P_RnBSxW)4yEiOx48ROqNkX!b=oPS5z z4Uz}0Y5Yj*e}m5dQ!eiRF%Rp%)Y5bNlh8Hx|CCe_DStqXjO||%wLFl{{%ij4#Q(g! z|IhZ$|H-m*{YgvT{Y;QNpF8__{;B-0FhBo``Cpj-bw2s0*bCBg64JywoQ@|iiTB=- zg$M4P58)|12lT-B+1q<(cGx|V>^-fj*zc;WCbcf3OUSv!oz=I@HJ$%?xXt=s%dh_> z{s)D4{JXOk`~fup{5A0(vHmaMzsdd|%i~Yj8t4BNtEqGuX}=|AxH6vqkBGV+NN4}` z{J)L=(lFTM{%RYaxOD!5 z23Y@T3XHS=cGc%U3R0?75gA{lB*yk%&HsC%76#JUf6f1${a+el{|`ZmZf@P2|EQFr zssaf3j-LOM@aH_(X8+&ijUNJ&KQ5mfxt=7-S>O;@!q_#4u7hs z|BmOs8}T{4SatpPMir6uhm^?J{ww}(M6DP|XaDv5zm5O-JjC-K3Q5}kNTP3{5-7l% zd^aoqtB?PLW&UrAKcxS{`G1`MFEIayvzJ?`|J&1LbO{BS*y8^&+L+G&TkZd6pWk&) zG{#?YAY~=EM-pGt=o}}{?5@MV&i`%sAM$@O{;z}1%I{a5|0}B^vc8}-#`a&$|7B6P z1L^Evp8rEd2#o)<0(5v~jK9SHw~x0tg4*@Z=B)wW@%*nU{!{G#Q2Nbdkp8vF^M8%V z7Q6Ul@gL?NIQ4^YA9}&*ka#>H-ofYTDToiJFiq$oVbnjIz1TY(;uDGCJow=dvV#|d zj2y!L`R_&*k@kla%GmxZ{%=IB7)WRTHUGEv?+1DPAFThO5T@miB>L{uOn?&ZMtz^^ zR|R}W^S@{y=Klu7-(>#p^7&tc(a#kBvCIETT}EF}+hzXK7XK?nT@9qO|C;~X^*FxQ@*8h|6=Nz_#w;2Chp8t*QUz$LaJAu{F z&q5sm-|_t)ss1}0|J`K#Z>sg0|NSSc|8AFmT2(}EKcFhc_TSt8?^=C3pw9j!{%3v| zZj%37nJsl?jK9SH`4sT|a!Vgg7ihqDeE&z{f98=*#{ZT=y&Y%O{NJh~@_!3?@5e1zbokRCX#0Pc>;C}P|93b;x5WBCSa1I4kS^m)pm8m>{Qr=s^MQ2sFZ+L> z|4s6L1IuC;#`sGP@Ape~zdV0OP!>v$KjkI=H|u}K35We(T>rUO148%f<$qBX zk@qEqGPeI}sxOMVJJ75A-`YPVdH)w&|8=G4=Gy7{@9pwZ{r}qkH~7Qyk8KkF&#?Z> ze&*d9;`{eBil5Qt?d|gY9zRn9_0!pZz5Z+PXF|eqE@DaxUYWcbRX^j7X1F2F!idWa+PpkglE&nqp34;4S zE^)7){mJ@&+s*$gNvTprB>o!n?oVU;2N`Gg-@Bfuy92+o0f|1I)=F#od=p|gus_kU?r5m|pofsF0H zTK_kqRt%)G|C;}s`9Jdm4)H(ae}ynDe`Jim#Q%9Epj}n@vwT?{{!r|@nE$u|f@8;uDkHJ=Kqc=BLDYsukVcQzc>HyT75mj>-&H0{EztI zCeQ!0GF$4(7=MZXi%4KM{X@I|OSzc;2L+%p|6>sqAauq3zq+c34nV0-#`a(Fe^=Dv zK)U%~J^yd>zX-|K{}}%fC29ZO7=MZX%h1p6nkDhId|$wKH2Z2uMicSS7@q_h8;|6BWq_P-ea5yj}_-WY$0|BIPGe(0`Q0|367 z_>WlsjfHvrKaBr4fxP$7RoDL!RS|ta5BvPa*#5io|DM&iL+R|l=Ks$8Kip>h?^b3@ zT^Zvq@qb zWCHdYLF@2Wod4hy*ME)>?>~0c=RX=%MDFjS-rgD8f5rcesM|+3S33Kz`MF+7ZU%UF84HfM(}^v`0^Hgzh=ky8eO3Sz%41mIg{T_hd&LeaQ+X>6@t63& zJah}K-=g^R-{OSo|p6QD7!)Oz%`8@Y5m$d|4(^|f4lx?9@c-UrRVl1p=<2_DXAh- z{(u@8+rK1gc_5wr*ZkkwKTQ8Ox&JTA&h;lPefKj#@_g>>~uIC`8n~95%W_Xv6vmAc<<9FNz>#0923~Y4|b31C23sl%WAWQ{4cdGqf5w@#GTc* zDmb10dAQB`U(2umCH@D6c>KHTFPK{>XI%mQn)r`c|Cfi@|L?v?{vTm$oc~v>rqX4k z{hkdQ&;Lh6T@R$Q|9bx4=6_K*0D$#>Yx%kSX^g+@K;mCf-+k!(KMlnA9~6MW{a=@` zH_wJn>+3!L@g}JvvVVhod0}k-HUCSZZU@rYzk>fc?*F#(b9p7vce9Zr8_(SL(BV%* zvHy2SJUsum(ER_fUj84^Wpo6kHQVC<5m9UN|78B>B(ML9{Xgb1?(5$(#$W9~9@qGI zxt+@nfPn9K{D({DKWMPY{GZoxv-Ov&KL1gWQmu-}_$r+-w*PAW-xIYkkk0;V{_pJn z(h$%82~u=(>*oANr4&^aK)`qO{GWtBD8e@JUw;3`*#2ApUmf~MuGuY(k{_4r>7L$l zt<~XAxpe-YWB(U7_AWkko#+3Rs)!Q+5q!8ewtuCl`vd9hzvlmT|A+7p;{Uk*>&wx_ zT}$8HiQFHr|H$+GKg|DHApdL4^&g8xNne-I3G`XSnEzj(jp_UkJHijUNJ&KQ5m zfxt=7U2*)sd1D>^s`~G^|KEtu>BXw+zc;FgtUshg#`a(FeNM{znph3za|t-sHPk`ConfCoJ=STl_I{#Nz zMPz+Jg^caLn*Yn9ZU@rYzdZkkiV#r$732S{3|(ER@joTOA*SIuVtWGr=X)$Vo=!oO z@-U7WNe{kvEDs$2Q{jL6c#9**SbZv96!0C*|Elm08S$a-yZ6mwK4+V(|ELh2mI&wPUCzZ@f9KHf6^N%r;*bH2U;%)lSY5+MICp#YZguS+fT zmfF+t&sPG7|5w64{h8jLQ0rOsAK*XL|9e>fTO~>RpV_kia{k|I``oLMpZfXvZ{+{36y03Q_}iNQRTBef1UVglkFL7^ryD3|Jyphk z;xxa8Is2I(`i$@2(|e$EPRHNp*#A!j1oOWfpe;Yr7XGFEL&WD& z*8J>_1o*#F|E;tCL%a~@|6LI}yS9aY;s4dkzZvU);uGBebB1~M!M5-(segCP;M`yL z`T6w>{`a@p|4Wpd`+HmXH}PKu2h8CA5YPX*$37py7XJ0#uj;L*37_Tvb@G47H;+>mgFtqcR zb=QC8`5y)OgWgW(e;Q!_2S)}kJl59yPkr*g9BexOQ>X@k>wf_%C%3lnZ{~jrMVO91 z_bJZ*o!~7#)GGdUt94HJR!*8dcQ=;+oK{sUY8#amCee~)4Qw zkpH*6{+}+9RzKTG|L529mHu&)e^^aQ8&$-%Z))`Io&Rb?tr$pW|M2@x{xA0bQi#ym zM-qLHHS(JI7CQe=11jJj`1$&Od;Gur{vTn~e*fps!v9KLMqki1W8?W>}X~0DOClvO7k^kMF{~(O5p8o}1MhDQbW6S(6h`Jm| zXa5TR$Nrxkj9gw0@qe8O*bC&&d=VY~G$aE5L;JT)#()06{GWTP=YORxqZcTw*)snt zMXj@cng6l>?@X$l{57ZX*Qy>U;5+U7Cl5Tf$@8Df^S`nEOB0B4C$O6jro$hqJq!My z26_MY?XCYNo3HT~pwq&7{eP>9=hpw9cTUA8*)F6}3E&&i)nr4*?)N|0PS( z{wETB7m3`8LtF+!>+px!zA*m}CE%zI{mcHJ@Bq($_J!!^&W-=Cl%nbdG#UTA z#P}xH|I7P-$$bNKkAHVo0{}YwDHMhGq2doCK8MPW-#iv@T>mdq|Cz8A`~M+b#+g9l zO>Fu9AyMZ8>Fi(j|BPe&w}Hs%g)#n;1Iar<)!pDCC<~#-pK_7^xu4g6-(LLJDb&h` z6S`mT`cG6vWiZ84m6$rq5TWif9i_R*|pR4AGCwWUwktD9Lhgz693Pz z{>y%*QSu|ce@~+rnz_BbUA{wGcSTw6mvvHyziR!*p#SUk{QvU!uY3JBpvyQ9Xl&P( z|9=}0^(_8pu>XVopBs!^UP#NgVk7^Vyzj;$`qSP>&;L^X4+%H9{`(XBzxMneS(lOi zTl=-e|6{bFvw!%#5dUY?+vNPe#bv20iT}$$u08UHe~1o$8i?n=dC~s^=l@I8|5)+) zFO@DM`S+;B7XPb6T@R$Qf0_TGmpIP7$Q;9hL7o_Os*6IGw;~78U zwE^FW^*^Bf`zFtSEFb@Iihug6(17XqL;aUc=Kn5_zchU-0y*kFo^u&Uhd&K`Vg3i{ zU%3AVwzI_g-(T<41MO zr~Jjp^Bw)l7=MZX^GZOws`6*~(jb@7^*`m}{2$uCVf`;hi1!~$=!*NlbW{;NKp*$| z&e;Ad{_lvoesuFwXa6~G_RoJ3kHG#9@_!>jXBWo!OZ;EX1R!Pnl2vaF_-g7u%KV?_ zf8zY#8SKr2SIz$&RYd-8An#om+kbEV-?jR77@hsA_&>z`zgB<_uZ;1R_`iq*cGExd zC6R{z#r!|!dzk;>5cT%ags!;%S63C$0koiZu8i%!;{UFwJC83eb@s2|f1hCdM--!z zdt>}1{x3s6yK9!j*YcGCUrqc+=6|}$^FK}?9)0Ml`M;}*$p1<`Ft-1S|GT0V2h!QU zivJnLe?&1lxi`jN;{ReMkRQ5h)&PL7X8teM|6xJ6$@=e3ARc|_s_XxVs)#;7(gkDt z@6P{wR^JY#vwsEuhuh5mZDqF9l`;Mj|JSwO+JAu#e+o4y`};o>N`Yehzm=f7D?(S? z|F>2}O>4c>Yr*O6#9FJ^xW9flkIBs{c?t|FcN`zp?#G z{y(n-`Qwt_<9qVsccHI3{8i_Fw^{$kx_N6Kxa#x2Jypa>fJzyR?Z5K>Jy8n->Fi(j z|Acv)JpU&MZh>24{3ZS`G6B2o-}U?dER@!N6xV+Xp#Rrjt3LnHs3LOzkTMzDf5rce zs1*ZE=YO9u-2Ycd(*8#>eP`!qWVQU8p8w_Z-+>4CZ!iB>aWLrb_v~~^fO@S5n(Gn|NW4489hOtL#*+?Eb3;U>HH5BAvPKRX+*Zz zg~b1-%RNn=5&YjZzPv@>uh}uaMpx(na7)T?&g6H~;ZH-c{#(fN{14>+1?qpm*~|C% z`Qtyc&Z`>xtACuvpGS2WJwa~B)%1N-Qj-l{_=3*<;rGJ&pL4!R{x`NKLGt=rWB)HX z5V>{~;H%01mHU6wP4d4UFWY~y>i*w-RYby9D37uIH}U@%8b)1i(ed{)ee?&?*}u&H z1PVan`R~3I-P{@DFY$kQ=oVVPMe*ss#Y+Ren*3jJ{?91G^B-;i7oNK6{okG{BK-@{ z7q`atU-5rW)WSeI`&aNk<+%SVNYTx$oAq}YOxV5OvjpY>lkw*~*k=E?HLU-j<#&{1 zx92^pj(=+LSJnRt@cfS(gBPB<>iRDoRYdPE0AJnO^8X!CO9M^je>nYt`CpP4oqQlI z-tDHt@Hm-?O(tjG6FFESBuZ-Pw2F@!u@|q z6_N4>w8+^0B~i-*>Fi(Te?MfK-2azl=lYYDzWbRVc|Ldc@%&TyUqOEU7w7*A%>Or# z|E1PtbO|}PxU>3}xu)|!54KtVYx(%U#Q&fWkAHXef(3t_RZDzdZk^L5TDJT6|7_8sjfJkoZ^BcON?cPXjUj z!9sV>F`#*&~*<}2G`Sl-RhxPLRh%TceD6QER|Br}ToBt>CKldr-|IVe`N#8TZ zU+q91*Z6q3oy!h@fbV$xhfC)_NPzSINjT2^<5i#kC`hSRMPz)Hju_j2HUIC4S{O)Y z|1$p*5@P<3AVoK~Zq9#HN>Nn-1bj!&|4I0BDF3lZ{Fk5q8{2>D|Eohk$u+yBQS#$* zJ>An=uC+S+;jovG|HT;ge{o~);#1do{$HtzH~|pBhkIlDSBknnkk0-k{%3^yF#pH? z?|nJCxNGUVJCXb2^&eTj|A+Zs3*>*Tx&C9ZDCz4mI)Ofm81w%Nv@xCk;Y2XT|N1~V zx--UKav*RLboc$9eY`hstizuw>c8Xp??!x1FIHXuy-`JE{UIeXw*QL%8&N9;(%HW} z|M!deAI|?3!nFL6MBhRsP=Gi2ZdU$RAO8u({NIpa5l{$B*nUT&rSZ%>!eB@|3z zi~q-HV>{3Ka_vP`oFCJ9bOsZFY*8F<1LP0?)q%r8t@&@|LVnm z$TyEcN7zly{~M7lcJbNbKVFg?`6=ZIiD`P~N$dwFZ=aBa(nGwb)QfrGbN@(^!(pEu zdCIuA4}E@uj?sST?H)3Uo&OK$GCG5nNo?`|fT)XsboMXvKO}%+ z{f`zThgWXmf6w^wzh?Tc<$no(&irlWe=pDf#`Z5wAo}$-a@Ez@f4}}$?*F0u+a~k> z92)w-qd!~yFRT8)uZrmQ6*^#S|JC}hFY5k4I{TOSAL@VQ^*=EF*O#MH6>O@>2bO$^V1* z$Nt~T^naGz_gnTe@7@sKzo$|Bj4p3)m+$xZnHs2{&i<9_Kj{Clz5LJR*Z+jAdHpw_ z%Qz2ctl5_TAEOPO{Y(6x@BeJF|4#$UViyws7aRG{b5RX1|)%@R8MdW{_9vIty#s6JV ziv#KGU&a3n<3FMpo!lGaFY$jd6UYycHfsRDR}=pc>;JGYum6kjA19Fa9=huKKcXt4 z59nc^-x%9}cmChA`gSOt{VVuC%=iDW{!c4IS69aPOZ;Egerw-Yhd+fHBtG*)MxgAQ zU*!L=JpUj0zm=h@D?+{X?*G=Rh`c|jKF0Q6@qaC9)j&G?m-*j^3{d2Ml`L(4=JfnW zl>|B&f2jYq&H8`KpZ_qnf64#nl^}my(tCVQe*7-fRfoUo{O>mF|Jb%}{qt6R{pwS` zcb~fI^B;{WBKLPO?_L|*f5rcesJjoZuBY?A5BtTq{!@t3`bRQ-XXj^Rwfvi&|K;=F zfd}|+kN=ln{}Hz0^WP6ym(dgSDa0E8%c5=un$G`F5fb-*8u_`rkof;}xu?l9g8#e5 zm$&HqH9N-F=n5SGZb=!=nfy9B{AnoGe+zk@|AG9!K>cSpd-)zefBYx;e$PJM|0$<) zjUU=Qu9qZjZvQ@hRBb5+fARNwb~+`&A*SIuVtWGr=X)$Vo=!oO@-U7WNe{kvtRCPr z{yeJ7=m~N|uBPv!lA3Jr!WVS*55E`I|C|S#sO|vIK^7>n2|1UWZxpoxbtI7Xm z0{)ECP4d4UFWY~y>i*w-RYby9D37uIH}U`Big7yom-(MS1xSqlDB!aGkum-f|Cfhu zq4ir7pZ;6CFW{@m{}t!|j50j`VI6DL16RHO+fzlPe--}5_FwUTPt?LdI{R1fKePl! z{ujjP~@<2NK zm-*ih`6l=OW!bs@q^0kECP<#ooqat2RQ^|xpZ~@AKjweMesJ>k2}vkD#CuA;7*d0{ zeo1sFC}&*({+jrYSpSy?*#ECev~`^SN7x$Y{}rpLbQx*CXT!$x{}EBw z1L^Evp8wO(!})(LKBqs8@s}M){446a51s#~ff)bc%)|5FPI0dv4wV*G82_nBNm4~* z{~Gw}r?LGHin<+0Xa5TRhaF(d|7s=Z?n3_p~D|m?n3-O^vNdU|I4rc z2s^Bo|3`Eg9YJZ$w)lTU)Y|+%ng6*@G5>ci-A?+RG5%@?^0>yw%k5lt00ex;<3C(F z|3UKlkK2p?PlC?MAFulSM?p%pDk9^n6vo*8tNDLV)WSeI`pcIjR7IQsC{)VW{*|Kc52UkyiT@da1t7-%`ciar*V1=)BKODZKl0rFZ*Tp# zy#HTy{l{Wa(${5l0(~Aa=KmLHV>MM?McodhvwwO1Pnn1H ze_H`MyehYEv~3^K$1^>jv-`7Z+fbo?`*@2Zn5RBR-w^N}&Ht+K4+x}v5RZTJNa%n0 z`v0R6`8>Zg!@qOD8tOd{HHmrA{#*R1&-41vUwZxjc_1Er6D#;9+1oqJ^oA5*2L8d9 zum3bE_0RK5EBMz176yVv)A6VNm*4;YJP?n*i5dRs&-C_$3eV&J0sa&HKa_t!{vQ{r zrQe$2U(Wh_&7KwSH68zue);;Z3ORhnkIeATiU0j_OCO^gFq!{JSj2yD{^wp}%|E>&OU7Oi|Keuk^9Vg>YJ@Vz_ ze?!9fWS^Vie~vHLl~HEN1w_f**>-=_UZv?VtGh z{vYE1+428)q|y8RlHD&)DEythso!@x{vQAG=Reh=_;jBd+W)0E;HUXlO~#+_kbm{_ zKN^{QmR}pQE(E6I5Ac8W{vR5-f0kdH;a|@GM!|=F=nVWRMf+bwMQq@&TLn(WAF6*)wEr)F zzc%%!_%{IlQ}_QM9DwV;B^1ES{?lKtXuu5oS$_Tl*Z=M{zzqLl{?{+|ta#@_>pNJQnR^1t)^ zkDWFN4CFC!{ueTa@xMh>!3_VB{~_XcDJ#9IUm5U!rT$-M|1abouK(1+wEU?V{>A)H zxBQ{lor0?*A^MB4+sK z`9D7{={>$DKYkCs@MQd{=jHu>(EksL)Y>o1@GsW?y5m2y_&>n;zcq}<9%P7rc7{{G zm$wV#{@kMD?`Nty6foWXeP~O9_CJQ3t>2m9FI0W{e$9^YRXPSf9e+3gg!tQnc=S1D z_-nWRim?IzY4Km``2p_#j^bwPmuC2v@!vP#{`mCY!B?7W|B(NK=f4NVY4sN~@h`u& zd%tIi%H#K6aXS71^^pJjLUeRD6aQ-e4~~39Np^eQvyt%cocTY;^`9fag~yuV-^71M zCj!&`Kcs(P{9i!I&FxJ5Z`t#5h9h6M=$>BF_#vG5=)dc9{)hSx$p3vII=Y*Qf5rcu z`fu6CGqSh-EBe1;{Lc~SoyVHtU!MOMC4U%e2LD3^K#c!%U~+b8h=2Sm(xG4AKP~@< z`X0vroI&1uuo?c%{BN`bz-0dqyZ;>be|k8%y0L(N-C%q06({pQ#DB2<1GZ}w~{}aStB}(g`&BVW2{}uk<#s05P z(f?b=TJ=CP{KvY!wWCe8e>nez>%UfhF0ah+7xw>w6;#dQCgV?`03fdaUBccx+YJA5 z|F_%gsd;ecVmkglBmiLirz1jVm*%HDtEs?9D;U6kTK)&rg8cIJU*{Q?Gn|JWYz_a; zR)E+F=2iRWEaU+EH_#U1ef9c}8^vGM3T6iWU%&p-7s0<`?~e868+ij0xgiqKd_En2 z4*z{||343Z)sg#YRTnf^h`&zmm%sB2o*23~+)sZWW41q#{gLqa9PE?ycnZTb@OZ)# zdL%4l$K=GqBNS+4G-aoQPxzU|dlGta;)N%=dnDO=`cHop!2k9751f4SIE4t{it9h9 zP_Alb82jVC=(Tso<5jH#$ErX8{x8k{TK^C2pV0qnv9s|9*6=Tvg31kn8Tdm1h)wdp z4M-k)z|;?{Qo5EZ2Yk`{EMvsbsE5{_&5Du!CUxWJ^uf7@wYV- z`KN9Y^eX<1|ED2aHUHZ%TuaZeX8$9*fUn};+CK@t{QmFPwf|}B&3gVnQUPW<{wp~D zyEXhFWHi(YXgdBZ@V|Qf=dIyC(hBHR{2TwzDEadBzqf`z90DGil1#_nV_!c1V{`Zq zw*s1uKMUwrKmWHi{F|cy@{>oM0bo|NJ@Bh9v{vT-s1NgtR|JU-r$G?32pRM6P(hBHR{M+@P?|=FF-&@1K znE$EI0>6rXYyXsg`Th@E!@u1BR~7_*75|R?2VegD&(`oCX$3PKf8u}n`d?eazqEg4 zD;U84rTP!8{X;7$jQT5U%vj+*6e?z70e9&Ctv>j-`4PN z;=eKnyvqOW{2v-XVg8p@o*#OcHU1xI1@kKYt^Wu2kNZDEkXilQ8ve?aU-_!j@%I?+ z|5=53@L|^QA87^iD*he*XY8xjf7zPl?cu=Z(L03aV{!~U8dE=J9-RCP*})6XAvvFYmcVgM zmZo94htA)o=RXP0&wuhf|7*4VzdzCW&;I%EdzxJ1%iB;P=b4K4s{Ob85A9#K>Hk%l zF)bf|B1RVHfARNJ-31KrpSb@I$G>s?HxbEse{K!`GXAe@1vK6M2_yjG`QLenPWzTQ z{^dk>tQF97{Jjv@f75WB``709S2Ku_RzTD7hy5S4|GAJ(`kpoXoA|$+12oG55UH_HmKVHT*|f0ljMfE&uz7ze~_N z&+f;6>ivKCRMp6*^SvpT)A8pl*rxq|-S|(r|Ep{TG#!7+0*wDpF|pZyYtH}wFaGs^ z)A*F$>q?MPCm_IoH2(v#y}tR-^QV~q<@sNXLIC)$Z@*^$t0sK$0}5B%VTzLej0X#t z<&T3uJD*3M3;#&??^{0EDSm#D{7*vZxA*w@<3ID>*PO5Zag%{q`L#wD|IY|?TmF@~ z{fm;8r3v;hYX7;t^zGk=2EdsABTLf$ClY&iIsu7&yylJ$_>P|crU5VTZ|DDkA42}$ z2KnEdPbY#z7+*90E43MYL8~Oz_+KgMavYug*ZkkwKlcA?5psJq#Q&;3eskYMhd-S5 z5&0h)j3NI&)AKpIKb_|P6BcZM{{?ME2hf?w--bFUuOc^ zhy?5Khior_|3jYFf5!R$0{jpCHdh?~>C8%{zP9KEfLf~P&6u2w2}o!EGXG=$-@lLP zV~P2n)97ni6YS;u-)8;C1G3TjPm9NY%;$g745Hi#?Bs)ieUHX}Dfpl~{@dWsXqeak z*`WV7=Gv(bAe61y|F^n`-d?F1=JsFBAX-UFrh|=>HeTpRiT?{~>M0 znLy=Gtoi>TNy9ig`*EuqNF?HyvqNr{Zqnl{nwG8yGy6*Kj;RLzxibRA^mrw z{r?u9|4ZKcE&G{9$&dIB-CW{lba{Kbe21>yseuaW@UPc@t^EgqkL$lh^1s)<{u|I{ zoCj3)Y|a0V@P^L*;rBxRHw!k({{}2O@2e937yJ0nAyB%Yy3aL8#?>1`M{u<0oZNU|8R)3`q6~0I{&|9rAil(_+=e1w|@|EcK^NWNxDCd&i-ZoCr}a&=l^{v zy18?@{{uC%pYYm%?`Zu834iGR;B6EC#rJ=l;-CI1G~iYIoBf}>{tM#2$o`Ksdn@8N z$~K;}2}p-O4X7~xgZfP`{3Q=kRsywnpX zTKPQaw7us1|Bf!A2T&=1x&2rC-;uO9j?VsN{wIE3{|WhDl%)N8bNnU#FJ}UfGk(d+ z_XT`4^&chv_vtq4f7u6E^Td_&e@7RQ|5d7BZvVacf5+eoa^Jv&gaK@_(EE zOZ))Nf8hLI5Tt`!bNnU#FGD}OYnH^<;*A0S(fkkC?Gev^5r3QezYY){f8@&fzpIPL z{|Z$wxBrU&yOI{h(b>OZ{!a+Te*`JIxi!aM;{ReMke|7$*8n>IU)TR>{XYxoHqZaK z0eR%9E3f|}>LU68K^M&JzdQf$T75f^&i-ruZ|$Ez16Z8@w-R)BWsbkZ|8?!R=B0J` zQ!dtjpe)4vpGJNzF9=GJ`SCkfR~`O}^S{sm81sKEo4WFG zD?k6+(?y&F$SPoN|CRsmNxDCd&i-rv-&y~$jq-nAa0}d-<1g`lkqOvo|Na91`@;SY ztO#-aw*dNo{k8J*AB`>|_p8;(-2N;6ZzL^`^D6&$*M9`hf5?J#@QKXc+4&h+E&rzH zfBF1(ko*4)p8s2X{kQV--w#=v(GzrO#2)|4lCH;jmH*rMKa~H${hwNVPJc@Lf4bb$ z{I@v&XDpBZZ7}}h3g*G*u6+Nur;AAc zvKpA%f5rbjN%zOm*?-Od?fM_0k{JK#3(?V?o7Hz2OxV5OvjnCBlkw+)7i`o2uVMWM zEx)5AyFKq&dHhq0zoPz^&pq`2j!eGy*p=6R>F6SQ{~qwwjWz$@k+e9@Wd3Jx{vZ9n zC`Kpu((0`_G1m{Bm<$g1kH`OlP^kY7(S&i-ru@5KMSJpTvJe@ddX{z1#${Y;QNojdz@{;B-0 zFyH^f`Tqj_pEr^JrPgM231a@2T27{r#qab(k(`yx90X(;yp4vYH#82?!!{^PIt{NIQ+qa(=e*&6?kNLpwA^8B9@ zZ=3c1=2GnJ@0sJT_8^aIe7xMwMF&8@cRc>XrSl(@Z8QI8HjZ<@xbpKK1uNCMh>S05 ziMjn(@t>Zg`{U^BzvlnW{x6gS#r@yD6y4mpIsZ}VL{$V3@Etw>C*jX|u+jcsi^qS= z?Z5T^)v2H4n%&YU`Ej|P?&%HGR);_3()oV~0B&;s-;I%vJaw(-|CPFk697pQ%E1Xx`>*-GjsFqw0OUyJ+ymDhhPRwaFHMkmmv5_A67 z^Tu@khZ!Np|GF?ayEeyP@*r>$bXOD;tY2D(Kjm`$cec&?zqQB~`?>P^?~N`Z>#J4D z-2N;6ZzL^`qqF~d{@=#`e9rOwhb%}3pGfR2R00KblkaBbfA#U7Q0)I6vV8rA`~OSq z|F1g#YfPKbB>^&wYW7e6@|FHbuCjT$K{+IZ_tmF1b;%ge6f_3P^VKMlnE-@p&Ing6@^`j0UF zxz2ys<$t9%qc5l})8hFLrKHPoboO8Kf4lx?B=7%&^ZynnS64&)pJZ?E5dE>@0Cf06 zw@0D=AJl){=Kk*=;QwF){4Z!TI)H{wtj+%`{ud-&jH9!E1^-iu`9BRtE-!}ozpe!K zhxUJA17MT!pXK=<*4v+||Esh4e_qY9r_JaEfLQwbzc#JDWv%J_&*8sK&i@y;f8qR} zUF%DKrnl!K&;J>X|4aTK`ak*GJpZvc{^s*PX$Dd51XgE1a~9CyFV}yEiZI)Z|FxvK z(AB4_|8AFoT3tkMuhb}W`>$pYt)!)KboMXtKl8)9{vXEwBvD%bz#M;x|MN*C>>Z_# zx(hVmJ9_?K=6~q_vCa6Ob$oRXTsi-@x`_O*R0VVUulT=}v^0*+{$>6r9@*ynUy_~c z4(-%*Xxz8X~7Z-NgT%@#BB>>|f9SHU3Th2j6V}$Hn>I-2SB*M8DhyXs_1)ck6!@ z{+|V8oAv*l+PdoDpRWFwUH{+LMRfa;3Ygn}wf^f%x;Ku_{%ihk{XYpX|Dz{DXE$#A zf29-W_;O8d&&l{h^`AWdZ-epQ#r?nJy#YGt|7JA+pu?Yr;`xt&hgkpNvCR43{hITC zhO`-H0+mOx=KqHz4ddwSU-thj@L~UdgZ%Hz&ksG=9Dm7!d_2rm*?*Y%0g-J zr$Nd8&HA5lLNNbh2r{dm6Xva%|3zIy-WRmT-2SV%z9{L=IIr@5YyXtw^&gS{J3@4H z>2&?~cFD{CYx%$S{|)|}lMw4aEi(U?y!Tu7GjHAy-@m6({ERMdZ%ReQ#(6+x&({3^2yf`@U*dnpf;|5l{l5X}zW7y%|BHS6 zXY#%q3FyE0?E24i_$%^%C|Wu{HC*N}G}VJA`75|5cK%#+lCl zG{pL^9grMen&Us3 z%n4E4V^82?*5 z|IaD@>90ZqUd6xJ|AG211o2;F{U^=dia5^4BZloisQ-NY{Y=|~JToashd&LdF#qG6 zZnOUX;`aaP`v2OU|0%Q?XMtTawl@D$NV*Ed&;M?4|F0I=Vn0{j|E1AIWPPmKl7mg z{3h3bvhW;#VvfJW|9K^#T~+$CczYfGl$Z1W7Juf2SpUm0^5x?RU3LGLjxM4HP$`tT z{a5_ok+e9D&i-ZoCw^Z4X@l`UQFPAl&GDD`znlp`&iExOUmx(*)PI!t->2KG|K%KS zae$Tce@7RQ|3&Qcdvp8m&Hp=A-;SoUe-;08Jpa`S(BYLi{u2Kek-$#+#|!x%V*VfV zG5^CM=$%KyPWzhk|GK(}4xod3eQ9q075{f7T|d0~`6~Z6`~Qsi!8Xr-3X*ewYmUFf z|7GZBcg>RcTKoe5|Iz#p*zFPeKY{{mbN|;p;KB$i=l`xQBL556=eOqeU-5re(!w}8 z`&Z2W3BmY}AVoK~=J-qeU(5vZ6QuPTK!BUx`>kiSr^Rhzw-Y* zN%zOm*?;Z-JL^A&`~Q6zy1FyRU*i8F6R^|%{RRH_h5a8`5#svK7396=uKfH*ql?J> zJ=mKYbNjFOzmatF=;q>8{_n2;HhTWMk=argGJ9v|XJobfo1XvW^WQ=4|2KI4Z}Iit z%Flm4WNk)I(B&3;{4Yzo9_Lm5Z|DC|{s;GeYVkS!De?d5a!-?I1pjx9FK^NJYj%vU z(G@xX+>#=iGyQdR_|veg|JM5dFd$g}d6D`r1kPT*$Il=CNxt8+kN1Dd*<9m>c8}{N zNt@fhk4;t5;otoIo}IiC@dC;Yk#~;I5o0`w_6a>l0<5YwyhczvMxr;!%LFCjU1O>c6l6 z<3C#Hto*^s`+xU!5eZ+?8FTw@;{Sss<8=04^M5=4C&5Pde23 z#i#$~?+^HD&VP&Zf5!6o-v;A9uHlxRzw-Uxo-QK&OYqw}|Iw3le;l3t*ZkkE{~;=g z@t?jB9o@NEeV4(6-TOUDpnB|N{5h=uwt4=44eLK>`5h(M?Rn42wmpT+KlYqBNKc4FG;!?=hgXtYyXtt{%<2brxz0cH~ToU>CAl* z9sV>F`+tW;{eO)AED`_l*L?nOM4QnOB+%K;D{71n`wJsv#%UWV?|5f~_C+Yq;I{UBr zzq9`fB|&ljw=YFEcW%yqR60==0R((U&;LpIa}Eh$o5X+d_>Z~$xBkC6^^;t)TN))l zF4xmNy@A^5@TXil{|^DcP454@G4he8uJ!!CQWtRoAZdcR{VOHi8%JmVHUGEqKLRHJ zH_87!xh-+ivUhhL_s8o$a<UyJ+ymDhhPRwaFHMkmna6?6XA^Tu@khZ!Np|GF?a zyEeyP@*r>$bXOD;tY2D(Kjm`$cec&?zqQB~`?>P^?~N`Z>#J4D-2N;6ZzL^`qqF~d z{@=#`e9rOwhb%}3pGfR2R00KblkaBbfA#U7Q0)I6vV8rA`~OSq|F1g#YfPKbB>^&wYW7e6@|FHbuCjT$K{+IZ_tmF1b;%ge6YU1ZvivS{&W1P7k>5qzoh^)9Oni6 zlkDvsCW7-7Ug&Iy05lwD4FB|JdV50s zX4C%w|Ed1pBgp@zVt?uTWB8X-`fexZmmVqo(G2_p2K^t9|0Pjc|6mONocLkpmp%+R zU^@SMA^Ga-KkG=jdEglSMe>Y=W1!RVhw`6aJ^#ZET-Kke#QzyT{%2_O-4}fof0*f<{_i_XFv0E zobmm88pY4(^7eN5evhAZS%1xw+h0${ADU8q`TDQzKmSu-jp1Lc|DVbG&Tt_x1Aiz> zjrPBU;*8;6&i@R-hkwL${7Dde_4*G>fM;P0J^tpa!0Gt=Bt-kSfUf=#WB6N0{V84m z@SnE-=Z7pr``3eK;V;JUPk-6bfXVn1&-?QIzbs(0#3RP=FXn$1YC%uOpD>R5ze~XX z*BJig{{J=m*YHiIHA~&o3H%JSD1`H?Em8a-%Icr|Irx!dH%X2g-^GC!b8me|Ks=zMS;Fw zvtxXnaSV7m{)|(^|BvHuVe6;(Hvsdhr`I%oOwRc4zvpEB zCvg5B`M)njM|Vp6EB^1)|H?j|(UR-G0QdhK0WLgt4F59!-;n%ayy^T;0*>}CAm!$E z6#w{FWRCu<^B)xBe{S#=pE`zrGygL@0zTdUGvCAdUm{da?zQ;W&65pApU(e2!TNs& z0-x~JG5jrT{S|LA+5X}D59WVP0MX!2mH3w*?xaS(Gsl^XKk-?B`M)!zJ?(2s{Hyg} z;s5{4{x1vA|4##Q=3kHDKhn}`(rDA|-($G`n{f2u>TLNFs~KZbo?2`{NLG- zPWs{){^kC!c`Kml_~!*+F#bOY$eDj!;eSh?QF49!fBDZm?fV~_B|u-qKV&q7xF65! ze}*B}f18Quw66`}Un|1ya?bVPEq! zI{seh-e-*zR$?Bc0{~ zpzb^HKk5a)k$;6dt)zeg{;$-3=-5BB|H1sPxsXo!-VpvaQ^2O^e=PB?Hn{*2=K?_~&${%`{Rv%`VY@rUg{jQ<MWP;=dUGdlkj`fA=E(4gb#x2Lb-C#Q!?}pJDyy z`H)Wf=8O23e>U?(@C^R<1C0Mp!g1~&zleX!{~veQ2{~TV*Xh65_nEf;!#P3zp9|@v z@4bk>Q8;kA{WF5||4A^;{^J+%Z|wg)JCWnw^N(R1acI*MF}e?;eZbO)@$?k>J`LdT zR*xMf&Hs4#&4cc5A>@DI{BJg_FMshx{L4R^c_LuC{d3qc!ua3IAR7GPi}=s9|1xa& zBL40EA0-~{{~CZg;j1;U6=B!6e^2B2W_hR#4^GD)j(>gi{r>^OngGore_8`>j{k6Y za60}p^uGH3&*t#YBSR1%P-F`M{;$M;I{eT4eEs*O>wkR_{B!2+CH~tXgz{fs#fxUx zKjG`(|3x_5WRT>3`8%)i7GG}Z{q*OOm{;-Os(J*-D0dg!9nn zcI+Sb|C_pRx%dA<{=de5V#04r_?4?4*!nHh4&*WF|DoHT!2h8a!1?br^1tB!hs<)Z z&!N)F*X4hu?x7EuqCB}zJ|{J zW&e-)A6@ymytadXpZ_~s^2Mv`@W=cQmk5tP`_JTmSmghXvLX48W3BqF9sAGuzXOCY zkk0;P{>S_eL4?k3?cm?%|L&H2{^Ecy=6_U;wVVMt^TB^6|HC5xca;qtz+^43WB<+k zAM-ze?BKtzn*U+RFspd*ocxdC{5Q0T!t-Avh1DjT`QR7q*nh|Wi(B$AGT6iT{vYOl z4C3SZ7k?)I!xGI%X$Lp~n5r*!>|X++_Zm9;&;4b8{}1aw^aSYerp6!hKP>lud~N^F zA+oPM`p@KlSmJ+W+5!52*N`v&s05F%8cV z+Y|Ud-(%77bP6J<6c)#fqzB(SR;tv#J^1%m>VJid_?-A2)c=A0k5K<@1NDDeA^o8% z8D^0Q8pX)*Kb(&IoOnloKIIXM*&&MeK8=zzJ?_sjAv6f);$NQs$ooG-`wtHJ|A@bP zrFdQPR5_Y@vpC5 z|4S6YC%w0U|M1Rl`H!BCKb!#k>ha%CinsV$6a7CA!USrAUdA_dba056#JkF{&COFeoPomXUBh_@H}S$oCVF-{~q*z zME?JD_!DUT1_VH&@SMW@kAUlkn&5sEpY}1~v6t+<*oOW49PxJ#K8!HY{?9?29^wE{ zkA0TU{|~SdIMIm0q~PEujF97bF8)RQ*AEK&ht7g%|4S&qYs-}3_+JzMQD**?{{oo* zkLLfv@ALitHvR`q2FO?M|FQ&l7RE5ezx@7qW9S+2ACF`F&jPyoN0{P2p7~RbKOKMQ zB8&Lz!L#re6aD{*>>>O^g2-nlKVk`V0X`fd5_sP4Pf46G_#dC^|MU1y5b|RG7fOPn z|F;0I_7SG`U-AD~`5$fmuk8Q*uiyVsEBVjy2NUg|#i!FDJcZHz7(>TzFZQBC0zrV} zz|KeL0_C4VGTZ(MAz^+V71sZZVf=p)1(}F{xTpMd;^&B@u=T^v2gv`(w}S{jh7kMr z;@~)Y{T~tr3rbe-Kl5P!ALBpnRbV3ip1(iEaTrFY_zdyi#E1PKFxe=k0YrWwGYmRI z&bI$f{MV=G|J{QRBe=uA%?x^R{@?b0g7&{(e*gcqiPa()j}0Q!|MC+E;L&|@I8)DO zam@EYNRNBMA?E9!f`CE(Cy)K$lpaq$PZHuEk7+={V@lKXK*9D6`oA{z&pCtvH^KkI zfi>AM^Zd)#`S<^4?_HPO#+5a}dX##AQ)S#i{@6b{W+LV#Izqu~mFIZd?q$2??6;pJ zC6NRH3IqWxNlRJXXPcyCP|h#!nMm9MeeqfBYn%tZE!H0{!{r~kNWuSm>i)ld^1q4y ziiFv2li=@Z06Ty`9qRvxe>T{sg_GdFoWU@yJYN9*d-DGa`wz!IKYRc0tH}Q)0d(2w zKZCxw3YUK-hyFj5{{;~m~1+KLXH z*kxUm5cbmw+*H}$_W6X?UAt2O?TQn3H1|1*KUSe8M({m}XEPo)1B zOo_^Wf&Fk9E&qf5Ul4e;{CMk=e~13Z{J)Ek8J?cfng8dCuI#_$9O}QQ z|1$u-T9S!_=l?yC{$DaB8UT|4qUC?`|F<^)`Q+cB|FQmO!j$bUPtR1j{I4T1^qBeoh5g^E1p4sjimmNGYy?O9pH1i&3p2q!cKo+yN^}4g z=%gME8F%j{P5X2OBMaeEAuNB--YCY$2=&9({b|M=RywSOeiL|3}cO-X3{m)0cO z{@VmzE;DJs|469+7fgxD zf2BsE<$qJ^=NI|pfAE*g{$Dr%0{4H;1k899jlZe?m!NrT$NEYkc~Qb?Z@Q^i5A*$DHXQ3pTEJ1CZQsJdj}46d zZ|lCJ9n&%M-!Ak2={W!GMb3Yj%0Y9RUn2XvlUY{yE7Fi!bY($R3BW4OI<32!=vr{y z@S2sp;$>TFK{X5l6iwB&1jhc0Qj-SG1WbniF#qWV&VRT&{z>%zvcTrxJJA0(>%Z{% zI{%|6?Ein|{htmpW_c8i|0VuQJpWe<^#8Kpy;CnOw>zMy4HO_y_0k^eg=J~GyiX^lDf7zab2Eb&2X!)P~|D^kWu>NDh zl!{6TjqX>omAI$&l z1To@kndiUm=hshpG?7)FLNb4G0T%^{Ac~YSqb*x z@7Vt%C7%D61@HBundiUWUH?0o5^cc52GR2GB=EHSc;SO=L4A8vmtg zh?akwz{};wYoGi(>_6`Rv#6NoK{WoR{&!Y_efVSl$369b*7>gwq5l_5iOPSaMxy0^ z(Ekepua+Ngee!SXf9(Ho(J{xfX#7q6kNqFO%mY6^&-o8o=f6IL{$DaB8URy^M9cr= z|6~8hDEFiIKac$9$^S+7f2g7V8~1-%=RfV&r>2;1-bKs*!v16b$Mem9e%StxRQbO( zQ=$W4=z?hZ$NrBqJ+F5E`sdjMe*F1gv;N<#1OrKKp8tjYANS1vWu5=J-(Sg8|8X)U z+JMOhMC*Sifv4ri3!nVM=j;AI?Ef%HnC)&A|0!KpL}}hI-88K#DnUx#ikfticWv8< zzNiK3`nqdc*0Ngnq7tQOm@G)gI~gS?Q4&;f$B{NJ2Exc~j1<{qGM`KR(l=D%c<>%9xx_zz=d0$`r< zf9tNF`4@!a57Q-IJpVIOFYi?n{L|O}VA|+w=l}AeKCiyA|2@odEi?xMtXf(L*~zvS zVDPEvD;W4%iC(p=Xn9#xtpr;Qa>_(a3&sZX&%yRI-@bRB=Jkgwkbf=Zcr=<;YW$Nh zasCrN8~z*ozq9>^FM|E28tuPp|KEPaY(lVkx5B@xq%5jlHYMp{x^K(-mK1Or8>EQpv_z&>^vH#-<{J**Wx0wm@;qTi2rNIBk`5&8No9u(k^MCip ztLyq-qDa(cN-X{@nJ3!*+XP-NKVJLf-_rjA^S>4mGd+mL-_-xkOt24s$Nn$DaQ;&! z#LZo%Rg21cg$6KHL+xnkl{l`Vf49^nl|K(Pj>DL0j zq4R$sUPS+YB4Eag%=3Q(6iJ>;#N?h{$`fb+OlFFf|22Wl<;Odp{5$l&!upSckQpB3 zkvg*|1o-aB|6S_;Q2$kgzxw%q8|Q!R*QYyqC0hO$@!xu@&Gjev@ORDs#rcm{>gD}2 z^Zu{>{F*EOw`NLo01SqTmVd1Op6Lln4*t6~wLQ)JhiLp~{l8fW2D;p||Bn4%YOw!U z|1m^0%T4C_zjxRFPNqZ~F!55f{5uIeEk9oPZp(Vy3%Q{FkWz zJGTEru~+Z^f5!L^Dgk5ucfAwHC;!<0anJqVS?B*AxBrPW(UhK?m=cZu(v(Eof1AL| z<;QEE{5$MF?*Fr>nCC$>{&V~{aDuZE?86`XKkljjv(Ep02>ricN>u(UH4-iVn^Hf& z$S41{{>T20nR2MRyhCTUFQEq z%l~Zur%cdPYQJ!;PyVt0l>tp@}2jrh{JpXqlV8*K?_@}S` zF#Y2BpNx1^`L9xOrR8}3D-;0n{MQZ)?vFp}UvEEtR`r*$p0eNm8YpkyB$5ARfyMDE z(Ep+Ne>t50@&fjMDhJFkzi<9O8h+s6670k94Fe?j2Y^5d;f z{%!rwvHs&CWQJ#n^}n+c0Qla_|8qrG_FqbZ=l^Cv+g>vB`M&{*Bu^${a!)VYQ_ujI zJPn1fDeEB z{9kA}!TGN_#NApm^ZuXv+y6CFq61*?K)n556WCaOyz$BZMgK3(f5iH~O~pJe zf18zHAO4R0KSJU8zj**(t(y7#-@EI7CsU#gn7ANX{+$G#mLD&C@^9;Zf&CvQ3A5d; z;=e@w-_iXag1mVD?=!}Kmk|elu6F|YnCC$>{&V~{aDuZE?86`XKkljjv(A4#9{(lO{|lx><-bxR z(el43_4A8-@^9;Z?Ejdlm-i|fe^dWs{|7Mhz|T1UHE_T3y8k<&{$DaB8URzDM9cr= z|6dwx%;m>BkcXT7UxU1u^WQl3e>muv<58acUu6G>)SAHlpI6@h|BU&+tx7P+_s#wv z?EkoD{KtO(Uj6+0UuTj1baChZ>#Ei#KHMA&w)sKWe#Kn&f6bKW02pQ@-u|x%Y%D+C z_~hSV|8f3Ozs*Xp4}a|cxTpTlI{)?V`9CL9q79gINwoYs2|O)7UijqS z*8kZ5VNx*PosIuZf38e?0vW6O%K~!`NI3qK^VgXF zk`LIOE8F-FZhHXC*FeGQ`j~$}IR4Q7^WyVgX6og=N`n8Qz~=h3;rMf=UOfNfqGOI{ ztNdSI!_-$=5ngFUR4rk$Yo*$zC`4H`tyZcKoe~Ww{+cpg*0pGhl2&y|>#8d%7Bl}% z3q~a)>~|t16*&LRb^g|RV$=o72~?=TCTNj zl8(#n= z2mk#JE1mz$lTS;Ie-iz_EU-C91Oy4L{}A@t`F{r)vpkB%{}TUwfBV+_*!YEj|IPeA ze7>sxF~!mU+n{FJ$I)M3`&#DxKl|gAb^Sk4Bx*Ay z7Jrr;6D|KXftSmV*FO2T{eR5=T13qBAR2#D|2s3mKKxz#e+1Znod22$^u@B7=f6IL z{$DU9D*pxc!)3Jm5Bh&W;MMZutxx`K{g3q@7aemvi^kv7|ISP>;CnOw>zMzF^M7)H z?N~GO{MU!j|4XJs17I>iwER#0f7<;&SpP9`%5|3~|7RA3cH4ib|Bv-wMd0GK^S{rS z|J&LL;KSdw|7Y0$h4Ei5sh`))Jpc9n_J7Tk=m5;%!&SWfUlZ6^e!TI?KYYHb|8tG? zUz?6Oop8tAx{qJN-v;h+nM9aUEz|->Mg-`x% z{V$-|^tJtelY;r~R`Fk={_p7i561Xw{@Vne*1ldGu>ZLK?@GMPe@5d!$A6oZU?2Y2|8dXx4_W8G9*_SL>iK|FVO?K}S`<`{17qt>=$QF`O2X7H$P^Fr|6p4(&j0H`;C`6% zU-rx2G5j^Ds-8Cmr6uj^LbDS7uhT*{B`ugLU^*y~Og2(z#Tpp>SaemZndH#0M+;de zz@G#Df@#SA;QYt@_}ltFP|Z*I->JdI;=FMC&tLxhCyR)g9wfnkQDArd%mesSf%6|- zq)h%S3I59ho8uP({x|Etw*Su|CGgt)zYa2Hc@&MmS?Y2A#&G#(Fb5p*cM>!4!)W~T zrkKj{Ajfmh3q zw?6rI=zq-rx(J!!Sv3Bp{&!}A0pFYXU&s7cKFt3^|33rZt0gn<|9J@gzhp`@044)O z%m3v6C*A*p^&b{`(;Ne*(mI*8iK8 zV4zFr{vXcq{3kVM9MYiI)FOsh?lylYd+PWBi?-UV8}QO_vEr6fI`WdN>nWc ziR?7#$_8fs_EpDws&rY3s;R54Zb{9`x(3rwQc7B07NRI;CnOw>zMy4H01x$|IdNAxn}11uK|iAPbOk=PcPe3&;XeDN6Y`3 zz~=JfolpK9`XB2*4m##|lt=2!q7dM_r~hNo|D*Y<*Z&-e;jqS>oy-y~|BLuMg-`wmy=m_M6xjb^k}%udI{r(u|0BBpgAMxsmCt{k`~>s--~IY@C&xs~|7`!S zF3z9poghB>$NrCd&VS&)f3JT2{jW%WHJV?o{*N@8$Yx43{!7&mE&n!wm&=dWKKXap zf876NQ8CYhX#7q6@2mv-@W=j-d+PtJ^I!M-D;f3wf+Br zZT*k^A1*rPcovPnssFM61DJW>=V!_PK5qX*910}A8&l}@38;4|KBEKmM78p&-#C}670ht z`#eRs)z8V10g(!m>NNxGwY)l>YAMAgB-27iDUSs}aKEd9( zvWy+@i$Zb&L11D|2Yw;|DCi<{V*E;Ec?F%Mf;x`Y4>`o_^)b8+H+PH zRi*l}>R=pyugbcrdtSpmk*!vw?TU_9y6aooYSK`}%a)a#cSwev1A1mz}HwnGy|v$pg{yKl%Sj_y1u1 z&x9%4U7q}(Srpu9|JAww3-bSp@K-6>gN~vCK?~Tc-a1r)c*gaJpm1X$tltDKl%UI z|1sFH-{617KhJs5hd=)>PyX+c6b_vKI?w;ng1`9w@5xS>=RfWDr%f^6yo;9qh5g^E z1cN*T&VR-Jk4?Z5fAFyVAF1sBnkmr%FgPe${&D`pnVzHBe?I#kjsL9wH!Hz@{jXO2 zKMMOl3@Ocb^RWFNiS)mdDbWT@3>7W^P6AKMj~71qhtF5?58JbexNk_9Do(Qv9%QHZ)$gi=<(AkwykIk1A2wP0=EXw^l{|AC?(Oq!MB z{8tY6%S``fW7%InepYV~wRitiUyTn|H}JK-Up-siCbs_@@n0#sidAKQss#LxD0+5+ zsZ8QTXjcOZc$b$bQ}9dkTe>3>~< zho^Z__if$xog}a~uPZB7N-4{ZgNIjuS!hZnX;0uJ09|ORbXkFgP`&Dlc>OOl_s##>z|-FB?$xYBh`I$lB$ zSU_N?Nhw<0@xCue6)*pa4)Gu5=>OHv<^LtE|3j>S_CJTcU#}VM|Cy!!_44C=xc|qb zM*rVQ%hV5}@y}BK;|k-y)JVJ6i^e}s{$H>c-~Z=EEB(`z|DVYJi{Agq$cx+m`{n-w z^M7;we=`&4v;X$~PpJjYe@HLvjukV{|K6XkSkM1Xq={^%#Nz*w4WjM8P2lD7oA#^2Qc&Psp}f5-mM(f;H7pG=@Hmd!l>`yurIf+Y5MwCyD`@Be%V{l8>NGyo82l7(|JMXImLG3?@;}&382AaB{x$R!LjRY*`masJJWrzWpX0yH zO0W-q$NXPSIxZt^X~fJ$&_dVCKia6e0n!fbb| z{*Ofc-?9B4QZqRJhh+O#FBZoApHXebpJw|%WrEf_!F=+M{U7(7|C)9F_i_85NE6vi ziN=4S{nFI`#svFX>gTukH&KlXpzQ~zh3|NRj9f5DWf z{8#EETK=*B<4jRda`4~N)&HXLH}(Ir)IM<<&?R*KH}-!_)XRJEu>BvY{r^jQ0vZ65 zpQ7b|3b3&M;~DjTp8Q`#|HnB00jA1f{_i>V|1a$SRwWqZB5?jU_J5dKI^W&H_J5?Z z|7)g12f$#ac>BL5u(AAjxAR}U{U7SZ=RZa3)1536E&sFqpE5z~O0ZA%wFI7Xd z{M!UxEG1G%+{7wD8D7DW&*oQy%f8105KWP6)LjAvBN>u){SdLfG z@;~VR1%X$~kGDSgcj$kd|KTEJhG)_EoBAL7KY*DBe*P^opOyh?=aLWG|B+DtFPRbz zfXN2Y@;~|i*#9xg{V4v=Bma5wf0v|i;Q8<4{EzYc7uf#w8t1<`(tL@|-!sL0^DbKc z7xsUv5)2}N{U6{a4;pd;d=uvPt^Yo3|3@nOzh+8w01RCaE&q7_!o&S+Y|2vryZNS7((em#k@U;AR;gf&(eAWMl{U0U; z^WClDzeMLh$M%0HXaL9gA5nujcCt*g{Ll7($^>Em$MfWW9`^i~M4HHEN;LjU)etTJ zHi4JRkJsV$ANxNnB4&CJjlZe?ot0o8{@DM4{U6=B|39JrUoa&q|CJtzmj6NjF9^I^ ze!TU`za9T^?Ei2PGQ+cI{7wD8EVWO+7VyRS9}ipqp^v%$E1~{hG9?;-Ni4^UX!)P~ zf9(Gl?AVi^|B^`RE=l1d{U4J5P81;$&ws)BAEv6xbN8V0pY!}#-u|x}Y>ef{n*;Hmk}tmh*QR2gC(-!N@!w`8*oQyP|F~!V??LB(B+~y*rbHVs zDU)dVcM^D7e!TF>{}4Bs^PjN)!=zxoyH)=O(G3;4Z)nqXs;^mHmqJ#uprkGuPL(PO zQP+x4$_mogZ3&Ya1uJX8+P={WhF3M;zIUIZ`#&hb{*R}N|1K%cKe7J<`#)fi-5=k- zwa4fGC(=YydU9e)H2zC#5^eu&0xy>zufy#>_J3GJ%=91{|2h8KtOWb;$NrCd#(xhw z|0ALPU$iHn@?YtZX!+li`uRmZ`M2Xg?Ejdlm-i|fe^dW2OYPGS2Yj*r1N%SvlPG$2 zDk~yRl=fxId!Z<6FFT?0WFjW_^wOSy24E7)@giFO*910~AMbqf@6i83$k*Qg;~-;} zM|q?!qW^=*--*;*)7Qp-niKIyeLWZ;ET6KITcYKEVgI)(!64r_|KnlpKYPsn&s6c> znkmr%Ff>E7{96q+#`5EhPyQYDANxOSGG=)ajsL9wH!HzD{IUPzp83BA?f*!m|D8;U zHek{v(em#k@U;AR;gf$`|6~7$Nx^(~tN2egRWDfG7Oa!C=qpD1rtSEUkL9e9EiId- z?y9zzj1{!0YeM>hh?;BG3EGlN|A+Z|0bdQrdJbD7v{L6S5=>mTO2E@|{|^lP-~uv* z5dXn{aQ>$>m&tSYp#9$)@t^7_X$!?qb=Nf~L8{_Z*P=NAWqaKeCGUC~G(gan|INXK zL;Sh_{fizT!1reScfnr^u9c(^{|o3TdX4^{JUkt+s*Qgj%v1hvYOt{09*)1DguizF z$0A~;2TAZ>6j)t9GaP@;1@`~DNSXZE%KnR%=$4hdtjdb%N=nZ9mQl%NSC#}EfI^d= zcXh34FGW>Xyl3#`u278iMX3_lf7t&gCFL;x8SVeKWndVln0InY68U#lediwl@_)1b zYukS=Up@anQ!npTH2!9($N6K!^*_@L?Z1O4ik#?^ajenl}kHq@F z8?V$)^Z0-9`+xL{_kY|k{}bkBewyn)o0$Ng{kPBmRYZgT|39gj-L>9>=KowjHOI5e z^Irqhs$XBAN2o%z^moQTc7;f`XBTEE;{CT7LC8D|DBm& z!1reUmn*uO{{i)XsQ;q=&w;qPX6EyMA42~xnGy|viGQ^GPyYX9gO$1bc;}OUhyKU< zkAsdm9_5L;&7u(CyQlx>(*HHof3;AU|8vd%*{@G`GE21lFYNz%tHt#v`0(e7t?j>7 z(Eow<|4PApe`cQlx}RUy=YJ&XM6t?~=l~eZ6fOT2fsN(I8=w5!_8;rNHWl+ciN=4{ z|C^OyAO4R0KhSW7`acii=Bk;`|Gm5ZcQPf~fQbpB<=;u*Y5DQOC;txpkNqDe3A5d; z;=e@uf1>+8pq==&`~R{31MPn{pBv2G|`lvoVF*S@n687rv5i3*e3!nmmjYW z*niyrWl=HDgJ}P6j{i0*!9M)4|6{q$CusX_G*dim|3^aozi3ZD<-g*XX!+li`uRmZ z`M2Xg?Ejdlm-i|fe^dW2OYPGS2Yj*r<6-B&Wj+7*c>XV;{$DaB8h}YG$BStBp8_oG z{}}Aplb`>aNa|wxKVbgPaK6-w@Be+q{2%szJfHkOZ2w0hO=PtvpaWny0P*&JOX}y> zgvWo_|1nc9?^QJZbNshi3HITS{U7(t|7D&3diVIx$&_dVCZ!TB|4sr=%a0d6`5)q@ z)%m~J|6!6a+ntU7&H0~F=|=0KKh?A(C!u(ADs;(Ds$wC$Y`5yr=Px+se^^>843p(=RJ3OFjhg})j#|C2G8`v-%< z30QxM^S@pDzxFHUot%i>`D|IhWG%}kIFe|!F`6b$FT=Hd6%s+sry?C00@ z{D(xIh|QE({8_Rl!uhXDG0k?JdH>Ht z=>G*%qVivHQndUJ26jQ<)$-%5PyPqJY1aR-{^O!!j%U&MoBH2b2>^U==Kme@Un#-) zuQ}kh*33Nr^&#~Ck}1&um@E)2|5JdKbpH?5|4f*&-Q~&unMJ{!_8(^cK>dHr|7$YL zf5ZHrb^hmmeY%rlqUC>K|F?Dm`0#hkf8`YCzwSgZ?rWL%|Lo`2T=~B>Q=$W4Fif=k zWBvb3Pf&93-z(?;HY>qEmz(q79s7UCF#jFre;cBj*Tjc+?D#1Sd?fI_)`#&<_yU5&6MZ>7#tOE|JMXImLG3?^6#+!IRC*WW0oh;_|N&j%}THjf9(JG zu=8K<9{)L+5^cbwOQPl9N#JSu@xmwnw*JTd50irV?ri*T&VMc2TJ{a8Pl9&xBq~v! zDwqLuYPzm2`ie2oxALhRXz&s_`rUJY5<^%Fi$%~)=Y!NZj zgCzJb3T&>Q8IC`k{|ECwUwQwJi;g*-CBZ*^{#WW3pZ}BtYuB35_?xL-*DJ#3e~Doq zAj-d!n28@o{=M}-s-O5Wg|E(ds4xG_^M@9Jxq$%q9jTSR_gfqubhwb-zCm}cg=s@ zADHcAl@=cS<`#;xzHZwuK_}`xY zs=%D!{-2!?Mtv>w{-6E&$}#^Tg(b3?5{o}ehKZJco50KE$7`Sb!{@91KY{sQi;8(3 zMB{Jje`hAxhrhl5M{VIb?2=EQv|09R~Z=C;=195ZB%=2F#LjNzB5)FWff3*Bh{{KMb&#g;l zee&XK+~5AMnGziUgA?NI|C+$Y^5cz9{xAAp3$B%whOxq79h1AX@&N1fG^3FMRUv(Er%~ zWs)%4-75Y|)c+OR{~;B_{*QO*|B9CX+5S(Np!H55pZsJ0$36G|^54H#KmY#MS&x6Z zlym;;3g(oD*xVMl`9WB}(z^d6ktVwSJLcnQ(fBWoNwoai1YRybUi;+VVgIrJ!=hrI z2hsSO`rlaz_Ti8HANSP%}e>PvLA3r{~tL4XApZweUANxOC zbj1)jYnbIqN)W6<-{H*FPU7~!-PL_$5 z|AqbEssw{P-0c6s{*Qacf9zw&e`}^h2f)w_(ejV;KhE?V&HnS*|7iSY{l8fW_UnJO z+W)7p|HF{dd^Zo<|B)#Eb225`fQg}^<=;u*Y5DQOC;#yIO8y1*f0!i9c4y;%GyZ5B zRaRX0Cs8o?A4H#x{{LmvcTK}5{^HG10Y{w{#!O&_bzPXKa}kQV4m_n z>#mRa7lh+42+seT>6h~=3I2-$o9nlRG_?O^U(#k*cM z6;&-M8rE=LRt1b@sHCEj)~qSZqN`;mBq=nNE$5nQ5;Ok`_Iy#vC>!R#X^r#0T>XFh z74uFuS>xXWyfDg*77eMI4s3eCSgUJ7nF0%6SAErRCW=D#yzY5bz$azdtGaASU6B;{ zlVScFlN9H_bzq5q%!%PJ!@Lv!B>LZ3^__nJ=>MDbU-*3G|8q%k{_{-0j91b4o24G- zFAcZO~d zv;{?{U6rU%jZoBH3G3HIUd*#E;M&VS8>xVdcR`L7S5{})V&%HM?l zw`lnv^#6jutL4XApZweUAL~CZI_7v5jlZe?ota?3_h$Z=E4s4(PyoXDuQ}kh*33Nr z^&#~Ck}1&um@E)2|C9fpcK;97e@vWm-Q~&unMI-9_J6GZ5A|OS2LQcx|A%8^&|}tr z7xsU>)#A#tKKxz#e+K+<{_B;3`Top2|MmX%f6bKW02s^^Z~xZ>HkKc6eDV*Uul#?k z|Jqc{^CTMoS^sZVf_?Zq=D)%Q0M!3^5I0xNy#MF!`rpZvXagoDh?ajRfv4ri3!nVk z`d?uGhe^V0ckB2sP5*av{|6cRe_y%({~6=I%ZQ_S)aHe2ee#d}AImoH^Y*!H0>4`L zar-~gXrd`SIc-lv-^V<_%EIQU$iHn@?WWuX!+li`uRmZ`M32y_J7RO%X<}#zp4M1rS|EE1HRb*@tX5r zA42~xnGy}aB$nevwER#0KlXnNcI?ULzb2ah5YzuL%zx0Fa$p z@n6>YulKkAYo>|ViKAY|TTCqDm8FnC%mJ!88c_MuU6TpRRPDR7}6Ki4j7T1O7 zsl1qm>pvIp*HVtDQLWTDMaslARSKV;E?gV{y(Q2{lC;myVr}x|04g*mv3LYQ}g%dFYR0R)qGdD{DX_}?EU{H2_E)twg01r zf$t?NY9^~*6-`&ST6c=bMmD@=LW{a-g|0cHf_I`VDU-dZ3Z`LNJkc$Wng4&5e>v2D z;TQne|0myrYQNyr%{QppcmGr?wl&`lU+a7Gk#Pr14*y-9+TVYR7OZIh+xU4~H;%Vl zym!YZf&Tw>7ZA{8kNscNe<%z9d~*M9AO2ggyxa45l>G^yaQwyd>;HZDFSdED;_r9j zfHe5uP5_1D5A8qC9{)WF{ww?6^>ErxrHiU9Mb!&I3NZLJDRo~rwIaOYv=Rg?zZNCY zy=Hw+1)K<@MAzxAfXq+x?R)phWM063fBuI-{zpnURS50>wE=9>3}N=y1YzSYjr`wE z00I7c@;?jv&tU-IOW6Mpp#RT`?p6XQT>nEn@$B*6lj#2h^6zZ`3&)@9r;q<%2LFrv z-$($3<1Zw8_WIwG;Gf6;yGH(Jrz04DsU&;${*PGvU7o>1`hRl+=vF4chd)u>Bv2bP@J{7{+*Y6L0@*`mSCvj6~j( z|6BC`WBwZFwh12KQ6J+q$angEWC67H-FUpUqb!A91L?e!lj}|DZQl^WU-m!zN>vCwcOJ5&a+I{2v$>@ap@2CquEw3_be(uXUyU z!m%LVH{*Zo|F~!T_ptpRsqFunE71X1Sds+xe@)-T>gP+J{5$MF?*F%_nCC$}{&W1d zRSEXtkNqF_)c+6L|B*=lJGl~Vz(t+J%fFMphO+X>zpekV|Kq*tKWFqleed4-Q}<8z z_2(zEcf$bk;LT3_=lTCsDxsdd|KlL7-utiF_+K@`B|(|s2Lb;3=l>1oKTs_h%zu6A z`M(M1_8v>v_+Rw-j|%2_*WKUMKcBz7#r+b%|7QOGqW@?4hwcr6sQ+`o zZLR6Wf0qAWZ*T@u{iO>P|DpdsRQ?4;`M0pj@xY6Jf^aZg{^0;Xl>Z!XTWfmppX49T z{$+JRXsxT0WJ;X`FVv}klV46kcCDrq=D2C<&<;WJFEyP14#xyP`}uDcr62Qw7yt7Y zqQb!g^8ftje>^7K*01v7e^CCHVPg>fgZy)%G5>EtmE}P${4Z)_3>(7nXN*02|8Eww z(l5I1|861krghId35URTQtLuX-jY^ylnYW4p?RaaniQ?%gg2zE8^POBYEqU}pW^&i zkbfcAaQ??r$N!lGD>Vl!8b7X@{^G%1Z-=Jy0LRY}FNDiK2LEP(qzR@7yyjH61J0^8aOE{9b=Axotnk&tP?pjsL8Wcm($WE)OuW40i5`X?9%z_-c z1%~`D%D)|0rDlMul*bAE#oBMwj1I`Z{wVW*X8oEPxistLk^iOl|J&aAALss`QT}23 z*R%KkJ&FC#ga1Vz9GU+-;t%2fv-kfz3I1vH|GWpp;r@B$9}fE&_W#cE&n49${~uxg z^P}s3MyRp>fBwDG2GmH`;O+At&9|>VtIuD%xAu=j4yMIsxS`{iKad=63z-AZ!pM)|YCzwP>u{}P}7&*^yn=TiS`@j=f2*_5w$#Q4^$pwr?R^}mzr z&<5N>+Wpy8{v(b2UvJ-jecvh)`s5!zU-^G9|Nmp;{}vnT-1NWq(^dRer^)|E#Q#J6 z54L|wjQ^dD6{-0p5B?GH|A@bm6661O!9R`upWQ)+`yUqnkM)1a$Ok$9efIxSOKzI= z-aGzJWMC{dL*MThKg|4@DBoit6Bz%G@{jf3V`zr|t*icjnE3y!|Mf8c;bY~0SMn7O zRhGZ;-tm7T{g3f~B5t_K|HH)pXZ?@$|3vuumbv2pN4?tKV}CH1h_+E>#dUuY1%v;g zM4aljRVU~atSZ5}l9KwuO7oi`zq4omj~0wdMhW!)YC7!y`q2G9GkNZMWy8GI1QTNn z_?K-h`-ap2eJ4+%66L8XC_gn_R~LQ77+2~j{^$JvF#m^ZIF=sqe-Fku8`4FZa^ zx0Zr1b9}dcYf%1oz@JGW0e_tTbBn5JJ`0q8sdNK9;r$6(Ldi)e-kb_u@{_7q&2+1D zSr!T8U%>(3DF0~?zP(By{;H$U@~b#Z2J4!WAXRayYtfv*!uPrLZVI(xle*$>DO9bNIbhN1? zSEs%O=tUvbsiMFMv@S$XD{$Mp!|@+%Jd;APQU8zqpQ!)6@I`+i5Pt>(06+n-lkPk4 z{}`Bl*b_ugRa;a{R-`FNl|uf>kpD&eqv7=~5Qu-Ts;;aQpk7fi`2QtD0xdsra0*J= zz`o78KZ^gD|DozE{~vz;mlv7$`NHw|-|X?Y^=rf9e+2`eG5)_r(^Q`w$KO%t-S|<# z_;W%y;=e)7w2uSzf6EF8|Cu^b(bAJ>U=~nWLnf#%ingg50smhf)&E2M4}w4D|Dg^{J>UnEOR4aw;1L0-I9w zHB14Zt!9ViABH~Yp&tS2Kb&L!HxJ;eRRi(Y1($6r`xEOR^)LDo>VHEyfXP}z?w3@m zJ{_5hTE3h)$2I5a>(N>k#r?%=L|JP8+|AWY$Dy|^&2b}>h8R)S5 zFYXaflxUNmLT2^Cc(3#EBo zFbO$7+QL*$=>L%wRc%+dR5lHxb=@hZ>yCF_$wBs;Z{NF5=D!8--_!py*Z-s-!~TyC zt^cd$n0QBVf|HFwjx18pBnn(U;-+%bsVfYX7 zFFAw!&r|3B4&(1|3^U4q9{f$GFdTmcZ6JvMQ|SNOTfaxO<-?y#G0y*(>_5zZ<$(W( z-v8$i^LzOJuwf%~{?EjgmqcH52$4qzS%C>|5qCHd(coERwqN3tfB1YQ|3f9u&G^rxUGBS;>?NxI#Kiy5{|n>aDaL>Ej@$Q({}bVVQ3`?c z->3SoRDwQx|L6VeX^_f@*8j8rw;2ie^uHPZgZvNiKl*<`I3E4-1IGV}bdbf4==(XC zBwGG0GTSfl$-n9U4f2omUz>_~o;KJ2z=yvX{}1>>{15y8G5*hk`Eu0`@S*?vL+5|; zK^nC3D*w~SvQw~?12%Ir)v2Lz7e;XvCG7UrO~rcH-vPa0!<@eYMt!t>3xgjT*!I!Z zeMdW{WBPwD_|vif>%;H=+pPaOJ{a;p`z0?1e??%ppXkbhsuF-zoON2manHIITsORC zC9gOP0n~zOXap2Z)wKk+eTY(%24(^z!(V*#`A-g*6KUQc{B8Xo#%u}uzZd!+I!m$t zF9^q@UyjBJc?-8-;2gSo&UeUpF;-VgZqD*`acZ6*l(}=zeM(b_Wg(7 z7v}#D{-0#}W%a*VFIWALhTlZ2#B$;vcR5=lt)cC*YI+OZ{)g zAG-fE?Ek|2Z;&V-`f|4V-~M=Y-T#%y23hQgxnBo|M9aTLX8R>R`G?Pk{@;cCGd<*g zK6L)iq2}+={~wM2tp7J70U!RC`riV7DTet!sQ+zQO8q2T{qOGj-^q?>16B+aE&on3 zn=kRnzpeiv{(ss2Ka+O3?^f|&qWVvK{QtW9e;kg%`^Eo>@Q;uGUw8h;`{Ez1|7ZVi zGZOIWe~kZs{H*FP-97yO2aNv{=^%?8(f4yW6VdW-k=cHUPyTKH595EEig}(y<3H>F z%}Bt9KgR!e)c?8m{~nM36X|~^JE9F(sgY>;caqtBiBJA*{g3g#Nx^(~tN35(x*{<0 zwPCtxT2)knl)M!+=_K#q^k>l*H5~H{XT3EoYgw)P;S69H87K<^BVgg|hlu&F@=E@D z=;bXmm(YLF(n`oqw!Hv>r=qW*;kOdK8jgVBWmUD3G1#XCllp2}FgA!khqE7IEMfomLjO}W?En1G`hU~@ z=hOABm80=bm;YJd&ouV`y3k7hEE@lG@!tY}E?;;4PkM}Rt(eFD&%Xcg?jZkvIR6ja z&*A*{*VO+G!w_bYZ^3PSmIwcfQV8q+KFj}zzlMVb5dUe=Zbtm0_5Ymz-}D50@_(KG zH0FN>{DsgQ`v0;2&l1<<53<#N_s1*i{_jLFsKt($`*(0pwESCSwqN3tfB1Zy|2)e- z`u{c+^E`>hf7bt-k$?~X>-?t~e<^VPPaeG2t7hx}zPtW+vLo7n6$3=ezmv@7OMLQg z>wk>@O$z3_Tg88g>OV2@|4{#tlzsI2pCimqRCx_zmOS{!#Qy{SO0bvB|2r7trQ(0U zAL4(3=RXC>@zF0oVEmuR23dhA`hE^3iT3|2GTSc+^Zy3V8#{ofJs z|JeUM1b|roeUJKY9{eNX{}F!;|Pz<-GUq5lW{zuemY zx8EKx{!e6sEOtcS&%sX7@^6vZeu+>1;q$rvKgvJE|1W#~qfNh@PonXk_5Wrh;KSdj z|Bv`X{U7@OQ2*yd{=D`B#{Y@*zmpx&2CP^rTK=76HeceCe_Q`U{U7H4zGnZoNxR&4 zHvTuO(8K1>LNRrsqNOL%=;~C~B{}s)(Kb~h;QzxO@Gqa6?!f+EIR6*sJ`erBgnjt= zZ@Gcpx&A8t?+B%31N-}7J0~MW-`290nzj`MU|Xq5!D&N^u2ij{s;)V-c@?!(4TT+` zw2V6cNlo}m!8pi2?*Clu|J*NmJCQ$s&6fY6*WP$HSC;F&q3l5GT z>VK1pdG7Ma|FQbtgufgHfV?XH8~vaAU9uoX$%Fr~`rnMd(&}aH|NG(}?f=dB-%U@@ zr~j|@zX^Xh|A}J%SCAYZ{c^VY-+q0y-v67(2esG{bH5HIiI#th%=Sxs@_)_$&hn4( zpH0O)PonXk_5Wriz=!{}{x{<OesPlJ?2wEmy{zs*R% zr~fhjzvKBIT>bvN`uX?2&LaEid-v9#x_`Q_KPNr3*$}e%L1+p2?eibax352|&tJQ@ z_K!q1$YMwI{T!MhTK+9E+b{9SzwQ5F{BKh+&y#5UXZ^n!3Hb2G`2UXjKiBzB`~8)5 z{x^~Scd{eefR!4FmVYOi&6oJ(-`4*a|CZMPE|$4Nhn^PYTEJ> zXH{83k0@hxf3*Ks!_X&5l^FYfKm7dXe2@mMyo&$(8oGXIMR=tZQMH85-b%GiQHZi? zTCG$eIwcwazNUinM(e@57F{tL8#WB->C|L>Q)82sUMpO$o$ zfMcNgw(9z-kX_ruVQ^I|q+%80y6al5wQiK|+7f#Ix>^$rM*xwEG$f|~ZR`M>0U7xIt(zfHwFPonXk_5Wrh;KTn?|6AZM`EdRN&i~1S`Eu24 z{l9nD|4w#98?Xc)uA}AONoMmUKKZxxKgRzi1@qml;=e@opX2fWx&9-`>*oLN8viH4 z|9Jd=#$WN_{6CEUUoZY2@rU?dLjDi^zkGWCy7B|Y|A};v6_}#$cLja%XSDxkk$JKB zyrze;KTQLb8fCZ_J_Wus^U!n1n z5B_yQN*7c>R zMM34*Fj#jYdj2oq5B=X#P!9Qj?EhTE|NA9xC-Ud7t?<{Rs(RiOFa?@+b)i`a|JP|D zo01kx70?|{B$Evs(4bfYt-nQAwVFu|6?nhWT%Ze}4RJ{Xg{o?q{}y z{l7E*f(r7o{XaqC8~x>I{L{t%3;bdHKjI&a?Vbgq@lWUfFYt%*|I6n8_n?;UJ9+H? zq59v9Kb-%-URM4$>VNx9au9Rm!T(VGZ-Kv*L;nxf|DM4AoAbY$o`5g^vzY&LiT|Mh zi06L>$??%IXRH71*H`QQ-$XH}#g3T!budY^|7Vfeeu+>1;q&qQ&pH2x{=ZGdJWrzW zpY{J{B;dn;G5_bH|0N@M{%0P*SF2{L|J_~xJJ}I!zzY9p`FE1pe2Gu~ZT*k&ze&M- zcdPg>QT-<-{vYZ;lIoY;|9#i^KN0>h@&ACo60h6;^}hH=>;KvR+l&N!`rnNIhxuO+ z|Ks@&`IP=*wk#< zasHc0#7uWK{x|!7;YjeNl3bnow&o{MNCo}BMR}riA$nRd!8_;y{PMZ!KFv9Rz4&Vl z=l-&>|CjNPZvQjk8M^E$|5Ite5Qe-046-56j$N__ropyUaM5Yfl`!I2^i{`us&rY3 zs)4z`9VboCpl@{O{cgT}?>?Eo0`S*T{{GwWr&8mego*Q?@ND>Rtp7~-L;vrP|HuB% zMg4!jB~64sR|5OL&G~1;H<)ZOV=>O07b1t#}*MnS|kMr37+4moQCHQv=wsnR0f0|(Z?_KPF9{ev# zA#DDyn&clGN)7)1YwCYTVmQnt3sMZx`hU*I8&;P8_#Gn>CV(!<$A<^=0k=cHUPyVg^k3j$5redBa(fH5$e=`#B;eW0F&H5k0 zf9(Iw1NdsyZ1ump>whOZq77K#A1(h*GMg{)$-k}tG5$9xnD1^K|D~z_#K!*;2F_so zXPp1IU!MjsOCJ2A~SwS7<=R{!g?1-*v;_ zW!;|I68f!$hW6{aVb!UF2Fx0u(1j?^4tWr>*z$kVtKa`C4)lM|^Iu>G*hlaG4?-#5 zmu>uq;4J{=i~hf~dN_aRUj0AgPhq6+tLA^4B+T|O8vlgx-;6&e1o6*?`Eucv{jXro zch6Z}RF&$>s*_aqs;sNJ=QZ3D*=j}FuIPBByS}BZCJj}*Y+1>9r&Lt`|Aaqm0~*f% z!u=nM{U7^P@)BO}b+r5^^#3RP1!RO@w*SuyS<)}$k^f`$zX^XSHO2X_@8bXG!QXTW zgZ)1V(dEmY|1;HH-Ua_?{Xge_H$6cg{@427B>zG{$^rX-lPda+#j^GP?)O)$`d=C! z)M7`>{W^FcTK+9E+b{9SKYYHL|BLaTO~pJg|7IlM!~a_UoAtkh6QEH4=Rw?D zHCzAh-Sxkd9nl7u@c$Mq|4uTSFY(F0L;qv^Z;~+E-75Y|RR4>K|A+anlDuyI|9#`X zJov}N{{#MtGwlC;(fEIu{|NCv%>TgszuDCOwD1GQ|A}mn6_}#$H=#aXMEidhndh~y zS7H9&Apa2mfAszjhnYXnzH=q<;XkACPagjd_(S}U`QO8shWy0`jQ>IJtgcHTD_Kxd7Y(OM6@{p4MJR>y zz52Q>p;9YgGN@oJj0;s=RR0fS3_6V^#D825{h!$Xv*`aRWmmDP>`#?|{}DycPSB4f zPL%d#%X^_HYY#XOw*24h|K0y94)p(y_!Gci5Gp^q|CdLcgI8V2|1igys+Kgm=&682 zTwN87Y8ypiM>ZVF!@IuXq^N3Aw|%Q&Q!IF z<1h4}{}qM)U)2Bk_5TWg4y~6A3`|QpAv+Byx3qvmS<6m!b=v|fbOk(QgttnQUITlS zaM|_(27*f(*Z(`}e;5=G_(K4Q_}@YPi>jy@m7P?e+K1WigmcmNjJLW_y6srgai!~? zbi4#75N5y=O-j*fI1!*PBIo~)_$xZ-f5K4zoB97t`rr2dhW$VLUzT$I`|SV0_#fQ= z5oE`YemNTdg!$hYe+Cu+g z|7IlM!~eSfW5!=Hh5dgS(6*P%*8g#L{qJN)v;iv?h?ajRna!8@4|FrUEdK8s|K-6yBK{xo2l!vM|LcA6kJkUQ|F;N z@jv+g82{(i{=fb9fboAK8)UH~`hE^}ik5$i%=Sxs@^AQmqx>^Dod5os=YQJt%lRZ4 z|5^WUMgl(kjrf1WAL4)L|3v+t6Z!Mn4;cR^(*I6&L>sVTsc89klG%KTPyTKF5ApxY z*8fb}<-S|Re~J2kBjW$D{}<-JLjLzP?f?76e|hkai2q0Yq5b=1{lE9cKU)9K{@-RK z;M4y`{6F9i@ju7&-*c<}as3C3{}b6DiyhJTb8u9&{99zUU*eO0!~YxQAL9R)wf{E# zaz2U1f7bt-k$?|>BmN)phxi}!zd2!_)_%bFKau`-vLo7n3HkFywER2CY`(-N|F-^z z`2S`5zfIcZzFWnAiS~a-#Q)>|Z`c6-viQGm{Fev+i1>fRUuiu5{l|0c|D655%}Btf z|Bd*6z<;R!gZyLtC%5*0$ohXG8)UH~`hE^}iuV63GTSfl$-m+Mjq(ri|I7A&+4Rf# zBpUx&|8GVDKKzaNf5ac+f876-6ZUEC2aNv{>3=6Xq79gkKVL-4zmv@7OMLQg>wk#< zU$*{d(k}PiD*j6}|1l!|ALl;`jr+gfC;rQWe?`p5Mju>POO23hQgzMq4mqUGNrv;7jE{2Tt?DF3+s-=<=oC(-!N z`hPPL@ZoR7|0Djm|344f_NosU|0mM_PIg2auwsE|`FE1pe2Gu~ZT*k?|4jb78=tVLfj+Ba>-Ib-mDjcjSzG<8?Cy=1JQOAIpXw-S3&l@$*EJ_Ws^V1FqB#+wd)*WzOy;Ng&wtqeD-N9hFrWWS z_%Q$fL&yL5L^^2YmHdmA=oUu0mlX{C*VS+iR}Tj~z&PHnED0!p0;az6uC6uhrKswP z!))+jD7<2Wf7k+!{U6cT?pYv@{XbU!oA8Iz|J2Lk z?@$b3CV3C+(|7XVZ#o6R_8%BQesure0rSn1_x79Q3EMpv(fWVR|89B$KK!rsze)bZ zdH+B5|L#FB?mOA)fBX6MQyv{;u_NYw9jp>9{}!3;m-yr#J|E|Q&+?D{zfHwFPonXk z_5Wrh;KTn~|C{lbf`V&eay{x>85K6w4-LE^tW_{YTm1OA%4?*4xVV`R+#MCT2`d`f?@@&m^IiENO?j_CU>aKBzg%fChD>&o+=KKVEOzd`;X{(ss0?@YJ% zxQfPq*8iK4fDeB&{vYs%_#e;zdJp)2d%*ZVk^XnGBieu!S4GRelg#EzeDZJWe~ABI z_WUQ4cDe6t{BKqvrP7VoMSrSkNlrrX=2YmCpH#(ardy@UvbZcT=K%KNKc4?cVE-T2 zihuO|-?@R^x&A8t?;2gUvL-Dl%dRDjCX~Z0=7x!~Ed}j$ODonW)z?(i4X3J56+CA} z4Nc?lSEKsBNBoELKO`5!`Cr)ox#<7gFL^tWKYz`Z{~_71|7D-dRFCuB!SXLCMg8w2 zW%7s7_$Q42CjBoMd)@ONlS6!S!Il26%2qcusXL`aQI~z&b6M7fZb?tI7DXd#MTmla zf7%pHAqvV%*dx@ja*F3WD;`m$TLX_Uo&4|8F86)M7`>{W>@# zTK+9E+b{9SKYTv+|IYFc@gJW5V-qsNlW6>B{l8fW@Zo>0|IPRd&B6Yo{?7pTYRPQ% zzq{*yCp)4ISm7Tn|4uTSFY(F0L;q{}vgf~;w99?BivJSTe>r{ z==(XCBwGG0GTSfl$-m+Mjq(ri|I6Bcn|?W;MB_i}|IJ9ihrbd3pY^{Mxc@gN?9_ zs-Oqx)O1~4z_?&=Ct(KYm(NXiVE*ge{{{13KYIUnZeVw=Z{t7oe+Iy8hWgI82Fd@> z|2@qAeA)SLP7-E&7>$2w|9`|E_WvRN*)U%&9F2c!`#<8(p#%6;^S?`U@?Wp=zk-$$ zc6DeN^R3#lA+lklq_S&an@6j=hL;U&%_@6R!3L44WmP@w6w#t2aAZ3xb$tIXhx5N- z{|CX>TD_%3vDkg^G9cbUuwt|N5zR|oP@FQSTP*spx=u(ot)UECn zFNN$$L3&Zf)?=R`M1byzr-j1@cE+ucaVRK|7|Mfc@mBPtp7JN0Y3bV`roMk;Q%O{|CR^v)vDR* ze|OjaPIg2au);rD{+(ntU*eO0TmNJJ&!k|!yH)-tQT{I?{vY#ykp6qw{-3+X|B3LA zi2q0Yq5OyWpFfQL@BQ{PNLfVd|JncBj0Al8--!PQ{D=4-+T`2VB#|2WM2xpB&-U(P4d_|N)(GZOIOZ^Zv2{t*9T z{x>J=)7lRh|0mM_PIg2aFd=`wh?ajRna!8@VwyRGvOJ!>?;2QqkfxOwjK0+ zRlMtA6gyQdDH_&rURDL?q>_qCTC)ZwJ$JS2AXQUnDq9%YNHvM+|GD7LhW@Yd{0Hp+ zS=9gcOI{EQtnu#wUTEc_MMJ8l1DP%uYjsU1Qy}4W)mIH?q9|m~>z-Eyd{UOZs>_zt z6-j|V$UhwaAVB{k{`v8@_5aXpw4d1$&VOF$f6y>5+y4_JzR_Qf#y?&Bzrdft0EiE6 z{~tR3510Q0^M4okbIxD3{u3?IK?~%u|A*>-GyYPt55NC!Q~n(vgqh?A!2eMFZ-Kv( zue<;6eesXB|8xF#(-ZLJe=haE8Gj)l3c>nckRTuYa<=+khzs%d`H$w?*Pqqruiabw z2Tv1&TI`6qUk8sw%fCft`z1d4zvO=x@{j(%O~pJg|7In?hySJix4>U=hV$R^ z0KQr^TmA3u`rpZpXaiRGN6WvH%;rmc@^9;ZjQ>pv=DSS;h z8PK+ue8l)ajsADCBieu!3q;Gmlg#EzeDZJWf6fKYe>91h>Fx~Qr|;ccf9n3}zW$uT z4=Wn&e*EnE{@dG6tHIav+jim)K_Am>nE%MY{eSlUZx2l#<>OiY*YG+%8RAgVG6K`N z$`k1;=l~b8ITa1>PppNWZLSN^Q#b{FoB)0OHv;}}p4acgInY|E@lV3U`A>K@{5Q{k z8SsbG-v|5uq5D6t#JTgI5%Pc0{NLZcH9v0tD*#_5&Uo*`pJ~dUeg5lBiagGT5%}+j z{LcT*VEj4k|3LiTg-IgtzsUcs&dg1kR09!b`yluxn(Eqzu0fY5F?Em}l^Zx@d%K6om{C7R<_N#PJwWX+fAxHrNUz1Yz zbyF+CD^4pxK=Ox?@4DBl@2P+tVM=tJ?h5eyn{VH{Pv$QM{P+9+4E%p7U;{A9|3Wi_ zNuEjmn|BWR4|~^+4PfE&Po;$U@2@idd8Pkv;+6Wtc>MigKsf$Pal}70)Xw$d@%M)T z;rK)P_l4&F+*AJZ*#E2NcV@XD{73r_^M76_|G&cD6&ZxtWT&LNALhaTq7yEu!EpRx z{KM<&|98bdUjNSx09&4b4}UJzoc|x>ALc*u7wZ3C$-i4rGd*Rz`2BnJ^Y4E}Bm(1k zD32Dh_z|oB78k{1XHnUDhfn_D^OgKVJ^}qdi;g)S#N$8f|E);Chd){vYdq zF#hY=>%YhGf7kdg5B?XOa3ua8@rQPy*OmWw#XnyE&;H+5B;eEkM*KhEkMVzQwLh)@ zknw*aD`fE_`hFAc*Nb@hx2Swwdiv8R|AzlJ%0I^c6Y(-%#N$8f|E);Chrbd3kN9K! zpBeUPdG?6$eWdKMenI|1Xt7J^TKT zXk^}JfhGRu>py=~U%OM?{ayX@`P*BHUjh0b4Eng=_xAqJ$^OGA&}Yy8rXbuqtl_}l zzo9qqcZK_ZaQw%!&;JRe(Y?OD#NVIEJN7HX@z<~c?8*0k93$1c|E(qdZ{3%!`q7=L zzhDkX!e0`OzoNtZ=cnHPlaPKnms;X~zW&p{p*!#gh2yWKM*IV*eC+E>{1^WJkPrIv z-&L9pyxsU;=l@Vmhxsok|Hm*5`CGT}ALl{c9__O0Re=9K|9{5+`S<_rqRjojw1I!p zRB*WbD>>x0Bft>+ z&-Fho^t12(^HTmXzOae^*Y4;4_wDV!|Ad<0KcAcRNT;A55BTq{|Bv`HN)^_BgJ^v8 z%bWPWef_IV0Y3b&aQxu}2(14eg*52zZQ?)b|1=pOz&|Yi!w9qiVEi8h0gc(4@&Eqq%YT24$A0)Z;qp)PaQ++O9|-1wuW#aiP6qt};s59A%m4l#*8bJ~ z{P~|l?ZM&r!;Wz1|3v%`02=sLHt~N02Ke&ruW02Dzb71jN>Tm;fjs#2P5j&X*N+t7 zAHtsq4*6e{|AU|g{IyN|-@Z5X=dVdpfJfwisQ)AW0YD!7>L&ibzPHuS?vHeGz$X4v{_o4@ufP6hNB}}c_;d50;p*R?|9XSqV7UD9tvRO$o4>2Ce|3;D zFphp6{{g}B4?%!L{PTkkZ`j8F$KQ#>|A7Aw`5%n`?~8x+<@0}j#d?0{e?qYQGpPR{ z{`bYd`SJPtmtTK<{yLQZzjn>fZ!jC?m@629KO6mj4hI0C{eKSz*wp`R_s<`H|N5)@ z4$}Z&EAY|YkKx}Cg8#+;*Teo_^#Ag6z$X4@|F8Yy7cfGkBM^>1Yy?LCFF*M3hE4nv z?f?b+L;HXEp#O3IZ+;fo#Q!S)&;t-#20S4Df@A+re(-O}|Cuq8>;MM*!}@<2;kf_n zUEtr=|5f+b=R^%4;qp(U!uhZ7!T_84Kj9AWaQUZV$p4}J&(8vz_z%ONzjR3&fJ5Ye zoc{{@KXLz0e(Z-gY~ue<_4db?&-EW)`yYQmFWBk(=kHyAED;cb|2h8WZ~_#@|L?&8 zoA_VqKgYYi=U*R=KULWOHPbKW)h7Pu50VUn47dM`QH=j{!al9Ni9g8yb^l-6)xRRP z{~gHxGS~ou`hNl`LAv`2YO%wTlt@#cvMBpQyq9WBqSI#7vhv@sFAY2KevJ z|DNMN#_;^FOn|SJ4Z;8DG$6n~u>Qw1*8f&OWxCyof7CRX5c$94|CGe}|Ctasm)(hf zR5w7l{BtTW|8D}7>24?f=dT^C28YQ1CI3rN{xbo-T6QP?QPsc@{LlJdKm!=o|5i|C zx!sBXMgEV?gFb-2#{Q2ia9fLR;&0`Dj)#CD_+R2b7yyX!Z-JHJK_LEzLO{TOPy7e- zAGDHmnE%P)67kOfx3%Pc{Evr#JLP|YKO4{g#{M4*uH+B)<9|E^JRtuH&;LmdwR6G! z_@BRdBm@kXf3EQSA17SNAMVHhcnBDdKa79H{*UBXyB6G!|3&@}gn;4rOM&PAxX?=f zY!mO@7=r&<|0_Y!|8v2W{8>2u7x@o}1Hn(cg&wp7?K3{}FPS|BdpW195ZBQ2fKAzuoe`z@H23|1ojO zbr*_%c=UHj{x!q=PcF!fbwlw#e?KJpJ0kx=WBhNxluFo459tc2Kr** zQ2a0Q9}xW=!e3(ke*sd)%Uk%v{_j7Q^FNNne#1`>iT^MC{|xKD89`qx9gKfq?03O` zPyDy2|3M=t?*Cb!l z@xNk}^Uo0c&)*G;{VwPMD_G#@9{Ex(b$MBboA^sCaIWI!+zsP@3>~|1< ztx*5xgne3j6aSm@Kf@xy@c2(sg7u#XqnsBJ`0tJc0slSz-=h8_6-WGY!al8i4F9l5 zaHsq)@Q3l=sQ)L7a$X$6KP(aqmw!0^6YGCDVV~AMhX46*?TrM(_{lr*U(|n?ROtU3 zG|Y7qhX3x6&-l53|DO2oj6Wm8{1>LN|KFftuAANXhlPB`&kd6Q5q|~;fMET{AY!JQ z-S~%ve8#UmApb(5{x@it>t;9p=P&LJ`HY_%B>&_*{|o0o8zjtjvm5`gkk9zNLHLjQ zANGHs|8LMR*UfJHFY>=Lt_Md5-|6{7(f-?DWq5K3|G+>H@DGgtgu?yb z8NhZd8H)drKrlr9&-R}Sjq|@9Xr+G?ivN*7@PPblh4ugRU^`X}#sB>Mus|?G{x9}l zqx?I-%J3)@|09852>xgNuL#!vGl1<_G8F%d{09Ys2k=)o|J4CjhDToff5Ab3apB*Q z`7avx|7HN&vE&i_1H-=o|IqkP;Q7xEurfRf#Q#Y67bO3e_z&m*XTaLEWFY=W!oMT( zuWemYY8gO`TH|Cjy`j^}^4 z&`SR-9RJYhZ^VC3{I{I{L9zaq9&6W%LHHkv{&vg%0)K}2e-~I8o(185DEd1j|1ba+ z`@b^4Z7msu|M|;-(ccmIhvQ$c|Hr~A$AcjJ4@G}R@RuCxe>u>$*9^k{BL890-x2&3 z#rmI(RgNc{_%}a3fB*6edO!bYzJ2}LH9x<-{rRJN`{#4hg~ov)@gGwR=6^_v`QIF9 z+iM=je}5bpRR25UuW{=bD) zjt7VEKmVoOabURoD}wv~bD(Xnc?kc&I4~T4O>zI9ja7~(hw#71|K2$80R9U1|K&j2 zUUL)w>dWW<{Q7z{vg!ysX{o4xo4hJ42FJs|&_;r>s9hPiI`;(z|$?vT&;xgqj@ z?f*gfH%OT6W-tDMA)oPkL-0TAf7t$k{+~g^TsM31zsUdIkk9zJ;rL4`QU4n>%yqMg z|BoMkM{fI@esT!@m-vrpl>do-IWGe6KN$V(?*CfEe+)K&WB*r9+^=iz!#^ha+ui@M zz@G~2|GFZS_s@O!$3%aJ~NYUp{~R^*{gne^~qd+n3MH ze}=1nfBx&O`q>SaU%oZfm*JxMyZZmzJGW)Wab!LBwV$G1pr>&Lc`+9|>L86oggJr6i5+AElJFZnO=(&4_f$;3FMCi@YT!|`~er;WQcmlf1G3g z{~`2$`DNSwKH!{J+OkfK2=s0i%!o5A8qa4CjBoH2+1w7-0WGCF5N)$@hx)Ohv~cf@IUK+jN|yvRDL;s z@!@}o=X}49cmIzujryN&DTiI}$bS*f`M$}={zv_nDz0$+*SDNQu6N|Wi06FY<8S|* zGK~Lxi#h0eNB-kwfdha(_J7X*q-g&^IK6Ljv> z0R!xxGF<=v3HgV`fC2U|Q2&1jeoy~@?gNf#oz7p--~I(O0NDR?F5rj@{rZ1lF`z$x z=>8-B2&(S4`td*8zh?~S&tEZte^~SvGXI+?wEq~Y?)Uoe4~zan>|apq|GP7c zxzdOKc&+~kpnv->D98SP3|8lRefWn(ewm^j zb-#C$fAde60Qg@?mH_qh{~Y2!CN<9gafcXjr62!*;19kV@OK>lgKd9_H1R)707CsY z0;}__&iq4zzmdNJS;y1^t<><7u<;m94gbDSL9f>TWE9u`a7Gw)p)>!`;BVybYyU!H z{!bKF*PEUBj~Ds}ems%=SDE@s!jfXeQ#lj&tL!i_A@#LgdU!Y|JShTAME=@hyFi~|HN^1z1yF^ ze+&rxUHX4l`~^`M|G6TJy3UJ#U<~MQ|4a+a|Bd46db1b*z!)&V{+Yn`-xXohbzc0( zi@jq&fBP3O4+Q&vQD7Z!_TnEH1N!ra{y*|}Bp7#@7yq;U`^JF&{1rj{FOIA0-JASB zzBl#nX;uJrDqyEPvuK(ytFzz}({{FF_`F6nHCH}h_{{i=p`QLG19q)GL9~k?c z`Mc;pSNvi5H;n%s3C3OK%s(*pJM;Ioe<3jcFAl8Z-Ol{S3%z5%Gk<6Mzl{H({W}7T zyv&(@VC?71|1$o^G5<4?tlRC*{Ll998~cUvmrSGocLNxC)h7S8{tGtq>puQ%ZRh9x z#(?;bVE^B{n1gQc;2#$J!ME~{|6u=j^#4ITeQ$E(9})bG{GI!MWBX5qLjBhlWzh9* z{3C+Dk-umB2m8O^_-_zT-<#a{k5>f+eNT82HfDxKOzJSuz!i`zXrezxW$?OcwtZo z=wtuq@jr?79{@Ap7H9quA)pWcbNr`p{(k_>fLomTpY1;+1oY=GVFNI1{{wJ(-?GUc z!oQ#0pTEKg;P2HR|K~qh`;YGDPuLCc5C8E%AO55NhxMN@{`Y1Xazg}v=U@={d+PsE zWBVUM)Bm0@{(ixrkNscTf1LmA&obnWF#dkQpuhbqitGP{(Dc72jQ@C-YcS|<|G*#H ze}9+(cZBix3kLo9Yh3>;fMv)nVf@eb?-&dQ@Ye$Kzd~?&-?Pd8`?o*-_505h!+$3K z&o}==;QH_0D1&bB;_n>sEx!uz_l*BIBmkoRvn<6tN3Q(+BEIE!^|Al+_#eUee_4jP zj$HZsMSRO|8({yi5h&XKvJ~?ix$+;cbB*|x-_^(d&*Og_?SEN@xsF`<`$c@qZ|lSV ztpC9VFlhhFQp|JY%KvQtjuGGTyZZB&6vzLTWti(|lmCDJ^T+@E_8gBHKZWi84X8|qKKw&tKp*=*$A1#X|1tr;d8`lr&=}C) z{sqDDzZ+1Q4t@BK*ZRkRKK4Jh|AH`F|0@&mO*h-0YRX@{bT{48?$sdBgpVjYQyN~GY|NQbl)v*6J!~Ne<(A|HAH-Ep- zZ}A$KU9GRN?wh@o*iU;KM&4^z-I_9{*z;^*;xC@t64U zKij`g=oiReL6;Hpf8*)yKVg&q$Jc+vM}Fh`9pnF@{U68w81DbQk1oj-9{d9$zlpzR z|Bp$H{yz!dy@$B*4~_gL{?7fsvHgeXpV6wA#VJ~ ztNbIsiNCY`pWAiR$pvowLnA+b{-ggFM46nJ-^;mkiG1Pri$NiqK4WtZXtXa3`bK_Q@z{h!x=k!b%Z=`{ekw=gO!0=nnsgh=2JfLV5W5 zeeSr6?|LDTp|KRsM z{r|B48~XoD(61hQhku0PbM*rF)!+Up?*FkY!fZ!(_)qo^ngFW{LTg=}Bva}nc%e>( z65UD2uGN%s#WmgZ4#3~N|0gxf|4>3f{wLahHtf@b@9-Z#`df(NH$8s%#ozv!B-sC3 z7GkEOJNyIfe+U~E3xs*`H}=m7#qpm^z;7O#iT^@21pe;v-^d@@6P*8ZgDT5mdj7U- z=xzT@OWgk@3vTP7>G|8Tk-z;@LQ(&>Smij8p8xp7YuU)#{yBv$AkhEk;C=hd=Hqv> zzUz6ZpI_@%E3I|UJ6Y7MlUf&A@|Lusqg;>@2IhF9x|$TNY)AT2*~bt3s1_`&Z%%f!$xt_%GW3 zwXk-dW9X-*xAxm5(myMBm&0HEguipcZ|g+?_Aib8M?(K7pZju+|Bj{M^G!bG+C28Z z)PTJB5B4uKeGmS_a`|cOD^K;v$bZs+fWN!`Gw_$<<@f(e-m!Wm{!0xg!2bEm@BejZ z{!0xg!2Z?C@BejZ{^K8i(SZEzUn=_Q`u~gYzx4Ivy8i#W`p^H>Rc-gN68-Pze}IrQ z=YN{^ACx8n`CmSJ3W)&?7w`ke*W|C$?NNHdAgRCy!785P)hIn ziuBkmKz{jOgZ+d4hxwlmsdN|r@2U074*%TL+p5p}_4nF;&=ha|zZB5^zl;CBiT~$< zd4k_)S-zJ4U#)N02833nr_+B&|1Z_6um3dG(@-oAyYHrxmT3GpLjN`SU!WFB)PHXh ztMgQejQqp&-+{k`8K7@5{_|@3ey(Uw;nzkNfz~ zX#ZTkdi-~2{^w5m_m6MQkA3|Q;=?)qyYNT-_aW{7CH{XJ{nzj}`Y*Kq?-Kv#@!V;i z`Z)bJQP;wf7cusasR_xOsg)6+J^jz}{~Yr_W0?Bfd-4BIqyLWnUz+~kyZC?9e_(zKsN@iedPd zZ7utT)PTN|CsB#=R27t;ny#yhzGAR%rwZjium8qj`wy7^iTwR}`rhHv|DS5Wne1|O zXa9{}{eQFmBNMp(+kUYoxgwbVG`h3@HGsdn{yXrOT)(>hzh5j*aYYFK<>=1-pAlgH z4A+0&FV_@Tc((si=?3P6_a~SVN=~p#XLBla$xo_cHPfxqWmzO>|H1xWef_U(!%BfI z*)u%(tB%5yU&T*#*EJ_Ws^V1FqB%hizt>Gs@~*enfphHtE2%*E7aaQkikS6ZvHzD0 z^X7v*`IoY*SXK5X==qxTf*_F6zHE6f6lJh$_{}2GuKcIK<$HOa@G{=a1f$UjV-sA%a) zG_WH;S%VVT7e(7tje!5jQ2%fAAEl)k|Ap;8(f=pmyZ4X~`#<~tWeU!n7x~-29`xUr zUjOY*c|6lcp7t-Qwk>t5Peon9?jNKgr?LSNpeaRP!zz%p)hyWlXZ=sX!iO0DKcb#^ z^4A5IZ7cf|>tN_d^d;nfnBgEMYYo~zsZ<>t|7#{7pb(?~*U{*%83c=9K-Xsb%= zQ(N_*|27oHe_+eDQ^gf1zmORQD?*d$~-sZn4thzEwd7aJw?7r;}JS)bW zCFF1aO1*sk*Eau@_@bY6v;G5ZK)~fiQ}(b2m#8XPD9!7FNf`5?tx^K=zhy;L+tn?V zO~YticZ%t{<6T#BX!|4ff0*naHW`#K|MyM%f1CEd@`0bt1=NOUGavl_{lTCB|4(0j z|KDx3+yH#hw?y+F|IWLE0p9*!5UP$a|NmxSH5z8X1ETpm3J3zzx_Y^|9gW0{`SwvA=ZC?0{@@K{%7sSG)aE^js1()-~Thf z|F3imv)g}0{*x#K{5|v^!yoV;Vg0Wg`+pz)v-T5c|CGJ2{=0%Xk>(cA|IcXulPC~i z|Kbq)f4r^zFGT_W_FoG2>ia*xNc$fh|Ia%CY$^eJ{JBuW`j1om2m61T{Es(X|7p|z z2e|L(TNo$q>;I?rSGQZlBvK!&iipcYZPt@$`?m_)e$}4+!{;~lPnntjgZiIcmZ{G~ z^Pm0yrV_NrpG$VbpTkr@^#7?@uRV`(@pk@ywIX60u!esn|I1Hu`fatam#^Bhf7}0q z{tNkk=>M1HnB(Xs{!f(u5u^XY{6E zej5HU`mf>7HAVfWs{hD8{+rSM1NGm*{ux32|AqOVJB6P6zs&icunh34&;K2Z;Qsz! zPtg7+jQ=;4puPV8QvU_}H~KHe{{gMJ|1D3Y|0aq)tcr-S|7OLAwtuUz?N{yD|1$nJ z_K*3Wb}8mL6U{%l{yX@8ZRY>t_+K98*H3*a{Wp>SUzH@W4YPh+|79lX_xKzAw~=M1U@0G1%#*23nT@-I zd#Vl{o)5Gx+o4qxq-Pf3Em5X7+!2)BgWv`~Mi?%eUm||IONud7GdA zH~oJh^a0oZSg9C(ov|far=yJgCsD|+|7ZAP{>R6UpO^d}cQMY1`0}DW{AcaQye+`~ zDLG*O@6GIg3JL@4pPT*Pj@JIa5C3IQ=x_fLR)9tR=ltKS{y)0^Kj(jJMuL0%&-s6| z{wugN{4xJy*;eN`%8>u(USDtLeKu__|Q{+snA+WxJ=wqLbp|91S3`mbGzdCo-hpZ)))612x3 z_219*|H;??bk{3x$Nv)f|J90!ZNRDniMIdM!d|{=&;D)ykNWSj1oIu;=s!x=6@e{Z z8>XA4RYfI8$y-sAPV%mW&Efi@7Od;*u4!4zYTb)Ul(1!-EC_4?D`Vz=DaK%GA(LkQ zpD^pc;r?&K`hTKo+fujsRMb^>5~L!hvXSc4l%lUy)zen9;0DCD|GRS$-M`DAeE;pw z3I=f`PT#H|F0Ha=94!6 zRQ5lQ0R-4THS0gUY5s3!qJEFRY5#ke%Ux(LrB*GigzRM73uy4E=qs4;U5Q?`tY~>z zRjq`!nrhBOO$)|M^Ut9jirD{sX#b^Y|Cv(Q{$J*QxI1G%^uyIR{JToZqUvQ+k{)LF zx4dsjp@B5){oBDtzYVV{s1%(pn~D|{t*I!QhNud9B)w?M1pLX+|5IT9*#G;C{$EJ@ zKMnP**1&-FUnpGvXSFO-pS0V5PyWlHzSXbi-TuS;e|^CGkJZA{85gCYt~3|2LJOJ^q*d z-)H`UP5GZ~K4Lf0reg@90MVO_cu; zqyL)x57+<{^`D2te;N4)>c56R?Eiki{J$sAe-qh%jQ(r*!}6c+oBy|hIZ@7KKr@li z{->lMXy4uQe~0`(SpONve_q)C$LPO<|7Sn(jr~)p4w(OAmt6ic(fnus zzo`W6@n7n{hCk@P(EmgKpP&Cz>A#8m|7u0VHej_fMce;sVJ}~`XaBbUXNT1PmZkQX zqnr3Y(ft1i{dbuEFNr|?_cP-El>Ec=-;qB|K*jvePssmF#6Lp+9r%M>i2CoV+kcq; zJK8@?K}Y+4UHgyFf6x5Q{_i;d;|1n_&+-4J613OA#8m|7u0VHej_fMce;sVJ}~`XaBbUhy33I>Oad;d(4r||86dtS^TX~Or5A` z=}9!YI+b-vPJL0dP1OkaAFKiU$L~!Su>Pl1Txm1^n``mL`+q$Ktm9j3{wC$f7xN_b zuD%uQ+bREV;xEV{_y1Zgyv!$U{;BML)_`68|HPl`ci#UiGf}_C-;DotgwnEs^}S&^ zM@EXitz|7WZ7T|ny_KpIoHnHBO4SOg>YBr3ucDT!p|AoZEp^oTj}w2%hx~tB|8da& z+?}x>`r&HZ{zLr#B=xU;g8=`}kGTKaYT;!*8O?tw^{;+gZ~qVLe;;xGx7EVSd@`DU zLj7;@|AGpJ@n2^0#nZax~^*3R2AYBRP8?`fZ_Vj=ked?ZvPYNKePQast>sS(?bmY<|TRTKP>-m;t#Wb z%=-U0{-3}9pOOD03VHYcrGy?J@_&T?&%=M#evG^E?*B{Z{vB}s&&K@EtnGgu{$cBX z&i-FYb-?!jefTd$0e|}k{s)ZzK7)UB{6FV^Y$^eJ{4e=`ll=>j|8V}_O;0_~VTSxa z_x9>`{m(?|gH;hR_hYm2MBBer*!HXT>>oZK*8iOCAN4=G6!V;k=0E%YO(kfL|0Vx# z<`3>4=Ko;+UmoVyPtB13_v!wBwIX60aE*QXJ=*?P3wwI}@XMb4+x{Q*-(?BrJGzPg z6XkzI>%WKG|L-&6zl{82^j}6%|GUipb#G4t+(aJ!(fTjZ2b}-&X7)cNg+cr7m;Vd9 zf8zMxwExo!+JChE3o`(4{O=Ltf6@K_IsV^Ng7*6VsQ->R_W{j!^es=N|0aq)tcr-S z|7JajwtuUz?N{yDza9Uh{%eXaB#c1nu!h{r5BdzxeXK`uWelkMxFFn^PRJ zITdX41JB7Ld7{tNiimB%sso9(|JA}?zG~0@ZU2w@@3I8*9ohWv+IFw1uB=pZswyf@ zLhZd}`Q(lLSCwo@SBZ-2zOA~xDrDF8OgB|4 zq+%80y6al5wQiK|+7jk|cC{wDA&OL_A<_0vl`#ANaWnpl_WuI*e~$mYe|&3xY}8fn z_8(?{;rd@2g_-ztwEs`1|6cX~DR`LUjsGP^>-4y2{^|6eEB=h)`Y%rSqA$wR{?FQv z`GS7_-?aY%763e+{ZG34uk~LaKc@VTto{Ft{3lV!um5lO!vX*Y?Ek+H|5^J9w0}qm zLHj@F|GcXGPohA8{VRUJ{@?r9|1v1_Z~vtdnE!jp|ITXv(f$89|6?-}+~a@F|2y~p zq5T(p$p6Lpf6?9h-3MgI|8uXexAVUfsgG7g#N3a~dJt{@R$<$(+OvQ7+|2*JvVSVb z`;PyuH1p$_t6g&W&qVW|{r{#Cw8#IP|98b-P=@P&wLV4L z|7u|`U$tldw*LqH_kjC9FH7w)M>qOkqWq6={ddg&fc5`S|9MFKmyv&v{yXxQkN}AK z-zVsQiR?dI{~h_m1~3Qg|E~lFe#uw?O-4rh57K|n_J7Frzu$*{xc)owNB#dH?SFLt ze~$k*m7u-;|F!-*@CW@@0)Nzh101^NEl;KYChC1y6%k|q%~}y{|5jn!uiCT!>-c}P zf5`tkVE%_)a{13h^Pm0yrV_Nr|62bY`3q|1|DpfS&;P0P-$ed@wIX60uv(d-?SHkf zm#^Bhf7|~vod3Bj!fZ!2|GV=)SwScPGd*e8DDEVffSKTw(-Z6hTQLbUfC^HD&i_`h z{SLpllRU4&Bik1!8U&*Ir(Cqq~6u90QT)N|9j>SqyG&1f2(Dg`lQW2mHp2e zu%rK<`E#Yo`;PylMi}+H8~wK_NI99Tj>b7jlu7$Z7ycb1J zSl5@L76p~Vf)RBmqWAv>|8MgDndadCasKBp|I^(W2h@Zc{+d)(&zk~vfu~(vXja1i zJ1t~W(t@c1RtEq&vVj>oiZw9xx9F-?Gs$6gFfC*qGyiwCe@V63{|Wi$=WqM}koJEX z>RGLU0qsA<`Jbyrnfzol|K(86>i6?*{{<}na=`rm)xyhsGMaxn{de~Nkp8KVe`fOI zbf(|@k`g9?1W@!y94zj;ZX{$Ei3-^?GnlMM5}F5~|%*#95+Poj`_|4#~-0D}2{ z=ls7HS%T<=uRt&j4w|I45d_$!e0jA_6MlB0>R z@fc1G|1SH#j^jVTpS|z+&r|V_?*GsEADfXN5WdU*YX6s!Kj4?p|HJ%`Evrp;K12SW zdwX>||2vWTY*j?e{n)H9(e`f@w*9I-`-jh${vY)pyAU&-iRM51|4k)mkN?&FFK7P` z3&6wtPxSwppkF;UL;l~V`~TI7h;6_X_4(Im`(G{W`T6FzJ^Q!)KkC2B63lmW6aOX3 z|A^6lhxI>U0x0S~pP>Jxa`({~Z5sDnWbw|E2y5{Ehw#`aj110S&t6 zEl;KYCh|U3Ma0;DvsOggzg5`wtM=@F8UGvmhy1_yo&T}o=Ew10yX5kpiRM51|4k)m zkN;BtHT=y6py>be)6cU0XSn{G$p5caL~H|At5dZ7uNL<5ReSbt`+r9G0rkISsXgY% z=6^RAsp)7_Nv=+PTl14Bq=Nb1MR}riA$nRd!8@1(_Q&r{H)Ov3?avB2@vi)}hJAn7 zxc|4A|M#Z(e~$s{_!gVLksN(7Pg3vdTYay=JoeOCHInMuEEz0C4qxmm~dRD)mcl!_ffAa&*|5+`(%qOGyC)9tZ_8+GI zQRJVQ{P_52{t5M;nLndu{pUB0|DQ_#&(r@4%m17B!~9>||6$tyJ8Srv4v@%>?s50$OwbTQcPTxwluh^S`S!>a$f5G52G$ zVno}&RoM2c_Us=%ALf70_K*6HU5a_mMDw5h|E3bO$N!T5H}eMp5a)mA0e2w5Y3x5*|JCAv<9}~v{}KAHWVrtC=ZycQvHxiOmx=ey|6MWjL^;<1-Nfkr z{~Z5sDnWbwf7E|R{sH)+Z+R~LH;wnPDk8@IoAo5x{;k5cU$tldcKna}uU(3H&P4N{ z{r{#Cw8tOy-_P{_;>-8y=Rf~G(o3;lL4^LB#{aKYL~H|A9Z0nOuNL<5ReSbt`+wAb zmnE3*$mV}H7U?>({kLvUZ3(l!g@#Gsb;GJt2h+gn4tDJvGdY0p_*RF&$>s*_aqs;sNJ z=QSJ?*=j}FuIPBByS}BZCJj}*Y+1>9r&QGX9}|Dr|5rl%hyDK-=>I3we? z@xSE%&HaBaHO&9O{a*qM+VhqS`G4;I%9{V3Mt!g-#$wfRlrN!y?DS&#?*g%BQ;X^lTqR^Pm0yrV<3@p@i68|M`;tH~W9V&HDf7 z|FdDA9-LwR_ow^+)ryF1z{LFemuUN6E$r(z1n7UL|1JwKH{|{{eJz)E9mt6ic(fnuszo`W6 z@n7n{#{Yx3{BOpXW<-7=TyvBE|EBuiW57DT z?9> z^FIdu4EReX&HPWC|1s#l?l$Mh{Q5H6{!Plc`zuq<|DNn$z=qK{|N9zKw%=|3sr>&u z2H@KMPyD%pS!U?}vq8Ul@QwdB`?yopl13Ljg=x65u8KyrjiRuE17zj%uCF*Ls+!bo z-|F716vAl9o3^ei+BWh2Uve}4!*KuiY5d3C8DDXA{q=^wE}@0jyr}!O?)y#>7>Vo3 zij`8zvg6Rg7f>ylQc2nqSP4cKnkrpZP(xI&`XZjc(5C$t6z2b8`=7u4zu^z-u0Y|J zeM>qaI}MauS_mS_PIYzLLRIJrILQcal_tH0>QTb4Z7&J|AH>c79sIu>`hQ%4{)7DU z^S9gokpAB^)Vu0-p#67R|7U9d1z|YYjAGgdnhhz)+J?w7TPcUyv%^|0`wm14W$KgB{1cA<&HkTqj{H-jUVUCP|AhMg%%9OC?*F?ADD%lY?SEMQ_rQN> z{{>T+|NW5mKO=wd{O_4R?Eihh@xPVCK)+xtz*pqqKWo3{D|z+*2K(nq9kBg>AO3^? zpF{z#_qkgRQ#j+|8xG|W+b@B|C0Ybwf~U* zXV(A1{O>F#UJr4;cW+|GfT#dwUw- zBl7T%(0>R1(EUH4|6g$okJkTF`u{NfceH;^aQ*Mk82?MeKSKXK^T+)k9+Uqa-T$BC z|4k)mum8W$e+_@oe>sf*V*DS_pnKl(RQhis?_*U&jQuxjMYR1}g>AoT&;A$j|6u>1 z{~oaYw@WVnnP~pA|KC)C_V_RK-+{j%X8&jO|M~epmHwN^|F2d=Yy(y+Q?&iB7WVR0 zd-iYpf6#vqsQ)ZW?J-9;`cI$X{yA|9fHn5&G}I zANK#n{Qno`ACdn%+P@YDZ2zA?|4-!qBlO=he|f<8|1;EtGU&G(% zztI1~_&wLV4L|7u|`U$tldw*LqH_ki<%m!9W*|0eSPtCA$P0XBR%6K(&ig}r>0zyHVef0reg@5ttV zcP|>%QPLKQpX#n_PJ&d$sjfwHB1HGPDN5KMo}M2tYrwAjrQ)#FG~|D9E#El*J0Hw| zC*SBlvZ;E(>b78=tVLfj+Ba|-?o3VRx4kC|A!Xfc=LZ&3or9Y z+y8sE|I4AC)o<(V{~-#X2b}-0T6mdHM)Oao|IYnCDvr4RTV~X&kB{b`Q2&|vL%50l zf0bA2lX?1ov-V@Yuebk~g5mzp)A+x8Fdk4jGV-590q_2w6j~o}{hy8epBLmm$NpjV z&lS%9e|h_#f>K>gS7hXDZ8e_oh>jQ%^=KR@F5 z{}bqcDgA$-{yW&e!u20MMgLF4KSuvO^T+jnAL9R``~P$Nzo`W6_5YXpFYp(j|Kj>@ z`8xeqPkt)>H<9&l<3+|DX9QPypVRe`11hkGmQF z=^9y{zLr#B=xU;g8=`p*#YN&tQKD8lhOQ_Qvd3=_4of;WB-4( zD3hOz=ATgio7#Vv{Y^3cOU`=jfj8}cRkpgRN!=+Win{FEp3AZ>bW3`wwI~`{D?${k z_d%PYDMUee32Q`ktTa7@O2)VUX8lLWsK)g_UZed_sQ+BqKkNYdw*CK=G>mG z=knNpSpMI@f3Sb9%>Iug_aASb|JRS7)dvhwb^oru-g_Th*7tWVfKCql`(g&WhM%Kd z49lZUz&Atd%@6!K<9EK^)<+rnPofZ%hr9kW@HYtn$p5_mch>kX5C2*FG4&4kDgx)6K(%iVcW0Tvw!$}+5a>8-vRsocFE;G6U~42|C>tC9{)@J-hE9`A6u#7ybv#|3>{6 z=6@LeM*qe6e-8-&o=X2s)cdd`NsRqB>r!<5Zxy!vDzEr|uzyO}0sDV;$>l#2%|EsN zJNkcV@_%srFF*eb^S{IO-$ed@Rg%OuV6`$u+y81|FJBel|K$<)|5%pVV~%Y8cXLt8 zww8TE>XV?IJc&w_rwZl(oSLqy3)n82!Okt`2h19<7yohow|wLE-yQ?j@hvuglXC2f zd6Ifp-wOQQ^ZyKg*!|P2|8lha@72P~eA4Eh%Km2!IKcj?$^Uv&{+WsTJ>KRYV*iuW zyZZ6~`{!o<*PHTRk@-fOTP?iIC!_fuvwA|1a+UyjqmWPe$`kt^W+|KNZ;j zPmX%^fzkX^$A1HV$OJ_Hzsf81$(#HiK}(o=p>zQoFtlYuWWz{FW!J(oFRktxUN(Z$ zvL_WR5L30Rs?AC#{Xr&-{XDD^Uwc% zLH@J$V|aM={|5dr|LcJH|0{ukUohrf`G5H(kNpq)CsD{N{u}s9X8ixr;=lLdKWjgM z_D?zH|GaSfKiK~y3I*6dWB{T4pYwlTu>D_(LjLW)gamNp{|Nrk@&BCvv8e>@@n6pW zhW@`<{srtG^FIPQbe^|l$p3TqS1a?sQ$(LuMa0~X%~}y{|5jn!uiCSJ`21@Ax3PcJ zf9+Dtb0(Vq?Eg2F;647!`QL`WU^(e}Su*vnV# z*}v`oar|dlg87ba^uI*oKN0%xF#ZD@z+n9M8TwC3{$cv>z#lpQ@0tCUjKig z{~G>A|AqVy)PDmUy5}uVrT-@CeOeU}WB<)s5pDlgVcW0Tv;Rf>KiEI$zX$C9+a;I( zOf>)5|8FWmd;Ayr@4z4S|HkpZ{P5uoPo@7R^8c$95!-;(`V?*dtA)LM)t>#^{vY(; z1Ll7&OYJd7HvhX?RNJVs;<`VHg28_%5vRIs)v1%Zss!swO6v0iW)0YpKTIH?u=5)+ z`+ti!UjO?sU>)CL^EWBSzL+PeclE8n-(~*y%wNFvj|a^ET`j!KCvEHK9yF3tv}# z)o>bLc7{{?|L$j8h7UoE`MC!_hN(|@n}|1kXv`DZ3SK0ca% zI{oL0KcnJ+^S>Sf{N^Qj`hTi=`t01)&4T>5XZ*JQ~5bN5%b^S^l-_1UV3nESC=O``4JDs1~z zd-e~XoB7{Y_K*6HU5a_mMDw5h|E3bO$N!xFcg0^&Gyet%$aNtFY}? z?b-h_{x|jy>0g-tWfx?IGtvBK|G%jO?eSmgzlQ%9|D*rU0Q}~W&!zvS@&Bt85!-+p z^5bE&{jV1G@>P5GZ~K4He-EhtEKBV%MLN=$O;r)rVuzCmAh3Kg~Hxrk? z5cq2;znFO-TB+eDVPgCV=ghx({|Dp$xq{E%cK+|BSf1i%ko_;0cisIvfUmFr*X-5x z-`!GqjtheLk2n48`^UHDhr^!%{FNB(-Oc`))cn=+{~gNZ87>Lp@1Xfz|84&KHKEA= zx(KuV9>o7_|IHuYzILbPpTGaneso{AzhVG?DbfFL7ihvyR(g5c;lF49?~(=P%^%i( zQ?DNXZ5viXdhw6C$^WQf!Jm>9HIr4Zil(butvf|zBO9|VycTuS3SDy$=y)gE67uwV zQ58(XYC%M|JZAl;G5(ik{GVyF{_Fd%|C2oC@~#jo_8(&9cf}yEf4}}e%>LJh$p5_I z?@cfMh86zF{QpHJ3h@6#y|4Zs4=v#nZv21O!)kw(E~>T^RS(N>6yWe{QtG~LYDIX( zX(b4Fel1F(d(HZu3Rn?NiLTRK0m@JF?R)pz@(Tigcm0Qf{v$aR@2mez<1D|{H}=2w ziJv!K7QDmJiv9b&(P06&0RPYBtB?O|_y5)?k3QFdSL#dR`JaQqJ7SQx|A+P8)FIaY zxv~G$a2=i(&wpPG^5+lkANf1bi@zkE|GpUH&z~`Z{y!eB!xQrK|1QeU_yxT95B)!o zf8JOB*>3;WKJn{~IS^l7l9B(U2>^fh_^SuK#wG|0@|k#QJ|X`p?_g|G7_i&I$7B|Be2inf+hifB&D) z=O5qypX2{6C1{WTLjN`VQUCRB$3t&;GW|DE{9)BZjQuw%NWA@9Wo^A_&;A$j|6u>9 z|5}BZ=|nvL+5c}TL3{ic`tQIW_1{dmt;ar@{+r1Euhv9t11$Q#{1R{ft7W}-)1Lj? V{vY+ 0 { - time.Sleep(time.Millisecond * time.Duration(*latency)) - } - switch r.Question[0].Qtype { - case dns.TypeA: - msg.Authoritative = true - domain := msg.Question[0].Name - msg.Answer = append(msg.Answer, &dns.A{ - Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60}, - A: net.ParseIP("123.212.196.123"), - }) - } - w.WriteMsg(&msg) -} - -// https://jameshfisher.com/2017/08/04/golang-dns-server/ -func mocDnsServer() { - srv := &dns.Server{Addr: *address + ":" + strconv.Itoa(*port), Net: "udp"} - srv.Handler = &handler{} - if err := srv.ListenAndServe(); err != nil { - log.Fatalf("Failed to set udp listener %s\n", err.Error()) - } -} - -func searchServerIP(domain string, version int, DNSserver string) (answer *dns.Msg, err error) { - dnsRequest := new(dns.Msg) - if dnsRequest == nil { - return nil, errors.New("Can not new dnsRequest") - } - dnsClient := new(dns.Client) - if dnsClient == nil { - return nil, errors.New("Can not new dnsClient") - } - if version == 4 { - dnsRequest.SetQuestion(domain+".", dns.TypeA) - } else if version == 6 { - dnsRequest.SetQuestion(domain+".", dns.TypeAAAA) - } else { - return nil, errors.New("wrong parameter in version") - } - dnsRequest.SetEdns0(4096, true) - answer, _, err = dnsClient.Exchange(dnsRequest, DNSserver) - if err != nil { - return nil, err - } - return answer, nil -} - -var ( - SuccessCount int64 - FailureCount int64 - TotalLatency int64 -) - -// https://golang.hotexamples.com/zh/examples/github.com.miekg.dns/Client/-/golang-client-class-examples.html -func mockDnsClient() { - subQps := *qps / *concurrency - subCount := *count / *concurrency / subQps - var wg sync.WaitGroup - for i := 0; i < *concurrency; i++ { - wg.Add(1) - go func(index int) { - for j := 0; j < subCount; j++ { - nowTime := time.Now() - for k := 0; k < subQps; k++ { - // request - rst, err := searchServerIP("sls.test.ebpf", 4, *address+":"+strconv.Itoa(*port)) - if *print { - if err == nil { - fmt.Printf("request : sls.test.ebpf , response : %s \n", rst.String()) - } else { - fmt.Printf("request : sls.test.ebpf , error : %s \n", err) - } - } - } - if duration := time.Since(nowTime); duration < time.Second { - time.Sleep(time.Second - duration) - } - } - wg.Done() - }(i) - - } - wg.Wait() -} diff --git a/test/e2e/test_cases/input_observer_dns/mock/go.mod b/test/e2e/test_cases/input_observer_dns/mock/go.mod deleted file mode 100644 index 0fe84627ca..0000000000 --- a/test/e2e/test_cases/input_observer_dns/mock/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module simulator - -go 1.16 - -require ( - github.com/go-sql-driver/mysql v1.6.0 - github.com/gorilla/mux v1.8.0 - github.com/lib/pq v1.10.4 - github.com/miekg/dns v1.1.46 - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect -) diff --git a/test/e2e/test_cases/input_observer_dns/mock/go.sum b/test/e2e/test_cases/input_observer_dns/mock/go.sum deleted file mode 100644 index 111b80bd0f..0000000000 --- a/test/e2e/test_cases/input_observer_dns/mock/go.sum +++ /dev/null @@ -1,42 +0,0 @@ -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= -github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/miekg/dns v1.1.46 h1:uzwpxRtSVxtcIZmz/4Uz6/Rn7G11DvsaslXoy5LxQio= -github.com/miekg/dns v1.1.46/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/test/e2e/test_cases/input_observer_dns/mock/main.go b/test/e2e/test_cases/input_observer_dns/mock/main.go deleted file mode 100644 index ecfc3afe2d..0000000000 --- a/test/e2e/test_cases/input_observer_dns/mock/main.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2022 iLogtail 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. - -package main - -import ( - "flag" - "fmt" - _ "github.com/go-sql-driver/mysql" - "github.com/gorilla/mux" - _ "github.com/lib/pq" - "net/http" - "strconv" - "strings" - "time" -) - -var protocl = flag.String("protocol", "http", "protocl") -var client = flag.Bool("client", true, "client or server, true as client, false as server") -var qps = flag.Int("qps", 100, "qps") -var latency = flag.Int("latency", 10, "latency ms") -var count = flag.Int("count", 1000, "total count") -var concurrency = flag.Int("concurrency", 10, "concurrency") -var payload = flag.Int("payload", 100, "payload size") -var port = flag.Int("port", 8000, "port") -var address = flag.String("address", "127.0.0.1", "address") -var print = flag.Bool("print", true, "print logs") -var delay = flag.Int("delay", 1, "delay start") - -var payloadString strings.Builder - -func generatePayload(size int) { - baseStr := "1234567890!@#$%^&*()_+{}[];':,.<>./>?abcdef" - for payloadString.Len() < size { - payloadString.WriteString(baseStr) - } -} - -func indexHandler(w http.ResponseWriter, r *http.Request) { - if *latency > 0 { - time.Sleep(time.Millisecond * time.Duration(*latency)) - } - w.Write([]byte(payloadString.String())) -} - -func mockHttpServer() { - generatePayload(*payload) - rtr := mux.NewRouter() - rtr.HandleFunc("/api/{name:[0-9]+}/get", indexHandler).Methods("GET") - http.Handle("/", rtr) - http.ListenAndServe(*address+":"+strconv.Itoa(*port), nil) -} - -func main() { - flag.Parse() - fmt.Println("started...") - time.Sleep(time.Duration(*delay) * time.Second) - now := time.Now() - switch *protocl { - - case "dns": - if *client { - mockDnsClient() - } else { - mocDnsServer() - } - } - after := time.Now() - if SuccessCount > 0 && FailureCount > 0 { - fmt.Printf("success:%d failure:%d totalLatency:%d avgLatency:%d qps:%f", SuccessCount, FailureCount, TotalLatency, TotalLatency/SuccessCount, (float64(SuccessCount))/(after.Sub(now).Seconds())) - } - time.Sleep(time.Hour) -} diff --git a/test/e2e/test_cases/input_observer_http/Dockerfile_client b/test/e2e/test_cases/input_observer_http/Dockerfile_client deleted file mode 100644 index 476081c77a..0000000000 --- a/test/e2e/test_cases/input_observer_http/Dockerfile_client +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2021 iLogtail 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. - -FROM golang:1.16 - -WORKDIR /src - -COPY mock/* ./ -RUN go env -w GO111MODULE=on -RUN go env -w GOPROXY=https://goproxy.cn,direct -RUN go build - -RUN mv /src/simulator /src/simulatorclient - -CMD ["/src/simulatorclient","-protocol=http","-client=true","-port=11800","-address=mockserver","-count=100","-concurrency=1"," -qps=50","-delay=3"] diff --git a/test/e2e/test_cases/input_observer_http/Dockerfile_server b/test/e2e/test_cases/input_observer_http/Dockerfile_server deleted file mode 100644 index d27fc5ee70..0000000000 --- a/test/e2e/test_cases/input_observer_http/Dockerfile_server +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2021 iLogtail 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. - -FROM golang:1.16 - -WORKDIR /src - -COPY mock/* ./ -RUN go env -w GO111MODULE=on -RUN go env -w GOPROXY=https://goproxy.cn,direct -RUN go build - -EXPOSE 11800 - -CMD ["/src/simulator","-protocol=http","-client=false","-address=0.0.0.0","-port=11800","-count=100","-concurrency=1"," -qps=50"] diff --git a/test/e2e/test_cases/input_observer_http/case.feature b/test/e2e/test_cases/input_observer_http/case.feature deleted file mode 100644 index 0065b0bd00..0000000000 --- a/test/e2e/test_cases/input_observer_http/case.feature +++ /dev/null @@ -1,27 +0,0 @@ -@input -Feature: input observer http - Test input observer http - - @e2e @docker-compose @ebpf - Scenario: TestInputObserverHTTP - Given {docker-compose} environment - Given subcribe data from {grpc} with config - """ - """ - Given {input-observer-http-case} local config as below - """ - enable: true - inputs: - - Type: input_observer_network - Common: - FlushOutL7Interval: 1 - FlushOutL4Interval: 100 - IncludeCmdRegex: "^.*simulatorclient.*$" - IncludeProtocols: [ "HTTP" ] - EBPF: - Enabled: true - processors: - - Type: processor_default - """ - When start docker-compose {input_observer_http} - Then there is at least {100} logs \ No newline at end of file diff --git a/test/e2e/test_cases/input_observer_http/docker-compose.yaml b/test/e2e/test_cases/input_observer_http/docker-compose.yaml deleted file mode 100644 index c62fd057b5..0000000000 --- a/test/e2e/test_cases/input_observer_http/docker-compose.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2021 iLogtail 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. - -version: '3.8' - -services: - mockserver: - build: - context: . - dockerfile: Dockerfile_server - hostname: mockserver - environment: - - MOCKSERFVER=true - healthcheck: - test: "/bin/netstat -anpt|grep 11800" - interval: 30s - timeout: 3s - retries: 1 - mockclient: - build: - context: . - dockerfile: Dockerfile_client - hostname: mockclient - environment: - - MOCKCLIENT=true - depends_on: - - mockserver - - ilogtailC diff --git a/test/e2e/test_cases/input_observer_http/mock/go.mod b/test/e2e/test_cases/input_observer_http/mock/go.mod deleted file mode 100644 index dfaa999d6a..0000000000 --- a/test/e2e/test_cases/input_observer_http/mock/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module simulator - -go 1.16 - -require ( - github.com/go-sql-driver/mysql v1.6.0 - github.com/gorilla/mux v1.8.0 - github.com/lib/pq v1.10.4 -) diff --git a/test/e2e/test_cases/input_observer_http/mock/go.sum b/test/e2e/test_cases/input_observer_http/mock/go.sum deleted file mode 100644 index 3ede3cce3c..0000000000 --- a/test/e2e/test_cases/input_observer_http/mock/go.sum +++ /dev/null @@ -1,6 +0,0 @@ -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= -github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/test/e2e/test_cases/input_observer_http/mock/main.go b/test/e2e/test_cases/input_observer_http/mock/main.go deleted file mode 100644 index 3248d0df0f..0000000000 --- a/test/e2e/test_cases/input_observer_http/mock/main.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2022 iLogtail 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. - -package main - -import ( - "flag" - "fmt" - "io" - "math/rand" - "net/http" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - _ "github.com/go-sql-driver/mysql" - "github.com/gorilla/mux" - _ "github.com/lib/pq" -) - -var protocl = flag.String("protocol", "http", "protocl") -var client = flag.Bool("client", true, "client or server, true as client, false as server") -var qps = flag.Int("qps", 100, "qps") -var latency = flag.Int("latency", 10, "latency ms") -var count = flag.Int("count", 1000, "total count") -var concurrency = flag.Int("concurrency", 10, "concurrency") -var payload = flag.Int("payload", 100, "payload size") -var port = flag.Int("port", 8000, "port") -var address = flag.String("address", "127.0.0.1", "address") -var print = flag.Bool("print", true, "print logs") -var delay = flag.Int("delay", 1, "delay start") - -var payloadString strings.Builder - -func generatePayload(size int) { - baseStr := "1234567890!@#$%^&*()_+{}[];':,.<>./>?abcdef" - for payloadString.Len() < size { - payloadString.WriteString(baseStr) - } -} - -func indexHandler(w http.ResponseWriter, r *http.Request) { - if *latency > 0 { - time.Sleep(time.Millisecond * time.Duration(*latency)) - } - w.Write([]byte(payloadString.String())) -} - -func mockHttpServer() { - generatePayload(*payload) - rtr := mux.NewRouter() - rtr.HandleFunc("/api/{name:[0-9]+}/get", indexHandler).Methods("GET") - http.Handle("/", rtr) - http.ListenAndServe(*address+":"+strconv.Itoa(*port), nil) -} - -var ( - SuccessCount int64 - FailureCount int64 - TotalLatency int64 -) - -func mockHttpClient() { - http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = 100 - subQps := *qps / *concurrency - subCount := *count / *concurrency / subQps - var wg sync.WaitGroup - for i := 0; i < *concurrency; i++ { - wg.Add(1) - go func(index int) { - var httpClient = &http.Client{} - for j := 0; j < subCount; j++ { - nowTime := time.Now() - for k := 0; k < subQps; k++ { - // request - intn := rand.Intn(10) - var request *http.Request - var err error - if intn < 9 { - request, err = http.NewRequest("GET", "http://"+*address+":"+strconv.Itoa(*port)+"/api/"+strconv.Itoa(index)+"/get", nil) - } else { - request, err = http.NewRequest("GET", "http://"+*address+":"+strconv.Itoa(*port)+"/api1/"+strconv.Itoa(index)+"/get", nil) - } - if err != nil { - panic(err) - } - beforeTime := time.Now() - // don't use keep alive connection to avoid out-of-order when concurrency. - request.Close = true - resp, err := httpClient.Do(request) - afterTime := time.Now() - if err == nil { - atomic.AddInt64(&SuccessCount, 1) - atomic.AddInt64(&TotalLatency, afterTime.Sub(beforeTime).Microseconds()) - io.Copy(io.Discard, resp.Body) - resp.Body.Close() - if *print { - fmt.Printf("request : %s, response : %d \n", request.URL.Path, resp.StatusCode) - } - } else { - atomic.AddInt64(&FailureCount, 1) - if *print { - fmt.Printf("request : %s, error : %s \n", request.URL.Path, err) - } - } - } - if duration := time.Since(nowTime); duration < time.Second { - time.Sleep(time.Second - duration) - } - } - wg.Done() - }(i) - - } - wg.Wait() -} - -func main() { - flag.Parse() - fmt.Println("started...") - time.Sleep(time.Duration(*delay) * time.Second) - now := time.Now() - switch *protocl { - case "http": - if *client { - mockHttpClient() - } else { - mockHttpServer() - } - } - after := time.Now() - if SuccessCount > 0 && FailureCount > 0 { - fmt.Printf("success:%d failure:%d totalLatency:%d avgLatency:%d qps:%f", SuccessCount, FailureCount, TotalLatency, TotalLatency/SuccessCount, (float64(SuccessCount))/(after.Sub(now).Seconds())) - } - time.Sleep(time.Hour) -}