From 2ad4838cf54efdcc0d9ea6d01cf8e465ca13c83c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=2E=20Mert=20Y=C4=B1ld=C4=B1ran?= Date: Sun, 12 Jun 2022 04:14:41 -0700 Subject: [PATCH] Add Go `crypto/tls` eBPF tracer for TLS connections (#1120) * Run `go generate tls_tapper.go` * Add `golang_uprobes.c` * Add Golang hooks and offsets * Add `golangConnection` struct and implement `pollGolangReadWrite` method * Upgrade `github.com/cilium/ebpf` version to `v0.8.1` * Fix the linter error * Move map related stuff to `maps.h` and run `go generate tls_tapper.go` * Remove unused parameter * Add an environment variable to test Golang locally * Replace `Libssl` occurrences with `Ssllib` for consistency * Fix exe path finding * Temporarily disable OpenSSL * Fix the mixed offsets and dissection preparation * Change the read symbol from `net/http.(*persistConn).Read` to `crypto/tls.(*Conn).Read` * Remove `len` and `cap` fields * Fix the indent * Fix the read data address * Make `golang_dial_writes` key `__u64` and include the PID * Fix the read data address one more time * Temporarily disable the PCAP capture * Add a uprobe for `net/http.(*gzipReader).Read` to read chunked HTTP response body * Cancel `golang_crypto_tls_read_uprobe` if it's a gzip read * Make hash map names more meaningful * Pass the connection address from `write` to `gzip` through a common address between `gzip` and `dial` * Fix the probed line number links * Add `golangReader` struct and implement its `Read` method * Have a single counter pair and request response matcher per Golang connection * Add `MIZU_GLOBAL_GOLANG_PATH` environment variable * `NULL` terminate the bytes with `unix.ByteSliceToString` * Temporarily reject the gzip chunks * Add malformed TODOs * Revert "`NULL` terminate the bytes with `unix.ByteSliceToString`" This reverts commit 7ee7ef7e44473d17293ca4d43950f7cb52d952bb. * Bring back `len` and `cap` fields * Set `len` and `cap` in `golang_net_http_gzipreader_read_uprobe` as well * Remove two `TODO`s * Fix the `key_gzip` offsets * Compress if it's gzip chunk (probably wrong!) * Revert "Compress if it's gzip chunk (probably wrong!)" This reverts commit 094a7c3da462031390c43514fb3824b01fd8c2b5. * Remove `golang_net_http_gzipreader_read_uprobe` * Read constant 4KiB * Use constant read length * Get the correct len of bytes (saw the second entry) * Set all buffer sizes to `CHUNK_SIZE` * Remove a `TODO` * Revert "Temporarily disable the PCAP capture" This reverts commit a2da15ef2d2364159980fdcd6059be2c39ca267b. * Update `golang_crypto_tls_read_uprobe` * Set the `reader` field of `tlsStream` to fix a `nil pointer dereference` error * Don't export any fields of `golangConnection` * Close the reader when we drop the connection * Add a tracepoint for `sys_enter_close` to detect socket closes * Rename `socket` struct to `golang_socket` * Call `should_tap` in Golang uprobes * Add `log_error` calls * Revert "Temporarily disable OpenSSL" This reverts commit f54d9a453fd1858d4a05f529c3c7b0b55f13daa3. * Fix linter * Revert "Revert "Temporarily disable OpenSSL"" This reverts commit 2433d867afd68ea6df16ec8dcff715ebe4e15cb5. * Change `golang_read_writes` map type from `BPF_RINGBUF` to `BPF_PERF_OUTPUT` * Rename `golang_read_write` to `golang_event` * Define an error * Add comments * Revert "Revert "Revert "Temporarily disable OpenSSL""" This reverts commit e5a1de9c717f09f567763302940ae64b7415c8fa. * Fix `pollGolang` * Revert "Revert "Revert "Revert "Temporarily disable OpenSSL"""" This reverts commit 6e1bd5d4f3eaf5b3a60cdf9c60a62c0f9da84f6f. * Fix `panic: send on closed channel` * Revert "Revert "Revert "Revert "Revert "Temporarily disable OpenSSL""""" This reverts commit 57d05846559137a03bf52c1a9dff2b20ed583e43. * Use `findLibraryByPid` * Revert "Revert "Revert "Revert "Revert "Revert "Temporarily disable OpenSSL"""""" This reverts commit 46f3d290b0df8a80f90078a57bc870a7891a7471. * Revert "Revert "Revert "Revert "Revert "Revert "Revert "Temporarily disable OpenSSL""""""" This reverts commit 775c833c065d018c19081517f5c002f573cb3a01. * Log tapping Golang * Fix `Poll` * Refactor `golang_net_http_dialconn_uprobe` * Remove an excess error check * Fix `can only use path@version syntax with 'go get' and 'go install' in module-aware mode` error in `tap/tlstapper/bpf-builder/build.sh` * Unify Golang and OpenSSL under a single perf event buffer and `tls_chunk` struct * Generate `tlsTapperChunkType` type (enum) as well * Use kernel page size for the `sys_closes` perf buffer * Fix the linter error * Fix `MIZU_GLOBAL_GOLANG_PID` environment variable's functionality * Rely on tracepoints for file descriptor retrieval in Golang implementation * Remove the unnecessary changes * Move common functions into `common.c` * Declare `lookup_ssl_info` function to reduce duplication * Fix linter * Add comments and TODOs * Remove `MIZU_GLOBAL_GOLANG_PATH` environment variable * Update the object files * Fix indentation * Update object files * Add `go_abi_internal.h` * Fix `lookup_ssl_info` * Convert indentation to spaces * Add header guard comment * Add more comments * Find the `ret` instructions using Capstone Engine and `uprobe` the `return` statements * Implement `get_fd_from_tcp_conn` function * Separate SSL contexts to OpenSSL and Go * Move `get_count_bytes` from `common.c` to `openssl_uprobes.c` * Rename everything contains Golang to Go * Reduce duplication in `go_uprobes.c` * Update the comments * Install Capstone in CI and Docker native builds * Update `devops/install-capstone.sh` * Add Capstone to AArch64 cross-compilation target * Fix some of the issues on ARM64 * Delete the map element in `_ex_urpobe` * Remove an unsued `LOG_` macro * Rename `aquynh` to `capstone-engine` * Add comment * Revert "Fix some of the issues on ARM64" This reverts commit 0b3eceddf4c7547f05aa3351ded1e8a2dc7f56e0. * Revert "Revert "Fix some of the issues on ARM64"" This reverts commit 681534ada128f54191277d9813bc68a8730db515. * Update object files * Remove unnecessary return * Increase timeout * #run_acceptance_tests * #run_acceptance_tests * Fix the `arm64v8` sourced builds * #run_acceptance_tests --- .github/workflows/static_code_analysis.yml | 11 +- .github/workflows/test.yml | 5 + Dockerfile | 12 +- agent/go.mod | 4 +- agent/go.sum | 8 +- devops/install-capstone.sh | 12 + .../Dockerfile | 7 + .../build-push.sh | 4 + .../linux-arm64-musl-go-libpcap/build-push.sh | 4 - .../Dockerfile | 8 + .../build-push.sh | 4 + .../build-push.sh | 4 - tap/go.mod | 4 +- tap/go.sum | 11 +- tap/passive_tapper.go | 19 +- tap/tlstapper/bpf-builder/Dockerfile | 4 +- tap/tlstapper/bpf/common.c | 145 ++++++++++++ tap/tlstapper/bpf/fd_tracepoints.c | 8 +- tap/tlstapper/bpf/go_uprobes.c | 154 +++++++++++++ tap/tlstapper/bpf/include/common.h | 19 ++ tap/tlstapper/bpf/include/go_abi_internal.h | 141 ++++++++++++ tap/tlstapper/bpf/include/go_types.h | 15 ++ tap/tlstapper/bpf/include/maps.h | 38 +++- tap/tlstapper/bpf/openssl_uprobes.c | 202 +++-------------- tap/tlstapper/bpf/tls_tapper.c | 2 + tap/tlstapper/bpf_logger_messages.go | 4 +- tap/tlstapper/chunk.go | 32 +-- tap/tlstapper/go_hooks.go | 105 +++++++++ tap/tlstapper/go_offsets.go | 213 ++++++++++++++++++ tap/tlstapper/ssllib_finder.go | 2 +- tap/tlstapper/tls_poller.go | 29 +-- tap/tlstapper/tls_process_discoverer.go | 6 +- tap/tlstapper/tls_reader.go | 8 +- tap/tlstapper/tls_tapper.go | 60 ++++- tap/tlstapper/tlstapper_bpfeb.go | 99 +++++--- tap/tlstapper/tlstapper_bpfeb.o | Bin 116056 -> 155864 bytes tap/tlstapper/tlstapper_bpfel.go | 99 +++++--- tap/tlstapper/tlstapper_bpfel.o | Bin 116056 -> 156680 bytes 38 files changed, 1172 insertions(+), 330 deletions(-) create mode 100755 devops/install-capstone.sh rename devops/{linux-arm64-musl-go-libpcap => linux-arm64-musl-go-libpcap-capstone}/Dockerfile (74%) create mode 100755 devops/linux-arm64-musl-go-libpcap-capstone/build-push.sh delete mode 100755 devops/linux-arm64-musl-go-libpcap/build-push.sh rename devops/{linux-x86_64-musl-go-libpcap => linux-x86_64-musl-go-libpcap-capstone}/Dockerfile (82%) create mode 100755 devops/linux-x86_64-musl-go-libpcap-capstone/build-push.sh delete mode 100755 devops/linux-x86_64-musl-go-libpcap/build-push.sh create mode 100644 tap/tlstapper/bpf/common.c create mode 100644 tap/tlstapper/bpf/go_uprobes.c create mode 100644 tap/tlstapper/bpf/include/common.h create mode 100644 tap/tlstapper/bpf/include/go_abi_internal.h create mode 100644 tap/tlstapper/bpf/include/go_types.h create mode 100644 tap/tlstapper/go_hooks.go create mode 100644 tap/tlstapper/go_offsets.go diff --git a/.github/workflows/static_code_analysis.yml b/.github/workflows/static_code_analysis.yml index 92bb6348b..2f91d49b2 100644 --- a/.github/workflows/static_code_analysis.yml +++ b/.github/workflows/static_code_analysis.yml @@ -26,6 +26,7 @@ jobs: run: | sudo apt update sudo apt install -y libpcap-dev + ./devops/install-capstone.sh - name: Check Agent modified files id: agent_modified_files @@ -37,7 +38,7 @@ jobs: with: version: latest working-directory: agent - args: --timeout=3m + args: --timeout=10m - name: Check shared modified files id: shared_modified_files @@ -49,7 +50,7 @@ jobs: with: version: latest working-directory: shared - args: --timeout=3m + args: --timeout=10m - name: Check tap modified files id: tap_modified_files @@ -61,7 +62,7 @@ jobs: with: version: latest working-directory: tap - args: --timeout=3m + args: --timeout=10m - name: Check cli modified files id: cli_modified_files @@ -73,7 +74,7 @@ jobs: with: version: latest working-directory: cli - args: --timeout=3m + args: --timeout=10m - name: Check acceptanceTests modified files id: acceptanceTests_modified_files @@ -85,7 +86,7 @@ jobs: with: version: latest working-directory: acceptanceTests - args: --timeout=3m + args: --timeout=10m - name: Check tap/api modified files id: tap_api_modified_files diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7abf91fa2..b8d6bd0bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,6 +35,11 @@ jobs: run: | sudo apt-get install libpcap-dev + - name: Install Capstone + shell: bash + run: | + ./devops/install-capstone.sh + - name: Check CLI modified files id: cli_modified_files run: devops/check_modified_files.sh cli/ diff --git a/Dockerfile b/Dockerfile index 50ad7e7c3..1c3842ff6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,9 @@ RUN npm run build ### Base builder image for native builds architecture FROM golang:1.17-alpine AS builder-native-base ENV CGO_ENABLED=1 GOOS=linux -RUN apk add --no-cache libpcap-dev g++ perl-utils +RUN apk add --no-cache libpcap-dev g++ perl-utils curl build-base binutils-gold bash +COPY devops/install-capstone.sh . +RUN ./install-capstone.sh ### Intermediate builder image for x86-64 to x86-64 native builds @@ -39,15 +41,15 @@ ENV GOARCH=arm64 ### Builder image for x86-64 to AArch64 cross-compilation -FROM up9inc/linux-arm64-musl-go-libpcap AS builder-from-amd64-to-arm64v8 +FROM up9inc/linux-arm64-musl-go-libpcap-capstone AS builder-from-amd64-to-arm64v8 ENV CGO_ENABLED=1 GOOS=linux -ENV GOARCH=arm64 CGO_CFLAGS="-I/work/libpcap" +ENV GOARCH=arm64 CGO_CFLAGS="-I/work/libpcap -I/work/capstone/include" ### Builder image for AArch64 to x86-64 cross-compilation -FROM up9inc/linux-x86_64-musl-go-libpcap AS builder-from-arm64v8-to-amd64 +FROM up9inc/linux-x86_64-musl-go-libpcap-capstone AS builder-from-arm64v8-to-amd64 ENV CGO_ENABLED=1 GOOS=linux -ENV GOARCH=amd64 CGO_CFLAGS="-I/libpcap" +ENV GOARCH=amd64 CGO_CFLAGS="-I/libpcap -I/capstone/include" ### Final builder image where the build happens diff --git a/agent/go.mod b/agent/go.mod index 7074cf75a..52b2586a5 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -47,12 +47,13 @@ require ( github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/semver v1.5.0 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/beevik/etree v1.1.0 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect github.com/chanced/dynamic v0.0.0-20211210164248-f8fadb1d735b // indirect - github.com/cilium/ebpf v0.8.0 // indirect + github.com/cilium/ebpf v0.8.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect @@ -84,6 +85,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.14.2 // indirect + github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect diff --git a/agent/go.sum b/agent/go.sum index a080e07bf..6464e82aa 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -77,6 +77,8 @@ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3 github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -126,8 +128,8 @@ github.com/chanced/openapi v0.0.8/go.mod h1:SxE2VMLPw+T7Vq8nwbVVhDF2PigvRF4n5Xyq github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.8.0 h1:2V6KSg3FRADVU2BMIRemZ0hV+9OM+aAHhZDjQyjJTAs= -github.com/cilium/ebpf v0.8.0/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= +github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao= +github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -455,6 +457,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.14.2 h1:S0OHlFk/Gbon/yauFJ4FfJJF5V0fc5HbBTJazi28pRw= github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e h1:6J5obSn9umEThiYzWzndcPOZR0Qj/sVCZpH6V1G7yNE= +github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e/go.mod h1:1K5hEzsMBLTPdRJKEHqBFJ8Zt2VRqDhomcQ11KH0WW4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= diff --git a/devops/install-capstone.sh b/devops/install-capstone.sh new file mode 100755 index 000000000..4db790f1f --- /dev/null +++ b/devops/install-capstone.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +SUDO='' +if (( $EUID != 0 )); then + SUDO='sudo' +fi + +curl https://github.com/capstone-engine/capstone/archive/4.0.2.tar.gz -Lo ./capstone.tar.gz \ + && tar -xzf capstone.tar.gz && mv ./capstone-* ./capstone \ + && cd capstone \ + && CAPSTONE_ARCHS="aarch64 x86" ./make.sh \ + && $SUDO ./make.sh install diff --git a/devops/linux-arm64-musl-go-libpcap/Dockerfile b/devops/linux-arm64-musl-go-libpcap-capstone/Dockerfile similarity index 74% rename from devops/linux-arm64-musl-go-libpcap/Dockerfile rename to devops/linux-arm64-musl-go-libpcap-capstone/Dockerfile index 03f8fb7cf..6f9fb0c8a 100644 --- a/devops/linux-arm64-musl-go-libpcap/Dockerfile +++ b/devops/linux-arm64-musl-go-libpcap-capstone/Dockerfile @@ -17,4 +17,11 @@ RUN curl https://www.tcpdump.org/release/libpcap-1.10.1.tar.gz -Lo ./libpcap.tar WORKDIR /work/libpcap RUN ./configure --host=arm && make \ && cp /work/libpcap/libpcap.a /usr/xcc/aarch64-linux-musl-cross/lib/gcc/aarch64-linux-musl/*/ +WORKDIR /work +# Build and install Capstone from source +RUN curl https://github.com/capstone-engine/capstone/archive/4.0.2.tar.gz -Lo ./capstone.tar.gz \ + && tar -xzf capstone.tar.gz && mv ./capstone-* ./capstone +WORKDIR /work/capstone +RUN CAPSTONE_ARCHS="aarch64" CAPSTONE_STATIC=yes ./make.sh \ +&& cp /work/capstone/libcapstone.a /usr/xcc/aarch64-linux-musl-cross/lib/gcc/aarch64-linux-musl/*/ diff --git a/devops/linux-arm64-musl-go-libpcap-capstone/build-push.sh b/devops/linux-arm64-musl-go-libpcap-capstone/build-push.sh new file mode 100755 index 000000000..1048c1d52 --- /dev/null +++ b/devops/linux-arm64-musl-go-libpcap-capstone/build-push.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +docker build . -t up9inc/linux-arm64-musl-go-libpcap-capstone && docker push up9inc/linux-arm64-musl-go-libpcap-capstone diff --git a/devops/linux-arm64-musl-go-libpcap/build-push.sh b/devops/linux-arm64-musl-go-libpcap/build-push.sh deleted file mode 100755 index 904cb40f4..000000000 --- a/devops/linux-arm64-musl-go-libpcap/build-push.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -e - -docker build . -t up9inc/linux-arm64-musl-go-libpcap && docker push up9inc/linux-arm64-musl-go-libpcap diff --git a/devops/linux-x86_64-musl-go-libpcap/Dockerfile b/devops/linux-x86_64-musl-go-libpcap-capstone/Dockerfile similarity index 82% rename from devops/linux-x86_64-musl-go-libpcap/Dockerfile rename to devops/linux-x86_64-musl-go-libpcap-capstone/Dockerfile index b82454453..14b3e4903 100644 --- a/devops/linux-x86_64-musl-go-libpcap/Dockerfile +++ b/devops/linux-x86_64-musl-go-libpcap-capstone/Dockerfile @@ -29,3 +29,11 @@ RUN curl https://www.tcpdump.org/release/libpcap-1.10.1.tar.gz -Lo ./libpcap.tar WORKDIR /libpcap RUN ./configure --host=x86_64 && make \ && cp /libpcap/libpcap.a /usr/local/musl/lib/gcc/x86_64-unknown-linux-musl/*/ +WORKDIR / + +# Build and install Capstone from source +RUN curl https://github.com/capstone-engine/capstone/archive/4.0.2.tar.gz -Lo ./capstone.tar.gz \ + && tar -xzf capstone.tar.gz && mv ./capstone-* ./capstone +WORKDIR /capstone +RUN ./make.sh \ + && cp /capstone/libcapstone.a /usr/local/musl/lib/gcc/x86_64-unknown-linux-musl/*/ diff --git a/devops/linux-x86_64-musl-go-libpcap-capstone/build-push.sh b/devops/linux-x86_64-musl-go-libpcap-capstone/build-push.sh new file mode 100755 index 000000000..565f36705 --- /dev/null +++ b/devops/linux-x86_64-musl-go-libpcap-capstone/build-push.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +docker build . -t up9inc/linux-x86_64-musl-go-libpcap-capstone && docker push up9inc/linux-x86_64-musl-go-libpcap-capstone diff --git a/devops/linux-x86_64-musl-go-libpcap/build-push.sh b/devops/linux-x86_64-musl-go-libpcap/build-push.sh deleted file mode 100755 index 725366a64..000000000 --- a/devops/linux-x86_64-musl-go-libpcap/build-push.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -e - -docker build . -t up9inc/linux-x86_64-musl-go-libpcap && docker push up9inc/linux-x86_64-musl-go-libpcap diff --git a/tap/go.mod b/tap/go.mod index 67b1c3174..b3390fe0c 100644 --- a/tap/go.mod +++ b/tap/go.mod @@ -3,10 +3,12 @@ module github.com/up9inc/mizu/tap go 1.17 require ( - github.com/cilium/ebpf v0.8.0 + github.com/Masterminds/semver v1.5.0 + github.com/cilium/ebpf v0.8.1 github.com/go-errors/errors v1.4.2 github.com/google/gopacket v1.1.19 github.com/hashicorp/golang-lru v0.5.4 + github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e github.com/shirou/gopsutil v3.21.11+incompatible github.com/struCoder/pidusage v0.2.1 github.com/up9inc/mizu/logger v0.0.0 diff --git a/tap/go.sum b/tap/go.sum index 786480904..d57e92ed0 100644 --- a/tap/go.sum +++ b/tap/go.sum @@ -1,12 +1,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cilium/ebpf v0.8.0 h1:2V6KSg3FRADVU2BMIRemZ0hV+9OM+aAHhZDjQyjJTAs= -github.com/cilium/ebpf v0.8.0/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= +github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao= +github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -81,6 +83,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e h1:6J5obSn9umEThiYzWzndcPOZR0Qj/sVCZpH6V1G7yNE= +github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e/go.mod h1:1K5hEzsMBLTPdRJKEHqBFJ8Zt2VRqDhomcQ11KH0WW4= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -290,5 +294,6 @@ sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/tap/passive_tapper.go b/tap/passive_tapper.go index 20d15a8a8..b62ce3b40 100644 --- a/tap/passive_tapper.go +++ b/tap/passive_tapper.go @@ -14,9 +14,9 @@ import ( "fmt" "os" "runtime" + "strconv" "strings" "time" - "strconv" "github.com/shirou/gopsutil/cpu" "github.com/struCoder/pidusage" @@ -59,8 +59,8 @@ var tls = flag.Bool("tls", false, "Enable TLS tapper") var memprofile = flag.String("memprofile", "", "Write memory profile") type TapOpts struct { - HostMode bool - IgnoredPorts []uint16 + HostMode bool + IgnoredPorts []uint16 } var extensions []*api.Extension // global @@ -100,7 +100,7 @@ func UpdateTapTargets(newTapTargets []v1.Pod) { packetSourceManager.UpdatePods(tapTargets, !*nodefrag, mainPacketInputChan) - if tlsTapperInstance != nil { + if tlsTapperInstance != nil && os.Getenv("MIZU_GLOBAL_GOLANG_PID") == "" { if err := tlstapper.UpdateTapTargets(tlsTapperInstance, &tapTargets, *procfs); err != nil { tlstapper.LogError(err) success = false @@ -278,7 +278,16 @@ func startTlsTapper(extension *api.Extension, outputItems chan *api.OutputChanne // A quick way to instrument libssl.so without PID filtering - used for debuging and troubleshooting // if os.Getenv("MIZU_GLOBAL_SSL_LIBRARY") != "" { - if err := tls.GlobalTap(os.Getenv("MIZU_GLOBAL_SSL_LIBRARY")); err != nil { + if err := tls.GlobalSsllibTap(os.Getenv("MIZU_GLOBAL_SSL_LIBRARY")); err != nil { + tlstapper.LogError(err) + return nil + } + } + + // A quick way to instrument Go `crypto/tls` without PID filtering - used for debuging and troubleshooting + // + if os.Getenv("MIZU_GLOBAL_GOLANG_PID") != "" { + if err := tls.GlobalGoTap(*procfs, os.Getenv("MIZU_GLOBAL_GOLANG_PID")); err != nil { tlstapper.LogError(err) return nil } diff --git a/tap/tlstapper/bpf-builder/Dockerfile b/tap/tlstapper/bpf-builder/Dockerfile index c817218cc..d16b41a2d 100644 --- a/tap/tlstapper/bpf-builder/Dockerfile +++ b/tap/tlstapper/bpf-builder/Dockerfile @@ -1,5 +1,5 @@ -FROM alpine:3.14 +FROM golang:1.17-alpine -RUN apk --no-cache update && apk --no-cache add clang llvm libbpf-dev go linux-headers +RUN apk --no-cache update && apk --no-cache add clang llvm libbpf-dev linux-headers WORKDIR /mizu diff --git a/tap/tlstapper/bpf/common.c b/tap/tlstapper/bpf/common.c new file mode 100644 index 000000000..d7f42f91b --- /dev/null +++ b/tap/tlstapper/bpf/common.c @@ -0,0 +1,145 @@ +/* +Note: This file is licenced differently from the rest of the project +SPDX-License-Identifier: GPL-2.0 +Copyright (C) UP9 Inc. +*/ + +#include "include/headers.h" +#include "include/util.h" +#include "include/maps.h" +#include "include/log.h" +#include "include/logger_messages.h" +#include "include/common.h" + + +static __always_inline int add_address_to_chunk(struct pt_regs *ctx, struct tls_chunk* chunk, __u64 id, __u32 fd) { + __u32 pid = id >> 32; + __u64 key = (__u64) pid << 32 | fd; + + struct fd_info *fdinfo = bpf_map_lookup_elem(&file_descriptor_to_ipv4, &key); + + if (fdinfo == NULL) { + return 0; + } + + int err = bpf_probe_read(chunk->address, sizeof(chunk->address), fdinfo->ipv4_addr); + chunk->flags |= (fdinfo->flags & FLAGS_IS_CLIENT_BIT); + + if (err != 0) { + log_error(ctx, LOG_ERROR_READING_FD_ADDRESS, id, err, 0l); + return 0; + } + + return 1; +} + +static __always_inline void send_chunk_part(struct pt_regs *ctx, __u8* buffer, __u64 id, + struct tls_chunk* chunk, int start, int end) { + size_t recorded = MIN(end - start, sizeof(chunk->data)); + + if (recorded <= 0) { + return; + } + + chunk->recorded = recorded; + chunk->start = start; + + // This ugly trick is for the ebpf verifier happiness + // + long err = 0; + if (chunk->recorded == sizeof(chunk->data)) { + err = bpf_probe_read(chunk->data, sizeof(chunk->data), buffer + start); + } else { + recorded &= (sizeof(chunk->data) - 1); // Buffer must be N^2 + err = bpf_probe_read(chunk->data, recorded, buffer + start); + } + + if (err != 0) { + log_error(ctx, LOG_ERROR_READING_FROM_SSL_BUFFER, id, err, 0l); + return; + } + + bpf_perf_event_output(ctx, &chunks_buffer, BPF_F_CURRENT_CPU, chunk, sizeof(struct tls_chunk)); +} + +static __always_inline void send_chunk(struct pt_regs *ctx, __u8* buffer, __u64 id, struct tls_chunk* chunk) { + // ebpf loops must be bounded at compile time, we can't use (i < chunk->len / CHUNK_SIZE) + // + // https://lwn.net/Articles/794934/ + // + // However we want to run in kernel older than 5.3, hence we use "#pragma unroll" anyway + // + #pragma unroll + for (int i = 0; i < MAX_CHUNKS_PER_OPERATION; i++) { + if (chunk->len <= (CHUNK_SIZE * i)) { + break; + } + + send_chunk_part(ctx, buffer, id, chunk, CHUNK_SIZE * i, chunk->len); + } +} + +static __always_inline void output_ssl_chunk(struct pt_regs *ctx, struct ssl_info* info, int count_bytes, __u64 id, __u32 flags) { + if (count_bytes <= 0) { + return; + } + + if (count_bytes > (CHUNK_SIZE * MAX_CHUNKS_PER_OPERATION)) { + log_error(ctx, LOG_ERROR_BUFFER_TOO_BIG, id, count_bytes, 0l); + return; + } + + struct tls_chunk* chunk; + int zero = 0; + + // If other thread, running on the same CPU get to this point at the same time like us (context switch) + // the data will be corrupted - protection may be added in the future + // + chunk = bpf_map_lookup_elem(&heap, &zero); + + if (!chunk) { + log_error(ctx, LOG_ERROR_ALLOCATING_CHUNK, id, 0l, 0l); + return; + } + + chunk->flags = flags; + chunk->pid = id >> 32; + chunk->tgid = id; + chunk->len = count_bytes; + chunk->fd = info->fd; + + if (!add_address_to_chunk(ctx, chunk, id, chunk->fd)) { + // Without an address, we drop the chunk because there is not much to do with it in Go + // + return; + } + + send_chunk(ctx, info->buffer, id, chunk); +} + +static __always_inline struct ssl_info new_ssl_info() { + struct ssl_info info = { .fd = invalid_fd, .created_at_nano = bpf_ktime_get_ns() }; + return info; +} + +static __always_inline struct ssl_info lookup_ssl_info(struct pt_regs *ctx, struct bpf_map_def* map_fd, __u64 pid_tgid) { + struct ssl_info *infoPtr = bpf_map_lookup_elem(map_fd, &pid_tgid); + struct ssl_info info = new_ssl_info(); + + if (infoPtr != NULL) { + long err = bpf_probe_read(&info, sizeof(struct ssl_info), infoPtr); + + if (err != 0) { + log_error(ctx, LOG_ERROR_READING_SSL_CONTEXT, pid_tgid, err, ORIGIN_SSL_UPROBE_CODE); + } + + if ((bpf_ktime_get_ns() - info.created_at_nano) > SSL_INFO_MAX_TTL_NANO) { + // If the ssl info is too old, we don't want to use its info because it may be incorrect. + // + info.fd = invalid_fd; + info.created_at_nano = bpf_ktime_get_ns(); + } + } + + return info; +} diff --git a/tap/tlstapper/bpf/fd_tracepoints.c b/tap/tlstapper/bpf/fd_tracepoints.c index 88add7f44..464da0ff0 100644 --- a/tap/tlstapper/bpf/fd_tracepoints.c +++ b/tap/tlstapper/bpf/fd_tracepoints.c @@ -28,7 +28,7 @@ void sys_enter_read(struct sys_enter_read_ctx *ctx) { return; } - struct ssl_info *infoPtr = bpf_map_lookup_elem(&ssl_read_context, &id); + struct ssl_info *infoPtr = bpf_map_lookup_elem(&openssl_read_context, &id); if (infoPtr == NULL) { return; @@ -44,7 +44,7 @@ void sys_enter_read(struct sys_enter_read_ctx *ctx) { info.fd = ctx->fd; - err = bpf_map_update_elem(&ssl_read_context, &id, &info, BPF_ANY); + err = bpf_map_update_elem(&openssl_read_context, &id, &info, BPF_ANY); if (err != 0) { log_error(ctx, LOG_ERROR_PUTTING_FILE_DESCRIPTOR, id, err, ORIGIN_SYS_ENTER_READ_CODE); @@ -68,7 +68,7 @@ void sys_enter_write(struct sys_enter_write_ctx *ctx) { return; } - struct ssl_info *infoPtr = bpf_map_lookup_elem(&ssl_write_context, &id); + struct ssl_info *infoPtr = bpf_map_lookup_elem(&openssl_write_context, &id); if (infoPtr == NULL) { return; @@ -84,7 +84,7 @@ void sys_enter_write(struct sys_enter_write_ctx *ctx) { info.fd = ctx->fd; - err = bpf_map_update_elem(&ssl_write_context, &id, &info, BPF_ANY); + err = bpf_map_update_elem(&openssl_write_context, &id, &info, BPF_ANY); if (err != 0) { log_error(ctx, LOG_ERROR_PUTTING_FILE_DESCRIPTOR, id, err, ORIGIN_SYS_ENTER_WRITE_CODE); diff --git a/tap/tlstapper/bpf/go_uprobes.c b/tap/tlstapper/bpf/go_uprobes.c new file mode 100644 index 000000000..d06930918 --- /dev/null +++ b/tap/tlstapper/bpf/go_uprobes.c @@ -0,0 +1,154 @@ +/* +Note: This file is licenced differently from the rest of the project +SPDX-License-Identifier: GPL-2.0 +Copyright (C) UP9 Inc. + + +--- + +README + +Go does not follow any platform ABI like x86-64 System V ABI. +Before 1.17, Go followed stack-based Plan9 (Bell Labs) calling convention. (ABI0) +After 1.17, Go switched to an internal register-based calling convention. (ABIInternal) +For now, the probes in this file supports only ABIInternal (Go 1.17+) + +`uretprobe` in Linux kernel uses trampoline pattern to jump to original return +address of the probed function. A Goroutine's stack size is 2Kb while a C thread is 2MB on Linux. +If stack size exceeds 2Kb, Go runtime relocates the stack. That causes the +return address to become incorrect in case of `uretprobe` and probed Go program crashes. +Therefore `uretprobe` CAN'T BE USED for a Go program. + +`_ex_uprobe` suffixed probes suppose to be `uretprobe`(s) are actually `uprobe`(s) +because of the non-standard ABI of Go. Therefore we probe all `ret` mnemonics under the symbol +by automatically finding them through reading the ELF binary and disassembling the symbols. +Disassembly related code located in `go_offsets.go` file and it uses Capstone Engine. +Solution based on: https://github.com/iovisor/bcc/issues/1320#issuecomment-407927542 +*Example* We probe an arbitrary point in a function body (offset +559): +https://github.com/golang/go/blob/go1.17.6/src/crypto/tls/conn.go#L1299 + +We get the file descriptor using the common $rax register that holds the address +of `go.itab.*net.TCPConn,net.Conn` and through a series of dereferencing +using `bpf_probe_read` calls in `go_crypto_tls_get_fd_from_tcp_conn` function. + +--- + +SOURCES: + +Tracing Go Functions with eBPF (before 1.17): https://www.grant.pizza/blog/tracing-go-functions-with-ebpf-part-2/ +Challenges of BPF Tracing Go: https://blog.0x74696d.com/posts/challenges-of-bpf-tracing-go/ +x86 calling conventions: https://en.wikipedia.org/wiki/X86_calling_conventions +Plan 9 from Bell Labs: https://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs +The issue for calling convention change in Go: https://github.com/golang/go/issues/40724 +Proposal of Register-based Go calling convention: https://go.googlesource.com/proposal/+/master/design/40724-register-calling.md +Go internal ABI (1.17) specification: https://go.googlesource.com/go/+/refs/heads/dev.regabi/src/cmd/compile/internal-abi.md +Go internal ABI (current) specification: https://go.googlesource.com/go/+/refs/heads/master/src/cmd/compile/abi-internal.md +A Quick Guide to Go's Assembler: https://go.googlesource.com/go/+/refs/heads/dev.regabi/doc/asm.html +Dissecting Go Binaries: https://www.grant.pizza/blog/dissecting-go-binaries/ +Capstone Engine: https://www.capstone-engine.org/ +*/ + +#include "include/headers.h" +#include "include/util.h" +#include "include/maps.h" +#include "include/log.h" +#include "include/logger_messages.h" +#include "include/pids.h" +#include "include/common.h" +#include "include/go_abi_internal.h" +#include "include/go_types.h" + +static __always_inline __u32 go_crypto_tls_get_fd_from_tcp_conn(struct pt_regs *ctx) { + struct go_interface conn; + long err = bpf_probe_read(&conn, sizeof(conn), (void*)GO_ABI_INTERNAL_PT_REGS_R1(ctx)); + if (err != 0) { + return invalid_fd; + } + + void* net_fd_ptr; + err = bpf_probe_read(&net_fd_ptr, sizeof(net_fd_ptr), conn.ptr); + if (err != 0) { + return invalid_fd; + } + + __u32 fd; + err = bpf_probe_read(&fd, sizeof(fd), net_fd_ptr + 0x10); + if (err != 0) { + return invalid_fd; + } + + return fd; +} + +static __always_inline void go_crypto_tls_uprobe(struct pt_regs *ctx, struct bpf_map_def* go_context) { + __u64 pid_tgid = bpf_get_current_pid_tgid(); + __u64 pid = pid_tgid >> 32; + if (!should_tap(pid)) { + return; + } + + struct ssl_info info = new_ssl_info(); + + info.buffer_len = GO_ABI_INTERNAL_PT_REGS_R2(ctx); + info.buffer = (void*)GO_ABI_INTERNAL_PT_REGS_R4(ctx); + info.fd = go_crypto_tls_get_fd_from_tcp_conn(ctx); + + // GO_ABI_INTERNAL_PT_REGS_GP is Goroutine address + __u64 pid_fp = pid << 32 | GO_ABI_INTERNAL_PT_REGS_GP(ctx); + long err = bpf_map_update_elem(go_context, &pid_fp, &info, BPF_ANY); + + if (err != 0) { + log_error(ctx, LOG_ERROR_PUTTING_SSL_CONTEXT, pid_tgid, err, 0l); + } + + return; +} + +static __always_inline void go_crypto_tls_ex_uprobe(struct pt_regs *ctx, struct bpf_map_def* go_context, __u32 flags) { + __u64 pid_tgid = bpf_get_current_pid_tgid(); + __u64 pid = pid_tgid >> 32; + if (!should_tap(pid)) { + return; + } + + // GO_ABI_INTERNAL_PT_REGS_GP is Goroutine address + __u64 pid_fp = pid << 32 | GO_ABI_INTERNAL_PT_REGS_GP(ctx); + struct ssl_info *info_ptr = bpf_map_lookup_elem(go_context, &pid_fp); + + if (info_ptr == NULL) { + return; + } + bpf_map_delete_elem(go_context, &pid_fp); + + struct ssl_info info; + long err = bpf_probe_read(&info, sizeof(struct ssl_info), info_ptr); + + if (err != 0) { + log_error(ctx, LOG_ERROR_READING_SSL_CONTEXT, pid_tgid, err, ORIGIN_SSL_URETPROBE_CODE); + return; + } + + output_ssl_chunk(ctx, &info, info.buffer_len, pid_tgid, flags); + + return; +} + +SEC("uprobe/go_crypto_tls_write") +void BPF_KPROBE(go_crypto_tls_write) { + go_crypto_tls_uprobe(ctx, &go_write_context); +} + +SEC("uprobe/go_crypto_tls_write_ex") +void BPF_KPROBE(go_crypto_tls_write_ex) { + go_crypto_tls_ex_uprobe(ctx, &go_write_context, 0); +} + +SEC("uprobe/go_crypto_tls_read") +void BPF_KPROBE(go_crypto_tls_read) { + go_crypto_tls_uprobe(ctx, &go_read_context); +} + +SEC("uprobe/go_crypto_tls_read_ex") +void BPF_KPROBE(go_crypto_tls_read_ex) { + go_crypto_tls_ex_uprobe(ctx, &go_read_context, FLAGS_IS_READ_BIT); +} diff --git a/tap/tlstapper/bpf/include/common.h b/tap/tlstapper/bpf/include/common.h new file mode 100644 index 000000000..613376396 --- /dev/null +++ b/tap/tlstapper/bpf/include/common.h @@ -0,0 +1,19 @@ +/* +Note: This file is licenced differently from the rest of the project +SPDX-License-Identifier: GPL-2.0 +Copyright (C) UP9 Inc. +*/ + +#ifndef __COMMON__ +#define __COMMON__ + +const __s32 invalid_fd = -1; + +static int add_address_to_chunk(struct pt_regs *ctx, struct tls_chunk* chunk, __u64 id, __u32 fd); +static void send_chunk_part(struct pt_regs *ctx, __u8* buffer, __u64 id, struct tls_chunk* chunk, int start, int end); +static void send_chunk(struct pt_regs *ctx, __u8* buffer, __u64 id, struct tls_chunk* chunk); +static void output_ssl_chunk(struct pt_regs *ctx, struct ssl_info* info, int count_bytes, __u64 id, __u32 flags); +static struct ssl_info new_ssl_info(); +static struct ssl_info lookup_ssl_info(struct pt_regs *ctx, struct bpf_map_def* map_fd, __u64 pid_tgid); + +#endif /* __COMMON__ */ diff --git a/tap/tlstapper/bpf/include/go_abi_internal.h b/tap/tlstapper/bpf/include/go_abi_internal.h new file mode 100644 index 000000000..368b32679 --- /dev/null +++ b/tap/tlstapper/bpf/include/go_abi_internal.h @@ -0,0 +1,141 @@ +/* +Note: This file is licenced differently from the rest of the project +SPDX-License-Identifier: GPL-2.0 +Copyright (C) UP9 Inc. +*/ + +#ifndef __GO_ABI_INTERNAL__ +#define __GO_ABI_INTERNAL__ + +/* +Go internal ABI specification +https://go.googlesource.com/go/+/refs/heads/master/src/cmd/compile/abi-internal.md +*/ + +/* Scan the ARCH passed in from ARCH env variable */ +#if defined(__TARGET_ARCH_x86) + #define bpf_target_x86 + #define bpf_target_defined +#elif defined(__TARGET_ARCH_s390) + #define bpf_target_s390 + #define bpf_target_defined +#elif defined(__TARGET_ARCH_arm) + #define bpf_target_arm + #define bpf_target_defined +#elif defined(__TARGET_ARCH_arm64) + #define bpf_target_arm64 + #define bpf_target_defined +#elif defined(__TARGET_ARCH_mips) + #define bpf_target_mips + #define bpf_target_defined +#elif defined(__TARGET_ARCH_powerpc) + #define bpf_target_powerpc + #define bpf_target_defined +#elif defined(__TARGET_ARCH_sparc) + #define bpf_target_sparc + #define bpf_target_defined +#else + #undef bpf_target_defined +#endif + +/* Fall back to what the compiler says */ +#ifndef bpf_target_defined +#if defined(__x86_64__) + #define bpf_target_x86 +#elif defined(__s390__) + #define bpf_target_s390 +#elif defined(__arm__) + #define bpf_target_arm +#elif defined(__aarch64__) + #define bpf_target_arm64 +#elif defined(__mips__) + #define bpf_target_mips +#elif defined(__powerpc__) + #define bpf_target_powerpc +#elif defined(__sparc__) + #define bpf_target_sparc +#endif +#endif + +#if defined(bpf_target_x86) + +#ifdef __i386__ + +/* +https://go.googlesource.com/go/+/refs/heads/dev.regabi/src/cmd/compile/internal-abi.md#amd64-architecture +https://github.com/golang/go/blob/go1.17.6/src/cmd/compile/internal/ssa/gen/AMD64Ops.go#L100 +*/ +#define GO_ABI_INTERNAL_PT_REGS_R1(x) ((x)->eax) +#define GO_ABI_INTERNAL_PT_REGS_P2(x) ((x)->ecx) +#define GO_ABI_INTERNAL_PT_REGS_P3(x) ((x)->edx) +#define GO_ABI_INTERNAL_PT_REGS_P4(x) 0 +#define GO_ABI_INTERNAL_PT_REGS_P5(x) 0 +#define GO_ABI_INTERNAL_PT_REGS_P6(x) 0 +#define GO_ABI_INTERNAL_PT_REGS_SP(x) ((x)->esp) +#define GO_ABI_INTERNAL_PT_REGS_FP(x) ((x)->ebp) +#define GO_ABI_INTERNAL_PT_REGS_GP(x) ((x)->e14) + +#else + +#define GO_ABI_INTERNAL_PT_REGS_R1(x) ((x)->rax) +#define GO_ABI_INTERNAL_PT_REGS_R2(x) ((x)->rcx) +#define GO_ABI_INTERNAL_PT_REGS_R3(x) ((x)->rdx) +#define GO_ABI_INTERNAL_PT_REGS_R4(x) ((x)->rbx) +#define GO_ABI_INTERNAL_PT_REGS_R5(x) ((x)->rbp) +#define GO_ABI_INTERNAL_PT_REGS_R6(x) ((x)->rsi) +#define GO_ABI_INTERNAL_PT_REGS_SP(x) ((x)->rsp) +#define GO_ABI_INTERNAL_PT_REGS_FP(x) ((x)->rbp) +#define GO_ABI_INTERNAL_PT_REGS_GP(x) ((x)->r14) + +#endif + +#elif defined(bpf_target_arm) + +/* +https://go.googlesource.com/go/+/refs/heads/master/src/cmd/compile/abi-internal.md#arm64-architecture +https://github.com/golang/go/blob/go1.17.6/src/cmd/compile/internal/ssa/gen/ARM64Ops.go#L129-L131 +*/ +#define GO_ABI_INTERNAL_PT_REGS_R1(x) ((x)->uregs[0]) +#define GO_ABI_INTERNAL_PT_REGS_R2(x) ((x)->uregs[1]) +#define GO_ABI_INTERNAL_PT_REGS_R3(x) ((x)->uregs[2]) +#define GO_ABI_INTERNAL_PT_REGS_R4(x) ((x)->uregs[3]) +#define GO_ABI_INTERNAL_PT_REGS_R5(x) ((x)->uregs[4]) +#define GO_ABI_INTERNAL_PT_REGS_R6(x) ((x)->uregs[5]) +#define GO_ABI_INTERNAL_PT_REGS_SP(x) ((x)->uregs[14]) +#define GO_ABI_INTERNAL_PT_REGS_FP(x) ((x)->uregs[29]) +#define GO_ABI_INTERNAL_PT_REGS_GP(x) ((x)->uregs[28]) + +#elif defined(bpf_target_arm64) + +/* arm64 provides struct user_pt_regs instead of struct pt_regs to userspace */ +struct pt_regs; +#define PT_REGS_ARM64 const volatile struct user_pt_regs +#define GO_ABI_INTERNAL_PT_REGS_R1(x) (((PT_REGS_ARM64 *)(x))->regs[0]) +#define GO_ABI_INTERNAL_PT_REGS_R2(x) (((PT_REGS_ARM64 *)(x))->regs[1]) +#define GO_ABI_INTERNAL_PT_REGS_R3(x) (((PT_REGS_ARM64 *)(x))->regs[2]) +#define GO_ABI_INTERNAL_PT_REGS_R4(x) (((PT_REGS_ARM64 *)(x))->regs[3]) +#define GO_ABI_INTERNAL_PT_REGS_R5(x) (((PT_REGS_ARM64 *)(x))->regs[4]) +#define GO_ABI_INTERNAL_PT_REGS_R6(x) (((PT_REGS_ARM64 *)(x))->regs[5]) +#define GO_ABI_INTERNAL_PT_REGS_SP(x) (((PT_REGS_ARM64 *)(x))->regs[30]) +#define GO_ABI_INTERNAL_PT_REGS_FP(x) (((PT_REGS_ARM64 *)(x))->regs[29]) +#define GO_ABI_INTERNAL_PT_REGS_GP(x) (((PT_REGS_ARM64 *)(x))->regs[28]) + +#elif defined(bpf_target_powerpc) + +/* +https://go.googlesource.com/go/+/refs/heads/master/src/cmd/compile/abi-internal.md#ppc64-architecture +https://github.com/golang/go/blob/go1.17.6/src/cmd/compile/internal/ssa/gen/PPC64Ops.go#L125-L127 +*/ +#define GO_ABI_INTERNAL_PT_REGS_R1(x) ((x)->gpr[3]) +#define GO_ABI_INTERNAL_PT_REGS_R2(x) ((x)->gpr[4]) +#define GO_ABI_INTERNAL_PT_REGS_R3(x) ((x)->gpr[5]) +#define GO_ABI_INTERNAL_PT_REGS_R4(x) ((x)->gpr[6]) +#define GO_ABI_INTERNAL_PT_REGS_R5(x) ((x)->gpr[7]) +#define GO_ABI_INTERNAL_PT_REGS_R6(x) ((x)->gpr[8]) +#define GO_ABI_INTERNAL_PT_REGS_SP(x) ((x)->sp) +#define GO_ABI_INTERNAL_PT_REGS_FP(x) ((x)->gpr[12]) +#define GO_ABI_INTERNAL_PT_REGS_GP(x) ((x)->gpr[30]) + +#endif + +#endif /* __GO_ABI_INTERNAL__ */ diff --git a/tap/tlstapper/bpf/include/go_types.h b/tap/tlstapper/bpf/include/go_types.h new file mode 100644 index 000000000..c865448c6 --- /dev/null +++ b/tap/tlstapper/bpf/include/go_types.h @@ -0,0 +1,15 @@ +/* +Note: This file is licenced differently from the rest of the project +SPDX-License-Identifier: GPL-2.0 +Copyright (C) UP9 Inc. +*/ + +#ifndef __GO_TYPES__ +#define __GO_TYPES__ + +struct go_interface { + __s64 type; + void* ptr; +}; + +#endif /* __GO_TYPES__ */ diff --git a/tap/tlstapper/bpf/include/maps.h b/tap/tlstapper/bpf/include/maps.h index 0774e775b..f8c799d13 100644 --- a/tap/tlstapper/bpf/include/maps.h +++ b/tap/tlstapper/bpf/include/maps.h @@ -16,11 +16,15 @@ Copyright (C) UP9 Inc. // One minute in nano seconds. Chosen by gut feeling. #define SSL_INFO_MAX_TTL_NANO (1000000000l * 60l) +#define MAX_ENTRIES_HASH (1 << 12) // 4096 +#define MAX_ENTRIES_PERF_OUTPUT (1 << 10) // 1024 +#define MAX_ENTRIES_LRU_HASH (1 << 14) // 16384 + // The same struct can be found in chunk.go // // Be careful when editing, alignment and padding should be exactly the same in go/c. // -struct tlsChunk { +struct tls_chunk { __u32 pid; __u32 tgid; __u32 len; @@ -34,6 +38,7 @@ struct tlsChunk { struct ssl_info { void* buffer; + __u32 buffer_len; __u32 fd; __u64 created_at_nano; @@ -48,6 +53,16 @@ struct fd_info { __u8 flags; }; +// Heap-like area for eBPF programs - stack size limited to 512 bytes, we must use maps for bigger (chunk) objects. +// +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __uint(max_entries, 1); + __type(key, int); + __type(value, struct tls_chunk); +} heap SEC(".maps"); + + #define BPF_MAP(_name, _type, _key_type, _value_type, _max_entries) \ struct bpf_map_def SEC("maps") _name = { \ .type = _type, \ @@ -57,19 +72,26 @@ struct fd_info { }; #define BPF_HASH(_name, _key_type, _value_type) \ - BPF_MAP(_name, BPF_MAP_TYPE_HASH, _key_type, _value_type, 4096) + BPF_MAP(_name, BPF_MAP_TYPE_HASH, _key_type, _value_type, MAX_ENTRIES_HASH) #define BPF_PERF_OUTPUT(_name) \ - BPF_MAP(_name, BPF_MAP_TYPE_PERF_EVENT_ARRAY, int, __u32, 1024) - -#define BPF_LRU_HASH(_name, _key_type, _value_type) \ - BPF_MAP(_name, BPF_MAP_TYPE_LRU_HASH, _key_type, _value_type, 16384) + BPF_MAP(_name, BPF_MAP_TYPE_PERF_EVENT_ARRAY, int, __u32, MAX_ENTRIES_PERF_OUTPUT) +#define BPF_LRU_HASH(_name, _key_type, _value_type) \ + BPF_MAP(_name, BPF_MAP_TYPE_LRU_HASH, _key_type, _value_type, MAX_ENTRIES_LRU_HASH) + +// Generic BPF_HASH(pids_map, __u32, __u32); -BPF_LRU_HASH(ssl_write_context, __u64, struct ssl_info); -BPF_LRU_HASH(ssl_read_context, __u64, struct ssl_info); BPF_LRU_HASH(file_descriptor_to_ipv4, __u64, struct fd_info); BPF_PERF_OUTPUT(chunks_buffer); BPF_PERF_OUTPUT(log_buffer); +// OpenSSL specific +BPF_LRU_HASH(openssl_write_context, __u64, struct ssl_info); +BPF_LRU_HASH(openssl_read_context, __u64, struct ssl_info); + +// Go specific +BPF_LRU_HASH(go_write_context, __u64, struct ssl_info); +BPF_LRU_HASH(go_read_context, __u64, struct ssl_info); + #endif /* __MAPS__ */ diff --git a/tap/tlstapper/bpf/openssl_uprobes.c b/tap/tlstapper/bpf/openssl_uprobes.c index d4efa3392..2f5151da9 100644 --- a/tap/tlstapper/bpf/openssl_uprobes.c +++ b/tap/tlstapper/bpf/openssl_uprobes.c @@ -10,149 +10,35 @@ Copyright (C) UP9 Inc. #include "include/log.h" #include "include/logger_messages.h" #include "include/pids.h" +#include "include/common.h" -// Heap-like area for eBPF programs - stack size limited to 512 bytes, we must use maps for bigger (chunk) objects. -// -struct { - __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); - __uint(max_entries, 1); - __type(key, int); - __type(value, struct tlsChunk); -} heap SEC(".maps"); static __always_inline int get_count_bytes(struct pt_regs *ctx, struct ssl_info* info, __u64 id) { - int returnValue = PT_REGS_RC(ctx); - - if (info->count_ptr == NULL) { - // ssl_read and ssl_write return the number of bytes written/read - // - return returnValue; - } - - // ssl_read_ex and ssl_write_ex return 1 for success - // - if (returnValue != 1) { - return 0; - } - - // ssl_read_ex and ssl_write_ex write the number of bytes to an arg named *count - // - size_t countBytes; - long err = bpf_probe_read(&countBytes, sizeof(size_t), (void*) info->count_ptr); - - if (err != 0) { - log_error(ctx, LOG_ERROR_READING_BYTES_COUNT, id, err, 0l); - return 0; - } - - return countBytes; -} + int returnValue = PT_REGS_RC(ctx); -static __always_inline int add_address_to_chunk(struct pt_regs *ctx, struct tlsChunk* chunk, __u64 id, __u32 fd) { - __u32 pid = id >> 32; - __u64 key = (__u64) pid << 32 | fd; - - struct fd_info *fdinfo = bpf_map_lookup_elem(&file_descriptor_to_ipv4, &key); - - if (fdinfo == NULL) { - return 0; - } - - int err = bpf_probe_read(chunk->address, sizeof(chunk->address), fdinfo->ipv4_addr); - chunk->flags |= (fdinfo->flags & FLAGS_IS_CLIENT_BIT); - - if (err != 0) { - log_error(ctx, LOG_ERROR_READING_FD_ADDRESS, id, err, 0l); - return 0; - } - - return 1; -} + if (info->count_ptr == NULL) { + // ssl_read and ssl_write return the number of bytes written/read + // + return returnValue; + } -static __always_inline void send_chunk_part(struct pt_regs *ctx, __u8* buffer, __u64 id, - struct tlsChunk* chunk, int start, int end) { - size_t recorded = MIN(end - start, sizeof(chunk->data)); - - if (recorded <= 0) { - return; - } - - chunk->recorded = recorded; - chunk->start = start; - - // This ugly trick is for the ebpf verifier happiness - // - long err = 0; - if (chunk->recorded == sizeof(chunk->data)) { - err = bpf_probe_read(chunk->data, sizeof(chunk->data), buffer + start); - } else { - recorded &= (sizeof(chunk->data) - 1); // Buffer must be N^2 - err = bpf_probe_read(chunk->data, recorded, buffer + start); - } - - if (err != 0) { - log_error(ctx, LOG_ERROR_READING_FROM_SSL_BUFFER, id, err, 0l); - return; - } - - bpf_perf_event_output(ctx, &chunks_buffer, BPF_F_CURRENT_CPU, chunk, sizeof(struct tlsChunk)); -} + // ssl_read_ex and ssl_write_ex return 1 for success + // + if (returnValue != 1) { + return 0; + } -static __always_inline void send_chunk(struct pt_regs *ctx, __u8* buffer, __u64 id, struct tlsChunk* chunk) { - // ebpf loops must be bounded at compile time, we can't use (i < chunk->len / CHUNK_SIZE) - // - // https://lwn.net/Articles/794934/ - // - // However we want to run in kernel older than 5.3, hence we use "#pragma unroll" anyway - // - #pragma unroll - for (int i = 0; i < MAX_CHUNKS_PER_OPERATION; i++) { - if (chunk->len <= (CHUNK_SIZE * i)) { - break; - } - - send_chunk_part(ctx, buffer, id, chunk, CHUNK_SIZE * i, chunk->len); - } -} + // ssl_read_ex and ssl_write_ex write the number of bytes to an arg named *count + // + size_t countBytes; + long err = bpf_probe_read(&countBytes, sizeof(size_t), (void*) info->count_ptr); -static __always_inline void output_ssl_chunk(struct pt_regs *ctx, struct ssl_info* info, __u64 id, __u32 flags) { - int countBytes = get_count_bytes(ctx, info, id); - - if (countBytes <= 0) { - return; - } - - if (countBytes > (CHUNK_SIZE * MAX_CHUNKS_PER_OPERATION)) { - log_error(ctx, LOG_ERROR_BUFFER_TOO_BIG, id, countBytes, 0l); - return; - } - - struct tlsChunk* chunk; - int zero = 0; - - // If other thread, running on the same CPU get to this point at the same time like us (context switch) - // the data will be corrupted - protection may be added in the future - // - chunk = bpf_map_lookup_elem(&heap, &zero); - - if (!chunk) { - log_error(ctx, LOG_ERROR_ALLOCATING_CHUNK, id, 0l, 0l); - return; - } - - chunk->flags = flags; - chunk->pid = id >> 32; - chunk->tgid = id; - chunk->len = countBytes; - chunk->fd = info->fd; - - if (!add_address_to_chunk(ctx, chunk, id, chunk->fd)) { - // Without an address, we drop the chunk because there is not much to do with it in Go - // - return; - } - - send_chunk(ctx, info->buffer, id, chunk); + if (err != 0) { + log_error(ctx, LOG_ERROR_READING_BYTES_COUNT, id, err, 0l); + return 0; + } + + return countBytes; } static __always_inline void ssl_uprobe(struct pt_regs *ctx, void* ssl, void* buffer, int num, struct bpf_map_def* map_fd, size_t *count_ptr) { @@ -163,25 +49,7 @@ static __always_inline void ssl_uprobe(struct pt_regs *ctx, void* ssl, void* buf } struct ssl_info *infoPtr = bpf_map_lookup_elem(map_fd, &id); - struct ssl_info info = {}; - - if (infoPtr == NULL) { - info.fd = -1; - info.created_at_nano = bpf_ktime_get_ns(); - } else { - long err = bpf_probe_read(&info, sizeof(struct ssl_info), infoPtr); - - if (err != 0) { - log_error(ctx, LOG_ERROR_READING_SSL_CONTEXT, id, err, ORIGIN_SSL_UPROBE_CODE); - } - - if ((bpf_ktime_get_ns() - info.created_at_nano) > SSL_INFO_MAX_TTL_NANO) { - // If the ssl info is too old, we don't want to use its info because it may be incorrect. - // - info.fd = -1; - info.created_at_nano = bpf_ktime_get_ns(); - } - } + struct ssl_info info = lookup_ssl_info(ctx, &openssl_write_context, id); info.count_ptr = count_ptr; info.buffer = buffer; @@ -227,50 +95,52 @@ static __always_inline void ssl_uretprobe(struct pt_regs *ctx, struct bpf_map_de return; } - if (info.fd == -1) { + if (info.fd == invalid_fd) { log_error(ctx, LOG_ERROR_MISSING_FILE_DESCRIPTOR, id, 0l, 0l); return; } - - output_ssl_chunk(ctx, &info, id, flags); + + int count_bytes = get_count_bytes(ctx, &info, id); + + output_ssl_chunk(ctx, &info, count_bytes, id, flags); } SEC("uprobe/ssl_write") void BPF_KPROBE(ssl_write, void* ssl, void* buffer, int num) { - ssl_uprobe(ctx, ssl, buffer, num, &ssl_write_context, 0); + ssl_uprobe(ctx, ssl, buffer, num, &openssl_write_context, 0); } SEC("uretprobe/ssl_write") void BPF_KPROBE(ssl_ret_write) { - ssl_uretprobe(ctx, &ssl_write_context, 0); + ssl_uretprobe(ctx, &openssl_write_context, 0); } SEC("uprobe/ssl_read") void BPF_KPROBE(ssl_read, void* ssl, void* buffer, int num) { - ssl_uprobe(ctx, ssl, buffer, num, &ssl_read_context, 0); + ssl_uprobe(ctx, ssl, buffer, num, &openssl_read_context, 0); } SEC("uretprobe/ssl_read") void BPF_KPROBE(ssl_ret_read) { - ssl_uretprobe(ctx, &ssl_read_context, FLAGS_IS_READ_BIT); + ssl_uretprobe(ctx, &openssl_read_context, FLAGS_IS_READ_BIT); } SEC("uprobe/ssl_write_ex") void BPF_KPROBE(ssl_write_ex, void* ssl, void* buffer, size_t num, size_t *written) { - ssl_uprobe(ctx, ssl, buffer, num, &ssl_write_context, written); + ssl_uprobe(ctx, ssl, buffer, num, &openssl_write_context, written); } SEC("uretprobe/ssl_write_ex") void BPF_KPROBE(ssl_ret_write_ex) { - ssl_uretprobe(ctx, &ssl_write_context, 0); + ssl_uretprobe(ctx, &openssl_write_context, 0); } SEC("uprobe/ssl_read_ex") void BPF_KPROBE(ssl_read_ex, void* ssl, void* buffer, size_t num, size_t *readbytes) { - ssl_uprobe(ctx, ssl, buffer, num, &ssl_read_context, readbytes); + ssl_uprobe(ctx, ssl, buffer, num, &openssl_read_context, readbytes); } SEC("uretprobe/ssl_read_ex") void BPF_KPROBE(ssl_ret_read_ex) { - ssl_uretprobe(ctx, &ssl_read_context, FLAGS_IS_READ_BIT); + ssl_uretprobe(ctx, &openssl_read_context, FLAGS_IS_READ_BIT); } diff --git a/tap/tlstapper/bpf/tls_tapper.c b/tap/tlstapper/bpf/tls_tapper.c index 9878ef690..e5388b9f8 100644 --- a/tap/tlstapper/bpf/tls_tapper.c +++ b/tap/tlstapper/bpf/tls_tapper.c @@ -13,7 +13,9 @@ Copyright (C) UP9 Inc. // To avoid multiple .o files // +#include "common.c" #include "openssl_uprobes.c" +#include "go_uprobes.c" #include "fd_tracepoints.c" #include "fd_to_address_tracepoints.c" diff --git a/tap/tlstapper/bpf_logger_messages.go b/tap/tlstapper/bpf_logger_messages.go index bfcf84138..06bb642e4 100644 --- a/tap/tlstapper/bpf_logger_messages.go +++ b/tap/tlstapper/bpf_logger_messages.go @@ -2,7 +2,7 @@ package tlstapper // Must be synced with logger_messages.h // -var bpfLogMessages = []string { +var bpfLogMessages = []string{ /*0000*/ "[%d] Unable to read bytes count from _ex methods [err: %d]", /*0001*/ "[%d] Unable to read ipv4 address [err: %d]", /*0002*/ "[%d] Unable to read ssl buffer [err: %d]", @@ -20,6 +20,4 @@ var bpfLogMessages = []string { /*0014*/ "[%d] Unable to put connect info [err: %d]", /*0015*/ "[%d] Unable to get connect info", /*0016*/ "[%d] Unable to read connect info [err: %d]", - } - diff --git a/tap/tlstapper/chunk.go b/tap/tlstapper/chunk.go index 2ffc153aa..006585ec1 100644 --- a/tap/tlstapper/chunk.go +++ b/tap/tlstapper/chunk.go @@ -12,23 +12,7 @@ import ( const FLAGS_IS_CLIENT_BIT uint32 = (1 << 0) const FLAGS_IS_READ_BIT uint32 = (1 << 1) -// The same struct can be found in maps.h -// -// Be careful when editing, alignment and padding should be exactly the same in go/c. -// -type tlsChunk struct { - Pid uint32 // process id - Tgid uint32 // thread id inside the process - Len uint32 // the size of the native buffer used to read/write the tls data (may be bigger than tlsChunk.Data[]) - Start uint32 // the start offset withing the native buffer - Recorded uint32 // number of bytes copied from the native buffer to tlsChunk.Data[] - Fd uint32 // the file descriptor used to read/write the tls data (probably socket file descriptor) - Flags uint32 // bitwise flags - Address [16]byte // ipv4 address and port - Data [4096]byte // actual tls data -} - -func (c *tlsChunk) getAddress() (net.IP, uint16, error) { +func (c *tlsTapperTlsChunk) getAddress() (net.IP, uint16, error) { address := bytes.NewReader(c.Address[:]) var family uint16 var port uint16 @@ -51,31 +35,31 @@ func (c *tlsChunk) getAddress() (net.IP, uint16, error) { return ip, port, nil } -func (c *tlsChunk) isClient() bool { +func (c *tlsTapperTlsChunk) isClient() bool { return c.Flags&FLAGS_IS_CLIENT_BIT != 0 } -func (c *tlsChunk) isServer() bool { +func (c *tlsTapperTlsChunk) isServer() bool { return !c.isClient() } -func (c *tlsChunk) isRead() bool { +func (c *tlsTapperTlsChunk) isRead() bool { return c.Flags&FLAGS_IS_READ_BIT != 0 } -func (c *tlsChunk) isWrite() bool { +func (c *tlsTapperTlsChunk) isWrite() bool { return !c.isRead() } -func (c *tlsChunk) getRecordedData() []byte { +func (c *tlsTapperTlsChunk) getRecordedData() []byte { return c.Data[:c.Recorded] } -func (c *tlsChunk) isRequest() bool { +func (c *tlsTapperTlsChunk) isRequest() bool { return (c.isClient() && c.isWrite()) || (c.isServer() && c.isRead()) } -func (c *tlsChunk) getAddressPair() (addressPair, error) { +func (c *tlsTapperTlsChunk) getAddressPair() (addressPair, error) { ip, port, err := c.getAddress() if err != nil { diff --git a/tap/tlstapper/go_hooks.go b/tap/tlstapper/go_hooks.go new file mode 100644 index 000000000..25cdc89fe --- /dev/null +++ b/tap/tlstapper/go_hooks.go @@ -0,0 +1,105 @@ +package tlstapper + +import ( + "github.com/cilium/ebpf/link" + "github.com/go-errors/errors" +) + +type goHooks struct { + goWriteProbe link.Link + goWriteExProbes []link.Link + goReadProbe link.Link + goReadExProbes []link.Link +} + +func (s *goHooks) installUprobes(bpfObjects *tlsTapperObjects, filePath string) error { + ex, err := link.OpenExecutable(filePath) + + if err != nil { + return errors.Wrap(err, 0) + } + + offsets, err := findGoOffsets(filePath) + + if err != nil { + return errors.Wrap(err, 0) + } + + return s.installHooks(bpfObjects, ex, offsets) +} + +func (s *goHooks) installHooks(bpfObjects *tlsTapperObjects, ex *link.Executable, offsets goOffsets) error { + var err error + + // Symbol points to + // [`crypto/tls.(*Conn).Write`](https://github.com/golang/go/blob/go1.17.6/src/crypto/tls/conn.go#L1099) + s.goWriteProbe, err = ex.Uprobe(goWriteSymbol, bpfObjects.GoCryptoTlsWrite, &link.UprobeOptions{ + Offset: offsets.GoWriteOffset.enter, + }) + + if err != nil { + return errors.Wrap(err, 0) + } + + for _, offset := range offsets.GoWriteOffset.exits { + probe, err := ex.Uprobe(goWriteSymbol, bpfObjects.GoCryptoTlsWriteEx, &link.UprobeOptions{ + Offset: offset, + }) + + if err != nil { + return errors.Wrap(err, 0) + } + + s.goWriteExProbes = append(s.goWriteExProbes, probe) + } + + // Symbol points to + // [`crypto/tls.(*Conn).Read`](https://github.com/golang/go/blob/go1.17.6/src/crypto/tls/conn.go#L1263) + s.goReadProbe, err = ex.Uprobe(goReadSymbol, bpfObjects.GoCryptoTlsRead, &link.UprobeOptions{ + Offset: offsets.GoReadOffset.enter, + }) + + if err != nil { + return errors.Wrap(err, 0) + } + + for _, offset := range offsets.GoReadOffset.exits { + probe, err := ex.Uprobe(goReadSymbol, bpfObjects.GoCryptoTlsReadEx, &link.UprobeOptions{ + Offset: offset, + }) + + if err != nil { + return errors.Wrap(err, 0) + } + + s.goReadExProbes = append(s.goReadExProbes, probe) + } + + return nil +} + +func (s *goHooks) close() []error { + errors := make([]error, 0) + + if err := s.goWriteProbe.Close(); err != nil { + errors = append(errors, err) + } + + for _, probe := range s.goWriteExProbes { + if err := probe.Close(); err != nil { + errors = append(errors, err) + } + } + + if err := s.goReadProbe.Close(); err != nil { + errors = append(errors, err) + } + + for _, probe := range s.goReadExProbes { + if err := probe.Close(); err != nil { + errors = append(errors, err) + } + } + + return errors +} diff --git a/tap/tlstapper/go_offsets.go b/tap/tlstapper/go_offsets.go new file mode 100644 index 000000000..239b76bc2 --- /dev/null +++ b/tap/tlstapper/go_offsets.go @@ -0,0 +1,213 @@ +package tlstapper + +import ( + "bufio" + "debug/elf" + "fmt" + "os" + "runtime" + + "github.com/Masterminds/semver" + "github.com/cilium/ebpf/link" + "github.com/knightsc/gapstone" +) + +type goOffsets struct { + GoWriteOffset *goExtendedOffset + GoReadOffset *goExtendedOffset +} + +type goExtendedOffset struct { + enter uint64 + exits []uint64 +} + +const ( + minimumSupportedGoVersion = "1.17.0" + goVersionSymbol = "runtime.buildVersion.str" + goWriteSymbol = "crypto/tls.(*Conn).Write" + goReadSymbol = "crypto/tls.(*Conn).Read" +) + +func findGoOffsets(filePath string) (goOffsets, error) { + offsets, err := getOffsets(filePath) + if err != nil { + return goOffsets{}, err + } + + goVersionOffset, err := getOffset(offsets, goVersionSymbol) + if err != nil { + return goOffsets{}, err + } + + passed, goVersion, err := checkGoVersion(filePath, goVersionOffset) + if err != nil { + return goOffsets{}, fmt.Errorf("Checking Go version: %s", err) + } + + if !passed { + return goOffsets{}, fmt.Errorf("Unsupported Go version: %s", goVersion) + } + + writeOffset, err := getOffset(offsets, goWriteSymbol) + if err != nil { + return goOffsets{}, fmt.Errorf("reading offset [%s]: %s", goWriteSymbol, err) + } + + readOffset, err := getOffset(offsets, goReadSymbol) + if err != nil { + return goOffsets{}, fmt.Errorf("reading offset [%s]: %s", goReadSymbol, err) + } + + return goOffsets{ + GoWriteOffset: writeOffset, + GoReadOffset: readOffset, + }, nil +} + +func getOffsets(filePath string) (offsets map[string]*goExtendedOffset, err error) { + var engine gapstone.Engine + switch runtime.GOARCH { + case "amd64": + engine, err = gapstone.New( + gapstone.CS_ARCH_X86, + gapstone.CS_MODE_64, + ) + case "arm64": + engine, err = gapstone.New( + gapstone.CS_ARCH_ARM64, + gapstone.CS_MODE_ARM, + ) + default: + err = fmt.Errorf("Unsupported architecture: %v", runtime.GOARCH) + } + if err != nil { + return + } + + offsets = make(map[string]*goExtendedOffset) + var fd *os.File + fd, err = os.Open(filePath) + if err != nil { + return + } + defer fd.Close() + + var se *elf.File + se, err = elf.NewFile(fd) + if err != nil { + return + } + + textSection := se.Section(".text") + if textSection == nil { + err = fmt.Errorf("No text section") + return + } + + // extract the raw bytes from the .text section + var textSectionData []byte + textSectionData, err = textSection.Data() + if err != nil { + return + } + + syms, err := se.Symbols() + for _, sym := range syms { + offset := sym.Value + + var lastProg *elf.Prog + for _, prog := range se.Progs { + if prog.Vaddr <= sym.Value && sym.Value < (prog.Vaddr+prog.Memsz) { + offset = sym.Value - prog.Vaddr + prog.Off + lastProg = prog + break + } + } + + extendedOffset := &goExtendedOffset{enter: offset} + + // source: https://gist.github.com/grantseltzer/3efa8ecc5de1fb566e8091533050d608 + // skip over any symbols that aren't functinons/methods + if sym.Info != byte(2) && sym.Info != byte(18) { + offsets[sym.Name] = extendedOffset + continue + } + + // skip over empty symbols + if sym.Size == 0 { + offsets[sym.Name] = extendedOffset + continue + } + + // calculate starting and ending index of the symbol within the text section + symStartingIndex := sym.Value - textSection.Addr + symEndingIndex := symStartingIndex + sym.Size + + // collect the bytes of the symbol + symBytes := textSectionData[symStartingIndex:symEndingIndex] + + // disasemble the symbol + var instructions []gapstone.Instruction + instructions, err = engine.Disasm(symBytes, sym.Value, 0) + if err != nil { + return + } + + // iterate over each instruction and if the mnemonic is `ret` then that's an exit offset + for _, ins := range instructions { + if ins.Mnemonic == "ret" { + extendedOffset.exits = append(extendedOffset.exits, uint64(ins.Address)-lastProg.Vaddr+lastProg.Off) + } + } + + offsets[sym.Name] = extendedOffset + } + + return +} + +func getOffset(offsets map[string]*goExtendedOffset, symbol string) (*goExtendedOffset, error) { + if offset, ok := offsets[symbol]; ok { + return offset, nil + } + return nil, fmt.Errorf("symbol %s: %w", symbol, link.ErrNoSymbol) +} + +func checkGoVersion(filePath string, offset *goExtendedOffset) (bool, string, error) { + fd, err := os.Open(filePath) + if err != nil { + return false, "", err + } + defer fd.Close() + + reader := bufio.NewReader(fd) + + _, err = reader.Discard(int(offset.enter)) + if err != nil { + return false, "", err + } + + line, err := reader.ReadString(0) + if err != nil { + return false, "", err + } + + if len(line) < 3 { + return false, "", fmt.Errorf("ELF data segment read error (corrupted result)") + } + + goVersionStr := line[2 : len(line)-1] + + goVersion, err := semver.NewVersion(goVersionStr) + if err != nil { + return false, goVersionStr, err + } + + goVersionConstraint, err := semver.NewConstraint(fmt.Sprintf(">= %s", minimumSupportedGoVersion)) + if err != nil { + return false, goVersionStr, err + } + + return goVersionConstraint.Check(goVersion), goVersionStr, nil +} diff --git a/tap/tlstapper/ssllib_finder.go b/tap/tlstapper/ssllib_finder.go index 596772be7..3be5e18e6 100644 --- a/tap/tlstapper/ssllib_finder.go +++ b/tap/tlstapper/ssllib_finder.go @@ -46,7 +46,7 @@ func findLibraryByPid(procfs string, pid uint32, libraryName string) (string, er filepath := parts[5] - if !strings.Contains(filepath, libraryName) { + if libraryName != "" && !strings.Contains(filepath, libraryName) { continue } diff --git a/tap/tlstapper/tls_poller.go b/tap/tlstapper/tls_poller.go index 23643fab1..c0dddf9a4 100644 --- a/tap/tlstapper/tls_poller.go +++ b/tap/tlstapper/tls_poller.go @@ -20,8 +20,10 @@ import ( "github.com/up9inc/mizu/tap/api" ) -const fdCachedItemAvgSize = 40 -const fdCacheMaxItems = 500000 / fdCachedItemAvgSize +const ( + fdCachedItemAvgSize = 40 + fdCacheMaxItems = 500000 / fdCachedItemAvgSize +) type tlsPoller struct { tls *TlsTapper @@ -74,7 +76,8 @@ func (p *tlsPoller) close() error { } func (p *tlsPoller) poll(emitter api.Emitter, options *api.TrafficFilteringOptions, streamsMap api.TcpStreamMap) { - chunks := make(chan *tlsChunk) + // tlsTapperTlsChunk is generated by bpf2go. + chunks := make(chan *tlsTapperTlsChunk) go p.pollChunksPerfBuffer(chunks) @@ -94,7 +97,7 @@ func (p *tlsPoller) poll(emitter api.Emitter, options *api.TrafficFilteringOptio } } -func (p *tlsPoller) pollChunksPerfBuffer(chunks chan<- *tlsChunk) { +func (p *tlsPoller) pollChunksPerfBuffer(chunks chan<- *tlsTapperTlsChunk) { logger.Log.Infof("Start polling for tls events") for { @@ -118,7 +121,7 @@ func (p *tlsPoller) pollChunksPerfBuffer(chunks chan<- *tlsChunk) { buffer := bytes.NewReader(record.RawSample) - var chunk tlsChunk + var chunk tlsTapperTlsChunk if err := binary.Read(buffer, binary.LittleEndian, &chunk); err != nil { LogError(errors.Errorf("Error parsing chunk %v", err)) @@ -129,7 +132,7 @@ func (p *tlsPoller) pollChunksPerfBuffer(chunks chan<- *tlsChunk) { } } -func (p *tlsPoller) handleTlsChunk(chunk *tlsChunk, extension *api.Extension, emitter api.Emitter, +func (p *tlsPoller) handleTlsChunk(chunk *tlsTapperTlsChunk, extension *api.Extension, emitter api.Emitter, options *api.TrafficFilteringOptions, streamsMap api.TcpStreamMap) error { address, err := p.getSockfdAddressPair(chunk) @@ -158,11 +161,11 @@ func (p *tlsPoller) handleTlsChunk(chunk *tlsChunk, extension *api.Extension, em return nil } -func (p *tlsPoller) startNewTlsReader(chunk *tlsChunk, address *addressPair, key string, +func (p *tlsPoller) startNewTlsReader(chunk *tlsTapperTlsChunk, address *addressPair, key string, emitter api.Emitter, extension *api.Extension, options *api.TrafficFilteringOptions, streamsMap api.TcpStreamMap) *tlsReader { - tcpid := p.buildTcpId(chunk, address) + tcpid := p.buildTcpId(address) doneHandler := func(r *tlsReader) { p.closeReader(key, r) @@ -175,7 +178,7 @@ func (p *tlsPoller) startNewTlsReader(chunk *tlsChunk, address *addressPair, key reader := &tlsReader{ key: key, - chunks: make(chan *tlsChunk, 1), + chunks: make(chan *tlsTapperTlsChunk, 1), doneHandler: doneHandler, progress: &api.ReadProgress{}, tcpID: &tcpid, @@ -198,7 +201,7 @@ func (p *tlsPoller) startNewTlsReader(chunk *tlsChunk, address *addressPair, key return reader } -func dissect(extension *api.Extension, reader *tlsReader, options *api.TrafficFilteringOptions) { +func dissect(extension *api.Extension, reader api.TcpReader, options *api.TrafficFilteringOptions) { b := bufio.NewReader(reader) err := extension.Dissector.Dissect(b, reader, options) @@ -213,7 +216,7 @@ func (p *tlsPoller) closeReader(key string, r *tlsReader) { p.closedReaders <- key } -func (p *tlsPoller) getSockfdAddressPair(chunk *tlsChunk) (addressPair, error) { +func (p *tlsPoller) getSockfdAddressPair(chunk *tlsTapperTlsChunk) (addressPair, error) { address, err := getAddressBySockfd(p.procfs, chunk.Pid, chunk.Fd) fdCacheKey := fmt.Sprintf("%d:%d", chunk.Pid, chunk.Fd) @@ -252,7 +255,7 @@ func buildTlsKey(address addressPair) string { return fmt.Sprintf("%s:%d>%s:%d", address.srcIp, address.srcPort, address.dstIp, address.dstPort) } -func (p *tlsPoller) buildTcpId(chunk *tlsChunk, address *addressPair) api.TcpID { +func (p *tlsPoller) buildTcpId(address *addressPair) api.TcpID { return api.TcpID{ SrcIP: address.srcIp.String(), DstIP: address.dstIp.String(), @@ -289,7 +292,7 @@ func (p *tlsPoller) clearPids() { }) } -func (p *tlsPoller) logTls(chunk *tlsChunk, key string, reader *tlsReader) { +func (p *tlsPoller) logTls(chunk *tlsTapperTlsChunk, key string, reader *tlsReader) { var flagsStr string if chunk.isClient() { diff --git a/tap/tlstapper/tls_process_discoverer.go b/tap/tlstapper/tls_process_discoverer.go index f4b604399..d55536798 100644 --- a/tap/tlstapper/tls_process_discoverer.go +++ b/tap/tlstapper/tls_process_discoverer.go @@ -28,7 +28,11 @@ func UpdateTapTargets(tls *TlsTapper, pods *[]v1.Pod, procfs string) error { tls.ClearPids() for pid, pod := range containerPids { - if err := tls.AddPid(procfs, pid, pod.Namespace); err != nil { + if err := tls.AddSsllibPid(procfs, pid, pod.Namespace); err != nil { + LogError(err) + } + + if err := tls.AddGoPid(procfs, pid, pod.Namespace); err != nil { LogError(err) } } diff --git a/tap/tlstapper/tls_reader.go b/tap/tlstapper/tls_reader.go index b76a0008d..88c65ee56 100644 --- a/tap/tlstapper/tls_reader.go +++ b/tap/tlstapper/tls_reader.go @@ -9,7 +9,7 @@ import ( type tlsReader struct { key string - chunks chan *tlsChunk + chunks chan *tlsTapperTlsChunk seenChunks int data []byte doneHandler func(r *tlsReader) @@ -24,14 +24,14 @@ type tlsReader struct { reqResMatcher api.RequestResponseMatcher } -func (r *tlsReader) newChunk(chunk *tlsChunk) { +func (r *tlsReader) newChunk(chunk *tlsTapperTlsChunk) { r.captureTime = time.Now() r.seenChunks = r.seenChunks + 1 r.chunks <- chunk } func (r *tlsReader) Read(p []byte) (int, error) { - var chunk *tlsChunk + var chunk *tlsTapperTlsChunk for len(r.data) == 0 { var ok bool @@ -42,7 +42,7 @@ func (r *tlsReader) Read(p []byte) (int, error) { } r.data = chunk.getRecordedData() - case <-time.After(time.Second * 3): + case <-time.After(time.Second * 120): r.doneHandler(r) return 0, io.EOF } diff --git a/tap/tlstapper/tls_tapper.go b/tap/tlstapper/tls_tapper.go index b6fd173e1..4e7229ab6 100644 --- a/tap/tlstapper/tls_tapper.go +++ b/tap/tlstapper/tls_tapper.go @@ -1,6 +1,7 @@ package tlstapper import ( + "strconv" "sync" "github.com/cilium/ebpf/rlimit" @@ -11,12 +12,13 @@ import ( const GLOABL_TAP_PID = 0 -//go:generate go run github.com/cilium/ebpf/cmd/bpf2go tlsTapper bpf/tls_tapper.c -- -O2 -g -D__TARGET_ARCH_x86 +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go@0d0727ef53e2f53b1731c73f4c61e0f58693083a -type tls_chunk tlsTapper bpf/tls_tapper.c -- -O2 -g -D__TARGET_ARCH_x86 type TlsTapper struct { bpfObjects tlsTapperObjects syscallHooks syscallHooks sslHooksStructs []sslHooks + goHooksStructs []goHooks poller *tlsPoller bpfLogger *bpfLogger registeredPids sync.Map @@ -64,11 +66,20 @@ func (t *TlsTapper) PollForLogging() { t.bpfLogger.poll() } -func (t *TlsTapper) GlobalTap(sslLibrary string) error { - return t.tapPid(GLOABL_TAP_PID, sslLibrary, api.UNKNOWN_NAMESPACE) +func (t *TlsTapper) GlobalSsllibTap(sslLibrary string) error { + return t.tapSsllibPid(GLOABL_TAP_PID, sslLibrary, api.UNKNOWN_NAMESPACE) } -func (t *TlsTapper) AddPid(procfs string, pid uint32, namespace string) error { +func (t *TlsTapper) GlobalGoTap(procfs string, pid string) error { + _pid, err := strconv.Atoi(pid) + if err != nil { + return err + } + + return t.tapGoPid(procfs, uint32(_pid), api.UNKNOWN_NAMESPACE) +} + +func (t *TlsTapper) AddSsllibPid(procfs string, pid uint32, namespace string) error { sslLibrary, err := findSsllib(procfs, pid) if err != nil { @@ -76,7 +87,11 @@ func (t *TlsTapper) AddPid(procfs string, pid uint32, namespace string) error { return nil // hide the error on purpose, its OK for a process to not use libssl.so } - return t.tapPid(pid, sslLibrary, namespace) + return t.tapSsllibPid(pid, sslLibrary, namespace) +} + +func (t *TlsTapper) AddGoPid(procfs string, pid uint32, namespace string) error { + return t.tapGoPid(procfs, pid, namespace) } func (t *TlsTapper) RemovePid(pid uint32) error { @@ -120,6 +135,10 @@ func (t *TlsTapper) Close() []error { errors = append(errors, sslHooks.close()...) } + for _, goHooks := range t.goHooksStructs { + errors = append(errors, goHooks.close()...) + } + if err := t.bpfLogger.close(); err != nil { errors = append(errors, err) } @@ -141,7 +160,7 @@ func setupRLimit() error { return nil } -func (t *TlsTapper) tapPid(pid uint32, sslLibrary string, namespace string) error { +func (t *TlsTapper) tapSsllibPid(pid uint32, sslLibrary string, namespace string) error { logger.Log.Infof("Tapping TLS (pid: %v) (sslLibrary: %v)", pid, sslLibrary) newSsl := sslHooks{} @@ -165,6 +184,35 @@ func (t *TlsTapper) tapPid(pid uint32, sslLibrary string, namespace string) erro return nil } +func (t *TlsTapper) tapGoPid(procfs string, pid uint32, namespace string) error { + exePath, err := findLibraryByPid(procfs, pid, "") + if err != nil { + return err + } + + hooks := goHooks{} + + if err := hooks.installUprobes(&t.bpfObjects, exePath); err != nil { + return err + } + + logger.Log.Infof("Tapping TLS (pid: %v) (Go: %v)", pid, exePath) + + t.goHooksStructs = append(t.goHooksStructs, hooks) + + t.poller.addPid(pid, namespace) + + pids := t.bpfObjects.tlsTapperMaps.PidsMap + + if err := pids.Put(pid, uint32(1)); err != nil { + return errors.Wrap(err, 0) + } + + t.registeredPids.Store(pid, true) + + return nil +} + func LogError(err error) { var e *errors.Error if errors.As(err, &e) { diff --git a/tap/tlstapper/tlstapper_bpfeb.go b/tap/tlstapper/tlstapper_bpfeb.go index a4b07d7f4..c6b046e97 100644 --- a/tap/tlstapper/tlstapper_bpfeb.go +++ b/tap/tlstapper/tlstapper_bpfeb.go @@ -1,4 +1,5 @@ // Code generated by bpf2go; DO NOT EDIT. +//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64 // +build arm64be armbe mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64 package tlstapper @@ -12,6 +13,18 @@ import ( "github.com/cilium/ebpf" ) +type tlsTapperTlsChunk struct { + Pid uint32 + Tgid uint32 + Len uint32 + Start uint32 + Recorded uint32 + Fd uint32 + Flags uint32 + Address [16]uint8 + Data [4096]uint8 +} + // loadTlsTapper returns the embedded CollectionSpec for tlsTapper. func loadTlsTapper() (*ebpf.CollectionSpec, error) { reader := bytes.NewReader(_TlsTapperBytes) @@ -53,20 +66,24 @@ type tlsTapperSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type tlsTapperProgramSpecs struct { - SslRead *ebpf.ProgramSpec `ebpf:"ssl_read"` - SslReadEx *ebpf.ProgramSpec `ebpf:"ssl_read_ex"` - SslRetRead *ebpf.ProgramSpec `ebpf:"ssl_ret_read"` - SslRetReadEx *ebpf.ProgramSpec `ebpf:"ssl_ret_read_ex"` - SslRetWrite *ebpf.ProgramSpec `ebpf:"ssl_ret_write"` - SslRetWriteEx *ebpf.ProgramSpec `ebpf:"ssl_ret_write_ex"` - SslWrite *ebpf.ProgramSpec `ebpf:"ssl_write"` - SslWriteEx *ebpf.ProgramSpec `ebpf:"ssl_write_ex"` - SysEnterAccept4 *ebpf.ProgramSpec `ebpf:"sys_enter_accept4"` - SysEnterConnect *ebpf.ProgramSpec `ebpf:"sys_enter_connect"` - SysEnterRead *ebpf.ProgramSpec `ebpf:"sys_enter_read"` - SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"` - SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"` - SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"` + GoCryptoTlsRead *ebpf.ProgramSpec `ebpf:"go_crypto_tls_read"` + GoCryptoTlsReadEx *ebpf.ProgramSpec `ebpf:"go_crypto_tls_read_ex"` + GoCryptoTlsWrite *ebpf.ProgramSpec `ebpf:"go_crypto_tls_write"` + GoCryptoTlsWriteEx *ebpf.ProgramSpec `ebpf:"go_crypto_tls_write_ex"` + SslRead *ebpf.ProgramSpec `ebpf:"ssl_read"` + SslReadEx *ebpf.ProgramSpec `ebpf:"ssl_read_ex"` + SslRetRead *ebpf.ProgramSpec `ebpf:"ssl_ret_read"` + SslRetReadEx *ebpf.ProgramSpec `ebpf:"ssl_ret_read_ex"` + SslRetWrite *ebpf.ProgramSpec `ebpf:"ssl_ret_write"` + SslRetWriteEx *ebpf.ProgramSpec `ebpf:"ssl_ret_write_ex"` + SslWrite *ebpf.ProgramSpec `ebpf:"ssl_write"` + SslWriteEx *ebpf.ProgramSpec `ebpf:"ssl_write_ex"` + SysEnterAccept4 *ebpf.ProgramSpec `ebpf:"sys_enter_accept4"` + SysEnterConnect *ebpf.ProgramSpec `ebpf:"sys_enter_connect"` + SysEnterRead *ebpf.ProgramSpec `ebpf:"sys_enter_read"` + SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"` + SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"` + SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"` } // tlsTapperMapSpecs contains maps before they are loaded into the kernel. @@ -77,11 +94,13 @@ type tlsTapperMapSpecs struct { ChunksBuffer *ebpf.MapSpec `ebpf:"chunks_buffer"` ConnectSyscallInfo *ebpf.MapSpec `ebpf:"connect_syscall_info"` FileDescriptorToIpv4 *ebpf.MapSpec `ebpf:"file_descriptor_to_ipv4"` + GoReadContext *ebpf.MapSpec `ebpf:"go_read_context"` + GoWriteContext *ebpf.MapSpec `ebpf:"go_write_context"` Heap *ebpf.MapSpec `ebpf:"heap"` LogBuffer *ebpf.MapSpec `ebpf:"log_buffer"` + OpensslReadContext *ebpf.MapSpec `ebpf:"openssl_read_context"` + OpensslWriteContext *ebpf.MapSpec `ebpf:"openssl_write_context"` PidsMap *ebpf.MapSpec `ebpf:"pids_map"` - SslReadContext *ebpf.MapSpec `ebpf:"ssl_read_context"` - SslWriteContext *ebpf.MapSpec `ebpf:"ssl_write_context"` } // tlsTapperObjects contains all objects after they have been loaded into the kernel. @@ -107,11 +126,13 @@ type tlsTapperMaps struct { ChunksBuffer *ebpf.Map `ebpf:"chunks_buffer"` ConnectSyscallInfo *ebpf.Map `ebpf:"connect_syscall_info"` FileDescriptorToIpv4 *ebpf.Map `ebpf:"file_descriptor_to_ipv4"` + GoReadContext *ebpf.Map `ebpf:"go_read_context"` + GoWriteContext *ebpf.Map `ebpf:"go_write_context"` Heap *ebpf.Map `ebpf:"heap"` LogBuffer *ebpf.Map `ebpf:"log_buffer"` + OpensslReadContext *ebpf.Map `ebpf:"openssl_read_context"` + OpensslWriteContext *ebpf.Map `ebpf:"openssl_write_context"` PidsMap *ebpf.Map `ebpf:"pids_map"` - SslReadContext *ebpf.Map `ebpf:"ssl_read_context"` - SslWriteContext *ebpf.Map `ebpf:"ssl_write_context"` } func (m *tlsTapperMaps) Close() error { @@ -120,11 +141,13 @@ func (m *tlsTapperMaps) Close() error { m.ChunksBuffer, m.ConnectSyscallInfo, m.FileDescriptorToIpv4, + m.GoReadContext, + m.GoWriteContext, m.Heap, m.LogBuffer, + m.OpensslReadContext, + m.OpensslWriteContext, m.PidsMap, - m.SslReadContext, - m.SslWriteContext, ) } @@ -132,24 +155,32 @@ func (m *tlsTapperMaps) Close() error { // // It can be passed to loadTlsTapperObjects or ebpf.CollectionSpec.LoadAndAssign. type tlsTapperPrograms struct { - SslRead *ebpf.Program `ebpf:"ssl_read"` - SslReadEx *ebpf.Program `ebpf:"ssl_read_ex"` - SslRetRead *ebpf.Program `ebpf:"ssl_ret_read"` - SslRetReadEx *ebpf.Program `ebpf:"ssl_ret_read_ex"` - SslRetWrite *ebpf.Program `ebpf:"ssl_ret_write"` - SslRetWriteEx *ebpf.Program `ebpf:"ssl_ret_write_ex"` - SslWrite *ebpf.Program `ebpf:"ssl_write"` - SslWriteEx *ebpf.Program `ebpf:"ssl_write_ex"` - SysEnterAccept4 *ebpf.Program `ebpf:"sys_enter_accept4"` - SysEnterConnect *ebpf.Program `ebpf:"sys_enter_connect"` - SysEnterRead *ebpf.Program `ebpf:"sys_enter_read"` - SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"` - SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"` - SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"` + GoCryptoTlsRead *ebpf.Program `ebpf:"go_crypto_tls_read"` + GoCryptoTlsReadEx *ebpf.Program `ebpf:"go_crypto_tls_read_ex"` + GoCryptoTlsWrite *ebpf.Program `ebpf:"go_crypto_tls_write"` + GoCryptoTlsWriteEx *ebpf.Program `ebpf:"go_crypto_tls_write_ex"` + SslRead *ebpf.Program `ebpf:"ssl_read"` + SslReadEx *ebpf.Program `ebpf:"ssl_read_ex"` + SslRetRead *ebpf.Program `ebpf:"ssl_ret_read"` + SslRetReadEx *ebpf.Program `ebpf:"ssl_ret_read_ex"` + SslRetWrite *ebpf.Program `ebpf:"ssl_ret_write"` + SslRetWriteEx *ebpf.Program `ebpf:"ssl_ret_write_ex"` + SslWrite *ebpf.Program `ebpf:"ssl_write"` + SslWriteEx *ebpf.Program `ebpf:"ssl_write_ex"` + SysEnterAccept4 *ebpf.Program `ebpf:"sys_enter_accept4"` + SysEnterConnect *ebpf.Program `ebpf:"sys_enter_connect"` + SysEnterRead *ebpf.Program `ebpf:"sys_enter_read"` + SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"` + SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"` + SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"` } func (p *tlsTapperPrograms) Close() error { return _TlsTapperClose( + p.GoCryptoTlsRead, + p.GoCryptoTlsReadEx, + p.GoCryptoTlsWrite, + p.GoCryptoTlsWriteEx, p.SslRead, p.SslReadEx, p.SslRetRead, diff --git a/tap/tlstapper/tlstapper_bpfeb.o b/tap/tlstapper/tlstapper_bpfeb.o index b4547a18d5fd72dd66e7e1c418fbc31f2ff51017..5098e04ad99bfcd678dd48a6add32b3ca8f11bd1 100644 GIT binary patch literal 155864 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 BuF(Jh literal 116056 zcmeIb3!GfXUFTcfJ&zte+j@>V`JGZS+>U^aay)zj}pahY&n_m>K>0q9^J@N zGc!s?BwRX60t1)3G=ab@pK!Oc3uJ(>G<@J%%d)H9T^<9=qc?=z0Rr3H1wI1{+|Ipl zncWT1{r#){)u+x+8a-?&v7qZS-F3eIdYr0%{p)e+oIdj{hmSs)PNz)QrOe;rI1qG0 zApQA8O9lP@N^{TTCoX9;+n*ueAlj!Z=b~X`T6{juhgZR?}Y)0Wz5H%XD&>*gLCgQ({iNc-133? z#UH_Jv&*&2Z#= z|2>TJqdXTqzg)COk+$a^lxMd{W|uC0R(v^6^rlnCah?@n(bLpSv`>y-ID6s*%F~MF zCn7nXI}=qPU-t5uGc#8HVl*`adDF{Zj82`A?dx` z_Wzhv0?vmOwFHPSakArc;(FzK z-WXdGZ})+nwk6-LJ$>?G$8vHU8N3Ph&r*UM-%V`{`i=4)h`g-F?*N&3o`1}>d@g0)?divLNsSo_KEB5P~FrHuhQz=)! zx%ivni+;oLZsMMcRgr)Foo2iE>y`g1_LIHw_H*&Kq&z71%eGwK>H6=v?>9xuZ+7GK z&b-MBzgUxR6TM&k=W7>#1)QSxUeR{QYQL8G_bVh}cYa;c`PJoPKJa7OUO#rRE%9#P zJiABooBgp{vwvXRvvcXWA9U-23$`|Bhn&>PM8BfGxBHjwylA_W<7(=qJB{rx>yA^t z9b7&cZ(rQbXrGRp@W!=^>kqKIY01}@Z}O8DKV#36oG(5j{V8;QzQA~F`_m=!xgUR% z_3QKXyMgt1QOXs}=XM-My`C2N1uggF9+MTG+->aqf8l$C5N-+UdKWL6RQcqhan}!f zot_}K?HH~zWqiT9wPN!bwb_}{0p~wsXF2q@f39|u)aMH8^McK#yBxzk!9{+Yxqd^=p2m3ZCulJWIk;S1j( z-#Bm0`RKQ8IqHtDy3Sokk7=*R8>ctD&bdX$-wTUQj~It-|Gn1hy$eTNI&J@ZwSVlH z&~ZT9Keg}9FZ>sY>8@Wp=7RQ<7bBZK`W5wG&I{+vz5_}7*%_Hb65_)rzt`Cv+moL! zd_UW_Kbg;khb0%3L(c>Iu=eKrbDLJ&s|1Doc8dXMw%t*S9z52`=*EnYXtqk863k?&5LnDy)zS6Z7Se_}4mL`gGSeZx�f43(2J9}$hx4k9ptZ;+vtkAPF-A@*Kx*6k{x0jo1 zXGO8IpNQ?O@k;D0Sf3TmxwpIiG5NTz(;iE%`+1#qZQ~m9y>wi&*DJ4~aV-)(-opDZ z*R}rl?ab?cv{PC1Fmao^o^b25ve?--h}?~5^;cJ0PJ(bcn-i`NBidh&V8rTKb{A8lW63N{X77k^C-)CeLGw@ zCxrIX=M}HE4~XYT?DecZPtki0fYvD&e!%Io|2p?)*1eyRxKEo)`F%Cqul)l#(_lgF z*W!5qeQxAB?$;uvzh9eM|GtKe72mJr`?Ij$vgG5R2k>?Ud%C9kWj%Yc&wsA_+^cWz zz^<mRoQ`?|t?f8-B$$#W)Gwy%75UcA=v3;n1fC!*&Jd-LM@^Wyc5 zUpk+zH;yHrle72x|A(~4Yvq1_a(*ma?taBJjblAKlUDWZdU4(P@)eI`*JWq+e*aI# z<$3k@`!76YtM{t!k0tG`cfTLwazixxTDcGF+nMj{`RBDSbHBeV<-bw)`~CS}?S0s! zo%Qbb-`aL&@AuoCC1+>Xf4|?`74G+M5PQ8*_xqD}c1zsv|8uE7?$du-{A<16@Ab#y z0`h6weDwYq?)Q%ipZm7n@BT#(=yx^sZ|r@4n=A5P=XWu*-oFXI6Yc&U=Ce{i*>CIp z?jP;3$;+R=X$^U zch_gV`(}Buhwk$^12^2h63>(Le)oUl*7v<}Z2fTz_x*dn`?vjiuwb9}X3TMPALuRmo}4ev@3DKo`|sJAdoB^{vg>-@#M|e!J#S*41L*zkA0yihu(wDC$lmY% zo!wsMIe^~p{(E*dfbs0Qt~34b@cB9u&y$s?@Av;@nTXu`OM1WmzgfSNeZ}L~b^R{8_xt}Fa36NJ^oLvZKI|9A&g^~v z-tYfq+$_B6`7=T`x2(Ng@BP^Ie&^f!{r^pWuUya0jM;N}JG1xwd%yq3IP~hrv(Mku z-~WF^21{e){n<-$@P7HiPs1nc`hVH&uTW5r$d1bm{@t^t|6=_A?Dt5)g5S};8_54Z zd+7@0`)Wyl9q;YmDe-Y#YxTnW*zWyyCjG_#f6r!A$~F5*_y5~5Uqr&^cDeVT;{RyY zgm2*Q*sTS4J@|e6Ypnm!P58f_!%{zeU#R(e_&lQ*Tuieys+r!Ii5#f zE=cNbzkH?dH`&me53CyhKG~sNR|R(X3~G4I>$2yKd#}7-UwHfT>x`>Bm$LCD*pu|5 zaDDYUVOlpX>iQ=~<4NCzip0LOpK*WEaVE2~ViN1Cm#a3pyqBmgNATCIfBEOSHr<5! z@0Rn;F1hvPt@fTMKc3?~Z*{2$wcGb;yY>0?#l+r=-zV{&&sja^y#7&tyN>R@A8P4h zRpKGvMe#3ulYHa42D`J4)%88^AFn{@h+ab$6Zg9?sVz>y8XqRKBFhxyuYYqeMx7lgub`l5ZWyYFA#uDl+uvToXZe~|L=`W^q*;AcdB znddrR>+9i1rQE^z_ZDC(7{}@oj&aP|4SZ`?JWo&PJPm!@_}(u3bLiCmOBdcOxx)VP z*8Xa250~13xm)6Q?ckFa+rr)PRsWA6&bKOZZJ#YSzWv?$gxmjC9cOMqd;L<**IRFb zJzdLuUDNSy_)9!r=Was%|6a`3n1|~!?qDANJ9yoB*pCxBKW3kD^CbWO*xWNZpI*>; z)6b{DJX)3f{d}tVrm%msAo6wQ_4!vkukUr^yf4T5b$$=ZQ<3x1xL)CL{KES4`id>L z?oZOZ-v1?-*MC{cNH1e)h^WXldoYzf&DASnx-0`&{Ov{%s8GKSU7HS1Q@G9_#6`D0~kxg`c|@$FIj{13u#J!bgrHeB>Vs$Z-Lm z`}MdC|3N*j!k5L6#Hr(RNRONF<#qI1@M+^C7l>O?%`*IB(lT%x@%b8CKv%yA{Gx_! z#_=f)6VHl8QR+6q7oFJFy;SZR9T6l>gpVxUH9PYyIPS-%fY1Mi&mcZke7+kWIZqRx zk4eGdBqN&+1rk4v&+GA#`u-paAu>DsNC4bUi2evCg>RN{{AciYNdS)j5?^v$!13qw zxQgSy(c>nL|GPVuY*R8QB7UlX(~3Zjt2maj%W)IO(vCQmv!_KTa$LX%y0*tv9Lr5P zkvH*?i37)?vGnbFT)?qh@QS>O<2UGW6USm3kc%4Ac|9)R_yIkx;#iiY5~qn{u>~AU z5z~jH&~T#Df*hz0ag=@zUpwHxTXkqBv`fPWY8t-h^65AIiH1vEE|dS}w`lm6trPx1 z4YwEkm!5y~|EuA5UOxSnw`sWO>@w%y@^c#gCJC2Tf*V1%_yRO4O2YA03~+9tj^n#= z?E9POQn(8FTj5JzY~uJ3e8@$+Tb~l;!rg=8ce1|qe3A#3?JE%fxR&i+3D>yy8*|&i z8&kGR&Uqj59@BG*E%|f8mbU%P^__FOj2)_v?XV+r(CMR_r|6LDd;srpyhG0~kw?j~yRHxVFb7d1K1n@%@^&-K150;=*wEpS{Yup)Rgv$v(ddd0*6X zihcO{6fm^E?p4<3ZOG%G<_&wh{oAz;qH7sTMgMTOe=ok|IKuJA^|%0E##oV;A^)Tv zS8@E)I<7tr87g)A&lCST@gETXO_J}(5N{!V6Y-m!ChdG{yN)!vx})$>P5IpMHcj_V zFp}IcCX<)Olm03$g8b<~{A94;MuPw{Z6WaduIK0bZ+^m|R^J#~bzyQ2@7AxrmY__2vf^wtPjlwO*slun&F z^WL+S($uM`mqzZAyhM6$B!_o@l^HsDA{ueoO0qlO8a@5|(WB8_cSZNa(zm`f+8>`L zdhyhm7bZ>>Paelv^2mZu{*(Yq63EtRnC7MaD0d?4`vBn&jT-=rd214nOdu6Mx$udZ7Y`sUo#c+gpej)RU#hpMUn*!%rV8JzjhsZDotZYL!HKy1TuZB=sqyR`k;B zi(mWoDB0@|U&mG4>rwV0G0M4_iOH!_Wd?Tp-VtQgEd-9yo;Y8(Ek`8&d*@ERG-XZs z^z6vpxFnPozi$%8F*kL*G%;5?J#qTX-O;0wwB(VepM0kD&O_s+W5U0PR?=sb!$BQ-JLz7=hgk^)8vZQL^GOj4w|;S90Ag6B%b!_StU!PlWqL9vxR@7GT(*uUPM(@79iN(=oH<#UJ2O+7J5xGY zdEY@)8>jKL_F06s1wG0z?y?Q$R#l5GT)Re;6vLQYp8Fn^vy~*aoJ?am&3zD+^s$`e zuIS05htLI%JXd=B=nC}w^N~J7^L)v$kz4}86);R z>ZQ`TbI%8bdVIoOpGbPUNG?syhehkkE;rTrKwqxQ z6j56m3uC9M(p+?pB%7N$EhB<2w$I5Vi{2Gq?(C>!x5#l`6UV6rQbQ44J_{Rmo#mGt zUy$KGKC4rtJ@X~HU;pIYR5qh?vvT=4HDlYPBv&pNu1=qw#p-JI-0bAUsZ%A4uE(cl zE|s62F|Y|^M^p)Y=%Bk6y7YnwIR$4ib`e-P14FuB&oL_pVHCI0#?TnY?Oq}!qjs+f z5*bKMqI+cel|Dxo4p+OTxyu;{T50kObTdCEi#&T(eD&e$%2yQcd9H-P5R;#bcK)iu zEk*4Vf&ybYk{Ki(vU%}&`O>|zb?9YRK{$1M4RqNO$1ULcikJ<_uF}Xho$DQx@Y43| zp5xUusAhAsTJ+t|9yxZn+oV@%zDvt;)7vjmzF6lIlao`Gxr4Un&z_lluT1cEGs|vv zb^DY?yGpD2SaI(?V^<8groD1U)`XX~eNeo(_3e7!7Z%gF?KPfdMwV+tsfaH5q*Ey( zl-AUjbT4rFwx`ic)D+1ZBk zOei&oec2&WD{mK~SK0Zw>k5jx@>0)VgkGYU`1*?)DOk2%c1tKIc5+w9FQt!TpVMEx zzw(0nHR>e=w?n2|q}ugeaGj=x*Ilz=qQq7k3?VR5zkK&Li|fB5#djUF3t{XgJsnPB z{&K-D^}B1z_?#~2zV3p(du|T!#qfPc%3;fAFIE!QK2B^WDYwUbx9fH=)+^6eSG;aP zhjL}Q*KT}X^Zp4g65e^JSj2^V(4+jeolIHdM~;ay;gtOP`*a#O`Qf+EVHI zJbO&z<1zAy4>RQ>AxKO9RWvpR6=ga`ta9-<<_U?H-djDU_83CbvEFAc;sL_ z;Qe?6;J$z#!fkuGJ?-WsVb*8K#&oAyZ#7bff- z>TiTUYs|g!NRT}C08Zf%nS&Bm2v{Crkz2n$d;rH%c46Eue?oLU?HJ$ARp3eZdyrHf zaamCQgfYc8;J6krbYtoP%VP=+<^O`GvQS6ccJ|Q-9M9nMohI`Oh$6SLyDa6(DgUZT ze^B&*y4Y}GDQBeoIg|dd)O*x1j+`sNL%F+p@%8k2DZ6m1C((=6+p?U$s^MNQT2Dxw zTZ9+t#n;p8rK#b9 z^|SFIuR_16CCU%=wc(#K=KCc6s^k7)V?HkB>nP)KmUHG}i70&3@xTs~{%e#! zGp^yoCjEJoKU3E5XYtSu`g8{64)vFrS4KS1U#6<8`pam2`xc!3GIh#tN#m>jGHu6R zf0>T5>aS1p>qj|6e|<R}BbJ#JWjtP1-T`haPhyBi|H!NcEamMav2}gJ8vb$gL+HCt z^&#c?l*G@w^7WOS{`;Vhu)ck&58qyWMGZ$ir9C8_#P_amTEn&eeRapa{xS|o{C*uL z`Za$)PKa;y^H=CPLx@*#Jd`%6KgPJ&Uke!J???U;e`v2s{k3pI&tGuu*WXlDd+Ki~ zFSzpetNw@HZBlD_q+iwWveQTZnq#95r*Qhw`VFYQeEkNBTAm-k)5nNBfPAEU-VJE| zhJMtfBRs}9P}TUKHt7+Gk8)Z47hL%V>dIRF0bf5Y|3H(V=D^$+zqcwWQLVd;eNe6X#&Y|I}+pM$H) zSkL{ngs%mR^Jk$C$!{b2{u;uw!vWt3jslh^7)L3+)*t#!8JsU$R{a-T`(-Q2+J0Ho z*S0Tw=|9<;=KqpO^&JE+YB=(dc{;n~*r>}kG+ga3i~8CAgmTL|CcCEj{kTb;hyKxp zT>XS$3`1GRKEENPv-SV9Nj-u34UH)OnMsYHy@m=Df6VcwH<*;P|B%*yQvpjloPP-Q z4cljE!LjIPPQvS2e&j3T>yWQc+0}RGyoRg3hFZ$VNAxw+c5KuQt!lXHV?*TF+xvzQ zWgTxeEGtjDb;*W~*5?eClxf&O-tk7&-^N0~(BH-}W!2Bds;2)flgguh8*3V_`rNpv z;i|8Vb!FApMwBPi*Tz-HUSBZL#CWhNr(AaW+JyGA_N4mSgz|^>g?6!wY^>Xa^4oCL z*QTmtudhuD%Bru;Bg)fGUz>|se%04zUtiVN=DG{F`h&hUFDa|OHn%ms>T7dH!&P6K z*EC%91rxFQK|QQ}Z5ee8v2|OtJYHX0P)=LFg0rtJ^U7tXuPs$&)z_9q+d_SLUttq1%vhLk_Udylg+Pdc0+gC0dF!YtnDXYG6MNO~z z%9S--^_9bU#D08#%vF?CU%9$tU!PplvDa6wrS&T~edXH9Wv8#4*0)dfg(=wScfslF zHk3Ei*KNZZuKKzS<+u8QT*fEadkFP)8}wlr^$>k+i?sZzuWedCudi+MF5KJKwyLt~ zYukBEulm}ytl_G!ZK!XkuWc*Js;})?$Ec=t+eaLGeQh7r`W2kMwvQ>7oxZkfeZ9W6 zPiy#s)7N&CKh)Rus)nn+wxj%^zP2}&wLk8_c|v{dK)$vi;lg%b}niCkdKV_JGFj&Wv8#5 zO$}H3+PR{PddYad6Xgx{wX>t)s;^xmj(vaJl~-1M?Sei+eeFW}P+z+d-_}>huiaw- zqd)F0DyzPBqg*z<>T7ph!&P6qmo!}UwY#CL`r6%gY}5_Im;AiG zX#@;??K!Wk`r5Om=~Z8Qvl_1Y+Kc+y{8e9jhm}=ddr=<=_iiuBCG7RJcV6oUJ&1kn zg&wRv%1&QIsmSRu)7u|RFQeb|_6N0JX@8M>AmRSt=C3mubj5DJO852$ zUsL;owtXPN4CHnfz{PO=}dVNJ`g>-~+Rj{WuZ#$v$G z-^Oue)sNo~+@#k(8@0av`g-G%=C9X38ym{1uZ=5?eSJ1|9D9ACizMu6Q%MN)1V=IHNoR-Jim+TjY<5hRRudWsJdVOs}eeHF;_D8>e zh<;)BpSP{Ka9^M8D7VD-`jY*^P+#5s!m`uXcCD}1*LIZO#$Ryu<@XD_`J?w;UUFyE~4(zEGWnzPkH`Wv8!UM6mkQ`BDCZvSrnm-!Js}507cM z>T9^_*z0Q;=_I}Hk9(jG%hOI@dyqcV*B)PA?T>rvF5K!5`r5Oktoquss_9iGNxJwXpPKX}&F@s0eu&sCmR z_Rl>n2YeTfTS0jx|2CC()p7Uv!K|`>esDzD?++G~{quumW&ix(w6cGGa9-O9>13TP z&ox;)KsuS{Q?OIZ+U}{kGUCa&l0rL6dt`imsIQH$?Vjp5_WdNarmW+Nf6mE2*OJ!! zy3Y?HKgqxQ{9rR+v`=~^VDy_b&L{D`%VagYZLaP4!Geq5eSWa4>HYJAs=opM{9r}v zi*%yDOjQ}_M1L8tPt{*$QNvY#nI%1+&kywv^(W6Y348s?=ALEMU!UgZ_1CBR^ZiAh zfe!Zvx7=>}A_JFo=k4tezP6qpwC#g&ptnCr{iN#bwYNVgofo>9R{iKW+k1Y{*@x2( zo)3h6WFF`}KZxfEv5@LLKj`dXqu(F&^KjYC)4k^hljH4B+p+IYe!sB0KNz`izb@(S z7wWpC_xxaTeBM+|>Z|wsVA8&N&kw4-=yihM5A0iT*IBpalKQ%BSmW#b-+O*g^`ZLe zJwK@WD7*Qs_xzxn|NVYfcR#S;uG@Rh4<^^oueIj~?Qge({lP8YVB}{Y-F*zpvVVqg zQP%yv{XzYG@rct$_xVBCyXdF;`{J^4Z-4L~)c&CFFY*qA#Qxy#o4zr0qu%~tx8J;` zo*&#I?bF*IG#S6|uIoM7cb9Rkw?8PI7i+)i7)6Q#fe^BRXU%uY+gRZ^o z?~SkJ_r>EbzO3(i&kthXyPCB3p?a(z`}<;lpRwSse|pakCiT^Oe$eT|KM&V?eo*zH z&)@W(A9VWg&nxwwA5{C$=izoV1O4E+klyoyZawOsQ|UcFsQSQul{}x?dw$UA!>GI7 z=LhY(8}E0Cw|+m~C-IZ+m1VMzA&mV%a~#iO$?uE1j4;_RwEQ{z*H}EyChu7k?z&MN zh0hBX9e1A>#JbZy@3-rV_nx$Fd1b$|Mde*_y!F3gU8nC!lla*GGxFZC6p{$b`;8W% zk5uGX?&H=a9M>TdF8|v1hxda~fBQUI!L1WhV~+iMu2Rs44KM5Sg3yO$|GeO|@`64u zI8XUkHC*pwrOrF{_mxvkWxcPQ()#B9kug7un`EgK%}*HbiAt>ojPP_;8Fl&v9H&Pd zOa8xz`lj<5{(HtOV}Ch4s^!u8rpFxn`pR=zVSUr%%39y_wDN+hue|5Y>eJ^>_4V&< z^Yu+HyY&59UwOV!(t9WS9KtEQr>TX;wC^Yl?Ebb7+9tO9cM=bFpXy86eX1{u+=g8~ z>Ca*N@7A*Wl)Nu&J-ff{XHDvkq}`|9n6&%UXwvReg~0A_`vTtYbQA5qn?Kso+R^pf zed<`!?o-E-c5lPI-EVu|q`nUA9oT*9cwqP2e$b?*Z=&6I^QZd03cF8LVE6W2yx12I zxBVDC@5A^efVv9HcrVZ2%6Q*pwR_8-Gp43?FWhybIC_nZ_ucY}-7jl>f5w=l-C&G| z624s+b(QzeTK=>#pV|d(2fP*B3F0GtO7*iH@}HG{k$2%T9{qy!3+O}g+ky8kHl<$_ z)NbM1=f#W4^SK!-Gen()_ ze+(ui`TO$zfk}gx8h-y-9uFW8embpAW(%|F+}ZTTSZY^1Fmp$M}7CY6(9U zlJ}!Z{=4rosk-o*W1~*i2oi4NN2q%)V1&t!2*UF9jX2)@gh_o$!i$cj{eBkp>l@eb zkK*^Nd2ksY=(Dfj?5(fj*srttppURU`>M)w&K~<}$_vgO`|6bcl7_22_O%`R_U!8@ z>pIK6yZw#EI;q$2!zP^*LS2M0pQQbJmb;9wVBARi_bf|z*Nx)nwK<=p??AiwabtMa zr0={QNSE_k}+p?quETcY|vg2J#CX5`i5Qn+qdI=?ZS~`qfXwZE&9d$ zk~xU5zR`fQVCX}_{rUZTg83ygD&g}k9PN`Spgw(7$G7KA<_WQt1$@H!WzlhWeyMAC z*{v7)mXy`r`pzpOANieQ-!kRj(r~r6{;Xq^$F6t85ADsYdG zV)W4Ww#yc_z}Kwj}u%#+^` z%LjsTX8L3KU=+t6{BN;5w8QL=#q#W09KT=MOZ6~Rh~=4Y4&*^OH+)qr-_VZZ4+Z(l zJI%ezeg}>;e|fjLcl|-TY^-`IOh@{ckG~wt2LgKt$CIHT|ILLse)eRXkGvDeyTR`X z2BQF9h=GSf2i+KpvDc^KdMeJqPcG0zGff zdMV~R8OKy_@(vj9`u;GG2l-_Fbs%r~*suqg*EAn_XM%Sdems`%jJy>6@$Nt#p7-Cx z@?D)ce&&Z_xx9T{GBZ~r`yulU$FugoJF-iVT0X?%HiiFbWNu{>SyQt0`2V|ik{6?@ZsGC}z_ zJQvFowgyxG@o$1P5)giAB;3Iyusgy<%#k36R}*X;$6SYQ<}fLyUx4J z*+3q&%Z3MH`G!>=8~P0Lmv_W@m;DfqG=F(loOk`Z0(s3#VHV|ikID@lF%3j)q%e-*yU6XWgs0(na# z!`twVSe_Vf|79Sr#PR!|j^&B*_HSbOU=+t6yb#M12X?#R zD`R?kiWE}AFH7`YZ-Wkgi z<85HKxq2Lb@K78-99IXvKafXp{61N?=y~OxjNYYxE|3T1%*eW0<0r=3K+lQwZD9Y2 z_3dxQ`N;o9@h8`_!Y4{vA(@4kO%tjmvy-2lUU!1J$U&7j@50H#l4rey%eH!EKjU& zMGqQ3T;FD7A4BEo0*=*X|0NDoo)~XW1oEK#8y=74iShQQ19=6<>iVC=f##nWZ~rou zC&t?#pTv0kJ#l=^&Yqz^&~svaI~Bwa>~@2!Gc|+6`ZmZvvA+GrIDTS%I}pf&ew6-= zKpxoDfbFpTzP^)uoefxzFPB3H#%K1Zw`_ z`l0V_fqYpad54Bi`Qy-sT*k6X9OPmvmpBOhzCI)b4iv|EVCKkvMHwAFmev8Cy@LdL9_=rdKwe}H&zaskx(tpYAqlr2O`W_3IwPZ(~bexZHTiRFCZ_|XIh`pXoO;Tbw#Z~RE=>y{fouEjpW{(U+72*O{HeFW)y z;|J_1=-*%R@naO_3CE8b@d`L>zXfnGe#ox5{eq1j9VEnq?{8KrU@5>Cn z@DY#lzoznq>1n)9iGG6cG==w#AUu*2;{zw_+E9O2JHPCwrCx`GzWmpjUhaz|(o^|t z`Gfo~DZj*z;`8@OenI=*Qm?l`or3n=Nwqt(=*~)`|<4c<@q4O zrN4MCfaUz^vPZ$9f6wD!sgHN}f;W4BkL#~gcr765!?pX@flqhWj|!U#m!@Pl4sWfp@KTAAJJVHE5Tm%Q>U4NOl zLR=-T5!ZW;5$A~u#6{vVafP@_ zTqCX%H;9|WE#fwDC&|+F!}@Q{5l6&%;sSAzxJ+Cjt`gUX>%CUJ|nP25Rx&JaTo z>LV8s=ZOo%MdC7Xg}6#wBd!xSh?~SM;x=(7$+Gnj*4N&T2+CxIx?`ZV|VMJ4u$U zrLex+bHovGp143dEy1)MdBsm^Tf-<5l6&%;sSAzxJ+Cjt`gUX>%CUJ|nP25Scy_Foa_m|};JR;5$ z7l@0*W#S5PmAFP+CvFfoiCe^N;!cw7qY**&d;yiJIxJX+ zP0W8IIpT;oPh21_5|@c9#8u)Nahaqblek6PChjEp9z&cXj)-NyBX}P39@*~*S@t_Zo+h3rULam1ULrnEyiB}8yh^;5 zULjs3UQ6=5S>j>h5#mweG2(IJY2ta} z1>!~GCF1kM%fu_htHf(b-j^jFCLSRkB_1OlC!QvrC$52m=b87_i5tXC;udk6xRd1j z3~`P)BF+;Rh>OH!;tFwOH!;tFw&A>jXjyNLD6Bme!#AV_Nah146 zTqkZ2H;G%sZQ@Rn-)e|+#1V0xxIkPaE)!RXtHd?pI&p)zN!%iC6L*sQs3Fb~N5pyJ z0;97l;>$mx#|3FB7j2uM)2%`E6O^Vd4?uQQ|S;apGy>dEy1) zMdBsm^Tf-gc!7A4xB(8{UvQ{N+#+rhcar>=Aaqblek6PChjEp8AF^Sj)?Qb1>z!cnYcn+C9VH0M~Fv>$B4&? zr-|o@7l;>$mx#|3FB7j2uM)2%`S~pIF!2cSDDfEaIB^9WyifG`DshdtPTU}F61Rxk z#GNF+%Mj;?BjP-9fw)LqCaw@yiEG4l;s$Y(xJBG1?j-r$hB!wY5$A~u#6{vVafP@_ zTqCX%H;9|WE#fwDC&@6+gguVuh$G@Wae=rC$M zagI15&J!1ii^OH(3UQUVMqDRu5I2ci#BJhElE2;%=ZGWXJaK`zNL(ha5Lbz7#C75Z zag(@3+$QcM`5O#zjyNLD6Bme!#AV_Nah146Tqiyc4&JB!4a>wU#H++>NiJoHhlxjs zM~TOX$BCzj=ZP1H7m1gM&l4{buMn>iuO+#hB_1XoAs!_jBOWK7CY~o=AYLS1B0f*N zOuRz8n&b%s4&J{%kt2?X^TY+>B5|3xLR=-T5!Zq)cq2clq2S4f_xq`QxnH8lSM+D$iat9OioT!=1Q~YW+x|3ohnV9 zIXyQuKWEG4&L2u_A}^5A-e)7*qsNXtd7q>dn46hEu4hi3p1WT&o|OX>@Z_AF;`G$y zRU)DkGjYt9CMvULa{BD)_s*7HID6v6)QmZG=0$%vJALNtspF-&iHfP5JU&}OY%@KD zFFjMYawkupIAiWRb?SXDl_riKpP4=RVzLdcTy|R|tCPz%loKX&>>`&=ot&IHJv-Gs zmuT@CPSuSTm_#f~oXCl1=Vs<6UNHB~o_opqGiQ!Z%uU!c-G|mOM<07^wDgF04>|9k z^Y(lH0mlba?EU?YM@tWDV&@-JKA`?+>7a%?|Df^#^+!twG~D?Il@F*tS{jwIJ?y-P zocEyf4m$4v=N)j~e&>yr9^q_`2|Df^#^`YMVHp2b`dN5kr?<5cT14MBWL~#d*lh;P=g2`bRLu;gfdwF0Y&wpA{**`#r^8%oeu@sXs{aVLqXPuf~*e(X}I&D zAPsju3`fJA55v)L=fiL`-1#sZ4R<~oP{W;%2Gnrp!>}~m`7kUEcRo~U`C;{;b;}Q` z55-!RhO!3_s1HTEfbM|`qycS+G@w0@2DAs#fc8Kd(8qBH(ttjWJ9wn@pld+uJ?!Wq z=RN2=Sd%uO^&e1FA2y~9=zPWf&d)2Vk9M-L(6y|OcCtRYll9R~8t#0wlZHDV9ZkcX z51Z0(_$N-DnkpTinw^|ES(!UCQ<^(dI$3$&!7Gn*G99=H=qeHI*z04$o>A0;Qt^k> zM^P7p_|8X{vJ6wVK6;Y%(JQRK-}}(36-ZCA2hx-5 zf%GJMAU(;)$@@6&Kzfpo;|}tr1Fn5Wtq&8oK1|&DFmVlkP{W-M{b;!Jp&t!*KJ=sE z&c{%r;m*fUqv6hnJ}g5I)|Vm09zYRJfLgf&^%)?*4@qafBtOYBGIv`d8huyWXcpB2LZ^R76~hXGoK0a_mhsNv3s z0cyDOVIUgM`Dj+l(qfV?s&9SiSU#dV5Cia0-GLZ@kCV3tlADj?4kW)kmf$zW{UWl_ z|FdZ1A5Vp@pwJA3IL^kLx`5+#(tB|qu0IkkO#hkn)AwsS`+wBK41)A&`|=%N>`l{An{*QdZ1@%w;!20PwJnvFnDgB`RX91c!>Cc~5 z{(ox11?M05bA+s;zwgv^_W#|783gH5ee0+HZcU$7J$ADQ(wpC3KmA8XU5fsZZc29$ zq)&ZikUo=)_XB0+dQxtr%<9r*9QgW6TLx~?9|!NIvmzfKkoq(E(uj2aMV~G>-zDko z%dgf+KdkadeeVVJ-|(#v`t!?+H3GM&`ZN0ip&a`7@YAOPrf7?@(E<0yf93q{w@)vE z-^3^6ZX{#=b>v_w7YOArh@Tz@lioFU>h*bdbjKQe=&AcZW_|rdKTUjw@VV5b-sbHl zLZCb!;m_;Sqg*Me+j{B85GJmSkEHkI4AZwDUoZU5&{h?`u zi7VqH>Af9=>E*}d>!n{t7+m-Kz9R?e%{D|>C;j6KHhFx0KfZ+N#eUaIzv|L=&p)X7 zh3TJPKfNFSyY=_!yf1bo_PfscpVWBW^ZR%~`qUTJPyf_1!o-#F5&dt#M_icxd%-?m z8`%9yjg9DW8NQ^Kf9xhMNIxk5*Jr)-3pTbf1^AL){xO=kApO9fg4avGY-1Z!g)iyl zAB%|#(%UYvPWmHbE_(O;Ta<(J=08KWUit-%UxhE{_y5BvNS~gDe4X@f_wnkQ-v1`i z%ccE)?F#9a5C(U=f-gD0f95YppL+k9i$`t0tRh0frG84r7jb##b-(>M0ztZnFPpv_ U>JL&sHRcXC$`1fS*G>Qb10z;c=l}o! diff --git a/tap/tlstapper/tlstapper_bpfel.go b/tap/tlstapper/tlstapper_bpfel.go index eb6ca7281..a149b76f6 100644 --- a/tap/tlstapper/tlstapper_bpfel.go +++ b/tap/tlstapper/tlstapper_bpfel.go @@ -1,4 +1,5 @@ // Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 // +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 package tlstapper @@ -12,6 +13,18 @@ import ( "github.com/cilium/ebpf" ) +type tlsTapperTlsChunk struct { + Pid uint32 + Tgid uint32 + Len uint32 + Start uint32 + Recorded uint32 + Fd uint32 + Flags uint32 + Address [16]uint8 + Data [4096]uint8 +} + // loadTlsTapper returns the embedded CollectionSpec for tlsTapper. func loadTlsTapper() (*ebpf.CollectionSpec, error) { reader := bytes.NewReader(_TlsTapperBytes) @@ -53,20 +66,24 @@ type tlsTapperSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type tlsTapperProgramSpecs struct { - SslRead *ebpf.ProgramSpec `ebpf:"ssl_read"` - SslReadEx *ebpf.ProgramSpec `ebpf:"ssl_read_ex"` - SslRetRead *ebpf.ProgramSpec `ebpf:"ssl_ret_read"` - SslRetReadEx *ebpf.ProgramSpec `ebpf:"ssl_ret_read_ex"` - SslRetWrite *ebpf.ProgramSpec `ebpf:"ssl_ret_write"` - SslRetWriteEx *ebpf.ProgramSpec `ebpf:"ssl_ret_write_ex"` - SslWrite *ebpf.ProgramSpec `ebpf:"ssl_write"` - SslWriteEx *ebpf.ProgramSpec `ebpf:"ssl_write_ex"` - SysEnterAccept4 *ebpf.ProgramSpec `ebpf:"sys_enter_accept4"` - SysEnterConnect *ebpf.ProgramSpec `ebpf:"sys_enter_connect"` - SysEnterRead *ebpf.ProgramSpec `ebpf:"sys_enter_read"` - SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"` - SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"` - SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"` + GoCryptoTlsRead *ebpf.ProgramSpec `ebpf:"go_crypto_tls_read"` + GoCryptoTlsReadEx *ebpf.ProgramSpec `ebpf:"go_crypto_tls_read_ex"` + GoCryptoTlsWrite *ebpf.ProgramSpec `ebpf:"go_crypto_tls_write"` + GoCryptoTlsWriteEx *ebpf.ProgramSpec `ebpf:"go_crypto_tls_write_ex"` + SslRead *ebpf.ProgramSpec `ebpf:"ssl_read"` + SslReadEx *ebpf.ProgramSpec `ebpf:"ssl_read_ex"` + SslRetRead *ebpf.ProgramSpec `ebpf:"ssl_ret_read"` + SslRetReadEx *ebpf.ProgramSpec `ebpf:"ssl_ret_read_ex"` + SslRetWrite *ebpf.ProgramSpec `ebpf:"ssl_ret_write"` + SslRetWriteEx *ebpf.ProgramSpec `ebpf:"ssl_ret_write_ex"` + SslWrite *ebpf.ProgramSpec `ebpf:"ssl_write"` + SslWriteEx *ebpf.ProgramSpec `ebpf:"ssl_write_ex"` + SysEnterAccept4 *ebpf.ProgramSpec `ebpf:"sys_enter_accept4"` + SysEnterConnect *ebpf.ProgramSpec `ebpf:"sys_enter_connect"` + SysEnterRead *ebpf.ProgramSpec `ebpf:"sys_enter_read"` + SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"` + SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"` + SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"` } // tlsTapperMapSpecs contains maps before they are loaded into the kernel. @@ -77,11 +94,13 @@ type tlsTapperMapSpecs struct { ChunksBuffer *ebpf.MapSpec `ebpf:"chunks_buffer"` ConnectSyscallInfo *ebpf.MapSpec `ebpf:"connect_syscall_info"` FileDescriptorToIpv4 *ebpf.MapSpec `ebpf:"file_descriptor_to_ipv4"` + GoReadContext *ebpf.MapSpec `ebpf:"go_read_context"` + GoWriteContext *ebpf.MapSpec `ebpf:"go_write_context"` Heap *ebpf.MapSpec `ebpf:"heap"` LogBuffer *ebpf.MapSpec `ebpf:"log_buffer"` + OpensslReadContext *ebpf.MapSpec `ebpf:"openssl_read_context"` + OpensslWriteContext *ebpf.MapSpec `ebpf:"openssl_write_context"` PidsMap *ebpf.MapSpec `ebpf:"pids_map"` - SslReadContext *ebpf.MapSpec `ebpf:"ssl_read_context"` - SslWriteContext *ebpf.MapSpec `ebpf:"ssl_write_context"` } // tlsTapperObjects contains all objects after they have been loaded into the kernel. @@ -107,11 +126,13 @@ type tlsTapperMaps struct { ChunksBuffer *ebpf.Map `ebpf:"chunks_buffer"` ConnectSyscallInfo *ebpf.Map `ebpf:"connect_syscall_info"` FileDescriptorToIpv4 *ebpf.Map `ebpf:"file_descriptor_to_ipv4"` + GoReadContext *ebpf.Map `ebpf:"go_read_context"` + GoWriteContext *ebpf.Map `ebpf:"go_write_context"` Heap *ebpf.Map `ebpf:"heap"` LogBuffer *ebpf.Map `ebpf:"log_buffer"` + OpensslReadContext *ebpf.Map `ebpf:"openssl_read_context"` + OpensslWriteContext *ebpf.Map `ebpf:"openssl_write_context"` PidsMap *ebpf.Map `ebpf:"pids_map"` - SslReadContext *ebpf.Map `ebpf:"ssl_read_context"` - SslWriteContext *ebpf.Map `ebpf:"ssl_write_context"` } func (m *tlsTapperMaps) Close() error { @@ -120,11 +141,13 @@ func (m *tlsTapperMaps) Close() error { m.ChunksBuffer, m.ConnectSyscallInfo, m.FileDescriptorToIpv4, + m.GoReadContext, + m.GoWriteContext, m.Heap, m.LogBuffer, + m.OpensslReadContext, + m.OpensslWriteContext, m.PidsMap, - m.SslReadContext, - m.SslWriteContext, ) } @@ -132,24 +155,32 @@ func (m *tlsTapperMaps) Close() error { // // It can be passed to loadTlsTapperObjects or ebpf.CollectionSpec.LoadAndAssign. type tlsTapperPrograms struct { - SslRead *ebpf.Program `ebpf:"ssl_read"` - SslReadEx *ebpf.Program `ebpf:"ssl_read_ex"` - SslRetRead *ebpf.Program `ebpf:"ssl_ret_read"` - SslRetReadEx *ebpf.Program `ebpf:"ssl_ret_read_ex"` - SslRetWrite *ebpf.Program `ebpf:"ssl_ret_write"` - SslRetWriteEx *ebpf.Program `ebpf:"ssl_ret_write_ex"` - SslWrite *ebpf.Program `ebpf:"ssl_write"` - SslWriteEx *ebpf.Program `ebpf:"ssl_write_ex"` - SysEnterAccept4 *ebpf.Program `ebpf:"sys_enter_accept4"` - SysEnterConnect *ebpf.Program `ebpf:"sys_enter_connect"` - SysEnterRead *ebpf.Program `ebpf:"sys_enter_read"` - SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"` - SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"` - SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"` + GoCryptoTlsRead *ebpf.Program `ebpf:"go_crypto_tls_read"` + GoCryptoTlsReadEx *ebpf.Program `ebpf:"go_crypto_tls_read_ex"` + GoCryptoTlsWrite *ebpf.Program `ebpf:"go_crypto_tls_write"` + GoCryptoTlsWriteEx *ebpf.Program `ebpf:"go_crypto_tls_write_ex"` + SslRead *ebpf.Program `ebpf:"ssl_read"` + SslReadEx *ebpf.Program `ebpf:"ssl_read_ex"` + SslRetRead *ebpf.Program `ebpf:"ssl_ret_read"` + SslRetReadEx *ebpf.Program `ebpf:"ssl_ret_read_ex"` + SslRetWrite *ebpf.Program `ebpf:"ssl_ret_write"` + SslRetWriteEx *ebpf.Program `ebpf:"ssl_ret_write_ex"` + SslWrite *ebpf.Program `ebpf:"ssl_write"` + SslWriteEx *ebpf.Program `ebpf:"ssl_write_ex"` + SysEnterAccept4 *ebpf.Program `ebpf:"sys_enter_accept4"` + SysEnterConnect *ebpf.Program `ebpf:"sys_enter_connect"` + SysEnterRead *ebpf.Program `ebpf:"sys_enter_read"` + SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"` + SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"` + SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"` } func (p *tlsTapperPrograms) Close() error { return _TlsTapperClose( + p.GoCryptoTlsRead, + p.GoCryptoTlsReadEx, + p.GoCryptoTlsWrite, + p.GoCryptoTlsWriteEx, p.SslRead, p.SslReadEx, p.SslRetRead, diff --git a/tap/tlstapper/tlstapper_bpfel.o b/tap/tlstapper/tlstapper_bpfel.o index dd5697f6aa2339dd95ca558f742c456aafd22c6e..260994b7f15ba47d78c9fc02bff38ab6fa8563a5 100644 GIT binary patch literal 156680 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

8LD}@xNqrj}-}Kd` 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 DE_^tD literal 116056 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