diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5208af1..8c10d29f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,466 +1,50 @@ -name: CI +name: Packaging on: push: + branches: ['**'] pull_request: workflow_dispatch: + workflow_call: -# Cache-key convention: keys carry a trailing `-vN`. Bump that suffix in EVERY -# affected job whenever you change CMake flags / configure commands / compiler -# for the cached artifact, or a stale build from a different config is restored. +permissions: + contents: read jobs: - format-check: - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@v6 - - name: Install clang-format - run: sudo apt-get update && sudo apt-get install -y clang-format - - name: Check formatting - run: | - fail=0 - while IFS= read -r f; do - if ! clang-format --dry-run --Werror "$f" 2>/dev/null; then - echo "needs formatting: $f"; fail=1 - fi - done < <(find src cli tests -type f \( -name '*.c' -o -name '*.h' \)) - if [ "$fail" -ne 0 ]; then - echo "Fix: find src cli tests -name '*.c' -o -name '*.h' | xargs clang-format -i" - exit 1 - fi - - build: - # gcc Release: build + full unit suite. e2e_multipath self-skips (exit 77, - # SKIP_RETURN_CODE) on the unprivileged runner — no tc/netem needed. - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y cmake libevent-dev libcurl4-openssl-dev libnghttp2-dev golang - - name: Compute xquic SHA - id: xqsha - run: | - sha=$(git -c safe.directory="$GITHUB_WORKSPACE" rev-parse HEAD:third_party/xquic) - echo "sha=$sha" >> "$GITHUB_OUTPUT" - - - name: Cache BoringSSL (gcc fPIC) - id: bssl-cache - uses: actions/cache@v5 - with: - path: third_party/xquic/third_party/boringssl/build - key: bssl-0ce57bb-linux-v1 - - name: Build BoringSSL (xquic submodule) - if: steps.bssl-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic/third_party/boringssl - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" .. - make -j"$(nproc)" ssl crypto - - - name: Cache xquic (gcc Release, EVENT_LOG) - id: xquic-cache - uses: actions/cache@v5 - with: - # xqc_configure.h is generated INTO the source tree (not the build - # dir); cache it too or a restored lib pairs with a stale header. - path: | - third_party/xquic/build - third_party/xquic/include/xquic/xqc_configure.h - key: xquic-${{ steps.xqsha.outputs.sha }}-linux-gcc-release-eventlog-bssl-0ce57bb-v3 - - name: Build xquic - if: steps.xquic-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSSL_TYPE=boringssl \ - -DXQC_ENABLE_EVENT_LOG=ON \ - -DXQC_ENABLE_BBR2=ON -DXQC_ENABLE_UNLIMITED=ON \ - -DXQC_ENABLE_FEC=ON -DXQC_ENABLE_XOR=ON \ - -DCMAKE_C_FLAGS="-Wno-dangling-pointer" .. - make -j"$(nproc)" - - - name: Build mqproxy (Release) - run: | - cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" - cmake --build build -j"$(nproc)" - - name: Unit tests (ctest) - run: ctest --test-dir build --output-on-failure - - packaging: - # Static-link (xquic+BoringSSL) packaging build: verify the install layout + - # self-containment, then a REAL `systemctl start` of the hardened unit (the only - # check that exercises MemoryDenyWriteExecute / @system-service / RestrictAddressFamilies - # / ProtectHome against a live process — systemd-analyze cannot). Self-contained: - # rebuilds BoringSSL+xquic from the same caches as `build` (no artifact to depend on). - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y cmake libevent-dev libcurl4-openssl-dev libnghttp2-dev golang - - name: Compute xquic SHA - id: xqsha - run: | - sha=$(git -c safe.directory="$GITHUB_WORKSPACE" rev-parse HEAD:third_party/xquic) - echo "sha=$sha" >> "$GITHUB_OUTPUT" - - - name: Cache BoringSSL (gcc fPIC) - id: bssl-cache - uses: actions/cache@v5 - with: - path: third_party/xquic/third_party/boringssl/build - key: bssl-0ce57bb-linux-v1 - - name: Build BoringSSL (xquic submodule) - if: steps.bssl-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic/third_party/boringssl - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" .. - make -j"$(nproc)" ssl crypto - - - name: Cache xquic (gcc Release, EVENT_LOG) - id: xquic-cache - uses: actions/cache@v5 - with: - path: | - third_party/xquic/build - third_party/xquic/include/xquic/xqc_configure.h - key: xquic-${{ steps.xqsha.outputs.sha }}-linux-gcc-release-eventlog-bssl-0ce57bb-v3 - - name: Build xquic - if: steps.xquic-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSSL_TYPE=boringssl \ - -DXQC_ENABLE_EVENT_LOG=ON \ - -DXQC_ENABLE_BBR2=ON -DXQC_ENABLE_UNLIMITED=ON \ - -DXQC_ENABLE_FEC=ON -DXQC_ENABLE_XOR=ON \ - -DCMAKE_C_FLAGS="-Wno-dangling-pointer" .. - make -j"$(nproc)" - - - name: Configure (static, prefix=/usr) - run: | - cmake -S . -B build-pkg -DCMAKE_BUILD_TYPE=Release \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" \ - -DMQPROXY_STATIC_XQUIC=ON -DCMAKE_INSTALL_PREFIX=/usr - - name: Build CLI - run: cmake --build build-pkg --target mqproxy_cli -j"$(nproc)" - - name: Verify packaging (layout + self-containment) - run: ctest --test-dir build-pkg -R packaging_install_static --output-on-failure - - name: Install + provision + live start smoke - run: | - sudo cmake --install build-pkg - sudo systemd-sysusers - sudo systemd-tmpfiles --create - sudo install -d -o mqproxy -g mqproxy -m 0750 /etc/mqproxy - # Cert/key MUST be readable by the hardened unit: the compiled-in default - # cert path is under /home (blocked by ProtectHome=yes), so copy the repo - # test cert into /etc/mqproxy (readable under ProtectSystem=strict). - sudo install -o mqproxy -g mqproxy -m 0644 tests/certs/test.crt /etc/mqproxy/test.crt - sudo install -o mqproxy -g mqproxy -m 0600 tests/certs/test.key /etc/mqproxy/test.key - printf '[Interface]\nListen = 127.0.0.1:4433\n[TLS]\nCert = /etc/mqproxy/test.crt\nKey = /etc/mqproxy/test.key\n[Auth]\nKey = citoken\n' \ - | sudo tee /etc/mqproxy/test.conf >/dev/null - sudo chown mqproxy:mqproxy /etc/mqproxy/test.conf - sudo chmod 0600 /etc/mqproxy/test.conf - sudo systemctl daemon-reload - sudo systemctl start mqproxy-server@test - sleep 2 - sudo journalctl -u mqproxy-server@test --no-pager | tail -30 - sudo systemctl is-active mqproxy-server@test # fails the job if the unit died - sudo systemctl stop mqproxy-server@test - - name: Client unit live start smoke - run: | - # Client unit: dummy unreachable server + a local SOCKS5 ingress. Reconnect is - # on by default, so it binds the ingress and stays up retrying — is-active holds. - # No TLS cert needed (client side). Proves the client ExecStart/path + ingress - # bind work under the same hardening sandbox as the server. - printf '[Server]\nAddress = 127.0.0.1:14999\n[Auth]\nKey = citoken\n[Ingress]\nSocks5 = 127.0.0.1:1080\n' \ - | sudo tee /etc/mqproxy/testclient.conf >/dev/null - sudo chown mqproxy:mqproxy /etc/mqproxy/testclient.conf - sudo chmod 0600 /etc/mqproxy/testclient.conf - sudo systemctl daemon-reload - sudo systemctl start mqproxy-client@testclient - sleep 2 - sudo journalctl -u mqproxy-client@testclient --no-pager | tail -30 - sudo systemctl is-active mqproxy-client@testclient # fails the job if the unit died - sudo systemctl stop mqproxy-client@testclient - - sanitizer: - # ASan/UBSan build + unit suite (the configuration the proxy was developed - # under). Same non-ASan Release xquic; only mqproxy gets the sanitizers. - runs-on: ubuntu-latest - timeout-minutes: 25 - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y cmake libevent-dev libcurl4-openssl-dev libnghttp2-dev golang - - name: Compute xquic SHA - id: xqsha - run: | - sha=$(git -c safe.directory="$GITHUB_WORKSPACE" rev-parse HEAD:third_party/xquic) - echo "sha=$sha" >> "$GITHUB_OUTPUT" - - - name: Cache BoringSSL (gcc fPIC) - id: bssl-cache - uses: actions/cache@v5 - with: - path: third_party/xquic/third_party/boringssl/build - key: bssl-0ce57bb-linux-v1 - - name: Build BoringSSL (xquic submodule) - if: steps.bssl-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic/third_party/boringssl - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" .. - make -j"$(nproc)" ssl crypto - - - name: Cache xquic (gcc Release, EVENT_LOG) - id: xquic-cache - uses: actions/cache@v5 - with: - path: | - third_party/xquic/build - third_party/xquic/include/xquic/xqc_configure.h - key: xquic-${{ steps.xqsha.outputs.sha }}-linux-gcc-release-eventlog-bssl-0ce57bb-v3 - - name: Build xquic - if: steps.xquic-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSSL_TYPE=boringssl \ - -DXQC_ENABLE_EVENT_LOG=ON \ - -DXQC_ENABLE_BBR2=ON -DXQC_ENABLE_UNLIMITED=ON \ - -DXQC_ENABLE_FEC=ON -DXQC_ENABLE_XOR=ON \ - -DCMAKE_C_FLAGS="-Wno-dangling-pointer" .. - make -j"$(nproc)" - - - name: Build mqproxy (ASan/UBSan) - run: | - cmake -S . -B build-asan -DMQPROXY_SANITIZE=ON \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" - cmake --build build-asan -j"$(nproc)" - - name: Unit tests (ASan/UBSan) - env: - ASAN_OPTIONS: detect_leaks=1 - # Without halt_on_error, UBSan prints a diagnostic but exits 0, so - # ctest would pass despite a UB finding — make UBSan actually gate. - UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1 - run: ctest --test-dir build-asan --output-on-failure - - perf: - # NET_ADMIN e2e tests (multipath aggregation, tproxy, MITM, UDP multipath - # sub-cases) that cannot run on the unprivileged default runner. Runs inside - # a Docker container with CAP_NET_ADMIN so tc/netem/nft work. The multipath - # aggregation test (e2e_multipath) is the perf regression gate: it asserts - # >=1.5x throughput over two shaped paths. All other NET_ADMIN-gated e2e - # sub-cases (e2e_gateway case 8, e2e_tproxy, e2e_mitm_h2, e2e_udp case 6) - # exercise their full codepath here instead of being SKIPPED. - runs-on: ubuntu-latest - timeout-minutes: 25 - container: - image: ubuntu:24.04 - options: --cap-add=NET_ADMIN - steps: - - name: Install git (needed before checkout for submodules) - run: apt-get update && apt-get install -y git - - uses: actions/checkout@v6 - with: - submodules: recursive - - name: Install dependencies - run: | - apt-get install -y \ - cmake gcc g++ make \ - libevent-dev libcurl4-openssl-dev libnghttp2-dev golang \ - iproute2 nftables curl python3 openssl sudo \ - ca-certificates - - name: Compute xquic SHA - id: xqsha - run: | - sha=$(git -c safe.directory="$GITHUB_WORKSPACE" rev-parse HEAD:third_party/xquic) - echo "sha=$sha" >> "$GITHUB_OUTPUT" - - - name: Cache BoringSSL (gcc fPIC) - id: bssl-cache - uses: actions/cache@v5 - with: - path: third_party/xquic/third_party/boringssl/build - key: bssl-0ce57bb-linux-v1 - - name: Build BoringSSL (xquic submodule) - if: steps.bssl-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic/third_party/boringssl - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" .. - make -j"$(nproc)" ssl crypto - - - name: Cache xquic (gcc Release, EVENT_LOG) - id: xquic-cache - uses: actions/cache@v5 - with: - path: | - third_party/xquic/build - third_party/xquic/include/xquic/xqc_configure.h - key: xquic-${{ steps.xqsha.outputs.sha }}-linux-gcc-release-eventlog-bssl-0ce57bb-v3 - - name: Build xquic - if: steps.xquic-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSSL_TYPE=boringssl \ - -DXQC_ENABLE_EVENT_LOG=ON \ - -DXQC_ENABLE_BBR2=ON -DXQC_ENABLE_UNLIMITED=ON \ - -DXQC_ENABLE_FEC=ON -DXQC_ENABLE_XOR=ON \ - -DCMAKE_C_FLAGS="-Wno-dangling-pointer" .. - make -j"$(nproc)" - - - name: Build mqproxy (Release) - run: | - cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" - cmake --build build -j"$(nproc)" - - name: E2E tests (ctest, with NET_ADMIN) - env: - # e2e_gateway: no tc on lo (cases 8 and 10) is a failure here, not a skip. - MQPROXY_E2E_REQUIRE_NETADMIN: 1 - run: ctest --test-dir build -V - - name: Upload e2e logs on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: perf-e2e-logs - path: /tmp/mqproxy_e2e_* - if-no-files-found: ignore - - h3-curl: - # OPTIONAL / NON-BLOCKING h3-libcurl build (Phase 6 origin-protocol h3). - # Builds ngtcp2 + nghttp3 + libcurl on the pinned BoringSSL via - # scripts/build-h3-curl.sh, then builds + tests mqproxy linked against it - # (-DMQPROXY_H3_CURL). This is heavy (clones + compiles a QUIC+TLS+curl - # stack) and version-pin-sensitive (ngtcp2 vs BoringSSL QUIC-API), so it - # runs ONLY on manual dispatch and is continue-on-error: it must never block - # the default-build CI. The DEFAULT mqproxy build does NOT use this. - if: github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - timeout-minutes: 60 - continue-on-error: true + build-release: + strategy: + fail-fast: false + matrix: + include: + # Retain the amd64 glibc 2.35 baseline; ARM uses a native 24.04 runner. + - { arch: amd64, runner: ubuntu-22.04 } + - { arch: arm64, runner: ubuntu-24.04-arm } + runs-on: ${{ matrix.runner }} + timeout-minutes: 45 steps: - uses: actions/checkout@v6 with: submodules: recursive - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y cmake libevent-dev libnghttp2-dev golang pkg-config - - name: Compute xquic SHA - id: xqsha - run: | - sha=$(git -c safe.directory="$GITHUB_WORKSPACE" rev-parse HEAD:third_party/xquic) - echo "sha=$sha" >> "$GITHUB_OUTPUT" - - - name: Cache BoringSSL (gcc fPIC) - id: bssl-cache - uses: actions/cache@v5 - with: - path: third_party/xquic/third_party/boringssl/build - key: bssl-0ce57bb-linux-v1 - - name: Build BoringSSL (xquic submodule) - if: steps.bssl-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic/third_party/boringssl - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" .. - make -j"$(nproc)" ssl crypto - - - name: Cache xquic (gcc Release, EVENT_LOG) - id: xquic-cache - uses: actions/cache@v5 - with: - path: | - third_party/xquic/build - third_party/xquic/include/xquic/xqc_configure.h - key: xquic-${{ steps.xqsha.outputs.sha }}-linux-gcc-release-eventlog-bssl-0ce57bb-v3 - - name: Build xquic - if: steps.xquic-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSSL_TYPE=boringssl \ - -DXQC_ENABLE_EVENT_LOG=ON \ - -DXQC_ENABLE_BBR2=ON -DXQC_ENABLE_UNLIMITED=ON \ - -DXQC_ENABLE_FEC=ON -DXQC_ENABLE_XOR=ON \ - -DCMAKE_C_FLAGS="-Wno-dangling-pointer" .. - make -j"$(nproc)" - - - name: Build h3-libcurl (ngtcp2 + nghttp3 + curl on BoringSSL) - run: scripts/build-h3-curl.sh - - - name: Build mqproxy (MQPROXY_H3_CURL) - run: | - cmake -S . -B build-h3 -DCMAKE_BUILD_TYPE=Release \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" \ - -DMQPROXY_H3_CURL="$PWD/third_party/h3-curl/install" - cmake --build build-h3 -j"$(nproc)" - - name: Verify RPATH resolves the vendored libcurl + - name: Verify tag matches Cargo version + shell: bash run: | - bin="$(find build-h3 -type f -name mqproxy | head -1)" - echo "binary: $bin" - readelf -d "$bin" | grep -iE 'r(un)?path' || { echo "no RPATH/RUNPATH"; exit 1; } - readelf -d "$bin" | grep -iE 'r(un)?path' | grep -q "third_party/h3-curl/install/lib" \ - || { echo "vendored libcurl dir NOT in RPATH"; exit 1; } - - name: Unit tests (ctest) - run: ctest --test-dir build-h3 --output-on-failure - - fuzz: - # libFuzzer harnesses over the wire/ingress parse surface. Self-contained: - # the parsers depend only on libc, so NO submodules / NO boringssl+xquic - # build / NO cache are needed (see fuzz/CMakeLists.txt). - runs-on: ubuntu-latest - timeout-minutes: 20 - env: - FUZZ_SECONDS: 30 # per-target exploration budget (tune here) - steps: - - uses: actions/checkout@v6 - # no submodules: fuzz harnesses compile only src/wire + src/ingress - - name: Install clang - run: sudo apt-get update && sudo apt-get install -y clang cmake - - name: Configure + build harnesses - run: | - cmake -S fuzz -B build-fuzz -DCMAKE_C_COMPILER=clang - cmake --build build-fuzz -j"$(nproc)" - - name: Replay committed corpus (deterministic gate) - run: | - set -e - for t in build-fuzz/fuzz_*; do - name=$(basename "$t"); dir="fuzz/corpus/${name#fuzz_}" - if [ -d "$dir" ] && [ -n "$(ls -A "$dir" 2>/dev/null)" ]; then - echo "== replay $name =="; "$t" "$dir"/* - else - echo "== $name: no corpus, skipping replay ==" - fi - done - - name: Short exploration (best-effort, fails on any new crash) - run: | - set -e - for t in build-fuzz/fuzz_*; do - name=$(basename "$t"); dir="fuzz/corpus/${name#fuzz_}"; mkdir -p "$dir" - echo "== explore $name (${FUZZ_SECONDS}s) ==" - "$t" "$dir" -max_total_time="${FUZZ_SECONDS}" -print_final_stats=1 - done - - name: Upload crashes on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: fuzz-crashes + VERSION=$(cargo metadata --no-deps --format-version 1 | python3 -c 'import json,sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"] == "mqproxy"))') + if [[ "$GITHUB_REF" == refs/tags/v* && "${GITHUB_REF_NAME#v}" != "$VERSION" ]]; then + echo "Tag ${GITHUB_REF_NAME#v} does not match Cargo $VERSION"; exit 1 + fi + - name: Install build and package tools + run: sudo apt-get update && sudo apt-get install -y build-essential cmake golang dpkg-dev python3 curl iproute2 + - name: Build and verify Cargo artifacts + run: python3 packaging/build.py + - name: Install deb and exercise both systemd units + run: sudo bash tests/packaging/smoke_deb.sh target/dist/mqproxy_*_${{ matrix.arch }}.deb + - name: Rust gateway benchmark smoke (native disposable runner) + if: github.event_name == 'push' && matrix.arch == 'amd64' + run: sudo env REPEAT=1 DURATION=1 bash scripts/ci_benchmarks/ci_bench_gateway.sh target/release/mqproxy + - uses: actions/upload-artifact@v7 + with: + name: release-${{ matrix.arch }} path: | - crash-* - leak-* - timeout-* - oom-* - if-no-files-found: ignore + target/dist/mqproxy_*.deb + target/dist/mqproxy_*.tar.gz + if-no-files-found: error diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b2d06624..586de84f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,10 +1,10 @@ name: CodeQL on: - # Scoped to the default branch: CodeQL builds xquic (heavy) and uploads to the + # Scoped to the default branch: CodeQL runs build scripts and uploads to the # repo code-scanning store, so run it on main pushes + PRs targeting main + # weekly, not on every feature-branch push. (The lightweight fuzz job in - # ci.yml still runs on all branches.) + # rust.yml still runs on all branches.) push: branches: [ main ] pull_request: @@ -13,9 +13,6 @@ on: - cron: '23 4 * * 1' # weekly, Monday 04:23 UTC workflow_dispatch: -# Cache-key convention mirrors ci.yml: keys carry a trailing `-vN`. Bump in -# lockstep with ci.yml if the cached BoringSSL/xquic config changes. - jobs: analyze: runs-on: ubuntu-latest @@ -28,68 +25,16 @@ jobs: - uses: actions/checkout@v6 with: submodules: recursive - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y cmake libevent-dev libcurl4-openssl-dev libnghttp2-dev golang - - # --- BoringSSL + xquic: mirror ci.yml `build` so mqproxy compiles for - # extraction. Same cache keys as ci.yml to ride the existing caches. --- - - name: Compute xquic SHA - id: xqsha - run: | - sha=$(git -c safe.directory="$GITHUB_WORKSPACE" rev-parse HEAD:third_party/xquic) - echo "sha=$sha" >> "$GITHUB_OUTPUT" - - - name: Cache BoringSSL (gcc fPIC) - id: bssl-cache - uses: actions/cache@v5 - with: - path: third_party/xquic/third_party/boringssl/build - key: bssl-0ce57bb-linux-v1 - - name: Build BoringSSL (xquic submodule) - if: steps.bssl-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic/third_party/boringssl - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" .. - make -j"$(nproc)" ssl crypto - - - name: Cache xquic (gcc Release, EVENT_LOG) - id: xquic-cache - uses: actions/cache@v5 - with: - path: | - third_party/xquic/build - third_party/xquic/include/xquic/xqc_configure.h - key: xquic-${{ steps.xqsha.outputs.sha }}-linux-gcc-release-eventlog-bssl-0ce57bb-v3 - - name: Build xquic - if: steps.xquic-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSSL_TYPE=boringssl \ - -DXQC_ENABLE_EVENT_LOG=ON \ - -DXQC_ENABLE_BBR2=ON -DXQC_ENABLE_UNLIMITED=ON \ - -DXQC_ENABLE_FEC=ON -DXQC_ENABLE_XOR=ON \ - -DCMAKE_C_FLAGS="-Wno-dangling-pointer" .. - make -j"$(nproc)" - - # CodeQL traces commands run AFTER init — build xquic above (ignored via - # paths-ignore), init here, then build mqproxy so only our code is extracted. + - name: Install native build dependencies + run: sudo apt-get update && sudo apt-get install -y cmake golang - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: - languages: c-cpp + languages: rust + build-mode: none queries: security-extended config-file: ./.github/codeql/codeql-config.yml - - - name: Build mqproxy - run: | - # EXACT flags from ci.yml `build` job so extraction compiles - cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" - cmake --build build -j"$(nproc)" - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: - category: /language:c-cpp + category: /language:rust diff --git a/.github/workflows/perf-weekly.yml b/.github/workflows/perf-weekly.yml index b5f2ee31..a0316ac4 100644 --- a/.github/workflows/perf-weekly.yml +++ b/.github/workflows/perf-weekly.yml @@ -35,7 +35,7 @@ jobs: run: | apt-get install -y \ cmake gcc g++ make \ - libevent-dev libcurl4-openssl-dev libnghttp2-dev golang \ + golang \ iproute2 nftables curl python3 openssl sudo \ iperf3 socat \ python3-pip pipx \ @@ -43,62 +43,23 @@ jobs: pipx install awscli echo "$HOME/.local/bin" >> "$GITHUB_PATH" - - name: Compute xquic SHA - id: xqsha + - name: Install Rust run: | - sha=$(git -c safe.directory="$GITHUB_WORKSPACE" rev-parse HEAD:third_party/xquic) - echo "sha=$sha" >> "$GITHUB_OUTPUT" - - - name: Cache BoringSSL (gcc fPIC) - id: bssl-cache - uses: actions/cache@v5 - with: - path: third_party/xquic/third_party/boringssl/build - key: bssl-0ce57bb-linux-v1 - - - name: Build BoringSSL (xquic submodule) - if: steps.bssl-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic/third_party/boringssl - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" .. - make -j"$(nproc)" ssl crypto - - - name: Cache xquic (gcc Release, EVENT_LOG) - id: xquic-cache - uses: actions/cache@v5 - with: - path: | - third_party/xquic/build - third_party/xquic/include/xquic/xqc_configure.h - key: xquic-${{ steps.xqsha.outputs.sha }}-linux-gcc-release-eventlog-bssl-0ce57bb-v3 - - - name: Build xquic - if: steps.xquic-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSSL_TYPE=boringssl \ - -DXQC_ENABLE_EVENT_LOG=ON \ - -DXQC_ENABLE_BBR2=ON -DXQC_ENABLE_UNLIMITED=ON \ - -DXQC_ENABLE_FEC=ON -DXQC_ENABLE_XOR=ON \ - -DCMAKE_C_FLAGS="-Wno-dangling-pointer" .. - make -j"$(nproc)" - - - name: Build mqproxy (Release) - run: | - cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" - cmake --build build -j"$(nproc)" + curl --proto '=https' -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain none + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + - name: Build mqproxy (Rust Release) + run: cargo build --release --locked -p mqproxy + - name: Generate MITM test certificates + run: bash tests/certs/generate-mitm.sh - name: TCP proxy benchmark - run: sudo bash scripts/ci_benchmarks/ci_bench_tcp_proxy.sh build/mqproxy + run: sudo bash scripts/ci_benchmarks/ci_bench_tcp_proxy.sh target/release/mqproxy - name: MITM H2 benchmark - run: sudo bash scripts/ci_benchmarks/ci_bench_mitm.sh build/mqproxy + run: sudo bash scripts/ci_benchmarks/ci_bench_mitm.sh target/release/mqproxy - name: Multi-stream sweep benchmark - run: sudo bash scripts/ci_benchmarks/ci_bench_multi_stream.sh build/mqproxy + run: sudo bash scripts/ci_benchmarks/ci_bench_multi_stream.sh target/release/mqproxy - name: Upload results if: always() diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml index 1b256398..fcb552f0 100644 --- a/.github/workflows/perf.yml +++ b/.github/workflows/perf.yml @@ -35,7 +35,7 @@ jobs: run: | apt-get install -y \ cmake gcc g++ make \ - libevent-dev libcurl4-openssl-dev libnghttp2-dev golang \ + golang \ iproute2 nftables curl python3 openssl sudo \ iperf3 socat \ python3-pip pipx \ @@ -43,59 +43,20 @@ jobs: pipx install awscli echo "$HOME/.local/bin" >> "$GITHUB_PATH" - - name: Compute xquic SHA - id: xqsha + - name: Install Rust run: | - sha=$(git -c safe.directory="$GITHUB_WORKSPACE" rev-parse HEAD:third_party/xquic) - echo "sha=$sha" >> "$GITHUB_OUTPUT" - - - name: Cache BoringSSL (gcc fPIC) - id: bssl-cache - uses: actions/cache@v5 - with: - path: third_party/xquic/third_party/boringssl/build - key: bssl-0ce57bb-linux-v1 - - - name: Build BoringSSL (xquic submodule) - if: steps.bssl-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic/third_party/boringssl - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" .. - make -j"$(nproc)" ssl crypto - - - name: Cache xquic (gcc Release, EVENT_LOG) - id: xquic-cache - uses: actions/cache@v5 - with: - path: | - third_party/xquic/build - third_party/xquic/include/xquic/xqc_configure.h - key: xquic-${{ steps.xqsha.outputs.sha }}-linux-gcc-release-eventlog-bssl-0ce57bb-v3 - - - name: Build xquic - if: steps.xquic-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSSL_TYPE=boringssl \ - -DXQC_ENABLE_EVENT_LOG=ON \ - -DXQC_ENABLE_BBR2=ON -DXQC_ENABLE_UNLIMITED=ON \ - -DXQC_ENABLE_FEC=ON -DXQC_ENABLE_XOR=ON \ - -DCMAKE_C_FLAGS="-Wno-dangling-pointer" .. - make -j"$(nproc)" - - - name: Build mqproxy (Release) - run: | - cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" - cmake --build build -j"$(nproc)" + curl --proto '=https' -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain none + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + - name: Build mqproxy (Rust Release) + run: cargo build --release --locked -p mqproxy + - name: Generate MITM test certificates + run: bash tests/certs/generate-mitm.sh - name: TCP proxy benchmark - run: sudo bash scripts/ci_benchmarks/ci_bench_tcp_proxy.sh build/mqproxy + run: sudo bash scripts/ci_benchmarks/ci_bench_tcp_proxy.sh target/release/mqproxy - name: MITM H2 benchmark - run: sudo bash scripts/ci_benchmarks/ci_bench_mitm.sh build/mqproxy + run: sudo bash scripts/ci_benchmarks/ci_bench_mitm.sh target/release/mqproxy - name: Upload results if: always() diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ba4bf0d..e9320a6f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,163 +6,15 @@ on: workflow_dispatch: permissions: - contents: write + contents: read jobs: build-release: - strategy: - fail-fast: false - matrix: - include: - # amd64 deb is intentionally built on 22.04, not ubuntu-latest. - # ubuntu-latest (24.04) links libevent-2.1-7t64 (the t64 transition - # rename), so dpkg-shlibdeps pins Depends: to a package name absent on - # 22.04/Debian 12. 22.04 yields Depends: libevent-2.1-7 -> satisfied - # natively on 22.04/Debian 12 and via Provides: libevent-2.1-7 on - # 24.04/Debian 13. glibc 2.35 baseline is forward-compatible. arm64 - # stays on 24.04 because GitHub has no 22.04 ARM runner. - - { arch: amd64, runner: ubuntu-22.04 } - - { arch: arm64, runner: ubuntu-24.04-arm } - runs-on: ${{ matrix.runner }} - timeout-minutes: 60 - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - - - name: Verify tag matches CMake version - shell: bash - run: | - CMAKE_VERSION=$(sed -n 's/^project(mqproxy VERSION \([0-9.][0-9.]*\).*/\1/p' CMakeLists.txt) - if [[ "$GITHUB_REF" == refs/tags/v* ]]; then - TAG_VERSION="${GITHUB_REF_NAME#v}" - if [[ "$TAG_VERSION" == *-* ]]; then - echo "Pre-release tag $TAG_VERSION - skipping strict version match" - elif [ "$TAG_VERSION" != "$CMAKE_VERSION" ]; then - echo "ERROR: Tag $TAG_VERSION != CMakeLists.txt $CMAKE_VERSION" - exit 1 - fi - fi - echo "VERSION=$CMAKE_VERSION" >> "$GITHUB_ENV" - - - name: Install dependencies - # libnghttp2-dev is REQUIRED: MQPROXY_STATIC_XQUIC=ON links libnghttp2.a - # and FATAL_ERRORs without it (CMakeLists.txt). golang: BoringSSL build. - run: | - sudo apt-get update - sudo apt-get install -y cmake libevent-dev libcurl4-openssl-dev libnghttp2-dev golang - - - name: Compute xquic SHA - id: xqsha - run: | - sha=$(git -c safe.directory="$GITHUB_WORKSPACE" rev-parse HEAD:third_party/xquic) - echo "sha=$sha" >> "$GITHUB_OUTPUT" - - # Cache keys are arch-qualified: the matrix runs amd64 + arm64, and a - # non-arch key would let arm64 restore an amd64 BoringSSL/xquic build. - - name: Cache BoringSSL (fPIC) - id: bssl-cache - uses: actions/cache@v5 - with: - path: third_party/xquic/third_party/boringssl/build - key: bssl-0ce57bb-${{ matrix.arch }}-v1 - - name: Build BoringSSL (xquic submodule) - if: steps.bssl-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic/third_party/boringssl - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" .. - make -j"$(nproc)" ssl crypto - - - name: Cache xquic (Release) - id: xquic-cache - uses: actions/cache@v5 - with: - path: | - third_party/xquic/build - third_party/xquic/include/xquic/xqc_configure.h - key: xquic-${{ steps.xqsha.outputs.sha }}-${{ matrix.arch }}-gcc-release-bssl-0ce57bb-v2 - - name: Build xquic - if: steps.xquic-cache.outputs.cache-hit != 'true' - run: | - cd third_party/xquic - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSSL_TYPE=boringssl \ - -DXQC_ENABLE_BBR2=ON -DXQC_ENABLE_UNLIMITED=ON \ - -DXQC_ENABLE_FEC=ON -DXQC_ENABLE_XOR=ON \ - -DCMAKE_C_FLAGS="-Wno-dangling-pointer" .. - make -j"$(nproc)" - - - name: Configure (static, prefix=/usr) - run: | - cmake -S . -B build-pkg -DCMAKE_BUILD_TYPE=Release \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" \ - -DMQPROXY_STATIC_XQUIC=ON -DCMAKE_INSTALL_PREFIX=/usr - - name: Build CLI - run: cmake --build build-pkg --target mqproxy_cli -j"$(nproc)" - - - name: Build deb package - run: cd build-pkg && cpack -G DEB - - - name: Verify license files in deb - # Release-side license notice gate. OPTIONAL install rules silently - # skip fetched third-party LICENSEs; release builds DO check out/build - # them, so mqproxy + xquic + BoringSSL + nghttp2 must be present. - run: | - set -e - deb=$(ls build-pkg/mqproxy_*.deb) - expected=4 - found=$(dpkg-deb -c "$deb" \ - | grep -cE 'usr/share/doc/mqproxy/(LICENSE|third-party/(xquic|boringssl|nghttp2)\.txt)$' \ - || true) - if [ "$found" -ne "$expected" ]; then - echo "::error::deb missing license files (found $found of $expected)" - dpkg-deb -c "$deb" | grep -E 'usr/share/doc/mqproxy|third-party' || echo " (none found)" - exit 1 - fi - - - name: Verify deb install + postinst + live start (amd64 only) - # NEW dpkg-install smoke - distinct from ci.yml's cmake--install smoke, - # which never exercises postinst. dpkg -i fires postinst (creates the - # mqproxy user + dirs), then we start a real server instance. amd64 only: - # the runner is native amd64; running the arm64 .deb would need emulation. - if: matrix.arch == 'amd64' - run: | - set -e - deb=$(ls build-pkg/mqproxy_*.deb) - # Re-assert dpkg -i after any dependency repair so a genuinely broken - # postinst or malformed deb still fails the job (a bare `|| apt-get -f - # install` would swallow it). The static deb has no deps to repair, so - # the fallback is normally a no-op. - sudo dpkg -i "$deb" || { sudo apt-get -f install -y; sudo dpkg -i "$deb"; } - id mqproxy # postinst created the user - test -d /etc/mqproxy # tmpfiles created the dir - # Provision a minimal server config + the repo test cert (compiled-in - # default cert path is under /home, blocked by ProtectHome=yes). - sudo install -o mqproxy -g mqproxy -m 0644 tests/certs/test.crt /etc/mqproxy/test.crt - sudo install -o mqproxy -g mqproxy -m 0600 tests/certs/test.key /etc/mqproxy/test.key - printf '[Interface]\nListen = 127.0.0.1:4433\n[TLS]\nCert = /etc/mqproxy/test.crt\nKey = /etc/mqproxy/test.key\n[Auth]\nKey = citoken\n' \ - | sudo tee /etc/mqproxy/test.conf >/dev/null - sudo chown mqproxy:mqproxy /etc/mqproxy/test.conf - sudo chmod 0600 /etc/mqproxy/test.conf - sudo systemctl daemon-reload - sudo systemctl start mqproxy-server@test - sleep 2 - sudo journalctl -u mqproxy-server@test --no-pager | tail -30 - sudo systemctl is-active mqproxy-server@test - sudo systemctl stop mqproxy-server@test - sudo dpkg -r mqproxy - - - name: Stage artifact - run: cp build-pkg/mqproxy_*.deb . - - - name: Upload artifact - uses: actions/upload-artifact@v7 - with: - name: release-${{ matrix.arch }} - path: mqproxy_*.deb + uses: ./.github/workflows/ci.yml publish-release: + permissions: + contents: write if: startsWith(github.ref, 'refs/tags/v') needs: [build-release] runs-on: ubuntu-latest @@ -180,7 +32,7 @@ jobs: run: | cd artifacts shopt -s nullglob - sha256sum mqproxy_*.deb > SHA256SUMS + sha256sum mqproxy_*.deb mqproxy_*.tar.gz > SHA256SUMS cat SHA256SUMS - name: Create GitHub Release @@ -189,7 +41,7 @@ jobs: run: | shopt -s nullglob VERSION="${GITHUB_REF_NAME#v}" - DEBS=(artifacts/mqproxy_*.deb) + ARTIFACTS=(artifacts/mqproxy_*.deb artifacts/mqproxy_*.tar.gz) PRERELEASE_FLAG="" if [[ "$GITHUB_REF_NAME" == *-* ]]; then PRERELEASE_FLAG="--prerelease" @@ -198,6 +50,6 @@ jobs: --title "mqproxy v${VERSION}" \ --notes "" \ $PRERELEASE_FLAG \ - "${DEBS[@]}" \ + "${ARTIFACTS[@]}" \ artifacts/SHA256SUMS \ LICENSE diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9655c602..8bfd4a76 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,6 +15,7 @@ jobs: - uses: actions/checkout@v6 with: submodules: recursive + - run: python3 scripts/ci_benchmarks/test_gateway_summary.py - run: cargo fmt --check - run: cargo clippy --workspace --all-targets -- -D warnings @@ -25,7 +26,8 @@ jobs: - uses: actions/checkout@v6 with: submodules: recursive - - run: cargo test --workspace + - run: cargo test --workspace --locked + - run: cargo test -p mqproxy --examples --locked build-matrix: runs-on: ubuntu-latest @@ -64,18 +66,18 @@ jobs: run: | for pair in varint:varint auth_req:wire_auth_req auth_resp:wire_auth_resp connect_tcp_req:wire_connect_tcp_req connect_tcp_resp:wire_connect_tcp_resp udp_session_open:wire_udp_session_open udp_session_resp:wire_udp_session_resp udp_msg_hdr:udp_msg_hdr; do t=${pair%%:*}; c=${pair##*:} - mkdir -p crates/mq-wire/fuzz/corpus/$t - cargo +nightly fuzz run --fuzz-dir crates/mq-wire/fuzz $t crates/mq-wire/fuzz/corpus/$t fuzz/corpus/$c -- -runs=0 + mkdir -p "crates/mq-wire/fuzz/corpus/$t" + cargo +nightly fuzz run --fuzz-dir crates/mq-wire/fuzz "$t" "crates/mq-wire/fuzz/corpus/$t" "fuzz/corpus/$c" -- -runs=0 done for pair in socks5:socks5_feed http_connect:http_connect socks5_udp_hdr:socks5_udp_hdr; do t=${pair%%:*}; c=${pair##*:} - mkdir -p crates/mq-proxy/fuzz/corpus/$t - cargo +nightly fuzz run --fuzz-dir crates/mq-proxy/fuzz $t crates/mq-proxy/fuzz/corpus/$t fuzz/corpus/$c -- -runs=0 + mkdir -p "crates/mq-proxy/fuzz/corpus/$t" + cargo +nightly fuzz run --fuzz-dir crates/mq-proxy/fuzz "$t" "crates/mq-proxy/fuzz/corpus/$t" "fuzz/corpus/$c" -- -runs=0 done for pair in h1_head:mq_http_h1_head parse_target:mq_http_parse_target format_req:mq_http_format_req; do t=${pair%%:*}; c=${pair##*:} - mkdir -p crates/mq-http/fuzz/corpus/$t - cargo +nightly fuzz run --fuzz-dir crates/mq-http/fuzz $t crates/mq-http/fuzz/corpus/$t fuzz/corpus/$c -- -runs=0 + mkdir -p "crates/mq-http/fuzz/corpus/$t" + cargo +nightly fuzz run --fuzz-dir crates/mq-http/fuzz "$t" "crates/mq-http/fuzz/corpus/$t" "fuzz/corpus/$c" -- -runs=0 done fuzz-explore: @@ -91,18 +93,18 @@ jobs: run: | for pair in varint:varint auth_req:wire_auth_req auth_resp:wire_auth_resp connect_tcp_req:wire_connect_tcp_req connect_tcp_resp:wire_connect_tcp_resp udp_session_open:wire_udp_session_open udp_session_resp:wire_udp_session_resp udp_msg_hdr:udp_msg_hdr; do t=${pair%%:*}; c=${pair##*:} - mkdir -p crates/mq-wire/fuzz/corpus/$t - cargo +nightly fuzz run --fuzz-dir crates/mq-wire/fuzz $t crates/mq-wire/fuzz/corpus/$t fuzz/corpus/$c -- -max_total_time=30 + mkdir -p "crates/mq-wire/fuzz/corpus/$t" + cargo +nightly fuzz run --fuzz-dir crates/mq-wire/fuzz "$t" "crates/mq-wire/fuzz/corpus/$t" "fuzz/corpus/$c" -- -max_total_time=30 done for pair in socks5:socks5_feed http_connect:http_connect socks5_udp_hdr:socks5_udp_hdr; do t=${pair%%:*}; c=${pair##*:} - mkdir -p crates/mq-proxy/fuzz/corpus/$t - cargo +nightly fuzz run --fuzz-dir crates/mq-proxy/fuzz $t crates/mq-proxy/fuzz/corpus/$t fuzz/corpus/$c -- -max_total_time=30 + mkdir -p "crates/mq-proxy/fuzz/corpus/$t" + cargo +nightly fuzz run --fuzz-dir crates/mq-proxy/fuzz "$t" "crates/mq-proxy/fuzz/corpus/$t" "fuzz/corpus/$c" -- -max_total_time=30 done for pair in h1_head:mq_http_h1_head parse_target:mq_http_parse_target format_req:mq_http_format_req; do t=${pair%%:*}; c=${pair##*:} - mkdir -p crates/mq-http/fuzz/corpus/$t - cargo +nightly fuzz run --fuzz-dir crates/mq-http/fuzz $t crates/mq-http/fuzz/corpus/$t fuzz/corpus/$c -- -max_total_time=30 + mkdir -p "crates/mq-http/fuzz/corpus/$t" + cargo +nightly fuzz run --fuzz-dir crates/mq-http/fuzz "$t" "crates/mq-http/fuzz/corpus/$t" "fuzz/corpus/$c" -- -max_total_time=30 done bindings-check: @@ -159,8 +161,7 @@ jobs: - run: cd third_party/xquic && scripts/validate.sh test e2e: - # Black box + interop (spec §8.2, §9.2) with the Release Rust binary, in the - # same NET_ADMIN container as ci.yml's e2e job. A skip (exit 77) fails the step. + # Rust black-box gates. A skip (exit 77) fails the step. runs-on: ubuntu-latest timeout-minutes: 45 container: @@ -179,61 +180,26 @@ jobs: run: | apt-get install -y \ cmake gcc g++ make \ - libevent-dev libcurl4-openssl-dev libnghttp2-dev golang \ + golang \ iproute2 nftables curl python3 openssl sudo \ iperf3 socat procps ca-certificates - name: Install Rust (rust-toolchain.toml picks the version) run: | curl --proto '=https' -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain none echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - - name: Compute xquic SHA - id: xqsha - run: | - sha=$(git -c safe.directory="$GITHUB_WORKSPACE" rev-parse HEAD:third_party/xquic) - echo "sha=$sha" >> "$GITHUB_OUTPUT" - - name: Cache xquic + BoringSSL (build-xquic.sh) - uses: actions/cache@v5 - with: - path: | - third_party/xquic/build - third_party/xquic/third_party/boringssl/build - third_party/xquic/include/xquic/xqc_configure.h - key: xquic-${{ steps.xqsha.outputs.sha }}-build-xquic-sh-v2 - - name: Build the C mqproxy (interop peer) - run: | - scripts/build-xquic.sh - cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" - cmake --build build -j"$(nproc)" --target mqproxy_cli - - run: cargo build --release --locked -p mqproxy - - name: e2e_multipath (Rust both ends) - run: MQPROXY_BIN=$PWD/target/release/mqproxy tests/integration/e2e_multipath.sh - - name: e2e_tproxy (Rust) - run: MQPROXY_BIN=$PWD/target/release/mqproxy tests/integration/e2e_tproxy.sh - - name: e2e_path_recovery (Rust) - run: MQPROXY_BIN=$PWD/target/release/mqproxy tests/integration/e2e_path_recovery.sh - - name: Interop, C client -> Rust server - run: | - MQPROXY_BIN=$PWD/tests/integration/mqproxy-interop-wrapper.sh \ - MQPROXY_CLIENT_BIN=$PWD/build/mqproxy MQPROXY_SERVER_BIN=$PWD/target/release/mqproxy \ - tests/integration/e2e_multipath.sh - - name: Interop, Rust client -> C server - run: | - MQPROXY_BIN=$PWD/tests/integration/mqproxy-interop-wrapper.sh \ - MQPROXY_CLIENT_BIN=$PWD/target/release/mqproxy MQPROXY_SERVER_BIN=$PWD/build/mqproxy \ - tests/integration/e2e_multipath.sh - # Gateway (spec §10.4): cases 1-13; 14 a-c self-skip against a Rust server. - - name: e2e_gateway (Rust both ends) - run: MQPROXY_BIN=$PWD/target/release/mqproxy tests/integration/e2e_gateway.sh - - name: e2e_gateway interop, C client -> Rust server - run: | - MQPROXY_BIN=$PWD/tests/integration/mqproxy-interop-wrapper.sh \ - MQPROXY_CLIENT_BIN=$PWD/build/mqproxy MQPROXY_SERVER_BIN=$PWD/target/release/mqproxy \ - tests/integration/e2e_gateway.sh - - name: e2e_gateway interop, Rust client -> C server + - run: cargo build --release --locked -p mqproxy --bins --examples + - name: Generate MITM test certificates + run: bash tests/certs/generate-mitm.sh + - name: Rust end-to-end gates + env: + MQPROXY_BIN: ${{ github.workspace }}/target/release/mqproxy + UDPSOCKS_BIN: ${{ github.workspace }}/target/release/examples/udpsocks + UDPECHO_BIN: ${{ github.workspace }}/target/release/examples/udp_echo run: | - MQPROXY_BIN=$PWD/tests/integration/mqproxy-interop-wrapper.sh \ - MQPROXY_CLIENT_BIN=$PWD/target/release/mqproxy MQPROXY_SERVER_BIN=$PWD/build/mqproxy \ - tests/integration/e2e_gateway.sh + bash tests/test_cli_help.sh "$MQPROXY_BIN" + for test in multipath tproxy path_recovery udp gateway mitm_smoke mitm_h2; do + bash "tests/integration/e2e_${test}.sh" + done - name: Upload e2e logs on failure if: failure() uses: actions/upload-artifact@v4 @@ -245,8 +211,7 @@ jobs: perf-rust: # The Rust binary in the perf job, under perf.yml's rules (privileged # container, never on PRs). Push to main: ci_bench_tcp_proxy.sh, informational. - # Weekly schedule / manual: unshaped + idle for both binaries (spec §9.2) and - # the gateway and MITM Rust-vs-C benchmarks (SP3 spec §10.6, SP4 spec §11.5). + # Weekly schedule / manual: Rust unshaped, idle, gateway and MITM benchmarks. if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest timeout-minutes: 60 @@ -263,7 +228,7 @@ jobs: run: | apt-get install -y \ cmake gcc g++ make \ - libevent-dev libcurl4-openssl-dev libnghttp2-dev golang \ + golang \ iproute2 nftables curl python3 openssl sudo \ iperf3 socat procps ca-certificates - name: Install Rust (rust-toolchain.toml picks the version) @@ -271,50 +236,22 @@ jobs: curl --proto '=https' -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain none echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - run: cargo build --release --locked -p mqproxy + - name: Generate MITM test certificates + run: bash tests/certs/generate-mitm.sh - name: TCP proxy benchmark (Rust, informational) if: github.event_name == 'push' continue-on-error: true run: bash scripts/ci_benchmarks/ci_bench_tcp_proxy.sh target/release/mqproxy - - name: Compute xquic SHA - if: github.event_name != 'push' - id: xqsha - run: | - sha=$(git -c safe.directory="$GITHUB_WORKSPACE" rev-parse HEAD:third_party/xquic) - echo "sha=$sha" >> "$GITHUB_OUTPUT" - - name: Cache xquic + BoringSSL (build-xquic.sh) - if: github.event_name != 'push' - uses: actions/cache@v5 - with: - path: | - third_party/xquic/build - third_party/xquic/third_party/boringssl/build - third_party/xquic/include/xquic/xqc_configure.h - key: xquic-${{ steps.xqsha.outputs.sha }}-build-xquic-sh-v2 - - name: Build the C mqproxy (baseline) - if: github.event_name != 'push' - run: | - scripts/build-xquic.sh - cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" - cmake --build build -j"$(nproc)" --target mqproxy_cli - - name: Unshaped + idle, C and Rust - if: github.event_name != 'push' - run: | - for b in c:build/mqproxy rust:target/release/mqproxy; do - MQPROXY_BIN=$PWD/${b#*:} BENCH_LABEL=${b%%:*} bash scripts/ci_benchmarks/ci_bench_unshaped.sh - MQPROXY_BIN=$PWD/${b#*:} BENCH_LABEL=${b%%:*} bash scripts/ci_benchmarks/ci_bench_idle.sh - done - # Gateway Rust vs C (SP3 spec §10.6): fails when a throughput ratio < 0.95. - - name: Gateway benchmark, Rust vs C + - name: Unshaped and idle Rust benchmarks if: github.event_name != 'push' run: | - MQPROXY_BIN_C=$PWD/build/mqproxy MQPROXY_BIN_RUST=$PWD/target/release/mqproxy \ - bash scripts/ci_benchmarks/ci_bench_gateway.sh - # MITM Rust vs C (SP4 spec §11.5): fails when a throughput ratio < 0.95. - - name: MITM benchmark, Rust vs C + MQPROXY_BIN=$PWD/target/release/mqproxy BENCH_LABEL=rust bash scripts/ci_benchmarks/ci_bench_unshaped.sh + MQPROXY_BIN=$PWD/target/release/mqproxy BENCH_LABEL=rust bash scripts/ci_benchmarks/ci_bench_idle.sh + - name: Gateway and MITM Rust benchmarks if: github.event_name != 'push' run: | - MQPROXY_BIN_C=$PWD/build/mqproxy MQPROXY_BIN_RUST=$PWD/target/release/mqproxy \ - bash scripts/ci_benchmarks/ci_bench_mitm.sh + bash scripts/ci_benchmarks/ci_bench_gateway.sh target/release/mqproxy + bash scripts/ci_benchmarks/ci_bench_mitm.sh target/release/mqproxy - name: Upload results if: always() uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index f401deb4..fef8fef3 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ third_party/h3-curl/ demo_ticket_store.bin demo_token_store.bin -# MITM test fixtures — regenerated at configure time by CMake (openssl), like the +# MITM test fixtures — generated by tests/certs/generate-mitm.sh, like the # committed test.crt/origin.crt but never committed (they include private keys). tests/certs/mitm-* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1b8fc47..b671acf8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,11 +3,7 @@ repos: rev: v8.21.2 hooks: - id: gitleaks - # clang-format gate so formatting is caught at commit time, not just by the - # CI format-check job. Pinned to v18.x to match the CI runner's apt - # clang-format (ubuntu-latest ships clang-format 18); do NOT - # `pre-commit autoupdate` past v18 or the hook drifts ahead of CI and - # reformat-churns. Uses the repo .clang-format by default. + # The Rust application uses cargo fmt; retain clang-format for xquic FFI glue. - repo: https://github.com/pre-commit/mirrors-clang-format rev: v18.1.8 hooks: diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 173131b4..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,1104 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(mqproxy VERSION 0.0.4 LANGUAGES C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) - -# Warning gate for first-party code. The tree compiles clean under these; not -# -Werror by default so a newer compiler's novel warning never blocks a distro -# build (CI can add -Werror). xquic/BoringSSL are prebuilt + linked, not compiled -# here, so these flags only touch mqproxy's own sources. -add_compile_options(-Wall -Wextra) - -include(cmake/BuildSetup.cmake) -include(GNUInstallDirs) -if(MQPROXY_H3_CURL) - add_library(CURL::libcurl SHARED IMPORTED) - set_target_properties(CURL::libcurl PROPERTIES - IMPORTED_LOCATION "${MQPROXY_H3_CURL}/lib/libcurl.so" - INTERFACE_INCLUDE_DIRECTORIES "${MQPROXY_H3_CURL}/include") - # RPATH: do NOT use a global set(CMAKE_BUILD_RPATH ...) here — this tree sets a - # bare per-target BUILD_RPATH "${XQUIC_BUILD_DIR}" on all 20 executables - # (e.g. :355 test_gw_server, :473 mqproxy_cli), and set_target_properties REPLACES - # (not appends), so a global value would be dropped. USE_LINK_PATH instead ADDS the - # imported .so's directory to each target's RPATH, combining with the per-target value. - set(CMAKE_BUILD_RPATH_USE_LINK_PATH ON) -else() - find_package(CURL REQUIRED) -endif() - -add_library(mqproxy STATIC - src/util/mq_log.c - src/util/mq_buf.c - src/util/mq_backoff.c - src/config/mq_config.c - src/wire/mq_varint.c - src/wire/mq_wire.c - src/wire/mq_udp_msg.c - src/proxy/mq_relay.c - src/proxy/mq_flow.c - src/proxy/mq_framebuf.c - src/proxy/mq_defrag.c - src/proxy/mq_client.c - src/proxy/mq_server.c - src/proxy/mq_udp_session.c - src/ingress/mq_socks5.c - src/ingress/mq_http_connect.c - src/ingress/mq_origdst.c - src/ingress/mq_clienthello.c - src/ingress/mq_tproxy.c - src/ingress/mq_tproxy_setup.c - src/ingress/mq_listener.c - src/ingress/mq_udp_assoc.c - src/gateway/mq_http1.c - src/gateway/mq_gw_headers.c - src/gateway/mq_fetch_listener.c - src/gateway/mq_gw_client.c - src/gateway/mq_gw_fetch_adapter.c - src/gateway/mq_gw_server.c - src/gateway/mq_gw_metrics.c - src/gateway/mq_gw_cache.c - src/gateway/mq_origin_curl.c - src/transport/mq_conn.c - src/transport/mq_h3.c - src/transport/mq_stream.c - src/transport/mq_transport.c - src/runtime/mq_runtime_libevent.c - src/gateway/mq_gw_h2_adapter.c - src/gateway/mq_gw_h2_submit_gwc.c - src/mitm/mq_ignore_hosts.c) -target_include_directories(mqproxy PUBLIC ${NGHTTP2_INCLUDE_DIRS}) -target_link_libraries(mqproxy PUBLIC CURL::libcurl) -target_include_directories(mqproxy PUBLIC src ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - -# Fail-closed MITM gate (Slice 3 Task 3, codex H7): mq_mitm_core.c #includes -# BoringSSL headers (openssl/bytestring.h etc.) that exist only with the static -# archives. Compile it — and its BoringSSL include dir — into the library ONLY -# when the archives are present (MQPROXY_MITM). A no-archive build therefore -# pulls NO MITM source and the whole tree (library + mqproxy_cli) builds/links -# cleanly; `--mitm` then errors at startup via the MQ_MITM_AVAILABLE=0 guard. -if(MQPROXY_MITM) - # mq_mitm_core.c #includes BoringSSL headers; mq_mitm_conn.c (live orchestrator, - # Task 10) references mq_mitm_core types. Both compile into the lib only under - # MQPROXY_MITM. The CLI links the lib, so adding mq_mitm_conn here (not to - # mqproxy_cli) also satisfies the test_mitm_conn target, which links `mqproxy`. - target_sources(mqproxy PRIVATE src/mitm/mq_mitm_core.c src/mitm/mq_mitm_conn.c) - target_include_directories(mqproxy PUBLIC ${MQPROXY_BORINGSSL_INCLUDE_DIR}) -endif() - -enable_testing() -add_executable(test_smoke tests/test_smoke.c) -target_include_directories(test_smoke PRIVATE tests) -add_test(NAME test_smoke COMMAND test_smoke) - -add_executable(test_varint tests/test_varint.c) -target_link_libraries(test_varint mqproxy) -target_include_directories(test_varint PRIVATE tests) -add_test(NAME test_varint COMMAND test_varint) - -add_executable(test_wire tests/test_wire.c) -target_link_libraries(test_wire mqproxy) -target_include_directories(test_wire PRIVATE tests) -add_test(NAME test_wire COMMAND test_wire) - -add_executable(test_udp_msg tests/test_udp_msg.c) -target_link_libraries(test_udp_msg mqproxy) -target_include_directories(test_udp_msg PRIVATE tests) -add_test(NAME test_udp_msg COMMAND test_udp_msg) - -add_executable(test_buf tests/test_buf.c) -target_link_libraries(test_buf mqproxy) -target_include_directories(test_buf PRIVATE tests) -add_test(NAME test_buf COMMAND test_buf) - -add_executable(test_config tests/test_config.c) -target_link_libraries(test_config mqproxy) -target_include_directories(test_config PRIVATE tests) -add_test(NAME test_config COMMAND test_config) - -add_executable(test_relay tests/test_relay.c) -target_link_libraries(test_relay mqproxy) -target_include_directories(test_relay PRIVATE tests) -add_test(NAME test_relay COMMAND test_relay) - -add_executable(test_socks5 tests/test_socks5.c) -target_link_libraries(test_socks5 mqproxy) -target_include_directories(test_socks5 PRIVATE tests) -add_test(NAME test_socks5 COMMAND test_socks5) - -add_executable(test_http_connect tests/test_http_connect.c) -target_link_libraries(test_http_connect mqproxy) -target_include_directories(test_http_connect PRIVATE tests) -add_test(NAME test_http_connect COMMAND test_http_connect) - -add_executable(test_http1 tests/test_http1.c) -target_link_libraries(test_http1 mqproxy) -target_include_directories(test_http1 PRIVATE tests) -add_test(NAME test_http1 COMMAND test_http1) - -add_executable(test_gw_headers tests/test_gw_headers.c) -target_link_libraries(test_gw_headers mqproxy) -target_include_directories(test_gw_headers PRIVATE tests) -add_test(NAME test_gw_headers COMMAND test_gw_headers) - -add_executable(test_gw_cache tests/test_gw_cache.c) -target_link_libraries(test_gw_cache mqproxy) -target_include_directories(test_gw_cache PRIVATE tests src) -add_test(NAME test_gw_cache COMMAND test_gw_cache) - -add_executable(test_defrag tests/test_defrag.c) -target_link_libraries(test_defrag mqproxy) -target_include_directories(test_defrag PRIVATE tests) -add_test(NAME test_defrag COMMAND test_defrag) - -add_executable(test_backoff tests/test_backoff.c) -target_link_libraries(test_backoff mqproxy) -target_include_directories(test_backoff PRIVATE tests) -add_test(NAME test_backoff COMMAND test_backoff) - -add_executable(test_gw_req_format tests/test_gw_req_format.c src/gateway/mq_gw_metrics.c) -target_include_directories(test_gw_req_format PRIVATE tests src) # tests → mqtest.h, src → gateway/mq_gw_metrics.h -add_test(NAME test_gw_req_format COMMAND test_gw_req_format) - -add_executable(test_origdst tests/test_origdst.c) -target_link_libraries(test_origdst mqproxy) -target_include_directories(test_origdst PRIVATE tests) -add_test(NAME test_origdst COMMAND test_origdst) - -add_executable(test_clienthello tests/test_clienthello.c) -target_link_libraries(test_clienthello mqproxy) -target_include_directories(test_clienthello PRIVATE tests src) -add_test(NAME test_clienthello COMMAND test_clienthello) - -add_executable(test_ignore_hosts tests/test_ignore_hosts.c) -target_link_libraries(test_ignore_hosts mqproxy) -target_include_directories(test_ignore_hosts PRIVATE tests src) -add_test(NAME test_ignore_hosts COMMAND test_ignore_hosts) - -# nghttp2 link probe (Slice 3 Task 4): permanent low-cost guard that the nghttp2 -# dependency stays compiled + linked + runnable in BOTH build modes. Links the -# static archive under MQPROXY_STATIC_XQUIC (self-contained packaging binary), -# else the system shared lib — same selection the live H2 MITM adapter uses. No -# xquic/BoringSSL needed (nghttp2 is a leaf), so it lives in the unconditional -# section. Calls nghttp2_version() and prints the runtime version string. -add_executable(nghttp2_link_probe tests/nghttp2_link_probe.c) -target_include_directories(nghttp2_link_probe PRIVATE ${NGHTTP2_INCLUDE_DIRS}) -if(MQPROXY_STATIC_XQUIC AND NGHTTP2_STATIC_LIB) - # Exercise the exact static archive the packaging cli links. (When the .a is - # absent the cli static branch already FATAL_ERRORs; the probe falls back to - # the shared lib here only to stay buildable for non-cli static configs.) - target_link_libraries(nghttp2_link_probe "${NGHTTP2_STATIC_LIB}") -else() - target_link_libraries(nghttp2_link_probe ${NGHTTP2_LIBRARIES}) -endif() -add_test(NAME nghttp2_link_probe COMMAND nghttp2_link_probe) - -# nghttp2 H2 server adapter unit suite (Slice 3 Task 5). Pure H2 framing — drives -# a real nghttp2 CLIENT through an in-test memory pipe against the adapter SERVER -# session; no xquic/BoringSSL needed (the adapter .o is pulled out of the base -# mqproxy lib, which references only nghttp2). Lives in the unconditional section. -add_executable(test_gw_h2_adapter tests/test_gw_h2_adapter.c) -target_link_libraries(test_gw_h2_adapter mqproxy ${NGHTTP2_LIBRARIES}) -target_include_directories(test_gw_h2_adapter PRIVATE tests src ${NGHTTP2_INCLUDE_DIRS}) -add_test(NAME test_gw_h2_adapter COMMAND test_gw_h2_adapter) - -# ---- Integration tests (require xquic + libevent) ---- -if(XQUIC_BUILD_DIR AND EVENT_CORE_LIB) - # ---- Phase 7 Slice 2: MITM crypto core unit suite ---- - # Needs the BoringSSL static archives libxquic was built against (SSL_*/X509_*). - # The mq_boringssl INTERFACE target is defined in cmake/BuildSetup.cmake - # (hoisted out of this test-only block so mqproxy_cli can also consume it). - # GATE: MQPROXY_MITM, NOT MQ_HAVE_BORINGSSL_ARCHIVES. These targets reference - # mq_mitm_core_* symbols, which are compiled into the mqproxy lib only under - # MQPROXY_MITM (see ~line 69). Gating them on the raw-archive predicate while - # the source compilation is gated on MQPROXY_MITM would let a hypothetical - # MQPROXY_MITM=OFF + archives-present build still emit these targets and then - # fail to LINK with undefined mq_mitm_core_* — a confusing failure far from the - # cause. MQPROXY_MITM is an alias of MQ_HAVE_BORINGSSL_ARCHIVES today, so this - # is behavior-preserving; it makes the documented decoupling actually safe. - if(MQPROXY_MITM) - add_executable(test_mitm_core tests/test_mitm_core.c) - target_link_libraries(test_mitm_core mqproxy mq_boringssl) - target_include_directories(test_mitm_core PRIVATE tests src) - add_test(NAME test_mitm_core COMMAND test_mitm_core) - - # ---- Task 10: live MITM conn orchestrator decision-seam unit ---- - # Exercises the drain→decide routing (ignore-hosts hit→opaque vs miss→MITM) - # via the socketless decision seam. Links mqproxy + mq_boringssl (touches - # mq_mitm_core types via the orchestrator) + libevent (the orchestrator - # creates events; the seam itself uses none). - # NOTE: as of Task 11 mq_mitm_conn.c wires the live MITM path through the - # production submit wrapper (mq_gw_h2_adapter → mq_gw_client_* → xquic), so - # the orchestrator object now references nghttp2 + the full xquic/BoringSSL - # stack even on the decision-seam path. These unit targets therefore link the - # whole stack (xquic + nghttp2 + mq_boringssl + libevent) like the other - # integration tests, plus the xquic runtime rpath. - add_executable(test_mitm_conn tests/test_mitm_conn.c) - target_link_libraries(test_mitm_conn mqproxy xquic mq_boringssl - ${NGHTTP2_LIBRARIES} ${EVENT_CORE_LIB} ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_mitm_conn PRIVATE tests src - ${NGHTTP2_INCLUDE_DIRS}) - set_target_properties(test_mitm_conn PROPERTIES BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_mitm_conn COMMAND test_mitm_conn) - - # ---- Task 11: live MITM conn registry + ordered teardown + re-entrancy ---- - # Builds real nghttp2 adapters holding in-flight streams, registers them in - # live MITM conns via the make_live_for_test seam, then mq_mitm_ctx_free — - # asserting each in-flight stream's req_aborted fires exactly once and the - # teardown is ASan/LSan-clean (no UAF/leak/double-free). Links the full stack - # (the orchestrator references SSL_*/BIO_* + the gwc submit wrapper → xquic). - add_executable(test_mitm_teardown tests/test_mitm_teardown.c) - target_link_libraries(test_mitm_teardown mqproxy xquic mq_boringssl - ${NGHTTP2_LIBRARIES} ${EVENT_CORE_LIB} ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_mitm_teardown PRIVATE tests src - ${NGHTTP2_INCLUDE_DIRS}) - set_target_properties(test_mitm_teardown PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_mitm_teardown COMMAND test_mitm_teardown) - - # ---- Task 8: openssl s_client cross-implementation smoke ---- - # The single-shot helper terminates a real loopback TLS handshake via - # mq_mitm_core; the shell driver runs `openssl s_client` against it and - # asserts ALPN=h2 + chain-verify. - add_executable(mitm_smoke_server tests/integration/mitm_smoke_server.c) - target_link_libraries(mitm_smoke_server mqproxy mq_boringssl) - target_include_directories(mitm_smoke_server PRIVATE src) - # NOTE: add_test for e2e_mitm_smoke is registered below, after the MITM CA - # fixture paths (MQ_MITM_CA_*) are defined — same pattern as the - # test_mitm_core compile_definitions block. - else() - message(STATUS "BoringSSL archives absent — test_mitm_core not built " - "(run scripts/build-xquic.sh).") - endif() - - add_executable(test_engine_boot tests/integration/test_engine_boot.c) - target_link_libraries(test_engine_boot - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_engine_boot PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - # libxquic.so is loaded at runtime from the xquic build dir; embed an - # rpath so ctest runs without LD_LIBRARY_PATH. - set_target_properties(test_engine_boot PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_engine_boot COMMAND test_engine_boot) - - add_executable(test_sched tests/test_sched.c) - target_link_libraries(test_sched - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_sched PRIVATE tests src ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - set_target_properties(test_sched PROPERTIES BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_sched COMMAND test_sched) - - add_executable(test_metrics_format tests/test_metrics_format.c) - target_link_libraries(test_metrics_format - mqproxy xquic ${EVENT_CORE_LIB} ${EVENT_EXTRA_LIB} pthread dl m stdc++) - target_include_directories(test_metrics_format PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - set_target_properties(test_metrics_format PROPERTIES BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_metrics_format COMMAND test_metrics_format) - - # Connection-count cap (--max-conns) counter API — pre-auth DoS. Pure counter - # logic, no handshake. - add_executable(test_max_conns tests/test_max_conns.c) - target_link_libraries(test_max_conns - mqproxy xquic ${EVENT_CORE_LIB} ${EVENT_EXTRA_LIB} pthread dl m stdc++) - target_include_directories(test_max_conns PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - set_target_properties(test_max_conns PROPERTIES BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_max_conns COMMAND test_max_conns) - - # mq_h3_req_get_stats + mq_h3_req_stream_id NULL-guard unit tests. - # Links mq_h3.o -> xqc_* so it lives in the xquic-gated block. - add_executable(test_h3_req_stats tests/test_h3_req_stats.c) - target_link_libraries(test_h3_req_stats - mqproxy xquic ${EVENT_CORE_LIB} ${EVENT_EXTRA_LIB} pthread dl m stdc++) - target_include_directories(test_h3_req_stats PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - set_target_properties(test_h3_req_stats PROPERTIES BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_h3_req_stats COMMAND test_h3_req_stats) - - add_executable(test_path_bind tests/integration/test_path_bind.c) - target_link_libraries(test_path_bind - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_path_bind PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - set_target_properties(test_path_bind PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_path_bind COMMAND test_path_bind) - - # Self-signed cert/key for the loopback handshake test. Committed under - # tests/certs (test-only); regenerated at configure time if missing. - set(MQ_CERT_DIR "${CMAKE_SOURCE_DIR}/tests/certs") - set(MQ_CERT_FILE "${MQ_CERT_DIR}/test.crt") - set(MQ_KEY_FILE "${MQ_CERT_DIR}/test.key") - if(NOT EXISTS "${MQ_CERT_FILE}" OR NOT EXISTS "${MQ_KEY_FILE}") - find_program(OPENSSL_BIN openssl REQUIRED) - file(MAKE_DIRECTORY "${MQ_CERT_DIR}") - message(STATUS "Generating self-signed test cert in ${MQ_CERT_DIR}") - execute_process( - COMMAND ${OPENSSL_BIN} req -x509 -newkey rsa:2048 -nodes - -keyout "${MQ_KEY_FILE}" -out "${MQ_CERT_FILE}" - -days 3650 -subj "/CN=mqproxy-test" - RESULT_VARIABLE _cert_rc - OUTPUT_QUIET ERROR_QUIET) - if(NOT _cert_rc EQUAL 0) - message(FATAL_ERROR "openssl failed to generate test cert") - endif() - endif() - - # Origin TLS material for the gateway e2e (e2e_gateway.sh): a test CA - # (origin-ca.crt, CA:TRUE) and a leaf (origin.crt/origin.key) signed by it with - # basicConstraints=CA:FALSE, extendedKeyUsage=serverAuth and - # subjectAltName=IP:127.0.0.1,DNS:localhost. The CA/leaf split is required: - # rustls-webpki rejects a CA:TRUE certificate used as an end entity. The - # gateway server verifies the origin against the CA (--origin-ca; we NEVER - # disable verification). The DNS:localhost SAN lets the gated h3 sub-case - # target https://localhost (curl sends SNI for a hostname but NOT for an IP - # literal, and xquic's demo_server REQUIRES SNI). Test-only; all four files are - # COMMITTED (like test.key) — a missing key would regenerate on every configure - # and dirty the tree. Regenerated when ANY of the four is missing. - set(MQ_ORIGIN_CA_FILE "${MQ_CERT_DIR}/origin-ca.crt") - set(MQ_ORIGIN_CA_KEY_FILE "${MQ_CERT_DIR}/origin-ca.key") - set(MQ_ORIGIN_CERT_FILE "${MQ_CERT_DIR}/origin.crt") - set(MQ_ORIGIN_KEY_FILE "${MQ_CERT_DIR}/origin.key") - if(NOT EXISTS "${MQ_ORIGIN_CA_FILE}" OR NOT EXISTS "${MQ_ORIGIN_CA_KEY_FILE}" - OR NOT EXISTS "${MQ_ORIGIN_CERT_FILE}" OR NOT EXISTS "${MQ_ORIGIN_KEY_FILE}") - find_program(OPENSSL_BIN openssl REQUIRED) - file(MAKE_DIRECTORY "${MQ_CERT_DIR}") - message(STATUS "Generating origin CA + leaf (SAN=IP:127.0.0.1,DNS:localhost) in ${MQ_CERT_DIR}") - # CSR and extension file live in the build tree, not in tests/certs. - set(_origin_csr "${CMAKE_BINARY_DIR}/origin.csr") - set(_origin_ext "${CMAKE_BINARY_DIR}/origin.ext") - file(WRITE "${_origin_ext}" - "basicConstraints=critical,CA:FALSE\n" - "extendedKeyUsage=serverAuth\n" - "subjectAltName=IP:127.0.0.1,DNS:localhost\n") - execute_process( - COMMAND ${OPENSSL_BIN} req -x509 -newkey rsa:2048 -nodes - -keyout "${MQ_ORIGIN_CA_KEY_FILE}" -out "${MQ_ORIGIN_CA_FILE}" - -days 3650 -subj "/CN=origin-ca" - -addext "basicConstraints=critical,CA:TRUE" - RESULT_VARIABLE _origin_ca_rc - OUTPUT_QUIET ERROR_QUIET) - execute_process( - COMMAND ${OPENSSL_BIN} req -new -newkey rsa:2048 -nodes - -keyout "${MQ_ORIGIN_KEY_FILE}" -out "${_origin_csr}" - -subj "/CN=mqproxy-origin" - RESULT_VARIABLE _origin_csr_rc - OUTPUT_QUIET ERROR_QUIET) - execute_process( - COMMAND ${OPENSSL_BIN} x509 -req -in "${_origin_csr}" - -CA "${MQ_ORIGIN_CA_FILE}" -CAkey "${MQ_ORIGIN_CA_KEY_FILE}" - -set_serial 2 -days 3650 -extfile "${_origin_ext}" - -out "${MQ_ORIGIN_CERT_FILE}" - RESULT_VARIABLE _origin_cert_rc - OUTPUT_QUIET ERROR_QUIET) - if(NOT _origin_ca_rc EQUAL 0 OR NOT _origin_csr_rc EQUAL 0 OR NOT _origin_cert_rc EQUAL 0) - message(FATAL_ERROR "openssl failed to generate origin CA/leaf test certs") - endif() - endif() - - # MITM CA + negative fixtures (test-only; regenerated if missing). - # NOTE (rev2/C1): do NOT reuse tests/certs/test.crt as the non-CA negative — - # it is actually CA:TRUE. We mint a dedicated CA:FALSE leaf below. - set(MQ_MITM_CA_CRT "${MQ_CERT_DIR}/mitm-ca.crt") - set(MQ_MITM_CA_KEY "${MQ_CERT_DIR}/mitm-ca.key") - set(MQ_MITM_CA_ENC_KEY "${MQ_CERT_DIR}/mitm-ca.enc.key") # encrypted (must be rejected) - set(MQ_MITM_LEAF_CRT "${MQ_CERT_DIR}/mitm-leaf.crt") # CA:FALSE → non-CA negative - set(MQ_MITM_LEAF_KEY "${MQ_CERT_DIR}/mitm-leaf.key") # P-256, ≠ CA key → mismatch negative - set(MQ_MITM_V1_CA_CRT "${MQ_CERT_DIR}/mitm-v1ca.crt") # X509v1, NO extensions → must be rejected (no explicit basicConstraints) - if(NOT EXISTS "${MQ_MITM_CA_CRT}" OR NOT EXISTS "${MQ_MITM_CA_KEY}" - OR NOT EXISTS "${MQ_MITM_CA_ENC_KEY}" - OR NOT EXISTS "${MQ_MITM_LEAF_CRT}" OR NOT EXISTS "${MQ_MITM_LEAF_KEY}" - OR NOT EXISTS "${MQ_MITM_V1_CA_CRT}") - find_program(OPENSSL_BIN openssl REQUIRED) - file(MAKE_DIRECTORY "${MQ_CERT_DIR}") - message(STATUS "Generating MITM CA/leaf fixtures in ${MQ_CERT_DIR}") - # P-256 CA with basicConstraints CA:TRUE + keyUsage keyCertSign. The key is - # PKCS#8 ("BEGIN PRIVATE KEY"): the Rust MITM loader accepts only PKCS#8 - # (ecparam -genkey writes SEC1), and the C loader accepts both. - execute_process( - COMMAND ${OPENSSL_BIN} genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 - -out "${MQ_MITM_CA_KEY}" - RESULT_VARIABLE _rc1 OUTPUT_QUIET ERROR_QUIET) - execute_process( - COMMAND ${OPENSSL_BIN} req -x509 -new -key "${MQ_MITM_CA_KEY}" - -out "${MQ_MITM_CA_CRT}" -days 3650 -subj "/CN=mqproxy-mitm-test-ca" - -addext "basicConstraints=critical,CA:TRUE" - -addext "keyUsage=critical,keyCertSign,cRLSign" - RESULT_VARIABLE _rc2 OUTPUT_QUIET ERROR_QUIET) - # Encrypted copy of the CA key (passphrase "x") — create() must reject it. - execute_process( - COMMAND ${OPENSSL_BIN} ec -in "${MQ_MITM_CA_KEY}" -aes256 -passout pass:x - -out "${MQ_MITM_CA_ENC_KEY}" - RESULT_VARIABLE _rc3 OUTPUT_QUIET ERROR_QUIET) - # P-256 leaf with CA:FALSE (matched key) — the ONLY rejection reason is CA:FALSE. - execute_process( - COMMAND ${OPENSSL_BIN} ecparam -name prime256v1 -genkey -noout -out "${MQ_MITM_LEAF_KEY}" - RESULT_VARIABLE _rc4 OUTPUT_QUIET ERROR_QUIET) - execute_process( - COMMAND ${OPENSSL_BIN} req -x509 -new -key "${MQ_MITM_LEAF_KEY}" - -out "${MQ_MITM_LEAF_CRT}" -days 3650 -subj "/CN=mqproxy-mitm-test-leaf" - -addext "basicConstraints=critical,CA:FALSE" - RESULT_VARIABLE _rc5 OUTPUT_QUIET ERROR_QUIET) - # X509v1 self-signed cert with NO extensions (CSR -> x509 -req with no - # -extfile keeps it v1). X509_check_ca() treats a v1 cert as CA-eligible, - # so this is the negative that proves the explicit-basicConstraints gate: - # it has no BCONS extension and MUST be rejected. Signed by the CA key so - # the ONLY rejection reason is the missing basicConstraints. - execute_process( - COMMAND ${OPENSSL_BIN} req -new -key "${MQ_MITM_CA_KEY}" - -subj "/CN=mqproxy-mitm-test-v1ca" -out "${MQ_CERT_DIR}/mitm-v1ca.csr" - RESULT_VARIABLE _rc6 OUTPUT_QUIET ERROR_QUIET) - execute_process( - COMMAND ${OPENSSL_BIN} x509 -req -in "${MQ_CERT_DIR}/mitm-v1ca.csr" - -signkey "${MQ_MITM_CA_KEY}" -days 3650 -out "${MQ_MITM_V1_CA_CRT}" - RESULT_VARIABLE _rc7 OUTPUT_QUIET ERROR_QUIET) - if(NOT _rc1 EQUAL 0 OR NOT _rc2 EQUAL 0 OR NOT _rc3 EQUAL 0 - OR NOT _rc4 EQUAL 0 OR NOT _rc5 EQUAL 0 - OR NOT _rc6 EQUAL 0 OR NOT _rc7 EQUAL 0) - message(FATAL_ERROR "openssl failed generating MITM CA/leaf fixtures") - endif() - endif() - - # Pass the fixture paths to the MITM unit suite (target created above, gated on - # MQPROXY_MITM). The fixtures are defined here (after MQ_CERT_DIR), so we attach - # the compile defs here rather than at the target block. Gate MUST match the - # target-definition block above (MQPROXY_MITM) so the targets and their tests - # appear/disappear together. - if(MQPROXY_MITM) - target_compile_definitions(test_mitm_core PRIVATE - MITM_CA_CRT="${MQ_MITM_CA_CRT}" - MITM_CA_KEY="${MQ_MITM_CA_KEY}" - MITM_CA_ENC_KEY="${MQ_MITM_CA_ENC_KEY}" - MITM_LEAF_CRT="${MQ_MITM_LEAF_CRT}" # CA:FALSE matched pair → non-CA negative - MITM_LEAF_KEY="${MQ_MITM_LEAF_KEY}" # P-256 ≠ CA key → mismatch negative - MITM_V1_CA_CRT="${MQ_MITM_V1_CA_CRT}") # X509v1 no-extensions → missing-basicConstraints negative - - # Task 8 smoke (mitm_smoke_server target created above): register here so - # the MQ_MITM_CA_* fixture paths are populated. SKIP (77) iff openssl absent. - add_test(NAME e2e_mitm_smoke - COMMAND ${CMAKE_COMMAND} -E env - MITM_SERVER_BIN=$ - MITM_CA_CRT=${MQ_MITM_CA_CRT} MITM_CA_KEY=${MQ_MITM_CA_KEY} - bash ${CMAKE_SOURCE_DIR}/tests/integration/e2e_mitm_smoke.sh) - set_tests_properties(e2e_mitm_smoke PROPERTIES SKIP_RETURN_CODE 77) - endif() - - add_executable(test_conn_handshake tests/integration/test_conn_handshake.c) - target_link_libraries(test_conn_handshake - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_conn_handshake PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_conn_handshake PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_conn_handshake PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_conn_handshake COMMAND test_conn_handshake) - - add_executable(test_client_server tests/integration/test_client_server.c) - target_link_libraries(test_client_server - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_client_server PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_client_server PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_client_server PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_client_server COMMAND test_client_server) - - # Phase 5c Task 3: mq_server_active_conn lifecycle (set on accept, cleared on - # close). Real client+server on a shared base, so it needs the test cert. - add_executable(test_metrics_active_conn tests/integration/test_metrics_active_conn.c) - target_link_libraries(test_metrics_active_conn - mqproxy xquic ${EVENT_CORE_LIB} ${EVENT_EXTRA_LIB} pthread dl m stdc++) - target_include_directories(test_metrics_active_conn PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_metrics_active_conn PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_metrics_active_conn PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_metrics_active_conn COMMAND test_metrics_active_conn) - - # --max-conns engine-wide connection cap: falsifiable real client+server proof - # (A admitted, B refused, A closed→slot frees, C re-admitted). Needs the cert. - add_executable(test_max_conns_e2e tests/integration/test_max_conns_e2e.c) - target_link_libraries(test_max_conns_e2e - mqproxy xquic ${EVENT_CORE_LIB} ${EVENT_EXTRA_LIB} pthread dl m stdc++) - target_include_directories(test_max_conns_e2e PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_max_conns_e2e PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_max_conns_e2e PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_max_conns_e2e COMMAND test_max_conns_e2e) - - # UDP relay server-side full-path integration (Task 5.3): real client+server - # over loopback UDP + a local UDP echo socket; the test drives 0x02 streams - # and DATAGRAM frames directly on the client conn (no client UDP ingress yet). - add_executable(test_udp_relay tests/integration/test_udp_relay.c) - target_link_libraries(test_udp_relay - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_udp_relay PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_udp_relay PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_udp_relay PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_udp_relay COMMAND test_udp_relay) - - # Client reconnect + keepalive integration (Phase 5b, Task 4.3): real - # client+server over loopback with RECONNECT ENABLED; drops the tunnel conn - # via mq_conn_close and proves reconnect/re-auth + queue carry-over + UDP - # rebind + free-while-reconnecting (ASan) + keepalive hold, end-to-end. - add_executable(test_client_reconnect tests/integration/test_client_reconnect.c) - target_link_libraries(test_client_reconnect - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_client_reconnect PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_client_reconnect PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_client_reconnect PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_client_reconnect COMMAND test_client_reconnect) - - add_executable(test_socks5_listener tests/integration/test_socks5_listener.c) - target_link_libraries(test_socks5_listener - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_socks5_listener PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - set_target_properties(test_socks5_listener PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_socks5_listener COMMAND test_socks5_listener) - - # Fetch-API listener (Task 2.2) + gateway client fetch→H3 bridge (Task 3.2). - # The listener cases drive the loopback HTTP/1.1 accept loop with fake gateway - # callbacks over real sockets; the gateway-client cases stand up real - # client+server transports + runtimes + mq_h3 over loopback UDP, so this test - # now needs the cert/key defines (mirrors test_e2e_single_path). - add_executable(test_gw_client tests/integration/test_gw_client.c) - target_link_libraries(test_gw_client - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_gw_client PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_gw_client PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_gw_client PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_gw_client COMMAND test_gw_client) - - # Gateway client reconnect (Task 5.2, Phase 5b): in-process proof that the - # gw_client reconnect path re-establishes the H3 tunnel on loss and a fresh - # fetch completes end-to-end. Same real client+server H3-over-loopback fixture - # as the gateway-client cases in test_gw_client, so it needs the cert/key - # defines too. - add_executable(test_gw_reconnect tests/integration/test_gw_reconnect.c) - target_link_libraries(test_gw_reconnect - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_gw_reconnect PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_gw_reconnect PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_gw_reconnect PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_gw_reconnect COMMAND test_gw_reconnect) - - # Neutral intake boundary relay test (Task 5, Phase 7 MITM Slice 0+1): - # drives mq_gw_client_prevalidate / mq_gw_client_req_begin / req_body / - # req_body_done / req_aborted / req_drained directly with a fake - # mq_gw_sink_ops_t (no fetch adapter / fetch listener), locking the - # boundary a future H2/H3 MITM adapter will also rely on. - add_executable(test_gw_relay tests/integration/test_gw_relay.c) - target_link_libraries(test_gw_relay - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_gw_relay PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_gw_relay PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_gw_relay PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_gw_relay COMMAND test_gw_relay) - - # mq_origin_curl (Task 4.2): the libcurl-multi origin client on libevent. - # Drives the mq_origin_* API against an in-process evhttp origin over plain - # loopback HTTP/1.1. evhttp lives in libevent_extra (EVENT_EXTRA_LIB); curl - # propagates from mqproxy's PUBLIC CURL::libcurl link. Task 4.3 extends this - # file with the gateway-server replay cases. - add_executable(test_gw_server tests/integration/test_gw_server.c) - target_link_libraries(test_gw_server - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_gw_server PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - # Task 4.3 extends this test with gw_server cases that stand up a real H3 - # client+server over loopback UDP, so it needs the cert/key defines. - target_compile_definitions(test_gw_server PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_gw_server PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_gw_server COMMAND test_gw_server) - - add_executable(test_e2e_single_path tests/integration/test_e2e_single_path.c) - target_link_libraries(test_e2e_single_path - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_e2e_single_path PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_e2e_single_path PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_e2e_single_path PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_e2e_single_path COMMAND test_e2e_single_path) - - add_executable(test_transport_fabric tests/integration/test_transport_fabric.c) - target_link_libraries(test_transport_fabric - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_transport_fabric PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_transport_fabric PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_transport_fabric PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_transport_fabric COMMAND test_transport_fabric) - - add_executable(test_h3_fabric tests/integration/test_h3_fabric.c) - target_link_libraries(test_h3_fabric - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_h3_fabric PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_h3_fabric PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_h3_fabric PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_h3_fabric COMMAND test_h3_fabric) - - add_executable(test_two_paths tests/integration/test_two_paths.c) - target_link_libraries(test_two_paths - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_two_paths PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_two_paths PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_two_paths PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_two_paths COMMAND test_two_paths) - - add_executable(test_qlog_blocked tests/integration/test_qlog_blocked.c) - target_link_libraries(test_qlog_blocked - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_qlog_blocked PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_qlog_blocked PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_qlog_blocked PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_qlog_blocked COMMAND test_qlog_blocked) - - add_executable(test_datagram_fabric tests/integration/test_datagram_fabric.c) - target_link_libraries(test_datagram_fabric - mqproxy - xquic - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(test_datagram_fabric PRIVATE tests src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - target_compile_definitions(test_datagram_fabric PRIVATE - TEST_CERT_FILE="${MQ_CERT_FILE}" - TEST_KEY_FILE="${MQ_KEY_FILE}") - set_target_properties(test_datagram_fabric PROPERTIES - BUILD_RPATH "${XQUIC_BUILD_DIR}") - add_test(NAME test_datagram_fabric COMMAND test_datagram_fabric) - - # ---- mqproxy CLI (client/server binaries) ---- - # The static library target is also named `mqproxy`; give the executable a - # distinct target name but emit it as the `mqproxy` binary. - add_executable(mqproxy_cli cli/main.c) - set_target_properties(mqproxy_cli PROPERTIES OUTPUT_NAME mqproxy) - # Surface project(VERSION) into the binary so `mqproxy --version` self-identifies. - target_compile_definitions(mqproxy_cli PRIVATE MQPROXY_VERSION="${PROJECT_VERSION}") - if(MQPROXY_STATIC_XQUIC) - # Self-contained binary for packaging: statically link xquic + the BoringSSL - # it was built against (libxquic-static.a leaves ~118 BoringSSL SSL_*/EVP_AEAD_*/ - # CRYPTO_BUFFER_* symbols undefined; system OpenSSL does not export these QUIC - # APIs, so they must resolve against BoringSSL's static archives). No BUILD_RPATH: - # nothing non-standard is loaded at runtime (the residual libssl.so.3 in ldd is - # libcurl's, not xquic's). - if(NOT TARGET xquic-static) - message(FATAL_ERROR - "MQPROXY_STATIC_XQUIC=ON but xquic-static was not imported " - "(no libxquic-static.a at ${XQUIC_BUILD_DIR}).") - endif() - set(_bssl_ssl "${MQPROXY_BORINGSSL_DIR}/libssl.a") - set(_bssl_crypto "${MQPROXY_BORINGSSL_DIR}/libcrypto.a") - if(NOT EXISTS "${_bssl_ssl}" OR NOT EXISTS "${_bssl_crypto}") - message(FATAL_ERROR - "MQPROXY_STATIC_XQUIC=ON but BoringSSL static archives not found at " - "${MQPROXY_BORINGSSL_DIR} (expected libssl.a + libcrypto.a). Build xquic " - "via scripts/build-xquic.sh or set -DMQPROXY_BORINGSSL_DIR=.") - endif() - # nghttp2 static archive (Slice 3 Task 4): the live H2 MITM adapter links it. - # nghttp2 is a leaf (no BoringSSL/xquic symbols), so link order is unconstrained - # and no symbol isolation is needed. Require the .a in this packaging build; - # mirror the BoringSSL archive-missing gate above with an actionable message. - if(NOT NGHTTP2_STATIC_LIB) - message(FATAL_ERROR - "MQPROXY_STATIC_XQUIC=ON but libnghttp2.a was not found (only the " - "shared library is installed). The static build links the H2 MITM " - "adapter against it. Install the nghttp2 static archive (Debian/Ubuntu: " - "libnghttp2-dev ships it as /usr/lib//libnghttp2.a; on some " - "distros it is a separate -static package), or build the dynamic " - "binary (omit -DMQPROXY_STATIC_XQUIC=ON).") - endif() - # Link order: xquic-static references BoringSSL symbols, so the archives follow it. - # NOTE: the raw archives (not mq_boringssl) are linked here because they must - # appear AFTER xquic-static to satisfy its undefined BoringSSL QUIC symbols; - # the MITM core's SSL_*/X509_* resolve against the very same objects. Symbol - # isolation (--exclude-libs) is applied below for both branches. - target_link_libraries(mqproxy_cli - mqproxy - xquic-static - "${_bssl_ssl}" "${_bssl_crypto}" - "${NGHTTP2_STATIC_LIB}" - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(mqproxy_cli PRIVATE ${NGHTTP2_INCLUDE_DIRS}) - else() - # Dynamic build: libxquic.so bakes its BoringSSL in internally, so the exe - # has no BoringSSL objects today. Link mq_boringssl so the MITM core's - # SSL_*/X509_* can resolve once a caller references them (Task 13). When the - # archives are absent, mq_boringssl does not exist and MITM is compiled out - # (MQ_MITM_AVAILABLE=0 below), so nothing references BoringSSL → clean link. - target_link_libraries(mqproxy_cli - mqproxy - xquic - ${NGHTTP2_LIBRARIES} - ${EVENT_CORE_LIB} - ${EVENT_EXTRA_LIB} - pthread dl m stdc++) - target_include_directories(mqproxy_cli PRIVATE ${NGHTTP2_INCLUDE_DIRS}) - if(MQ_HAVE_BORINGSSL_ARCHIVES) - target_link_libraries(mqproxy_cli mq_boringssl) - endif() - set_target_properties(mqproxy_cli PROPERTIES BUILD_RPATH "${XQUIC_BUILD_DIR}") - endif() - - # ---- MITM availability + BoringSSL symbol isolation (Slice 3 Task 3) ------- - # MQ_MITM_AVAILABLE is the fail-closed gate: the live MITM path is compiled in - # ONLY when the BoringSSL archives are present. All MITM references in cli/main.c - # must be wrapped in `#if MQ_MITM_AVAILABLE` (added in Task 13) so a no-archive - # build pulls NO MITM symbols and therefore LINKS — a runtime branch alone would - # fail at link time. When unavailable, `--mitm` errors at startup and exits != 0. - # MQPROXY_MITM (CMake var, set in BuildSetup.cmake) gates whether the live MITM - # path is compiled in; MQ_MITM_AVAILABLE is the matching C compile-definition. - if(MQPROXY_MITM) - target_compile_definitions(mqproxy_cli PRIVATE MQ_MITM_AVAILABLE=1) - # Task 10: src/mitm/mq_mitm_conn.c (live orchestrator) is compiled into the - # mqproxy LIB under MQPROXY_MITM (see ~line 71), which mqproxy_cli links — - # no per-cli target_sources needed. Task 13 wires it into cli/main.c under - # `#if MQ_MITM_AVAILABLE`. - # - # Isolate the BoringSSL the MITM core links against so its SSL_*/X509_*/EVP_* - # cannot interpose libcurl's system OpenSSL (libssl.so) at runtime. Both the - # static branch (raw archives) and the dynamic branch (mq_boringssl) pull - # libssl.a/libcrypto.a by name, so --exclude-libs by archive base-name hides - # all their global symbols (makes them link-local). GNU ld + lld (Linux) only; - # applied to mqproxy_cli ONLY — the test targets MAY export (they link - # mq_boringssl directly without this flag). Verified by ctest - # check_boringssl_isolation. - target_link_options(mqproxy_cli PRIVATE - -Wl,--exclude-libs,libssl.a - -Wl,--exclude-libs,libcrypto.a) - else() - target_compile_definitions(mqproxy_cli PRIVATE MQ_MITM_AVAILABLE=0) - endif() - target_include_directories(mqproxy_cli PRIVATE src - ${XQUIC_INCLUDE_DIR} ${EVENT_INCLUDE_DIR}) - # The installed binary has NO bundled default cert/key: `server` always - # requires explicit --cert/--key (the committed tests/certs/* are public - # test fixtures and must never be a shipped server's default identity). - - # CLI help / smoke test (shell): asserts --help works and unknown - # subcommands fail. Passes the binary path via $. - add_test(NAME test_cli_help - COMMAND ${CMAKE_COMMAND} -E env - sh ${CMAKE_SOURCE_DIR}/tests/test_cli_help.sh $) - - # ---- BoringSSL symbol-isolation gate (Slice 3 Task 3) ---- - # Asserts the BoringSSL the MITM core links against is NOT exported from the - # mqproxy exe (cannot interpose libcurl's system OpenSSL), no undefined SSL_* - # remains, and libcurl still dynamically needs libssl.so. Gated on the archives - # since --exclude-libs / the MITM link only apply then. See the script header - # for why the checks tolerate symbol ABSENCE (static vs dynamic build). - if(MQ_HAVE_BORINGSSL_ARCHIVES) - add_test(NAME check_boringssl_isolation - COMMAND bash ${CMAKE_SOURCE_DIR}/tests/integration/check_boringssl_isolation.sh - $) - endif() - - # ---- 1-B multipath aggregation benchmark (design §9.2) ---- - # Self-contained bash benchmark the USER runs with sudo: it shapes two - # loopback paths with tc/netem, runs a single-path vs two-path bulk transfer - # through the proxy, and asserts >=1.5x aggregation + zero blocked frames in - # the client qlog. It REQUIRES NET_ADMIN (tc); without it the script exits 77 - # so ctest marks it SKIPPED rather than FAILED. The mqproxy binary path and - # the test cert/key are passed via env so the script needs no build knowledge. - add_test(NAME e2e_multipath - COMMAND ${CMAKE_COMMAND} -E env - MQPROXY_BIN=$ - MQPROXY_CERT=${MQ_CERT_FILE} - MQPROXY_KEY=${MQ_KEY_FILE} - bash ${CMAKE_SOURCE_DIR}/tests/integration/e2e_multipath.sh) - set_tests_properties(e2e_multipath PROPERTIES SKIP_RETURN_CODE 77) - - # ---- Phase 2 Task 5.1: full-chain HTTP-gateway e2e ---- - # Drives the whole fetch path against a real python3 TLS origin: 8MB - # download/upload byte-exact, the 4xx/502/504 error surface (with x-mq-error - # / x-mq-origin-protocol assertions), and a NET_ADMIN-gated 2-path - # aggregation smoke. Cases 1-7 run anywhere; case 8 needs tc on lo and is - # skipped (a note) without it. The WHOLE script exits 77 (SKIP) ONLY when the - # python3 TLS origin can't start. The mqproxy binary, the tunnel cert/key, - # and the SAN'd origin cert/key are passed via env. - add_test(NAME e2e_gateway - COMMAND ${CMAKE_COMMAND} -E env - MQPROXY_BIN=$ - MQPROXY_CERT=${MQ_CERT_FILE} - MQPROXY_KEY=${MQ_KEY_FILE} - MQPROXY_ORIGIN_CA=${MQ_ORIGIN_CA_FILE} - MQPROXY_ORIGIN_CERT=${MQ_ORIGIN_CERT_FILE} - MQPROXY_ORIGIN_KEY=${MQ_ORIGIN_KEY_FILE} - bash ${CMAKE_SOURCE_DIR}/tests/integration/e2e_gateway.sh) - set_tests_properties(e2e_gateway PROPERTIES SKIP_RETURN_CODE 77) - - # ---- Phase 7 Slice 0+1: transparent capture + opaque relay e2e ---- - # Drives the transparent-capture (REDIRECT) path end-to-end against a real - # python3 TLS origin on 127.0.0.1:443: - # case 1: TLS opacity proof — curl as nobody through the nft REDIRECT rule - # → tunnel → server relay → origin; curl verifies the origin's cert - # (--cacert origin-ca.crt); a MITM would break cert verification. - # case 2: (NET_ADMIN) 2-path multipath smoke — same capture with tc shaping. - # case 3: teardown assertion — nft table gone after client exit. - # REQUIRES root + CAP_NET_ADMIN (nft + IP_TRANSPARENT + port 443). - # Exits 77 (SKIP) when not root, nft absent, or no CAP_NET_ADMIN in container. - add_test(NAME e2e_tproxy - COMMAND ${CMAKE_COMMAND} -E env - MQPROXY_BIN=$ - MQPROXY_CERT=${MQ_CERT_FILE} - MQPROXY_KEY=${MQ_KEY_FILE} - MQPROXY_ORIGIN_CA=${MQ_ORIGIN_CA_FILE} - MQPROXY_ORIGIN_CERT=${MQ_ORIGIN_CERT_FILE} - MQPROXY_ORIGIN_KEY=${MQ_ORIGIN_KEY_FILE} - bash ${CMAKE_SOURCE_DIR}/tests/integration/e2e_tproxy.sh) - set_tests_properties(e2e_tproxy PROPERTIES SKIP_RETURN_CODE 77) - - # ---- Phase 7 Slice 3 Task 16: NET_ADMIN transparent-MITM H2 e2e ---- - # The capstone for the 🔒 security gate: the ONLY test that drives the LIVE - # mq_mitm_conn orchestrator end-to-end. curl --http2 is transparently captured - # (nft REDIRECT), the client forges a per-SNI leaf signed by the MITM CA, - # terminates TLS, speaks h2 over the tunnel to the gateway server, which - # fetches the real origin via libcurl. Asserts: - # (a) transparent MITM works (h2 200 against the forged leaf trusted via the - # MITM CA; a non-MITM relay would present the origin cert and be rejected), - # (b) cache HIT — a second fetch is served origin-once (origin /__count == 1), - # (c) a cookie-authenticated request reaches the origin WITH its Cookie (§4.5), - # (d) an --ignore-host SNI is spliced OPAQUELY (origin cert verifies; the MITM - # CA is REJECTED — no forged leaf presented for the excluded host), - # (e) concurrent multi-stream fetch succeeds (N streams on one h2 conn). - # REQUIRES root + CAP_NET_ADMIN (nft) + curl(--http2) + openssl + python3 + a - # MITM-capable binary. Exits 77 (SKIP) when any of those is absent (mirrors - # e2e_tproxy). Gated on MQPROXY_MITM so it only registers for a MITM build; the - # MITM CA fixtures (MQ_MITM_CA_*) and a runtime-minted origin cert drive it. - if(MQPROXY_MITM) - add_test(NAME e2e_mitm_h2 - COMMAND ${CMAKE_COMMAND} -E env - MQPROXY_BIN=$ - MQPROXY_CERT=${MQ_CERT_FILE} - MQPROXY_KEY=${MQ_KEY_FILE} - MQ_MITM_CA_CRT=${MQ_MITM_CA_CRT} - MQ_MITM_CA_KEY=${MQ_MITM_CA_KEY} - bash ${CMAKE_SOURCE_DIR}/tests/integration/e2e_mitm_h2.sh) - set_tests_properties(e2e_mitm_h2 PROPERTIES SKIP_RETURN_CODE 77) - endif() - - # ---- Phase 3 Task 7.1: e2e UDP relay test utilities ---- - # These are standalone CLI helpers driven by e2e_udp.sh (Task 7.2). - # They are NOT registered as ctest tests (no add_test). - - # udp_echo: binds 127.0.0.1:, echoes every datagram back to sender. - # No mqproxy dependency — pure POSIX sockets. - add_executable(udp_echo tests/integration/udp_echo.c) - target_include_directories(udp_echo PRIVATE tests) - - # udpsocks: SOCKS5 UDP ASSOCIATE client; links mqproxy for - # mq_socks5_build_udp_hdr / mq_socks5_parse_udp_hdr. - add_executable(udpsocks tests/integration/udpsocks.c) - target_link_libraries(udpsocks mqproxy) - target_include_directories(udpsocks PRIVATE tests src) - - # ---- Phase 3 Task 7.2: e2e UDP relay end-to-end scenarios ---- - # Drives the full UDP relay path against udp_echo using udpsocks: - # case 1: 64-byte byte-exact echo. - # case 2: 3000-byte echo + frags_reassembled > 0 from server stats. - # case 3: two concurrent targets, both byte-exact. - # case 4: idle-timeout 1s expiry → re-OPEN path. - # case 5: kill udpsocks mid-flight → server reaps; health check. - # case 6: (NET_ADMIN) 2-path smoke, both paths carried bytes. - # Cases 1-5 run anywhere; case 6 needs tc on lo and is skipped (a note) - # without NET_ADMIN. The WHOLE script exits 77 (SKIP) ONLY when required - # binaries (mqproxy, udpsocks, udp_echo) or certs are missing. - add_test(NAME e2e_udp - COMMAND ${CMAKE_COMMAND} -E env - MQPROXY_BIN=$ - UDPSOCKS_BIN=$ - UDPECHO_BIN=$ - MQPROXY_CERT=${MQ_CERT_FILE} - MQPROXY_KEY=${MQ_KEY_FILE} - bash ${CMAKE_SOURCE_DIR}/tests/integration/e2e_udp.sh) - set_tests_properties(e2e_udp PROPERTIES SKIP_RETURN_CODE 77) - - # ---- packaging / install ---- - configure_file(packaging/systemd/mqproxy-server@.service.in - ${CMAKE_BINARY_DIR}/packaging/mqproxy-server@.service @ONLY) - configure_file(packaging/systemd/mqproxy-client@.service.in - ${CMAKE_BINARY_DIR}/packaging/mqproxy-client@.service @ONLY) - - install(TARGETS mqproxy_cli RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) - - # systemd units / sysusers / tmpfiles are NOT multiarch — literal lib/ paths. - install(FILES - ${CMAKE_BINARY_DIR}/packaging/mqproxy-server@.service - ${CMAKE_BINARY_DIR}/packaging/mqproxy-client@.service - DESTINATION lib/systemd/system) - install(FILES packaging/sysusers.d/mqproxy.conf DESTINATION lib/sysusers.d) - install(FILES packaging/tmpfiles.d/mqproxy.conf DESTINATION lib/tmpfiles.d) - - install(FILES server.conf.example client.conf.example - DESTINATION ${CMAKE_INSTALL_DOCDIR}) - - # License bundling (Apache-2.0 §4(a) + third-party notices). The packaging - # binary statically links xquic, BoringSSL, and nghttp2, so bundle their - # notices alongside mqproxy's own license. OPTIONAL only so CI jobs that skip - # fetched third-party sources don't fail the install rule. - install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) - install(FILES third_party/xquic/LICENSE - DESTINATION ${CMAKE_INSTALL_DOCDIR}/third-party RENAME xquic.txt OPTIONAL) - install(FILES third_party/xquic/third_party/boringssl/LICENSE - DESTINATION ${CMAKE_INSTALL_DOCDIR}/third-party RENAME boringssl.txt OPTIONAL) - install(FILES third_party/nghttp2.LICENSE - DESTINATION ${CMAKE_INSTALL_DOCDIR}/third-party RENAME nghttp2.txt) - - # Verify the install layout + static self-containment. SKIPs (77) unless the - # build was configured with -DCMAKE_INSTALL_PREFIX=/usr (packaging build). - add_test(NAME packaging_install_static - COMMAND bash ${CMAKE_SOURCE_DIR}/tests/packaging/verify_install.sh - ${CMAKE_BINARY_DIR}) - set_tests_properties(packaging_install_static PROPERTIES SKIP_RETURN_CODE 77) - - # ---------- CPack DEB packaging (only when dpkg is available) ---------- - # Builds a self-contained .deb from the static (MQPROXY_STATIC_XQUIC) binary: - # xquic + BoringSSL + nghttp2 are statically linked, so SHLIBDEPS pins only - # libevent/libcurl/libc. Maintainer scripts provision the mqproxy user/dirs - # (raw CPack DEB has no debhelper -> sysusers.d/tmpfiles.d are not auto-run). - find_program(DPKG_CMD dpkg) - if(DPKG_CMD) - execute_process(COMMAND ${DPKG_CMD} --print-architecture - OUTPUT_VARIABLE DPKG_ARCH OUTPUT_STRIP_TRAILING_WHITESPACE) - set(CPACK_GENERATOR "DEB") - set(CPACK_DEBIAN_PACKAGE_NAME "mqproxy") - set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") - set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "${DPKG_ARCH}") - set(CPACK_DEBIAN_FILE_NAME "mqproxy_${PROJECT_VERSION}_${DPKG_ARCH}.deb") - set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) - set(CPACK_DEBIAN_PACKAGE_SECTION "net") - set(CPACK_DEBIAN_PACKAGE_DESCRIPTION - "Multipath QUIC application proxy/accelerator (MPQUIC-native)") - set(CPACK_DEBIAN_PACKAGE_MAINTAINER "mp0rta") - set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/mp0rta/mqproxy") - set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA - "${CMAKE_SOURCE_DIR}/packaging/postinst;${CMAKE_SOURCE_DIR}/packaging/postrm") - include(CPack) - endif() -endif() diff --git a/Cargo.lock b/Cargo.lock index 54cee7a4..e97f1252 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -647,26 +647,21 @@ dependencies = [ "proptest", ] -[[package]] -name = "mq-wire-difftest" -version = "0.0.4" -dependencies = [ - "cc", - "mq-wire", -] - [[package]] name = "mqproxy" version = "0.0.4" dependencies = [ "clap", "log", + "mio", "mq-h3", "mq-linux", "mq-proxy", "mq-runtime", "mq-transport", "mq-transport-api", + "mq-wire", + "signal-hook", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f2a51d1c..545462fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "2" members = [ - "crates/xquic-sys", "crates/mq-linux", "crates/mq-wire", "crates/mq-http", "crates/mq-wire-difftest", + "crates/xquic-sys", "crates/mq-linux", "crates/mq-wire", "crates/mq-http", "crates/mq-transport-api", "crates/mq-transport", "crates/mq-runtime", "crates/mq-h3", "crates/mq-proxy", "crates/mqproxy", "crates/mq-integration", ] diff --git a/NOTICE b/NOTICE index 20756379..c2b1386b 100644 --- a/NOTICE +++ b/NOTICE @@ -15,7 +15,6 @@ Third-party software included: Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. ISC-style license — see third_party/xquic/third_party/boringssl/LICENSE - * nghttp2 (https://nghttp2.org) - Copyright (c) 2012, 2014, 2015, 2016 Tatsuhiro Tsujikawa - Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors - MIT License — see third_party/nghttp2.LICENSE + * h3wire and the Rust dependencies listed in Cargo.lock + Per-crate license texts and metadata are included in release packages under + /usr/share/doc/mqproxy/third-party/ (RUST-DEPENDENCIES.txt is the index). diff --git a/README.md b/README.md index 22e6bab9..a8dce5da 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Application (curl / SDK) │ POST /_mqproxy/fetch (X-Mq-Auth / X-Mq-Target / X-Mq-Method + raw body) ▼ mqproxy-client ──────── HTTP/3 over MPQUIC (ALPN h3) ────────► mqproxy-server - 1 HTTP request = 1 H3 request stream = 1 MPQUIC stream │ libcurl (h2→h1, + 1 HTTP request = 1 H3 request stream = 1 MPQUIC stream │ hyper (h2→h1, spread across paths │ TLS verify ON) ▼ Origin server @@ -74,7 +74,7 @@ mqproxy-client ──────── HTTP/3 over MPQUIC (ALPN h3) ─── Instead of tunneling opaque bytes, the gateway **executes delegated HTTP requests**: the client validates `X-Mq-*` headers, maps the request onto a standard H3 request stream (QPACK, trailers and stream management come from the H3 stack — no custom HTTP framing on the wire), and the server authenticates each request (`X-Mq-Auth`, per-request), strips the `X-Mq-*` controls, and runs the request against the origin with full TLS verification. Errors map to HTTP statuses (DNS failure → 502, connect timeout → 504, bad token → 403, …) and responses carry an `X-Mq-Origin-Protocol` diagnostic header. Because each request is one MPQUIC stream, large downloads *and uploads* get within-stream multipath aggregation. -Per-request `X-Mq-*` controls let a caller opt into gateway features without changing the API: `X-Mq-Origin-Protocol` pins the upstream HTTP version (`h1`/`h2`/`h3`), `X-Mq-Accept-Encoding` requests download compression, `X-Mq-Forward-Cookie` forwards the `Cookie` header upstream (otherwise withheld), and `X-Mq-Cache` opts the response into the in-memory origin cache (`--cache-max-bytes`). The server also pools and reuses origin connections across requests. +Per-request `X-Mq-*` controls let a caller opt into gateway features without changing the API: `X-Mq-Origin-Protocol` pins the upstream HTTP version (`h1`/`h2`; `h3` is rejected), `X-Mq-Accept-Encoding` requests download compression, `X-Mq-Forward-Cookie` forwards the `Cookie` header upstream (otherwise withheld), and the legacy `X-Mq-Cache` control is ignored (the origin cache has been removed). The server also pools and reuses origin connections across requests. ### UDP Relay @@ -93,36 +93,22 @@ UDP relay carries non-QUIC UDP traffic (DNS, NTP, game, VoIP, app-specific UDP) ## Building from source -**Requirements:** `cmake`, `make`, a C11 compiler, `git`, the `openssl` CLI (used at configure time to generate the bundled test certificates), `libevent`, `libcurl` dev headers (e.g. `libcurl4-openssl-dev` — the gateway's origin client), `libnghttp2` dev headers (`libnghttp2-dev` — HTTP/2 termination for the TLS MITM L7 path; the static binary additionally needs the bundled `libnghttp2.a`), and `golang` (BoringSSL's build needs Go). Network access is required on first build (BoringSSL is cloned). - -On Debian/Ubuntu: +Install Rust with rustup; `rust-toolchain.toml` pins the supported toolchain. +Native dependencies are a C/C++ toolchain, CMake, Go and Git for the vendored +xquic/BoringSSL build. Cargo builds and statically links them automatically. ```bash -sudo apt-get install -y build-essential cmake git openssl \ - libevent-dev libcurl4-openssl-dev libnghttp2-dev golang-go -``` - -> Runtime packaging dependency: the dynamically-linked binary depends on the nghttp2 shared library (Debian/Ubuntu package `libnghttp2-14`). The `-DMQPROXY_STATIC_XQUIC=ON` packaging binary instead statically links `libnghttp2.a` and so carries no runtime nghttp2 dependency. - -```bash -# 1. Clone with the xquic submodule +sudo apt-get install -y build-essential cmake git golang-go git clone --recursive https://github.com/mp0rta/mqproxy.git cd mqproxy -# (if you cloned without --recursive: git submodule update --init --recursive) - -# 2. Build the in-tree xquic (BoringSSL + xquic, with qlog enabled) -# Produces third_party/xquic/build/libxquic.so. Pins BoringSSL to a known commit. -./scripts/build-xquic.sh - -# 3. Configure + build mqproxy against that xquic -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" -cmake --build build -j"$(nproc)" - -# The binary is at build/mqproxy -./build/mqproxy --help +git submodule update --init --recursive +cargo build --release --locked -p mqproxy +./target/release/mqproxy --help ``` +The application is Rust. HTTP/3 uses h3wire; origin HTTP/1.1 and HTTP/2 use +hyper and rustls. xquic and its nested BoringSSL remain pinned submodules. + ## Quick Start Run a server and a client locally, then send TCP traffic through the client's SOCKS5 ingress. @@ -130,11 +116,11 @@ Run a server and a client locally, then send TCP traffic through the client's SO ```bash # Server — listens for MPQUIC on UDP :4433. --cert/--key are required; # the repo ships a self-signed test cert under tests/certs for local use. -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ --cert tests/certs/test.crt --key tests/certs/test.key # Client — connects to the server, exposes a local SOCKS5 listener on :1080. -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 \ --token secret123 \ --socks5 127.0.0.1:1080 @@ -148,7 +134,7 @@ curl --socks5-hostname 127.0.0.1:1080 https://example.com/ **Multipath** — bind additional local IPs as MPQUIC paths (e.g. WiFi + LTE) by repeating `--path`; the stream is then aggregated across them: ```bash -./build/mqproxy client \ +./target/release/mqproxy client \ --server :4433 --token secret123 \ --socks5 127.0.0.1:1080 \ --path 192.168.1.50 \ @@ -162,7 +148,7 @@ You can also expose an HTTP CONNECT ingress with `--http-connect ` (use The gateway is enabled on the server by default. On the client, add `--gateway` (works with or without `--socks5`): ```bash -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --gateway 127.0.0.1:8080 @@ -188,11 +174,11 @@ UDP relay is exposed on the **same `--socks5` listener** — any SOCKS5 client t ```bash # Server (UDP relay on by default; 30s idle timeout shown) -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ --udp-idle-timeout 30 # Client — the SOCKS5 listener handles both TCP and UDP -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --socks5 127.0.0.1:1080 ``` @@ -201,7 +187,7 @@ UDP relay is exposed on the **same `--socks5` listener** — any SOCKS5 client t ```bash # Relay a UDP packet to a target through the client's SOCKS5 listener -./build/udpsocks --proxy 127.0.0.1:1080 --target 8.8.8.8:53 --send 32 --count 1 +./target/release/examples/udpsocks --proxy 127.0.0.1:1080 --target 8.8.8.8:53 --send 32 --count 1 ``` ### Transparent capture quick start @@ -219,10 +205,10 @@ Two kernel capture mechanisms are supported: ```bash # Server — no change from the regular config. -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 # Client — transparent listener on :12443; self-installs nft rules (needs root). -sudo ./build/mqproxy client \ +sudo ./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --tproxy 127.0.0.1:12443 \ --setup-redirect @@ -240,7 +226,7 @@ curl https://example.com/ When the router stack already owns the firewall and policy-routing rules (any setup that places a `TPROXY` target in `PREROUTING` and marks the packets), leave `--setup-redirect` OFF and let mqproxy just provide the listener — match its `--tproxy-fwmark`/`--tproxy-table` to whatever the rules use: ```bash -sudo ./build/mqproxy client \ +sudo ./target/release/mqproxy client \ --server :4433 --token secret123 \ --tproxy 0.0.0.0:12443 \ --tproxy-mode tproxy \ @@ -282,11 +268,11 @@ To limit the CA to your own domains, add a `nameConstraints` extension to the co ```bash # Server — unchanged; the gateway origin bridge does the origin fetch. -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ --cert /etc/mqproxy/tls/server.pem --key /etc/mqproxy/tls/server.key # Client — transparent capture + MITM. Requires --tproxy and a signing CA. -sudo ./build/mqproxy client \ +sudo ./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --tproxy 127.0.0.1:12443 --setup-redirect \ --mitm \ @@ -359,7 +345,7 @@ mq.mitm conns= streams= mitm= opaque_not_tls= opaque_no_sni= **Known limitation — large downloads to slow devices.** The client's HTTP/3 receive side does not yet apply end-to-end back-pressure: xquic copies response data into client memory as it arrives and returns flow-control credit to the server immediately. If a browser (or the device it runs on) consumes a large download more slowly than the tunnel delivers it, the client buffers the difference in memory. The same applies to the gateway fetch ingress. The fix is tracked upstream ([alibaba/xquic#959](https://github.com/alibaba/xquic/issues/959), [PR #960](https://github.com/alibaba/xquic/pull/960)) and will be picked up when it lands. On memory-constrained routers, watch the client's memory during large downloads to slow clients, or add the affected hosts to the ignore list. +> HTTP/3 uses h3wire over raw xquic streams. Receive credit follows consumption, with bounded adapter queues for slow consumers. ### Resilience: reconnect and keepalive @@ -417,35 +403,28 @@ See [`server.conf.example`](server.conf.example) and [`client.conf.example`](cli ## Install from a `.deb` -Prebuilt `amd64` and `arm64` packages are attached to each [GitHub Release](https://github.com/mp0rta/mqproxy/releases). The binary is self-contained (xquic + BoringSSL + nghttp2 statically linked), so it depends only on the system libevent/libcurl. +Prebuilt `amd64` and `arm64` packages are attached to each [GitHub Release](https://github.com/mp0rta/mqproxy/releases). Rust packages statically link xquic, BoringSSL and the Rust dependencies. Their system runtime dependencies are recorded in the `.deb` metadata; libevent, libcurl and nghttp2 are no longer required. ```bash # pick the .deb for your architecture from the latest release -sudo dpkg -i mqproxy__amd64.deb # or _arm64.deb +sudo apt install ./mqproxy__amd64.deb # or _arm64.deb ``` The package installs `/usr/bin/mqproxy`, the `mqproxy-server@` / `mqproxy-client@` systemd template units, and creates the unprivileged `mqproxy` user plus `/etc/mqproxy` (via the bundled `sysusers.d`/`tmpfiles.d`, applied in the package's `postinst`). Continue from the per-instance config steps in [Install as a systemd service](#install-as-a-systemd-service) below — the configure/enable steps are identical; only the build-from-source steps are skipped. ## Install as a systemd service -mqproxy ships systemd template units so each instance runs as a hardened, unprivileged service. Build a self-contained binary (xquic + BoringSSL statically linked, so the installed binary has no non-standard runtime deps) and install it: +mqproxy ships hardened, unprivileged systemd template units. Install a release +package, or build a native package locally (amd64 or arm64): ```bash -# -DMQPROXY_STATIC_XQUIC statically links xquic+BoringSSL; the install prefix is -# baked into the unit ExecStart at *configure* time, so set it now (not at --install). -cmake -S . -B build \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" \ - -DMQPROXY_STATIC_XQUIC=ON -DCMAKE_INSTALL_PREFIX=/usr -cmake --build build --target mqproxy_cli -j -sudo cmake --install build # → /usr/bin/mqproxy, units, sysusers.d, tmpfiles.d +sudo apt-get install -y dpkg-dev python3 +python3 packaging/build.py +sudo apt install ./target/dist/mqproxy_*.deb ``` -Create the `mqproxy` system user and its directories (declared by the bundled `sysusers.d`/`tmpfiles.d`): - -```bash -sudo systemd-sysusers -sudo systemd-tmpfiles --create # creates /etc/mqproxy (0750 mqproxy:mqproxy) -``` +The build also creates a tar archive with the same `/usr` layout. `.deb` +installation applies the bundled sysusers/tmpfiles rules automatically. Drop a per-instance config and lock it down (the service reads it as user `mqproxy`): @@ -462,7 +441,7 @@ sudo systemctl enable --now mqproxy-server@edge1 # → /etc/mqproxy/edge1.co journalctl -u mqproxy-server@edge1 -f # logs ``` -The client side uses `mqproxy-client@` the same way (`/etc/mqproxy/.conf`). +The client side uses `mqproxy-client@` the same way (`/etc/mqproxy/.conf`). Instance names use 1–64 ASCII letters, digits, dots, underscores or dashes; units pass the name as `--instance-id`, which appends `instance_id=` to logs. Use separate QLog directories when running multiple instances; the label does not change filenames. **Notes:** - **qlog:** to capture xquic qlog, set `[Log] QLog = /var/log/mqproxy` in the config. The unit's `LogsDirectory=` creates `/var/log/mqproxy`; `ProtectSystem=strict` blocks writing qlog anywhere else (except `PrivateTmp`). qlog stays off unless `QLog` is set. @@ -477,6 +456,8 @@ The tables below are split: **common flags first**, then one block per mode. Wit ### Common flags (all modes) +`--instance-id ` optionally labels process logs (1–64 ASCII letters, digits, dots, underscores or dashes). It does not change protocol identity or qlog filenames. + **Server** — `mqproxy server …` | Flag | Description | @@ -532,7 +513,7 @@ The tables below are split: **common flags first**, then one block per mode. Wit | `--no-gateway` | **Disables HTTP Gateway mode for this server** (it is enabled by default). The TCP-proxy core keeps running; only the gateway origin bridge is turned off, so a client's `--gateway` ingress has nothing to talk to. | | `--origin-ca ` | Extra CA bundle for origin TLS verification (private CAs / tests); verification itself is always on | | `--request-metrics` | Emit one `mq.req` logfmt line per gateway request (method/status/target/ttfb/origin_protocol/cache/…). Opt-in; off by default. Independent of `--metrics-interval`. | -| `--cache-max-bytes ` | In-memory origin response cache bounded to `N` bytes (`0` = off = default; e.g. `67108864` = 64 MiB). Opt-in per request via `X-Mq-Cache`. | +| `--cache-max-bytes ` | Accepted with a warning and ignored; the origin response cache has been removed. | | `--masquerade` | Answer **unauthenticated** gateway requests with a bare `404` (no `X-Mq-*` headers), so probes/scanners see a generic HTTP/3 server instead of a fingerprintable `403 auth-failed`. Authenticated requests are unaffected. Gateway-only; off by default; recommended for internet-exposed servers. | **Client** @@ -599,7 +580,11 @@ The tables below are split: **common flags first**, then one block per mode. Wit After building, run the bundled test suite: ```bash -ctest --test-dir build --output-on-failure +cargo test --workspace --locked +cargo clippy --workspace --all-targets --locked -- -D warnings +cargo build --release --locked -p mqproxy --bins --examples +bash tests/test_cli_help.sh target/release/mqproxy +bash tests/integration/e2e_udp.sh ``` It covers wire framing, the relay/flow state machine, ingress parsing, the gateway request path, and the TLS MITM crypto core, alongside end-to-end scripts for multipath aggregation, the full gateway chain, UDP relay, and transparent capture. Tests that need root or `NET_ADMIN` (the multipath and transparent-capture end-to-end runs) skip automatically when run unprivileged. @@ -612,7 +597,7 @@ Pass `--qlog ` to either side to emit xquic qlog. Per-path byte counts conf mqproxy uses a **trusted proxy** model: mqproxy-client, mqproxy-server, and the MPQUIC connection between them are trusted. In TCP Proxy Mode and Transparent Capture mode (without `--mitm`), application↔origin TLS is preserved end-to-end (mqproxy never sees plaintext — it relays raw TLS bytes opaquely). Applications with certificate pinning continue to work. The HTTP Request Execution Gateway is an explicit delegation model — the client delegates HTTP request execution to a trusted gateway that establishes (and always verifies) the origin TLS — not a transparent MITM. Gateway requests are authenticated individually (`X-Mq-Auth`, per-request); `Authorization` is reserved for the origin and forwarded, while `Cookie` and `X-Mq-*` never leave the gateway. -**TLS MITM ingress** (`--mitm`, opt-in) is an **operator-controlled / consenting-endpoint** model for managed devices where the operator's CA is installed locally — a corporate-proxy or personal-VPN posture, not a transparent attack on third parties. The client forges per-host leaves from that CA, terminates the browser's TLS as HTTP/2, and maps each request onto the Gateway tunnel. Its trust assumptions: the **CA private key is the anchor** (an unencrypted PKCS#8 file owned by the running user, not group/other accessible, not a symlink; it stays in memory while mqproxy runs); a CA can be limited to your own domains with an X.509 `nameConstraints` extension; browser-supplied `X-Mq-*` headers are **always stripped** (never interpreted as controls — the client injects its own `x-mq-auth`); and the feature is **fail-closed** (misconfiguration is a startup error, never silent passthrough, with a bounded ClientHello read and header, stream and connection limits). Anything that is not positively an HTTP/2 TLS client for a valid host name is relayed opaquely and never inspected. Cert-pinned hosts can be excluded with `--ignore-host(s)`, which relays them opaquely so the origin's real certificate reaches the client. Known limitation: the client's HTTP/3 receive side buffers a large download in memory when the consuming device is slower than the tunnel, until an upstream xquic fix lands (see [TLS MITM mode](#tls-mitm-mode)). +**TLS MITM ingress** (`--mitm`, opt-in) is an **operator-controlled / consenting-endpoint** model for managed devices where the operator's CA is installed locally — a corporate-proxy or personal-VPN posture, not a transparent attack on third parties. The client forges per-host leaves from that CA, terminates the browser's TLS as HTTP/2, and maps each request onto the Gateway tunnel. Its trust assumptions: the **CA private key is the anchor** (an unencrypted PKCS#8 file owned by the running user, not group/other accessible, not a symlink; it stays in memory while mqproxy runs); a CA can be limited to your own domains with an X.509 `nameConstraints` extension; browser-supplied `X-Mq-*` headers are **always stripped** (never interpreted as controls — the client injects its own `x-mq-auth`); and the feature is **fail-closed** (misconfiguration is a startup error, never silent passthrough, with a bounded ClientHello read and header, stream and connection limits). Anything that is not positively an HTTP/2 TLS client for a valid host name is relayed opaquely and never inspected. Cert-pinned hosts can be excluded with `--ignore-host(s)`, which relays them opaquely so the origin's real certificate reaches the client. ## License @@ -628,4 +613,6 @@ Use of mqproxy is at your own risk. Users are solely responsible for validating - [XQUIC](https://github.com/alibaba/xquic) (Alibaba) — the QUIC/MPQUIC transport, via the [mp0rta fork](https://github.com/mp0rta/xquic). - [BoringSSL](https://boringssl.googlesource.com/boringssl) — TLS backend. -- [nghttp2](https://nghttp2.org/) — HTTP/2 framing for the C build's TLS MITM ingress (the Rust binary uses the `h2` crate and rustls). +- [h3wire](https://crates.io/crates/h3wire) — HTTP/3 framing and QPACK. +- [hyper](https://hyper.rs/), h2 and [rustls](https://rustls.dev/) — origin HTTP and MITM TLS/HTTP/2. +- All bundled dependency licenses are included in release artifacts. diff --git a/cli/main.c b/cli/main.c deleted file mode 100644 index c26c849f..00000000 --- a/cli/main.c +++ /dev/null @@ -1,1811 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* main.c — the `mqproxy` CLI: client and server subcommands. - * - * Usage: - * mqproxy server --listen --token [--cert --key ] - * [--origin-ca ] [--no-gateway] - * mqproxy client --server --token - * [--socks5 ] [--http-connect ] - * [--gateway ] [--path ]... - * [--client-id ] [--qlog ] - * - * The client needs at least one ingress: --socks5, --http-connect, or - * --gateway. --socks5 / --http-connect drive the TCP-proxy core (mq_client); - * --gateway runs the independent HTTP-gateway fetch ingress (mq_gw_client over - * its own H3 tunnel) and may be used on its own. - * - * The server runs the HTTP-gateway origin bridge (mq_gw_server: H3→curl→origin) - * by DEFAULT alongside the TCP-proxy server core. --no-gateway disables it; - * --origin-ca overrides the CA bundle used to verify origin TLS. - * - * Dispatch is on argv[1] (`client` / `server`). `--help`/`-h` at the top level - * and per subcommand prints usage and exits 0. An unknown subcommand or a - * missing required flag prints usage to stderr and exits non-zero. - * - * This binary wires the transport (mq_transport + mq_runtime), the proxy core - * (mq_client / mq_server), and the ingress listeners (SOCKS5 / HTTP CONNECT) - * into runnable processes, then runs the libevent loop until SIGINT/SIGTERM. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "gateway/mq_fetch_listener.h" -#include "gateway/mq_gw_client.h" -#include "gateway/mq_gw_fetch_adapter.h" -#include "gateway/mq_gw_server.h" -#include "ingress/mq_listener.h" -#include "ingress/mq_tproxy.h" -#include "ingress/mq_tproxy_setup.h" -#include "proxy/mq_client.h" -#include "proxy/mq_server.h" -#include "runtime/mq_runtime_libevent.h" -#include "config/mq_config.h" -#include "transport/mq_h3.h" -#include "transport/mq_transport.h" -#include "util/mq_log.h" - -/* Release version string. Defined by CMake from project(VERSION) as a compile - * define (-DMQPROXY_VERSION=...); the fallback keeps non-CMake/ad-hoc builds - * compiling and makes an un-versioned build self-identify as such. */ -#ifndef MQPROXY_VERSION -# define MQPROXY_VERSION "0.0.0-unknown" -#endif - -#if MQ_MITM_AVAILABLE -/* Live MITM orchestrator (Phase 7 Slice 3). Compiled in only when the BoringSSL - * archives are present (MQ_MITM_AVAILABLE=1); a no-archive build sets it to 0 and - * these headers / all MITM wiring below compile out so the CLI still links. */ -# include "mitm/mq_ignore_hosts.h" -# include "mitm/mq_mitm_conn.h" -# include "mitm/mq_mitm_core.h" -#endif - -/* Flow-control windows (MQ_STREAM_WINDOW / MQ_CONN_WINDOW) are defined in - * transport/mq_conn.h and applied by mq_client_new / mq_server_new via - * mq_conn_apply_mp_settings — no CLI window flags in Phase 1. */ - -#define MQ_MAX_EXTRA_PATHS 8 - -/* Scan argv for --config or --config=; returns the path or NULL. - * Independent of getopt so the file can be loaded before the override pass. - * If --config is passed twice, the first wins (spec is silent; undocumented). */ -static const char * -find_config_arg(int argc, char **argv) -{ - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--config") == 0 && i + 1 < argc) return argv[i + 1]; - if (strncmp(argv[i], "--config=", 9) == 0) return argv[i] + 9; - } - return NULL; -} - -/* ── usage text ─────────────────────────────────────────────────────────────*/ - -static void -usage_top(FILE *out) -{ - fprintf(out, "Usage: mqproxy [options]\n" - "\n" - "Commands:\n" - " server Run the mqproxy server (terminates proxied TCP).\n" - " client Run the mqproxy client with local SOCKS5 / HTTP CONNECT\n" - " ingress listeners.\n" - "\n" - " -V, --version Print the mqproxy version and exit.\n" - "\n" - "Run 'mqproxy --help' for command-specific options.\n"); -} - -static void -usage_server(FILE *out) -{ - fprintf(out, "Usage: mqproxy server --listen --token \n" - " [--cert ] [--key ]\n" - " [--origin-ca ] [--no-gateway]\n" - " [--udp-idle-timeout ] [--no-udp]\n" - "\n" - "Options:\n" - " --listen UDP address to accept MPQUIC connections on " - "(required).\n" - " --token Shared auth token clients must present " - "(required).\n" - " --cert TLS certificate (PEM) " - "(required).\n" - " --key TLS private key (PEM) " - "(required).\n" - " --origin-ca CA bundle (PEM) used to verify origin TLS for " - "the\n" - " HTTP gateway. Defaults to the system trust " - "store.\n" - " --no-gateway Disable the HTTP gateway origin bridge " - "(enabled by\n" - " default; the server still serves the TCP-proxy " - "core).\n" - " --udp-idle-timeout \n" - " Idle timeout for UDP relay sessions in seconds\n" - " (default: 60; must be > 0).\n" - " --no-udp Disable UDP relay (do not advertise " - "MQ_FEAT_UDP_RELAY).\n" - " --qlog Write xquic qlog (EXTRA importance) to " - "/server.qlog.\n" - " --cc Congestion control: bbr (default) | bbr2 | " - "cubic.\n" - " --scheduler Multipath scheduler: minrtt (default) | " - "backup | wlb.\n" - " --metrics-interval \n" - " Periodically log per-path stats " - "(mq.conn/mq.path)\n" - " every s (must be > 0; omit to disable).\n" - " Logs the most-recently-accepted TCP and " - "gateway conn.\n" - " --request-metrics Emit one mq.req logfmt line per gateway " - "request\n" - " (method/status/target/ttfb/origin_protocol/" - "cache/…).\n" - " Opt-in; off by default. Independent of " - "--metrics-interval.\n" - " --masquerade Answer unauthenticated requests with a bare " - "404\n" - " (hides mqproxy from probes; gateway only).\n" - " --cache-max-bytes \n" - " In-memory origin response cache bounded to N " - "bytes\n" - " (0 = off = default; opt-in per request via " - "X-Mq-Cache;\n" - " e.g. 67108864 = 64 MiB).\n" - " --max-conns Max simultaneous QUIC connections " - "(default: 16;\n" - " 0 = unlimited). Caps established " - "connections;\n" - " excess are refused (CONNECTION_REFUSED).\n" - " --config Load settings from an INI file (CLI flags\n" - " override file values).\n" - " -h, --help Show this help and exit.\n"); -} - -static void -usage_client(FILE *out) -{ - fprintf( - out, - "Usage: mqproxy client --server --token \n" - " [--socks5 ] [--http-connect ]\n" - " [--gateway ] [--tproxy ]\n" - " [--path ]... [--client-id ]\n" - "\n" - "At least one ingress is required: --socks5, --http-connect, --gateway, " - "or --tproxy.\n" - "\n" - "Options:\n" - " --server UDP address of the mqproxy server " - "(required).\n" - " --token Shared auth token (required).\n" - " --socks5 Local TCP address for the SOCKS5 ingress\n" - " (UDP ASSOCIATE supported).\n" - " --http-connect Local TCP address for the HTTP CONNECT " - "ingress.\n" - " --gateway Local TCP address for the HTTP gateway " - "fetch\n" - " ingress (POST /_mqproxy/fetch over its own " - "H3\n" - " tunnel; independent of the SOCKS5/CONNECT " - "core).\n" - " --path Local IP to bind a path to (repeatable). " - "The\n" - " first is the primary bind; each extra " - "becomes a\n" - " second/third MPQUIC path once the " - "connection is\n" - " multipath-ready.\n" - " --client-id Client identifier sent at auth " - "(default: mqproxy).\n" - " --qlog Write xquic qlog (EXTRA importance) to " - "/client.qlog\n" - " (the 1-B blocked-frame instrument).\n" - " --cc Congestion control: bbr (default) | bbr2 " - "| cubic.\n" - " --scheduler Multipath scheduler: minrtt (default) | backup | " - "wlb\n" - " --keepalive-idle QUIC idle timeout in seconds, kept alive by\n" - " PINGs (default: 30; 0 = disable; <15 not useful).\n" - " --reconnect Re-establish the server connection on loss\n" - " (default: enabled).\n" - " --no-reconnect Disable automatic reconnect on connection\n" - " loss.\n" - " --reconnect-max-backoff \n" - " Maximum reconnect back-off in seconds\n" - " (default: 30; must be > 0).\n" - " --metrics-interval Periodically log per-path stats " - "(mq.conn/mq.path)\n" - " every s (must be > 0; omit to " - "disable). Logs the\n" - " proxy conn (and the gateway conn with " - "--gateway).\n" - " --tproxy Local TCP address for the transparent capture\n" - " ingress (REDIRECT or TPROXY mode).\n" - " --tproxy-mode redirect|tproxy\n" - " Kernel capture mechanism (default: redirect).\n" - " redirect — nft nat REDIRECT (single-host, no\n" - " CAP_NET_ADMIN on the socket).\n" - " tproxy — nft mangle TPROXY (router, needs\n" - " CAP_NET_ADMIN for IP_TRANSPARENT).\n" - " --tproxy-fwmark Packet mark for TPROXY routing (default: 1;\n" - " tproxy mode only).\n" - " --tproxy-table ip routing table for TPROXY (default: 100;\n" - " tproxy mode only).\n" - " --tproxy-dport TCP destination port the --setup-redirect rule\n" - " captures (default: 443).\n" - " --setup-redirect Install nft/ip-rule firewall rules on start\n" - " and remove them on exit (requires root or\n" - " CAP_NET_ADMIN; off by default).\n" - " --tproxy-uid UID whose outbound traffic is NOT redirected\n" - " (default: geteuid() of the process).\n" - " --mitm Terminate TLS on captured flows (HTTPS MITM) and\n" - " re-encrypt to the origin. Requires --tproxy and\n" - " --ca-cert/--ca-key. Off by default.\n" - " --ca-cert Signing CA certificate (PEM) used to forge\n" - " per-host leaf certs (required with --mitm).\n" - " --ca-key Signing CA private key (PEM) (required with\n" - " --mitm).\n" - " --ignore-host Leave the named host OPAQUE (no MITM; pass TLS\n" - " through). Repeatable. Leading-dot suffix matches\n" - " subdomains (e.g. .example.com).\n" - " --ignore-hosts Comma-separated form of --ignore-host (union with\n" - " repeated --ignore-host and [Mitm] IgnoreHosts).\n" - " --config Load settings from an INI file (CLI flags\n" - " override file values).\n" - " -h, --help Show this help and exit.\n"); -} - -/* ── ip:port parsing ────────────────────────────────────────────────────────*/ - -/* Split "ip:port" into ip_out (capacity ip_cap) and *port_out. Supports - * "[v6]:port" bracketed IPv6 form and plain "v4:port". Returns 0 on success, - * -1 on malformed input. */ -static int -parse_ip_port(const char *s, char *ip_out, size_t ip_cap, uint16_t *port_out) -{ - if (!s || !ip_out || !port_out) return -1; - - const char *colon; - if (s[0] == '[') { - const char *end = strchr(s, ']'); - if (!end || end[1] != ':') return -1; - size_t iplen = (size_t)(end - (s + 1)); - if (iplen == 0 || iplen >= ip_cap) return -1; - memcpy(ip_out, s + 1, iplen); - ip_out[iplen] = '\0'; - colon = end + 1; - } else { - colon = strrchr(s, ':'); - if (!colon || colon == s) return -1; - size_t iplen = (size_t)(colon - s); - if (iplen >= ip_cap) return -1; - memcpy(ip_out, s, iplen); - ip_out[iplen] = '\0'; - } - - const char *port_str = colon + 1; - if (*port_str == '\0') return -1; - char *endp = NULL; - errno = 0; - long port = strtol(port_str, &endp, 10); - if (errno != 0 || endp == port_str || *endp != '\0') return -1; - if (port < 1 || port > 65535) return -1; - - /* Validate that ip_out parses as a v4 or v6 literal. */ - struct in_addr a4; - struct in6_addr a6; - if (inet_pton(AF_INET, ip_out, &a4) != 1 && inet_pton(AF_INET6, ip_out, &a6) != 1) { - return -1; - } - - *port_out = (uint16_t)port; - return 0; -} - -/* ── shutdown wiring (SIGINT/SIGTERM → mq_runtime_stop) ──────────────────────*/ - -static void -on_signal(evutil_socket_t sig, short what, void *user) -{ - (void)sig; - (void)what; - mq_runtime_t *rt = (mq_runtime_t *)user; - MQ_LOGI("signal received, shutting down"); - mq_runtime_stop(rt); -} - -/* Install SIGINT + SIGTERM handlers that break the loop. The returned events - * must be freed by the caller after the loop exits. Returns 0 on success. */ -static int -install_signal_handlers(struct event_base *base, mq_runtime_t *rt, struct event **out_int, - struct event **out_term) -{ - struct event *sint = evsignal_new(base, SIGINT, on_signal, rt); - struct event *sterm = evsignal_new(base, SIGTERM, on_signal, rt); - if (!sint || !sterm) { - if (sint) event_free(sint); - if (sterm) event_free(sterm); - return -1; - } - event_add(sint, NULL); - event_add(sterm, NULL); - *out_int = sint; - *out_term = sterm; - return 0; -} - -/* ── periodic metrics dump (opt-in via --metrics-interval) ───────────────────*/ - -/* Context for the client-side periodic metrics tick: carries the TCP-proxy core - * (mq_client) and the gateway client (mq_gw_client). Either may be NULL; the - * dump helpers guard NULL / closed conns. */ -struct cli_metrics_ctx { - mq_client_t *client; - mq_gw_client_t *gwc; -}; - -static void -cli_metrics_tick(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - struct cli_metrics_ctx *m = (struct cli_metrics_ctx *)arg; - mq_conn_t *conn = mq_client_conn(m->client); - if (conn) { - mq_conn_dump_stats(conn); - } - if (m->gwc) { - mq_gw_client_dump_stats(m->gwc); - } -} - -struct srv_metrics_ctx { - mq_server_t *server; /* TCP-proxy server (always present) */ - mq_gw_server_t *gws; /* gateway server (NULL when --no-gateway) */ -}; - -static void -srv_metrics_tick(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - struct srv_metrics_ctx *m = (struct srv_metrics_ctx *)arg; - mq_conn_t *conn = mq_server_active_conn(m->server); - if (conn) { - mq_conn_dump_stats(conn); - } - /* Gateway accepts its own H3 conns (separate from the TCP conns) — dump the - * most-recent one too, else a gateway-only server emits no metrics. */ - if (m->gws) { - mq_h3_conn_t *gc = mq_gw_server_active_conn(m->gws); - if (gc) { - mq_h3_conn_dump_stats(gc); - } - } -} - -/* ── server subcommand ──────────────────────────────────────────────────────*/ - -/* Default origin connect timeout (seconds) for the gateway bridge. */ -#define MQ_GW_ORIGIN_CONNECT_TIMEOUT_DEFAULT_S 10L - -/* Resolve the origin connect timeout. The default is - * MQ_GW_ORIGIN_CONNECT_TIMEOUT_DEFAULT_S; the env var - * MQ_GW_ORIGIN_CONNECT_TIMEOUT_S is a TEST-ONLY knob (NOT documented in --help) - * that lets e2e tests force a short timeout so a black-holed origin yields a - * deterministic 504. Parsed with strtol, clamped to [1, 600]; anything invalid - * (non-numeric, trailing junk, empty, out of range) is ignored and the default - * stands. */ -static long -gw_origin_connect_timeout_s(void) -{ - const char *env = getenv("MQ_GW_ORIGIN_CONNECT_TIMEOUT_S"); - if (!env || env[0] == '\0') return MQ_GW_ORIGIN_CONNECT_TIMEOUT_DEFAULT_S; - char *endp = NULL; - errno = 0; - long v = strtol(env, &endp, 10); - if (errno != 0 || endp == env || *endp != '\0') { - return MQ_GW_ORIGIN_CONNECT_TIMEOUT_DEFAULT_S; - } - if (v < 1 || v > 600) return MQ_GW_ORIGIN_CONNECT_TIMEOUT_DEFAULT_S; - return v; -} - -static int -cmd_server(int argc, char **argv) -{ - const char *listen = NULL; - const char *token = NULL; - const char *cert = NULL; - const char *key = NULL; - const char *origin_ca = NULL; /* nullable: NULL = system trust store */ - const char *qlog_dir = NULL; - const char *cc_name = NULL; - mq_cc_t cc = MQ_CC_DEFAULT; - const char *sched_arg = NULL; - mq_sched_t sched = MQ_SCHED_DEFAULT; - int gateway_enabled = 1; /* gateway on by default; --no-gateway opts out */ - long udp_idle_timeout_s = 60; /* --udp-idle-timeout , default 60 */ - int udp_enabled = 1; /* --no-udp clears this */ - uint64_t metrics_interval_ms = 0; /* --metrics-interval ; 0 = off */ - int request_metrics = 0; /* --request-metrics; off by default */ - int masquerade = 0; /* --masquerade; off by default */ - size_t cache_max_bytes = 0; /* --cache-max-bytes ; 0 = off (default) */ - long max_conns = 16; /* --max-conns ; default 16, 0 = unlimited */ - - /* Seed locals from --config (if present) BEFORE getopt, so CLI flags override - * file values (precedence: defaults < file < CLI). fcfg is function-scoped so - * the const char* aliases below stay valid for the whole function. */ - mq_file_config_t fcfg; - mq_config_defaults(&fcfg); - const char *config_path = find_config_arg(argc, argv); - if (config_path) { - if (mq_config_load(&fcfg, config_path, 1 /*server*/) != 0) return 2; - if (mq_config_perms_insecure(config_path)) - MQ_LOGW("config %s is group/world-readable; chmod 0600 to protect the token", - config_path); - if (fcfg.listen[0]) listen = fcfg.listen; - if (fcfg.token[0]) token = fcfg.token; - if (fcfg.cert[0]) cert = fcfg.cert; - if (fcfg.key[0]) key = fcfg.key; - if (fcfg.origin_ca[0]) origin_ca = fcfg.origin_ca; - if (fcfg.qlog[0]) qlog_dir = fcfg.qlog; - if (fcfg.cc[0]) cc_name = fcfg.cc; - if (fcfg.scheduler[0]) sched_arg = fcfg.scheduler; - gateway_enabled = fcfg.gateway_enabled; - udp_enabled = fcfg.udp_enabled; - udp_idle_timeout_s = fcfg.udp_idle_timeout_s; - max_conns = fcfg.max_conns; - cache_max_bytes = fcfg.cache_max_bytes; - request_metrics = fcfg.request_metrics; - masquerade = fcfg.masquerade; - if (fcfg.metrics_interval_s > 0) - metrics_interval_ms = (uint64_t)fcfg.metrics_interval_s * 1000; - } - - enum { - OPT_LISTEN = 256, - OPT_TOKEN, - OPT_CERT, - OPT_KEY, - OPT_ORIGIN_CA, - OPT_NO_GATEWAY, - OPT_UDP_IDLE_TIMEOUT, - OPT_NO_UDP, - OPT_QLOG, - OPT_CC, - OPT_SCHEDULER, - OPT_METRICS_INTERVAL, - OPT_REQUEST_METRICS, - OPT_MASQUERADE, - OPT_CACHE_MAX_BYTES, - OPT_MAX_CONNS, - OPT_CONFIG, - }; - static const struct option longopts[] = { - {"listen", required_argument, NULL, OPT_LISTEN}, - {"token", required_argument, NULL, OPT_TOKEN}, - {"cert", required_argument, NULL, OPT_CERT}, - {"key", required_argument, NULL, OPT_KEY}, - {"origin-ca", required_argument, NULL, OPT_ORIGIN_CA}, - {"no-gateway", no_argument, NULL, OPT_NO_GATEWAY}, - {"udp-idle-timeout", required_argument, NULL, OPT_UDP_IDLE_TIMEOUT}, - {"no-udp", no_argument, NULL, OPT_NO_UDP}, - {"qlog", required_argument, NULL, OPT_QLOG}, - {"cc", required_argument, NULL, OPT_CC}, - {"scheduler", required_argument, NULL, OPT_SCHEDULER}, - {"metrics-interval", required_argument, NULL, OPT_METRICS_INTERVAL}, - {"request-metrics", no_argument, NULL, OPT_REQUEST_METRICS}, - {"masquerade", no_argument, NULL, OPT_MASQUERADE}, - {"cache-max-bytes", required_argument, NULL, OPT_CACHE_MAX_BYTES}, - {"max-conns", required_argument, NULL, OPT_MAX_CONNS}, - {"config", required_argument, NULL, OPT_CONFIG}, - {"help", no_argument, NULL, 'h'}, - {NULL, 0, NULL, 0}, - }; - - optind = 1; /* argv here starts at the subcommand */ - int c; - while ((c = getopt_long(argc, argv, "h", longopts, NULL)) != -1) { - switch (c) { - case OPT_LISTEN: listen = optarg; break; - case OPT_TOKEN: token = optarg; break; - case OPT_CERT: cert = optarg; break; - case OPT_KEY: key = optarg; break; - case OPT_ORIGIN_CA: origin_ca = optarg; break; - case OPT_NO_GATEWAY: gateway_enabled = 0; break; - case OPT_UDP_IDLE_TIMEOUT: { - char *endp = NULL; - errno = 0; - long v = strtol(optarg, &endp, 10); - if (errno != 0 || endp == optarg || *endp != '\0' || v <= 0) { - fprintf( - stderr, - "mqproxy server: invalid --udp-idle-timeout '%s' (must be > 0)\n\n", - optarg); - usage_server(stderr); - return 2; - } - udp_idle_timeout_s = v; - break; - } - case OPT_NO_UDP: udp_enabled = 0; break; - case OPT_QLOG: qlog_dir = optarg; break; - case OPT_CC: cc_name = optarg; break; - case OPT_SCHEDULER: sched_arg = optarg; break; - case OPT_METRICS_INTERVAL: { - char *end = NULL; - errno = 0; - long v = strtol(optarg, &end, 10); /* seconds */ - if (errno != 0 || end == optarg || *end != '\0' || v <= 0) { - fprintf(stderr, - "mqproxy server: invalid --metrics-interval '%s' (must be > 0; " - "omit to disable)\n\n", - optarg); - usage_server(stderr); - return 2; - } - metrics_interval_ms = (uint64_t)v * 1000; - break; - } - case OPT_REQUEST_METRICS: request_metrics = 1; break; - case OPT_MASQUERADE: masquerade = 1; break; - case OPT_CACHE_MAX_BYTES: { - /* SIGNED strtoll: strtoull("-5") silently returns a huge unsigned with - * NO error, so a `< 0` check could never fire. 0 is VALID (= disabled = - * default); reject only `< 0` (unlike --metrics-interval's `<= 0`). */ - char *end = NULL; - errno = 0; - long long v = strtoll(optarg, &end, 10); /* bytes */ - /* Reject v < 0 first (short-circuits), so the SIZE_MAX guard runs only - * for v >= 0 and can compare in the UNSIGNED domain. Do NOT cast - * SIZE_MAX to long long: on 64-bit (size_t == unsigned long long) that - * cast wraps to -1, rejecting every positive value. The guard is purely - * theoretical where size_t is 64-bit (LLONG_MAX < SIZE_MAX), but it makes - * the (size_t)v cast total on any platform. */ - if (errno != 0 || end == optarg || *end != '\0' || v < 0 || - (unsigned long long)v > (unsigned long long)SIZE_MAX) { - fprintf(stderr, - "mqproxy server: invalid --cache-max-bytes '%s' (must be >= 0; " - "0 = disabled)\n\n", - optarg); - usage_server(stderr); - return 2; - } - cache_max_bytes = (size_t)v; - break; - } - case OPT_MAX_CONNS: { - /* 0 is a VALID "unlimited" sentinel (like --cache-max-bytes); reject - * only < 0. SIGNED strtol so a negative is caught (strtoul would wrap). - * Reject > UINT32_MAX too: max_conns is a uint32_t, and a bare cast - * would silently truncate (notably 2^32 -> 0 = unlimited, the opposite - * of what the operator asked). Compare in the unsigned domain after the - * v<0 short-circuit, mirroring --cache-max-bytes's SIZE_MAX guard. */ - char *endp = NULL; - errno = 0; - long v = strtol(optarg, &endp, 10); - if (errno != 0 || endp == optarg || *endp != '\0' || v < 0 || - (unsigned long long)v > (unsigned long long)UINT32_MAX) { - fprintf(stderr, - "mqproxy server: invalid --max-conns '%s' " - "(must be 0..4294967295; 0 = unlimited)\n\n", - optarg); - usage_server(stderr); - return 2; - } - max_conns = v; - break; - } - case OPT_CONFIG: break; /* already consumed pre-getopt by find_config_arg */ - case 'h': usage_server(stdout); return 0; - default: usage_server(stderr); return 2; - } - } - - if (cc_name) { - int ok = 0; - cc = mq_cc_from_string(cc_name, &ok); - if (!ok) { - fprintf(stderr, "mqproxy server: invalid --cc '%s' (bbr2|bbr|cubic)\n\n", - cc_name); - usage_server(stderr); - return 2; - } - } - if (sched_arg) { - int sched_ok = 0; - sched = mq_sched_from_string(sched_arg, &sched_ok); - if (!sched_ok) { - fprintf(stderr, - "mqproxy server: invalid --scheduler '%s' (minrtt|backup|wlb)\n\n", - sched_arg); - usage_server(stderr); - return 2; - } - } - mq_conn_set_scheduler(sched); - - /* --request-metrics only takes effect inside the gateway block below; with - * --no-gateway it is silently ignored. Warn (not error: the flag is opt-in) - * so the operator knows it has no effect. Emitted here, after option parsing, - * so it surfaces regardless of which required arg may be missing. */ - if (request_metrics && !gateway_enabled) { - fprintf(stderr, "mqproxy server: --request-metrics has no effect with " - "--no-gateway (request metrics are gateway-only); ignoring\n"); - } - /* --cache-max-bytes is gateway-only too (the cache lives on the gw server); - * warn (not error: opt-in) when paired with --no-gateway. */ - if (cache_max_bytes > 0 && !gateway_enabled) { - fprintf(stderr, "mqproxy server: --cache-max-bytes has no effect with " - "--no-gateway (the response cache is gateway-only); ignoring\n"); - } - /* --masquerade is gateway-only (it changes the gw server's unauth response); - * warn (not error: opt-in) when paired with --no-gateway. */ - if (masquerade && !gateway_enabled) { - fprintf(stderr, "mqproxy server: --masquerade has no effect with " - "--no-gateway (masquerade is gateway-only); ignoring\n"); - } - - if (!listen) { - fprintf(stderr, "mqproxy server: missing required --listen\n\n"); - usage_server(stderr); - return 2; - } - if (!token) { - fprintf(stderr, "mqproxy server: missing required --token\n\n"); - usage_server(stderr); - return 2; - } - if (!cert || cert[0] == '\0' || !key || key[0] == '\0') { - fprintf(stderr, "mqproxy server: --cert and --key are required\n\n"); - usage_server(stderr); - return 2; - } - - char listen_ip[INET6_ADDRSTRLEN]; - uint16_t listen_port = 0; - if (parse_ip_port(listen, listen_ip, sizeof(listen_ip), &listen_port) != 0) { - fprintf(stderr, "mqproxy server: invalid --listen address: %s\n", listen); - return 2; - } - - int rc = 1; - struct event_base *base = NULL; - mq_transport_t *transport = NULL; - mq_runtime_t *rt = NULL; - mq_server_t *server = NULL; - mq_gw_server_t *gws = NULL; - struct srv_metrics_ctx smctx = {0}; /* metrics tick ctx; filled before arming */ - struct event *sint = NULL, *sterm = NULL; - struct event *metrics_ev = NULL; - - base = event_base_new(); - if (!base) { - MQ_LOGE("failed to create event base"); - goto out; - } - /* The runtime installs its callbacks onto the transport via - * mq_transport_set_callbacks (in mq_runtime_new). */ - transport = mq_transport_new_server(cert, key); - if (!transport) { - MQ_LOGE("failed to create server transport (cert=%s key=%s)", cert, key); - goto out; - } - mq_transport_set_max_conns(transport, (uint32_t)max_conns); - if (qlog_dir) { - const char *qpath = NULL; - if (mq_transport_enable_qlog(transport, qlog_dir, &qpath) != 0) { - MQ_LOGE("failed to enable qlog in %s", qlog_dir); - goto out; - } - MQ_LOGI("server qlog -> %s", qpath); - } - rt = mq_runtime_new(transport, base); - if (!rt) { - MQ_LOGE("failed to create runtime"); - goto out; - } - if (mq_runtime_open_udp_path(rt, listen_ip, listen_port) != 0) { - MQ_LOGE("failed to bind listen path %s:%u", listen_ip, listen_port); - goto out; - } - server = mq_server_new(transport, rt, token, cc, (uint64_t)udp_idle_timeout_s * 1000u, - udp_enabled); - if (!server) { - MQ_LOGE("failed to create server"); - goto out; - } - /* HTTP gateway origin bridge (mq_gw_server: H3 request → curl → origin). On - * by default; --no-gateway opts out. Because it is default-on, a failure to - * stand it up is FATAL (exit non-zero) rather than a silent degrade — a - * server that was meant to gateway but isn't is worse than a loud failure. - * mq_gw_server_new calls mq_h3_init internally and owns the H3 hooks; the - * created mq_h3 is reclaimed in the teardown block per the header contract. */ - if (gateway_enabled) { - long connect_timeout_s = gw_origin_connect_timeout_s(); - gws = mq_gw_server_new(transport, rt, token, origin_ca, connect_timeout_s); - if (!gws) { - MQ_LOGE("failed to create HTTP gateway server (origin_ca=%s, " - "connect_timeout=%lds)", - origin_ca ? origin_ca : "(system)", connect_timeout_s); - goto out; - } - if (request_metrics) mq_gw_server_set_request_metrics(gws, 1); - if (masquerade) mq_gw_server_set_masquerade(gws, 1); - mq_gw_server_set_cache(gws, cache_max_bytes); /* 0 ⇒ disabled (no-op) */ - } - if (install_signal_handlers(base, rt, &sint, &sterm) != 0) { - MQ_LOGE("failed to install signal handlers"); - goto out; - } - - MQ_LOGI("mqproxy server listening on %s:%u (cc=%s, sched=%s, gateway=%s, udp=%s, " - "udp-idle=%lds)", - listen_ip, listen_port, mq_cc_name(cc), mq_sched_name(sched), - gateway_enabled ? "on" : "off", udp_enabled ? "on" : "off", - udp_idle_timeout_s); - - /* Phase 5c periodic metrics (opt-in via --metrics-interval). Dumps the - * most-recently-accepted conn's per-path stats every interval. */ - if (metrics_interval_ms > 0) { - smctx.server = server; - smctx.gws = gws; - metrics_ev = event_new(base, -1, EV_PERSIST, srv_metrics_tick, &smctx); - if (metrics_ev) { - struct timeval tv = {.tv_sec = (time_t)(metrics_interval_ms / 1000), - .tv_usec = - (suseconds_t)((metrics_interval_ms % 1000) * 1000)}; - event_add(metrics_ev, &tv); - } - } - - mq_runtime_run(rt); - rc = 0; - -out: - /* Teardown order — the TCP-proxy server core and the HTTP-gateway origin - * bridge have ordering contracts that pull in OPPOSITE directions across - * mq_transport_free; both are honored below. - * - * (A) TCP-proxy server core (mq_server + runtime): the server's per-conn - * state + the runtime are both touched by conn-close callbacks fired - * while the engine tears down its connections (inside mq_transport_free): - * - the transport's send_udp callback (the runtime, as cbs.user) may be - * invoked for a final CONNECTION_CLOSE — so the RUNTIME must outlive - * mq_transport_free; - * - conn-close fires the server's on_state(CLOSED) which reaps flows and - * reads mq_runtime_base(rt) — so the SERVER and the RUNTIME must both - * be alive during mq_transport_free. - * Therefore free the TRANSPORT first (engine destroy, callbacks land on - * the live runtime + server), then the runtime (closes sockets/timer; - * does not free base), then the server. - * - * (B) HTTP-gateway bridge (mq_gw_server + the mq_h3 it created): the - * SANCTIONED order (mq_gw_server.h) is gw_server_free → mq_h3_free → - * mq_transport_free. gw_server_free MUST run while the H3 engine is STILL - * LIVE (it touches r->req on live in-flight requests via - * mq_h3_req_set_cbs(NULL,...)); mq_h3_free MUST precede mq_transport_free. - * Capture the h3 handle BEFORE gw_server_free — the accessor reads the - * gw_server struct, which free() releases. - * - * Combined order: - * signal events - * -> gw_server_free (engine live; detaches in-flight H3 req cbs, aborts - * origin requests, frees the origin client) - * -> mq_h3_free (the gw_server's H3 engine; gateway/tcp ALPN conn - * graphs are independent, so this does not disturb the - * tcp conns mq_transport_free later tears down) - * -> mq_transport_free (fires graph-A conn-close cbs into live runtime+server) - * -> runtime free - * -> mq_server_free - * -> base free (CLI owns base). */ - if (metrics_ev) { - event_del(metrics_ev); - event_free(metrics_ev); - } - if (sint) event_free(sint); - if (sterm) event_free(sterm); - { - mq_h3_t *gw_h3 = gws ? mq_gw_server_h3(gws) : NULL; - if (gws) mq_gw_server_free(gws); - if (gw_h3) mq_h3_free(gw_h3); - } - if (transport) mq_transport_free(transport); - if (rt) mq_runtime_free(rt); - if (server) mq_server_free(server); - if (base) event_base_free(base); - return rc; -} - -/* ── client subcommand ──────────────────────────────────────────────────────*/ - -/* Context for the on_auth callback: carries the SOCKS5 listener handle so the - * callback can drive the UDP-availability tri-state after auth settles. - * socks5_l may be NULL (HTTP-only client); mq_listener_set_udp_availability - * is a no-op on NULL, so the glue is always safe to call. */ -struct client_auth_ctx { - mq_client_t *client; - mq_listener_t *socks5_l; -}; - -/* Called once by mq_client when the AUTH_RESPONSE arrives. Drives the SOCKS5 - * listener's UDP-availability tri-state (Task 6.3 supply point): - * ok=1 → mq_client_udp_available() — 1 if server advertised the feature AND - * datagram mss > 0, else 0. - * ok=0 → 0 (auth failed; sweep any pre-auth ASSOCIATE sessions). */ -static void -client_on_auth(int ok, mq_auth_err_t err, void *user) -{ - struct client_auth_ctx *ctx = (struct client_auth_ctx *)user; - (void)err; - int avail = ok ? mq_client_udp_available(ctx->client) : 0; - mq_listener_set_udp_availability(ctx->socks5_l, avail); -} - -static int -cmd_client(int argc, char **argv) -{ - const char *server = NULL; - const char *token = NULL; - const char *socks5 = NULL; - const char *http_connect = NULL; - const char *gateway = NULL; - const char *client_id = "mqproxy"; - const char *qlog_dir = NULL; - const char *cc_name = NULL; - mq_cc_t cc = MQ_CC_DEFAULT; - const char *sched_arg = NULL; - mq_sched_t sched = MQ_SCHED_DEFAULT; - const char *paths[MQ_MAX_EXTRA_PATHS]; - size_t npaths = 0; - long keepalive_idle_s = 30; /* --keepalive-idle ; 0 = disable */ - int reconnect_enabled = 1; /* --reconnect / --no-reconnect */ - long reconnect_max_backoff_s = 30; /* --reconnect-max-backoff */ - uint64_t metrics_interval_ms = 0; /* --metrics-interval ; 0 = off */ - const char *tproxy_addr = NULL; /* --tproxy ; NULL = disabled */ - const char *tproxy_mode_s = "redirect"; /* --tproxy-mode */ - long tproxy_fwmark = 1; /* --tproxy-fwmark */ - long tproxy_table = 100; /* --tproxy-table */ - long tproxy_dport = 443; /* --tproxy-dport (captured TCP dport) */ - int setup_redirect = 0; /* --setup-redirect */ - long tproxy_uid = -1; /* --tproxy-uid; -1 = geteuid() */ - - /* MITM (Phase 7 Slice 3). Populated by --mitm / [Mitm] config in Tasks 14/15; - * inert (off) until then — mitm_enabled defaults 0, so the entire MITM wiring - * below is compiled-in but never taken, leaving existing behavior unchanged. */ - int mitm_enabled = 0; - const char *ca_cert = NULL; /* --ca-cert (MITM signing CA) */ - const char *ca_key = NULL; /* --ca-key (MITM signing CA key) */ - - /* MITM ignore-hosts collection. CLI --ignore-host(s) + [Mitm] IgnoreHosts - * entries accumulate here (union), then feed the mq_ignore_hosts list built - * in the MITM block below. Entries point into argv (optarg, alive for the - * whole main()) or into ig_buf (comma-split tokens, also function-scoped); - * mq_ignore_hosts_add COPIES the pattern, so all pointers are add()-safe. */ - const char *cli_ignore_hosts[MQ_CONFIG_MAX_IGNORE_HOSTS]; - int n_cli_ignore = 0; - /* Backing storage for --ignore-hosts comma-split tokens (one NUL-terminated - * copy of each optarg; tokens point into it). 256 bytes/entry matches the - * config-side cap; over-long inputs are truncated by snprintf below. */ - static char ig_buf[MQ_CONFIG_MAX_IGNORE_HOSTS][256]; - int n_ig_buf = 0; - - /* Seed from --config before getopt (defaults < file < CLI). fcfg is - * function-scoped so the const char* aliases stay valid for the function. */ - mq_file_config_t fcfg; - mq_config_defaults(&fcfg); - const char *config_path = find_config_arg(argc, argv); - if (config_path) { - if (mq_config_load(&fcfg, config_path, 0 /*client*/) != 0) return 2; - if (mq_config_perms_insecure(config_path)) - MQ_LOGW("config %s is group/world-readable; chmod 0600 to protect the token", - config_path); - if (fcfg.server_addr[0]) server = fcfg.server_addr; - if (fcfg.token[0]) token = fcfg.token; - if (fcfg.socks5[0]) socks5 = fcfg.socks5; - if (fcfg.http_connect[0]) http_connect = fcfg.http_connect; - if (fcfg.gw_listen[0]) gateway = fcfg.gw_listen; - /* client_id default ("mqproxy") is mirrored in mq_config_defaults, so - * this always repoints to fcfg.client_id — value-identical, lifetime-safe. - * (The fcfg.x[0] guard can't tell file-set from default here; harmless.) */ - if (fcfg.client_id[0]) client_id = fcfg.client_id; - if (fcfg.qlog[0]) qlog_dir = fcfg.qlog; - if (fcfg.cc[0]) cc_name = fcfg.cc; - if (fcfg.scheduler[0]) sched_arg = fcfg.scheduler; - keepalive_idle_s = fcfg.keepalive_idle_s; - reconnect_enabled = fcfg.reconnect; - reconnect_max_backoff_s = fcfg.reconnect_max_backoff_s; - if (fcfg.metrics_interval_s > 0) - metrics_interval_ms = (uint64_t)fcfg.metrics_interval_s * 1000; - for (int i = 0; i < fcfg.n_paths && npaths < MQ_MAX_EXTRA_PATHS; i++) - paths[npaths++] = fcfg.paths[i]; - if (fcfg.tproxy[0]) tproxy_addr = fcfg.tproxy; - if (fcfg.tproxy_mode[0]) tproxy_mode_s = fcfg.tproxy_mode; - tproxy_fwmark = fcfg.tproxy_fwmark; - tproxy_table = fcfg.tproxy_table; - tproxy_dport = fcfg.tproxy_dport; - setup_redirect = fcfg.setup_redirect; - tproxy_uid = fcfg.tproxy_skip_uid; - /* MITM ([Mitm] section). Scalars follow seed-then-override (CLI switch - * below wins); ignore-hosts accumulate (config + CLI union). */ - if (fcfg.mitm_enabled) mitm_enabled = 1; - if (fcfg.ca_cert[0]) ca_cert = fcfg.ca_cert; - if (fcfg.ca_key[0]) ca_key = fcfg.ca_key; - for (int i = 0; - i < fcfg.n_ignore_hosts && n_cli_ignore < MQ_CONFIG_MAX_IGNORE_HOSTS; i++) - cli_ignore_hosts[n_cli_ignore++] = fcfg.ignore_hosts[i]; - } - - enum { - OPT_SERVER = 256, - OPT_TOKEN, - OPT_SOCKS5, - OPT_HTTP, - OPT_GATEWAY, - OPT_PATH, - OPT_CLIENT_ID, - OPT_QLOG, - OPT_CC, - OPT_SCHEDULER, - OPT_KEEPALIVE_IDLE, - OPT_RECONNECT, - OPT_NO_RECONNECT, - OPT_RECONNECT_MAX_BACKOFF, - OPT_METRICS_INTERVAL, - OPT_CONFIG, - OPT_TPROXY, - OPT_TPROXY_MODE, - OPT_TPROXY_FWMARK, - OPT_TPROXY_TABLE, - OPT_TPROXY_DPORT, - OPT_SETUP_REDIRECT, - OPT_TPROXY_UID, - OPT_MITM, - OPT_CA_CERT, - OPT_CA_KEY, - OPT_IGNORE_HOST, - OPT_IGNORE_HOSTS, - }; - static const struct option longopts[] = { - {"server", required_argument, NULL, OPT_SERVER}, - {"token", required_argument, NULL, OPT_TOKEN}, - {"socks5", required_argument, NULL, OPT_SOCKS5}, - {"http-connect", required_argument, NULL, OPT_HTTP}, - {"gateway", required_argument, NULL, OPT_GATEWAY}, - {"path", required_argument, NULL, OPT_PATH}, - {"client-id", required_argument, NULL, OPT_CLIENT_ID}, - {"qlog", required_argument, NULL, OPT_QLOG}, - {"cc", required_argument, NULL, OPT_CC}, - {"scheduler", required_argument, NULL, OPT_SCHEDULER}, - {"keepalive-idle", required_argument, NULL, OPT_KEEPALIVE_IDLE}, - {"reconnect", no_argument, NULL, OPT_RECONNECT}, - {"no-reconnect", no_argument, NULL, OPT_NO_RECONNECT}, - {"reconnect-max-backoff", required_argument, NULL, OPT_RECONNECT_MAX_BACKOFF}, - {"metrics-interval", required_argument, NULL, OPT_METRICS_INTERVAL}, - {"config", required_argument, NULL, OPT_CONFIG}, - {"tproxy", required_argument, NULL, OPT_TPROXY}, - {"tproxy-mode", required_argument, NULL, OPT_TPROXY_MODE}, - {"tproxy-fwmark", required_argument, NULL, OPT_TPROXY_FWMARK}, - {"tproxy-table", required_argument, NULL, OPT_TPROXY_TABLE}, - {"tproxy-dport", required_argument, NULL, OPT_TPROXY_DPORT}, - {"setup-redirect", no_argument, NULL, OPT_SETUP_REDIRECT}, - {"tproxy-uid", required_argument, NULL, OPT_TPROXY_UID}, - {"mitm", no_argument, NULL, OPT_MITM}, - {"ca-cert", required_argument, NULL, OPT_CA_CERT}, - {"ca-key", required_argument, NULL, OPT_CA_KEY}, - {"ignore-host", required_argument, NULL, OPT_IGNORE_HOST}, - {"ignore-hosts", required_argument, NULL, OPT_IGNORE_HOSTS}, - {"help", no_argument, NULL, 'h'}, - {NULL, 0, NULL, 0}, - }; - - optind = 1; - int c; - while ((c = getopt_long(argc, argv, "h", longopts, NULL)) != -1) { - switch (c) { - case OPT_SERVER: server = optarg; break; - case OPT_TOKEN: token = optarg; break; - case OPT_SOCKS5: socks5 = optarg; break; - case OPT_HTTP: http_connect = optarg; break; - case OPT_GATEWAY: gateway = optarg; break; - case OPT_PATH: - if (npaths < MQ_MAX_EXTRA_PATHS) { - paths[npaths++] = optarg; - } else { - MQ_LOGW("too many --path options (max %d); ignoring %s", - MQ_MAX_EXTRA_PATHS, optarg); - } - break; - case OPT_CLIENT_ID: client_id = optarg; break; - case OPT_QLOG: qlog_dir = optarg; break; - case OPT_CC: cc_name = optarg; break; - case OPT_SCHEDULER: sched_arg = optarg; break; - case OPT_KEEPALIVE_IDLE: { - char *endp = NULL; - errno = 0; - long v = strtol(optarg, &endp, 10); - if (errno != 0 || endp == optarg || *endp != '\0' || v < 0) { - fprintf( - stderr, - "mqproxy client: invalid --keepalive-idle '%s' (must be >= 0)\n\n", - optarg); - usage_client(stderr); - return 2; - } - keepalive_idle_s = v; - break; - } - case OPT_RECONNECT: reconnect_enabled = 1; break; - case OPT_NO_RECONNECT: reconnect_enabled = 0; break; - case OPT_RECONNECT_MAX_BACKOFF: { - char *endp = NULL; - errno = 0; - long v = strtol(optarg, &endp, 10); - if (errno != 0 || endp == optarg || *endp != '\0' || v <= 0) { - fprintf(stderr, - "mqproxy client: invalid --reconnect-max-backoff '%s' " - "(must be > 0)\n\n", - optarg); - usage_client(stderr); - return 2; - } - reconnect_max_backoff_s = v; - break; - } - case OPT_METRICS_INTERVAL: { - char *end = NULL; - errno = 0; - long v = strtol(optarg, &end, 10); /* seconds */ - if (errno != 0 || end == optarg || *end != '\0' || v <= 0) { - fprintf(stderr, - "mqproxy client: invalid --metrics-interval '%s' (must be > 0; " - "omit to disable)\n\n", - optarg); - usage_client(stderr); - return 2; - } - metrics_interval_ms = (uint64_t)v * 1000; - break; - } - case OPT_CONFIG: break; /* already consumed pre-getopt by find_config_arg */ - case OPT_TPROXY: tproxy_addr = optarg; break; - case OPT_TPROXY_MODE: tproxy_mode_s = optarg; break; - case OPT_TPROXY_FWMARK: { - char *endp = NULL; - errno = 0; - long v = strtol(optarg, &endp, 10); - if (errno != 0 || endp == optarg || *endp != '\0' || v <= 0 || v > INT_MAX) { - fprintf(stderr, - "mqproxy client: invalid --tproxy-fwmark '%s' (must be " - "1..2147483647)\n\n", - optarg); - usage_client(stderr); - return 2; - } - tproxy_fwmark = v; - break; - } - case OPT_TPROXY_TABLE: { - char *endp = NULL; - errno = 0; - long v = strtol(optarg, &endp, 10); - if (errno != 0 || endp == optarg || *endp != '\0' || v <= 0 || v > 65535) { - fprintf( - stderr, - "mqproxy client: invalid --tproxy-table '%s' (must be 1..65535)\n\n", - optarg); - usage_client(stderr); - return 2; - } - tproxy_table = v; - break; - } - case OPT_TPROXY_DPORT: { - char *endp = NULL; - errno = 0; - long v = strtol(optarg, &endp, 10); - if (errno != 0 || endp == optarg || *endp != '\0' || v <= 0 || v > 65535) { - fprintf( - stderr, - "mqproxy client: invalid --tproxy-dport '%s' (must be 1..65535)\n\n", - optarg); - usage_client(stderr); - return 2; - } - tproxy_dport = v; - break; - } - case OPT_SETUP_REDIRECT: setup_redirect = 1; break; - case OPT_TPROXY_UID: { - char *endp = NULL; - errno = 0; - long v = strtol(optarg, &endp, 10); - if (errno != 0 || endp == optarg || *endp != '\0' || v < 0 || v > INT_MAX) { - fprintf(stderr, - "mqproxy client: invalid --tproxy-uid '%s' (must be " - "0..2147483647)\n\n", - optarg); - usage_client(stderr); - return 2; - } - tproxy_uid = v; - break; - } - case OPT_MITM: mitm_enabled = 1; break; - case OPT_CA_CERT: ca_cert = optarg; break; - case OPT_CA_KEY: ca_key = optarg; break; - case OPT_IGNORE_HOST: - if (n_cli_ignore < MQ_CONFIG_MAX_IGNORE_HOSTS) { - cli_ignore_hosts[n_cli_ignore++] = optarg; - } else { - MQ_LOGW("too many ignore-host entries (max %d); ignoring %s", - MQ_CONFIG_MAX_IGNORE_HOSTS, optarg); - } - break; - case OPT_IGNORE_HOSTS: { - /* Comma-split into a function-scoped backing buffer so the tokens - * outlive this switch (mq_ignore_hosts_add copies them later). */ - if (n_ig_buf >= MQ_CONFIG_MAX_IGNORE_HOSTS) { - MQ_LOGW("too many --ignore-hosts buffers (max %d); ignoring %s", - MQ_CONFIG_MAX_IGNORE_HOSTS, optarg); - break; - } - snprintf(ig_buf[n_ig_buf], sizeof(ig_buf[n_ig_buf]), "%s", optarg); - char *save = NULL; - for (char *tok = strtok_r(ig_buf[n_ig_buf], ",", &save); tok; - tok = strtok_r(NULL, ",", &save)) { - if (*tok == '\0') continue; /* skip empty (e.g. "a,,b") */ - if (n_cli_ignore < MQ_CONFIG_MAX_IGNORE_HOSTS) { - cli_ignore_hosts[n_cli_ignore++] = tok; - } else { - MQ_LOGW("too many ignore-host entries (max %d); ignoring %s", - MQ_CONFIG_MAX_IGNORE_HOSTS, tok); - } - } - n_ig_buf++; - break; - } - case 'h': usage_client(stdout); return 0; - default: usage_client(stderr); return 2; - } - } - - if (cc_name) { - int ok = 0; - cc = mq_cc_from_string(cc_name, &ok); - if (!ok) { - fprintf(stderr, "mqproxy client: invalid --cc '%s' (bbr2|bbr|cubic)\n\n", - cc_name); - usage_client(stderr); - return 2; - } - } - if (sched_arg) { - int sched_ok = 0; - sched = mq_sched_from_string(sched_arg, &sched_ok); - if (!sched_ok) { - fprintf(stderr, - "mqproxy client: invalid --scheduler '%s' (minrtt|backup|wlb)\n\n", - sched_arg); - usage_client(stderr); - return 2; - } - } - mq_conn_set_scheduler(sched); - - /* Validate --tproxy-mode: only "redirect" and "tproxy" are accepted. */ - mq_capture_mode_t tproxy_capture_mode = MQ_CAPTURE_REDIRECT; - if (tproxy_mode_s) { - if (strcmp(tproxy_mode_s, "redirect") == 0) { - tproxy_capture_mode = MQ_CAPTURE_REDIRECT; - } else if (strcmp(tproxy_mode_s, "tproxy") == 0) { - tproxy_capture_mode = MQ_CAPTURE_TPROXY; - } else { - fprintf(stderr, - "mqproxy client: invalid --tproxy-mode '%s' (redirect|tproxy)\n\n", - tproxy_mode_s); - usage_client(stderr); - return 2; - } - } - - if (!server) { - fprintf(stderr, "mqproxy client: missing required --server\n\n"); - usage_client(stderr); - return 2; - } - if (!token) { - fprintf(stderr, "mqproxy client: missing required --token\n\n"); - usage_client(stderr); - return 2; - } - if (!socks5 && !http_connect && !gateway && !tproxy_addr) { - fprintf(stderr, "mqproxy client: at least one ingress is required " - "(--socks5, --http-connect, --gateway, or --tproxy)\n\n"); - usage_client(stderr); - return 2; - } - - /* MITM fail-closed validation. --mitm rides the transparent-capture ingress - * and forges leaf certs from a signing CA, so it REQUIRES --tproxy and both - * --ca-cert/--ca-key. A no-archive build (built without BoringSSL) cannot - * terminate TLS at all → hard error so MITM never silently no-ops. */ - if (mitm_enabled) { -#if !MQ_MITM_AVAILABLE - fprintf(stderr, "mqproxy client: --mitm unavailable: built without BoringSSL " - "archives — run scripts/build-xquic.sh\n"); - return 2; -#else - if (!tproxy_addr) { - fprintf(stderr, "mqproxy client: --mitm requires --tproxy (MITM rides the " - "transparent-capture ingress)\n\n"); - usage_client(stderr); - return 2; - } - if (!ca_cert || !ca_key) { - fprintf(stderr, "mqproxy client: --mitm requires --ca-cert and --ca-key (the " - "signing CA used to forge per-host leaf certificates)\n\n"); - usage_client(stderr); - return 2; - } -#endif - } - - char server_ip[INET6_ADDRSTRLEN]; - uint16_t server_port = 0; - if (parse_ip_port(server, server_ip, sizeof(server_ip), &server_port) != 0) { - fprintf(stderr, "mqproxy client: invalid --server address: %s\n", server); - return 2; - } - char socks5_ip[INET6_ADDRSTRLEN]; - uint16_t socks5_port = 0; - if (socks5 && - parse_ip_port(socks5, socks5_ip, sizeof(socks5_ip), &socks5_port) != 0) { - fprintf(stderr, "mqproxy client: invalid --socks5 address: %s\n", socks5); - return 2; - } - char http_ip[INET6_ADDRSTRLEN]; - uint16_t http_port = 0; - if (http_connect && - parse_ip_port(http_connect, http_ip, sizeof(http_ip), &http_port) != 0) { - fprintf(stderr, "mqproxy client: invalid --http-connect address: %s\n", - http_connect); - return 2; - } - char gw_ip[INET6_ADDRSTRLEN]; - uint16_t gw_port = 0; - if (gateway && parse_ip_port(gateway, gw_ip, sizeof(gw_ip), &gw_port) != 0) { - fprintf(stderr, "mqproxy client: invalid --gateway address: %s\n", gateway); - return 2; - } - char tproxy_ip[INET6_ADDRSTRLEN]; - uint16_t tproxy_port = 0; - if (tproxy_addr && - parse_ip_port(tproxy_addr, tproxy_ip, sizeof(tproxy_ip), &tproxy_port) != 0) { - fprintf(stderr, "mqproxy client: invalid --tproxy address: %s\n", tproxy_addr); - return 2; - } - - /* The TCP-proxy core (mq_client) is needed iff a SOCKS5, HTTP CONNECT, or - * transparent-capture (tproxy) ingress is requested — all three ride - * mq_client's tcp_open core. The gateway ingress is independent (its own - * mq_gw_client conn + auth), so a gateway-only client skips mq_client. */ - const int need_client = - (socks5 != NULL) || (http_connect != NULL) || (tproxy_addr != NULL); - - /* The primary local bind for the client path. If --path was given, the - * first one is the primary bind; each extra --path is brought up as an - * additional MPQUIC path once the connection is multipath-ready (wired - * below via mq_client_add_paths). Otherwise bind the unspecified IPv4 - * address with an ephemeral port. */ - const char *primary_ip = (npaths > 0) ? paths[0] : "0.0.0.0"; - - /* Seed the process-global RNG once per client process so that reconnect - * backoff jitter is decorrelated across fleet processes that start or drop - * connectivity at the same time (e.g. a Starlink+LTE fleet dropping at - * once). Without seeding, all processes draw the identical jitter sequence - * and reconnect in lockstep, defeating the anti-thundering-herd purpose. */ - srandom((unsigned)(getpid() ^ (unsigned)time(NULL))); - - int rc = 1; - struct event_base *base = NULL; - mq_transport_t *transport = NULL; - mq_runtime_t *rt = NULL; - mq_client_t *client = NULL; - mq_listener_t *socks5_l = NULL; - mq_listener_t *http_l = NULL; - mq_tproxy_listener_t *tproxy_l = NULL; - mq_tproxy_setup_t *tproxy_setup = NULL; - mq_h3_t *h3 = NULL; - mq_gw_client_t *gwc = NULL; - mq_gw_fetch_adapter_t *fetch_adp = NULL; - mq_fetch_listener_t *fetch_l = NULL; -#if MQ_MITM_AVAILABLE - /* MITM orchestrator handles (Slice 3). NULL when MITM is off (the common - * case until Tasks 14/15 set mitm_enabled); freed in teardown order below. */ - mq_ignore_hosts_t *ign = NULL; - mq_mitm_core_t *core = NULL; - mq_mitm_ctx_t *mitm_ctx = NULL; -#endif - struct event *sint = NULL, *sterm = NULL; - struct event *metrics_ev = NULL; - struct client_auth_ctx auth_ctx = {NULL, NULL}; - - base = event_base_new(); - if (!base) { - MQ_LOGE("failed to create event base"); - goto out; - } - /* The runtime installs its callbacks onto the transport via - * mq_transport_set_callbacks (in mq_runtime_new). */ - transport = mq_transport_new(/*is_server=*/0); - if (!transport) { - MQ_LOGE("failed to create client transport"); - goto out; - } - if (qlog_dir) { - const char *qpath = NULL; - if (mq_transport_enable_qlog(transport, qlog_dir, &qpath) != 0) { - MQ_LOGE("failed to enable qlog in %s", qlog_dir); - goto out; - } - MQ_LOGI("client qlog -> %s", qpath); - } - rt = mq_runtime_new(transport, base); - if (!rt) { - MQ_LOGE("failed to create runtime"); - goto out; - } - if (mq_runtime_open_udp_path(rt, primary_ip, 0) != 0) { - MQ_LOGE("failed to bind primary path %s", primary_ip); - goto out; - } - - /* Gateway H3 tunnel (mq_h3 + mq_gw_client). Needed by BOTH the --gateway fetch - * ingress AND the live MITM path (the MITM orchestrator submits H2 requests - * over this same gwc tunnel). HOISTED here — before the tproxy listener below — - * because the listener captures its open_fn/core at creation, so the MITM ctx - * (which references gwc) must exist first. The fetch listener/adapter that turn - * the gwc into a local fetch API stay gated on --gateway only (MITM has no - * fetch ingress). The single `if (gwc) mq_gw_client_free(gwc)` teardown covers - * both callers. mq_h3_init takes NULL server hooks (pure client); the gateway - * conn is established EAGERLY inside mq_gw_client_new. */ - if (gateway || mitm_enabled) { - h3 = mq_h3_init(transport, NULL, NULL, NULL); - if (!h3) { - MQ_LOGE("failed to init H3 stack for gateway/mitm"); - goto out; - } - gwc = mq_gw_client_new(transport, rt, h3, server_ip, server_port, token, cc, - (uint64_t)keepalive_idle_s * 1000u, reconnect_enabled, - (uint64_t)reconnect_max_backoff_s * 1000u); - if (!gwc) { - MQ_LOGE("failed to create gateway client (conn to %s:%u)", server_ip, - server_port); - goto out; - } - /* The gateway conn gets its OWN extra paths — independent of mq_client's. - * Use the same --path list (paths[1..]); paths[0] is the primary bind, - * already in effect on the shared transport. */ - if (npaths > 1) { - int added = mq_gw_client_add_paths(gwc, &paths[1], npaths - 1); - if (added < 0) { - MQ_LOGE("failed to register extra gateway paths"); - goto out; - } - MQ_LOGI("registered %d extra gateway multipath bind(s)", added); - } - } - - /* TCP-proxy core (mq_client) + its SOCKS5/HTTP CONNECT ingress. Only created - * when a SOCKS5 or HTTP CONNECT ingress was requested; a gateway-only client - * skips it (the gateway conn below is independent, with its own auth). */ - if (need_client) { - /* Window + multipath conn settings are applied inside mq_client_new - * (mq_conn_apply_mp_settings). */ - client = - mq_client_new(transport, rt, server_ip, server_port, client_id, token, cc); - if (!client) { - MQ_LOGE("failed to create client"); - goto out; - } - mq_client_set_keepalive(client, (uint64_t)keepalive_idle_s * 1000u); - mq_client_set_reconnect(client, reconnect_enabled, - (uint64_t)reconnect_max_backoff_s * 1000u); - if (mq_client_start(client) != 0) { - MQ_LOGE("failed to start client connection to %s:%u", server_ip, server_port); - goto out; - } - - /* Bring up each extra --path (paths[1..]) as an additional MPQUIC path - * once the connection is multipath-ready (deferred internally via - * mq_conn_add_path). paths[0] is the primary bind handled above. */ - if (npaths > 1) { - int added = mq_client_add_paths(client, &paths[1], npaths - 1); - if (added < 0) { - MQ_LOGE("failed to register extra paths"); - goto out; - } - MQ_LOGI("registered %d extra multipath bind(s) (added once mp-ready)", added); - } - - mq_tcp_open_fn open_fn = mq_client_tcp_open_fn(); - void *open_core = mq_client_tcp_open_core(client); - - if (socks5) { - /* SOCKS5 listener also services CMD UDP ASSOCIATE via the UDP relay - * boundary (mq_ingress.h). The UDP `core` is the relay table - * (mq_udp_cli_t*), distinct from the TCP `core` (mq_client_t*). The - * availability tri-state stays at its -1 default until the on_auth - * glue (Task 6.4) calls mq_listener_set_udp_availability. */ - socks5_l = mq_socks5_listener_new( - base, socks5_ip, socks5_port, open_fn, open_core, mq_client_udp_open_fn(), - mq_client_udp_send_fn(), mq_client_udp_close_fn(), - mq_client_udp_open_core(client)); - if (!socks5_l) { - MQ_LOGE("failed to bind SOCKS5 listener on %s:%u", socks5_ip, - socks5_port); - goto out; - } - } - if (http_connect) { - /* HTTP CONNECT has no ASSOCIATE; pass the udp quad NULL. */ - http_l = mq_http_connect_listener_new(base, http_ip, http_port, open_fn, - open_core, NULL, NULL, NULL, NULL); - if (!http_l) { - MQ_LOGE("failed to bind HTTP CONNECT listener on %s:%u", http_ip, - http_port); - goto out; - } - } - /* The tproxy listener captures its open_fn/core at creation. By default - * it rides the plain TCP-proxy boundary (open_fn/open_core above). When - * MITM is enabled, it instead rides the MITM orchestrator - * (mq_mitm_conn_open + the mq_mitm_ctx), which terminates TLS per-SNI, - * forges a leaf, and splices ignore-hosts flows back onto the plain - * boundary. The orchestrator MUST be built BEFORE the listener. */ - mq_tcp_open_fn tproxy_open_fn = open_fn; - void *tproxy_open_core = open_core; -#if !MQ_MITM_AVAILABLE - /* ca_cert/ca_key + the ignore-hosts collection are consumed only by the - * MITM block below; in a no-archive build that block is compiled out, so - * silence unused-var. (--mitm itself hard-errors at validation here.) */ - (void)ca_cert; - (void)ca_key; - (void)cli_ignore_hosts; - (void)n_cli_ignore; - (void)ig_buf; - (void)n_ig_buf; -#endif -#if MQ_MITM_AVAILABLE - if (mitm_enabled) { - /* Build the ignore-hosts list and feed the accumulated CLI - * --ignore-host(s) + [Mitm] IgnoreHosts entries (union) into it. - * A malformed/over-full single entry warns + is skipped — it must - * not kill startup (the rest of the list is still useful). */ - ign = mq_ignore_hosts_new(); - if (!ign) { - MQ_LOGE("mitm: failed to allocate ignore-hosts list"); - goto out; - } - for (int i = 0; i < n_cli_ignore; i++) { - if (mq_ignore_hosts_add(ign, cli_ignore_hosts[i]) != 0) - MQ_LOGW("mitm: skipping invalid/rejected ignore-host entry '%s'", - cli_ignore_hosts[i]); - } - /* NULL opts → the core applies (and clamps) its documented defaults - * (cache_size=256, leaf_ttl_sec=86400). Single source of truth — do - * not duplicate those constants here (F-1). */ - core = mq_mitm_core_create(ca_cert, ca_key, NULL); - if (!core) { - MQ_LOGE("mitm: failed to load CA from --ca-cert/--ca-key"); - goto out; - } - /* The opaque (ignore-hosts) fallback splices through the plain - * mq_client TCP boundary. `client` is always created for a tproxy - * run (need_client includes tproxy_addr), so open_core is valid even - * under --mitm without --socks5/--http-connect. */ - mitm_ctx = mq_mitm_ctx_new(core, ign, gwc, mq_client_tcp_open_fn(), - mq_client_tcp_open_core(client), base); - if (!mitm_ctx) { - MQ_LOGE("mitm: failed to create orchestrator context"); - goto out; - } - tproxy_open_fn = mq_mitm_conn_open; - tproxy_open_core = mitm_ctx; - } -#endif - if (tproxy_addr) { - /* Transparent capture: kernel redirects intercepted TCP connections - * to this listener; no in-band protocol is spoken toward the client. - * open_fn/open_core ride the plain TCP-proxy boundary, OR — when MITM - * is enabled — the MITM orchestrator selected just above. */ - tproxy_l = - mq_tproxy_listener_new(base, tproxy_ip, tproxy_port, tproxy_capture_mode, - tproxy_open_fn, tproxy_open_core); - if (!tproxy_l) { - MQ_LOGE("failed to bind tproxy listener on %s:%u", tproxy_ip, - tproxy_port); - goto out; - } - - if (setup_redirect) { - uid_t skip_uid = - (tproxy_uid < 0) ? (uid_t)-1 : (uid_t)(unsigned long)tproxy_uid; - tproxy_setup = mq_tproxy_setup_new(tproxy_capture_mode, tproxy_ip, - mq_tproxy_listener_port(tproxy_l), - (uint16_t)tproxy_dport, skip_uid, - (int)tproxy_fwmark, (int)tproxy_table); - if (tproxy_setup) { - if (mq_tproxy_setup_install(tproxy_setup) != 0) - MQ_LOGW("tproxy: firewall setup failed (rules may be partial)"); - } else { - MQ_LOGW("tproxy: OOM allocating setup handle; skipping rule install"); - } - } - } - - /* Wire the auth-result callback: on auth settle, propagate the UDP - * relay availability tri-state to the SOCKS5 listener so it can - * admit or sweep ASSOCIATE sessions (Task 6.3 supply point). - * socks5_l may be NULL here (HTTP-only client) — that is safe because - * mq_listener_set_udp_availability(NULL, ...) is a no-op. */ - auth_ctx.client = client; - auth_ctx.socks5_l = socks5_l; - mq_client_set_on_auth(client, client_on_auth, &auth_ctx); - } - - /* HTTP gateway ingress (--gateway): the local fetch-API listener fronting the - * gwc H3 tunnel hoisted above. The gwc + h3 are created earlier (before the - * tproxy listener) since the MITM path shares them; here we only build the - * fetch adapter/listener, which are gateway-specific (MITM has no fetch - * ingress). gwc is guaranteed non-NULL here (gateway implies the hoist ran). */ - if (gateway) { - fetch_adp = mq_gw_fetch_adapter_new(gwc); - if (!fetch_adp) { - MQ_LOGE("failed to create gateway fetch adapter (OOM)"); - goto out; - } - fetch_l = mq_fetch_listener_new(mq_runtime_base(rt), gw_ip, gw_port, - mq_gw_fetch_adapter_cbs(fetch_adp), - mq_gw_fetch_adapter_user(fetch_adp)); - if (!fetch_l) { - MQ_LOGE("failed to bind gateway fetch listener on %s:%u", gw_ip, gw_port); - goto out; - } - } - - if (install_signal_handlers(base, rt, &sint, &sterm) != 0) { - MQ_LOGE("failed to install signal handlers"); - goto out; - } - - { - char ingress[256]; - int n = 0; - ingress[0] = '\0'; - if (socks5) - n += snprintf(ingress + n, sizeof(ingress) - (size_t)n, " socks5=%s:%u", - socks5_ip, socks5_port); - if (http_connect) - n += snprintf(ingress + n, sizeof(ingress) - (size_t)n, " http-connect=%s:%u", - http_ip, http_port); - if (gateway) - n += snprintf(ingress + n, sizeof(ingress) - (size_t)n, " gateway=%s:%u", - gw_ip, gw_port); - if (tproxy_addr) - n += snprintf(ingress + n, sizeof(ingress) - (size_t)n, " tproxy=%s:%u(%s)", - tproxy_ip, mq_tproxy_listener_port(tproxy_l), tproxy_mode_s); - MQ_LOGI("mqproxy client: server=%s:%u%s (bind %s, cc=%s, sched=%s)", server_ip, - server_port, ingress, primary_ip, mq_cc_name(cc), mq_sched_name(sched)); - } - - /* Phase 5c periodic metrics (opt-in via --metrics-interval). mctx outlives - * mq_runtime_run (same function), so a local is fine. */ - struct cli_metrics_ctx mctx = {.client = client, .gwc = gwc}; - if (metrics_interval_ms > 0) { - metrics_ev = event_new(base, -1, EV_PERSIST, cli_metrics_tick, &mctx); - if (metrics_ev) { - struct timeval tv = {.tv_sec = (time_t)(metrics_interval_ms / 1000), - .tv_usec = - (suseconds_t)((metrics_interval_ms % 1000) * 1000)}; - event_add(metrics_ev, &tv); - } - } - - mq_runtime_run(rt); - rc = 0; - - /* The loop has broken (SIGINT/SIGTERM) but the conn is still alive (the - * engine has not been freed yet). Dump per-path byte counters at INFO so - * external benchmarks (1-B / e2e_multipath.sh) can confirm both paths - * carried traffic. Safe on a closed/NULL conn (mq_conn_dump_stats guards). */ - { - mq_conn_t *dump_conn = mq_client_conn(client); - if (dump_conn) { - mq_conn_dump_stats(dump_conn); - } - /* The gateway tunnel is an INDEPENDENT conn (mq_gw_client's own mq_h3 - * conn), so dump its per-path counters too — e2e_gateway's 2-path smoke - * greps these to confirm both MPQUIC paths carried gateway traffic. */ - if (gwc) { - mq_gw_client_dump_stats(gwc); - } - } - -out: - /* Teardown order — THREE callback graphs co-exist here (the TCP-proxy core, - * the HTTP-gateway ingress, and — when --mitm — the live MITM orchestrator), - * with ordering contracts that pull in different directions across - * mq_transport_free. All three are honored below. - * - * (C) MITM orchestrator (mq_mitm_ctx, when MQ_MITM_AVAILABLE && --mitm) — - * the tproxy listener's open_fn=mq_mitm_conn_open hands accepted fds to - * the ctx, which registers LIVE conns that hold references to the gwc - * tunnel (H2→tunnel submit), an mq_gw_h2_adapter, and a terminating SSL - * whose ex-data holds a core ref. So the order is: - * 1. STOP the tproxy listener FIRST (uninstall firewall rules + free the - * listener) so NO new fd is accepted into the orchestrator mid-drain. - * 2. mq_mitm_ctx_free(mitm_ctx) — drains the live-conn registry (§5.1): - * closes any conn still in the drain phase, freeing its events/timers/ - * fds and releasing its SSL (which drops that conn's core ref). This - * MUST run BEFORE mq_gw_client_free(gwc) and mq_transport_free, since - * the draining conns touch gwc/adapter while those are still live. - * 3. mq_mitm_core_destroy(core) — NAMED, AFTER the ctx is freed (so all - * per-SSL core refs are gone; the owner ref then hits 0). - * 4. mq_ignore_hosts_free(ign) — borrowed by the ctx; free after it. - * (All four are NULL / no-op when MITM is off, so the MITM-off path is - * unchanged.) - * - * (A) TCP-proxy core (mq_client + SOCKS5/HTTP listeners) — verified against - * the in-flight callback graph (an earlier "free client first" ordering - * tripped a heap-use-after-free under ASan): - * - The client registers client_on_state() on its mq_conn. That cb is - * invoked DURING mq_transport_free (conn destroy -> close-notify), - * and on MQ_CONN_CLOSED it (a) touches the mq_client and (b) FAILS - * every in-flight tcp_open, firing each open-result cb, which writes - * the SOCKS5/HTTP reject reply onto the listener's per-conn state. - * - So BOTH the client AND its listeners must outlive mq_transport_free - * (transport freed FIRST so its callbacks land on live objects), then - * runtime, then client, then listeners. - * - The transport's send_udp cb (the runtime, as cbs.user) may also - * fire during engine destroy (final CONNECTION_CLOSE), so the runtime - * must outlive mq_transport_free too. - * - UDP ASSOCIATE (mq_udp_assoc, owned by the SOCKS5 listener's active - * list) rides the SAME graph-A ordering. Three teardown paths: - * 1. TCP control-fd EOF → the assoc tears itself down (close_fn on - * every live relay session) and self-removes from the list. - * 2. mq_transport_free (runs FIRST) → conn destroy → every live UDP - * relay session's on_err lands on the still-live assoc, which - * marks the DST entry dead (and, per the boundary contract, will - * NOT call close_fn on a handle after on_err). - * 3. mq_listener_free (LAST) → reaps any surviving assoc shells - * (UDP fd / TCP fd / events); their sessions are already dead - * from path 2, so close_fn is a no-op. Order is unchanged. - * - * (B) HTTP-gateway ingress (mq_gw_client + mq_h3 + fetch listener) — the - * SANCTIONED order is the REVERSE: mq_gw_client_free must run while the - * H3 engine is STILL LIVE (it touches r->req for each in-flight request) - * AND while the fetch listener is STILL LIVE (it mq_fetch_conn_abort()s - * each live local handle). gw_client_free also DETACHES the gateway - * conn-state cb, so the gateway conn's later close (fired inside - * mq_h3_free / engine teardown) is a no-op back into the freed gw_client. - * Therefore: gw_client BEFORE fetch listener BEFORE mq_h3_free BEFORE - * mq_transport_free. (mq_h3_free before mq_transport_free is also the - * standalone mq_h3.h contract; the gateway and tcp ALPN conn graphs are - * independent, so freeing the H3 ctx does not disturb the tcp conns that - * mq_transport_free later tears down for graph A.) - * - * Combined order: - * signal events - * -> tproxy setup uninstall + tproxy listener free (graph C step 1: stop - * accepting new fds into the MITM orchestrator; - * also the plain-tproxy listener teardown — no - * callbacks fire from it after this point) - * -> mq_mitm_ctx_free (graph C step 2: drain live MITM conns BEFORE - * gwc/transport; NULL/no-op when MITM off) - * -> gw_client_free (engine+h3+fetch listener live; detaches gw - * conn-state, aborts live local handles) - * -> fetch listener free (gw handles already aborted/detached: safe) - * -> mq_h3_free (engine live; gw conn-close cb detached -> no-op) - * -> mq_transport_free (fires graph-A conn-close + in-flight-open cbs - * into the live runtime/client/socks5/http) - * -> mq_mitm_core_destroy (graph C step 3: AFTER ctx freed; NULL/no-op off) - * -> runtime free (closes sockets/timer; does not free base) - * -> mq_client_free - * -> socks5 / http listener free - * -> mq_ignore_hosts_free (graph C step 4: borrowed by the freed ctx) - * -> base free (CLI owns base; strictly LAST — the MITM drain used it). */ - if (metrics_ev) { - event_del(metrics_ev); - event_free(metrics_ev); - } - if (sint) event_free(sint); - if (sterm) event_free(sterm); - /* (C step 1) Stop the tproxy ingress FIRST: uninstall firewall rules BEFORE - * freeing the listener (rules reference the port), then free the listener so - * no new fd is accepted into the MITM orchestrator while it drains below. */ - if (tproxy_setup) { - mq_tproxy_setup_uninstall(tproxy_setup); - mq_tproxy_setup_free(tproxy_setup); - } - if (tproxy_l) mq_tproxy_listener_free(tproxy_l); -#if MQ_MITM_AVAILABLE - /* (C step 2) Drain the live MITM-conn registry while gwc/transport are still - * live (the draining conns touch them). NULL/no-op when MITM is off. */ - if (mitm_ctx) mq_mitm_ctx_free(mitm_ctx); -#endif - if (gwc) mq_gw_client_free(gwc); - if (fetch_l) mq_fetch_listener_free(fetch_l); - if (fetch_adp) mq_gw_fetch_adapter_free(fetch_adp); - if (h3) mq_h3_free(h3); - if (transport) mq_transport_free(transport); -#if MQ_MITM_AVAILABLE - /* (C step 3) Destroy the MITM core AFTER the ctx (and thus all per-SSL core - * refs) are gone. NULL/no-op when MITM is off. */ - if (core) mq_mitm_core_destroy(core); -#endif - if (rt) mq_runtime_free(rt); - if (client) mq_client_free(client); - if (socks5_l) mq_listener_free(socks5_l); - if (http_l) mq_listener_free(http_l); -#if MQ_MITM_AVAILABLE - /* (C step 4) Free the ignore-hosts list (borrowed by the now-freed ctx). */ - if (ign) mq_ignore_hosts_free(ign); -#endif - if (base) event_base_free(base); - return rc; -} - -/* ── dispatch ───────────────────────────────────────────────────────────────*/ - -int -main(int argc, char **argv) -{ - mq_log_set_level(MQ_LOG_INFO); - - if (argc < 2) { - usage_top(stderr); - return 2; - } - - const char *sub = argv[1]; - if (strcmp(sub, "-h") == 0 || strcmp(sub, "--help") == 0) { - usage_top(stdout); - return 0; - } - if (strcmp(sub, "-V") == 0 || strcmp(sub, "--version") == 0 || - strcmp(sub, "version") == 0) { - printf("mqproxy %s\n", MQPROXY_VERSION); - return 0; - } - if (strcmp(sub, "server") == 0) { - return cmd_server(argc - 1, argv + 1); - } - if (strcmp(sub, "client") == 0) { - return cmd_client(argc - 1, argv + 1); - } - - fprintf(stderr, "mqproxy: unknown command '%s'\n\n", sub); - usage_top(stderr); - return 2; -} diff --git a/cmake/BuildSetup.cmake b/cmake/BuildSetup.cmake deleted file mode 100644 index 7425e910..00000000 --- a/cmake/BuildSetup.cmake +++ /dev/null @@ -1,156 +0,0 @@ -# BuildSetup.cmake — compiler standards, optional deps, sanitizer flags - -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) - -# ---------- xquic ---------- -# -# QLOG REQUIREMENT (read this if test_qlog_blocked or e2e_multipath fail): -# The qlog-based milestone 1-B benchmark (tests/integration/e2e_multipath.sh) -# and the `test_qlog_blocked` unit test assert on xquic qlog EXTRA-importance -# events (frames_processed, xqc_parse_*_blocked_frame). Those events are ONLY -# emitted when the linked xquic was compiled with -DXQC_ENABLE_EVENT_LOG=ON. -# Against a stock xquic build those assertions are vacuous or fail. -# -# Build a qlog-enabled xquic for mqproxy by running: -# scripts/build-xquic.sh -# then configure with: -# -DXQUIC_BUILD_DIR=${CMAKE_SOURCE_DIR}/third_party/xquic/build -# or point XQUIC_BUILD_DIR at any other XQC_ENABLE_EVENT_LOG=ON build. -set(XQUIC_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/third_party/xquic/include) - -set(XQUIC_BUILD_DIR "" CACHE PATH "Path to pre-built xquic build directory") - -# Static-link xquic + its BoringSSL into shipped binaries (packaging). Default OFF -# keeps the dynamic build + all test executables on the shared libxquic.so. -option(MQPROXY_STATIC_XQUIC "Statically link libxquic-static.a + BoringSSL into mqproxy (packaging)" OFF) -# BoringSSL static archives that libxquic-static.a was built against. Default matches -# the in-tree scripts/build-xquic.sh layout; override for out-of-tree xquic builds. -set(MQPROXY_BORINGSSL_DIR "${XQUIC_BUILD_DIR}/../third_party/boringssl/build" - CACHE PATH "Dir containing BoringSSL libssl.a/libcrypto.a for static linking") - -# Hint string shown whenever XQUIC_BUILD_DIR is unset or the lib looks like it -# was built WITHOUT qlog (XQC_ENABLE_EVENT_LOG). Kept as one variable so both -# branches below print the same actionable guidance. -set(_xquic_qlog_hint - " qlog-based tests (test_qlog_blocked, e2e_multipath) need an xquic built\n" - " with -DXQC_ENABLE_EVENT_LOG=ON. Run scripts/build-xquic.sh, then set\n" - " -DXQUIC_BUILD_DIR=${CMAKE_SOURCE_DIR}/third_party/xquic/build\n" - " (or point XQUIC_BUILD_DIR at any XQC_ENABLE_EVENT_LOG=ON build).") - -if(XQUIC_BUILD_DIR) - message(STATUS "Using pre-built xquic from: ${XQUIC_BUILD_DIR}") - add_library(xquic SHARED IMPORTED) - set_target_properties(xquic PROPERTIES - IMPORTED_LOCATION "${XQUIC_BUILD_DIR}/libxquic.so" - ) - if(EXISTS "${XQUIC_BUILD_DIR}/libxquic-static.a") - add_library(xquic-static STATIC IMPORTED) - set_target_properties(xquic-static PROPERTIES - IMPORTED_LOCATION "${XQUIC_BUILD_DIR}/libxquic-static.a" - ) - endif() - - # Best-effort qlog-enabled probe: a build with XQC_ENABLE_EVENT_LOG=ON - # embeds the EXTRA-importance frame-tracing markers (e.g. "frames_processed") - # in libxquic.so. If we can read the lib and the marker is absent, warn that - # the qlog tests will not behave as expected. - set(_xquic_lib "${XQUIC_BUILD_DIR}/libxquic.so") - if(EXISTS "${_xquic_lib}") - file(STRINGS "${_xquic_lib}" _xquic_qlog_marker - REGEX "frames_processed" LIMIT_COUNT 1) - if(NOT _xquic_qlog_marker) - message(STATUS - "xquic at ${XQUIC_BUILD_DIR} appears to LACK qlog " - "(XQC_ENABLE_EVENT_LOG=ON) — no 'frames_processed' marker found.\n" - "${_xquic_qlog_hint}") - endif() - endif() -else() - message(STATUS "XQUIC_BUILD_DIR not set — xquic targets not imported.\n" - "${_xquic_qlog_hint}") -endif() - -# ---------- BoringSSL (vendored under xquic) for the MITM core ---------- -set(MQPROXY_BORINGSSL_INCLUDE_DIR "${MQPROXY_BORINGSSL_DIR}/../include" - CACHE PATH "BoringSSL include dir (must track MQPROXY_BORINGSSL_DIR)") -set(MQ_HAVE_BORINGSSL_ARCHIVES OFF) -if(EXISTS "${MQPROXY_BORINGSSL_DIR}/libssl.a" AND - EXISTS "${MQPROXY_BORINGSSL_DIR}/libcrypto.a") - set(MQ_HAVE_BORINGSSL_ARCHIVES ON) -endif() - -# Derived MITM availability flag: the live MITM L7 path (Phase 7 Slice 3) needs -# the BoringSSL static archives the MITM core links against. Kept as its own -# name so call sites read intent ("is MITM compiled in?") rather than re-deriving -# the archive predicate, and so a future decoupling has one place to change. -set(MQPROXY_MITM ${MQ_HAVE_BORINGSSL_ARCHIVES}) - -# ---- mq_boringssl: single source of truth for the BoringSSL static-link recipe. -# Hoisted here (out of the test-only block in CMakeLists.txt) so it exists -# whenever the archives are present, independent of which targets consume it: -# the MITM unit/smoke test targets AND mqproxy_cli (the live MITM path) all link -# it. Archive ORDER matters (libssl.a before libcrypto.a). NO xquic here: the -# MITM core is sans-io / BoringSSL-only and references no xqc_* symbols, so the -# static archive pulls no xquic-dependent objects — keep it xquic-free. -if(MQ_HAVE_BORINGSSL_ARCHIVES AND NOT TARGET mq_boringssl) - add_library(mq_boringssl INTERFACE) - target_link_libraries(mq_boringssl INTERFACE - "${MQPROXY_BORINGSSL_DIR}/libssl.a" - "${MQPROXY_BORINGSSL_DIR}/libcrypto.a" - pthread dl m stdc++) - target_include_directories(mq_boringssl INTERFACE - ${MQPROXY_BORINGSSL_INCLUDE_DIR}) -endif() - -# ---------- nghttp2 (HTTP/2 termination for the live MITM L7 path, Slice 3) ---------- -# -# nghttp2 is a LEAF dependency: pure HTTP/2 framing, no TLS/crypto of its own, so -# it shares NO symbols with BoringSSL/xquic and needs no recursive build or symbol -# isolation (unlike BoringSSL). Found via pkg-config in BOTH build modes: -# - dynamic (default): link the system shared libnghttp2.so (${NGHTTP2_LIBRARIES}). -# - static (MQPROXY_STATIC_XQUIC): link the system multiarch libnghttp2.a so the -# shipped binary stays self-contained. If the .a is absent (only the .so is -# installed), the static build MUST FATAL_ERROR — mirroring the BoringSSL -# archive-missing gate in CMakeLists.txt — telling the user to install it. -# The H2 server adapter TU (#include ) and nghttp2_link_probe -# consume NGHTTP2_INCLUDE_DIRS / the lib chosen by mode. -find_package(PkgConfig REQUIRED) -pkg_check_modules(NGHTTP2 REQUIRED libnghttp2) -message(STATUS "Using nghttp2 ${NGHTTP2_VERSION} (dynamic: ${NGHTTP2_LIBRARIES})") - -# Static archive: locate libnghttp2.a so the static-link branch can use it. find_library -# searches the standard system/multiarch lib dirs (e.g. /usr/lib/x86_64-linux-gnu). -# Probe unconditionally (cheap); the FATAL_ERROR for absence fires ONLY in the -# MQPROXY_STATIC_XQUIC branch (CMakeLists.txt) so the dynamic build never needs the .a. -find_library(NGHTTP2_STATIC_LIB - NAMES libnghttp2.a - HINTS ${NGHTTP2_STATIC_LIBRARY_DIRS} ${NGHTTP2_LIBRARY_DIRS}) - -# ---------- libevent ---------- -find_path(EVENT_INCLUDE_DIR event2/event.h) -find_library(EVENT_CORE_LIB event_core) -find_library(EVENT_EXTRA_LIB event_extra) - -if(NOT EVENT_CORE_LIB) - message(WARNING "libevent_core not found — some targets may fail to link. " - "Install: apt install libevent-dev") -endif() - -# ---------- HTTP/3-capable libcurl (opt-in) ---------- -# -# Default build links the system libcurl (no HTTP/3). To speak h3 to an origin -# (X-Mq-Origin-Protocol: h3), point this at the install prefix produced by -# scripts/build-h3-curl.sh (ngtcp2 + nghttp3 + libcurl on the pinned BoringSSL): -# -DMQPROXY_H3_CURL=${CMAKE_SOURCE_DIR}/third_party/h3-curl/install -# When set, CMakeLists.txt defines the CURL::libcurl imported target from that -# prefix instead of find_package(CURL). Unset = system libcurl, unchanged. -set(MQPROXY_H3_CURL "" CACHE PATH "Install prefix of an HTTP/3-capable libcurl (scripts/build-h3-curl.sh)") - -# ---------- Sanitizers ---------- -option(MQPROXY_SANITIZE "Enable ASan + UBSan" OFF) - -if(MQPROXY_SANITIZE) - add_compile_options(-fsanitize=address,undefined -fno-omit-frame-pointer) - add_link_options(-fsanitize=address,undefined) -endif() diff --git a/crates/mq-wire-difftest/Cargo.toml b/crates/mq-wire-difftest/Cargo.toml deleted file mode 100644 index d104cf9f..00000000 --- a/crates/mq-wire-difftest/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "mq-wire-difftest" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -publish = false - -[dependencies] -mq-wire = { path = "../mq-wire" } - -[build-dependencies] -cc = { workspace = true } diff --git a/crates/mq-wire-difftest/build.rs b/crates/mq-wire-difftest/build.rs deleted file mode 100644 index 4745b374..00000000 --- a/crates/mq-wire-difftest/build.rs +++ /dev/null @@ -1,21 +0,0 @@ -// spec §8.2: build the C codec the differential test compares against. -fn main() { - let srcs = [ - "../../src/wire/mq_wire.c", - "../../src/wire/mq_varint.c", - "../../src/wire/mq_udp_msg.c", - "layout.c", - ]; - let headers = [ - "../../src/wire/mq_wire.h", - "../../src/wire/mq_varint.h", - "../../src/wire/mq_udp_msg.h", - ]; - for f in srcs.iter().chain(&headers) { - println!("cargo:rerun-if-changed={f}"); - } - cc::Build::new() - .files(srcs) - .include("../../src") - .compile("mq_wire_c"); -} diff --git a/crates/mq-wire-difftest/layout.c b/crates/mq-wire-difftest/layout.c deleted file mode 100644 index 1c896d3c..00000000 --- a/crates/mq-wire-difftest/layout.c +++ /dev/null @@ -1,60 +0,0 @@ -// spec §8.2: sizeof/offsetof of the mq_wire.h and mq_udp_msg.h structs, checked -// against the #[repr(C)] mirrors in src/lib.rs (rust_layout()). -#include - -#include "wire/mq_udp_msg.h" -#include "wire/mq_wire.h" - -static const size_t layout[] = { - sizeof(mq_auth_req_t), - offsetof(mq_auth_req_t, version), - offsetof(mq_auth_req_t, client_id), - offsetof(mq_auth_req_t, auth_token), - offsetof(mq_auth_req_t, features), - sizeof(mq_auth_resp_t), - offsetof(mq_auth_resp_t, status), - offsetof(mq_auth_resp_t, error_code), - offsetof(mq_auth_resp_t, server_id), - offsetof(mq_auth_resp_t, features), - sizeof(mq_connect_tcp_req_t), - offsetof(mq_connect_tcp_req_t, flags), - offsetof(mq_connect_tcp_req_t, address_type), - offsetof(mq_connect_tcp_req_t, host), - offsetof(mq_connect_tcp_req_t, host_len), - offsetof(mq_connect_tcp_req_t, port), - sizeof(mq_connect_tcp_resp_t), - offsetof(mq_connect_tcp_resp_t, status), - offsetof(mq_connect_tcp_resp_t, error_code), - offsetof(mq_connect_tcp_resp_t, message), - offsetof(mq_connect_tcp_resp_t, message_len), - sizeof(mq_status_t), - sizeof(mq_addr_type_t), - sizeof(mq_udp_session_open_t), - offsetof(mq_udp_session_open_t, session_id), - offsetof(mq_udp_session_open_t, flags), - offsetof(mq_udp_session_open_t, address_type), - offsetof(mq_udp_session_open_t, host), - offsetof(mq_udp_session_open_t, host_len), - offsetof(mq_udp_session_open_t, port), - offsetof(mq_udp_session_open_t, idle_timeout_ms), - sizeof(mq_udp_session_resp_t), - offsetof(mq_udp_session_resp_t, status), - offsetof(mq_udp_session_resp_t, error_code), - offsetof(mq_udp_session_resp_t, message), - offsetof(mq_udp_session_resp_t, message_len), - offsetof(mq_udp_session_resp_t, idle_timeout_ms), - sizeof(mq_udp_msg_hdr_t), - offsetof(mq_udp_msg_hdr_t, session_id), - offsetof(mq_udp_msg_hdr_t, packet_id), - offsetof(mq_udp_msg_hdr_t, flags), - offsetof(mq_udp_msg_hdr_t, frag_id), - offsetof(mq_udp_msg_hdr_t, frag_count), - sizeof(mq_udp_err_t), -}; - -const size_t * -mq_difftest_layout(size_t *n) -{ - *n = sizeof layout / sizeof layout[0]; - return layout; -} diff --git a/crates/mq-wire-difftest/src/lib.rs b/crates/mq-wire-difftest/src/lib.rs deleted file mode 100644 index 7e12d14d..00000000 --- a/crates/mq-wire-difftest/src/lib.rs +++ /dev/null @@ -1,271 +0,0 @@ -//! spec §8.2: FFI to the C wire codec (`src/wire/mq_wire.c`, `mq_varint.c`, -//! `mq_udp_msg.c`) for the differential test. Structs mirror -//! `src/wire/mq_wire.h` / `mq_udp_msg.h` exactly; C enums are `c_int`. Every -//! frame codec function returns bytes written/consumed, or -1 on error (the -//! `codec!` wrappers map that to `Option`); the datagram header functions -//! return 0 / -1 and have hand-written wrappers. -use std::ffi::c_int; -use std::mem::{offset_of, size_of}; - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct AuthReqC { - pub version: u64, - pub client_id: [u8; 64], - pub auth_token: [u8; 256], - pub features: u64, -} - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct AuthRespC { - pub status: c_int, - pub error_code: c_int, - pub server_id: [u8; 64], - pub features: u64, -} - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct ConnectTcpReqC { - pub flags: u64, - pub address_type: c_int, - pub host: [u8; 255], - pub host_len: usize, - pub port: u16, -} - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct ConnectTcpRespC { - pub status: c_int, - pub error_code: c_int, - pub message: [u8; 256], - pub message_len: usize, -} - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct UdpSessionOpenC { - pub session_id: u32, - pub flags: u64, - pub address_type: c_int, - pub host: [u8; 255], - pub host_len: usize, - pub port: u16, - pub idle_timeout_ms: u64, -} - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct UdpSessionRespC { - pub status: c_int, - pub error_code: c_int, - pub message: [u8; 256], - pub message_len: usize, - pub idle_timeout_ms: u64, -} - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct UdpMsgHdrC { - pub session_id: u32, - pub packet_id: u16, - pub flags: u8, - pub frag_id: u8, - pub frag_count: u8, -} - -unsafe extern "C" { - fn mq_encode_auth_req(buf: *mut u8, cap: usize, f: *const AuthReqC) -> c_int; - fn mq_decode_auth_req(buf: *const u8, len: usize, out: *mut AuthReqC) -> c_int; - fn mq_encode_auth_resp(buf: *mut u8, cap: usize, f: *const AuthRespC) -> c_int; - fn mq_decode_auth_resp(buf: *const u8, len: usize, out: *mut AuthRespC) -> c_int; - fn mq_encode_connect_tcp_req(buf: *mut u8, cap: usize, f: *const ConnectTcpReqC) -> c_int; - fn mq_decode_connect_tcp_req(buf: *const u8, len: usize, out: *mut ConnectTcpReqC) -> c_int; - fn mq_encode_connect_tcp_resp(buf: *mut u8, cap: usize, f: *const ConnectTcpRespC) -> c_int; - fn mq_decode_connect_tcp_resp(buf: *const u8, len: usize, out: *mut ConnectTcpRespC) -> c_int; - fn mq_encode_udp_session_open(buf: *mut u8, cap: usize, f: *const UdpSessionOpenC) -> c_int; - fn mq_decode_udp_session_open(buf: *const u8, len: usize, out: *mut UdpSessionOpenC) -> c_int; - fn mq_encode_udp_session_resp(buf: *mut u8, cap: usize, f: *const UdpSessionRespC) -> c_int; - fn mq_decode_udp_session_resp(buf: *const u8, len: usize, out: *mut UdpSessionRespC) -> c_int; - fn mq_udp_msg_encode_hdr(buf: *mut u8, h: *const UdpMsgHdrC) -> c_int; - fn mq_udp_msg_decode_hdr(buf: *const u8, len: usize, out: *mut UdpMsgHdrC) -> c_int; - fn mq_varint_encode(buf: *mut u8, cap: usize, v: u64) -> c_int; - fn mq_varint_decode(buf: *const u8, len: usize, out: *mut u64) -> c_int; - fn mq_difftest_layout(n: *mut usize) -> *const usize; -} - -/// Ample for any of these frames (largest AUTH_REQUEST is < 350 bytes). -const ENC_CAP: usize = 1024; - -macro_rules! codec { - ($dec:ident, $enc:ident, $ty:ty, $c_dec:ident, $c_enc:ident, [$($cstr:ident),*]) => { - /// C decode: `(frame, consumed)`, or `None` when C returns -1. - pub fn $dec(buf: &[u8]) -> Option<($ty, usize)> { - // SAFETY: all-zero is a valid value of these plain-data structs; C - // reads at most `buf.len()` bytes and writes only into `out`. - let mut out: $ty = unsafe { std::mem::zeroed() }; - let n = unsafe { $c_dec(buf.as_ptr(), buf.len(), &mut out) }; - usize::try_from(n).ok().map(|n| (out, n)) - } - /// C encode, or `None` when C returns -1. - /// - /// # Panics - /// If a field C reads with `strlen` holds no NUL (C would read past it). - pub fn $enc(f: &$ty) -> Option> { - $(assert!( - f.$cstr.contains(&0), - concat!(stringify!($cstr), " is not NUL-terminated") - );)* - let mut buf = vec![0u8; ENC_CAP]; - // SAFETY: every strlen'd field is NUL-terminated (asserted above); - // C writes at most `ENC_CAP` bytes and only reads `f`. - let n = unsafe { $c_enc(buf.as_mut_ptr(), buf.len(), f) }; - buf.truncate(usize::try_from(n).ok()?); - Some(buf) - } - }; -} - -codec!( - decode_auth_req, - encode_auth_req, - AuthReqC, - mq_decode_auth_req, - mq_encode_auth_req, - [client_id, auth_token] -); -codec!( - decode_auth_resp, - encode_auth_resp, - AuthRespC, - mq_decode_auth_resp, - mq_encode_auth_resp, - [server_id] -); -codec!( - decode_connect_tcp_req, - encode_connect_tcp_req, - ConnectTcpReqC, - mq_decode_connect_tcp_req, - mq_encode_connect_tcp_req, - [] -); -codec!( - decode_connect_tcp_resp, - encode_connect_tcp_resp, - ConnectTcpRespC, - mq_decode_connect_tcp_resp, - mq_encode_connect_tcp_resp, - [message] -); - -codec!( - decode_udp_session_open, - encode_udp_session_open, - UdpSessionOpenC, - mq_decode_udp_session_open, - mq_encode_udp_session_open, - [] -); -// C re-encodes `message` by `message_len`, not strlen: no NUL requirement. -codec!( - decode_udp_session_resp, - encode_udp_session_resp, - UdpSessionRespC, - mq_decode_udp_session_resp, - mq_encode_udp_session_resp, - [] -); - -/// C datagram header encode (writes exactly 9 bytes, returns 0), or `None` on -1. -pub fn c_udp_hdr_encode(h: &UdpMsgHdrC) -> Option<[u8; 9]> { - let mut buf = [0u8; 9]; - // SAFETY: C writes exactly 9 bytes into `buf` and only reads `h`. - let r = unsafe { mq_udp_msg_encode_hdr(buf.as_mut_ptr(), h) }; - (r == 0).then_some(buf) -} - -/// C datagram header decode of the first 9 bytes, or `None` on -1 (`buf` < 9 bytes). -pub fn c_udp_hdr_decode(buf: &[u8]) -> Option { - // SAFETY: all-zero is a valid value of this plain-data struct; C reads at - // most `buf.len()` bytes and writes only into `out`. - let mut out: UdpMsgHdrC = unsafe { std::mem::zeroed() }; - let r = unsafe { mq_udp_msg_decode_hdr(buf.as_ptr(), buf.len(), &mut out) }; - (r == 0).then_some(out) -} - -/// C varint encode into a `cap`-byte buffer: the written bytes, or `None` on -1. -pub fn varint_encode(cap: usize, v: u64) -> Option> { - let mut buf = vec![0u8; cap]; - // SAFETY: C writes at most `cap` bytes. - let n = unsafe { mq_varint_encode(buf.as_mut_ptr(), cap, v) }; - buf.truncate(usize::try_from(n).ok()?); - Some(buf) -} - -/// C varint decode: `(value, consumed)`, or `None` on -1. -pub fn varint_decode(buf: &[u8]) -> Option<(u64, usize)> { - let mut v = 0; - // SAFETY: C reads at most `buf.len()` bytes and writes only `v`. - let n = unsafe { mq_varint_decode(buf.as_ptr(), buf.len(), &mut v) }; - usize::try_from(n).ok().map(|n| (v, n)) -} - -/// sizeof/offsetof table as the C compiler sees `mq_wire.h` / `mq_udp_msg.h` (layout.c). -pub fn c_layout() -> Vec { - let mut n = 0; - // SAFETY: returns a pointer to a static array of `n` elements. - unsafe { std::slice::from_raw_parts(mq_difftest_layout(&mut n), n) }.to_vec() -} - -/// The same table for the Rust mirrors, in layout.c order. -pub fn rust_layout() -> Vec { - vec![ - size_of::(), - offset_of!(AuthReqC, version), - offset_of!(AuthReqC, client_id), - offset_of!(AuthReqC, auth_token), - offset_of!(AuthReqC, features), - size_of::(), - offset_of!(AuthRespC, status), - offset_of!(AuthRespC, error_code), - offset_of!(AuthRespC, server_id), - offset_of!(AuthRespC, features), - size_of::(), - offset_of!(ConnectTcpReqC, flags), - offset_of!(ConnectTcpReqC, address_type), - offset_of!(ConnectTcpReqC, host), - offset_of!(ConnectTcpReqC, host_len), - offset_of!(ConnectTcpReqC, port), - size_of::(), - offset_of!(ConnectTcpRespC, status), - offset_of!(ConnectTcpRespC, error_code), - offset_of!(ConnectTcpRespC, message), - offset_of!(ConnectTcpRespC, message_len), - size_of::(), // mq_status_t - size_of::(), // mq_addr_type_t - size_of::(), - offset_of!(UdpSessionOpenC, session_id), - offset_of!(UdpSessionOpenC, flags), - offset_of!(UdpSessionOpenC, address_type), - offset_of!(UdpSessionOpenC, host), - offset_of!(UdpSessionOpenC, host_len), - offset_of!(UdpSessionOpenC, port), - offset_of!(UdpSessionOpenC, idle_timeout_ms), - size_of::(), - offset_of!(UdpSessionRespC, status), - offset_of!(UdpSessionRespC, error_code), - offset_of!(UdpSessionRespC, message), - offset_of!(UdpSessionRespC, message_len), - offset_of!(UdpSessionRespC, idle_timeout_ms), - size_of::(), - offset_of!(UdpMsgHdrC, session_id), - offset_of!(UdpMsgHdrC, packet_id), - offset_of!(UdpMsgHdrC, flags), - offset_of!(UdpMsgHdrC, frag_id), - offset_of!(UdpMsgHdrC, frag_count), - size_of::(), // mq_udp_err_t - ] -} diff --git a/crates/mq-wire-difftest/tests/diff.rs b/crates/mq-wire-difftest/tests/diff.rs deleted file mode 100644 index 75b0c628..00000000 --- a/crates/mq-wire-difftest/tests/diff.rs +++ /dev/null @@ -1,698 +0,0 @@ -// spec §8.2: differential test of mq-wire against the C codec (src/wire/). -// The C codec is normative inside the domain it can represent: no NUL in the -// fields C reads back with strlen (client_id, auth_token, server_id, message) -// and error_code < 2^31 (C narrows it to an `int` enum). Outside that domain -// only the decode outcome (Ok vs Err) is compared. The UDP frames and the -// datagram header have no such limits: every decoded value is compared in full. -use mq_wire::frames::{ - AddrType, AuthReq, AuthResp, ConnectTcpReq, ConnectTcpResp, EncodeError, MAX_FRAME, - UdpSessionOpen, UdpSessionResp, -}; -use mq_wire::udp_msg::{UDP_MSG_HDR, UdpMsgHdr}; -use mq_wire::varint; -use mq_wire_difftest as c; -use std::ffi::CStr; - -const N: usize = 10_000; - -/// xorshift64*: fixed seed so every run sees the same inputs. -struct Rng(u64); -impl Rng { - fn next(&mut self) -> u64 { - self.0 ^= self.0 >> 12; - self.0 ^= self.0 << 25; - self.0 ^= self.0 >> 27; - self.0.wrapping_mul(0x2545_F491_4F6C_DD1D) - } - fn below(&mut self, n: u64) -> u64 { - self.next() % n - } - fn chance(&mut self, pct: u64) -> bool { - self.below(100) < pct - } - /// Value of a random bit width 0..=bits, so every varint prefix length is hit. - fn width(&mut self, bits: u32) -> u64 { - let w = self.below(u64::from(bits) + 1) as u32; - if w == 0 { 0 } else { self.next() >> (64 - w) } - } -} - -// ---- domain + comparison, one per frame; used by every test group ---- - -const ERR_DOMAIN: u64 = 1 << 31; - -fn cstr(b: &[u8]) -> &[u8] { - CStr::from_bytes_until_nul(b) - .expect("C wrote a NUL") - .to_bytes() -} - -fn rust_reencode(f: impl FnOnce(&mut [u8]) -> usize) -> Vec { - let mut out = [0u8; MAX_FRAME]; - let n = f(&mut out); - out[..n].to_vec() -} - -/// Some(in_domain) when both decoded, None when both rejected; panics on divergence. -fn cmp_auth_req(input: &[u8]) -> Option { - let r = AuthReq::decode(input); - let cd = c::decode_auth_req(input); - assert_eq!( - r.is_ok(), - cd.is_some(), - "auth_req outcome differs on {input:02x?}" - ); - let ((r, rn), (cf, cn)) = (r.ok()?, cd?); - // Consumed length and scalars do not depend on the domain limits. - assert_eq!(rn, cn, "consumed on {input:02x?}"); - assert_eq!(r.version, cf.version); - assert_eq!(r.features, cf.features); - if r.client_id.contains(&0) || r.auth_token.contains(&0) { - return Some(false); - } - assert_eq!(r.client_id, cstr(&cf.client_id)); - assert_eq!(r.auth_token, cstr(&cf.auth_token)); - let re = rust_reencode(|o| r.encode(o).unwrap()); - assert_eq!( - re, - c::encode_auth_req(&cf).unwrap(), - "re-encode on {input:02x?}" - ); - Some(true) -} - -fn cmp_auth_resp(input: &[u8]) -> Option { - let r = AuthResp::decode(input); - let cd = c::decode_auth_resp(input); - assert_eq!( - r.is_ok(), - cd.is_some(), - "auth_resp outcome differs on {input:02x?}" - ); - let ((r, rn), (cf, cn)) = (r.ok()?, cd?); - // Consumed length and scalars do not depend on the domain limits. - assert_eq!(rn, cn, "consumed on {input:02x?}"); - assert_eq!(u32::from(r.status), cf.status as u32); - assert_eq!(r.features, cf.features); - if r.server_id.contains(&0) || r.error_code >= ERR_DOMAIN { - return Some(false); - } - assert_eq!(r.error_code, u64::from(cf.error_code as u32)); - assert_eq!(r.server_id, cstr(&cf.server_id)); - let re = rust_reencode(|o| r.encode(o).unwrap()); - assert_eq!( - re, - c::encode_auth_resp(&cf).unwrap(), - "re-encode on {input:02x?}" - ); - Some(true) -} - -fn cmp_connect_tcp_req(input: &[u8]) -> Option { - let r = ConnectTcpReq::decode(input); - let cd = c::decode_connect_tcp_req(input); - assert_eq!( - r.is_ok(), - cd.is_some(), - "connect_tcp_req outcome differs on {input:02x?}" - ); - let ((r, rn), (cf, cn)) = (r.ok()?, cd?); - // host carries an explicit length on both sides: always fully comparable. - assert_eq!(rn, cn, "consumed on {input:02x?}"); - assert_eq!(r.flags, cf.flags); - assert_eq!(r.address_type as i32, cf.address_type); - assert_eq!(r.host, &cf.host[..cf.host_len]); - assert_eq!(r.port, cf.port); - let re = rust_reencode(|o| r.encode(o).unwrap()); - assert_eq!( - re, - c::encode_connect_tcp_req(&cf).unwrap(), - "re-encode on {input:02x?}" - ); - Some(true) -} - -fn cmp_connect_tcp_resp(input: &[u8]) -> Option { - let r = ConnectTcpResp::decode(input); - let cd = c::decode_connect_tcp_resp(input); - assert_eq!( - r.is_ok(), - cd.is_some(), - "connect_tcp_resp outcome differs on {input:02x?}" - ); - let ((r, rn), (cf, cn)) = (r.ok()?, cd?); - // Consumed length, status and the length-carrying message do not depend - // on the domain limits. - assert_eq!(rn, cn, "consumed on {input:02x?}"); - assert_eq!(u32::from(r.status), cf.status as u32); - assert_eq!(r.message, &cf.message[..cf.message_len]); - // C decodes message with an explicit length but re-encodes it with strlen. - if r.message.contains(&0) || r.error_code >= ERR_DOMAIN { - return Some(false); - } - assert_eq!(r.error_code, u64::from(cf.error_code as u32)); - let re = rust_reencode(|o| r.encode(o).unwrap()); - assert_eq!( - re, - c::encode_connect_tcp_resp(&cf).unwrap(), - "re-encode on {input:02x?}" - ); - Some(true) -} - -fn cmp_udp_open(input: &[u8]) -> Option { - let r = UdpSessionOpen::decode(input); - let cd = c::decode_udp_session_open(input); - assert_eq!( - r.is_ok(), - cd.is_some(), - "udp_open outcome differs on {input:02x?}" - ); - let ((r, rn), (cf, cn)) = (r.ok()?, cd?); - // host carries an explicit length on both sides: always fully comparable. - assert_eq!(rn, cn, "consumed on {input:02x?}"); - assert_eq!(r.session_id, cf.session_id); - assert_eq!(r.flags, cf.flags); - assert_eq!(r.address_type as i32, cf.address_type); - assert_eq!(r.host, &cf.host[..cf.host_len]); - assert_eq!(r.port, cf.port); - assert_eq!(r.idle_timeout_ms, cf.idle_timeout_ms); - let re = rust_reencode(|o| r.encode(o).unwrap()); - assert_eq!( - re, - c::encode_udp_session_open(&cf).unwrap(), - "re-encode on {input:02x?}" - ); - Some(true) -} - -fn cmp_udp_resp(input: &[u8]) -> Option { - let r = UdpSessionResp::decode(input); - let cd = c::decode_udp_session_resp(input); - assert_eq!( - r.is_ok(), - cd.is_some(), - "udp_resp outcome differs on {input:02x?}" - ); - let ((r, rn), (cf, cn)) = (r.ok()?, cd?); - // Both sides carry the message length explicitly and C re-encodes it with - // `message_len` (no strlen); error_code is <= 4 on every accepted frame. - assert_eq!(rn, cn, "consumed on {input:02x?}"); - assert_eq!(u32::from(r.status), cf.status as u32); - assert_eq!(r.error_code, u64::from(cf.error_code as u32)); - assert_eq!(r.message, &cf.message[..cf.message_len]); - assert_eq!(r.idle_timeout_ms, cf.idle_timeout_ms); - let re = rust_reencode(|o| r.encode(o).unwrap()); - assert_eq!( - re, - c::encode_udp_session_resp(&cf).unwrap(), - "re-encode on {input:02x?}" - ); - Some(true) -} - -/// The header has no padding or length: it decodes iff `input` holds 9 bytes. -fn cmp_udp_hdr(input: &[u8]) -> Option { - let r = UdpMsgHdr::decode(input); - let cd = c::c_udp_hdr_decode(input); - assert_eq!( - r.is_some(), - cd.is_some(), - "udp_hdr outcome differs on {input:02x?}" - ); - let (r, cf) = (r?, cd?); - assert_eq!(r.session_id, cf.session_id); - assert_eq!(r.packet_id, cf.packet_id); - assert_eq!(r.flags, cf.flags); - assert_eq!(r.frag_id, cf.frag_id); - assert_eq!(r.frag_count, cf.frag_count); - let mut re = [0u8; UDP_MSG_HDR]; - r.encode(&mut re); - assert_eq!(re[..], input[..UDP_MSG_HDR], "re-encode on {input:02x?}"); - assert_eq!(Some(re), c::c_udp_hdr_encode(&cf)); - Some(true) -} - -fn cmp_varint_decode(input: &[u8]) -> Option { - let r = varint::decode(input).ok(); - assert_eq!(r, c::varint_decode(input), "varint decode on {input:02x?}"); - r.map(|_| true) -} - -// ---- 1. corpus replay ---- - -fn replay(dir: &str, cmp: Cmp) { - let path = format!("{}/../../fuzz/corpus/{dir}", env!("CARGO_MANIFEST_DIR")); - let mut n = 0; - for e in std::fs::read_dir(&path).unwrap_or_else(|e| panic!("{path}: {e}")) { - cmp(&std::fs::read(e.unwrap().path()).unwrap()); - n += 1; - } - assert!(n > 0, "empty corpus {path}"); -} - -#[test] -fn corpus_replay() { - replay("wire_auth_req", cmp_auth_req); - replay("wire_auth_resp", cmp_auth_resp); - replay("wire_connect_tcp_req", cmp_connect_tcp_req); - replay("wire_connect_tcp_resp", cmp_connect_tcp_resp); - replay("wire_udp_session_open", cmp_udp_open); - replay("wire_udp_session_resp", cmp_udp_resp); - replay("udp_msg_hdr", cmp_udp_hdr); - replay("varint", cmp_varint_decode); -} - -// ---- generators: raw wire bytes built field by field ---- -// `wild` leaves the domain (over-cap strings, NULs, error codes >= 2^31, bad -// address types). In-domain frames still get non-minimal varints, padding and -// trailing bytes, so the consumed length is exercised. - -fn put_varint(out: &mut Vec, rng: &mut Rng, v: u64) { - // Sometimes a wider prefix than needed; both decoders must accept it. - let n = [1, 2, 4, 8][rng.below(4) as usize].max(varint::len(v)); - let mut b = v.to_be_bytes()[8 - n..].to_vec(); - b[0] |= match n { - 1 => 0x00, - 2 => 0x40, - 4 => 0x80, - _ => 0xC0, - }; - out.extend(b); -} - -fn put_string(out: &mut Vec, rng: &mut Rng, cap: u64, wild: bool, nul_ok: bool) { - let len = if wild && rng.chance(20) { - cap + 1 + rng.below(4) - } else { - rng.below(cap + 1) - }; - put_varint(out, rng, len); - for _ in 0..len { - let b = rng.next() as u8; - out.push(if b == 0 && !(nul_ok || wild) { 1 } else { b }); - } -} - -fn error_code(rng: &mut Rng, wild: bool) -> u64 { - if wild && rng.chance(20) { - ERR_DOMAIN + rng.below(varint::MAX - ERR_DOMAIN + 1) - } else { - rng.width(31) - } -} - -fn finish(out: &mut Vec, rng: &mut Rng) { - let pad = if rng.chance(30) { rng.below(16) } else { 0 }; - put_varint(out, rng, pad); - let trailing = if rng.chance(20) { rng.below(8) } else { 0 }; - out.extend((0..pad + trailing).map(|_| rng.next() as u8)); -} - -fn gen_auth_req(rng: &mut Rng, wild: bool) -> Vec { - let mut o = Vec::new(); - let v = rng.width(62); - put_varint(&mut o, rng, v); - put_string(&mut o, rng, 63, wild, false); - put_string(&mut o, rng, 255, wild, false); - let v = rng.width(62); - put_varint(&mut o, rng, v); - finish(&mut o, rng); - o -} - -fn gen_auth_resp(rng: &mut Rng, wild: bool) -> Vec { - let mut o = vec![rng.next() as u8]; - let e = error_code(rng, wild); - put_varint(&mut o, rng, e); - put_string(&mut o, rng, 63, wild, false); - let v = rng.width(62); - put_varint(&mut o, rng, v); - finish(&mut o, rng); - o -} - -fn gen_connect_tcp_req(rng: &mut Rng, wild: bool) -> Vec { - let mut o = Vec::new(); - let v = rng.width(62); - put_varint(&mut o, rng, v); - let at = [AddrType::Ipv4, AddrType::Domain, AddrType::Ipv6][rng.below(3) as usize] as u8; - o.push(if wild && rng.chance(20) { - rng.next() as u8 - } else { - at - }); - put_string(&mut o, rng, 255, wild, true); - o.extend((rng.next() as u16).to_be_bytes()); - finish(&mut o, rng); - o -} - -fn gen_connect_tcp_resp(rng: &mut Rng, wild: bool) -> Vec { - let mut o = vec![rng.next() as u8]; - let e = error_code(rng, wild); - put_varint(&mut o, rng, e); - put_string(&mut o, rng, 255, wild, false); - finish(&mut o, rng); - o -} - -fn gen_udp_open(rng: &mut Rng, wild: bool) -> Vec { - let mut o = Vec::new(); - let sid = if wild && rng.chance(20) { - (1 << 32) | rng.width(30) - } else { - rng.width(32) - }; - put_varint(&mut o, rng, sid); - let v = rng.width(62); - put_varint(&mut o, rng, v); - let at = [AddrType::Ipv4, AddrType::Domain, AddrType::Ipv6][rng.below(3) as usize] as u8; - o.push(if wild && rng.chance(20) { - rng.next() as u8 - } else { - at - }); - put_string(&mut o, rng, 255, wild, true); - o.extend((rng.next() as u16).to_be_bytes()); - let v = rng.width(62); - put_varint(&mut o, rng, v); - finish(&mut o, rng); - o -} - -fn gen_udp_resp(rng: &mut Rng, wild: bool) -> Vec { - // In domain: OK with code 0, or ERROR with code 1..=4. Wild: any status - // 0..=3 with any code, so inconsistent pairs and code >= 5 are both hit. - let (status, code) = if wild && rng.chance(30) { - let code = if rng.chance(30) { - error_code(rng, true) - } else { - rng.below(8) - }; - (rng.below(4) as u8, code) - } else if rng.chance(50) { - (0, 0) - } else { - (1, 1 + rng.below(4)) - }; - let mut o = vec![status]; - put_varint(&mut o, rng, code); - put_string(&mut o, rng, 255, wild, true); - let v = rng.width(62); - put_varint(&mut o, rng, v); - finish(&mut o, rng); - o -} - -/// 9 random bytes, then a random payload (ignored by both decoders). -fn gen_udp_hdr(rng: &mut Rng, _wild: bool) -> Vec { - (0..UDP_MSG_HDR as u64 + rng.below(8)) - .map(|_| rng.next() as u8) - .collect() -} - -type Gen = fn(&mut Rng, bool) -> Vec; -type Cmp = fn(&[u8]) -> Option; -const FRAMES: [(&str, Gen, Cmp); 4] = [ - ("auth_req", gen_auth_req, cmp_auth_req), - ("auth_resp", gen_auth_resp, cmp_auth_resp), - ("connect_tcp_req", gen_connect_tcp_req, cmp_connect_tcp_req), - ( - "connect_tcp_resp", - gen_connect_tcp_resp, - cmp_connect_tcp_resp, - ), -]; - -// ---- 2. in-domain generated frames ---- - -fn check_in_domain(name: &str, rng: &mut Rng, generate: Gen, cmp: Cmp) { - for _ in 0..N { - let input = generate(rng, false); - assert_eq!( - cmp(&input), - Some(true), - "{name}: not decoded in-domain: {input:02x?}" - ); - } -} - -#[test] -fn generated_in_domain() { - let mut rng = Rng(0x9E37_79B9_7F4A_7C15); - for (name, generate, cmp) in FRAMES { - check_in_domain(name, &mut rng, generate, cmp); - } -} - -// ---- 3. out-of-domain / mutated frames: outcome parity ---- - -fn check_mutated(name: &str, rng: &mut Rng, generate: Gen, cmp: Cmp) { - let (mut ok, mut err) = (0, 0); - for i in 0..N { - let mut input = generate(rng, true); - match i % 4 { - 0 => input.truncate(rng.below(input.len() as u64) as usize), - 1 => { - let at = rng.below(input.len() as u64) as usize; - input[at] = rng.next() as u8; - } - 2 => input = (0..rng.below(40)).map(|_| rng.next() as u8).collect(), - _ => {} // wild fields only - } - match cmp(&input) { - Some(_) => ok += 1, - None => err += 1, - } - } - // Both outcomes must actually be exercised. - assert!(ok > N / 10 && err > N / 10, "{name}: ok={ok} err={err}"); -} - -#[test] -fn generated_out_of_domain_and_mutated() { - let mut rng = Rng(0xD1B5_4A32_D192_ED03); - for (name, generate, cmp) in FRAMES { - check_mutated(name, &mut rng, generate, cmp); - } -} - -// ---- 4. varint ---- - -#[test] -fn varint_encode_parity() { - let mut rng = Rng(0xA076_1D64_78BD_642F); - for _ in 0..N { - let v = rng.width(64); // widths 63/64 exceed MAX: rejection parity - let cap = if rng.chance(10) { - rng.below(8) as usize - } else { - 8 - }; - let mut buf = [0u8; 8]; - let r = varint::encode(&mut buf[..cap], v) - .ok() - .map(|n| buf[..n].to_vec()); - assert_eq!( - r, - c::varint_encode(cap, v), - "varint encode {v:#x} cap {cap}" - ); - } -} - -#[test] -fn varint_decode_parity() { - let mut rng = Rng(0xE703_7ED1_A0B4_28DB); - for _ in 0..N { - let input: Vec = (0..rng.below(10)).map(|_| rng.next() as u8).collect(); - cmp_varint_decode(&input); - } -} - -// ---- 5. UDP frames and the datagram header (spec §2.1, §2.2) ---- - -fn assert_rejected(cmp: Cmp, input: &[u8]) { - assert_eq!(cmp(input), None, "both must reject {input:02x?}"); -} - -/// Both decoders accept `valid` and reject every strict prefix of it (the -/// trailing padding length is mandatory, so no prefix is a frame). -fn assert_prefixes_rejected(cmp: Cmp, valid: &[u8]) { - assert_eq!(cmp(valid), Some(true), "control {valid:02x?}"); - for n in 0..valid.len() { - assert_rejected(cmp, &valid[..n]); - } -} - -fn open_c(f: &UdpSessionOpen) -> c::UdpSessionOpenC { - // SAFETY: all-zero is a valid value of this plain-data struct. - let mut cf: c::UdpSessionOpenC = unsafe { std::mem::zeroed() }; - cf.session_id = f.session_id; - cf.flags = f.flags; - cf.address_type = f.address_type as i32; - cf.host[..f.host.len()].copy_from_slice(f.host); - cf.host_len = f.host.len(); - cf.port = f.port; - cf.idle_timeout_ms = f.idle_timeout_ms; - cf -} - -fn resp_c(f: &UdpSessionResp) -> c::UdpSessionRespC { - // SAFETY: all-zero is a valid value of this plain-data struct. - let mut cf: c::UdpSessionRespC = unsafe { std::mem::zeroed() }; - cf.status = i32::from(f.status); - cf.error_code = f.error_code as i32; - cf.message[..f.message.len()].copy_from_slice(f.message); - cf.message_len = f.message.len(); - cf.idle_timeout_ms = f.idle_timeout_ms; - cf -} - -fn rust_encode(f: impl FnOnce(&mut [u8]) -> Result) -> Option> { - let mut out = [0u8; MAX_FRAME]; - f(&mut out).ok().map(|n| out[..n].to_vec()) -} - -fn random_bytes(rng: &mut Rng, max: u64) -> Vec { - (0..rng.below(max + 1)).map(|_| rng.next() as u8).collect() -} - -#[test] -fn udp_open_equal() { - let mut rng = Rng(0x1F83_D9AB_FB41_BD6B); - check_in_domain("udp_open", &mut rng, gen_udp_open, cmp_udp_open); - check_mutated("udp_open", &mut rng, gen_udp_open, cmp_udp_open); - - // Encode side: random structs, including varints above 2^62-1 (both reject). - let mut encoded = 0; - for _ in 0..N { - let host = random_bytes(&mut rng, 255); - let f = UdpSessionOpen { - session_id: rng.width(32) as u32, - flags: rng.width(64), - address_type: [AddrType::Ipv4, AddrType::Domain, AddrType::Ipv6][rng.below(3) as usize], - host: &host, - port: rng.next() as u16, - idle_timeout_ms: rng.width(64), - }; - let r = rust_encode(|o| f.encode(o)); - assert_eq!(r, c::encode_udp_session_open(&open_c(&f)), "encode {f:?}"); - encoded += usize::from(r.is_some()); - } - assert!(encoded > N / 10 && encoded < N, "encoded {encoded}/{N}"); - - // sid 0 | flags 0 | atype 1 | host "" | port 0 | idle 0 | pad 0 - let valid = [0, 0, 1, 0, 0, 0, 0, 0]; - assert_prefixes_rejected(cmp_udp_open, &valid); - let sid_2_32 = [0xC0, 0, 0, 1, 0, 0, 0, 0]; - assert_rejected(cmp_udp_open, &[&sid_2_32[..], &valid[1..]].concat()); - assert_rejected(cmp_udp_open, &sid_2_32); // invalid AND truncated - for at in [0x00, 0x02, 0x05, 0xFF] { - assert_rejected(cmp_udp_open, &[0, 0, at, 0, 0, 0, 0, 0]); - } - // C refuses to encode an address type outside {1, 3, 4}. - let mut cf = open_c(&UdpSessionOpen { - session_id: 0, - flags: 0, - address_type: AddrType::Ipv4, - host: b"", - port: 0, - idle_timeout_ms: 0, - }); - cf.address_type = 5; - assert_eq!(c::encode_udp_session_open(&cf), None); -} - -#[test] -fn udp_resp_equal() { - let mut rng = Rng(0x5BE0_CD19_137E_2179); - check_in_domain("udp_resp", &mut rng, gen_udp_resp, cmp_udp_resp); - check_mutated("udp_resp", &mut rng, gen_udp_resp, cmp_udp_resp); - - // Encode side: status 0..=2 with code 0..=6 (5 is C's boundary-only CLOSED). - let mut encoded = 0; - for _ in 0..N { - let message = random_bytes(&mut rng, 255); - let f = UdpSessionResp { - status: rng.below(3) as u8, - error_code: rng.below(7), - message: &message, - idle_timeout_ms: rng.width(64), - }; - let r = rust_encode(|o| f.encode(o)); - assert_eq!(r, c::encode_udp_session_resp(&resp_c(&f)), "encode {f:?}"); - encoded += usize::from(r.is_some()); - } - assert!(encoded > N / 10 && encoded < N, "encoded {encoded}/{N}"); - - // status | code | message "" | idle 0 | pad 0 - assert_prefixes_rejected(cmp_udp_resp, &[0, 0, 0, 0, 0]); - assert_prefixes_rejected(cmp_udp_resp, &[1, 4, 0, 0, 0]); - for bad in [ - [1, 5, 0, 0, 0], // error_code 5 (CLOSED) is boundary-only - [1, 6, 0, 0, 0], - [0, 1, 0, 0, 0], // OK with a non-zero code - [1, 0, 0, 0, 0], // ERROR with code 0 - [2, 0, 0, 0, 0], // status is neither OK nor ERROR - [2, 1, 0, 0, 0], - ] { - assert_rejected(cmp_udp_resp, &bad); - assert_rejected(cmp_udp_resp, &bad[..2]); // invalid AND truncated - } -} - -#[test] -fn udp_hdr_equal() { - let mut rng = Rng(0x6C44_198C_4A47_5817); - check_in_domain("udp_hdr", &mut rng, gen_udp_hdr, cmp_udp_hdr); - check_mutated("udp_hdr", &mut rng, gen_udp_hdr, cmp_udp_hdr); - - // Encode side: C and Rust emit the same 9 bytes, field extremes included. - for i in 0..N { - let pick = |rng: &mut Rng, max: u64| match i % 3 { - 0 => 0, - 1 => max, - _ => rng.below(max + 1), - }; - let h = UdpMsgHdr { - session_id: pick(&mut rng, u64::from(u32::MAX)) as u32, - packet_id: pick(&mut rng, u64::from(u16::MAX)) as u16, - flags: pick(&mut rng, 255) as u8, - frag_id: pick(&mut rng, 255) as u8, - frag_count: pick(&mut rng, 255) as u8, - }; - let mut re = [0u8; UDP_MSG_HDR]; - h.encode(&mut re); - let cf = c::UdpMsgHdrC { - session_id: h.session_id, - packet_id: h.packet_id, - flags: h.flags, - frag_id: h.frag_id, - frag_count: h.frag_count, - }; - assert_eq!(c::c_udp_hdr_encode(&cf), Some(re), "encode {h:?}"); - } - - // Strictly shorter than 9 bytes: rejected; 9 bytes or more: accepted. - let valid = [0u8; UDP_MSG_HDR]; - for n in 0..UDP_MSG_HDR { - assert_rejected(cmp_udp_hdr, &valid[..n]); - } - assert_eq!(cmp_udp_hdr(&valid), Some(true)); - assert_eq!(cmp_udp_hdr(&[0xFF; UDP_MSG_HDR + 5]), Some(true)); -} - -#[test] -fn struct_layout_matches_header() { - assert_eq!(c::rust_layout(), c::c_layout()); -} - -#[test] -#[should_panic(expected = "server_id is not NUL-terminated")] -fn c_encode_refuses_unterminated_string() { - let (mut f, _) = c::decode_auth_resp(&[0, 0, 0, 0, 0]).unwrap(); - f.server_id = [b'x'; 64]; - c::encode_auth_resp(&f); -} diff --git a/crates/mq-wire/tests/c_golden.rs b/crates/mq-wire/tests/c_golden.rs new file mode 100644 index 00000000..77711af0 --- /dev/null +++ b/crates/mq-wire/tests/c_golden.rs @@ -0,0 +1,66 @@ +//! Frozen at SP5 from C/Rust differential comparisons on cd41d699. +//! Every full row passed field, consumed-length and re-encode parity against C. +//! Outside C's representable domain only acceptance is frozen (as in the old test). +use mq_wire::{ + frames::*, + udp_msg::{UDP_MSG_HDR, UdpMsgHdr}, + varint, +}; + +#[test] +fn c_codec_vectors() { + for (i, row) in include_str!("data/c-codec-vectors.txt").lines().enumerate() { + let mut fields = row.splitn(3, '\t'); + let kind = fields.next().unwrap(); + let hex = fields.next().unwrap(); + let want = fields.next().unwrap(); + let input: Vec<_> = (0..hex.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()) + .collect(); + if want == "accept" { + let accepted = match kind { + "auth_req" => AuthReq::decode(&input).is_ok(), + "auth_resp" => AuthResp::decode(&input).is_ok(), + "tcp_resp" => ConnectTcpResp::decode(&input).is_ok(), + _ => panic!("unexpected limited-domain kind {kind}"), + }; + assert!(accepted, "vector {} {kind}", i + 1); + } else { + assert_eq!(snapshot(kind, &input), want, "vector {} {kind}", i + 1); + } + } +} + +fn snapshot(kind: &str, input: &[u8]) -> String { + macro_rules! frame { + ($ty:ty) => { + match <$ty>::decode(input) { + Ok((f, n)) => { + let mut out = [0; MAX_FRAME]; + let encoded = f.encode(&mut out).unwrap(); + format!("{f:?};{n};{:02x?}", &out[..encoded]) + } + Err(_) => "reject".to_string(), + } + }; + } + match kind { + "auth_req" => frame!(AuthReq), + "auth_resp" => frame!(AuthResp), + "tcp_req" => frame!(ConnectTcpReq), + "tcp_resp" => frame!(ConnectTcpResp), + "udp_open" => frame!(UdpSessionOpen), + "udp_resp" => frame!(UdpSessionResp), + "udp_hdr" => match UdpMsgHdr::decode(input) { + Some(h) => { + let mut b = [0; UDP_MSG_HDR]; + h.encode(&mut b); + format!("{h:?};{b:02x?}") + } + None => "reject".to_string(), + }, + "varint" => format!("{:?}", varint::decode(input).ok()), + _ => panic!("unknown vector kind {kind}"), + } +} diff --git a/crates/mq-wire/tests/data/c-codec-vectors.txt b/crates/mq-wire/tests/data/c-codec-vectors.txt new file mode 100644 index 00000000..c42bd68f --- /dev/null +++ b/crates/mq-wire/tests/data/c-codec-vectors.txt @@ -0,0 +1,1892 @@ +auth_req c0000000ffffffff3f41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414140ff424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242c0000000deadbeef00 AuthReq { version: 4294967295, client_id: [65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65], auth_token: [66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66], features: 3735928559 };338;[c0, 00, 00, 00, ff, ff, ff, ff, 3f, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 40, ff, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, c0, 00, 00, 00, de, ad, be, ef, 00] +auth_req 010263310274310000 AuthReq { version: 1, client_id: [99, 49], auth_token: [116, 49], features: 0 };9;[01, 02, 63, 31, 02, 74, 31, 00, 00] +auth_req reject +auth_req 80 reject +auth_req 8000 reject +auth_req 800042 reject +auth_req 800042d0 reject +auth_req 800042d040 reject +auth_req 800042d0402d reject +auth_req 800042d0402dc6 reject +auth_req 800042d0402dc6fb reject +auth_req 800042d0402dc6fb42 reject +auth_req 800042d0402dc6fb4244 reject +auth_req 800042d0402dc6fb424429 reject +auth_req 800042d0402dc6fb42442990 reject +auth_req 800042d0402dc6fb42442990d8 reject +auth_req 800042d0402dc6fb42442990d8e5 reject +auth_req 800042d0402dc6fb42442990d8e52b reject +auth_req 800042d0402dc6fb42442990d8e52beb reject +auth_req 800042d0402dc6fb42442990d8e52beb09 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5a reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1b reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1bab reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaa reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0de reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1de reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded3 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded35878 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab166396 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16639675 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d2 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa3365 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33659324 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241f reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe448 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4481271 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc000 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc00000 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc000000000 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc00000000000 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a3 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a4 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f11 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f8840 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a30 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bf reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd6 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2b reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf7 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5e reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef8 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f1 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f141 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b7 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d0 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05b reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbd reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdab reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c436 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e88 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5d reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d067 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a3814 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140e reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d3 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c0 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c09806 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1e reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b8 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b888 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b5 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b59 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b5934 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e6 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64b reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be1 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a7 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a79682 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d71 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d7166 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c836 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a79 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e7 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e722 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262ba reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacd reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd1 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f66 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f97 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f9770 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f97708996 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f69 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f699932 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e52 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e5205 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a3 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e4 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44a reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add8688 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e332 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322b reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfb reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe3 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39c reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a7 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74d reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd9 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd91771 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717e reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c65 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657a reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad3 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4b reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd6 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd696 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69601 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd6960180 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd696018000 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69601800003 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd696018000037d reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd696018000037d40 reject +auth_req 800042d0402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714cc0000000000000a384a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd696018000037d4000 AuthReq { version: 17104, client_id: [198, 251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76], auth_token: [132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244, 107, 30, 239, 29, 38, 233, 184, 136, 69, 242, 213, 229, 181, 139, 89, 52, 116, 230, 75, 225, 17, 167, 150, 130, 93, 113, 102, 21, 200, 54, 126, 138, 121, 96, 231, 34, 98, 186, 205, 209, 72, 172, 95, 102, 63, 151, 112, 137, 150, 127, 105, 153, 50, 30, 82, 5, 84, 163, 148, 204, 77, 58, 12, 100, 228, 74, 221, 134, 136, 76, 55, 227, 50, 43, 251, 227, 156, 202, 123, 83, 167, 77, 217, 23, 113, 126, 173, 44, 101, 122, 211, 16, 172, 2, 236, 62, 75, 214, 150, 1], features: 893 };228;[80, 00, 42, d0, 2d, c6, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, 40, a3, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 6b, 1e, ef, 1d, 26, e9, b8, 88, 45, f2, d5, e5, b5, 8b, 59, 34, 74, e6, 4b, e1, 11, a7, 96, 82, 5d, 71, 66, 15, c8, 36, 7e, 8a, 79, 60, e7, 22, 62, ba, cd, d1, 48, ac, 5f, 66, 3f, 97, 70, 89, 96, 7f, 69, 99, 32, 1e, 52, 05, 54, a3, 94, cc, 4d, 3a, 0c, 64, e4, 4a, dd, 86, 88, 4c, 37, e3, 32, 2b, fb, e3, 9c, ca, 7b, 53, a7, 4d, d9, 17, 71, 7e, ad, 2c, 65, 7a, d3, 10, ac, 02, ec, 3e, 4b, d6, 96, 01, 43, 7d, 00] +auth_req 40ab402837a4c9d4896d14dcc8be6fcb3cae2a8ed41906905a00c79116c42f6efc3abeb588cd8433e48b916d40a0f1f56dfaae34040bfee40c54373755fff8070a31ca4e98ed392278b7de69abdbccd6ed89165eb4cbdf27f34cdb610cfbb98a6eb961a9f34e79c941b28dceab810b061154399cb6cb22960706ecdc059f8ab72d06629b4d6881b73d140f46922fe7829f40b4905e000456b9d4cfa52935adb2d90b422e69297d8132b12d4bee0c567158668dce9817977065d567234e29dc1c0b073bf895cd7c66ace40ecab63cc00fac3b85dc84ce00 accept +auth_req c000000fc5ce8624403151a93fe5a5c892b3dccdef9372f6478d38d1ce0428cddc2a44f8de699a9723e60222de2997de191c1d712fffa0700dd5448000003f9102b81179b3a2f93ca80eddf35bce6aa609439c89860b0173935e59d0429831aedbee71abb4ded9744f25778f2c5e18c83a516e0f8c52a1325cd3df62256080eb31ea00 AuthReq { version: 67743155748, client_id: [81, 169, 63, 229, 165, 200, 146, 179, 220, 205, 239, 147, 114, 246, 71, 141, 56, 209, 206, 4, 40, 205, 220, 42, 68, 248, 222, 105, 154, 151, 35, 230, 2, 34, 222, 41, 151, 222, 25, 28, 29, 113, 47, 255, 160, 112, 13, 213, 68], auth_token: [145, 2, 184, 17, 121, 179, 162, 249, 60, 168, 14, 221, 243, 91, 206, 106, 166, 9, 67, 156, 137, 134, 11, 1, 115, 147, 94, 89, 208, 66, 152, 49, 174, 219, 238, 113, 171, 180, 222, 217, 116, 79, 37, 119, 143, 44, 94, 24, 200, 58, 81, 110, 15, 140, 82, 161, 50, 92, 211, 223, 98, 37, 96], features: 15413738 };131;[c0, 00, 00, 0f, c5, ce, 86, 24, 31, 51, a9, 3f, e5, a5, c8, 92, b3, dc, cd, ef, 93, 72, f6, 47, 8d, 38, d1, ce, 04, 28, cd, dc, 2a, 44, f8, de, 69, 9a, 97, 23, e6, 02, 22, de, 29, 97, de, 19, 1c, 1d, 71, 2f, ff, a0, 70, 0d, d5, 44, 3f, 91, 02, b8, 11, 79, b3, a2, f9, 3c, a8, 0e, dd, f3, 5b, ce, 6a, a6, 09, 43, 9c, 89, 86, 0b, 01, 73, 93, 5e, 59, d0, 42, 98, 31, ae, db, ee, 71, ab, b4, de, d9, 74, 4f, 25, 77, 8f, 2c, 5e, 18, c8, 3a, 51, 6e, 0f, 8c, 52, a1, 32, 5c, d3, df, 62, 25, 60, 80, eb, 31, ea, 00] +auth_req 80000575c00000000000000ffb440fc12d9199d03b0f93eec7afbb4063fc94f152f44291b4754d6941f0a709f23f7a075e2826813e113c9a06f49871ddb099ab2ad13be057c703b111585e8fe76d619f2dfb72c906ad202f604a64b4338f589eb76fd4f39162284f326cd28e0daa0fc04b7e0f07f875c4491f0876db23e7a935c002c6837e6a20de4000 AuthReq { version: 1397, client_id: [251, 68, 15, 193, 45, 145, 153, 208, 59, 15, 147, 238, 199, 175, 187], auth_token: [252, 148, 241, 82, 244, 66, 145, 180, 117, 77, 105, 65, 240, 167, 9, 242, 63, 122, 7, 94, 40, 38, 129, 62, 17, 60, 154, 6, 244, 152, 113, 221, 176, 153, 171, 42, 209, 59, 224, 87, 199, 3, 177, 17, 88, 94, 143, 231, 109, 97, 159, 45, 251, 114, 201, 6, 173, 32, 47, 96, 74, 100, 180, 51, 143, 88, 158, 183, 111, 212, 243, 145, 98, 40, 79, 50, 108, 210, 142, 13, 170, 15, 192, 75, 126, 15, 7, 248, 117, 196, 73, 31, 8, 118, 219, 35, 231, 169, 53], features: 781218017321182 };138;[45, 75, 0f, fb, 44, 0f, c1, 2d, 91, 99, d0, 3b, 0f, 93, ee, c7, af, bb, 40, 63, fc, 94, f1, 52, f4, 42, 91, b4, 75, 4d, 69, 41, f0, a7, 09, f2, 3f, 7a, 07, 5e, 28, 26, 81, 3e, 11, 3c, 9a, 06, f4, 98, 71, dd, b0, 99, ab, 2a, d1, 3b, e0, 57, c7, 03, b1, 11, 58, 5e, 8f, e7, 6d, 61, 9f, 2d, fb, 72, c9, 06, ad, 20, 2f, 60, 4a, 64, b4, 33, 8f, 58, 9e, b7, 6f, d4, f3, 91, 62, 28, 4f, 32, 6c, d2, 8e, 0d, aa, 0f, c0, 4b, 7e, 0f, 07, f8, 75, c4, 49, 1f, 08, 76, db, 23, e7, a9, 35, c0, 02, c6, 83, 7e, 6a, 20, de, 00] +auth_req c0000f96f846a62d03b16bc3800000485ebdf687fa97bc7df349eebf01d7dbf32b7b4a8184c8e308239c4836e06e6f380ce1d0ac10416355ea789123d60736259222b981ebb7f3cd4831e326334a213ea9200cb0349e1029c000000000000003400c7d48993335dbf42f5f671920 AuthReq { version: 17141084890669, client_id: [177, 107, 195], auth_token: [94, 189, 246, 135, 250, 151, 188, 125, 243, 73, 238, 191, 1, 215, 219, 243, 43, 123, 74, 129, 132, 200, 227, 8, 35, 156, 72, 54, 224, 110, 111, 56, 12, 225, 208, 172, 16, 65, 99, 85, 234, 120, 145, 35, 214, 7, 54, 37, 146, 34, 185, 129, 235, 183, 243, 205, 72, 49, 227, 38, 51, 74, 33, 62, 169, 32, 12, 176, 52, 158, 16, 41], features: 3 };110;[c0, 00, 0f, 96, f8, 46, a6, 2d, 03, b1, 6b, c3, 40, 48, 5e, bd, f6, 87, fa, 97, bc, 7d, f3, 49, ee, bf, 01, d7, db, f3, 2b, 7b, 4a, 81, 84, c8, e3, 08, 23, 9c, 48, 36, e0, 6e, 6f, 38, 0c, e1, d0, ac, 10, 41, 63, 55, ea, 78, 91, 23, d6, 07, 36, 25, 92, 22, b9, 81, eb, b7, f3, cd, 48, 31, e3, 26, 33, 4a, 21, 3e, a9, 20, 0c, b0, 34, 9e, 10, 29, 03, 00] +auth_req c00001b8fc94b8db403323f338e439ffa35fc5b8e912eea23e9e2244c404deb1a55b145b518b6690a19107239e1ae2aa02ac8bb836e4bc10bdd6c8c78280000040db22e04dad0a29845530607429de147262c0d0a4cafdd275a3086e36c82209de102a61b2521a8db3d8856f8f26e3cce9a378180da007ed46690447f9dbfdac364013c000000000000000 AuthReq { version: 1894023215323, client_id: [35, 243, 56, 228, 57, 255, 163, 95, 197, 184, 233, 18, 238, 162, 62, 158, 34, 68, 196, 4, 222, 177, 165, 91, 20, 91, 81, 139, 102, 144, 161, 145, 7, 35, 158, 26, 226, 170, 2, 172, 139, 184, 54, 228, 188, 16, 189, 214, 200, 199, 130], auth_token: [219, 34, 224, 77, 173, 10, 41, 132, 85, 48, 96, 116, 41, 222, 20, 114, 98, 192, 208, 164, 202, 253, 210, 117, 163, 8, 110, 54, 200, 34, 9, 222, 16, 42, 97, 178, 82, 26, 141, 179, 216, 133, 111, 143, 38, 227, 204, 233, 163, 120, 24, 13, 160, 7, 237, 70, 105, 4, 71, 249, 219, 253, 172, 54], features: 19 };139;[c0, 00, 01, b8, fc, 94, b8, db, 33, 23, f3, 38, e4, 39, ff, a3, 5f, c5, b8, e9, 12, ee, a2, 3e, 9e, 22, 44, c4, 04, de, b1, a5, 5b, 14, 5b, 51, 8b, 66, 90, a1, 91, 07, 23, 9e, 1a, e2, aa, 02, ac, 8b, b8, 36, e4, bc, 10, bd, d6, c8, c7, 82, 40, 40, db, 22, e0, 4d, ad, 0a, 29, 84, 55, 30, 60, 74, 29, de, 14, 72, 62, c0, d0, a4, ca, fd, d2, 75, a3, 08, 6e, 36, c8, 22, 09, de, 10, 2a, 61, b2, 52, 1a, 8d, b3, d8, 85, 6f, 8f, 26, e3, cc, e9, a3, 78, 18, 0d, a0, 07, ed, 46, 69, 04, 47, f9, db, fd, ac, 36, 13, 00] +auth_req c00000007ac4121d80000012909386f0d69d94ff84807fdf7f175f02c04c2bc4391545f302f04acfd1107e3320759d09fdb3441b6f706af9a059f36b595a73942c77f0355f628d447143c0030902bfe04f0e80000000 AuthReq { version: 2059670045, client_id: [144, 147, 134, 240, 214, 157, 148, 255, 132, 128, 127, 223, 127, 23, 95, 2, 192, 76], auth_token: [196, 57, 21, 69, 243, 2, 240, 74, 207, 209, 16, 126, 51, 32, 117, 157, 9, 253, 179, 68, 27, 111, 112, 106, 249, 160, 89, 243, 107, 89, 90, 115, 148, 44, 119, 240, 53, 95, 98, 141, 68, 113, 67], features: 854332343865102 };86;[c0, 00, 00, 00, 7a, c4, 12, 1d, 12, 90, 93, 86, f0, d6, 9d, 94, ff, 84, 80, 7f, df, 7f, 17, 5f, 02, c0, 4c, 2b, c4, 39, 15, 45, f3, 02, f0, 4a, cf, d1, 10, 7e, 33, 20, 75, 9d, 09, fd, b3, 44, 1b, 6f, 70, 6a, f9, a0, 59, f3, 6b, 59, 5a, 73, 94, 2c, 77, f0, 35, 5f, 62, 8d, 44, 71, 43, c0, 03, 09, 02, bf, e0, 4f, 0e, 00] +auth_req c000001bb6ac799bc0000000000000404cf8091dd248e79b017248944b89864107dbf685fcbe49684a8eba6596a100ae82f2974ee2ccd7a3a4bc853fa8d8403bf5d3646a569ca22bdab971748c9fe003407c31efa1386799e942737721a21f11fb7dc3537fea049b46022c1b35e94d3e4df3e7bd7b77c0478621e9b68d05f26056b7dcba0d10fd36f177853ea5ecf64e1d1f3b0ebdce35069376735d0a7020a1e7e6b7aeec5c6e786d545a46e23b2916bc104f33c25262177da56b178762e599b12c606b256d4133f364f97e373dc0000000000000004000a2d794 reject +auth_req c0019b124d7f18cdc00000000000003e7241574f136235d96ee0961c25b4d2a6315fea3009dea32eddcfec7a30f014861e9822713d0d43407ddee1caa46a2c39d4b269ead41d9542b8014440b9cf408152dfe4a3a944b1194ebcb6f61a0868e12b6dc145a506dfd0423712f2e914219b90bca8abb2b4051c3b54a0ef9f1cca6c0113b0e0d2829140f72cb5d47c0add31b1c23fde5e9c17a297d1a34c3a30db4265cb082c8919d32ccb6a9a48ac18ce1d12b8bfbad1a0e58ad50963e8bbbe813ed69d013c99af6694f304c22848dafd9c61c00017123dcb39fd00 AuthReq { version: 451977888602317, client_id: [114, 65, 87, 79, 19, 98, 53, 217, 110, 224, 150, 28, 37, 180, 210, 166, 49, 95, 234, 48, 9, 222, 163, 46, 221, 207, 236, 122, 48, 240, 20, 134, 30, 152, 34, 113, 61, 13, 67, 64, 125, 222, 225, 202, 164, 106, 44, 57, 212, 178, 105, 234, 212, 29, 149, 66, 184, 1, 68, 64, 185, 207], auth_token: [82, 223, 228, 163, 169, 68, 177, 25, 78, 188, 182, 246, 26, 8, 104, 225, 43, 109, 193, 69, 165, 6, 223, 208, 66, 55, 18, 242, 233, 20, 33, 155, 144, 188, 168, 171, 178, 180, 5, 28, 59, 84, 160, 239, 159, 28, 202, 108, 1, 19, 176, 224, 210, 130, 145, 64, 247, 44, 181, 212, 124, 10, 221, 49, 177, 194, 63, 222, 94, 156, 23, 162, 151, 209, 163, 76, 58, 48, 219, 66, 101, 203, 8, 44, 137, 25, 211, 44, 203, 106, 154, 72, 172, 24, 206, 29, 18, 184, 191, 186, 209, 160, 229, 138, 213, 9, 99, 232, 187, 190, 129, 62, 214, 157, 1, 60, 153, 175, 102, 148, 243, 4, 194, 40, 72, 218, 253, 156, 97], features: 25367113579005 };218;[c0, 01, 9b, 12, 4d, 7f, 18, cd, 3e, 72, 41, 57, 4f, 13, 62, 35, d9, 6e, e0, 96, 1c, 25, b4, d2, a6, 31, 5f, ea, 30, 09, de, a3, 2e, dd, cf, ec, 7a, 30, f0, 14, 86, 1e, 98, 22, 71, 3d, 0d, 43, 40, 7d, de, e1, ca, a4, 6a, 2c, 39, d4, b2, 69, ea, d4, 1d, 95, 42, b8, 01, 44, 40, b9, cf, 40, 81, 52, df, e4, a3, a9, 44, b1, 19, 4e, bc, b6, f6, 1a, 08, 68, e1, 2b, 6d, c1, 45, a5, 06, df, d0, 42, 37, 12, f2, e9, 14, 21, 9b, 90, bc, a8, ab, b2, b4, 05, 1c, 3b, 54, a0, ef, 9f, 1c, ca, 6c, 01, 13, b0, e0, d2, 82, 91, 40, f7, 2c, b5, d4, 7c, 0a, dd, 31, b1, c2, 3f, de, 5e, 9c, 17, a2, 97, d1, a3, 4c, 3a, 30, db, 42, 65, cb, 08, 2c, 89, 19, d3, 2c, cb, 6a, 9a, 48, ac, 18, ce, 1d, 12, b8, bf, ba, d1, a0, e5, 8a, d5, 09, 63, e8, bb, be, 81, 3e, d6, 9d, 01, 3c, 99, af, 66, 94, f3, 04, c2, 28, 48, da, fd, 9c, 61, c0, 00, 17, 12, 3d, cb, 39, fd, 00] +auth_req c00004024138f1fa4040158042e8ef218fae7f134905df35216f23dc5cd18d2bdb1ed3b1de0d1912792624d925037c6804c98ce21fd0c967a0cab946bb853b8ff747349c1bea4b2efe1940cf39c3bb19c35e4c2bbd0b31adb1d3a1c9c0b43406a6cf4cec80e53f6ea0aba168e2872121a86b8574e1afc3978ac4db0c7a499d9173cef8755605b81f4de7a3d3a83db474c95f9ca6871fb7f32440ba1b10de500d0d259b5e52df7149ea99d44d5a9261837640e64ca491932cbe3953e3ff059159d4798b17ed87db84080468cdbe464687ab16d89ff2ddb657aa1e292401e6eadbf1f82e4b805b7c534d32cbde351b5cd8b0b48fc752e36ebb4c8b8ef7b57a4771dc86f1a519c93f92335862f6d73107fee4c6d94533aa23c5c74bbcc000000e908374fa80000000 reject +auth_req 877052d780000036443427c77260111cac482c1346b28121f1a2cbe2b37067e3fe6fcb87c92f1e7ffa3b27be3408dff01ed962bf124abbd243f7015c4950409dc568629433cac03123405e39d0f9924c4624785ff92f3750420dd8d7120ae0877fbe0527d2d75dd4cc65b5876179054ee3e5ec265fa3878a1925a7db8154a680e7825d253769adf8e7986ee54c97b1d4d9a77fb755eaa841e446cc262d8ea5012b84f291c8255c4eed48cfd63bc7c4865b1c2b9c456b64b8417b93a9c8903932efd3b74a5b528d29c97428d2cbd63b3b4075499a1683a97e2e3b82f70b800d9ba4800000099e8340c5ba1c37fcd4 AuthReq { version: 124801751, client_id: [68, 52, 39, 199, 114, 96, 17, 28, 172, 72, 44, 19, 70, 178, 129, 33, 241, 162, 203, 226, 179, 112, 103, 227, 254, 111, 203, 135, 201, 47, 30, 127, 250, 59, 39, 190, 52, 8, 223, 240, 30, 217, 98, 191, 18, 74, 187, 210, 67, 247, 1, 92, 73, 80], auth_token: [197, 104, 98, 148, 51, 202, 192, 49, 35, 64, 94, 57, 208, 249, 146, 76, 70, 36, 120, 95, 249, 47, 55, 80, 66, 13, 216, 215, 18, 10, 224, 135, 127, 190, 5, 39, 210, 215, 93, 212, 204, 101, 181, 135, 97, 121, 5, 78, 227, 229, 236, 38, 95, 163, 135, 138, 25, 37, 167, 219, 129, 84, 166, 128, 231, 130, 93, 37, 55, 105, 173, 248, 231, 152, 110, 229, 76, 151, 177, 212, 217, 167, 127, 183, 85, 234, 168, 65, 228, 70, 204, 38, 45, 142, 165, 1, 43, 132, 242, 145, 200, 37, 92, 78, 237, 72, 207, 214, 59, 199, 196, 134, 91, 28, 43, 156, 69, 107, 100, 184, 65, 123, 147, 169, 200, 144, 57, 50, 239, 211, 183, 74, 91, 82, 141, 41, 201, 116, 40, 210, 203, 214, 59, 59, 64, 117, 73, 154, 22, 131, 169, 126, 46, 59, 130, 247, 11], features: 891812 };238;[87, 70, 52, d7, 36, 44, 34, 27, c7, 72, 60, 11, 1c, ac, 48, 2c, 13, 46, b2, 81, 21, f1, a2, cb, e2, b3, 70, 67, e3, fe, 6f, cb, 87, c9, 2f, 1e, 7f, fa, 3b, 27, be, 34, 08, df, f0, 1e, d9, 62, bf, 12, 4a, bb, d2, 43, f7, 01, 5c, 49, 50, 40, 9d, c5, 68, 62, 94, 33, ca, c0, 31, 23, 40, 5e, 39, d0, f9, 92, 4c, 46, 24, 78, 5f, f9, 2f, 37, 50, 42, 0d, d8, d7, 12, 0a, e0, 87, 7f, be, 05, 27, d2, d7, 5d, d4, cc, 65, b5, 87, 61, 79, 05, 4e, e3, e5, ec, 26, 5f, a3, 87, 8a, 19, 25, a7, db, 81, 54, a6, 80, e7, 82, 5d, 25, 37, 69, ad, f8, e7, 98, 6e, e5, 4c, 97, b1, d4, d9, a7, 7f, b7, 55, ea, a8, 41, e4, 46, cc, 26, 2d, 8e, a5, 01, 2b, 84, f2, 91, c8, 25, 5c, 4e, ed, 48, cf, d6, 3b, c7, c4, 86, 5b, 1c, 2b, 9c, 45, 6b, 64, b8, 41, 7b, 93, a9, c8, 90, 39, 32, ef, d3, b7, 4a, 5b, 52, 8d, 29, c9, 74, 28, d2, cb, d6, 3b, 3b, 40, 75, 49, 9a, 16, 83, a9, 7e, 2e, 3b, 82, f7, 0b, 80, 0d, 9b, a4, 00] +auth_req 00401933a18e41c7733c83748c72e4a198db8ce4f183ce5b66014c8640588d2131086ae83feeedc398d9b88363bc79892934a7e819d2cf0ea0a41c07b59a06a2791ca24314194d8c1a74e817c1b139cfe2daedaa3a190e69742df4489bc00400a8d0cf4440b1142c96d2ab0497ee6372806c8743b1ec008000000158 accept +auth_req 40d6401d364732643b81dcf20bf823206672fb702f2c22e308de17fd7322d088994099870106b23ed44056e0bc3b6dc55b5787a938ff50485da1bc991985b29164997d339f4159d0ebf5d82de63ccda8d1f2f15a9f934a56f3e4db1ca35663161701cf464706193259507dc67d0dca619e396add06cc46f941a7ca77ffa99001859fe49cbf853d0a33c25539b8298c6a64b778f1fd703a61a1a5661d0c8ffa679bb2a0d63f89bbdd867cc8e993cb3cef180175b61deda347c7d10cbfc04f07270eb8820fc00000000000000c5c3d52163251e0927e021c98 AuthReq { version: 214, client_id: [54, 71, 50, 100, 59, 129, 220, 242, 11, 248, 35, 32, 102, 114, 251, 112, 47, 44, 34, 227, 8, 222, 23, 253, 115, 34, 208, 136, 153], auth_token: [135, 1, 6, 178, 62, 212, 64, 86, 224, 188, 59, 109, 197, 91, 87, 135, 169, 56, 255, 80, 72, 93, 161, 188, 153, 25, 133, 178, 145, 100, 153, 125, 51, 159, 65, 89, 208, 235, 245, 216, 45, 230, 60, 205, 168, 209, 242, 241, 90, 159, 147, 74, 86, 243, 228, 219, 28, 163, 86, 99, 22, 23, 1, 207, 70, 71, 6, 25, 50, 89, 80, 125, 198, 125, 13, 202, 97, 158, 57, 106, 221, 6, 204, 70, 249, 65, 167, 202, 119, 255, 169, 144, 1, 133, 159, 228, 156, 191, 133, 61, 10, 51, 194, 85, 57, 184, 41, 140, 106, 100, 183, 120, 241, 253, 112, 58, 97, 161, 165, 102, 29, 12, 143, 250, 103, 155, 178, 160, 214, 63, 137, 187, 221, 134, 124, 200, 233, 147, 203, 60, 239, 24, 1, 117, 182, 29, 237, 163, 71, 199, 209, 12, 191], features: 22244387492233743 };216;[40, d6, 1d, 36, 47, 32, 64, 3b, 81, dc, f2, 0b, f8, 23, 20, 66, 72, fb, 70, 2f, 2c, 22, e3, 08, de, 17, fd, 73, 22, d0, 88, 99, 40, 99, 87, 01, 06, b2, 3e, d4, 40, 56, e0, bc, 3b, 6d, c5, 5b, 57, 87, a9, 38, ff, 50, 48, 5d, a1, bc, 99, 19, 85, b2, 91, 64, 99, 7d, 33, 9f, 41, 59, d0, eb, f5, d8, 2d, e6, 3c, cd, a8, d1, f2, f1, 5a, 9f, 93, 4a, 56, f3, e4, db, 1c, a3, 56, 63, 16, 17, 01, cf, 46, 47, 06, 19, 32, 59, 50, 7d, c6, 7d, 0d, ca, 61, 9e, 39, 6a, dd, 06, cc, 46, f9, 41, a7, ca, 77, ff, a9, 90, 01, 85, 9f, e4, 9c, bf, 85, 3d, 0a, 33, c2, 55, 39, b8, 29, 8c, 6a, 64, b7, 78, f1, fd, 70, 3a, 61, a1, a5, 66, 1d, 0c, 8f, fa, 67, 9b, b2, a0, d6, 3f, 89, bb, dd, 86, 7c, c8, e9, 93, cb, 3c, ef, 18, 01, 75, b6, 1d, ed, a3, 47, c7, d1, 0c, bf, c0, 4f, 07, 27, 0e, b8, 82, 0f, 00] +auth_req c0424de6e19be2dd8000003d02a2378edcafb402131ac332d1e83d60aefe35bd40501a78b08031a1a309acf267f46473d6058b2e337a6a887509673329822b9bd838c2de468019edd2c0000000000000103ce00514be245d9c07914b51e15c363dd1f6df542f587c74400d14a2cb85c1e1662312bdeb05ee AuthReq { version: 18663002485809885, client_id: [2, 162, 55, 142, 220, 175, 180, 2, 19, 26, 195, 50, 209, 232, 61, 96, 174, 254, 53, 189, 64, 80, 26, 120, 176, 128, 49, 161, 163, 9, 172, 242, 103, 244, 100, 115, 214, 5, 139, 46, 51, 122, 106, 136, 117, 9, 103, 51, 41, 130, 43, 155, 216, 56, 194, 222, 70, 128, 25, 237, 210], auth_token: [60, 224, 5, 20, 190, 36, 93, 156, 7, 145, 75, 81, 225, 92, 54, 61], features: 1294467495580171380 };120;[c0, 42, 4d, e6, e1, 9b, e2, dd, 3d, 02, a2, 37, 8e, dc, af, b4, 02, 13, 1a, c3, 32, d1, e8, 3d, 60, ae, fe, 35, bd, 40, 50, 1a, 78, b0, 80, 31, a1, a3, 09, ac, f2, 67, f4, 64, 73, d6, 05, 8b, 2e, 33, 7a, 6a, 88, 75, 09, 67, 33, 29, 82, 2b, 9b, d8, 38, c2, de, 46, 80, 19, ed, d2, 10, 3c, e0, 05, 14, be, 24, 5d, 9c, 07, 91, 4b, 51, e1, 5c, 36, 3d, d1, f6, df, 54, 2f, 58, 7c, 74, 00] +auth_req 8285f5e6c000000000000028b3f9787b4098b529cf314ba725c0da3c06db39d9c2e2a1f64115e518c8ec7914d4dd2dbc85e149b8800000cd7e17c302cd853c75effe80a0906501ce05fbc007c7c982a90dbba03d0fff7d4455e7cf363ffe21fc4ff0acd913caf851484038d8d358837b4f66adb9852c48e8acecc2025fe5c85b83df064d27b984a136d847fe0ee7f9923fccf69a5f6c184a70f6be47322f3dd3f54f37f398d176a1a2b3e75bb6e4d787422383ccd8dc66abecdcfc51a143eed6fd98eae4a1d7ebe3ffbba034ea4d98cda4e6532a90749882ca384df9f2d4630d74b9daa6b26b69c0b30e2c447e652a51b496928b175c6a1e76a94bbd61f5cfe293b2bf4fdbe33031c2094f594bc000000000000000 AuthReq { version: 42333670, client_id: [179, 249, 120, 123, 64, 152, 181, 41, 207, 49, 75, 167, 37, 192, 218, 60, 6, 219, 57, 217, 194, 226, 161, 246, 65, 21, 229, 24, 200, 236, 121, 20, 212, 221, 45, 188, 133, 225, 73, 184], auth_token: [126, 23, 195, 2, 205, 133, 60, 117, 239, 254, 128, 160, 144, 101, 1, 206, 5, 251, 192, 7, 199, 201, 130, 169, 13, 187, 160, 61, 15, 255, 125, 68, 85, 231, 207, 54, 63, 254, 33, 252, 79, 240, 172, 217, 19, 202, 248, 81, 72, 64, 56, 216, 211, 88, 131, 123, 79, 102, 173, 185, 133, 44, 72, 232, 172, 236, 194, 2, 95, 229, 200, 91, 131, 223, 6, 77, 39, 185, 132, 161, 54, 216, 71, 254, 14, 231, 249, 146, 63, 204, 246, 154, 95, 108, 24, 74, 112, 246, 190, 71, 50, 47, 61, 211, 245, 79, 55, 243, 152, 209, 118, 161, 162, 179, 231, 91, 182, 228, 215, 135, 66, 35, 131, 204, 216, 220, 102, 171, 236, 220, 252, 81, 161, 67, 238, 214, 253, 152, 234, 228, 161, 215, 235, 227, 255, 187, 160, 52, 234, 77, 152, 205, 164, 230, 83, 42, 144, 116, 152, 130, 202, 56, 77, 249, 242, 212, 99, 13, 116, 185, 218, 166, 178, 107, 105, 192, 179, 14, 44, 68, 126, 101, 42, 81, 180, 150, 146, 139, 23, 92, 106, 30, 118, 169, 75, 189, 97, 245, 207, 226, 147, 178, 191, 79, 219], features: 2535581299659200843 };277;[82, 85, f5, e6, 28, b3, f9, 78, 7b, 40, 98, b5, 29, cf, 31, 4b, a7, 25, c0, da, 3c, 06, db, 39, d9, c2, e2, a1, f6, 41, 15, e5, 18, c8, ec, 79, 14, d4, dd, 2d, bc, 85, e1, 49, b8, 40, cd, 7e, 17, c3, 02, cd, 85, 3c, 75, ef, fe, 80, a0, 90, 65, 01, ce, 05, fb, c0, 07, c7, c9, 82, a9, 0d, bb, a0, 3d, 0f, ff, 7d, 44, 55, e7, cf, 36, 3f, fe, 21, fc, 4f, f0, ac, d9, 13, ca, f8, 51, 48, 40, 38, d8, d3, 58, 83, 7b, 4f, 66, ad, b9, 85, 2c, 48, e8, ac, ec, c2, 02, 5f, e5, c8, 5b, 83, df, 06, 4d, 27, b9, 84, a1, 36, d8, 47, fe, 0e, e7, f9, 92, 3f, cc, f6, 9a, 5f, 6c, 18, 4a, 70, f6, be, 47, 32, 2f, 3d, d3, f5, 4f, 37, f3, 98, d1, 76, a1, a2, b3, e7, 5b, b6, e4, d7, 87, 42, 23, 83, cc, d8, dc, 66, ab, ec, dc, fc, 51, a1, 43, ee, d6, fd, 98, ea, e4, a1, d7, eb, e3, ff, bb, a0, 34, ea, 4d, 98, cd, a4, e6, 53, 2a, 90, 74, 98, 82, ca, 38, 4d, f9, f2, d4, 63, 0d, 74, b9, da, a6, b2, 6b, 69, c0, b3, 0e, 2c, 44, 7e, 65, 2a, 51, b4, 96, 92, 8b, 17, 5c, 6a, 1e, 76, a9, 4b, bd, 61, f5, cf, e2, 93, b2, bf, 4f, db, e3, 30, 31, c2, 09, 4f, 59, 4b, 00] +auth_req c00000c9ec6b464e249a978d386ddad1eea0b5b5c37e702c5d52cf0dcca392d7b090d5985c4f326dba59bcc2f040c49cce531a126ba085e59253a89cac3e355f80fbd268148f123f31773cedde97849a26dd2edf4ef322341e14c431f8bce0f12367a30c0b9c5438863e1df7064caa1dbdeca852d3b91536c8878b90056548bdcd316279b15bfe2b5ee07693d9f2fe341944dd0efadb710b208f7714a405c03dc03a09cd45861220d203b1fb072c415c6a07afedca2bff6bd6745ff60259919cc4ef982f3ed12e369dc45053d8ed3a427d6b4be8a46178d86ea93088ec8bc32c73369d3d4b6f18913f25d7b5cf23eccae3c604c000a60108ebe68fc000000000000000 AuthReq { version: 867254879822, client_id: [154, 151, 141, 56, 109, 218, 209, 238, 160, 181, 181, 195, 126, 112, 44, 93, 82, 207, 13, 204, 163, 146, 215, 176, 144, 213, 152, 92, 79, 50, 109, 186, 89, 188, 194, 240], auth_token: [156, 206, 83, 26, 18, 107, 160, 133, 229, 146, 83, 168, 156, 172, 62, 53, 95, 128, 251, 210, 104, 20, 143, 18, 63, 49, 119, 60, 237, 222, 151, 132, 154, 38, 221, 46, 223, 78, 243, 34, 52, 30, 20, 196, 49, 248, 188, 224, 241, 35, 103, 163, 12, 11, 156, 84, 56, 134, 62, 29, 247, 6, 76, 170, 29, 189, 236, 168, 82, 211, 185, 21, 54, 200, 135, 139, 144, 5, 101, 72, 189, 205, 49, 98, 121, 177, 91, 254, 43, 94, 224, 118, 147, 217, 242, 254, 52, 25, 68, 221, 14, 250, 219, 113, 11, 32, 143, 119, 20, 164, 5, 192, 61, 192, 58, 9, 205, 69, 134, 18, 32, 210, 3, 177, 251, 7, 44, 65, 92, 106, 7, 175, 237, 202, 43, 255, 107, 214, 116, 95, 246, 2, 89, 145, 156, 196, 239, 152, 47, 62, 209, 46, 54, 157, 196, 80, 83, 216, 237, 58, 66, 125, 107, 75, 232, 164, 97, 120, 216, 110, 169, 48, 136, 236, 139, 195, 44, 115, 54, 157, 61, 75, 111, 24, 145, 63, 37, 215, 181, 207, 35, 236, 202, 227, 198, 4], features: 182523374855823 };259;[c0, 00, 00, c9, ec, 6b, 46, 4e, 24, 9a, 97, 8d, 38, 6d, da, d1, ee, a0, b5, b5, c3, 7e, 70, 2c, 5d, 52, cf, 0d, cc, a3, 92, d7, b0, 90, d5, 98, 5c, 4f, 32, 6d, ba, 59, bc, c2, f0, 40, c4, 9c, ce, 53, 1a, 12, 6b, a0, 85, e5, 92, 53, a8, 9c, ac, 3e, 35, 5f, 80, fb, d2, 68, 14, 8f, 12, 3f, 31, 77, 3c, ed, de, 97, 84, 9a, 26, dd, 2e, df, 4e, f3, 22, 34, 1e, 14, c4, 31, f8, bc, e0, f1, 23, 67, a3, 0c, 0b, 9c, 54, 38, 86, 3e, 1d, f7, 06, 4c, aa, 1d, bd, ec, a8, 52, d3, b9, 15, 36, c8, 87, 8b, 90, 05, 65, 48, bd, cd, 31, 62, 79, b1, 5b, fe, 2b, 5e, e0, 76, 93, d9, f2, fe, 34, 19, 44, dd, 0e, fa, db, 71, 0b, 20, 8f, 77, 14, a4, 05, c0, 3d, c0, 3a, 09, cd, 45, 86, 12, 20, d2, 03, b1, fb, 07, 2c, 41, 5c, 6a, 07, af, ed, ca, 2b, ff, 6b, d6, 74, 5f, f6, 02, 59, 91, 9c, c4, ef, 98, 2f, 3e, d1, 2e, 36, 9d, c4, 50, 53, d8, ed, 3a, 42, 7d, 6b, 4b, e8, a4, 61, 78, d8, 6e, a9, 30, 88, ec, 8b, c3, 2c, 73, 36, 9d, 3d, 4b, 6f, 18, 91, 3f, 25, d7, b5, cf, 23, ec, ca, e3, c6, 04, c0, 00, a6, 01, 08, eb, e6, 8f, 00] +auth_req c01b94ea4383009e19a8a6e22b2f3cc686f76474b13e132f1b1d6d213a7ccf0f15718000007cf210b683e87801c653ef6af4ed30ddc8ef277beabc39a544c935960f3c76b8f72bf0a8400d38ad5863f309c782b3dec64e382d9fae33c25503e5757b0b57e6d8bfa70dd0ccf09ad742b9bc6704678be0cfb5eeae0ceebb902912cc6237d70869bced05f947632599829b2bfde2214fd826ec3405fda931439ed15358c0005932ab409d5e80000000 AuthReq { version: 7763558247104670, client_id: [168, 166, 226, 43, 47, 60, 198, 134, 247, 100, 116, 177, 62, 19, 47, 27, 29, 109, 33, 58, 124, 207, 15, 21, 113], auth_token: [242, 16, 182, 131, 232, 120, 1, 198, 83, 239, 106, 244, 237, 48, 221, 200, 239, 39, 123, 234, 188, 57, 165, 68, 201, 53, 150, 15, 60, 118, 184, 247, 43, 240, 168, 64, 13, 56, 173, 88, 99, 243, 9, 199, 130, 179, 222, 198, 78, 56, 45, 159, 174, 51, 194, 85, 3, 229, 117, 123, 11, 87, 230, 216, 191, 167, 13, 208, 204, 240, 154, 215, 66, 185, 188, 103, 4, 103, 139, 224, 207, 181, 238, 174, 12, 238, 187, 144, 41, 18, 204, 98, 55, 215, 8, 105, 188, 237, 5, 249, 71, 99, 37, 153, 130, 155, 43, 253, 226, 33, 79, 216, 38, 236, 52, 5, 253, 169, 49, 67, 158, 209, 83, 88], features: 98074156375390 };174;[c0, 1b, 94, ea, 43, 83, 00, 9e, 19, a8, a6, e2, 2b, 2f, 3c, c6, 86, f7, 64, 74, b1, 3e, 13, 2f, 1b, 1d, 6d, 21, 3a, 7c, cf, 0f, 15, 71, 40, 7c, f2, 10, b6, 83, e8, 78, 01, c6, 53, ef, 6a, f4, ed, 30, dd, c8, ef, 27, 7b, ea, bc, 39, a5, 44, c9, 35, 96, 0f, 3c, 76, b8, f7, 2b, f0, a8, 40, 0d, 38, ad, 58, 63, f3, 09, c7, 82, b3, de, c6, 4e, 38, 2d, 9f, ae, 33, c2, 55, 03, e5, 75, 7b, 0b, 57, e6, d8, bf, a7, 0d, d0, cc, f0, 9a, d7, 42, b9, bc, 67, 04, 67, 8b, e0, cf, b5, ee, ae, 0c, ee, bb, 90, 29, 12, cc, 62, 37, d7, 08, 69, bc, ed, 05, f9, 47, 63, 25, 99, 82, 9b, 2b, fd, e2, 21, 4f, d8, 26, ec, 34, 05, fd, a9, 31, 43, 9e, d1, 53, 58, c0, 00, 59, 32, ab, 40, 9d, 5e, 00] +auth_req c0000006b69e3e708000002099c9653cbdefd0b6e8b29b8174b3a8d61a1775352d5590b443e70e3d18c3fe06800000fc87e6d434a33c3cd177a3040a50a45d44a643e454626796aed84efe5ba633191e5e5ba9494ca56b50b93c39df1ef23612134025bcfd7d6c7738d04a192e298f944bd89d6c201cee8341047cc096d3b0af4e4788be8a4d937e6bc019377d6090b9a0ab813a509f38a62c5159e3e6629694af9570e348d624f7b947d55404ef01cb849d6e4a1d2474f1461985cb8e814a8b81ae702fbf9c2e13e08ad79a7f720ae2f432fd3e3b2758db4ea1c55b17ee3de2dcbba20ad50d8768de9fbec1cd8da6cad135109efa7c3ec9178607e91958dd81df9c9818197ee0cfab02c22835e652b71454beab76a8c8315083f950c71bb7ae1b5808ed557ca40a4efd719c80000000400440b5db3f AuthReq { version: 28833627760, client_id: [153, 201, 101, 60, 189, 239, 208, 182, 232, 178, 155, 129, 116, 179, 168, 214, 26, 23, 117, 53, 45, 85, 144, 180, 67, 231, 14, 61, 24, 195, 254, 6], auth_token: [135, 230, 212, 52, 163, 60, 60, 209, 119, 163, 4, 10, 80, 164, 93, 68, 166, 67, 228, 84, 98, 103, 150, 174, 216, 78, 254, 91, 166, 51, 25, 30, 94, 91, 169, 73, 76, 165, 107, 80, 185, 60, 57, 223, 30, 242, 54, 18, 19, 64, 37, 188, 253, 125, 108, 119, 56, 208, 74, 25, 46, 41, 143, 148, 75, 216, 157, 108, 32, 28, 238, 131, 65, 4, 124, 192, 150, 211, 176, 175, 78, 71, 136, 190, 138, 77, 147, 126, 107, 192, 25, 55, 125, 96, 144, 185, 160, 171, 129, 58, 80, 159, 56, 166, 44, 81, 89, 227, 230, 98, 150, 148, 175, 149, 112, 227, 72, 214, 36, 247, 185, 71, 213, 84, 4, 239, 1, 203, 132, 157, 110, 74, 29, 36, 116, 241, 70, 25, 133, 203, 142, 129, 74, 139, 129, 174, 112, 47, 191, 156, 46, 19, 224, 138, 215, 154, 127, 114, 10, 226, 244, 50, 253, 62, 59, 39, 88, 219, 78, 161, 197, 91, 23, 238, 61, 226, 220, 187, 162, 10, 213, 13, 135, 104, 222, 159, 190, 193, 205, 141, 166, 202, 209, 53, 16, 158, 250, 124, 62, 201, 23, 134, 7, 233, 25, 88, 221, 129, 223, 156, 152, 24, 25, 126, 224, 207, 171, 2, 194, 40, 53, 230, 82, 183, 20, 84, 190, 171, 118, 168, 200, 49, 80, 131, 249, 80, 199, 27, 183, 174, 27, 88, 8, 237, 85, 124, 164, 10, 78, 253, 113, 156], features: 0 };310;[c0, 00, 00, 06, b6, 9e, 3e, 70, 20, 99, c9, 65, 3c, bd, ef, d0, b6, e8, b2, 9b, 81, 74, b3, a8, d6, 1a, 17, 75, 35, 2d, 55, 90, b4, 43, e7, 0e, 3d, 18, c3, fe, 06, 40, fc, 87, e6, d4, 34, a3, 3c, 3c, d1, 77, a3, 04, 0a, 50, a4, 5d, 44, a6, 43, e4, 54, 62, 67, 96, ae, d8, 4e, fe, 5b, a6, 33, 19, 1e, 5e, 5b, a9, 49, 4c, a5, 6b, 50, b9, 3c, 39, df, 1e, f2, 36, 12, 13, 40, 25, bc, fd, 7d, 6c, 77, 38, d0, 4a, 19, 2e, 29, 8f, 94, 4b, d8, 9d, 6c, 20, 1c, ee, 83, 41, 04, 7c, c0, 96, d3, b0, af, 4e, 47, 88, be, 8a, 4d, 93, 7e, 6b, c0, 19, 37, 7d, 60, 90, b9, a0, ab, 81, 3a, 50, 9f, 38, a6, 2c, 51, 59, e3, e6, 62, 96, 94, af, 95, 70, e3, 48, d6, 24, f7, b9, 47, d5, 54, 04, ef, 01, cb, 84, 9d, 6e, 4a, 1d, 24, 74, f1, 46, 19, 85, cb, 8e, 81, 4a, 8b, 81, ae, 70, 2f, bf, 9c, 2e, 13, e0, 8a, d7, 9a, 7f, 72, 0a, e2, f4, 32, fd, 3e, 3b, 27, 58, db, 4e, a1, c5, 5b, 17, ee, 3d, e2, dc, bb, a2, 0a, d5, 0d, 87, 68, de, 9f, be, c1, cd, 8d, a6, ca, d1, 35, 10, 9e, fa, 7c, 3e, c9, 17, 86, 07, e9, 19, 58, dd, 81, df, 9c, 98, 18, 19, 7e, e0, cf, ab, 02, c2, 28, 35, e6, 52, b7, 14, 54, be, ab, 76, a8, c8, 31, 50, 83, f9, 50, c7, 1b, b7, ae, 1b, 58, 08, ed, 55, 7c, a4, 0a, 4e, fd, 71, 9c, 00, 00] +auth_req c00d2d76ffd213ed8000003a8a39e667b6baa5a0b020c4724110dd82f48333ab830124757b67ff381a22ef298edf7212e3f714cab3df51498cc2547b8e9d70f9bc0532e26ff540d4ce257c4127940d248ecb7d177f96b6c4255daec41dc0a709c02031c22ec7348f8eaaff154035c903b48adab82d2be991db143d10d731b1a707832e2e225d38cb42dfb889ec7acc9de755c818f1825e1443c5d4e6615bb0f89c4592442c8ab44e9564d604e3fed8103bd2302b26877bb1161f4da1f3f3fa1d76cc7d5065e35864f4c8d79b1ec23475f2b4a8909a846911ad9f94ecc6642c333949406d1fe0afa35a0a063ed032f25b6d5207cd8054afe21a9b172113cba0b5847542244641ff9fa7da4d6eac482a748a775d66a0d438253e7c93acc000028568782dd6c000000000000000 AuthReq { version: 3709163818587117, client_id: [138, 57, 230, 103, 182, 186, 165, 160, 176, 32, 196, 114, 65, 16, 221, 130, 244, 131, 51, 171, 131, 1, 36, 117, 123, 103, 255, 56, 26, 34, 239, 41, 142, 223, 114, 18, 227, 247, 20, 202, 179, 223, 81, 73, 140, 194, 84, 123, 142, 157, 112, 249, 188, 5, 50, 226, 111, 245], auth_token: [206, 37, 124, 65, 39, 148, 13, 36, 142, 203, 125, 23, 127, 150, 182, 196, 37, 93, 174, 196, 29, 192, 167, 9, 192, 32, 49, 194, 46, 199, 52, 143, 142, 170, 255, 21, 64, 53, 201, 3, 180, 138, 218, 184, 45, 43, 233, 145, 219, 20, 61, 16, 215, 49, 177, 167, 7, 131, 46, 46, 34, 93, 56, 203, 66, 223, 184, 137, 236, 122, 204, 157, 231, 85, 200, 24, 241, 130, 94, 20, 67, 197, 212, 230, 97, 91, 176, 248, 156, 69, 146, 68, 44, 138, 180, 78, 149, 100, 214, 4, 227, 254, 216, 16, 59, 210, 48, 43, 38, 135, 123, 177, 22, 31, 77, 161, 243, 243, 250, 29, 118, 204, 125, 80, 101, 227, 88, 100, 244, 200, 215, 155, 30, 194, 52, 117, 242, 180, 168, 144, 154, 132, 105, 17, 173, 159, 148, 236, 198, 100, 44, 51, 57, 73, 64, 109, 31, 224, 175, 163, 90, 10, 6, 62, 208, 50, 242, 91, 109, 82, 7, 205, 128, 84, 175, 226, 26, 155, 23, 33, 19, 203, 160, 181, 132, 117, 66, 36, 70, 65, 255, 159, 167, 218, 77, 110, 172, 72, 42, 116, 138, 119, 93, 102, 160, 212, 56, 37, 62, 124, 147, 172], features: 2772006612438 };300;[c0, 0d, 2d, 76, ff, d2, 13, ed, 3a, 8a, 39, e6, 67, b6, ba, a5, a0, b0, 20, c4, 72, 41, 10, dd, 82, f4, 83, 33, ab, 83, 01, 24, 75, 7b, 67, ff, 38, 1a, 22, ef, 29, 8e, df, 72, 12, e3, f7, 14, ca, b3, df, 51, 49, 8c, c2, 54, 7b, 8e, 9d, 70, f9, bc, 05, 32, e2, 6f, f5, 40, d4, ce, 25, 7c, 41, 27, 94, 0d, 24, 8e, cb, 7d, 17, 7f, 96, b6, c4, 25, 5d, ae, c4, 1d, c0, a7, 09, c0, 20, 31, c2, 2e, c7, 34, 8f, 8e, aa, ff, 15, 40, 35, c9, 03, b4, 8a, da, b8, 2d, 2b, e9, 91, db, 14, 3d, 10, d7, 31, b1, a7, 07, 83, 2e, 2e, 22, 5d, 38, cb, 42, df, b8, 89, ec, 7a, cc, 9d, e7, 55, c8, 18, f1, 82, 5e, 14, 43, c5, d4, e6, 61, 5b, b0, f8, 9c, 45, 92, 44, 2c, 8a, b4, 4e, 95, 64, d6, 04, e3, fe, d8, 10, 3b, d2, 30, 2b, 26, 87, 7b, b1, 16, 1f, 4d, a1, f3, f3, fa, 1d, 76, cc, 7d, 50, 65, e3, 58, 64, f4, c8, d7, 9b, 1e, c2, 34, 75, f2, b4, a8, 90, 9a, 84, 69, 11, ad, 9f, 94, ec, c6, 64, 2c, 33, 39, 49, 40, 6d, 1f, e0, af, a3, 5a, 0a, 06, 3e, d0, 32, f2, 5b, 6d, 52, 07, cd, 80, 54, af, e2, 1a, 9b, 17, 21, 13, cb, a0, b5, 84, 75, 42, 24, 46, 41, ff, 9f, a7, da, 4d, 6e, ac, 48, 2a, 74, 8a, 77, 5d, 66, a0, d4, 38, 25, 3e, 7c, 93, ac, c0, 00, 02, 85, 68, 78, 2d, d6, 00] +auth_req c0000000000654d3800000109f9c416923ac7adf987dda9c999151fb800000a7c68cb5ea852640a25c1a45c79a48238fdb54ff15f001000495ac029e587d060efe4c38a4e4cdc3a18dcf68ef7e1744978309570bcc5ef361dbdcf78279a49b55bd12ad36a644d5ff25b2aed7d41747f597b0af107dc92b8764d63a60b3be39e00b2eb1675921e16738589aca3d650af306671080ed050e0fef093a1bfd289e395b458a7860b1a1a6bf771d3481f1abfee23047d287a57d16ff83e03b28a55a70281b1afded9d6dc0abff5ab5da202c0197 accept +auth_req c000000000000081800000355fda2f9742996de561a9204f529e86a0dd1075de11667e6155220d71f36b5becf558f91910d643651f076b0d68d4ef61b631dac8cc800000773a43898140bf7aeef4f8137b674ac7e61701fd4d743117da6178fede80170d81518479d47af9bfb9871f864c9d2991fc76f1622d57d75224abb0298a5a16ed8102c97177416711639d337d79209317848ec370fa5e8fec3d88f102cd314687614463acd5817f578996479b97dc976a804f218231c0d47ebdb558f84000 AuthReq { version: 129, client_id: [95, 218, 47, 151, 66, 153, 109, 229, 97, 169, 32, 79, 82, 158, 134, 160, 221, 16, 117, 222, 17, 102, 126, 97, 85, 34, 13, 113, 243, 107, 91, 236, 245, 88, 249, 25, 16, 214, 67, 101, 31, 7, 107, 13, 104, 212, 239, 97, 182, 49, 218, 200, 204], auth_token: [58, 67, 137, 129, 64, 191, 122, 238, 244, 248, 19, 123, 103, 74, 199, 230, 23, 1, 253, 77, 116, 49, 23, 218, 97, 120, 254, 222, 128, 23, 13, 129, 81, 132, 121, 212, 122, 249, 191, 185, 135, 31, 134, 76, 157, 41, 145, 252, 118, 241, 98, 45, 87, 215, 82, 36, 171, 176, 41, 138, 90, 22, 237, 129, 2, 201, 113, 119, 65, 103, 17, 99, 157, 51, 125, 121, 32, 147, 23, 132, 142, 195, 112, 250, 94, 143, 236, 61, 136, 241, 2, 205, 49, 70, 135, 97, 68, 99, 172, 213, 129, 127, 87, 137, 150, 71, 155, 151, 220, 151, 106, 128, 79, 33, 130, 49, 192, 212, 126], features: 1035294968 };194;[40, 81, 35, 5f, da, 2f, 97, 42, 99, 6d, e5, 61, a9, 20, 4f, 52, 9e, 86, a0, dd, 10, 75, de, 11, 66, 7e, 61, 55, 22, 0d, 71, f3, 6b, 5b, ec, f5, 58, f9, 19, 10, d6, 43, 65, 1f, 07, 6b, 0d, 68, d4, ef, 61, b6, 31, da, c8, cc, 40, 77, 3a, 43, 89, 81, 40, bf, 7a, ee, f4, f8, 13, 7b, 67, 4a, c7, e6, 17, 01, fd, 4d, 74, 31, 17, da, 61, 78, fe, de, 80, 17, 0d, 81, 51, 84, 79, d4, 7a, f9, bf, b9, 87, 1f, 86, 4c, 9d, 29, 91, fc, 76, f1, 62, 2d, 57, d7, 52, 24, ab, b0, 29, 8a, 5a, 16, ed, 81, 02, c9, 71, 77, 41, 67, 11, 63, 9d, 33, 7d, 79, 20, 93, 17, 84, 8e, c3, 70, fa, 5e, 8f, ec, 3d, 88, f1, 02, cd, 31, 46, 87, 61, 44, 63, ac, d5, 81, 7f, 57, 89, 96, 47, 9b, 97, dc, 97, 6a, 80, 4f, 21, 82, 31, c0, d4, 7e, bd, b5, 58, f8, 00] +auth_req c027ea463dd9340e03cc482a80000002593ac0000001bc0c973e40003721 AuthReq { version: 11235111497970702, client_id: [204, 72, 42], auth_token: [89, 58], features: 7449909054 };28;[c0, 27, ea, 46, 3d, d9, 34, 0e, 03, cc, 48, 2a, 02, 59, 3a, c0, 00, 00, 01, bc, 0c, 97, 3e, 00] +auth_req cfcb425157ecf2668000000fb943f4631a4742585f5b3879f3fa7840a84dcc4c53912fd380ebf62d4521ec6f78b118ea7b151977a4e53586a52c73af7dc59ff0afd22041351d30d1ddd2ec7c5f2b62199b9a334938cea6b44ec9a790ca35912061c046139b79a2de5542744caf7bccf40fc202566ea328667f0e5ce974865e7b55732b77a38b871bf22e7e311ac78cd042e5a05aa5501e9c2d89fcf62d74af38ef88e5562b9a5a09b41089ddea74b9e2de05c9640fbee5b9790931a79ed1ad961486fe47db8000000f8000000011ca48 AuthReq { version: 1138076247976112742, client_id: [185, 67, 244, 99, 26, 71, 66, 88, 95, 91, 56, 121, 243, 250, 120], auth_token: [77, 204, 76, 83, 145, 47, 211, 128, 235, 246, 45, 69, 33, 236, 111, 120, 177, 24, 234, 123, 21, 25, 119, 164, 229, 53, 134, 165, 44, 115, 175, 125, 197, 159, 240, 175, 210, 32, 65, 53, 29, 48, 209, 221, 210, 236, 124, 95, 43, 98, 25, 155, 154, 51, 73, 56, 206, 166, 180, 78, 201, 167, 144, 202, 53, 145, 32, 97, 192, 70, 19, 155, 121, 162, 222, 85, 66, 116, 76, 175, 123, 204, 244, 15, 194, 2, 86, 110, 163, 40, 102, 127, 14, 92, 233, 116, 134, 94, 123, 85, 115, 43, 119, 163, 139, 135, 27, 242, 46, 126, 49, 26, 199, 140, 208, 66, 229, 160, 90, 165, 80, 30, 156, 45, 137, 252, 246, 45, 116, 175, 56, 239, 136, 229, 86, 43, 154, 90, 9, 180, 16, 137, 221, 234, 116, 185, 226, 222, 5, 201, 100, 15, 190, 229, 185, 121, 9, 49, 167, 158, 209, 173, 150, 20, 134, 254, 71, 219], features: 15 };205;[cf, cb, 42, 51, 57, ec, f2, 66, 0f, b9, 43, f4, 63, 1a, 47, 42, 58, 5f, 5b, 38, 79, f3, fa, 78, 40, a8, 4d, cc, 4c, 53, 91, 2f, d3, 80, eb, f6, 2d, 45, 21, ec, 6f, 78, b1, 18, ea, 7b, 15, 19, 77, a4, e5, 35, 86, a5, 2c, 73, af, 7d, c5, 9f, f0, af, d2, 20, 41, 35, 1d, 30, d1, dd, d2, ec, 7c, 5f, 2b, 62, 19, 9b, 9a, 33, 49, 38, ce, a6, b4, 4e, c9, a7, 90, ca, 35, 91, 20, 61, c0, 46, 13, 9b, 79, a2, de, 55, 42, 74, 4c, af, 7b, cc, f4, 0f, c2, 02, 56, 6e, a3, 28, 66, 7f, 0e, 5c, e9, 74, 86, 5e, 7b, 55, 73, 2b, 77, a3, 8b, 87, 1b, f2, 2e, 7e, 31, 1a, c7, 8c, d0, 42, e5, a0, 5a, a5, 50, 1e, 9c, 2d, 89, fc, f6, 2d, 74, af, 38, ef, 88, e5, 56, 2b, 9a, 5a, 09, b4, 10, 89, dd, ea, 74, b9, e2, de, 05, c9, 64, 0f, be, e5, b9, 79, 09, 31, a7, 9e, d1, ad, 96, 14, 86, fe, 47, db, 0f, 00] +auth_req 80ab818e4043136aa27b2f0c4a71d67d39a00135fa3427208cccb550918996738f63d0c03b32ee60fcd08feb11f85c4e3f01f29371ad4d39ba70f6f70c4113b63fec8621251cff69bf41028c0261ccf41bd969b26c1592b89bb2da2019e4a4c94e4477279bcf1c05946130bc594d2bdc7fd3f17c77505d0634151e8e261edae8274aa74c6796ab25a5d7fb9cfc9549cb085519646bb25253dc45b1544a3952f89ec1a1d6e52a46508b16244fc455bbc24e5691e3181745e2fdfd7df415beb72ea019d4200bbe91e63a44b3bca821da0452d4dac40e193853b759c85f46ffe088c5cac6eda2329bf36d332f7f88ad4202a7b81ab742daf013d896db3a1beae72712d796248c114f1ff7482b3b656019bf03abe52d79f41073e3d4c3dc75739f73af5931c203049ade9b883bb9ea0a6da87d5262c22d86bb391b706e74193cb6cfaa7bc18e799e5e50e1d74b27b5c0003188bf6c70b8c000000000000000 reject +auth_req 80026e2c80000008ea5d1e6dda96617e401c5c7926541872b001071fbc7a58089942e4912101f6614cece510ab37e67b903358dd0eea00 AuthReq { version: 159276, client_id: [234, 93, 30, 109, 218, 150, 97, 126], auth_token: [92, 121, 38, 84, 24, 114, 176, 1, 7, 31, 188, 122, 88, 8, 153, 66, 228, 145, 33, 1, 246, 97, 76, 236, 229, 16, 171, 55], features: 2772968545785286378 };55;[80, 02, 6e, 2c, 08, ea, 5d, 1e, 6d, da, 96, 61, 7e, 1c, 5c, 79, 26, 54, 18, 72, b0, 01, 07, 1f, bc, 7a, 58, 08, 99, 42, e4, 91, 21, 01, f6, 61, 4c, ec, e5, 10, ab, 37, e6, 7b, 90, 33, 58, dd, 0e, ea, 00] +auth_req c0000039aa099e9fc00000000000003a66ec518a21bdb0e179c540175ff467c9e882bf97665c574840ddb53d6d41519743e0a52c9da4ea2719ee82930e57507349c348382ad569edb6ae40e73b5cb4566814af329d265fdbe23228194713f3b5023506036b0cb4bcf799732cb307aebd7f5c6aee3d3e126bacf2d0db6f8f370f891c2e62214922304753818a75584b44a6e5cb31369a825acb59a49b0a14b6c2334e342fa5c493178cc61cf6b3db50d644025063e74040180ff140a4cfac05f60a7163c3973d7c1644fef8015c4095d264fd3e8dcdfca1106e619766cabfd0c18343c4a308a51f840b2edc47337b1554cc7dba5a85268978f6a7a098eb8e6286a2170d58b3c42a909b2b8ea006be485f49c67696450e48ad90f86efd1134537c06140da88af33ac0e0c10af998354fa85c581dc00000000090c09dc000000000000000 AuthReq { version: 247665893023, client_id: [102, 236, 81, 138, 33, 189, 176, 225, 121, 197, 64, 23, 95, 244, 103, 201, 232, 130, 191, 151, 102, 92, 87, 72, 64, 221, 181, 61, 109, 65, 81, 151, 67, 224, 165, 44, 157, 164, 234, 39, 25, 238, 130, 147, 14, 87, 80, 115, 73, 195, 72, 56, 42, 213, 105, 237, 182, 174], auth_token: [59, 92, 180, 86, 104, 20, 175, 50, 157, 38, 95, 219, 226, 50, 40, 25, 71, 19, 243, 181, 2, 53, 6, 3, 107, 12, 180, 188, 247, 153, 115, 44, 179, 7, 174, 189, 127, 92, 106, 238, 61, 62, 18, 107, 172, 242, 208, 219, 111, 143, 55, 15, 137, 28, 46, 98, 33, 73, 34, 48, 71, 83, 129, 138, 117, 88, 75, 68, 166, 229, 203, 49, 54, 154, 130, 90, 203, 89, 164, 155, 10, 20, 182, 194, 51, 78, 52, 47, 165, 196, 147, 23, 140, 198, 28, 246, 179, 219, 80, 214, 68, 2, 80, 99, 231, 64, 64, 24, 15, 241, 64, 164, 207, 172, 5, 246, 10, 113, 99, 195, 151, 61, 124, 22, 68, 254, 248, 1, 92, 64, 149, 210, 100, 253, 62, 141, 205, 252, 161, 16, 110, 97, 151, 102, 202, 191, 208, 193, 131, 67, 196, 163, 8, 165, 31, 132, 11, 46, 220, 71, 51, 123, 21, 84, 204, 125, 186, 90, 133, 38, 137, 120, 246, 167, 160, 152, 235, 142, 98, 134, 162, 23, 13, 88, 179, 196, 42, 144, 155, 43, 142, 160, 6, 190, 72, 95, 73, 198, 118, 150, 69, 14, 72, 173, 144, 248, 110, 253, 17, 52, 83, 124, 6, 20, 13, 168, 138, 243, 58, 192, 224, 193, 10, 249, 152, 53, 79, 168, 92, 88, 29], features: 9486493 };323;[c0, 00, 00, 39, aa, 09, 9e, 9f, 3a, 66, ec, 51, 8a, 21, bd, b0, e1, 79, c5, 40, 17, 5f, f4, 67, c9, e8, 82, bf, 97, 66, 5c, 57, 48, 40, dd, b5, 3d, 6d, 41, 51, 97, 43, e0, a5, 2c, 9d, a4, ea, 27, 19, ee, 82, 93, 0e, 57, 50, 73, 49, c3, 48, 38, 2a, d5, 69, ed, b6, ae, 40, e7, 3b, 5c, b4, 56, 68, 14, af, 32, 9d, 26, 5f, db, e2, 32, 28, 19, 47, 13, f3, b5, 02, 35, 06, 03, 6b, 0c, b4, bc, f7, 99, 73, 2c, b3, 07, ae, bd, 7f, 5c, 6a, ee, 3d, 3e, 12, 6b, ac, f2, d0, db, 6f, 8f, 37, 0f, 89, 1c, 2e, 62, 21, 49, 22, 30, 47, 53, 81, 8a, 75, 58, 4b, 44, a6, e5, cb, 31, 36, 9a, 82, 5a, cb, 59, a4, 9b, 0a, 14, b6, c2, 33, 4e, 34, 2f, a5, c4, 93, 17, 8c, c6, 1c, f6, b3, db, 50, d6, 44, 02, 50, 63, e7, 40, 40, 18, 0f, f1, 40, a4, cf, ac, 05, f6, 0a, 71, 63, c3, 97, 3d, 7c, 16, 44, fe, f8, 01, 5c, 40, 95, d2, 64, fd, 3e, 8d, cd, fc, a1, 10, 6e, 61, 97, 66, ca, bf, d0, c1, 83, 43, c4, a3, 08, a5, 1f, 84, 0b, 2e, dc, 47, 33, 7b, 15, 54, cc, 7d, ba, 5a, 85, 26, 89, 78, f6, a7, a0, 98, eb, 8e, 62, 86, a2, 17, 0d, 58, b3, c4, 2a, 90, 9b, 2b, 8e, a0, 06, be, 48, 5f, 49, c6, 76, 96, 45, 0e, 48, ad, 90, f8, 6e, fd, 11, 34, 53, 7c, 06, 14, 0d, a8, 8a, f3, 3a, c0, e0, c1, 0a, f9, 98, 35, 4f, a8, 5c, 58, 1d, 80, 90, c0, 9d, 00] +auth_req 98cb95e7c00000000000003341be48e229228163ea509281ca419d02d2be251f7be03f2dd49dd3f51b2112a2b9216d245442db4b72305638a2d76a1e9a0cea800000b580c548dc082d4c44234b6aa28b6a8867a959018812b838d816553dd7eea2087c9623e306f6068c463de5b61040404b2a7b7cb9db3c14fd4b196d86eeb188bd4330680a3e83bc67dd473438b6b04a62c2b4c04961c7ba1f7bdb35639ce6f4a3c619bbd04641117c985154b0a5e161e3cad150363c94030c4eb29e04fbb4cf443e667b8f65dc908f95a7cbfb5a5c3d4cf7b7d3aed973aedad8c2264f119c163c9866b111d25c1ef1aefd0f8b2786901251aaa325b194c00004e7cefc37444000 AuthReq { version: 415995367, client_id: [65, 190, 72, 226, 41, 34, 129, 99, 234, 80, 146, 129, 202, 65, 157, 2, 210, 190, 37, 31, 123, 224, 63, 45, 212, 157, 211, 245, 27, 33, 18, 162, 185, 33, 109, 36, 84, 66, 219, 75, 114, 48, 86, 56, 162, 215, 106, 30, 154, 12, 234], auth_token: [128, 197, 72, 220, 8, 45, 76, 68, 35, 75, 106, 162, 139, 106, 136, 103, 169, 89, 1, 136, 18, 184, 56, 216, 22, 85, 61, 215, 238, 162, 8, 124, 150, 35, 227, 6, 246, 6, 140, 70, 61, 229, 182, 16, 64, 64, 75, 42, 123, 124, 185, 219, 60, 20, 253, 75, 25, 109, 134, 238, 177, 136, 189, 67, 48, 104, 10, 62, 131, 188, 103, 221, 71, 52, 56, 182, 176, 74, 98, 194, 180, 192, 73, 97, 199, 186, 31, 123, 219, 53, 99, 156, 230, 244, 163, 198, 25, 187, 208, 70, 65, 17, 124, 152, 81, 84, 176, 165, 225, 97, 227, 202, 209, 80, 54, 60, 148, 3, 12, 78, 178, 158, 4, 251, 180, 207, 68, 62, 102, 123, 143, 101, 220, 144, 143, 149, 167, 203, 251, 90, 92, 61, 76, 247, 183, 211, 174, 217, 115, 174, 218, 216, 194, 38, 79, 17, 156, 22, 60, 152, 102, 177, 17, 210, 92, 30, 241, 174, 253, 15, 139, 39, 134, 144, 18, 81, 170, 163, 37, 177, 148], features: 5393656592196 };258;[98, cb, 95, e7, 33, 41, be, 48, e2, 29, 22, 81, 63, ea, 50, 92, 81, ca, 41, 9d, 02, d2, be, 25, 1f, 7b, e0, 3f, 2d, d4, 9d, d3, f5, 1b, 21, 12, a2, b9, 21, 6d, 24, 54, 42, db, 4b, 72, 30, 56, 38, a2, d7, 6a, 1e, 9a, 0c, ea, 40, b5, 80, c5, 48, dc, 08, 2d, 4c, 44, 23, 4b, 6a, a2, 8b, 6a, 88, 67, a9, 59, 01, 88, 12, b8, 38, d8, 16, 55, 3d, d7, ee, a2, 08, 7c, 96, 23, e3, 06, f6, 06, 8c, 46, 3d, e5, b6, 10, 40, 40, 4b, 2a, 7b, 7c, b9, db, 3c, 14, fd, 4b, 19, 6d, 86, ee, b1, 88, bd, 43, 30, 68, 0a, 3e, 83, bc, 67, dd, 47, 34, 38, b6, b0, 4a, 62, c2, b4, c0, 49, 61, c7, ba, 1f, 7b, db, 35, 63, 9c, e6, f4, a3, c6, 19, bb, d0, 46, 41, 11, 7c, 98, 51, 54, b0, a5, e1, 61, e3, ca, d1, 50, 36, 3c, 94, 03, 0c, 4e, b2, 9e, 04, fb, b4, cf, 44, 3e, 66, 7b, 8f, 65, dc, 90, 8f, 95, a7, cb, fb, 5a, 5c, 3d, 4c, f7, b7, d3, ae, d9, 73, ae, da, d8, c2, 26, 4f, 11, 9c, 16, 3c, 98, 66, b1, 11, d2, 5c, 1e, f1, ae, fd, 0f, 8b, 27, 86, 90, 12, 51, aa, a3, 25, b1, 94, c0, 00, 04, e7, ce, fc, 37, 44, 00] +auth_req c000a651a23603208000001fbab4a76c40d67b4a5e59b3eec91aa719ee5f7a511d2648ffcc11b430106c1fc00000000000007c19446652449db1f8b74077dee7cde1bb700713d325d2af9797dd1d3bb994672b53f44687164876474062ff85527a27b8cd375116f3eac6dc0f279e3c9a88266b71fbed0fbccb2d0cb9037211343c9ea59814905aefa1576a2c1e95971539cd8295d245092bfe38ce1b5acc2a3eeef63a4e942aa3136ce44f9100ba08c000021fcb9c859980000003560d50 accept +auth_req 800000003d1889cf68d1636c36e3c4a8230566058685f3b066a16b0bad3fdff97d27692a6506402f4ea8d8c8c7df3ba2c8b6c827e484d67a33cb974a70dc43a9cda340ff422e21dc8ec71c5d55a3c175d5a51e2eeedf653f4c6940d00705a9733d1ee17182732cd1ddfc586369f78fb7e12a908e0b078256d4367d681f02b27e7ed8deefec652ac84b096bdd2ea1059afbea11055302cabfde6310fbfa16ba36798f0ffa44d5a2a4b46be5b8c61f77d8125e61cbc5c4057ad41ad8d138a68ba1634cb1b1cac9b40dd375bb764eb54550d2cd636592e071337821551c5d8ffc6ff0dc4e717586e43fc73c505c01b6db40de7057aee55d30c2dab80b2d5a59f2618ab16989045b8ee8750f512ea80af79967cf3159656481eba5c3361a0f6df5e531fb4ae08ec453d83c33ec60143815e7eab8b73d67981c87bf8401dbb26955373f028c40cb4000 AuthReq { version: 0, client_id: [24, 137, 207, 104, 209, 99, 108, 54, 227, 196, 168, 35, 5, 102, 5, 134, 133, 243, 176, 102, 161, 107, 11, 173, 63, 223, 249, 125, 39, 105, 42, 101, 6, 64, 47, 78, 168, 216, 200, 199, 223, 59, 162, 200, 182, 200, 39, 228, 132, 214, 122, 51, 203, 151, 74, 112, 220, 67, 169, 205, 163], auth_token: [66, 46, 33, 220, 142, 199, 28, 93, 85, 163, 193, 117, 213, 165, 30, 46, 238, 223, 101, 63, 76, 105, 64, 208, 7, 5, 169, 115, 61, 30, 225, 113, 130, 115, 44, 209, 221, 252, 88, 99, 105, 247, 143, 183, 225, 42, 144, 142, 11, 7, 130, 86, 212, 54, 125, 104, 31, 2, 178, 126, 126, 216, 222, 239, 236, 101, 42, 200, 75, 9, 107, 221, 46, 161, 5, 154, 251, 234, 17, 5, 83, 2, 202, 191, 222, 99, 16, 251, 250, 22, 186, 54, 121, 143, 15, 250, 68, 213, 162, 164, 180, 107, 229, 184, 198, 31, 119, 216, 18, 94, 97, 203, 197, 196, 5, 122, 212, 26, 216, 209, 56, 166, 139, 161, 99, 76, 177, 177, 202, 201, 180, 13, 211, 117, 187, 118, 78, 181, 69, 80, 210, 205, 99, 101, 146, 224, 113, 51, 120, 33, 85, 28, 93, 143, 252, 111, 240, 220, 78, 113, 117, 134, 228, 63, 199, 60, 80, 92, 1, 182, 219, 64, 222, 112, 87, 174, 229, 93, 48, 194, 218, 184, 11, 45, 90, 89, 242, 97, 138, 177, 105, 137, 4, 91, 142, 232, 117, 15, 81, 46, 168, 10, 247, 153, 103, 207, 49, 89, 101, 100, 129, 235, 165, 195, 54, 26, 15, 109, 245, 229, 49, 251, 74, 224, 142, 196, 83, 216, 60, 51, 236, 96, 20, 56, 21, 231, 234, 184, 183, 61, 103, 152, 28, 135, 191, 132, 1, 219, 178, 105, 85, 55, 63, 2, 140], features: 203 };327;[00, 3d, 18, 89, cf, 68, d1, 63, 6c, 36, e3, c4, a8, 23, 05, 66, 05, 86, 85, f3, b0, 66, a1, 6b, 0b, ad, 3f, df, f9, 7d, 27, 69, 2a, 65, 06, 40, 2f, 4e, a8, d8, c8, c7, df, 3b, a2, c8, b6, c8, 27, e4, 84, d6, 7a, 33, cb, 97, 4a, 70, dc, 43, a9, cd, a3, 40, ff, 42, 2e, 21, dc, 8e, c7, 1c, 5d, 55, a3, c1, 75, d5, a5, 1e, 2e, ee, df, 65, 3f, 4c, 69, 40, d0, 07, 05, a9, 73, 3d, 1e, e1, 71, 82, 73, 2c, d1, dd, fc, 58, 63, 69, f7, 8f, b7, e1, 2a, 90, 8e, 0b, 07, 82, 56, d4, 36, 7d, 68, 1f, 02, b2, 7e, 7e, d8, de, ef, ec, 65, 2a, c8, 4b, 09, 6b, dd, 2e, a1, 05, 9a, fb, ea, 11, 05, 53, 02, ca, bf, de, 63, 10, fb, fa, 16, ba, 36, 79, 8f, 0f, fa, 44, d5, a2, a4, b4, 6b, e5, b8, c6, 1f, 77, d8, 12, 5e, 61, cb, c5, c4, 05, 7a, d4, 1a, d8, d1, 38, a6, 8b, a1, 63, 4c, b1, b1, ca, c9, b4, 0d, d3, 75, bb, 76, 4e, b5, 45, 50, d2, cd, 63, 65, 92, e0, 71, 33, 78, 21, 55, 1c, 5d, 8f, fc, 6f, f0, dc, 4e, 71, 75, 86, e4, 3f, c7, 3c, 50, 5c, 01, b6, db, 40, de, 70, 57, ae, e5, 5d, 30, c2, da, b8, 0b, 2d, 5a, 59, f2, 61, 8a, b1, 69, 89, 04, 5b, 8e, e8, 75, 0f, 51, 2e, a8, 0a, f7, 99, 67, cf, 31, 59, 65, 64, 81, eb, a5, c3, 36, 1a, 0f, 6d, f5, e5, 31, fb, 4a, e0, 8e, c4, 53, d8, 3c, 33, ec, 60, 14, 38, 15, e7, ea, b8, b7, 3d, 67, 98, 1c, 87, bf, 84, 01, db, b2, 69, 55, 37, 3f, 02, 8c, 40, cb, 00] +auth_req 800095bac00000000000002dd808512170e79e408750ea5fc69be90a9c6334472867a74b30ae697318e9b6a636392acdaeadbddc6804b2d692410197d419ba08202dac557755e984a140c72290be145aaa35d1a1fb61f44010caf4a3995f144ee2cc496f120b3e1c3380b35728c971611438d0a7a192b67e36fc026828af0d8525b2e0e8e3ee602d34160af700801d2f1b18c9d3f3a142f8528daccef0be2c56f56957557509fb46aec851c9fd6bdd592e8cea5e65ccb3ec89be6ece3ccf6d3f6a439a384165e129cef120ae8c66e9c791d83e3e9301f9eda1a52ab147063edd730f91424c66f6e8362beb131ddd4ec8028da046be6d3a1181e3ccd3a46369f2bd22062f958f686f1defb9c375c07e8c52c78d6cf99d2f994ca856fbdfc09f547e6caa7cdcfd8e846b8c993189f896fc53a438eacbf02892b04090afc0000000007cb7f74000 reject +auth_req 1b80000019bbb82998525d512181ed44d3be3a21868b4053ec2418c4e93240d53084bef42ba1e8b63f7edaa3bd44cddc457fc86cf2ff9beccc826cc2cd7f01e5372ecf29729a3d2375c1a70b2719810ad96a65e13dff59101d908dfdfaafb78f3ea28a6d4554b2eaa8d0382b36ff823654cd22e16c640d5b017cff1e711f0bacd158ee3aa694a79af8e9a7c30f3dc546c9786b263612ed3c957cfa22266c829e406884d6b199aff61d815d85b3d020e0f9a427ce0de0f6a20806fed1d9f273c435d56d419db849dd5729afc9a7c0b0222da75de6406e41acc3275d01f33f895a45ad4e268cf393d2c2bde7bdfde84d75d9b7b3f227800028a500 AuthReq { version: 27, client_id: [187, 184, 41, 152, 82, 93, 81, 33, 129, 237, 68, 211, 190, 58, 33, 134, 139, 64, 83, 236, 36, 24, 196, 233, 50], auth_token: [48, 132, 190, 244, 43, 161, 232, 182, 63, 126, 218, 163, 189, 68, 205, 220, 69, 127, 200, 108, 242, 255, 155, 236, 204, 130, 108, 194, 205, 127, 1, 229, 55, 46, 207, 41, 114, 154, 61, 35, 117, 193, 167, 11, 39, 25, 129, 10, 217, 106, 101, 225, 61, 255, 89, 16, 29, 144, 141, 253, 250, 175, 183, 143, 62, 162, 138, 109, 69, 84, 178, 234, 168, 208, 56, 43, 54, 255, 130, 54, 84, 205, 34, 225, 108, 100, 13, 91, 1, 124, 255, 30, 113, 31, 11, 172, 209, 88, 238, 58, 166, 148, 167, 154, 248, 233, 167, 195, 15, 61, 197, 70, 201, 120, 107, 38, 54, 18, 237, 60, 149, 124, 250, 34, 38, 108, 130, 158, 64, 104, 132, 214, 177, 153, 175, 246, 29, 129, 93, 133, 179, 208, 32, 224, 249, 164, 39, 206, 13, 224, 246, 162, 8, 6, 254, 209, 217, 242, 115, 196, 53, 213, 109, 65, 157, 184, 73, 221, 87, 41, 175, 201, 167, 192, 176, 34, 45, 167, 93, 230, 64, 110, 65, 172, 195, 39, 93, 1, 243, 63, 137, 90, 69, 173, 78, 38, 140, 243, 147, 210, 194, 189, 231, 189, 253, 232, 77, 117, 217, 183, 179, 242, 39], features: 10405 };250;[1b, 19, bb, b8, 29, 98, 52, 5d, 51, 21, 81, ed, 44, d3, be, 3a, 21, 86, 8b, 40, 53, ec, 24, 18, c4, e9, 32, 40, d5, 30, 84, be, f4, 2b, a1, e8, b6, 3f, 7e, da, a3, bd, 44, cd, dc, 45, 7f, c8, 6c, f2, ff, 9b, ec, cc, 82, 6c, c2, cd, 7f, 01, e5, 37, 2e, cf, 29, 72, 9a, 3d, 23, 75, c1, a7, 0b, 27, 19, 81, 0a, d9, 6a, 65, e1, 3d, ff, 59, 10, 1d, 90, 8d, fd, fa, af, b7, 8f, 3e, a2, 8a, 6d, 45, 54, b2, ea, a8, d0, 38, 2b, 36, ff, 82, 36, 54, cd, 22, e1, 6c, 64, 0d, 5b, 01, 7c, ff, 1e, 71, 1f, 0b, ac, d1, 58, ee, 3a, a6, 94, a7, 9a, f8, e9, a7, c3, 0f, 3d, c5, 46, c9, 78, 6b, 26, 36, 12, ed, 3c, 95, 7c, fa, 22, 26, 6c, 82, 9e, 40, 68, 84, d6, b1, 99, af, f6, 1d, 81, 5d, 85, b3, d0, 20, e0, f9, a4, 27, ce, 0d, e0, f6, a2, 08, 06, fe, d1, d9, f2, 73, c4, 35, d5, 6d, 41, 9d, b8, 49, dd, 57, 29, af, c9, a7, c0, b0, 22, 2d, a7, 5d, e6, 40, 6e, 41, ac, c3, 27, 5d, 01, f3, 3f, 89, 5a, 45, ad, 4e, 26, 8c, f3, 93, d2, c2, bd, e7, bd, fd, e8, 4d, 75, d9, b7, b3, f2, 27, 68, a5, 00] +auth_req c00000000000000340430f524f58bbc3bde32089da6b1771b160b3c91cb542b6c0d8874f5614f3f775e09181467f4dc1daf095518ae57491875716f705b265843c161b995611177adac62e0f558000009493a466d05c87991e82c4cbad570887ef351f409832082d75be0ce0ae64980bf5d783fdefdaba34f04c3dbc0333192180687a4f96967eb3a8e79e687de43a61884ecd321c7a91e7df5486c8aa965ab8f8e08b77fad93c2a83a9d54ff957be9d3a7ce831aa801f97aca2dc045e0e2d02d4e35337298bab1ad1a8657f3a9e479b88dd46e9542365d051ff7abab2f3e1a791d2d09a0ac0000072f689f17300 reject +auth_req 85bde0c92dc4d51d4832351f403c7ae53058c6215c06dab444112a8b35536bd4c55d130ce32c42886d8773be5869d7ebb77b80000001f4c0001469eb5bbeb14000 AuthReq { version: 96329929, client_id: [196, 213, 29, 72, 50, 53, 31, 64, 60, 122, 229, 48, 88, 198, 33, 92, 6, 218, 180, 68, 17, 42, 139, 53, 83, 107, 212, 197, 93, 19, 12, 227, 44, 66, 136, 109, 135, 115, 190, 88, 105, 215, 235, 183, 123], auth_token: [244], features: 22445152779953 };65;[85, bd, e0, c9, 2d, c4, d5, 1d, 48, 32, 35, 1f, 40, 3c, 7a, e5, 30, 58, c6, 21, 5c, 06, da, b4, 44, 11, 2a, 8b, 35, 53, 6b, d4, c5, 5d, 13, 0c, e3, 2c, 42, 88, 6d, 87, 73, be, 58, 69, d7, eb, b7, 7b, 01, f4, c0, 00, 14, 69, eb, 5b, be, b1, 00] +auth_req 802382d0c00000000000001ab2b87362605ff2c4c56c09ae661c12340fcc411c958a3e801caa4100ae2bed87fc166cda8cb86999d1946a74d7ae30ac02ed035b3f283fa7979369b35e2122dd946950e7e126147bc9f3475dd9fdc96f943e7416ebb193193c7323a14adc32f5c0f8a4accd82f8b29dc9a4e0b8806710ed29abfc1bb50088f5ae5232db2dede99f3d9df5fb2b7974a27cbb0f15c6289f859e915de8ce4c88b927ce7400d1de787d5f3256b298d9f1f5368bf0e70060e6d90248bd69195725073b7f723bc53840a2bdd3270b848fffdc57fc6c15656fd941ec41c82cefd8ee190ac5861e18c10ba282ea0621582d6370323d167d062a6bcfb685e41a9dccf7dffcf0d358d1ab92a751d52dffa9899e3c25d2ed27f0ba1435ac979b974b492d01faf0f9c000000000000158400006 reject +auth_req c000033aa7f240e505a222596c754078fc9cc55f0176f40d72d05472b1f148dd3e01b1d1e12256c408ea9df51aa86c6edac0f7a83ede9f4e6215211254eceb160c0bbab5e4760a4e85ce56d370e7a860a9b667ed5469da0e6bb656482c0783d41d473d7f740515490a0c363895d9a47b4ab75b469e096a4a0c4090296a2f6d315cbe30603f841bccc000000e0b6b9713c000000000000000 AuthReq { version: 3550460657893, client_id: [162, 34, 89, 108, 117], auth_token: [252, 156, 197, 95, 1, 118, 244, 13, 114, 208, 84, 114, 177, 241, 72, 221, 62, 1, 177, 209, 225, 34, 86, 196, 8, 234, 157, 245, 26, 168, 108, 110, 218, 192, 247, 168, 62, 222, 159, 78, 98, 21, 33, 18, 84, 236, 235, 22, 12, 11, 186, 181, 228, 118, 10, 78, 133, 206, 86, 211, 112, 231, 168, 96, 169, 182, 103, 237, 84, 105, 218, 14, 107, 182, 86, 72, 44, 7, 131, 212, 29, 71, 61, 127, 116, 5, 21, 73, 10, 12, 54, 56, 149, 217, 164, 123, 74, 183, 91, 70, 158, 9, 106, 74, 12, 64, 144, 41, 106, 47, 109, 49, 92, 190, 48, 96, 63, 132, 27, 204], features: 60321142547 };152;[c0, 00, 03, 3a, a7, f2, 40, e5, 05, a2, 22, 59, 6c, 75, 40, 78, fc, 9c, c5, 5f, 01, 76, f4, 0d, 72, d0, 54, 72, b1, f1, 48, dd, 3e, 01, b1, d1, e1, 22, 56, c4, 08, ea, 9d, f5, 1a, a8, 6c, 6e, da, c0, f7, a8, 3e, de, 9f, 4e, 62, 15, 21, 12, 54, ec, eb, 16, 0c, 0b, ba, b5, e4, 76, 0a, 4e, 85, ce, 56, d3, 70, e7, a8, 60, a9, b6, 67, ed, 54, 69, da, 0e, 6b, b6, 56, 48, 2c, 07, 83, d4, 1d, 47, 3d, 7f, 74, 05, 15, 49, 0a, 0c, 36, 38, 95, d9, a4, 7b, 4a, b7, 5b, 46, 9e, 09, 6a, 4a, 0c, 40, 90, 29, 6a, 2f, 6d, 31, 5c, be, 30, 60, 3f, 84, 1b, cc, c0, 00, 00, 0e, 0b, 6b, 97, 13, 00] +auth_req 800022ccc000000000000040b845e5fb89fb89ea9f779af91233dbe69abf88f9eaafea2ab1effe8b102a9171fb1ec1283a1c39b9be0bf7801db8dfe6801478dc1a1dfc56dddd2c409d5add0a800000c14d866d9a002beda7f34fca3e0221736934023203172c15f5fc3307fd0cb7a229d5b24e732cd2c84a71a19c109eb8a73ca2ace7053ee1499c2b8d0718dff2e9d41f3967108c21314cf6ed751be9f09b0810222a05969abf779e52ed84f6c286de1934e9d3a0d2e5019105a42f2155f82046734410aa980163320ae8255035fd1c73fb7bd825b8d8237f2a8d6f1ed4e0a1d5f7c77fa848e351841f708195e68556e11cfbaa92b69fdffa16b35eed59c9ac7f8d57ace5012e6db05d3cdf1e354f262e550e8000000d5d6cd1c51976c82109ae879881 reject +auth_req 80033805800000103c91ac73209ea5ba13729425bbbec48c8000007cb21974a55ffcbffb38047ce29eb77c5278e97edb36b0f1976e464388c091edf6fa2fa7251aa73b94df79cb65aeaad1ff7a4cab74739c551dad0d100299929f019d363edb9e5ae5cbab0d342f8b6333e3adc353cc241a9e3382d074f2920510cc4b93dc3b56f27774c5593edbd6652813e391a8fac9b7a61ac73976b4c0000000003adc6a4000 AuthReq { version: 210949, client_id: [60, 145, 172, 115, 32, 158, 165, 186, 19, 114, 148, 37, 187, 190, 196, 140], auth_token: [178, 25, 116, 165, 95, 252, 191, 251, 56, 4, 124, 226, 158, 183, 124, 82, 120, 233, 126, 219, 54, 176, 241, 151, 110, 70, 67, 136, 192, 145, 237, 246, 250, 47, 167, 37, 26, 167, 59, 148, 223, 121, 203, 101, 174, 170, 209, 255, 122, 76, 171, 116, 115, 156, 85, 29, 173, 13, 16, 2, 153, 146, 159, 1, 157, 54, 62, 219, 158, 90, 229, 203, 171, 13, 52, 47, 139, 99, 51, 227, 173, 195, 83, 204, 36, 26, 158, 51, 130, 208, 116, 242, 146, 5, 16, 204, 75, 147, 220, 59, 86, 242, 119, 116, 197, 89, 62, 219, 214, 101, 40, 19, 227, 145, 168, 250, 201, 183, 166, 26, 199, 57, 118, 180], features: 3857514 };162;[80, 03, 38, 05, 10, 3c, 91, ac, 73, 20, 9e, a5, ba, 13, 72, 94, 25, bb, be, c4, 8c, 40, 7c, b2, 19, 74, a5, 5f, fc, bf, fb, 38, 04, 7c, e2, 9e, b7, 7c, 52, 78, e9, 7e, db, 36, b0, f1, 97, 6e, 46, 43, 88, c0, 91, ed, f6, fa, 2f, a7, 25, 1a, a7, 3b, 94, df, 79, cb, 65, ae, aa, d1, ff, 7a, 4c, ab, 74, 73, 9c, 55, 1d, ad, 0d, 10, 02, 99, 92, 9f, 01, 9d, 36, 3e, db, 9e, 5a, e5, cb, ab, 0d, 34, 2f, 8b, 63, 33, e3, ad, c3, 53, cc, 24, 1a, 9e, 33, 82, d0, 74, f2, 92, 05, 10, cc, 4b, 93, dc, 3b, 56, f2, 77, 74, c5, 59, 3e, db, d6, 65, 28, 13, e3, 91, a8, fa, c9, b7, a6, 1a, c7, 39, 76, b4, 80, 3a, dc, 6a, 00] +auth_req c00082a25e9491d3c00000000000002e29c65756c75dec5baf7bddc04b1496604ee04cf943a8dee9b883dd7c8a391e13a70b316a0c4c64fe7f520439228380000103183ae4e8807f6275651a7306238880dcdaf6fee977cb94610dcf9e14fc7c8eadfda3b8e6401e9948dc632d90149f52110b5b8ba6bcfb0a40a1e9b88c01901a85c5ce3a87abe3aedc3da4ad006b1bc68cf495c9c140521d43cfba2702d7747b8f022d7f2ced5983e1fad1263186b59cd92331e22fb3352cc2c6e9e0aa374218819ce3b43dd19c919f23849ef82b5a8c21dbd63f7cb7b487cb6945900f954a8a9fbeaa0c5d1b6da65c2fe18eb5868cc092f4a64b45328db28365e9b693c143632e9537036d6a997039a945c6086244d990de35b88aec1a523a3db7d666c8180076ca31cbe0507c67a23092eca6cbfca466db548731483abb996eeddefce24a13dcf361d9c00000004b45806cc000000000000002b6583c2e2f20 reject +auth_req c0000000ae5e5fd380000030d3ca77c8711b979604ff73d136cdb1c889b883b5f9e204a850facd80e6876e54522ace010cedafcff4309f693daad905c0000000000000a365ba0cc58d17e42c06f76222d93f4836b10b3beb3bae786db4700751bfddcd56cf66086ebb9fac54c28c1a6621205e7348313c0424abee1fb2fd7f46332c4ab201132b5b7acf662a4b9a10853a82c149af5d599a94ff082eb7acd18851df44bdb90537ae685fc53737f1d89cf94999ee348511904fb99ca5b485674d32ff76fe42686e77768bed546aa273e2c834fd73c6d2d61bfd552b20fdde2345a11b326bc132dcc000000eca6bff4380000007986a1ec108af61 AuthReq { version: 2925420499, client_id: [211, 202, 119, 200, 113, 27, 151, 150, 4, 255, 115, 209, 54, 205, 177, 200, 137, 184, 131, 181, 249, 226, 4, 168, 80, 250, 205, 128, 230, 135, 110, 84, 82, 42, 206, 1, 12, 237, 175, 207, 244, 48, 159, 105, 61, 170, 217, 5], auth_token: [101, 186, 12, 197, 141, 23, 228, 44, 6, 247, 98, 34, 217, 63, 72, 54, 177, 11, 59, 235, 59, 174, 120, 109, 180, 112, 7, 81, 191, 221, 205, 86, 207, 102, 8, 110, 187, 159, 172, 84, 194, 140, 26, 102, 33, 32, 94, 115, 72, 49, 60, 4, 36, 171, 238, 31, 178, 253, 127, 70, 51, 44, 74, 178, 1, 19, 43, 91, 122, 207, 102, 42, 75, 154, 16, 133, 58, 130, 193, 73, 175, 93, 89, 154, 148, 255, 8, 46, 183, 172, 209, 136, 81, 223, 68, 189, 185, 5, 55, 174, 104, 95, 197, 55, 55, 241, 216, 156, 249, 73, 153, 238, 52, 133, 17, 144, 79, 185, 156, 165, 180, 133, 103, 77, 50, 255, 118, 254, 66, 104, 110, 119, 118, 139, 237, 84, 106, 162, 115, 226, 200, 52, 253, 115, 198, 210, 214, 27, 253, 85, 43, 32, 253, 222, 35, 69, 161, 27, 50, 107, 193, 50, 220], features: 63525617475 };250;[c0, 00, 00, 00, ae, 5e, 5f, d3, 30, d3, ca, 77, c8, 71, 1b, 97, 96, 04, ff, 73, d1, 36, cd, b1, c8, 89, b8, 83, b5, f9, e2, 04, a8, 50, fa, cd, 80, e6, 87, 6e, 54, 52, 2a, ce, 01, 0c, ed, af, cf, f4, 30, 9f, 69, 3d, aa, d9, 05, 40, a3, 65, ba, 0c, c5, 8d, 17, e4, 2c, 06, f7, 62, 22, d9, 3f, 48, 36, b1, 0b, 3b, eb, 3b, ae, 78, 6d, b4, 70, 07, 51, bf, dd, cd, 56, cf, 66, 08, 6e, bb, 9f, ac, 54, c2, 8c, 1a, 66, 21, 20, 5e, 73, 48, 31, 3c, 04, 24, ab, ee, 1f, b2, fd, 7f, 46, 33, 2c, 4a, b2, 01, 13, 2b, 5b, 7a, cf, 66, 2a, 4b, 9a, 10, 85, 3a, 82, c1, 49, af, 5d, 59, 9a, 94, ff, 08, 2e, b7, ac, d1, 88, 51, df, 44, bd, b9, 05, 37, ae, 68, 5f, c5, 37, 37, f1, d8, 9c, f9, 49, 99, ee, 34, 85, 11, 90, 4f, b9, 9c, a5, b4, 85, 67, 4d, 32, ff, 76, fe, 42, 68, 6e, 77, 76, 8b, ed, 54, 6a, a2, 73, e2, c8, 34, fd, 73, c6, d2, d6, 1b, fd, 55, 2b, 20, fd, de, 23, 45, a1, 1b, 32, 6b, c1, 32, dc, c0, 00, 00, 0e, ca, 6b, ff, 43, 00] +auth_req c0000000dc18ee91283de9b4e2851b75249ea5ded5f0a98cc12929e6b5b20ac012c188a4d19845db771e1a4107de6945bb4075cef80fd2eaddfd18ce1a515a4c770fd2964a1ec541aab37909f01555f44703fbae86d09d89e3ad049951c098020afcea7d8092f1c4982d2a8d7eb52bf6b4755a9b6690ecc4aad5619938290a4e17523a223c4da6556919dfbb139e53e931c94ec1577e124676df96d102abceedcd25c24fd949b33f800034fc4000a779cfbd AuthReq { version: 3692621457, client_id: [61, 233, 180, 226, 133, 27, 117, 36, 158, 165, 222, 213, 240, 169, 140, 193, 41, 41, 230, 181, 178, 10, 192, 18, 193, 136, 164, 209, 152, 69, 219, 119, 30, 26, 65, 7, 222, 105, 69, 187], auth_token: [206, 248, 15, 210, 234, 221, 253, 24, 206, 26, 81, 90, 76, 119, 15, 210, 150, 74, 30, 197, 65, 170, 179, 121, 9, 240, 21, 85, 244, 71, 3, 251, 174, 134, 208, 157, 137, 227, 173, 4, 153, 81, 192, 152, 2, 10, 252, 234, 125, 128, 146, 241, 196, 152, 45, 42, 141, 126, 181, 43, 246, 180, 117, 90, 155, 102, 144, 236, 196, 170, 213, 97, 153, 56, 41, 10, 78, 23, 82, 58, 34, 60, 77, 166, 85, 105, 25, 223, 187, 19, 158, 83, 233, 49, 201, 78, 193, 87, 126, 18, 70, 118, 223, 150, 209, 2, 171, 206, 237, 205, 37, 194, 79, 217, 73, 179, 63], features: 13564 };174;[c0, 00, 00, 00, dc, 18, ee, 91, 28, 3d, e9, b4, e2, 85, 1b, 75, 24, 9e, a5, de, d5, f0, a9, 8c, c1, 29, 29, e6, b5, b2, 0a, c0, 12, c1, 88, a4, d1, 98, 45, db, 77, 1e, 1a, 41, 07, de, 69, 45, bb, 40, 75, ce, f8, 0f, d2, ea, dd, fd, 18, ce, 1a, 51, 5a, 4c, 77, 0f, d2, 96, 4a, 1e, c5, 41, aa, b3, 79, 09, f0, 15, 55, f4, 47, 03, fb, ae, 86, d0, 9d, 89, e3, ad, 04, 99, 51, c0, 98, 02, 0a, fc, ea, 7d, 80, 92, f1, c4, 98, 2d, 2a, 8d, 7e, b5, 2b, f6, b4, 75, 5a, 9b, 66, 90, ec, c4, aa, d5, 61, 99, 38, 29, 0a, 4e, 17, 52, 3a, 22, 3c, 4d, a6, 55, 69, 19, df, bb, 13, 9e, 53, e9, 31, c9, 4e, c1, 57, 7e, 12, 46, 76, df, 96, d1, 02, ab, ce, ed, cd, 25, c2, 4f, d9, 49, b3, 3f, 74, fc, 00] +auth_req 4d9f39410d450d66bcd124906fec13fb63823ae46f4302bbccb77420938cfcfd1393c239521740a4bb1ab7e37d37d6f6b3a6c9bbdc4f56a14cd7d8fe40a8a06c83f250e42a61d6632a04c88a3a295281da08c153b5fb483903754cb5e45ce230b7886622e44111493dcd1880f2e731a0c9f1d1b1f381ab800a25dad3e8da87f4d49cb0d2103775714c9ba0723dddbec2d3d95d9e4a943827e5812fea4af16b3aa16247108990423f109153d514d2b7d9ec2c877bba926ec275bc0e35a0ebafe9575d248e5bbda7d0e6c11a41726b868def372a9b9b0eb7a6be5aef6c75a505131aa6635e0530735e00 AuthReq { version: 3487, client_id: [65, 13, 69, 13, 102, 188, 209, 36, 144, 111, 236, 19, 251, 99, 130, 58, 228, 111, 67, 2, 187, 204, 183, 116, 32, 147, 140, 252, 253, 19, 147, 194, 57, 82, 23, 64, 164, 187, 26, 183, 227, 125, 55, 214, 246, 179, 166, 201, 187, 220, 79, 86, 161, 76, 215, 216, 254], auth_token: [160, 108, 131, 242, 80, 228, 42, 97, 214, 99, 42, 4, 200, 138, 58, 41, 82, 129, 218, 8, 193, 83, 181, 251, 72, 57, 3, 117, 76, 181, 228, 92, 226, 48, 183, 136, 102, 34, 228, 65, 17, 73, 61, 205, 24, 128, 242, 231, 49, 160, 201, 241, 209, 177, 243, 129, 171, 128, 10, 37, 218, 211, 232, 218, 135, 244, 212, 156, 176, 210, 16, 55, 117, 113, 76, 155, 160, 114, 61, 221, 190, 194, 211, 217, 93, 158, 74, 148, 56, 39, 229, 129, 47, 234, 74, 241, 107, 58, 161, 98, 71, 16, 137, 144, 66, 63, 16, 145, 83, 213, 20, 210, 183, 217, 236, 44, 135, 123, 186, 146, 110, 194, 117, 188, 14, 53, 160, 235, 175, 233, 87, 93, 36, 142, 91, 189, 167, 208, 230, 193, 26, 65, 114, 107, 134, 141, 239, 55, 42, 155, 155, 14, 183, 166, 190, 90, 239, 108, 117, 165, 5, 19, 26, 166, 99, 94, 5, 48], features: 13150 };233;[4d, 9f, 39, 41, 0d, 45, 0d, 66, bc, d1, 24, 90, 6f, ec, 13, fb, 63, 82, 3a, e4, 6f, 43, 02, bb, cc, b7, 74, 20, 93, 8c, fc, fd, 13, 93, c2, 39, 52, 17, 40, a4, bb, 1a, b7, e3, 7d, 37, d6, f6, b3, a6, c9, bb, dc, 4f, 56, a1, 4c, d7, d8, fe, 40, a8, a0, 6c, 83, f2, 50, e4, 2a, 61, d6, 63, 2a, 04, c8, 8a, 3a, 29, 52, 81, da, 08, c1, 53, b5, fb, 48, 39, 03, 75, 4c, b5, e4, 5c, e2, 30, b7, 88, 66, 22, e4, 41, 11, 49, 3d, cd, 18, 80, f2, e7, 31, a0, c9, f1, d1, b1, f3, 81, ab, 80, 0a, 25, da, d3, e8, da, 87, f4, d4, 9c, b0, d2, 10, 37, 75, 71, 4c, 9b, a0, 72, 3d, dd, be, c2, d3, d9, 5d, 9e, 4a, 94, 38, 27, e5, 81, 2f, ea, 4a, f1, 6b, 3a, a1, 62, 47, 10, 89, 90, 42, 3f, 10, 91, 53, d5, 14, d2, b7, d9, ec, 2c, 87, 7b, ba, 92, 6e, c2, 75, bc, 0e, 35, a0, eb, af, e9, 57, 5d, 24, 8e, 5b, bd, a7, d0, e6, c1, 1a, 41, 72, 6b, 86, 8d, ef, 37, 2a, 9b, 9b, 0e, b7, a6, be, 5a, ef, 6c, 75, a5, 05, 13, 1a, a6, 63, 5e, 05, 30, 73, 5e, 00] +auth_req 8000e25480000043676ef304008c622956ddf98f17a6662978f5a75a37875511dc0cf70e279590a25ced79de8c2f425d10dc518f40dc9a81e6c4a3b650d16ead4ce84f443c34a1d5ac86dc40a915ebb31097c0d0663a23eb7d16dfb5996c9e18aadb664f963011a6a72af271ff8b61e0a6aef8288cac5a8624335112a0bfd30cb5c34086881be36bfcd93d8bb685dc0037a588de4bb8140b659ff8a80ed013da3f7c12131d0137c7afb5d9c4cb80bc227673abef2c98f3f13c7568df33c33a205ee7cdd153b51450e42be4d0a3f9d8367de79100d1f98b458d56819364e5705259c8b5239d266d662ba4cc6df93a77ccb8df1309a6a8c0030f27fccc405a8000000402abb327 reject +auth_req c040125341d65bd94001a980000044408e27de660a6d9e91118c411d8e0fbe01bdd0863f5a3621c6eaba7805915b8b2375508a23773a769913542fff46950543e85810961cf87a4f5425f66184df58611be5824ff9004b AuthReq { version: 18034547305634777, client_id: [169], auth_token: [64, 142, 39, 222, 102, 10, 109, 158, 145, 17, 140, 65, 29, 142, 15, 190, 1, 189, 208, 134, 63, 90, 54, 33, 198, 234, 186, 120, 5, 145, 91, 139, 35, 117, 80, 138, 35, 119, 58, 118, 153, 19, 84, 47, 255, 70, 149, 5, 67, 232, 88, 16, 150, 28, 248, 122, 79, 84, 37, 246, 97, 132, 223, 88, 97, 27, 229, 130], features: 4089 };86;[c0, 40, 12, 53, 41, d6, 5b, d9, 01, a9, 40, 44, 40, 8e, 27, de, 66, 0a, 6d, 9e, 91, 11, 8c, 41, 1d, 8e, 0f, be, 01, bd, d0, 86, 3f, 5a, 36, 21, c6, ea, ba, 78, 05, 91, 5b, 8b, 23, 75, 50, 8a, 23, 77, 3a, 76, 99, 13, 54, 2f, ff, 46, 95, 05, 43, e8, 58, 10, 96, 1c, f8, 7a, 4f, 54, 25, f6, 61, 84, df, 58, 61, 1b, e5, 82, 4f, f9, 00] +auth_req c005f4df80797db3c00000000000000cec929d47e72e4445845359084103a9eac1a89a68eed3a63d65c92184b0ebd94ba48e5e4c7e5b4f3273a38d073880582372750d496d4548e2bcdd4f5e7667ff9d93fbfa560397772e6587f215fa0c80868b216c9295a7ae4424b065bf503eb6370edabce1e6936c64d987c915cb244ae1a706f4bc36bb1e44935c6cd17c3b4cfa9aba70cda7b939cb52d97f70ef53593eff40019b9eff39e9469840f24caf20f09c388d6090d1e9b00f261c68323daaf58d284cb1dcab88edea9acaa6fe83e2e79f329eba9a29b68744b25a82a24948f23852fc8c3bb5199533a207688867f00e85effcc53e8524fa2d82d2759d7de1d48bc2c11fdb384b9f5157e23bf9046223492239884c19fa9d7ef651213c9004b4c38dbf43cf80000003a8c4ad05f250fc reject +auth_req c00000002f942dae38cfba5fcbb661e090aef9123239c72309014dd274b8b9ed60b5a2130c2c715afb62ad9c7004bbdd3864f368f2aae5c9b363115453503c636c8000008dee6d16549ea8b226414e6213389fcf9edcb8d30891f2a14fdfc8aa373ce8aa4497e690e36986be6ca0cf34057edafc432d06176226dae286e9b5517243569e1b1e1ffcd69d05cfda022709a152b399b4758df5b53b396dfc1eeca665867d0fa3c654bfb512c37a34b2df238f37e10efc20f7d9baea9fe5c4f5220d65c50afeb50c420260ad82ca9834c211f5fe4e5080000000 AuthReq { version: 798240174, client_id: [207, 186, 95, 203, 182, 97, 224, 144, 174, 249, 18, 50, 57, 199, 35, 9, 1, 77, 210, 116, 184, 185, 237, 96, 181, 162, 19, 12, 44, 113, 90, 251, 98, 173, 156, 112, 4, 187, 221, 56, 100, 243, 104, 242, 170, 229, 201, 179, 99, 17, 84, 83, 80, 60, 99, 108], auth_token: [238, 109, 22, 84, 158, 168, 178, 38, 65, 78, 98, 19, 56, 159, 207, 158, 220, 184, 211, 8, 145, 242, 161, 79, 223, 200, 170, 55, 60, 232, 170, 68, 151, 230, 144, 227, 105, 134, 190, 108, 160, 207, 52, 5, 126, 218, 252, 67, 45, 6, 23, 98, 38, 218, 226, 134, 233, 181, 81, 114, 67, 86, 158, 27, 30, 31, 252, 214, 157, 5, 207, 218, 2, 39, 9, 161, 82, 179, 153, 180, 117, 141, 245, 181, 59, 57, 109, 252, 30, 236, 166, 101, 134, 125, 15, 163, 198, 84, 191, 181, 18, 195, 122, 52, 178, 223, 35, 143, 55, 225, 14, 252, 32, 247, 217, 186, 234, 159, 229, 196, 245, 34, 13, 101, 197, 10, 254, 181, 12, 66, 2, 96, 173, 130, 202, 152, 52, 194, 17, 245, 254], features: 3664 };216;[af, 94, 2d, ae, 38, cf, ba, 5f, cb, b6, 61, e0, 90, ae, f9, 12, 32, 39, c7, 23, 09, 01, 4d, d2, 74, b8, b9, ed, 60, b5, a2, 13, 0c, 2c, 71, 5a, fb, 62, ad, 9c, 70, 04, bb, dd, 38, 64, f3, 68, f2, aa, e5, c9, b3, 63, 11, 54, 53, 50, 3c, 63, 6c, 40, 8d, ee, 6d, 16, 54, 9e, a8, b2, 26, 41, 4e, 62, 13, 38, 9f, cf, 9e, dc, b8, d3, 08, 91, f2, a1, 4f, df, c8, aa, 37, 3c, e8, aa, 44, 97, e6, 90, e3, 69, 86, be, 6c, a0, cf, 34, 05, 7e, da, fc, 43, 2d, 06, 17, 62, 26, da, e2, 86, e9, b5, 51, 72, 43, 56, 9e, 1b, 1e, 1f, fc, d6, 9d, 05, cf, da, 02, 27, 09, a1, 52, b3, 99, b4, 75, 8d, f5, b5, 3b, 39, 6d, fc, 1e, ec, a6, 65, 86, 7d, 0f, a3, c6, 54, bf, b5, 12, c3, 7a, 34, b2, df, 23, 8f, 37, e1, 0e, fc, 20, f7, d9, ba, ea, 9f, e5, c4, f5, 22, 0d, 65, c5, 0a, fe, b5, 0c, 42, 02, 60, ad, 82, ca, 98, 34, c2, 11, f5, fe, 4e, 50, 00] +auth_req c000278f0fbcf9f620ec01aa61f5247aae5ee8fba3545b79b9c51567e6479f213a8ec0a823d252b66880000101297b96115df86e75421a057532baf77033e3288975f59dd476c52349eb26ed5aabee05d46f92cb1fdba20247becbb9056ac135ae09f08208eafac0729a161114523eb1820c43cee81a7e4b13b659f54d75f051d4d63b14ce6355d1b94118170257e341484000b30e3f7c670b49004b5e3e601fec09b981d5183c5a271c14a547e099874c37e39650b61d4244b6f65b085855228fa4466715b07bfa3665e79388ea5711206c066e25535af6dfe92f6714920a024d6ff966034cc35f164c91926760e99d07e82eef9e3033b5e16e85ed6f87b9734bfb2829c64cb6b49b0feda0aa9348b861a1134cc896d8ae572e837a5dc00ff0c691c8d0bfe1bddda36a91de6108c000019de7f36ff38000000631355f9f0d55 reject +auth_req c0000000000769e68000002385421701686eb64a77a728b31819c510cadbf14c995403a1b95eefd42e0a2af22884704079577b1ddd48cadf2d5222e9309f8d60a768498a0a8097f9aab5e6c089148493c0684a1727ef6f939fc330cfa1f5b2bb389f68fd09a0567dc08a55bc687e5cd466a1696cc579dc4cd9121ae07c82b9392b8e7e03fea26a371ecbd417a1b7b2264f51cc788a059798ff60ad7992524c0a08389e5fd308c9baa868c0002c7dc5f0308cc00000000000000f4878237748db0fa24a0dd474218f7872 AuthReq { version: 485862, client_id: [133, 66, 23, 1, 104, 110, 182, 74, 119, 167, 40, 179, 24, 25, 197, 16, 202, 219, 241, 76, 153, 84, 3, 161, 185, 94, 239, 212, 46, 10, 42, 242, 40, 132, 112], auth_token: [87, 123, 29, 221, 72, 202, 223, 45, 82, 34, 233, 48, 159, 141, 96, 167, 104, 73, 138, 10, 128, 151, 249, 170, 181, 230, 192, 137, 20, 132, 147, 192, 104, 74, 23, 39, 239, 111, 147, 159, 195, 48, 207, 161, 245, 178, 187, 56, 159, 104, 253, 9, 160, 86, 125, 192, 138, 85, 188, 104, 126, 92, 212, 102, 161, 105, 108, 197, 121, 220, 76, 217, 18, 26, 224, 124, 130, 185, 57, 43, 142, 126, 3, 254, 162, 106, 55, 30, 203, 212, 23, 161, 183, 178, 38, 79, 81, 204, 120, 138, 5, 151, 152, 255, 96, 173, 121, 146, 82, 76, 10, 8, 56, 158, 95, 211, 8, 201, 186, 168, 104], features: 48918703386764 };201;[80, 07, 69, e6, 23, 85, 42, 17, 01, 68, 6e, b6, 4a, 77, a7, 28, b3, 18, 19, c5, 10, ca, db, f1, 4c, 99, 54, 03, a1, b9, 5e, ef, d4, 2e, 0a, 2a, f2, 28, 84, 70, 40, 79, 57, 7b, 1d, dd, 48, ca, df, 2d, 52, 22, e9, 30, 9f, 8d, 60, a7, 68, 49, 8a, 0a, 80, 97, f9, aa, b5, e6, c0, 89, 14, 84, 93, c0, 68, 4a, 17, 27, ef, 6f, 93, 9f, c3, 30, cf, a1, f5, b2, bb, 38, 9f, 68, fd, 09, a0, 56, 7d, c0, 8a, 55, bc, 68, 7e, 5c, d4, 66, a1, 69, 6c, c5, 79, dc, 4c, d9, 12, 1a, e0, 7c, 82, b9, 39, 2b, 8e, 7e, 03, fe, a2, 6a, 37, 1e, cb, d4, 17, a1, b7, b2, 26, 4f, 51, cc, 78, 8a, 05, 97, 98, ff, 60, ad, 79, 92, 52, 4c, 0a, 08, 38, 9e, 5f, d3, 08, c9, ba, a8, 68, c0, 00, 2c, 7d, c5, f0, 30, 8c, 00] +auth_req c0000141a88d5b934014f5cfe1dc0f534ed3073f14c381831c85350327ed8000005e94ac34a0c141b73cce81ba71333960eed42cebe07df733e07378b07d294d4e394ec6815bea36a195d00d0d701f52ecd482b8bd494c11d3fb998590f9b1b51b191265945ebbcfdd6778d10a26d58fc65bfbb7b129c031826196beed318208c8278fe21ca635f980000008de90fbdee12a06a7 AuthReq { version: 1381512338323, client_id: [245, 207, 225, 220, 15, 83, 78, 211, 7, 63, 20, 195, 129, 131, 28, 133, 53, 3, 39, 237], auth_token: [148, 172, 52, 160, 193, 65, 183, 60, 206, 129, 186, 113, 51, 57, 96, 238, 212, 44, 235, 224, 125, 247, 51, 224, 115, 120, 176, 125, 41, 77, 78, 57, 78, 198, 129, 91, 234, 54, 161, 149, 208, 13, 13, 112, 31, 82, 236, 212, 130, 184, 189, 73, 76, 17, 211, 251, 153, 133, 144, 249, 177, 181, 27, 25, 18, 101, 148, 94, 187, 207, 221, 103, 120, 209, 10, 38, 213, 143, 198, 91, 251, 183, 177, 41, 192, 49, 130, 97, 150, 190, 237, 49, 130, 8], features: 587596477701174777 };148;[c0, 00, 01, 41, a8, 8d, 5b, 93, 14, f5, cf, e1, dc, 0f, 53, 4e, d3, 07, 3f, 14, c3, 81, 83, 1c, 85, 35, 03, 27, ed, 40, 5e, 94, ac, 34, a0, c1, 41, b7, 3c, ce, 81, ba, 71, 33, 39, 60, ee, d4, 2c, eb, e0, 7d, f7, 33, e0, 73, 78, b0, 7d, 29, 4d, 4e, 39, 4e, c6, 81, 5b, ea, 36, a1, 95, d0, 0d, 0d, 70, 1f, 52, ec, d4, 82, b8, bd, 49, 4c, 11, d3, fb, 99, 85, 90, f9, b1, b5, 1b, 19, 12, 65, 94, 5e, bb, cf, dd, 67, 78, d1, 0a, 26, d5, 8f, c6, 5b, fb, b7, b1, 29, c0, 31, 82, 61, 96, be, ed, 31, 82, 08, c8, 27, 8f, e2, 1c, a6, 35, f9, 00] +auth_req c00015e208be02ad8000002674f7ac83e01e831e014ea48948634511c9973f97dcdf448a617e573df879534aad52d111755d4059518cabf14e6d3b73cda68489a24e400ee9528dca53aaf68105475351072ac4db2acd98fd7538722a14174589e1b1d97283c9c22ff3361bc518014581ad195bfdca8ae01a443d3e0be03d90e62bf70cf549835f9acc4df57316687100 AuthReq { version: 24060553462445, client_id: [116, 247, 172, 131, 224, 30, 131, 30, 1, 78, 164, 137, 72, 99, 69, 17, 201, 151, 63, 151, 220, 223, 68, 138, 97, 126, 87, 61, 248, 121, 83, 74, 173, 82, 209, 17, 117, 93], auth_token: [81, 140, 171, 241, 78, 109, 59, 115, 205, 166, 132, 137, 162, 78, 64, 14, 233, 82, 141, 202, 83, 170, 246, 129, 5, 71, 83, 81, 7, 42, 196, 219, 42, 205, 152, 253, 117, 56, 114, 42, 20, 23, 69, 137, 225, 177, 217, 114, 131, 201, 194, 47, 243, 54, 27, 197, 24, 1, 69, 129, 173, 25, 91, 253, 202, 138, 224, 26, 68, 61, 62, 11, 224, 61, 144, 230, 43, 247, 12, 245, 73, 131, 95, 154, 204, 77, 245, 115, 22], features: 10353 };144;[c0, 00, 15, e2, 08, be, 02, ad, 26, 74, f7, ac, 83, e0, 1e, 83, 1e, 01, 4e, a4, 89, 48, 63, 45, 11, c9, 97, 3f, 97, dc, df, 44, 8a, 61, 7e, 57, 3d, f8, 79, 53, 4a, ad, 52, d1, 11, 75, 5d, 40, 59, 51, 8c, ab, f1, 4e, 6d, 3b, 73, cd, a6, 84, 89, a2, 4e, 40, 0e, e9, 52, 8d, ca, 53, aa, f6, 81, 05, 47, 53, 51, 07, 2a, c4, db, 2a, cd, 98, fd, 75, 38, 72, 2a, 14, 17, 45, 89, e1, b1, d9, 72, 83, c9, c2, 2f, f3, 36, 1b, c5, 18, 01, 45, 81, ad, 19, 5b, fd, ca, 8a, e0, 1a, 44, 3d, 3e, 0b, e0, 3d, 90, e6, 2b, f7, 0c, f5, 49, 83, 5f, 9a, cc, 4d, f5, 73, 16, 68, 71, 00] +auth_req c0000000c171cfce800000157e5fdd5030c9c48518a360ccc0472f1e1ae353d920c0000000000000f945ba20f11511396ec0b86d4b9b9a07d55523cfd48a3a1c72f25ff424c07ef49d5c9b680a751e8745dbd1b22be5374d98f1f2cb06b8a5ff6cf45737a9f8117fbe842e13a5e39f6b09f9dbfc992555de3b66a57803b57bd9c7cf34ba6729559ccebd9a0a023a90e5ee1b1f3ef64e41176d00a9ed53a2fbbbe426e7ed24122bf5f65a11fa931a9e161cc820655df59f8d8ac69305e34631bf5f143619d2ad7abea4c5c6c11649d73f299ed11f3e49d947db287542c1fa504c67bc5516d46d0d0a806f203a6001d5cd09814dd3250360dd59b45f58d002649d45126762e07f75a62dce2cd92032165687c52ede4635820694edd6254b0166c7f12f80069f2f80000000 accept +auth_req c000036abc212908c00000000000001b07dea81b5376a1da612ce5d57237f92e638cba2fdcadeb629abed0800000f9f0622ce4687f120f424d6a715dab237434641ee707a82014eed2608372abff9520564a91f1d07f1d1486b66961f041a5159aac1ff8742ae0f5b58abe06ec028474bca80ac04ee2cb4aa64b39c7759768bc602a6299311c771d2cb39a288445ae5cd3c46a336d69b65d2926cf9e634ef88d796f4586fc637e69073f06011ad05e7a0ca91e5b752dda2621fe634f5b503cd2b2751f3dc0fe63a7cfa00256e31813a32c6392c62d6502a98427d110dc343d67ce809ea5d86696d810f429389a4da37cfa4d476823ba07d325588a4d0df4281cf64ff08f42e7d3a896232d68ec1b9388043d2e64c848e920c10bb5c4bfab22116ee0d2f50cd791a1c000003580ad9fd780000004f7b00d58 AuthReq { version: 3756957706504, client_id: [7, 222, 168, 27, 83, 118, 161, 218, 97, 44, 229, 213, 114, 55, 249, 46, 99, 140, 186, 47, 220, 173, 235, 98, 154, 190, 208], auth_token: [240, 98, 44, 228, 104, 127, 18, 15, 66, 77, 106, 113, 93, 171, 35, 116, 52, 100, 30, 231, 7, 168, 32, 20, 238, 210, 96, 131, 114, 171, 255, 149, 32, 86, 74, 145, 241, 208, 127, 29, 20, 134, 182, 105, 97, 240, 65, 165, 21, 154, 172, 31, 248, 116, 42, 224, 245, 181, 138, 190, 6, 236, 2, 132, 116, 188, 168, 10, 192, 78, 226, 203, 74, 166, 75, 57, 199, 117, 151, 104, 188, 96, 42, 98, 153, 49, 28, 119, 29, 44, 179, 154, 40, 132, 69, 174, 92, 211, 196, 106, 51, 109, 105, 182, 93, 41, 38, 207, 158, 99, 78, 248, 141, 121, 111, 69, 134, 252, 99, 126, 105, 7, 63, 6, 1, 26, 208, 94, 122, 12, 169, 30, 91, 117, 45, 218, 38, 33, 254, 99, 79, 91, 80, 60, 210, 178, 117, 31, 61, 192, 254, 99, 167, 207, 160, 2, 86, 227, 24, 19, 163, 44, 99, 146, 198, 45, 101, 2, 169, 132, 39, 209, 16, 220, 52, 61, 103, 206, 128, 158, 165, 216, 102, 150, 216, 16, 244, 41, 56, 154, 77, 163, 124, 250, 77, 71, 104, 35, 186, 7, 211, 37, 88, 138, 77, 13, 244, 40, 28, 246, 79, 240, 143, 66, 231, 211, 168, 150, 35, 45, 104, 236, 27, 147, 136, 4, 61, 46, 100, 200, 72, 233, 32, 193, 11, 181, 196, 191, 171, 34, 17, 110, 224, 210, 245, 12, 215, 145, 161], features: 229792128983 };312;[c0, 00, 03, 6a, bc, 21, 29, 08, 1b, 07, de, a8, 1b, 53, 76, a1, da, 61, 2c, e5, d5, 72, 37, f9, 2e, 63, 8c, ba, 2f, dc, ad, eb, 62, 9a, be, d0, 40, f9, f0, 62, 2c, e4, 68, 7f, 12, 0f, 42, 4d, 6a, 71, 5d, ab, 23, 74, 34, 64, 1e, e7, 07, a8, 20, 14, ee, d2, 60, 83, 72, ab, ff, 95, 20, 56, 4a, 91, f1, d0, 7f, 1d, 14, 86, b6, 69, 61, f0, 41, a5, 15, 9a, ac, 1f, f8, 74, 2a, e0, f5, b5, 8a, be, 06, ec, 02, 84, 74, bc, a8, 0a, c0, 4e, e2, cb, 4a, a6, 4b, 39, c7, 75, 97, 68, bc, 60, 2a, 62, 99, 31, 1c, 77, 1d, 2c, b3, 9a, 28, 84, 45, ae, 5c, d3, c4, 6a, 33, 6d, 69, b6, 5d, 29, 26, cf, 9e, 63, 4e, f8, 8d, 79, 6f, 45, 86, fc, 63, 7e, 69, 07, 3f, 06, 01, 1a, d0, 5e, 7a, 0c, a9, 1e, 5b, 75, 2d, da, 26, 21, fe, 63, 4f, 5b, 50, 3c, d2, b2, 75, 1f, 3d, c0, fe, 63, a7, cf, a0, 02, 56, e3, 18, 13, a3, 2c, 63, 92, c6, 2d, 65, 02, a9, 84, 27, d1, 10, dc, 34, 3d, 67, ce, 80, 9e, a5, d8, 66, 96, d8, 10, f4, 29, 38, 9a, 4d, a3, 7c, fa, 4d, 47, 68, 23, ba, 07, d3, 25, 58, 8a, 4d, 0d, f4, 28, 1c, f6, 4f, f0, 8f, 42, e7, d3, a8, 96, 23, 2d, 68, ec, 1b, 93, 88, 04, 3d, 2e, 64, c8, 48, e9, 20, c1, 0b, b5, c4, bf, ab, 22, 11, 6e, e0, d2, f5, 0c, d7, 91, a1, c0, 00, 00, 35, 80, ad, 9f, d7, 00] +auth_req c0000002dfe40b434042e9772546ee97e481c9d7761e793602daed343c69bd659d4857385134cb7cb7ba0099e0284662228e996665021b033d534d42f8ec63c0d3981f35611f07ad8ead1c000cf99b0184196bcdf3518fa46dc0000ad60faf535d4000 reject +auth_req 400c314d1af9048cb42222b004914a03fdbca88332be6795c624b20fb51694429e5de0f7ae0bebcee4a4967aa901a9a2f4eb1c5dc000000000000014b68c45cd7283609a7d0c7cd04d32d3baece982cc800003310042c9d2 AuthReq { version: 12, client_id: [77, 26, 249, 4, 140, 180, 34, 34, 176, 4, 145, 74, 3, 253, 188, 168, 131, 50, 190, 103, 149, 198, 36, 178, 15, 181, 22, 148, 66, 158, 93, 224, 247, 174, 11, 235, 206, 228, 164, 150, 122, 169, 1, 169, 162, 244, 235, 28, 93], auth_token: [182, 140, 69, 205, 114, 131, 96, 154, 125, 12, 124, 208, 77, 50, 211, 186, 236, 233, 130, 204], features: 817 };85;[0c, 31, 4d, 1a, f9, 04, 8c, b4, 22, 22, b0, 04, 91, 4a, 03, fd, bc, a8, 83, 32, be, 67, 95, c6, 24, b2, 0f, b5, 16, 94, 42, 9e, 5d, e0, f7, ae, 0b, eb, ce, e4, a4, 96, 7a, a9, 01, a9, a2, f4, eb, 1c, 5d, 14, b6, 8c, 45, cd, 72, 83, 60, 9a, 7d, 0c, 7c, d0, 4d, 32, d3, ba, ec, e9, 82, cc, 43, 31, 00] +auth_req 022763fcaf833ee00daa38e9db031976e6af3a67593e2bd8a24572d5bf3cab43411d2c5d5bb595a2f6408cdeca6952d4b2749a3bdd05a3ddd06eb8dca75a12a314f36a6f3d5e7983526bcbec3d4a384bd6276f267dd48e6722b7a1c950812a3f9b5bd5c1586210be1a77b69e21e13f12f88c683c35ebd628b126a8f7223250838fee20d454c848a54a247f63b706c5e5b2eb9de47a86b25f51eea1601bcdd2ff584937557a0f0b6a3a9bd6c1ad7fc2d3c17ebee2d6861cc0e5b60257bd280f400be357c7ffe0c35e32a7fac3 AuthReq { version: 2, client_id: [99, 252, 175, 131, 62, 224, 13, 170, 56, 233, 219, 3, 25, 118, 230, 175, 58, 103, 89, 62, 43, 216, 162, 69, 114, 213, 191, 60, 171, 67, 65, 29, 44, 93, 91, 181, 149, 162, 246], auth_token: [222, 202, 105, 82, 212, 178, 116, 154, 59, 221, 5, 163, 221, 208, 110, 184, 220, 167, 90, 18, 163, 20, 243, 106, 111, 61, 94, 121, 131, 82, 107, 203, 236, 61, 74, 56, 75, 214, 39, 111, 38, 125, 212, 142, 103, 34, 183, 161, 201, 80, 129, 42, 63, 155, 91, 213, 193, 88, 98, 16, 190, 26, 119, 182, 158, 33, 225, 63, 18, 248, 140, 104, 60, 53, 235, 214, 40, 177, 38, 168, 247, 34, 50, 80, 131, 143, 238, 32, 212, 84, 200, 72, 165, 74, 36, 127, 99, 183, 6, 197, 229, 178, 235, 157, 228, 122, 134, 178, 95, 81, 238, 161, 96, 27, 205, 210, 255, 88, 73, 55, 85, 122, 15, 11, 106, 58, 155, 214, 193, 173, 127, 194, 211, 193, 126, 190, 226, 214, 134, 28], features: 64657890844944399 };204;[02, 27, 63, fc, af, 83, 3e, e0, 0d, aa, 38, e9, db, 03, 19, 76, e6, af, 3a, 67, 59, 3e, 2b, d8, a2, 45, 72, d5, bf, 3c, ab, 43, 41, 1d, 2c, 5d, 5b, b5, 95, a2, f6, 40, 8c, de, ca, 69, 52, d4, b2, 74, 9a, 3b, dd, 05, a3, dd, d0, 6e, b8, dc, a7, 5a, 12, a3, 14, f3, 6a, 6f, 3d, 5e, 79, 83, 52, 6b, cb, ec, 3d, 4a, 38, 4b, d6, 27, 6f, 26, 7d, d4, 8e, 67, 22, b7, a1, c9, 50, 81, 2a, 3f, 9b, 5b, d5, c1, 58, 62, 10, be, 1a, 77, b6, 9e, 21, e1, 3f, 12, f8, 8c, 68, 3c, 35, eb, d6, 28, b1, 26, a8, f7, 22, 32, 50, 83, 8f, ee, 20, d4, 54, c8, 48, a5, 4a, 24, 7f, 63, b7, 06, c5, e5, b2, eb, 9d, e4, 7a, 86, b2, 5f, 51, ee, a1, 60, 1b, cd, d2, ff, 58, 49, 37, 55, 7a, 0f, 0b, 6a, 3a, 9b, d6, c1, ad, 7f, c2, d3, c1, 7e, be, e2, d6, 86, 1c, c0, e5, b6, 02, 57, bd, 28, 0f, 00] +auth_req c021206b4a95f4a68000002820c8cfc2d9610f3774d0d0a148255fe78bc99fd1ae8e2af1a3a76db136553555727633b01b6fc694c0000000000000503d3d508b1265e7ce1ac0561d230b5d0e772165c0c2b1199dbea518efa1fac41d0bc038bcd696faeca382038dc0c769fea470b8a1a83cb6665204fdf5ee44325734fc3b061bf27eeffc5a7803014413d4514a8000000031960fa6c682 AuthReq { version: 9324319416382630, client_id: [32, 200, 207, 194, 217, 97, 15, 55, 116, 208, 208, 161, 72, 37, 95, 231, 139, 201, 159, 209, 174, 142, 42, 241, 163, 167, 109, 177, 54, 85, 53, 85, 114, 118, 51, 176, 27, 111, 198, 148], auth_token: [61, 61, 80, 139, 18, 101, 231, 206, 26, 192, 86, 29, 35, 11, 93, 14, 119, 33, 101, 192, 194, 177, 25, 157, 190, 165, 24, 239, 161, 250, 196, 29, 11, 192, 56, 188, 214, 150, 250, 236, 163, 130, 3, 141, 192, 199, 105, 254, 164, 112, 184, 161, 168, 60, 182, 102, 82, 4, 253, 245, 238, 68, 50, 87, 52, 252, 59, 6, 27, 242, 126, 239, 252, 90, 120, 3, 1, 68, 19, 212], features: 4426 };146;[c0, 21, 20, 6b, 4a, 95, f4, a6, 28, 20, c8, cf, c2, d9, 61, 0f, 37, 74, d0, d0, a1, 48, 25, 5f, e7, 8b, c9, 9f, d1, ae, 8e, 2a, f1, a3, a7, 6d, b1, 36, 55, 35, 55, 72, 76, 33, b0, 1b, 6f, c6, 94, 40, 50, 3d, 3d, 50, 8b, 12, 65, e7, ce, 1a, c0, 56, 1d, 23, 0b, 5d, 0e, 77, 21, 65, c0, c2, b1, 19, 9d, be, a5, 18, ef, a1, fa, c4, 1d, 0b, c0, 38, bc, d6, 96, fa, ec, a3, 82, 03, 8d, c0, c7, 69, fe, a4, 70, b8, a1, a8, 3c, b6, 66, 52, 04, fd, f5, ee, 44, 32, 57, 34, fc, 3b, 06, 1b, f2, 7e, ef, fc, 5a, 78, 03, 01, 44, 13, d4, 51, 4a, 00] +auth_req c0000000000fcdd5403789159b4f0164045a4afaf92fc31d8b076b70351276d28077e6a2004ac25a06e99d50d73529334e4560eb49aa86b363bbcea351e3de4eaac0000000000000ad693c67e77613c2523935284003f4a373b6d1a21d4a1a244ca9823b175c5066eb0a599efba6a02198f5fa0db8ce5a9297a3b4e7fa50bb33b58170b8f57c67540289bd894bb1e11cb95cc944970c021bf995a2899ad2c763556ac5ebf64458c6963cb8db143e1f668dd93b899083399c24f5c489732a85cb91a3420354197b334c7c43d50c4b2d358d19539738f05ecd4a815a4944648442fc13edf8bce409724a6398ab610fc6d642bf83830a3a074004b2e12130 accept +auth_req c00000000c05a89e400d01445ce528969860f52701b780c0000000000000d7ff025c42c36ab0b206df459baddb2fdf0fa5b707567d2a0a795c378b259031cd61d39ab266740f3e9430b0cb0183e0b0e8047ec555f447d59714d0967bd4abe6e535e596898a11332f51f977f64fe7d0b3e164f89b4b2ad6f4acf727bbdb5e0376d07e023607864be216bef358ac92c0b69ce3f661657f634f1209a45444b49e45e446dbd2d5c9abc875aa400999121bdc24d163192c5ea737338577f54d83f82708bc56c059def138dcf8cb04fa788b61abfaf6f41a69fcc99d57bca3513b6017ce433bdc126d77c9a2945f2808e4239cb9c248a97f31c067f2ea0089ebadc000000000000000 AuthReq { version: 201697438, client_id: [1, 68, 92, 229, 40, 150, 152, 96, 245, 39, 1, 183, 128], auth_token: [255, 2, 92, 66, 195, 106, 176, 178, 6, 223, 69, 155, 173, 219, 47, 223, 15, 165, 183, 7, 86, 125, 42, 10, 121, 92, 55, 139, 37, 144, 49, 205, 97, 211, 154, 178, 102, 116, 15, 62, 148, 48, 176, 203, 1, 131, 224, 176, 232, 4, 126, 197, 85, 244, 71, 213, 151, 20, 208, 150, 123, 212, 171, 230, 229, 53, 229, 150, 137, 138, 17, 51, 47, 81, 249, 119, 246, 79, 231, 208, 179, 225, 100, 248, 155, 75, 42, 214, 244, 172, 247, 39, 187, 219, 94, 3, 118, 208, 126, 2, 54, 7, 134, 75, 226, 22, 190, 243, 88, 172, 146, 192, 182, 156, 227, 246, 97, 101, 127, 99, 79, 18, 9, 164, 84, 68, 180, 158, 69, 228, 70, 219, 210, 213, 201, 171, 200, 117, 170, 64, 9, 153, 18, 27, 220, 36, 209, 99, 25, 44, 94, 167, 55, 51, 133, 119, 245, 77, 131, 248, 39, 8, 188, 86, 192, 89, 222, 241, 56, 220, 248, 203, 4, 250, 120, 139, 97, 171, 250, 246, 244, 26, 105, 252, 201, 157, 87, 188, 163, 81, 59, 96, 23, 206, 67, 59, 220, 18, 109, 119, 201, 162, 148, 95, 40, 8, 228, 35, 156, 185, 194, 72, 169, 127, 49], features: 29259009446505389 };262;[8c, 05, a8, 9e, 0d, 01, 44, 5c, e5, 28, 96, 98, 60, f5, 27, 01, b7, 80, 40, d7, ff, 02, 5c, 42, c3, 6a, b0, b2, 06, df, 45, 9b, ad, db, 2f, df, 0f, a5, b7, 07, 56, 7d, 2a, 0a, 79, 5c, 37, 8b, 25, 90, 31, cd, 61, d3, 9a, b2, 66, 74, 0f, 3e, 94, 30, b0, cb, 01, 83, e0, b0, e8, 04, 7e, c5, 55, f4, 47, d5, 97, 14, d0, 96, 7b, d4, ab, e6, e5, 35, e5, 96, 89, 8a, 11, 33, 2f, 51, f9, 77, f6, 4f, e7, d0, b3, e1, 64, f8, 9b, 4b, 2a, d6, f4, ac, f7, 27, bb, db, 5e, 03, 76, d0, 7e, 02, 36, 07, 86, 4b, e2, 16, be, f3, 58, ac, 92, c0, b6, 9c, e3, f6, 61, 65, 7f, 63, 4f, 12, 09, a4, 54, 44, b4, 9e, 45, e4, 46, db, d2, d5, c9, ab, c8, 75, aa, 40, 09, 99, 12, 1b, dc, 24, d1, 63, 19, 2c, 5e, a7, 37, 33, 85, 77, f5, 4d, 83, f8, 27, 08, bc, 56, c0, 59, de, f1, 38, dc, f8, cb, 04, fa, 78, 8b, 61, ab, fa, f6, f4, 1a, 69, fc, c9, 9d, 57, bc, a3, 51, 3b, 60, 17, ce, 43, 3b, dc, 12, 6d, 77, c9, a2, 94, 5f, 28, 08, e4, 23, 9c, b9, c2, 48, a9, 7f, 31, c0, 67, f2, ea, 00, 89, eb, ad, 00] +auth_req 4006404392c6034d5f76dc7dcf6119aa92367bf7e967c34f5d2434ab88f817a4ffc943fc9e1c6251721b8050462129af36f2e07dd488d01bd24180d8dcbdf45fb976f5e295d2278000010211c1b3120bf23b952970855a20d88f5df0f6abb798f390eec64e758e340684b0c4979287ef275d84415822dc61889a00f539748047b6f9a0a512374dae6cf7213945db555ee05050034ee48d9ef69e34360a324657c7338a3a2367b5d8328b8fa72fff07cfdaa828c07825ef1cc0fa520aecbda6410afa1a323a48501dd7e0c8d28e178535aaf48a85560687a4f65942a27b093ab675d7133f9bb9bc79c94bbeaa442d3a16d1ea57b8bae1fff1b3289d16930b3b2b2b64ca400db34e00d4ce3803c498bed66ab0d130ced474aa8f76538489ceed2a223c8b46dfb9d8e5082d93174f77770ff40a2e806b16ae3d7f2fc58a873f768ba7118d40748f122bded1c60f79c00000000000b05600 reject +auth_req 8010585dc00000000000000638ac94940db3800000ac44a5a08cb39821b123474db9fb7c1cf31dda9f5fa5e9972bbaa06266aecdff95797c7fab47df8753a4daddae4a43e6c0c481e92f681ccbd24c409fd54007d6fb2d5831583e1a1610c41920a583dd0c7e12c3a4683d6a6f7077a1b9d480d51dc2017e2f93b740475f5e1dc7afd922223668e63349b82fe32929f7945511103d22037738841cfc60dc6842f9f517a522178f469a0258d2664a94309a9cc9b9e15abce203ffec5fa31ff8f188dd800006a800 AuthReq { version: 1071197, client_id: [56, 172, 148, 148, 13, 179], auth_token: [68, 165, 160, 140, 179, 152, 33, 177, 35, 71, 77, 185, 251, 124, 28, 243, 29, 218, 159, 95, 165, 233, 151, 43, 186, 160, 98, 102, 174, 205, 255, 149, 121, 124, 127, 171, 71, 223, 135, 83, 164, 218, 221, 174, 74, 67, 230, 192, 196, 129, 233, 47, 104, 28, 203, 210, 76, 64, 159, 213, 64, 7, 214, 251, 45, 88, 49, 88, 62, 26, 22, 16, 196, 25, 32, 165, 131, 221, 12, 126, 18, 195, 164, 104, 61, 106, 111, 112, 119, 161, 185, 212, 128, 213, 29, 194, 1, 126, 47, 147, 183, 64, 71, 95, 94, 29, 199, 175, 217, 34, 34, 54, 104, 230, 51, 73, 184, 47, 227, 41, 41, 247, 148, 85, 17, 16, 61, 34, 3, 119, 56, 132, 28, 252, 96, 220, 104, 66, 249, 245, 23, 165, 34, 23, 143, 70, 154, 2, 88, 210, 102, 74, 148, 48, 154, 156, 201, 185, 225, 90, 188, 226, 3, 255, 236, 95, 163, 31, 248, 241, 136, 221], features: 1704 };199;[80, 10, 58, 5d, 06, 38, ac, 94, 94, 0d, b3, 40, ac, 44, a5, a0, 8c, b3, 98, 21, b1, 23, 47, 4d, b9, fb, 7c, 1c, f3, 1d, da, 9f, 5f, a5, e9, 97, 2b, ba, a0, 62, 66, ae, cd, ff, 95, 79, 7c, 7f, ab, 47, df, 87, 53, a4, da, dd, ae, 4a, 43, e6, c0, c4, 81, e9, 2f, 68, 1c, cb, d2, 4c, 40, 9f, d5, 40, 07, d6, fb, 2d, 58, 31, 58, 3e, 1a, 16, 10, c4, 19, 20, a5, 83, dd, 0c, 7e, 12, c3, a4, 68, 3d, 6a, 6f, 70, 77, a1, b9, d4, 80, d5, 1d, c2, 01, 7e, 2f, 93, b7, 40, 47, 5f, 5e, 1d, c7, af, d9, 22, 22, 36, 68, e6, 33, 49, b8, 2f, e3, 29, 29, f7, 94, 55, 11, 10, 3d, 22, 03, 77, 38, 84, 1c, fc, 60, dc, 68, 42, f9, f5, 17, a5, 22, 17, 8f, 46, 9a, 02, 58, d2, 66, 4a, 94, 30, 9a, 9c, c9, b9, e1, 5a, bc, e2, 03, ff, ec, 5f, a3, 1f, f8, f1, 88, dd, 46, a8, 00] +auth_req c00087928450a12f8000000434eb9e2dc0000000000000be20a8e4d18a544a649a00308413c58680005cf1ef71b34952d9aca5329eece17e79960616ef8ba68daba71ba16c12be32655ac1c5fbaeb5694c754874ad11b34f9f8f40e14b6d0762b68b9d331415c9276294f61190e25df9f0917aa751066713a8cf117ba9abbe9cd8eb649bb442af495ed867865b8076aa5eb7016c95a878ad95385fbe3a9f2684bf6f5ac6f7b5c1afdcbdb481c00cc8969787aee68dff197e4c5c313b02bcec708efcb3e513a7958eeee871ecedf36d4451cbcda44566c000cb7183d8ff83c000000000000000 accept +auth_req c00007cd469ef2264017a0c3af65841ee774fc1c6e3819801cb35986856dcb5df240aa31d48a3bde47d48b308a214b54c32ec486340dc27bd68557b537ccb9b9b5c85b8caa5c21058b0ab1882bf0cb1a1190cee27021c3903ab1c601e141a14809428e50b271db6fa0b19486a3759237bd260567ebb3f4aa11f5c30cd0243f7698c96901d288149df5dff01ab463725c8bc7104a19a585c9c167746caec4884a5bef4aa302bf4602cbc2aa4ff9343c42c90adbcd9a842c315f10e0609a37a614165588282d1e87107abda01667c00000000000000180000003744597 AuthReq { version: 8578234511910, client_id: [160, 195, 175, 101, 132, 30, 231, 116, 252, 28, 110, 56, 25, 128, 28, 179, 89, 134, 133, 109, 203, 93, 242], auth_token: [49, 212, 138, 59, 222, 71, 212, 139, 48, 138, 33, 75, 84, 195, 46, 196, 134, 52, 13, 194, 123, 214, 133, 87, 181, 55, 204, 185, 185, 181, 200, 91, 140, 170, 92, 33, 5, 139, 10, 177, 136, 43, 240, 203, 26, 17, 144, 206, 226, 112, 33, 195, 144, 58, 177, 198, 1, 225, 65, 161, 72, 9, 66, 142, 80, 178, 113, 219, 111, 160, 177, 148, 134, 163, 117, 146, 55, 189, 38, 5, 103, 235, 179, 244, 170, 17, 245, 195, 12, 208, 36, 63, 118, 152, 201, 105, 1, 210, 136, 20, 157, 245, 223, 240, 26, 180, 99, 114, 92, 139, 199, 16, 74, 25, 165, 133, 201, 193, 103, 116, 108, 174, 196, 136, 74, 91, 239, 74, 163, 2, 191, 70, 2, 203, 194, 170, 79, 249, 52, 60, 66, 201, 10, 219, 205, 154, 132, 44, 49, 95, 16, 224, 96, 154, 55, 166, 20, 22, 85, 136, 40, 45, 30, 135, 16, 122, 189, 160, 22, 103], features: 1 };220;[c0, 00, 07, cd, 46, 9e, f2, 26, 17, a0, c3, af, 65, 84, 1e, e7, 74, fc, 1c, 6e, 38, 19, 80, 1c, b3, 59, 86, 85, 6d, cb, 5d, f2, 40, aa, 31, d4, 8a, 3b, de, 47, d4, 8b, 30, 8a, 21, 4b, 54, c3, 2e, c4, 86, 34, 0d, c2, 7b, d6, 85, 57, b5, 37, cc, b9, b9, b5, c8, 5b, 8c, aa, 5c, 21, 05, 8b, 0a, b1, 88, 2b, f0, cb, 1a, 11, 90, ce, e2, 70, 21, c3, 90, 3a, b1, c6, 01, e1, 41, a1, 48, 09, 42, 8e, 50, b2, 71, db, 6f, a0, b1, 94, 86, a3, 75, 92, 37, bd, 26, 05, 67, eb, b3, f4, aa, 11, f5, c3, 0c, d0, 24, 3f, 76, 98, c9, 69, 01, d2, 88, 14, 9d, f5, df, f0, 1a, b4, 63, 72, 5c, 8b, c7, 10, 4a, 19, a5, 85, c9, c1, 67, 74, 6c, ae, c4, 88, 4a, 5b, ef, 4a, a3, 02, bf, 46, 02, cb, c2, aa, 4f, f9, 34, 3c, 42, c9, 0a, db, cd, 9a, 84, 2c, 31, 5f, 10, e0, 60, 9a, 37, a6, 14, 16, 55, 88, 28, 2d, 1e, 87, 10, 7a, bd, a0, 16, 67, 01, 00] +auth_req 8001007a4011ffdec38b222fab9166825de8817d1f8d4dc00000000000001e2a522a8add4a71f9d8f9cef4332560579f2a33478fa0359c1d0b78af6394c000000006f2957d40002fa68e AuthReq { version: 65658, client_id: [255, 222, 195, 139, 34, 47, 171, 145, 102, 130, 93, 232, 129, 125, 31, 141, 77], auth_token: [42, 82, 42, 138, 221, 74, 113, 249, 216, 249, 206, 244, 51, 37, 96, 87, 159, 42, 51, 71, 143, 160, 53, 156, 29, 11, 120, 175, 99, 148], features: 116561277 };71;[80, 01, 00, 7a, 11, ff, de, c3, 8b, 22, 2f, ab, 91, 66, 82, 5d, e8, 81, 7d, 1f, 8d, 4d, 1e, 2a, 52, 2a, 8a, dd, 4a, 71, f9, d8, f9, ce, f4, 33, 25, 60, 57, 9f, 2a, 33, 47, 8f, a0, 35, 9c, 1d, 0b, 78, af, 63, 94, 86, f2, 95, 7d, 00] +auth_req c000000000004a5d403bbf563d4efa3dd8d46336edada9a8b48e8fb916a769e012389c6abcf351860f57bf8bb6f9be9d2751f01e3140b8e464eada79d13beffbe804535d828000004ead6179bb16ad67ffbb3a877716561c413567f6c7c5839899a7bd981afc447701fdee3e606dbb5ccbae408a2c2fc3c97c8f2a89ce5ceedb80c394afa5c012337271e835f5f235a975d4ef444a0c9742f700 AuthReq { version: 19037, client_id: [191, 86, 61, 78, 250, 61, 216, 212, 99, 54, 237, 173, 169, 168, 180, 142, 143, 185, 22, 167, 105, 224, 18, 56, 156, 106, 188, 243, 81, 134, 15, 87, 191, 139, 182, 249, 190, 157, 39, 81, 240, 30, 49, 64, 184, 228, 100, 234, 218, 121, 209, 59, 239, 251, 232, 4, 83, 93, 130], auth_token: [173, 97, 121, 187, 22, 173, 103, 255, 187, 58, 135, 119, 22, 86, 28, 65, 53, 103, 246, 199, 197, 131, 152, 153, 167, 189, 152, 26, 252, 68, 119, 1, 253, 238, 62, 96, 109, 187, 92, 203, 174, 64, 138, 44, 47, 195, 201, 124, 143, 42, 137, 206, 92, 238, 219, 128, 195, 148, 175, 165, 192, 18, 51, 114, 113, 232, 53, 245, 242, 53, 169, 117, 212, 239, 68, 74, 12, 151], features: 759 };154;[80, 00, 4a, 5d, 3b, bf, 56, 3d, 4e, fa, 3d, d8, d4, 63, 36, ed, ad, a9, a8, b4, 8e, 8f, b9, 16, a7, 69, e0, 12, 38, 9c, 6a, bc, f3, 51, 86, 0f, 57, bf, 8b, b6, f9, be, 9d, 27, 51, f0, 1e, 31, 40, b8, e4, 64, ea, da, 79, d1, 3b, ef, fb, e8, 04, 53, 5d, 82, 40, 4e, ad, 61, 79, bb, 16, ad, 67, ff, bb, 3a, 87, 77, 16, 56, 1c, 41, 35, 67, f6, c7, c5, 83, 98, 99, a7, bd, 98, 1a, fc, 44, 77, 01, fd, ee, 3e, 60, 6d, bb, 5c, cb, ae, 40, 8a, 2c, 2f, c3, c9, 7c, 8f, 2a, 89, ce, 5c, ee, db, 80, c3, 94, af, a5, c0, 12, 33, 72, 71, e8, 35, f5, f2, 35, a9, 75, d4, ef, 44, 4a, 0c, 97, 42, f7, 00] +auth_req c000000000000000c0000000000000170bf3ed09c9c780e788055df69466393f1abcb00f9a6aca800000863bfe1688e86cc673b43e073896b6f4c9ecb8465f1250f594efa764745e3f4066e8754a8161e4d10aeea953b70b11f7949fd6e59408db40501fa2fc2931920a2a3eaabad2753b51b8c9dd4677d18e5225183b7ee73819210e6f363b7d68069e39c239d56c8b8a5ac6f43ab634f0c8e26a34cb78a52eee796349bfaa605135525380b69e4b86f28b36694c00 AuthReq { version: 0, client_id: [11, 243, 237, 9, 201, 199, 128, 231, 136, 5, 93, 246, 148, 102, 57, 63, 26, 188, 176, 15, 154, 106, 202], auth_token: [59, 254, 22, 136, 232, 108, 198, 115, 180, 62, 7, 56, 150, 182, 244, 201, 236, 184, 70, 95, 18, 80, 245, 148, 239, 167, 100, 116, 94, 63, 64, 102, 232, 117, 74, 129, 97, 228, 209, 10, 238, 169, 83, 183, 11, 17, 247, 148, 159, 214, 229, 148, 8, 219, 64, 80, 31, 162, 252, 41, 49, 146, 10, 42, 62, 170, 186, 210, 117, 59, 81, 184, 201, 221, 70, 119, 209, 142, 82, 37, 24, 59, 126, 231, 56, 25, 33, 14, 111, 54, 59, 125, 104, 6, 158, 57, 194, 57, 213, 108, 139, 138, 90, 198, 244, 58, 182, 52, 240, 200, 226, 106, 52, 203, 120, 165, 46, 238, 121, 99, 73, 191, 170, 96, 81, 53, 82, 83, 128, 182, 158, 75, 134, 242], features: 188115276 };182;[00, 17, 0b, f3, ed, 09, c9, c7, 80, e7, 88, 05, 5d, f6, 94, 66, 39, 3f, 1a, bc, b0, 0f, 9a, 6a, ca, 40, 86, 3b, fe, 16, 88, e8, 6c, c6, 73, b4, 3e, 07, 38, 96, b6, f4, c9, ec, b8, 46, 5f, 12, 50, f5, 94, ef, a7, 64, 74, 5e, 3f, 40, 66, e8, 75, 4a, 81, 61, e4, d1, 0a, ee, a9, 53, b7, 0b, 11, f7, 94, 9f, d6, e5, 94, 08, db, 40, 50, 1f, a2, fc, 29, 31, 92, 0a, 2a, 3e, aa, ba, d2, 75, 3b, 51, b8, c9, dd, 46, 77, d1, 8e, 52, 25, 18, 3b, 7e, e7, 38, 19, 21, 0e, 6f, 36, 3b, 7d, 68, 06, 9e, 39, c2, 39, d5, 6c, 8b, 8a, 5a, c6, f4, 3a, b6, 34, f0, c8, e2, 6a, 34, cb, 78, a5, 2e, ee, 79, 63, 49, bf, aa, 60, 51, 35, 52, 53, 80, b6, 9e, 4b, 86, f2, 8b, 36, 69, 4c, 00] +auth_req c0000000be05066180000020404095f797286858f2040506161a209f36f158b7130e767e3717c7c9beeb1c51c0000000000000f4f74eefbc2d72e1cf3fa7540f4c36c7818d4990f0f6c5fe2a11018a54273367259cabd748f4cb402df9e3f6815eae998afe2ef0b045896cd95b78c555f5076ed28c26fe459296cd22cf76deb630171353efb1ac6601e480e17b873d4d2e907f5b51f4d224a56d38fa72ce101067c858da17619e8b8fc3a6c4d9a4b99a3c35c58e25e491617cca10a3f09df20d3e10a8f894f9956d5a50079ed85bc0d412e9e4f9ed10b81ad401968df6fecb8512466472b51ae0925032a1f71c817da01b920272eea890ce6f05cd0a159e19af19e181ce2b12856445688afe73fd8fd79b29b1bcd75f2c38ad1d68d4326b56d2c7549e0137e9f1f9c0000000f8aca91a00 AuthReq { version: 3188000353, client_id: [64, 64, 149, 247, 151, 40, 104, 88, 242, 4, 5, 6, 22, 26, 32, 159, 54, 241, 88, 183, 19, 14, 118, 126, 55, 23, 199, 201, 190, 235, 28, 81], auth_token: [247, 78, 239, 188, 45, 114, 225, 207, 63, 167, 84, 15, 76, 54, 199, 129, 141, 73, 144, 240, 246, 197, 254, 42, 17, 1, 138, 84, 39, 51, 103, 37, 156, 171, 215, 72, 244, 203, 64, 45, 249, 227, 246, 129, 94, 174, 153, 138, 254, 46, 240, 176, 69, 137, 108, 217, 91, 120, 197, 85, 245, 7, 110, 210, 140, 38, 254, 69, 146, 150, 205, 34, 207, 118, 222, 182, 48, 23, 19, 83, 239, 177, 172, 102, 1, 228, 128, 225, 123, 135, 61, 77, 46, 144, 127, 91, 81, 244, 210, 36, 165, 109, 56, 250, 114, 206, 16, 16, 103, 200, 88, 218, 23, 97, 158, 139, 143, 195, 166, 196, 217, 164, 185, 154, 60, 53, 197, 142, 37, 228, 145, 97, 124, 202, 16, 163, 240, 157, 242, 13, 62, 16, 168, 248, 148, 249, 149, 109, 90, 80, 7, 158, 216, 91, 192, 212, 18, 233, 228, 249, 237, 16, 184, 26, 212, 1, 150, 141, 246, 254, 203, 133, 18, 70, 100, 114, 181, 26, 224, 146, 80, 50, 161, 247, 28, 129, 125, 160, 27, 146, 2, 114, 238, 168, 144, 206, 111, 5, 205, 10, 21, 158, 25, 175, 25, 225, 129, 206, 43, 18, 133, 100, 69, 104, 138, 254, 115, 253, 143, 215, 155, 41, 177, 188, 215, 95, 44, 56, 173, 29, 104, 212, 50, 107, 86, 210, 199, 84, 158, 1, 55, 233, 241, 249], features: 4172065050 };305;[c0, 00, 00, 00, be, 05, 06, 61, 20, 40, 40, 95, f7, 97, 28, 68, 58, f2, 04, 05, 06, 16, 1a, 20, 9f, 36, f1, 58, b7, 13, 0e, 76, 7e, 37, 17, c7, c9, be, eb, 1c, 51, 40, f4, f7, 4e, ef, bc, 2d, 72, e1, cf, 3f, a7, 54, 0f, 4c, 36, c7, 81, 8d, 49, 90, f0, f6, c5, fe, 2a, 11, 01, 8a, 54, 27, 33, 67, 25, 9c, ab, d7, 48, f4, cb, 40, 2d, f9, e3, f6, 81, 5e, ae, 99, 8a, fe, 2e, f0, b0, 45, 89, 6c, d9, 5b, 78, c5, 55, f5, 07, 6e, d2, 8c, 26, fe, 45, 92, 96, cd, 22, cf, 76, de, b6, 30, 17, 13, 53, ef, b1, ac, 66, 01, e4, 80, e1, 7b, 87, 3d, 4d, 2e, 90, 7f, 5b, 51, f4, d2, 24, a5, 6d, 38, fa, 72, ce, 10, 10, 67, c8, 58, da, 17, 61, 9e, 8b, 8f, c3, a6, c4, d9, a4, b9, 9a, 3c, 35, c5, 8e, 25, e4, 91, 61, 7c, ca, 10, a3, f0, 9d, f2, 0d, 3e, 10, a8, f8, 94, f9, 95, 6d, 5a, 50, 07, 9e, d8, 5b, c0, d4, 12, e9, e4, f9, ed, 10, b8, 1a, d4, 01, 96, 8d, f6, fe, cb, 85, 12, 46, 64, 72, b5, 1a, e0, 92, 50, 32, a1, f7, 1c, 81, 7d, a0, 1b, 92, 02, 72, ee, a8, 90, ce, 6f, 05, cd, 0a, 15, 9e, 19, af, 19, e1, 81, ce, 2b, 12, 85, 64, 45, 68, 8a, fe, 73, fd, 8f, d7, 9b, 29, b1, bc, d7, 5f, 2c, 38, ad, 1d, 68, d4, 32, 6b, 56, d2, c7, 54, 9e, 01, 37, e9, f1, f9, c0, 00, 00, 00, f8, ac, a9, 1a, 00] +auth_req 80004c084026e02fbda771b3a6fe8f4bdb3171e20cf8314475e8d0cfa7090f5c8b89e584ef5790fc0704918e4102811028313ad3ba3c804df57da6fdf1c8224e388e391dec7a0a4502fe8003241ee58f3b869c282f45506672b6f1b768e22d75f6e9e60ca3b2c754b51d68e1e5d1e6d1fb19e0a7d9165466c438811674b45222872b5bda1faf1a25b9af3ea3e20c5f19edbc13558d20fb22e83cf4a7dc7bba131d6c42c0b79c4f22f3e60f1a0418ee0e805fad3e0250b02a598a92f15de06356a7ead14fe8f2e51e3a35d3225320e1636a3c50d41ea0d30afbfb307c7d95b2031bd51adef996a0d409582c2ae1d3de6b2bc222f3cdaeb3653fb29e2b451889fa8067ccd0c16b7c8d661b254873dd26d94f8accb2a9dce295a40c2c17c4cf04b0321d62f90892bd7d0bffc0d0998dc265c01c44eb0a19d3bac00000000000000bb349b4f7910c506e94d368 reject +auth_req 8117b46e8000003fb4f25f4925279d2b6c77e9d78ae28fa713cda27629b5f22956749e2294e67cffa06f3d0169d9b1e6d09a66f85b182234a9986695e06454b2135a308a714cf740e635a81632b0a14562388a9f0b87dccef3dd3c2c75697bf77b3f6f46b89e4d8420c77a79b2df2a6089c15fdc7e795f98219ef90dbb1ed3091a1d25309ccb79596a9f9a2ba1e01eab7c234ff713fb16db5d4e315290c78dc65abeda64131c8ab3e7f5bb240cd340d25027fe7d51f8936bd8efad6b2d47304bdbb9ffd98f1b84ecafe91701ec906b0dd43398ee6beb556ade678fec468a801debd0030e25c011e12c67fa729d8d142233fc2ba49009436e500fa3e6189b610d31a9449298b7947d17f2654e9d2661899561163e0f613247b1d570940ca7a04736a4bd3c290c55526ebb977c9b97c680004bad400e923a8ff5e272bfd4ede9ebe272bf AuthReq { version: 18330734, client_id: [180, 242, 95, 73, 37, 39, 157, 43, 108, 119, 233, 215, 138, 226, 143, 167, 19, 205, 162, 118, 41, 181, 242, 41, 86, 116, 158, 34, 148, 230, 124, 255, 160, 111, 61, 1, 105, 217, 177, 230, 208, 154, 102, 248, 91, 24, 34, 52, 169, 152, 102, 149, 224, 100, 84, 178, 19, 90, 48, 138, 113, 76, 247], auth_token: [53, 168, 22, 50, 176, 161, 69, 98, 56, 138, 159, 11, 135, 220, 206, 243, 221, 60, 44, 117, 105, 123, 247, 123, 63, 111, 70, 184, 158, 77, 132, 32, 199, 122, 121, 178, 223, 42, 96, 137, 193, 95, 220, 126, 121, 95, 152, 33, 158, 249, 13, 187, 30, 211, 9, 26, 29, 37, 48, 156, 203, 121, 89, 106, 159, 154, 43, 161, 224, 30, 171, 124, 35, 79, 247, 19, 251, 22, 219, 93, 78, 49, 82, 144, 199, 141, 198, 90, 190, 218, 100, 19, 28, 138, 179, 231, 245, 187, 36, 12, 211, 64, 210, 80, 39, 254, 125, 81, 248, 147, 107, 216, 239, 173, 107, 45, 71, 48, 75, 219, 185, 255, 217, 143, 27, 132, 236, 175, 233, 23, 1, 236, 144, 107, 13, 212, 51, 152, 238, 107, 235, 85, 106, 222, 103, 143, 236, 70, 138, 128, 29, 235, 208, 3, 14, 37, 192, 17, 225, 44, 103, 250, 114, 157, 141, 20, 34, 51, 252, 43, 164, 144, 9, 67, 110, 80, 15, 163, 230, 24, 155, 97, 13, 49, 169, 68, 146, 152, 183, 148, 125, 23, 242, 101, 78, 157, 38, 97, 137, 149, 97, 22, 62, 15, 97, 50, 71, 177, 213, 112, 148, 12, 167, 160, 71, 54, 164, 189, 60, 41, 12, 85, 82, 110, 187, 151, 124, 155, 151, 198], features: 19373 };323;[81, 17, b4, 6e, 3f, b4, f2, 5f, 49, 25, 27, 9d, 2b, 6c, 77, e9, d7, 8a, e2, 8f, a7, 13, cd, a2, 76, 29, b5, f2, 29, 56, 74, 9e, 22, 94, e6, 7c, ff, a0, 6f, 3d, 01, 69, d9, b1, e6, d0, 9a, 66, f8, 5b, 18, 22, 34, a9, 98, 66, 95, e0, 64, 54, b2, 13, 5a, 30, 8a, 71, 4c, f7, 40, e6, 35, a8, 16, 32, b0, a1, 45, 62, 38, 8a, 9f, 0b, 87, dc, ce, f3, dd, 3c, 2c, 75, 69, 7b, f7, 7b, 3f, 6f, 46, b8, 9e, 4d, 84, 20, c7, 7a, 79, b2, df, 2a, 60, 89, c1, 5f, dc, 7e, 79, 5f, 98, 21, 9e, f9, 0d, bb, 1e, d3, 09, 1a, 1d, 25, 30, 9c, cb, 79, 59, 6a, 9f, 9a, 2b, a1, e0, 1e, ab, 7c, 23, 4f, f7, 13, fb, 16, db, 5d, 4e, 31, 52, 90, c7, 8d, c6, 5a, be, da, 64, 13, 1c, 8a, b3, e7, f5, bb, 24, 0c, d3, 40, d2, 50, 27, fe, 7d, 51, f8, 93, 6b, d8, ef, ad, 6b, 2d, 47, 30, 4b, db, b9, ff, d9, 8f, 1b, 84, ec, af, e9, 17, 01, ec, 90, 6b, 0d, d4, 33, 98, ee, 6b, eb, 55, 6a, de, 67, 8f, ec, 46, 8a, 80, 1d, eb, d0, 03, 0e, 25, c0, 11, e1, 2c, 67, fa, 72, 9d, 8d, 14, 22, 33, fc, 2b, a4, 90, 09, 43, 6e, 50, 0f, a3, e6, 18, 9b, 61, 0d, 31, a9, 44, 92, 98, b7, 94, 7d, 17, f2, 65, 4e, 9d, 26, 61, 89, 95, 61, 16, 3e, 0f, 61, 32, 47, b1, d5, 70, 94, 0c, a7, a0, 47, 36, a4, bd, 3c, 29, 0c, 55, 52, 6e, bb, 97, 7c, 9b, 97, c6, 80, 00, 4b, ad, 00] +auth_req 99a4646d80000041a72666ed3e821c71ea9d545da57eb7f32870e58ee813a71737ee3d7c7dd2933b9da5f4843405b1c94e03c18a0d4acf4b601fcd9b619f66414b030583d015efec2f4054f064dad2aeac257760f558184d831a17537bc5833d3ef3ca4cf30640b1e4dee9400c609b8e6a0a5425148d105ad5287d03cd7e25365ece8e0ba4c30abcb75acc1d80b4ba0f7b88b310d65686d7428d072373c9f9c61ae6507d79562000 reject +auth_req 40dc28ebf488112bc160598dd4956c3f6562494eb82e020291ebafdfbbc8ad434f0f2ab9deb4f10e714f7fc0000000000000900a0317628216cde760af4d8e18aa6154067e6b020ee873d9dc63312649a0020afc105ce3d7c33de1adcd337d6101b6c794c1416f8775c73d6b8ef89f29165162435c271e0e03be2f27c4a02e547ac9074af629e79d8b1ec003dd0c271ffacdfe4430081490f5843a5d6ad560c7fd564b5c94853941bad88c0866b1a789d29b902a2a3e3ab1792ea8898d19619912d714c00032f0c7dda31c4000 AuthReq { version: 220, client_id: [235, 244, 136, 17, 43, 193, 96, 89, 141, 212, 149, 108, 63, 101, 98, 73, 78, 184, 46, 2, 2, 145, 235, 175, 223, 187, 200, 173, 67, 79, 15, 42, 185, 222, 180, 241, 14, 113, 79, 127], auth_token: [10, 3, 23, 98, 130, 22, 205, 231, 96, 175, 77, 142, 24, 170, 97, 84, 6, 126, 107, 2, 14, 232, 115, 217, 220, 99, 49, 38, 73, 160, 2, 10, 252, 16, 92, 227, 215, 195, 61, 225, 173, 205, 51, 125, 97, 1, 182, 199, 148, 193, 65, 111, 135, 117, 199, 61, 107, 142, 248, 159, 41, 22, 81, 98, 67, 92, 39, 30, 14, 3, 190, 47, 39, 196, 160, 46, 84, 122, 201, 7, 74, 246, 41, 231, 157, 139, 30, 192, 3, 221, 12, 39, 31, 250, 205, 254, 68, 48, 8, 20, 144, 245, 132, 58, 93, 106, 213, 96, 199, 253, 86, 75, 92, 148, 133, 57, 65, 186, 216, 140, 8, 102, 177, 167, 137, 210, 155, 144, 42, 42, 62, 58, 177, 121, 46, 168, 137, 141, 25, 97, 153, 18, 215, 20], features: 56009726731036 };205;[40, dc, 28, eb, f4, 88, 11, 2b, c1, 60, 59, 8d, d4, 95, 6c, 3f, 65, 62, 49, 4e, b8, 2e, 02, 02, 91, eb, af, df, bb, c8, ad, 43, 4f, 0f, 2a, b9, de, b4, f1, 0e, 71, 4f, 7f, 40, 90, 0a, 03, 17, 62, 82, 16, cd, e7, 60, af, 4d, 8e, 18, aa, 61, 54, 06, 7e, 6b, 02, 0e, e8, 73, d9, dc, 63, 31, 26, 49, a0, 02, 0a, fc, 10, 5c, e3, d7, c3, 3d, e1, ad, cd, 33, 7d, 61, 01, b6, c7, 94, c1, 41, 6f, 87, 75, c7, 3d, 6b, 8e, f8, 9f, 29, 16, 51, 62, 43, 5c, 27, 1e, 0e, 03, be, 2f, 27, c4, a0, 2e, 54, 7a, c9, 07, 4a, f6, 29, e7, 9d, 8b, 1e, c0, 03, dd, 0c, 27, 1f, fa, cd, fe, 44, 30, 08, 14, 90, f5, 84, 3a, 5d, 6a, d5, 60, c7, fd, 56, 4b, 5c, 94, 85, 39, 41, ba, d8, 8c, 08, 66, b1, a7, 89, d2, 9b, 90, 2a, 2a, 3e, 3a, b1, 79, 2e, a8, 89, 8d, 19, 61, 99, 12, d7, 14, c0, 00, 32, f0, c7, dd, a3, 1c, 00] +auth_req c00000190dfed0fd8000003cda5cbdd9b84ad4ccd6027e33405f8330e0391e2db36271716c22ef246ecf3b3c661fd780a459afd2497f4dbb469a0ad369e0e4535328bbba23e213ce40ad2adccccc95d62e9d88f06417c4189f461dd16a136c47e8f070bfc2a37519cd391b1c5be765a20a039ab13f0ca774c05d39994d8d5ae0755d945448886378cb78477816dc1356745567923958a3bbb3768c9be8fd79ddde69ecd0a1027cdf9474b48173c7edda14c7a4078ee3d5783cafd5895220d105a0bb2f2280e18a241695c555c9912a641d41adb439a8772ae9e1a50922c4889dd341299acc6e0cbc7fe55a88a68221152d60743fcb72cdc0055a3dd28855614000 AuthReq { version: 107608985853, client_id: [218, 92, 189, 217, 184, 74, 212, 204, 214, 2, 126, 51, 64, 95, 131, 48, 224, 57, 30, 45, 179, 98, 113, 113, 108, 34, 239, 36, 110, 207, 59, 60, 102, 31, 215, 128, 164, 89, 175, 210, 73, 127, 77, 187, 70, 154, 10, 211, 105, 224, 228, 83, 83, 40, 187, 186, 35, 226, 19, 206], auth_token: [42, 220, 204, 204, 149, 214, 46, 157, 136, 240, 100, 23, 196, 24, 159, 70, 29, 209, 106, 19, 108, 71, 232, 240, 112, 191, 194, 163, 117, 25, 205, 57, 27, 28, 91, 231, 101, 162, 10, 3, 154, 177, 63, 12, 167, 116, 192, 93, 57, 153, 77, 141, 90, 224, 117, 93, 148, 84, 72, 136, 99, 120, 203, 120, 71, 120, 22, 220, 19, 86, 116, 85, 103, 146, 57, 88, 163, 187, 179, 118, 140, 155, 232, 253, 121, 221, 222, 105, 236, 208, 161, 2, 124, 223, 148, 116, 180, 129, 115, 199, 237, 218, 20, 199, 164, 7, 142, 227, 213, 120, 60, 175, 213, 137, 82, 32, 209, 5, 160, 187, 47, 34, 128, 225, 138, 36, 22, 149, 197, 85, 201, 145, 42, 100, 29, 65, 173, 180, 57, 168, 119, 42, 233, 225, 165, 9, 34, 196, 136, 157, 211, 65, 41, 154, 204, 110, 12, 188, 127, 229, 90, 136, 166, 130, 33, 21, 45, 96, 116, 63, 203, 114, 205], features: 1506596455208289 };257;[c0, 00, 00, 19, 0d, fe, d0, fd, 3c, da, 5c, bd, d9, b8, 4a, d4, cc, d6, 02, 7e, 33, 40, 5f, 83, 30, e0, 39, 1e, 2d, b3, 62, 71, 71, 6c, 22, ef, 24, 6e, cf, 3b, 3c, 66, 1f, d7, 80, a4, 59, af, d2, 49, 7f, 4d, bb, 46, 9a, 0a, d3, 69, e0, e4, 53, 53, 28, bb, ba, 23, e2, 13, ce, 40, ad, 2a, dc, cc, cc, 95, d6, 2e, 9d, 88, f0, 64, 17, c4, 18, 9f, 46, 1d, d1, 6a, 13, 6c, 47, e8, f0, 70, bf, c2, a3, 75, 19, cd, 39, 1b, 1c, 5b, e7, 65, a2, 0a, 03, 9a, b1, 3f, 0c, a7, 74, c0, 5d, 39, 99, 4d, 8d, 5a, e0, 75, 5d, 94, 54, 48, 88, 63, 78, cb, 78, 47, 78, 16, dc, 13, 56, 74, 55, 67, 92, 39, 58, a3, bb, b3, 76, 8c, 9b, e8, fd, 79, dd, de, 69, ec, d0, a1, 02, 7c, df, 94, 74, b4, 81, 73, c7, ed, da, 14, c7, a4, 07, 8e, e3, d5, 78, 3c, af, d5, 89, 52, 20, d1, 05, a0, bb, 2f, 22, 80, e1, 8a, 24, 16, 95, c5, 55, c9, 91, 2a, 64, 1d, 41, ad, b4, 39, a8, 77, 2a, e9, e1, a5, 09, 22, c4, 88, 9d, d3, 41, 29, 9a, cc, 6e, 0c, bc, 7f, e5, 5a, 88, a6, 82, 21, 15, 2d, 60, 74, 3f, cb, 72, cd, c0, 05, 5a, 3d, d2, 88, 55, 61, 00] +auth_req e73617ae0635d9fa8000002c1d3e16ea21be3d7d0402afb6713adf16b3f383f646b93a2c50e97a0bade5e30c4682ab1696c4aadc67ac671b8000001eb35d9b590b6c61999c214566806d26f17fe727653dfa9e1986655551817cc053f76c50707ca307767a09308e4d95 AuthReq { version: 2825471852417505786, client_id: [29, 62, 22, 234, 33, 190, 61, 125, 4, 2, 175, 182, 113, 58, 223, 22, 179, 243, 131, 246, 70, 185, 58, 44, 80, 233, 122, 11, 173, 229, 227, 12, 70, 130, 171, 22, 150, 196, 170, 220, 103, 172, 103, 27], auth_token: [179, 93, 155, 89, 11, 108, 97, 153, 156, 33, 69, 102, 128, 109, 38, 241, 127, 231, 39, 101, 61, 250, 158, 25, 134, 101, 85, 81, 129, 124], features: 23634467645062307 };106;[e7, 36, 17, ae, 06, 35, d9, fa, 2c, 1d, 3e, 16, ea, 21, be, 3d, 7d, 04, 02, af, b6, 71, 3a, df, 16, b3, f3, 83, f6, 46, b9, 3a, 2c, 50, e9, 7a, 0b, ad, e5, e3, 0c, 46, 82, ab, 16, 96, c4, aa, dc, 67, ac, 67, 1b, 1e, b3, 5d, 9b, 59, 0b, 6c, 61, 99, 9c, 21, 45, 66, 80, 6d, 26, f1, 7f, e7, 27, 65, 3d, fa, 9e, 19, 86, 65, 55, 51, 81, 7c, c0, 53, f7, 6c, 50, 70, 7c, a3, 00] +auth_req 80163dac8000002f95b1adb72acfcdd77b1a92395caac32e0fbc1bb8aa8724d39aa65bad37d37ba6ce4685a7c38fb1dc6d9dc94fe65f2740190c7fa9956a4f6cdcf27e2c143851311dc94824d6c62dc36d49800f934200 AuthReq { version: 1457580, client_id: [149, 177, 173, 183, 42, 207, 205, 215, 123, 26, 146, 57, 92, 170, 195, 46, 15, 188, 27, 184, 170, 135, 36, 211, 154, 166, 91, 173, 55, 211, 123, 166, 206, 70, 133, 167, 195, 143, 177, 220, 109, 157, 201, 79, 230, 95, 39], auth_token: [12, 127, 169, 149, 106, 79, 108, 220, 242, 126, 44, 20, 56, 81, 49, 29, 201, 72, 36, 214, 198, 45, 195, 109, 73], features: 1020738 };87;[80, 16, 3d, ac, 2f, 95, b1, ad, b7, 2a, cf, cd, d7, 7b, 1a, 92, 39, 5c, aa, c3, 2e, 0f, bc, 1b, b8, aa, 87, 24, d3, 9a, a6, 5b, ad, 37, d3, 7b, a6, ce, 46, 85, a7, c3, 8f, b1, dc, 6d, 9d, c9, 4f, e6, 5f, 27, 19, 0c, 7f, a9, 95, 6a, 4f, 6c, dc, f2, 7e, 2c, 14, 38, 51, 31, 1d, c9, 48, 24, d6, c6, 2d, c3, 6d, 49, 80, 0f, 93, 42, 00] +auth_req c0000010676064b54021fe15ee3c9701d337e174591c774638b13feffd0e069f7623195c491d48a934f1174046ca1c1e99e98c43a8a113ecdbf2bcde608d9437f3abf6235a8758e69d0f2b6ea5eba43e5abda1c50576fd61f1d1268c92aeb95ab903da7f9f3183327b9164fa7812331267bd51405500 AuthReq { version: 70453847221, client_id: [254, 21, 238, 60, 151, 1, 211, 55, 225, 116, 89, 28, 119, 70, 56, 177, 63, 239, 253, 14, 6, 159, 118, 35, 25, 92, 73, 29, 72, 169, 52, 241, 23], auth_token: [202, 28, 30, 153, 233, 140, 67, 168, 161, 19, 236, 219, 242, 188, 222, 96, 141, 148, 55, 243, 171, 246, 35, 90, 135, 88, 230, 157, 15, 43, 110, 165, 235, 164, 62, 90, 189, 161, 197, 5, 118, 253, 97, 241, 209, 38, 140, 146, 174, 185, 90, 185, 3, 218, 127, 159, 49, 131, 50, 123, 145, 100, 250, 120, 18, 51, 18, 103, 189, 81], features: 85 };118;[c0, 00, 00, 10, 67, 60, 64, b5, 21, fe, 15, ee, 3c, 97, 01, d3, 37, e1, 74, 59, 1c, 77, 46, 38, b1, 3f, ef, fd, 0e, 06, 9f, 76, 23, 19, 5c, 49, 1d, 48, a9, 34, f1, 17, 40, 46, ca, 1c, 1e, 99, e9, 8c, 43, a8, a1, 13, ec, db, f2, bc, de, 60, 8d, 94, 37, f3, ab, f6, 23, 5a, 87, 58, e6, 9d, 0f, 2b, 6e, a5, eb, a4, 3e, 5a, bd, a1, c5, 05, 76, fd, 61, f1, d1, 26, 8c, 92, ae, b9, 5a, b9, 03, da, 7f, 9f, 31, 83, 32, 7b, 91, 64, fa, 78, 12, 33, 12, 67, bd, 51, 40, 55, 00] +auth_req 8295fa41c00000000000000760927af6a7d0a5c0000000000000e8aa1423842c15b020ee91b040773b4a8cfba743e2598d1aec979ad865a8c39ef293942312e9777703c3fabb96113cffcef6e8836e872969c8e7bcc40891d2746a0cdb05752a2cab37e976be10bcb87c732bfbda8bf86691a47d79f118f678301e7ccaf73392c79fad790bf875b265af706dade4d49b9234790724b4dd9b5f3e3090dfad493d1ae6da8127818f615b8341e639d895231f3993f8103b9587261153fdd3bcd2a8bb70d2fac74a08a4d7d7fadbc7e4c74ec75054e8699f9ea1083c4e247c02bb8007015f3b4cd46289d6cde5a7c241a309894a105d3ebd8309273d3aa9000f11a35a5f54c00000000afd9e160bbf8c59acd4dfe0ec213618 accept +auth_req e1c06327e0b4e1d33676ae499975f8ae174a8a098b6da14c014446216f208c3af1030da63c63bed7d2f16bcd06235be5c5d69161bd2f5dffa6f7ab52cbd96d407e1cd3604f581e387626043ec69ebf1c110e6611955637f8dd4490837f9350776d757343dcf7b65dc218156445213ead2623a229dde151c50613502285ab5f5f32908189dcb7f418fe6d9af2da494543e0907243761c60788595e2af5b855f25d698b4eeb73d3fb97adb78efa3b39736e09ca68b4d58497ff9ea29fd3d78b181b60c96004d82183c83 AuthReq { version: 2432052821704892883, client_id: [118, 174, 73, 153, 117, 248, 174, 23, 74, 138, 9, 139, 109, 161, 76, 1, 68, 70, 33, 111, 32, 140, 58, 241, 3, 13, 166, 60, 99, 190, 215, 210, 241, 107, 205, 6, 35, 91, 229, 197, 214, 145, 97, 189, 47, 93, 255, 166, 247, 171, 82, 203, 217, 109], auth_token: [28, 211, 96, 79, 88, 30, 56, 118, 38, 4, 62, 198, 158, 191, 28, 17, 14, 102, 17, 149, 86, 55, 248, 221, 68, 144, 131, 127, 147, 80, 119, 109, 117, 115, 67, 220, 247, 182, 93, 194, 24, 21, 100, 69, 33, 62, 173, 38, 35, 162, 41, 221, 225, 81, 197, 6, 19, 80, 34, 133, 171, 95, 95, 50, 144, 129, 137, 220, 183, 244, 24, 254, 109, 154, 242, 218, 73, 69, 67, 224, 144, 114, 67, 118, 28, 96, 120, 133, 149, 226, 175, 91, 133, 95, 37, 214, 152, 180, 238, 183, 61, 63, 185, 122, 219, 120, 239, 163, 179, 151, 54, 224, 156, 166, 139, 77, 88, 73, 127, 249, 234, 41, 253, 61, 120, 177], features: 28707990 };196;[e1, c0, 63, 27, e0, b4, e1, d3, 36, 76, ae, 49, 99, 75, f8, ae, 17, 4a, 8a, 09, 8b, 6d, a1, 4c, 01, 44, 46, 21, 6f, 20, 8c, 3a, f1, 03, 0d, a6, 3c, 63, be, d7, d2, f1, 6b, cd, 06, 23, 5b, e5, c5, d6, 91, 61, bd, 2f, 5d, ff, a6, f7, ab, 52, cb, d9, 6d, 40, 7e, 1c, d3, 60, 4f, 58, 1e, 38, 76, 26, 04, 3e, c6, 9e, bf, 1c, 11, 0e, 66, 11, 95, 56, 37, f8, dd, 44, 90, 83, 7f, 93, 50, 77, 6d, 75, 73, 43, dc, f7, b6, 5d, c2, 18, 15, 64, 45, 21, 3e, ad, 26, 23, a2, 29, dd, e1, 51, c5, 06, 13, 50, 22, 85, ab, 5f, 5f, 32, 90, 81, 89, dc, b7, f4, 18, fe, 6d, 9a, f2, da, 49, 45, 43, e0, 90, 72, 43, 76, 1c, 60, 78, 85, 95, e2, af, 5b, 85, 5f, 25, d6, 98, b4, ee, b7, 3d, 3f, b9, 7a, db, 78, ef, a3, b3, 97, 36, e0, 9c, a6, 8b, 4d, 58, 49, 7f, f9, ea, 29, fd, 3d, 78, b1, 81, b6, 0c, 96, 00] +auth_req 81f2e3640d5ed3a5b180a13ebcec9a16af49c0000000000000aecfc05e55e5a464ae35fe85c3b756295696caaf205a2906b2c0c77494794e73d2aec67cb404f7e8cbadb03454d75bfe6eaa6d9150f2929abe8e9c1d92b5749b7e3f880982ac93a9a10f6ec0e7b644057c7f5859ee9d528388c3009d062fb4b800ac52acd3f00ec720c1eb1a85b02c22f2165fc827affe798e7a46604f01c24b329a27b73ec02c4eb8113a80e0863ad0aa353fced0cf996db8611eda40ea1b8579d31ad1434ec86152a6851313ef6bc1fb3a66d909eff10bf42c12e951ad6c557818e5 accept +auth_req 80000f2003929a1340b5a6c4707226f7ffd2f14140af4b0c443d757fbf33fa50f3b2c1d13b44fc063dab0c9f517f8ec854cfe7c6b8f0e55dc58834d8ec9048bc7ea20ef28dd8e5aa0cb5055577261858a21beaa04e1828dfc8f757de203f333da2241176b1ea160668dc9fa9aa47ffc3fd20943f2a92849eabd10ba7b7e37a7eabd36bc63716d5b1c61fde87efd4d9aaa5e68c3932895310c58c56db261f94e1377b3e09487d66d8b2c121b03dccddf7a7b56ceba60f30596564c68d1e74364a5c80000000321bd600 AuthReq { version: 3872, client_id: [146, 154, 19], auth_token: [166, 196, 112, 114, 38, 247, 255, 210, 241, 65, 64, 175, 75, 12, 68, 61, 117, 127, 191, 51, 250, 80, 243, 178, 193, 209, 59, 68, 252, 6, 61, 171, 12, 159, 81, 127, 142, 200, 84, 207, 231, 198, 184, 240, 229, 93, 197, 136, 52, 216, 236, 144, 72, 188, 126, 162, 14, 242, 141, 216, 229, 170, 12, 181, 5, 85, 119, 38, 24, 88, 162, 27, 234, 160, 78, 24, 40, 223, 200, 247, 87, 222, 32, 63, 51, 61, 162, 36, 17, 118, 177, 234, 22, 6, 104, 220, 159, 169, 170, 71, 255, 195, 253, 32, 148, 63, 42, 146, 132, 158, 171, 209, 11, 167, 183, 227, 122, 126, 171, 211, 107, 198, 55, 22, 213, 177, 198, 31, 222, 135, 239, 212, 217, 170, 165, 230, 140, 57, 50, 137, 83, 16, 197, 140, 86, 219, 38, 31, 148, 225, 55, 123, 62, 9, 72, 125, 102, 216, 178, 193, 33, 176, 61, 204, 221, 247, 167, 181, 108, 235, 166, 15, 48, 89, 101, 100, 198, 141, 30, 116, 54], features: 2652 };197;[4f, 20, 03, 92, 9a, 13, 40, b5, a6, c4, 70, 72, 26, f7, ff, d2, f1, 41, 40, af, 4b, 0c, 44, 3d, 75, 7f, bf, 33, fa, 50, f3, b2, c1, d1, 3b, 44, fc, 06, 3d, ab, 0c, 9f, 51, 7f, 8e, c8, 54, cf, e7, c6, b8, f0, e5, 5d, c5, 88, 34, d8, ec, 90, 48, bc, 7e, a2, 0e, f2, 8d, d8, e5, aa, 0c, b5, 05, 55, 77, 26, 18, 58, a2, 1b, ea, a0, 4e, 18, 28, df, c8, f7, 57, de, 20, 3f, 33, 3d, a2, 24, 11, 76, b1, ea, 16, 06, 68, dc, 9f, a9, aa, 47, ff, c3, fd, 20, 94, 3f, 2a, 92, 84, 9e, ab, d1, 0b, a7, b7, e3, 7a, 7e, ab, d3, 6b, c6, 37, 16, d5, b1, c6, 1f, de, 87, ef, d4, d9, aa, a5, e6, 8c, 39, 32, 89, 53, 10, c5, 8c, 56, db, 26, 1f, 94, e1, 37, 7b, 3e, 09, 48, 7d, 66, d8, b2, c1, 21, b0, 3d, cc, dd, f7, a7, b5, 6c, eb, a6, 0f, 30, 59, 65, 64, c6, 8d, 1e, 74, 36, 4a, 5c, 00] +auth_req 4012800000415a8b1ea93773caa6995b89f2b7314887492f2a67f86c1f16c51d1ae4da81b65b0ddb07f559289c832268d3a00fa4dfd9afbdfd54f7db154539b91130c624ed451740ddf0c3bddc7a80dd024e1d940ccd7c250967320c6abbb7c6dbe983e20a14938c06684bf2114a30f0450900d9207b133bb2f4fcfa8c1142a1a965ab8f8839745e22cdc1506953cca24272bc1f25a9ef66563f8b8884d748711735bd818a284e66bb66b7770f76debbe7ee36e5139bbb28eb575972c12e3a9aa6cb0ca865da7cbcff3b84d8447f46c0a85f05ae4b67f2e167a8f9b1154d47e8065211cfcea8381fc41a359ea38d69bc90472c5febd6cda5e453e90dbd6c0a574810f41806038a3555e95670e579cb87b5ee36deb082b8b67aa929996105c31023add1916bcd8000000b00 reject +auth_req 800044dac000000000000011a001ee81ae0d8e6bb21f01a3ba17abc25940fe7f5277af87fe7cb8099e7ef4510ffdf26345e0fc23b855e8cb5a508613f06395a6c915d07b5f3b2658aab24979a807da78ca0556e3a93a051f9380683167072054d668dd54356364ab45ff0e2e6e908b038fc864bf8cb399eebdd35e68f4884bb53534ea7b2b9d9d53e28ba377b517d525a5c0f3831e2a92402afaab47dd2fe4d01dbaaf3ef959e049414b79bb22ee8e95709bf194e002d4e9d7678aa6c0fd94a6bc54c7912dcad8c36ec5834a0525b62b31f0092884d8cee97509a23528f905ec4fbfddbbdbefa2088ae77b6dd15daf38faa02aae3816d0193c319682fcda109dcdf2eccaefdef663871ac33bb52e13575cbbcf44a72f6c3bed0b541e3ac0000011058f5cc6c000000000000000 AuthReq { version: 17626, client_id: [160, 1, 238, 129, 174, 13, 142, 107, 178, 31, 1, 163, 186, 23, 171, 194, 89], auth_token: [127, 82, 119, 175, 135, 254, 124, 184, 9, 158, 126, 244, 81, 15, 253, 242, 99, 69, 224, 252, 35, 184, 85, 232, 203, 90, 80, 134, 19, 240, 99, 149, 166, 201, 21, 208, 123, 95, 59, 38, 88, 170, 178, 73, 121, 168, 7, 218, 120, 202, 5, 86, 227, 169, 58, 5, 31, 147, 128, 104, 49, 103, 7, 32, 84, 214, 104, 221, 84, 53, 99, 100, 171, 69, 255, 14, 46, 110, 144, 139, 3, 143, 200, 100, 191, 140, 179, 153, 238, 189, 211, 94, 104, 244, 136, 75, 181, 53, 52, 234, 123, 43, 157, 157, 83, 226, 139, 163, 119, 181, 23, 213, 37, 165, 192, 243, 131, 30, 42, 146, 64, 42, 250, 171, 71, 221, 47, 228, 208, 29, 186, 175, 62, 249, 89, 224, 73, 65, 75, 121, 187, 34, 238, 142, 149, 112, 155, 241, 148, 224, 2, 212, 233, 215, 103, 138, 166, 192, 253, 148, 166, 188, 84, 199, 145, 45, 202, 216, 195, 110, 197, 131, 74, 5, 37, 182, 43, 49, 240, 9, 40, 132, 216, 206, 233, 117, 9, 162, 53, 40, 249, 5, 236, 79, 191, 221, 187, 219, 239, 162, 8, 138, 231, 123, 109, 209, 93, 175, 56, 250, 160, 42, 174, 56, 22, 208, 25, 60, 49, 150, 130, 252, 218, 16, 157, 205, 242, 236, 202, 239, 222, 246, 99, 135, 26, 195, 59, 181, 46, 19, 87, 92, 187, 207, 68, 167, 47, 108, 59, 237, 11, 84, 30, 58], features: 73107725510 };301;[80, 00, 44, da, 11, a0, 01, ee, 81, ae, 0d, 8e, 6b, b2, 1f, 01, a3, ba, 17, ab, c2, 59, 40, fe, 7f, 52, 77, af, 87, fe, 7c, b8, 09, 9e, 7e, f4, 51, 0f, fd, f2, 63, 45, e0, fc, 23, b8, 55, e8, cb, 5a, 50, 86, 13, f0, 63, 95, a6, c9, 15, d0, 7b, 5f, 3b, 26, 58, aa, b2, 49, 79, a8, 07, da, 78, ca, 05, 56, e3, a9, 3a, 05, 1f, 93, 80, 68, 31, 67, 07, 20, 54, d6, 68, dd, 54, 35, 63, 64, ab, 45, ff, 0e, 2e, 6e, 90, 8b, 03, 8f, c8, 64, bf, 8c, b3, 99, ee, bd, d3, 5e, 68, f4, 88, 4b, b5, 35, 34, ea, 7b, 2b, 9d, 9d, 53, e2, 8b, a3, 77, b5, 17, d5, 25, a5, c0, f3, 83, 1e, 2a, 92, 40, 2a, fa, ab, 47, dd, 2f, e4, d0, 1d, ba, af, 3e, f9, 59, e0, 49, 41, 4b, 79, bb, 22, ee, 8e, 95, 70, 9b, f1, 94, e0, 02, d4, e9, d7, 67, 8a, a6, c0, fd, 94, a6, bc, 54, c7, 91, 2d, ca, d8, c3, 6e, c5, 83, 4a, 05, 25, b6, 2b, 31, f0, 09, 28, 84, d8, ce, e9, 75, 09, a2, 35, 28, f9, 05, ec, 4f, bf, dd, bb, db, ef, a2, 08, 8a, e7, 7b, 6d, d1, 5d, af, 38, fa, a0, 2a, ae, 38, 16, d0, 19, 3c, 31, 96, 82, fc, da, 10, 9d, cd, f2, ec, ca, ef, de, f6, 63, 87, 1a, c3, 3b, b5, 2e, 13, 57, 5c, bb, cf, 44, a7, 2f, 6c, 3b, ed, 0b, 54, 1e, 3a, c0, 00, 00, 11, 05, 8f, 5c, c6, 00] +auth_req 8036975d8000001ea2fdbd1e4ab43534790f34229621d810b8dee350cb4fcee6ee42608958f840585602a301fb4c6a3cb62292db2f4a824b485b78261d503a9ae8fb79c754580aab5a6b37fd47b8267f45997ed226b64bd4c61ef4e4de5e649e3bbc9ac4a2c4f2631281708fb257748d8ab3175ba9bc9500005c0bae539a99ba8000944fc000000000000009ba48638abc785632d8ff279803 accept +auth_req 40028000003292790a93175f33fe49d90419ef5b1b805536ef039461644674ad67283101f124eb4473deaac5dd6d3497694dc33154f7159c2e3f55187da79a51553af929ed9e22990998866f77c5d324f2da561a0e64807ae00c2f570ad22ce910913cd49721aa816d579a8000000f5952ee15caf62b9b2697aad555ce6f AuthReq { version: 2, client_id: [146, 121, 10, 147, 23, 95, 51, 254, 73, 217, 4, 25, 239, 91, 27, 128, 85, 54, 239, 3, 148, 97, 100, 70, 116, 173, 103, 40, 49, 1, 241, 36, 235, 68, 115, 222, 170, 197, 221, 109, 52, 151, 105, 77, 195, 49, 84, 247, 21, 156], auth_token: [63, 85, 24, 125, 167, 154, 81, 85, 58, 249, 41, 237, 158, 34, 153, 9, 152, 134, 111, 119, 197, 211, 36, 242, 218, 86, 26, 14, 100, 128, 122, 224, 12, 47, 87, 10, 210, 44, 233, 16, 145, 60, 212, 151, 33, 170], features: 23943066 };126;[02, 32, 92, 79, 0a, 93, 17, 5f, 33, fe, 49, d9, 04, 19, ef, 5b, 1b, 80, 55, 36, ef, 03, 94, 61, 64, 46, 74, ad, 67, 28, 31, 01, f1, 24, eb, 44, 73, de, aa, c5, dd, 6d, 34, 97, 69, 4d, c3, 31, 54, f7, 15, 9c, 2e, 3f, 55, 18, 7d, a7, 9a, 51, 55, 3a, f9, 29, ed, 9e, 22, 99, 09, 98, 86, 6f, 77, c5, d3, 24, f2, da, 56, 1a, 0e, 64, 80, 7a, e0, 0c, 2f, 57, 0a, d2, 2c, e9, 10, 91, 3c, d4, 97, 21, aa, 81, 6d, 57, 9a, 00] +auth_req 80003123c00000000000000f7cf75497ab55de2205445bb8986edd40cd3abb8075c9e7164f24af606ff8dcb0c7190a9bbbccd222a733a90c8576d18c0afdb2640dea97cc2c0ded34c823f5104ab7be43557737f96507f66dfa193a64b399c3284f0056730613d697dcca2bc853e420c4ad120da9317119edeb9870140f47411bf6d556eaf2b1df13397ff7defdaceab34f9cd1db212f15e7c5dcc6d36e2070d7878ff423778ac4a03ef471f8b50d9501aad9301c1e35117d1dedff21ad492e05dde9e4f802e16eec251fc2c21165643d692aec2e592c55327d4acb8b560949c0e75722ce7760bf4e73e1c00000000000000700 accept +auth_req 464cc0000000000000380d1dacc1dec1bf621a7cbfc76ef2104e68e8194dad6821e2f62ba60e24c3825f2e28fa061a32ef19b3690bfd0814e0dec1022d58269a5aba800000a6d4635676617b370e127eea9b7bd11bafb1d0be093c304cf0624a78bc1015027e08f81d7e920d99599bfc425c4b20a6ea289c07d770d60582c8949001874edab608a455f8e4144069bc79985e197fb403d5a434cb9bbe0ea89eda27b187924c4fcbeb1d53ce282816d9ea253c7cc1a177342736ca7e0573982346792ce22e7f55d87952d9581b85eefad77b0bfa9fd946e9a46101b3d7dcbccacb60f8fa352a6a2d081abc5f71c00000054441b0e1004021231b AuthReq { version: 1612, client_id: [13, 29, 172, 193, 222, 193, 191, 98, 26, 124, 191, 199, 110, 242, 16, 78, 104, 232, 25, 77, 173, 104, 33, 226, 246, 43, 166, 14, 36, 195, 130, 95, 46, 40, 250, 6, 26, 50, 239, 25, 179, 105, 11, 253, 8, 20, 224, 222, 193, 2, 45, 88, 38, 154, 90, 186], auth_token: [212, 99, 86, 118, 97, 123, 55, 14, 18, 126, 234, 155, 123, 209, 27, 175, 177, 208, 190, 9, 60, 48, 76, 240, 98, 74, 120, 188, 16, 21, 2, 126, 8, 248, 29, 126, 146, 13, 153, 89, 155, 252, 66, 92, 75, 32, 166, 234, 40, 156, 7, 215, 112, 214, 5, 130, 200, 148, 144, 1, 135, 78, 218, 182, 8, 164, 85, 248, 228, 20, 64, 105, 188, 121, 152, 94, 25, 127, 180, 3, 213, 164, 52, 203, 155, 190, 14, 168, 158, 218, 39, 177, 135, 146, 76, 79, 203, 235, 29, 83, 206, 40, 40, 22, 217, 234, 37, 60, 124, 193, 161, 119, 52, 39, 54, 202, 126, 5, 115, 152, 35, 70, 121, 44, 226, 46, 127, 85, 216, 121, 82, 217, 88, 27, 133, 238, 250, 215, 123, 11, 250, 159, 217, 70, 233, 164, 97, 1, 179, 215, 220, 188, 202, 203, 96, 248, 250, 53, 42, 106, 45, 8, 26, 188, 95, 113], features: 22619992289 };245;[46, 4c, 38, 0d, 1d, ac, c1, de, c1, bf, 62, 1a, 7c, bf, c7, 6e, f2, 10, 4e, 68, e8, 19, 4d, ad, 68, 21, e2, f6, 2b, a6, 0e, 24, c3, 82, 5f, 2e, 28, fa, 06, 1a, 32, ef, 19, b3, 69, 0b, fd, 08, 14, e0, de, c1, 02, 2d, 58, 26, 9a, 5a, ba, 40, a6, d4, 63, 56, 76, 61, 7b, 37, 0e, 12, 7e, ea, 9b, 7b, d1, 1b, af, b1, d0, be, 09, 3c, 30, 4c, f0, 62, 4a, 78, bc, 10, 15, 02, 7e, 08, f8, 1d, 7e, 92, 0d, 99, 59, 9b, fc, 42, 5c, 4b, 20, a6, ea, 28, 9c, 07, d7, 70, d6, 05, 82, c8, 94, 90, 01, 87, 4e, da, b6, 08, a4, 55, f8, e4, 14, 40, 69, bc, 79, 98, 5e, 19, 7f, b4, 03, d5, a4, 34, cb, 9b, be, 0e, a8, 9e, da, 27, b1, 87, 92, 4c, 4f, cb, eb, 1d, 53, ce, 28, 28, 16, d9, ea, 25, 3c, 7c, c1, a1, 77, 34, 27, 36, ca, 7e, 05, 73, 98, 23, 46, 79, 2c, e2, 2e, 7f, 55, d8, 79, 52, d9, 58, 1b, 85, ee, fa, d7, 7b, 0b, fa, 9f, d9, 46, e9, a4, 61, 01, b3, d7, dc, bc, ca, cb, 60, f8, fa, 35, 2a, 6a, 2d, 08, 1a, bc, 5f, 71, c0, 00, 00, 05, 44, 41, b0, e1, 00] +auth_req 8000c0cc07b426537bd78bc341012196d3bf011bdd05c70b7b420f8b2ced0ea18fdf8720d77dd9e196402b019729d4df270f3b9c6231de8c542d92da8d2eb191ea683197c91a170b73439370ba0b38a868d19841dc8e937cb059a98648ed4cfdd12cf3dbeb1253009f2545bf8cdd754e65d2d2afcb74de1cc7f03f03a39a645000717d003b7c2bf7bd3bebaeab687b201c22a0a5bf75db58f33dbe72d0360fcd0b54684126e40186b3fe8e10bb587443a6a255410d471a146b016c8bf3665322123996a29dc6a4afa8dcc09f8e9118a40d6ddc45ead0f3fc98375dbb9bbe1a8595222fc555e30dd9a4ab66f4cd23f79e488418afe4400a8ac2234c848775b42e30aa0b1c1c2ebf185fd53c37bacfad65624000 reject +auth_req 5277400fe857a49cd8f211f8f2532f6f0945af800000f73254418cc49db357abdcb0ccaf2c7c91ba246168c898214506fb39fd11e8074497da3a46783796810a13ac551ef430b7e20df62cde129bd251cc8b439981fa3ff9b7e271747d80e578025ae07d4961940aaf1094c07c24a8770a836b9f607c019b86035ddaa509fd3d129abb333dc910ffd120a6e41df043b9780deaa95f9442e03bf989f46a5ac81ef1279d6efafae6aacbe91703c9ed25d8dec184ddb0164d2cd37f5f3c0ee9816a0471c41b771e04ba71ace06d48911d0c945fd28c8dea4f6630c311805c3bfa643db92e82aa8e24ff95c16006bb339a0bf5324173ab9003c8fff56501a3c7b5259fb4b3e775a6f201d45dc783ea6a8e4995cb00 AuthReq { version: 4727, client_id: [232, 87, 164, 156, 216, 242, 17, 248, 242, 83, 47, 111, 9, 69, 175], auth_token: [50, 84, 65, 140, 196, 157, 179, 87, 171, 220, 176, 204, 175, 44, 124, 145, 186, 36, 97, 104, 200, 152, 33, 69, 6, 251, 57, 253, 17, 232, 7, 68, 151, 218, 58, 70, 120, 55, 150, 129, 10, 19, 172, 85, 30, 244, 48, 183, 226, 13, 246, 44, 222, 18, 155, 210, 81, 204, 139, 67, 153, 129, 250, 63, 249, 183, 226, 113, 116, 125, 128, 229, 120, 2, 90, 224, 125, 73, 97, 148, 10, 175, 16, 148, 192, 124, 36, 168, 119, 10, 131, 107, 159, 96, 124, 1, 155, 134, 3, 93, 218, 165, 9, 253, 61, 18, 154, 187, 51, 61, 201, 16, 255, 209, 32, 166, 228, 29, 240, 67, 185, 120, 13, 234, 169, 95, 148, 66, 224, 59, 249, 137, 244, 106, 90, 200, 30, 241, 39, 157, 110, 250, 250, 230, 170, 203, 233, 23, 3, 201, 237, 37, 216, 222, 193, 132, 221, 176, 22, 77, 44, 211, 127, 95, 60, 14, 233, 129, 106, 4, 113, 196, 27, 119, 30, 4, 186, 113, 172, 224, 109, 72, 145, 29, 12, 148, 95, 210, 140, 141, 234, 79, 102, 48, 195, 17, 128, 92, 59, 250, 100, 61, 185, 46, 130, 170, 142, 36, 255, 149, 193, 96, 6, 187, 51, 154, 11, 245, 50, 65, 115, 171, 144, 3, 200, 255, 245, 101, 1, 163, 199, 181, 37, 159, 180, 179, 231, 117, 166, 242, 1, 212, 93, 199, 131, 234, 106], features: 239703499 };275;[52, 77, 0f, e8, 57, a4, 9c, d8, f2, 11, f8, f2, 53, 2f, 6f, 09, 45, af, 40, f7, 32, 54, 41, 8c, c4, 9d, b3, 57, ab, dc, b0, cc, af, 2c, 7c, 91, ba, 24, 61, 68, c8, 98, 21, 45, 06, fb, 39, fd, 11, e8, 07, 44, 97, da, 3a, 46, 78, 37, 96, 81, 0a, 13, ac, 55, 1e, f4, 30, b7, e2, 0d, f6, 2c, de, 12, 9b, d2, 51, cc, 8b, 43, 99, 81, fa, 3f, f9, b7, e2, 71, 74, 7d, 80, e5, 78, 02, 5a, e0, 7d, 49, 61, 94, 0a, af, 10, 94, c0, 7c, 24, a8, 77, 0a, 83, 6b, 9f, 60, 7c, 01, 9b, 86, 03, 5d, da, a5, 09, fd, 3d, 12, 9a, bb, 33, 3d, c9, 10, ff, d1, 20, a6, e4, 1d, f0, 43, b9, 78, 0d, ea, a9, 5f, 94, 42, e0, 3b, f9, 89, f4, 6a, 5a, c8, 1e, f1, 27, 9d, 6e, fa, fa, e6, aa, cb, e9, 17, 03, c9, ed, 25, d8, de, c1, 84, dd, b0, 16, 4d, 2c, d3, 7f, 5f, 3c, 0e, e9, 81, 6a, 04, 71, c4, 1b, 77, 1e, 04, ba, 71, ac, e0, 6d, 48, 91, 1d, 0c, 94, 5f, d2, 8c, 8d, ea, 4f, 66, 30, c3, 11, 80, 5c, 3b, fa, 64, 3d, b9, 2e, 82, aa, 8e, 24, ff, 95, c1, 60, 06, bb, 33, 9a, 0b, f5, 32, 41, 73, ab, 90, 03, c8, ff, f5, 65, 01, a3, c7, b5, 25, 9f, b4, b3, e7, 75, a6, f2, 01, d4, 5d, c7, 83, ea, 6a, 8e, 49, 95, cb, 00] +auth_req 8011b0e7c00000000000000cb206793ad645ae27b05a6b71406a905fd8878b7f98a3f08b956d825c0a0184590f3ba88b72c36010ca7b22b995d526fa615a3c5e9fe9d819c235bf28ef971b2221ac5eb967f26a5cca835c743460594c4588a8768bdd1bec889d73fe5a4e4532afa2e5cf61bb732834f2e69793ea16c5e3edd2c76391664cc00f50b15c22763b4000 AuthReq { version: 1159399, client_id: [178, 6, 121, 58, 214, 69, 174, 39, 176, 90, 107, 113], auth_token: [144, 95, 216, 135, 139, 127, 152, 163, 240, 139, 149, 109, 130, 92, 10, 1, 132, 89, 15, 59, 168, 139, 114, 195, 96, 16, 202, 123, 34, 185, 149, 213, 38, 250, 97, 90, 60, 94, 159, 233, 216, 25, 194, 53, 191, 40, 239, 151, 27, 34, 33, 172, 94, 185, 103, 242, 106, 92, 202, 131, 92, 116, 52, 96, 89, 76, 69, 136, 168, 118, 139, 221, 27, 236, 136, 157, 115, 254, 90, 78, 69, 50, 175, 162, 229, 207, 97, 187, 115, 40, 52, 242, 230, 151, 147, 234, 22, 197, 227, 237, 210, 199, 99, 145, 102, 76], features: 4310847335855675 };142;[80, 11, b0, e7, 0c, b2, 06, 79, 3a, d6, 45, ae, 27, b0, 5a, 6b, 71, 40, 6a, 90, 5f, d8, 87, 8b, 7f, 98, a3, f0, 8b, 95, 6d, 82, 5c, 0a, 01, 84, 59, 0f, 3b, a8, 8b, 72, c3, 60, 10, ca, 7b, 22, b9, 95, d5, 26, fa, 61, 5a, 3c, 5e, 9f, e9, d8, 19, c2, 35, bf, 28, ef, 97, 1b, 22, 21, ac, 5e, b9, 67, f2, 6a, 5c, ca, 83, 5c, 74, 34, 60, 59, 4c, 45, 88, a8, 76, 8b, dd, 1b, ec, 88, 9d, 73, fe, 5a, 4e, 45, 32, af, a2, e5, cf, 61, bb, 73, 28, 34, f2, e6, 97, 93, ea, 16, c5, e3, ed, d2, c7, 63, 91, 66, 4c, c0, 0f, 50, b1, 5c, 22, 76, 3b, 00] +auth_req c0000058a4f0f886c00000000000003f9fd6deae7fd2ddbbbf2ec351e3d7fa3c10cc9407d46540010bff2a93dc327618dd5925bbef52f8768601826002cc9de59f9c19e0b77dd88a529581c84528f61530f3b94b3c25e55508038e1de19cba4c3d6093f3f4438ac000000000000000 AuthReq { version: 380724377734, client_id: [159, 214, 222, 174, 127, 210, 221, 187, 191, 46, 195, 81, 227, 215, 250, 60, 16, 204, 148, 7, 212, 101, 64, 1, 11, 255, 42, 147, 220, 50, 118, 24, 221, 89, 37, 187, 239, 82, 248, 118, 134, 1, 130, 96, 2, 204, 157, 229, 159, 156, 25, 224, 183, 125, 216, 138, 82, 149, 129, 200, 69, 40, 246], auth_token: [48, 243, 185, 75, 60, 37, 229, 85, 8, 3, 142, 29, 225, 156, 186, 76, 61, 96, 147, 243, 244], features: 906 };111;[c0, 00, 00, 58, a4, f0, f8, 86, 3f, 9f, d6, de, ae, 7f, d2, dd, bb, bf, 2e, c3, 51, e3, d7, fa, 3c, 10, cc, 94, 07, d4, 65, 40, 01, 0b, ff, 2a, 93, dc, 32, 76, 18, dd, 59, 25, bb, ef, 52, f8, 76, 86, 01, 82, 60, 02, cc, 9d, e5, 9f, 9c, 19, e0, b7, 7d, d8, 8a, 52, 95, 81, c8, 45, 28, f6, 15, 30, f3, b9, 4b, 3c, 25, e5, 55, 08, 03, 8e, 1d, e1, 9c, ba, 4c, 3d, 60, 93, f3, f4, 43, 8a, 00] +auth_req c000003b584f069040424a171c232d5ad1035af0bc8f277b37383bbcf9993d617aa6041e6d0f1268ef2d1455dd522f7abcfe3158681cd7347f3899bf2ac3deb08ef6159a8e91c50bfc200509800000d09ad12aeb3d1d314979e2bac4ed76a94318539c83a45f68ceb207497abb2e6da0e9fc648bf074523a86037fb01b996286840b6e9ab728724186caaf1075cbfca3319ac5a6a4d0ac0b135509108362e5d9e749deee730ef185abde5c957e77a3a0d93ccc65f530e5a51792bcb22b6ebca281832075059903f65a8cb9bcfa551505053e12755ea22fbaac33f73dece7e341bda5c929f507c5a33ded7fa2481e3bd541783b4b1e29566ad120e78f08c95a26a8e7e903936db52b9ed87639b588b0ce368c1fe04565fc5ce02699917765862800c000000000000000 reject +auth_req 800eacb6c00000000000001446ae4c9dfecae3e2e799075bc0c9bcc473ff5cdc40e3554d0509700351e34dead2d9e2d636ffa8357493fd937d019dfa7582228a4ff9f7d7673ad0b1517612e1c23aa2a2f701830b03cacd2f7ca2d4a5a5ce20a45db87dbd86800bbd62a0d2bc82f3ecd05ce7b5884db965ba15604e0b0bfd4f2ba99d5ac73f56295c37c92390400614d491f02bb1839359632d583881e23ef5804d53b34e5a0b9112aba0a8cf1fe2effab0989254455601a4b6d8defebb275c1b616458e2016fc8fb7f1268d47a098fa68841252daceda89092e167ac37475acbf2879d83aaacce33e11c1b701ef3f96b3269ccb88e6d3d25b15c5d19683a957c8649214030c0001a9445e8cb3c00 AuthReq { version: 961718, client_id: [70, 174, 76, 157, 254, 202, 227, 226, 231, 153, 7, 91, 192, 201, 188, 196, 115, 255, 92, 220], auth_token: [85, 77, 5, 9, 112, 3, 81, 227, 77, 234, 210, 217, 226, 214, 54, 255, 168, 53, 116, 147, 253, 147, 125, 1, 157, 250, 117, 130, 34, 138, 79, 249, 247, 215, 103, 58, 208, 177, 81, 118, 18, 225, 194, 58, 162, 162, 247, 1, 131, 11, 3, 202, 205, 47, 124, 162, 212, 165, 165, 206, 32, 164, 93, 184, 125, 189, 134, 128, 11, 189, 98, 160, 210, 188, 130, 243, 236, 208, 92, 231, 181, 136, 77, 185, 101, 186, 21, 96, 78, 11, 11, 253, 79, 43, 169, 157, 90, 199, 63, 86, 41, 92, 55, 201, 35, 144, 64, 6, 20, 212, 145, 240, 43, 177, 131, 147, 89, 99, 45, 88, 56, 129, 226, 62, 245, 128, 77, 83, 179, 78, 90, 11, 145, 18, 171, 160, 168, 207, 31, 226, 239, 250, 176, 152, 146, 84, 69, 86, 1, 164, 182, 216, 222, 254, 187, 39, 92, 27, 97, 100, 88, 226, 1, 111, 200, 251, 127, 18, 104, 212, 122, 9, 143, 166, 136, 65, 37, 45, 172, 237, 168, 144, 146, 225, 103, 172, 55, 71, 90, 203, 242, 135, 157, 131, 170, 172, 206, 51, 225, 28, 27, 112, 30, 243, 249, 107, 50, 105, 204, 184, 142, 109, 61, 37, 177, 92, 93, 25, 104, 58, 149, 124, 134, 73, 33, 64, 48], features: 29224130366268 };270;[80, 0e, ac, b6, 14, 46, ae, 4c, 9d, fe, ca, e3, e2, e7, 99, 07, 5b, c0, c9, bc, c4, 73, ff, 5c, dc, 40, e3, 55, 4d, 05, 09, 70, 03, 51, e3, 4d, ea, d2, d9, e2, d6, 36, ff, a8, 35, 74, 93, fd, 93, 7d, 01, 9d, fa, 75, 82, 22, 8a, 4f, f9, f7, d7, 67, 3a, d0, b1, 51, 76, 12, e1, c2, 3a, a2, a2, f7, 01, 83, 0b, 03, ca, cd, 2f, 7c, a2, d4, a5, a5, ce, 20, a4, 5d, b8, 7d, bd, 86, 80, 0b, bd, 62, a0, d2, bc, 82, f3, ec, d0, 5c, e7, b5, 88, 4d, b9, 65, ba, 15, 60, 4e, 0b, 0b, fd, 4f, 2b, a9, 9d, 5a, c7, 3f, 56, 29, 5c, 37, c9, 23, 90, 40, 06, 14, d4, 91, f0, 2b, b1, 83, 93, 59, 63, 2d, 58, 38, 81, e2, 3e, f5, 80, 4d, 53, b3, 4e, 5a, 0b, 91, 12, ab, a0, a8, cf, 1f, e2, ef, fa, b0, 98, 92, 54, 45, 56, 01, a4, b6, d8, de, fe, bb, 27, 5c, 1b, 61, 64, 58, e2, 01, 6f, c8, fb, 7f, 12, 68, d4, 7a, 09, 8f, a6, 88, 41, 25, 2d, ac, ed, a8, 90, 92, e1, 67, ac, 37, 47, 5a, cb, f2, 87, 9d, 83, aa, ac, ce, 33, e1, 1c, 1b, 70, 1e, f3, f9, 6b, 32, 69, cc, b8, 8e, 6d, 3d, 25, b1, 5c, 5d, 19, 68, 3a, 95, 7c, 86, 49, 21, 40, 30, c0, 00, 1a, 94, 45, e8, cb, 3c, 00] +auth_req 802eb20ec00000000000002d69937d2bb32b5b5470a861f6fb926efa99d7464eed0398e7907963e2e38a91f288d148ebde3d8cf335d5380338c0000000000000eb2b72058387ac2ce1eb64d62e4113e9cb3225109993b59204d33b2817c0131c96a63d901dbd6e070b2fb9b1c5d39ea02f82e27cec97a072c93009243d1ce00f5798f831b2e9b3d54380226fecf80ecb72cc1fb3f2e4ed8af1b6ac9d01ff3f5472ab22d271d45727179e3c329530f75d85f85d0c51bdf3055d92f37752e47865c38d55ccbae59331b3b2f9014956645e8f6c867fba0f83e27f0ad01de68119ac13d6615f77b6e2c10b944b091fe315bde697db3a1ddc3a982af26eabe11ba980c249fdd6485c70a0d761c1a631359a5852d06e6708e7ac6dcfc00fbde58866c6ddfacb08f6544bb3a762eca8c00000001f7b1be2c000000000000000 AuthReq { version: 3060238, client_id: [105, 147, 125, 43, 179, 43, 91, 84, 112, 168, 97, 246, 251, 146, 110, 250, 153, 215, 70, 78, 237, 3, 152, 231, 144, 121, 99, 226, 227, 138, 145, 242, 136, 209, 72, 235, 222, 61, 140, 243, 53, 213, 56, 3, 56], auth_token: [43, 114, 5, 131, 135, 172, 44, 225, 235, 100, 214, 46, 65, 19, 233, 203, 50, 37, 16, 153, 147, 181, 146, 4, 211, 59, 40, 23, 192, 19, 28, 150, 166, 61, 144, 29, 189, 110, 7, 11, 47, 185, 177, 197, 211, 158, 160, 47, 130, 226, 124, 236, 151, 160, 114, 201, 48, 9, 36, 61, 28, 224, 15, 87, 152, 248, 49, 178, 233, 179, 213, 67, 128, 34, 111, 236, 248, 14, 203, 114, 204, 31, 179, 242, 228, 237, 138, 241, 182, 172, 157, 1, 255, 63, 84, 114, 171, 34, 210, 113, 212, 87, 39, 23, 158, 60, 50, 149, 48, 247, 93, 133, 248, 93, 12, 81, 189, 243, 5, 93, 146, 243, 119, 82, 228, 120, 101, 195, 141, 85, 204, 186, 229, 147, 49, 179, 178, 249, 1, 73, 86, 100, 94, 143, 108, 134, 127, 186, 15, 131, 226, 127, 10, 208, 29, 230, 129, 25, 172, 19, 214, 97, 95, 119, 182, 226, 193, 11, 148, 75, 9, 31, 227, 21, 189, 230, 151, 219, 58, 29, 220, 58, 152, 42, 242, 110, 171, 225, 27, 169, 128, 194, 73, 253, 214, 72, 92, 112, 160, 215, 97, 193, 166, 49, 53, 154, 88, 82, 208, 110, 103, 8, 231, 172, 109, 207, 192, 15, 189, 229, 136, 102, 198, 221, 250, 203, 8, 246, 84, 75, 179, 167, 98, 236, 168], features: 528161762 };316;[80, 2e, b2, 0e, 2d, 69, 93, 7d, 2b, b3, 2b, 5b, 54, 70, a8, 61, f6, fb, 92, 6e, fa, 99, d7, 46, 4e, ed, 03, 98, e7, 90, 79, 63, e2, e3, 8a, 91, f2, 88, d1, 48, eb, de, 3d, 8c, f3, 35, d5, 38, 03, 38, 40, eb, 2b, 72, 05, 83, 87, ac, 2c, e1, eb, 64, d6, 2e, 41, 13, e9, cb, 32, 25, 10, 99, 93, b5, 92, 04, d3, 3b, 28, 17, c0, 13, 1c, 96, a6, 3d, 90, 1d, bd, 6e, 07, 0b, 2f, b9, b1, c5, d3, 9e, a0, 2f, 82, e2, 7c, ec, 97, a0, 72, c9, 30, 09, 24, 3d, 1c, e0, 0f, 57, 98, f8, 31, b2, e9, b3, d5, 43, 80, 22, 6f, ec, f8, 0e, cb, 72, cc, 1f, b3, f2, e4, ed, 8a, f1, b6, ac, 9d, 01, ff, 3f, 54, 72, ab, 22, d2, 71, d4, 57, 27, 17, 9e, 3c, 32, 95, 30, f7, 5d, 85, f8, 5d, 0c, 51, bd, f3, 05, 5d, 92, f3, 77, 52, e4, 78, 65, c3, 8d, 55, cc, ba, e5, 93, 31, b3, b2, f9, 01, 49, 56, 64, 5e, 8f, 6c, 86, 7f, ba, 0f, 83, e2, 7f, 0a, d0, 1d, e6, 81, 19, ac, 13, d6, 61, 5f, 77, b6, e2, c1, 0b, 94, 4b, 09, 1f, e3, 15, bd, e6, 97, db, 3a, 1d, dc, 3a, 98, 2a, f2, 6e, ab, e1, 1b, a9, 80, c2, 49, fd, d6, 48, 5c, 70, a0, d7, 61, c1, a6, 31, 35, 9a, 58, 52, d0, 6e, 67, 08, e7, ac, 6d, cf, c0, 0f, bd, e5, 88, 66, c6, dd, fa, cb, 08, f6, 54, 4b, b3, a7, 62, ec, a8, 9f, 7b, 1b, e2, 00] +auth_req 800000018000000d61733b6feeb0c52c82b957bd61c00000000000007e51c545ccc903ac1f9db4cc11bd3bd951e33871018694942b20b4d64b61ae408125ec58f76c7ebdd99148b0af5e54b5dcb59f8c39e44affa2c325cd31ba48b2df12021c807ccf8a4b832cb60f9223bbc5a2086a0b940ea2283ba6192985f78b2d51f4700467bcb2ceee92b38db99c1afb75d2824882d382484cd944f303d743d04000d2 AuthReq { version: 1, client_id: [97, 115, 59, 111, 238, 176, 197, 44, 130, 185, 87, 189, 97], auth_token: [81, 197, 69, 204, 201, 3, 172, 31, 157, 180, 204, 17, 189, 59, 217, 81, 227, 56, 113, 1, 134, 148, 148, 43, 32, 180, 214, 75, 97, 174, 64, 129, 37, 236, 88, 247, 108, 126, 189, 217, 145, 72, 176, 175, 94, 84, 181, 220, 181, 159, 140, 57, 228, 74, 255, 162, 195, 37, 205, 49, 186, 72, 178, 223, 18, 2, 28, 128, 124, 207, 138, 75, 131, 44, 182, 15, 146, 35, 187, 197, 162, 8, 106, 11, 148, 14, 162, 40, 59, 166, 25, 41, 133, 247, 139, 45, 81, 244, 112, 4, 103, 188, 178, 206, 238, 146, 179, 141, 185, 156, 26, 251, 117, 210, 130, 72, 130, 211, 130, 72, 76, 217, 68, 243, 3, 215], features: 976 };159;[01, 0d, 61, 73, 3b, 6f, ee, b0, c5, 2c, 82, b9, 57, bd, 61, 40, 7e, 51, c5, 45, cc, c9, 03, ac, 1f, 9d, b4, cc, 11, bd, 3b, d9, 51, e3, 38, 71, 01, 86, 94, 94, 2b, 20, b4, d6, 4b, 61, ae, 40, 81, 25, ec, 58, f7, 6c, 7e, bd, d9, 91, 48, b0, af, 5e, 54, b5, dc, b5, 9f, 8c, 39, e4, 4a, ff, a2, c3, 25, cd, 31, ba, 48, b2, df, 12, 02, 1c, 80, 7c, cf, 8a, 4b, 83, 2c, b6, 0f, 92, 23, bb, c5, a2, 08, 6a, 0b, 94, 0e, a2, 28, 3b, a6, 19, 29, 85, f7, 8b, 2d, 51, f4, 70, 04, 67, bc, b2, ce, ee, 92, b3, 8d, b9, 9c, 1a, fb, 75, d2, 82, 48, 82, d3, 82, 48, 4c, d9, 44, f3, 03, d7, 43, d0, 00] +auth_req c0000000000000054041c5184be56112a9186d5a41d1b5cfb9a6fcbe9e9c0c5d8e45d105e91d301a37fd17edd137fe65352c92a25861a868f6b46d16a184cc2cd4e979fccd5fd398128bf44098733767e2997651478e84ac4cbdcf6952d0403c89983d5953f6444bc665a89977fc57293a9c12b5b02541a27116cb592ee1ae1518c97c13cc96183e8638426aecaafb888167ba18c7bc2decc563089b477c54c08d6c4202ab60b0c3ecca96dc8a39a40464874ad40cd192da81eb27aaf0969005014e0a180218a491d89782a165eb3b8e616f6817c135987a6c31d3d1679cc10965c5e856b5c00000361da1f2234000 reject +auth_req 8000562b8000000deae6158e02f56ae3b4d79edcd8404c0e80044ca3d40d2e01b96c7ad4f2b0de06ba15ce62d81f5770d4e0707badcb9d991653103dfbb3579b6dff14f108b6c9054b5aa387ab860ad80cea4a85162539af905b7407b937d4d2e69705c0030a650e8f52774000 AuthReq { version: 22059, client_id: [234, 230, 21, 142, 2, 245, 106, 227, 180, 215, 158, 220, 216], auth_token: [14, 128, 4, 76, 163, 212, 13, 46, 1, 185, 108, 122, 212, 242, 176, 222, 6, 186, 21, 206, 98, 216, 31, 87, 112, 212, 224, 112, 123, 173, 203, 157, 153, 22, 83, 16, 61, 251, 179, 87, 155, 109, 255, 20, 241, 8, 182, 201, 5, 75, 90, 163, 135, 171, 134, 10, 216, 12, 234, 74, 133, 22, 37, 57, 175, 144, 91, 116, 7, 185, 55, 212, 210, 230, 151, 5], features: 855854082380407 };109;[80, 00, 56, 2b, 0d, ea, e6, 15, 8e, 02, f5, 6a, e3, b4, d7, 9e, dc, d8, 40, 4c, 0e, 80, 04, 4c, a3, d4, 0d, 2e, 01, b9, 6c, 7a, d4, f2, b0, de, 06, ba, 15, ce, 62, d8, 1f, 57, 70, d4, e0, 70, 7b, ad, cb, 9d, 99, 16, 53, 10, 3d, fb, b3, 57, 9b, 6d, ff, 14, f1, 08, b6, c9, 05, 4b, 5a, a3, 87, ab, 86, 0a, d8, 0c, ea, 4a, 85, 16, 25, 39, af, 90, 5b, 74, 07, b9, 37, d4, d2, e6, 97, 05, c0, 03, 0a, 65, 0e, 8f, 52, 77, 00] +auth_req 8003b9df8000002b9b23111efbff538f2d95a22d136d575448ea94fa155369f5dea727f3de473a4bd422669f57bd10efe9bc92c000000000000045d8295f75384c911d3a02d4bc1c3cd77e83359ce8404946e8a2d044222c077c626612f1f099f7f6884c50d89fba3bdaa2bc12ce190cd2773c09d73c58df8eccecccafc53e76c071b0c295b9f0e0c000000000000005e7ae376ce2 AuthReq { version: 244191, client_id: [155, 35, 17, 30, 251, 255, 83, 143, 45, 149, 162, 45, 19, 109, 87, 84, 72, 234, 148, 250, 21, 83, 105, 245, 222, 167, 39, 243, 222, 71, 58, 75, 212, 34, 102, 159, 87, 189, 16, 239, 233, 188, 146], auth_token: [216, 41, 95, 117, 56, 76, 145, 29, 58, 2, 212, 188, 28, 60, 215, 126, 131, 53, 156, 232, 64, 73, 70, 232, 162, 208, 68, 34, 44, 7, 124, 98, 102, 18, 241, 240, 153, 247, 246, 136, 76, 80, 216, 159, 186, 59, 218, 162, 188, 18, 206, 25, 12, 210, 119, 60, 9, 215, 60, 88, 223, 142, 204, 236, 204, 175, 197, 62, 118], features: 32001022150439136 };149;[80, 03, b9, df, 2b, 9b, 23, 11, 1e, fb, ff, 53, 8f, 2d, 95, a2, 2d, 13, 6d, 57, 54, 48, ea, 94, fa, 15, 53, 69, f5, de, a7, 27, f3, de, 47, 3a, 4b, d4, 22, 66, 9f, 57, bd, 10, ef, e9, bc, 92, 40, 45, d8, 29, 5f, 75, 38, 4c, 91, 1d, 3a, 02, d4, bc, 1c, 3c, d7, 7e, 83, 35, 9c, e8, 40, 49, 46, e8, a2, d0, 44, 22, 2c, 07, 7c, 62, 66, 12, f1, f0, 99, f7, f6, 88, 4c, 50, d8, 9f, ba, 3b, da, a2, bc, 12, ce, 19, 0c, d2, 77, 3c, 09, d7, 3c, 58, df, 8e, cc, ec, cc, af, c5, 3e, 76, c0, 71, b0, c2, 95, b9, f0, e0, 00] +auth_req 8019e4d24021b381ae60a07f414c0aaeff79f7ea740d121c7d225e9ceae232bf30ed74235aca608000007e67e9947dfa3a7c3dff8bce72f328b23fe3a1d4f1d59afcb5e48f646c2e4acc2ce533f3612cdc6bd4fa20d860f2dc8e9f3eb9254bebf524352165eaaa282f073bfd66316defd9699d4563888108ce7d92ad7adfb9397134fbe88d0681cfe7172163a9e3784c7eeeaab38631620c9a2a6e26d8d2418f8fd2e1ea3ab9216b32c3e3d881917a5c994000 AuthReq { version: 1696978, client_id: [179, 129, 174, 96, 160, 127, 65, 76, 10, 174, 255, 121, 247, 234, 116, 13, 18, 28, 125, 34, 94, 156, 234, 226, 50, 191, 48, 237, 116, 35, 90, 202, 96], auth_token: [103, 233, 148, 125, 250, 58, 124, 61, 255, 139, 206, 114, 243, 40, 178, 63, 227, 161, 212, 241, 213, 154, 252, 181, 228, 143, 100, 108, 46, 74, 204, 44, 229, 51, 243, 97, 44, 220, 107, 212, 250, 32, 216, 96, 242, 220, 142, 159, 62, 185, 37, 75, 235, 245, 36, 53, 33, 101, 234, 170, 40, 47, 7, 59, 253, 102, 49, 109, 239, 217, 105, 157, 69, 99, 136, 129, 8, 206, 125, 146, 173, 122, 223, 185, 57, 113, 52, 251, 232, 141, 6, 129, 207, 231, 23, 33, 99, 169, 227, 120, 76, 126, 238, 170, 179, 134, 49, 98, 12, 154, 42, 110, 38, 216, 210, 65, 143, 143, 210, 225, 234, 58, 185, 33, 107, 50], features: 280305652830198937 };179;[80, 19, e4, d2, 21, b3, 81, ae, 60, a0, 7f, 41, 4c, 0a, ae, ff, 79, f7, ea, 74, 0d, 12, 1c, 7d, 22, 5e, 9c, ea, e2, 32, bf, 30, ed, 74, 23, 5a, ca, 60, 40, 7e, 67, e9, 94, 7d, fa, 3a, 7c, 3d, ff, 8b, ce, 72, f3, 28, b2, 3f, e3, a1, d4, f1, d5, 9a, fc, b5, e4, 8f, 64, 6c, 2e, 4a, cc, 2c, e5, 33, f3, 61, 2c, dc, 6b, d4, fa, 20, d8, 60, f2, dc, 8e, 9f, 3e, b9, 25, 4b, eb, f5, 24, 35, 21, 65, ea, aa, 28, 2f, 07, 3b, fd, 66, 31, 6d, ef, d9, 69, 9d, 45, 63, 88, 81, 08, ce, 7d, 92, ad, 7a, df, b9, 39, 71, 34, fb, e8, 8d, 06, 81, cf, e7, 17, 21, 63, a9, e3, 78, 4c, 7e, ee, aa, b3, 86, 31, 62, 0c, 9a, 2a, 6e, 26, d8, d2, 41, 8f, 8f, d2, e1, ea, 3a, b9, 21, 6b, 32, c3, e3, d8, 81, 91, 7a, 5c, 99, 00] +auth_req c00000000000000e1b72688abeeb713b0c3d62e1506775d05f0a0a8dbc1a7d8a0081ecb440a37d579f0e9234748c21edf367a25fab0ecb4dd631189044f735f5d156cd3d056742e8159430ea3def4cf444e938acb10ac429315ea15c385cdcbc729838315b0d0c6a39110afb7e7f0c51510740ecb156bd6636d483c4a135e863c42e3315db15485823fba1f79d7b60483b4d19a7079920e8b01715a161fde8d1b654534b2db45d1b1a25724bfe6b2e45d6104e41764e2cdb22337455b9b7051d3cc166204ac109542ccd365710d494f8f8c00000000000000cb342b1f0628508ddacb7e243 accept +auth_req 800005ac401ba20892c89f490fa4449a762a77acba4bd4a3511fe283f7f89962b1c0000000000000bfac460f292b7f77188ec0e6d7b09da8cf95f392fb181a35d142df536fb74937a9c1c926afac2eca5ce61bf660b2d1b17fc6ed4e463bcb594ee2fa62a00789ff3dd47067c79ced8d696da08e5e0b1eb8347de15b17d5d789608e8da0f18a73675b96b1a31699f054ad2e0c34fdc11e967ebaf626346e645eb330438647d9ff5b5821159a1c10ab38af39d1d63dc1272a62010683ec40d84aad357106db24012b5932e2491532f4b516e7349664facfa0ef62fc85825971100499e7dd0669f67303c0000000000000006e76db AuthReq { version: 1452, client_id: [162, 8, 146, 200, 159, 73, 15, 164, 68, 154, 118, 42, 119, 172, 186, 75, 212, 163, 81, 31, 226, 131, 247, 248, 153, 98, 177], auth_token: [172, 70, 15, 41, 43, 127, 119, 24, 142, 192, 230, 215, 176, 157, 168, 207, 149, 243, 146, 251, 24, 26, 53, 209, 66, 223, 83, 111, 183, 73, 55, 169, 193, 201, 38, 175, 172, 46, 202, 92, 230, 27, 246, 96, 178, 209, 177, 127, 198, 237, 78, 70, 59, 203, 89, 78, 226, 250, 98, 160, 7, 137, 255, 61, 212, 112, 103, 199, 156, 237, 141, 105, 109, 160, 142, 94, 11, 30, 184, 52, 125, 225, 91, 23, 213, 215, 137, 96, 142, 141, 160, 241, 138, 115, 103, 91, 150, 177, 163, 22, 153, 240, 84, 173, 46, 12, 52, 253, 193, 30, 150, 126, 186, 246, 38, 52, 110, 100, 94, 179, 48, 67, 134, 71, 217, 255, 91, 88, 33, 21, 154, 28, 16, 171, 56, 175, 57, 209, 214, 61, 193, 39, 42, 98, 1, 6, 131, 236, 64, 216, 74, 173, 53, 113, 6, 219, 36, 1, 43, 89, 50, 226, 73, 21, 50, 244, 181, 22, 231, 52, 150, 100, 250, 207, 160, 239, 98, 252, 133, 130, 89, 113, 16, 4, 153, 231, 221, 6, 105, 246, 115], features: 3 };241;[45, ac, 1b, a2, 08, 92, c8, 9f, 49, 0f, a4, 44, 9a, 76, 2a, 77, ac, ba, 4b, d4, a3, 51, 1f, e2, 83, f7, f8, 99, 62, b1, 40, bf, ac, 46, 0f, 29, 2b, 7f, 77, 18, 8e, c0, e6, d7, b0, 9d, a8, cf, 95, f3, 92, fb, 18, 1a, 35, d1, 42, df, 53, 6f, b7, 49, 37, a9, c1, c9, 26, af, ac, 2e, ca, 5c, e6, 1b, f6, 60, b2, d1, b1, 7f, c6, ed, 4e, 46, 3b, cb, 59, 4e, e2, fa, 62, a0, 07, 89, ff, 3d, d4, 70, 67, c7, 9c, ed, 8d, 69, 6d, a0, 8e, 5e, 0b, 1e, b8, 34, 7d, e1, 5b, 17, d5, d7, 89, 60, 8e, 8d, a0, f1, 8a, 73, 67, 5b, 96, b1, a3, 16, 99, f0, 54, ad, 2e, 0c, 34, fd, c1, 1e, 96, 7e, ba, f6, 26, 34, 6e, 64, 5e, b3, 30, 43, 86, 47, d9, ff, 5b, 58, 21, 15, 9a, 1c, 10, ab, 38, af, 39, d1, d6, 3d, c1, 27, 2a, 62, 01, 06, 83, ec, 40, d8, 4a, ad, 35, 71, 06, db, 24, 01, 2b, 59, 32, e2, 49, 15, 32, f4, b5, 16, e7, 34, 96, 64, fa, cf, a0, ef, 62, fc, 85, 82, 59, 71, 10, 04, 99, e7, dd, 06, 69, f6, 73, 03, 00] +auth_req 8001936cc000000000000005e2f61f3869c000000000000070138d3801c72848426f381ec527a84631625492f25f087cb8d5628e843382aa225c94978540938fa8fe3da53c097bd185d25fdc57e3b973abe24e526b2e23003f1a0f4b11e449c576ad9acd1dc2f9931fedb33afbd50d690bc78991f87713559a71ce19349754a4bd144cefc3f6ba810bc217b73ce8cb565500 accept +auth_req c0000000001df0b5048eddeb0540bb317343711b0281e517773beabec732e9654fde62fa6559aa7ebf9504e06520568f872e46a2261d91cd6cae15ad4b8049935996c2ffbc4115a0fa0df1e5cfcb1f1c130a134ca4bf59b393a6b87124297f4c67946d5adb09a4bd39d4b8d4214521ba3bbfe86c0d8737892dbcffb88d8108993bc887780ab4f11bddabc5a9e0438a2129768d06c05650c92364e3785da37274b61b7b6c814ed5c6fc94241fd69152e6c23e07dc8d4e1f1f6504119111397eb51fb944cdfa0ccf84b4dfc0000afb960c4c458000000d4786ca6eb1a6ebbc1473c18a72 AuthReq { version: 1962165, client_id: [142, 221, 235, 5], auth_token: [49, 115, 67, 113, 27, 2, 129, 229, 23, 119, 59, 234, 190, 199, 50, 233, 101, 79, 222, 98, 250, 101, 89, 170, 126, 191, 149, 4, 224, 101, 32, 86, 143, 135, 46, 70, 162, 38, 29, 145, 205, 108, 174, 21, 173, 75, 128, 73, 147, 89, 150, 194, 255, 188, 65, 21, 160, 250, 13, 241, 229, 207, 203, 31, 28, 19, 10, 19, 76, 164, 191, 89, 179, 147, 166, 184, 113, 36, 41, 127, 76, 103, 148, 109, 90, 219, 9, 164, 189, 57, 212, 184, 212, 33, 69, 33, 186, 59, 191, 232, 108, 13, 135, 55, 137, 45, 188, 255, 184, 141, 129, 8, 153, 59, 200, 135, 120, 10, 180, 241, 27, 221, 171, 197, 169, 224, 67, 138, 33, 41, 118, 141, 6, 192, 86, 80, 201, 35, 100, 227, 120, 93, 163, 114, 116, 182, 27, 123, 108, 129, 78, 213, 198, 252, 148, 36, 31, 214, 145, 82, 230, 194, 62, 7, 220, 141, 78, 31, 31, 101, 4, 17, 145, 17, 57, 126, 181, 31, 185, 68, 205, 250, 12, 207, 132, 180, 223], features: 12075670457413 };227;[80, 1d, f0, b5, 04, 8e, dd, eb, 05, 40, bb, 31, 73, 43, 71, 1b, 02, 81, e5, 17, 77, 3b, ea, be, c7, 32, e9, 65, 4f, de, 62, fa, 65, 59, aa, 7e, bf, 95, 04, e0, 65, 20, 56, 8f, 87, 2e, 46, a2, 26, 1d, 91, cd, 6c, ae, 15, ad, 4b, 80, 49, 93, 59, 96, c2, ff, bc, 41, 15, a0, fa, 0d, f1, e5, cf, cb, 1f, 1c, 13, 0a, 13, 4c, a4, bf, 59, b3, 93, a6, b8, 71, 24, 29, 7f, 4c, 67, 94, 6d, 5a, db, 09, a4, bd, 39, d4, b8, d4, 21, 45, 21, ba, 3b, bf, e8, 6c, 0d, 87, 37, 89, 2d, bc, ff, b8, 8d, 81, 08, 99, 3b, c8, 87, 78, 0a, b4, f1, 1b, dd, ab, c5, a9, e0, 43, 8a, 21, 29, 76, 8d, 06, c0, 56, 50, c9, 23, 64, e3, 78, 5d, a3, 72, 74, b6, 1b, 7b, 6c, 81, 4e, d5, c6, fc, 94, 24, 1f, d6, 91, 52, e6, c2, 3e, 07, dc, 8d, 4e, 1f, 1f, 65, 04, 11, 91, 11, 39, 7e, b5, 1f, b9, 44, cd, fa, 0c, cf, 84, b4, df, c0, 00, 0a, fb, 96, 0c, 4c, 45, 00] +auth_req c0000000f6b4329e4043f53408114c80c1502b3862c34b5d2c298124b7dcb067024937c4a9130e2d452d60c033a07e28fbae57eb8e0d456117fd0b2ae774eb812518d426fda60e1eb7e632187c40486cd5a818e3371155685bb8634ad6e630d26a9a41629dfbe3d2afcbab5943458e352dcb25d4e7445c169aa11dfe29e03a6b6bfa8e817d6d6b5e03e061701a30a81f1acc3b233ea1a9c12ab127a0f6fa3b400017 reject +auth_req 801650954027d031429c7ea5665aff199e60caaf03872f244f3d5395e5f5eaa6b63d7e0182d78b667f214bc6fec000000000000096608c4386be981cd91458c711623927dc9c162830d48cd962dfccca018108f357f724a7d7974456626227c038ae83fea7574e53aceb48e56a0af74f4811828b83947e1c1973cc9765e01c1eb4f2a69ff5c418e89bb77106ad7bc912015bbc7f413ae0c5a96334bff8dd1ee051d4fff706b151f50af4112da2cc12b51f7373f88a10f0a2e670d5cef983e103a7294dce03a439f3688a80c000fbb59b5c844d80000008edb727aee22e048d03876b AuthReq { version: 1462421, client_id: [208, 49, 66, 156, 126, 165, 102, 90, 255, 25, 158, 96, 202, 175, 3, 135, 47, 36, 79, 61, 83, 149, 229, 245, 234, 166, 182, 61, 126, 1, 130, 215, 139, 102, 127, 33, 75, 198, 254], auth_token: [96, 140, 67, 134, 190, 152, 28, 217, 20, 88, 199, 17, 98, 57, 39, 220, 156, 22, 40, 48, 212, 140, 217, 98, 223, 204, 202, 1, 129, 8, 243, 87, 247, 36, 167, 215, 151, 68, 86, 98, 98, 39, 192, 56, 174, 131, 254, 167, 87, 78, 83, 172, 235, 72, 229, 106, 10, 247, 79, 72, 17, 130, 139, 131, 148, 126, 28, 25, 115, 204, 151, 101, 224, 28, 30, 180, 242, 166, 159, 245, 196, 24, 232, 155, 183, 113, 6, 173, 123, 201, 18, 1, 91, 188, 127, 65, 58, 224, 197, 169, 99, 52, 191, 248, 221, 30, 224, 81, 212, 255, 247, 6, 177, 81, 245, 10, 244, 17, 45, 162, 204, 18, 181, 31, 115, 115, 248, 138, 16, 240, 162, 230, 112, 213, 206, 249, 131, 225, 3, 167, 41, 77, 206, 3, 164, 57, 243, 104, 138, 128], features: 276757414184013 };223;[80, 16, 50, 95, 27, d0, 31, 42, 9c, 7e, a5, 66, 5a, ff, 19, 9e, 60, ca, af, 03, 87, 2f, 24, 4f, 3d, 53, 95, e5, f5, ea, a6, b6, 3d, 7e, 01, 82, d7, 8b, 66, 7f, 21, 4b, c6, fe, 40, 96, 60, 8c, 43, 86, be, 98, 1c, d9, 14, 58, c7, 11, 62, 39, 27, dc, 9c, 16, 28, 30, d4, 8c, d9, 62, df, cc, ca, 01, 81, 08, f3, 57, f7, 24, a7, d7, 97, 44, 56, 62, 62, 27, c0, 38, ae, 83, fe, a7, 57, 4e, 53, ac, eb, 48, e5, 6a, 0a, f7, 4f, 48, 11, 82, 8b, 83, 94, 7e, 1c, 19, 73, cc, 97, 65, e0, 1c, 1e, b4, f2, a6, 9f, f5, c4, 18, e8, 9b, b7, 71, 06, ad, 7b, c9, 12, 01, 5b, bc, 7f, 41, 3a, e0, c5, a9, 63, 34, bf, f8, dd, 1e, e0, 51, d4, ff, f7, 06, b1, 51, f5, 0a, f4, 11, 2d, a2, cc, 12, b5, 1f, 73, 73, f8, 8a, 10, f0, a2, e6, 70, d5, ce, f9, 83, e1, 03, a7, 29, 4d, ce, 03, a4, 39, f3, 68, 8a, 80, c0, 00, fb, b5, 9b, 5c, 84, 4d, 00] +auth_req c1332e43338047c840412e90341bbe1e078bd95055117946ab4a4ea8f429f0d2658056a4485db0a3a8b03c12d7d6d477bcdaccd7de0d49aae853e76e380401b324f579947bb5aaece46f94c0000000000000101b5053db8bb295874b1308d22e18493b406c00 reject +auth_req c0000000000042598000000e835bf2bce3639c3daa3801b61d0640d1259659e79d577fa2f655b3c3da0aa2fea5fd526babe629d84ade7721de1244f34266480d4f489ebc1c2af3c7c13b947919a685615f3e3d2ed1981f67e9b724f4623b59027c86033f694f7423f1d4779b6d262f36614d95827daa8cc2014229cbd0b3f186f6acc65d75e375e4a791dd661a0969e94ccea889b757cd8d3acc6483a99ce0c34426227dbdd1ecb3fb5fffef67ba4d5bd7b1d18a4973f838028f8bccbee8014d739c30b7983eacc1f8154140e2560cc947c3691ac719e71753d48470bd3c7b4a02d5387d67da0fdccc8517d384c0139462e6dcb37b80000000 AuthReq { version: 16985, client_id: [131, 91, 242, 188, 227, 99, 156, 61, 170, 56, 1, 182, 29, 6], auth_token: [37, 150, 89, 231, 157, 87, 127, 162, 246, 85, 179, 195, 218, 10, 162, 254, 165, 253, 82, 107, 171, 230, 41, 216, 74, 222, 119, 33, 222, 18, 68, 243, 66, 102, 72, 13, 79, 72, 158, 188, 28, 42, 243, 199, 193, 59, 148, 121, 25, 166, 133, 97, 95, 62, 61, 46, 209, 152, 31, 103, 233, 183, 36, 244, 98, 59, 89, 2, 124, 134, 3, 63, 105, 79, 116, 35, 241, 212, 119, 155, 109, 38, 47, 54, 97, 77, 149, 130, 125, 170, 140, 194, 1, 66, 41, 203, 208, 179, 241, 134, 246, 172, 198, 93, 117, 227, 117, 228, 167, 145, 221, 102, 26, 9, 105, 233, 76, 206, 168, 137, 183, 87, 205, 141, 58, 204, 100, 131, 169, 156, 224, 195, 68, 38, 34, 125, 189, 209, 236, 179, 251, 95, 255, 239, 103, 186, 77, 91, 215, 177, 209, 138, 73, 115, 248, 56, 2, 143, 139, 204, 190, 232, 1, 77, 115, 156, 48, 183, 152, 62, 172, 193, 248, 21, 65, 64, 226, 86, 12, 201, 71, 195, 105, 26, 199, 25, 231, 23, 83, 212, 132, 112, 189, 60, 123, 74, 2, 213, 56, 125, 103, 218, 15, 220, 204, 133, 23, 211, 132], features: 5511177058431867 };249;[80, 00, 42, 59, 0e, 83, 5b, f2, bc, e3, 63, 9c, 3d, aa, 38, 01, b6, 1d, 06, 40, d1, 25, 96, 59, e7, 9d, 57, 7f, a2, f6, 55, b3, c3, da, 0a, a2, fe, a5, fd, 52, 6b, ab, e6, 29, d8, 4a, de, 77, 21, de, 12, 44, f3, 42, 66, 48, 0d, 4f, 48, 9e, bc, 1c, 2a, f3, c7, c1, 3b, 94, 79, 19, a6, 85, 61, 5f, 3e, 3d, 2e, d1, 98, 1f, 67, e9, b7, 24, f4, 62, 3b, 59, 02, 7c, 86, 03, 3f, 69, 4f, 74, 23, f1, d4, 77, 9b, 6d, 26, 2f, 36, 61, 4d, 95, 82, 7d, aa, 8c, c2, 01, 42, 29, cb, d0, b3, f1, 86, f6, ac, c6, 5d, 75, e3, 75, e4, a7, 91, dd, 66, 1a, 09, 69, e9, 4c, ce, a8, 89, b7, 57, cd, 8d, 3a, cc, 64, 83, a9, 9c, e0, c3, 44, 26, 22, 7d, bd, d1, ec, b3, fb, 5f, ff, ef, 67, ba, 4d, 5b, d7, b1, d1, 8a, 49, 73, f8, 38, 02, 8f, 8b, cc, be, e8, 01, 4d, 73, 9c, 30, b7, 98, 3e, ac, c1, f8, 15, 41, 40, e2, 56, 0c, c9, 47, c3, 69, 1a, c7, 19, e7, 17, 53, d4, 84, 70, bd, 3c, 7b, 4a, 02, d5, 38, 7d, 67, da, 0f, dc, cc, 85, 17, d3, 84, c0, 13, 94, 62, e6, dc, b3, 7b, 00] +auth_req c0a837bec863e7608000002258fe2b8d6f1ca599d1bfb1ab3f345eabeb92136752259566fe6410d88fdca78ab1a740f913f6c6abadd457be5d1d562c57ed533fe7693030af7c4019698d325fe996f618ff831ae210fb4e1bc0a69fd9bf5c78cd787616c0ed060bf933aeb1b77a7c7e20607be44214c6317fe2df6732e42c7aa3726cd30883a4910ee25549a9aef52e966ad0f9f2664cc0b6a112f6193a6fd4a1e9e1b8c82f43a035e9b7720679d07d7cd41f05fc2bb9c3254a348c32abdc5f3ef7b5de4dd515a0eb6bc63aa4126cdf4fae0900b775ae26b08d8e689d2321bbe6d3d52e36f9692a9437abe6d06235e2037566b4f817efbcfe4394c543eba8e21e0ce8d6ae9142b47037fcfcfceda5267534e7764fce6089ae743a863fcdc36c3e189f6c0760753c241dc000000000000011800000082bde55737ce723ec2e3c accept +auth_req c00000005d88ba7411da43acdca61ed13b50e021e6a4de1b8cc440f1a25de88ebb1ae48cd081726d889a0c257b3e9ac3b8ae8742d7a7261825d0dae115e279ee5f212726d458a0701ecdd4879c4c84e8db12ac013638b0db2d5f016dc4b1d8e6053a4be7f62de99bb274c851210934667fdf1518e806a28485af429a0979065db79470129b7826e63ef066cfc08e9d5392c5b8e512045dfc65b8eb8d57828c18d7aac5ad17e2c2fabafd28d0b40fb8424fabe8310e010f5f9c63361699d329ee330d551882bad3a3a6c0752856e1503e66eb832813774605781b9d31a2417fb998dc3cbcf9e2e1cbb33f480a28216038f6d9c85f1c3a2c3784d53182146ddeaf354771a9126c58be357b8b8054c0000000000415b34000ee53387f8b189b AuthReq { version: 1569241716, client_id: [218, 67, 172, 220, 166, 30, 209, 59, 80, 224, 33, 230, 164, 222, 27, 140, 196], auth_token: [162, 93, 232, 142, 187, 26, 228, 140, 208, 129, 114, 109, 136, 154, 12, 37, 123, 62, 154, 195, 184, 174, 135, 66, 215, 167, 38, 24, 37, 208, 218, 225, 21, 226, 121, 238, 95, 33, 39, 38, 212, 88, 160, 112, 30, 205, 212, 135, 156, 76, 132, 232, 219, 18, 172, 1, 54, 56, 176, 219, 45, 95, 1, 109, 196, 177, 216, 230, 5, 58, 75, 231, 246, 45, 233, 155, 178, 116, 200, 81, 33, 9, 52, 102, 127, 223, 21, 24, 232, 6, 162, 132, 133, 175, 66, 154, 9, 121, 6, 93, 183, 148, 112, 18, 155, 120, 38, 230, 62, 240, 102, 207, 192, 142, 157, 83, 146, 197, 184, 229, 18, 4, 93, 252, 101, 184, 235, 141, 87, 130, 140, 24, 215, 170, 197, 173, 23, 226, 194, 250, 186, 253, 40, 208, 180, 15, 184, 66, 79, 171, 232, 49, 14, 1, 15, 95, 156, 99, 54, 22, 153, 211, 41, 238, 51, 13, 85, 24, 130, 186, 211, 163, 166, 192, 117, 40, 86, 225, 80, 62, 102, 235, 131, 40, 19, 119, 70, 5, 120, 27, 157, 49, 162, 65, 127, 185, 152, 220, 60, 188, 249, 226, 225, 203, 179, 63, 72, 10, 40, 33, 96, 56, 246, 217, 200, 95, 28, 58, 44, 55, 132, 213, 49, 130, 20, 109, 222, 175, 53, 71, 113, 169, 18, 108, 88, 190, 53, 123, 139, 128, 84], features: 267699 };279;[c0, 00, 00, 00, 5d, 88, ba, 74, 11, da, 43, ac, dc, a6, 1e, d1, 3b, 50, e0, 21, e6, a4, de, 1b, 8c, c4, 40, f1, a2, 5d, e8, 8e, bb, 1a, e4, 8c, d0, 81, 72, 6d, 88, 9a, 0c, 25, 7b, 3e, 9a, c3, b8, ae, 87, 42, d7, a7, 26, 18, 25, d0, da, e1, 15, e2, 79, ee, 5f, 21, 27, 26, d4, 58, a0, 70, 1e, cd, d4, 87, 9c, 4c, 84, e8, db, 12, ac, 01, 36, 38, b0, db, 2d, 5f, 01, 6d, c4, b1, d8, e6, 05, 3a, 4b, e7, f6, 2d, e9, 9b, b2, 74, c8, 51, 21, 09, 34, 66, 7f, df, 15, 18, e8, 06, a2, 84, 85, af, 42, 9a, 09, 79, 06, 5d, b7, 94, 70, 12, 9b, 78, 26, e6, 3e, f0, 66, cf, c0, 8e, 9d, 53, 92, c5, b8, e5, 12, 04, 5d, fc, 65, b8, eb, 8d, 57, 82, 8c, 18, d7, aa, c5, ad, 17, e2, c2, fa, ba, fd, 28, d0, b4, 0f, b8, 42, 4f, ab, e8, 31, 0e, 01, 0f, 5f, 9c, 63, 36, 16, 99, d3, 29, ee, 33, 0d, 55, 18, 82, ba, d3, a3, a6, c0, 75, 28, 56, e1, 50, 3e, 66, eb, 83, 28, 13, 77, 46, 05, 78, 1b, 9d, 31, a2, 41, 7f, b9, 98, dc, 3c, bc, f9, e2, e1, cb, b3, 3f, 48, 0a, 28, 21, 60, 38, f6, d9, c8, 5f, 1c, 3a, 2c, 37, 84, d5, 31, 82, 14, 6d, de, af, 35, 47, 71, a9, 12, 6c, 58, be, 35, 7b, 8b, 80, 54, 80, 04, 15, b3, 00] +auth_req c00000000000014b40159d6e76dabc6745df2df8b7a14af98f2956d78e913940fbb59720bc94c7740e2b0a17a279331d66ddea5d0b40d443bb014225aa644b7875dd1a96c2ba1617a3567191aba33a6138309819762668b8895f4eef478be8941383b223925283875cd47bec57cdad2fff20134d1027c3880256e92fcf95f2909850251cb90d53cd5d6ba9ce890460ab866fd3c2387630f0d51fa9089c7e9345da507c9a825a3d24c0a9544f9d3ef44637323331931e1912b776838d283268efb461dea2d78c9209054a48ed1d00aa0b9530ab827ec66e59d1e43eb693b04c0b01562e90fac427b3c58e692d1a37d8b8489f3749ddb5472123801221e6ca3e9114a307544a28e2cb1b3427f46ec4635f9b260267a1258df53629e48ac00000004bd398ba00 accept +auth_req ba7e363080000006937788aa32e5800000b12cebd6f79baa9083e32e9a05d2526046826b40f4acdce621fa3bb6b63d984aa7dd6d5f710747cca65bbce7a044a70faa0d559c56cc6407c2953ba1a44d29c0e575aee2857b749759c098aec715c7e9e91bc9a64f84b2ccbba344975972f93392764bedc08189e7f0c46eab3c621bf1d3d9d1e7353f64a9f80979393006f5be0f1964db0fef5c28eb168889da5bce13f4661a2e06e1a7b62a9c2a6910cb126ba617d2291053f7efcc958c359a227fb92fa11a00 AuthReq { version: 981349936, client_id: [147, 119, 136, 170, 50, 229], auth_token: [44, 235, 214, 247, 155, 170, 144, 131, 227, 46, 154, 5, 210, 82, 96, 70, 130, 107, 64, 244, 172, 220, 230, 33, 250, 59, 182, 182, 61, 152, 74, 167, 221, 109, 95, 113, 7, 71, 204, 166, 91, 188, 231, 160, 68, 167, 15, 170, 13, 85, 156, 86, 204, 100, 7, 194, 149, 59, 161, 164, 77, 41, 192, 229, 117, 174, 226, 133, 123, 116, 151, 89, 192, 152, 174, 199, 21, 199, 233, 233, 27, 201, 166, 79, 132, 178, 204, 187, 163, 68, 151, 89, 114, 249, 51, 146, 118, 75, 237, 192, 129, 137, 231, 240, 196, 110, 171, 60, 98, 27, 241, 211, 217, 209, 231, 53, 63, 100, 169, 248, 9, 121, 57, 48, 6, 245, 190, 15, 25, 100, 219, 15, 239, 92, 40, 235, 22, 136, 137, 218, 91, 206, 19, 244, 102, 26, 46, 6, 225, 167, 182, 42, 156, 42, 105, 16, 203, 18, 107, 166, 23, 210, 41, 16, 83, 247, 239, 204, 149, 140, 53, 154, 34, 127, 185, 47, 161], features: 26 };197;[ba, 7e, 36, 30, 06, 93, 77, 88, aa, 32, e5, 40, b1, 2c, eb, d6, f7, 9b, aa, 90, 83, e3, 2e, 9a, 05, d2, 52, 60, 46, 82, 6b, 40, f4, ac, dc, e6, 21, fa, 3b, b6, b6, 3d, 98, 4a, a7, dd, 6d, 5f, 71, 07, 47, cc, a6, 5b, bc, e7, a0, 44, a7, 0f, aa, 0d, 55, 9c, 56, cc, 64, 07, c2, 95, 3b, a1, a4, 4d, 29, c0, e5, 75, ae, e2, 85, 7b, 74, 97, 59, c0, 98, ae, c7, 15, c7, e9, e9, 1b, c9, a6, 4f, 84, b2, cc, bb, a3, 44, 97, 59, 72, f9, 33, 92, 76, 4b, ed, c0, 81, 89, e7, f0, c4, 6e, ab, 3c, 62, 1b, f1, d3, d9, d1, e7, 35, 3f, 64, a9, f8, 09, 79, 39, 30, 06, f5, be, 0f, 19, 64, db, 0f, ef, 5c, 28, eb, 16, 88, 89, da, 5b, ce, 13, f4, 66, 1a, 2e, 06, e1, a7, b6, 2a, 9c, 2a, 69, 10, cb, 12, 6b, a6, 17, d2, 29, 10, 53, f7, ef, cc, 95, 8c, 35, 9a, 22, 7f, b9, 2f, a1, 1a, 00] +auth_req c01fdcb9e46a970c80000033c352f40d0aca463b743d2f52dd0d0973a6e6d0c16efbe501af0bae45ee8f12e6fe20f4ef39602270b9eb19da9a2b6f6355882340477a1d366ace2dbb34cbe499719eb456b534d48841d5766e1c9b9102580f1d531858a625b85232f3563a60fa09d42d4a876a9eb050251c3d05d9708253df60e816a4bee4c168d6d9800002edc000000000000000 AuthReq { version: 8968415237281548, client_id: [195, 82, 244, 13, 10, 202, 70, 59, 116, 61, 47, 82, 221, 13, 9, 115, 166, 230, 208, 193, 110, 251, 229, 1, 175, 11, 174, 69, 238, 143, 18, 230, 254, 32, 244, 239, 57, 96, 34, 112, 185, 235, 25, 218, 154, 43, 111, 99, 85, 136, 35], auth_token: [122, 29, 54, 106, 206, 45, 187, 52, 203, 228, 153, 113, 158, 180, 86, 181, 52, 212, 136, 65, 213, 118, 110, 28, 155, 145, 2, 88, 15, 29, 83, 24, 88, 166, 37, 184, 82, 50, 243, 86, 58, 96, 250, 9, 212, 45, 74, 135, 106, 158, 176, 80, 37, 28, 61, 5, 217, 112, 130, 83, 223, 96, 232, 22, 164, 190, 228, 193, 104, 214, 217], features: 749 };148;[c0, 1f, dc, b9, e4, 6a, 97, 0c, 33, c3, 52, f4, 0d, 0a, ca, 46, 3b, 74, 3d, 2f, 52, dd, 0d, 09, 73, a6, e6, d0, c1, 6e, fb, e5, 01, af, 0b, ae, 45, ee, 8f, 12, e6, fe, 20, f4, ef, 39, 60, 22, 70, b9, eb, 19, da, 9a, 2b, 6f, 63, 55, 88, 23, 40, 47, 7a, 1d, 36, 6a, ce, 2d, bb, 34, cb, e4, 99, 71, 9e, b4, 56, b5, 34, d4, 88, 41, d5, 76, 6e, 1c, 9b, 91, 02, 58, 0f, 1d, 53, 18, 58, a6, 25, b8, 52, 32, f3, 56, 3a, 60, fa, 09, d4, 2d, 4a, 87, 6a, 9e, b0, 50, 25, 1c, 3d, 05, d9, 70, 82, 53, df, 60, e8, 16, a4, be, e4, c1, 68, d6, d9, 42, ed, 00] +auth_req c00000000125956ac00000000000003abadc39f6c985c0f67d97b18def765feaff3504b3dc0c3a209395a939d40cf461ca195ea216f9c29a3b41a11c176f44f72f499f6b45c6fb226525c00000000000008714a87f832df09ed107786bb197c00f466981dea2479c36a0742e742f6920c237745eb8ca20a4cbcaf4d99e5dea256dfcf2cbd83e8e536bf61bf393b10133479783d2856abae42bbdb1df122c5eaee257c88312cf6b8a4540163c30aafa87373298e53f5f119c2e66dd79249ce7d48c23215eb42bec7b17f72b230663e0232f09526d767218b08441e14000 AuthReq { version: 19240298, client_id: [186, 220, 57, 246, 201, 133, 192, 246, 125, 151, 177, 141, 239, 118, 95, 234, 255, 53, 4, 179, 220, 12, 58, 32, 147, 149, 169, 57, 212, 12, 244, 97, 202, 25, 94, 162, 22, 249, 194, 154, 59, 65, 161, 28, 23, 111, 68, 247, 47, 73, 159, 107, 69, 198, 251, 34, 101, 37], auth_token: [20, 168, 127, 131, 45, 240, 158, 209, 7, 120, 107, 177, 151, 192, 15, 70, 105, 129, 222, 162, 71, 156, 54, 160, 116, 46, 116, 47, 105, 32, 194, 55, 116, 94, 184, 202, 32, 164, 203, 202, 244, 217, 158, 93, 234, 37, 109, 252, 242, 203, 216, 62, 142, 83, 107, 246, 27, 243, 147, 177, 1, 51, 71, 151, 131, 210, 133, 106, 186, 228, 43, 189, 177, 223, 18, 44, 94, 174, 226, 87, 200, 131, 18, 207, 107, 138, 69, 64, 22, 60, 48, 170, 250, 135, 55, 50, 152, 229, 63, 95, 17, 156, 46, 102, 221, 121, 36, 156, 231, 212, 140, 35, 33, 94, 180, 43, 236, 123, 23, 247, 43, 35, 6, 99, 224, 35, 47, 9, 82, 109, 118, 114, 24, 176, 132], features: 481 };221;[81, 25, 95, 6a, 3a, ba, dc, 39, f6, c9, 85, c0, f6, 7d, 97, b1, 8d, ef, 76, 5f, ea, ff, 35, 04, b3, dc, 0c, 3a, 20, 93, 95, a9, 39, d4, 0c, f4, 61, ca, 19, 5e, a2, 16, f9, c2, 9a, 3b, 41, a1, 1c, 17, 6f, 44, f7, 2f, 49, 9f, 6b, 45, c6, fb, 22, 65, 25, 40, 87, 14, a8, 7f, 83, 2d, f0, 9e, d1, 07, 78, 6b, b1, 97, c0, 0f, 46, 69, 81, de, a2, 47, 9c, 36, a0, 74, 2e, 74, 2f, 69, 20, c2, 37, 74, 5e, b8, ca, 20, a4, cb, ca, f4, d9, 9e, 5d, ea, 25, 6d, fc, f2, cb, d8, 3e, 8e, 53, 6b, f6, 1b, f3, 93, b1, 01, 33, 47, 97, 83, d2, 85, 6a, ba, e4, 2b, bd, b1, df, 12, 2c, 5e, ae, e2, 57, c8, 83, 12, cf, 6b, 8a, 45, 40, 16, 3c, 30, aa, fa, 87, 37, 32, 98, e5, 3f, 5f, 11, 9c, 2e, 66, dd, 79, 24, 9c, e7, d4, 8c, 23, 21, 5e, b4, 2b, ec, 7b, 17, f7, 2b, 23, 06, 63, e0, 23, 2f, 09, 52, 6d, 76, 72, 18, b0, 84, 41, e1, 00] +auth_req c309f004dbad08cfc0000000000000385de2c92ac2e2e9337796429cea98d4273c6376953954477a1597ea0e18c86e8bf76835ce43afa5b1670ed9be0c39208be4b07f5a6b04cc4a1d810e0a59538c16709ce0e601d099cca8f8bc3ab22c194351323c030bedc0003d884a75ea96c000000000000000 AuthReq { version: 218969960560265423, client_id: [93, 226, 201, 42, 194, 226, 233, 51, 119, 150, 66, 156, 234, 152, 212, 39, 60, 99, 118, 149, 57, 84, 71, 122, 21, 151, 234, 14, 24, 200, 110, 139, 247, 104, 53, 206, 67, 175, 165, 177, 103, 14, 217, 190, 12, 57, 32, 139, 228, 176, 127, 90, 107, 4, 204, 74], auth_token: [129, 14, 10, 89, 83, 140, 22, 112, 156, 224, 230, 1, 208, 153, 204, 168, 248, 188, 58, 178, 44, 25, 67, 81, 50, 60, 3, 11, 237], features: 67655574088342 };118;[c3, 09, f0, 04, db, ad, 08, cf, 38, 5d, e2, c9, 2a, c2, e2, e9, 33, 77, 96, 42, 9c, ea, 98, d4, 27, 3c, 63, 76, 95, 39, 54, 47, 7a, 15, 97, ea, 0e, 18, c8, 6e, 8b, f7, 68, 35, ce, 43, af, a5, b1, 67, 0e, d9, be, 0c, 39, 20, 8b, e4, b0, 7f, 5a, 6b, 04, cc, 4a, 1d, 81, 0e, 0a, 59, 53, 8c, 16, 70, 9c, e0, e6, 01, d0, 99, cc, a8, f8, bc, 3a, b2, 2c, 19, 43, 51, 32, 3c, 03, 0b, ed, c0, 00, 3d, 88, 4a, 75, ea, 96, 00] +auth_req c052932226665cd02cc44c597c453c0e6925e1fa0723094a37cb38d34924e0e02e84ba729979507f3e98df3d8562ee3fa28ebd780940d5766b2d2a168c88a14ce4d557719a20fb7a84055c0c433377381c9e766f367a32bd2361dc237375497eee8f6a22ae9bd8ce5d3072e57e7a76b0c0902b8fc51bdb8945a7173fef367e118945db454f1a81e4eeecdd98ab472caccb05ef8c34093901134df8266edb50f84b01647be35d7c760fe55d5c136dcf0b0e190c4bbe7ab4419b4095d34b4ab31595ecb446f89e5a28b30fd89ceebff21187cb4b1269bf218ccc86049d47335ed1770e4ea4b8d167dfc326adb222fac178421432b0a9cdbe4ab3f5c630c1f11bc574e804e92088464a63471aa1c40673bc391d412f4000 AuthReq { version: 23242722972687568, client_id: [196, 76, 89, 124, 69, 60, 14, 105, 37, 225, 250, 7, 35, 9, 74, 55, 203, 56, 211, 73, 36, 224, 224, 46, 132, 186, 114, 153, 121, 80, 127, 62, 152, 223, 61, 133, 98, 238, 63, 162, 142, 189, 120, 9], auth_token: [118, 107, 45, 42, 22, 140, 136, 161, 76, 228, 213, 87, 113, 154, 32, 251, 122, 132, 5, 92, 12, 67, 51, 119, 56, 28, 158, 118, 111, 54, 122, 50, 189, 35, 97, 220, 35, 115, 117, 73, 126, 238, 143, 106, 34, 174, 155, 216, 206, 93, 48, 114, 229, 126, 122, 118, 176, 192, 144, 43, 143, 197, 27, 219, 137, 69, 167, 23, 63, 239, 54, 126, 17, 137, 69, 219, 69, 79, 26, 129, 228, 238, 236, 221, 152, 171, 71, 44, 172, 203, 5, 239, 140, 52, 9, 57, 1, 19, 77, 248, 38, 110, 219, 80, 248, 75, 1, 100, 123, 227, 93, 124, 118, 15, 229, 93, 92, 19, 109, 207, 11, 14, 25, 12, 75, 190, 122, 180, 65, 155, 64, 149, 211, 75, 74, 179, 21, 149, 236, 180, 70, 248, 158, 90, 40, 179, 15, 216, 156, 238, 191, 242, 17, 135, 203, 75, 18, 105, 191, 33, 140, 204, 134, 4, 157, 71, 51, 94, 209, 119, 14, 78, 164, 184, 209, 103, 223, 195, 38, 173, 178, 34, 250, 193, 120, 66, 20, 50, 176, 169, 205, 190, 74, 179, 245, 198, 48, 193, 241, 27, 197, 116, 232, 4, 233, 32, 136, 70, 74, 99, 71, 26, 161], features: 290046478261240111 };278;[c0, 52, 93, 22, 26, 66, 5c, d0, 2c, c4, 4c, 59, 7c, 45, 3c, 0e, 69, 25, e1, fa, 07, 23, 09, 4a, 37, cb, 38, d3, 49, 24, e0, e0, 2e, 84, ba, 72, 99, 79, 50, 7f, 3e, 98, df, 3d, 85, 62, ee, 3f, a2, 8e, bd, 78, 09, 40, d5, 76, 6b, 2d, 2a, 16, 8c, 88, a1, 4c, e4, d5, 57, 71, 9a, 20, fb, 7a, 84, 05, 5c, 0c, 43, 33, 77, 38, 1c, 9e, 76, 6f, 36, 7a, 32, bd, 23, 61, dc, 23, 73, 75, 49, 7e, ee, 8f, 6a, 22, ae, 9b, d8, ce, 5d, 30, 72, e5, 7e, 7a, 76, b0, c0, 90, 2b, 8f, c5, 1b, db, 89, 45, a7, 17, 3f, ef, 36, 7e, 11, 89, 45, db, 45, 4f, 1a, 81, e4, ee, ec, dd, 98, ab, 47, 2c, ac, cb, 05, ef, 8c, 34, 09, 39, 01, 13, 4d, f8, 26, 6e, db, 50, f8, 4b, 01, 64, 7b, e3, 5d, 7c, 76, 0f, e5, 5d, 5c, 13, 6d, cf, 0b, 0e, 19, 0c, 4b, be, 7a, b4, 41, 9b, 40, 95, d3, 4b, 4a, b3, 15, 95, ec, b4, 46, f8, 9e, 5a, 28, b3, 0f, d8, 9c, ee, bf, f2, 11, 87, cb, 4b, 12, 69, bf, 21, 8c, cc, 86, 04, 9d, 47, 33, 5e, d1, 77, 0e, 4e, a4, b8, d1, 67, df, c3, 26, ad, b2, 22, fa, c1, 78, 42, 14, 32, b0, a9, cd, be, 4a, b3, f5, c6, 30, c1, f1, 1b, c5, 74, e8, 04, e9, 20, 88, 46, 4a, 63, 47, 1a, a1, c4, 06, 73, bc, 39, 1d, 41, 2f, 00] +auth_req 8021a838800000310b7052515a3f79a408725891d7cdd1ec978887519ef090d558bd61f36dfa7b5e1d5b519a18c32c5361f7a01c79ec6e0dc3800000b4893edaad563b98845eee2c95af4d3835be16d9c730ef5968a6df4713fa967e7567f9390273daaf0fc2ae1194090de63c34254fceff8edd4e5f444fef5e89dc6787be0e4d6dd27ca750b86ca418a010790ce4451a055083e1aa5d323cb4cf168302bff780eb6f702112687ff932c34b36958ffdcd4134d7711191c3c6c119f4b1b04926ec7122d10dd5449fb632e69d54f095c26571a1923dab8ee58a30367395e8746202aa45a85489f3cdb88692816fb861c8fa4000c000000000000000 AuthReq { version: 2205752, client_id: [11, 112, 82, 81, 90, 63, 121, 164, 8, 114, 88, 145, 215, 205, 209, 236, 151, 136, 135, 81, 158, 240, 144, 213, 88, 189, 97, 243, 109, 250, 123, 94, 29, 91, 81, 154, 24, 195, 44, 83, 97, 247, 160, 28, 121, 236, 110, 13, 195], auth_token: [137, 62, 218, 173, 86, 59, 152, 132, 94, 238, 44, 149, 175, 77, 56, 53, 190, 22, 217, 199, 48, 239, 89, 104, 166, 223, 71, 19, 250, 150, 126, 117, 103, 249, 57, 2, 115, 218, 175, 15, 194, 174, 17, 148, 9, 13, 230, 60, 52, 37, 79, 206, 255, 142, 221, 78, 95, 68, 79, 239, 94, 137, 220, 103, 135, 190, 14, 77, 109, 210, 124, 167, 80, 184, 108, 164, 24, 160, 16, 121, 12, 228, 69, 26, 5, 80, 131, 225, 170, 93, 50, 60, 180, 207, 22, 131, 2, 191, 247, 128, 235, 111, 112, 33, 18, 104, 127, 249, 50, 195, 75, 54, 149, 143, 253, 205, 65, 52, 215, 113, 17, 145, 195, 198, 193, 25, 244, 177, 176, 73, 38, 236, 113, 34, 209, 13, 213, 68, 159, 182, 50, 230, 157, 84, 240, 149, 194, 101, 113, 161, 146, 61, 171, 142, 229, 138, 48, 54, 115, 149, 232, 116, 98, 2, 170, 69, 168, 84, 137, 243, 205, 184, 134, 146, 129, 111, 184, 97, 200, 250], features: 0 };251;[80, 21, a8, 38, 31, 0b, 70, 52, 51, 5a, 3f, 79, a4, 08, 72, 58, 91, d7, cd, d1, ec, 97, 88, 87, 51, 9e, f0, 90, d5, 58, bd, 61, f3, 6d, fa, 7b, 5e, 1d, 5b, 51, 9a, 18, c3, 2c, 53, 61, f7, a0, 1c, 79, ec, 6e, 0d, c3, 40, b4, 89, 3e, da, ad, 56, 3b, 98, 84, 5e, ee, 2c, 95, af, 4d, 38, 35, be, 16, d9, c7, 30, ef, 59, 68, a6, df, 47, 13, fa, 96, 7e, 75, 67, f9, 39, 02, 73, da, af, 0f, c2, ae, 11, 94, 09, 0d, e6, 3c, 34, 25, 4f, ce, ff, 8e, dd, 4e, 5f, 44, 4f, ef, 5e, 89, dc, 67, 87, be, 0e, 4d, 6d, d2, 7c, a7, 50, b8, 6c, a4, 18, a0, 10, 79, 0c, e4, 45, 1a, 05, 50, 83, e1, aa, 5d, 32, 3c, b4, cf, 16, 83, 02, bf, f7, 80, eb, 6f, 70, 21, 12, 68, 7f, f9, 32, c3, 4b, 36, 95, 8f, fd, cd, 41, 34, d7, 71, 11, 91, c3, c6, c1, 19, f4, b1, b0, 49, 26, ec, 71, 22, d1, 0d, d5, 44, 9f, b6, 32, e6, 9d, 54, f0, 95, c2, 65, 71, a1, 92, 3d, ab, 8e, e5, 8a, 30, 36, 73, 95, e8, 74, 62, 02, aa, 45, a8, 54, 89, f3, cd, b8, 86, 92, 81, 6f, b8, 61, c8, fa, 00, 00] +auth_req 800000b93eb97c7355a708355f9852ce497a237049552fad15a6b1d5b5338efb2abc044cf5e953bd2463d17cf2c2024fb245b24d6485e7fa8d87c00bc564d424b345922b7fb055072e6b94997a85fa5e7866cdf6546cb487a1e0381a5125f85e7d3a922346d40fe257fc8a8aecceb5c0000000e3049e398000000e37555417cdd756a5acd5145453b2 AuthReq { version: 185, client_id: [185, 124, 115, 85, 167, 8, 53, 95, 152, 82, 206, 73, 122, 35, 112, 73, 85, 47, 173, 21, 166, 177, 213, 181, 51, 142, 251, 42, 188, 4, 76, 245, 233, 83, 189, 36, 99, 209, 124, 242, 194, 2, 79, 178, 69, 178, 77, 100, 133, 231, 250, 141, 135, 192, 11, 197, 100, 212, 36, 179, 69, 146], auth_token: [127, 176, 85, 7, 46, 107, 148, 153, 122, 133, 250, 94, 120, 102, 205, 246, 84, 108, 180, 135, 161, 224, 56, 26, 81, 37, 248, 94, 125, 58, 146, 35, 70, 212, 15, 226, 87, 252, 138, 138, 236, 206, 181], features: 3808730681 };137;[40, b9, 3e, b9, 7c, 73, 55, a7, 08, 35, 5f, 98, 52, ce, 49, 7a, 23, 70, 49, 55, 2f, ad, 15, a6, b1, d5, b5, 33, 8e, fb, 2a, bc, 04, 4c, f5, e9, 53, bd, 24, 63, d1, 7c, f2, c2, 02, 4f, b2, 45, b2, 4d, 64, 85, e7, fa, 8d, 87, c0, 0b, c5, 64, d4, 24, b3, 45, 92, 2b, 7f, b0, 55, 07, 2e, 6b, 94, 99, 7a, 85, fa, 5e, 78, 66, cd, f6, 54, 6c, b4, 87, a1, e0, 38, 1a, 51, 25, f8, 5e, 7d, 3a, 92, 23, 46, d4, 0f, e2, 57, fc, 8a, 8a, ec, ce, b5, c0, 00, 00, 00, e3, 04, 9e, 39, 00] +auth_req 8000003b8000000a64852009a3257158fc8ac00000000000003cb9ca5f4000a4175231d4847a7b92d9e17db0254d7a8f8eef6d2f0a47fba0f2562749e9b754c034eb3b6a1f49650ee6f8c7ed09b4cee5fbbd8c53a01a9dd16b630517326a08c5 accept +auth_req c0000000004529de400436ffe55c4039377d2bac973343781ac86dd9a0c1ce56585d631251d68758bedf5d590765126a0f14e7cd2e11cfcd5bec8a85c2f94f385c6f75527dedb5b70cc0000000000001b000 AuthReq { version: 4532702, client_id: [54, 255, 229, 92], auth_token: [55, 125, 43, 172, 151, 51, 67, 120, 26, 200, 109, 217, 160, 193, 206, 86, 88, 93, 99, 18, 81, 214, 135, 88, 190, 223, 93, 89, 7, 101, 18, 106, 15, 20, 231, 205, 46, 17, 207, 205, 91, 236, 138, 133, 194, 249, 79, 56, 92, 111, 117, 82, 125, 237, 181, 183, 12], features: 432 };82;[80, 45, 29, de, 04, 36, ff, e5, 5c, 39, 37, 7d, 2b, ac, 97, 33, 43, 78, 1a, c8, 6d, d9, a0, c1, ce, 56, 58, 5d, 63, 12, 51, d6, 87, 58, be, df, 5d, 59, 07, 65, 12, 6a, 0f, 14, e7, cd, 2e, 11, cf, cd, 5b, ec, 8a, 85, c2, f9, 4f, 38, 5c, 6f, 75, 52, 7d, ed, b5, b7, 0c, 41, b0, 00] +auth_req c34ab51f54700ee980000026fd7fd80de307cde89b33fd71b6549f7146d1de51a6329c5f993a0791a623a6b6b5a5d4bc181a80000052acda829f617e2beb93dbd55d1bcf46f322b1d8afc3089691fbb7b4b77e6f5f5f2a28f2ffc05eb947eee8577884fe35dfdc0205cec81365b1ef066ab91b99f757967da65c5de048214454c61b7ce1afb4f67dc062127bf78683f780000000 AuthReq { version: 237201076555615977, client_id: [253, 127, 216, 13, 227, 7, 205, 232, 155, 51, 253, 113, 182, 84, 159, 113, 70, 209, 222, 81, 166, 50, 156, 95, 153, 58, 7, 145, 166, 35, 166, 182, 181, 165, 212, 188, 24, 26], auth_token: [172, 218, 130, 159, 97, 126, 43, 235, 147, 219, 213, 93, 27, 207, 70, 243, 34, 177, 216, 175, 195, 8, 150, 145, 251, 183, 180, 183, 126, 111, 95, 95, 42, 40, 242, 255, 192, 94, 185, 71, 238, 232, 87, 120, 132, 254, 53, 223, 220, 2, 5, 206, 200, 19, 101, 177, 239, 6, 106, 185, 27, 153, 247, 87, 150, 125, 166, 92, 93, 224, 72, 33, 68, 84, 198, 27, 124, 225, 175, 180, 246, 125], features: 27604871360709623 };148;[c3, 4a, b5, 1f, 54, 70, 0e, e9, 26, fd, 7f, d8, 0d, e3, 07, cd, e8, 9b, 33, fd, 71, b6, 54, 9f, 71, 46, d1, de, 51, a6, 32, 9c, 5f, 99, 3a, 07, 91, a6, 23, a6, b6, b5, a5, d4, bc, 18, 1a, 40, 52, ac, da, 82, 9f, 61, 7e, 2b, eb, 93, db, d5, 5d, 1b, cf, 46, f3, 22, b1, d8, af, c3, 08, 96, 91, fb, b7, b4, b7, 7e, 6f, 5f, 5f, 2a, 28, f2, ff, c0, 5e, b9, 47, ee, e8, 57, 78, 84, fe, 35, df, dc, 02, 05, ce, c8, 13, 65, b1, ef, 06, 6a, b9, 1b, 99, f7, 57, 96, 7d, a6, 5c, 5d, e0, 48, 21, 44, 54, c6, 1b, 7c, e1, af, b4, f6, 7d, c0, 62, 12, 7b, f7, 86, 83, f7, 00] +auth_req c01b7382d38ace4f24f27a79032a4f06d0a7554d639c4be523c7ee9958cc401b07cbaaff98b07ce7d8a11dc7a8800000de466cbbd9b2084e3f73af7ea0cb8319607056d9df2aac20471a7471d10da7abf830a2b8a09ef35bacaa012d41e2ee96fed90ea38d01d07d89ecfb484a1a919e88c281ad8582ad1898110cbb54fe25e4d92555f10c29068258536b2eb80fec02459ea0ac34391765d0e66c2d0d8ace94b84af37ce8d9845477037eb07d117ac32f13d49f289687d31dc1993f2eccb36082876dc23650c6b6419dd8f69b41793c53b6247a15bf29f2100f62f1376d2939477d9d1487f67d92c4445ff836e3f5dec62ac2e6d75c2e83b27f02313dda344796ddde50086822e9590a66656353bf419bc000000000000000 AuthReq { version: 7726830103219791, client_id: [242, 122, 121, 3, 42, 79, 6, 208, 167, 85, 77, 99, 156, 75, 229, 35, 199, 238, 153, 88, 204, 64, 27, 7, 203, 170, 255, 152, 176, 124, 231, 216, 161, 29, 199, 168], auth_token: [70, 108, 187, 217, 178, 8, 78, 63, 115, 175, 126, 160, 203, 131, 25, 96, 112, 86, 217, 223, 42, 172, 32, 71, 26, 116, 113, 209, 13, 167, 171, 248, 48, 162, 184, 160, 158, 243, 91, 172, 170, 1, 45, 65, 226, 238, 150, 254, 217, 14, 163, 141, 1, 208, 125, 137, 236, 251, 72, 74, 26, 145, 158, 136, 194, 129, 173, 133, 130, 173, 24, 152, 17, 12, 187, 84, 254, 37, 228, 217, 37, 85, 241, 12, 41, 6, 130, 88, 83, 107, 46, 184, 15, 236, 2, 69, 158, 160, 172, 52, 57, 23, 101, 208, 230, 108, 45, 13, 138, 206, 148, 184, 74, 243, 124, 232, 217, 132, 84, 119, 3, 126, 176, 125, 17, 122, 195, 47, 19, 212, 159, 40, 150, 135, 211, 29, 193, 153, 63, 46, 204, 179, 96, 130, 135, 109, 194, 54, 80, 198, 182, 65, 157, 216, 246, 155, 65, 121, 60, 83, 182, 36, 122, 21, 191, 41, 242, 16, 15, 98, 241, 55, 109, 41, 57, 71, 125, 157, 20, 135, 246, 125, 146, 196, 68, 95, 248, 54, 227, 245, 222, 198, 42, 194, 230, 215, 92, 46, 131, 178, 127, 2, 49, 61, 218, 52, 71, 150, 221, 222, 80, 8, 104, 34, 233, 89, 10, 102, 101, 99, 83, 191], features: 411 };281;[c0, 1b, 73, 82, d3, 8a, ce, 4f, 24, f2, 7a, 79, 03, 2a, 4f, 06, d0, a7, 55, 4d, 63, 9c, 4b, e5, 23, c7, ee, 99, 58, cc, 40, 1b, 07, cb, aa, ff, 98, b0, 7c, e7, d8, a1, 1d, c7, a8, 40, de, 46, 6c, bb, d9, b2, 08, 4e, 3f, 73, af, 7e, a0, cb, 83, 19, 60, 70, 56, d9, df, 2a, ac, 20, 47, 1a, 74, 71, d1, 0d, a7, ab, f8, 30, a2, b8, a0, 9e, f3, 5b, ac, aa, 01, 2d, 41, e2, ee, 96, fe, d9, 0e, a3, 8d, 01, d0, 7d, 89, ec, fb, 48, 4a, 1a, 91, 9e, 88, c2, 81, ad, 85, 82, ad, 18, 98, 11, 0c, bb, 54, fe, 25, e4, d9, 25, 55, f1, 0c, 29, 06, 82, 58, 53, 6b, 2e, b8, 0f, ec, 02, 45, 9e, a0, ac, 34, 39, 17, 65, d0, e6, 6c, 2d, 0d, 8a, ce, 94, b8, 4a, f3, 7c, e8, d9, 84, 54, 77, 03, 7e, b0, 7d, 11, 7a, c3, 2f, 13, d4, 9f, 28, 96, 87, d3, 1d, c1, 99, 3f, 2e, cc, b3, 60, 82, 87, 6d, c2, 36, 50, c6, b6, 41, 9d, d8, f6, 9b, 41, 79, 3c, 53, b6, 24, 7a, 15, bf, 29, f2, 10, 0f, 62, f1, 37, 6d, 29, 39, 47, 7d, 9d, 14, 87, f6, 7d, 92, c4, 44, 5f, f8, 36, e3, f5, de, c6, 2a, c2, e6, d7, 5c, 2e, 83, b2, 7f, 02, 31, 3d, da, 34, 47, 96, dd, de, 50, 08, 68, 22, e9, 59, 0a, 66, 65, 63, 53, bf, 41, 9b, 00] +auth_req 821bbb408000003a28f60e4ded22767ab9e3b0d4ecaeb699d710ff604b127f3e72971c8d53277e2d04e63ee4c3c12d8b025cdea82fa8c7955849f48cdc321c2ef06d40d29144d10d973dbbec51f112d31e699103d1ae09817dc90e5ecc6191eb0f7652b144b8f0fc6546484051e3c15cf8eb125d9babddf7c933c78dd6bf2599a77942339195023c9510486cc194ad07f74679bc3c21e89ef76ef6ac4c5ea8e492be44afca92899e056d394108e9e68bbf1141431625f2388b9d3dd20966b2eb471cd3a22f042595329a49285e2055498b997e9fba1a658295fcefc235c3c96aa158ff0a734f694fab12e0ca971b86dd2c34e42b49284bc3e4342547d4e7056343b7d53015a78dd5e5b222d82fd495b900045441ab988d6b71c24004873b93cee5736d06 accept +auth_req c00000000005e03405f0a5230e752ed7028bafabfcde7ed14b0721da77c17a59e8db947274d1a282eb38873e461d5ade80c5a7599b08e60125a927b25c807b7fb94000 AuthReq { version: 385076, client_id: [240, 165, 35, 14, 117], auth_token: [215, 2, 139, 175, 171, 252, 222, 126, 209, 75, 7, 33, 218, 119, 193, 122, 89, 232, 219, 148, 114, 116, 209, 162, 130, 235, 56, 135, 62, 70, 29, 90, 222, 128, 197, 167, 89, 155, 8, 230, 1, 37, 169, 39, 178, 92], features: 8093625 };67;[80, 05, e0, 34, 05, f0, a5, 23, 0e, 75, 2e, d7, 02, 8b, af, ab, fc, de, 7e, d1, 4b, 07, 21, da, 77, c1, 7a, 59, e8, db, 94, 72, 74, d1, a2, 82, eb, 38, 87, 3e, 46, 1d, 5a, de, 80, c5, a7, 59, 9b, 08, e6, 01, 25, a9, 27, b2, 5c, 80, 7b, 7f, b9, 00] +auth_req c000000000b5b9cdc000000000000005cb0821bea740555dba376be60eba9fa76488930e802ae5fded344d658ed33c43a156f148777a9b91177f0cd841b1957c35adf8d2900d3448571c0921109b44272aeee5d51a1e4a824ff89ed7924a2ead63516ce99ad6b742e98ac540c000b7149125fa0480000000 AuthReq { version: 11909581, client_id: [203, 8, 33, 190, 167], auth_token: [93, 186, 55, 107, 230, 14, 186, 159, 167, 100, 136, 147, 14, 128, 42, 229, 253, 237, 52, 77, 101, 142, 211, 60, 67, 161, 86, 241, 72, 119, 122, 155, 145, 23, 127, 12, 216, 65, 177, 149, 124, 53, 173, 248, 210, 144, 13, 52, 72, 87, 28, 9, 33, 16, 155, 68, 39, 42, 238, 229, 213, 26, 30, 74, 130, 79, 248, 158, 215, 146, 74, 46, 173, 99, 81, 108, 233, 154, 214, 183, 66, 233, 138, 197, 64], features: 201298962414084 };120;[80, b5, b9, cd, 05, cb, 08, 21, be, a7, 40, 55, 5d, ba, 37, 6b, e6, 0e, ba, 9f, a7, 64, 88, 93, 0e, 80, 2a, e5, fd, ed, 34, 4d, 65, 8e, d3, 3c, 43, a1, 56, f1, 48, 77, 7a, 9b, 91, 17, 7f, 0c, d8, 41, b1, 95, 7c, 35, ad, f8, d2, 90, 0d, 34, 48, 57, 1c, 09, 21, 10, 9b, 44, 27, 2a, ee, e5, d5, 1a, 1e, 4a, 82, 4f, f8, 9e, d7, 92, 4a, 2e, ad, 63, 51, 6c, e9, 9a, d6, b7, 42, e9, 8a, c5, 40, c0, 00, b7, 14, 91, 25, fa, 04, 00] +auth_req c0000074de7dfb8d800000226d454d92598ec0ce991a97948d28a5c799018d0d8d98659f49ca31d064f0e1b4c60c40edc1196505b64c01dfae25ddc2a4f83dbe886f5d3dd08ec1377c8ec5cd52bf0b90675ba34921d94af6c30a3a11ccd856ea503bf2e47ca307f9e2fc04fc6ec5402ae81a2d740d68f0848bd619ff0f5aec0dbfdaf0af48f63e47915431b743438dbbae7a376087478ed50e3687323fdcb2b3bb924ffacbccef35e5df54c379b9959123bb71a9290c18ff27a3a3ba9337bcd8214501fee9cd0e0e9f5f475983c2e918f4a80849777f0f2ffabd035a6128c4448a8709bbe0081fe25abd8fdba36e7ca5dba2192cc6a5e564b10f9f939f36d2ddce017c2a7650ab32ad125a3f399903b88b6f69f85fd36852a341c68ed2c1130ea6a8d602cb4000 AuthReq { version: 501949004685, client_id: [109, 69, 77, 146, 89, 142, 192, 206, 153, 26, 151, 148, 141, 40, 165, 199, 153, 1, 141, 13, 141, 152, 101, 159, 73, 202, 49, 208, 100, 240, 225, 180, 198, 12], auth_token: [193, 25, 101, 5, 182, 76, 1, 223, 174, 37, 221, 194, 164, 248, 61, 190, 136, 111, 93, 61, 208, 142, 193, 55, 124, 142, 197, 205, 82, 191, 11, 144, 103, 91, 163, 73, 33, 217, 74, 246, 195, 10, 58, 17, 204, 216, 86, 234, 80, 59, 242, 228, 124, 163, 7, 249, 226, 252, 4, 252, 110, 197, 64, 42, 232, 26, 45, 116, 13, 104, 240, 132, 139, 214, 25, 255, 15, 90, 236, 13, 191, 218, 240, 175, 72, 246, 62, 71, 145, 84, 49, 183, 67, 67, 141, 187, 174, 122, 55, 96, 135, 71, 142, 213, 14, 54, 135, 50, 63, 220, 178, 179, 187, 146, 79, 250, 203, 204, 239, 53, 229, 223, 84, 195, 121, 185, 149, 145, 35, 187, 113, 169, 41, 12, 24, 255, 39, 163, 163, 186, 147, 55, 188, 216, 33, 69, 1, 254, 233, 205, 14, 14, 159, 95, 71, 89, 131, 194, 233, 24, 244, 168, 8, 73, 119, 127, 15, 47, 250, 189, 3, 90, 97, 40, 196, 68, 138, 135, 9, 187, 224, 8, 31, 226, 90, 189, 143, 219, 163, 110, 124, 165, 219, 162, 25, 44, 198, 165, 229, 100, 177, 15, 159, 147, 159, 54, 210, 221, 206, 1, 124, 42, 118, 80, 171, 50, 173, 18, 90, 63, 57, 153, 3, 184, 139, 111, 105, 248, 95, 211, 104, 82, 163, 65, 198, 142, 210], features: 77421727555388107 };295;[c0, 00, 00, 74, de, 7d, fb, 8d, 22, 6d, 45, 4d, 92, 59, 8e, c0, ce, 99, 1a, 97, 94, 8d, 28, a5, c7, 99, 01, 8d, 0d, 8d, 98, 65, 9f, 49, ca, 31, d0, 64, f0, e1, b4, c6, 0c, 40, ed, c1, 19, 65, 05, b6, 4c, 01, df, ae, 25, dd, c2, a4, f8, 3d, be, 88, 6f, 5d, 3d, d0, 8e, c1, 37, 7c, 8e, c5, cd, 52, bf, 0b, 90, 67, 5b, a3, 49, 21, d9, 4a, f6, c3, 0a, 3a, 11, cc, d8, 56, ea, 50, 3b, f2, e4, 7c, a3, 07, f9, e2, fc, 04, fc, 6e, c5, 40, 2a, e8, 1a, 2d, 74, 0d, 68, f0, 84, 8b, d6, 19, ff, 0f, 5a, ec, 0d, bf, da, f0, af, 48, f6, 3e, 47, 91, 54, 31, b7, 43, 43, 8d, bb, ae, 7a, 37, 60, 87, 47, 8e, d5, 0e, 36, 87, 32, 3f, dc, b2, b3, bb, 92, 4f, fa, cb, cc, ef, 35, e5, df, 54, c3, 79, b9, 95, 91, 23, bb, 71, a9, 29, 0c, 18, ff, 27, a3, a3, ba, 93, 37, bc, d8, 21, 45, 01, fe, e9, cd, 0e, 0e, 9f, 5f, 47, 59, 83, c2, e9, 18, f4, a8, 08, 49, 77, 7f, 0f, 2f, fa, bd, 03, 5a, 61, 28, c4, 44, 8a, 87, 09, bb, e0, 08, 1f, e2, 5a, bd, 8f, db, a3, 6e, 7c, a5, db, a2, 19, 2c, c6, a5, e5, 64, b1, 0f, 9f, 93, 9f, 36, d2, dd, ce, 01, 7c, 2a, 76, 50, ab, 32, ad, 12, 5a, 3f, 39, 99, 03, b8, 8b, 6f, 69, f8, 5f, d3, 68, 52, a3, 41, c6, 8e, d2, c1, 13, 0e, a6, a8, d6, 02, cb, 00] +auth_req 8000754d3353c60338e4d20ef910d6bc76f212fb644fcad9a73f070035000634904813bfc4b88034e5cab02680bf919ea3ba19e9f0f4627fc00000000000005153e6dde7656213a5cd9ca214aca2a756cdcb859a411ee797ac32e7458de02613925a3987bf0afb90d2d80e7b0ebee9a414f25c1836322d2acf607416b9204f463b761caeb7b90f82da567c2089c2f78a3080002205c000000000000000 accept +auth_req c00000000000255fc00000000000002562ded9dfbc1d33011543a2691f0afa6d736b3cb906e62140c3edd37386912c8a15d764c22f800000904ffd411d3daad9d59faf91cdcad7878860a4f8af256304cc1a55515e7990ca35b470337d42880e1eec374c9fa7d4921f2d4a153e13375e95c137d0c57b480d121bf43f85eee5a623c0a28c2a39e752f83920033a2368c563c0d69f32ce479e641ba1a797f39162dd966614e8b37dd1bf9260c2b1d30146a5796c7eb1e4570bec51f12c4d13c97a92aa0526c3310d9f668000000dc000000000000000 AuthReq { version: 9567, client_id: [98, 222, 217, 223, 188, 29, 51, 1, 21, 67, 162, 105, 31, 10, 250, 109, 115, 107, 60, 185, 6, 230, 33, 64, 195, 237, 211, 115, 134, 145, 44, 138, 21, 215, 100, 194, 47], auth_token: [79, 253, 65, 29, 61, 170, 217, 213, 159, 175, 145, 205, 202, 215, 135, 136, 96, 164, 248, 175, 37, 99, 4, 204, 26, 85, 81, 94, 121, 144, 202, 53, 180, 112, 51, 125, 66, 136, 14, 30, 236, 55, 76, 159, 167, 212, 146, 31, 45, 74, 21, 62, 19, 55, 94, 149, 193, 55, 208, 197, 123, 72, 13, 18, 27, 244, 63, 133, 238, 229, 166, 35, 192, 162, 140, 42, 57, 231, 82, 248, 57, 32, 3, 58, 35, 104, 197, 99, 192, 214, 159, 50, 206, 71, 158, 100, 27, 161, 167, 151, 243, 145, 98, 221, 150, 102, 20, 232, 179, 125, 209, 191, 146, 96, 194, 177, 211, 1, 70, 165, 121, 108, 126, 177, 228, 87, 11, 236, 81, 241, 44, 77, 19, 201, 122, 146, 170, 5, 38, 195, 49, 13, 159, 102], features: 13 };213;[65, 5f, 25, 62, de, d9, df, bc, 1d, 33, 01, 15, 43, a2, 69, 1f, 0a, fa, 6d, 73, 6b, 3c, b9, 06, e6, 21, 40, c3, ed, d3, 73, 86, 91, 2c, 8a, 15, d7, 64, c2, 2f, 40, 90, 4f, fd, 41, 1d, 3d, aa, d9, d5, 9f, af, 91, cd, ca, d7, 87, 88, 60, a4, f8, af, 25, 63, 04, cc, 1a, 55, 51, 5e, 79, 90, ca, 35, b4, 70, 33, 7d, 42, 88, 0e, 1e, ec, 37, 4c, 9f, a7, d4, 92, 1f, 2d, 4a, 15, 3e, 13, 37, 5e, 95, c1, 37, d0, c5, 7b, 48, 0d, 12, 1b, f4, 3f, 85, ee, e5, a6, 23, c0, a2, 8c, 2a, 39, e7, 52, f8, 39, 20, 03, 3a, 23, 68, c5, 63, c0, d6, 9f, 32, ce, 47, 9e, 64, 1b, a1, a7, 97, f3, 91, 62, dd, 96, 66, 14, e8, b3, 7d, d1, bf, 92, 60, c2, b1, d3, 01, 46, a5, 79, 6c, 7e, b1, e4, 57, 0b, ec, 51, f1, 2c, 4d, 13, c9, 7a, 92, aa, 05, 26, c3, 31, 0d, 9f, 66, 0d, 00] +auth_req c0000000000922468000002216cc40926aae01df9e31348b01428dfaf217ed936d780b3f1731aea72789aaf234114003fc73c9c00009c1a0de2e3600 AuthReq { version: 598598, client_id: [22, 204, 64, 146, 106, 174, 1, 223, 158, 49, 52, 139, 1, 66, 141, 250, 242, 23, 237, 147, 109, 120, 11, 63, 23, 49, 174, 167, 39, 137, 170, 242, 52, 17], auth_token: [252, 115, 201], features: 10727232253494 };60;[80, 09, 22, 46, 22, 16, cc, 40, 92, 6a, ae, 01, df, 9e, 31, 34, 8b, 01, 42, 8d, fa, f2, 17, ed, 93, 6d, 78, 0b, 3f, 17, 31, ae, a7, 27, 89, aa, f2, 34, 11, 03, fc, 73, c9, c0, 00, 09, c1, a0, de, 2e, 36, 00] +auth_req c03a80fa09021d13c0000000000000129ec5ef49446a9862481c78cde6576de314e33e2489951e73812664f93412366db5cf59188e08ecff34ac3bbee2e880b15a5d17f35a233cfedd439e1397e7371e1d47ebcb8fa8d653ffbc0cafae76cbc5a38003bde5c000000000000000df13e312 AuthReq { version: 16467360030530835, client_id: [158, 197, 239, 73, 68, 106, 152, 98, 72, 28, 120, 205, 230, 87, 109, 227, 20, 227], auth_token: [36, 137, 149, 30, 115, 129, 38, 100, 249, 52, 18, 54, 109, 181, 207, 89, 24, 142, 8, 236, 255, 52, 172, 59, 190, 226, 232, 128, 177, 90, 93, 23, 243, 90, 35, 60, 254, 221, 67, 158, 19, 151, 231, 55, 30, 29, 71, 235, 203, 143, 168, 214, 83, 255, 188, 12, 175, 174, 118, 203, 197, 163], features: 245221 };109;[c0, 3a, 80, fa, 09, 02, 1d, 13, 12, 9e, c5, ef, 49, 44, 6a, 98, 62, 48, 1c, 78, cd, e6, 57, 6d, e3, 14, e3, 3e, 24, 89, 95, 1e, 73, 81, 26, 64, f9, 34, 12, 36, 6d, b5, cf, 59, 18, 8e, 08, ec, ff, 34, ac, 3b, be, e2, e8, 80, b1, 5a, 5d, 17, f3, 5a, 23, 3c, fe, dd, 43, 9e, 13, 97, e7, 37, 1e, 1d, 47, eb, cb, 8f, a8, d6, 53, ff, bc, 0c, af, ae, 76, cb, c5, a3, 80, 03, bd, e5, 00] +auth_req 8000002a80000041288ea1dfc174b67d54cc4e20b56e934d19cd612b78265136e75a9d35d28233c0356f8a24fb74f8422ae1c1b35cf7ac6f34e7058fb77449367cb0a701e9b176b1ce410358ae5da1722a90c941fc021cf304efeeae2fe35400ec674e42869f78d57de417a179f2be99447c757405e63a8e18f0268f8ae9eae90c67aa32237950a39bf513ed36d5ecb238a830cc35ac657e453621ef9a7c1661bc2291b8c7e4a9165725140dc58249a61fd065362ff344db3f64f424ac09f1ff264f77e6a76e2aed4d2bbc7c322303910960703952b43e2cd78d21e66893a2439666f52b57e9b6d4ccf7083d42f65c01fd9c3ed03e3c00bf8576b276952b07d86f7d21d469025269a83f33187b9944252a7e0a2e091b0fcd6e3b8b7f1ff509d94ec41319dccd38c3fbd02a96f733f2eb631f9328c744aaa6ec16e36b523dc9b756ab67670b8d56e6c208587433f380020d3bc000000000000000 reject +auth_req 8001478480000036e7e843bfa0056c414ef39f6c813db8fa120b9bedf14e1ea286452a38888dd2f01049e65e8f03ed28388c67b6a6ce6a2a71ec895aad1d40342ef34435aa24e9496fc02834a407ce788562eafa9235a650aaf2784207e05bb678dbd1309c73977a518ced142b68f3c48e48fa774f1dc000000000000000 AuthReq { version: 83844, client_id: [231, 232, 67, 191, 160, 5, 108, 65, 78, 243, 159, 108, 129, 61, 184, 250, 18, 11, 155, 237, 241, 78, 30, 162, 134, 69, 42, 56, 136, 141, 210, 240, 16, 73, 230, 94, 143, 3, 237, 40, 56, 140, 103, 182, 166, 206, 106, 42, 113, 236, 137, 90, 173, 29], auth_token: [46, 243, 68, 53, 170, 36, 233, 73, 111, 192, 40, 52, 164, 7, 206, 120, 133, 98, 234, 250, 146, 53, 166, 80, 170, 242, 120, 66, 7, 224, 91, 182, 120, 219, 209, 48, 156, 115, 151, 122, 81, 140, 237, 20, 43, 104, 243, 196, 142, 72, 250, 119], features: 3869 };126;[80, 01, 47, 84, 36, e7, e8, 43, bf, a0, 05, 6c, 41, 4e, f3, 9f, 6c, 81, 3d, b8, fa, 12, 0b, 9b, ed, f1, 4e, 1e, a2, 86, 45, 2a, 38, 88, 8d, d2, f0, 10, 49, e6, 5e, 8f, 03, ed, 28, 38, 8c, 67, b6, a6, ce, 6a, 2a, 71, ec, 89, 5a, ad, 1d, 34, 2e, f3, 44, 35, aa, 24, e9, 49, 6f, c0, 28, 34, a4, 07, ce, 78, 85, 62, ea, fa, 92, 35, a6, 50, aa, f2, 78, 42, 07, e0, 5b, b6, 78, db, d1, 30, 9c, 73, 97, 7a, 51, 8c, ed, 14, 2b, 68, f3, c4, 8e, 48, fa, 77, 4f, 1d, 00] +auth_req c000063311969c0c800000256e4530647de393c9e5f285a9d53ce00061df7814133b7f39c81cde3b7524a180240991f043c000000000000101654d82ed1153a032a7cbda9f63387576f295ccbb210a24eaebf03aa4c4c7e564bf59e24c72bdd58d8dcdae14a9617eb17d13bf13aa0e6ca3961511725939d3afb61aa111aeb1f72319eee7958453472eec6fb2416275c97d0643f68c65f02756688f699a23226b3381af1ef62bde96a91b59aa98d8414cce5cb02a2686f6b759e40d49a5bfcc2023e3b696948b930281510ac09d2a77c50a68d3e8d1846db645771c0a3f11ab4eeb486fde7b3ccad4af7e654a0cd4e75e8b9884ec893799102644da35fc3a33a898f9394ac7b4fffb077240a47c230be843a83a83d790471b939efbc9c79d699362168a50e05efbc1bf5f7b4741e5080275bf119445f80bf0c34d8183c7584000 reject +auth_resp 01020273310000 AuthResp { status: 1, error_code: 2, server_id: [115, 49], features: 0 };7;[01, 02, 02, 73, 31, 00, 00] +auth_resp 00000273310100 AuthResp { status: 0, error_code: 0, server_id: [115, 49], features: 1 };7;[00, 00, 02, 73, 31, 01, 00] +auth_resp reject +auth_resp 11 reject +auth_resp 1140 reject +auth_resp 114000 reject +auth_resp 11400040 reject +auth_resp 114000402d reject +auth_resp 114000402dc6 reject +auth_resp 114000402dc6fb reject +auth_resp 114000402dc6fb42 reject +auth_resp 114000402dc6fb4244 reject +auth_resp 114000402dc6fb424429 reject +auth_resp 114000402dc6fb42442990 reject +auth_resp 114000402dc6fb42442990d8 reject +auth_resp 114000402dc6fb42442990d8e5 reject +auth_resp 114000402dc6fb42442990d8e52b reject +auth_resp 114000402dc6fb42442990d8e52beb reject +auth_resp 114000402dc6fb42442990d8e52beb09 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5a reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1b reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1bab reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaa reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0de reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1de reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded3 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded35878 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab166396 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16639675 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d2 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa3365 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33659324 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241f reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe448 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4481271 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c80 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c8013 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c8013ad reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c8013adca reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c8013adcac0 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c8013adcac000 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c8013adcac00000 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c8013adcac0000000 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c8013adcac000000000 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c8013adcac00000000000 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c8013adcac0000000000000 reject +auth_resp 114000402dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c8013adcac000000000000000 AuthResp { status: 17, error_code: 0, server_id: [198, 251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76], features: 1289674 };62;[11, 00, 2d, c6, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, 80, 13, ad, ca, 00] +auth_resp 7a8000008538bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26c07a7f17f959ea2480000000b58b593474 AuthResp { status: 122, error_code: 133, server_id: [191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244, 107, 30, 239, 29, 38], features: 34479688103094820 };74;[7a, 40, 85, 38, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 6b, 1e, ef, 1d, 26, c0, 7a, 7f, 17, f9, 59, ea, 24, 00] +auth_resp e6412880000027825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a404d4000 AuthResp { status: 230, error_code: 296, server_id: [130, 93, 113, 102, 21, 200, 54, 126, 138, 121, 96, 231, 34, 98, 186, 205, 209, 72, 172, 95, 102, 63, 151, 112, 137, 150, 127, 105, 153, 50, 30, 82, 5, 84, 163, 148, 204, 77, 58], features: 77 };50;[e6, 41, 28, 27, 82, 5d, 71, 66, 15, c8, 36, 7e, 8a, 79, 60, e7, 22, 62, ba, cd, d1, 48, ac, 5f, 66, 3f, 97, 70, 89, 96, 7f, 69, 99, 32, 1e, 52, 05, 54, a3, 94, cc, 4d, 3a, 40, 4d, 00] +auth_resp 4cc0000000000000004043ca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48682d37a4c9d4896d14dcc8be6fcb3cae2a8ed41906905a00c79116c42f6efc3abeb5c000000f7345c40700 reject +auth_resp 91521b4000c0000000ebf257b300 AuthResp { status: 145, error_code: 4635, server_id: [], features: 3958527923 };14;[91, 52, 1b, 00, c0, 00, 00, 00, eb, f2, 57, b3, 00] +auth_resp 0b094043fff8070a31ca4e98ed392278b7de69abdbccd6ed89165eb4cbdf27f34cdb610cfbb98a6eb961a9f34e79c941b28dceab810b061154399cb6cb22960706ecdc059f8ab7c000000076bf7f360db73d140f46922fe7829f40b490 reject +auth_resp 5e004016d4cfa52935adb2d90b422e69297d8132b12d4bee0c56c0039ceb36041b4c80000000 AuthResp { status: 94, error_code: 0, server_id: [212, 207, 165, 41, 53, 173, 178, 217, 11, 66, 46, 105, 41, 125, 129, 50, 177, 45, 75, 238, 12, 86], features: 1016958967618380 };38;[5e, 00, 16, d4, cf, a5, 29, 35, ad, b2, d9, 0b, 42, 2e, 69, 29, 7d, 81, 32, b1, 2d, 4b, ee, 0c, 56, c0, 03, 9c, eb, 36, 04, 1b, 4c, 00] +auth_resp 178000b0078000002329dc1c0b073bf895cd7c66ace40ecab63c2d2a9be3487e51cd29f1ed51a93fe5a5c89240c6c000000000000000 AuthResp { status: 23, error_code: 45063, server_id: [41, 220, 28, 11, 7, 59, 248, 149, 205, 124, 102, 172, 228, 14, 202, 182, 60, 45, 42, 155, 227, 72, 126, 81, 205, 41, 241, 237, 81, 169, 63, 229, 165, 200, 146], features: 198 };54;[17, 80, 00, b0, 07, 23, 29, dc, 1c, 0b, 07, 3b, f8, 95, cd, 7c, 66, ac, e4, 0e, ca, b6, 3c, 2d, 2a, 9b, e3, 48, 7e, 51, cd, 29, f1, ed, 51, a9, 3f, e5, a5, c8, 92, 40, c6, 00] +auth_resp f604800000110428cddc2a44f8de699a9723e60222de294068400dffa0700dd5443f929102b81179 AuthResp { status: 246, error_code: 4, server_id: [4, 40, 205, 220, 42, 68, 248, 222, 105, 154, 151, 35, 230, 2, 34, 222, 41], features: 104 };40;[f6, 04, 11, 04, 28, cd, dc, 2a, 44, f8, de, 69, 9a, 97, 23, e6, 02, 22, de, 29, 40, 68, 00] +auth_resp b3808fa5d4c00000000000001d5bce6aa609439c89860b0173935e59d0429831aedbee71abb4ded9744fc00000000000138c0e3a516e0f8c52a1325cd3df622560 AuthResp { status: 179, error_code: 9414100, server_id: [91, 206, 106, 166, 9, 67, 156, 137, 134, 11, 1, 115, 147, 94, 89, 208, 66, 152, 49, 174, 219, 238, 113, 171, 180, 222, 217, 116, 79], features: 5004 };65;[b3, 80, 8f, a5, d4, 1d, 5b, ce, 6a, a6, 09, 43, 9c, 89, 86, 0b, 01, 73, 93, 5e, 59, d0, 42, 98, 31, ae, db, ee, 71, ab, b4, de, d9, 74, 4f, 53, 8c, 00] +auth_resp 8ec0000000000000bb80000018ae3c4e964fc7fb440fc12d9199d03b0f93eec7afbbbd6354c000eeab66708bc600 AuthResp { status: 142, error_code: 187, server_id: [174, 60, 78, 150, 79, 199, 251, 68, 15, 193, 45, 145, 153, 208, 59, 15, 147, 238, 199, 175, 187, 189, 99, 84], features: 262419925470150 };46;[8e, 40, bb, 18, ae, 3c, 4e, 96, 4f, c7, fb, 44, 0f, c1, 2d, 91, 99, d0, 3b, 0f, 93, ee, c7, af, bb, bd, 63, 54, c0, 00, ee, ab, 66, 70, 8b, c6, 00] +auth_resp 91800957dbc00000000000004009f23f7a075e2826813e113c9a06f49871ddb099ab2ad13be057c703b111585e8fe76d619f2dfb72c906ad202f604a64b4338f589eb76fd4f39162284f326cd2c0051b91c41a2fc700 reject +auth_resp 7e428c35491f0876db23e7a935cf8c5a485121a92b78c348b16bc348be5ebdf687fa97bc7df349eebf01d7dbf32b7b4a8184c8e308239c4836c000000018abf0a800 AuthResp { status: 126, error_code: 652, server_id: [73, 31, 8, 118, 219, 35, 231, 169, 53, 207, 140, 90, 72, 81, 33, 169, 43, 120, 195, 72, 177, 107, 195, 72, 190, 94, 189, 246, 135, 250, 151, 188, 125, 243, 73, 238, 191, 1, 215, 219, 243, 43, 123, 74, 129, 132, 200, 227, 8, 35, 156, 72, 54], features: 413921448 };66;[7e, 42, 8c, 35, 49, 1f, 08, 76, db, 23, e7, a9, 35, cf, 8c, 5a, 48, 51, 21, a9, 2b, 78, c3, 48, b1, 6b, c3, 48, be, 5e, bd, f6, 87, fa, 97, bc, 7d, f3, 49, ee, bf, 01, d7, db, f3, 2b, 7b, 4a, 81, 84, c8, e3, 08, 23, 9c, 48, 36, 98, ab, f0, a8, 00] +auth_resp d0c000000000004dc740429123d60736259222b981ebb7f3cd4831e326334a213ea9200cb0349e10298d37c30f1c7d2b7d48993335dbf42f5f671920826c8b20336523f338e439ffa35fc5b8e9c0000001d27e525e80000000 reject +auth_resp 448000000e40315b145b518b6690a19107239e1ae2aa02ac8bb836e4bc10bdd6c8c782ee40c2db22e04dad0a29845530607429de147262c0c6ee82bd81ac87754002086e AuthResp { status: 68, error_code: 14, server_id: [91, 20, 91, 81, 139, 102, 144, 161, 145, 7, 35, 158, 26, 226, 170, 2, 172, 139, 184, 54, 228, 188, 16, 189, 214, 200, 199, 130, 238, 64, 194, 219, 34, 224, 77, 173, 10, 41, 132, 85, 48, 96, 116, 41, 222, 20, 114, 98, 192], features: 499480359120701301 };68;[44, 0e, 31, 5b, 14, 5b, 51, 8b, 66, 90, a1, 91, 07, 23, 9e, 1a, e2, aa, 02, ac, 8b, b8, 36, e4, bc, 10, bd, d6, c8, c7, 82, ee, 40, c2, db, 22, e0, 4d, ad, 0a, 29, 84, 55, 30, 60, 74, 29, de, 14, 72, 62, c0, c6, ee, 82, bd, 81, ac, 87, 75, 00] +auth_resp 36c64545248b825a228000001061b2521a8db3d8856f8f26e3cce9a378004000 accept +auth_resp 69400e401bac36a6fb516443f6f8ed3df6922e909386f0d69d94ff84807fdf7f87ff777400 AuthResp { status: 105, error_code: 14, server_id: [172, 54, 166, 251, 81, 100, 67, 246, 248, 237, 61, 246, 146, 46, 144, 147, 134, 240, 214, 157, 148, 255, 132, 128, 127, 223, 127], features: 134182772 };37;[69, 0e, 1b, ac, 36, a6, fb, 51, 64, 43, f6, f8, ed, 3d, f6, 92, 2e, 90, 93, 86, f0, d6, 9d, 94, ff, 84, 80, 7f, df, 7f, 87, ff, 77, 74, 00] +auth_resp c4fee637b23600ad15024acf800000f40009fdb3441b accept +auth_resp 6f8000d5a14020f36b595a73942c77f0355f628d447143c8a4d62e22277d6a2638382f4cf8091dc0000ac8e877e2c14000 AuthResp { status: 111, error_code: 54689, server_id: [243, 107, 89, 90, 115, 148, 44, 119, 240, 53, 95, 98, 141, 68, 113, 67, 200, 164, 214, 46, 34, 39, 125, 106, 38, 56, 56, 47, 76, 248, 9, 29], features: 11858009907905 };49;[6f, 80, 00, d5, a1, 20, f3, 6b, 59, 5a, 73, 94, 2c, 77, f0, 35, 5f, 62, 8d, 44, 71, 43, c8, a4, d6, 2e, 22, 27, 7d, 6a, 26, 38, 38, 2f, 4c, f8, 09, 1d, c0, 00, 0a, c8, e8, 77, e2, c1, 00] +auth_resp 48f41ba1c9eba03d4bc000000000000041dbf685fcbe49684a8eba6596a100ae82f2974ee2ccd7a3a4bc853fa8d8403bf5d3646a569ca22bdab971748c9fe003c77c6d31efa1386799e942737721a21f11fbc0000000000000048000000046022c reject +auth_resp 1b80027a53403ef3e7bd7b77c0478621e9b68d05f26056b7dcba0d10fd36f177853ea5ecf64e1d1f3b0ebdce35069376735d0a7020a1e7e6b7aeec5c6e786d545a46e23b29cc8514a2b1da64f7c000000000000000 AuthResp { status: 27, error_code: 162387, server_id: [243, 231, 189, 123, 119, 192, 71, 134, 33, 233, 182, 141, 5, 242, 96, 86, 183, 220, 186, 13, 16, 253, 54, 241, 119, 133, 62, 165, 236, 246, 78, 29, 31, 59, 14, 189, 206, 53, 6, 147, 118, 115, 93, 10, 112, 32, 161, 231, 230, 183, 174, 236, 92, 110, 120, 109, 84, 90, 70, 226, 59, 41], features: 902149989358789879 };85;[1b, 80, 02, 7a, 53, 3e, f3, e7, bd, 7b, 77, c0, 47, 86, 21, e9, b6, 8d, 05, f2, 60, 56, b7, dc, ba, 0d, 10, fd, 36, f1, 77, 85, 3e, a5, ec, f6, 4e, 1d, 1f, 3b, 0e, bd, ce, 35, 06, 93, 76, 73, 5d, 0a, 70, 20, a1, e7, e6, b7, ae, ec, 5c, 6e, 78, 6d, 54, 5a, 46, e2, 3b, 29, cc, 85, 14, a2, b1, da, 64, f7, 00] +auth_resp 5280594b1ec00000000000001762e599b12c606b256d4133f364f97e373df78a9b4a3116c0000002ad4def92c000000000000000 AuthResp { status: 82, error_code: 5851934, server_id: [98, 229, 153, 177, 44, 96, 107, 37, 109, 65, 51, 243, 100, 249, 126, 55, 61, 247, 138, 155, 74, 49, 22], features: 11497500562 };52;[52, 80, 59, 4b, 1e, 17, 62, e5, 99, b1, 2c, 60, 6b, 25, 6d, 41, 33, f3, 64, f9, 7e, 37, 3d, f7, 8a, 9b, 4a, 31, 16, c0, 00, 00, 02, ad, 4d, ef, 92, 00] +auth_resp fe8014f901c000000000000017136235d96ee0961c25b4d2a6315fea3009dea32eddcfecc000000c33a7ac7380000000 AuthResp { status: 254, error_code: 1374465, server_id: [19, 98, 53, 217, 110, 224, 150, 28, 37, 180, 210, 166, 49, 95, 234, 48, 9, 222, 163, 46, 221, 207, 236], features: 52406234227 };48;[fe, 80, 14, f9, 01, 17, 13, 62, 35, d9, 6e, e0, 96, 1c, 25, b4, d2, a6, 31, 5f, ea, 30, 09, de, a3, 2e, dd, cf, ec, c0, 00, 00, 0c, 33, a7, ac, 73, 00] +auth_resp 984dc84000c141eda10bef0b7f0ab269ead41d9542b8004440b9cf81 AuthResp { status: 152, error_code: 3528, server_id: [], features: 90614743469853567 };24;[98, 4d, c8, 00, c1, 41, ed, a1, 0b, ef, 0b, 7f, 00] +auth_resp 388003feb2402344b1194ebcb6f61a0868e12b6dc145a506dfd0423712f2e914219b90bca8abb2b4051cc0000000801698b70f6c0013b0e0d2829140f72cb5d47c0a AuthResp { status: 56, error_code: 261810, server_id: [68, 177, 25, 78, 188, 182, 246, 26, 8, 104, 225, 43, 109, 193, 69, 165, 6, 223, 208, 66, 55, 18, 242, 233, 20, 33, 155, 144, 188, 168, 171, 178, 180, 5, 28], features: 2148964535 };66;[38, 80, 03, fe, b2, 23, 44, b1, 19, 4e, bc, b6, f6, 1a, 08, 68, e1, 2b, 6d, c1, 45, a5, 06, df, d0, 42, 37, 12, f2, e9, 14, 21, 9b, 90, bc, a8, ab, b2, b4, 05, 1c, c0, 00, 00, 00, 80, 16, 98, b7, 00] +auth_resp ddc000000000010ea31e17a297d1a34c3a30db4265cb082c8919d32ccb6a9a48ac18ce1d12b8bfba40494000 AuthResp { status: 221, error_code: 69283, server_id: [23, 162, 151, 209, 163, 76, 58, 48, 219, 66, 101, 203, 8, 44, 137, 25, 211, 44, 203, 106, 154, 72, 172, 24, 206, 29, 18, 184, 191, 186], features: 73 };44;[dd, 80, 01, 0e, a3, 1e, 17, a2, 97, d1, a3, 4c, 3a, 30, db, 42, 65, cb, 08, 2c, 89, 19, d3, 2c, cb, 6a, 9a, 48, ac, 18, ce, 1d, 12, b8, bf, ba, 40, 49, 00] +auth_resp 638000005780000001d6c2837e78d2ba16008000000ff304c22848dafd9c618edeca65f053 AuthResp { status: 99, error_code: 87, server_id: [214], features: 181127467421537792 };37;[63, 40, 57, 01, d6, c2, 83, 7e, 78, d2, ba, 16, 00, 00] +auth_resp a0808bba381542e8ef218fae7f134905df35216f23dc5cd18d2bdb4e424000 AuthResp { status: 160, error_code: 9157176, server_id: [66, 232, 239, 33, 143, 174, 127, 19, 73, 5, 223, 53, 33, 111, 35, 220, 92, 209, 141, 43, 219], features: 3650 };31;[a0, 80, 8b, ba, 38, 15, 42, e8, ef, 21, 8f, ae, 7f, 13, 49, 05, df, 35, 21, 6f, 23, dc, 5c, d1, 8d, 2b, db, 4e, 42, 00] +auth_resp 12807ab7d84019037c6804c98ce21fd0c967a0cab946bb853b8ff747349c1bea8ee43226c00000000000000839c3bb19c35e4c2b AuthResp { status: 18, error_code: 8042456, server_id: [3, 124, 104, 4, 201, 140, 226, 31, 208, 201, 103, 160, 202, 185, 70, 187, 133, 59, 143, 247, 71, 52, 156, 27, 234], features: 249836070 };52;[12, 80, 7a, b7, d8, 19, 03, 7c, 68, 04, c9, 8c, e2, 1f, d0, c9, 67, a0, ca, b9, 46, bb, 85, 3b, 8f, f7, 47, 34, 9c, 1b, ea, 8e, e4, 32, 26, 00] +auth_resp bdc9990dbbc747ca314013c9c0b43406a6cf4cec80e53f6ea0aba168e28745ea05afc3978ac4 accept +auth_resp db40df401d73cef8755605b81f4de7a3d3a83db474c95f9ca6871fb7f32440ba1b10c0279cba030321824000 AuthResp { status: 219, error_code: 223, server_id: [115, 206, 248, 117, 86, 5, 184, 31, 77, 231, 163, 211, 168, 61, 180, 116, 201, 95, 156, 166, 135, 31, 183, 243, 36, 64, 186, 27, 16], features: 11149846820102530 };44;[db, 40, df, 1d, 73, ce, f8, 75, 56, 05, b8, 1f, 4d, e7, a3, d3, a8, 3d, b4, 74, c9, 5f, 9c, a6, 87, 1f, b7, f3, 24, 40, ba, 1b, 10, c0, 27, 9c, ba, 03, 03, 21, 82, 00] +auth_resp 5ebf8da3b2194d5a9261837640e64ca491932cbe3953e3ff059159d4798b17c00000000a0380e100 AuthResp { status: 94, error_code: 1066247090, server_id: [77, 90, 146, 97, 131, 118, 64, 230, 76, 164, 145, 147, 44, 190, 57, 83, 227, 255, 5, 145, 89, 212, 121, 139, 23], features: 168001761 };40;[5e, bf, 8d, a3, b2, 19, 4d, 5a, 92, 61, 83, 76, 40, e6, 4c, a4, 91, 93, 2c, be, 39, 53, e3, ff, 05, 91, 59, d4, 79, 8b, 17, 8a, 03, 80, e1, 00] +auth_resp 6880001342c000000000000006ab16d89ff2dd80000ec24000e6 AuthResp { status: 104, error_code: 4930, server_id: [171, 22, 216, 159, 242, 221], features: 3778 };25;[68, 53, 42, 06, ab, 16, d8, 9f, f2, dd, 4e, c2, 00] +auth_resp eaff609a4fd51edbf10b5b7c534d32cbde351b5cd8c00001ffac309f89c0000000000000024c8b8ef7b5 accept +auth_resp 7a40494006a519c93f9233800000154000 AuthResp { status: 122, error_code: 73, server_id: [165, 25, 201, 63, 146, 51], features: 21 };17;[7a, 40, 49, 06, a5, 19, c9, 3f, 92, 33, 15, 00] +auth_resp fe4007c00000000000002ac5c74bbc6183f92c663919fb083692443427c77260111cac482c1346b28121f1a2cbe2b37067e3fe6fcbc000000000294672c00000000000000027be34 AuthResp { status: 254, error_code: 7, server_id: [197, 199, 75, 188, 97, 131, 249, 44, 102, 57, 25, 251, 8, 54, 146, 68, 52, 39, 199, 114, 96, 17, 28, 172, 72, 44, 19, 70, 178, 129, 33, 241, 162, 203, 226, 179, 112, 103, 227, 254, 111, 203], features: 2705010 };69;[fe, 07, 2a, c5, c7, 4b, bc, 61, 83, f9, 2c, 66, 39, 19, fb, 08, 36, 92, 44, 34, 27, c7, 72, 60, 11, 1c, ac, 48, 2c, 13, 46, b2, 81, 21, f1, a2, cb, e2, b3, 70, 67, e3, fe, 6f, cb, 80, 29, 46, 72, 00] +auth_resp 08be387c3880000019bf124abbd243f7015c49509d70c568629433cac03123405e39c0000041de4f753f80000000 AuthResp { status: 8, error_code: 1043889208, server_id: [191, 18, 74, 187, 210, 67, 247, 1, 92, 73, 80, 157, 112, 197, 104, 98, 148, 51, 202, 192, 49, 35, 64, 94, 57], features: 282902623551 };46;[08, be, 38, 7c, 38, 19, bf, 12, 4a, bb, d2, 43, f7, 01, 5c, 49, 50, 9d, 70, c5, 68, 62, 94, 33, ca, c0, 31, 23, 40, 5e, 39, c0, 00, 00, 41, de, 4f, 75, 3f, 00] +auth_resp 78c000000000ea4d524002d8d73fc000000000000000 AuthResp { status: 120, error_code: 15355218, server_id: [216, 215], features: 63 };22;[78, 80, ea, 4d, 52, 02, d8, d7, 3f, 00] +auth_resp 05c0000000000000541dcc65b5876179054ee3e5ec265fa3878a1925a7db8154a680e7825d2537c002adb129c4fc0700 AuthResp { status: 5, error_code: 84, server_id: [204, 101, 181, 135, 97, 121, 5, 78, 227, 229, 236, 38, 95, 163, 135, 138, 25, 37, 167, 219, 129, 84, 166, 128, 231, 130, 93, 37, 55], features: 753926375013383 };48;[05, 40, 54, 1d, cc, 65, b5, 87, 61, 79, 05, 4e, e3, e5, ec, 26, 5f, a3, 87, 8a, 19, 25, a7, db, 81, 54, a6, 80, e7, 82, 5d, 25, 37, c0, 02, ad, b1, 29, c4, fc, 07, 00] +auth_resp e5805fb890c000000000000043b755eaa841e446cc262d8ea5012b84f291c8255c4eed48cfd63bc7c4865b1c2b9c456b64b8417b93a9c8903932efd3b74a5b528d29c97428d2cbd63b3b4075499a1683c00000060bfd97b8c0000000000000027858 reject +auth_resp dc8000022a8000001c8340c5ba1c37fcd40bf879995533a18e41c7733c83748c72e4a198db6f3f80000000 AuthResp { status: 220, error_code: 554, server_id: [131, 64, 197, 186, 28, 55, 252, 212, 11, 248, 121, 153, 85, 51, 161, 142, 65, 199, 115, 60, 131, 116, 140, 114, 228, 161, 152, 219], features: 12095 };43;[dc, 42, 2a, 1c, 83, 40, c5, ba, 1c, 37, fc, d4, 0b, f8, 79, 99, 55, 33, a1, 8e, 41, c7, 73, 3c, 83, 74, 8c, 72, e4, a1, 98, db, 6f, 3f, 00] +auth_resp 6680000bb3402c2131086ae83feeedc398d9b88363bc79892934a7e819d2cf0ea0a41c07b59a06a2791ca24314194d8c1a74e88064376ac000000000000000edaa AuthResp { status: 102, error_code: 2995, server_id: [33, 49, 8, 106, 232, 63, 238, 237, 195, 152, 217, 184, 131, 99, 188, 121, 137, 41, 52, 167, 232, 25, 210, 207, 14, 160, 164, 28, 7, 181, 154, 6, 162, 121, 28, 162, 67, 20, 25, 77, 140, 26, 116, 232], features: 6567786 };63;[66, 4b, b3, 2c, 21, 31, 08, 6a, e8, 3f, ee, ed, c3, 98, d9, b8, 83, 63, bc, 79, 89, 29, 34, a7, e8, 19, d2, cf, 0e, a0, a4, 1c, 07, b5, 9a, 06, a2, 79, 1c, a2, 43, 14, 19, 4d, 8c, 1a, 74, e8, 80, 64, 37, 6a, 00] +auth_resp 3a81406c4d4034f4489bc00401a8d0cf4440b1142c96d2ab0497ee6372806c8743b1ecec52a45511024058488c01dd81364732643b81dcf20bf823c00000007c5e0611c000000000000000 AuthResp { status: 58, error_code: 20999245, server_id: [244, 72, 155, 192, 4, 1, 168, 208, 207, 68, 64, 177, 20, 44, 150, 210, 171, 4, 151, 238, 99, 114, 128, 108, 135, 67, 177, 236, 236, 82, 164, 85, 17, 2, 64, 88, 72, 140, 1, 221, 129, 54, 71, 50, 100, 59, 129, 220, 242, 11, 248, 35], features: 2086536721 };75;[3a, 81, 40, 6c, 4d, 34, f4, 48, 9b, c0, 04, 01, a8, d0, cf, 44, 40, b1, 14, 2c, 96, d2, ab, 04, 97, ee, 63, 72, 80, 6c, 87, 43, b1, ec, ec, 52, a4, 55, 11, 02, 40, 58, 48, 8c, 01, dd, 81, 36, 47, 32, 64, 3b, 81, dc, f2, 0b, f8, 23, c0, 00, 00, 00, 7c, 5e, 06, 11, 00] +auth_resp 22c00000000000000c80000033d088999921870006b23ed44056e0bc3b6dc55b5787a938ff50485da1bc991985b29164997d339f4159d0ebf5d82de63ccda8d180637b7780000003f3e4db accept +auth_resp 1cc000000000000002c00000000000001601cf464706193259507dc67d0dca619e396add06cc46c000000000000000c0000000000000079000859fe49cbf AuthResp { status: 28, error_code: 2, server_id: [1, 207, 70, 71, 6, 25, 50, 89, 80, 125, 198, 125, 13, 202, 97, 158, 57, 106, 221, 6, 204, 70], features: 0 };62;[1c, 02, 16, 01, cf, 46, 47, 06, 19, 32, 59, 50, 7d, c6, 7d, 0d, ca, 61, 9e, 39, 6a, dd, 06, cc, 46, 00, 00] +auth_resp 858000019339298c6a64b778f1fd703a61a1a5661d0c8ffa679bb2a0d63f89bbdd867cc8e993cb3cef180175b61deda347c7d10cbf73664d99fc53c25c3d52c235233d011d3ce0800000001c98 AuthResp { status: 133, error_code: 403, server_id: [41, 140, 106, 100, 183, 120, 241, 253, 112, 58, 97, 161, 165, 102, 29, 12, 143, 250, 103, 155, 178, 160, 214, 63, 137, 187, 221, 134, 124, 200, 233, 147, 203, 60, 239, 24, 1, 117, 182, 29, 237, 163, 71, 199, 209, 12, 191, 115, 102, 77, 153, 252, 83, 194, 92, 61, 82], features: 159072106760191200 };75;[85, 41, 93, 39, 29, 8c, 6a, 64, b7, 78, f1, fd, 70, 3a, 61, a1, a5, 66, 1d, 0c, 8f, fa, 67, 9b, b2, a0, d6, 3f, 89, bb, dd, 86, 7c, c8, e9, 93, cb, 3c, ef, 18, 01, 75, b6, 1d, ed, a3, 47, c7, d1, 0c, bf, 73, 66, 4d, 99, fc, 53, c2, 5c, 3d, 52, c2, 35, 23, 3d, 01, 1d, 3c, e0, 00] +auth_resp f78000441c8000003d02a2378edcafb402131ac332d1e83d60aefe35bd40501a78b08031a1a309acf267f46473d6058b2e337a6a887509673329822b9bd838c2de468019edd2c00000000000035400 AuthResp { status: 247, error_code: 17436, server_id: [2, 162, 55, 142, 220, 175, 180, 2, 19, 26, 195, 50, 209, 232, 61, 96, 174, 254, 53, 189, 64, 80, 26, 120, 176, 128, 49, 161, 163, 9, 172, 242, 103, 244, 100, 115, 214, 5, 139, 46, 51, 122, 106, 136, 117, 9, 103, 51, 41, 130, 43, 155, 216, 56, 194, 222, 70, 128, 25, 237, 210], features: 852 };79;[f7, 80, 00, 44, 1c, 3d, 02, a2, 37, 8e, dc, af, b4, 02, 13, 1a, c3, 32, d1, e8, 3d, 60, ae, fe, 35, bd, 40, 50, 1a, 78, b0, 80, 31, a1, a3, 09, ac, f2, 67, f4, 64, 73, d6, 05, 8b, 2e, 33, 7a, 6a, 88, 75, 09, 67, 33, 29, 82, 2b, 9b, d8, 38, c2, de, 46, 80, 19, ed, d2, 43, 54, 00] +auth_resp 1404c00000000000001151e15c363d1ed1a1c7ed397b14a2cb85c1c0000000035aeeb94000 AuthResp { status: 20, error_code: 4, server_id: [81, 225, 92, 54, 61, 30, 209, 161, 199, 237, 57, 123, 20, 162, 203, 133, 193], features: 56291001 };37;[14, 04, 11, 51, e1, 5c, 36, 3d, 1e, d1, a1, c7, ed, 39, 7b, 14, a2, cb, 85, c1, 83, 5a, ee, b9, 00] +auth_resp 057b661d33b3f9787b4098b529cf314ba725c0da3c06db39d9c2e2a1f64115e518c000dcb48793162e00 AuthResp { status: 5, error_code: 15206, server_id: [51, 179, 249, 120, 123, 64, 152, 181, 41, 207, 49, 75, 167, 37, 192, 218, 60, 6, 219, 57, 217, 194, 226, 161, 246, 65, 21, 229, 24], features: 242667926787630 };42;[05, 7b, 66, 1d, 33, b3, f9, 78, 7b, 40, 98, b5, 29, cf, 31, 4b, a7, 25, c0, da, 3c, 06, db, 39, d9, c2, e2, a1, f6, 41, 15, e5, 18, c0, 00, dc, b4, 87, 93, 16, 2e, 00] +auth_resp 2d400f800000417e17c302cd853c75effe80a0906501ce05fbc007c7c982a90dbba03d0fff7d4455e7cf363ffe21fc4ff0acd913caf851484038d8d358837b4f66adb9852c48e8acc0000296ed57389f400083df06 reject +auth_resp 4d406880000021d847fe0ee7f9923fccf69a5f6c184a70f6be47322f3dd3f54f37f398d176a1a2b3c0000001a7c8396fc0000000000000072383ccd8dc66ab AuthResp { status: 77, error_code: 104, server_id: [216, 71, 254, 14, 231, 249, 146, 63, 204, 246, 154, 95, 108, 24, 74, 112, 246, 190, 71, 50, 47, 61, 211, 245, 79, 55, 243, 152, 209, 118, 161, 162, 179], features: 7109884271 };63;[4d, 40, 68, 21, d8, 47, fe, 0e, e7, f9, 92, 3f, cc, f6, 9a, 5f, 6c, 18, 4a, 70, f6, be, 47, 32, 2f, 3d, d3, f5, 4f, 37, f3, 98, d1, 76, a1, a2, b3, c0, 00, 00, 01, a7, c8, 39, 6f, 00] +auth_resp ece253b94467c32bfc80000003d6fd98402ac000000000000000 accept +auth_resp ff86b6faea402a98cda4e6532a90749882ca384df9f2d4630d74b9daa6b26b69c0b30e2c447e652a51b496928b175c6a1ec00000000005735f4000 AuthResp { status: 255, error_code: 112655082, server_id: [152, 205, 164, 230, 83, 42, 144, 116, 152, 130, 202, 56, 77, 249, 242, 212, 99, 13, 116, 185, 218, 166, 178, 107, 105, 192, 179, 14, 44, 68, 126, 101, 42, 81, 180, 150, 146, 139, 23, 92, 106, 30], features: 357215 };59;[ff, 86, b6, fa, ea, 2a, 98, cd, a4, e6, 53, 2a, 90, 74, 98, 82, ca, 38, 4d, f9, f2, d4, 63, 0d, 74, b9, da, a6, b2, 6b, 69, c0, b3, 0e, 2c, 44, 7e, 65, 2a, 51, b4, 96, 92, 8b, 17, 5c, 6a, 1e, 80, 05, 73, 5f, 00] +auth_resp cffb83a6547fd5e193c00000000000000ffc2cef6c8b0cb5f2931764c49a978dc00165951c41cc2880000000 accept +auth_resp b580162aff305d52cf0dcca392d7b090d5985c4f326dba59bcc2f082c4219cce531a126ba085e59253a89cac3e355f80fbd268148f12c0000000001a0b0b4000 AuthResp { status: 181, error_code: 1452799, server_id: [93, 82, 207, 13, 204, 163, 146, 215, 176, 144, 213, 152, 92, 79, 50, 109, 186, 89, 188, 194, 240, 130, 196, 33, 156, 206, 83, 26, 18, 107, 160, 133, 229, 146, 83, 168, 156, 172, 62, 53, 95, 128, 251, 210, 104, 20, 143, 18], features: 1706763 };64;[b5, 80, 16, 2a, ff, 30, 5d, 52, cf, 0d, cc, a3, 92, d7, b0, 90, d5, 98, 5c, 4f, 32, 6d, ba, 59, bc, c2, f0, 82, c4, 21, 9c, ce, 53, 1a, 12, 6b, a0, 85, e5, 92, 53, a8, 9c, ac, 3e, 35, 5f, 80, fb, d2, 68, 14, 8f, 12, 80, 1a, 0b, 0b, 00] +auth_resp 97834ab9f78000001ff322341e14c431f8bce0f12367a30c0b9c5438863e1df7064caa1dbdeca852c000007d2bb0bd4200900565 AuthResp { status: 151, error_code: 55228919, server_id: [243, 34, 52, 30, 20, 196, 49, 248, 188, 224, 241, 35, 103, 163, 12, 11, 156, 84, 56, 134, 62, 29, 247, 6, 76, 170, 29, 189, 236, 168, 82], features: 537603915074 };49;[97, 83, 4a, b9, f7, 1f, f3, 22, 34, 1e, 14, c4, 31, f8, bc, e0, f1, 23, 67, a3, 0c, 0b, 9c, 54, 38, 86, 3e, 1d, f7, 06, 4c, aa, 1d, bd, ec, a8, 52, c0, 00, 00, 7d, 2b, b0, bd, 42, 00] +auth_resp 488d539b0a4022b15bfe2b5ee07693d9f2fe341944dd0efadb710b208f7714a405c03dc03a09cd45868000005ec0000000000000012c AuthResp { status: 72, error_code: 223582986, server_id: [177, 91, 254, 43, 94, 224, 118, 147, 217, 242, 254, 52, 25, 68, 221, 14, 250, 219, 113, 11, 32, 143, 119, 20, 164, 5, 192, 61, 192, 58, 9, 205, 69, 134], features: 94 };54;[48, 8d, 53, 9b, 0a, 22, b1, 5b, fe, 2b, 5e, e0, 76, 93, d9, f2, fe, 34, 19, 44, dd, 0e, fa, db, 71, 0b, 20, 8f, 77, 14, a4, 05, c0, 3d, c0, 3a, 09, cd, 45, 86, 40, 5e, 00] +auth_resp 41da2f136dde84856a8000002d2bff6bd6745ff60259919cc4ef982f3ed12e369dc45053d8ed3a427d6b4be8a46178d86ea93088ec8bc32c7336c0002afb535bf1ec00 accept +auth_resp 3f4006c00000000000000feccae3c604a0f8b8b7837679b3d259c005f4566d32cf6fc000000000000000 AuthResp { status: 63, error_code: 6, server_id: [236, 202, 227, 198, 4, 160, 248, 184, 183, 131, 118, 121, 179, 210, 89], features: 1676026919964527 };42;[3f, 06, 0f, ec, ca, e3, c6, 04, a0, f8, b8, b7, 83, 76, 79, b3, d2, 59, c0, 05, f4, 56, 6d, 32, cf, 6f, 00] +auth_resp 3c1680000041132f1b1d6d213a7ccf0f15717c3ef210b683e87801c653ef6af4ed30ddc8ef277beabc39a544c935960f3c76b8f72bf0a8400d38ad5863f309c782b3dec64e382dc000000000005c70c000000000000005757b0b57e6 reject +auth_resp d8ae029d5a10f09ad742b9bc6704678be0cfb5eeae0c020237d7 AuthResp { status: 216, error_code: 771923290, server_id: [240, 154, 215, 66, 185, 188, 103, 4, 103, 139, 224, 207, 181, 238, 174, 12], features: 2 };26;[d8, ae, 02, 9d, 5a, 10, f0, 9a, d7, 42, b9, bc, 67, 04, 67, 8b, e0, cf, b5, ee, ae, 0c, 02, 00] +auth_resp 088d3357fdc0000000000000072599829b2bfde2c000008a071027db00fda931439e AuthResp { status: 8, error_code: 221468669, server_id: [37, 153, 130, 155, 43, 253, 226], features: 592823986139 };29;[08, 8d, 33, 57, fd, 07, 25, 99, 82, 9b, 2b, fd, e2, c0, 00, 00, 8a, 07, 10, 27, db, 00] +auth_resp d180012cf080000030adbaad8d2dddd3a04299c9653cbdefd0b6e8b29b8174b3a8d61a1775352d5590b443e70e3d18c3fe0693fc1a87e6d434c0000000a6b6a8d4c000000000000000 AuthResp { status: 209, error_code: 77040, server_id: [173, 186, 173, 141, 45, 221, 211, 160, 66, 153, 201, 101, 60, 189, 239, 208, 182, 232, 178, 155, 129, 116, 179, 168, 214, 26, 23, 117, 53, 45, 85, 144, 180, 67, 231, 14, 61, 24, 195, 254, 6, 147, 252, 26, 135, 230, 212, 52], features: 2796988628 };73;[d1, 80, 01, 2c, f0, 30, ad, ba, ad, 8d, 2d, dd, d3, a0, 42, 99, c9, 65, 3c, bd, ef, d0, b6, e8, b2, 9b, 81, 74, b3, a8, d6, 1a, 17, 75, 35, 2d, 55, 90, b4, 43, e7, 0e, 3d, 18, c3, fe, 06, 93, fc, 1a, 87, e6, d4, 34, c0, 00, 00, 00, a6, b6, a8, d4, 00] +auth_resp 048000b910c000000000000026e454626796aed84efe5ba633191e5e5ba9494ca56b50b93c39df1ef23612134025bcfd7d6c77c063342391cf4ade80000000 AuthResp { status: 4, error_code: 47376, server_id: [228, 84, 98, 103, 150, 174, 216, 78, 254, 91, 166, 51, 25, 30, 94, 91, 169, 73, 76, 165, 107, 80, 185, 60, 57, 223, 30, 242, 54, 18, 19, 64, 37, 188, 253, 125, 108, 119], features: 27923350069136094 };63;[04, 80, 00, b9, 10, 26, e4, 54, 62, 67, 96, ae, d8, 4e, fe, 5b, a6, 33, 19, 1e, 5e, 5b, a9, 49, 4c, a5, 6b, 50, b9, 3c, 39, df, 1e, f2, 36, 12, 13, 40, 25, bc, fd, 7d, 6c, 77, c0, 63, 34, 23, 91, cf, 4a, de, 00] +auth_resp 8f800803ee1d201cee8341047cc096d3b0af4e4788be8a4d937e6bc019377d6090b9a0c000001c357e80e2c000000000000000 AuthResp { status: 143, error_code: 525294, server_id: [32, 28, 238, 131, 65, 4, 124, 192, 150, 211, 176, 175, 78, 71, 136, 190, 138, 77, 147, 126, 107, 192, 25, 55, 125, 96, 144, 185, 160], features: 121156567266 };51;[8f, 80, 08, 03, ee, 1d, 20, 1c, ee, 83, 41, 04, 7c, c0, 96, d3, b0, af, 4e, 47, 88, be, 8a, 4d, 93, 7e, 6b, c0, 19, 37, 7d, 60, 90, b9, a0, c0, 00, 00, 1c, 35, 7e, 80, e2, 00] +auth_resp a6c0000000000018648000004294af9570e348d624f7b947d55404ef01cb849d6e4a1d2474f1461985cb8e814a8b81ae702fbf9c2e13e08ad79a7f720ae2f432fd3e3b2758db4ea1c55b17ee3de2dcc012481acd1caac44000 reject +auth_resp dec00000004fca18e2400da6cad135109efa7c3ec9178607c00038355d0cdb674000 AuthResp { status: 222, error_code: 1338644706, server_id: [166, 202, 209, 53, 16, 158, 250, 124, 62, 201, 23, 134, 7], features: 61801845545831 };34;[de, c0, 00, 00, 00, 4f, ca, 18, e2, 0d, a6, ca, d1, 35, 10, 9e, fa, 7c, 3e, c9, 17, 86, 07, c0, 00, 38, 35, 5d, 0c, db, 67, 00] +auth_resp 9cf5c98c0b0adec018c000000000000020ab02c22835e652b71454beab76a8c8315083f950c71bb7ae1b5808ed557ca40a80005adc00 accept +auth_resp b78007ab984000c00001475b7d491b40008a39 AuthResp { status: 183, error_code: 502680, server_id: [], features: 1405989243163 };17;[b7, 80, 07, ab, 98, 00, c0, 00, 01, 47, 5b, 7d, 49, 1b, 00] +auth_resp e6f57b1496ed88f8b62020c4724110dd82f48333ab830024757b67ff381a22ef298edf7212e3f714cab380010d8d80000000 accept +auth_resp 7b5bdc390532e26ff5d4a8ce257c4127940d248ecb7d177f96b6c4255daec41dc0a709c02031c22ec7348f8eaaff154035c903b48adab82d2be991db14004000 AuthResp { status: 123, error_code: 7132, server_id: [5, 50, 226, 111, 245, 212, 168, 206, 37, 124, 65, 39, 148, 13, 36, 142, 203, 125, 23, 127, 150, 182, 196, 37, 93, 174, 196, 29, 192, 167, 9, 192, 32, 49, 194, 46, 199, 52, 143, 142, 170, 255, 21, 64, 53, 201, 3, 180, 138, 218, 184, 45, 43, 233, 145, 219, 20], features: 0 };64;[7b, 5b, dc, 39, 05, 32, e2, 6f, f5, d4, a8, ce, 25, 7c, 41, 27, 94, 0d, 24, 8e, cb, 7d, 17, 7f, 96, b6, c4, 25, 5d, ae, c4, 1d, c0, a7, 09, c0, 20, 31, c2, 2e, c7, 34, 8f, 8e, aa, ff, 15, 40, 35, c9, 03, b4, 8a, da, b8, 2d, 2b, e9, 91, db, 14, 00, 00] +auth_resp a7c8ce41d76fbbca83402238cb42dfb889ec7acc9de755c818f1825e1443c5d4e6615bb0f89c4592442c8ab44e80000007c000000000000000 accept +auth_resp d880005178c00000000000003026877bb1161f4da1f3f3fa1d76cc7d5065e35864f4c8d79b1ec23475f2b4a8909a846911ad9f94ecc6642c333949406dc0000026c7fae17b80000000 AuthResp { status: 216, error_code: 20856, server_id: [38, 135, 123, 177, 22, 31, 77, 161, 243, 243, 250, 29, 118, 204, 125, 80, 101, 227, 88, 100, 244, 200, 215, 155, 30, 194, 52, 117, 242, 180, 168, 144, 154, 132, 105, 17, 173, 159, 148, 236, 198, 100, 44, 51, 57, 73, 64, 109], features: 166563864955 };73;[d8, 80, 00, 51, 78, 30, 26, 87, 7b, b1, 16, 1f, 4d, a1, f3, f3, fa, 1d, 76, cc, 7d, 50, 65, e3, 58, 64, f4, c8, d7, 9b, 1e, c2, 34, 75, f2, b4, a8, 90, 9a, 84, 69, 11, ad, 9f, 94, ec, c6, 64, 2c, 33, 39, 49, 40, 6d, c0, 00, 00, 26, c7, fa, e1, 7b, 00] +auth_resp 068000225c8000002d07cd8054afe21a9b172113cba0b5847542244641ff9fa7da4d6eac482a748a775d66a0d438253e7c93acd65da2c00003be05286662c00000000000000b90be9f9c416923ac7adf98 AuthResp { status: 6, error_code: 8796, server_id: [7, 205, 128, 84, 175, 226, 26, 155, 23, 33, 19, 203, 160, 181, 132, 117, 66, 36, 70, 65, 255, 159, 167, 218, 77, 110, 172, 72, 42, 116, 138, 119, 93, 102, 160, 212, 56, 37, 62, 124, 147, 172, 214, 93, 162], features: 4114665203298 };81;[06, 62, 5c, 2d, 07, cd, 80, 54, af, e2, 1a, 9b, 17, 21, 13, cb, a0, b5, 84, 75, 42, 24, 46, 41, ff, 9f, a7, da, 4d, 6e, ac, 48, 2a, 74, 8a, 77, 5d, 66, a0, d4, 38, 25, 3e, 7c, 93, ac, d6, 5d, a2, c0, 00, 03, be, 05, 28, 66, 62, 00] +auth_resp 7d80e4c23a4011fbfba706c68cb5ea852640a25c1a45c79ac0000000002f0f9bc000000000000004f0010004 AuthResp { status: 125, error_code: 14991930, server_id: [251, 251, 167, 6, 198, 140, 181, 234, 133, 38, 64, 162, 92, 26, 69, 199, 154], features: 3084187 };44;[7d, 80, e4, c2, 3a, 11, fb, fb, a7, 06, c6, 8c, b5, ea, 85, 26, 40, a2, 5c, 1a, 45, c7, 9a, 80, 2f, 0f, 9b, 00] +auth_resp 950380000006fe4c38a4e4cd419700 AuthResp { status: 149, error_code: 3, server_id: [254, 76, 56, 164, 228, 205], features: 407 };15;[95, 03, 06, fe, 4c, 38, a4, e4, cd, 41, 97, 00] +auth_resp 7ec00000000007a0234003570bcc803bd50bc00000000000000c79a49b55bd12ad36a644d5ff AuthResp { status: 126, error_code: 499747, server_id: [87, 11, 204], features: 3921163 };38;[7e, 80, 07, a0, 23, 03, 57, 0b, cc, 80, 3b, d5, 0b, 00] +auth_resp 25431d400797b0af107dc92bc0170367003cd94600 AuthResp { status: 37, error_code: 797, server_id: [151, 176, 175, 16, 125, 201, 43], features: 6477665384847686 };21;[25, 43, 1d, 07, 97, b0, af, 10, 7d, c9, 2b, c0, 17, 03, 67, 00, 3c, d9, 46, 00] +auth_resp bec00000000051cbfd402e675921e16738589aca3d650af306671080ed050e0fef093a1bfd289e395b458a7860b1a1a6bf771d3481f1abfee2800000eb4005ff83e03b28 AuthResp { status: 190, error_code: 5360637, server_id: [103, 89, 33, 225, 103, 56, 88, 154, 202, 61, 101, 10, 243, 6, 103, 16, 128, 237, 5, 14, 15, 239, 9, 58, 27, 253, 40, 158, 57, 91, 69, 138, 120, 96, 177, 161, 166, 191, 119, 29, 52, 129, 241, 171, 254, 226], features: 235 };68;[be, 80, 51, cb, fd, 2e, 67, 59, 21, e1, 67, 38, 58, 9a, ca, 3d, 65, 0a, f3, 06, 67, 10, 80, ed, 05, 0e, 0f, ef, 09, 3a, 1b, fd, 28, 9e, 39, 5b, 45, 8a, 78, 60, b1, a1, a6, bf, 77, 1d, 34, 81, f1, ab, fe, e2, 40, eb, 00] +auth_resp a5c000000000001e3f403d9d6dbd25f08a01ec929793d7c7f51e5fda2f9742996de561a9204f529e86a0dd1075de11667e6155220d71f36b5becf558f91910d643651f076b0d68d4c0112aa255542aa30abe3a43898140bf7aeef4f8137b674ac7e6 AuthResp { status: 165, error_code: 7743, server_id: [157, 109, 189, 37, 240, 138, 1, 236, 146, 151, 147, 215, 199, 245, 30, 95, 218, 47, 151, 66, 153, 109, 229, 97, 169, 32, 79, 82, 158, 134, 160, 221, 16, 117, 222, 17, 102, 126, 97, 85, 34, 13, 113, 243, 107, 91, 236, 245, 88, 249, 25, 16, 214, 67, 101, 31, 7, 107, 13, 104, 212], features: 4831951308728995 };91;[a5, 5e, 3f, 3d, 9d, 6d, bd, 25, f0, 8a, 01, ec, 92, 97, 93, d7, c7, f5, 1e, 5f, da, 2f, 97, 42, 99, 6d, e5, 61, a9, 20, 4f, 52, 9e, 86, a0, dd, 10, 75, de, 11, 66, 7e, 61, 55, 22, 0d, 71, f3, 6b, 5b, ec, f5, 58, f9, 19, 10, d6, 43, 65, 1f, 07, 6b, 0d, 68, d4, c0, 11, 2a, a2, 55, 54, 2a, a3, 00] +auth_resp 174000403417da6178fede80170d81518479d47af9bfb9871f864c9d2991fc76f1622d57d75224abb0298a5a16ed8102c97177416711639d33405d07c370fa5e8fec3d AuthResp { status: 23, error_code: 0, server_id: [23, 218, 97, 120, 254, 222, 128, 23, 13, 129, 81, 132, 121, 212, 122, 249, 191, 185, 135, 31, 134, 76, 157, 41, 145, 252, 118, 241, 98, 45, 87, 215, 82, 36, 171, 176, 41, 138, 90, 22, 237, 129, 2, 201, 113, 119, 65, 103, 17, 99, 157, 51], features: 93 };67;[17, 00, 34, 17, da, 61, 78, fe, de, 80, 17, 0d, 81, 51, 84, 79, d4, 7a, f9, bf, b9, 87, 1f, 86, 4c, 9d, 29, 91, fc, 76, f1, 62, 2d, 57, d7, 52, 24, ab, b0, 29, 8a, 5a, 16, ed, 81, 02, c9, 71, 77, 41, 67, 11, 63, 9d, 33, 40, 5d, 00] +auth_resp 88400240074463acd5817f57c000000002cda933076a804f218231c0 AuthResp { status: 136, error_code: 2, server_id: [68, 99, 172, 213, 129, 127, 87], features: 47032627 };28;[88, 02, 07, 44, 63, ac, d5, 81, 7f, 57, 82, cd, a9, 33, 00] +auth_resp d4846d90a4c00000000000002a81a90140d1f103a4cc482ac50262593a81ca8036ededba3721c16e4f8f42b943f4631a4742585f5b38790140094c53912fd380ebf62d AuthResp { status: 212, error_code: 74289316, server_id: [129, 169, 1, 64, 209, 241, 3, 164, 204, 72, 42, 197, 2, 98, 89, 58, 129, 202, 128, 54, 237, 237, 186, 55, 33, 193, 110, 79, 143, 66, 185, 67, 244, 99, 26, 71, 66, 88, 95, 91, 56, 121], features: 1 };67;[d4, 84, 6d, 90, a4, 2a, 81, a9, 01, 40, d1, f1, 03, a4, cc, 48, 2a, c5, 02, 62, 59, 3a, 81, ca, 80, 36, ed, ed, ba, 37, 21, c1, 6e, 4f, 8f, 42, b9, 43, f4, 63, 1a, 47, 42, 58, 5f, 5b, 38, 79, 01, 00] +auth_resp 45492b800000187b151977a4e53586a52c73af7dc59ff0afd22041351d30d18001b5a9c000000000000000 AuthResp { status: 69, error_code: 2347, server_id: [123, 21, 25, 119, 164, 229, 53, 134, 165, 44, 115, 175, 125, 197, 159, 240, 175, 210, 32, 65, 53, 29, 48, 209], features: 112041 };43;[45, 49, 2b, 18, 7b, 15, 19, 77, a4, e5, 35, 86, a5, 2c, 73, af, 7d, c5, 9f, f0, af, d2, 20, 41, 35, 1d, 30, d1, 80, 01, b5, a9, 00] +auth_resp 6280661d0f403338cea6b44ec9a790ca35912061c046139b79a2de5542744caf7bccf40fc202566ea328667f0e5ce974865e7b55732b77a38b878036485c4000 AuthResp { status: 98, error_code: 6692111, server_id: [56, 206, 166, 180, 78, 201, 167, 144, 202, 53, 145, 32, 97, 192, 70, 19, 155, 121, 162, 222, 85, 66, 116, 76, 175, 123, 204, 244, 15, 194, 2, 86, 110, 163, 40, 102, 127, 14, 92, 233, 116, 134, 94, 123, 85, 115, 43, 119, 163, 139, 135], features: 3557468 };64;[62, 80, 66, 1d, 0f, 33, 38, ce, a6, b4, 4e, c9, a7, 90, ca, 35, 91, 20, 61, c0, 46, 13, 9b, 79, a2, de, 55, 42, 74, 4c, af, 7b, cc, f4, 0f, c2, 02, 56, 6e, a3, 28, 66, 7f, 0e, 5c, e9, 74, 86, 5e, 7b, 55, 73, 2b, 77, a3, 8b, 87, 80, 36, 48, 5c, 00] +auth_resp c773ed401a501e9c2d89fcf62d74af38ef88e5562b9a5a09b41089ddea74b9804ee1dd00 AuthResp { status: 199, error_code: 13293, server_id: [80, 30, 156, 45, 137, 252, 246, 45, 116, 175, 56, 239, 136, 229, 86, 43, 154, 90, 9, 180, 16, 137, 221, 234, 116, 185], features: 5169629 };36;[c7, 73, ed, 1a, 50, 1e, 9c, 2d, 89, fc, f6, 2d, 74, af, 38, ef, 88, e5, 56, 2b, 9a, 5a, 09, b4, 10, 89, dd, ea, 74, b9, 80, 4e, e1, dd, 00] +auth_resp be401c4009a79ed1ad961486fe474026c000000000000000 AuthResp { status: 190, error_code: 28, server_id: [167, 158, 209, 173, 150, 20, 134, 254, 71], features: 38 };24;[be, 1c, 09, a7, 9e, d1, ad, 96, 14, 86, fe, 47, 26, 00] +auth_resp e380013c4a403907fbfd136aa27b2f0c4a71d67d39a00135fa3427208cccb550918996738f63d0c03b32ee60fcd08feb11f85c4e3f01f29371ad4d39ba70f6f7c000018a344f489a0f21251cff69bffbae588c0261ccf41b AuthResp { status: 227, error_code: 80970, server_id: [7, 251, 253, 19, 106, 162, 123, 47, 12, 74, 113, 214, 125, 57, 160, 1, 53, 250, 52, 39, 32, 140, 204, 181, 80, 145, 137, 150, 115, 143, 99, 208, 192, 59, 50, 238, 96, 252, 208, 143, 235, 17, 248, 92, 78, 63, 1, 242, 147, 113, 173, 77, 57, 186, 112, 246, 247], features: 1693094725786 };88;[e3, 80, 01, 3c, 4a, 39, 07, fb, fd, 13, 6a, a2, 7b, 2f, 0c, 4a, 71, d6, 7d, 39, a0, 01, 35, fa, 34, 27, 20, 8c, cc, b5, 50, 91, 89, 96, 73, 8f, 63, d0, c0, 3b, 32, ee, 60, fc, d0, 8f, eb, 11, f8, 5c, 4e, 3f, 01, f2, 93, 71, ad, 4d, 39, ba, 70, f6, f7, c0, 00, 01, 8a, 34, 4f, 48, 9a, 00] +auth_resp d940aa80000015b89bb2da2019e4a4c94e4477279bcf1c05946130bcc00000000000ede7c00000000000000f7c77505d0634151e8e261edae8274a AuthResp { status: 217, error_code: 170, server_id: [184, 155, 178, 218, 32, 25, 228, 164, 201, 78, 68, 119, 39, 155, 207, 28, 5, 148, 97, 48, 188], features: 60903 };59;[d9, 40, aa, 15, b8, 9b, b2, da, 20, 19, e4, a4, c9, 4e, 44, 77, 27, 9b, cf, 1c, 05, 94, 61, 30, bc, 80, 00, ed, e7, 00] +auth_resp a7c000000000000048c000000000000025fb9cfc9549cb085519646bb25253dc45b1544a3952f89ec1a1d6e52a46508b16244fc455bb80000000c00000000000000117 AuthResp { status: 167, error_code: 72, server_id: [251, 156, 252, 149, 73, 203, 8, 85, 25, 100, 107, 178, 82, 83, 220, 69, 177, 84, 74, 57, 82, 248, 158, 193, 161, 214, 229, 42, 70, 80, 139, 22, 36, 79, 196, 85, 187], features: 0 };67;[a7, 40, 48, 25, fb, 9c, fc, 95, 49, cb, 08, 55, 19, 64, 6b, b2, 52, 53, dc, 45, b1, 54, 4a, 39, 52, f8, 9e, c1, a1, d6, e5, 2a, 46, 50, 8b, 16, 24, 4f, c4, 55, bb, 00, 00] +auth_resp 4540013d15beb72ea019d4200bbe91e63a44b3bca821da0452d4dac40e193853b759c85f46ffe088c5cac6eda2329bf36d332f7f88ad4202a7b81ab742daf013d8c000102c4473d7c0c00000000000000a12d796248c114f1ff748 AuthResp { status: 69, error_code: 1, server_id: [21, 190, 183, 46, 160, 25, 212, 32, 11, 190, 145, 230, 58, 68, 179, 188, 168, 33, 218, 4, 82, 212, 218, 196, 14, 25, 56, 83, 183, 89, 200, 95, 70, 255, 224, 136, 197, 202, 198, 237, 162, 50, 155, 243, 109, 51, 47, 127, 136, 173, 66, 2, 167, 184, 26, 183, 66, 218, 240, 19, 216], features: 17782313048000 };91;[45, 01, 3d, 15, be, b7, 2e, a0, 19, d4, 20, 0b, be, 91, e6, 3a, 44, b3, bc, a8, 21, da, 04, 52, d4, da, c4, 0e, 19, 38, 53, b7, 59, c8, 5f, 46, ff, e0, 88, c5, ca, c6, ed, a2, 32, 9b, f3, 6d, 33, 2f, 7f, 88, ad, 42, 02, a7, b8, 1a, b7, 42, da, f0, 13, d8, c0, 00, 10, 2c, 44, 73, d7, c0, 00] +auth_resp 2b4018c000000000000003e52d79c0000000000003c700 AuthResp { status: 43, error_code: 24, server_id: [229, 45, 121], features: 967 };23;[2b, 18, 03, e5, 2d, 79, 43, c7, 00] +auth_resp dcc00000000001bb6ac0000000000000335931c203049ade9b883bb9ea0a6da87d5262c22d86bb391b706e74193cb6cfaa7bc18e799e5e50e1d74b27b59bbd3d5d5b66c9422f400a6dda96617e1c6d5c7926 AuthResp { status: 220, error_code: 113514, server_id: [89, 49, 194, 3, 4, 154, 222, 155, 136, 59, 185, 234, 10, 109, 168, 125, 82, 98, 194, 45, 134, 187, 57, 27, 112, 110, 116, 25, 60, 182, 207, 170, 123, 193, 142, 121, 158, 94, 80, 225, 215, 75, 39, 181, 155, 189, 61, 93, 91, 102, 201], features: 559 };82;[dc, 80, 01, bb, 6a, 33, 59, 31, c2, 03, 04, 9a, de, 9b, 88, 3b, b9, ea, 0a, 6d, a8, 7d, 52, 62, c2, 2d, 86, bb, 39, 1b, 70, 6e, 74, 19, 3c, b6, cf, aa, 7b, c1, 8e, 79, 9e, 5e, 50, e1, d7, 4b, 27, b5, 9b, bd, 3d, 5d, 5b, 66, c9, 42, 2f, 00] +auth_resp 54800289421f7a58089942e4912100f6614cece510ab37d5a879ad6c070cb43ebfba9766ec476000 accept +auth_resp 79c0000000000000181f67c9e882bf97665c574840ddb53d6d41519743e0a52c9da4ea2719ee82930ec00000000023c4d4c000000000000000 AuthResp { status: 121, error_code: 24, server_id: [103, 201, 232, 130, 191, 151, 102, 92, 87, 72, 64, 221, 181, 61, 109, 65, 81, 151, 67, 224, 165, 44, 157, 164, 234, 39, 25, 238, 130, 147, 14], features: 2344148 };57;[79, 18, 1f, 67, c9, e8, 82, bf, 97, 66, 5c, 57, 48, 40, dd, b5, 3d, 6d, 41, 51, 97, 43, e0, a5, 2c, 9d, a4, ea, 27, 19, ee, 82, 93, 0e, 80, 23, c4, d4, 00] +auth_resp 38f5c954a4ad6b37d5800000365ce72d3b5cb4566814af329d265fdbe23228194713f3b5023506036b0cb4bcf799732cb307aebd7f5c6aee3d3e126bacf2d0db6f8f37c000019dd9123f4680000000 accept +auth_resp 49c00000000000000140138a75584b44a6e5cb31369a825acb59a49b0a14c00000000000007900c493178cc6 AuthResp { status: 73, error_code: 1, server_id: [138, 117, 88, 75, 68, 166, 229, 203, 49, 54, 154, 130, 90, 203, 89, 164, 155, 10, 20], features: 121 };39;[49, 01, 13, 8a, 75, 58, 4b, 44, a6, e5, cb, 31, 36, 9a, 82, 5a, cb, 59, a4, 9b, 0a, 14, 40, 79, 00] +auth_resp 1c8002a23a800000405063e74040180ff140a4cfac05f60a7163c3973d7c1644fef8015c4095d264fd3e8dcdfca1106e619766cabfd0c18343c4a308a51f840b2edc47337b1554cc7d8009805b09a7a098eb8e6286a217 reject +auth_resp 0d809e2a5f2a9b2b8ea006be485f49c67696450e48ad90f86efd1134537c06140da88af33ac0e0c10af998354fa85c58c00000e88190f56700 AuthResp { status: 13, error_code: 10365535, server_id: [155, 43, 142, 160, 6, 190, 72, 95, 73, 198, 118, 150, 69, 14, 72, 173, 144, 248, 110, 253, 17, 52, 83, 124, 6, 20, 13, 168, 138, 243, 58, 192, 224, 193, 10, 249, 152, 53, 79, 168, 92, 88], features: 998606173543 };57;[0d, 80, 9e, 2a, 5f, 2a, 9b, 2b, 8e, a0, 06, be, 48, 5f, 49, c6, 76, 96, 45, 0e, 48, ad, 90, f8, 6e, fd, 11, 34, 53, 7c, 06, 14, 0d, a8, 8a, f3, 3a, c0, e0, c1, 0a, f9, 98, 35, 4f, a8, 5c, 58, c0, 00, 00, e8, 81, 90, f5, 67, 00] +auth_resp f3c0000000000103338000000148c0000017d2cacbc3800000039281ca AuthResp { status: 243, error_code: 66355, server_id: [72], features: 102320753603 };29;[f3, 80, 01, 03, 33, 01, 48, c0, 00, 00, 17, d2, ca, cb, c3, 00] +auth_resp 418a3d3629403e1f7be03f2dd49dd3f51b2112a2b9216d245442db4b72305638a2d76a1e9a0ceab54a80c548dc082d4c44234b6aa28b6a8867a959018812b838d816553dd7c00000727c0a120f80000000 AuthResp { status: 65, error_code: 171783721, server_id: [31, 123, 224, 63, 45, 212, 157, 211, 245, 27, 33, 18, 162, 185, 33, 109, 36, 84, 66, 219, 75, 114, 48, 86, 56, 162, 215, 106, 30, 154, 12, 234, 181, 74, 128, 197, 72, 220, 8, 45, 76, 68, 35, 75, 106, 162, 139, 106, 136, 103, 169, 89, 1, 136, 18, 184, 56, 216, 22, 85, 61, 215], features: 491707306511 };81;[41, 8a, 3d, 36, 29, 3e, 1f, 7b, e0, 3f, 2d, d4, 9d, d3, f5, 1b, 21, 12, a2, b9, 21, 6d, 24, 54, 42, db, 4b, 72, 30, 56, 38, a2, d7, 6a, 1e, 9a, 0c, ea, b5, 4a, 80, c5, 48, dc, 08, 2d, 4c, 44, 23, 4b, 6a, a2, 8b, 6a, 88, 67, a9, 59, 01, 88, 12, b8, 38, d8, 16, 55, 3d, d7, c0, 00, 00, 72, 7c, 0a, 12, 0f, 00] +auth_resp e3800470e2403db61040404b2a7b7cb9db3c14fd4b196d86eeb188bd4330680a3e83bc67dd473438b6b04a62c2b4c04961c7ba1f7bdb35639ce6f4a3c619bbd04641117cc00000007e2555e24000 AuthResp { status: 227, error_code: 291042, server_id: [182, 16, 64, 64, 75, 42, 123, 124, 185, 219, 60, 20, 253, 75, 25, 109, 134, 238, 177, 136, 189, 67, 48, 104, 10, 62, 131, 188, 103, 221, 71, 52, 56, 182, 176, 74, 98, 194, 180, 192, 73, 97, 199, 186, 31, 123, 219, 53, 99, 156, 230, 244, 163, 198, 25, 187, 208, 70, 65, 17, 124], features: 2116376034 };78;[e3, 80, 04, 70, e2, 3d, b6, 10, 40, 40, 4b, 2a, 7b, 7c, b9, db, 3c, 14, fd, 4b, 19, 6d, 86, ee, b1, 88, bd, 43, 30, 68, 0a, 3e, 83, bc, 67, dd, 47, 34, 38, b6, b0, 4a, 62, c2, b4, c0, 49, 61, c7, ba, 1f, 7b, db, 35, 63, 9c, e6, f4, a3, c6, 19, bb, d0, 46, 41, 11, 7c, c0, 00, 00, 00, 7e, 25, 55, e2, 00] +auth_resp 614007800000103c94030c4eb29e04fbb4cf443e667b8f829d4f6d4000 AuthResp { status: 97, error_code: 7, server_id: [60, 148, 3, 12, 78, 178, 158, 4, 251, 180, 207, 68, 62, 102, 123, 143], features: 43863917 };29;[61, 07, 10, 3c, 94, 03, 0c, 4e, b2, 9e, 04, fb, b4, cf, 44, 3e, 66, 7b, 8f, 82, 9d, 4f, 6d, 00] +auth_resp cb827c0efbc000000000000037d3aed973aedad8c2264f119c163c9866b111d25c1ef1aefd0f8b2786901251aaa325b194eabcccdc099f20e25c415ff2bab4a76c40d67bc0ba2c93bb5280d580000000 AuthResp { status: 203, error_code: 41684731, server_id: [211, 174, 217, 115, 174, 218, 216, 194, 38, 79, 17, 156, 22, 60, 152, 102, 177, 17, 210, 92, 30, 241, 174, 253, 15, 139, 39, 134, 144, 18, 81, 170, 163, 37, 177, 148, 234, 188, 204, 220, 9, 159, 32, 226, 92, 65, 95, 242, 186, 180, 167, 108, 64, 214, 123], features: 52403358682742997 };80;[cb, 82, 7c, 0e, fb, 37, d3, ae, d9, 73, ae, da, d8, c2, 26, 4f, 11, 9c, 16, 3c, 98, 66, b1, 11, d2, 5c, 1e, f1, ae, fd, 0f, 8b, 27, 86, 90, 12, 51, aa, a3, 25, b1, 94, ea, bc, cc, dc, 09, 9f, 20, e2, 5c, 41, 5f, f2, ba, b4, a7, 6c, 40, d6, 7b, c0, ba, 2c, 93, bb, 52, 80, d5, 00] +auth_resp 1a8000001e8000001f511d2648ffcc11b430106c1f557c3319446652449db1f8b74077dee7cde1bbc000000003be78814000 AuthResp { status: 26, error_code: 30, server_id: [81, 29, 38, 72, 255, 204, 17, 180, 48, 16, 108, 31, 85, 124, 51, 25, 68, 102, 82, 68, 157, 177, 248, 183, 64, 119, 222, 231, 205, 225, 187], features: 62814337 };50;[1a, 1e, 1f, 51, 1d, 26, 48, ff, cc, 11, b4, 30, 10, 6c, 1f, 55, 7c, 33, 19, 44, 66, 52, 44, 9d, b1, f8, b7, 40, 77, de, e7, cd, e1, bb, 83, be, 78, 81, 00] +auth_resp afd4cad565d816af97403b94672b53f44687164876474062ff85527a27b8cd375116f3eac6dc0f279e3c9a88266b71fbed0fbccb2d0cb9037211343c9ea59814905aefa1576ac000003354a681af40058295d24509 accept +auth_resp 2bad01fb348000001bcc2a3eeef63a4e942aa3136ce44f9101ba08d1c0667753a2e290564dbf4000 AuthResp { status: 43, error_code: 755104564, server_id: [204, 42, 62, 238, 246, 58, 78, 148, 42, 163, 19, 108, 228, 79, 145, 1, 186, 8, 209, 192, 102, 119, 83, 162, 226, 144, 86], features: 3519 };40;[2b, ad, 01, fb, 34, 1b, cc, 2a, 3e, ee, f6, 3a, 4e, 94, 2a, a3, 13, 6c, e4, 4f, 91, 01, ba, 08, d1, c0, 66, 77, 53, a2, e2, 90, 56, 4d, bf, 00] +auth_resp 185cf48000002ce3c4a8230566058685f3b066a16b0bad3fdff97d27692a6506402f4ea8d8c8c7df3ba2c8b6c827e484d67a33825aa8f3c00000000000000ccda3ff30422e21dc8ec71c5d AuthResp { status: 24, error_code: 7412, server_id: [227, 196, 168, 35, 5, 102, 5, 134, 133, 243, 176, 102, 161, 107, 11, 173, 63, 223, 249, 125, 39, 105, 42, 101, 6, 64, 47, 78, 168, 216, 200, 199, 223, 59, 162, 200, 182, 200, 39, 228, 132, 214, 122, 51], features: 39495923 };75;[18, 5c, f4, 2c, e3, c4, a8, 23, 05, 66, 05, 86, 85, f3, b0, 66, a1, 6b, 0b, ad, 3f, df, f9, 7d, 27, 69, 2a, 65, 06, 40, 2f, 4e, a8, d8, c8, c7, df, 3b, a2, c8, b6, c8, 27, e4, 84, d6, 7a, 33, 82, 5a, a8, f3, 00] +auth_resp 55400240151e2eeedf653f4c6940d00705a9733d1ee17182732c95807706c000000000000000 AuthResp { status: 85, error_code: 2, server_id: [30, 46, 238, 223, 101, 63, 76, 105, 64, 208, 7, 5, 169, 115, 61, 30, 225, 113, 130, 115, 44], features: 360740614 };38;[55, 02, 15, 1e, 2e, ee, df, 65, 3f, 4c, 69, 40, d0, 07, 05, a9, 73, 3d, 1e, e1, 71, 82, 73, 2c, 95, 80, 77, 06, 00] +auth_resp f780120a3ec00000000000000e078256d4367d681f02b27e7ed8dec3151b0dae4108e7006b AuthResp { status: 247, error_code: 1182270, server_id: [7, 130, 86, 212, 54, 125, 104, 31, 2, 178, 126, 126, 216, 222], features: 222113502196730087 };36;[f7, 80, 12, 0a, 3e, 0e, 07, 82, 56, d4, 36, 7d, 68, 1f, 02, b2, 7e, 7e, d8, de, c3, 15, 1b, 0d, ae, 41, 08, e7, 00] +auth_resp dd4715c00000000000001aea11055302cabfde6310fbfa16ba36798f0ffa44d5a2a4b46be5c0000001b58a53b700 AuthResp { status: 221, error_code: 1813, server_id: [234, 17, 5, 83, 2, 202, 191, 222, 99, 16, 251, 250, 22, 186, 54, 121, 143, 15, 250, 68, 213, 162, 164, 180, 107, 229], features: 7340708791 };46;[dd, 47, 15, 1a, ea, 11, 05, 53, 02, ca, bf, de, 63, 10, fb, fa, 16, ba, 36, 79, 8f, 0f, fa, 44, d5, a2, a4, b4, 6b, e5, c0, 00, 00, 01, b5, 8a, 53, b7, 00] +auth_resp 5e430540421ad8d138a68ba1634cb1b1cac9b40dd375bb764eb54550d2cd636592e071337821551c5d8ffc6ff0dc4e717586e43fc73c505c00b6db40de7057aee55d30c2dab80b800666508000000169 reject +auth_resp 898000000c40280f512ea80af79967cf3159656481eba5c3361a0f6df5e531fb4ae08ec453d83c33ec60143815e7eac00000019ea8bb6800 AuthResp { status: 137, error_code: 12, server_id: [15, 81, 46, 168, 10, 247, 153, 103, 207, 49, 89, 101, 100, 129, 235, 165, 195, 54, 26, 15, 109, 245, 229, 49, 251, 74, 224, 142, 196, 83, 216, 60, 51, 236, 96, 20, 56, 21, 231, 234], features: 6956825448 };56;[89, 0c, 28, 0f, 51, 2e, a8, 0a, f7, 99, 67, cf, 31, 59, 65, 64, 81, eb, a5, c3, 36, 1a, 0f, 6d, f5, e5, 31, fb, 4a, e0, 8e, c4, 53, d8, 3c, 33, ec, 60, 14, 38, 15, e7, ea, c0, 00, 00, 01, 9e, a8, bb, 68, 00] +auth_resp 87c0000000000000034029373f028cddd5880c59841155a15aedebd808512170e79e408750ea5fc69be90a9c6334472867a74b30c0000000000000274000 AuthResp { status: 135, error_code: 3, server_id: [55, 63, 2, 140, 221, 213, 136, 12, 89, 132, 17, 85, 161, 90, 237, 235, 216, 8, 81, 33, 112, 231, 158, 64, 135, 80, 234, 95, 198, 155, 233, 10, 156, 99, 52, 71, 40, 103, 167, 75, 48], features: 39 };62;[87, 03, 29, 37, 3f, 02, 8c, dd, d5, 88, 0c, 59, 84, 11, 55, a1, 5a, ed, eb, d8, 08, 51, 21, 70, e7, 9e, 40, 87, 50, ea, 5f, c6, 9b, e9, 0a, 9c, 63, 34, 47, 28, 67, a7, 4b, 30, 27, 00] +auth_resp a680279e798000000dadbddc6804b2d6925445ed97d4800057d54000 AuthResp { status: 166, error_code: 2596473, server_id: [173, 189, 220, 104, 4, 178, 214, 146, 84, 69, 237, 151, 212], features: 22485 };28;[a6, 80, 27, 9e, 79, 0d, ad, bd, dc, 68, 04, b2, d6, 92, 54, 45, ed, 97, d4, 80, 00, 57, d5, 00] +auth_resp 558005856ec0000000000000402290be145aaa35d1a1fb61f44010caf4a3995f144ee2cc496f120b3e1c3380b35728c971611438d0a7a192b67e36fc026828af0d8525b2e0e8e3ee602d34160ac0000e3468d05ba2c00000000000000fc9d3f3a142f8528daccef0be2c56f5 reject +auth_resp 69800a88a5c00000000000000946aec851c9fd6bdd598041a5034000ec89be AuthResp { status: 105, error_code: 690341, server_id: [70, 174, 200, 81, 201, 253, 107, 221, 89], features: 4302083 };28;[69, 80, 0a, 88, a5, 09, 46, ae, c8, 51, c9, fd, 6b, dd, 59, 80, 41, a5, 03, 00] +auth_resp 6e85831961c00000000000002a9a384165e129cef120ae8c66e9c791d83e3e9301f9eda1a52ab147063edd730f91424c66f6e8362beb138001063380000000 AuthResp { status: 110, error_code: 92477793, server_id: [154, 56, 65, 101, 225, 41, 206, 241, 32, 174, 140, 102, 233, 199, 145, 216, 62, 62, 147, 1, 249, 237, 161, 165, 42, 177, 71, 6, 62, 221, 115, 15, 145, 66, 76, 102, 246, 232, 54, 43, 235, 19], features: 67123 };63;[6e, 85, 83, 19, 61, 2a, 9a, 38, 41, 65, e1, 29, ce, f1, 20, ae, 8c, 66, e9, c7, 91, d8, 3e, 3e, 93, 01, f9, ed, a1, a5, 2a, b1, 47, 06, 3e, dd, 73, 0f, 91, 42, 4c, 66, f6, e8, 36, 2b, eb, 13, 80, 01, 06, 33, 00] +auth_resp a04033403a81e3ccd3a46369f2bd22062f958f686f1defb9c375c07e8c52c78d6cf99d2f994ca856fbdfc09f547e6caa7cdcfd8e846b8c993189f896fc53a4c029b8d94f6527c9800000084090af6d0ac7df15 AuthResp { status: 160, error_code: 51, server_id: [129, 227, 204, 211, 164, 99, 105, 242, 189, 34, 6, 47, 149, 143, 104, 111, 29, 239, 185, 195, 117, 192, 126, 140, 82, 199, 141, 108, 249, 157, 47, 153, 76, 168, 86, 251, 223, 192, 159, 84, 126, 108, 170, 124, 220, 253, 142, 132, 107, 140, 153, 49, 137, 248, 150, 252, 83, 164], features: 11743717524580297 };83;[a0, 33, 3a, 81, e3, cc, d3, a4, 63, 69, f2, bd, 22, 06, 2f, 95, 8f, 68, 6f, 1d, ef, b9, c3, 75, c0, 7e, 8c, 52, c7, 8d, 6c, f9, 9d, 2f, 99, 4c, a8, 56, fb, df, c0, 9f, 54, 7e, 6c, aa, 7c, dc, fd, 8e, 84, 6b, 8c, 99, 31, 89, f8, 96, fc, 53, a4, c0, 29, b8, d9, 4f, 65, 27, c9, 00] +auth_resp b8da04cc984e6245e7c00000000000000eb82998525d512181ed44d3be3a21801eef6a00 accept +auth_resp c441692d84bef42ba1e8b63f7edaa3bd44cddc457fc86cf2ff9beccc826cc2cd7f01e5372ecf29729a3d2375c1a70b271943f34000 AuthResp { status: 196, error_code: 361, server_id: [132, 190, 244, 43, 161, 232, 182, 63, 126, 218, 163, 189, 68, 205, 220, 69, 127, 200, 108, 242, 255, 155, 236, 204, 130, 108, 194, 205, 127, 1, 229, 55, 46, 207, 41, 114, 154, 61, 35, 117, 193, 167, 11, 39, 25], features: 1011 };53;[c4, 41, 69, 2d, 84, be, f4, 2b, a1, e8, b6, 3f, 7e, da, a3, bd, 44, cd, dc, 45, 7f, c8, 6c, f2, ff, 9b, ec, cc, 82, 6c, c2, cd, 7f, 01, e5, 37, 2e, cf, 29, 72, 9a, 3d, 23, 75, c1, a7, 0b, 27, 19, 43, f3, 00] +auth_resp 3d813a4fd54041faafb78f3ea28a6d4554b2eaa8d0382b36ff823654cd22e16c640d5b017cff1e711f0bacd158ee3aa694a79af8e9a7c30f3dc546c9786b263612ed3c957cfa22268003825608b199aff61d815d85 reject +auth_resp b380007d6b3927ce0de0f6a20806fed1d9f273c435d56d419db849dd5729afc9a7c0b0222da75de6406e41acc3275d01f33f895a45ad4e268cf393d2c2bde7c4afb3a5271962474005f227fab9ee619cbd AuthResp { status: 179, error_code: 32107, server_id: [39, 206, 13, 224, 246, 162, 8, 6, 254, 209, 217, 242, 115, 196, 53, 213, 109, 65, 157, 184, 73, 221, 87, 41, 175, 201, 167, 192, 176, 34, 45, 167, 93, 230, 64, 110, 65, 172, 195, 39, 93, 1, 243, 63, 137, 90, 69, 173, 78, 38, 140, 243, 147, 210, 194, 189, 231], features: 337686018983027271 };78;[b3, 80, 00, 7d, 6b, 39, 27, ce, 0d, e0, f6, a2, 08, 06, fe, d1, d9, f2, 73, c4, 35, d5, 6d, 41, 9d, b8, 49, dd, 57, 29, af, c9, a7, c0, b0, 22, 2d, a7, 5d, e6, 40, 6e, 41, ac, c3, 27, 5d, 01, f3, 3f, 89, 5a, 45, ad, 4e, 26, 8c, f3, 93, d2, c2, bd, e7, c4, af, b3, a5, 27, 19, 62, 47, 00] +auth_resp 55c00000000005f8978000000f4f58bbc3bde32089da6b1771b160b38001f6dd06874f5614f3f7 AuthResp { status: 85, error_code: 391319, server_id: [79, 88, 187, 195, 189, 227, 32, 137, 218, 107, 23, 113, 177, 96, 179], features: 128733 };39;[55, 80, 05, f8, 97, 0f, 4f, 58, bb, c3, bd, e3, 20, 89, da, 6b, 17, 71, b1, 60, b3, 80, 01, f6, dd, 00] +auth_resp 754000800000017f84e3514a4000 AuthResp { status: 117, error_code: 0, server_id: [127], features: 82006346 };14;[75, 00, 01, 7f, 84, e3, 51, 4a, 00] +auth_resp 8ac00000000009de47c00000000000004205b265843c161b995611177adac62e0f55df94a293a466d05c87991e82c4cbad570887ef351f409832082d75be0ce0ae64980bf5d783fdefdaba34f04c3dbc033319c00000020fe89437c000000000000000 reject +auth_resp 9697a6fb3980000027687de43a61884ecd321c7a91e7df5486c8aa965ab8f8e08b77fad93c2a83a9d54ff957be9d3a7c809a49a9c000000000000000a2dc045e AuthResp { status: 150, error_code: 396819257, server_id: [104, 125, 228, 58, 97, 136, 78, 205, 50, 28, 122, 145, 231, 223, 84, 134, 200, 170, 150, 90, 184, 248, 224, 139, 119, 250, 217, 60, 42, 131, 169, 213, 79, 249, 87, 190, 157, 58, 124], features: 10111401 };60;[96, 97, a6, fb, 39, 27, 68, 7d, e4, 3a, 61, 88, 4e, cd, 32, 1c, 7a, 91, e7, df, 54, 86, c8, aa, 96, 5a, b8, f8, e0, 8b, 77, fa, d9, 3c, 2a, 83, a9, d5, 4f, f9, 57, be, 9d, 3a, 7c, 80, 9a, 49, a9, 00] +auth_resp 0ec00000000000000240378bab1ad1a8657f3a9e479b88dd46e9542365d051ff7abab2f3e1a791d2d09a0a99ec270ca4ae7c4f46adc0c4d51d4832351f403c7ae530400e80000000 AuthResp { status: 14, error_code: 2, server_id: [139, 171, 26, 209, 168, 101, 127, 58, 158, 71, 155, 136, 221, 70, 233, 84, 35, 101, 208, 81, 255, 122, 186, 178, 243, 225, 167, 145, 210, 208, 154, 10, 153, 236, 39, 12, 164, 174, 124, 79, 70, 173, 192, 196, 213, 29, 72, 50, 53, 31, 64, 60, 122, 229, 48], features: 14 };72;[0e, 02, 37, 8b, ab, 1a, d1, a8, 65, 7f, 3a, 9e, 47, 9b, 88, dd, 46, e9, 54, 23, 65, d0, 51, ff, 7a, ba, b2, f3, e1, a7, 91, d2, d0, 9a, 0a, 99, ec, 27, 0c, a4, ae, 7c, 4f, 46, ad, c0, c4, d5, 1d, 48, 32, 35, 1f, 40, 3c, 7a, e5, 30, 0e, 00] +tcp_req 00030b6578616d706c652e636f6d01bb00 ConnectTcpReq { flags: 0, address_type: Domain, host: [101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109], port: 443 };17;[00, 03, 0b, 65, 78, 61, 6d, 70, 6c, 65, 2e, 63, 6f, 6d, 01, bb, 00] +tcp_req 0001047f000001005000 ConnectTcpReq { flags: 0, address_type: Ipv4, host: [127, 0, 0, 1], port: 80 };10;[00, 01, 04, 7f, 00, 00, 01, 00, 50, 00] +tcp_req reject +tcp_req 80 reject +tcp_req 8000 reject +tcp_req 800042 reject +tcp_req 800042d0 reject +tcp_req 800042d003 reject +tcp_req 800042d00380 reject +tcp_req 800042d0038000 reject +tcp_req 800042d003800000 reject +tcp_req 800042d0038000006d reject +tcp_req 800042d0038000006dfb reject +tcp_req 800042d0038000006dfb42 reject +tcp_req 800042d0038000006dfb4244 reject +tcp_req 800042d0038000006dfb424429 reject +tcp_req 800042d0038000006dfb42442990 reject +tcp_req 800042d0038000006dfb42442990d8 reject +tcp_req 800042d0038000006dfb42442990d8e5 reject +tcp_req 800042d0038000006dfb42442990d8e52b reject +tcp_req 800042d0038000006dfb42442990d8e52beb reject +tcp_req 800042d0038000006dfb42442990d8e52beb09 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5a reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1b reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1bab reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaa reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0de reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1de reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded3 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded35878 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab166396 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16639675 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d2 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa3365 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33659324 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241f reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe448 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4481271 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d7 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a4 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f11 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f8840 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a30 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bf reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd6 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2b reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf7 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5e reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef8 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f1 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f141 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b7 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d0 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05b reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbd reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdab reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c436 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e88 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5d reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d067 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a3814 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140e reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d3 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c0 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c09806 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f405 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056b reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc0 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc000 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc00000 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc0000000 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc000000000 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc00000000000 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc0000000000000 reject +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc000000000000000 ConnectTcpReq { flags: 17104, address_type: Domain, host: [251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76, 163, 215, 132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244], port: 1387 };128;[80, 00, 42, d0, 03, 40, 6d, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, a3, d7, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 05, 6b, 00] +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc000000000000000e9 ConnectTcpReq { flags: 17104, address_type: Domain, host: [251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76, 163, 215, 132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244], port: 1387 };128;[80, 00, 42, d0, 03, 40, 6d, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, a3, d7, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 05, 6b, 00] +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc000000000000000e9b8 ConnectTcpReq { flags: 17104, address_type: Domain, host: [251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76, 163, 215, 132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244], port: 1387 };128;[80, 00, 42, d0, 03, 40, 6d, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, a3, d7, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 05, 6b, 00] +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc000000000000000e9b888 ConnectTcpReq { flags: 17104, address_type: Domain, host: [251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76, 163, 215, 132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244], port: 1387 };128;[80, 00, 42, d0, 03, 40, 6d, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, a3, d7, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 05, 6b, 00] +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc000000000000000e9b88845 ConnectTcpReq { flags: 17104, address_type: Domain, host: [251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76, 163, 215, 132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244], port: 1387 };128;[80, 00, 42, d0, 03, 40, 6d, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, a3, d7, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 05, 6b, 00] +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc000000000000000e9b88845f2 ConnectTcpReq { flags: 17104, address_type: Domain, host: [251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76, 163, 215, 132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244], port: 1387 };128;[80, 00, 42, d0, 03, 40, 6d, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, a3, d7, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 05, 6b, 00] +tcp_req 800042d0038000006dfb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4056bc000000000000000e9b88845f2d5 ConnectTcpReq { flags: 17104, address_type: Domain, host: [251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76, 163, 215, 132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244], port: 1387 };128;[80, 00, 42, d0, 03, 40, 6d, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, a3, d7, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 05, 6b, 00] +tcp_req c00000000000000e01c0000000000000e6e111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48682d37a4c9d4896d14dcc8be6fcb3cae2a8ed41906905a00c79116c42f6efc3abeb588cd8433e48b916d29a040f1f56dfaae34040bfee40c54373755fff8070a31ca4e98ed392278b7de69abdbccd6ed89165eb4cbdf27f34cdb610cfbb98a6eb961a9f34e79c941b28dceab810b061154399cb6cb22960706ecdc059f8ab72d06629b4d6881b73d140f462e92c000000000000000 ConnectTcpReq { flags: 14, address_type: Ipv4, host: [225, 17, 167, 150, 130, 93, 113, 102, 21, 200, 54, 126, 138, 121, 96, 231, 34, 98, 186, 205, 209, 72, 172, 95, 102, 63, 151, 112, 137, 150, 127, 105, 153, 50, 30, 82, 5, 84, 163, 148, 204, 77, 58, 12, 100, 228, 74, 221, 134, 136, 76, 55, 227, 50, 43, 251, 227, 156, 202, 123, 83, 167, 77, 217, 23, 113, 126, 173, 44, 101, 122, 211, 16, 172, 2, 236, 62, 75, 214, 150, 0, 200, 1, 18, 249, 1, 92, 94, 95, 29, 72, 104, 45, 55, 164, 201, 212, 137, 109, 20, 220, 200, 190, 111, 203, 60, 174, 42, 142, 212, 25, 6, 144, 90, 0, 199, 145, 22, 196, 47, 110, 252, 58, 190, 181, 136, 205, 132, 51, 228, 139, 145, 109, 41, 160, 64, 241, 245, 109, 250, 174, 52, 4, 11, 254, 228, 12, 84, 55, 55, 85, 255, 248, 7, 10, 49, 202, 78, 152, 237, 57, 34, 120, 183, 222, 105, 171, 219, 204, 214, 237, 137, 22, 94, 180, 203, 223, 39, 243, 76, 219, 97, 12, 251, 185, 138, 110, 185, 97, 169, 243, 78, 121, 201, 65, 178, 141, 206, 171, 129, 11, 6, 17, 84, 57, 156, 182, 203, 34, 150, 7, 6, 236, 220, 5, 159, 138, 183, 45, 6, 98, 155, 77, 104, 129, 183, 61, 20, 15, 70], port: 11922 };257;[0e, 01, 40, e6, e1, 11, a7, 96, 82, 5d, 71, 66, 15, c8, 36, 7e, 8a, 79, 60, e7, 22, 62, ba, cd, d1, 48, ac, 5f, 66, 3f, 97, 70, 89, 96, 7f, 69, 99, 32, 1e, 52, 05, 54, a3, 94, cc, 4d, 3a, 0c, 64, e4, 4a, dd, 86, 88, 4c, 37, e3, 32, 2b, fb, e3, 9c, ca, 7b, 53, a7, 4d, d9, 17, 71, 7e, ad, 2c, 65, 7a, d3, 10, ac, 02, ec, 3e, 4b, d6, 96, 00, c8, 01, 12, f9, 01, 5c, 5e, 5f, 1d, 48, 68, 2d, 37, a4, c9, d4, 89, 6d, 14, dc, c8, be, 6f, cb, 3c, ae, 2a, 8e, d4, 19, 06, 90, 5a, 00, c7, 91, 16, c4, 2f, 6e, fc, 3a, be, b5, 88, cd, 84, 33, e4, 8b, 91, 6d, 29, a0, 40, f1, f5, 6d, fa, ae, 34, 04, 0b, fe, e4, 0c, 54, 37, 37, 55, ff, f8, 07, 0a, 31, ca, 4e, 98, ed, 39, 22, 78, b7, de, 69, ab, db, cc, d6, ed, 89, 16, 5e, b4, cb, df, 27, f3, 4c, db, 61, 0c, fb, b9, 8a, 6e, b9, 61, a9, f3, 4e, 79, c9, 41, b2, 8d, ce, ab, 81, 0b, 06, 11, 54, 39, 9c, b6, cb, 22, 96, 07, 06, ec, dc, 05, 9f, 8a, b7, 2d, 06, 62, 9b, 4d, 68, 81, b7, 3d, 14, 0f, 46, 2e, 92, 00] +tcp_req 0103405e0456b9d4cfa52935adb2d90b422e69297d8132b12d4bee0c567158668dce9817977065d567234e29dc1c0b073bf895cd7c66ace40ecab63c2d2a9be3487e51cd29f1ed51a93fe5a5c892b3dccdef9372f6478d38d1ce0428cddc2a44f8debd69c000000000000000 ConnectTcpReq { flags: 1, address_type: Domain, host: [4, 86, 185, 212, 207, 165, 41, 53, 173, 178, 217, 11, 66, 46, 105, 41, 125, 129, 50, 177, 45, 75, 238, 12, 86, 113, 88, 102, 141, 206, 152, 23, 151, 112, 101, 213, 103, 35, 78, 41, 220, 28, 11, 7, 59, 248, 149, 205, 124, 102, 172, 228, 14, 202, 182, 60, 45, 42, 155, 227, 72, 126, 81, 205, 41, 241, 237, 81, 169, 63, 229, 165, 200, 146, 179, 220, 205, 239, 147, 114, 246, 71, 141, 56, 209, 206, 4, 40, 205, 220, 42, 68, 248, 222], port: 48489 };108;[01, 03, 40, 5e, 04, 56, b9, d4, cf, a5, 29, 35, ad, b2, d9, 0b, 42, 2e, 69, 29, 7d, 81, 32, b1, 2d, 4b, ee, 0c, 56, 71, 58, 66, 8d, ce, 98, 17, 97, 70, 65, d5, 67, 23, 4e, 29, dc, 1c, 0b, 07, 3b, f8, 95, cd, 7c, 66, ac, e4, 0e, ca, b6, 3c, 2d, 2a, 9b, e3, 48, 7e, 51, cd, 29, f1, ed, 51, a9, 3f, e5, a5, c8, 92, b3, dc, cd, ef, 93, 72, f6, 47, 8d, 38, d1, ce, 04, 28, cd, dc, 2a, 44, f8, de, bd, 69, 00] +tcp_req 8000000f0440de1c1d712fffa0700dd5443f929102b81179b3a2f93ca80eddf35bce6aa609439c89860b0173935e59d0429831aedbee71abb4ded9744f25778f2c5e18c83a516e0f8c52a1325cd3df6225608e089cb79822ae3c4e964fc7fb440fc12d9199d03b0f93eec7afbbbd6354fc94f152f44291b4754d6941f0a709f23f7a075e2826813e113c9a06f49871ddb099ab2ad13be057c703b111585e8fe76d619f2dfb72c906ad202f604a64b4338f589eb76fd4f39162284f326cd28e0daa0fc04b7e0f07f875c4491f0876db23e7a935cf8c5a485121a92b78c348b16bc348be5ebd1ef680000000 ConnectTcpReq { flags: 15, address_type: Ipv6, host: [28, 29, 113, 47, 255, 160, 112, 13, 213, 68, 63, 146, 145, 2, 184, 17, 121, 179, 162, 249, 60, 168, 14, 221, 243, 91, 206, 106, 166, 9, 67, 156, 137, 134, 11, 1, 115, 147, 94, 89, 208, 66, 152, 49, 174, 219, 238, 113, 171, 180, 222, 217, 116, 79, 37, 119, 143, 44, 94, 24, 200, 58, 81, 110, 15, 140, 82, 161, 50, 92, 211, 223, 98, 37, 96, 142, 8, 156, 183, 152, 34, 174, 60, 78, 150, 79, 199, 251, 68, 15, 193, 45, 145, 153, 208, 59, 15, 147, 238, 199, 175, 187, 189, 99, 84, 252, 148, 241, 82, 244, 66, 145, 180, 117, 77, 105, 65, 240, 167, 9, 242, 63, 122, 7, 94, 40, 38, 129, 62, 17, 60, 154, 6, 244, 152, 113, 221, 176, 153, 171, 42, 209, 59, 224, 87, 199, 3, 177, 17, 88, 94, 143, 231, 109, 97, 159, 45, 251, 114, 201, 6, 173, 32, 47, 96, 74, 100, 180, 51, 143, 88, 158, 183, 111, 212, 243, 145, 98, 40, 79, 50, 108, 210, 142, 13, 170, 15, 192, 75, 126, 15, 7, 248, 117, 196, 73, 31, 8, 118, 219, 35, 231, 169, 53, 207, 140, 90, 72, 81, 33, 169, 43, 120, 195, 72, 177, 107, 195, 72, 190, 94, 189], port: 7926 };235;[0f, 04, 40, de, 1c, 1d, 71, 2f, ff, a0, 70, 0d, d5, 44, 3f, 92, 91, 02, b8, 11, 79, b3, a2, f9, 3c, a8, 0e, dd, f3, 5b, ce, 6a, a6, 09, 43, 9c, 89, 86, 0b, 01, 73, 93, 5e, 59, d0, 42, 98, 31, ae, db, ee, 71, ab, b4, de, d9, 74, 4f, 25, 77, 8f, 2c, 5e, 18, c8, 3a, 51, 6e, 0f, 8c, 52, a1, 32, 5c, d3, df, 62, 25, 60, 8e, 08, 9c, b7, 98, 22, ae, 3c, 4e, 96, 4f, c7, fb, 44, 0f, c1, 2d, 91, 99, d0, 3b, 0f, 93, ee, c7, af, bb, bd, 63, 54, fc, 94, f1, 52, f4, 42, 91, b4, 75, 4d, 69, 41, f0, a7, 09, f2, 3f, 7a, 07, 5e, 28, 26, 81, 3e, 11, 3c, 9a, 06, f4, 98, 71, dd, b0, 99, ab, 2a, d1, 3b, e0, 57, c7, 03, b1, 11, 58, 5e, 8f, e7, 6d, 61, 9f, 2d, fb, 72, c9, 06, ad, 20, 2f, 60, 4a, 64, b4, 33, 8f, 58, 9e, b7, 6f, d4, f3, 91, 62, 28, 4f, 32, 6c, d2, 8e, 0d, aa, 0f, c0, 4b, 7e, 0f, 07, f8, 75, c4, 49, 1f, 08, 76, db, 23, e7, a9, 35, cf, 8c, 5a, 48, 51, 21, a9, 2b, 78, c3, 48, b1, 6b, c3, 48, be, 5e, bd, 1e, f6, 00] +tcp_req c000019ac644597601c0000000000000ee00d7dbf32b7b4a8184c8e308239c4836e06e6f380ce1d0ac10416355ea789123d60736259222b981ebb7f3cd4831e326334a213ea9200cb0349e10298d37c30f1c7d2b7d48993335dbf42f5f671920826c8b20336523f338e439ffa35fc5b8e912eea23e9e2244c404deb1a55b145b518b6690a19107239e1ae2aa02ac8bb836e4bc10bdd6c8c782ee40c2db22e04dad0a29845530607429de147262c0d0a4cafdd275a3086e36c82209de102a61b2521a8db3d8856f8f26e3cce9a378180da007ed46690447f9dbfdac36a6fb516443f6f8ed3df6922e909386f0d69d94ff84807fdf7f175f02c04c2b48c43915e84580000000 ConnectTcpReq { flags: 1764262959478, address_type: Ipv4, host: [0, 215, 219, 243, 43, 123, 74, 129, 132, 200, 227, 8, 35, 156, 72, 54, 224, 110, 111, 56, 12, 225, 208, 172, 16, 65, 99, 85, 234, 120, 145, 35, 214, 7, 54, 37, 146, 34, 185, 129, 235, 183, 243, 205, 72, 49, 227, 38, 51, 74, 33, 62, 169, 32, 12, 176, 52, 158, 16, 41, 141, 55, 195, 15, 28, 125, 43, 125, 72, 153, 51, 53, 219, 244, 47, 95, 103, 25, 32, 130, 108, 139, 32, 51, 101, 35, 243, 56, 228, 57, 255, 163, 95, 197, 184, 233, 18, 238, 162, 62, 158, 34, 68, 196, 4, 222, 177, 165, 91, 20, 91, 81, 139, 102, 144, 161, 145, 7, 35, 158, 26, 226, 170, 2, 172, 139, 184, 54, 228, 188, 16, 189, 214, 200, 199, 130, 238, 64, 194, 219, 34, 224, 77, 173, 10, 41, 132, 85, 48, 96, 116, 41, 222, 20, 114, 98, 192, 208, 164, 202, 253, 210, 117, 163, 8, 110, 54, 200, 34, 9, 222, 16, 42, 97, 178, 82, 26, 141, 179, 216, 133, 111, 143, 38, 227, 204, 233, 163, 120, 24, 13, 160, 7, 237, 70, 105, 4, 71, 249, 219, 253, 172, 54, 166, 251, 81, 100, 67, 246, 248, 237, 61, 246, 146, 46, 144, 147, 134, 240, 214, 157, 148, 255, 132, 128, 127, 223, 127, 23, 95, 2, 192, 76, 43, 72, 196, 57, 21], port: 59461 };261;[c0, 00, 01, 9a, c6, 44, 59, 76, 01, 40, ee, 00, d7, db, f3, 2b, 7b, 4a, 81, 84, c8, e3, 08, 23, 9c, 48, 36, e0, 6e, 6f, 38, 0c, e1, d0, ac, 10, 41, 63, 55, ea, 78, 91, 23, d6, 07, 36, 25, 92, 22, b9, 81, eb, b7, f3, cd, 48, 31, e3, 26, 33, 4a, 21, 3e, a9, 20, 0c, b0, 34, 9e, 10, 29, 8d, 37, c3, 0f, 1c, 7d, 2b, 7d, 48, 99, 33, 35, db, f4, 2f, 5f, 67, 19, 20, 82, 6c, 8b, 20, 33, 65, 23, f3, 38, e4, 39, ff, a3, 5f, c5, b8, e9, 12, ee, a2, 3e, 9e, 22, 44, c4, 04, de, b1, a5, 5b, 14, 5b, 51, 8b, 66, 90, a1, 91, 07, 23, 9e, 1a, e2, aa, 02, ac, 8b, b8, 36, e4, bc, 10, bd, d6, c8, c7, 82, ee, 40, c2, db, 22, e0, 4d, ad, 0a, 29, 84, 55, 30, 60, 74, 29, de, 14, 72, 62, c0, d0, a4, ca, fd, d2, 75, a3, 08, 6e, 36, c8, 22, 09, de, 10, 2a, 61, b2, 52, 1a, 8d, b3, d8, 85, 6f, 8f, 26, e3, cc, e9, a3, 78, 18, 0d, a0, 07, ed, 46, 69, 04, 47, f9, db, fd, ac, 36, a6, fb, 51, 64, 43, f6, f8, ed, 3d, f6, 92, 2e, 90, 93, 86, f0, d6, 9d, 94, ff, 84, 80, 7f, df, 7f, 17, 5f, 02, c0, 4c, 2b, 48, c4, 39, 15, e8, 45, 00] +tcp_req 82809e350440209d09fdb3441b6f706af9a059f36b595a73942c77f0355f628d447143c8a4d62e3d224000 ConnectTcpReq { flags: 41983541, address_type: Ipv6, host: [157, 9, 253, 179, 68, 27, 111, 112, 106, 249, 160, 89, 243, 107, 89, 90, 115, 148, 44, 119, 240, 53, 95, 98, 141, 68, 113, 67, 200, 164, 214, 46], port: 15650 };43;[82, 80, 9e, 35, 04, 20, 9d, 09, fd, b3, 44, 1b, 6f, 70, 6a, f9, a0, 59, f3, 6b, 59, 5a, 73, 94, 2c, 77, f0, 35, 5f, 62, 8d, 44, 71, 43, c8, a4, d6, 2e, 3d, 22, 00] +tcp_req c0000005fcd0db4504404c091dd248e79b017248944b89864107dbf685fcbe49684a8eba6596a100ae82f2974ee2ccd7a3a4bc853fa8d8403bf5d3646a569ca22bdab971748c9fe003c77c6d31efa1386799e942737721aaa24000 ConnectTcpReq { flags: 25716382533, address_type: Ipv6, host: [9, 29, 210, 72, 231, 155, 1, 114, 72, 148, 75, 137, 134, 65, 7, 219, 246, 133, 252, 190, 73, 104, 74, 142, 186, 101, 150, 161, 0, 174, 130, 242, 151, 78, 226, 204, 215, 163, 164, 188, 133, 63, 168, 216, 64, 59, 245, 211, 100, 106, 86, 156, 162, 43, 218, 185, 113, 116, 140, 159, 224, 3, 199, 124, 109, 49, 239, 161, 56, 103, 153, 233, 66, 115, 119, 33], port: 43682 };91;[c0, 00, 00, 05, fc, d0, db, 45, 04, 40, 4c, 09, 1d, d2, 48, e7, 9b, 01, 72, 48, 94, 4b, 89, 86, 41, 07, db, f6, 85, fc, be, 49, 68, 4a, 8e, ba, 65, 96, a1, 00, ae, 82, f2, 97, 4e, e2, cc, d7, a3, a4, bc, 85, 3f, a8, d8, 40, 3b, f5, d3, 64, 6a, 56, 9c, a2, 2b, da, b9, 71, 74, 8c, 9f, e0, 03, c7, 7c, 6d, 31, ef, a1, 38, 67, 99, e9, 42, 73, 77, 21, aa, a2, 00] +tcp_req c0000000000000040180000103022c1b35e94d3e4df3e7bd7b77c0478621e9b68d05f26056b7dcba0d10fd36f177853ea5ecf64e1d1f3b0ebdce35069376735d0a7020a1e7e6b7aeec5c6e786d545a46e23b2916bc104f33c25262177da56b178762e599b12c606b256d4133f364f97e373df78a9b4a3116d3a2d7948fdfb8fe7b7241574f136235d96ee0961c25b4d2a6315fea3009dea32eddcfec7a30f014861e9822713d0d43407ddee1caa46a2c39d4b269ead41d9542b8004440b9cf813852dfe4a3a944b1194ebcb6f61a0868e12b6dc145a506dfd0423712f2e914219b90bca8abb2b4051c3b54a0ef9f1cca6c0013b0e0d2829140f72cb5d47c0add31b1c23fde5e9c17a297d1a34c3a30dbce42c000000000000000 reject +tcp_req 400304c00000000000002c6a9a48ac18ce1d12b8bfbad1a0e58ad50963e8bbbe813ed69d003c99af6694f304c22848dafd9c618edeca6592f000 ConnectTcpReq { flags: 3, address_type: Ipv6, host: [106, 154, 72, 172, 24, 206, 29, 18, 184, 191, 186, 209, 160, 229, 138, 213, 9, 99, 232, 187, 190, 129, 62, 214, 157, 0, 60, 153, 175, 102, 148, 243, 4, 194, 40, 72, 218, 253, 156, 97, 142, 222, 202, 101], port: 37616 };58;[03, 04, 2c, 6a, 9a, 48, ac, 18, ce, 1d, 12, b8, bf, ba, d1, a0, e5, 8a, d5, 09, 63, e8, bb, be, 81, 3e, d6, 9d, 00, 3c, 99, af, 66, 94, f3, 04, c2, 28, 48, da, fd, 9c, 61, 8e, de, ca, 65, 92, f0, 00] +tcp_req c0000000000000000180000080e8ef218fae7f134905df35216f23dc5cd18d2bdb1ed3b1de0d1912792624d925037c6804c98ce21fd0c967a0cab946bb853b8ff747349c1bea4b2efe19e8cfdd39c3bb19c35e4c2bbd0b31adb1d3a1c9c0b43406a6cf4cec80e53f6ea0aba168e2872121a86b8574e1afc3978ac4db0c7a499d9173cef8755605b81f4de7a3d33ca8045f9ca687 ConnectTcpReq { flags: 0, address_type: Ipv4, host: [232, 239, 33, 143, 174, 127, 19, 73, 5, 223, 53, 33, 111, 35, 220, 92, 209, 141, 43, 219, 30, 211, 177, 222, 13, 25, 18, 121, 38, 36, 217, 37, 3, 124, 104, 4, 201, 140, 226, 31, 208, 201, 103, 160, 202, 185, 70, 187, 133, 59, 143, 247, 71, 52, 156, 27, 234, 75, 46, 254, 25, 232, 207, 221, 57, 195, 187, 25, 195, 94, 76, 43, 189, 11, 49, 173, 177, 211, 161, 201, 192, 180, 52, 6, 166, 207, 76, 236, 128, 229, 63, 110, 160, 171, 161, 104, 226, 135, 33, 33, 168, 107, 133, 116, 225, 175, 195, 151, 138, 196, 219, 12, 122, 73, 157, 145, 115, 206, 248, 117, 86, 5, 184, 31, 77, 231, 163, 211], port: 15528 };148;[00, 01, 40, 80, e8, ef, 21, 8f, ae, 7f, 13, 49, 05, df, 35, 21, 6f, 23, dc, 5c, d1, 8d, 2b, db, 1e, d3, b1, de, 0d, 19, 12, 79, 26, 24, d9, 25, 03, 7c, 68, 04, c9, 8c, e2, 1f, d0, c9, 67, a0, ca, b9, 46, bb, 85, 3b, 8f, f7, 47, 34, 9c, 1b, ea, 4b, 2e, fe, 19, e8, cf, dd, 39, c3, bb, 19, c3, 5e, 4c, 2b, bd, 0b, 31, ad, b1, d3, a1, c9, c0, b4, 34, 06, a6, cf, 4c, ec, 80, e5, 3f, 6e, a0, ab, a1, 68, e2, 87, 21, 21, a8, 6b, 85, 74, e1, af, c3, 97, 8a, c4, db, 0c, 7a, 49, 9d, 91, 73, ce, f8, 75, 56, 05, b8, 1f, 4d, e7, a3, d3, 3c, a8, 00] +tcp_req c0000000001f1fb601800000401b10de500d0d259b5e52df7149ea99d44d5a9261837640e64ca491932cbe3953e3ff059159d4798b17ed87db84080468cdbe464687ab16d89ff2ddb657aa1e295b2480000000 ConnectTcpReq { flags: 2039734, address_type: Ipv4, host: [27, 16, 222, 80, 13, 13, 37, 155, 94, 82, 223, 113, 73, 234, 153, 212, 77, 90, 146, 97, 131, 118, 64, 230, 76, 164, 145, 147, 44, 190, 57, 83, 227, 255, 5, 145, 89, 212, 121, 139, 23, 237, 135, 219, 132, 8, 4, 104, 205, 190, 70, 70, 135, 171, 22, 216, 159, 242, 221, 182, 87, 170, 30, 41], port: 23332 };83;[80, 1f, 1f, b6, 01, 40, 40, 1b, 10, de, 50, 0d, 0d, 25, 9b, 5e, 52, df, 71, 49, ea, 99, d4, 4d, 5a, 92, 61, 83, 76, 40, e6, 4c, a4, 91, 93, 2c, be, 39, 53, e3, ff, 05, 91, 59, d4, 79, 8b, 17, ed, 87, db, 84, 08, 04, 68, cd, be, 46, 46, 87, ab, 16, d8, 9f, f2, dd, b6, 57, aa, 1e, 29, 5b, 24, 00] +tcp_req 5fd8044103534d32cbde351b5cd8b0b48fc752e36ebb4c8b8ef7b57a4771dc86f1a519c93f92335862f6d73107fee4c6d94533aa23c5c74bbc6183f92c663919fb083692443427c77260111cac482c1346b28121f1a2cbe2b37067e3fe6fcb87c92f1e7ffa3b27be3408dff01ed962bf124abbd243f7015c49509d70c568629433cac03123405e39d0f9924c4624785ff92f3750420dd8d7120ae0877fbe0527d2d75dd4cc65b5876179054ee3e5ec265fa3878a1925a7db8154a680e7825d253769adf8e7986ee54c97b1d4d9a77fb755eaa841e446cc262d8ea5012b84f291c8255c4eed48cfd63bc7c4865b1c2b9c456b64b8417b93a9c8903932efd3b74a5b528d29c97428d2a2cbc000000000000000 reject +tcp_req 800000000340837e2e3b82f70b7858dc2b599edc9e8340c5ba1c37fcd40bf879995533a18e41c7733c83748c72e4a198db8ce4f183ce5b66014c865e58ac8d2131086ae83feeedc398d9b88363bc79892934a7e819d2cf0ea0a41c07b59a06a2791ca24314194d8c1a74e817c1b139cfe2daedaa3a190e69742df4489bc00400a8d0cf4440b1142c96d229abc000000000000000 ConnectTcpReq { flags: 0, address_type: Domain, host: [126, 46, 59, 130, 247, 11, 120, 88, 220, 43, 89, 158, 220, 158, 131, 64, 197, 186, 28, 55, 252, 212, 11, 248, 121, 153, 85, 51, 161, 142, 65, 199, 115, 60, 131, 116, 140, 114, 228, 161, 152, 219, 140, 228, 241, 131, 206, 91, 102, 1, 76, 134, 94, 88, 172, 141, 33, 49, 8, 106, 232, 63, 238, 237, 195, 152, 217, 184, 131, 99, 188, 121, 137, 41, 52, 167, 232, 25, 210, 207, 14, 160, 164, 28, 7, 181, 154, 6, 162, 121, 28, 162, 67, 20, 25, 77, 140, 26, 116, 232, 23, 193, 177, 57, 207, 226, 218, 237, 170, 58, 25, 14, 105, 116, 45, 244, 72, 155, 192, 4, 0, 168, 208, 207, 68, 64, 177, 20, 44, 150, 210], port: 10667 };148;[00, 03, 40, 83, 7e, 2e, 3b, 82, f7, 0b, 78, 58, dc, 2b, 59, 9e, dc, 9e, 83, 40, c5, ba, 1c, 37, fc, d4, 0b, f8, 79, 99, 55, 33, a1, 8e, 41, c7, 73, 3c, 83, 74, 8c, 72, e4, a1, 98, db, 8c, e4, f1, 83, ce, 5b, 66, 01, 4c, 86, 5e, 58, ac, 8d, 21, 31, 08, 6a, e8, 3f, ee, ed, c3, 98, d9, b8, 83, 63, bc, 79, 89, 29, 34, a7, e8, 19, d2, cf, 0e, a0, a4, 1c, 07, b5, 9a, 06, a2, 79, 1c, a2, 43, 14, 19, 4d, 8c, 1a, 74, e8, 17, c1, b1, 39, cf, e2, da, ed, aa, 3a, 19, 0e, 69, 74, 2d, f4, 48, 9b, c0, 04, 00, a8, d0, cf, 44, 40, b1, 14, 2c, 96, d2, 29, ab, 00] +tcp_req c0020fa0dd309f80044101ec52a45511024058488c00dd81364732643b81dcf20bf823206672fb702f2c22e308de17fd7322d088999921870006b23ed44056e0bc3b6dc55b5787a938ff50485da1bc991985b29164997d339f4159d0ebf5d82de63ccda8d1f2f15a9f934a56f3e4db1ca35663161701cf464706193259507dc67d0dca619e396add06cc46f941a7ca77ffa99000859fe49cbf853d0a33c25539b8298c6a64b778f1fd703a61a1a5661d0c8ffa679bb2a0d63f89bbdd867cc8e993cb3cef180175b61deda347c7d10cbf73664d99fc53c25c3d52163251e0927e021c98f76f48127d1e02a2378edcafb402131ac332d1e83d60aefe35bd40501a78b08031a1a309acf267f464aa73c0000000000000057a6a887509673329 reject +tcp_req c447564e305641290480000038de468019edd22c10733ce00514be245d9c07914b51e15c363d1ed1a1c7ed397b14a2cb85c1e1662312bdeb05eec1f0dde833b3f9787b40982bb5c000000000000000a725c0 ConnectTcpReq { flags: 308309993316565289, address_type: Ipv6, host: [222, 70, 128, 25, 237, 210, 44, 16, 115, 60, 224, 5, 20, 190, 36, 93, 156, 7, 145, 75, 81, 225, 92, 54, 61, 30, 209, 161, 199, 237, 57, 123, 20, 162, 203, 133, 193, 225, 102, 35, 18, 189, 235, 5, 238, 193, 240, 221, 232, 51, 179, 249, 120, 123, 64, 152], port: 11189 };79;[c4, 47, 56, 4e, 30, 56, 41, 29, 04, 38, de, 46, 80, 19, ed, d2, 2c, 10, 73, 3c, e0, 05, 14, be, 24, 5d, 9c, 07, 91, 4b, 51, e1, 5c, 36, 3d, 1e, d1, a1, c7, ed, 39, 7b, 14, a2, cb, 85, c1, e1, 66, 23, 12, bd, eb, 05, ee, c1, f0, dd, e8, 33, b3, f9, 78, 7b, 40, 98, 2b, b5, 00] +tcp_req 8000012203800000c2a1f64115e518c8ec7914d4dd2dbc85e149b8cd3e7e17c302cd853c75effe80a0906501ce05fbc007c7c982a90dbba03d0fff7d4455e7cf363ffe21fc4ff0acd913caf851484038d8d358837b4f66adb9852c48e8acecc2025fe5c85b83df064d27b984a136d847fe0ee7f9923fccf69a5f6c184a70f6be47322f3dd3f54f37f398d176a1a2b3e75bb6e4d787422383ccd8dc66abecdcfc51a143eed6fd98eae4a1d7ebe3ffbba034ea4d98cda4e6532a90749882ca384df9f2d4630d74b9daa6b26bce6980000003447e65 ConnectTcpReq { flags: 290, address_type: Domain, host: [161, 246, 65, 21, 229, 24, 200, 236, 121, 20, 212, 221, 45, 188, 133, 225, 73, 184, 205, 62, 126, 23, 195, 2, 205, 133, 60, 117, 239, 254, 128, 160, 144, 101, 1, 206, 5, 251, 192, 7, 199, 201, 130, 169, 13, 187, 160, 61, 15, 255, 125, 68, 85, 231, 207, 54, 63, 254, 33, 252, 79, 240, 172, 217, 19, 202, 248, 81, 72, 64, 56, 216, 211, 88, 131, 123, 79, 102, 173, 185, 133, 44, 72, 232, 172, 236, 194, 2, 95, 229, 200, 91, 131, 223, 6, 77, 39, 185, 132, 161, 54, 216, 71, 254, 14, 231, 249, 146, 63, 204, 246, 154, 95, 108, 24, 74, 112, 246, 190, 71, 50, 47, 61, 211, 245, 79, 55, 243, 152, 209, 118, 161, 162, 179, 231, 91, 182, 228, 215, 135, 66, 35, 131, 204, 216, 220, 102, 171, 236, 220, 252, 81, 161, 67, 238, 214, 253, 152, 234, 228, 161, 215, 235, 227, 255, 187, 160, 52, 234, 77, 152, 205, 164, 230, 83, 42, 144, 116, 152, 130, 202, 56, 77, 249, 242, 212, 99, 13, 116, 185, 218, 166, 178, 107], port: 52841 };212;[41, 22, 03, 40, c2, a1, f6, 41, 15, e5, 18, c8, ec, 79, 14, d4, dd, 2d, bc, 85, e1, 49, b8, cd, 3e, 7e, 17, c3, 02, cd, 85, 3c, 75, ef, fe, 80, a0, 90, 65, 01, ce, 05, fb, c0, 07, c7, c9, 82, a9, 0d, bb, a0, 3d, 0f, ff, 7d, 44, 55, e7, cf, 36, 3f, fe, 21, fc, 4f, f0, ac, d9, 13, ca, f8, 51, 48, 40, 38, d8, d3, 58, 83, 7b, 4f, 66, ad, b9, 85, 2c, 48, e8, ac, ec, c2, 02, 5f, e5, c8, 5b, 83, df, 06, 4d, 27, b9, 84, a1, 36, d8, 47, fe, 0e, e7, f9, 92, 3f, cc, f6, 9a, 5f, 6c, 18, 4a, 70, f6, be, 47, 32, 2f, 3d, d3, f5, 4f, 37, f3, 98, d1, 76, a1, a2, b3, e7, 5b, b6, e4, d7, 87, 42, 23, 83, cc, d8, dc, 66, ab, ec, dc, fc, 51, a1, 43, ee, d6, fd, 98, ea, e4, a1, d7, eb, e3, ff, bb, a0, 34, ea, 4d, 98, cd, a4, e6, 53, 2a, 90, 74, 98, 82, ca, 38, 4d, f9, f2, d4, 63, 0d, 74, b9, da, a6, b2, 6b, ce, 69, 00] +tcp_req c00a5d05ea09c6af03c000000000000092175c6a1e76a94bbd61f5cfe293b2bf4fdbfc2cef6c8b0cb5f2931764c49a978d386ddad1eea0b5b5c37e702c5d52cf0dcca392d7b090d5985c4f326dba59bcc2f082c4219cce531a126ba085e59253a89cac3e355f80fbd268148f123f31773cedde97849a26dd2edf4ef322341e14c431f8bce0f12367a30c0b9c5438863e1df7064caa1dbdeca852d3b91536c8878b90052f654000 ConnectTcpReq { flags: 2917029749835439, address_type: Domain, host: [23, 92, 106, 30, 118, 169, 75, 189, 97, 245, 207, 226, 147, 178, 191, 79, 219, 252, 44, 239, 108, 139, 12, 181, 242, 147, 23, 100, 196, 154, 151, 141, 56, 109, 218, 209, 238, 160, 181, 181, 195, 126, 112, 44, 93, 82, 207, 13, 204, 163, 146, 215, 176, 144, 213, 152, 92, 79, 50, 109, 186, 89, 188, 194, 240, 130, 196, 33, 156, 206, 83, 26, 18, 107, 160, 133, 229, 146, 83, 168, 156, 172, 62, 53, 95, 128, 251, 210, 104, 20, 143, 18, 63, 49, 119, 60, 237, 222, 151, 132, 154, 38, 221, 46, 223, 78, 243, 34, 52, 30, 20, 196, 49, 248, 188, 224, 241, 35, 103, 163, 12, 11, 156, 84, 56, 134, 62, 29, 247, 6, 76, 170, 29, 189, 236, 168, 82, 211, 185, 21, 54, 200, 135, 139, 144, 5], port: 12133 };167;[c0, 0a, 5d, 05, ea, 09, c6, af, 03, 40, 92, 17, 5c, 6a, 1e, 76, a9, 4b, bd, 61, f5, cf, e2, 93, b2, bf, 4f, db, fc, 2c, ef, 6c, 8b, 0c, b5, f2, 93, 17, 64, c4, 9a, 97, 8d, 38, 6d, da, d1, ee, a0, b5, b5, c3, 7e, 70, 2c, 5d, 52, cf, 0d, cc, a3, 92, d7, b0, 90, d5, 98, 5c, 4f, 32, 6d, ba, 59, bc, c2, f0, 82, c4, 21, 9c, ce, 53, 1a, 12, 6b, a0, 85, e5, 92, 53, a8, 9c, ac, 3e, 35, 5f, 80, fb, d2, 68, 14, 8f, 12, 3f, 31, 77, 3c, ed, de, 97, 84, 9a, 26, dd, 2e, df, 4e, f3, 22, 34, 1e, 14, c4, 31, f8, bc, e0, f1, 23, 67, a3, 0c, 0b, 9c, 54, 38, 86, 3e, 1d, f7, 06, 4c, aa, 1d, bd, ec, a8, 52, d3, b9, 15, 36, c8, 87, 8b, 90, 05, 2f, 65, 00] +tcp_req 8001d10f038000002be07693d9f2fe341944dd0efadb710b208f7714a405c03dc03a09cd45861220d203b1fb072c415c6a07afed57cac000000000000000 ConnectTcpReq { flags: 119055, address_type: Domain, host: [224, 118, 147, 217, 242, 254, 52, 25, 68, 221, 14, 250, 219, 113, 11, 32, 143, 119, 20, 164, 5, 192, 61, 192, 58, 9, 205, 69, 134, 18, 32, 210, 3, 177, 251, 7, 44, 65, 92, 106, 7, 175, 237], port: 22474 };62;[80, 01, d1, 0f, 03, 2b, e0, 76, 93, d9, f2, fe, 34, 19, 44, dd, 0e, fa, db, 71, 0b, 20, 8f, 77, 14, a4, 05, c0, 3d, c0, 3a, 09, cd, 45, 86, 12, 20, d2, 03, b1, fb, 07, 2c, 41, 5c, 6a, 07, af, ed, 57, ca, 00] +tcp_req c00000316b789bcc044002919caec4c000000000000008d12e369dc45053d8 ConnectTcpReq { flags: 212256463820, address_type: Ipv6, host: [145, 156], port: 44740 };31;[c0, 00, 00, 31, 6b, 78, 9b, cc, 04, 02, 91, 9c, ae, c4, 00] +tcp_req d27370e2192fba8e4b40a478d86ea93088ec8bc32c73369d3d4b6f18913f25d7b5cf23eccae3c604a0f8b8b7837679b3d259aca8a6e22b2f3cc686f76474b13e132f1b1d6d213a7ccf0f15717c3ef210b683e87801c653ef6af4ed30ddc8ef277beabc39a544c935960f3c76b8f72bf0a8400d38ad5863f309c782b3dec64e382d9fae33c25503e5757b0b57e6d8bfa70dd0ccf09ad742b9bc6704678be0cfb5eeae0ceebb902912cc6237d70869bc6cedc00000000000000999829b2bfde2214fd826ec3405fd reject +tcp_req c000000017d0504903c0000000000000d1582630beadbaad8d2dddd3a04299c9653cbdefd0b6e8b29b8174b3a8d61a1775352d5590b443e70e3d18c3fe0693fc1a87e6d434a33c3cd177a3040a50a45d44a643e454626796aed84efe5ba633191e5e5ba9494ca56b50b93c39df1ef23612134025bcfd7d6c7738d04a192e298f944bd89d6c201cee8341047cc096d3b0af4e4788be8a4d937e6bc019377d6090b9a0ab813a509f38a62c5159e3e6629694af9570e348d624f7b947d55404ef01cb849d6e4a1d2474f1461985cb8e814a8b81ae702fbf9c2e13e08ad79a7f720ae2f4113280000000 ConnectTcpReq { flags: 399528009, address_type: Domain, host: [88, 38, 48, 190, 173, 186, 173, 141, 45, 221, 211, 160, 66, 153, 201, 101, 60, 189, 239, 208, 182, 232, 178, 155, 129, 116, 179, 168, 214, 26, 23, 117, 53, 45, 85, 144, 180, 67, 231, 14, 61, 24, 195, 254, 6, 147, 252, 26, 135, 230, 212, 52, 163, 60, 60, 209, 119, 163, 4, 10, 80, 164, 93, 68, 166, 67, 228, 84, 98, 103, 150, 174, 216, 78, 254, 91, 166, 51, 25, 30, 94, 91, 169, 73, 76, 165, 107, 80, 185, 60, 57, 223, 30, 242, 54, 18, 19, 64, 37, 188, 253, 125, 108, 119, 56, 208, 74, 25, 46, 41, 143, 148, 75, 216, 157, 108, 32, 28, 238, 131, 65, 4, 124, 192, 150, 211, 176, 175, 78, 71, 136, 190, 138, 77, 147, 126, 107, 192, 25, 55, 125, 96, 144, 185, 160, 171, 129, 58, 80, 159, 56, 166, 44, 81, 89, 227, 230, 98, 150, 148, 175, 149, 112, 227, 72, 214, 36, 247, 185, 71, 213, 84, 4, 239, 1, 203, 132, 157, 110, 74, 29, 36, 116, 241, 70, 25, 133, 203, 142, 129, 74, 139, 129, 174, 112, 47, 191, 156, 46, 19, 224, 138, 215, 154, 127, 114, 10, 226, 244], port: 4402 };232;[97, d0, 50, 49, 03, 40, d1, 58, 26, 30, be, ad, ba, ad, 8d, 2d, dd, d3, a0, 42, 99, c9, 65, 3c, bd, ef, d0, b6, e8, b2, 9b, 81, 74, b3, a8, d6, 1a, 17, 75, 35, 2d, 55, 90, b4, 43, e7, 0e, 3d, 18, c3, fe, 06, 93, fc, 1a, 87, e6, d4, 34, a3, 3c, 3c, d1, 77, a3, 04, 0a, 50, a4, 5d, 44, a6, 43, e4, 54, 62, 67, 96, ae, d8, 4e, fe, 5b, a6, 33, 19, 1e, 5e, 5b, a9, 49, 4c, a5, 6b, 50, b9, 3c, 39, df, 1e, f2, 36, 12, 13, 40, 25, bc, fd, 7d, 6c, 77, 38, d0, 4a, 19, 2e, 29, 8f, 94, 4b, d8, 9d, 6c, 20, 1c, ee, 83, 41, 04, 7c, c0, 96, d3, b0, af, 4e, 47, 88, be, 8a, 4d, 93, 7e, 6b, c0, 19, 37, 7d, 60, 90, b9, a0, ab, 81, 3a, 50, 9f, 38, a6, 2c, 51, 59, e3, e6, 62, 96, 94, af, 95, 70, e3, 48, d6, 24, f7, b9, 47, d5, 54, 04, ef, 01, cb, 84, 9d, 6e, 4a, 1d, 24, 74, f1, 46, 19, 85, cb, 8e, 81, 4a, 8b, 81, ae, 70, 2f, bf, 9c, 2e, 13, e0, 8a, d7, 9a, 7f, 72, 0a, e2, f4, 11, 32, 00] +tcp_req c000000000d37d1c01c00000000000005bee3de2dcbba20ad50d8768de9fbec1cd8da6cad135109efa7c3ec9178607e91958dd81df9c9818197ee0cfab02c22835e652b71454beab76a8c8315083f950c71bb7ae1b5808ed557ca40a4efd719ce0beb754c56640b5db3fc3e8aa958000000ae667b6baa5a0b020c472 ConnectTcpReq { flags: 13860124, address_type: Ipv4, host: [238, 61, 226, 220, 187, 162, 10, 213, 13, 135, 104, 222, 159, 190, 193, 205, 141, 166, 202, 209, 53, 16, 158, 250, 124, 62, 201, 23, 134, 7, 233, 25, 88, 221, 129, 223, 156, 152, 24, 25, 126, 224, 207, 171, 2, 194, 40, 53, 230, 82, 183, 20, 84, 190, 171, 118, 168, 200, 49, 80, 131, 249, 80, 199, 27, 183, 174, 27, 88, 8, 237, 85, 124, 164, 10, 78, 253, 113, 156, 224, 190, 183, 84, 197, 102, 64, 181, 219, 63, 195, 232], port: 43669 };124;[80, d3, 7d, 1c, 01, 40, 5b, ee, 3d, e2, dc, bb, a2, 0a, d5, 0d, 87, 68, de, 9f, be, c1, cd, 8d, a6, ca, d1, 35, 10, 9e, fa, 7c, 3e, c9, 17, 86, 07, e9, 19, 58, dd, 81, df, 9c, 98, 18, 19, 7e, e0, cf, ab, 02, c2, 28, 35, e6, 52, b7, 14, 54, be, ab, 76, a8, c8, 31, 50, 83, f9, 50, c7, 1b, b7, ae, 1b, 58, 08, ed, 55, 7c, a4, 0a, 4e, fd, 71, 9c, e0, be, b7, 54, c5, 66, 40, b5, db, 3f, c3, e8, aa, 95, 00] +tcp_req c000000d308a4dee01c0000000000000f433ab830024757b67ff381a22ef298edf7212e3f714cab3df51498cc2547b8e9d70f9bc0532e26ff5d4a8ce257c4127940d248ecb7d177f96b6c4255daec41dc0a709c02031c22ec7348f8eaaff154035c903b48adab82d2be991db143d10d731b1a707832e2e225d38cb42dfb889ec7acc9de755c818f1825e1443c5d4e6615bb0f89c4592442c8ab44e9564d604e3fed8103bd2302b26877bb1161f4da1f3f3fa1d76cc7d5065e35864f4c8d79b1ec23475f2b4a8909a846911ad9f94ecc6642c333949406d1fe0afa35a0a063ed032f25b6d5207cd8054afe21a9b172113cba0b5847542244641ff9fa7da4d6eac482a748a77415d003e7c93acd6 ConnectTcpReq { flags: 56648945134, address_type: Ipv4, host: [51, 171, 131, 0, 36, 117, 123, 103, 255, 56, 26, 34, 239, 41, 142, 223, 114, 18, 227, 247, 20, 202, 179, 223, 81, 73, 140, 194, 84, 123, 142, 157, 112, 249, 188, 5, 50, 226, 111, 245, 212, 168, 206, 37, 124, 65, 39, 148, 13, 36, 142, 203, 125, 23, 127, 150, 182, 196, 37, 93, 174, 196, 29, 192, 167, 9, 192, 32, 49, 194, 46, 199, 52, 143, 142, 170, 255, 21, 64, 53, 201, 3, 180, 138, 218, 184, 45, 43, 233, 145, 219, 20, 61, 16, 215, 49, 177, 167, 7, 131, 46, 46, 34, 93, 56, 203, 66, 223, 184, 137, 236, 122, 204, 157, 231, 85, 200, 24, 241, 130, 94, 20, 67, 197, 212, 230, 97, 91, 176, 248, 156, 69, 146, 68, 44, 138, 180, 78, 149, 100, 214, 4, 227, 254, 216, 16, 59, 210, 48, 43, 38, 135, 123, 177, 22, 31, 77, 161, 243, 243, 250, 29, 118, 204, 125, 80, 101, 227, 88, 100, 244, 200, 215, 155, 30, 194, 52, 117, 242, 180, 168, 144, 154, 132, 105, 17, 173, 159, 148, 236, 198, 100, 44, 51, 57, 73, 64, 109, 31, 224, 175, 163, 90, 10, 6, 62, 208, 50, 242, 91, 109, 82, 7, 205, 128, 84, 175, 226, 26, 155, 23, 33, 19, 203, 160, 181, 132, 117, 66, 36, 70, 65, 255, 159, 167, 218, 77, 110, 172, 72, 42, 116, 138, 119], port: 16733 };264;[c0, 00, 00, 0d, 30, 8a, 4d, ee, 01, 40, f4, 33, ab, 83, 00, 24, 75, 7b, 67, ff, 38, 1a, 22, ef, 29, 8e, df, 72, 12, e3, f7, 14, ca, b3, df, 51, 49, 8c, c2, 54, 7b, 8e, 9d, 70, f9, bc, 05, 32, e2, 6f, f5, d4, a8, ce, 25, 7c, 41, 27, 94, 0d, 24, 8e, cb, 7d, 17, 7f, 96, b6, c4, 25, 5d, ae, c4, 1d, c0, a7, 09, c0, 20, 31, c2, 2e, c7, 34, 8f, 8e, aa, ff, 15, 40, 35, c9, 03, b4, 8a, da, b8, 2d, 2b, e9, 91, db, 14, 3d, 10, d7, 31, b1, a7, 07, 83, 2e, 2e, 22, 5d, 38, cb, 42, df, b8, 89, ec, 7a, cc, 9d, e7, 55, c8, 18, f1, 82, 5e, 14, 43, c5, d4, e6, 61, 5b, b0, f8, 9c, 45, 92, 44, 2c, 8a, b4, 4e, 95, 64, d6, 04, e3, fe, d8, 10, 3b, d2, 30, 2b, 26, 87, 7b, b1, 16, 1f, 4d, a1, f3, f3, fa, 1d, 76, cc, 7d, 50, 65, e3, 58, 64, f4, c8, d7, 9b, 1e, c2, 34, 75, f2, b4, a8, 90, 9a, 84, 69, 11, ad, 9f, 94, ec, c6, 64, 2c, 33, 39, 49, 40, 6d, 1f, e0, af, a3, 5a, 0a, 06, 3e, d0, 32, f2, 5b, 6d, 52, 07, cd, 80, 54, af, e2, 1a, 9b, 17, 21, 13, cb, a0, b5, 84, 75, 42, 24, 46, 41, ff, 9f, a7, da, 4d, 6e, ac, 48, 2a, 74, 8a, 77, 41, 5d, 00] +tcp_req 4dca04c000000000000103b490be9f9c416923ac7adf987dda9c999151fbfba706c68cb5ea852640a25c1a45c79a48238fdb54ff15f001000495ac029e587d060efe4c38a4e4cdc3a18dcf68ef7e1744978309570bcc5ef361dbdcf78279a49b55bd12ad36a644d5ff25b2aed7d41747f597b0af107dc92b8764d63a60b3be39e00b2eb1675921e16738589aca3d650af306671080ed050e0fef093a1bfd289e395b458a7860b1a1a6bf771d3481f1abfee23047d287a57d16ff83e03b28a55a70281b1afded9d6dbd25f08a01ec929793d7c7f51e5fda2f9742996de561a9204f529e86a0dd1075de11667e6155220d71f36b5becf558f91910d643651f076b0d68d4ef61b631dac8cc77be3a43258900eef4 reject +tcp_req c000000000028d0101c00000000000004ae61701fd4d743117da6178fede80170d81518479d47af9bfb9871f864c9d2991fc76f1622d57d75224abb0298a5a16ed8102c97177416711639d337d79209317848ec370fa5e8fec3d88c1f14000 ConnectTcpReq { flags: 167169, address_type: Ipv4, host: [230, 23, 1, 253, 77, 116, 49, 23, 218, 97, 120, 254, 222, 128, 23, 13, 129, 81, 132, 121, 212, 122, 249, 191, 185, 135, 31, 134, 76, 157, 41, 145, 252, 118, 241, 98, 45, 87, 215, 82, 36, 171, 176, 41, 138, 90, 22, 237, 129, 2, 201, 113, 119, 65, 103, 17, 99, 157, 51, 125, 121, 32, 147, 23, 132, 142, 195, 112, 250, 94, 143, 236, 61, 136], port: 49649 };95;[80, 02, 8d, 01, 01, 40, 4a, e6, 17, 01, fd, 4d, 74, 31, 17, da, 61, 78, fe, de, 80, 17, 0d, 81, 51, 84, 79, d4, 7a, f9, bf, b9, 87, 1f, 86, 4c, 9d, 29, 91, fc, 76, f1, 62, 2d, 57, d7, 52, 24, ab, b0, 29, 8a, 5a, 16, ed, 81, 02, c9, 71, 77, 41, 67, 11, 63, 9d, 33, 7d, 79, 20, 93, 17, 84, 8e, c3, 70, fa, 5e, 8f, ec, 3d, 88, c1, f1, 00] +tcp_req c000000fea268a380141017f578996479b97dc976a804f218231c0d47ec0b8ea0381a90140d1f103a4cc482ac50262593a81ca8036ededba3721c16e4f8f42b943f4631a4742585f5b3879f3fa78a8094dcc4c53912fd380ebf62d4521ec6f78b118ea7b151977a4e53586a52c73af7dc59ff0afd22041351d30d1ddd2ec7c5f2b62199b9a334938cea6b44ec9a790ca35912061c046139b79a2de5542744caf7bccf40fc202566ea328667f0e5ce974865e7b55732b77a38b871bf22e7e311ac78cd042e5a05aa5501e9c2d89fcf62d74af38ef88e5562b9a5a09b41089ddea74b9e2de05c9640fbee5b9790931a79ed1ad961486fe47db9f09fa7b62e38b11ca4850b95107fbfd136aa27b4b2f800000007d39a00135fa reject +tcp_req c0000001ba61c2800140cc50918996738f63d0c03b32ee60fcd08feb11f85c4e3f01f29371ad4d39ba70f6f70c4113b63fec8621251cff69bffbae588c0261ccf41bd969b26c1592b89bb2da2019e4a4c94e4477279bcf1c05946130bc594d2bdc7fd3f17c77505d0634151e8e261edae8274aa74c6796ab25a5d7fb9cfc9549cb085519646bb25253dc45b1544a3952f89ec1a1d6e52a46508b16244fc455bbc24e5691e3181745e2fdfd7df415beb72ea019d4200bbe91e63a44b3bca821da0452d4dac40e193853b759c85f46ffe088c5cac6eda2329d9b4000 ConnectTcpReq { flags: 7421936256, address_type: Ipv4, host: [80, 145, 137, 150, 115, 143, 99, 208, 192, 59, 50, 238, 96, 252, 208, 143, 235, 17, 248, 92, 78, 63, 1, 242, 147, 113, 173, 77, 57, 186, 112, 246, 247, 12, 65, 19, 182, 63, 236, 134, 33, 37, 28, 255, 105, 191, 251, 174, 88, 140, 2, 97, 204, 244, 27, 217, 105, 178, 108, 21, 146, 184, 155, 178, 218, 32, 25, 228, 164, 201, 78, 68, 119, 39, 155, 207, 28, 5, 148, 97, 48, 188, 89, 77, 43, 220, 127, 211, 241, 124, 119, 80, 93, 6, 52, 21, 30, 142, 38, 30, 218, 232, 39, 74, 167, 76, 103, 150, 171, 37, 165, 215, 251, 156, 252, 149, 73, 203, 8, 85, 25, 100, 107, 178, 82, 83, 220, 69, 177, 84, 74, 57, 82, 248, 158, 193, 161, 214, 229, 42, 70, 80, 139, 22, 36, 79, 196, 85, 187, 194, 78, 86, 145, 227, 24, 23, 69, 226, 253, 253, 125, 244, 21, 190, 183, 46, 160, 25, 212, 32, 11, 190, 145, 230, 58, 68, 179, 188, 168, 33, 218, 4, 82, 212, 218, 196, 14, 25, 56, 83, 183, 89, 200, 95, 70, 255, 224, 136, 197, 202, 198, 237, 162, 50], port: 40347 };219;[c0, 00, 00, 01, ba, 61, c2, 80, 01, 40, cc, 50, 91, 89, 96, 73, 8f, 63, d0, c0, 3b, 32, ee, 60, fc, d0, 8f, eb, 11, f8, 5c, 4e, 3f, 01, f2, 93, 71, ad, 4d, 39, ba, 70, f6, f7, 0c, 41, 13, b6, 3f, ec, 86, 21, 25, 1c, ff, 69, bf, fb, ae, 58, 8c, 02, 61, cc, f4, 1b, d9, 69, b2, 6c, 15, 92, b8, 9b, b2, da, 20, 19, e4, a4, c9, 4e, 44, 77, 27, 9b, cf, 1c, 05, 94, 61, 30, bc, 59, 4d, 2b, dc, 7f, d3, f1, 7c, 77, 50, 5d, 06, 34, 15, 1e, 8e, 26, 1e, da, e8, 27, 4a, a7, 4c, 67, 96, ab, 25, a5, d7, fb, 9c, fc, 95, 49, cb, 08, 55, 19, 64, 6b, b2, 52, 53, dc, 45, b1, 54, 4a, 39, 52, f8, 9e, c1, a1, d6, e5, 2a, 46, 50, 8b, 16, 24, 4f, c4, 55, bb, c2, 4e, 56, 91, e3, 18, 17, 45, e2, fd, fd, 7d, f4, 15, be, b7, 2e, a0, 19, d4, 20, 0b, be, 91, e6, 3a, 44, b3, bc, a8, 21, da, 04, 52, d4, da, c4, 0e, 19, 38, 53, b7, 59, c8, 5f, 46, ff, e0, 88, c5, ca, c6, ed, a2, 32, 9d, 9b, 00] +tcp_req 0e0140a71ab742daf013d896db3a1beae72712d796248c114f1ff7482b3b656019bf03abe52d79f41073e3d4c3dc75739f73af5931c203049ade9b883bb9ea0a6da87d5262c22d86bb391b706e74193cb6cfaa7bc18e799e5e50e1d74b27b59bbd3d5d5b66c9c72e8802ea5d1e6dda96617e1c6d5c7926541872b000071fbc7a58089942e4912100f6614cece510ab37d5a879ad6c070cb43ebfba9766ec518a21bdb0e179c540175ff46749c980000000 ConnectTcpReq { flags: 14, address_type: Ipv4, host: [26, 183, 66, 218, 240, 19, 216, 150, 219, 58, 27, 234, 231, 39, 18, 215, 150, 36, 140, 17, 79, 31, 247, 72, 43, 59, 101, 96, 25, 191, 3, 171, 229, 45, 121, 244, 16, 115, 227, 212, 195, 220, 117, 115, 159, 115, 175, 89, 49, 194, 3, 4, 154, 222, 155, 136, 59, 185, 234, 10, 109, 168, 125, 82, 98, 194, 45, 134, 187, 57, 27, 112, 110, 116, 25, 60, 182, 207, 170, 123, 193, 142, 121, 158, 94, 80, 225, 215, 75, 39, 181, 155, 189, 61, 93, 91, 102, 201, 199, 46, 136, 2, 234, 93, 30, 109, 218, 150, 97, 126, 28, 109, 92, 121, 38, 84, 24, 114, 176, 0, 7, 31, 188, 122, 88, 8, 153, 66, 228, 145, 33, 0, 246, 97, 76, 236, 229, 16, 171, 55, 213, 168, 121, 173, 108, 7, 12, 180, 62, 191, 186, 151, 102, 236, 81, 138, 33, 189, 176, 225, 121, 197, 64, 23, 95, 244, 103], port: 18889 };177;[0e, 01, 40, a7, 1a, b7, 42, da, f0, 13, d8, 96, db, 3a, 1b, ea, e7, 27, 12, d7, 96, 24, 8c, 11, 4f, 1f, f7, 48, 2b, 3b, 65, 60, 19, bf, 03, ab, e5, 2d, 79, f4, 10, 73, e3, d4, c3, dc, 75, 73, 9f, 73, af, 59, 31, c2, 03, 04, 9a, de, 9b, 88, 3b, b9, ea, 0a, 6d, a8, 7d, 52, 62, c2, 2d, 86, bb, 39, 1b, 70, 6e, 74, 19, 3c, b6, cf, aa, 7b, c1, 8e, 79, 9e, 5e, 50, e1, d7, 4b, 27, b5, 9b, bd, 3d, 5d, 5b, 66, c9, c7, 2e, 88, 02, ea, 5d, 1e, 6d, da, 96, 61, 7e, 1c, 6d, 5c, 79, 26, 54, 18, 72, b0, 00, 07, 1f, bc, 7a, 58, 08, 99, 42, e4, 91, 21, 00, f6, 61, 4c, ec, e5, 10, ab, 37, d5, a8, 79, ad, 6c, 07, 0c, b4, 3e, bf, ba, 97, 66, ec, 51, 8a, 21, bd, b0, e1, 79, c5, 40, 17, 5f, f4, 67, 49, c9, 00] +tcp_req 800195a0044048ddb53d6d41519743e0a52c9da4ea2719ee82930e57507349c348382ad569edb6ae5ce72d3b5cb4566814af329d265fdbe23228194713f3b5023506036b0cb4bcf799732cb307aebd107f8000000a3e126bacf2d0db6f8f37 ConnectTcpReq { flags: 103840, address_type: Ipv6, host: [221, 181, 61, 109, 65, 81, 151, 67, 224, 165, 44, 157, 164, 234, 39, 25, 238, 130, 147, 14, 87, 80, 115, 73, 195, 72, 56, 42, 213, 105, 237, 182, 174, 92, 231, 45, 59, 92, 180, 86, 104, 20, 175, 50, 157, 38, 95, 219, 226, 50, 40, 25, 71, 19, 243, 181, 2, 53, 6, 3, 107, 12, 180, 188, 247, 153, 115, 44, 179, 7, 174, 189], port: 4223 };95;[80, 01, 95, a0, 04, 40, 48, dd, b5, 3d, 6d, 41, 51, 97, 43, e0, a5, 2c, 9d, a4, ea, 27, 19, ee, 82, 93, 0e, 57, 50, 73, 49, c3, 48, 38, 2a, d5, 69, ed, b6, ae, 5c, e7, 2d, 3b, 5c, b4, 56, 68, 14, af, 32, 9d, 26, 5f, db, e2, 32, 28, 19, 47, 13, f3, b5, 02, 35, 06, 03, 6b, 0c, b4, bc, f7, 99, 73, 2c, b3, 07, ae, bd, 10, 7f, 00] +tcp_req c000019dd9123f460180000049304753818a75584b44a6e5cb31369a825acb59a49b0a14b6c2334e342fa5c493178cc61cf6b3db50d644025063e74040180ff140a4cfac05f60a7163c3973d7c1644fef8015c4095d28d6480000000 ConnectTcpReq { flags: 1777463344966, address_type: Ipv4, host: [48, 71, 83, 129, 138, 117, 88, 75, 68, 166, 229, 203, 49, 54, 154, 130, 90, 203, 89, 164, 155, 10, 20, 182, 194, 51, 78, 52, 47, 165, 196, 147, 23, 140, 198, 28, 246, 179, 219, 80, 214, 68, 2, 80, 99, 231, 64, 64, 24, 15, 241, 64, 164, 207, 172, 5, 246, 10, 113, 99, 195, 151, 61, 124, 22, 68, 254, 248, 1, 92, 64, 149, 210], port: 36196 };92;[c0, 00, 01, 9d, d9, 12, 3f, 46, 01, 40, 49, 30, 47, 53, 81, 8a, 75, 58, 4b, 44, a6, e5, cb, 31, 36, 9a, 82, 5a, cb, 59, a4, 9b, 0a, 14, b6, c2, 33, 4e, 34, 2f, a5, c4, 93, 17, 8c, c6, 1c, f6, b3, db, 50, d6, 44, 02, 50, 63, e7, 40, 40, 18, 0f, f1, 40, a4, cf, ac, 05, f6, 0a, 71, 63, c3, 97, 3d, 7c, 16, 44, fe, f8, 01, 5c, 40, 95, d2, 8d, 64, 00] +tcp_req 400204406e9766cabfd0c18343c4a308a51f840b2edc47337b1554cc7dba5a85268978f6a7a098eb8e6286a2170d58b3c42a909b2b8ea006be485f49c67696450e48ad90f86efd1134537c06140da88af33ac0e0c10af998354fa85c581de0f62becaff30871a5b33f41be48e229228163ea508e9280000000 ConnectTcpReq { flags: 2, address_type: Ipv6, host: [151, 102, 202, 191, 208, 193, 131, 67, 196, 163, 8, 165, 31, 132, 11, 46, 220, 71, 51, 123, 21, 84, 204, 125, 186, 90, 133, 38, 137, 120, 246, 167, 160, 152, 235, 142, 98, 134, 162, 23, 13, 88, 179, 196, 42, 144, 155, 43, 142, 160, 6, 190, 72, 95, 73, 198, 118, 150, 69, 14, 72, 173, 144, 248, 110, 253, 17, 52, 83, 124, 6, 20, 13, 168, 138, 243, 58, 192, 224, 193, 10, 249, 152, 53, 79, 168, 92, 88, 29, 224, 246, 43, 236, 175, 243, 8, 113, 165, 179, 63, 65, 190, 72, 226, 41, 34, 129, 99, 234, 80], port: 36498 };121;[02, 04, 40, 6e, 97, 66, ca, bf, d0, c1, 83, 43, c4, a3, 08, a5, 1f, 84, 0b, 2e, dc, 47, 33, 7b, 15, 54, cc, 7d, ba, 5a, 85, 26, 89, 78, f6, a7, a0, 98, eb, 8e, 62, 86, a2, 17, 0d, 58, b3, c4, 2a, 90, 9b, 2b, 8e, a0, 06, be, 48, 5f, 49, c6, 76, 96, 45, 0e, 48, ad, 90, f8, 6e, fd, 11, 34, 53, 7c, 06, 14, 0d, a8, 8a, f3, 3a, c0, e0, c1, 0a, f9, 98, 35, 4f, a8, 5c, 58, 1d, e0, f6, 2b, ec, af, f3, 08, 71, a5, b3, 3f, 41, be, 48, e2, 29, 22, 81, 63, ea, 50, 8e, 92, 00] +tcp_req 8000000003407b3f2dd49dd3f51b2112a2b9216d245442db4b72305638a2d76a1e9a0ceab54a80c548dc082d4c44234b6aa28b6a8867a959018812b838d816553dd7eea2087c9623e306f6068c463de5b61040404b2a7b7cb9db3c14fd4b196d86eeb188bd4330680a3e83bc67dd473438b6b04a62c2b4c04961c7ba1f7bdb35639c2fe6c000000000000000bb ConnectTcpReq { flags: 0, address_type: Domain, host: [63, 45, 212, 157, 211, 245, 27, 33, 18, 162, 185, 33, 109, 36, 84, 66, 219, 75, 114, 48, 86, 56, 162, 215, 106, 30, 154, 12, 234, 181, 74, 128, 197, 72, 220, 8, 45, 76, 68, 35, 75, 106, 162, 139, 106, 136, 103, 169, 89, 1, 136, 18, 184, 56, 216, 22, 85, 61, 215, 238, 162, 8, 124, 150, 35, 227, 6, 246, 6, 140, 70, 61, 229, 182, 16, 64, 64, 75, 42, 123, 124, 185, 219, 60, 20, 253, 75, 25, 109, 134, 238, 177, 136, 189, 67, 48, 104, 10, 62, 131, 188, 103, 221, 71, 52, 56, 182, 176, 74, 98, 194, 180, 192, 73, 97, 199, 186, 31, 123, 219, 53, 99, 156], port: 12262 };140;[00, 03, 40, 7b, 3f, 2d, d4, 9d, d3, f5, 1b, 21, 12, a2, b9, 21, 6d, 24, 54, 42, db, 4b, 72, 30, 56, 38, a2, d7, 6a, 1e, 9a, 0c, ea, b5, 4a, 80, c5, 48, dc, 08, 2d, 4c, 44, 23, 4b, 6a, a2, 8b, 6a, 88, 67, a9, 59, 01, 88, 12, b8, 38, d8, 16, 55, 3d, d7, ee, a2, 08, 7c, 96, 23, e3, 06, f6, 06, 8c, 46, 3d, e5, b6, 10, 40, 40, 4b, 2a, 7b, 7c, b9, db, 3c, 14, fd, 4b, 19, 6d, 86, ee, b1, 88, bd, 43, 30, 68, 0a, 3e, 83, bc, 67, dd, 47, 34, 38, b6, b0, 4a, 62, c2, b4, c0, 49, 61, c7, ba, 1f, 7b, db, 35, 63, 9c, 2f, e6, 00] +tcp_req 421501407c5154b0a5e161e3cad150363c94030c4eb29e04fbb4cf443e667b8f65dc908f95a7cbfb5a5c3d4cf7b7d3aed973aedad8c2264f119c163c9866b111d25c1ef1aefd0f8b2786901251aaa325b194eabcccdc099f20e25c415ff2bab4a76c40d67b4a5e59b3eec91aa719ee5f7a511d2648ffcc11b430106c1f557c3319924480000000 ConnectTcpReq { flags: 533, address_type: Ipv4, host: [81, 84, 176, 165, 225, 97, 227, 202, 209, 80, 54, 60, 148, 3, 12, 78, 178, 158, 4, 251, 180, 207, 68, 62, 102, 123, 143, 101, 220, 144, 143, 149, 167, 203, 251, 90, 92, 61, 76, 247, 183, 211, 174, 217, 115, 174, 218, 216, 194, 38, 79, 17, 156, 22, 60, 152, 102, 177, 17, 210, 92, 30, 241, 174, 253, 15, 139, 39, 134, 144, 18, 81, 170, 163, 37, 177, 148, 234, 188, 204, 220, 9, 159, 32, 226, 92, 65, 95, 242, 186, 180, 167, 108, 64, 214, 123, 74, 94, 89, 179, 238, 201, 26, 167, 25, 238, 95, 122, 81, 29, 38, 72, 255, 204, 17, 180, 48, 16, 108, 31, 85, 124, 51, 25], port: 37444 };135;[42, 15, 01, 40, 7c, 51, 54, b0, a5, e1, 61, e3, ca, d1, 50, 36, 3c, 94, 03, 0c, 4e, b2, 9e, 04, fb, b4, cf, 44, 3e, 66, 7b, 8f, 65, dc, 90, 8f, 95, a7, cb, fb, 5a, 5c, 3d, 4c, f7, b7, d3, ae, d9, 73, ae, da, d8, c2, 26, 4f, 11, 9c, 16, 3c, 98, 66, b1, 11, d2, 5c, 1e, f1, ae, fd, 0f, 8b, 27, 86, 90, 12, 51, aa, a3, 25, b1, 94, ea, bc, cc, dc, 09, 9f, 20, e2, 5c, 41, 5f, f2, ba, b4, a7, 6c, 40, d6, 7b, 4a, 5e, 59, b3, ee, c9, 1a, a7, 19, ee, 5f, 7a, 51, 1d, 26, 48, ff, cc, 11, b4, 30, 10, 6c, 1f, 55, 7c, 33, 19, 92, 44, 00] +tcp_req c00001609450264b04c0000000000000decde1bb700713d325d2af9797dd1d3bb994672b53f44687164876474062ff85527a27b8cd375116f3eac6dc0f279e3c9a88266b71fbed0fbccb2d0cb9037211343c9ea59814905aefa1576a2c1e95971539cd8295d245092bfe38ce1b5acc2a3eeef63a4e942aa3136ce44f9100ba08d1c0667753a2e290560d506162fd541889cf68d1636c36e3c4a8230566058685f3b066a16b0bad3fdff97d27692a6506402f4ea8d8c8c7df3ba2c8b6c827e484d67a33cb974a70dc43a9cda3ff30422e21dc8ec71c5d55a3c175d5a51e2eeedf653f4c6940d00705a9733d1ee171828673400158 ConnectTcpReq { flags: 1514316768843, address_type: Ipv6, host: [205, 225, 187, 112, 7, 19, 211, 37, 210, 175, 151, 151, 221, 29, 59, 185, 148, 103, 43, 83, 244, 70, 135, 22, 72, 118, 71, 64, 98, 255, 133, 82, 122, 39, 184, 205, 55, 81, 22, 243, 234, 198, 220, 15, 39, 158, 60, 154, 136, 38, 107, 113, 251, 237, 15, 188, 203, 45, 12, 185, 3, 114, 17, 52, 60, 158, 165, 152, 20, 144, 90, 239, 161, 87, 106, 44, 30, 149, 151, 21, 57, 205, 130, 149, 210, 69, 9, 43, 254, 56, 206, 27, 90, 204, 42, 62, 238, 246, 58, 78, 148, 42, 163, 19, 108, 228, 79, 145, 0, 186, 8, 209, 192, 102, 119, 83, 162, 226, 144, 86, 13, 80, 97, 98, 253, 84, 24, 137, 207, 104, 209, 99, 108, 54, 227, 196, 168, 35, 5, 102, 5, 134, 133, 243, 176, 102, 161, 107, 11, 173, 63, 223, 249, 125, 39, 105, 42, 101, 6, 64, 47, 78, 168, 216, 200, 199, 223, 59, 162, 200, 182, 200, 39, 228, 132, 214, 122, 51, 203, 151, 74, 112, 220, 67, 169, 205, 163, 255, 48, 66, 46, 33, 220, 142, 199, 28, 93, 85, 163, 193, 117, 213, 165, 30, 46, 238, 223, 101, 63, 76, 105, 64, 208, 7, 5, 169, 115, 61, 30, 225, 113, 130], port: 34419 };244;[c0, 00, 01, 60, 94, 50, 26, 4b, 04, 40, de, cd, e1, bb, 70, 07, 13, d3, 25, d2, af, 97, 97, dd, 1d, 3b, b9, 94, 67, 2b, 53, f4, 46, 87, 16, 48, 76, 47, 40, 62, ff, 85, 52, 7a, 27, b8, cd, 37, 51, 16, f3, ea, c6, dc, 0f, 27, 9e, 3c, 9a, 88, 26, 6b, 71, fb, ed, 0f, bc, cb, 2d, 0c, b9, 03, 72, 11, 34, 3c, 9e, a5, 98, 14, 90, 5a, ef, a1, 57, 6a, 2c, 1e, 95, 97, 15, 39, cd, 82, 95, d2, 45, 09, 2b, fe, 38, ce, 1b, 5a, cc, 2a, 3e, ee, f6, 3a, 4e, 94, 2a, a3, 13, 6c, e4, 4f, 91, 00, ba, 08, d1, c0, 66, 77, 53, a2, e2, 90, 56, 0d, 50, 61, 62, fd, 54, 18, 89, cf, 68, d1, 63, 6c, 36, e3, c4, a8, 23, 05, 66, 05, 86, 85, f3, b0, 66, a1, 6b, 0b, ad, 3f, df, f9, 7d, 27, 69, 2a, 65, 06, 40, 2f, 4e, a8, d8, c8, c7, df, 3b, a2, c8, b6, c8, 27, e4, 84, d6, 7a, 33, cb, 97, 4a, 70, dc, 43, a9, cd, a3, ff, 30, 42, 2e, 21, dc, 8e, c7, 1c, 5d, 55, a3, c1, 75, d5, a5, 1e, 2e, ee, df, 65, 3f, 4c, 69, 40, d0, 07, 05, a9, 73, 3d, 1e, e1, 71, 82, 86, 73, 00] +tcp_req c000000001afb9ba0440b72a908e0b078256d4367d681f02b27e7ed8deefec652ac84b096bdd2ea1059afbea11055302cabfde6310fbfa16ba36798f0ffa44d5a2a4b46be5b8c61f77d8125e61cbc5c4057ad41ad8d138a68ba1634cb1b1cac9b40dd375bb764eb54550d2cd636592e071337821551c5d8ffc6ff0dc4e717586e43fc73c505c00b6db40de7057aee55d30c2dab80b2d5a59f2618ab16989045b8ee8750f512ea80af79967cf3159656481eba5c3361a0f6df5e531fb4ae08ec453d84d3c0c15e7eab8b73d67981c87bf84 ConnectTcpReq { flags: 28293562, address_type: Ipv6, host: [42, 144, 142, 11, 7, 130, 86, 212, 54, 125, 104, 31, 2, 178, 126, 126, 216, 222, 239, 236, 101, 42, 200, 75, 9, 107, 221, 46, 161, 5, 154, 251, 234, 17, 5, 83, 2, 202, 191, 222, 99, 16, 251, 250, 22, 186, 54, 121, 143, 15, 250, 68, 213, 162, 164, 180, 107, 229, 184, 198, 31, 119, 216, 18, 94, 97, 203, 197, 196, 5, 122, 212, 26, 216, 209, 56, 166, 139, 161, 99, 76, 177, 177, 202, 201, 180, 13, 211, 117, 187, 118, 78, 181, 69, 80, 210, 205, 99, 101, 146, 224, 113, 51, 120, 33, 85, 28, 93, 143, 252, 111, 240, 220, 78, 113, 117, 134, 228, 63, 199, 60, 80, 92, 0, 182, 219, 64, 222, 112, 87, 174, 229, 93, 48, 194, 218, 184, 11, 45, 90, 89, 242, 97, 138, 177, 105, 137, 4, 91, 142, 232, 117, 15, 81, 46, 168, 10, 247, 153, 103, 207, 49, 89, 101, 100, 129, 235, 165, 195, 54, 26, 15, 109, 245, 229, 49, 251, 74, 224, 142, 196, 83, 216], port: 19772 };209;[81, af, b9, ba, 04, 40, b7, 2a, 90, 8e, 0b, 07, 82, 56, d4, 36, 7d, 68, 1f, 02, b2, 7e, 7e, d8, de, ef, ec, 65, 2a, c8, 4b, 09, 6b, dd, 2e, a1, 05, 9a, fb, ea, 11, 05, 53, 02, ca, bf, de, 63, 10, fb, fa, 16, ba, 36, 79, 8f, 0f, fa, 44, d5, a2, a4, b4, 6b, e5, b8, c6, 1f, 77, d8, 12, 5e, 61, cb, c5, c4, 05, 7a, d4, 1a, d8, d1, 38, a6, 8b, a1, 63, 4c, b1, b1, ca, c9, b4, 0d, d3, 75, bb, 76, 4e, b5, 45, 50, d2, cd, 63, 65, 92, e0, 71, 33, 78, 21, 55, 1c, 5d, 8f, fc, 6f, f0, dc, 4e, 71, 75, 86, e4, 3f, c7, 3c, 50, 5c, 00, b6, db, 40, de, 70, 57, ae, e5, 5d, 30, c2, da, b8, 0b, 2d, 5a, 59, f2, 61, 8a, b1, 69, 89, 04, 5b, 8e, e8, 75, 0f, 51, 2e, a8, 0a, f7, 99, 67, cf, 31, 59, 65, 64, 81, eb, a5, c3, 36, 1a, 0f, 6d, f5, e5, 31, fb, 4a, e0, 8e, c4, 53, d8, 4d, 3c, 00] +tcp_req c000000243308725048000003f8cddd5880c59841155a15aedebd808512170e79e408750ea5fc69be90a9c6334472867a74b30ae697318e9b6a636392acdaeadbddc6804b2d6925445ed97d4a51900 ConnectTcpReq { flags: 9717188389, address_type: Ipv6, host: [140, 221, 213, 136, 12, 89, 132, 17, 85, 161, 90, 237, 235, 216, 8, 81, 33, 112, 231, 158, 64, 135, 80, 234, 95, 198, 155, 233, 10, 156, 99, 52, 71, 40, 103, 167, 75, 48, 174, 105, 115, 24, 233, 182, 166, 54, 57, 42, 205, 174, 173, 189, 220, 104, 4, 178, 214, 146, 84, 69, 237, 151, 212], port: 42265 };79;[c0, 00, 00, 02, 43, 30, 87, 25, 04, 3f, 8c, dd, d5, 88, 0c, 59, 84, 11, 55, a1, 5a, ed, eb, d8, 08, 51, 21, 70, e7, 9e, 40, 87, 50, ea, 5f, c6, 9b, e9, 0a, 9c, 63, 34, 47, 28, 67, a7, 4b, 30, ae, 69, 73, 18, e9, b6, a6, 36, 39, 2a, cd, ae, ad, bd, dc, 68, 04, b2, d6, 92, 54, 45, ed, 97, d4, a5, 19, 00] +tcp_req c01b24c4437bccdc03405584a140c72290be145aaa35d1a1fb61f44010caf4a3995f144ee2cc496f120b3e1c3380b35728c971611438d0a7a192b67e36fc026828af0d8525b2e0e8e3ee602d34160af700801d2f1b18c9d3f3a142f8528dacce9df0006957557509 ConnectTcpReq { flags: 7640249735564508, address_type: Domain, host: [132, 161, 64, 199, 34, 144, 190, 20, 90, 170, 53, 209, 161, 251, 97, 244, 64, 16, 202, 244, 163, 153, 95, 20, 78, 226, 204, 73, 111, 18, 11, 62, 28, 51, 128, 179, 87, 40, 201, 113, 97, 20, 56, 208, 167, 161, 146, 182, 126, 54, 252, 2, 104, 40, 175, 13, 133, 37, 178, 224, 232, 227, 238, 96, 45, 52, 22, 10, 247, 0, 128, 29, 47, 27, 24, 201, 211, 243, 161, 66, 248, 82, 141, 172, 206], port: 40432 };99;[c0, 1b, 24, c4, 43, 7b, cc, dc, 03, 40, 55, 84, a1, 40, c7, 22, 90, be, 14, 5a, aa, 35, d1, a1, fb, 61, f4, 40, 10, ca, f4, a3, 99, 5f, 14, 4e, e2, cc, 49, 6f, 12, 0b, 3e, 1c, 33, 80, b3, 57, 28, c9, 71, 61, 14, 38, d0, a7, a1, 92, b6, 7e, 36, fc, 02, 68, 28, af, 0d, 85, 25, b2, e0, e8, e3, ee, 60, 2d, 34, 16, 0a, f7, 00, 80, 1d, 2f, 1b, 18, c9, d3, f3, a1, 42, f8, 52, 8d, ac, ce, 9d, f0, 00] +tcp_req c000023e0f116ab7c9406b592e8cea5e65ccb3ec89be6ece3ccf6d3f6a439a384165e129cef120ae8c66e9c791d83e3e9301f9eda1a52ab147063edd730f91424c66f6e8362beb131ddd4ec8028da046be6d3a1181e3ccd3a46369f2bd22062f958f686f1defb9c375c07e8c52c78d6cf99d2f994ca8a1560f547e6caa7cdcfd8e846b8c993189f8 reject +tcp_req c00000000001acd70380000038cbf02892b04090af6d0ac7df15b8d9e7c4590ebbb82998525d512181ed44d3be3a21868b4053ec2418c4e932d52d3084bef42ba1e8b63f7e61da4000dc457fc86c ConnectTcpReq { flags: 109783, address_type: Domain, host: [203, 240, 40, 146, 176, 64, 144, 175, 109, 10, 199, 223, 21, 184, 217, 231, 196, 89, 14, 187, 184, 41, 152, 82, 93, 81, 33, 129, 237, 68, 211, 190, 58, 33, 134, 139, 64, 83, 236, 36, 24, 196, 233, 50, 213, 45, 48, 132, 190, 244, 43, 161, 232, 182, 63, 126], port: 25050 };73;[80, 01, ac, d7, 03, 38, cb, f0, 28, 92, b0, 40, 90, af, 6d, 0a, c7, df, 15, b8, d9, e7, c4, 59, 0e, bb, b8, 29, 98, 52, 5d, 51, 21, 81, ed, 44, d3, be, 3a, 21, 86, 8b, 40, 53, ec, 24, 18, c4, e9, 32, d5, 2d, 30, 84, be, f4, 2b, a1, e8, b6, 3f, 7e, 61, da, 00] +tcp_req c00714d01cb61ea60480000100cd7f00e5372ecf29729a3d2375c1a70b2719810ad96a65e13dff59101d908dfdfaafb78f3ea28a6d4554b2eaa8d0382b36ff823654cd22e16c640d5b017cff1e711f0bacd158ee3aa694a79af8e9a7c30f3dc546c9786b263612ed3c957cfa22266c829e406884d6b199aff61d815d85b3d020e0f9a427ce0de0f6a20806fed1d9f273c435d56d419db849dd5729afc9a7c0b0222da75de6406e41acc3275d01f33f895a45ad4e268cf393d2c2bde7bdfde84d75d9b7b3f227fab9ee619cbd557533e6db080f524f58bbc3bde32089da6b1771b160b3c91cb542b6c0d8874f5614f3f775e09181467f4dc1daf095518ae57491875716f705b265843c161b9956e61180000000 reject +tcp_req c0000000000e30db0340dfa293a466d05c87991e82c4cbad570887ef351f409832082d75be0ce0ae64980bf5d783fdefdaba34f04c3dbc0333192180687a4f96967eb3a8e79e687de43a61884ecd321c7a91e7df5486c8aa965ab8f8e08b77fad93c2a83a9d54ff957be9d3a7ce831aa801f97aca2dc045e0e2d02d4e35337298bab1ad1a8657f3a9e479b88dd46e9542365d051ff7abab2f3e1a791d2d09a0a99ec270ca4ae7c4f46adc0c4d51d4832351f403c7ae53058c6215c06dab444112a8b35536bd4c55d130ce32c42886d8773be5869d7ebb77b0146f4b01835e93d9d62548293da83b2b87363628000000fc56c09ae661c12340fcc411c958a3e ConnectTcpReq { flags: 930011, address_type: Domain, host: [162, 147, 164, 102, 208, 92, 135, 153, 30, 130, 196, 203, 173, 87, 8, 135, 239, 53, 31, 64, 152, 50, 8, 45, 117, 190, 12, 224, 174, 100, 152, 11, 245, 215, 131, 253, 239, 218, 186, 52, 240, 76, 61, 188, 3, 51, 25, 33, 128, 104, 122, 79, 150, 150, 126, 179, 168, 231, 158, 104, 125, 228, 58, 97, 136, 78, 205, 50, 28, 122, 145, 231, 223, 84, 134, 200, 170, 150, 90, 184, 248, 224, 139, 119, 250, 217, 60, 42, 131, 169, 213, 79, 249, 87, 190, 157, 58, 124, 232, 49, 170, 128, 31, 151, 172, 162, 220, 4, 94, 14, 45, 2, 212, 227, 83, 55, 41, 139, 171, 26, 209, 168, 101, 127, 58, 158, 71, 155, 136, 221, 70, 233, 84, 35, 101, 208, 81, 255, 122, 186, 178, 243, 225, 167, 145, 210, 208, 154, 10, 153, 236, 39, 12, 164, 174, 124, 79, 70, 173, 192, 196, 213, 29, 72, 50, 53, 31, 64, 60, 122, 229, 48, 88, 198, 33, 92, 6, 218, 180, 68, 17, 42, 139, 53, 83, 107, 212, 197, 93, 19, 12, 227, 44, 66, 136, 109, 135, 115, 190, 88, 105, 215, 235, 183, 123, 1, 70, 244, 176, 24, 53, 233, 61, 157, 98, 84, 130, 147, 218, 131, 178, 184, 115], port: 25442 };255;[80, 0e, 30, db, 03, 40, df, a2, 93, a4, 66, d0, 5c, 87, 99, 1e, 82, c4, cb, ad, 57, 08, 87, ef, 35, 1f, 40, 98, 32, 08, 2d, 75, be, 0c, e0, ae, 64, 98, 0b, f5, d7, 83, fd, ef, da, ba, 34, f0, 4c, 3d, bc, 03, 33, 19, 21, 80, 68, 7a, 4f, 96, 96, 7e, b3, a8, e7, 9e, 68, 7d, e4, 3a, 61, 88, 4e, cd, 32, 1c, 7a, 91, e7, df, 54, 86, c8, aa, 96, 5a, b8, f8, e0, 8b, 77, fa, d9, 3c, 2a, 83, a9, d5, 4f, f9, 57, be, 9d, 3a, 7c, e8, 31, aa, 80, 1f, 97, ac, a2, dc, 04, 5e, 0e, 2d, 02, d4, e3, 53, 37, 29, 8b, ab, 1a, d1, a8, 65, 7f, 3a, 9e, 47, 9b, 88, dd, 46, e9, 54, 23, 65, d0, 51, ff, 7a, ba, b2, f3, e1, a7, 91, d2, d0, 9a, 0a, 99, ec, 27, 0c, a4, ae, 7c, 4f, 46, ad, c0, c4, d5, 1d, 48, 32, 35, 1f, 40, 3c, 7a, e5, 30, 58, c6, 21, 5c, 06, da, b4, 44, 11, 2a, 8b, 35, 53, 6b, d4, c5, 5d, 13, 0c, e3, 2c, 42, 88, 6d, 87, 73, be, 58, 69, d7, eb, b7, 7b, 01, 46, f4, b0, 18, 35, e9, 3d, 9d, 62, 54, 82, 93, da, 83, b2, b8, 73, 63, 62, 00] +tcp_req c00010dde6ea0b62c4402b87fc166cda8cb86999d1946a74d7ae30ac02ed035b3f283fa7979369b35e2122dd946950e7e126147bc9f32f474000 reject +tcp_req 801ecba80480000074ebb193193c7323a14adc32f5c0f8a4accd82f8b29dc9a4e0b8806710ed29abfc1bb50088f5ae5232db2dede99f3d9df5fb2b7974a27cbb0f15c6289f859e915de8ce4c88b927ce7400d1de787d5f3256b298d9f1f5368bf0e70060e6d90248bd69195725073b7f723bc53840a2bdd3270b848ffff7dc00 ConnectTcpReq { flags: 2018216, address_type: Ipv6, host: [235, 177, 147, 25, 60, 115, 35, 161, 74, 220, 50, 245, 192, 248, 164, 172, 205, 130, 248, 178, 157, 201, 164, 224, 184, 128, 103, 16, 237, 41, 171, 252, 27, 181, 0, 136, 245, 174, 82, 50, 219, 45, 237, 233, 159, 61, 157, 245, 251, 43, 121, 116, 162, 124, 187, 15, 21, 198, 40, 159, 133, 158, 145, 93, 232, 206, 76, 136, 185, 39, 206, 116, 0, 209, 222, 120, 125, 95, 50, 86, 178, 152, 217, 241, 245, 54, 139, 240, 231, 0, 96, 230, 217, 2, 72, 189, 105, 25, 87, 37, 7, 59, 127, 114, 59, 197, 56, 64, 162, 189, 211, 39, 11, 132, 143, 255], port: 63452 };128;[80, 1e, cb, a8, 04, 40, 74, eb, b1, 93, 19, 3c, 73, 23, a1, 4a, dc, 32, f5, c0, f8, a4, ac, cd, 82, f8, b2, 9d, c9, a4, e0, b8, 80, 67, 10, ed, 29, ab, fc, 1b, b5, 00, 88, f5, ae, 52, 32, db, 2d, ed, e9, 9f, 3d, 9d, f5, fb, 2b, 79, 74, a2, 7c, bb, 0f, 15, c6, 28, 9f, 85, 9e, 91, 5d, e8, ce, 4c, 88, b9, 27, ce, 74, 00, d1, de, 78, 7d, 5f, 32, 56, b2, 98, d9, f1, f5, 36, 8b, f0, e7, 00, 60, e6, d9, 02, 48, bd, 69, 19, 57, 25, 07, 3b, 7f, 72, 3b, c5, 38, 40, a2, bd, d3, 27, 0b, 84, 8f, ff, f7, dc, 00] +tcp_req c000000060effeaf0440412cefd8ee190ac5861e18c10ba282ea0621582d6370323d167d062a6bcfb685e41a9dccf7dffcf0d358d1ab92a751d52dffa9899e3c25d2ed27f0ba1435ac979b97e74b400df0f93b8f6fd0f031fa79062c06 ConnectTcpReq { flags: 1626341039, address_type: Ipv6, host: [44, 239, 216, 238, 25, 10, 197, 134, 30, 24, 193, 11, 162, 130, 234, 6, 33, 88, 45, 99, 112, 50, 61, 22, 125, 6, 42, 107, 207, 182, 133, 228, 26, 157, 204, 247, 223, 252, 240, 211, 88, 209, 171, 146, 167, 81, 213, 45, 255, 169, 137, 158, 60, 37, 210, 237, 39, 240, 186, 20, 53, 172, 151, 155, 151], port: 59211 };93;[c0, 00, 00, 00, 60, ef, fe, af, 04, 40, 41, 2c, ef, d8, ee, 19, 0a, c5, 86, 1e, 18, c1, 0b, a2, 82, ea, 06, 21, 58, 2d, 63, 70, 32, 3d, 16, 7d, 06, 2a, 6b, cf, b6, 85, e4, 1a, 9d, cc, f7, df, fc, f0, d3, 58, d1, ab, 92, a7, 51, d5, 2d, ff, a9, 89, 9e, 3c, 25, d2, ed, 27, f0, ba, 14, 35, ac, 97, 9b, 97, e7, 4b, 00] +tcp_req c0003f729825a3ca0440226c75783dfc9cc55f0176f40d72d05472b1f148dd3e01b1d1e12256c408ea9df51aa8506c80000000 ConnectTcpReq { flags: 69761411425226, address_type: Ipv6, host: [108, 117, 120, 61, 252, 156, 197, 95, 1, 118, 244, 13, 114, 208, 84, 114, 177, 241, 72, 221, 62, 1, 177, 209, 225, 34, 86, 196, 8, 234, 157, 245, 26, 168], port: 20588 };51;[c0, 00, 3f, 72, 98, 25, a3, ca, 04, 22, 6c, 75, 78, 3d, fc, 9c, c5, 5f, 01, 76, f4, 0d, 72, d0, 54, 72, b1, f1, 48, dd, 3e, 01, b1, d1, e1, 22, 56, c4, 08, ea, 9d, f5, 1a, a8, 50, 6c, 00] +tcp_req c0000000a23cef9f014062211254eceb160c0bbab5e4760a4e85ce56d370e7a860a9b667ed5469da0e6bb656482c0783d41d473d7f740515490a0c363895d9a47b4ab75b469e096a4a0c4090296a2f6d315cbe30603f841bccd8b7d5702f6aa47d5e537c5bb845e5fb89fb89ead09f80000000 ConnectTcpReq { flags: 2721902495, address_type: Ipv4, host: [33, 18, 84, 236, 235, 22, 12, 11, 186, 181, 228, 118, 10, 78, 133, 206, 86, 211, 112, 231, 168, 96, 169, 182, 103, 237, 84, 105, 218, 14, 107, 182, 86, 72, 44, 7, 131, 212, 29, 71, 61, 127, 116, 5, 21, 73, 10, 12, 54, 56, 149, 217, 164, 123, 74, 183, 91, 70, 158, 9, 106, 74, 12, 64, 144, 41, 106, 47, 109, 49, 92, 190, 48, 96, 63, 132, 27, 204, 216, 183, 213, 112, 47, 106, 164, 125, 94, 83, 124, 91, 184, 69, 229, 251, 137, 251, 137, 234], port: 53407 };115;[c0, 00, 00, 00, a2, 3c, ef, 9f, 01, 40, 62, 21, 12, 54, ec, eb, 16, 0c, 0b, ba, b5, e4, 76, 0a, 4e, 85, ce, 56, d3, 70, e7, a8, 60, a9, b6, 67, ed, 54, 69, da, 0e, 6b, b6, 56, 48, 2c, 07, 83, d4, 1d, 47, 3d, 7f, 74, 05, 15, 49, 0a, 0c, 36, 38, 95, d9, a4, 7b, 4a, b7, 5b, 46, 9e, 09, 6a, 4a, 0c, 40, 90, 29, 6a, 2f, 6d, 31, 5c, be, 30, 60, 3f, 84, 1b, cc, d8, b7, d5, 70, 2f, 6a, a4, 7d, 5e, 53, 7c, 5b, b8, 45, e5, fb, 89, fb, 89, ea, d0, 9f, 00] +tcp_req c00000000000000903c00000000000009a88f9eaafea2ab1effe8b102a9171fb1ec1283a1c39b9be0bf7801db8dfe6801478dc1a1dfc56dddd2c409d5add0a83c1d64d866d9a002beda7f34fca3e0221736934023203172c15f5fc3307fd0cb7a229d5b24e732cd2c84a71a19c109eb8a73ca2ace7053ee1499c2b8d0718dff2e9d41f3967108c21314cf6ed751be9f09b0810222a05969abf779e52ed84f6c286de1934e9d3a0d2e501918705c000000000000000f820467344 ConnectTcpReq { flags: 9, address_type: Domain, host: [136, 249, 234, 175, 234, 42, 177, 239, 254, 139, 16, 42, 145, 113, 251, 30, 193, 40, 58, 28, 57, 185, 190, 11, 247, 128, 29, 184, 223, 230, 128, 20, 120, 220, 26, 29, 252, 86, 221, 221, 44, 64, 157, 90, 221, 10, 131, 193, 214, 77, 134, 109, 154, 0, 43, 237, 167, 243, 79, 202, 62, 2, 33, 115, 105, 52, 2, 50, 3, 23, 44, 21, 245, 252, 51, 7, 253, 12, 183, 162, 41, 213, 178, 78, 115, 44, 210, 200, 74, 113, 161, 156, 16, 158, 184, 167, 60, 162, 172, 231, 5, 62, 225, 73, 156, 43, 141, 7, 24, 223, 242, 233, 212, 31, 57, 103, 16, 140, 33, 49, 76, 246, 237, 117, 27, 233, 240, 155, 8, 16, 34, 42, 5, 150, 154, 191, 119, 158, 82, 237, 132, 246, 194, 134, 222, 25, 52, 233, 211, 160, 210, 229, 1, 145], port: 34565 };181;[09, 03, 40, 9a, 88, f9, ea, af, ea, 2a, b1, ef, fe, 8b, 10, 2a, 91, 71, fb, 1e, c1, 28, 3a, 1c, 39, b9, be, 0b, f7, 80, 1d, b8, df, e6, 80, 14, 78, dc, 1a, 1d, fc, 56, dd, dd, 2c, 40, 9d, 5a, dd, 0a, 83, c1, d6, 4d, 86, 6d, 9a, 00, 2b, ed, a7, f3, 4f, ca, 3e, 02, 21, 73, 69, 34, 02, 32, 03, 17, 2c, 15, f5, fc, 33, 07, fd, 0c, b7, a2, 29, d5, b2, 4e, 73, 2c, d2, c8, 4a, 71, a1, 9c, 10, 9e, b8, a7, 3c, a2, ac, e7, 05, 3e, e1, 49, 9c, 2b, 8d, 07, 18, df, f2, e9, d4, 1f, 39, 67, 10, 8c, 21, 31, 4c, f6, ed, 75, 1b, e9, f0, 9b, 08, 10, 22, 2a, 05, 96, 9a, bf, 77, 9e, 52, ed, 84, f6, c2, 86, de, 19, 34, e9, d3, a0, d2, e5, 01, 91, 87, 05, 00] +tcp_req 82fb88fd3240e85035fd1c73fb7bd825b8d8237f2a8d6f1ed4e0a1d5f7c77fa848e351841f708195e68556e11cfbaa92b69fdffa16b35eed59c9ac7f8d57ace5012e6db05d3cdf1e354f262ef8a038442db6d35d6cd1c51976c82109ae879881fc6ee1d0ca3c91ac73209ea5ba13729425bbbec48c7cd6b21974a55ffcbffb38047ce29eb77c5278e97edb36b0f1976e464388c091edf6fa2fa7251aa73b94df79cb65aeaad1ff7a4cab74739c551dad0d100299929f009d363edb9e5ae5cbab0d342f8b6333e3adc353cc241a9e3382d074f2920510cc4b93dc3b56f27774c5593edbd6652813e391a8fac9b7a61a12c780000000 reject +tcp_req c0000000003adc6a01403d56873601ee9729c65756c75dec5baf7bddc04b1496604ee04cf943a8dee9b883dd7c8a391e13a70b316a0c4c64fe7f520439228396a736183ae4e8807f3a628000000506238880dc ConnectTcpReq { flags: 3857514, address_type: Ipv4, host: [86, 135, 54, 1, 238, 151, 41, 198, 87, 86, 199, 93, 236, 91, 175, 123, 221, 192, 75, 20, 150, 96, 78, 224, 76, 249, 67, 168, 222, 233, 184, 131, 221, 124, 138, 57, 30, 19, 167, 11, 49, 106, 12, 76, 100, 254, 127, 82, 4, 57, 34, 131, 150, 167, 54, 24, 58, 228, 232, 128, 127], port: 14946 };83;[80, 3a, dc, 6a, 01, 3d, 56, 87, 36, 01, ee, 97, 29, c6, 57, 56, c7, 5d, ec, 5b, af, 7b, dd, c0, 4b, 14, 96, 60, 4e, e0, 4c, f9, 43, a8, de, e9, b8, 83, dd, 7c, 8a, 39, 1e, 13, a7, 0b, 31, 6a, 0c, 4c, 64, fe, 7f, 52, 04, 39, 22, 83, 96, a7, 36, 18, 3a, e4, e8, 80, 7f, 3a, 62, 00] +tcp_req c000090b08bbc3510441000dcf9e14fc7c8eadfda3b8e6401e9948dc632d90149f52110b5b8ba6bcfb0a40a1e9b88c01901a85c5ce3a87abe3aedc3da4ad006b1bc68cf495c9c140521d43cfba2702d7747b8f022d7f2ced5983e1fad1263186b59cd92331e22fb3352cc2c6e9e0aa374218819ce3b43dd19c919f23849ef82b5a8c21dbd63f7cb7b487cb6945900f954a8a9fbeaa0c5d1b6da65c2fe18eb5868cc092f4a64b45328db28365e9b693c143632e9537036d6a997039a945c6086244d990de35b88aec1a523a3db7d666c8180076ca31cbe0507c67a23092eca6cbfca466db548731483abb996eeddefce24a13dcf361d919fdabdc72ff13a4b6583c2e2f20dc4169b042d3cae377c00000000000000196 reject +tcp_req c0143b1bbbd3f764034036b1c889b883b5f9e204a850facd80e6876e54522ace000cedafcff4309f693daad905a39365ba0cc58d17e42c06f76222d93f4836b10b133bc000000000000000 ConnectTcpReq { flags: 5694489835599716, address_type: Domain, host: [177, 200, 137, 184, 131, 181, 249, 226, 4, 168, 80, 250, 205, 128, 230, 135, 110, 84, 82, 42, 206, 0, 12, 237, 175, 207, 244, 48, 159, 105, 61, 170, 217, 5, 163, 147, 101, 186, 12, 197, 141, 23, 228, 44, 6, 247, 98, 34, 217, 63, 72, 54, 177, 11], port: 4923 };75;[c0, 14, 3b, 1b, bb, d3, f7, 64, 03, 36, b1, c8, 89, b8, 83, b5, f9, e2, 04, a8, 50, fa, cd, 80, e6, 87, 6e, 54, 52, 2a, ce, 00, 0c, ed, af, cf, f4, 30, 9f, 69, 3d, aa, d9, 05, a3, 93, 65, ba, 0c, c5, 8d, 17, e4, 2c, 06, f7, 62, 22, d9, 3f, 48, 36, b1, 0b, 13, 3b, 00] +tcp_req 0c0140bfcd56cf66086ebb9fac54c28c1a6621205e7348313c0424abee1fb2fd7f46332c4ab200132b5b7acf662a4b9a10853a82c149af5d599a94ff082eb7acd18851df44bdb90537ae685fc53737f1d89cf94999ee348511904fb99ca5b485674d32ff76fe42686e77768bed546aa273e2c834fd73c6d2d61bfd552b20fdde2345a11b326bc132dcfc77f37af75608986a1ec108af61961a9b1728783de9b4e2851b75249ea5ded5f0a98cc12929e6b5b20ac012c188a4d19845db771e1a4107de691c45c000000000000000cef80fd2 ConnectTcpReq { flags: 12, address_type: Ipv4, host: [205, 86, 207, 102, 8, 110, 187, 159, 172, 84, 194, 140, 26, 102, 33, 32, 94, 115, 72, 49, 60, 4, 36, 171, 238, 31, 178, 253, 127, 70, 51, 44, 74, 178, 0, 19, 43, 91, 122, 207, 102, 42, 75, 154, 16, 133, 58, 130, 193, 73, 175, 93, 89, 154, 148, 255, 8, 46, 183, 172, 209, 136, 81, 223, 68, 189, 185, 5, 55, 174, 104, 95, 197, 55, 55, 241, 216, 156, 249, 73, 153, 238, 52, 133, 17, 144, 79, 185, 156, 165, 180, 133, 103, 77, 50, 255, 118, 254, 66, 104, 110, 119, 118, 139, 237, 84, 106, 162, 115, 226, 200, 52, 253, 115, 198, 210, 214, 27, 253, 85, 43, 32, 253, 222, 35, 69, 161, 27, 50, 107, 193, 50, 220, 252, 119, 243, 122, 247, 86, 8, 152, 106, 30, 193, 8, 175, 97, 150, 26, 155, 23, 40, 120, 61, 233, 180, 226, 133, 27, 117, 36, 158, 165, 222, 213, 240, 169, 140, 193, 41, 41, 230, 181, 178, 10, 192, 18, 193, 136, 164, 209, 152, 69, 219, 119, 30, 26, 65, 7, 222, 105], port: 7237 };205;[0c, 01, 40, bf, cd, 56, cf, 66, 08, 6e, bb, 9f, ac, 54, c2, 8c, 1a, 66, 21, 20, 5e, 73, 48, 31, 3c, 04, 24, ab, ee, 1f, b2, fd, 7f, 46, 33, 2c, 4a, b2, 00, 13, 2b, 5b, 7a, cf, 66, 2a, 4b, 9a, 10, 85, 3a, 82, c1, 49, af, 5d, 59, 9a, 94, ff, 08, 2e, b7, ac, d1, 88, 51, df, 44, bd, b9, 05, 37, ae, 68, 5f, c5, 37, 37, f1, d8, 9c, f9, 49, 99, ee, 34, 85, 11, 90, 4f, b9, 9c, a5, b4, 85, 67, 4d, 32, ff, 76, fe, 42, 68, 6e, 77, 76, 8b, ed, 54, 6a, a2, 73, e2, c8, 34, fd, 73, c6, d2, d6, 1b, fd, 55, 2b, 20, fd, de, 23, 45, a1, 1b, 32, 6b, c1, 32, dc, fc, 77, f3, 7a, f7, 56, 08, 98, 6a, 1e, c1, 08, af, 61, 96, 1a, 9b, 17, 28, 78, 3d, e9, b4, e2, 85, 1b, 75, 24, 9e, a5, de, d5, f0, a9, 8c, c1, 29, 29, e6, b5, b2, 0a, c0, 12, c1, 88, a4, d1, 98, 45, db, 77, 1e, 1a, 41, 07, de, 69, 1c, 45, 00] +tcp_req c0000032a9547ae403800000ce515a4c770fd2964a1ec541aab37909f01555f44703fbae86d09d89e3ad049951c098020afcea7d8092f1c4982d2a8d7eb52bf6b4755a9b6690ecc4aad5619938290a4e17523a223c4da6556919dfbb139e53e931c94ec1577e124676df96d102abceedcd25c24fd949b33f3e4f864de173eca779cfbd40bb24f94c410d450d66bcd124906fec13fb63823ae46f4302bbccb77420938cfcfd1393c239521740a4bb1ab7e37d37d6f6b3a6c9bbdc4f56a14cd7d8fea8bda06c83f250e42a61d6632a04c88a3a295281da08c153b5fb0548c000000000000000 ConnectTcpReq { flags: 217589250788, address_type: Domain, host: [81, 90, 76, 119, 15, 210, 150, 74, 30, 197, 65, 170, 179, 121, 9, 240, 21, 85, 244, 71, 3, 251, 174, 134, 208, 157, 137, 227, 173, 4, 153, 81, 192, 152, 2, 10, 252, 234, 125, 128, 146, 241, 196, 152, 45, 42, 141, 126, 181, 43, 246, 180, 117, 90, 155, 102, 144, 236, 196, 170, 213, 97, 153, 56, 41, 10, 78, 23, 82, 58, 34, 60, 77, 166, 85, 105, 25, 223, 187, 19, 158, 83, 233, 49, 201, 78, 193, 87, 126, 18, 70, 118, 223, 150, 209, 2, 171, 206, 237, 205, 37, 194, 79, 217, 73, 179, 63, 62, 79, 134, 77, 225, 115, 236, 167, 121, 207, 189, 64, 187, 36, 249, 76, 65, 13, 69, 13, 102, 188, 209, 36, 144, 111, 236, 19, 251, 99, 130, 58, 228, 111, 67, 2, 187, 204, 183, 116, 32, 147, 140, 252, 253, 19, 147, 194, 57, 82, 23, 64, 164, 187, 26, 183, 227, 125, 55, 214, 246, 179, 166, 201, 187, 220, 79, 86, 161, 76, 215, 216, 254, 168, 189, 160, 108, 131, 242, 80, 228, 42, 97, 214, 99, 42, 4, 200, 138, 58, 41, 82, 129, 218, 8, 193, 83, 181, 251], port: 1352 };229;[c0, 00, 00, 32, a9, 54, 7a, e4, 03, 40, ce, 51, 5a, 4c, 77, 0f, d2, 96, 4a, 1e, c5, 41, aa, b3, 79, 09, f0, 15, 55, f4, 47, 03, fb, ae, 86, d0, 9d, 89, e3, ad, 04, 99, 51, c0, 98, 02, 0a, fc, ea, 7d, 80, 92, f1, c4, 98, 2d, 2a, 8d, 7e, b5, 2b, f6, b4, 75, 5a, 9b, 66, 90, ec, c4, aa, d5, 61, 99, 38, 29, 0a, 4e, 17, 52, 3a, 22, 3c, 4d, a6, 55, 69, 19, df, bb, 13, 9e, 53, e9, 31, c9, 4e, c1, 57, 7e, 12, 46, 76, df, 96, d1, 02, ab, ce, ed, cd, 25, c2, 4f, d9, 49, b3, 3f, 3e, 4f, 86, 4d, e1, 73, ec, a7, 79, cf, bd, 40, bb, 24, f9, 4c, 41, 0d, 45, 0d, 66, bc, d1, 24, 90, 6f, ec, 13, fb, 63, 82, 3a, e4, 6f, 43, 02, bb, cc, b7, 74, 20, 93, 8c, fc, fd, 13, 93, c2, 39, 52, 17, 40, a4, bb, 1a, b7, e3, 7d, 37, d6, f6, b3, a6, c9, bb, dc, 4f, 56, a1, 4c, d7, d8, fe, a8, bd, a0, 6c, 83, f2, 50, e4, 2a, 61, d6, 63, 2a, 04, c8, 8a, 3a, 29, 52, 81, da, 08, c1, 53, b5, fb, 05, 48, 00] +tcp_req c0001e2b4fde3b670441036622e44111493dcd1880f2e731a0c9f1d1b1f381ab800a25dad3e8da87f4d49cb0d2103775714c9ba0723dddbec2d3d95d9e4a943827e5812fea4af16b3aa16247108990423f109153d514d2b7d9ec2c877bba926ec275bc0e35a0ebafe9575d248e5bbda7d0e6c11a41726b868def372a9b9b0eb7a6be5aef6c75a505131aa6635e0530133c0d7aec27f9993cff1bfa676ef304008c622956ddf98f17a6662978f5a75a37875511dc0cf70e279590a25ced79de8c2f425d10dc518f40dc9a81e6c4a3b650d16ead4ce84f443c34a1d5ac86dc11a98015ebb31097c0d0663a23eb7d16dfb5996c9e18aadb664f963011a6a72af271ff8b61e0a6aef8288cac5a862433c75100 reject +tcp_req 407b0380000040881be36bfcd93d8bb685dc0037a588de4bb8140b659ff8a80ed013da3f7c12131d0137c7afb5d9c4cb80bc227673abef2c98f3f13c7568df33c33a205ee7cdd1ac53042be4d0a3 ConnectTcpReq { flags: 123, address_type: Domain, host: [136, 27, 227, 107, 252, 217, 61, 139, 182, 133, 220, 0, 55, 165, 136, 222, 75, 184, 20, 11, 101, 159, 248, 168, 14, 208, 19, 218, 63, 124, 18, 19, 29, 1, 55, 199, 175, 181, 217, 196, 203, 128, 188, 34, 118, 115, 171, 239, 44, 152, 243, 241, 60, 117, 104, 223, 51, 195, 58, 32, 94, 231, 205, 209], port: 44115 };78;[40, 7b, 03, 40, 40, 88, 1b, e3, 6b, fc, d9, 3d, 8b, b6, 85, dc, 00, 37, a5, 88, de, 4b, b8, 14, 0b, 65, 9f, f8, a8, 0e, d0, 13, da, 3f, 7c, 12, 13, 1d, 01, 37, c7, af, b5, d9, c4, cb, 80, bc, 22, 76, 73, ab, ef, 2c, 98, f3, f1, 3c, 75, 68, df, 33, c3, 3a, 20, 5e, e7, cd, d1, ac, 53, 00] +tcp_req c005da97c2a6e2fa014000c8f94005819364e570 ConnectTcpReq { flags: 1647720224187130, address_type: Ipv4, host: [], port: 51449 };20;[c0, 05, da, 97, c2, a6, e2, fa, 01, 00, c8, f9, 00] +tcp_req c0000000744d67bd044023266d662ba4cc6df93a77ccb8df1309a6a85fbaa961347ad202abb3275ffdd8812da944291ec00000000000000e660a6d9e91118c411d8e0fbe00bd ConnectTcpReq { flags: 1951229885, address_type: Ipv6, host: [38, 109, 102, 43, 164, 204, 109, 249, 58, 119, 204, 184, 223, 19, 9, 166, 168, 95, 186, 169, 97, 52, 122, 210, 2, 171, 179, 39, 95, 253, 216, 129, 45, 169, 68], port: 10526 };70;[c0, 00, 00, 00, 74, 4d, 67, bd, 04, 23, 26, 6d, 66, 2b, a4, cc, 6d, f9, 3a, 77, cc, b8, df, 13, 09, a6, a8, 5f, ba, a9, 61, 34, 7a, d2, 02, ab, b3, 27, 5f, fd, d8, 81, 2d, a9, 44, 29, 1e, 00] +tcp_req c000000000732fe201800000c6ba7805915b8b2375508a23773a769913542fff46950543e85810961cf87a4f5425f66184df58611be5825dbafda26ca7314b24939fee8cffec929d47e72e444584535908ec4b20a9eac1a89a68eed3a63d65c92184b0ebd94ba48e5e4c7e5b4f3273a38d073880582372750d496d4548e2bcdd4f5e7667ff9d93fbfa560397772e6587f215fa0c80868b216c9295a7ae4424b065bf503eb6370edabce1e6936c64d987c915cb244ae1a706f4bc36bb1e44935c6cd17c3b4cfa9aba70cda7b939cb52d97f70efd953c00000000000000e019b9eff39e9469840f24caf20f0 ConnectTcpReq { flags: 7548898, address_type: Ipv4, host: [186, 120, 5, 145, 91, 139, 35, 117, 80, 138, 35, 119, 58, 118, 153, 19, 84, 47, 255, 70, 149, 5, 67, 232, 88, 16, 150, 28, 248, 122, 79, 84, 37, 246, 97, 132, 223, 88, 97, 27, 229, 130, 93, 186, 253, 162, 108, 167, 49, 75, 36, 147, 159, 238, 140, 255, 236, 146, 157, 71, 231, 46, 68, 69, 132, 83, 89, 8, 236, 75, 32, 169, 234, 193, 168, 154, 104, 238, 211, 166, 61, 101, 201, 33, 132, 176, 235, 217, 75, 164, 142, 94, 76, 126, 91, 79, 50, 115, 163, 141, 7, 56, 128, 88, 35, 114, 117, 13, 73, 109, 69, 72, 226, 188, 221, 79, 94, 118, 103, 255, 157, 147, 251, 250, 86, 3, 151, 119, 46, 101, 135, 242, 21, 250, 12, 128, 134, 139, 33, 108, 146, 149, 167, 174, 68, 36, 176, 101, 191, 80, 62, 182, 55, 14, 218, 188, 225, 230, 147, 108, 100, 217, 135, 201, 21, 203, 36, 74, 225, 167, 6, 244, 188, 54, 187, 30, 68, 147, 92, 108, 209, 124, 59, 76, 250, 154, 186, 112, 205, 167, 185, 57, 203, 82, 217, 127, 112, 239], port: 55635 };235;[80, 73, 2f, e2, 01, 40, c6, ba, 78, 05, 91, 5b, 8b, 23, 75, 50, 8a, 23, 77, 3a, 76, 99, 13, 54, 2f, ff, 46, 95, 05, 43, e8, 58, 10, 96, 1c, f8, 7a, 4f, 54, 25, f6, 61, 84, df, 58, 61, 1b, e5, 82, 5d, ba, fd, a2, 6c, a7, 31, 4b, 24, 93, 9f, ee, 8c, ff, ec, 92, 9d, 47, e7, 2e, 44, 45, 84, 53, 59, 08, ec, 4b, 20, a9, ea, c1, a8, 9a, 68, ee, d3, a6, 3d, 65, c9, 21, 84, b0, eb, d9, 4b, a4, 8e, 5e, 4c, 7e, 5b, 4f, 32, 73, a3, 8d, 07, 38, 80, 58, 23, 72, 75, 0d, 49, 6d, 45, 48, e2, bc, dd, 4f, 5e, 76, 67, ff, 9d, 93, fb, fa, 56, 03, 97, 77, 2e, 65, 87, f2, 15, fa, 0c, 80, 86, 8b, 21, 6c, 92, 95, a7, ae, 44, 24, b0, 65, bf, 50, 3e, b6, 37, 0e, da, bc, e1, e6, 93, 6c, 64, d9, 87, c9, 15, cb, 24, 4a, e1, a7, 06, f4, bc, 36, bb, 1e, 44, 93, 5c, 6c, d1, 7c, 3b, 4c, fa, 9a, ba, 70, cd, a7, b9, 39, cb, 52, d9, 7f, 70, ef, d9, 53, 00] +tcp_req c0118361993b1fe9044090e9b00f261c68323daaf58d284cb1dcab88edea9acaa6fe83e2e79f329eba9a29b68744b25a82a24948f23852fc8c3bb5199533a207688867f00e85effcc53e8524fa2d82d2759d7de1d48bc2c11fdb384b9f5157e23bf9046223492239884c19fa9d7ef651213c9004b4c33277c0e7635eaafca8c4ad05f250fc01c3373898cfba5fcbb661e090aef9123239c7230900d84d00 ConnectTcpReq { flags: 4929529809936361, address_type: Ipv6, host: [233, 176, 15, 38, 28, 104, 50, 61, 170, 245, 141, 40, 76, 177, 220, 171, 136, 237, 234, 154, 202, 166, 254, 131, 226, 231, 159, 50, 158, 186, 154, 41, 182, 135, 68, 178, 90, 130, 162, 73, 72, 242, 56, 82, 252, 140, 59, 181, 25, 149, 51, 162, 7, 104, 136, 103, 240, 14, 133, 239, 252, 197, 62, 133, 36, 250, 45, 130, 210, 117, 157, 125, 225, 212, 139, 194, 193, 31, 219, 56, 75, 159, 81, 87, 226, 59, 249, 4, 98, 35, 73, 34, 57, 136, 76, 25, 250, 157, 126, 246, 81, 33, 60, 144, 4, 180, 195, 50, 119, 192, 231, 99, 94, 170, 252, 168, 196, 173, 5, 242, 80, 252, 1, 195, 55, 56, 152, 207, 186, 95, 203, 182, 97, 224, 144, 174, 249, 18, 50, 57, 199, 35, 9, 0], port: 55373 };158;[c0, 11, 83, 61, 99, 3b, 1f, e9, 04, 40, 90, e9, b0, 0f, 26, 1c, 68, 32, 3d, aa, f5, 8d, 28, 4c, b1, dc, ab, 88, ed, ea, 9a, ca, a6, fe, 83, e2, e7, 9f, 32, 9e, ba, 9a, 29, b6, 87, 44, b2, 5a, 82, a2, 49, 48, f2, 38, 52, fc, 8c, 3b, b5, 19, 95, 33, a2, 07, 68, 88, 67, f0, 0e, 85, ef, fc, c5, 3e, 85, 24, fa, 2d, 82, d2, 75, 9d, 7d, e1, d4, 8b, c2, c1, 1f, db, 38, 4b, 9f, 51, 57, e2, 3b, f9, 04, 62, 23, 49, 22, 39, 88, 4c, 19, fa, 9d, 7e, f6, 51, 21, 3c, 90, 04, b4, c3, 32, 77, c0, e7, 63, 5e, aa, fc, a8, c4, ad, 05, f2, 50, fc, 01, c3, 37, 38, 98, cf, ba, 5f, cb, b6, 61, e0, 90, ae, f9, 12, 32, 39, c7, 23, 09, 00, d8, 4d, 00] +tcp_req 800aa733010c715afb62ad9c7004bbdd3864e0f380000000 ConnectTcpReq { flags: 698163, address_type: Ipv4, host: [113, 90, 251, 98, 173, 156, 112, 4, 187, 221, 56, 100], port: 57587 };24;[80, 0a, a7, 33, 01, 0c, 71, 5a, fb, 62, ad, 9c, 70, 04, bb, dd, 38, 64, e0, f3, 00] +tcp_req c00ff9b55abe7305011153503c636c8dc2ee6d16549ea8b226414e136200 ConnectTcpReq { flags: 4496681957487365, address_type: Ipv4, host: [83, 80, 60, 99, 108, 141, 194, 238, 109, 22, 84, 158, 168, 178, 38, 65, 78], port: 4962 };30;[c0, 0f, f9, b5, 5a, be, 73, 05, 01, 11, 53, 50, 3c, 63, 6c, 8d, c2, ee, 6d, 16, 54, 9e, a8, b2, 26, 41, 4e, 13, 62, 00] +tcp_req c00033b78cf32e8d0380000091a14fdfc8aa373ce8aa4497e690e36986be6ca0cf34057edafc432d06176226dae286e9b5517243569e1b1e1ffcd69d05cfda022709a152b399b4758df5b53b396dfc1eeca665867d0fa3c654bfb512c37a34b2df238f37e10efc20f7d9baea9fe5c4f5220d65c50afeb50c420260ad82ca9834c211f5feffcbe15252a3530cc70a6098ec01aa61f5247aae5ee8fba3545b52794000 ConnectTcpReq { flags: 56863436779149, address_type: Domain, host: [161, 79, 223, 200, 170, 55, 60, 232, 170, 68, 151, 230, 144, 227, 105, 134, 190, 108, 160, 207, 52, 5, 126, 218, 252, 67, 45, 6, 23, 98, 38, 218, 226, 134, 233, 181, 81, 114, 67, 86, 158, 27, 30, 31, 252, 214, 157, 5, 207, 218, 2, 39, 9, 161, 82, 179, 153, 180, 117, 141, 245, 181, 59, 57, 109, 252, 30, 236, 166, 101, 134, 125, 15, 163, 198, 84, 191, 181, 18, 195, 122, 52, 178, 223, 35, 143, 55, 225, 14, 252, 32, 247, 217, 186, 234, 159, 229, 196, 245, 34, 13, 101, 197, 10, 254, 181, 12, 66, 2, 96, 173, 130, 202, 152, 52, 194, 17, 245, 254, 255, 203, 225, 82, 82, 163, 83, 12, 199, 10, 96, 152, 236, 1, 170, 97, 245, 36, 122, 174, 94, 232, 251, 163, 84, 91], port: 21113 };162;[c0, 00, 33, b7, 8c, f3, 2e, 8d, 03, 40, 91, a1, 4f, df, c8, aa, 37, 3c, e8, aa, 44, 97, e6, 90, e3, 69, 86, be, 6c, a0, cf, 34, 05, 7e, da, fc, 43, 2d, 06, 17, 62, 26, da, e2, 86, e9, b5, 51, 72, 43, 56, 9e, 1b, 1e, 1f, fc, d6, 9d, 05, cf, da, 02, 27, 09, a1, 52, b3, 99, b4, 75, 8d, f5, b5, 3b, 39, 6d, fc, 1e, ec, a6, 65, 86, 7d, 0f, a3, c6, 54, bf, b5, 12, c3, 7a, 34, b2, df, 23, 8f, 37, e1, 0e, fc, 20, f7, d9, ba, ea, 9f, e5, c4, f5, 22, 0d, 65, c5, 0a, fe, b5, 0c, 42, 02, 60, ad, 82, ca, 98, 34, c2, 11, f5, fe, ff, cb, e1, 52, 52, a3, 53, 0c, c7, 0a, 60, 98, ec, 01, aa, 61, f5, 24, 7a, ae, 5e, e8, fb, a3, 54, 5b, 52, 79, 00] +tcp_req c0025907c4377e5003800000218ec0a823d252b668239dca297b96115df86e75421a057532baf77033e3288975f57e9d80000000 ConnectTcpReq { flags: 660839845035600, address_type: Domain, host: [142, 192, 168, 35, 210, 82, 182, 104, 35, 157, 202, 41, 123, 150, 17, 93, 248, 110, 117, 66, 26, 5, 117, 50, 186, 247, 112, 51, 227, 40, 137, 117, 245], port: 32413 };52;[c0, 02, 59, 07, c4, 37, 7e, 50, 03, 21, 8e, c0, a8, 23, d2, 52, b6, 68, 23, 9d, ca, 29, 7b, 96, 11, 5d, f8, 6e, 75, 42, 1a, 05, 75, 32, ba, f7, 70, 33, e3, 28, 89, 75, f5, 7e, 9d, 00] +tcp_req c0a565c09dfc6fcd03800000ab05d46f92cb1fdba20247becbb9056ac135ae09f08208eafac0729a161114523eb1820c43cee81a7e4b13b659f54d75f051d4d63b14ce6355d1b94118170257e341484000b30e3f7c670b49004b5e3e601fec09b981d5183c5a271c14a547e099874c37e39650b61d4244b6f65b085855228fa4466715b07bfa3665e79388ea5711206c066e25535af6dfe92f6714920a024d6ff966034cc35f164c91926760e99d07e82eef9e3033b5e16e0585c00000000000000f734bfb2829c64cb6b49b0feda0aa93 ConnectTcpReq { flags: 46555249115951053, address_type: Domain, host: [5, 212, 111, 146, 203, 31, 219, 162, 2, 71, 190, 203, 185, 5, 106, 193, 53, 174, 9, 240, 130, 8, 234, 250, 192, 114, 154, 22, 17, 20, 82, 62, 177, 130, 12, 67, 206, 232, 26, 126, 75, 19, 182, 89, 245, 77, 117, 240, 81, 212, 214, 59, 20, 206, 99, 85, 209, 185, 65, 24, 23, 2, 87, 227, 65, 72, 64, 0, 179, 14, 63, 124, 103, 11, 73, 0, 75, 94, 62, 96, 31, 236, 9, 185, 129, 213, 24, 60, 90, 39, 28, 20, 165, 71, 224, 153, 135, 76, 55, 227, 150, 80, 182, 29, 66, 68, 182, 246, 91, 8, 88, 85, 34, 143, 164, 70, 103, 21, 176, 123, 250, 54, 101, 231, 147, 136, 234, 87, 17, 32, 108, 6, 110, 37, 83, 90, 246, 223, 233, 47, 103, 20, 146, 10, 2, 77, 111, 249, 102, 3, 76, 195, 95, 22, 76, 145, 146, 103, 96, 233, 157, 7, 232, 46, 239, 158, 48, 51, 181, 225, 110], port: 1413 };209;[c0, a5, 65, c0, 9d, fc, 6f, cd, 03, 40, ab, 05, d4, 6f, 92, cb, 1f, db, a2, 02, 47, be, cb, b9, 05, 6a, c1, 35, ae, 09, f0, 82, 08, ea, fa, c0, 72, 9a, 16, 11, 14, 52, 3e, b1, 82, 0c, 43, ce, e8, 1a, 7e, 4b, 13, b6, 59, f5, 4d, 75, f0, 51, d4, d6, 3b, 14, ce, 63, 55, d1, b9, 41, 18, 17, 02, 57, e3, 41, 48, 40, 00, b3, 0e, 3f, 7c, 67, 0b, 49, 00, 4b, 5e, 3e, 60, 1f, ec, 09, b9, 81, d5, 18, 3c, 5a, 27, 1c, 14, a5, 47, e0, 99, 87, 4c, 37, e3, 96, 50, b6, 1d, 42, 44, b6, f6, 5b, 08, 58, 55, 22, 8f, a4, 46, 67, 15, b0, 7b, fa, 36, 65, e7, 93, 88, ea, 57, 11, 20, 6c, 06, 6e, 25, 53, 5a, f6, df, e9, 2f, 67, 14, 92, 0a, 02, 4d, 6f, f9, 66, 03, 4c, c3, 5f, 16, 4c, 91, 92, 67, 60, e9, 9d, 07, e8, 2e, ef, 9e, 30, 33, b5, e1, 6e, 05, 85, 00] +tcp_req c000000098568e010413c896d8ae572e837a5dc00ff0c691c8d0bfe1bde4dd80000000 ConnectTcpReq { flags: 2555809281, address_type: Ipv6, host: [200, 150, 216, 174, 87, 46, 131, 122, 93, 192, 15, 240, 198, 145, 200, 208, 191, 225, 189], port: 58589 };35;[c0, 00, 00, 00, 98, 56, 8e, 01, 04, 13, c8, 96, d8, ae, 57, 2e, 83, 7a, 5d, c0, 0f, f0, c6, 91, c8, d0, bf, e1, bd, e4, dd, 00] +tcp_req c0228eab6176bd6b6e800000068431355f9f0d8055c000000000000001da reject +tcp_req c00000005e0f49410380000068b64a77a728b31819c510cadbf14c995403a1b95eefd42e0a2af228847079cc577b1ddd48cadf2d5222e9309f8d60a768498a0a8097f9aab5e6c089148493c0684a1727ef6f939fc330cfa1f5b2bb389f68fd09a0567dc08a55bc687e5cd466a1696cc579dc4cd9129a1a8000000c392b8e7e03fea26a371ecbd4 ConnectTcpReq { flags: 1578060097, address_type: Domain, host: [182, 74, 119, 167, 40, 179, 24, 25, 197, 16, 202, 219, 241, 76, 153, 84, 3, 161, 185, 94, 239, 212, 46, 10, 42, 242, 40, 132, 112, 121, 204, 87, 123, 29, 221, 72, 202, 223, 45, 82, 34, 233, 48, 159, 141, 96, 167, 104, 73, 138, 10, 128, 151, 249, 170, 181, 230, 192, 137, 20, 132, 147, 192, 104, 74, 23, 39, 239, 111, 147, 159, 195, 48, 207, 161, 245, 178, 187, 56, 159, 104, 253, 9, 160, 86, 125, 192, 138, 85, 188, 104, 126, 92, 212, 102, 161, 105, 108, 197, 121, 220, 76, 217, 18], port: 39450 };135;[c0, 00, 00, 00, 5e, 0f, 49, 41, 03, 40, 68, b6, 4a, 77, a7, 28, b3, 18, 19, c5, 10, ca, db, f1, 4c, 99, 54, 03, a1, b9, 5e, ef, d4, 2e, 0a, 2a, f2, 28, 84, 70, 79, cc, 57, 7b, 1d, dd, 48, ca, df, 2d, 52, 22, e9, 30, 9f, 8d, 60, a7, 68, 49, 8a, 0a, 80, 97, f9, aa, b5, e6, c0, 89, 14, 84, 93, c0, 68, 4a, 17, 27, ef, 6f, 93, 9f, c3, 30, cf, a1, f5, b2, bb, 38, 9f, 68, fd, 09, a0, 56, 7d, c0, 8a, 55, bc, 68, 7e, 5c, d4, 66, a1, 69, 6c, c5, 79, dc, 4c, d9, 12, 9a, 1a, 00] +tcp_req c000000000001d1b4f40cc8a059798ff60ad7992524c0a08389e5fd308c9baa8686e4bf3a0afc332094878237748db0fa24a0dd474218f7872be05198a943df5cfe1dc0f534ed3073f14c381831c85350327ed555e1694ac34a0c141b73cce81ba71333960eed42cebe07df733e07378b07d294d4e394ec6815bea36a195d00d0d701f52ecd482b8bd494c11d3fb998590f9b1b51b191265945ebbcfdd6778d10a26d58fc65bfbb7b129c031826196beed3182088b90fdcaf822c2de90fbdee12a06a782833ea6c274f7ac83e01e831e014ea4894863450f11c000000000000000 reject +tcp_req c0cff15390f823fd03408a7e573df879534aad52d111755d5928518cabf14e6d3b73cda68489a24e400ee9528dca53aaf68105475351072ac4db2acd98fd7538722a14174589e1b1d97283c9c22ff3361bc518014581ad195bfdca8ae01a443d3e0be03d90e62bf70cf549835f9acc4df573165d5d7ccee0bb521c96b615267e5fdd5030c9c48518a360ccc0472f1e1ae353d9200dbbf94000 ConnectTcpReq { flags: 58530661395866621, address_type: Domain, host: [126, 87, 61, 248, 121, 83, 74, 173, 82, 209, 17, 117, 93, 89, 40, 81, 140, 171, 241, 78, 109, 59, 115, 205, 166, 132, 137, 162, 78, 64, 14, 233, 82, 141, 202, 83, 170, 246, 129, 5, 71, 83, 81, 7, 42, 196, 219, 42, 205, 152, 253, 117, 56, 114, 42, 20, 23, 69, 137, 225, 177, 217, 114, 131, 201, 194, 47, 243, 54, 27, 197, 24, 1, 69, 129, 173, 25, 91, 253, 202, 138, 224, 26, 68, 61, 62, 11, 224, 61, 144, 230, 43, 247, 12, 245, 73, 131, 95, 154, 204, 77, 245, 115, 22, 93, 93, 124, 206, 224, 187, 82, 28, 150, 182, 21, 38, 126, 95, 221, 80, 48, 201, 196, 133, 24, 163, 96, 204, 192, 71, 47, 30, 26, 227, 83, 217, 32, 13], port: 48121 };153;[c0, cf, f1, 53, 90, f8, 23, fd, 03, 40, 8a, 7e, 57, 3d, f8, 79, 53, 4a, ad, 52, d1, 11, 75, 5d, 59, 28, 51, 8c, ab, f1, 4e, 6d, 3b, 73, cd, a6, 84, 89, a2, 4e, 40, 0e, e9, 52, 8d, ca, 53, aa, f6, 81, 05, 47, 53, 51, 07, 2a, c4, db, 2a, cd, 98, fd, 75, 38, 72, 2a, 14, 17, 45, 89, e1, b1, d9, 72, 83, c9, c2, 2f, f3, 36, 1b, c5, 18, 01, 45, 81, ad, 19, 5b, fd, ca, 8a, e0, 1a, 44, 3d, 3e, 0b, e0, 3d, 90, e6, 2b, f7, 0c, f5, 49, 83, 5f, 9a, cc, 4d, f5, 73, 16, 5d, 5d, 7c, ce, e0, bb, 52, 1c, 96, b6, 15, 26, 7e, 5f, dd, 50, 30, c9, c4, 85, 18, a3, 60, cc, c0, 47, 2f, 1e, 1a, e3, 53, d9, 20, 0d, bb, f9, 00] +tcp_req c000c597969653cf6e40b84b9b9a07d55523cfd48a3a1c72f25ff424c07ef49d5c9b680a751e8745dbd1b22be5374d98f1f2cb06b8a5ff6cf45737a9f8117fbe842e13a5e39f6b09f9dbfc992555de3b66a57803b57bd9c7cf34ba6729559ccebd9a0a023a90e5ee1b1f3ef64e41176d00a9ed53a2fbbbe426e7ed24122bf5f65a11fa931a9e161cc820655df59f8d8ac69305e34631bf5f143619d2ad7abea4c5c6c11649d73f299ed11f3e49d947db287542c1fa504c67bc5516d46d0d0a806f203a40604000 reject +tcp_req 82ba001a01c00000000000002560dd59b45f58d002649d45126762e07f75a62dce2cd92032165687c52ede4635820694edd659254000f12fbfd2b19d3a ConnectTcpReq { flags: 45744154, address_type: Ipv4, host: [96, 221, 89, 180, 95, 88, 208, 2, 100, 157, 69, 18, 103, 98, 224, 127, 117, 166, 45, 206, 44, 217, 32, 50, 22, 86, 135, 197, 46, 222, 70, 53, 130, 6, 148, 237, 214], port: 22821 };54;[82, ba, 00, 1a, 01, 25, 60, dd, 59, b4, 5f, 58, d0, 02, 64, 9d, 45, 12, 67, 62, e0, 7f, 75, a6, 2d, ce, 2c, d9, 20, 32, 16, 56, 87, c5, 2e, de, 46, 35, 82, 06, 94, ed, d6, 59, 25, 00] +tcp_req 800000060380000007a81b5376a1da61872c4000f92e638cba2fdc ConnectTcpReq { flags: 6, address_type: Domain, host: [168, 27, 83, 118, 161, 218, 97], port: 34604 };20;[06, 03, 07, a8, 1b, 53, 76, a1, da, 61, 87, 2c, 00] +tcp_req c1611a32e86734bf0140bef912f0622ce4687f120f424d6a715dab237434641ee707a82014eed2608372abff9520564a91f1d07f1d1486b66961f041a5159aac1ff8742ae0f5b58abe06ec028474bca80ac04ee2cb4aa64b39c7759768bc602a6299311c771d2cb39a288445ae5cd3c46a336d69b65d2926cf9e634ef88d796f4586fc637e69073f06011ad05e7a0ca91e5b752dda2621fe634f5b503cd2b2751f3dc0fe63a7cfa00256e31813a32c6392c62d6502a98427d110dc343d67ce809ea5d86696d810f429e2384000 ConnectTcpReq { flags: 99389472728626367, address_type: Ipv4, host: [249, 18, 240, 98, 44, 228, 104, 127, 18, 15, 66, 77, 106, 113, 93, 171, 35, 116, 52, 100, 30, 231, 7, 168, 32, 20, 238, 210, 96, 131, 114, 171, 255, 149, 32, 86, 74, 145, 241, 208, 127, 29, 20, 134, 182, 105, 97, 240, 65, 165, 21, 154, 172, 31, 248, 116, 42, 224, 245, 181, 138, 190, 6, 236, 2, 132, 116, 188, 168, 10, 192, 78, 226, 203, 74, 166, 75, 57, 199, 117, 151, 104, 188, 96, 42, 98, 153, 49, 28, 119, 29, 44, 179, 154, 40, 132, 69, 174, 92, 211, 196, 106, 51, 109, 105, 182, 93, 41, 38, 207, 158, 99, 78, 248, 141, 121, 111, 69, 134, 252, 99, 126, 105, 7, 63, 6, 1, 26, 208, 94, 122, 12, 169, 30, 91, 117, 45, 218, 38, 33, 254, 99, 79, 91, 80, 60, 210, 178, 117, 31, 61, 192, 254, 99, 167, 207, 160, 2, 86, 227, 24, 19, 163, 44, 99, 146, 198, 45, 101, 2, 169, 132, 39, 209, 16, 220, 52, 61, 103, 206, 128, 158, 165, 216, 102, 150, 216, 16, 244, 41], port: 57912 };205;[c1, 61, 1a, 32, e8, 67, 34, bf, 01, 40, be, f9, 12, f0, 62, 2c, e4, 68, 7f, 12, 0f, 42, 4d, 6a, 71, 5d, ab, 23, 74, 34, 64, 1e, e7, 07, a8, 20, 14, ee, d2, 60, 83, 72, ab, ff, 95, 20, 56, 4a, 91, f1, d0, 7f, 1d, 14, 86, b6, 69, 61, f0, 41, a5, 15, 9a, ac, 1f, f8, 74, 2a, e0, f5, b5, 8a, be, 06, ec, 02, 84, 74, bc, a8, 0a, c0, 4e, e2, cb, 4a, a6, 4b, 39, c7, 75, 97, 68, bc, 60, 2a, 62, 99, 31, 1c, 77, 1d, 2c, b3, 9a, 28, 84, 45, ae, 5c, d3, c4, 6a, 33, 6d, 69, b6, 5d, 29, 26, cf, 9e, 63, 4e, f8, 8d, 79, 6f, 45, 86, fc, 63, 7e, 69, 07, 3f, 06, 01, 1a, d0, 5e, 7a, 0c, a9, 1e, 5b, 75, 2d, da, 26, 21, fe, 63, 4f, 5b, 50, 3c, d2, b2, 75, 1f, 3d, c0, fe, 63, a7, cf, a0, 02, 56, e3, 18, 13, a3, 2c, 63, 92, c6, 2d, 65, 02, a9, 84, 27, d1, 10, dc, 34, 3d, 67, ce, 80, 9e, a5, d8, 66, 96, d8, 10, f4, 29, e2, 38, 00] +tcp_req c065a2712a192aaf04c0000000000000bad325588a4d0df4281cf64ff08f42e7d3a896232d68ec1b9388043d2e64c848e920c10bb5c4bfab22116ee0d2f50cd791a16625494ca492f0f7b00d58107be583ba4ce9772546ee97e481c9d7761e793602daed343c69bd659d4857385134cb7cb7ba0099e0284662228e996665021b033d534d42f8ec63c0d3981f35611f07ad8ead1c004e0c8cf99b0184196bcdf3518fa46d0bb837ed1d603020cb1db1d04d1af9048cb42222b004914a03fdbca88332be6795c624b20fb5166b9480000000 ConnectTcpReq { flags: 28607579569072815, address_type: Ipv6, host: [211, 37, 88, 138, 77, 13, 244, 40, 28, 246, 79, 240, 143, 66, 231, 211, 168, 150, 35, 45, 104, 236, 27, 147, 136, 4, 61, 46, 100, 200, 72, 233, 32, 193, 11, 181, 196, 191, 171, 34, 17, 110, 224, 210, 245, 12, 215, 145, 161, 102, 37, 73, 76, 164, 146, 240, 247, 176, 13, 88, 16, 123, 229, 131, 186, 76, 233, 119, 37, 70, 238, 151, 228, 129, 201, 215, 118, 30, 121, 54, 2, 218, 237, 52, 60, 105, 189, 101, 157, 72, 87, 56, 81, 52, 203, 124, 183, 186, 0, 153, 224, 40, 70, 98, 34, 142, 153, 102, 101, 2, 27, 3, 61, 83, 77, 66, 248, 236, 99, 192, 211, 152, 31, 53, 97, 31, 7, 173, 142, 173, 28, 0, 78, 12, 140, 249, 155, 1, 132, 25, 107, 205, 243, 81, 143, 164, 109, 11, 184, 55, 237, 29, 96, 48, 32, 203, 29, 177, 208, 77, 26, 249, 4, 140, 180, 34, 34, 176, 4, 145, 74, 3, 253, 188, 168, 131, 50, 190, 103, 149, 198, 36, 178, 15, 181, 22], port: 27540 };209;[c0, 65, a2, 71, 2a, 19, 2a, af, 04, 40, ba, d3, 25, 58, 8a, 4d, 0d, f4, 28, 1c, f6, 4f, f0, 8f, 42, e7, d3, a8, 96, 23, 2d, 68, ec, 1b, 93, 88, 04, 3d, 2e, 64, c8, 48, e9, 20, c1, 0b, b5, c4, bf, ab, 22, 11, 6e, e0, d2, f5, 0c, d7, 91, a1, 66, 25, 49, 4c, a4, 92, f0, f7, b0, 0d, 58, 10, 7b, e5, 83, ba, 4c, e9, 77, 25, 46, ee, 97, e4, 81, c9, d7, 76, 1e, 79, 36, 02, da, ed, 34, 3c, 69, bd, 65, 9d, 48, 57, 38, 51, 34, cb, 7c, b7, ba, 00, 99, e0, 28, 46, 62, 22, 8e, 99, 66, 65, 02, 1b, 03, 3d, 53, 4d, 42, f8, ec, 63, c0, d3, 98, 1f, 35, 61, 1f, 07, ad, 8e, ad, 1c, 00, 4e, 0c, 8c, f9, 9b, 01, 84, 19, 6b, cd, f3, 51, 8f, a4, 6d, 0b, b8, 37, ed, 1d, 60, 30, 20, cb, 1d, b1, d0, 4d, 1a, f9, 04, 8c, b4, 22, 22, b0, 04, 91, 4a, 03, fd, bc, a8, 83, 32, be, 67, 95, c6, 24, b2, 0f, b5, 16, 6b, 94, 00] +tcp_req 8000006d01800000ebe4a4967aa901a9a2f4eb1c5d14c7b68c45cd7283609a7d0c7cd04d32d3baece982cce65676804c011b42c9d2b584d84567b463fcaf833ee00daa38e9db031976e6af3a67593e2bd8a24572d5bf3cab43411d2c5d5bb595a2f6a28c9ddeca6952d4b2749a3bdd05a3ddd06eb8dca75a12a314f36a6f3d5e7983526bcbec3d4a384bd6276f267dd48e6722b7a1c950812a3f9b5bd5c1586210be1a77b69e21e13f12f88c683c35ebd628b126a8f7223250838fee20d454c848a54a247f63b706c5e5b2eb9de47a86b25f51eea1601bcdd2ff584937557a0f0b6a3a9bd6c1ad7fc2d3c17ebee2d6861cb5f1effaf90d57c7ffe0c35e32a7fac3391c9e ConnectTcpReq { flags: 109, address_type: Ipv4, host: [228, 164, 150, 122, 169, 1, 169, 162, 244, 235, 28, 93, 20, 199, 182, 140, 69, 205, 114, 131, 96, 154, 125, 12, 124, 208, 77, 50, 211, 186, 236, 233, 130, 204, 230, 86, 118, 128, 76, 1, 27, 66, 201, 210, 181, 132, 216, 69, 103, 180, 99, 252, 175, 131, 62, 224, 13, 170, 56, 233, 219, 3, 25, 118, 230, 175, 58, 103, 89, 62, 43, 216, 162, 69, 114, 213, 191, 60, 171, 67, 65, 29, 44, 93, 91, 181, 149, 162, 246, 162, 140, 157, 222, 202, 105, 82, 212, 178, 116, 154, 59, 221, 5, 163, 221, 208, 110, 184, 220, 167, 90, 18, 163, 20, 243, 106, 111, 61, 94, 121, 131, 82, 107, 203, 236, 61, 74, 56, 75, 214, 39, 111, 38, 125, 212, 142, 103, 34, 183, 161, 201, 80, 129, 42, 63, 155, 91, 213, 193, 88, 98, 16, 190, 26, 119, 182, 158, 33, 225, 63, 18, 248, 140, 104, 60, 53, 235, 214, 40, 177, 38, 168, 247, 34, 50, 80, 131, 143, 238, 32, 212, 84, 200, 72, 165, 74, 36, 127, 99, 183, 6, 197, 229, 178, 235, 157, 228, 122, 134, 178, 95, 81, 238, 161, 96, 27, 205, 210, 255, 88, 73, 55, 85, 122, 15, 11, 106, 58, 155, 214, 193, 173, 127, 194, 211, 193, 126, 190, 226, 214, 134, 28, 181, 241, 239], port: 64249 };260;[40, 6d, 01, 40, eb, e4, a4, 96, 7a, a9, 01, a9, a2, f4, eb, 1c, 5d, 14, c7, b6, 8c, 45, cd, 72, 83, 60, 9a, 7d, 0c, 7c, d0, 4d, 32, d3, ba, ec, e9, 82, cc, e6, 56, 76, 80, 4c, 01, 1b, 42, c9, d2, b5, 84, d8, 45, 67, b4, 63, fc, af, 83, 3e, e0, 0d, aa, 38, e9, db, 03, 19, 76, e6, af, 3a, 67, 59, 3e, 2b, d8, a2, 45, 72, d5, bf, 3c, ab, 43, 41, 1d, 2c, 5d, 5b, b5, 95, a2, f6, a2, 8c, 9d, de, ca, 69, 52, d4, b2, 74, 9a, 3b, dd, 05, a3, dd, d0, 6e, b8, dc, a7, 5a, 12, a3, 14, f3, 6a, 6f, 3d, 5e, 79, 83, 52, 6b, cb, ec, 3d, 4a, 38, 4b, d6, 27, 6f, 26, 7d, d4, 8e, 67, 22, b7, a1, c9, 50, 81, 2a, 3f, 9b, 5b, d5, c1, 58, 62, 10, be, 1a, 77, b6, 9e, 21, e1, 3f, 12, f8, 8c, 68, 3c, 35, eb, d6, 28, b1, 26, a8, f7, 22, 32, 50, 83, 8f, ee, 20, d4, 54, c8, 48, a5, 4a, 24, 7f, 63, b7, 06, c5, e5, b2, eb, 9d, e4, 7a, 86, b2, 5f, 51, ee, a1, 60, 1b, cd, d2, ff, 58, 49, 37, 55, 7a, 0f, 0b, 6a, 3a, 9b, d6, c1, ad, 7f, c2, d3, c1, 7e, be, e2, d6, 86, 1c, b5, f1, ef, fa, f9, 00] +tcp_req c3e85e2e04efe9920140d90f3774d0d0a148255fe78bc99fd1ae8e2af1a3a76db136553555727633b01b6fc69450773d3d508b1265e7ce1ac0561d230b5d0e772165c0c2b1199dbea518efa1fac41d0bc038bcd696faeca382038dc0c769fea470b8a1a83cb6665204fdf5ee44325734fc3b061bf27eeffc5a7803004413d4dae881462654e631960fa6c6828518b3ff37f189159b4f0164045a4afaf92fc31d8b076b70351276d28077e6a2004ac25a06e99d50d73529334e4560eb49aa86b363bbcea351e3de4eaaaead97693c67e77613c2523935284003f4a373b6d1a21d4a1a244c41a9c000000000000000 ConnectTcpReq { flags: 281578528454994322, address_type: Ipv4, host: [15, 55, 116, 208, 208, 161, 72, 37, 95, 231, 139, 201, 159, 209, 174, 142, 42, 241, 163, 167, 109, 177, 54, 85, 53, 85, 114, 118, 51, 176, 27, 111, 198, 148, 80, 119, 61, 61, 80, 139, 18, 101, 231, 206, 26, 192, 86, 29, 35, 11, 93, 14, 119, 33, 101, 192, 194, 177, 25, 157, 190, 165, 24, 239, 161, 250, 196, 29, 11, 192, 56, 188, 214, 150, 250, 236, 163, 130, 3, 141, 192, 199, 105, 254, 164, 112, 184, 161, 168, 60, 182, 102, 82, 4, 253, 245, 238, 68, 50, 87, 52, 252, 59, 6, 27, 242, 126, 239, 252, 90, 120, 3, 0, 68, 19, 212, 218, 232, 129, 70, 38, 84, 230, 49, 150, 15, 166, 198, 130, 133, 24, 179, 255, 55, 241, 137, 21, 155, 79, 1, 100, 4, 90, 74, 250, 249, 47, 195, 29, 139, 7, 107, 112, 53, 18, 118, 210, 128, 119, 230, 162, 0, 74, 194, 90, 6, 233, 157, 80, 215, 53, 41, 51, 78, 69, 96, 235, 73, 170, 134, 179, 99, 187, 206, 163, 81, 227, 222, 78, 170, 174, 173, 151, 105, 60, 103, 231, 118, 19, 194, 82, 57, 53, 40, 64, 3, 244, 163, 115, 182, 209, 162, 29, 74, 26, 36, 76], port: 16809 };238;[c3, e8, 5e, 2e, 04, ef, e9, 92, 01, 40, d9, 0f, 37, 74, d0, d0, a1, 48, 25, 5f, e7, 8b, c9, 9f, d1, ae, 8e, 2a, f1, a3, a7, 6d, b1, 36, 55, 35, 55, 72, 76, 33, b0, 1b, 6f, c6, 94, 50, 77, 3d, 3d, 50, 8b, 12, 65, e7, ce, 1a, c0, 56, 1d, 23, 0b, 5d, 0e, 77, 21, 65, c0, c2, b1, 19, 9d, be, a5, 18, ef, a1, fa, c4, 1d, 0b, c0, 38, bc, d6, 96, fa, ec, a3, 82, 03, 8d, c0, c7, 69, fe, a4, 70, b8, a1, a8, 3c, b6, 66, 52, 04, fd, f5, ee, 44, 32, 57, 34, fc, 3b, 06, 1b, f2, 7e, ef, fc, 5a, 78, 03, 00, 44, 13, d4, da, e8, 81, 46, 26, 54, e6, 31, 96, 0f, a6, c6, 82, 85, 18, b3, ff, 37, f1, 89, 15, 9b, 4f, 01, 64, 04, 5a, 4a, fa, f9, 2f, c3, 1d, 8b, 07, 6b, 70, 35, 12, 76, d2, 80, 77, e6, a2, 00, 4a, c2, 5a, 06, e9, 9d, 50, d7, 35, 29, 33, 4e, 45, 60, eb, 49, aa, 86, b3, 63, bb, ce, a3, 51, e3, de, 4e, aa, ae, ad, 97, 69, 3c, 67, e7, 76, 13, c2, 52, 39, 35, 28, 40, 03, f4, a3, 73, b6, d1, a2, 1d, 4a, 1a, 24, 4c, 41, a9, 00] +tcp_req c0000008f974cba904400a9efba6a02198f5fa0db890cec000000000000002b4e7 ConnectTcpReq { flags: 38544919465, address_type: Ipv6, host: [158, 251, 166, 160, 33, 152, 245, 250, 13, 184], port: 37070 };33;[c0, 00, 00, 08, f9, 74, cb, a9, 04, 0a, 9e, fb, a6, a0, 21, 98, f5, fa, 0d, b8, 90, ce, 00] +tcp_req c000a9b8ebc7e7bf034070f57c67540289bd894bb1e11cb95cc944970c021bf995a2899ad2c763556ac5ebf64458c6963cb8db143e1f668dd93b899083399c24f5c489732a85cb91a3420354197b334c7c43d50c4b2d358d19539738f05ecd4a815a4944648442fc13edf8bce409724a6398ab610fc6d642bf8383530a03140118 ConnectTcpReq { flags: 186611694823359, address_type: Domain, host: [245, 124, 103, 84, 2, 137, 189, 137, 75, 177, 225, 28, 185, 92, 201, 68, 151, 12, 2, 27, 249, 149, 162, 137, 154, 210, 199, 99, 85, 106, 197, 235, 246, 68, 88, 198, 150, 60, 184, 219, 20, 62, 31, 102, 141, 217, 59, 137, 144, 131, 57, 156, 36, 245, 196, 137, 115, 42, 133, 203, 145, 163, 66, 3, 84, 25, 123, 51, 76, 124, 67, 213, 12, 75, 45, 53, 141, 25, 83, 151, 56, 240, 94, 205, 74, 129, 90, 73, 68, 100, 132, 66, 252, 19, 237, 248, 188, 228, 9, 114, 74, 99, 152, 171, 97, 15, 198, 214, 66, 191, 131, 131], port: 21258 };129;[c0, 00, a9, b8, eb, c7, e7, bf, 03, 40, 70, f5, 7c, 67, 54, 02, 89, bd, 89, 4b, b1, e1, 1c, b9, 5c, c9, 44, 97, 0c, 02, 1b, f9, 95, a2, 89, 9a, d2, c7, 63, 55, 6a, c5, eb, f6, 44, 58, c6, 96, 3c, b8, db, 14, 3e, 1f, 66, 8d, d9, 3b, 89, 90, 83, 39, 9c, 24, f5, c4, 89, 73, 2a, 85, cb, 91, a3, 42, 03, 54, 19, 7b, 33, 4c, 7c, 43, d5, 0c, 4b, 2d, 35, 8d, 19, 53, 97, 38, f0, 5e, cd, 4a, 81, 5a, 49, 44, 64, 84, 42, fc, 13, ed, f8, bc, e4, 09, 72, 4a, 63, 98, ab, 61, 0f, c6, d6, 42, bf, 83, 83, 53, 0a, 00] +tcp_req c0000dce2257d5830340eaa78da900445ce528969860f52700b780d71bff025c42c36ab0b206df459baddb2fdf0fa5b707567d2a0a795c378b259031cd61d39ab266740f3e9430b0cb0083e0b0e8047ec555f447d59714d0967bd4abe6e535e596898a11332f51f977f64fe7d0b3e164f89b4b2ad6f4acf727bbdb5e0376d07e023607864be216bef358ac92c0b69ce3f661657f634f1209a45444b49e45e446dbd2d5c9abc875aa400999121bdc24d163192c5ea737338577f54d83f82708bc56c059def138dcf8cb04fa788b61abfaf6f41a69fcc99d57bca3513b6017ce433bdc126d77c9a2945f2808e4239cb9c248a97f31a85b1dc000000000000000 ConnectTcpReq { flags: 15178990605699, address_type: Domain, host: [167, 141, 169, 0, 68, 92, 229, 40, 150, 152, 96, 245, 39, 0, 183, 128, 215, 27, 255, 2, 92, 66, 195, 106, 176, 178, 6, 223, 69, 155, 173, 219, 47, 223, 15, 165, 183, 7, 86, 125, 42, 10, 121, 92, 55, 139, 37, 144, 49, 205, 97, 211, 154, 178, 102, 116, 15, 62, 148, 48, 176, 203, 0, 131, 224, 176, 232, 4, 126, 197, 85, 244, 71, 213, 151, 20, 208, 150, 123, 212, 171, 230, 229, 53, 229, 150, 137, 138, 17, 51, 47, 81, 249, 119, 246, 79, 231, 208, 179, 225, 100, 248, 155, 75, 42, 214, 244, 172, 247, 39, 187, 219, 94, 3, 118, 208, 126, 2, 54, 7, 134, 75, 226, 22, 190, 243, 88, 172, 146, 192, 182, 156, 227, 246, 97, 101, 127, 99, 79, 18, 9, 164, 84, 68, 180, 158, 69, 228, 70, 219, 210, 213, 201, 171, 200, 117, 170, 64, 9, 153, 18, 27, 220, 36, 209, 99, 25, 44, 94, 167, 55, 51, 133, 119, 245, 77, 131, 248, 39, 8, 188, 86, 192, 89, 222, 241, 56, 220, 248, 203, 4, 250, 120, 139, 97, 171, 250, 246, 244, 26, 105, 252, 201, 157, 87, 188, 163, 81, 59, 96, 23, 206, 67, 59, 220, 18, 109, 119, 201, 162, 148, 95, 40, 8, 228, 35, 156, 185, 194, 72, 169, 127, 49, 168], port: 23325 };255;[c0, 00, 0d, ce, 22, 57, d5, 83, 03, 40, ea, a7, 8d, a9, 00, 44, 5c, e5, 28, 96, 98, 60, f5, 27, 00, b7, 80, d7, 1b, ff, 02, 5c, 42, c3, 6a, b0, b2, 06, df, 45, 9b, ad, db, 2f, df, 0f, a5, b7, 07, 56, 7d, 2a, 0a, 79, 5c, 37, 8b, 25, 90, 31, cd, 61, d3, 9a, b2, 66, 74, 0f, 3e, 94, 30, b0, cb, 00, 83, e0, b0, e8, 04, 7e, c5, 55, f4, 47, d5, 97, 14, d0, 96, 7b, d4, ab, e6, e5, 35, e5, 96, 89, 8a, 11, 33, 2f, 51, f9, 77, f6, 4f, e7, d0, b3, e1, 64, f8, 9b, 4b, 2a, d6, f4, ac, f7, 27, bb, db, 5e, 03, 76, d0, 7e, 02, 36, 07, 86, 4b, e2, 16, be, f3, 58, ac, 92, c0, b6, 9c, e3, f6, 61, 65, 7f, 63, 4f, 12, 09, a4, 54, 44, b4, 9e, 45, e4, 46, db, d2, d5, c9, ab, c8, 75, aa, 40, 09, 99, 12, 1b, dc, 24, d1, 63, 19, 2c, 5e, a7, 37, 33, 85, 77, f5, 4d, 83, f8, 27, 08, bc, 56, c0, 59, de, f1, 38, dc, f8, cb, 04, fa, 78, 8b, 61, ab, fa, f6, f4, 1a, 69, fc, c9, 9d, 57, bc, a3, 51, 3b, 60, 17, ce, 43, 3b, dc, 12, 6d, 77, c9, a2, 94, 5f, 28, 08, e4, 23, 9c, b9, c2, 48, a9, 7f, 31, a8, 5b, 1d, 00] +tcp_req c00885c8289190fecf80000092034d5f76dc7dcf6119aa92367bf7e967c34f5d2434ab88f817a4ffc943fc9e1c6251721b8050462129af36f2e07dd488d01bd24180d8dcbdf45fb976f5e295d2274672c611c1b3120bf23b952970855a20d88f5df0f6abb798f390eec64e758e340684b0c4979287ef275d84415822dc61889a00f539748047b6f9a0a512374dae6cf7213945db555ee05050034ee48d9ef6fe9e800000064657c7338a3a reject +tcp_req 80073c8003c0000000000000328fa72fff07cfdaa828c07825ef1cc0fa520aecbda6410afa1a323a48501dd7e0c8d28e178535aaf48a85560687a4f65942a25b7b80000000 ConnectTcpReq { flags: 474240, address_type: Domain, host: [143, 167, 47, 255, 7, 207, 218, 168, 40, 192, 120, 37, 239, 28, 192, 250, 82, 10, 236, 189, 166, 65, 10, 250, 26, 50, 58, 72, 80, 29, 215, 224, 200, 210, 142, 23, 133, 53, 170, 244, 138, 133, 86, 6, 135, 164, 246, 89, 66, 162], port: 23419 };69;[80, 07, 3c, 80, 03, 32, 8f, a7, 2f, ff, 07, cf, da, a8, 28, c0, 78, 25, ef, 1c, c0, fa, 52, 0a, ec, bd, a6, 41, 0a, fa, 1a, 32, 3a, 48, 50, 1d, d7, e0, c8, d2, 8e, 17, 85, 35, aa, f4, 8a, 85, 56, 06, 87, a4, f6, 59, 42, a2, 5b, 7b, 00] +tcp_req c0000002f261cf710440bcc94bbeaa442d3a16d1ea57b8bae1fff1b3289d16930b3b2b2b64ca400db34e00d4ce3803c498bed66ab0d130ced474aa8f76538489ceed2a223c8b46dfb9d8e5082d93174f77770ff40a2e806b16ae3d7f2fc58a873f768ba7118d40748f122bded1c60f7969a2d7518c318d6f5e460338ac94940db3ac1644a5a08cb39821b123474db9fb7c1cf31dda9f5fa5e9972bbaa06266aecdff95797c7fab47df8753a4daddae4a43e6c0c481e92f681ccbd24c409fd54007d6fb2d5831587a3e80000000 ConnectTcpReq { flags: 12656430961, address_type: Ipv6, host: [201, 75, 190, 170, 68, 45, 58, 22, 209, 234, 87, 184, 186, 225, 255, 241, 179, 40, 157, 22, 147, 11, 59, 43, 43, 100, 202, 64, 13, 179, 78, 0, 212, 206, 56, 3, 196, 152, 190, 214, 106, 176, 209, 48, 206, 212, 116, 170, 143, 118, 83, 132, 137, 206, 237, 42, 34, 60, 139, 70, 223, 185, 216, 229, 8, 45, 147, 23, 79, 119, 119, 15, 244, 10, 46, 128, 107, 22, 174, 61, 127, 47, 197, 138, 135, 63, 118, 139, 167, 17, 141, 64, 116, 143, 18, 43, 222, 209, 198, 15, 121, 105, 162, 215, 81, 140, 49, 141, 111, 94, 70, 3, 56, 172, 148, 148, 13, 179, 172, 22, 68, 165, 160, 140, 179, 152, 33, 177, 35, 71, 77, 185, 251, 124, 28, 243, 29, 218, 159, 95, 165, 233, 151, 43, 186, 160, 98, 102, 174, 205, 255, 149, 121, 124, 127, 171, 71, 223, 135, 83, 164, 218, 221, 174, 74, 67, 230, 192, 196, 129, 233, 47, 104, 28, 203, 210, 76, 64, 159, 213, 64, 7, 214, 251, 45, 88, 49, 88], port: 31294 };205;[c0, 00, 00, 02, f2, 61, cf, 71, 04, 40, bc, c9, 4b, be, aa, 44, 2d, 3a, 16, d1, ea, 57, b8, ba, e1, ff, f1, b3, 28, 9d, 16, 93, 0b, 3b, 2b, 2b, 64, ca, 40, 0d, b3, 4e, 00, d4, ce, 38, 03, c4, 98, be, d6, 6a, b0, d1, 30, ce, d4, 74, aa, 8f, 76, 53, 84, 89, ce, ed, 2a, 22, 3c, 8b, 46, df, b9, d8, e5, 08, 2d, 93, 17, 4f, 77, 77, 0f, f4, 0a, 2e, 80, 6b, 16, ae, 3d, 7f, 2f, c5, 8a, 87, 3f, 76, 8b, a7, 11, 8d, 40, 74, 8f, 12, 2b, de, d1, c6, 0f, 79, 69, a2, d7, 51, 8c, 31, 8d, 6f, 5e, 46, 03, 38, ac, 94, 94, 0d, b3, ac, 16, 44, a5, a0, 8c, b3, 98, 21, b1, 23, 47, 4d, b9, fb, 7c, 1c, f3, 1d, da, 9f, 5f, a5, e9, 97, 2b, ba, a0, 62, 66, ae, cd, ff, 95, 79, 7c, 7f, ab, 47, df, 87, 53, a4, da, dd, ae, 4a, 43, e6, c0, c4, 81, e9, 2f, 68, 1c, cb, d2, 4c, 40, 9f, d5, 40, 07, d6, fb, 2d, 58, 31, 58, 7a, 3e, 00] +tcp_req c0000a01fbd93fe70140830c7e12c3a4683d6a6f7077a1b9d480d51dc2007e2f93b740475f5e1dc7afd922223668e63349b82fe32929f7945511103d22037738841cfc60dc6842f9f517a522178f469a0258d2664a94309a9cc9b9e15abce203ffec5fa31ff8f188dd23e87e3d8c32ed5569aec42634eb9e2da9be1b20a8e4d18a544a649a00308413c58680005cabf1c00000000000000152 ConnectTcpReq { flags: 11003636563943, address_type: Ipv4, host: [12, 126, 18, 195, 164, 104, 61, 106, 111, 112, 119, 161, 185, 212, 128, 213, 29, 194, 0, 126, 47, 147, 183, 64, 71, 95, 94, 29, 199, 175, 217, 34, 34, 54, 104, 230, 51, 73, 184, 47, 227, 41, 41, 247, 148, 85, 17, 16, 61, 34, 3, 119, 56, 132, 28, 252, 96, 220, 104, 66, 249, 245, 23, 165, 34, 23, 143, 70, 154, 2, 88, 210, 102, 74, 148, 48, 154, 156, 201, 185, 225, 90, 188, 226, 3, 255, 236, 95, 163, 31, 248, 241, 136, 221, 35, 232, 126, 61, 140, 50, 237, 85, 105, 174, 196, 38, 52, 235, 158, 45, 169, 190, 27, 32, 168, 228, 209, 138, 84, 74, 100, 154, 0, 48, 132, 19, 197, 134, 128, 0, 92], port: 44017 };153;[c0, 00, 0a, 01, fb, d9, 3f, e7, 01, 40, 83, 0c, 7e, 12, c3, a4, 68, 3d, 6a, 6f, 70, 77, a1, b9, d4, 80, d5, 1d, c2, 00, 7e, 2f, 93, b7, 40, 47, 5f, 5e, 1d, c7, af, d9, 22, 22, 36, 68, e6, 33, 49, b8, 2f, e3, 29, 29, f7, 94, 55, 11, 10, 3d, 22, 03, 77, 38, 84, 1c, fc, 60, dc, 68, 42, f9, f5, 17, a5, 22, 17, 8f, 46, 9a, 02, 58, d2, 66, 4a, 94, 30, 9a, 9c, c9, b9, e1, 5a, bc, e2, 03, ff, ec, 5f, a3, 1f, f8, f1, 88, dd, 23, e8, 7e, 3d, 8c, 32, ed, 55, 69, ae, c4, 26, 34, eb, 9e, 2d, a9, be, 1b, 20, a8, e4, d1, 8a, 54, 4a, 64, 9a, 00, 30, 84, 13, c5, 86, 80, 00, 5c, ab, f1, 00] +tcp_req 8000ffc503800000e179960616ef8ba68daba71ba16c12be32655ac1c5fbaeb5694c754874ad11b34f9f8f40e14b6d0762b68b9d331415c9276294f61190e25df9f0917aa751066713a8cf117ba9abbe9cd8eb649bb442af495ed867865b8076aa5eb7016c95a878ad95385fbe3a9f2684bf6f5ac6f7b5c1afdcbdb481c00cc8969787aee68dff197e4c5c313b02bcec708efcb3e513a7958eeee871ecedf36d4451cbcda44566dea75fe7c75c826f3717a9a0c3af65841ee774fc1c6e3819801cb35986856dcb5df2aa0031d48a3bde47d48b308a214b54c32ec486340dc27bd68557b537ccb9b9b5c8bc5b80000000 ConnectTcpReq { flags: 65477, address_type: Domain, host: [121, 150, 6, 22, 239, 139, 166, 141, 171, 167, 27, 161, 108, 18, 190, 50, 101, 90, 193, 197, 251, 174, 181, 105, 76, 117, 72, 116, 173, 17, 179, 79, 159, 143, 64, 225, 75, 109, 7, 98, 182, 139, 157, 51, 20, 21, 201, 39, 98, 148, 246, 17, 144, 226, 93, 249, 240, 145, 122, 167, 81, 6, 103, 19, 168, 207, 17, 123, 169, 171, 190, 156, 216, 235, 100, 155, 180, 66, 175, 73, 94, 216, 103, 134, 91, 128, 118, 170, 94, 183, 1, 108, 149, 168, 120, 173, 149, 56, 95, 190, 58, 159, 38, 132, 191, 111, 90, 198, 247, 181, 193, 175, 220, 189, 180, 129, 192, 12, 200, 150, 151, 135, 174, 230, 141, 255, 25, 126, 76, 92, 49, 59, 2, 188, 236, 112, 142, 252, 179, 229, 19, 167, 149, 142, 238, 232, 113, 236, 237, 243, 109, 68, 81, 203, 205, 164, 69, 102, 222, 167, 95, 231, 199, 92, 130, 111, 55, 23, 169, 160, 195, 175, 101, 132, 30, 231, 116, 252, 28, 110, 56, 25, 128, 28, 179, 89, 134, 133, 109, 203, 93, 242, 170, 0, 49, 212, 138, 59, 222, 71, 212, 139, 48, 138, 33, 75, 84, 195, 46, 196, 134, 52, 13, 194, 123, 214, 133, 87, 181, 55, 204, 185, 185, 181, 200], port: 48219 };240;[80, 00, ff, c5, 03, 40, e1, 79, 96, 06, 16, ef, 8b, a6, 8d, ab, a7, 1b, a1, 6c, 12, be, 32, 65, 5a, c1, c5, fb, ae, b5, 69, 4c, 75, 48, 74, ad, 11, b3, 4f, 9f, 8f, 40, e1, 4b, 6d, 07, 62, b6, 8b, 9d, 33, 14, 15, c9, 27, 62, 94, f6, 11, 90, e2, 5d, f9, f0, 91, 7a, a7, 51, 06, 67, 13, a8, cf, 11, 7b, a9, ab, be, 9c, d8, eb, 64, 9b, b4, 42, af, 49, 5e, d8, 67, 86, 5b, 80, 76, aa, 5e, b7, 01, 6c, 95, a8, 78, ad, 95, 38, 5f, be, 3a, 9f, 26, 84, bf, 6f, 5a, c6, f7, b5, c1, af, dc, bd, b4, 81, c0, 0c, c8, 96, 97, 87, ae, e6, 8d, ff, 19, 7e, 4c, 5c, 31, 3b, 02, bc, ec, 70, 8e, fc, b3, e5, 13, a7, 95, 8e, ee, e8, 71, ec, ed, f3, 6d, 44, 51, cb, cd, a4, 45, 66, de, a7, 5f, e7, c7, 5c, 82, 6f, 37, 17, a9, a0, c3, af, 65, 84, 1e, e7, 74, fc, 1c, 6e, 38, 19, 80, 1c, b3, 59, 86, 85, 6d, cb, 5d, f2, aa, 00, 31, d4, 8a, 3b, de, 47, d4, 8b, 30, 8a, 21, 4b, 54, c3, 2e, c4, 86, 34, 0d, c2, 7b, d6, 85, 57, b5, 37, cc, b9, b9, b5, c8, bc, 5b, 00] +tcp_req c760ea16a4dd32580140b12bf0cb1a1190cee27021c3903ab1c600e141a14809428e50b271db6fa0b19486a3759237bd260567ebb3f4aa11f5c30cd0243f7698c96901d288149df5dff01ab463725c8bc7104a19a585c9c167746caec4884a5bef4aa302bf4602cbc2aa4ff9343c42c90adbcd9a842c315f10e0609a37a614165588282d1e87107abda01667daa9d7e6130e66744597508620b5517dffdec38b222fab9166825de8817d1f8d4dc01ecb2a522a8add4a71f9d8f9cef44a3300 ConnectTcpReq { flags: 531682139005858392, address_type: Ipv4, host: [43, 240, 203, 26, 17, 144, 206, 226, 112, 33, 195, 144, 58, 177, 198, 0, 225, 65, 161, 72, 9, 66, 142, 80, 178, 113, 219, 111, 160, 177, 148, 134, 163, 117, 146, 55, 189, 38, 5, 103, 235, 179, 244, 170, 17, 245, 195, 12, 208, 36, 63, 118, 152, 201, 105, 1, 210, 136, 20, 157, 245, 223, 240, 26, 180, 99, 114, 92, 139, 199, 16, 74, 25, 165, 133, 201, 193, 103, 116, 108, 174, 196, 136, 74, 91, 239, 74, 163, 2, 191, 70, 2, 203, 194, 170, 79, 249, 52, 60, 66, 201, 10, 219, 205, 154, 132, 44, 49, 95, 16, 224, 96, 154, 55, 166, 20, 22, 85, 136, 40, 45, 30, 135, 16, 122, 189, 160, 22, 103, 218, 169, 215, 230, 19, 14, 102, 116, 69, 151, 80, 134, 32, 181, 81, 125, 255, 222, 195, 139, 34, 47, 171, 145, 102, 130, 93, 232, 129, 125, 31, 141, 77, 192, 30, 203, 42, 82, 42, 138, 221, 74, 113, 249, 216, 249, 206, 244], port: 18995 };191;[c7, 60, ea, 16, a4, dd, 32, 58, 01, 40, b1, 2b, f0, cb, 1a, 11, 90, ce, e2, 70, 21, c3, 90, 3a, b1, c6, 00, e1, 41, a1, 48, 09, 42, 8e, 50, b2, 71, db, 6f, a0, b1, 94, 86, a3, 75, 92, 37, bd, 26, 05, 67, eb, b3, f4, aa, 11, f5, c3, 0c, d0, 24, 3f, 76, 98, c9, 69, 01, d2, 88, 14, 9d, f5, df, f0, 1a, b4, 63, 72, 5c, 8b, c7, 10, 4a, 19, a5, 85, c9, c1, 67, 74, 6c, ae, c4, 88, 4a, 5b, ef, 4a, a3, 02, bf, 46, 02, cb, c2, aa, 4f, f9, 34, 3c, 42, c9, 0a, db, cd, 9a, 84, 2c, 31, 5f, 10, e0, 60, 9a, 37, a6, 14, 16, 55, 88, 28, 2d, 1e, 87, 10, 7a, bd, a0, 16, 67, da, a9, d7, e6, 13, 0e, 66, 74, 45, 97, 50, 86, 20, b5, 51, 7d, ff, de, c3, 8b, 22, 2f, ab, 91, 66, 82, 5d, e8, 81, 7d, 1f, 8d, 4d, c0, 1e, cb, 2a, 52, 2a, 8a, dd, 4a, 71, f9, d8, f9, ce, f4, 4a, 33, 00] +tcp_req c198312949a91ec404351d0b78af63948b31fb1f895edb2fa68e546183fbe1bf563d4efa3dd8d46336edada9a8b48e8fb916a769e012389c6abcf351860f5723bf40069d2751f01e31 ConnectTcpReq { flags: 114895843897188036, address_type: Ipv6, host: [29, 11, 120, 175, 99, 148, 139, 49, 251, 31, 137, 94, 219, 47, 166, 142, 84, 97, 131, 251, 225, 191, 86, 61, 78, 250, 61, 216, 212, 99, 54, 237, 173, 169, 168, 180, 142, 143, 185, 22, 167, 105, 224, 18, 56, 156, 106, 188, 243, 81, 134, 15, 87], port: 9151 };73;[c1, 98, 31, 29, 49, a9, 1e, c4, 04, 35, 1d, 0b, 78, af, 63, 94, 8b, 31, fb, 1f, 89, 5e, db, 2f, a6, 8e, 54, 61, 83, fb, e1, bf, 56, 3d, 4e, fa, 3d, d8, d4, 63, 36, ed, ad, a9, a8, b4, 8e, 8f, b9, 16, a7, 69, e0, 12, 38, 9c, 6a, bc, f3, 51, 86, 0f, 57, 23, bf, 00] +tcp_req cf64b6b872b97c4b03800000ea79d13beffbe804535d824ee6ad6179bb16ad67ffbb3a877716561c413567f6c7c5839899a7bd981afc447701fdee3e606dbb5ccbae408a2c2fc3c97c8f2a89ce5ceedb80c394afa5c012337271e835f5f235a975d4ef444a0c97af021554a4e0ce304b01178f0bf3ed09c9c780e788055df69466393f1abcb00f9a6aca4f86ae3bfe1688e86cc673b43e073896b6f4c9ecb8465f1250f594efa764745e3f4066e8754a8161e4d10aeea953b70b11f7949fd6e59408db40501fa2fc2931920a2a3eaabad2753b51b8c9dd4677d18e5225183b7ee73819210e6f363b7d68069e39c239d56c8b8a5ac6f43a2eb600 ConnectTcpReq { flags: 1109212311554980939, address_type: Domain, host: [121, 209, 59, 239, 251, 232, 4, 83, 93, 130, 78, 230, 173, 97, 121, 187, 22, 173, 103, 255, 187, 58, 135, 119, 22, 86, 28, 65, 53, 103, 246, 199, 197, 131, 152, 153, 167, 189, 152, 26, 252, 68, 119, 1, 253, 238, 62, 96, 109, 187, 92, 203, 174, 64, 138, 44, 47, 195, 201, 124, 143, 42, 137, 206, 92, 238, 219, 128, 195, 148, 175, 165, 192, 18, 51, 114, 113, 232, 53, 245, 242, 53, 169, 117, 212, 239, 68, 74, 12, 151, 175, 2, 21, 84, 164, 224, 206, 48, 75, 1, 23, 143, 11, 243, 237, 9, 201, 199, 128, 231, 136, 5, 93, 246, 148, 102, 57, 63, 26, 188, 176, 15, 154, 106, 202, 79, 134, 174, 59, 254, 22, 136, 232, 108, 198, 115, 180, 62, 7, 56, 150, 182, 244, 201, 236, 184, 70, 95, 18, 80, 245, 148, 239, 167, 100, 116, 94, 63, 64, 102, 232, 117, 74, 129, 97, 228, 209, 10, 238, 169, 83, 183, 11, 17, 247, 148, 159, 214, 229, 148, 8, 219, 64, 80, 31, 162, 252, 41, 49, 146, 10, 42, 62, 170, 186, 210, 117, 59, 81, 184, 201, 221, 70, 119, 209, 142, 82, 37, 24, 59, 126, 231, 56, 25, 33, 14, 111, 54, 59, 125, 104, 6, 158, 57, 194, 57, 213, 108, 139, 138, 90, 198, 244, 58], port: 11958 };250;[cf, 64, b6, b8, 72, b9, 7c, 4b, 03, 40, ea, 79, d1, 3b, ef, fb, e8, 04, 53, 5d, 82, 4e, e6, ad, 61, 79, bb, 16, ad, 67, ff, bb, 3a, 87, 77, 16, 56, 1c, 41, 35, 67, f6, c7, c5, 83, 98, 99, a7, bd, 98, 1a, fc, 44, 77, 01, fd, ee, 3e, 60, 6d, bb, 5c, cb, ae, 40, 8a, 2c, 2f, c3, c9, 7c, 8f, 2a, 89, ce, 5c, ee, db, 80, c3, 94, af, a5, c0, 12, 33, 72, 71, e8, 35, f5, f2, 35, a9, 75, d4, ef, 44, 4a, 0c, 97, af, 02, 15, 54, a4, e0, ce, 30, 4b, 01, 17, 8f, 0b, f3, ed, 09, c9, c7, 80, e7, 88, 05, 5d, f6, 94, 66, 39, 3f, 1a, bc, b0, 0f, 9a, 6a, ca, 4f, 86, ae, 3b, fe, 16, 88, e8, 6c, c6, 73, b4, 3e, 07, 38, 96, b6, f4, c9, ec, b8, 46, 5f, 12, 50, f5, 94, ef, a7, 64, 74, 5e, 3f, 40, 66, e8, 75, 4a, 81, 61, e4, d1, 0a, ee, a9, 53, b7, 0b, 11, f7, 94, 9f, d6, e5, 94, 08, db, 40, 50, 1f, a2, fc, 29, 31, 92, 0a, 2a, 3e, aa, ba, d2, 75, 3b, 51, b8, c9, dd, 46, 77, d1, 8e, 52, 25, 18, 3b, 7e, e7, 38, 19, 21, 0e, 6f, 36, 3b, 7d, 68, 06, 9e, 39, c2, 39, d5, 6c, 8b, 8a, 5a, c6, f4, 3a, 2e, b6, 00] +tcp_req 862d2c2c0180000102796349bfaa605135525380b69e4b86f272b82e8f30211bc21960da404095f797286858f2040506161a209f36f158b7130e767e3717c7c9beeb1c51f423f74eefbc2d72e1cf3fa7540f4c36c7818d4990f0f6c5fe2a11018a54273367259cabd748f4cb402df9e3f6815eae998afe2ef0b045896cd95b78c555f5076ed28c26fe459296cd22cf76deb630171353efb1ac6601e480e17b873d4d2e907f5b51f4d224a56d38fa72ce101067c858da17619e8b8fc3a6c4d9a4b99a3c35c58e25e491617cca10a3f09df20d3e10a8f894f9956d5a50079ed85bc0d412e9e4f9ed10b81ad400968df6fecb8512466472b51ae0925032a1f71c817da01b920272eea890ce6fc00580000000 reject +tcp_req c0000000000096cf0140e1ce2b12856445688afe73fd8fd79b29b1bcd75f2c38ad1d68d4326b56d2c7549e0137e9f1f97838e40334866a0754522689e02fbda771b3a6fe8f4bdb3171e20cf8314475e8d0cfa7090f5c8b89e584ef5790fc0704918e449ae5811028313ad3ba3c804df57da6fdf1c8224e388e391dec7a0a4502fe8003241ee58f3b869c282f45506672b6f1b768e22d75f6e9e60ca3b2c754b51d68e1e5d1e6d1fb19e0a7d9165466c438811674b45222872b5bda1faf1a25b9af3ea3e20c5f19edbc13558d20fb22e83cf4a7dc7bba131d6c42c0b79c4f22f3e60f1a0418ee0e805fad3e020a5080000000 ConnectTcpReq { flags: 38607, address_type: Ipv4, host: [206, 43, 18, 133, 100, 69, 104, 138, 254, 115, 253, 143, 215, 155, 41, 177, 188, 215, 95, 44, 56, 173, 29, 104, 212, 50, 107, 86, 210, 199, 84, 158, 1, 55, 233, 241, 249, 120, 56, 228, 3, 52, 134, 106, 7, 84, 82, 38, 137, 224, 47, 189, 167, 113, 179, 166, 254, 143, 75, 219, 49, 113, 226, 12, 248, 49, 68, 117, 232, 208, 207, 167, 9, 15, 92, 139, 137, 229, 132, 239, 87, 144, 252, 7, 4, 145, 142, 68, 154, 229, 129, 16, 40, 49, 58, 211, 186, 60, 128, 77, 245, 125, 166, 253, 241, 200, 34, 78, 56, 142, 57, 29, 236, 122, 10, 69, 2, 254, 128, 3, 36, 30, 229, 143, 59, 134, 156, 40, 47, 69, 80, 102, 114, 182, 241, 183, 104, 226, 45, 117, 246, 233, 230, 12, 163, 178, 199, 84, 181, 29, 104, 225, 229, 209, 230, 209, 251, 25, 224, 167, 217, 22, 84, 102, 196, 56, 129, 22, 116, 180, 82, 34, 135, 43, 91, 218, 31, 175, 26, 37, 185, 175, 62, 163, 226, 12, 95, 25, 237, 188, 19, 85, 141, 32, 251, 34, 232, 60, 244, 167, 220, 123, 186, 19, 29, 108, 66, 192, 183, 156, 79, 34, 243, 230, 15, 26, 4, 24, 238, 14, 128, 95, 173, 62, 2], port: 2640 };242;[80, 00, 96, cf, 01, 40, e1, ce, 2b, 12, 85, 64, 45, 68, 8a, fe, 73, fd, 8f, d7, 9b, 29, b1, bc, d7, 5f, 2c, 38, ad, 1d, 68, d4, 32, 6b, 56, d2, c7, 54, 9e, 01, 37, e9, f1, f9, 78, 38, e4, 03, 34, 86, 6a, 07, 54, 52, 26, 89, e0, 2f, bd, a7, 71, b3, a6, fe, 8f, 4b, db, 31, 71, e2, 0c, f8, 31, 44, 75, e8, d0, cf, a7, 09, 0f, 5c, 8b, 89, e5, 84, ef, 57, 90, fc, 07, 04, 91, 8e, 44, 9a, e5, 81, 10, 28, 31, 3a, d3, ba, 3c, 80, 4d, f5, 7d, a6, fd, f1, c8, 22, 4e, 38, 8e, 39, 1d, ec, 7a, 0a, 45, 02, fe, 80, 03, 24, 1e, e5, 8f, 3b, 86, 9c, 28, 2f, 45, 50, 66, 72, b6, f1, b7, 68, e2, 2d, 75, f6, e9, e6, 0c, a3, b2, c7, 54, b5, 1d, 68, e1, e5, d1, e6, d1, fb, 19, e0, a7, d9, 16, 54, 66, c4, 38, 81, 16, 74, b4, 52, 22, 87, 2b, 5b, da, 1f, af, 1a, 25, b9, af, 3e, a3, e2, 0c, 5f, 19, ed, bc, 13, 55, 8d, 20, fb, 22, e8, 3c, f4, a7, dc, 7b, ba, 13, 1d, 6c, 42, c0, b7, 9c, 4f, 22, f3, e6, 0f, 1a, 04, 18, ee, 0e, 80, 5f, ad, 3e, 02, 0a, 50, 00] +tcp_req 6eaa04c000000000000056ead14fe8f2e51e3a35d3225320e1636a3c50d41ea0d30afbfb307c7d95b2031bd51adef996a0d409582c2ae1d3de6b2bc222f3cdaeb3653fb29e2b451889fa8067ccd0c16b7c8d661b254873dd26d94f8accb2a9dce2389500 ConnectTcpReq { flags: 11946, address_type: Ipv6, host: [234, 209, 79, 232, 242, 229, 30, 58, 53, 211, 34, 83, 32, 225, 99, 106, 60, 80, 212, 30, 160, 211, 10, 251, 251, 48, 124, 125, 149, 178, 3, 27, 213, 26, 222, 249, 150, 160, 212, 9, 88, 44, 42, 225, 211, 222, 107, 43, 194, 34, 243, 205, 174, 179, 101, 63, 178, 158, 43, 69, 24, 137, 250, 128, 103, 204, 208, 193, 107, 124, 141, 102, 27, 37, 72, 115, 221, 38, 217, 79, 138, 204, 178, 169, 220, 226], port: 14485 };100;[6e, aa, 04, 40, 56, ea, d1, 4f, e8, f2, e5, 1e, 3a, 35, d3, 22, 53, 20, e1, 63, 6a, 3c, 50, d4, 1e, a0, d3, 0a, fb, fb, 30, 7c, 7d, 95, b2, 03, 1b, d5, 1a, de, f9, 96, a0, d4, 09, 58, 2c, 2a, e1, d3, de, 6b, 2b, c2, 22, f3, cd, ae, b3, 65, 3f, b2, 9e, 2b, 45, 18, 89, fa, 80, 67, cc, d0, c1, 6b, 7c, 8d, 66, 1b, 25, 48, 73, dd, 26, d9, 4f, 8a, cc, b2, a9, dc, e2, 38, 95, 00] +tcp_req c00000000000004003800000b01d62f90892bd7d0bffc0d0998dc26588d932984bef3fb349b4f7910c506e94d368d4aedaff8ab4f25f4925279d2b6c77e9d78ae28fa713cda27629b5f22956749e2294e67cffa06f3d0069d9b1e6d09a66f85b182234a9986695e06454b2135a308a714cf7e66935a81632b0a14562388a9f0b87dccef3dd3c2c75697bf77b3f6f46b89e4d8420c77a79b2df2a6089c15fdc7e795f98219ef90dbb1ed3091a1d25309ccb79596a9f9a2ba1e01eab7c23fd4fc000000000000000 ConnectTcpReq { flags: 64, address_type: Domain, host: [29, 98, 249, 8, 146, 189, 125, 11, 255, 192, 208, 153, 141, 194, 101, 136, 217, 50, 152, 75, 239, 63, 179, 73, 180, 247, 145, 12, 80, 110, 148, 211, 104, 212, 174, 218, 255, 138, 180, 242, 95, 73, 37, 39, 157, 43, 108, 119, 233, 215, 138, 226, 143, 167, 19, 205, 162, 118, 41, 181, 242, 41, 86, 116, 158, 34, 148, 230, 124, 255, 160, 111, 61, 0, 105, 217, 177, 230, 208, 154, 102, 248, 91, 24, 34, 52, 169, 152, 102, 149, 224, 100, 84, 178, 19, 90, 48, 138, 113, 76, 247, 230, 105, 53, 168, 22, 50, 176, 161, 69, 98, 56, 138, 159, 11, 135, 220, 206, 243, 221, 60, 44, 117, 105, 123, 247, 123, 63, 111, 70, 184, 158, 77, 132, 32, 199, 122, 121, 178, 223, 42, 96, 137, 193, 95, 220, 126, 121, 95, 152, 33, 158, 249, 13, 187, 30, 211, 9, 26, 29, 37, 48, 156, 203, 121, 89, 106, 159, 154, 43, 161, 224, 30, 171, 124, 35], port: 64847 };199;[40, 40, 03, 40, b0, 1d, 62, f9, 08, 92, bd, 7d, 0b, ff, c0, d0, 99, 8d, c2, 65, 88, d9, 32, 98, 4b, ef, 3f, b3, 49, b4, f7, 91, 0c, 50, 6e, 94, d3, 68, d4, ae, da, ff, 8a, b4, f2, 5f, 49, 25, 27, 9d, 2b, 6c, 77, e9, d7, 8a, e2, 8f, a7, 13, cd, a2, 76, 29, b5, f2, 29, 56, 74, 9e, 22, 94, e6, 7c, ff, a0, 6f, 3d, 00, 69, d9, b1, e6, d0, 9a, 66, f8, 5b, 18, 22, 34, a9, 98, 66, 95, e0, 64, 54, b2, 13, 5a, 30, 8a, 71, 4c, f7, e6, 69, 35, a8, 16, 32, b0, a1, 45, 62, 38, 8a, 9f, 0b, 87, dc, ce, f3, dd, 3c, 2c, 75, 69, 7b, f7, 7b, 3f, 6f, 46, b8, 9e, 4d, 84, 20, c7, 7a, 79, b2, df, 2a, 60, 89, c1, 5f, dc, 7e, 79, 5f, 98, 21, 9e, f9, 0d, bb, 1e, d3, 09, 1a, 1d, 25, 30, 9c, cb, 79, 59, 6a, 9f, 9a, 2b, a1, e0, 1e, ab, 7c, 23, fd, 4f, 00] +tcp_req bf22d4485240c7c65abeda64131c8ab3e7f5bb240cd340d25027fe7d51f8936bd8efad6b2d47304bdbb9ffd98f1b84ecafe91701ec906b0dd43398ee6beb556ade678fec468a801debd0030e25c011e12c67fa729d8d142233fc2ba49009436e500fa3e6189b610d31a9449298b7947d17f2654e9d2661899561163e0f613247b1d570940ca7a04736a4bd3c290c55526ebb977c9b97c64721e4641e351c923a8ff5e272bfd4ede9ebe272bf0cee21c0297aa72666ed3e821c71ea9d545da57eb7f32870e58ee813a71737ee3d7c9c7dc000000000000003a5f484 reject +tcp_req 400104c00000000000004ec18a0d4acf4b601fcd9b619f66414b030583d015efec2f2b54a1f064dad2aeac257760f558184d831a17537bc5833d3ef3ca4cf30640b1e4dee9400c609b8e6a0a5425148d105ad5287d03cd7e2557368000000ea4c30abcb75acc1d80b4ba0f7b88 ConnectTcpReq { flags: 1, address_type: Ipv6, host: [193, 138, 13, 74, 207, 75, 96, 31, 205, 155, 97, 159, 102, 65, 75, 3, 5, 131, 208, 21, 239, 236, 47, 43, 84, 161, 240, 100, 218, 210, 174, 172, 37, 119, 96, 245, 88, 24, 77, 131, 26, 23, 83, 123, 197, 131, 61, 62, 243, 202, 76, 243, 6, 64, 177, 228, 222, 233, 64, 12, 96, 155, 142, 106, 10, 84, 37, 20, 141, 16, 90, 213, 40, 125, 3, 205, 126, 37], port: 22326 };109;[01, 04, 40, 4e, c1, 8a, 0d, 4a, cf, 4b, 60, 1f, cd, 9b, 61, 9f, 66, 41, 4b, 03, 05, 83, d0, 15, ef, ec, 2f, 2b, 54, a1, f0, 64, da, d2, ae, ac, 25, 77, 60, f5, 58, 18, 4d, 83, 1a, 17, 53, 7b, c5, 83, 3d, 3e, f3, ca, 4c, f3, 06, 40, b1, e4, de, e9, 40, 0c, 60, 9b, 8e, 6a, 0a, 54, 25, 14, 8d, 10, 5a, d5, 28, 7d, 03, cd, 7e, 25, 57, 36, 00] +tcp_req c0003f274e93e4be034102072373c9f9350603fa20a0500e6828f8ebf488112bc160598dd4956c3f6562494eb82e020291ebafdfbbc8ad434f0f2ab9deb4f10e714f7f90fb0a0317628216cde760af4d8e18aa6154067e6b020ee873d9dc63312649a0020afc105ce3d7c33de1adcd337d6100b6c794c1416f8775c73d6b8ef89f29165162435c271e0e03be2f27c4a02e547ac9074af629e79d8b1ec003dd0c271ffacdfe4430081490f5843a5d6ad560c7fd564b5c94853941bad88c0866b1a789d29b902a2a3e3ab1792ea8898d19619912d7149558e1ac55ed2fdc6696fc6ada5cbdd9b84ad4ccd6027e33405f8330e0391e2db36271716c22ef246ecf3b3c661fd780a459afd2497f4dbbbd4680000000 reject +tcp_req 42c3014053bbba23e213ceb7adb42adccccc95d62e9d88f06417c4189f461dd16a136c47e8f070bfc2a37519cd391b1c5be765a20a039ab13f0ca774c05d39994d8d5ae0755d945448886378cb78477816dc1356745567926939c000000000000000 ConnectTcpReq { flags: 707, address_type: Ipv4, host: [187, 186, 35, 226, 19, 206, 183, 173, 180, 42, 220, 204, 204, 149, 214, 46, 157, 136, 240, 100, 23, 196, 24, 159, 70, 29, 209, 106, 19, 108, 71, 232, 240, 112, 191, 194, 163, 117, 25, 205, 57, 27, 28, 91, 231, 101, 162, 10, 3, 154, 177, 63, 12, 167, 116, 192, 93, 57, 153, 77, 141, 90, 224, 117, 93, 148, 84, 72, 136, 99, 120, 203, 120, 71, 120, 22, 220, 19, 86, 116, 85, 103, 146], port: 26937 };98;[42, c3, 01, 40, 53, bb, ba, 23, e2, 13, ce, b7, ad, b4, 2a, dc, cc, cc, 95, d6, 2e, 9d, 88, f0, 64, 17, c4, 18, 9f, 46, 1d, d1, 6a, 13, 6c, 47, e8, f0, 70, bf, c2, a3, 75, 19, cd, 39, 1b, 1c, 5b, e7, 65, a2, 0a, 03, 9a, b1, 3f, 0c, a7, 74, c0, 5d, 39, 99, 4d, 8d, 5a, e0, 75, 5d, 94, 54, 48, 88, 63, 78, cb, 78, 47, 78, 16, dc, 13, 56, 74, 55, 67, 92, 69, 39, 00] +tcp_req c00040315f9f15bc014079de69ecd0a1027cdf9474b48173c7edda14c7a4078ee3d5783cafd5895220d105a0bb2f2280e18a241695c555c9912a641d41adb439a8772ae9e1a50922c4889dd341299acc6e0cbc7fe55a88a68221152d60743fcb72cd3cbaa35c69fe10eb1b6cee1d3e16ea21be3d7d0402afb6713adf16b3f383f646b93acd2c4000ade5e3 ConnectTcpReq { flags: 70580801836476, address_type: Ipv4, host: [222, 105, 236, 208, 161, 2, 124, 223, 148, 116, 180, 129, 115, 199, 237, 218, 20, 199, 164, 7, 142, 227, 213, 120, 60, 175, 213, 137, 82, 32, 209, 5, 160, 187, 47, 34, 128, 225, 138, 36, 22, 149, 197, 85, 201, 145, 42, 100, 29, 65, 173, 180, 57, 168, 119, 42, 233, 225, 165, 9, 34, 196, 136, 157, 211, 65, 41, 154, 204, 110, 12, 188, 127, 229, 90, 136, 166, 130, 33, 21, 45, 96, 116, 63, 203, 114, 205, 60, 186, 163, 92, 105, 254, 16, 235, 27, 108, 238, 29, 62, 22, 234, 33, 190, 61, 125, 4, 2, 175, 182, 113, 58, 223, 22, 179, 243, 131, 246, 70, 185, 58], port: 52524 };136;[c0, 00, 40, 31, 5f, 9f, 15, bc, 01, 40, 79, de, 69, ec, d0, a1, 02, 7c, df, 94, 74, b4, 81, 73, c7, ed, da, 14, c7, a4, 07, 8e, e3, d5, 78, 3c, af, d5, 89, 52, 20, d1, 05, a0, bb, 2f, 22, 80, e1, 8a, 24, 16, 95, c5, 55, c9, 91, 2a, 64, 1d, 41, ad, b4, 39, a8, 77, 2a, e9, e1, a5, 09, 22, c4, 88, 9d, d3, 41, 29, 9a, cc, 6e, 0c, bc, 7f, e5, 5a, 88, a6, 82, 21, 15, 2d, 60, 74, 3f, cb, 72, cd, 3c, ba, a3, 5c, 69, fe, 10, eb, 1b, 6c, ee, 1d, 3e, 16, ea, 21, be, 3d, 7d, 04, 02, af, b6, 71, 3a, df, 16, b3, f3, 83, f6, 46, b9, 3a, cd, 2c, 00] +tcp_req 80000f5f0380000016c4aadc67ac671b1ebab35d9b590b6c61999c21456680c66d4000 ConnectTcpReq { flags: 3935, address_type: Domain, host: [196, 170, 220, 103, 172, 103, 27, 30, 186, 179, 93, 155, 89, 11, 108, 97, 153, 156, 33, 69, 102, 128], port: 50797 };35;[4f, 5f, 03, 16, c4, aa, dc, 67, ac, 67, 1b, 1e, ba, b3, 5d, 9b, 59, 0b, 6c, 61, 99, 9c, 21, 45, 66, 80, c6, 6d, 00] +tcp_req 40079e41025551817c40c61d6847a04e767a09308e4d95c7e88c856f5e95b1adb72acfcdd77b1a92395caac32e0fbc1bb8aa8724d39aa65bad37d37ba6ce4685a7c38fb1dc6d9dc94fe65f27c319550c7fa9956a4f6cdcf27e2c143851311dc94824d6c62dc36d490605856f7065aed7302119fe15ee3c9701d337e174591c774638b13feffd0e069f7623195c491d48a934f117460dca1c1e99e98c43a8a113ecdbf2bcde608d9437f3abf6235a8758e69d0f2b6ea5eba43e5abda1c50576fd61f1d1268c92aeb95ab903da7f9f3183327b9164fa7812331267bd51a5200d9e486cfdf31a2d079f60927af6a7d0a554e83baa1423842c15b020ee91b040773b4a8cfba743e2597f8d00 reject +tcp_req 8000e0fe03800000c3f293942312e9777703c3fabb96113cffcef6e8836e872969c8e7bcc40891d2746a0cdb05752a2cab37e976be10bcb87c732bfbda8bf86691a47d79f118f678301e7ccaf73392c79fad790bf875b265af706dade4d49b9234790724b4dd9b5f3e3090dfad493d1ae6da8127818f615b8341e639d895231f3993f8103b9587261153fdd3bcd2a8bb70d2fac74a08a4d7d7fadbc7e4c74ec75054e8699f9ea1083c4e247c02bb8007015f3b4cd46289d6cde5a7c241a309894a105d3ebd8309273d3aa900770fc000000000000000 ConnectTcpReq { flags: 57598, address_type: Domain, host: [242, 147, 148, 35, 18, 233, 119, 119, 3, 195, 250, 187, 150, 17, 60, 255, 206, 246, 232, 131, 110, 135, 41, 105, 200, 231, 188, 196, 8, 145, 210, 116, 106, 12, 219, 5, 117, 42, 44, 171, 55, 233, 118, 190, 16, 188, 184, 124, 115, 43, 251, 218, 139, 248, 102, 145, 164, 125, 121, 241, 24, 246, 120, 48, 30, 124, 202, 247, 51, 146, 199, 159, 173, 121, 11, 248, 117, 178, 101, 175, 112, 109, 173, 228, 212, 155, 146, 52, 121, 7, 36, 180, 221, 155, 95, 62, 48, 144, 223, 173, 73, 61, 26, 230, 218, 129, 39, 129, 143, 97, 91, 131, 65, 230, 57, 216, 149, 35, 31, 57, 147, 248, 16, 59, 149, 135, 38, 17, 83, 253, 211, 188, 210, 168, 187, 112, 210, 250, 199, 74, 8, 164, 215, 215, 250, 219, 199, 228, 199, 78, 199, 80, 84, 232, 105, 159, 158, 161, 8, 60, 78, 36, 124, 2, 187, 128, 7, 1, 95, 59, 76, 212, 98, 137, 214, 205, 229, 167, 194, 65, 163, 9, 137, 74, 16, 93, 62, 189, 131, 9, 39, 61, 58, 169, 0], port: 30479 };214;[80, 00, e0, fe, 03, 40, c3, f2, 93, 94, 23, 12, e9, 77, 77, 03, c3, fa, bb, 96, 11, 3c, ff, ce, f6, e8, 83, 6e, 87, 29, 69, c8, e7, bc, c4, 08, 91, d2, 74, 6a, 0c, db, 05, 75, 2a, 2c, ab, 37, e9, 76, be, 10, bc, b8, 7c, 73, 2b, fb, da, 8b, f8, 66, 91, a4, 7d, 79, f1, 18, f6, 78, 30, 1e, 7c, ca, f7, 33, 92, c7, 9f, ad, 79, 0b, f8, 75, b2, 65, af, 70, 6d, ad, e4, d4, 9b, 92, 34, 79, 07, 24, b4, dd, 9b, 5f, 3e, 30, 90, df, ad, 49, 3d, 1a, e6, da, 81, 27, 81, 8f, 61, 5b, 83, 41, e6, 39, d8, 95, 23, 1f, 39, 93, f8, 10, 3b, 95, 87, 26, 11, 53, fd, d3, bc, d2, a8, bb, 70, d2, fa, c7, 4a, 08, a4, d7, d7, fa, db, c7, e4, c7, 4e, c7, 50, 54, e8, 69, 9f, 9e, a1, 08, 3c, 4e, 24, 7c, 02, bb, 80, 07, 01, 5f, 3b, 4c, d4, 62, 89, d6, cd, e5, a7, c2, 41, a3, 09, 89, 4a, 10, 5d, 3e, bd, 83, 09, 27, 3d, 3a, a9, 00, 77, 0f, 00] +tcp_req 403b9f80000030bf8c59acd4dfe0ec213618714eabb69c76ae499975f8ae174a8a098b6da14c014446216f208c3af1030da63c63bed7d20df14000 reject +tcp_req 4e950440d661bd2f5dffa6f7ab52cbd96d7e1d1cd3604f581e387626043ec69ebf1c110e6611955637f8dd4490837f9350776d757343dcf7b65dc218156445213ead2623a229dde151c50613502285ab5f5f32908189dcb7f418fe6d9af2da494543e0907243761c60788595e2af5b855f25d698b4eeb73d3fb97adb78efa3b39736e09ca68b4d58497ff9ea29fd3d78b1e9334a037c27a54d82183c83a51039d9cd945ed3a5b180a13ebcec9a16af49feae63cfc05e55e5a464ae35fe85c3b756295696caaf205a2906b2c0c77494794e73d2aec67cb404f7e8cbafad005bfe6eaa6d9150 ConnectTcpReq { flags: 3733, address_type: Ipv6, host: [97, 189, 47, 93, 255, 166, 247, 171, 82, 203, 217, 109, 126, 29, 28, 211, 96, 79, 88, 30, 56, 118, 38, 4, 62, 198, 158, 191, 28, 17, 14, 102, 17, 149, 86, 55, 248, 221, 68, 144, 131, 127, 147, 80, 119, 109, 117, 115, 67, 220, 247, 182, 93, 194, 24, 21, 100, 69, 33, 62, 173, 38, 35, 162, 41, 221, 225, 81, 197, 6, 19, 80, 34, 133, 171, 95, 95, 50, 144, 129, 137, 220, 183, 244, 24, 254, 109, 154, 242, 218, 73, 69, 67, 224, 144, 114, 67, 118, 28, 96, 120, 133, 149, 226, 175, 91, 133, 95, 37, 214, 152, 180, 238, 183, 61, 63, 185, 122, 219, 120, 239, 163, 179, 151, 54, 224, 156, 166, 139, 77, 88, 73, 127, 249, 234, 41, 253, 61, 120, 177, 233, 51, 74, 3, 124, 39, 165, 77, 130, 24, 60, 131, 165, 16, 57, 217, 205, 148, 94, 211, 165, 177, 128, 161, 62, 188, 236, 154, 22, 175, 73, 254, 174, 99, 207, 192, 94, 85, 229, 164, 100, 174, 53, 254, 133, 195, 183, 86, 41, 86, 150, 202, 175, 32, 90, 41, 6, 178, 192, 199, 116, 148, 121, 78, 115, 210, 174, 198, 124, 180, 4, 247, 232, 203], port: 44973 };222;[4e, 95, 04, 40, d6, 61, bd, 2f, 5d, ff, a6, f7, ab, 52, cb, d9, 6d, 7e, 1d, 1c, d3, 60, 4f, 58, 1e, 38, 76, 26, 04, 3e, c6, 9e, bf, 1c, 11, 0e, 66, 11, 95, 56, 37, f8, dd, 44, 90, 83, 7f, 93, 50, 77, 6d, 75, 73, 43, dc, f7, b6, 5d, c2, 18, 15, 64, 45, 21, 3e, ad, 26, 23, a2, 29, dd, e1, 51, c5, 06, 13, 50, 22, 85, ab, 5f, 5f, 32, 90, 81, 89, dc, b7, f4, 18, fe, 6d, 9a, f2, da, 49, 45, 43, e0, 90, 72, 43, 76, 1c, 60, 78, 85, 95, e2, af, 5b, 85, 5f, 25, d6, 98, b4, ee, b7, 3d, 3f, b9, 7a, db, 78, ef, a3, b3, 97, 36, e0, 9c, a6, 8b, 4d, 58, 49, 7f, f9, ea, 29, fd, 3d, 78, b1, e9, 33, 4a, 03, 7c, 27, a5, 4d, 82, 18, 3c, 83, a5, 10, 39, d9, cd, 94, 5e, d3, a5, b1, 80, a1, 3e, bc, ec, 9a, 16, af, 49, fe, ae, 63, cf, c0, 5e, 55, e5, a4, 64, ae, 35, fe, 85, c3, b7, 56, 29, 56, 96, ca, af, 20, 5a, 29, 06, b2, c0, c7, 74, 94, 79, 4e, 73, d2, ae, c6, 7c, b4, 04, f7, e8, cb, af, ad, 00] +tcp_req 8054fe33048000001db5749b7e3f880982ac93a9a10f6ec0e7b644057c7f5859ee9d528388c3df0080000000 ConnectTcpReq { flags: 5570099, address_type: Ipv6, host: [181, 116, 155, 126, 63, 136, 9, 130, 172, 147, 169, 161, 15, 110, 192, 231, 182, 68, 5, 124, 127, 88, 89, 238, 157, 82, 131, 136, 195], port: 57088 };44;[80, 54, fe, 33, 04, 1d, b5, 74, 9b, 7e, 3f, 88, 09, 82, ac, 93, a9, a1, 0f, 6e, c0, e7, b6, 44, 05, 7c, 7f, 58, 59, ee, 9d, 52, 83, 88, c3, df, 00, 00] +tcp_req c24051d25851fbd5014052d3f00ec720c1eb1a85b02c22f2165fc827affe798e7a46604f01c24b329a27b73ec02c4eb8113a80e0863ad0aa353fced0cf996db8611eda40ea1b8579d31ad1434ec86152a6851313ef6b20c5abc38ba84784f480000000ad ConnectTcpReq { flags: 162219550452087765, address_type: Ipv4, host: [211, 240, 14, 199, 32, 193, 235, 26, 133, 176, 44, 34, 242, 22, 95, 200, 39, 175, 254, 121, 142, 122, 70, 96, 79, 1, 194, 75, 50, 154, 39, 183, 62, 192, 44, 78, 184, 17, 58, 128, 224, 134, 58, 208, 170, 53, 63, 206, 208, 207, 153, 109, 184, 97, 30, 218, 64, 234, 27, 133, 121, 211, 26, 209, 67, 78, 200, 97, 82, 166, 133, 19, 19, 239, 107, 32, 197, 171, 195, 139, 168, 71], port: 34036 };99;[c2, 40, 51, d2, 58, 51, fb, d5, 01, 40, 52, d3, f0, 0e, c7, 20, c1, eb, 1a, 85, b0, 2c, 22, f2, 16, 5f, c8, 27, af, fe, 79, 8e, 7a, 46, 60, 4f, 01, c2, 4b, 32, 9a, 27, b7, 3e, c0, 2c, 4e, b8, 11, 3a, 80, e0, 86, 3a, d0, aa, 35, 3f, ce, d0, cf, 99, 6d, b8, 61, 1e, da, 40, ea, 1b, 85, 79, d3, 1a, d1, 43, 4e, c8, 61, 52, a6, 85, 13, 13, ef, 6b, 20, c5, ab, c3, 8b, a8, 47, 84, f4, 00] +tcp_req 0903800001020384929a13b5c8a6c4707226f7ffd2f14140af4b0c443d757fbf33fa50f3b2c1d13b44fc063dab0c9f517f8ec854cfe7c6b8f0e55dc58834d8ec9048bc7ea20ef28dd8e5aa0cb5055577261858a21beaa04e1828dfc8f757de203f333da2241176b1ea160668dc9fa9aa47ffc3fd20943f2a92849eabd10ba7b7e37a7eabd36bc63716d5b1c61fde87efd4d9aaa5e68c3932895310c58c56db261f94e1377b3e09487d66d8b2c121b03dccddf7a7b56ceba60f30596564c68d1e7436d1652016d2fb3c321bd600d86ead0e69365a8b1ea93773caa6995b89f2b7314887492f2a67f86c1f16c51d1ae4da81b65b0ddb07f559289c832268d3a00fa4dfd9afbdfd54f79fdb400530c624ed4517 reject +tcp_req c03d6cd5f24bc9d80140c3dc7a80dd024e1d940ccd7c250967320c6abbb7c6dbe983e20a14938c06684bf2114a30f0450900d9207b133bb2f4fcfa8c1142a1a965ab8f8839745e22cdc1506953cca24272bc1f25a9ef66563f8b8884d748711735bd818a284e66bb66b7770f76debbe7ee36e5139bbb28eb575972c12e3a9aa6cb0ca865da7cbcff3b84d8447f46c0a85f05ae4b67f2e167a8f9b1154d47e8065211cfcea8381fc41a359ea38d69bc90472c5febd6cda5e453e90dbd6c0a574810f41806038a3555e95670e579cbde8780000000 ConnectTcpReq { flags: 17289639728237016, address_type: Ipv4, host: [220, 122, 128, 221, 2, 78, 29, 148, 12, 205, 124, 37, 9, 103, 50, 12, 106, 187, 183, 198, 219, 233, 131, 226, 10, 20, 147, 140, 6, 104, 75, 242, 17, 74, 48, 240, 69, 9, 0, 217, 32, 123, 19, 59, 178, 244, 252, 250, 140, 17, 66, 161, 169, 101, 171, 143, 136, 57, 116, 94, 34, 205, 193, 80, 105, 83, 204, 162, 66, 114, 188, 31, 37, 169, 239, 102, 86, 63, 139, 136, 132, 215, 72, 113, 23, 53, 189, 129, 138, 40, 78, 102, 187, 102, 183, 119, 15, 118, 222, 187, 231, 238, 54, 229, 19, 155, 187, 40, 235, 87, 89, 114, 193, 46, 58, 154, 166, 203, 12, 168, 101, 218, 124, 188, 255, 59, 132, 216, 68, 127, 70, 192, 168, 95, 5, 174, 75, 103, 242, 225, 103, 168, 249, 177, 21, 77, 71, 232, 6, 82, 17, 207, 206, 168, 56, 31, 196, 26, 53, 158, 163, 141, 105, 188, 144, 71, 44, 95, 235, 214, 205, 165, 228, 83, 233, 13, 189, 108, 10, 87, 72, 16, 244, 24, 6, 3, 138, 53, 85, 233, 86, 112, 229, 121, 203], port: 56967 };212;[c0, 3d, 6c, d5, f2, 4b, c9, d8, 01, 40, c3, dc, 7a, 80, dd, 02, 4e, 1d, 94, 0c, cd, 7c, 25, 09, 67, 32, 0c, 6a, bb, b7, c6, db, e9, 83, e2, 0a, 14, 93, 8c, 06, 68, 4b, f2, 11, 4a, 30, f0, 45, 09, 00, d9, 20, 7b, 13, 3b, b2, f4, fc, fa, 8c, 11, 42, a1, a9, 65, ab, 8f, 88, 39, 74, 5e, 22, cd, c1, 50, 69, 53, cc, a2, 42, 72, bc, 1f, 25, a9, ef, 66, 56, 3f, 8b, 88, 84, d7, 48, 71, 17, 35, bd, 81, 8a, 28, 4e, 66, bb, 66, b7, 77, 0f, 76, de, bb, e7, ee, 36, e5, 13, 9b, bb, 28, eb, 57, 59, 72, c1, 2e, 3a, 9a, a6, cb, 0c, a8, 65, da, 7c, bc, ff, 3b, 84, d8, 44, 7f, 46, c0, a8, 5f, 05, ae, 4b, 67, f2, e1, 67, a8, f9, b1, 15, 4d, 47, e8, 06, 52, 11, cf, ce, a8, 38, 1f, c4, 1a, 35, 9e, a3, 8d, 69, bc, 90, 47, 2c, 5f, eb, d6, cd, a5, e4, 53, e9, 0d, bd, 6c, 0a, 57, 48, 10, f4, 18, 06, 03, 8a, 35, 55, e9, 56, 70, e5, 79, cb, de, 87, 00] +tcp_req 80002cb60340a9996105c31023add1916bcd3176ca4760ca4863f520514ba001ee81ae0d8e6bb21f00a3ba17abc259feb97f5277af87fe7cb8099e7ef4510ffdf26345e0fc23b855e8cb5a508613f06395a6c915d07b5f3b2658aab24979a807da78ca0556e3a93a051f9380683167072054d668dd54356364ab45ff0e2e6e908b038fc864bf8cb399eebdd35e68f4884bb53534ea7b2b9d9d53e28ba377b517d525a5c0f3831e2a92402afaab47dd2fe3e44000 ConnectTcpReq { flags: 11446, address_type: Domain, host: [153, 97, 5, 195, 16, 35, 173, 209, 145, 107, 205, 49, 118, 202, 71, 96, 202, 72, 99, 245, 32, 81, 75, 160, 1, 238, 129, 174, 13, 142, 107, 178, 31, 0, 163, 186, 23, 171, 194, 89, 254, 185, 127, 82, 119, 175, 135, 254, 124, 184, 9, 158, 126, 244, 81, 15, 253, 242, 99, 69, 224, 252, 35, 184, 85, 232, 203, 90, 80, 134, 19, 240, 99, 149, 166, 201, 21, 208, 123, 95, 59, 38, 88, 170, 178, 73, 121, 168, 7, 218, 120, 202, 5, 86, 227, 169, 58, 5, 31, 147, 128, 104, 49, 103, 7, 32, 84, 214, 104, 221, 84, 53, 99, 100, 171, 69, 255, 14, 46, 110, 144, 139, 3, 143, 200, 100, 191, 140, 179, 153, 238, 189, 211, 94, 104, 244, 136, 75, 181, 53, 52, 234, 123, 43, 157, 157, 83, 226, 139, 163, 119, 181, 23, 213, 37, 165, 192, 243, 131, 30, 42, 146, 64, 42, 250, 171, 71, 221, 47], port: 58340 };180;[6c, b6, 03, 40, a9, 99, 61, 05, c3, 10, 23, ad, d1, 91, 6b, cd, 31, 76, ca, 47, 60, ca, 48, 63, f5, 20, 51, 4b, a0, 01, ee, 81, ae, 0d, 8e, 6b, b2, 1f, 00, a3, ba, 17, ab, c2, 59, fe, b9, 7f, 52, 77, af, 87, fe, 7c, b8, 09, 9e, 7e, f4, 51, 0f, fd, f2, 63, 45, e0, fc, 23, b8, 55, e8, cb, 5a, 50, 86, 13, f0, 63, 95, a6, c9, 15, d0, 7b, 5f, 3b, 26, 58, aa, b2, 49, 79, a8, 07, da, 78, ca, 05, 56, e3, a9, 3a, 05, 1f, 93, 80, 68, 31, 67, 07, 20, 54, d6, 68, dd, 54, 35, 63, 64, ab, 45, ff, 0e, 2e, 6e, 90, 8b, 03, 8f, c8, 64, bf, 8c, b3, 99, ee, bd, d3, 5e, 68, f4, 88, 4b, b5, 35, 34, ea, 7b, 2b, 9d, 9d, 53, e2, 8b, a3, 77, b5, 17, d5, 25, a5, c0, f3, 83, 1e, 2a, 92, 40, 2a, fa, ab, 47, dd, 2f, e3, e4, 00] +tcp_req 5f170440e0414b79bb22ee8e95709bf194e002d4e9d7678aa6c0fd94a6bc54c7912dcad8c36ec5834a0525b62b31f0092884d8cee97509a23528f905ec4fbfddbbdbefa2088ae77b6dd15daf38faa02aae3816d0193c319682fcda109dcdf2eccaefdef663871ac33bb52e13575cbbcf44a72f6c3bed0b541e3ad3fb70597b9505fb461b1e7ea2fdbd1e4ab43534790f34229621d810b8dee350cb4fcee6ee42608958f8f858c95602a301fb4c6a3cb62292db2f4a824b485b78261d503a9ae8fb79c754580aab5a6b37fd47b8267f45997ed226b64bd4c61ef4e4de5e649e3bbc9ac4a2c442f280000000 ConnectTcpReq { flags: 7959, address_type: Ipv6, host: [65, 75, 121, 187, 34, 238, 142, 149, 112, 155, 241, 148, 224, 2, 212, 233, 215, 103, 138, 166, 192, 253, 148, 166, 188, 84, 199, 145, 45, 202, 216, 195, 110, 197, 131, 74, 5, 37, 182, 43, 49, 240, 9, 40, 132, 216, 206, 233, 117, 9, 162, 53, 40, 249, 5, 236, 79, 191, 221, 187, 219, 239, 162, 8, 138, 231, 123, 109, 209, 93, 175, 56, 250, 160, 42, 174, 56, 22, 208, 25, 60, 49, 150, 130, 252, 218, 16, 157, 205, 242, 236, 202, 239, 222, 246, 99, 135, 26, 195, 59, 181, 46, 19, 87, 92, 187, 207, 68, 167, 47, 108, 59, 237, 11, 84, 30, 58, 211, 251, 112, 89, 123, 149, 5, 251, 70, 27, 30, 126, 162, 253, 189, 30, 74, 180, 53, 52, 121, 15, 52, 34, 150, 33, 216, 16, 184, 222, 227, 80, 203, 79, 206, 230, 238, 66, 96, 137, 88, 248, 248, 88, 201, 86, 2, 163, 1, 251, 76, 106, 60, 182, 34, 146, 219, 47, 74, 130, 75, 72, 91, 120, 38, 29, 80, 58, 154, 232, 251, 121, 199, 84, 88, 10, 171, 90, 107, 55, 253, 71, 184, 38, 127, 69, 153, 126, 210, 38, 182, 75, 212, 198, 30, 244, 228, 222, 94, 100, 158, 59, 188, 154, 196, 162, 196], port: 17138 };235;[5f, 17, 04, 40, e0, 41, 4b, 79, bb, 22, ee, 8e, 95, 70, 9b, f1, 94, e0, 02, d4, e9, d7, 67, 8a, a6, c0, fd, 94, a6, bc, 54, c7, 91, 2d, ca, d8, c3, 6e, c5, 83, 4a, 05, 25, b6, 2b, 31, f0, 09, 28, 84, d8, ce, e9, 75, 09, a2, 35, 28, f9, 05, ec, 4f, bf, dd, bb, db, ef, a2, 08, 8a, e7, 7b, 6d, d1, 5d, af, 38, fa, a0, 2a, ae, 38, 16, d0, 19, 3c, 31, 96, 82, fc, da, 10, 9d, cd, f2, ec, ca, ef, de, f6, 63, 87, 1a, c3, 3b, b5, 2e, 13, 57, 5c, bb, cf, 44, a7, 2f, 6c, 3b, ed, 0b, 54, 1e, 3a, d3, fb, 70, 59, 7b, 95, 05, fb, 46, 1b, 1e, 7e, a2, fd, bd, 1e, 4a, b4, 35, 34, 79, 0f, 34, 22, 96, 21, d8, 10, b8, de, e3, 50, cb, 4f, ce, e6, ee, 42, 60, 89, 58, f8, f8, 58, c9, 56, 02, a3, 01, fb, 4c, 6a, 3c, b6, 22, 92, db, 2f, 4a, 82, 4b, 48, 5b, 78, 26, 1d, 50, 3a, 9a, e8, fb, 79, c7, 54, 58, 0a, ab, 5a, 6b, 37, fd, 47, b8, 26, 7f, 45, 99, 7e, d2, 26, b6, 4b, d4, c6, 1e, f4, e4, de, 5e, 64, 9e, 3b, bc, 9a, c4, a2, c4, 42, f2, 00] +tcp_req 8000bdee04c00000000000008a175ba9bc9500005c0bae539a99ba7acf5c04693fdf2cba48638abc785632d8ff279803c87b59b2d292790a93175f33fe49d90419ef5b1b805536ef039461644674ad67283100f124eb4473deaac5dd6d3497694dc33154f7159c2e603f55187da79a51553af929ed9e22990998866f77c5d324f2da561a0e64807ae00c2f570ad22ce910913cd49721aa7b7b4000 ConnectTcpReq { flags: 48622, address_type: Ipv6, host: [23, 91, 169, 188, 149, 0, 0, 92, 11, 174, 83, 154, 153, 186, 122, 207, 92, 4, 105, 63, 223, 44, 186, 72, 99, 138, 188, 120, 86, 50, 216, 255, 39, 152, 3, 200, 123, 89, 178, 210, 146, 121, 10, 147, 23, 95, 51, 254, 73, 217, 4, 25, 239, 91, 27, 128, 85, 54, 239, 3, 148, 97, 100, 70, 116, 173, 103, 40, 49, 0, 241, 36, 235, 68, 115, 222, 170, 197, 221, 109, 52, 151, 105, 77, 195, 49, 84, 247, 21, 156, 46, 96, 63, 85, 24, 125, 167, 154, 81, 85, 58, 249, 41, 237, 158, 34, 153, 9, 152, 134, 111, 119, 197, 211, 36, 242, 218, 86, 26, 14, 100, 128, 122, 224, 12, 47, 87, 10, 210, 44, 233, 16, 145, 60, 212, 151, 33, 170], port: 31611 };155;[80, 00, bd, ee, 04, 40, 8a, 17, 5b, a9, bc, 95, 00, 00, 5c, 0b, ae, 53, 9a, 99, ba, 7a, cf, 5c, 04, 69, 3f, df, 2c, ba, 48, 63, 8a, bc, 78, 56, 32, d8, ff, 27, 98, 03, c8, 7b, 59, b2, d2, 92, 79, 0a, 93, 17, 5f, 33, fe, 49, d9, 04, 19, ef, 5b, 1b, 80, 55, 36, ef, 03, 94, 61, 64, 46, 74, ad, 67, 28, 31, 00, f1, 24, eb, 44, 73, de, aa, c5, dd, 6d, 34, 97, 69, 4d, c3, 31, 54, f7, 15, 9c, 2e, 60, 3f, 55, 18, 7d, a7, 9a, 51, 55, 3a, f9, 29, ed, 9e, 22, 99, 09, 98, 86, 6f, 77, c5, d3, 24, f2, da, 56, 1a, 0e, 64, 80, 7a, e0, 0c, 2f, 57, 0a, d2, 2c, e9, 10, 91, 3c, d4, 97, 21, aa, 7b, 7b, 00] +tcp_req 8002d6b4048000005215caf62b9b2697aad555ce6fb84c1aabcf537cf75497ab55de2205445bb8986eddd9cdad3abb8075c9e7164f24af606ff8dcb0c7190a9bbbccd222a733a90c8576d18c0afdb2640dea97cc2c0ded34c823f54010c000000000000000 ConnectTcpReq { flags: 186036, address_type: Ipv6, host: [21, 202, 246, 43, 155, 38, 151, 170, 213, 85, 206, 111, 184, 76, 26, 171, 207, 83, 124, 247, 84, 151, 171, 85, 222, 34, 5, 68, 91, 184, 152, 110, 221, 217, 205, 173, 58, 187, 128, 117, 201, 231, 22, 79, 36, 175, 96, 111, 248, 220, 176, 199, 25, 10, 155, 187, 204, 210, 34, 167, 51, 169, 12, 133, 118, 209, 140, 10, 253, 178, 100, 13, 234, 151, 204, 44, 13, 237, 52, 200, 35, 245], port: 16400 };101;[80, 02, d6, b4, 04, 40, 52, 15, ca, f6, 2b, 9b, 26, 97, aa, d5, 55, ce, 6f, b8, 4c, 1a, ab, cf, 53, 7c, f7, 54, 97, ab, 55, de, 22, 05, 44, 5b, b8, 98, 6e, dd, d9, cd, ad, 3a, bb, 80, 75, c9, e7, 16, 4f, 24, af, 60, 6f, f8, dc, b0, c7, 19, 0a, 9b, bb, cc, d2, 22, a7, 33, a9, 0c, 85, 76, d1, 8c, 0a, fd, b2, 64, 0d, ea, 97, cc, 2c, 0d, ed, 34, c8, 23, f5, 40, 10, 00] +tcp_req c00000000000043804800000076dfa193a64b399a2c3c000000000000000 ConnectTcpReq { flags: 1080, address_type: Ipv6, host: [109, 250, 25, 58, 100, 179, 153], port: 41667 };30;[44, 38, 04, 07, 6d, fa, 19, 3a, 64, b3, 99, a2, c3, 00] +tcp_req 8000003404c0000000000000d6dcca2bc853e420c4ad120da9317119edeb9870140f47411bf6d556eaf2b1df13397ff7defdaceab34f9cd1db212f15e7c5dcc6d36e2070d7878ff423778ac4a03ef471f8b50d9501aad9301c1e35117d1dedff21ad492e05dde9e4f802e16eec251fc2c21165643d692aec2e592c55327d4acb8b560949c0e75722ce7760bf4e73e1cf82e3a40c60c4245438f70d1dacc1dec1bf621a7cbfc76ef2104e68e8194dad6821e2f62ba60e24c3825f2e28fa061a32ef19b3690bfd0814e0dec1022d58269a5abaa61ad4635676617b370e127eea9b7bd11b6baf00 ConnectTcpReq { flags: 52, address_type: Ipv6, host: [220, 202, 43, 200, 83, 228, 32, 196, 173, 18, 13, 169, 49, 113, 25, 237, 235, 152, 112, 20, 15, 71, 65, 27, 246, 213, 86, 234, 242, 177, 223, 19, 57, 127, 247, 222, 253, 172, 234, 179, 79, 156, 209, 219, 33, 47, 21, 231, 197, 220, 198, 211, 110, 32, 112, 215, 135, 143, 244, 35, 119, 138, 196, 160, 62, 244, 113, 248, 181, 13, 149, 1, 170, 217, 48, 28, 30, 53, 17, 125, 29, 237, 255, 33, 173, 73, 46, 5, 221, 233, 228, 248, 2, 225, 110, 236, 37, 31, 194, 194, 17, 101, 100, 61, 105, 42, 236, 46, 89, 44, 85, 50, 125, 74, 203, 139, 86, 9, 73, 192, 231, 87, 34, 206, 119, 96, 191, 78, 115, 225, 207, 130, 227, 164, 12, 96, 196, 36, 84, 56, 247, 13, 29, 172, 193, 222, 193, 191, 98, 26, 124, 191, 199, 110, 242, 16, 78, 104, 232, 25, 77, 173, 104, 33, 226, 246, 43, 166, 14, 36, 195, 130, 95, 46, 40, 250, 6, 26, 50, 239, 25, 179, 105, 11, 253, 8, 20, 224, 222, 193, 2, 45, 88, 38, 154, 90, 186, 166, 26, 212, 99, 86, 118, 97, 123, 55, 14, 18, 126, 234, 155, 123, 209, 27], port: 27567 };230;[34, 04, 40, d6, dc, ca, 2b, c8, 53, e4, 20, c4, ad, 12, 0d, a9, 31, 71, 19, ed, eb, 98, 70, 14, 0f, 47, 41, 1b, f6, d5, 56, ea, f2, b1, df, 13, 39, 7f, f7, de, fd, ac, ea, b3, 4f, 9c, d1, db, 21, 2f, 15, e7, c5, dc, c6, d3, 6e, 20, 70, d7, 87, 8f, f4, 23, 77, 8a, c4, a0, 3e, f4, 71, f8, b5, 0d, 95, 01, aa, d9, 30, 1c, 1e, 35, 11, 7d, 1d, ed, ff, 21, ad, 49, 2e, 05, dd, e9, e4, f8, 02, e1, 6e, ec, 25, 1f, c2, c2, 11, 65, 64, 3d, 69, 2a, ec, 2e, 59, 2c, 55, 32, 7d, 4a, cb, 8b, 56, 09, 49, c0, e7, 57, 22, ce, 77, 60, bf, 4e, 73, e1, cf, 82, e3, a4, 0c, 60, c4, 24, 54, 38, f7, 0d, 1d, ac, c1, de, c1, bf, 62, 1a, 7c, bf, c7, 6e, f2, 10, 4e, 68, e8, 19, 4d, ad, 68, 21, e2, f6, 2b, a6, 0e, 24, c3, 82, 5f, 2e, 28, fa, 06, 1a, 32, ef, 19, b3, 69, 0b, fd, 08, 14, e0, de, c1, 02, 2d, 58, 26, 9a, 5a, ba, a6, 1a, d4, 63, 56, 76, 61, 7b, 37, 0e, 12, 7e, ea, 9b, 7b, d1, 1b, 6b, af, 00] +tcp_req 50bb04404abc1015027e08f81d7e920d99599bfc425c4b20a6ea289c07d770d60582c8949001874edab608a455f8e4144069bc79985e197fb403d5a434cb9bbe0ea89eda27b187924c4fcbeb1d53ce812880000000 ConnectTcpReq { flags: 4283, address_type: Ipv6, host: [188, 16, 21, 2, 126, 8, 248, 29, 126, 146, 13, 153, 89, 155, 252, 66, 92, 75, 32, 166, 234, 40, 156, 7, 215, 112, 214, 5, 130, 200, 148, 144, 1, 135, 78, 218, 182, 8, 164, 85, 248, 228, 20, 64, 105, 188, 121, 152, 94, 25, 127, 180, 3, 213, 164, 52, 203, 155, 190, 14, 168, 158, 218, 39, 177, 135, 146, 76, 79, 203, 235, 29, 83, 206], port: 33064 };85;[50, bb, 04, 40, 4a, bc, 10, 15, 02, 7e, 08, f8, 1d, 7e, 92, 0d, 99, 59, 9b, fc, 42, 5c, 4b, 20, a6, ea, 28, 9c, 07, d7, 70, d6, 05, 82, c8, 94, 90, 01, 87, 4e, da, b6, 08, a4, 55, f8, e4, 14, 40, 69, bc, 79, 98, 5e, 19, 7f, b4, 03, d5, a4, 34, cb, 9b, be, 0e, a8, 9e, da, 27, b1, 87, 92, 4c, 4f, cb, eb, 1d, 53, ce, 81, 28, 00] +tcp_req c0286f61dc2e13080340c177342736ca7e0573982346792ce22e7f55d87952d9581b85eefad77b0bfa9fd946e9a46101b3d7dcbccacb60f8fa352a6a2d081abc5f717dd12c7f3060b44021231b8ebd3ac30734b426537bd78bc355eded2196d3bf011bdd05c70b7b420f8b2ced0ea18fdf8720d77dd9e196402b019729d4df270f3b9c6231de8c542d92da8d2eb191ea683197c91a170b73439370ba0b38a868d19841dc8e937cb059a98648ed4cfdd12cf3dbeb1253009f2545bf8cdd754e65d2d2afcb74de1cc7f03f03a3679a00 ConnectTcpReq { flags: 11381465164944136, address_type: Domain, host: [119, 52, 39, 54, 202, 126, 5, 115, 152, 35, 70, 121, 44, 226, 46, 127, 85, 216, 121, 82, 217, 88, 27, 133, 238, 250, 215, 123, 11, 250, 159, 217, 70, 233, 164, 97, 1, 179, 215, 220, 188, 202, 203, 96, 248, 250, 53, 42, 106, 45, 8, 26, 188, 95, 113, 125, 209, 44, 127, 48, 96, 180, 64, 33, 35, 27, 142, 189, 58, 195, 7, 52, 180, 38, 83, 123, 215, 139, 195, 85, 237, 237, 33, 150, 211, 191, 1, 27, 221, 5, 199, 11, 123, 66, 15, 139, 44, 237, 14, 161, 143, 223, 135, 32, 215, 125, 217, 225, 150, 64, 43, 1, 151, 41, 212, 223, 39, 15, 59, 156, 98, 49, 222, 140, 84, 45, 146, 218, 141, 46, 177, 145, 234, 104, 49, 151, 201, 26, 23, 11, 115, 67, 147, 112, 186, 11, 56, 168, 104, 209, 152, 65, 220, 142, 147, 124, 176, 89, 169, 134, 72, 237, 76, 253, 209, 44, 243, 219, 235, 18, 83, 0, 159, 37, 69, 191, 140, 221, 117, 78, 101, 210, 210, 175, 203, 116, 222, 28, 199, 240, 63, 3, 163], port: 26522 };207;[c0, 28, 6f, 61, dc, 2e, 13, 08, 03, 40, c1, 77, 34, 27, 36, ca, 7e, 05, 73, 98, 23, 46, 79, 2c, e2, 2e, 7f, 55, d8, 79, 52, d9, 58, 1b, 85, ee, fa, d7, 7b, 0b, fa, 9f, d9, 46, e9, a4, 61, 01, b3, d7, dc, bc, ca, cb, 60, f8, fa, 35, 2a, 6a, 2d, 08, 1a, bc, 5f, 71, 7d, d1, 2c, 7f, 30, 60, b4, 40, 21, 23, 1b, 8e, bd, 3a, c3, 07, 34, b4, 26, 53, 7b, d7, 8b, c3, 55, ed, ed, 21, 96, d3, bf, 01, 1b, dd, 05, c7, 0b, 7b, 42, 0f, 8b, 2c, ed, 0e, a1, 8f, df, 87, 20, d7, 7d, d9, e1, 96, 40, 2b, 01, 97, 29, d4, df, 27, 0f, 3b, 9c, 62, 31, de, 8c, 54, 2d, 92, da, 8d, 2e, b1, 91, ea, 68, 31, 97, c9, 1a, 17, 0b, 73, 43, 93, 70, ba, 0b, 38, a8, 68, d1, 98, 41, dc, 8e, 93, 7c, b0, 59, a9, 86, 48, ed, 4c, fd, d1, 2c, f3, db, eb, 12, 53, 00, 9f, 25, 45, bf, 8c, dd, 75, 4e, 65, d2, d2, af, cb, 74, de, 1c, c7, f0, 3f, 03, a3, 67, 9a, 00] +tcp_req 804275a30440f73bebaeab687b201c22a0a5bf75db58f33dbe72d0360fcd0b54684126e40186b3fe8e10bb587443a6a255410d471a146b016c8bf3665322123996a29dc6a4afa8dcc09f8e9118a40d6ddc45ead0f3fc98375dbb9bbe1a8595222fc555e30dd9a4ab66f4cd23f79e488418afe4400a8ac2234c848775b42e30aa0b1c1c2ebf185fd53c37bacfadccd22586bda092434dcff9e857a49cd8f211f8f2532f6f0945aff7263254418cc49db357abdcb0ccaf2c7c91ba246168c898214506fb39fd11e8074497da3a46783796810a13ac551ef430b7e20df62cde129bd251cc8b439981fa3ff9b7e271747d80e578025ae07d4961940aaf1094c0b07c00 ConnectTcpReq { flags: 4355491, address_type: Ipv6, host: [59, 235, 174, 171, 104, 123, 32, 28, 34, 160, 165, 191, 117, 219, 88, 243, 61, 190, 114, 208, 54, 15, 205, 11, 84, 104, 65, 38, 228, 1, 134, 179, 254, 142, 16, 187, 88, 116, 67, 166, 162, 85, 65, 13, 71, 26, 20, 107, 1, 108, 139, 243, 102, 83, 34, 18, 57, 150, 162, 157, 198, 164, 175, 168, 220, 192, 159, 142, 145, 24, 164, 13, 109, 220, 69, 234, 208, 243, 252, 152, 55, 93, 187, 155, 190, 26, 133, 149, 34, 47, 197, 85, 227, 13, 217, 164, 171, 102, 244, 205, 35, 247, 158, 72, 132, 24, 175, 228, 64, 10, 138, 194, 35, 76, 132, 135, 117, 180, 46, 48, 170, 11, 28, 28, 46, 191, 24, 95, 213, 60, 55, 186, 207, 173, 204, 210, 37, 134, 189, 160, 146, 67, 77, 207, 249, 232, 87, 164, 156, 216, 242, 17, 248, 242, 83, 47, 111, 9, 69, 175, 247, 38, 50, 84, 65, 140, 196, 157, 179, 87, 171, 220, 176, 204, 175, 44, 124, 145, 186, 36, 97, 104, 200, 152, 33, 69, 6, 251, 57, 253, 17, 232, 7, 68, 151, 218, 58, 70, 120, 55, 150, 129, 10, 19, 172, 85, 30, 244, 48, 183, 226, 13, 246, 44, 222, 18, 155, 210, 81, 204, 139, 67, 153, 129, 250, 63, 249, 183, 226, 113, 116, 125, 128, 229, 120, 2, 90, 224, 125, 73, 97, 148, 10, 175, 16, 148, 192], port: 45180 };257;[80, 42, 75, a3, 04, 40, f7, 3b, eb, ae, ab, 68, 7b, 20, 1c, 22, a0, a5, bf, 75, db, 58, f3, 3d, be, 72, d0, 36, 0f, cd, 0b, 54, 68, 41, 26, e4, 01, 86, b3, fe, 8e, 10, bb, 58, 74, 43, a6, a2, 55, 41, 0d, 47, 1a, 14, 6b, 01, 6c, 8b, f3, 66, 53, 22, 12, 39, 96, a2, 9d, c6, a4, af, a8, dc, c0, 9f, 8e, 91, 18, a4, 0d, 6d, dc, 45, ea, d0, f3, fc, 98, 37, 5d, bb, 9b, be, 1a, 85, 95, 22, 2f, c5, 55, e3, 0d, d9, a4, ab, 66, f4, cd, 23, f7, 9e, 48, 84, 18, af, e4, 40, 0a, 8a, c2, 23, 4c, 84, 87, 75, b4, 2e, 30, aa, 0b, 1c, 1c, 2e, bf, 18, 5f, d5, 3c, 37, ba, cf, ad, cc, d2, 25, 86, bd, a0, 92, 43, 4d, cf, f9, e8, 57, a4, 9c, d8, f2, 11, f8, f2, 53, 2f, 6f, 09, 45, af, f7, 26, 32, 54, 41, 8c, c4, 9d, b3, 57, ab, dc, b0, cc, af, 2c, 7c, 91, ba, 24, 61, 68, c8, 98, 21, 45, 06, fb, 39, fd, 11, e8, 07, 44, 97, da, 3a, 46, 78, 37, 96, 81, 0a, 13, ac, 55, 1e, f4, 30, b7, e2, 0d, f6, 2c, de, 12, 9b, d2, 51, cc, 8b, 43, 99, 81, fa, 3f, f9, b7, e2, 71, 74, 7d, 80, e5, 78, 02, 5a, e0, 7d, 49, 61, 94, 0a, af, 10, 94, c0, b0, 7c, 00] +tcp_req c00000388bbceb70044060009b86035ddaa509fd3d129abb333dc910ffd120a6e41df043b9780deaa95f9442e03bf989f46a5ac81ef1279d6efafae6aacbe91703c9ed25d8dec184ddb0164d2cd37f5f3c0ee9816a0471c41b771e04ba71ace06d48911d0c945fd28c8dea6f4f00 ConnectTcpReq { flags: 242862582640, address_type: Ipv6, host: [0, 155, 134, 3, 93, 218, 165, 9, 253, 61, 18, 154, 187, 51, 61, 201, 16, 255, 209, 32, 166, 228, 29, 240, 67, 185, 120, 13, 234, 169, 95, 148, 66, 224, 59, 249, 137, 244, 106, 90, 200, 30, 241, 39, 157, 110, 250, 250, 230, 170, 203, 233, 23, 3, 201, 237, 37, 216, 222, 193, 132, 221, 176, 22, 77, 44, 211, 127, 95, 60, 14, 233, 129, 106, 4, 113, 196, 27, 119, 30, 4, 186, 113, 172, 224, 109, 72, 145, 29, 12, 148, 95, 210, 140, 141, 234], port: 28495 };110;[c0, 00, 00, 38, 8b, bc, eb, 70, 04, 40, 60, 00, 9b, 86, 03, 5d, da, a5, 09, fd, 3d, 12, 9a, bb, 33, 3d, c9, 10, ff, d1, 20, a6, e4, 1d, f0, 43, b9, 78, 0d, ea, a9, 5f, 94, 42, e0, 3b, f9, 89, f4, 6a, 5a, c8, 1e, f1, 27, 9d, 6e, fa, fa, e6, aa, cb, e9, 17, 03, c9, ed, 25, d8, de, c1, 84, dd, b0, 16, 4d, 2c, d3, 7f, 5f, 3c, 0e, e9, 81, 6a, 04, 71, c4, 1b, 77, 1e, 04, ba, 71, ac, e0, 6d, 48, 91, 1d, 0c, 94, 5f, d2, 8c, 8d, ea, 6f, 4f, 00] +tcp_req 49240441012e82aa8e24ff95c16006bb339a0bf5324173ab9003c8fff56500a3c7b5259fb4b3e775a6f200d45dc783ea6aeff1a299d00cbe5059ffcc23b206793ad645ae27b05a6b71936a70905fd8878b7f98a3f08b956d825c0a0184590f3ba88b72c36010ca7b22b995d526fa615a3c5e9fe9d819c235bf28ef971b2221ac5eb967f26a5cca835c743460594c4588a8768bdd1bec889d73fe5a4e4532afa2e5cf61bb732834f2e69793ea16c5e3edd2c76391664c06c5277719f2770c577fab9fd6deae7fd2ddbbbf2ec351e3d7fa3c10cc9407d46540010bff2a93dc327618dd5925bbef52f8768601826002cc9de59f9c19e0b77dd88a529581c84528f6151830f3b94bbf3c4005038e1de19c reject +tcp_req c00000d426ec57ea04c000000000000093f48e3d185bcfcf94ea8492a6a84a171c232d5ad1035af0bc8f277b37383bbcf9993d617aa6041e6d0f1268ef2d1455dd522f7abcfe3158681cd7347f3899bf2ac3deb08ef6159a8e91c50bfc20050926d0469ad12aeb3d1d314979e2bac4ed76a94318539c83a45f68ceb207497abb2e6da0e9fc648bf074523a86037fb01b996286840b6e9ab728724186caaf1075cbfca331e89a06ac0b13550910 ConnectTcpReq { flags: 911186089962, address_type: Ipv6, host: [244, 142, 61, 24, 91, 207, 207, 148, 234, 132, 146, 166, 168, 74, 23, 28, 35, 45, 90, 209, 3, 90, 240, 188, 143, 39, 123, 55, 56, 59, 188, 249, 153, 61, 97, 122, 166, 4, 30, 109, 15, 18, 104, 239, 45, 20, 85, 221, 82, 47, 122, 188, 254, 49, 88, 104, 28, 215, 52, 127, 56, 153, 191, 42, 195, 222, 176, 142, 246, 21, 154, 142, 145, 197, 11, 252, 32, 5, 9, 38, 208, 70, 154, 209, 42, 235, 61, 29, 49, 73, 121, 226, 186, 196, 237, 118, 169, 67, 24, 83, 156, 131, 164, 95, 104, 206, 178, 7, 73, 122, 187, 46, 109, 160, 233, 252, 100, 139, 240, 116, 82, 58, 134, 3, 127, 176, 27, 153, 98, 134, 132, 11, 110, 154, 183, 40, 114, 65, 134, 202, 175, 16, 117, 203, 252, 163, 49], port: 59546 };173;[c0, 00, 00, d4, 26, ec, 57, ea, 04, 40, 93, f4, 8e, 3d, 18, 5b, cf, cf, 94, ea, 84, 92, a6, a8, 4a, 17, 1c, 23, 2d, 5a, d1, 03, 5a, f0, bc, 8f, 27, 7b, 37, 38, 3b, bc, f9, 99, 3d, 61, 7a, a6, 04, 1e, 6d, 0f, 12, 68, ef, 2d, 14, 55, dd, 52, 2f, 7a, bc, fe, 31, 58, 68, 1c, d7, 34, 7f, 38, 99, bf, 2a, c3, de, b0, 8e, f6, 15, 9a, 8e, 91, c5, 0b, fc, 20, 05, 09, 26, d0, 46, 9a, d1, 2a, eb, 3d, 1d, 31, 49, 79, e2, ba, c4, ed, 76, a9, 43, 18, 53, 9c, 83, a4, 5f, 68, ce, b2, 07, 49, 7a, bb, 2e, 6d, a0, e9, fc, 64, 8b, f0, 74, 52, 3a, 86, 03, 7f, b0, 1b, 99, 62, 86, 84, 0b, 6e, 9a, b7, 28, 72, 41, 86, ca, af, 10, 75, cb, fc, a3, 31, e8, 9a, 00] +tcp_req c0001cb111f5391e0380000102730ef185abde5c957e77a3a0d93ccc65f530e5a51792bcb22b6ebca281832075059903f65a8cb9bcfa551505053e12755ea22fbaac33f73dece7e341bda5c929f507c5a33ded7fa2481e3bd541783b4b1e29566ad120e78f08c95a26a8e7e903936db52b9ed87639b588b0ce368c1fe04565fc5ce0269991776586282c3664a2c38577e7a1145b46ae4c9dfecae3e2e799075bc0c9bcc473ff5cdce39c554d0509700351e34dead2d9e2d636ffa8357493fd937d019dfa7582228a4ff9f7d7673ad0b1517612e1c23aa2a2f701830b03cacd2f7ca2d4a5a5ce20a45db87dbd86800bbd62a0d2bc82f3ecd05ce7b5884db965ba15604e0b0bfd4f2ba99d5ac73f56292f5c4000 reject +tcp_req 800010d90140d4f02bb1839359632d583881e23ef5804d53b34e5a0b9112aba0a8cf1fe2effab0989254455601a4b6d8defebb275c1b616458e2016fc8fb7f1268d47a098fa68841252daceda89092e167ac37475acbf2879d83aaacce33e11c1b701ef3f96b3269ccb88e6d3d25b15c5d19683a957c8649214030957dd02f50f32039a877adcb69937d2bb32b5b5470a861f6fb926efa99d7464eed0398e7907963e2e38a91f288d148ebde3d8cf335d53803388cebab2b72058387ac2ce1eb64d62e4113e9cb3225109993b59204d33b2817c0131c96a63d901dbabdc000000000000000 ConnectTcpReq { flags: 4313, address_type: Ipv4, host: [240, 43, 177, 131, 147, 89, 99, 45, 88, 56, 129, 226, 62, 245, 128, 77, 83, 179, 78, 90, 11, 145, 18, 171, 160, 168, 207, 31, 226, 239, 250, 176, 152, 146, 84, 69, 86, 1, 164, 182, 216, 222, 254, 187, 39, 92, 27, 97, 100, 88, 226, 1, 111, 200, 251, 127, 18, 104, 212, 122, 9, 143, 166, 136, 65, 37, 45, 172, 237, 168, 144, 146, 225, 103, 172, 55, 71, 90, 203, 242, 135, 157, 131, 170, 172, 206, 51, 225, 28, 27, 112, 30, 243, 249, 107, 50, 105, 204, 184, 142, 109, 61, 37, 177, 92, 93, 25, 104, 58, 149, 124, 134, 73, 33, 64, 48, 149, 125, 208, 47, 80, 243, 32, 57, 168, 119, 173, 203, 105, 147, 125, 43, 179, 43, 91, 84, 112, 168, 97, 246, 251, 146, 110, 250, 153, 215, 70, 78, 237, 3, 152, 231, 144, 121, 99, 226, 227, 138, 145, 242, 136, 209, 72, 235, 222, 61, 140, 243, 53, 213, 56, 3, 56, 140, 235, 171, 43, 114, 5, 131, 135, 172, 44, 225, 235, 100, 214, 46, 65, 19, 233, 203, 50, 37, 16, 153, 147, 181, 146, 4, 211, 59, 40, 23, 192, 19, 28, 150, 166, 61, 144, 29], port: 47805 };229;[50, d9, 01, 40, d4, f0, 2b, b1, 83, 93, 59, 63, 2d, 58, 38, 81, e2, 3e, f5, 80, 4d, 53, b3, 4e, 5a, 0b, 91, 12, ab, a0, a8, cf, 1f, e2, ef, fa, b0, 98, 92, 54, 45, 56, 01, a4, b6, d8, de, fe, bb, 27, 5c, 1b, 61, 64, 58, e2, 01, 6f, c8, fb, 7f, 12, 68, d4, 7a, 09, 8f, a6, 88, 41, 25, 2d, ac, ed, a8, 90, 92, e1, 67, ac, 37, 47, 5a, cb, f2, 87, 9d, 83, aa, ac, ce, 33, e1, 1c, 1b, 70, 1e, f3, f9, 6b, 32, 69, cc, b8, 8e, 6d, 3d, 25, b1, 5c, 5d, 19, 68, 3a, 95, 7c, 86, 49, 21, 40, 30, 95, 7d, d0, 2f, 50, f3, 20, 39, a8, 77, ad, cb, 69, 93, 7d, 2b, b3, 2b, 5b, 54, 70, a8, 61, f6, fb, 92, 6e, fa, 99, d7, 46, 4e, ed, 03, 98, e7, 90, 79, 63, e2, e3, 8a, 91, f2, 88, d1, 48, eb, de, 3d, 8c, f3, 35, d5, 38, 03, 38, 8c, eb, ab, 2b, 72, 05, 83, 87, ac, 2c, e1, eb, 64, d6, 2e, 41, 13, e9, cb, 32, 25, 10, 99, 93, b5, 92, 04, d3, 3b, 28, 17, c0, 13, 1c, 96, a6, 3d, 90, 1d, ba, bd, 00] +tcp_req c0342e9fba68774803c0000000000000a082e27cec97a072c93009243d1ce00f5798f831b2e9b3d54380226fecf80ecb72cc1fb3f2e4ed8af1b6ac9d01ff3f5472ab22d271d45727179e3c329530f75d85f85d0c51bdf3055d92f37752e47865c38d55ccbae59331b3b2f9014956645e8f6c867fba0f83e27f0ad01de68119ac13d6615f77b6e2c10b944b091fe315bde697db3a1ddc3a982af26eabe11ba980c249fdd6485c70a0d761c1a631359a585235d0c000000000000000 ConnectTcpReq { flags: 14687962351040328, address_type: Domain, host: [130, 226, 124, 236, 151, 160, 114, 201, 48, 9, 36, 61, 28, 224, 15, 87, 152, 248, 49, 178, 233, 179, 213, 67, 128, 34, 111, 236, 248, 14, 203, 114, 204, 31, 179, 242, 228, 237, 138, 241, 182, 172, 157, 1, 255, 63, 84, 114, 171, 34, 210, 113, 212, 87, 39, 23, 158, 60, 50, 149, 48, 247, 93, 133, 248, 93, 12, 81, 189, 243, 5, 93, 146, 243, 119, 82, 228, 120, 101, 195, 141, 85, 204, 186, 229, 147, 49, 179, 178, 249, 1, 73, 86, 100, 94, 143, 108, 134, 127, 186, 15, 131, 226, 127, 10, 208, 29, 230, 129, 25, 172, 19, 214, 97, 95, 119, 182, 226, 193, 11, 148, 75, 9, 31, 227, 21, 189, 230, 151, 219, 58, 29, 220, 58, 152, 42, 242, 110, 171, 225, 27, 169, 128, 194, 73, 253, 214, 72, 92, 112, 160, 215, 97, 193, 166, 49, 53, 154, 88, 82], port: 13776 };187;[c0, 34, 2e, 9f, ba, 68, 77, 48, 03, 40, a0, 82, e2, 7c, ec, 97, a0, 72, c9, 30, 09, 24, 3d, 1c, e0, 0f, 57, 98, f8, 31, b2, e9, b3, d5, 43, 80, 22, 6f, ec, f8, 0e, cb, 72, cc, 1f, b3, f2, e4, ed, 8a, f1, b6, ac, 9d, 01, ff, 3f, 54, 72, ab, 22, d2, 71, d4, 57, 27, 17, 9e, 3c, 32, 95, 30, f7, 5d, 85, f8, 5d, 0c, 51, bd, f3, 05, 5d, 92, f3, 77, 52, e4, 78, 65, c3, 8d, 55, cc, ba, e5, 93, 31, b3, b2, f9, 01, 49, 56, 64, 5e, 8f, 6c, 86, 7f, ba, 0f, 83, e2, 7f, 0a, d0, 1d, e6, 81, 19, ac, 13, d6, 61, 5f, 77, b6, e2, c1, 0b, 94, 4b, 09, 1f, e3, 15, bd, e6, 97, db, 3a, 1d, dc, 3a, 98, 2a, f2, 6e, ab, e1, 1b, a9, 80, c2, 49, fd, d6, 48, 5c, 70, a0, d7, 61, c1, a6, 31, 35, 9a, 58, 52, 35, d0, 00] +tcp_req c000dfe7ed4076ad01c0000000000000c0bde58866c6ddfacb08f6544bb3a762eca851afab691bb4ba30460d6e61733b6feeb0c52c82b957bd617e9f51c545ccc903ac1f9db4cc11bd3bd951e33871018694942b20b4d64b61ae408125ec58f76c7ebdd99148b0af5e54b5dcb59f8c39e44affa2c325cd31ba48b2df12021c807ccf8a4b832cb60f9223bbc5a2086a0b940ea2283ba6192985f78b2d51f4700467bcb2ceee92b38db99c1afb75d2824882d382484cd944f303d7805fc922659f91d21995cb834d25c5184be56112a9186d0a5a4000 ConnectTcpReq { flags: 246187210864301, address_type: Ipv4, host: [189, 229, 136, 102, 198, 221, 250, 203, 8, 246, 84, 75, 179, 167, 98, 236, 168, 81, 175, 171, 105, 27, 180, 186, 48, 70, 13, 110, 97, 115, 59, 111, 238, 176, 197, 44, 130, 185, 87, 189, 97, 126, 159, 81, 197, 69, 204, 201, 3, 172, 31, 157, 180, 204, 17, 189, 59, 217, 81, 227, 56, 113, 1, 134, 148, 148, 43, 32, 180, 214, 75, 97, 174, 64, 129, 37, 236, 88, 247, 108, 126, 189, 217, 145, 72, 176, 175, 94, 84, 181, 220, 181, 159, 140, 57, 228, 74, 255, 162, 195, 37, 205, 49, 186, 72, 178, 223, 18, 2, 28, 128, 124, 207, 138, 75, 131, 44, 182, 15, 146, 35, 187, 197, 162, 8, 106, 11, 148, 14, 162, 40, 59, 166, 25, 41, 133, 247, 139, 45, 81, 244, 112, 4, 103, 188, 178, 206, 238, 146, 179, 141, 185, 156, 26, 251, 117, 210, 130, 72, 130, 211, 130, 72, 76, 217, 68, 243, 3, 215, 128, 95, 201, 34, 101, 159, 145, 210, 25, 149, 203, 131, 77, 37, 197, 24, 75, 229, 97, 18, 169, 24, 109], port: 2650 };213;[c0, 00, df, e7, ed, 40, 76, ad, 01, 40, c0, bd, e5, 88, 66, c6, dd, fa, cb, 08, f6, 54, 4b, b3, a7, 62, ec, a8, 51, af, ab, 69, 1b, b4, ba, 30, 46, 0d, 6e, 61, 73, 3b, 6f, ee, b0, c5, 2c, 82, b9, 57, bd, 61, 7e, 9f, 51, c5, 45, cc, c9, 03, ac, 1f, 9d, b4, cc, 11, bd, 3b, d9, 51, e3, 38, 71, 01, 86, 94, 94, 2b, 20, b4, d6, 4b, 61, ae, 40, 81, 25, ec, 58, f7, 6c, 7e, bd, d9, 91, 48, b0, af, 5e, 54, b5, dc, b5, 9f, 8c, 39, e4, 4a, ff, a2, c3, 25, cd, 31, ba, 48, b2, df, 12, 02, 1c, 80, 7c, cf, 8a, 4b, 83, 2c, b6, 0f, 92, 23, bb, c5, a2, 08, 6a, 0b, 94, 0e, a2, 28, 3b, a6, 19, 29, 85, f7, 8b, 2d, 51, f4, 70, 04, 67, bc, b2, ce, ee, 92, b3, 8d, b9, 9c, 1a, fb, 75, d2, 82, 48, 82, d3, 82, 48, 4c, d9, 44, f3, 03, d7, 80, 5f, c9, 22, 65, 9f, 91, d2, 19, 95, cb, 83, 4d, 25, c5, 18, 4b, e5, 61, 12, a9, 18, 6d, 0a, 5a, 00] +tcp_req 8000f7fa9e400c8e45d105e91d301a37fd17ed80d180000000 reject +tcp_req c00e272c0bedbccc014058a868f6b46d16a184cc2cd4e979fccd5fd398128bf4749898733767e2997651478e84ac4cbdcf6952d0403c89983d5953f6444bc665a89977fc57293a9c12b5b02541a27116cb592ee1ae1518c97c13cc96183e8638426aecb3aa00 ConnectTcpReq { flags: 3983719806123212, address_type: Ipv4, host: [168, 104, 246, 180, 109, 22, 161, 132, 204, 44, 212, 233, 121, 252, 205, 95, 211, 152, 18, 139, 244, 116, 152, 152, 115, 55, 103, 226, 153, 118, 81, 71, 142, 132, 172, 76, 189, 207, 105, 82, 208, 64, 60, 137, 152, 61, 89, 83, 246, 68, 75, 198, 101, 168, 153, 119, 252, 87, 41, 58, 156, 18, 181, 176, 37, 65, 162, 113, 22, 203, 89, 46, 225, 174, 21, 24, 201, 124, 19, 204, 150, 24, 62, 134, 56, 66, 106, 236], port: 45994 };102;[c0, 0e, 27, 2c, 0b, ed, bc, cc, 01, 40, 58, a8, 68, f6, b4, 6d, 16, a1, 84, cc, 2c, d4, e9, 79, fc, cd, 5f, d3, 98, 12, 8b, f4, 74, 98, 98, 73, 37, 67, e2, 99, 76, 51, 47, 8e, 84, ac, 4c, bd, cf, 69, 52, d0, 40, 3c, 89, 98, 3d, 59, 53, f6, 44, 4b, c6, 65, a8, 99, 77, fc, 57, 29, 3a, 9c, 12, b5, b0, 25, 41, a2, 71, 16, cb, 59, 2e, e1, ae, 15, 18, c9, 7c, 13, cc, 96, 18, 3e, 86, 38, 42, 6a, ec, b3, aa, 00] +tcp_req c00000133ca481f90140ec63089b477c54c08d6c4202ab60b0c3ecca96dc8a39a40464874ad40cd192da81eb27aaf0969005014e0a180218a491d89782a165eb3b8e616f6817c135987a6c31d3d1679cc10965c5e856b5d4fd9c483914b13f72cdfaeae6158e02f56ae3b4d79edcd84c650e80044ca3d40d2e00b96c7ad4f2b0de06ba15ce62d81f5770d4e0707badcb9d991653103dfbb3579b6dff14f108b6c9054b5aa387ab860ad80cea4a85162539af905b7407b937d4d2e69705449220ddd59b169fea012b5a9b23111efbff538f2d95a22d136d575448ea94fa155369f5dea727f3de473a4bd422669f57bd10efe9bc927a450384d8c000000000000000 ConnectTcpReq { flags: 82621792761, address_type: Ipv4, host: [99, 8, 155, 71, 124, 84, 192, 141, 108, 66, 2, 171, 96, 176, 195, 236, 202, 150, 220, 138, 57, 164, 4, 100, 135, 74, 212, 12, 209, 146, 218, 129, 235, 39, 170, 240, 150, 144, 5, 1, 78, 10, 24, 2, 24, 164, 145, 216, 151, 130, 161, 101, 235, 59, 142, 97, 111, 104, 23, 193, 53, 152, 122, 108, 49, 211, 209, 103, 156, 193, 9, 101, 197, 232, 86, 181, 212, 253, 156, 72, 57, 20, 177, 63, 114, 205, 250, 234, 230, 21, 142, 2, 245, 106, 227, 180, 215, 158, 220, 216, 76, 101, 14, 128, 4, 76, 163, 212, 13, 46, 0, 185, 108, 122, 212, 242, 176, 222, 6, 186, 21, 206, 98, 216, 31, 87, 112, 212, 224, 112, 123, 173, 203, 157, 153, 22, 83, 16, 61, 251, 179, 87, 155, 109, 255, 20, 241, 8, 182, 201, 5, 75, 90, 163, 135, 171, 134, 10, 216, 12, 234, 74, 133, 22, 37, 57, 175, 144, 91, 116, 7, 185, 55, 212, 210, 230, 151, 5, 68, 146, 32, 221, 213, 155, 22, 159, 234, 1, 43, 90, 155, 35, 17, 30, 251, 255, 83, 143, 45, 149, 162, 45, 19, 109, 87, 84, 72, 234, 148, 250, 21, 83, 105, 245, 222, 167, 39, 243, 222, 71, 58, 75, 212, 34, 102, 159, 87, 189, 16, 239, 233, 188, 146, 122, 69, 3], port: 34008 };257;[c0, 00, 00, 13, 3c, a4, 81, f9, 01, 40, ec, 63, 08, 9b, 47, 7c, 54, c0, 8d, 6c, 42, 02, ab, 60, b0, c3, ec, ca, 96, dc, 8a, 39, a4, 04, 64, 87, 4a, d4, 0c, d1, 92, da, 81, eb, 27, aa, f0, 96, 90, 05, 01, 4e, 0a, 18, 02, 18, a4, 91, d8, 97, 82, a1, 65, eb, 3b, 8e, 61, 6f, 68, 17, c1, 35, 98, 7a, 6c, 31, d3, d1, 67, 9c, c1, 09, 65, c5, e8, 56, b5, d4, fd, 9c, 48, 39, 14, b1, 3f, 72, cd, fa, ea, e6, 15, 8e, 02, f5, 6a, e3, b4, d7, 9e, dc, d8, 4c, 65, 0e, 80, 04, 4c, a3, d4, 0d, 2e, 00, b9, 6c, 7a, d4, f2, b0, de, 06, ba, 15, ce, 62, d8, 1f, 57, 70, d4, e0, 70, 7b, ad, cb, 9d, 99, 16, 53, 10, 3d, fb, b3, 57, 9b, 6d, ff, 14, f1, 08, b6, c9, 05, 4b, 5a, a3, 87, ab, 86, 0a, d8, 0c, ea, 4a, 85, 16, 25, 39, af, 90, 5b, 74, 07, b9, 37, d4, d2, e6, 97, 05, 44, 92, 20, dd, d5, 9b, 16, 9f, ea, 01, 2b, 5a, 9b, 23, 11, 1e, fb, ff, 53, 8f, 2d, 95, a2, 2d, 13, 6d, 57, 54, 48, ea, 94, fa, 15, 53, 69, f5, de, a7, 27, f3, de, 47, 3a, 4b, d4, 22, 66, 9f, 57, bd, 10, ef, e9, bc, 92, 7a, 45, 03, 84, d8, 00] +tcp_req 412c018000003ad4bc1c3cd77e83359ce8404946e8a2d044222c077c626612f1f099f7f6884c50d89fba3bdaa2bc12ce190cd2773c09d73c58df8eccecccafc53e2c76c0000000000000070b6ce7ae376ce2121a80a109 ConnectTcpReq { flags: 300, address_type: Ipv4, host: [212, 188, 28, 60, 215, 126, 131, 53, 156, 232, 64, 73, 70, 232, 162, 208, 68, 34, 44, 7, 124, 98, 102, 18, 241, 240, 153, 247, 246, 136, 76, 80, 216, 159, 186, 59, 218, 162, 188, 18, 206, 25, 12, 210, 119, 60, 9, 215, 60, 88, 223, 142, 204, 236, 204, 175, 197, 62], port: 11382 };82;[41, 2c, 01, 3a, d4, bc, 1c, 3c, d7, 7e, 83, 35, 9c, e8, 40, 49, 46, e8, a2, d0, 44, 22, 2c, 07, 7c, 62, 66, 12, f1, f0, 99, f7, f6, 88, 4c, 50, d8, 9f, ba, 3b, da, a2, bc, 12, ce, 19, 0c, d2, 77, 3c, 09, d7, 3c, 58, df, 8e, cc, ec, cc, af, c5, 3e, 2c, 76, 00] +tcp_req c000000bd4c86b450340410aaeff79f7ea740d121c7d225e9ceae232bf30ed74235aca607e8a67e9947dfa3a7c3dff8bce72f328b23fe3a1d4f1d59afcb5e48f646c2e4acc2ce533f3612cdcd86b0a60f2dc8e9f3eb9254beb ConnectTcpReq { flags: 50814544709, address_type: Domain, host: [10, 174, 255, 121, 247, 234, 116, 13, 18, 28, 125, 34, 94, 156, 234, 226, 50, 191, 48, 237, 116, 35, 90, 202, 96, 126, 138, 103, 233, 148, 125, 250, 58, 124, 61, 255, 139, 206, 114, 243, 40, 178, 63, 227, 161, 212, 241, 213, 154, 252, 181, 228, 143, 100, 108, 46, 74, 204, 44, 229, 51, 243, 97, 44, 220], port: 55403 };89;[c0, 00, 00, 0b, d4, c8, 6b, 45, 03, 40, 41, 0a, ae, ff, 79, f7, ea, 74, 0d, 12, 1c, 7d, 22, 5e, 9c, ea, e2, 32, bf, 30, ed, 74, 23, 5a, ca, 60, 7e, 8a, 67, e9, 94, 7d, fa, 3a, 7c, 3d, ff, 8b, ce, 72, f3, 28, b2, 3f, e3, a1, d4, f1, d5, 9a, fc, b5, e4, 8f, 64, 6c, 2e, 4a, cc, 2c, e5, 33, f3, 61, 2c, dc, d8, 6b, 00] +tcp_req c005ff8628503e900480000065aa282f073bfd66316defd9699d4563888108ce7d92ad7adfb9397134fbe88d0681cfe7172163a9e3784c7eeeaab38631620c9a2a6e26d8d2418f8fd2e1ea3ab9216b32fc5e5aeab16e3e3c4fcd1b1c72688abeeb713b0c3d62e1506775d05f0a0a8dbc1a7dc98a4000 ConnectTcpReq { flags: 1688326550601360, address_type: Ipv6, host: [170, 40, 47, 7, 59, 253, 102, 49, 109, 239, 217, 105, 157, 69, 99, 136, 129, 8, 206, 125, 146, 173, 122, 223, 185, 57, 113, 52, 251, 232, 141, 6, 129, 207, 231, 23, 33, 99, 169, 227, 120, 76, 126, 238, 170, 179, 134, 49, 98, 12, 154, 42, 110, 38, 216, 210, 65, 143, 143, 210, 225, 234, 58, 185, 33, 107, 50, 252, 94, 90, 234, 177, 110, 62, 60, 79, 205, 27, 28, 114, 104, 138, 190, 235, 113, 59, 12, 61, 98, 225, 80, 103, 117, 208, 95, 10, 10, 141, 188, 26, 125], port: 51594 };118;[c0, 05, ff, 86, 28, 50, 3e, 90, 04, 40, 65, aa, 28, 2f, 07, 3b, fd, 66, 31, 6d, ef, d9, 69, 9d, 45, 63, 88, 81, 08, ce, 7d, 92, ad, 7a, df, b9, 39, 71, 34, fb, e8, 8d, 06, 81, cf, e7, 17, 21, 63, a9, e3, 78, 4c, 7e, ee, aa, b3, 86, 31, 62, 0c, 9a, 2a, 6e, 26, d8, d2, 41, 8f, 8f, d2, e1, ea, 3a, b9, 21, 6b, 32, fc, 5e, 5a, ea, b1, 6e, 3e, 3c, 4f, cd, 1b, 1c, 72, 68, 8a, be, eb, 71, 3b, 0c, 3d, 62, e1, 50, 67, 75, d0, 5f, 0a, 0a, 8d, bc, 1a, 7d, c9, 8a, 00] +tcp_req c00000d1a74f9d7101800001039234748c21edf367a25fab0ecb4dd631189044f735f5d156cd3d056742e8159430ea3def4cf444e938acb10ac429315ea15c385cdcbc729838315b0d0c6a39110afb7e7f0c51510740ecb156bd6636d483c4a135e863c42e3315db15485823fba1f79d7b60483b4d19a7079920e8b01715a161fde8d1b654534b2db45d1b1a25724bfe6b2e45d6104e41764e2cdb22337455b9b7051d3cc166204ac109542c168353674cdfd5b342b1f0628508ddacb7e243b7c8aadbfda20892c89f490fa4449a762a77acba4bd4a3511fe283f7f89962b1bf43ac460f292b7f77188ec0e6d7b09da8cf95f392fb181a35d142df536fb74937a9c1c926afac2eca5ce61bf660b2d1b1ea7f8000000d3bcb594ee2fa62a00789ff3dd4 reject +tcp_resp 010212636f6e6e656374696f6e207265667573656400 ConnectTcpResp { status: 1, error_code: 2, message: [99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 32, 114, 101, 102, 117, 115, 101, 100] };22;[01, 02, 12, 63, 6f, 6e, 6e, 65, 63, 74, 69, 6f, 6e, 20, 72, 65, 66, 75, 73, 65, 64, 00] +tcp_resp 00000000 ConnectTcpResp { status: 0, error_code: 0, message: [] };4;[00, 00, 00, 00] +tcp_resp reject +tcp_resp 11 reject +tcp_resp 1140 reject +tcp_resp 114000 reject +tcp_resp 11400040 reject +tcp_resp 11400040ad reject +tcp_resp 11400040adc6 reject +tcp_resp 11400040adc6fb reject +tcp_resp 11400040adc6fb42 reject +tcp_resp 11400040adc6fb4244 reject +tcp_resp 11400040adc6fb424429 reject +tcp_resp 11400040adc6fb42442990 reject +tcp_resp 11400040adc6fb42442990d8 reject +tcp_resp 11400040adc6fb42442990d8e5 reject +tcp_resp 11400040adc6fb42442990d8e52b reject +tcp_resp 11400040adc6fb42442990d8e52beb reject +tcp_resp 11400040adc6fb42442990d8e52beb09 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5a reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1b reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1bab reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaa reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0de reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1de reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded3 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded35878 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab166396 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16639675 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d2 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa3365 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33659324 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241f reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe448 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4481271 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d7 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a4 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f11 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f8840 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a30 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bf reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd6 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2b reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf7 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5e reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef8 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f1 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f141 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b7 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d0 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05b reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbd reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdab reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c436 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e88 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5d reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d067 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a3814 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140e reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d3 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c0 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c09806 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1e reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b8 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b888 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b5 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b59 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b5934 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e6 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64b reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be1 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a7 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a79682 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d71 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d7166 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c836 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a79 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e7 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e722 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262ba reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacd reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd1 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f66 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f97 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f9770 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f97708996 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f69 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f699932 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e52 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e5205 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a3 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c80 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c8000 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c800000 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c80000004 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c8000000486 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c800000048688 reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c8000000486884c reject +tcp_resp 11400040adc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c8000000486884c37 ConnectTcpResp { status: 17, error_code: 0, message: [198, 251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76, 163, 215, 132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244, 107, 30, 239, 29, 38, 233, 184, 136, 69, 242, 213, 229, 181, 139, 89, 52, 116, 230, 75, 225, 17, 167, 150, 130, 93, 113, 102, 21, 200, 54, 126, 138, 121, 96, 231, 34, 98, 186, 205, 209, 72, 172, 95, 102, 63, 151, 112, 137, 150, 127, 105, 153, 50, 30, 82, 5, 84, 163, 148, 204, 77, 58, 12] };186;[11, 00, 40, ad, c6, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, a3, d7, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 6b, 1e, ef, 1d, 26, e9, b8, 88, 45, f2, d5, e5, b5, 8b, 59, 34, 74, e6, 4b, e1, 11, a7, 96, 82, 5d, 71, 66, 15, c8, 36, 7e, 8a, 79, 60, e7, 22, 62, ba, cd, d1, 48, ac, 5f, 66, 3f, 97, 70, 89, 96, 7f, 69, 99, 32, 1e, 52, 05, 54, a3, 94, cc, 4d, 3a, 0c, 00] +tcp_resp e3c000000000000251c00000000000010253a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48682d37a4c9d4896d14dcc8be6fcb3cae2a8ed41906905a00c79116c42f6efc3abeb588cd8433e48b916d29a040f1f56dfaae34040bfee40c54373755fff8070a31ca4e98ed392278b7de69abdbccd6ed89165eb4cbdf27f34cdb610cfbb98a6eb961a9f34e79c941b28dceab810b061154399cb6cb22960706ecdc059f8ab72d06629b4d6881b73d140f46922fe7829f40b4905e000456b9d4cfa52935adb2d90b422e69297d8132b12d4bee0c567158668dce9817977065d567234e29dc1c0b073bf895cd7c66ace40ecab63c2d2a9be3487e51cd29f1ed51a93fe5a5c892b3dccdef800000028d38 reject +tcp_resp d16d6840cd2a44f8de699a9723e60222de2997de191c1d712fffa0700dd5443f929102b81179b3a2f93ca80eddf35bce6aa609439c89860b0173935e59d0429831aedbee71abb4ded9744f25778f2c5e18c83a516e0f8c52a1325cd3df6225608e089cb79822ae3c4e964fc7fb440fc12d9199d03b0f93eec7afbbbd6354fc94f152f44291b4754d6941f0a709f23f7a075e2826813e113c9a06f49871ddb099ab2ad13be057c703b111585e8fe76d619f2dfb72c906ad202f604a64b4338f589eb76fd4f39162284f326cd28e0daa0fc04bc000000000000000 ConnectTcpResp { status: 209, error_code: 11624, message: [42, 68, 248, 222, 105, 154, 151, 35, 230, 2, 34, 222, 41, 151, 222, 25, 28, 29, 113, 47, 255, 160, 112, 13, 213, 68, 63, 146, 145, 2, 184, 17, 121, 179, 162, 249, 60, 168, 14, 221, 243, 91, 206, 106, 166, 9, 67, 156, 137, 134, 11, 1, 115, 147, 94, 89, 208, 66, 152, 49, 174, 219, 238, 113, 171, 180, 222, 217, 116, 79, 37, 119, 143, 44, 94, 24, 200, 58, 81, 110, 15, 140, 82, 161, 50, 92, 211, 223, 98, 37, 96, 142, 8, 156, 183, 152, 34, 174, 60, 78, 150, 79, 199, 251, 68, 15, 193, 45, 145, 153, 208, 59, 15, 147, 238, 199, 175, 187, 189, 99, 84, 252, 148, 241, 82, 244, 66, 145, 180, 117, 77, 105, 65, 240, 167, 9, 242, 63, 122, 7, 94, 40, 38, 129, 62, 17, 60, 154, 6, 244, 152, 113, 221, 176, 153, 171, 42, 209, 59, 224, 87, 199, 3, 177, 17, 88, 94, 143, 231, 109, 97, 159, 45, 251, 114, 201, 6, 173, 32, 47, 96, 74, 100, 180, 51, 143, 88, 158, 183, 111, 212, 243, 145, 98, 40, 79, 50, 108, 210, 142, 13, 170, 15, 192, 75] };218;[d1, 6d, 68, 40, cd, 2a, 44, f8, de, 69, 9a, 97, 23, e6, 02, 22, de, 29, 97, de, 19, 1c, 1d, 71, 2f, ff, a0, 70, 0d, d5, 44, 3f, 92, 91, 02, b8, 11, 79, b3, a2, f9, 3c, a8, 0e, dd, f3, 5b, ce, 6a, a6, 09, 43, 9c, 89, 86, 0b, 01, 73, 93, 5e, 59, d0, 42, 98, 31, ae, db, ee, 71, ab, b4, de, d9, 74, 4f, 25, 77, 8f, 2c, 5e, 18, c8, 3a, 51, 6e, 0f, 8c, 52, a1, 32, 5c, d3, df, 62, 25, 60, 8e, 08, 9c, b7, 98, 22, ae, 3c, 4e, 96, 4f, c7, fb, 44, 0f, c1, 2d, 91, 99, d0, 3b, 0f, 93, ee, c7, af, bb, bd, 63, 54, fc, 94, f1, 52, f4, 42, 91, b4, 75, 4d, 69, 41, f0, a7, 09, f2, 3f, 7a, 07, 5e, 28, 26, 81, 3e, 11, 3c, 9a, 06, f4, 98, 71, dd, b0, 99, ab, 2a, d1, 3b, e0, 57, c7, 03, b1, 11, 58, 5e, 8f, e7, 6d, 61, 9f, 2d, fb, 72, c9, 06, ad, 20, 2f, 60, 4a, 64, b4, 33, 8f, 58, 9e, b7, 6f, d4, f3, 91, 62, 28, 4f, 32, 6c, d2, 8e, 0d, aa, 0f, c0, 4b, 00] +tcp_resp f8c00000000000000fc00000000000007623e7a935cf8c5a485121a92b78c348b16bc348be5ebdf687fa97bc7df349eebf00d7dbf32b7b4a8184c8e308239c4836e06e6f380ce1d0ac10416355ea789123d60736259222b981ebb7f3cd4831e326334a213ea9200cb0349e10298d37c30f1c7d2b7d48993335dbf42f5f671920826c8b2033652300 accept +tcp_resp 39c000000076ddc87e40c5e912eea23e9e2244c404deb1a55b145b518b6690a19107239e1ae2aa02ac8bb836e4bc10bdd6c8c782ee40c2db22e04dad0a29845530607429de147262c0d0a4cafdd275a3086e36c82209de102a61b2521a8db3d8856f8f26e3cce9a378180da007ed46690447f9dbfdac36a6fb516443f6f8ed3df6922e909386f0d69d94ff84807fdf7f175f02c04c2b48c4391545f302f04acfd1107e3320759d09fdb3441b6f706af9a059f36b595a73942c77f0355f628d447143c8a4d62e22277d6a2638382f4cf88000000de79b017248944b89864107dbf6 ConnectTcpResp { status: 57, error_code: 1994246270, message: [233, 18, 238, 162, 62, 158, 34, 68, 196, 4, 222, 177, 165, 91, 20, 91, 81, 139, 102, 144, 161, 145, 7, 35, 158, 26, 226, 170, 2, 172, 139, 184, 54, 228, 188, 16, 189, 214, 200, 199, 130, 238, 64, 194, 219, 34, 224, 77, 173, 10, 41, 132, 85, 48, 96, 116, 41, 222, 20, 114, 98, 192, 208, 164, 202, 253, 210, 117, 163, 8, 110, 54, 200, 34, 9, 222, 16, 42, 97, 178, 82, 26, 141, 179, 216, 133, 111, 143, 38, 227, 204, 233, 163, 120, 24, 13, 160, 7, 237, 70, 105, 4, 71, 249, 219, 253, 172, 54, 166, 251, 81, 100, 67, 246, 248, 237, 61, 246, 146, 46, 144, 147, 134, 240, 214, 157, 148, 255, 132, 128, 127, 223, 127, 23, 95, 2, 192, 76, 43, 72, 196, 57, 21, 69, 243, 2, 240, 74, 207, 209, 16, 126, 51, 32, 117, 157, 9, 253, 179, 68, 27, 111, 112, 106, 249, 160, 89, 243, 107, 89, 90, 115, 148, 44, 119, 240, 53, 95, 98, 141, 68, 113, 67, 200, 164, 214, 46, 34, 39, 125, 106, 38, 56, 56, 47, 76, 248] };225;[39, c0, 00, 00, 00, 76, dd, c8, 7e, 40, c5, e9, 12, ee, a2, 3e, 9e, 22, 44, c4, 04, de, b1, a5, 5b, 14, 5b, 51, 8b, 66, 90, a1, 91, 07, 23, 9e, 1a, e2, aa, 02, ac, 8b, b8, 36, e4, bc, 10, bd, d6, c8, c7, 82, ee, 40, c2, db, 22, e0, 4d, ad, 0a, 29, 84, 55, 30, 60, 74, 29, de, 14, 72, 62, c0, d0, a4, ca, fd, d2, 75, a3, 08, 6e, 36, c8, 22, 09, de, 10, 2a, 61, b2, 52, 1a, 8d, b3, d8, 85, 6f, 8f, 26, e3, cc, e9, a3, 78, 18, 0d, a0, 07, ed, 46, 69, 04, 47, f9, db, fd, ac, 36, a6, fb, 51, 64, 43, f6, f8, ed, 3d, f6, 92, 2e, 90, 93, 86, f0, d6, 9d, 94, ff, 84, 80, 7f, df, 7f, 17, 5f, 02, c0, 4c, 2b, 48, c4, 39, 15, 45, f3, 02, f0, 4a, cf, d1, 10, 7e, 33, 20, 75, 9d, 09, fd, b3, 44, 1b, 6f, 70, 6a, f9, a0, 59, f3, 6b, 59, 5a, 73, 94, 2c, 77, f0, 35, 5f, 62, 8d, 44, 71, 43, c8, a4, d6, 2e, 22, 27, 7d, 6a, 26, 38, 38, 2f, 4c, f8, 00] +tcp_resp 85a24002b98000008e6596a100ae82f2974ee2ccd7a3a4bc853fa8d8403bf5d3646a569ca22bdab971748c9fe003c77c6d31efa1386799e942737721a21f11fb7dc3537fea049b46022c1b35e94d3e4df3e7bd7b77c0478621e9b68d05f26056b7dcba0d10fd36f177853ea5ecf64e1d1f3b0ebdce35069376735d0a7020a1e7e6b7aeec5c6e786d545a46e23b2916bc104f33c25262174000 accept +tcp_resp 17401b40992c606b256d4133f364f97e373df78a9b4a3116d3a2d7948fdfb8fe7b7241574f136235d96ee0961c25b4d2a6315fea3009dea32eddcfec7a30f014861e9822713d0d43407ddee1caa46a2c39d4b269ead41d9542b8014440b9cf813852dfe4a3a944b1194ebcb6f61a0868e12b6dc145a506dfd0423712f2e914219b90bca8abb2b4051c3b54a0ef9f1cca6c0113b0e0d2829140f72cb5d47c4000 ConnectTcpResp { status: 23, error_code: 27, message: [44, 96, 107, 37, 109, 65, 51, 243, 100, 249, 126, 55, 61, 247, 138, 155, 74, 49, 22, 211, 162, 215, 148, 143, 223, 184, 254, 123, 114, 65, 87, 79, 19, 98, 53, 217, 110, 224, 150, 28, 37, 180, 210, 166, 49, 95, 234, 48, 9, 222, 163, 46, 221, 207, 236, 122, 48, 240, 20, 134, 30, 152, 34, 113, 61, 13, 67, 64, 125, 222, 225, 202, 164, 106, 44, 57, 212, 178, 105, 234, 212, 29, 149, 66, 184, 1, 68, 64, 185, 207, 129, 56, 82, 223, 228, 163, 169, 68, 177, 25, 78, 188, 182, 246, 26, 8, 104, 225, 43, 109, 193, 69, 165, 6, 223, 208, 66, 55, 18, 242, 233, 20, 33, 155, 144, 188, 168, 171, 178, 180, 5, 28, 59, 84, 160, 239, 159, 28, 202, 108, 1, 19, 176, 224, 210, 130, 145, 64, 247, 44, 181, 212, 124] };160;[17, 1b, 40, 99, 2c, 60, 6b, 25, 6d, 41, 33, f3, 64, f9, 7e, 37, 3d, f7, 8a, 9b, 4a, 31, 16, d3, a2, d7, 94, 8f, df, b8, fe, 7b, 72, 41, 57, 4f, 13, 62, 35, d9, 6e, e0, 96, 1c, 25, b4, d2, a6, 31, 5f, ea, 30, 09, de, a3, 2e, dd, cf, ec, 7a, 30, f0, 14, 86, 1e, 98, 22, 71, 3d, 0d, 43, 40, 7d, de, e1, ca, a4, 6a, 2c, 39, d4, b2, 69, ea, d4, 1d, 95, 42, b8, 01, 44, 40, b9, cf, 81, 38, 52, df, e4, a3, a9, 44, b1, 19, 4e, bc, b6, f6, 1a, 08, 68, e1, 2b, 6d, c1, 45, a5, 06, df, d0, 42, 37, 12, f2, e9, 14, 21, 9b, 90, bc, a8, ab, b2, b4, 05, 1c, 3b, 54, a0, ef, 9f, 1c, ca, 6c, 01, 13, b0, e0, d2, 82, 91, 40, f7, 2c, b5, d4, 7c, 00] +tcp_resp b1f26d592da16f3e3fc00000000000009ca297d1a34c3a30db4265cb082c8919d32ccb6a9a48ac18ce1d12b8bfbad1a0e58ad50963e8bbbe813ed69d003c99af6694f304c22848dafd9c618edeca65f053a022f8ef88a9158042e8ef218fae7f134905df35216f23dc5cd18d2bdb1ed3b1de0d1912792624d925037c6804c98ce21fd0c967a0cab946bb853b8ff747349c1bea4b2efe19e8cfdd39c3bb19c35e4c2bbd0b31adb1d3a1c9c0b434c00000000000000680e53f6ea0aba168e287 accept +tcp_resp 21c0000000000000014085e1afc3978ac4db0c7a499d9173cef8755605b81f4de7a3d3a83db474c95f9ca6871fb7f32440ba1b10de500d0d259b5e52df7149ea99d44d5a9261837640e64ca491932cbe3953e3ff059159d4798b17ed87db84080468cdbe464687ab16d89ff2ddb657aa1e292401e6eadbf1f82e4b805b7c534d32cbde351b5cd8b0b48fc752e36ebb4cc00000000000000e7a4771dc86f1a519c93f92335862 ConnectTcpResp { status: 33, error_code: 1, message: [225, 175, 195, 151, 138, 196, 219, 12, 122, 73, 157, 145, 115, 206, 248, 117, 86, 5, 184, 31, 77, 231, 163, 211, 168, 61, 180, 116, 201, 95, 156, 166, 135, 31, 183, 243, 36, 64, 186, 27, 16, 222, 80, 13, 13, 37, 155, 94, 82, 223, 113, 73, 234, 153, 212, 77, 90, 146, 97, 131, 118, 64, 230, 76, 164, 145, 147, 44, 190, 57, 83, 227, 255, 5, 145, 89, 212, 121, 139, 23, 237, 135, 219, 132, 8, 4, 104, 205, 190, 70, 70, 135, 171, 22, 216, 159, 242, 221, 182, 87, 170, 30, 41, 36, 1, 230, 234, 219, 241, 248, 46, 75, 128, 91, 124, 83, 77, 50, 203, 222, 53, 27, 92, 216, 176, 180, 143, 199, 82, 227, 110, 187, 76] };166;[21, 01, 40, 85, e1, af, c3, 97, 8a, c4, db, 0c, 7a, 49, 9d, 91, 73, ce, f8, 75, 56, 05, b8, 1f, 4d, e7, a3, d3, a8, 3d, b4, 74, c9, 5f, 9c, a6, 87, 1f, b7, f3, 24, 40, ba, 1b, 10, de, 50, 0d, 0d, 25, 9b, 5e, 52, df, 71, 49, ea, 99, d4, 4d, 5a, 92, 61, 83, 76, 40, e6, 4c, a4, 91, 93, 2c, be, 39, 53, e3, ff, 05, 91, 59, d4, 79, 8b, 17, ed, 87, db, 84, 08, 04, 68, cd, be, 46, 46, 87, ab, 16, d8, 9f, f2, dd, b6, 57, aa, 1e, 29, 24, 01, e6, ea, db, f1, f8, 2e, 4b, 80, 5b, 7c, 53, 4d, 32, cb, de, 35, 1b, 5c, d8, b0, b4, 8f, c7, 52, e3, 6e, bb, 4c, 00] +tcp_resp f68000706940c64533aa23c5c74bbc6183f92c663919fb083692443427c77260111cac482c1346b28121f1a2cbe2b37067e3fe6fcb87c92f1e7ffa3b27be3408dff01ed962bf124abbd243f7015c49509d70c568629433cac03123405e39d0f9924c4624785ff92f3750420dd8d7120ae0877fbe0527d2d75dd4cc65b5876179054ee3e5ec265fa3878a1925a7db8154a680e7825d253769adf8e7986ee54c97b1d4d9a77fb755eaa841e446cc262d8ea5012b84f291c8255c4eed48cfd63bc7c4865b1c2b9c456b64b8417b930032 ConnectTcpResp { status: 246, error_code: 28777, message: [69, 51, 170, 35, 197, 199, 75, 188, 97, 131, 249, 44, 102, 57, 25, 251, 8, 54, 146, 68, 52, 39, 199, 114, 96, 17, 28, 172, 72, 44, 19, 70, 178, 129, 33, 241, 162, 203, 226, 179, 112, 103, 227, 254, 111, 203, 135, 201, 47, 30, 127, 250, 59, 39, 190, 52, 8, 223, 240, 30, 217, 98, 191, 18, 74, 187, 210, 67, 247, 1, 92, 73, 80, 157, 112, 197, 104, 98, 148, 51, 202, 192, 49, 35, 64, 94, 57, 208, 249, 146, 76, 70, 36, 120, 95, 249, 47, 55, 80, 66, 13, 216, 215, 18, 10, 224, 135, 127, 190, 5, 39, 210, 215, 93, 212, 204, 101, 181, 135, 97, 121, 5, 78, 227, 229, 236, 38, 95, 163, 135, 138, 25, 37, 167, 219, 129, 84, 166, 128, 231, 130, 93, 37, 55, 105, 173, 248, 231, 152, 110, 229, 76, 151, 177, 212, 217, 167, 127, 183, 85, 234, 168, 65, 228, 70, 204, 38, 45, 142, 165, 1, 43, 132, 242, 145, 200, 37, 92, 78, 237, 72, 207, 214, 59, 199, 196, 134, 91, 28, 43, 156, 69, 107, 100, 184, 65, 123, 147] };206;[f6, 80, 00, 70, 69, 40, c6, 45, 33, aa, 23, c5, c7, 4b, bc, 61, 83, f9, 2c, 66, 39, 19, fb, 08, 36, 92, 44, 34, 27, c7, 72, 60, 11, 1c, ac, 48, 2c, 13, 46, b2, 81, 21, f1, a2, cb, e2, b3, 70, 67, e3, fe, 6f, cb, 87, c9, 2f, 1e, 7f, fa, 3b, 27, be, 34, 08, df, f0, 1e, d9, 62, bf, 12, 4a, bb, d2, 43, f7, 01, 5c, 49, 50, 9d, 70, c5, 68, 62, 94, 33, ca, c0, 31, 23, 40, 5e, 39, d0, f9, 92, 4c, 46, 24, 78, 5f, f9, 2f, 37, 50, 42, 0d, d8, d7, 12, 0a, e0, 87, 7f, be, 05, 27, d2, d7, 5d, d4, cc, 65, b5, 87, 61, 79, 05, 4e, e3, e5, ec, 26, 5f, a3, 87, 8a, 19, 25, a7, db, 81, 54, a6, 80, e7, 82, 5d, 25, 37, 69, ad, f8, e7, 98, 6e, e5, 4c, 97, b1, d4, d9, a7, 7f, b7, 55, ea, a8, 41, e4, 46, cc, 26, 2d, 8e, a5, 01, 2b, 84, f2, 91, c8, 25, 5c, 4e, ed, 48, cf, d6, 3b, c7, c4, 86, 5b, 1c, 2b, 9c, 45, 6b, 64, b8, 41, 7b, 93, 00] +tcp_resp ef800736608000005b8d29c97428d2cbd63b3b4075499a1683a97e2e3b82f70b7858dc2b599edc9e8340c5ba1c37fcd40bf879995533a18e41c7733c83748c72e4a198db8ce4f183ce5b66014c865e58ac8d2131086ae83feeedc398d9b88363bc79892907d2cf0ea0a41c07 ConnectTcpResp { status: 239, error_code: 472672, message: [141, 41, 201, 116, 40, 210, 203, 214, 59, 59, 64, 117, 73, 154, 22, 131, 169, 126, 46, 59, 130, 247, 11, 120, 88, 220, 43, 89, 158, 220, 158, 131, 64, 197, 186, 28, 55, 252, 212, 11, 248, 121, 153, 85, 51, 161, 142, 65, 199, 115, 60, 131, 116, 140, 114, 228, 161, 152, 219, 140, 228, 241, 131, 206, 91, 102, 1, 76, 134, 94, 88, 172, 141, 33, 49, 8, 106, 232, 63, 238, 237, 195, 152, 217, 184, 131, 99, 188, 121, 137, 41] };108;[ef, 80, 07, 36, 60, 40, 5b, 8d, 29, c9, 74, 28, d2, cb, d6, 3b, 3b, 40, 75, 49, 9a, 16, 83, a9, 7e, 2e, 3b, 82, f7, 0b, 78, 58, dc, 2b, 59, 9e, dc, 9e, 83, 40, c5, ba, 1c, 37, fc, d4, 0b, f8, 79, 99, 55, 33, a1, 8e, 41, c7, 73, 3c, 83, 74, 8c, 72, e4, a1, 98, db, 8c, e4, f1, 83, ce, 5b, 66, 01, 4c, 86, 5e, 58, ac, 8d, 21, 31, 08, 6a, e8, 3f, ee, ed, c3, 98, d9, b8, 83, 63, bc, 79, 89, 29, 00] +tcp_resp b54035c0000000000000a214194d8c1a74e817c1b139cfe2daedaa3a190e69742df4489bc00400a8d0cf4440b1142c96d2ab0497ee6372806c8743b1ecec52a45511024058488c00dd81364732643b81dcf20bf823206672fb702f2c22e308de17fd7322d088999921870006b23ed44056e0bc3b6dc55b5787a938ff50485da1bc991985b29164997d339f4159d0ebf5d82de63ccda8d1f2f15a9f934a56f3e4db1ca35663161701cf464706194000 accept +tcp_resp 7d402a40ca9e396add06cc46f941a7ca77ffa99001859fe49cbf853d0a33c25539b8298c6a64b778f1fd703a61a1a5661d0c8ffa679bb2a0d63f89bbdd867cc8e993cb3cef180175b61deda347c7d10cbf73664d99fc53c25c3d52163251e0927e021c98f76f48127d1e02a2378edcafb402131ac332d1e83d60aefe35bd40501a78b08031a1a309acf267f46473d6058b2e337a6a887509673329822b9bd838c2de468019edd22c10733ce00514be245d9c07914b51e15c363d1ed1a1c7ed397b14a2cb85c1e1662312bdeb05eec14000 ConnectTcpResp { status: 125, error_code: 42, message: [158, 57, 106, 221, 6, 204, 70, 249, 65, 167, 202, 119, 255, 169, 144, 1, 133, 159, 228, 156, 191, 133, 61, 10, 51, 194, 85, 57, 184, 41, 140, 106, 100, 183, 120, 241, 253, 112, 58, 97, 161, 165, 102, 29, 12, 143, 250, 103, 155, 178, 160, 214, 63, 137, 187, 221, 134, 124, 200, 233, 147, 203, 60, 239, 24, 1, 117, 182, 29, 237, 163, 71, 199, 209, 12, 191, 115, 102, 77, 153, 252, 83, 194, 92, 61, 82, 22, 50, 81, 224, 146, 126, 2, 28, 152, 247, 111, 72, 18, 125, 30, 2, 162, 55, 142, 220, 175, 180, 2, 19, 26, 195, 50, 209, 232, 61, 96, 174, 254, 53, 189, 64, 80, 26, 120, 176, 128, 49, 161, 163, 9, 172, 242, 103, 244, 100, 115, 214, 5, 139, 46, 51, 122, 106, 136, 117, 9, 103, 51, 41, 130, 43, 155, 216, 56, 194, 222, 70, 128, 25, 237, 210, 44, 16, 115, 60, 224, 5, 20, 190, 36, 93, 156, 7, 145, 75, 81, 225, 92, 54, 61, 30, 209, 161, 199, 237, 57, 123, 20, 162, 203, 133, 193, 225, 102, 35, 18, 189, 235, 5, 238, 193] };209;[7d, 2a, 40, ca, 9e, 39, 6a, dd, 06, cc, 46, f9, 41, a7, ca, 77, ff, a9, 90, 01, 85, 9f, e4, 9c, bf, 85, 3d, 0a, 33, c2, 55, 39, b8, 29, 8c, 6a, 64, b7, 78, f1, fd, 70, 3a, 61, a1, a5, 66, 1d, 0c, 8f, fa, 67, 9b, b2, a0, d6, 3f, 89, bb, dd, 86, 7c, c8, e9, 93, cb, 3c, ef, 18, 01, 75, b6, 1d, ed, a3, 47, c7, d1, 0c, bf, 73, 66, 4d, 99, fc, 53, c2, 5c, 3d, 52, 16, 32, 51, e0, 92, 7e, 02, 1c, 98, f7, 6f, 48, 12, 7d, 1e, 02, a2, 37, 8e, dc, af, b4, 02, 13, 1a, c3, 32, d1, e8, 3d, 60, ae, fe, 35, bd, 40, 50, 1a, 78, b0, 80, 31, a1, a3, 09, ac, f2, 67, f4, 64, 73, d6, 05, 8b, 2e, 33, 7a, 6a, 88, 75, 09, 67, 33, 29, 82, 2b, 9b, d8, 38, c2, de, 46, 80, 19, ed, d2, 2c, 10, 73, 3c, e0, 05, 14, be, 24, 5d, 9c, 07, 91, 4b, 51, e1, 5c, 36, 3d, 1e, d1, a1, c7, ed, 39, 7b, 14, a2, cb, 85, c1, e1, 66, 23, 12, bd, eb, 05, ee, c1, 00] +tcp_resp 33c00000000121070c410029cf314ba725c0da3c06db39d9c2e2a1f64115e518c8ec7914d4dd2dbc85e149b8cd3e7e17c302cd853c75effe80a0906501ce05fbc007c7c982a90dbba03d0fff7d4455e7cf363ffe21fc4ff0acd913caf851484038d8d358837b4f66adb9852c48e8acecc2025fe5c85b83df064d27b984a136d847fe0ee7f9923fccf69a5f6c184a70f6be47322f3dd3f54f37f398d176a1a2b3e75bb6e4d787422383ccd8dc66abecdcfc51a143eed6fd98eae4a1d7ebe3ffbba034ea4d98cda4e6532a90749882ca384df9f2d4630d74b9daa6b26b69c0b30e2c447e652a51b496928b175c6a1e76a94bbd61f5cfe293b2bf4fdbfc2cef6c8b0cb5f2931764c49a978d38400aa0b5b5c37e702c5d52cf reject +tcp_resp 0d800009d540d790d5985c4f326dba59bcc2f082c4219cce531a126ba085e59253a89cac3e355f80fbd268148f123f31773cedde97849a26dd2edf4ef322341e14c431f8bce0f12367a30c0b9c5438863e1df7064caa1dbdeca852d3b91536c8878b90056548bdcd316279b15bfe2b5ee07693d9f2fe341944dd0efadb710b208f7714a405c03dc03a09cd45861220d203b1fb072c415c6a07afedca2bff6bd6745ff60259919cc4ef982f3ed12e369dc45053d8ed3a427d6b4be8a46178d86ea93088ec8bc32c73369d3d4b6f18913f25d7b5cf23eccae3c604a0f8b8b780000000 ConnectTcpResp { status: 13, error_code: 2517, message: [144, 213, 152, 92, 79, 50, 109, 186, 89, 188, 194, 240, 130, 196, 33, 156, 206, 83, 26, 18, 107, 160, 133, 229, 146, 83, 168, 156, 172, 62, 53, 95, 128, 251, 210, 104, 20, 143, 18, 63, 49, 119, 60, 237, 222, 151, 132, 154, 38, 221, 46, 223, 78, 243, 34, 52, 30, 20, 196, 49, 248, 188, 224, 241, 35, 103, 163, 12, 11, 156, 84, 56, 134, 62, 29, 247, 6, 76, 170, 29, 189, 236, 168, 82, 211, 185, 21, 54, 200, 135, 139, 144, 5, 101, 72, 189, 205, 49, 98, 121, 177, 91, 254, 43, 94, 224, 118, 147, 217, 242, 254, 52, 25, 68, 221, 14, 250, 219, 113, 11, 32, 143, 119, 20, 164, 5, 192, 61, 192, 58, 9, 205, 69, 134, 18, 32, 210, 3, 177, 251, 7, 44, 65, 92, 106, 7, 175, 237, 202, 43, 255, 107, 214, 116, 95, 246, 2, 89, 145, 156, 196, 239, 152, 47, 62, 209, 46, 54, 157, 196, 80, 83, 216, 237, 58, 66, 125, 107, 75, 232, 164, 97, 120, 216, 110, 169, 48, 136, 236, 139, 195, 44, 115, 54, 157, 61, 75, 111, 24, 145, 63, 37, 215, 181, 207, 35, 236, 202, 227, 198, 4, 160, 248, 184, 183] };226;[0d, 49, d5, 40, d7, 90, d5, 98, 5c, 4f, 32, 6d, ba, 59, bc, c2, f0, 82, c4, 21, 9c, ce, 53, 1a, 12, 6b, a0, 85, e5, 92, 53, a8, 9c, ac, 3e, 35, 5f, 80, fb, d2, 68, 14, 8f, 12, 3f, 31, 77, 3c, ed, de, 97, 84, 9a, 26, dd, 2e, df, 4e, f3, 22, 34, 1e, 14, c4, 31, f8, bc, e0, f1, 23, 67, a3, 0c, 0b, 9c, 54, 38, 86, 3e, 1d, f7, 06, 4c, aa, 1d, bd, ec, a8, 52, d3, b9, 15, 36, c8, 87, 8b, 90, 05, 65, 48, bd, cd, 31, 62, 79, b1, 5b, fe, 2b, 5e, e0, 76, 93, d9, f2, fe, 34, 19, 44, dd, 0e, fa, db, 71, 0b, 20, 8f, 77, 14, a4, 05, c0, 3d, c0, 3a, 09, cd, 45, 86, 12, 20, d2, 03, b1, fb, 07, 2c, 41, 5c, 6a, 07, af, ed, ca, 2b, ff, 6b, d6, 74, 5f, f6, 02, 59, 91, 9c, c4, ef, 98, 2f, 3e, d1, 2e, 36, 9d, c4, 50, 53, d8, ed, 3a, 42, 7d, 6b, 4b, e8, a4, 61, 78, d8, 6e, a9, 30, 88, ec, 8b, c3, 2c, 73, 36, 9d, 3d, 4b, 6f, 18, 91, 3f, 25, d7, b5, cf, 23, ec, ca, e3, c6, 04, a0, f8, b8, b7, 00] +tcp_resp b380cacff1c0000000000000e22f3cc686f76474b13e132f1b1d6d213a7ccf0f15717c3ef210b683e87801c653ef6af4ed30ddc8ef277beabc39a544c935960f3c76b8f72bf0a8400d38ad5863f309c782b3dec64e382d9fae33c25503e5757b0b57e6d8bfa70dd0ccf09ad742b9bc6704678be0cfb5eeae0ceebb902912cc6237d70869bced05f947632599829b2bfde2214fd826ec3405fda931439ed153582630beadbaad8d2dddd3a04299c9653cbdefd0b6e8b29b8174b3a8d61a1775352d5590b443e70e3d18c3fe0693fc1a87e6d434a33c3cd177a3040a50a45d44a643e454626796aed84efe5ba633191ec000000000000000 ConnectTcpResp { status: 179, error_code: 13291505, message: [47, 60, 198, 134, 247, 100, 116, 177, 62, 19, 47, 27, 29, 109, 33, 58, 124, 207, 15, 21, 113, 124, 62, 242, 16, 182, 131, 232, 120, 1, 198, 83, 239, 106, 244, 237, 48, 221, 200, 239, 39, 123, 234, 188, 57, 165, 68, 201, 53, 150, 15, 60, 118, 184, 247, 43, 240, 168, 64, 13, 56, 173, 88, 99, 243, 9, 199, 130, 179, 222, 198, 78, 56, 45, 159, 174, 51, 194, 85, 3, 229, 117, 123, 11, 87, 230, 216, 191, 167, 13, 208, 204, 240, 154, 215, 66, 185, 188, 103, 4, 103, 139, 224, 207, 181, 238, 174, 12, 238, 187, 144, 41, 18, 204, 98, 55, 215, 8, 105, 188, 237, 5, 249, 71, 99, 37, 153, 130, 155, 43, 253, 226, 33, 79, 216, 38, 236, 52, 5, 253, 169, 49, 67, 158, 209, 83, 88, 38, 48, 190, 173, 186, 173, 141, 45, 221, 211, 160, 66, 153, 201, 101, 60, 189, 239, 208, 182, 232, 178, 155, 129, 116, 179, 168, 214, 26, 23, 117, 53, 45, 85, 144, 180, 67, 231, 14, 61, 24, 195, 254, 6, 147, 252, 26, 135, 230, 212, 52, 163, 60, 60, 209, 119, 163, 4, 10, 80, 164, 93, 68, 166, 67, 228, 84, 98, 103, 150, 174, 216, 78, 254, 91, 166, 51, 25, 30] };247;[b3, 80, ca, cf, f1, 40, e2, 2f, 3c, c6, 86, f7, 64, 74, b1, 3e, 13, 2f, 1b, 1d, 6d, 21, 3a, 7c, cf, 0f, 15, 71, 7c, 3e, f2, 10, b6, 83, e8, 78, 01, c6, 53, ef, 6a, f4, ed, 30, dd, c8, ef, 27, 7b, ea, bc, 39, a5, 44, c9, 35, 96, 0f, 3c, 76, b8, f7, 2b, f0, a8, 40, 0d, 38, ad, 58, 63, f3, 09, c7, 82, b3, de, c6, 4e, 38, 2d, 9f, ae, 33, c2, 55, 03, e5, 75, 7b, 0b, 57, e6, d8, bf, a7, 0d, d0, cc, f0, 9a, d7, 42, b9, bc, 67, 04, 67, 8b, e0, cf, b5, ee, ae, 0c, ee, bb, 90, 29, 12, cc, 62, 37, d7, 08, 69, bc, ed, 05, f9, 47, 63, 25, 99, 82, 9b, 2b, fd, e2, 21, 4f, d8, 26, ec, 34, 05, fd, a9, 31, 43, 9e, d1, 53, 58, 26, 30, be, ad, ba, ad, 8d, 2d, dd, d3, a0, 42, 99, c9, 65, 3c, bd, ef, d0, b6, e8, b2, 9b, 81, 74, b3, a8, d6, 1a, 17, 75, 35, 2d, 55, 90, b4, 43, e7, 0e, 3d, 18, c3, fe, 06, 93, fc, 1a, 87, e6, d4, 34, a3, 3c, 3c, d1, 77, a3, 04, 0a, 50, a4, 5d, 44, a6, 43, e4, 54, 62, 67, 96, ae, d8, 4e, fe, 5b, a6, 33, 19, 1e, 00] +tcp_resp 49c000000000000e0d40503c39df1ef23612134025bcfd7d6c7738d04a192e298f944bd89d6c201cee8341047cc096d3b0af4e4788be8a4d937e6bc019377d6090b9a0ab813a509f38a62c5159e3e6629694af9570e348d624f7b94000 ConnectTcpResp { status: 73, error_code: 3597, message: [60, 57, 223, 30, 242, 54, 18, 19, 64, 37, 188, 253, 125, 108, 119, 56, 208, 74, 25, 46, 41, 143, 148, 75, 216, 157, 108, 32, 28, 238, 131, 65, 4, 124, 192, 150, 211, 176, 175, 78, 71, 136, 190, 138, 77, 147, 126, 107, 192, 25, 55, 125, 96, 144, 185, 160, 171, 129, 58, 80, 159, 56, 166, 44, 81, 89, 227, 230, 98, 150, 148, 175, 149, 112, 227, 72, 214, 36, 247, 185] };93;[49, 4e, 0d, 40, 50, 3c, 39, df, 1e, f2, 36, 12, 13, 40, 25, bc, fd, 7d, 6c, 77, 38, d0, 4a, 19, 2e, 29, 8f, 94, 4b, d8, 9d, 6c, 20, 1c, ee, 83, 41, 04, 7c, c0, 96, d3, b0, af, 4e, 47, 88, be, 8a, 4d, 93, 7e, 6b, c0, 19, 37, 7d, 60, 90, b9, a0, ab, 81, 3a, 50, 9f, 38, a6, 2c, 51, 59, e3, e6, 62, 96, 94, af, 95, 70, e3, 48, d6, 24, f7, b9, 00] +tcp_resp 04008000006e1d2474f1461985cb8e814a8b81ae702fbf9c2e13e08ad79a7f720ae2f432fd3e3b2758db4ea1c55b17ee3de2dcbba20ad50d8768de9fbec1cd8da6cad135109efa7c3ec9178607e91958dd81df9c9818197ee0cfab02c22835e652b71454beab76a8c8315083f950c71bb7ae1b584000a40a4efd ConnectTcpResp { status: 4, error_code: 0, message: [29, 36, 116, 241, 70, 25, 133, 203, 142, 129, 74, 139, 129, 174, 112, 47, 191, 156, 46, 19, 224, 138, 215, 154, 127, 114, 10, 226, 244, 50, 253, 62, 59, 39, 88, 219, 78, 161, 197, 91, 23, 238, 61, 226, 220, 187, 162, 10, 213, 13, 135, 104, 222, 159, 190, 193, 205, 141, 166, 202, 209, 53, 16, 158, 250, 124, 62, 201, 23, 134, 7, 233, 25, 88, 221, 129, 223, 156, 152, 24, 25, 126, 224, 207, 171, 2, 194, 40, 53, 230, 82, 183, 20, 84, 190, 171, 118, 168, 200, 49, 80, 131, 249, 80, 199, 27, 183, 174, 27, 88] };118;[04, 00, 40, 6e, 1d, 24, 74, f1, 46, 19, 85, cb, 8e, 81, 4a, 8b, 81, ae, 70, 2f, bf, 9c, 2e, 13, e0, 8a, d7, 9a, 7f, 72, 0a, e2, f4, 32, fd, 3e, 3b, 27, 58, db, 4e, a1, c5, 5b, 17, ee, 3d, e2, dc, bb, a2, 0a, d5, 0d, 87, 68, de, 9f, be, c1, cd, 8d, a6, ca, d1, 35, 10, 9e, fa, 7c, 3e, c9, 17, 86, 07, e9, 19, 58, dd, 81, df, 9c, 98, 18, 19, 7e, e0, cf, ab, 02, c2, 28, 35, e6, 52, b7, 14, 54, be, ab, 76, a8, c8, 31, 50, 83, f9, 50, c7, 1b, b7, ae, 1b, 58, 00] +tcp_resp 71819a38ba40b7c56640b5db3fc3e8957a3a8a39e667b6baa5a0b020c4724110dd82f48333ab830124757b67ff381a22ef298edf7212e3f714cab3df51498cc2547b8e9d70f9bc0532e26ff5d4a8ce257c4127940d248ecb7d177f96b6c4255daec41dc0a709c02031c22ec7348f8eaaff154035c903b48adab82d2be991db143d10d731b1a707832e2e225d38cb42dfb889ec7acc9de755c818f1825e1443c5d4e6615bb0f89c4592442c8ab44e9564d604e3fed8103bd2302b26877bb1400ff3f3fa1d76cc7d5065e35864f4c8d7 ConnectTcpResp { status: 113, error_code: 26884282, message: [197, 102, 64, 181, 219, 63, 195, 232, 149, 122, 58, 138, 57, 230, 103, 182, 186, 165, 160, 176, 32, 196, 114, 65, 16, 221, 130, 244, 131, 51, 171, 131, 1, 36, 117, 123, 103, 255, 56, 26, 34, 239, 41, 142, 223, 114, 18, 227, 247, 20, 202, 179, 223, 81, 73, 140, 194, 84, 123, 142, 157, 112, 249, 188, 5, 50, 226, 111, 245, 212, 168, 206, 37, 124, 65, 39, 148, 13, 36, 142, 203, 125, 23, 127, 150, 182, 196, 37, 93, 174, 196, 29, 192, 167, 9, 192, 32, 49, 194, 46, 199, 52, 143, 142, 170, 255, 21, 64, 53, 201, 3, 180, 138, 218, 184, 45, 43, 233, 145, 219, 20, 61, 16, 215, 49, 177, 167, 7, 131, 46, 46, 34, 93, 56, 203, 66, 223, 184, 137, 236, 122, 204, 157, 231, 85, 200, 24, 241, 130, 94, 20, 67, 197, 212, 230, 97, 91, 176, 248, 156, 69, 146, 68, 44, 138, 180, 78, 149, 100, 214, 4, 227, 254, 216, 16, 59, 210, 48, 43, 38, 135, 123, 177] };207;[71, 81, 9a, 38, ba, 40, b7, c5, 66, 40, b5, db, 3f, c3, e8, 95, 7a, 3a, 8a, 39, e6, 67, b6, ba, a5, a0, b0, 20, c4, 72, 41, 10, dd, 82, f4, 83, 33, ab, 83, 01, 24, 75, 7b, 67, ff, 38, 1a, 22, ef, 29, 8e, df, 72, 12, e3, f7, 14, ca, b3, df, 51, 49, 8c, c2, 54, 7b, 8e, 9d, 70, f9, bc, 05, 32, e2, 6f, f5, d4, a8, ce, 25, 7c, 41, 27, 94, 0d, 24, 8e, cb, 7d, 17, 7f, 96, b6, c4, 25, 5d, ae, c4, 1d, c0, a7, 09, c0, 20, 31, c2, 2e, c7, 34, 8f, 8e, aa, ff, 15, 40, 35, c9, 03, b4, 8a, da, b8, 2d, 2b, e9, 91, db, 14, 3d, 10, d7, 31, b1, a7, 07, 83, 2e, 2e, 22, 5d, 38, cb, 42, df, b8, 89, ec, 7a, cc, 9d, e7, 55, c8, 18, f1, 82, 5e, 14, 43, c5, d4, e6, 61, 5b, b0, f8, 9c, 45, 92, 44, 2c, 8a, b4, 4e, 95, 64, d6, 04, e3, fe, d8, 10, 3b, d2, 30, 2b, 26, 87, 7b, b1, 00] +tcp_resp 9b400340b4909a846911ad9f94ecc6642c333949406d1fe0afa35a0a063ed032f25b6d5207cd8054afe21a9b172113cba0b5847542244641ff9fa7da4d6eac482a748a775d66a0d438253e7c93acd65da2d1532de33b97b490be9f9c416923ac7adf987dda9c999151fbfba706c68cb5ea852640a25c1a45c79a48238fdb54ff15f001000495ac029e587d060efe4c38a4e4cdc3a18dcf68ef7e1744978309570bcc5ef361dbdcf78279a49b55bd12ad36a644d5ff25b2aed7c00000000000000097b0af107d accept +tcp_resp c94219800000d660b3be39e00b2eb1675921e16738589aca3d650af306671080ed050e0fef093a1bfd289e395b458a7860b1a1a6bf771d3481f1abfee23047d287a57d16ff83e03b28a55a70281b1afded9d6dbd25f08a01ec929793d7c7f51e5fda2f9742996de561a9204f529e86a0dd1075de11667e6155220d71f36b5becf558f91910d643651f076b0d68d4ef61b631dac8cc77be3a43898140bf7aeef4f8137b674ac7e61701fd4d743117da6178fede80170d81518479d47af9bfb9871f864c9d2991fc76f1622d57d75224abb0298a5a16ed8102c971774167c000000000000000 ConnectTcpResp { status: 201, error_code: 537, message: [96, 179, 190, 57, 224, 11, 46, 177, 103, 89, 33, 225, 103, 56, 88, 154, 202, 61, 101, 10, 243, 6, 103, 16, 128, 237, 5, 14, 15, 239, 9, 58, 27, 253, 40, 158, 57, 91, 69, 138, 120, 96, 177, 161, 166, 191, 119, 29, 52, 129, 241, 171, 254, 226, 48, 71, 210, 135, 165, 125, 22, 255, 131, 224, 59, 40, 165, 90, 112, 40, 27, 26, 253, 237, 157, 109, 189, 37, 240, 138, 1, 236, 146, 151, 147, 215, 199, 245, 30, 95, 218, 47, 151, 66, 153, 109, 229, 97, 169, 32, 79, 82, 158, 134, 160, 221, 16, 117, 222, 17, 102, 126, 97, 85, 34, 13, 113, 243, 107, 91, 236, 245, 88, 249, 25, 16, 214, 67, 101, 31, 7, 107, 13, 104, 212, 239, 97, 182, 49, 218, 200, 204, 119, 190, 58, 67, 137, 129, 64, 191, 122, 238, 244, 248, 19, 123, 103, 74, 199, 230, 23, 1, 253, 77, 116, 49, 23, 218, 97, 120, 254, 222, 128, 23, 13, 129, 81, 132, 121, 212, 122, 249, 191, 185, 135, 31, 134, 76, 157, 41, 145, 252, 118, 241, 98, 45, 87, 215, 82, 36, 171, 176, 41, 138, 90, 22, 237, 129, 2, 201, 113, 119, 65, 103] };229;[c9, 42, 19, 40, d6, 60, b3, be, 39, e0, 0b, 2e, b1, 67, 59, 21, e1, 67, 38, 58, 9a, ca, 3d, 65, 0a, f3, 06, 67, 10, 80, ed, 05, 0e, 0f, ef, 09, 3a, 1b, fd, 28, 9e, 39, 5b, 45, 8a, 78, 60, b1, a1, a6, bf, 77, 1d, 34, 81, f1, ab, fe, e2, 30, 47, d2, 87, a5, 7d, 16, ff, 83, e0, 3b, 28, a5, 5a, 70, 28, 1b, 1a, fd, ed, 9d, 6d, bd, 25, f0, 8a, 01, ec, 92, 97, 93, d7, c7, f5, 1e, 5f, da, 2f, 97, 42, 99, 6d, e5, 61, a9, 20, 4f, 52, 9e, 86, a0, dd, 10, 75, de, 11, 66, 7e, 61, 55, 22, 0d, 71, f3, 6b, 5b, ec, f5, 58, f9, 19, 10, d6, 43, 65, 1f, 07, 6b, 0d, 68, d4, ef, 61, b6, 31, da, c8, cc, 77, be, 3a, 43, 89, 81, 40, bf, 7a, ee, f4, f8, 13, 7b, 67, 4a, c7, e6, 17, 01, fd, 4d, 74, 31, 17, da, 61, 78, fe, de, 80, 17, 0d, 81, 51, 84, 79, d4, 7a, f9, bf, b9, 87, 1f, 86, 4c, 9d, 29, 91, fc, 76, f1, 62, 2d, 57, d7, 52, 24, ab, b0, 29, 8a, 5a, 16, ed, 81, 02, c9, 71, 77, 41, 67, 00] +tcp_resp 33fa64ea92322f617941038ec370fa5e8fec3d88f102cd314687614463acd5817f578996479b97dc976a804f218231c0d47ec0b8ea0381a90140d1f103a4cc482ac50262593a81ca8036ededba3721c16e4f8f42b943f4631a4742585f5b3879f3fa78a8094dcc4c53912fd380ebf62d4521ec6f78b118ea7b151977a4e53586a52c73af7dc59ff0afd22041351d30d1ddd2ec7c5f2b62199b9a334938cea6b44ec9a790ca35912061c046139b79a2de5542744caf7bccf40fc202566ea328667f0e5ce974865e7b55732b77a38b871bf22e7e311ac78cd042e5a05aa5501e9c2d89fcf62d74af38ef88e5562b9a5a09b41089ddea74b9e2de05c9640fbee5b9790931a79ed1ad961486fe47db9fc00000000000000ae38b11ca4850b95107fb reject +tcp_resp fd80068cb6c00000000000007b0c4a71d67d39a00135fa3427208cccb550918996738f63d0c03b32ee60fcd08feb11f85c4e3f01f29371ad4d39ba70f6f70c4113b63fec8621251cff69bffbae588c0261ccf41bd969b26c1592b89bb2da2019e4a4c94e4477279bcf1c05946130bc594d2bdc7fd3f17c77505d0634151e8e261edae8274aa74c67c000000000000000 ConnectTcpResp { status: 253, error_code: 429238, message: [12, 74, 113, 214, 125, 57, 160, 1, 53, 250, 52, 39, 32, 140, 204, 181, 80, 145, 137, 150, 115, 143, 99, 208, 192, 59, 50, 238, 96, 252, 208, 143, 235, 17, 248, 92, 78, 63, 1, 242, 147, 113, 173, 77, 57, 186, 112, 246, 247, 12, 65, 19, 182, 63, 236, 134, 33, 37, 28, 255, 105, 191, 251, 174, 88, 140, 2, 97, 204, 244, 27, 217, 105, 178, 108, 21, 146, 184, 155, 178, 218, 32, 25, 228, 164, 201, 78, 68, 119, 39, 155, 207, 28, 5, 148, 97, 48, 188, 89, 77, 43, 220, 127, 211, 241, 124, 119, 80, 93, 6, 52, 21, 30, 142, 38, 30, 218, 232, 39, 74, 167, 76, 103] };144;[fd, 80, 06, 8c, b6, 40, 7b, 0c, 4a, 71, d6, 7d, 39, a0, 01, 35, fa, 34, 27, 20, 8c, cc, b5, 50, 91, 89, 96, 73, 8f, 63, d0, c0, 3b, 32, ee, 60, fc, d0, 8f, eb, 11, f8, 5c, 4e, 3f, 01, f2, 93, 71, ad, 4d, 39, ba, 70, f6, f7, 0c, 41, 13, b6, 3f, ec, 86, 21, 25, 1c, ff, 69, bf, fb, ae, 58, 8c, 02, 61, cc, f4, 1b, d9, 69, b2, 6c, 15, 92, b8, 9b, b2, da, 20, 19, e4, a4, c9, 4e, 44, 77, 27, 9b, cf, 1c, 05, 94, 61, 30, bc, 59, 4d, 2b, dc, 7f, d3, f1, 7c, 77, 50, 5d, 06, 34, 15, 1e, 8e, 26, 1e, da, e8, 27, 4a, a7, 4c, 67, 00] +tcp_resp a584cc528bc000000000000101085519646bb25253dc45b1544a3952f89ec1a1d6e52a46508b16244fc455bbc24e5691e3181745e2fdfd7df415beb72ea019d4200bbe91e63a44b3bca821da0452d4dac40e193853b759c85f46ffe088c5cac6eda2329bf36d332f7f88ad4202a7b81ab742daf013d896db3a1beae72712d796248c114f1ff7482b3b656019bf03abe52d79f41073e3d4c3dc75739f73af5931c203049ade9b883bb9ea0a6da87d5262c22d86bb391b706e74193cb6cfaa7bc18e799e5e50e1d74b27b59bbd3d5d5b66c9c72e8802ea5d1e6dda96617e1c6d5c7926541872b000071fbc7a58089942e4912100f6614cece510ab37d5a879ad6c070cb43ebfba9766ec518a21bdb04000 reject +tcp_resp 40802544db4067e882bf97665c574840ddb53d6d41519743e0a52c9da4ea2719ee82930e57507349c348382ad569edb6ae5ce72d3b5cb4566814af329d265fdbe23228194713f3b5023506036b0cb4bcf799732cb307aebd7f5c6aee3d3e126bacf2d0db6f8f370f891c2e622149c000000000000000 ConnectTcpResp { status: 64, error_code: 2442459, message: [232, 130, 191, 151, 102, 92, 87, 72, 64, 221, 181, 61, 109, 65, 81, 151, 67, 224, 165, 44, 157, 164, 234, 39, 25, 238, 130, 147, 14, 87, 80, 115, 73, 195, 72, 56, 42, 213, 105, 237, 182, 174, 92, 231, 45, 59, 92, 180, 86, 104, 20, 175, 50, 157, 38, 95, 219, 226, 50, 40, 25, 71, 19, 243, 181, 2, 53, 6, 3, 107, 12, 180, 188, 247, 153, 115, 44, 179, 7, 174, 189, 127, 92, 106, 238, 61, 62, 18, 107, 172, 242, 208, 219, 111, 143, 55, 15, 137, 28, 46, 98, 33, 73] };118;[40, 80, 25, 44, db, 40, 67, e8, 82, bf, 97, 66, 5c, 57, 48, 40, dd, b5, 3d, 6d, 41, 51, 97, 43, e0, a5, 2c, 9d, a4, ea, 27, 19, ee, 82, 93, 0e, 57, 50, 73, 49, c3, 48, 38, 2a, d5, 69, ed, b6, ae, 5c, e7, 2d, 3b, 5c, b4, 56, 68, 14, af, 32, 9d, 26, 5f, db, e2, 32, 28, 19, 47, 13, f3, b5, 02, 35, 06, 03, 6b, 0c, b4, bc, f7, 99, 73, 2c, b3, 07, ae, bd, 7f, 5c, 6a, ee, 3d, 3e, 12, 6b, ac, f2, d0, db, 6f, 8f, 37, 0f, 89, 1c, 2e, 62, 21, 49, 00] +tcp_resp 81c0000000000aa63240a6cb31369a825acb59a49b0a14b6c2334e342fa5c493178cc61cf6b3db50d644025063e74040180ff140a4cfac05f60a7163c3973d7c1644fef8015c4095d264fd3e8dcdfca1106e619766cabfd0c18343c4a308a51f840b2edc47337b1554cc7dba5a85268978f6a7a098eb8e6286a2170d58b3c42a909b2b8ea006be485f49c67696450e48ad90f86efd1134537c06140da88af33ac0e0c10af998354fa85c581de0f62becaf00 ConnectTcpResp { status: 129, error_code: 697906, message: [203, 49, 54, 154, 130, 90, 203, 89, 164, 155, 10, 20, 182, 194, 51, 78, 52, 47, 165, 196, 147, 23, 140, 198, 28, 246, 179, 219, 80, 214, 68, 2, 80, 99, 231, 64, 64, 24, 15, 241, 64, 164, 207, 172, 5, 246, 10, 113, 99, 195, 151, 61, 124, 22, 68, 254, 248, 1, 92, 64, 149, 210, 100, 253, 62, 141, 205, 252, 161, 16, 110, 97, 151, 102, 202, 191, 208, 193, 131, 67, 196, 163, 8, 165, 31, 132, 11, 46, 220, 71, 51, 123, 21, 84, 204, 125, 186, 90, 133, 38, 137, 120, 246, 167, 160, 152, 235, 142, 98, 134, 162, 23, 13, 88, 179, 196, 42, 144, 155, 43, 142, 160, 6, 190, 72, 95, 73, 198, 118, 150, 69, 14, 72, 173, 144, 248, 110, 253, 17, 52, 83, 124, 6, 20, 13, 168, 138, 243, 58, 192, 224, 193, 10, 249, 152, 53, 79, 168, 92, 88, 29, 224, 246, 43, 236, 175] };178;[81, 80, 0a, a6, 32, 40, a6, cb, 31, 36, 9a, 82, 5a, cb, 59, a4, 9b, 0a, 14, b6, c2, 33, 4e, 34, 2f, a5, c4, 93, 17, 8c, c6, 1c, f6, b3, db, 50, d6, 44, 02, 50, 63, e7, 40, 40, 18, 0f, f1, 40, a4, cf, ac, 05, f6, 0a, 71, 63, c3, 97, 3d, 7c, 16, 44, fe, f8, 01, 5c, 40, 95, d2, 64, fd, 3e, 8d, cd, fc, a1, 10, 6e, 61, 97, 66, ca, bf, d0, c1, 83, 43, c4, a3, 08, a5, 1f, 84, 0b, 2e, dc, 47, 33, 7b, 15, 54, cc, 7d, ba, 5a, 85, 26, 89, 78, f6, a7, a0, 98, eb, 8e, 62, 86, a2, 17, 0d, 58, b3, c4, 2a, 90, 9b, 2b, 8e, a0, 06, be, 48, 5f, 49, c6, 76, 96, 45, 0e, 48, ad, 90, f8, 6e, fd, 11, 34, 53, 7c, 06, 14, 0d, a8, 8a, f3, 3a, c0, e0, c1, 0a, f9, 98, 35, 4f, a8, 5c, 58, 1d, e0, f6, 2b, ec, af, 00] +tcp_resp a580007e4e40bee229228163ea509281ca419d02d2be251f7be03f2dd49dd3f51b2112a2b9216d245442db4b72305638a2d76a1e9a0ceab54a80c548dc082d4c44234b6aa28b6a8867a959018812b838d816553dd7eea2087c9623e306f6068c463de5b61040404b2a7b7cb9db3c14fd4b196d86eeb188bd4330680a3e83bc67dd473438b6b04a62c2b4c04961c7ba1f7bdb35639ce6f4a3c619bbd04641117c985154b0a5e161e3cad150363c94030c4eb29e04fbb4cf443e667b8f65dc908f95a7cbfb5a4000 ConnectTcpResp { status: 165, error_code: 32334, message: [226, 41, 34, 129, 99, 234, 80, 146, 129, 202, 65, 157, 2, 210, 190, 37, 31, 123, 224, 63, 45, 212, 157, 211, 245, 27, 33, 18, 162, 185, 33, 109, 36, 84, 66, 219, 75, 114, 48, 86, 56, 162, 215, 106, 30, 154, 12, 234, 181, 74, 128, 197, 72, 220, 8, 45, 76, 68, 35, 75, 106, 162, 139, 106, 136, 103, 169, 89, 1, 136, 18, 184, 56, 216, 22, 85, 61, 215, 238, 162, 8, 124, 150, 35, 227, 6, 246, 6, 140, 70, 61, 229, 182, 16, 64, 64, 75, 42, 123, 124, 185, 219, 60, 20, 253, 75, 25, 109, 134, 238, 177, 136, 189, 67, 48, 104, 10, 62, 131, 188, 103, 221, 71, 52, 56, 182, 176, 74, 98, 194, 180, 192, 73, 97, 199, 186, 31, 123, 219, 53, 99, 156, 230, 244, 163, 198, 25, 187, 208, 70, 65, 17, 124, 152, 81, 84, 176, 165, 225, 97, 227, 202, 209, 80, 54, 60, 148, 3, 12, 78, 178, 158, 4, 251, 180, 207, 68, 62, 102, 123, 143, 101, 220, 144, 143, 149, 167, 203, 251, 90] };199;[a5, 80, 00, 7e, 4e, 40, be, e2, 29, 22, 81, 63, ea, 50, 92, 81, ca, 41, 9d, 02, d2, be, 25, 1f, 7b, e0, 3f, 2d, d4, 9d, d3, f5, 1b, 21, 12, a2, b9, 21, 6d, 24, 54, 42, db, 4b, 72, 30, 56, 38, a2, d7, 6a, 1e, 9a, 0c, ea, b5, 4a, 80, c5, 48, dc, 08, 2d, 4c, 44, 23, 4b, 6a, a2, 8b, 6a, 88, 67, a9, 59, 01, 88, 12, b8, 38, d8, 16, 55, 3d, d7, ee, a2, 08, 7c, 96, 23, e3, 06, f6, 06, 8c, 46, 3d, e5, b6, 10, 40, 40, 4b, 2a, 7b, 7c, b9, db, 3c, 14, fd, 4b, 19, 6d, 86, ee, b1, 88, bd, 43, 30, 68, 0a, 3e, 83, bc, 67, dd, 47, 34, 38, b6, b0, 4a, 62, c2, b4, c0, 49, 61, c7, ba, 1f, 7b, db, 35, 63, 9c, e6, f4, a3, c6, 19, bb, d0, 46, 41, 11, 7c, 98, 51, 54, b0, a5, e1, 61, e3, ca, d1, 50, 36, 3c, 94, 03, 0c, 4e, b2, 9e, 04, fb, b4, cf, 44, 3e, 66, 7b, 8f, 65, dc, 90, 8f, 95, a7, cb, fb, 5a, 00] +tcp_resp f7d4c3043bad5cf5d380000073dad8c2264f119c163c9866b111d25c1ef1aefd0f8b2786901251aaa325b194eabcccdc099f20e25c415ff2bab4a76c40d67b4a5e59b3eec91aa719ee5f7a511d2648ffcc11b430106c1f557c3319446652449db1f8b74077dee7cde1bb700713d325d2af9797dd1d3bb994672b53f446871648c000000000000000ff85 accept +tcp_resp 528379d86bc0000000000000cd5116f3eac6dc0f279e3c9a88266b71fbed0fbccb2d0cb9037211343c9ea59814905aefa1576a2c1e95971539cd8295d245092bfe38ce1b5acc2a3eeef63a4e942aa3136ce44f9101ba08d1c0667753a2e290560d506162fd541889cf68d1636c36e3c4a8230566058685f3b066a16b0bad3fdff97d27692a6506402f4ea8d8c8c7df3ba2c8b6c827e484d67a33cb974a70dc43a9cda3ff30422e21dc8ec71c5d55a3c175d5a51e2eeedf653f4c6940d00705a9733d1ee17182732cd1ddfc586369f78fb7e12a908e0b078256d40d02b27e7ed8deefec652ac84b09 ConnectTcpResp { status: 82, error_code: 58316907, message: [81, 22, 243, 234, 198, 220, 15, 39, 158, 60, 154, 136, 38, 107, 113, 251, 237, 15, 188, 203, 45, 12, 185, 3, 114, 17, 52, 60, 158, 165, 152, 20, 144, 90, 239, 161, 87, 106, 44, 30, 149, 151, 21, 57, 205, 130, 149, 210, 69, 9, 43, 254, 56, 206, 27, 90, 204, 42, 62, 238, 246, 58, 78, 148, 42, 163, 19, 108, 228, 79, 145, 1, 186, 8, 209, 192, 102, 119, 83, 162, 226, 144, 86, 13, 80, 97, 98, 253, 84, 24, 137, 207, 104, 209, 99, 108, 54, 227, 196, 168, 35, 5, 102, 5, 134, 133, 243, 176, 102, 161, 107, 11, 173, 63, 223, 249, 125, 39, 105, 42, 101, 6, 64, 47, 78, 168, 216, 200, 199, 223, 59, 162, 200, 182, 200, 39, 228, 132, 214, 122, 51, 203, 151, 74, 112, 220, 67, 169, 205, 163, 255, 48, 66, 46, 33, 220, 142, 199, 28, 93, 85, 163, 193, 117, 213, 165, 30, 46, 238, 223, 101, 63, 76, 105, 64, 208, 7, 5, 169, 115, 61, 30, 225, 113, 130, 115, 44, 209, 221, 252, 88, 99, 105, 247, 143, 183, 225, 42, 144, 142, 11, 7, 130, 86, 212] };232;[52, 83, 79, d8, 6b, 40, cd, 51, 16, f3, ea, c6, dc, 0f, 27, 9e, 3c, 9a, 88, 26, 6b, 71, fb, ed, 0f, bc, cb, 2d, 0c, b9, 03, 72, 11, 34, 3c, 9e, a5, 98, 14, 90, 5a, ef, a1, 57, 6a, 2c, 1e, 95, 97, 15, 39, cd, 82, 95, d2, 45, 09, 2b, fe, 38, ce, 1b, 5a, cc, 2a, 3e, ee, f6, 3a, 4e, 94, 2a, a3, 13, 6c, e4, 4f, 91, 01, ba, 08, d1, c0, 66, 77, 53, a2, e2, 90, 56, 0d, 50, 61, 62, fd, 54, 18, 89, cf, 68, d1, 63, 6c, 36, e3, c4, a8, 23, 05, 66, 05, 86, 85, f3, b0, 66, a1, 6b, 0b, ad, 3f, df, f9, 7d, 27, 69, 2a, 65, 06, 40, 2f, 4e, a8, d8, c8, c7, df, 3b, a2, c8, b6, c8, 27, e4, 84, d6, 7a, 33, cb, 97, 4a, 70, dc, 43, a9, cd, a3, ff, 30, 42, 2e, 21, dc, 8e, c7, 1c, 5d, 55, a3, c1, 75, d5, a5, 1e, 2e, ee, df, 65, 3f, 4c, 69, 40, d0, 07, 05, a9, 73, 3d, 1e, e1, 71, 82, 73, 2c, d1, dd, fc, 58, 63, 69, f7, 8f, b7, e1, 2a, 90, 8e, 0b, 07, 82, 56, d4, 00] +tcp_resp 6b4715800000fb11055302cabfde6310fbfa16ba36798f0ffa44d5a2a4b46be5b8c61f77d8125e61cbc5c4057ad41ad8d138a68ba1634cb1b1cac9b40dd375bb764eb54550d2cd636592e071337821551c5d8ffc6ff0dc4e717586e43fc73c505c00b6db40de7057aee55d30c2dab80b2d5a59f2618ab16989045b8ee8750f512ea80af79967cf3159656481eba5c3361a0f6df5e531fb4ae08ec453d83c33ec60143815e7eab8b73d67981c87bf8401dbb26955373f028cddd5880c59841155a15aedebd808512170e79e408750ea5fc69be90a9c6334472867a74b30ae697318e9b6a636392acdaeadbddc6804b2d6925445ed97d419ba08202dac557755e984a1c000000000000000 accept +tcp_resp 90b71cd13a40aad1a1fb61f44010caf4a3995f144ee2cc496f120b3e1c3380b35728c971611438d0a7a192b67e36fc026828af0d8525b2e0e8e3ee602d34160af701801d2f1b18c9d3f3a142f8528daccef0be2c56f56957557509fb46aec851c9fd6bdd592e8cea5e65ccb3ec89be6ece3ccf6d3f6a439a384165e129cef120ae8c66e9c791d83e3e9301f9eda1a52ab147063edd730f91424c66f6e8362beb131ddd4ec8028da046be6d3a1181e3ccd3c000000000000000bd22 ConnectTcpResp { status: 144, error_code: 924635450, message: [209, 161, 251, 97, 244, 64, 16, 202, 244, 163, 153, 95, 20, 78, 226, 204, 73, 111, 18, 11, 62, 28, 51, 128, 179, 87, 40, 201, 113, 97, 20, 56, 208, 167, 161, 146, 182, 126, 54, 252, 2, 104, 40, 175, 13, 133, 37, 178, 224, 232, 227, 238, 96, 45, 52, 22, 10, 247, 1, 128, 29, 47, 27, 24, 201, 211, 243, 161, 66, 248, 82, 141, 172, 206, 240, 190, 44, 86, 245, 105, 87, 85, 117, 9, 251, 70, 174, 200, 81, 201, 253, 107, 221, 89, 46, 140, 234, 94, 101, 204, 179, 236, 137, 190, 110, 206, 60, 207, 109, 63, 106, 67, 154, 56, 65, 101, 225, 41, 206, 241, 32, 174, 140, 102, 233, 199, 145, 216, 62, 62, 147, 1, 249, 237, 161, 165, 42, 177, 71, 6, 62, 221, 115, 15, 145, 66, 76, 102, 246, 232, 54, 43, 235, 19, 29, 221, 78, 200, 2, 141, 160, 70, 190, 109, 58, 17, 129, 227, 204, 211] };185;[90, b7, 1c, d1, 3a, 40, aa, d1, a1, fb, 61, f4, 40, 10, ca, f4, a3, 99, 5f, 14, 4e, e2, cc, 49, 6f, 12, 0b, 3e, 1c, 33, 80, b3, 57, 28, c9, 71, 61, 14, 38, d0, a7, a1, 92, b6, 7e, 36, fc, 02, 68, 28, af, 0d, 85, 25, b2, e0, e8, e3, ee, 60, 2d, 34, 16, 0a, f7, 01, 80, 1d, 2f, 1b, 18, c9, d3, f3, a1, 42, f8, 52, 8d, ac, ce, f0, be, 2c, 56, f5, 69, 57, 55, 75, 09, fb, 46, ae, c8, 51, c9, fd, 6b, dd, 59, 2e, 8c, ea, 5e, 65, cc, b3, ec, 89, be, 6e, ce, 3c, cf, 6d, 3f, 6a, 43, 9a, 38, 41, 65, e1, 29, ce, f1, 20, ae, 8c, 66, e9, c7, 91, d8, 3e, 3e, 93, 01, f9, ed, a1, a5, 2a, b1, 47, 06, 3e, dd, 73, 0f, 91, 42, 4c, 66, f6, e8, 36, 2b, eb, 13, 1d, dd, 4e, c8, 02, 8d, a0, 46, be, 6d, 3a, 11, 81, e3, cc, d3, 00] +tcp_resp 06801517e6c00000000000001db9c375c07e8c52c78d6cf99d2f994ca856fbdfc09f547e6caa7cdcfd8e0b89f896fc53a438eacbf02892 ConnectTcpResp { status: 6, error_code: 1382374, message: [185, 195, 117, 192, 126, 140, 82, 199, 141, 108, 249, 157, 47, 153, 76, 168, 86, 251, 223, 192, 159, 84, 126, 108, 170, 124, 220, 253, 142] };54;[06, 80, 15, 17, e6, 1d, b9, c3, 75, c0, 7e, 8c, 52, c7, 8d, 6c, f9, 9d, 2f, 99, 4c, a8, 56, fb, df, c0, 9f, 54, 7e, 6c, aa, 7c, dc, fd, 8e, 00] +tcp_resp b00040af0ac7df15b8d9e7c4590ebbb82998525d512181ed44d3be3a21868b4053ec2418c4e932d52d3084bef42ba1e8b63f7edaa3bd44cddc457fc86cf2ff9beccc826cc2cd7f01e5372ecf29729a3d2375c1a70b2719810ad96a65e13dff59101d908dfdfaafb78f3ea28a6d4554b2eaa8d0382b36ff823654cd22e16c640d5b017cff1e711f0bacd158ee3aa694a79af8e9a7c30f3dc546c9786b263612ed3c957cfa22266c829e406884d6b199aff61d81c00000000000000520e0f9a427 ConnectTcpResp { status: 176, error_code: 0, message: [10, 199, 223, 21, 184, 217, 231, 196, 89, 14, 187, 184, 41, 152, 82, 93, 81, 33, 129, 237, 68, 211, 190, 58, 33, 134, 139, 64, 83, 236, 36, 24, 196, 233, 50, 213, 45, 48, 132, 190, 244, 43, 161, 232, 182, 63, 126, 218, 163, 189, 68, 205, 220, 69, 127, 200, 108, 242, 255, 155, 236, 204, 130, 108, 194, 205, 127, 1, 229, 55, 46, 207, 41, 114, 154, 61, 35, 117, 193, 167, 11, 39, 25, 129, 10, 217, 106, 101, 225, 61, 255, 89, 16, 29, 144, 141, 253, 250, 175, 183, 143, 62, 162, 138, 109, 69, 84, 178, 234, 168, 208, 56, 43, 54, 255, 130, 54, 84, 205, 34, 225, 108, 100, 13, 91, 1, 124, 255, 30, 113, 31, 11, 172, 209, 88, 238, 58, 166, 148, 167, 154, 248, 233, 167, 195, 15, 61, 197, 70, 201, 120, 107, 38, 54, 18, 237, 60, 149, 124, 250, 34, 38, 108, 130, 158, 64, 104, 132, 214, 177, 153, 175, 246, 29, 129] };192;[b0, 00, 40, af, 0a, c7, df, 15, b8, d9, e7, c4, 59, 0e, bb, b8, 29, 98, 52, 5d, 51, 21, 81, ed, 44, d3, be, 3a, 21, 86, 8b, 40, 53, ec, 24, 18, c4, e9, 32, d5, 2d, 30, 84, be, f4, 2b, a1, e8, b6, 3f, 7e, da, a3, bd, 44, cd, dc, 45, 7f, c8, 6c, f2, ff, 9b, ec, cc, 82, 6c, c2, cd, 7f, 01, e5, 37, 2e, cf, 29, 72, 9a, 3d, 23, 75, c1, a7, 0b, 27, 19, 81, 0a, d9, 6a, 65, e1, 3d, ff, 59, 10, 1d, 90, 8d, fd, fa, af, b7, 8f, 3e, a2, 8a, 6d, 45, 54, b2, ea, a8, d0, 38, 2b, 36, ff, 82, 36, 54, cd, 22, e1, 6c, 64, 0d, 5b, 01, 7c, ff, 1e, 71, 1f, 0b, ac, d1, 58, ee, 3a, a6, 94, a7, 9a, f8, e9, a7, c3, 0f, 3d, c5, 46, c9, 78, 6b, 26, 36, 12, ed, 3c, 95, 7c, fa, 22, 26, 6c, 82, 9e, 40, 68, 84, d6, b1, 99, af, f6, 1d, 81, 00] +tcp_resp ce8000000080000008fed1d9f273c435d54000 ConnectTcpResp { status: 206, error_code: 0, message: [254, 209, 217, 242, 115, 196, 53, 213] };19;[ce, 00, 08, fe, d1, d9, f2, 73, c4, 35, d5, 00] +tcp_resp b8c0000000000000e1c000000000000029c9a7c0b0222da75de6406e41acc3275d01f33f895a45ad4e268cf393d2c2bde7bdfde84d75d9b7b3f280000000 ConnectTcpResp { status: 184, error_code: 225, message: [201, 167, 192, 176, 34, 45, 167, 93, 230, 64, 110, 65, 172, 195, 39, 93, 1, 243, 63, 137, 90, 69, 173, 78, 38, 140, 243, 147, 210, 194, 189, 231, 189, 253, 232, 77, 117, 217, 183, 179, 242] };62;[b8, 40, e1, 29, c9, a7, c0, b0, 22, 2d, a7, 5d, e6, 40, 6e, 41, ac, c3, 27, 5d, 01, f3, 3f, 89, 5a, 45, ad, 4e, 26, 8c, f3, 93, d2, c2, bd, e7, bd, fd, e8, 4d, 75, d9, b7, b3, f2, 00] +tcp_resp ee8d20127b80000033db080f524f58bbc3bde32089da6b1771b160b3c91cb542b6c0d8874f5614f3f775e09181467f4dc1daf095518ae574918757164000 ConnectTcpResp { status: 238, error_code: 220205691, message: [219, 8, 15, 82, 79, 88, 187, 195, 189, 227, 32, 137, 218, 107, 23, 113, 177, 96, 179, 201, 28, 181, 66, 182, 192, 216, 135, 79, 86, 20, 243, 247, 117, 224, 145, 129, 70, 127, 77, 193, 218, 240, 149, 81, 138, 229, 116, 145, 135, 87, 22] };62;[ee, 8d, 20, 12, 7b, 33, db, 08, 0f, 52, 4f, 58, bb, c3, bd, e3, 20, 89, da, 6b, 17, 71, b1, 60, b3, c9, 1c, b5, 42, b6, c0, d8, 87, 4f, 56, 14, f3, f7, 75, e0, 91, 81, 46, 7f, 4d, c1, da, f0, 95, 51, 8a, e5, 74, 91, 87, 57, 16, 00] +tcp_resp 6580000009401b5611177adac62e0f55df94a293a466d05c87991e82c4cbad570887c0000000000000059832082d75 ConnectTcpResp { status: 101, error_code: 9, message: [86, 17, 23, 122, 218, 198, 46, 15, 85, 223, 148, 162, 147, 164, 102, 208, 92, 135, 153, 30, 130, 196, 203, 173, 87, 8, 135] };47;[65, 09, 1b, 56, 11, 17, 7a, da, c6, 2e, 0f, 55, df, 94, a2, 93, a4, 66, d0, 5c, 87, 99, 1e, 82, c4, cb, ad, 57, 08, 87, 00] +tcp_resp be80000000c000000000000098f5d783fdefdaba34f04c3dbc0333192180687a4f96967eb3a8e79e687de43a61884ecd321c7a91e7df5486c8aa965ab8f8e08b77fad93c2a83a9d54ff957be9d3a7ce831aa801f97aca2dc045e0e2d02d4e35337298bab1ad1a8657f3a9e479b88dd46e9542365d051ff7abab2f3e1a791d2d09a0a99ec270ca4ae7c4f46adc0c4d51d4832351f403c7ae53058c6215c06dab444112a8b35c0000000000000005d130ce32c ConnectTcpResp { status: 190, error_code: 0, message: [245, 215, 131, 253, 239, 218, 186, 52, 240, 76, 61, 188, 3, 51, 25, 33, 128, 104, 122, 79, 150, 150, 126, 179, 168, 231, 158, 104, 125, 228, 58, 97, 136, 78, 205, 50, 28, 122, 145, 231, 223, 84, 134, 200, 170, 150, 90, 184, 248, 224, 139, 119, 250, 217, 60, 42, 131, 169, 213, 79, 249, 87, 190, 157, 58, 124, 232, 49, 170, 128, 31, 151, 172, 162, 220, 4, 94, 14, 45, 2, 212, 227, 83, 55, 41, 139, 171, 26, 209, 168, 101, 127, 58, 158, 71, 155, 136, 221, 70, 233, 84, 35, 101, 208, 81, 255, 122, 186, 178, 243, 225, 167, 145, 210, 208, 154, 10, 153, 236, 39, 12, 164, 174, 124, 79, 70, 173, 192, 196, 213, 29, 72, 50, 53, 31, 64, 60, 122, 229, 48, 88, 198, 33, 92, 6, 218, 180, 68, 17, 42, 139, 53] };173;[be, 00, 40, 98, f5, d7, 83, fd, ef, da, ba, 34, f0, 4c, 3d, bc, 03, 33, 19, 21, 80, 68, 7a, 4f, 96, 96, 7e, b3, a8, e7, 9e, 68, 7d, e4, 3a, 61, 88, 4e, cd, 32, 1c, 7a, 91, e7, df, 54, 86, c8, aa, 96, 5a, b8, f8, e0, 8b, 77, fa, d9, 3c, 2a, 83, a9, d5, 4f, f9, 57, be, 9d, 3a, 7c, e8, 31, aa, 80, 1f, 97, ac, a2, dc, 04, 5e, 0e, 2d, 02, d4, e3, 53, 37, 29, 8b, ab, 1a, d1, a8, 65, 7f, 3a, 9e, 47, 9b, 88, dd, 46, e9, 54, 23, 65, d0, 51, ff, 7a, ba, b2, f3, e1, a7, 91, d2, d0, 9a, 0a, 99, ec, 27, 0c, a4, ae, 7c, 4f, 46, ad, c0, c4, d5, 1d, 48, 32, 35, 1f, 40, 3c, 7a, e5, 30, 58, c6, 21, 5c, 06, da, b4, 44, 11, 2a, 8b, 35, 00] +tcp_resp 42c0000000000000c7800000735869d7ebb77b0146f4b01835e93d9d62548293da83b2b87362605ff2c4c56c09ae661c12340fcc411c958a3e801caa7848c4ae2bed87fc166cda8cb86999d1946a74d7ae30ac02ed035b3f283fa7979369b35e2122dd946950e7e126147bc9f3475dd9fdc96f943e7416ebb193193c7323a14a80000000 ConnectTcpResp { status: 66, error_code: 199, message: [88, 105, 215, 235, 183, 123, 1, 70, 244, 176, 24, 53, 233, 61, 157, 98, 84, 130, 147, 218, 131, 178, 184, 115, 98, 96, 95, 242, 196, 197, 108, 9, 174, 102, 28, 18, 52, 15, 204, 65, 28, 149, 138, 62, 128, 28, 170, 120, 72, 196, 174, 43, 237, 135, 252, 22, 108, 218, 140, 184, 105, 153, 209, 148, 106, 116, 215, 174, 48, 172, 2, 237, 3, 91, 63, 40, 63, 167, 151, 147, 105, 179, 94, 33, 34, 221, 148, 105, 80, 231, 225, 38, 20, 123, 201, 243, 71, 93, 217, 253, 201, 111, 148, 62, 116, 22, 235, 177, 147, 25, 60, 115, 35, 161, 74] };132;[42, 40, c7, 40, 73, 58, 69, d7, eb, b7, 7b, 01, 46, f4, b0, 18, 35, e9, 3d, 9d, 62, 54, 82, 93, da, 83, b2, b8, 73, 62, 60, 5f, f2, c4, c5, 6c, 09, ae, 66, 1c, 12, 34, 0f, cc, 41, 1c, 95, 8a, 3e, 80, 1c, aa, 78, 48, c4, ae, 2b, ed, 87, fc, 16, 6c, da, 8c, b8, 69, 99, d1, 94, 6a, 74, d7, ae, 30, ac, 02, ed, 03, 5b, 3f, 28, 3f, a7, 97, 93, 69, b3, 5e, 21, 22, dd, 94, 69, 50, e7, e1, 26, 14, 7b, c9, f3, 47, 5d, d9, fd, c9, 6f, 94, 3e, 74, 16, eb, b1, 93, 19, 3c, 73, 23, a1, 4a, 00] +tcp_resp c04005800000f89dc9a4e0b8806710ed29abfc1bb50088f5ae5232db2dede99f3d9df5fb2b7974a27cbb0f15c6289f859e915de8ce4c88b927ce7400d1de787d5f3256b298d9f1f5368bf0e70060e6d90248bd69195725073b7f723bc53840a2bdd3270b848fffdc57fc6c15656fd941ec41c82cefd8ee190ac5861e18c10ba282ea0621582d6370323d167d062a6bcfb685e41a9dccf7dffcf0d358d1ab92a751d52dffa9899e3c25d2ed27f0ba1435ac979b974b492d01faf0f93b8f6fd0f031fa79062c061e055ca222596c75783dfc9cc55f0176f40d72d05472b1f148dd3e01b1d1e12256c408ea9df51aa86c6edac0f7a83ede9f4e6215211254eceb00 accept +tcp_resp ba8007fd978000000a85ce56d370e7a860a9b60dda0e6bb656482c0783d41d473d ConnectTcpResp { status: 186, error_code: 523671, message: [133, 206, 86, 211, 112, 231, 168, 96, 169, 182] };33;[ba, 80, 07, fd, 97, 0a, 85, ce, 56, d3, 70, e7, a8, 60, a9, b6, 00] +tcp_resp 7fd95e07e6346914050a363895d9a47b4ab75b464000 accept +tcp_resp 4a40ef800000292f6d315cbe30603f841bccd8b7d5702f6aa47d5e537c5bb845e5fb89fb89ea9f779af91233dbe69abf4000 ConnectTcpResp { status: 74, error_code: 239, message: [47, 109, 49, 92, 190, 48, 96, 63, 132, 27, 204, 216, 183, 213, 112, 47, 106, 164, 125, 94, 83, 124, 91, 184, 69, 229, 251, 137, 251, 137, 234, 159, 119, 154, 249, 18, 51, 219, 230, 154, 191] };50;[4a, 40, ef, 29, 2f, 6d, 31, 5c, be, 30, 60, 3f, 84, 1b, cc, d8, b7, d5, 70, 2f, 6a, a4, 7d, 5e, 53, 7c, 5b, b8, 45, e5, fb, 89, fb, 89, ea, 9f, 77, 9a, f9, 12, 33, db, e6, 9a, bf, 00] +tcp_resp afd14a797c2bc5672ac0000000000000fe102a9171fb1ec1283a1c39b9be0bf7801db8dfe6801478dc1a1dfc56dddd2c409d5add0a83c1d64d866d9a002beda7f34fca3e0221736934023203172c15f5fc3307fd0cb7a229d5b24e732cd2c84a71a19c109eb8a73ca2ace7053ee1499c2b8d0718dff2e9d41f3967108c21314cf6ed751be9f09b0810222a05969abf779e52ed84f6c286de1934e9d3a0d2e5019105a42f2155f82046734410aa980163320ae8255035fd1c73fb7bd825b8d8237f2a8d6f1ed4e0a1d5f7c77fa848e351841f708195e68556e11cfbaa92b69fdffa16b35eed59c9ac7f8d57ace5012e6db05d3cdf1e354f262ef8a038442db6d35d6cd1c51976c82109ae879881fc6e00 accept +tcp_resp 3cc00000000001d03b80000020a5ba13729425bbbec48c7cd6b21974a55ffcbffb38047ce29eb77c5278e97edb00 ConnectTcpResp { status: 60, error_code: 118843, message: [165, 186, 19, 114, 148, 37, 187, 190, 196, 140, 124, 214, 178, 25, 116, 165, 95, 252, 191, 251, 56, 4, 124, 226, 158, 183, 124, 82, 120, 233, 126, 219] };46;[3c, 80, 01, d0, 3b, 20, a5, ba, 13, 72, 94, 25, bb, be, c4, 8c, 7c, d6, b2, 19, 74, a5, 5f, fc, bf, fb, 38, 04, 7c, e2, 9e, b7, 7c, 52, 78, e9, 7e, db, 00] +tcp_resp 970b4091f6fa2fa7251aa73b94df79cb65aeaad1ff7a4cab74739c551dad0d100299929f009d363edb9e5ae5cbab0d342f8b6333e3adc353cc241a9e3382d074f2920510cc4b93dc3b56f27774c5593edbd6652813e391a8fac9b7a61ac73976b48fe217fd3de956873601ee9729c65756c75dec5baf7bddc04b1496604ee04cf943a8dee9b883dd7c8a391e13a70b316a0c4c64fe8000000022 accept +tcp_resp 83802b3c6980000018e4e8807f6275651a7306238880dcdaf6fee977cb94610dcf00 ConnectTcpResp { status: 131, error_code: 2833513, message: [228, 232, 128, 127, 98, 117, 101, 26, 115, 6, 35, 136, 128, 220, 218, 246, 254, 233, 119, 203, 148, 97, 13, 207] };34;[83, 80, 2b, 3c, 69, 18, e4, e8, 80, 7f, 62, 75, 65, 1a, 73, 06, 23, 88, 80, dc, da, f6, fe, e9, 77, cb, 94, 61, 0d, cf, 00] +tcp_resp 7cc000000000001cb740e61e9948dc632d90149f52110b5b8ba6bcfb0a40a1e9b88c01901a85c5ce3a87abe3aedc3da4ad006b1bc68cf495c9c140521d43cfba2702d7747b8f022d7f2ced5983e1fad1263186b59cd92331e22fb3352cc2c6e9e0aa374218819ce3b43dd19c919f23849ef82b5a8c21dbd63f7cb7b487cb6945900f954a8a9fbeaa0c5d1b6da65c2fe18eb5868cc092f4a64b45328db28365e9b693c143632e9537036d6a997039a945c6086244d990de35b88aec1a523a3db7d666c8180076ca31cbe0507c67a23092eca6cbfca466db548731483abb996eeddefce24a13dcf361d919fdabdc72ff13a400 accept +tcp_resp 2e640a4041b042d3ca77c8711b979604ff73d136cdb1c889b883b5f9e204a850facd80e6876e54522ace010cedafcff4309f693daad905a39365ba0cc58d17e42c06f76222d9000b ConnectTcpResp { status: 46, error_code: 9226, message: [176, 66, 211, 202, 119, 200, 113, 27, 151, 150, 4, 255, 115, 209, 54, 205, 177, 200, 137, 184, 131, 181, 249, 226, 4, 168, 80, 250, 205, 128, 230, 135, 110, 84, 82, 42, 206, 1, 12, 237, 175, 207, 244, 48, 159, 105, 61, 170, 217, 5, 163, 147, 101, 186, 12, 197, 141, 23, 228, 44, 6, 247, 98, 34, 217] };71;[2e, 64, 0a, 40, 41, b0, 42, d3, ca, 77, c8, 71, 1b, 97, 96, 04, ff, 73, d1, 36, cd, b1, c8, 89, b8, 83, b5, f9, e2, 04, a8, 50, fa, cd, 80, e6, 87, 6e, 54, 52, 2a, ce, 01, 0c, ed, af, cf, f4, 30, 9f, 69, 3d, aa, d9, 05, a3, 93, 65, ba, 0c, c5, 8d, 17, e4, 2c, 06, f7, 62, 22, d9, 00] +tcp_resp 3b80943c2640b40751bfddcd56cf66086ebb9fac54c28c1a6621205e7348313c0424abee1fb2fd7f46332c4ab200132b5b7acf662a4b9a10853a82c149af5d599a94ff082eb7acd18851df44bdb90537ae685fc53737f1d89cf94999ee348511904fb99ca5b485674d32ff76fe42686e77768bed546aa273e2c834fd73c6d2d61bfd552b20fdde2345a11b326bc132dcfc77f37af75608986a1ec108af61961a9b1728783de9b4e2851b75249ea5ded5f0a98cc12929e6b5b20ac04000 accept +tcp_resp a480004e44c0000000000000db1e1a4107de6945bb97758ccef80fd2eaddfd18ce1a515a4c770fd2964a1ec541aab37909f01555f44703fbae86d09d89e3ad049951c098020afcea7d8092f1c4982d2a8d7eb52bf6b4755a9b6690ecc4aad5619938290a4e17523a223c4da6556919dfbb139e53e931c94ec1577e124676df96d102abceedcd25c24fd949b33f3e4f864de173eca779cfbd40bb24f94c410d450d66bcd124906fec13fb63823ae46f4302bbccb77420938cfcfd1393c239521740a4bb1ab7e37d37d6f6b3a6c9bbdc4f56a14cd7d8fea8bda06c83f250e42a61d6632a04c88a3a294000 ConnectTcpResp { status: 164, error_code: 20036, message: [30, 26, 65, 7, 222, 105, 69, 187, 151, 117, 140, 206, 248, 15, 210, 234, 221, 253, 24, 206, 26, 81, 90, 76, 119, 15, 210, 150, 74, 30, 197, 65, 170, 179, 121, 9, 240, 21, 85, 244, 71, 3, 251, 174, 134, 208, 157, 137, 227, 173, 4, 153, 81, 192, 152, 2, 10, 252, 234, 125, 128, 146, 241, 196, 152, 45, 42, 141, 126, 181, 43, 246, 180, 117, 90, 155, 102, 144, 236, 196, 170, 213, 97, 153, 56, 41, 10, 78, 23, 82, 58, 34, 60, 77, 166, 85, 105, 25, 223, 187, 19, 158, 83, 233, 49, 201, 78, 193, 87, 126, 18, 70, 118, 223, 150, 209, 2, 171, 206, 237, 205, 37, 194, 79, 217, 73, 179, 63, 62, 79, 134, 77, 225, 115, 236, 167, 121, 207, 189, 64, 187, 36, 249, 76, 65, 13, 69, 13, 102, 188, 209, 36, 144, 111, 236, 19, 251, 99, 130, 58, 228, 111, 67, 2, 187, 204, 183, 116, 32, 147, 140, 252, 253, 19, 147, 194, 57, 82, 23, 64, 164, 187, 26, 183, 227, 125, 55, 214, 246, 179, 166, 201, 187, 220, 79, 86, 161, 76, 215, 216, 254, 168, 189, 160, 108, 131, 242, 80, 228, 42, 97, 214, 99, 42, 4, 200, 138, 58, 41] };234;[a4, 80, 00, 4e, 44, 40, db, 1e, 1a, 41, 07, de, 69, 45, bb, 97, 75, 8c, ce, f8, 0f, d2, ea, dd, fd, 18, ce, 1a, 51, 5a, 4c, 77, 0f, d2, 96, 4a, 1e, c5, 41, aa, b3, 79, 09, f0, 15, 55, f4, 47, 03, fb, ae, 86, d0, 9d, 89, e3, ad, 04, 99, 51, c0, 98, 02, 0a, fc, ea, 7d, 80, 92, f1, c4, 98, 2d, 2a, 8d, 7e, b5, 2b, f6, b4, 75, 5a, 9b, 66, 90, ec, c4, aa, d5, 61, 99, 38, 29, 0a, 4e, 17, 52, 3a, 22, 3c, 4d, a6, 55, 69, 19, df, bb, 13, 9e, 53, e9, 31, c9, 4e, c1, 57, 7e, 12, 46, 76, df, 96, d1, 02, ab, ce, ed, cd, 25, c2, 4f, d9, 49, b3, 3f, 3e, 4f, 86, 4d, e1, 73, ec, a7, 79, cf, bd, 40, bb, 24, f9, 4c, 41, 0d, 45, 0d, 66, bc, d1, 24, 90, 6f, ec, 13, fb, 63, 82, 3a, e4, 6f, 43, 02, bb, cc, b7, 74, 20, 93, 8c, fc, fd, 13, 93, c2, 39, 52, 17, 40, a4, bb, 1a, b7, e3, 7d, 37, d6, f6, b3, a6, c9, bb, dc, 4f, 56, a1, 4c, d7, d8, fe, a8, bd, a0, 6c, 83, f2, 50, e4, 2a, 61, d6, 63, 2a, 04, c8, 8a, 3a, 29, 00] +tcp_resp 08c00000000003a4b3c000000000000039754cb5e45ce230b7886622e44111493dcd1880f2e731a0c9f1d1b1f381ab800a25dad3e8da87f4d49cb0d2103775714c9ba0723dddbec2d3d980000000 ConnectTcpResp { status: 8, error_code: 238771, message: [117, 76, 181, 228, 92, 226, 48, 183, 136, 102, 34, 228, 65, 17, 73, 61, 205, 24, 128, 242, 231, 49, 160, 201, 241, 209, 177, 243, 129, 171, 128, 10, 37, 218, 211, 232, 218, 135, 244, 212, 156, 176, 210, 16, 55, 117, 113, 76, 155, 160, 114, 61, 221, 190, 194, 211, 217] };78;[08, 80, 03, a4, b3, 39, 75, 4c, b5, e4, 5c, e2, 30, b7, 88, 66, 22, e4, 41, 11, 49, 3d, cd, 18, 80, f2, e7, 31, a0, c9, f1, d1, b1, f3, 81, ab, 80, 0a, 25, da, d3, e8, da, 87, f4, d4, 9c, b0, d2, 10, 37, 75, 71, 4c, 9b, a0, 72, 3d, dd, be, c2, d3, d9, 00] +tcp_resp 948045e0fdc000000000000081ea4af16b3aa16247108990423f109153d514d2b7d9ec2c877bba926ec275bc0e35a0ebafe9575d248e5bbda7d0e6c11a41726b868def372a9b9b0eb7a6be5aef6c75a505131aa6635e0530133c0d7aec27f9993cff1bfa676ef304018c622956ddf98f17a6662978f5a75a37875511dc0cf70e279590a25ced79de8c2f425d10dcc0000000000000009a81e6c4 ConnectTcpResp { status: 148, error_code: 4579581, message: [234, 74, 241, 107, 58, 161, 98, 71, 16, 137, 144, 66, 63, 16, 145, 83, 213, 20, 210, 183, 217, 236, 44, 135, 123, 186, 146, 110, 194, 117, 188, 14, 53, 160, 235, 175, 233, 87, 93, 36, 142, 91, 189, 167, 208, 230, 193, 26, 65, 114, 107, 134, 141, 239, 55, 42, 155, 155, 14, 183, 166, 190, 90, 239, 108, 117, 165, 5, 19, 26, 166, 99, 94, 5, 48, 19, 60, 13, 122, 236, 39, 249, 153, 60, 255, 27, 250, 103, 110, 243, 4, 1, 140, 98, 41, 86, 221, 249, 143, 23, 166, 102, 41, 120, 245, 167, 90, 55, 135, 85, 17, 220, 12, 247, 14, 39, 149, 144, 162, 92, 237, 121, 222, 140, 47, 66, 93, 16, 220] };150;[94, 80, 45, e0, fd, 40, 81, ea, 4a, f1, 6b, 3a, a1, 62, 47, 10, 89, 90, 42, 3f, 10, 91, 53, d5, 14, d2, b7, d9, ec, 2c, 87, 7b, ba, 92, 6e, c2, 75, bc, 0e, 35, a0, eb, af, e9, 57, 5d, 24, 8e, 5b, bd, a7, d0, e6, c1, 1a, 41, 72, 6b, 86, 8d, ef, 37, 2a, 9b, 9b, 0e, b7, a6, be, 5a, ef, 6c, 75, a5, 05, 13, 1a, a6, 63, 5e, 05, 30, 13, 3c, 0d, 7a, ec, 27, f9, 99, 3c, ff, 1b, fa, 67, 6e, f3, 04, 01, 8c, 62, 29, 56, dd, f9, 8f, 17, a6, 66, 29, 78, f5, a7, 5a, 37, 87, 55, 11, dc, 0c, f7, 0e, 27, 95, 90, a2, 5c, ed, 79, de, 8c, 2f, 42, 5d, 10, dc, 00] +tcp_resp a38000a904404c4f443c34a1d5ac86dc11a98015ebb31097c0d0663a23eb7d16dfb5996c9e18aadb664f963011a6a72af271ff8b61e0a6aef8288cac5a8624335112a0bfd30cb5c34086881be36bfcd93d8bb60ca588de4bb8140b659ff8a80e ConnectTcpResp { status: 163, error_code: 43268, message: [79, 68, 60, 52, 161, 213, 172, 134, 220, 17, 169, 128, 21, 235, 179, 16, 151, 192, 208, 102, 58, 35, 235, 125, 22, 223, 181, 153, 108, 158, 24, 170, 219, 102, 79, 150, 48, 17, 166, 167, 42, 242, 113, 255, 139, 97, 224, 166, 174, 248, 40, 140, 172, 90, 134, 36, 51, 81, 18, 160, 191, 211, 12, 181, 195, 64, 134, 136, 27, 227, 107, 252, 217, 61, 139, 182] };96;[a3, 80, 00, a9, 04, 40, 4c, 4f, 44, 3c, 34, a1, d5, ac, 86, dc, 11, a9, 80, 15, eb, b3, 10, 97, c0, d0, 66, 3a, 23, eb, 7d, 16, df, b5, 99, 6c, 9e, 18, aa, db, 66, 4f, 96, 30, 11, a6, a7, 2a, f2, 71, ff, 8b, 61, e0, a6, ae, f8, 28, 8c, ac, 5a, 86, 24, 33, 51, 12, a0, bf, d3, 0c, b5, c3, 40, 86, 88, 1b, e3, 6b, fc, d9, 3d, 8b, b6, 00] +tcp_resp d0c000000000033f468000007c131d0137c7afb5d9c4cb80bc227673abef2c98f3f13c7568df33c33a205ee7cdd153b51450e42be4d0a3f9d8367de79101d1f98b458d56819364e5705259c8b5239d266d662ba4cc6df93a77ccb8df1309a6a85fbaa961347ad202abb3275ffdd8812da9441e408e27de660a6d9e91118c411d8e0fbe01bdd0863f5a4000 ConnectTcpResp { status: 208, error_code: 212806, message: [19, 29, 1, 55, 199, 175, 181, 217, 196, 203, 128, 188, 34, 118, 115, 171, 239, 44, 152, 243, 241, 60, 117, 104, 223, 51, 195, 58, 32, 94, 231, 205, 209, 83, 181, 20, 80, 228, 43, 228, 208, 163, 249, 216, 54, 125, 231, 145, 1, 209, 249, 139, 69, 141, 86, 129, 147, 100, 229, 112, 82, 89, 200, 181, 35, 157, 38, 109, 102, 43, 164, 204, 109, 249, 58, 119, 204, 184, 223, 19, 9, 166, 168, 95, 186, 169, 97, 52, 122, 210, 2, 171, 179, 39, 95, 253, 216, 129, 45, 169, 68, 30, 64, 142, 39, 222, 102, 10, 109, 158, 145, 17, 140, 65, 29, 142, 15, 190, 1, 189, 208, 134, 63, 90] };139;[d0, 80, 03, 3f, 46, 40, 7c, 13, 1d, 01, 37, c7, af, b5, d9, c4, cb, 80, bc, 22, 76, 73, ab, ef, 2c, 98, f3, f1, 3c, 75, 68, df, 33, c3, 3a, 20, 5e, e7, cd, d1, 53, b5, 14, 50, e4, 2b, e4, d0, a3, f9, d8, 36, 7d, e7, 91, 01, d1, f9, 8b, 45, 8d, 56, 81, 93, 64, e5, 70, 52, 59, c8, b5, 23, 9d, 26, 6d, 66, 2b, a4, cc, 6d, f9, 3a, 77, cc, b8, df, 13, 09, a6, a8, 5f, ba, a9, 61, 34, 7a, d2, 02, ab, b3, 27, 5f, fd, d8, 81, 2d, a9, 44, 1e, 40, 8e, 27, de, 66, 0a, 6d, 9e, 91, 11, 8c, 41, 1d, 8e, 0f, be, 01, bd, d0, 86, 3f, 5a, 00] +tcp_resp ea801fcb43c00000000000008b75508a23773a769913542fff46950543e85810961cf87a4f5425f66184df58611be5825dbafda26ca7314b24939fee8cffec929d47e72e444584535908ec4b20a9eac1a89a68eed3a63d65c92184b0ebd94ba48e5e4c7e5b4f3273a38d073880582372750d496d4548e2bcdd4f5e7667ff9d93fbfa560397772e6587f215fa0c80868b216c9295a7ae44244000 ConnectTcpResp { status: 234, error_code: 2083651, message: [117, 80, 138, 35, 119, 58, 118, 153, 19, 84, 47, 255, 70, 149, 5, 67, 232, 88, 16, 150, 28, 248, 122, 79, 84, 37, 246, 97, 132, 223, 88, 97, 27, 229, 130, 93, 186, 253, 162, 108, 167, 49, 75, 36, 147, 159, 238, 140, 255, 236, 146, 157, 71, 231, 46, 68, 69, 132, 83, 89, 8, 236, 75, 32, 169, 234, 193, 168, 154, 104, 238, 211, 166, 61, 101, 201, 33, 132, 176, 235, 217, 75, 164, 142, 94, 76, 126, 91, 79, 50, 115, 163, 141, 7, 56, 128, 88, 35, 114, 117, 13, 73, 109, 69, 72, 226, 188, 221, 79, 94, 118, 103, 255, 157, 147, 251, 250, 86, 3, 151, 119, 46, 101, 135, 242, 21, 250, 12, 128, 134, 139, 33, 108, 146, 149, 167, 174, 68, 36] };154;[ea, 80, 1f, cb, 43, 40, 8b, 75, 50, 8a, 23, 77, 3a, 76, 99, 13, 54, 2f, ff, 46, 95, 05, 43, e8, 58, 10, 96, 1c, f8, 7a, 4f, 54, 25, f6, 61, 84, df, 58, 61, 1b, e5, 82, 5d, ba, fd, a2, 6c, a7, 31, 4b, 24, 93, 9f, ee, 8c, ff, ec, 92, 9d, 47, e7, 2e, 44, 45, 84, 53, 59, 08, ec, 4b, 20, a9, ea, c1, a8, 9a, 68, ee, d3, a6, 3d, 65, c9, 21, 84, b0, eb, d9, 4b, a4, 8e, 5e, 4c, 7e, 5b, 4f, 32, 73, a3, 8d, 07, 38, 80, 58, 23, 72, 75, 0d, 49, 6d, 45, 48, e2, bc, dd, 4f, 5e, 76, 67, ff, 9d, 93, fb, fa, 56, 03, 97, 77, 2e, 65, 87, f2, 15, fa, 0c, 80, 86, 8b, 21, 6c, 92, 95, a7, ae, 44, 24, 00] +tcp_resp 50c00000003f4625618000000ebce1e6936c64d987c915cb244ae180000000 ConnectTcpResp { status: 80, error_code: 1061561697, message: [188, 225, 230, 147, 108, 100, 217, 135, 201, 21, 203, 36, 74, 225] };31;[50, bf, 46, 25, 61, 0e, bc, e1, e6, 93, 6c, 64, d9, 87, c9, 15, cb, 24, 4a, e1, 00] +tcp_resp bcda90ba50a56d04bb40936cd17c3b4cfa9aba70cda7b939cb52d97f70ef53593eff40019b9eff39e9469840f24caf20f09c388d6090d1e9b00f261c68323daaf58d284cb1dcab88edea9acaa6fe83e2e79f329eba9a29b68744b25a82a24948f23852fc8c3bb5199533a207688867f00e85effcc53e8524fa2d82d2759d7de1d48bc2c11fdb384b9f5157e23bf9046223492239884c19fa9d7ef651213c003277c0 accept +tcp_resp e78000000740fcc4ad05f250fc01c3373898cfba5fcbb661e090aef9123239c72309014dd274b8b9ed60b5a2130c2c715afb62ad9c7004bbdd3864f368f2aae5c9b363115453503c636c8dc2ee6d16549ea8b226414e6213389fcf9edcb8d30891f2a14fdfc8aa373ce8aa4497e690e36986be6ca0cf34057edafc432d06176226dae286e9b5517243569e1b1e1ffcd69d05cfda022709a152b399b4758df5b53b396dfc1eeca665867d0fa3c654bfb512c37a34b2df238f37e10efc20f7d9baea9fe5c4f5220d65c50afeb50c420260ad82ca9834c211f5feffcbe15252a3530cc70a6098ec01aa61f5247aae5ee8fba3545b79b9c51567e6479f213a8ec0a823d25200ca297b9611 ConnectTcpResp { status: 231, error_code: 7, message: [196, 173, 5, 242, 80, 252, 1, 195, 55, 56, 152, 207, 186, 95, 203, 182, 97, 224, 144, 174, 249, 18, 50, 57, 199, 35, 9, 1, 77, 210, 116, 184, 185, 237, 96, 181, 162, 19, 12, 44, 113, 90, 251, 98, 173, 156, 112, 4, 187, 221, 56, 100, 243, 104, 242, 170, 229, 201, 179, 99, 17, 84, 83, 80, 60, 99, 108, 141, 194, 238, 109, 22, 84, 158, 168, 178, 38, 65, 78, 98, 19, 56, 159, 207, 158, 220, 184, 211, 8, 145, 242, 161, 79, 223, 200, 170, 55, 60, 232, 170, 68, 151, 230, 144, 227, 105, 134, 190, 108, 160, 207, 52, 5, 126, 218, 252, 67, 45, 6, 23, 98, 38, 218, 226, 134, 233, 181, 81, 114, 67, 86, 158, 27, 30, 31, 252, 214, 157, 5, 207, 218, 2, 39, 9, 161, 82, 179, 153, 180, 117, 141, 245, 181, 59, 57, 109, 252, 30, 236, 166, 101, 134, 125, 15, 163, 198, 84, 191, 181, 18, 195, 122, 52, 178, 223, 35, 143, 55, 225, 14, 252, 32, 247, 217, 186, 234, 159, 229, 196, 245, 34, 13, 101, 197, 10, 254, 181, 12, 66, 2, 96, 173, 130, 202, 152, 52, 194, 17, 245, 254, 255, 203, 225, 82, 82, 163, 83, 12, 199, 10, 96, 152, 236, 1, 170, 97, 245, 36, 122, 174, 94, 232, 251, 163, 84, 91, 121, 185, 197, 21, 103, 230, 71, 159, 33, 58, 142, 192, 168, 35, 210, 82] };260;[e7, 07, 40, fc, c4, ad, 05, f2, 50, fc, 01, c3, 37, 38, 98, cf, ba, 5f, cb, b6, 61, e0, 90, ae, f9, 12, 32, 39, c7, 23, 09, 01, 4d, d2, 74, b8, b9, ed, 60, b5, a2, 13, 0c, 2c, 71, 5a, fb, 62, ad, 9c, 70, 04, bb, dd, 38, 64, f3, 68, f2, aa, e5, c9, b3, 63, 11, 54, 53, 50, 3c, 63, 6c, 8d, c2, ee, 6d, 16, 54, 9e, a8, b2, 26, 41, 4e, 62, 13, 38, 9f, cf, 9e, dc, b8, d3, 08, 91, f2, a1, 4f, df, c8, aa, 37, 3c, e8, aa, 44, 97, e6, 90, e3, 69, 86, be, 6c, a0, cf, 34, 05, 7e, da, fc, 43, 2d, 06, 17, 62, 26, da, e2, 86, e9, b5, 51, 72, 43, 56, 9e, 1b, 1e, 1f, fc, d6, 9d, 05, cf, da, 02, 27, 09, a1, 52, b3, 99, b4, 75, 8d, f5, b5, 3b, 39, 6d, fc, 1e, ec, a6, 65, 86, 7d, 0f, a3, c6, 54, bf, b5, 12, c3, 7a, 34, b2, df, 23, 8f, 37, e1, 0e, fc, 20, f7, d9, ba, ea, 9f, e5, c4, f5, 22, 0d, 65, c5, 0a, fe, b5, 0c, 42, 02, 60, ad, 82, ca, 98, 34, c2, 11, f5, fe, ff, cb, e1, 52, 52, a3, 53, 0c, c7, 0a, 60, 98, ec, 01, aa, 61, f5, 24, 7a, ae, 5e, e8, fb, a3, 54, 5b, 79, b9, c5, 15, 67, e6, 47, 9f, 21, 3a, 8e, c0, a8, 23, d2, 52, 00] +tcp_resp 5de64281665611de6e401a7532baf77033e3288975f59dd476c52349eb26ed5aabee05d46fc000000000000000a20247 accept +tcp_resp be4226406a35ae09f08208eafac0729a161114523eb1820c43cee81a7e4b13b659f54d75f051d4d63b14ce6355d1b94118170257e341484001b30e3f7c670b49014b5e3e601fec09b981d5183c5a271c14a547e099874c37e39650b61d4244b6f65b085855228fa4466715b07bfa36c000000000000000 ConnectTcpResp { status: 190, error_code: 550, message: [53, 174, 9, 240, 130, 8, 234, 250, 192, 114, 154, 22, 17, 20, 82, 62, 177, 130, 12, 67, 206, 232, 26, 126, 75, 19, 182, 89, 245, 77, 117, 240, 81, 212, 214, 59, 20, 206, 99, 85, 209, 185, 65, 24, 23, 2, 87, 227, 65, 72, 64, 1, 179, 14, 63, 124, 103, 11, 73, 1, 75, 94, 62, 96, 31, 236, 9, 185, 129, 213, 24, 60, 90, 39, 28, 20, 165, 71, 224, 153, 135, 76, 55, 227, 150, 80, 182, 29, 66, 68, 182, 246, 91, 8, 88, 85, 34, 143, 164, 70, 103, 21, 176, 123, 250, 54] };119;[be, 42, 26, 40, 6a, 35, ae, 09, f0, 82, 08, ea, fa, c0, 72, 9a, 16, 11, 14, 52, 3e, b1, 82, 0c, 43, ce, e8, 1a, 7e, 4b, 13, b6, 59, f5, 4d, 75, f0, 51, d4, d6, 3b, 14, ce, 63, 55, d1, b9, 41, 18, 17, 02, 57, e3, 41, 48, 40, 01, b3, 0e, 3f, 7c, 67, 0b, 49, 01, 4b, 5e, 3e, 60, 1f, ec, 09, b9, 81, d5, 18, 3c, 5a, 27, 1c, 14, a5, 47, e0, 99, 87, 4c, 37, e3, 96, 50, b6, 1d, 42, 44, b6, f6, 5b, 08, 58, 55, 22, 8f, a4, 46, 67, 15, b0, 7b, fa, 36, 00] +tcp_resp 88800aaf0b8000000625535af6dfe9070a024d6ff96603 ConnectTcpResp { status: 136, error_code: 700171, message: [37, 83, 90, 246, 223, 233] };23;[88, 80, 0a, af, 0b, 06, 25, 53, 5a, f6, df, e9, 00] +tcp_resp 4c80000003404c926760e99d07e82eef9e3033b5e16e85ed6f87b9734bfb2829c64cb6b49b0feda0aa9348b861a1134cc896d8ae572e837a5dc00ff0c691c8d0bfe1bddda36a91de6108137b6eb80606843135c000000000000000 ConnectTcpResp { status: 76, error_code: 3, message: [146, 103, 96, 233, 157, 7, 232, 46, 239, 158, 48, 51, 181, 225, 110, 133, 237, 111, 135, 185, 115, 75, 251, 40, 41, 198, 76, 182, 180, 155, 15, 237, 160, 170, 147, 72, 184, 97, 161, 19, 76, 200, 150, 216, 174, 87, 46, 131, 122, 93, 192, 15, 240, 198, 145, 200, 208, 191, 225, 189, 221, 163, 106, 145, 222, 97, 8, 19, 123, 110, 184, 6, 6, 132, 49, 53] };91;[4c, 03, 40, 4c, 92, 67, 60, e9, 9d, 07, e8, 2e, ef, 9e, 30, 33, b5, e1, 6e, 85, ed, 6f, 87, b9, 73, 4b, fb, 28, 29, c6, 4c, b6, b4, 9b, 0f, ed, a0, aa, 93, 48, b8, 61, a1, 13, 4c, c8, 96, d8, ae, 57, 2e, 83, 7a, 5d, c0, 0f, f0, c6, 91, c8, d0, bf, e1, bd, dd, a3, 6a, 91, de, 61, 08, 13, 7b, 6e, b8, 06, 06, 84, 31, 35, 00] +tcp_resp 55c000000000000000800000851700686eb64a77a728b31819c510cadbf14c995403a1b95eefd42e0a2af228847079cc577b1ddd48cadf2d5222e9309f8d60a768498a0a8097f9aab5e6c089148493c0684a1727ef6f939fc330cfa1f5b2bb389f68fd09a0567dc08a55bc687e5cd466a1696cc579dc4cd9121ae07c82b9392b8e7e03fea26a371ecbd417a1b7b2264f51cc80000000 accept +tcp_resp 9780c6b31c40ad92524c0a08389e5fd308c9baa8686e4bf3a0afc332094878237748db0fa24a0dd474218f7872be05198a943df5cfe1dc0f534ed3073f14c381831c85350327ed555e1694ac34a0c141b73cce81ba71333960eed42cebe07df733e07378b07d294d4e394ec6815bea36a195d00d0d701f52ecd482b8bd494c11d3fb998590f9b1b51b191265945ebbcfdd6778d10a26d58fc65bfbb7b129c031826196beed3182088b90fdcaf822c2de90fbdee180000000833e ConnectTcpResp { status: 151, error_code: 13021980, message: [146, 82, 76, 10, 8, 56, 158, 95, 211, 8, 201, 186, 168, 104, 110, 75, 243, 160, 175, 195, 50, 9, 72, 120, 35, 119, 72, 219, 15, 162, 74, 13, 212, 116, 33, 143, 120, 114, 190, 5, 25, 138, 148, 61, 245, 207, 225, 220, 15, 83, 78, 211, 7, 63, 20, 195, 129, 131, 28, 133, 53, 3, 39, 237, 85, 94, 22, 148, 172, 52, 160, 193, 65, 183, 60, 206, 129, 186, 113, 51, 57, 96, 238, 212, 44, 235, 224, 125, 247, 51, 224, 115, 120, 176, 125, 41, 77, 78, 57, 78, 198, 129, 91, 234, 54, 161, 149, 208, 13, 13, 112, 31, 82, 236, 212, 130, 184, 189, 73, 76, 17, 211, 251, 153, 133, 144, 249, 177, 181, 27, 25, 18, 101, 148, 94, 187, 207, 221, 103, 120, 209, 10, 38, 213, 143, 198, 91, 251, 183, 177, 41, 192, 49, 130, 97, 150, 190, 237, 49, 130, 8, 139, 144, 253, 202, 248, 34, 194, 222, 144, 251, 222, 225] };184;[97, 80, c6, b3, 1c, 40, ad, 92, 52, 4c, 0a, 08, 38, 9e, 5f, d3, 08, c9, ba, a8, 68, 6e, 4b, f3, a0, af, c3, 32, 09, 48, 78, 23, 77, 48, db, 0f, a2, 4a, 0d, d4, 74, 21, 8f, 78, 72, be, 05, 19, 8a, 94, 3d, f5, cf, e1, dc, 0f, 53, 4e, d3, 07, 3f, 14, c3, 81, 83, 1c, 85, 35, 03, 27, ed, 55, 5e, 16, 94, ac, 34, a0, c1, 41, b7, 3c, ce, 81, ba, 71, 33, 39, 60, ee, d4, 2c, eb, e0, 7d, f7, 33, e0, 73, 78, b0, 7d, 29, 4d, 4e, 39, 4e, c6, 81, 5b, ea, 36, a1, 95, d0, 0d, 0d, 70, 1f, 52, ec, d4, 82, b8, bd, 49, 4c, 11, d3, fb, 99, 85, 90, f9, b1, b5, 1b, 19, 12, 65, 94, 5e, bb, cf, dd, 67, 78, d1, 0a, 26, d5, 8f, c6, 5b, fb, b7, b1, 29, c0, 31, 82, 61, 96, be, ed, 31, 82, 08, 8b, 90, fd, ca, f8, 22, c2, de, 90, fb, de, e1, 00] +tcp_resp a6800fdc5d80000100831e014ea48948634511c9973f97dcdf448a617e573df879534aad52d111755d5928518cabf14e6d3b73cda68489a24e400ee9528dca53aaf68105475351072ac4db2acd98fd7538722a14174589e1b1d97283c9c22ff3361bc518014581ad195bfdca8ae01a443d3e0be03d90e62bf70cf549835f9acc4df573165d5d7ccee0bb521c96b615267e5fdd5030c9c48518a360ccc0472f1e1ae353d9200df96f45ba20f11511396ec0b86d4b9b9a07d55523cfd48a3a1c72f25ff424c07ef49d5c9b680a751e8745dbd1b22be5374d98f1f2cb06b8a5ff6cf45737a9f8117fbe842e13a5e39f6b09f9dbfc992555de3b66a57803b57bd9c7cf34ba6729559ccebd80000000 reject +tcp_resp 3a8000bc4fc00000000000001b3ef64e41176d01a9ed53a2fbbbe426e7ed24122bf5f65a11fa931a0620655df59f8d ConnectTcpResp { status: 58, error_code: 48207, message: [62, 246, 78, 65, 23, 109, 1, 169, 237, 83, 162, 251, 187, 228, 38, 231, 237, 36, 18, 43, 245, 246, 90, 17, 250, 147, 26] };47;[3a, 80, 00, bc, 4f, 1b, 3e, f6, 4e, 41, 17, 6d, 01, a9, ed, 53, a2, fb, bb, e4, 26, e7, ed, 24, 12, 2b, f5, f6, 5a, 11, fa, 93, 1a, 00] +tcp_resp 8ac00000000001be33c0000000000001015f143619d2ad7abea4c5c6c11649d73f299ed11f3e49d947db287542c1fa504c67bc5516d46d0d0a806f203a6001d5cd09814dd3250360dd59b45f58d002649d45126762e07f75a62dce2cd92032165687c52ede4635820694edd6254b0166c7f12fbfd2b19d3a4c02ee939beb07dea81b5376a1da612ce5d57237f92e638cba2fdcadeb629abed0f912f0622ce4687f120f424d6a715dab237434641ee707a82014eed2608372abff9520564a91f1d07f1d1486b66961f041a5159aac1ff8742ae0f5b58abe06ec028474bca80ac04ee2cb4aa64b39c7759768bc602a6299311c771d2cb39a288445ae5cd3c46a336d69b65d2926cf9e634ef88d796f4586fc634000 reject +tcp_resp 3f8000002e800000d07a0ca91e5b752dda2621fe634f5b503cd2b2751f3dc0fe63a7cfa00256e31813a32c6392c62d6502a98427d110dc343d67ce809ea5d86696d810f429389a4da37cfa4d476823ba07d325588a4d0df4281cf64ff08f42e7d3a896232d68ec1b9388043d2e64c848e920c10bb5c4bfab22116ee0d2f50cd791a16625494ca492f0f7b00d58107be583ba4ce9772546ee97e481c9d7761e793602daed343c69bd659d4857385134cb7cb7ba0199e0284662228e996665021b033d534d42f8ec63c0d3981f35611f07ad8ead1c014e0c8cf94000 ConnectTcpResp { status: 63, error_code: 46, message: [122, 12, 169, 30, 91, 117, 45, 218, 38, 33, 254, 99, 79, 91, 80, 60, 210, 178, 117, 31, 61, 192, 254, 99, 167, 207, 160, 2, 86, 227, 24, 19, 163, 44, 99, 146, 198, 45, 101, 2, 169, 132, 39, 209, 16, 220, 52, 61, 103, 206, 128, 158, 165, 216, 102, 150, 216, 16, 244, 41, 56, 154, 77, 163, 124, 250, 77, 71, 104, 35, 186, 7, 211, 37, 88, 138, 77, 13, 244, 40, 28, 246, 79, 240, 143, 66, 231, 211, 168, 150, 35, 45, 104, 236, 27, 147, 136, 4, 61, 46, 100, 200, 72, 233, 32, 193, 11, 181, 196, 191, 171, 34, 17, 110, 224, 210, 245, 12, 215, 145, 161, 102, 37, 73, 76, 164, 146, 240, 247, 176, 13, 88, 16, 123, 229, 131, 186, 76, 233, 119, 37, 70, 238, 151, 228, 129, 201, 215, 118, 30, 121, 54, 2, 218, 237, 52, 60, 105, 189, 101, 157, 72, 87, 56, 81, 52, 203, 124, 183, 186, 1, 153, 224, 40, 70, 98, 34, 142, 153, 102, 101, 2, 27, 3, 61, 83, 77, 66, 248, 236, 99, 192, 211, 152, 31, 53, 97, 31, 7, 173, 142, 173, 28, 1, 78, 12, 140, 249] };219;[3f, 2e, 40, d0, 7a, 0c, a9, 1e, 5b, 75, 2d, da, 26, 21, fe, 63, 4f, 5b, 50, 3c, d2, b2, 75, 1f, 3d, c0, fe, 63, a7, cf, a0, 02, 56, e3, 18, 13, a3, 2c, 63, 92, c6, 2d, 65, 02, a9, 84, 27, d1, 10, dc, 34, 3d, 67, ce, 80, 9e, a5, d8, 66, 96, d8, 10, f4, 29, 38, 9a, 4d, a3, 7c, fa, 4d, 47, 68, 23, ba, 07, d3, 25, 58, 8a, 4d, 0d, f4, 28, 1c, f6, 4f, f0, 8f, 42, e7, d3, a8, 96, 23, 2d, 68, ec, 1b, 93, 88, 04, 3d, 2e, 64, c8, 48, e9, 20, c1, 0b, b5, c4, bf, ab, 22, 11, 6e, e0, d2, f5, 0c, d7, 91, a1, 66, 25, 49, 4c, a4, 92, f0, f7, b0, 0d, 58, 10, 7b, e5, 83, ba, 4c, e9, 77, 25, 46, ee, 97, e4, 81, c9, d7, 76, 1e, 79, 36, 02, da, ed, 34, 3c, 69, bd, 65, 9d, 48, 57, 38, 51, 34, cb, 7c, b7, ba, 01, 99, e0, 28, 46, 62, 22, 8e, 99, 66, 65, 02, 1b, 03, 3d, 53, 4d, 42, f8, ec, 63, c0, d3, 98, 1f, 35, 61, 1f, 07, ad, 8e, ad, 1c, 01, 4e, 0c, 8c, f9, 00] +tcp_resp 194df440a40bb837ed1d603020cb1db1d04d1af9048cb42222b004914a03fdbca88332be6795c624b20fb51694429e5de0f7ae0bebcee4a4967aa901a9a2f4eb1c5d14c7b68c45cd7283609a7d0c7cd04d32d3baece982cce65676804c011b42c9d2b584d84567b463fcaf833ee00daa38e9db031976e6af3a67593e2bd8a24572d5bf3cab43411d2c5d5bb595a2f6a28c9ddeca6952d4b2749a3bdd05a3ddd06eb8dca75a12a314f3c0000000000000007983526bcbec ConnectTcpResp { status: 25, error_code: 3572, message: [11, 184, 55, 237, 29, 96, 48, 32, 203, 29, 177, 208, 77, 26, 249, 4, 140, 180, 34, 34, 176, 4, 145, 74, 3, 253, 188, 168, 131, 50, 190, 103, 149, 198, 36, 178, 15, 181, 22, 148, 66, 158, 93, 224, 247, 174, 11, 235, 206, 228, 164, 150, 122, 169, 1, 169, 162, 244, 235, 28, 93, 20, 199, 182, 140, 69, 205, 114, 131, 96, 154, 125, 12, 124, 208, 77, 50, 211, 186, 236, 233, 130, 204, 230, 86, 118, 128, 76, 1, 27, 66, 201, 210, 181, 132, 216, 69, 103, 180, 99, 252, 175, 131, 62, 224, 13, 170, 56, 233, 219, 3, 25, 118, 230, 175, 58, 103, 89, 62, 43, 216, 162, 69, 114, 213, 191, 60, 171, 67, 65, 29, 44, 93, 91, 181, 149, 162, 246, 162, 140, 157, 222, 202, 105, 82, 212, 178, 116, 154, 59, 221, 5, 163, 221, 208, 110, 184, 220, 167, 90, 18, 163, 20, 243] };177;[19, 4d, f4, 40, a4, 0b, b8, 37, ed, 1d, 60, 30, 20, cb, 1d, b1, d0, 4d, 1a, f9, 04, 8c, b4, 22, 22, b0, 04, 91, 4a, 03, fd, bc, a8, 83, 32, be, 67, 95, c6, 24, b2, 0f, b5, 16, 94, 42, 9e, 5d, e0, f7, ae, 0b, eb, ce, e4, a4, 96, 7a, a9, 01, a9, a2, f4, eb, 1c, 5d, 14, c7, b6, 8c, 45, cd, 72, 83, 60, 9a, 7d, 0c, 7c, d0, 4d, 32, d3, ba, ec, e9, 82, cc, e6, 56, 76, 80, 4c, 01, 1b, 42, c9, d2, b5, 84, d8, 45, 67, b4, 63, fc, af, 83, 3e, e0, 0d, aa, 38, e9, db, 03, 19, 76, e6, af, 3a, 67, 59, 3e, 2b, d8, a2, 45, 72, d5, bf, 3c, ab, 43, 41, 1d, 2c, 5d, 5b, b5, 95, a2, f6, a2, 8c, 9d, de, ca, 69, 52, d4, b2, 74, 9a, 3b, dd, 05, a3, dd, d0, 6e, b8, dc, a7, 5a, 12, a3, 14, f3, 00] +tcp_resp 3dc0000000000001f7c0000000000000d66f267dd48e6722b7a1c950812a3f9b5bd5c1586210be1a77b69e21e13f12f88c683c35ebd628b126a8f7223250838fee20d454c848a54a247f63b706c5e5b2eb9de47a86b25f51eea1601bcdd2ff584937557a0f0b6a3a9bd6c1ad7fc2d3c17ebee2d6861cb5f1eff94badfce357c7ffe0c35e32a7fac3391c9ea89620c8cfc2d9610f3774d0d0a148255fe78bc99fd1ae8e2af1a3a76db136553555727633b01b6fc69450773d3d508b1265e7ce1ac0561d230b5d0e772165c0c2b1199dbea518efa1fac41d0bc038bcd696faeca382038dc0c769fe00 ConnectTcpResp { status: 61, error_code: 503, message: [111, 38, 125, 212, 142, 103, 34, 183, 161, 201, 80, 129, 42, 63, 155, 91, 213, 193, 88, 98, 16, 190, 26, 119, 182, 158, 33, 225, 63, 18, 248, 140, 104, 60, 53, 235, 214, 40, 177, 38, 168, 247, 34, 50, 80, 131, 143, 238, 32, 212, 84, 200, 72, 165, 74, 36, 127, 99, 183, 6, 197, 229, 178, 235, 157, 228, 122, 134, 178, 95, 81, 238, 161, 96, 27, 205, 210, 255, 88, 73, 55, 85, 122, 15, 11, 106, 58, 155, 214, 193, 173, 127, 194, 211, 193, 126, 190, 226, 214, 134, 28, 181, 241, 239, 249, 75, 173, 252, 227, 87, 199, 255, 224, 195, 94, 50, 167, 250, 195, 57, 28, 158, 168, 150, 32, 200, 207, 194, 217, 97, 15, 55, 116, 208, 208, 161, 72, 37, 95, 231, 139, 201, 159, 209, 174, 142, 42, 241, 163, 167, 109, 177, 54, 85, 53, 85, 114, 118, 51, 176, 27, 111, 198, 148, 80, 119, 61, 61, 80, 139, 18, 101, 231, 206, 26, 192, 86, 29, 35, 11, 93, 14, 119, 33, 101, 192, 194, 177, 25, 157, 190, 165, 24, 239, 161, 250, 196, 29, 11, 192, 56, 188, 214, 150, 250, 236, 163, 130, 3, 141, 192, 199, 105, 254] };232;[3d, 41, f7, 40, d6, 6f, 26, 7d, d4, 8e, 67, 22, b7, a1, c9, 50, 81, 2a, 3f, 9b, 5b, d5, c1, 58, 62, 10, be, 1a, 77, b6, 9e, 21, e1, 3f, 12, f8, 8c, 68, 3c, 35, eb, d6, 28, b1, 26, a8, f7, 22, 32, 50, 83, 8f, ee, 20, d4, 54, c8, 48, a5, 4a, 24, 7f, 63, b7, 06, c5, e5, b2, eb, 9d, e4, 7a, 86, b2, 5f, 51, ee, a1, 60, 1b, cd, d2, ff, 58, 49, 37, 55, 7a, 0f, 0b, 6a, 3a, 9b, d6, c1, ad, 7f, c2, d3, c1, 7e, be, e2, d6, 86, 1c, b5, f1, ef, f9, 4b, ad, fc, e3, 57, c7, ff, e0, c3, 5e, 32, a7, fa, c3, 39, 1c, 9e, a8, 96, 20, c8, cf, c2, d9, 61, 0f, 37, 74, d0, d0, a1, 48, 25, 5f, e7, 8b, c9, 9f, d1, ae, 8e, 2a, f1, a3, a7, 6d, b1, 36, 55, 35, 55, 72, 76, 33, b0, 1b, 6f, c6, 94, 50, 77, 3d, 3d, 50, 8b, 12, 65, e7, ce, 1a, c0, 56, 1d, 23, 0b, 5d, 0e, 77, 21, 65, c0, c2, b1, 19, 9d, be, a5, 18, ef, a1, fa, c4, 1d, 0b, c0, 38, bc, d6, 96, fa, ec, a3, 82, 03, 8d, c0, c7, 69, fe, 00] +tcp_resp a1cd01bfed5deed73c4102fdf5ee44325734fc3b061bf27eeffc5a7803004413d4dae881462654e631960fa6c6828518b3ff37f189159b4f0164045a4afaf92fc31d8b076b70351276d28077e6a2004ac25a06e99d50d73529334e4560eb49aa86b363bbcea351e3de4eaaaead97693c67e77613c2523935284003f4a373b6d1a21d4a1a244ca9823b175c5066eb0a599efba6a02198f5fa0db8ce5a9297a3b4e7fa50bb33b58170b8f57c67540289bd894bb1e11cb95cc944970c021bf995a2899ad2c763556ac5ebf64458c6963cb8db143e1f668dd93b899083399c24f5c489732a85cb91a3420354197b334c7c43d50c4b2d358d19539738f05ecd4a815a4944648442fc13edf8bce4097203610fc6 reject +tcp_resp d6c000000000000001800000833a53545848140118b2e12130ea51a78da901445ce528969860f52701b780d71bff025c42c36ab0b206df459baddb2fdf0fa5b707567d2a0a795c378b259031cd61d39ab266740f3e9430b0cb0183e0b0e8047ec555f447d59714d0967bd4abe6e535e596898a11332f51f977f64fe7d0b3e164f89b4b2ad6f4acf727bbdb5e0376d07e80000000 ConnectTcpResp { status: 214, error_code: 1, message: [58, 83, 84, 88, 72, 20, 1, 24, 178, 225, 33, 48, 234, 81, 167, 141, 169, 1, 68, 92, 229, 40, 150, 152, 96, 245, 39, 1, 183, 128, 215, 27, 255, 2, 92, 66, 195, 106, 176, 178, 6, 223, 69, 155, 173, 219, 47, 223, 15, 165, 183, 7, 86, 125, 42, 10, 121, 92, 55, 139, 37, 144, 49, 205, 97, 211, 154, 178, 102, 116, 15, 62, 148, 48, 176, 203, 1, 131, 224, 176, 232, 4, 126, 197, 85, 244, 71, 213, 151, 20, 208, 150, 123, 212, 171, 230, 229, 53, 229, 150, 137, 138, 17, 51, 47, 81, 249, 119, 246, 79, 231, 208, 179, 225, 100, 248, 155, 75, 42, 214, 244, 172, 247, 39, 187, 219, 94, 3, 118, 208, 126] };148;[d6, 01, 40, 83, 3a, 53, 54, 58, 48, 14, 01, 18, b2, e1, 21, 30, ea, 51, a7, 8d, a9, 01, 44, 5c, e5, 28, 96, 98, 60, f5, 27, 01, b7, 80, d7, 1b, ff, 02, 5c, 42, c3, 6a, b0, b2, 06, df, 45, 9b, ad, db, 2f, df, 0f, a5, b7, 07, 56, 7d, 2a, 0a, 79, 5c, 37, 8b, 25, 90, 31, cd, 61, d3, 9a, b2, 66, 74, 0f, 3e, 94, 30, b0, cb, 01, 83, e0, b0, e8, 04, 7e, c5, 55, f4, 47, d5, 97, 14, d0, 96, 7b, d4, ab, e6, e5, 35, e5, 96, 89, 8a, 11, 33, 2f, 51, f9, 77, f6, 4f, e7, d0, b3, e1, 64, f8, 9b, 4b, 2a, d6, f4, ac, f7, 27, bb, db, 5e, 03, 76, d0, 7e, 00] +tcp_resp 8680000002405892c0b69ce3f661657f634f1209a45444b49e45e446dbd2d5c9abc875aa400999121bdc24d163192c5ea737338577f54d83f82708bc56c059def138dcf8cb04fa788b61abfaf6f41a69fcc99d57bca3513b6017ce433bdc124007945f2808e4239c ConnectTcpResp { status: 134, error_code: 2, message: [146, 192, 182, 156, 227, 246, 97, 101, 127, 99, 79, 18, 9, 164, 84, 68, 180, 158, 69, 228, 70, 219, 210, 213, 201, 171, 200, 117, 170, 64, 9, 153, 18, 27, 220, 36, 209, 99, 25, 44, 94, 167, 55, 51, 133, 119, 245, 77, 131, 248, 39, 8, 188, 86, 192, 89, 222, 241, 56, 220, 248, 203, 4, 250, 120, 139, 97, 171, 250, 246, 244, 26, 105, 252, 201, 157, 87, 188, 163, 81, 59, 96, 23, 206, 67, 59, 220, 18] };104;[86, 02, 40, 58, 92, c0, b6, 9c, e3, f6, 61, 65, 7f, 63, 4f, 12, 09, a4, 54, 44, b4, 9e, 45, e4, 46, db, d2, d5, c9, ab, c8, 75, aa, 40, 09, 99, 12, 1b, dc, 24, d1, 63, 19, 2c, 5e, a7, 37, 33, 85, 77, f5, 4d, 83, f8, 27, 08, bc, 56, c0, 59, de, f1, 38, dc, f8, cb, 04, fa, 78, 8b, 61, ab, fa, f6, f4, 1a, 69, fc, c9, 9d, 57, bc, a3, 51, 3b, 60, 17, ce, 43, 3b, dc, 12, 00] +tcp_resp b94002407fa81d243b9b44816891edcfb592c6034d5f76dc7dcf6119aa92367bf7e967c34f5d2434ab88f817a4ffc943fc9e1c6251721b8050462129af36f2e07dd488d01bd24180d8dcbdf45fb976f5e295d2274672c611c1b3120bf23b952970855a20d88f5df0f6abb798f390eec64e758e340684b0c4979287ef275d84415822dc6180000000 ConnectTcpResp { status: 185, error_code: 2, message: [168, 29, 36, 59, 155, 68, 129, 104, 145, 237, 207, 181, 146, 198, 3, 77, 95, 118, 220, 125, 207, 97, 25, 170, 146, 54, 123, 247, 233, 103, 195, 79, 93, 36, 52, 171, 136, 248, 23, 164, 255, 201, 67, 252, 158, 28, 98, 81, 114, 27, 128, 80, 70, 33, 41, 175, 54, 242, 224, 125, 212, 136, 208, 27, 210, 65, 128, 216, 220, 189, 244, 95, 185, 118, 245, 226, 149, 210, 39, 70, 114, 198, 17, 193, 179, 18, 11, 242, 59, 149, 41, 112, 133, 90, 32, 216, 143, 93, 240, 246, 171, 183, 152, 243, 144, 238, 198, 78, 117, 142, 52, 6, 132, 176, 196, 151, 146, 135, 239, 39, 93, 132, 65, 88, 34, 220, 97] };136;[b9, 02, 40, 7f, a8, 1d, 24, 3b, 9b, 44, 81, 68, 91, ed, cf, b5, 92, c6, 03, 4d, 5f, 76, dc, 7d, cf, 61, 19, aa, 92, 36, 7b, f7, e9, 67, c3, 4f, 5d, 24, 34, ab, 88, f8, 17, a4, ff, c9, 43, fc, 9e, 1c, 62, 51, 72, 1b, 80, 50, 46, 21, 29, af, 36, f2, e0, 7d, d4, 88, d0, 1b, d2, 41, 80, d8, dc, bd, f4, 5f, b9, 76, f5, e2, 95, d2, 27, 46, 72, c6, 11, c1, b3, 12, 0b, f2, 3b, 95, 29, 70, 85, 5a, 20, d8, 8f, 5d, f0, f6, ab, b7, 98, f3, 90, ee, c6, 4e, 75, 8e, 34, 06, 84, b0, c4, 97, 92, 87, ef, 27, 5d, 84, 41, 58, 22, dc, 61, 00] +tcp_resp f5e4e2b92e4c2a69744102a0a512374dae6cf7213945db555ee05050034ee48d9ef69e34360a324657c7338a3a2367b5d8328b8fa72fff07cfdaa828c07825ef1cc0fa520aecbda6410afa1a323a48501dd7e0c8d28e178535aaf48a85560687a4f65942a27b093ab675d7133f9bb9bc79c94bbeaa442d3a16d1ea57b8bae1fff1b3289d16930b3b2b2b64ca400db34e00d4ce3803c498bed66ab0d130ced474aa8f76538489ceed2a223c8b46dfb9d8e5082d93174f77770ff40a2e806b16ae3d7f2fc58a873f768ba7118d40748f122bded1c60f7969a2d7518c318d6f5e460338ac94940db3ac1644a5a08cb39821b123474db9fb7c1cf31dda9f5fa5e9972bbaa06266aecdff95797c7fabc00000000000000fa4daddae4a43e6c0c481e92f681ccb reject +tcp_resp d2c00000000000080c40d507d6fb2d5831583e1a1610c41920a583dd0c7e12c3a4683d6a6f7077a1b9d480d51dc2017e2f93b740475f5e1dc7afd922223668e63349b82fe32929f7945511103d22037738841cfc60dc6842f9f517a522178f469a0258d2664a94309a9cc9b9e15abce203ffec5fa31ff8f188dd23e87e3d8c32ed5569aec42634eb9e2da9be1b20a8e4d18a544a649a01308413c58680015cf1ef71b34952d9aca5329eece17e79960616ef8ba68daba71ba16c12be32655ac1c5fbaeb5694c754874ad11b34f9f8f40e14b6d0762b68b9d331415c9276294f680000000 ConnectTcpResp { status: 210, error_code: 2060, message: [7, 214, 251, 45, 88, 49, 88, 62, 26, 22, 16, 196, 25, 32, 165, 131, 221, 12, 126, 18, 195, 164, 104, 61, 106, 111, 112, 119, 161, 185, 212, 128, 213, 29, 194, 1, 126, 47, 147, 183, 64, 71, 95, 94, 29, 199, 175, 217, 34, 34, 54, 104, 230, 51, 73, 184, 47, 227, 41, 41, 247, 148, 85, 17, 16, 61, 34, 3, 119, 56, 132, 28, 252, 96, 220, 104, 66, 249, 245, 23, 165, 34, 23, 143, 70, 154, 2, 88, 210, 102, 74, 148, 48, 154, 156, 201, 185, 225, 90, 188, 226, 3, 255, 236, 95, 163, 31, 248, 241, 136, 221, 35, 232, 126, 61, 140, 50, 237, 85, 105, 174, 196, 38, 52, 235, 158, 45, 169, 190, 27, 32, 168, 228, 209, 138, 84, 74, 100, 154, 1, 48, 132, 19, 197, 134, 128, 1, 92, 241, 239, 113, 179, 73, 82, 217, 172, 165, 50, 158, 236, 225, 126, 121, 150, 6, 22, 239, 139, 166, 141, 171, 167, 27, 161, 108, 18, 190, 50, 101, 90, 193, 197, 251, 174, 181, 105, 76, 117, 72, 116, 173, 17, 179, 79, 159, 143, 64, 225, 75, 109, 7, 98, 182, 139, 157, 51, 20, 21, 201, 39, 98, 148, 246] };228;[d2, 48, 0c, 40, d5, 07, d6, fb, 2d, 58, 31, 58, 3e, 1a, 16, 10, c4, 19, 20, a5, 83, dd, 0c, 7e, 12, c3, a4, 68, 3d, 6a, 6f, 70, 77, a1, b9, d4, 80, d5, 1d, c2, 01, 7e, 2f, 93, b7, 40, 47, 5f, 5e, 1d, c7, af, d9, 22, 22, 36, 68, e6, 33, 49, b8, 2f, e3, 29, 29, f7, 94, 55, 11, 10, 3d, 22, 03, 77, 38, 84, 1c, fc, 60, dc, 68, 42, f9, f5, 17, a5, 22, 17, 8f, 46, 9a, 02, 58, d2, 66, 4a, 94, 30, 9a, 9c, c9, b9, e1, 5a, bc, e2, 03, ff, ec, 5f, a3, 1f, f8, f1, 88, dd, 23, e8, 7e, 3d, 8c, 32, ed, 55, 69, ae, c4, 26, 34, eb, 9e, 2d, a9, be, 1b, 20, a8, e4, d1, 8a, 54, 4a, 64, 9a, 01, 30, 84, 13, c5, 86, 80, 01, 5c, f1, ef, 71, b3, 49, 52, d9, ac, a5, 32, 9e, ec, e1, 7e, 79, 96, 06, 16, ef, 8b, a6, 8d, ab, a7, 1b, a1, 6c, 12, be, 32, 65, 5a, c1, c5, fb, ae, b5, 69, 4c, 75, 48, 74, ad, 11, b3, 4f, 9f, 8f, 40, e1, 4b, 6d, 07, 62, b6, 8b, 9d, 33, 14, 15, c9, 27, 62, 94, f6, 00] +tcp_resp f9c3f407427a218291800000516713a8cf117ba9abbe9cd8eb649bb442af495ed867865b8076aa5eb7016c95a878ad95385fbe3a9f2684bf6f5ac6f7b5c1afdcbdb481c00cc8969787aee68dff197e4c5c313b02bcec708efcb3e513a79580000000ec accept +tcp_resp ed80078da9c000000000000051cda44566dea75fe7c75c826f3717a9a0c3af65841ee774fc1c6e3819801cb35986856dcb5df2aa0131d48a3bde47d48b308a214b54c32ec486340dc27bd68557b537ccb9b9b5c85b8caa5c21058b0ab188001190 ConnectTcpResp { status: 237, error_code: 495017, message: [205, 164, 69, 102, 222, 167, 95, 231, 199, 92, 130, 111, 55, 23, 169, 160, 195, 175, 101, 132, 30, 231, 116, 252, 28, 110, 56, 25, 128, 28, 179, 89, 134, 133, 109, 203, 93, 242, 170, 1, 49, 212, 138, 59, 222, 71, 212, 139, 48, 138, 33, 75, 84, 195, 46, 196, 134, 52, 13, 194, 123, 214, 133, 87, 181, 55, 204, 185, 185, 181, 200, 91, 140, 170, 92, 33, 5, 139, 10, 177, 136] };95;[ed, 80, 07, 8d, a9, 40, 51, cd, a4, 45, 66, de, a7, 5f, e7, c7, 5c, 82, 6f, 37, 17, a9, a0, c3, af, 65, 84, 1e, e7, 74, fc, 1c, 6e, 38, 19, 80, 1c, b3, 59, 86, 85, 6d, cb, 5d, f2, aa, 01, 31, d4, 8a, 3b, de, 47, d4, 8b, 30, 8a, 21, 4b, 54, c3, 2e, c4, 86, 34, 0d, c2, 7b, d6, 85, 57, b5, 37, cc, b9, b9, b5, c8, 5b, 8c, aa, 5c, 21, 05, 8b, 0a, b1, 88, 00] +tcp_resp cec000000000001691403ac600e141a14809428e50b271db6fa0b19486a3759237bd260567ebb3f4aa11f5c30cd0243f7698c96901d288149df5dff01ab463725c8bc7104a4000 accept +tcp_resp c9018000006cc4884a5bef4aa302bf4602cbc2aa4ff9343c42c90adbcd9a842c315f10e0609a37a614165588282d1e87107abda01667daa9d7e6130e66744597508620b5517dffdec38b222fab9166825de8817d1f8d4dc01ecb2a522a8add4a71f9d8f9cef4332560579f2a33478fa0359c0b63948b31fb1f895edb2fa6 ConnectTcpResp { status: 201, error_code: 1, message: [196, 136, 74, 91, 239, 74, 163, 2, 191, 70, 2, 203, 194, 170, 79, 249, 52, 60, 66, 201, 10, 219, 205, 154, 132, 44, 49, 95, 16, 224, 96, 154, 55, 166, 20, 22, 85, 136, 40, 45, 30, 135, 16, 122, 189, 160, 22, 103, 218, 169, 215, 230, 19, 14, 102, 116, 69, 151, 80, 134, 32, 181, 81, 125, 255, 222, 195, 139, 34, 47, 171, 145, 102, 130, 93, 232, 129, 125, 31, 141, 77, 192, 30, 203, 42, 82, 42, 138, 221, 74, 113, 249, 216, 249, 206, 244, 51, 37, 96, 87, 159, 42, 51, 71, 143, 160, 53, 156] };126;[c9, 01, 40, 6c, c4, 88, 4a, 5b, ef, 4a, a3, 02, bf, 46, 02, cb, c2, aa, 4f, f9, 34, 3c, 42, c9, 0a, db, cd, 9a, 84, 2c, 31, 5f, 10, e0, 60, 9a, 37, a6, 14, 16, 55, 88, 28, 2d, 1e, 87, 10, 7a, bd, a0, 16, 67, da, a9, d7, e6, 13, 0e, 66, 74, 45, 97, 50, 86, 20, b5, 51, 7d, ff, de, c3, 8b, 22, 2f, ab, 91, 66, 82, 5d, e8, 81, 7d, 1f, 8d, 4d, c0, 1e, cb, 2a, 52, 2a, 8a, dd, 4a, 71, f9, d8, f9, ce, f4, 33, 25, 60, 57, 9f, 2a, 33, 47, 8f, a0, 35, 9c, 00] +tcp_resp 8ec000000000000000800000bf3d4efa3dd8d46336edada9a8b48e8fb916a769e012389c6abcf351860f57bf8bb6f9be9d2751f01e3140b8e464eada79d13beffbe804535d824ee6ad6179bb16ad67ffbb3a877716561c413567f6c7c5839899a7bd981afc447701fdee3e606dbb5ccbae408a2c2fc3c97c8f2a89ce5ceedb80c394afa5c012337271e835f5f235a975d4ef444a0c97af021554a4e0ce304b01178f0bf3ed09c9c780e788055df69466393f1abcb00f9a6aca4f86ae3bfe1688e86cc673b43e073896b6f4c9800000081250f594efa76474 ConnectTcpResp { status: 142, error_code: 0, message: [61, 78, 250, 61, 216, 212, 99, 54, 237, 173, 169, 168, 180, 142, 143, 185, 22, 167, 105, 224, 18, 56, 156, 106, 188, 243, 81, 134, 15, 87, 191, 139, 182, 249, 190, 157, 39, 81, 240, 30, 49, 64, 184, 228, 100, 234, 218, 121, 209, 59, 239, 251, 232, 4, 83, 93, 130, 78, 230, 173, 97, 121, 187, 22, 173, 103, 255, 187, 58, 135, 119, 22, 86, 28, 65, 53, 103, 246, 199, 197, 131, 152, 153, 167, 189, 152, 26, 252, 68, 119, 1, 253, 238, 62, 96, 109, 187, 92, 203, 174, 64, 138, 44, 47, 195, 201, 124, 143, 42, 137, 206, 92, 238, 219, 128, 195, 148, 175, 165, 192, 18, 51, 114, 113, 232, 53, 245, 242, 53, 169, 117, 212, 239, 68, 74, 12, 151, 175, 2, 21, 84, 164, 224, 206, 48, 75, 1, 23, 143, 11, 243, 237, 9, 201, 199, 128, 231, 136, 5, 93, 246, 148, 102, 57, 63, 26, 188, 176, 15, 154, 106, 202, 79, 134, 174, 59, 254, 22, 136, 232, 108, 198, 115, 180, 62, 7, 56, 150, 182, 244, 201] };216;[8e, 00, 40, bf, 3d, 4e, fa, 3d, d8, d4, 63, 36, ed, ad, a9, a8, b4, 8e, 8f, b9, 16, a7, 69, e0, 12, 38, 9c, 6a, bc, f3, 51, 86, 0f, 57, bf, 8b, b6, f9, be, 9d, 27, 51, f0, 1e, 31, 40, b8, e4, 64, ea, da, 79, d1, 3b, ef, fb, e8, 04, 53, 5d, 82, 4e, e6, ad, 61, 79, bb, 16, ad, 67, ff, bb, 3a, 87, 77, 16, 56, 1c, 41, 35, 67, f6, c7, c5, 83, 98, 99, a7, bd, 98, 1a, fc, 44, 77, 01, fd, ee, 3e, 60, 6d, bb, 5c, cb, ae, 40, 8a, 2c, 2f, c3, c9, 7c, 8f, 2a, 89, ce, 5c, ee, db, 80, c3, 94, af, a5, c0, 12, 33, 72, 71, e8, 35, f5, f2, 35, a9, 75, d4, ef, 44, 4a, 0c, 97, af, 02, 15, 54, a4, e0, ce, 30, 4b, 01, 17, 8f, 0b, f3, ed, 09, c9, c7, 80, e7, 88, 05, 5d, f6, 94, 66, 39, 3f, 1a, bc, b0, 0f, 9a, 6a, ca, 4f, 86, ae, 3b, fe, 16, 88, e8, 6c, c6, 73, b4, 3e, 07, 38, 96, b6, f4, c9, 00] +tcp_resp 5ec0000000629bb9e340e84a8161e4d10aeea953b70b11f7949fd6e59408db40501fa2fc2931920a2a3eaabad2753b51b8c9dd4677d18e5225183b7ee73819210e6f363b7d68069e39c239d56c8b8a5ac6f43ab634f0c8e26a34cb78a52eee796349bfaa605135525380b69e4b86f272b82e8f30211bc21960da404095f797286858f2040506161a209f36f158b7130e767e3717c7c9beeb1c51f423f74eefbc2d72e1cf3fa7540f4c36c7818d4990f0f6c5fe2a11018a54273367259cabd748f4cb402df9e3f6815eae998afe2ef0b045896cd95b78c555f5076ed28c26fe459296cd22cf76deb630171353efb1ac6601e480c000000000000000 ConnectTcpResp { status: 94, error_code: 1654372835, message: [74, 129, 97, 228, 209, 10, 238, 169, 83, 183, 11, 17, 247, 148, 159, 214, 229, 148, 8, 219, 64, 80, 31, 162, 252, 41, 49, 146, 10, 42, 62, 170, 186, 210, 117, 59, 81, 184, 201, 221, 70, 119, 209, 142, 82, 37, 24, 59, 126, 231, 56, 25, 33, 14, 111, 54, 59, 125, 104, 6, 158, 57, 194, 57, 213, 108, 139, 138, 90, 198, 244, 58, 182, 52, 240, 200, 226, 106, 52, 203, 120, 165, 46, 238, 121, 99, 73, 191, 170, 96, 81, 53, 82, 83, 128, 182, 158, 75, 134, 242, 114, 184, 46, 143, 48, 33, 27, 194, 25, 96, 218, 64, 64, 149, 247, 151, 40, 104, 88, 242, 4, 5, 6, 22, 26, 32, 159, 54, 241, 88, 183, 19, 14, 118, 126, 55, 23, 199, 201, 190, 235, 28, 81, 244, 35, 247, 78, 239, 188, 45, 114, 225, 207, 63, 167, 84, 15, 76, 54, 199, 129, 141, 73, 144, 240, 246, 197, 254, 42, 17, 1, 138, 84, 39, 51, 103, 37, 156, 171, 215, 72, 244, 203, 64, 45, 249, 227, 246, 129, 94, 174, 153, 138, 254, 46, 240, 176, 69, 137, 108, 217, 91, 120, 197, 85, 245, 7, 110, 210, 140, 38, 254, 69, 146, 150, 205, 34, 207, 118, 222, 182, 48, 23, 19, 83, 239, 177, 172, 102, 1, 228, 128] };251;[5e, c0, 00, 00, 00, 62, 9b, b9, e3, 40, e8, 4a, 81, 61, e4, d1, 0a, ee, a9, 53, b7, 0b, 11, f7, 94, 9f, d6, e5, 94, 08, db, 40, 50, 1f, a2, fc, 29, 31, 92, 0a, 2a, 3e, aa, ba, d2, 75, 3b, 51, b8, c9, dd, 46, 77, d1, 8e, 52, 25, 18, 3b, 7e, e7, 38, 19, 21, 0e, 6f, 36, 3b, 7d, 68, 06, 9e, 39, c2, 39, d5, 6c, 8b, 8a, 5a, c6, f4, 3a, b6, 34, f0, c8, e2, 6a, 34, cb, 78, a5, 2e, ee, 79, 63, 49, bf, aa, 60, 51, 35, 52, 53, 80, b6, 9e, 4b, 86, f2, 72, b8, 2e, 8f, 30, 21, 1b, c2, 19, 60, da, 40, 40, 95, f7, 97, 28, 68, 58, f2, 04, 05, 06, 16, 1a, 20, 9f, 36, f1, 58, b7, 13, 0e, 76, 7e, 37, 17, c7, c9, be, eb, 1c, 51, f4, 23, f7, 4e, ef, bc, 2d, 72, e1, cf, 3f, a7, 54, 0f, 4c, 36, c7, 81, 8d, 49, 90, f0, f6, c5, fe, 2a, 11, 01, 8a, 54, 27, 33, 67, 25, 9c, ab, d7, 48, f4, cb, 40, 2d, f9, e3, f6, 81, 5e, ae, 99, 8a, fe, 2e, f0, b0, 45, 89, 6c, d9, 5b, 78, c5, 55, f5, 07, 6e, d2, 8c, 26, fe, 45, 92, 96, cd, 22, cf, 76, de, b6, 30, 17, 13, 53, ef, b1, ac, 66, 01, e4, 80, 00] +tcp_resp 3dc000000000003f1e4051d224a56d38fa72ce101067c858da17619e8b8fc3a6c4d9a4b99a3c35c58e25e491617cca10a3f09df20d3e10a8f894f9956d5a50079ed85bc0d412e9e4f9ed10b81ad400968df6fecb8512466472b51ae000 accept +tcp_resp a1805051ef407d1b920272eea890ce6f05cd0a159e19af19e181ce2b12856445688afe73fd8fd79b29b1bcd75f2c38ad1d68d4326b56d2c7549e0137e9f1f97838e40334866a0754522689e02fbda771b3a6fe8f4bdb3171e20cf8314475e8d0cfa7090f5c8b89e584ef5790fc0704918e449ae5811028313ad3ba3c804df57da6fdf1c880000000 ConnectTcpResp { status: 161, error_code: 5263855, message: [27, 146, 2, 114, 238, 168, 144, 206, 111, 5, 205, 10, 21, 158, 25, 175, 25, 225, 129, 206, 43, 18, 133, 100, 69, 104, 138, 254, 115, 253, 143, 215, 155, 41, 177, 188, 215, 95, 44, 56, 173, 29, 104, 212, 50, 107, 86, 210, 199, 84, 158, 1, 55, 233, 241, 249, 120, 56, 228, 3, 52, 134, 106, 7, 84, 82, 38, 137, 224, 47, 189, 167, 113, 179, 166, 254, 143, 75, 219, 49, 113, 226, 12, 248, 49, 68, 117, 232, 208, 207, 167, 9, 15, 92, 139, 137, 229, 132, 239, 87, 144, 252, 7, 4, 145, 142, 68, 154, 229, 129, 16, 40, 49, 58, 211, 186, 60, 128, 77, 245, 125, 166, 253, 241, 200] };136;[a1, 80, 50, 51, ef, 40, 7d, 1b, 92, 02, 72, ee, a8, 90, ce, 6f, 05, cd, 0a, 15, 9e, 19, af, 19, e1, 81, ce, 2b, 12, 85, 64, 45, 68, 8a, fe, 73, fd, 8f, d7, 9b, 29, b1, bc, d7, 5f, 2c, 38, ad, 1d, 68, d4, 32, 6b, 56, d2, c7, 54, 9e, 01, 37, e9, f1, f9, 78, 38, e4, 03, 34, 86, 6a, 07, 54, 52, 26, 89, e0, 2f, bd, a7, 71, b3, a6, fe, 8f, 4b, db, 31, 71, e2, 0c, f8, 31, 44, 75, e8, d0, cf, a7, 09, 0f, 5c, 8b, 89, e5, 84, ef, 57, 90, fc, 07, 04, 91, 8e, 44, 9a, e5, 81, 10, 28, 31, 3a, d3, ba, 3c, 80, 4d, f5, 7d, a6, fd, f1, c8, 00] +tcp_resp 8e95297e6d80000045fe8003241ee58f3b869c282f45506672b6f1b768e22d75f6e9e60ca3b2c754b51d68e1e5d1e6d1fb19e0a7d9165466c438811674b45222872b5bda1faf1a25b9af3ea3e20c400013558d20 ConnectTcpResp { status: 142, error_code: 355040877, message: [254, 128, 3, 36, 30, 229, 143, 59, 134, 156, 40, 47, 69, 80, 102, 114, 182, 241, 183, 104, 226, 45, 117, 246, 233, 230, 12, 163, 178, 199, 84, 181, 29, 104, 225, 229, 209, 230, 209, 251, 25, 224, 167, 217, 22, 84, 102, 196, 56, 129, 22, 116, 180, 82, 34, 135, 43, 91, 218, 31, 175, 26, 37, 185, 175, 62, 163, 226, 12] };80;[8e, 95, 29, 7e, 6d, 40, 45, fe, 80, 03, 24, 1e, e5, 8f, 3b, 86, 9c, 28, 2f, 45, 50, 66, 72, b6, f1, b7, 68, e2, 2d, 75, f6, e9, e6, 0c, a3, b2, c7, 54, b5, 1d, 68, e1, e5, d1, e6, d1, fb, 19, e0, a7, d9, 16, 54, 66, c4, 38, 81, 16, 74, b4, 52, 22, 87, 2b, 5b, da, 1f, af, 1a, 25, b9, af, 3e, a3, e2, 0c, 00] +tcp_resp fb02c0000000000000f4dc7bba131d6c42c0b79c4f22f3e60f1a0418ee0e805fad3e0250b02a598a92f15de06356a7ead14fe8f2e51e3a35d3225320e1636a3c50d41ea0d30afbfb307c7d95b2031bd51adef996a0d409582c2ae1d3de6b2bc222f3cdaeb3653fb29e2b451889fa8067ccd0c16b7c8d661b254873dd26d94f8accb2a9dce295a40c2c17c4cf04b0321d62f90892bd7d0bffc0d0998dc26588d932984bef3fb349b4f7910c506e94d368d4aedaff8ab4f25f4925279d2b6c77e9d78ae28fa713cda27629b5f22956749e2294e67cffa06f3d0169d9b1e6d09a66f85b182234a9986695e06454b2135a308a714cf7e66935a81632b0a145628000000087dcce ConnectTcpResp { status: 251, error_code: 2, message: [220, 123, 186, 19, 29, 108, 66, 192, 183, 156, 79, 34, 243, 230, 15, 26, 4, 24, 238, 14, 128, 95, 173, 62, 2, 80, 176, 42, 89, 138, 146, 241, 93, 224, 99, 86, 167, 234, 209, 79, 232, 242, 229, 30, 58, 53, 211, 34, 83, 32, 225, 99, 106, 60, 80, 212, 30, 160, 211, 10, 251, 251, 48, 124, 125, 149, 178, 3, 27, 213, 26, 222, 249, 150, 160, 212, 9, 88, 44, 42, 225, 211, 222, 107, 43, 194, 34, 243, 205, 174, 179, 101, 63, 178, 158, 43, 69, 24, 137, 250, 128, 103, 204, 208, 193, 107, 124, 141, 102, 27, 37, 72, 115, 221, 38, 217, 79, 138, 204, 178, 169, 220, 226, 149, 164, 12, 44, 23, 196, 207, 4, 176, 50, 29, 98, 249, 8, 146, 189, 125, 11, 255, 192, 208, 153, 141, 194, 101, 136, 217, 50, 152, 75, 239, 63, 179, 73, 180, 247, 145, 12, 80, 110, 148, 211, 104, 212, 174, 218, 255, 138, 180, 242, 95, 73, 37, 39, 157, 43, 108, 119, 233, 215, 138, 226, 143, 167, 19, 205, 162, 118, 41, 181, 242, 41, 86, 116, 158, 34, 148, 230, 124, 255, 160, 111, 61, 1, 105, 217, 177, 230, 208, 154, 102, 248, 91, 24, 34, 52, 169, 152, 102, 149, 224, 100, 84, 178, 19, 90, 48, 138, 113, 76, 247, 230, 105, 53, 168, 22, 50, 176, 161, 69, 98] };258;[fb, 02, 40, f4, dc, 7b, ba, 13, 1d, 6c, 42, c0, b7, 9c, 4f, 22, f3, e6, 0f, 1a, 04, 18, ee, 0e, 80, 5f, ad, 3e, 02, 50, b0, 2a, 59, 8a, 92, f1, 5d, e0, 63, 56, a7, ea, d1, 4f, e8, f2, e5, 1e, 3a, 35, d3, 22, 53, 20, e1, 63, 6a, 3c, 50, d4, 1e, a0, d3, 0a, fb, fb, 30, 7c, 7d, 95, b2, 03, 1b, d5, 1a, de, f9, 96, a0, d4, 09, 58, 2c, 2a, e1, d3, de, 6b, 2b, c2, 22, f3, cd, ae, b3, 65, 3f, b2, 9e, 2b, 45, 18, 89, fa, 80, 67, cc, d0, c1, 6b, 7c, 8d, 66, 1b, 25, 48, 73, dd, 26, d9, 4f, 8a, cc, b2, a9, dc, e2, 95, a4, 0c, 2c, 17, c4, cf, 04, b0, 32, 1d, 62, f9, 08, 92, bd, 7d, 0b, ff, c0, d0, 99, 8d, c2, 65, 88, d9, 32, 98, 4b, ef, 3f, b3, 49, b4, f7, 91, 0c, 50, 6e, 94, d3, 68, d4, ae, da, ff, 8a, b4, f2, 5f, 49, 25, 27, 9d, 2b, 6c, 77, e9, d7, 8a, e2, 8f, a7, 13, cd, a2, 76, 29, b5, f2, 29, 56, 74, 9e, 22, 94, e6, 7c, ff, a0, 6f, 3d, 01, 69, d9, b1, e6, d0, 9a, 66, f8, 5b, 18, 22, 34, a9, 98, 66, 95, e0, 64, 54, b2, 13, 5a, 30, 8a, 71, 4c, f7, e6, 69, 35, a8, 16, 32, b0, a1, 45, 62, 00] +tcp_resp f38b291bd6c00000000000007b7b3f6f46b89e4d8420c77a79b2df2a6089c15fdc7e795f98219ef90dbb1ed3091a1d25309ccb79596a9f9a2ba1e01eab7c234ff713fb16db5d4e315290c78dc65abeda64131c8ab3e7f5bb240cd340d25027fe7d51f8936bd8efad6b2d47304bdbb9ffd98f1b84ecafe91701ec906b0dd43398ee6beb556ade678f800000061debd0030e25 ConnectTcpResp { status: 243, error_code: 187243478, message: [123, 63, 111, 70, 184, 158, 77, 132, 32, 199, 122, 121, 178, 223, 42, 96, 137, 193, 95, 220, 126, 121, 95, 152, 33, 158, 249, 13, 187, 30, 211, 9, 26, 29, 37, 48, 156, 203, 121, 89, 106, 159, 154, 43, 161, 224, 30, 171, 124, 35, 79, 247, 19, 251, 22, 219, 93, 78, 49, 82, 144, 199, 141, 198, 90, 190, 218, 100, 19, 28, 138, 179, 231, 245, 187, 36, 12, 211, 64, 210, 80, 39, 254, 125, 81, 248, 147, 107, 216, 239, 173, 107, 45, 71, 48, 75, 219, 185, 255, 217, 143, 27, 132, 236, 175, 233, 23, 1, 236, 144, 107, 13, 212, 51, 152, 238, 107, 235, 85, 106, 222, 103, 143] };146;[f3, 8b, 29, 1b, d6, 40, 7b, 7b, 3f, 6f, 46, b8, 9e, 4d, 84, 20, c7, 7a, 79, b2, df, 2a, 60, 89, c1, 5f, dc, 7e, 79, 5f, 98, 21, 9e, f9, 0d, bb, 1e, d3, 09, 1a, 1d, 25, 30, 9c, cb, 79, 59, 6a, 9f, 9a, 2b, a1, e0, 1e, ab, 7c, 23, 4f, f7, 13, fb, 16, db, 5d, 4e, 31, 52, 90, c7, 8d, c6, 5a, be, da, 64, 13, 1c, 8a, b3, e7, f5, bb, 24, 0c, d3, 40, d2, 50, 27, fe, 7d, 51, f8, 93, 6b, d8, ef, ad, 6b, 2d, 47, 30, 4b, db, b9, ff, d9, 8f, 1b, 84, ec, af, e9, 17, 01, ec, 90, 6b, 0d, d4, 33, 98, ee, 6b, eb, 55, 6a, de, 67, 8f, 00] +tcp_resp c08000de9980000067729d8d142233fc2ba49009436e500fa3e6189b610d31a9449298b7947d17f2654e9d2661899561163e0f613247b1d570940ca7a04736a4bd3c290c55526ebb977c9b97c64721e4641e351c923a8ff5e272bfd4ede9ebe272bf0cee21c0297aa72666ed3e821c710da57eb7f32870e58ee813a71737 ConnectTcpResp { status: 192, error_code: 56985, message: [114, 157, 141, 20, 34, 51, 252, 43, 164, 144, 9, 67, 110, 80, 15, 163, 230, 24, 155, 97, 13, 49, 169, 68, 146, 152, 183, 148, 125, 23, 242, 101, 78, 157, 38, 97, 137, 149, 97, 22, 62, 15, 97, 50, 71, 177, 213, 112, 148, 12, 167, 160, 71, 54, 164, 189, 60, 41, 12, 85, 82, 110, 187, 151, 124, 155, 151, 198, 71, 33, 228, 100, 30, 53, 28, 146, 58, 143, 245, 226, 114, 191, 212, 237, 233, 235, 226, 114, 191, 12, 238, 33, 192, 41, 122, 167, 38, 102, 237, 62, 130, 28, 113] };126;[c0, 80, 00, de, 99, 40, 67, 72, 9d, 8d, 14, 22, 33, fc, 2b, a4, 90, 09, 43, 6e, 50, 0f, a3, e6, 18, 9b, 61, 0d, 31, a9, 44, 92, 98, b7, 94, 7d, 17, f2, 65, 4e, 9d, 26, 61, 89, 95, 61, 16, 3e, 0f, 61, 32, 47, b1, d5, 70, 94, 0c, a7, a0, 47, 36, a4, bd, 3c, 29, 0c, 55, 52, 6e, bb, 97, 7c, 9b, 97, c6, 47, 21, e4, 64, 1e, 35, 1c, 92, 3a, 8f, f5, e2, 72, bf, d4, ed, e9, eb, e2, 72, bf, 0c, ee, 21, c0, 29, 7a, a7, 26, 66, ed, 3e, 82, 1c, 71, 00] +tcp_resp ee86635669403ba5f4843405b1c94e03c18a0d4acf4b601fcd9b619f66414b030583d015efec2f2b54a1f064dad2aeac257760f558184d831a17537bc5833d3ef3ca80000003b1e4de ConnectTcpResp { status: 238, error_code: 107173481, message: [165, 244, 132, 52, 5, 177, 201, 78, 3, 193, 138, 13, 74, 207, 75, 96, 31, 205, 155, 97, 159, 102, 65, 75, 3, 5, 131, 208, 21, 239, 236, 47, 43, 84, 161, 240, 100, 218, 210, 174, 172, 37, 119, 96, 245, 88, 24, 77, 131, 26, 23, 83, 123, 197, 131, 61, 62, 243, 202] };73;[ee, 86, 63, 56, 69, 3b, a5, f4, 84, 34, 05, b1, c9, 4e, 03, c1, 8a, 0d, 4a, cf, 4b, 60, 1f, cd, 9b, 61, 9f, 66, 41, 4b, 03, 05, 83, d0, 15, ef, ec, 2f, 2b, 54, a1, f0, 64, da, d2, ae, ac, 25, 77, 60, f5, 58, 18, 4d, 83, 1a, 17, 53, 7b, c5, 83, 3d, 3e, f3, ca, 00] +tcp_resp e900c0000000000000608e6a0a5425148d105ad5287d03cd7e25365ece8e0ba4c30abcb75acc1d80b4ba0f7b88b310d65686d7428d072373c9f9350603fa20a0500e6828f8ebf488112bc160598dd4956c3f6562494eb82e020291ebafdfbbc8ad434f0f2ab9deb4f10ec000000000000000 ConnectTcpResp { status: 233, error_code: 0, message: [142, 106, 10, 84, 37, 20, 141, 16, 90, 213, 40, 125, 3, 205, 126, 37, 54, 94, 206, 142, 11, 164, 195, 10, 188, 183, 90, 204, 29, 128, 180, 186, 15, 123, 136, 179, 16, 214, 86, 134, 215, 66, 141, 7, 35, 115, 201, 249, 53, 6, 3, 250, 32, 160, 80, 14, 104, 40, 248, 235, 244, 136, 17, 43, 193, 96, 89, 141, 212, 149, 108, 63, 101, 98, 73, 78, 184, 46, 2, 2, 145, 235, 175, 223, 187, 200, 173, 67, 79, 15, 42, 185, 222, 180, 241, 14] };114;[e9, 00, 40, 60, 8e, 6a, 0a, 54, 25, 14, 8d, 10, 5a, d5, 28, 7d, 03, cd, 7e, 25, 36, 5e, ce, 8e, 0b, a4, c3, 0a, bc, b7, 5a, cc, 1d, 80, b4, ba, 0f, 7b, 88, b3, 10, d6, 56, 86, d7, 42, 8d, 07, 23, 73, c9, f9, 35, 06, 03, fa, 20, a0, 50, 0e, 68, 28, f8, eb, f4, 88, 11, 2b, c1, 60, 59, 8d, d4, 95, 6c, 3f, 65, 62, 49, 4e, b8, 2e, 02, 02, 91, eb, af, df, bb, c8, ad, 43, 4f, 0f, 2a, b9, de, b4, f1, 0e, 00] +tcp_resp 90c00000000000036d80000082cde760af4d8e18aa6154067e6b020ee873d9dc63312649a0020afc105ce3d7c33de1adcd337d6100b6c794c1416f8775c73d6b8ef89f29165162435c271e0e03be2f27c4a02e547ac9074af629e79d8b1ec003dd0c271ffacdfe4430081490f5843a5d6ad560c7fd564b5c94853941bad88c0866b1a789d29b902a2a3e3ab1792ea8400099 accept +tcp_resp 128015680c4058ac55ed2fdc6696fc6ada5cbdd9b84ad4ccd6027e33405f8330e0391e2db36271716c22ef246ecf3b3c661fd780a459afd2497f4dbb469a0ad369e0e4535328bbba23e213ceb7adb42adccccc95d62e9d88f06417c4189f464000 ConnectTcpResp { status: 18, error_code: 1402892, message: [172, 85, 237, 47, 220, 102, 150, 252, 106, 218, 92, 189, 217, 184, 74, 212, 204, 214, 2, 126, 51, 64, 95, 131, 48, 224, 57, 30, 45, 179, 98, 113, 113, 108, 34, 239, 36, 110, 207, 59, 60, 102, 31, 215, 128, 164, 89, 175, 210, 73, 127, 77, 187, 70, 154, 10, 211, 105, 224, 228, 83, 83, 40, 187, 186, 35, 226, 19, 206, 183, 173, 180, 42, 220, 204, 204, 149, 214, 46, 157, 136, 240, 100, 23, 196, 24, 159, 70] };97;[12, 80, 15, 68, 0c, 40, 58, ac, 55, ed, 2f, dc, 66, 96, fc, 6a, da, 5c, bd, d9, b8, 4a, d4, cc, d6, 02, 7e, 33, 40, 5f, 83, 30, e0, 39, 1e, 2d, b3, 62, 71, 71, 6c, 22, ef, 24, 6e, cf, 3b, 3c, 66, 1f, d7, 80, a4, 59, af, d2, 49, 7f, 4d, bb, 46, 9a, 0a, d3, 69, e0, e4, 53, 53, 28, bb, ba, 23, e2, 13, ce, b7, ad, b4, 2a, dc, cc, cc, 95, d6, 2e, 9d, 88, f0, 64, 17, c4, 18, 9f, 46, 00] +tcp_resp 13404c80000103a37519cd391b1c5be765a20a039ab13f0ca774c05d39994d8d5ae0755d945448886378cb78477816dc1356745567923958a3bbb3768c9be8fd79ddde69ecd0a1027cdf9474b48173c7edda14c7a4078ee3d5783cafd5895220d105a0bb2f2280e18a241695c555c9912a641d41adb439a8772ae9e1a50922c4889dd341299acc6e0cbc7fe55a88a68221152d60743fcb72cd3cbaa35c69fe10eb1b6cee1d3e16ea21be3d7d0402afb6713adf16b3f383f646b93a2c50e97a0bade5e30c4682ab1696c4aadc67ac671b1ebab35d9b590b6c61999c214566806d26f17fe727653dfa9e1986655551817c40c61d6847a04e767a09308e4d95c7e88c856f5e95b1adb72acfc000000000000000 reject +tcp_resp 1a8000be6dc0000000000000aa2e0fbc1bb8aa8724d39aa65bad37d37ba6ce4685a7c38fb1dc6d9dc94fe65f27c319550c7fa9956a4f6cdcf27e2c143851311dc94824d6c62dc36d490605856f7065aed7302119fe15ee3c9701d337e174591c774638b13feffd0e069f7623195c491d48a934f117460dca1c1e99e98c43a8a113ecdbf2bcde608d9437f3abf6235a8758e69d0f2b6ea5eba43e5abda1c50576fd61f1d1268c92aeb95ab903da7f9f3183327b9164fa78c000000000000000 ConnectTcpResp { status: 26, error_code: 48749, message: [46, 15, 188, 27, 184, 170, 135, 36, 211, 154, 166, 91, 173, 55, 211, 123, 166, 206, 70, 133, 167, 195, 143, 177, 220, 109, 157, 201, 79, 230, 95, 39, 195, 25, 85, 12, 127, 169, 149, 106, 79, 108, 220, 242, 126, 44, 20, 56, 81, 49, 29, 201, 72, 36, 214, 198, 45, 195, 109, 73, 6, 5, 133, 111, 112, 101, 174, 215, 48, 33, 25, 254, 21, 238, 60, 151, 1, 211, 55, 225, 116, 89, 28, 119, 70, 56, 177, 63, 239, 253, 14, 6, 159, 118, 35, 25, 92, 73, 29, 72, 169, 52, 241, 23, 70, 13, 202, 28, 30, 153, 233, 140, 67, 168, 161, 19, 236, 219, 242, 188, 222, 96, 141, 148, 55, 243, 171, 246, 35, 90, 135, 88, 230, 157, 15, 43, 110, 165, 235, 164, 62, 90, 189, 161, 197, 5, 118, 253, 97, 241, 209, 38, 140, 146, 174, 185, 90, 185, 3, 218, 127, 159, 49, 131, 50, 123, 145, 100, 250, 120] };191;[1a, 80, 00, be, 6d, 40, aa, 2e, 0f, bc, 1b, b8, aa, 87, 24, d3, 9a, a6, 5b, ad, 37, d3, 7b, a6, ce, 46, 85, a7, c3, 8f, b1, dc, 6d, 9d, c9, 4f, e6, 5f, 27, c3, 19, 55, 0c, 7f, a9, 95, 6a, 4f, 6c, dc, f2, 7e, 2c, 14, 38, 51, 31, 1d, c9, 48, 24, d6, c6, 2d, c3, 6d, 49, 06, 05, 85, 6f, 70, 65, ae, d7, 30, 21, 19, fe, 15, ee, 3c, 97, 01, d3, 37, e1, 74, 59, 1c, 77, 46, 38, b1, 3f, ef, fd, 0e, 06, 9f, 76, 23, 19, 5c, 49, 1d, 48, a9, 34, f1, 17, 46, 0d, ca, 1c, 1e, 99, e9, 8c, 43, a8, a1, 13, ec, db, f2, bc, de, 60, 8d, 94, 37, f3, ab, f6, 23, 5a, 87, 58, e6, 9d, 0f, 2b, 6e, a5, eb, a4, 3e, 5a, bd, a1, c5, 05, 76, fd, 61, f1, d1, 26, 8c, 92, ae, b9, 5a, b9, 03, da, 7f, 9f, 31, 83, 32, 7b, 91, 64, fa, 78, 00] +tcp_resp 678000d4c3409e6cfdf31a2d079f60927af6a7d0a554e83baa1423842c15b020ee91b040773b4a8cfba743e2598d1aec979ad865a8c39ef293942312e9777703c3fabb96113cffcef6e8836e872969c8e7bcc40891d2746a0cdb05752a2cab37e976be10bcb87c732bfbda8bf86691a47d79f118f678301e7ccaf73392c79fad790bf875b265af706dade4d49b9234790724b4dd9b5f3e3090dfad493d1ae6da8127818f61c000000000000000 ConnectTcpResp { status: 103, error_code: 54467, message: [108, 253, 243, 26, 45, 7, 159, 96, 146, 122, 246, 167, 208, 165, 84, 232, 59, 170, 20, 35, 132, 44, 21, 176, 32, 238, 145, 176, 64, 119, 59, 74, 140, 251, 167, 67, 226, 89, 141, 26, 236, 151, 154, 216, 101, 168, 195, 158, 242, 147, 148, 35, 18, 233, 119, 119, 3, 195, 250, 187, 150, 17, 60, 255, 206, 246, 232, 131, 110, 135, 41, 105, 200, 231, 188, 196, 8, 145, 210, 116, 106, 12, 219, 5, 117, 42, 44, 171, 55, 233, 118, 190, 16, 188, 184, 124, 115, 43, 251, 218, 139, 248, 102, 145, 164, 125, 121, 241, 24, 246, 120, 48, 30, 124, 202, 247, 51, 146, 199, 159, 173, 121, 11, 248, 117, 178, 101, 175, 112, 109, 173, 228, 212, 155, 146, 52, 121, 7, 36, 180, 221, 155, 95, 62, 48, 144, 223, 173, 73, 61, 26, 230, 218, 129, 39, 129, 143, 97] };173;[67, 80, 00, d4, c3, 40, 9e, 6c, fd, f3, 1a, 2d, 07, 9f, 60, 92, 7a, f6, a7, d0, a5, 54, e8, 3b, aa, 14, 23, 84, 2c, 15, b0, 20, ee, 91, b0, 40, 77, 3b, 4a, 8c, fb, a7, 43, e2, 59, 8d, 1a, ec, 97, 9a, d8, 65, a8, c3, 9e, f2, 93, 94, 23, 12, e9, 77, 77, 03, c3, fa, bb, 96, 11, 3c, ff, ce, f6, e8, 83, 6e, 87, 29, 69, c8, e7, bc, c4, 08, 91, d2, 74, 6a, 0c, db, 05, 75, 2a, 2c, ab, 37, e9, 76, be, 10, bc, b8, 7c, 73, 2b, fb, da, 8b, f8, 66, 91, a4, 7d, 79, f1, 18, f6, 78, 30, 1e, 7c, ca, f7, 33, 92, c7, 9f, ad, 79, 0b, f8, 75, b2, 65, af, 70, 6d, ad, e4, d4, 9b, 92, 34, 79, 07, 24, b4, dd, 9b, 5f, 3e, 30, 90, df, ad, 49, 3d, 1a, e6, da, 81, 27, 81, 8f, 61, 00] +tcp_resp e681fb9a52c0000000000000233993f8103b9587261153fdd3bcd2a8bb70d2fac74a08a4d7d7fadbc7e4c74ec75054e88000000f083c4e247c02bb8007015f3b4cd462 ConnectTcpResp { status: 230, error_code: 33266258, message: [57, 147, 248, 16, 59, 149, 135, 38, 17, 83, 253, 211, 188, 210, 168, 187, 112, 210, 250, 199, 74, 8, 164, 215, 215, 250, 219, 199, 228, 199, 78, 199, 80, 84, 232] };67;[e6, 81, fb, 9a, 52, 23, 39, 93, f8, 10, 3b, 95, 87, 26, 11, 53, fd, d3, bc, d2, a8, bb, 70, d2, fa, c7, 4a, 08, a4, d7, d7, fa, db, c7, e4, c7, 4e, c7, 50, 54, e8, 00] +tcp_resp 89c000000000000067c00000000000004109894a105d3ebd8309273d3aa9000f11a35a5f547173c39ffb30cabf8c59acd4dfe0ec213618714eabb69c76ae499975f8ae174a8a098b6da14c014446216f208c4000 accept +tcp_resp 0dc000000000000001c0000000000000bed2f16bcd06235be5c5d69161bd2f5dffa6f7ab52cbd96d7e1d1cd3604f581e387626043ec69ebf1c110e6611955637f8dd4490837f9350776d757343dcf7b65dc218156445213ead2623a229dde151c50613502285ab5f5f32908189dcb7f418fe6d9af2da494543e0907243761c60788595e2af5b855f25d698b4eeb73d3fb97adb78efa3b39736e09ca68b4d58497ff9ea29fd3d78b1e9334a037c27a54d82183c83a51039d9cd945ed3a5b180a13ebcec9a16af49feae63cfc05e55e500 ConnectTcpResp { status: 13, error_code: 1, message: [210, 241, 107, 205, 6, 35, 91, 229, 197, 214, 145, 97, 189, 47, 93, 255, 166, 247, 171, 82, 203, 217, 109, 126, 29, 28, 211, 96, 79, 88, 30, 56, 118, 38, 4, 62, 198, 158, 191, 28, 17, 14, 102, 17, 149, 86, 55, 248, 221, 68, 144, 131, 127, 147, 80, 119, 109, 117, 115, 67, 220, 247, 182, 93, 194, 24, 21, 100, 69, 33, 62, 173, 38, 35, 162, 41, 221, 225, 81, 197, 6, 19, 80, 34, 133, 171, 95, 95, 50, 144, 129, 137, 220, 183, 244, 24, 254, 109, 154, 242, 218, 73, 69, 67, 224, 144, 114, 67, 118, 28, 96, 120, 133, 149, 226, 175, 91, 133, 95, 37, 214, 152, 180, 238, 183, 61, 63, 185, 122, 219, 120, 239, 163, 179, 151, 54, 224, 156, 166, 139, 77, 88, 73, 127, 249, 234, 41, 253, 61, 120, 177, 233, 51, 74, 3, 124, 39, 165, 77, 130, 24, 60, 131, 165, 16, 57, 217, 205, 148, 94, 211, 165, 177, 128, 161, 62, 188, 236, 154, 22, 175, 73, 254, 174, 99, 207, 192, 94, 85, 229] };208;[0d, 01, 40, be, d2, f1, 6b, cd, 06, 23, 5b, e5, c5, d6, 91, 61, bd, 2f, 5d, ff, a6, f7, ab, 52, cb, d9, 6d, 7e, 1d, 1c, d3, 60, 4f, 58, 1e, 38, 76, 26, 04, 3e, c6, 9e, bf, 1c, 11, 0e, 66, 11, 95, 56, 37, f8, dd, 44, 90, 83, 7f, 93, 50, 77, 6d, 75, 73, 43, dc, f7, b6, 5d, c2, 18, 15, 64, 45, 21, 3e, ad, 26, 23, a2, 29, dd, e1, 51, c5, 06, 13, 50, 22, 85, ab, 5f, 5f, 32, 90, 81, 89, dc, b7, f4, 18, fe, 6d, 9a, f2, da, 49, 45, 43, e0, 90, 72, 43, 76, 1c, 60, 78, 85, 95, e2, af, 5b, 85, 5f, 25, d6, 98, b4, ee, b7, 3d, 3f, b9, 7a, db, 78, ef, a3, b3, 97, 36, e0, 9c, a6, 8b, 4d, 58, 49, 7f, f9, ea, 29, fd, 3d, 78, b1, e9, 33, 4a, 03, 7c, 27, a5, 4d, 82, 18, 3c, 83, a5, 10, 39, d9, cd, 94, 5e, d3, a5, b1, 80, a1, 3e, bc, ec, 9a, 16, af, 49, fe, ae, 63, cf, c0, 5e, 55, e5, 00] +tcp_resp 35c00000000000000a8000010196caaf205a2906b2c0c77494794e73d2aec67cb404f7e8cbadb03454d75bfe6eaa6d9150f2929abe8e9c1d92b5749b7e3f880982ac93a9a10f6ec0e7b644057c7f5859ee9d528388c3009d062fb4b800ac52acd3f00ec720c1eb1a85b02c22f2165fc827affe798e7a46604f01c24b329a27b73ec02c4eb8113a80e0863ad0aa353fced0cf996db8611eda40ea1b8579d31ad1434ec86152a6851313ef6b20c5abc38ba847f42c12e951ad6c557818e56a3aae0384929a13b5c8a6c4707226f7ffd2f14140af4b0c443d757fbf33fa50f3b2c1d13b44fc063dab0c9f517f8ec854cfe7c6b8f0e55dc58834d8ec9048bc7ea20ef28dd8e5aa0cb5055577261858a280000000 reject +tcp_resp 4ec0000000007933afc0000000000000c857de203f333da2241176b1ea160668dc9fa9aa47ffc3fd20943f2a92849eabd10ba7b7e37a7eabd36bc63716d5b1c61fde87efd4d9aaa5e68c3932895310c58c56db261f94e1377b3e09487d66d8b2c121b03dccddf7a7b56ceba60f30596564c68d1e7436d1652016d2fb3c321bd601d86ead0e69365a8b1ea93773caa6995b89f2b7314887492f2a67f86c1f16c51d1ae4da81b65b0ddb07f559289c832268d3a00fa4dfd9afbdfd54f7db154539b91130c624ed45176cdd60f0c3bddc7a80dd024e1d940ccd7cc0000000000000090c6abbb7c6dbe983e2 ConnectTcpResp { status: 78, error_code: 7943087, message: [87, 222, 32, 63, 51, 61, 162, 36, 17, 118, 177, 234, 22, 6, 104, 220, 159, 169, 170, 71, 255, 195, 253, 32, 148, 63, 42, 146, 132, 158, 171, 209, 11, 167, 183, 227, 122, 126, 171, 211, 107, 198, 55, 22, 213, 177, 198, 31, 222, 135, 239, 212, 217, 170, 165, 230, 140, 57, 50, 137, 83, 16, 197, 140, 86, 219, 38, 31, 148, 225, 55, 123, 62, 9, 72, 125, 102, 216, 178, 193, 33, 176, 61, 204, 221, 247, 167, 181, 108, 235, 166, 15, 48, 89, 101, 100, 198, 141, 30, 116, 54, 209, 101, 32, 22, 210, 251, 60, 50, 27, 214, 1, 216, 110, 173, 14, 105, 54, 90, 139, 30, 169, 55, 115, 202, 166, 153, 91, 137, 242, 183, 49, 72, 135, 73, 47, 42, 103, 248, 108, 31, 22, 197, 29, 26, 228, 218, 129, 182, 91, 13, 219, 7, 245, 89, 40, 156, 131, 34, 104, 211, 160, 15, 164, 223, 217, 175, 189, 253, 84, 247, 219, 21, 69, 57, 185, 17, 48, 198, 36, 237, 69, 23, 108, 221, 96, 240, 195, 189, 220, 122, 128, 221, 2, 78, 29, 148, 12, 205, 124] };234;[4e, 80, 79, 33, af, 40, c8, 57, de, 20, 3f, 33, 3d, a2, 24, 11, 76, b1, ea, 16, 06, 68, dc, 9f, a9, aa, 47, ff, c3, fd, 20, 94, 3f, 2a, 92, 84, 9e, ab, d1, 0b, a7, b7, e3, 7a, 7e, ab, d3, 6b, c6, 37, 16, d5, b1, c6, 1f, de, 87, ef, d4, d9, aa, a5, e6, 8c, 39, 32, 89, 53, 10, c5, 8c, 56, db, 26, 1f, 94, e1, 37, 7b, 3e, 09, 48, 7d, 66, d8, b2, c1, 21, b0, 3d, cc, dd, f7, a7, b5, 6c, eb, a6, 0f, 30, 59, 65, 64, c6, 8d, 1e, 74, 36, d1, 65, 20, 16, d2, fb, 3c, 32, 1b, d6, 01, d8, 6e, ad, 0e, 69, 36, 5a, 8b, 1e, a9, 37, 73, ca, a6, 99, 5b, 89, f2, b7, 31, 48, 87, 49, 2f, 2a, 67, f8, 6c, 1f, 16, c5, 1d, 1a, e4, da, 81, b6, 5b, 0d, db, 07, f5, 59, 28, 9c, 83, 22, 68, d3, a0, 0f, a4, df, d9, af, bd, fd, 54, f7, db, 15, 45, 39, b9, 11, 30, c6, 24, ed, 45, 17, 6c, dd, 60, f0, c3, bd, dc, 7a, 80, dd, 02, 4e, 1d, 94, 0c, cd, 7c, 00] +tcp_resp 0a800283258000004b114a30f0450900d9207b133bb2f4fcfa8c1142a1a965ab8f8839745e22cdc1506953cca24272bc1f25a9ef66563f8b8884d748711735bd818a284e66bb66b7770f76debbe7ee36e5139bbbc00000000000000072 accept +tcp_resp c180003f89c0000000000000a60ca865da7cbcff3b84d8447f46c0a85f05ae4b67f2e167a8f9b1154d47e8065211cfcea8381fc41a359ea38d69bc90472c5febd6cda5e453e90dbd6c0a574810f41806038a3555e95670e579cb87b5ee36deb082b8b67aa929996105c31023add1916bcd3176ca4760ca4863f520514ba001ee81ae0d8e6bb21f01a3ba17abc259feb97f5277af87fe7cb8099e7ef4510ffdf26345e0fc23b855e8cb5a508613f06395a6c915c00000000000000b2658aab24979a807da78ca ConnectTcpResp { status: 193, error_code: 16265, message: [12, 168, 101, 218, 124, 188, 255, 59, 132, 216, 68, 127, 70, 192, 168, 95, 5, 174, 75, 103, 242, 225, 103, 168, 249, 177, 21, 77, 71, 232, 6, 82, 17, 207, 206, 168, 56, 31, 196, 26, 53, 158, 163, 141, 105, 188, 144, 71, 44, 95, 235, 214, 205, 165, 228, 83, 233, 13, 189, 108, 10, 87, 72, 16, 244, 24, 6, 3, 138, 53, 85, 233, 86, 112, 229, 121, 203, 135, 181, 238, 54, 222, 176, 130, 184, 182, 122, 169, 41, 153, 97, 5, 195, 16, 35, 173, 209, 145, 107, 205, 49, 118, 202, 71, 96, 202, 72, 99, 245, 32, 81, 75, 160, 1, 238, 129, 174, 13, 142, 107, 178, 31, 1, 163, 186, 23, 171, 194, 89, 254, 185, 127, 82, 119, 175, 135, 254, 124, 184, 9, 158, 126, 244, 81, 15, 253, 242, 99, 69, 224, 252, 35, 184, 85, 232, 203, 90, 80, 134, 19, 240, 99, 149, 166, 201, 21] };198;[c1, 7f, 89, 40, a6, 0c, a8, 65, da, 7c, bc, ff, 3b, 84, d8, 44, 7f, 46, c0, a8, 5f, 05, ae, 4b, 67, f2, e1, 67, a8, f9, b1, 15, 4d, 47, e8, 06, 52, 11, cf, ce, a8, 38, 1f, c4, 1a, 35, 9e, a3, 8d, 69, bc, 90, 47, 2c, 5f, eb, d6, cd, a5, e4, 53, e9, 0d, bd, 6c, 0a, 57, 48, 10, f4, 18, 06, 03, 8a, 35, 55, e9, 56, 70, e5, 79, cb, 87, b5, ee, 36, de, b0, 82, b8, b6, 7a, a9, 29, 99, 61, 05, c3, 10, 23, ad, d1, 91, 6b, cd, 31, 76, ca, 47, 60, ca, 48, 63, f5, 20, 51, 4b, a0, 01, ee, 81, ae, 0d, 8e, 6b, b2, 1f, 01, a3, ba, 17, ab, c2, 59, fe, b9, 7f, 52, 77, af, 87, fe, 7c, b8, 09, 9e, 7e, f4, 51, 0f, fd, f2, 63, 45, e0, fc, 23, b8, 55, e8, cb, 5a, 50, 86, 13, f0, 63, 95, a6, c9, 15, 00] +tcp_resp 0580000006c00000000000001f80683167072054d668dd54356364ab45ff0e2e6e908b038fc864bf8cb399eec000000000000000 ConnectTcpResp { status: 5, error_code: 6, message: [128, 104, 49, 103, 7, 32, 84, 214, 104, 221, 84, 53, 99, 100, 171, 69, 255, 14, 46, 110, 144, 139, 3, 143, 200, 100, 191, 140, 179, 153, 238] };52;[05, 06, 1f, 80, 68, 31, 67, 07, 20, 54, d6, 68, dd, 54, 35, 63, 64, ab, 45, ff, 0e, 2e, 6e, 90, 8b, 03, 8f, c8, 64, bf, 8c, b3, 99, ee, 00] +tcp_resp 68c0000000000a2c4440b534ea7b2b9d9d53e28ba377b517d525a5c0f3831e2a92402afaab47dd2fe4d01dbaaf3ef959e049414b79bb22ee8e95709bf194e002d4e9d7678aa6c0fd94a6bc54c7912dcad8c36ec5834a0525b62b31f0092884d8cee97509a23528f905ec4fbfddbbdbefa2088ae77b6dd15daf38faa02aae3816d0193c319682fcda109dcdf2eccaefdef663871ac33bb52e13575cbbcf44a72f6c3bed0b541e3ad3fb70597b9505fb461b1e7ea2fdbd1e4ab43534790f34229600 ConnectTcpResp { status: 104, error_code: 666692, message: [52, 234, 123, 43, 157, 157, 83, 226, 139, 163, 119, 181, 23, 213, 37, 165, 192, 243, 131, 30, 42, 146, 64, 42, 250, 171, 71, 221, 47, 228, 208, 29, 186, 175, 62, 249, 89, 224, 73, 65, 75, 121, 187, 34, 238, 142, 149, 112, 155, 241, 148, 224, 2, 212, 233, 215, 103, 138, 166, 192, 253, 148, 166, 188, 84, 199, 145, 45, 202, 216, 195, 110, 197, 131, 74, 5, 37, 182, 43, 49, 240, 9, 40, 132, 216, 206, 233, 117, 9, 162, 53, 40, 249, 5, 236, 79, 191, 221, 187, 219, 239, 162, 8, 138, 231, 123, 109, 209, 93, 175, 56, 250, 160, 42, 174, 56, 22, 208, 25, 60, 49, 150, 130, 252, 218, 16, 157, 205, 242, 236, 202, 239, 222, 246, 99, 135, 26, 195, 59, 181, 46, 19, 87, 92, 187, 207, 68, 167, 47, 108, 59, 237, 11, 84, 30, 58, 211, 251, 112, 89, 123, 149, 5, 251, 70, 27, 30, 126, 162, 253, 189, 30, 74, 180, 53, 52, 121, 15, 52, 34, 150] };193;[68, 80, 0a, 2c, 44, 40, b5, 34, ea, 7b, 2b, 9d, 9d, 53, e2, 8b, a3, 77, b5, 17, d5, 25, a5, c0, f3, 83, 1e, 2a, 92, 40, 2a, fa, ab, 47, dd, 2f, e4, d0, 1d, ba, af, 3e, f9, 59, e0, 49, 41, 4b, 79, bb, 22, ee, 8e, 95, 70, 9b, f1, 94, e0, 02, d4, e9, d7, 67, 8a, a6, c0, fd, 94, a6, bc, 54, c7, 91, 2d, ca, d8, c3, 6e, c5, 83, 4a, 05, 25, b6, 2b, 31, f0, 09, 28, 84, d8, ce, e9, 75, 09, a2, 35, 28, f9, 05, ec, 4f, bf, dd, bb, db, ef, a2, 08, 8a, e7, 7b, 6d, d1, 5d, af, 38, fa, a0, 2a, ae, 38, 16, d0, 19, 3c, 31, 96, 82, fc, da, 10, 9d, cd, f2, ec, ca, ef, de, f6, 63, 87, 1a, c3, 3b, b5, 2e, 13, 57, 5c, bb, cf, 44, a7, 2f, 6c, 3b, ed, 0b, 54, 1e, 3a, d3, fb, 70, 59, 7b, 95, 05, fb, 46, 1b, 1e, 7e, a2, fd, bd, 1e, 4a, b4, 35, 34, 79, 0f, 34, 22, 96, 00] +tcp_resp b8ec2acfc8e80d6be38000004fe6ee42608958f8f858c95602a301fb4c6a3cb62292db2f4a824b485b78261d503a9ae8fb79c754580aab5a6b37fd47b8267f45997ed226b64bd4c61ef4e4de5e649e3bbc9ac4a2c4f2631281708fb200 accept +tcp_resp 8ac00000000006c32240a99501015c0bae539a99ba7acf5c04693fdf2cba48638abc785632d8ff279803c87b59b2d292790a93175f33fe49d90419ef5b1b805536ef039461644674ad67283101f124eb4473deaac5dd6d3497694dc33154f7159c2e603f55187da79a51553af929ed9e22990998866f77c5d324f2da561a0e64807ae00c2f570ad22ce910913cd49721aa7bb25d231fdabe5952ee15caf62b9b2697aad555ce6fb84c1aabcf537cf75497ab55de4000 ConnectTcpResp { status: 138, error_code: 443170, message: [149, 1, 1, 92, 11, 174, 83, 154, 153, 186, 122, 207, 92, 4, 105, 63, 223, 44, 186, 72, 99, 138, 188, 120, 86, 50, 216, 255, 39, 152, 3, 200, 123, 89, 178, 210, 146, 121, 10, 147, 23, 95, 51, 254, 73, 217, 4, 25, 239, 91, 27, 128, 85, 54, 239, 3, 148, 97, 100, 70, 116, 173, 103, 40, 49, 1, 241, 36, 235, 68, 115, 222, 170, 197, 221, 109, 52, 151, 105, 77, 195, 49, 84, 247, 21, 156, 46, 96, 63, 85, 24, 125, 167, 154, 81, 85, 58, 249, 41, 237, 158, 34, 153, 9, 152, 134, 111, 119, 197, 211, 36, 242, 218, 86, 26, 14, 100, 128, 122, 224, 12, 47, 87, 10, 210, 44, 233, 16, 145, 60, 212, 151, 33, 170, 123, 178, 93, 35, 31, 218, 190, 89, 82, 238, 21, 202, 246, 43, 155, 38, 151, 170, 213, 85, 206, 111, 184, 76, 26, 171, 207, 83, 124, 247, 84, 151, 171, 85, 222] };182;[8a, 80, 06, c3, 22, 40, a9, 95, 01, 01, 5c, 0b, ae, 53, 9a, 99, ba, 7a, cf, 5c, 04, 69, 3f, df, 2c, ba, 48, 63, 8a, bc, 78, 56, 32, d8, ff, 27, 98, 03, c8, 7b, 59, b2, d2, 92, 79, 0a, 93, 17, 5f, 33, fe, 49, d9, 04, 19, ef, 5b, 1b, 80, 55, 36, ef, 03, 94, 61, 64, 46, 74, ad, 67, 28, 31, 01, f1, 24, eb, 44, 73, de, aa, c5, dd, 6d, 34, 97, 69, 4d, c3, 31, 54, f7, 15, 9c, 2e, 60, 3f, 55, 18, 7d, a7, 9a, 51, 55, 3a, f9, 29, ed, 9e, 22, 99, 09, 98, 86, 6f, 77, c5, d3, 24, f2, da, 56, 1a, 0e, 64, 80, 7a, e0, 0c, 2f, 57, 0a, d2, 2c, e9, 10, 91, 3c, d4, 97, 21, aa, 7b, b2, 5d, 23, 1f, da, be, 59, 52, ee, 15, ca, f6, 2b, 9b, 26, 97, aa, d5, 55, ce, 6f, b8, 4c, 1a, ab, cf, 53, 7c, f7, 54, 97, ab, 55, de, 00] +tcp_resp 5b80321d6c40cd3abb8075c9e7164f24af606ff8dcb0c7190a9bbbccd222a733a90c8576d18c0afdb2640dea97cc2c0ded34c823f5104ab7be43557737f96507f66dfa193a64b399c3284f0056730613d697dcca2bc853e420c4ad120da9317119edeb9870140f47411bf6d556eaf2b1df13397ff7defdaceab34f9cd1db212f15e7c5dcc6d36e2070d7878ff423778ac4a03ef471f8b50d9501aad9301c1e35117d1dedff21ad492e05dde9e4f802e16eec251fc2c21165643d692aec2e592c55327d4acb8b560949c0e75722ce7760bf4e73e180000000 accept +tcp_resp a441182438f70d1dacc1dec1bf621a7cbfc76ef2104e68e8194dad6821e2f62ba60e24c3825f2e2880000000 ConnectTcpResp { status: 164, error_code: 280, message: [56, 247, 13, 29, 172, 193, 222, 193, 191, 98, 26, 124, 191, 199, 110, 242, 16, 78, 104, 232, 25, 77, 173, 104, 33, 226, 246, 43, 166, 14, 36, 195, 130, 95, 46, 40] };44;[a4, 41, 18, 24, 38, f7, 0d, 1d, ac, c1, de, c1, bf, 62, 1a, 7c, bf, c7, 6e, f2, 10, 4e, 68, e8, 19, 4d, ad, 68, 21, e2, f6, 2b, a6, 0e, 24, c3, 82, 5f, 2e, 28, 00] +tcp_resp 32803c2d6640fd14e0dec1022d58269a5abaa61ad4635676617b370e127eea9b7bd11bafb1d0be093c304cf0624a78bc1015027e08f81d7e920d99599bfc425c4b20a6ea289c07d770d60582c8949001874edab608a455f8e4144069bc79985e197fb403d5a434cb9bbe0ea89eda27b187924c4fcbeb1d53ce282816d9ea253c7cc1a177342736ca7e0573982346792ce22e7f55d87952d9581b85eefad77b0bfa9fd946e9a46101b3d7dcbccacb60f8fa352a6a2d081abc5f717dd12c7f3060b44021231b8ebd3ac30734b426537bd78bc355eded2196d3bf011bdd05c70b7b420f8b2ced0ea18fdf8720d77dd9e196402b019729d4df270f3b9c6231de8c542d92da8d4000 ConnectTcpResp { status: 50, error_code: 3943782, message: [20, 224, 222, 193, 2, 45, 88, 38, 154, 90, 186, 166, 26, 212, 99, 86, 118, 97, 123, 55, 14, 18, 126, 234, 155, 123, 209, 27, 175, 177, 208, 190, 9, 60, 48, 76, 240, 98, 74, 120, 188, 16, 21, 2, 126, 8, 248, 29, 126, 146, 13, 153, 89, 155, 252, 66, 92, 75, 32, 166, 234, 40, 156, 7, 215, 112, 214, 5, 130, 200, 148, 144, 1, 135, 78, 218, 182, 8, 164, 85, 248, 228, 20, 64, 105, 188, 121, 152, 94, 25, 127, 180, 3, 213, 164, 52, 203, 155, 190, 14, 168, 158, 218, 39, 177, 135, 146, 76, 79, 203, 235, 29, 83, 206, 40, 40, 22, 217, 234, 37, 60, 124, 193, 161, 119, 52, 39, 54, 202, 126, 5, 115, 152, 35, 70, 121, 44, 226, 46, 127, 85, 216, 121, 82, 217, 88, 27, 133, 238, 250, 215, 123, 11, 250, 159, 217, 70, 233, 164, 97, 1, 179, 215, 220, 188, 202, 203, 96, 248, 250, 53, 42, 106, 45, 8, 26, 188, 95, 113, 125, 209, 44, 127, 48, 96, 180, 64, 33, 35, 27, 142, 189, 58, 195, 7, 52, 180, 38, 83, 123, 215, 139, 195, 85, 237, 237, 33, 150, 211, 191, 1, 27, 221, 5, 199, 11, 123, 66, 15, 139, 44, 237, 14, 161, 143, 223, 135, 32, 215, 125, 217, 225, 150, 64, 43, 1, 151, 41, 212, 223, 39, 15, 59, 156, 98, 49, 222, 140, 84, 45, 146, 218, 141] };262;[32, 80, 3c, 2d, 66, 40, fd, 14, e0, de, c1, 02, 2d, 58, 26, 9a, 5a, ba, a6, 1a, d4, 63, 56, 76, 61, 7b, 37, 0e, 12, 7e, ea, 9b, 7b, d1, 1b, af, b1, d0, be, 09, 3c, 30, 4c, f0, 62, 4a, 78, bc, 10, 15, 02, 7e, 08, f8, 1d, 7e, 92, 0d, 99, 59, 9b, fc, 42, 5c, 4b, 20, a6, ea, 28, 9c, 07, d7, 70, d6, 05, 82, c8, 94, 90, 01, 87, 4e, da, b6, 08, a4, 55, f8, e4, 14, 40, 69, bc, 79, 98, 5e, 19, 7f, b4, 03, d5, a4, 34, cb, 9b, be, 0e, a8, 9e, da, 27, b1, 87, 92, 4c, 4f, cb, eb, 1d, 53, ce, 28, 28, 16, d9, ea, 25, 3c, 7c, c1, a1, 77, 34, 27, 36, ca, 7e, 05, 73, 98, 23, 46, 79, 2c, e2, 2e, 7f, 55, d8, 79, 52, d9, 58, 1b, 85, ee, fa, d7, 7b, 0b, fa, 9f, d9, 46, e9, a4, 61, 01, b3, d7, dc, bc, ca, cb, 60, f8, fa, 35, 2a, 6a, 2d, 08, 1a, bc, 5f, 71, 7d, d1, 2c, 7f, 30, 60, b4, 40, 21, 23, 1b, 8e, bd, 3a, c3, 07, 34, b4, 26, 53, 7b, d7, 8b, c3, 55, ed, ed, 21, 96, d3, bf, 01, 1b, dd, 05, c7, 0b, 7b, 42, 0f, 8b, 2c, ed, 0e, a1, 8f, df, 87, 20, d7, 7d, d9, e1, 96, 40, 2b, 01, 97, 29, d4, df, 27, 0f, 3b, 9c, 62, 31, de, 8c, 54, 2d, 92, da, 8d, 00] +tcp_resp eac0000000000000cd800000c9170b73439370ba0b38a868d19841dc8e937cb059a98648ed4cfdd12cf3dbeb1253019f2545bf8cdd754e65d2d2afcb74de1cc7f03f03a39a645001717d013b7c2bf7bd3bebaeab687b201c22a0a5bf75db58f33dbe72d0360fcd0b54684126e40186b3fe8e10bb587443a6a255410d471a146b016c8bf3665322123996a29dc6a4afa8dcc09f8e9118a40d6ddc45ead0f3fc98375dbb9bbe1a8595222fc555e30dd9a4ab66f4cd23f79e488418afe4400a8ac2234c848775b42e30aa0b1c1c2ebf185fd53c37bacfad80000000 ConnectTcpResp { status: 234, error_code: 205, message: [23, 11, 115, 67, 147, 112, 186, 11, 56, 168, 104, 209, 152, 65, 220, 142, 147, 124, 176, 89, 169, 134, 72, 237, 76, 253, 209, 44, 243, 219, 235, 18, 83, 1, 159, 37, 69, 191, 140, 221, 117, 78, 101, 210, 210, 175, 203, 116, 222, 28, 199, 240, 63, 3, 163, 154, 100, 80, 1, 113, 125, 1, 59, 124, 43, 247, 189, 59, 235, 174, 171, 104, 123, 32, 28, 34, 160, 165, 191, 117, 219, 88, 243, 61, 190, 114, 208, 54, 15, 205, 11, 84, 104, 65, 38, 228, 1, 134, 179, 254, 142, 16, 187, 88, 116, 67, 166, 162, 85, 65, 13, 71, 26, 20, 107, 1, 108, 139, 243, 102, 83, 34, 18, 57, 150, 162, 157, 198, 164, 175, 168, 220, 192, 159, 142, 145, 24, 164, 13, 109, 220, 69, 234, 208, 243, 252, 152, 55, 93, 187, 155, 190, 26, 133, 149, 34, 47, 197, 85, 227, 13, 217, 164, 171, 102, 244, 205, 35, 247, 158, 72, 132, 24, 175, 228, 64, 10, 138, 194, 35, 76, 132, 135, 117, 180, 46, 48, 170, 11, 28, 28, 46, 191, 24, 95, 213, 60, 55, 186, 207, 173] };218;[ea, 40, cd, 40, c9, 17, 0b, 73, 43, 93, 70, ba, 0b, 38, a8, 68, d1, 98, 41, dc, 8e, 93, 7c, b0, 59, a9, 86, 48, ed, 4c, fd, d1, 2c, f3, db, eb, 12, 53, 01, 9f, 25, 45, bf, 8c, dd, 75, 4e, 65, d2, d2, af, cb, 74, de, 1c, c7, f0, 3f, 03, a3, 9a, 64, 50, 01, 71, 7d, 01, 3b, 7c, 2b, f7, bd, 3b, eb, ae, ab, 68, 7b, 20, 1c, 22, a0, a5, bf, 75, db, 58, f3, 3d, be, 72, d0, 36, 0f, cd, 0b, 54, 68, 41, 26, e4, 01, 86, b3, fe, 8e, 10, bb, 58, 74, 43, a6, a2, 55, 41, 0d, 47, 1a, 14, 6b, 01, 6c, 8b, f3, 66, 53, 22, 12, 39, 96, a2, 9d, c6, a4, af, a8, dc, c0, 9f, 8e, 91, 18, a4, 0d, 6d, dc, 45, ea, d0, f3, fc, 98, 37, 5d, bb, 9b, be, 1a, 85, 95, 22, 2f, c5, 55, e3, 0d, d9, a4, ab, 66, f4, cd, 23, f7, 9e, 48, 84, 18, af, e4, 40, 0a, 8a, c2, 23, 4c, 84, 87, 75, b4, 2e, 30, aa, 0b, 1c, 1c, 2e, bf, 18, 5f, d5, 3c, 37, ba, cf, ad, 00] +tcp_resp 8680000000c000000000000101f9e857a49cd8f211f8f2532f6f0945aff7263254418cc49db357abdcb0ccaf2c7c91ba246168c898214506fb39fd11e8074497da3a46783796810a13ac551ef430b7e20df62cde129bd251cc8b439981fa3ff9b7e271747d80e578025ae07d4961940aaf1094c07c24a8770a836b9f607c009b86035ddaa509fd3d129abb333dc910ffd120a6e41df043b9780deaa95f9442e03bf989f46a5ac81ef1279d6efafae6aacbe91703c9ed25d8dec184ddb0164d2cd37f5f3c0ee9816a0471c41b771e04ba71ace06d48911d0c945fd28c8dea4f6630c311805c3bfa643db92e82aa8e24ff95c16006bb339a0bf5324173ab9003c8fff56500a3c7b5259fb4b3e775a600 reject +tcp_resp 5d80000017c00000000000006af1a299d00cbe5059ffcc23b206793ad645ae27b05a6b71936a70905fd8878b7f98a3f08b956d825c0a0184590f3ba88b72c36010ca7b22b995d526fa615a3c5e9fe9d819c235bf28ef971b2221ac5eb967f26a5cca835c743460594c4588a8768bdd1bec889d73fe5a4ec000000000000002e5cf ConnectTcpResp { status: 93, error_code: 23, message: [241, 162, 153, 208, 12, 190, 80, 89, 255, 204, 35, 178, 6, 121, 58, 214, 69, 174, 39, 176, 90, 107, 113, 147, 106, 112, 144, 95, 216, 135, 139, 127, 152, 163, 240, 139, 149, 109, 130, 92, 10, 1, 132, 89, 15, 59, 168, 139, 114, 195, 96, 16, 202, 123, 34, 185, 149, 213, 38, 250, 97, 90, 60, 94, 159, 233, 216, 25, 194, 53, 191, 40, 239, 151, 27, 34, 33, 172, 94, 185, 103, 242, 106, 92, 202, 131, 92, 116, 52, 96, 89, 76, 69, 136, 168, 118, 139, 221, 27, 236, 136, 157, 115, 254, 90, 78] };129;[5d, 17, 40, 6a, f1, a2, 99, d0, 0c, be, 50, 59, ff, cc, 23, b2, 06, 79, 3a, d6, 45, ae, 27, b0, 5a, 6b, 71, 93, 6a, 70, 90, 5f, d8, 87, 8b, 7f, 98, a3, f0, 8b, 95, 6d, 82, 5c, 0a, 01, 84, 59, 0f, 3b, a8, 8b, 72, c3, 60, 10, ca, 7b, 22, b9, 95, d5, 26, fa, 61, 5a, 3c, 5e, 9f, e9, d8, 19, c2, 35, bf, 28, ef, 97, 1b, 22, 21, ac, 5e, b9, 67, f2, 6a, 5c, ca, 83, 5c, 74, 34, 60, 59, 4c, 45, 88, a8, 76, 8b, dd, 1b, ec, 88, 9d, 73, fe, 5a, 4e, 00] +tcp_resp 618002ddf2c0000000000000e693ea16c5e3edd2c76391664c06c5277719f2770c577fab9fd6deae7fd2ddbbbf2ec351e3d7fa3c10cc9407d46540010bff2a93dc327618dd5925bbef52f8768601826002cc9de59f9c19e0b77dd88a529581c84528f6151830f3b94b3c25e55508038e1de19cba4c3d6093f3f48e3d185bcfcf94ea8492a6a84a171c232d5ad1035af0bc8f277b37383bbcf9993d617aa6041e6d0f1268ef2d1455dd522f7abcfe3158681cd7347f3899bf2ac3deb08ef6159a8e91c50bfc20050926d0469ad12aeb3d1d314979e2bac4ed76a94318539c83a45f68ceb207497abb2e6da0e9fc648bf074523ac000000000000000 ConnectTcpResp { status: 97, error_code: 187890, message: [147, 234, 22, 197, 227, 237, 210, 199, 99, 145, 102, 76, 6, 197, 39, 119, 25, 242, 119, 12, 87, 127, 171, 159, 214, 222, 174, 127, 210, 221, 187, 191, 46, 195, 81, 227, 215, 250, 60, 16, 204, 148, 7, 212, 101, 64, 1, 11, 255, 42, 147, 220, 50, 118, 24, 221, 89, 37, 187, 239, 82, 248, 118, 134, 1, 130, 96, 2, 204, 157, 229, 159, 156, 25, 224, 183, 125, 216, 138, 82, 149, 129, 200, 69, 40, 246, 21, 24, 48, 243, 185, 75, 60, 37, 229, 85, 8, 3, 142, 29, 225, 156, 186, 76, 61, 96, 147, 243, 244, 142, 61, 24, 91, 207, 207, 148, 234, 132, 146, 166, 168, 74, 23, 28, 35, 45, 90, 209, 3, 90, 240, 188, 143, 39, 123, 55, 56, 59, 188, 249, 153, 61, 97, 122, 166, 4, 30, 109, 15, 18, 104, 239, 45, 20, 85, 221, 82, 47, 122, 188, 254, 49, 88, 104, 28, 215, 52, 127, 56, 153, 191, 42, 195, 222, 176, 142, 246, 21, 154, 142, 145, 197, 11, 252, 32, 5, 9, 38, 208, 70, 154, 209, 42, 235, 61, 29, 49, 73, 121, 226, 186, 196, 237, 118, 169, 67, 24, 83, 156, 131, 164, 95, 104, 206, 178, 7, 73, 122, 187, 46, 109, 160, 233, 252, 100, 139, 240, 116, 82, 58] };251;[61, 80, 02, dd, f2, 40, e6, 93, ea, 16, c5, e3, ed, d2, c7, 63, 91, 66, 4c, 06, c5, 27, 77, 19, f2, 77, 0c, 57, 7f, ab, 9f, d6, de, ae, 7f, d2, dd, bb, bf, 2e, c3, 51, e3, d7, fa, 3c, 10, cc, 94, 07, d4, 65, 40, 01, 0b, ff, 2a, 93, dc, 32, 76, 18, dd, 59, 25, bb, ef, 52, f8, 76, 86, 01, 82, 60, 02, cc, 9d, e5, 9f, 9c, 19, e0, b7, 7d, d8, 8a, 52, 95, 81, c8, 45, 28, f6, 15, 18, 30, f3, b9, 4b, 3c, 25, e5, 55, 08, 03, 8e, 1d, e1, 9c, ba, 4c, 3d, 60, 93, f3, f4, 8e, 3d, 18, 5b, cf, cf, 94, ea, 84, 92, a6, a8, 4a, 17, 1c, 23, 2d, 5a, d1, 03, 5a, f0, bc, 8f, 27, 7b, 37, 38, 3b, bc, f9, 99, 3d, 61, 7a, a6, 04, 1e, 6d, 0f, 12, 68, ef, 2d, 14, 55, dd, 52, 2f, 7a, bc, fe, 31, 58, 68, 1c, d7, 34, 7f, 38, 99, bf, 2a, c3, de, b0, 8e, f6, 15, 9a, 8e, 91, c5, 0b, fc, 20, 05, 09, 26, d0, 46, 9a, d1, 2a, eb, 3d, 1d, 31, 49, 79, e2, ba, c4, ed, 76, a9, 43, 18, 53, 9c, 83, a4, 5f, 68, ce, b2, 07, 49, 7a, bb, 2e, 6d, a0, e9, fc, 64, 8b, f0, 74, 52, 3a, 00] +tcp_resp b0850bd54f40860b6e9ab728724186caaf1075cbfca3319ac5a6a4d0ac0b135509108362e5d9e749deee730ef185abde5c957e77a3a0d93ccc65f530e5a51792bcb22b6ebca281832075059903f65a8cb9bcfa551505053e12755ea22fbaac33f73dece7e341bda5c929f507c5a33ded7fa2481e3bd541783b4b1e29566ad120e78f08c95a26a8e7e903936db58000000039b588b0ce36 ConnectTcpResp { status: 176, error_code: 84661583, message: [11, 110, 154, 183, 40, 114, 65, 134, 202, 175, 16, 117, 203, 252, 163, 49, 154, 197, 166, 164, 208, 172, 11, 19, 85, 9, 16, 131, 98, 229, 217, 231, 73, 222, 238, 115, 14, 241, 133, 171, 222, 92, 149, 126, 119, 163, 160, 217, 60, 204, 101, 245, 48, 229, 165, 23, 146, 188, 178, 43, 110, 188, 162, 129, 131, 32, 117, 5, 153, 3, 246, 90, 140, 185, 188, 250, 85, 21, 5, 5, 62, 18, 117, 94, 162, 47, 186, 172, 51, 247, 61, 236, 231, 227, 65, 189, 165, 201, 41, 245, 7, 197, 163, 61, 237, 127, 162, 72, 30, 59, 213, 65, 120, 59, 75, 30, 41, 86, 106, 209, 32, 231, 143, 8, 201, 90, 38, 168, 231, 233, 3, 147, 109, 181] };145;[b0, 85, 0b, d5, 4f, 40, 86, 0b, 6e, 9a, b7, 28, 72, 41, 86, ca, af, 10, 75, cb, fc, a3, 31, 9a, c5, a6, a4, d0, ac, 0b, 13, 55, 09, 10, 83, 62, e5, d9, e7, 49, de, ee, 73, 0e, f1, 85, ab, de, 5c, 95, 7e, 77, a3, a0, d9, 3c, cc, 65, f5, 30, e5, a5, 17, 92, bc, b2, 2b, 6e, bc, a2, 81, 83, 20, 75, 05, 99, 03, f6, 5a, 8c, b9, bc, fa, 55, 15, 05, 05, 3e, 12, 75, 5e, a2, 2f, ba, ac, 33, f7, 3d, ec, e7, e3, 41, bd, a5, c9, 29, f5, 07, c5, a3, 3d, ed, 7f, a2, 48, 1e, 3b, d5, 41, 78, 3b, 4b, 1e, 29, 56, 6a, d1, 20, e7, 8f, 08, c9, 5a, 26, a8, e7, e9, 03, 93, 6d, b5, 00] +tcp_resp 8c400040fce0269991776586282c3664a2c38577e7a1145b46ae4c9dfecae3e2e799075bc0c9bcc473ff5cdce39c554d0509700351e34dead2d9e2d636ffa8357493fd937d019dfa7582228a4ff9f7d7673ad0b1517612e1c23aa2a2f701830b03cacd2f7ca2d4a5a5ce20a45db87dbd86800bbd62a0d2bc82f3ecd05ce7b5884db965ba15604e0b0bfd4f2ba99d5ac73f56295c37c92390400614d491f02bb1839359632d583881e23ef5804d53b34e5a0b9112aba0a8cf1fe2effab0989254455601a4b6d8defebb275c1b616458e2016fc8fb7f1268d47a098fa68841252daceda89092e167ac37475acbf2879d83aaacce33e11c1b701ef3f96b3269ccb88e4000 ConnectTcpResp { status: 140, error_code: 0, message: [224, 38, 153, 145, 119, 101, 134, 40, 44, 54, 100, 162, 195, 133, 119, 231, 161, 20, 91, 70, 174, 76, 157, 254, 202, 227, 226, 231, 153, 7, 91, 192, 201, 188, 196, 115, 255, 92, 220, 227, 156, 85, 77, 5, 9, 112, 3, 81, 227, 77, 234, 210, 217, 226, 214, 54, 255, 168, 53, 116, 147, 253, 147, 125, 1, 157, 250, 117, 130, 34, 138, 79, 249, 247, 215, 103, 58, 208, 177, 81, 118, 18, 225, 194, 58, 162, 162, 247, 1, 131, 11, 3, 202, 205, 47, 124, 162, 212, 165, 165, 206, 32, 164, 93, 184, 125, 189, 134, 128, 11, 189, 98, 160, 210, 188, 130, 243, 236, 208, 92, 231, 181, 136, 77, 185, 101, 186, 21, 96, 78, 11, 11, 253, 79, 43, 169, 157, 90, 199, 63, 86, 41, 92, 55, 201, 35, 144, 64, 6, 20, 212, 145, 240, 43, 177, 131, 147, 89, 99, 45, 88, 56, 129, 226, 62, 245, 128, 77, 83, 179, 78, 90, 11, 145, 18, 171, 160, 168, 207, 31, 226, 239, 250, 176, 152, 146, 84, 69, 86, 1, 164, 182, 216, 222, 254, 187, 39, 92, 27, 97, 100, 88, 226, 1, 111, 200, 251, 127, 18, 104, 212, 122, 9, 143, 166, 136, 65, 37, 45, 172, 237, 168, 144, 146, 225, 103, 172, 55, 71, 90, 203, 242, 135, 157, 131, 170, 172, 206, 51, 225, 28, 27, 112, 30, 243, 249, 107, 50, 105, 204, 184, 142] };259;[8c, 00, 40, fc, e0, 26, 99, 91, 77, 65, 86, 28, 2c, 36, 64, a2, c3, 85, 77, e7, a1, 14, 5b, 46, ae, 4c, 9d, fe, ca, e3, e2, e7, 99, 07, 5b, c0, c9, bc, c4, 73, ff, 5c, dc, e3, 9c, 55, 4d, 05, 09, 70, 03, 51, e3, 4d, ea, d2, d9, e2, d6, 36, ff, a8, 35, 74, 93, fd, 93, 7d, 01, 9d, fa, 75, 82, 22, 8a, 4f, f9, f7, d7, 67, 3a, d0, b1, 51, 76, 12, e1, c2, 3a, a2, a2, f7, 01, 83, 0b, 03, ca, cd, 2f, 7c, a2, d4, a5, a5, ce, 20, a4, 5d, b8, 7d, bd, 86, 80, 0b, bd, 62, a0, d2, bc, 82, f3, ec, d0, 5c, e7, b5, 88, 4d, b9, 65, ba, 15, 60, 4e, 0b, 0b, fd, 4f, 2b, a9, 9d, 5a, c7, 3f, 56, 29, 5c, 37, c9, 23, 90, 40, 06, 14, d4, 91, f0, 2b, b1, 83, 93, 59, 63, 2d, 58, 38, 81, e2, 3e, f5, 80, 4d, 53, b3, 4e, 5a, 0b, 91, 12, ab, a0, a8, cf, 1f, e2, ef, fa, b0, 98, 92, 54, 45, 56, 01, a4, b6, d8, de, fe, bb, 27, 5c, 1b, 61, 64, 58, e2, 01, 6f, c8, fb, 7f, 12, 68, d4, 7a, 09, 8f, a6, 88, 41, 25, 2d, ac, ed, a8, 90, 92, e1, 67, ac, 37, 47, 5a, cb, f2, 87, 9d, 83, aa, ac, ce, 33, e1, 1c, 1b, 70, 1e, f3, f9, 6b, 32, 69, cc, b8, 8e, 00] +tcp_resp b18d0f68a680000068957c8649214030957dd02f50f32039a877adcb69937d2bb32b5b5470a861f6fb926efa99d7464eed0398e7907963e2e38a91f288d148ebde3d8cf335d53803388cebab2b72058387ac2ce1eb64d62e4113e9cb3225109993b59204d33b2817c0131c96a63d901dbdc000000000000000 ConnectTcpResp { status: 177, error_code: 219113638, message: [149, 124, 134, 73, 33, 64, 48, 149, 125, 208, 47, 80, 243, 32, 57, 168, 119, 173, 203, 105, 147, 125, 43, 179, 43, 91, 84, 112, 168, 97, 246, 251, 146, 110, 250, 153, 215, 70, 78, 237, 3, 152, 231, 144, 121, 99, 226, 227, 138, 145, 242, 136, 209, 72, 235, 222, 61, 140, 243, 53, 213, 56, 3, 56, 140, 235, 171, 43, 114, 5, 131, 135, 172, 44, 225, 235, 100, 214, 46, 65, 19, 233, 203, 50, 37, 16, 153, 147, 181, 146, 4, 211, 59, 40, 23, 192, 19, 28, 150, 166, 61, 144, 29, 189] };121;[b1, 8d, 0f, 68, a6, 40, 68, 95, 7c, 86, 49, 21, 40, 30, 95, 7d, d0, 2f, 50, f3, 20, 39, a8, 77, ad, cb, 69, 93, 7d, 2b, b3, 2b, 5b, 54, 70, a8, 61, f6, fb, 92, 6e, fa, 99, d7, 46, 4e, ed, 03, 98, e7, 90, 79, 63, e2, e3, 8a, 91, f2, 88, d1, 48, eb, de, 3d, 8c, f3, 35, d5, 38, 03, 38, 8c, eb, ab, 2b, 72, 05, 83, 87, ac, 2c, e1, eb, 64, d6, 2e, 41, 13, e9, cb, 32, 25, 10, 99, 93, b5, 92, 04, d3, 3b, 28, 17, c0, 13, 1c, 96, a6, 3d, 90, 1d, bd, 00] +tcp_resp 2ff181f16d848758b1409e2f82e27cec97a072c93009243d1ce00f5798f831b2e9b3d54380226fecf80ecb72cc1fb3f2e4ed8af1b6ac9d01ff3f5472ab22d271d45727179e3c329530f75d85f85d0c51bdf3055d92f37752e47865c38d55ccbae59331b3b2f9014956645e8f6c867fba0f83e27f0ad01de68119ac13d6615f77b6e2c10b944b091fe315bde697db3a1ddc3a982af26eabe11ba980c249fdd6485c70a0d761c1a6313500 accept +tcp_resp d044a340e76dcfc00fbde58866c6ddfacb08f6544bb3a762eca851afab691bb4ba30460d6e61733b6feeb0c52c82b957bd617e9f51c545ccc903ac1f9db4cc11bd3bd951e33871018694942b20b4d64b61ae408125ec58f76c7ebdd99148b0af5e54b5dcb59f8c39e44affa2c325cd31ba48b2df12021c807ccf8a4b832cb60f9223bbc5a2086a0b940ea2283ba6192985f78b2d51f4700467bcb2ceee92b38db99c1afb75d2824882d382484cd944f303d7805fc922659f91d21995cb834d25c5184be56112a9186d5a41d1b5cfb9a6fcbe9e9c0c5d8e45d105e91d301a37fd17edd137fe65352c92a2586100 ConnectTcpResp { status: 208, error_code: 1187, message: [109, 207, 192, 15, 189, 229, 136, 102, 198, 221, 250, 203, 8, 246, 84, 75, 179, 167, 98, 236, 168, 81, 175, 171, 105, 27, 180, 186, 48, 70, 13, 110, 97, 115, 59, 111, 238, 176, 197, 44, 130, 185, 87, 189, 97, 126, 159, 81, 197, 69, 204, 201, 3, 172, 31, 157, 180, 204, 17, 189, 59, 217, 81, 227, 56, 113, 1, 134, 148, 148, 43, 32, 180, 214, 75, 97, 174, 64, 129, 37, 236, 88, 247, 108, 126, 189, 217, 145, 72, 176, 175, 94, 84, 181, 220, 181, 159, 140, 57, 228, 74, 255, 162, 195, 37, 205, 49, 186, 72, 178, 223, 18, 2, 28, 128, 124, 207, 138, 75, 131, 44, 182, 15, 146, 35, 187, 197, 162, 8, 106, 11, 148, 14, 162, 40, 59, 166, 25, 41, 133, 247, 139, 45, 81, 244, 112, 4, 103, 188, 178, 206, 238, 146, 179, 141, 185, 156, 26, 251, 117, 210, 130, 72, 130, 211, 130, 72, 76, 217, 68, 243, 3, 215, 128, 95, 201, 34, 101, 159, 145, 210, 25, 149, 203, 131, 77, 37, 197, 24, 75, 229, 97, 18, 169, 24, 109, 90, 65, 209, 181, 207, 185, 166, 252, 190, 158, 156, 12, 93, 142, 69, 209, 5, 233, 29, 48, 26, 55, 253, 23, 237, 209, 55, 254, 101, 53, 44, 146, 162, 88, 97] };237;[d0, 44, a3, 40, e7, 6d, cf, c0, 0f, bd, e5, 88, 66, c6, dd, fa, cb, 08, f6, 54, 4b, b3, a7, 62, ec, a8, 51, af, ab, 69, 1b, b4, ba, 30, 46, 0d, 6e, 61, 73, 3b, 6f, ee, b0, c5, 2c, 82, b9, 57, bd, 61, 7e, 9f, 51, c5, 45, cc, c9, 03, ac, 1f, 9d, b4, cc, 11, bd, 3b, d9, 51, e3, 38, 71, 01, 86, 94, 94, 2b, 20, b4, d6, 4b, 61, ae, 40, 81, 25, ec, 58, f7, 6c, 7e, bd, d9, 91, 48, b0, af, 5e, 54, b5, dc, b5, 9f, 8c, 39, e4, 4a, ff, a2, c3, 25, cd, 31, ba, 48, b2, df, 12, 02, 1c, 80, 7c, cf, 8a, 4b, 83, 2c, b6, 0f, 92, 23, bb, c5, a2, 08, 6a, 0b, 94, 0e, a2, 28, 3b, a6, 19, 29, 85, f7, 8b, 2d, 51, f4, 70, 04, 67, bc, b2, ce, ee, 92, b3, 8d, b9, 9c, 1a, fb, 75, d2, 82, 48, 82, d3, 82, 48, 4c, d9, 44, f3, 03, d7, 80, 5f, c9, 22, 65, 9f, 91, d2, 19, 95, cb, 83, 4d, 25, c5, 18, 4b, e5, 61, 12, a9, 18, 6d, 5a, 41, d1, b5, cf, b9, a6, fc, be, 9e, 9c, 0c, 5d, 8e, 45, d1, 05, e9, 1d, 30, 1a, 37, fd, 17, ed, d1, 37, fe, 65, 35, 2c, 92, a2, 58, 61, 00] +tcp_resp b4800e20bf2ce979fccd5fd398128bf4749898733767e2997651478e84ac4cbdcf6952d0403c89983d5953f6444bc665a89900 ConnectTcpResp { status: 180, error_code: 925887, message: [233, 121, 252, 205, 95, 211, 152, 18, 139, 244, 116, 152, 152, 115, 55, 103, 226, 153, 118, 81, 71, 142, 132, 172, 76, 189, 207, 105, 82, 208, 64, 60, 137, 152, 61, 89, 83, 246, 68, 75, 198, 101, 168, 153] };51;[b4, 80, 0e, 20, bf, 2c, e9, 79, fc, cd, 5f, d3, 98, 12, 8b, f4, 74, 98, 98, 73, 37, 67, e2, 99, 76, 51, 47, 8e, 84, ac, 4c, bd, cf, 69, 52, d0, 40, 3c, 89, 98, 3d, 59, 53, f6, 44, 4b, c6, 65, a8, 99, 00] +tcp_resp 298044d6f740b52541a27116cb592ee1ae1518c97c13cc96183e8638426aecaafb888167ba18c7bc2decc563089b477c54c08d6c4202ab60b0c3ecca96dc8a39a40464874ad40cd192da81eb27aaf0969005014e0a180218a491d89782a165eb3b8e616f6817c135987a6c31d3d1679cc10965c5e856b5d4fd9c483914b13f72cdfaeae6158e02f56ae3b4d79edcd84c650e80044ca3d40d2e01b96c7ad4f2b0de06ba15ce62d81f5770d4e0707badcb9d991653103dfbb3579b6dff01054b ConnectTcpResp { status: 41, error_code: 4511479, message: [37, 65, 162, 113, 22, 203, 89, 46, 225, 174, 21, 24, 201, 124, 19, 204, 150, 24, 62, 134, 56, 66, 106, 236, 170, 251, 136, 129, 103, 186, 24, 199, 188, 45, 236, 197, 99, 8, 155, 71, 124, 84, 192, 141, 108, 66, 2, 171, 96, 176, 195, 236, 202, 150, 220, 138, 57, 164, 4, 100, 135, 74, 212, 12, 209, 146, 218, 129, 235, 39, 170, 240, 150, 144, 5, 1, 78, 10, 24, 2, 24, 164, 145, 216, 151, 130, 161, 101, 235, 59, 142, 97, 111, 104, 23, 193, 53, 152, 122, 108, 49, 211, 209, 103, 156, 193, 9, 101, 197, 232, 86, 181, 212, 253, 156, 72, 57, 20, 177, 63, 114, 205, 250, 234, 230, 21, 142, 2, 245, 106, 227, 180, 215, 158, 220, 216, 76, 101, 14, 128, 4, 76, 163, 212, 13, 46, 1, 185, 108, 122, 212, 242, 176, 222, 6, 186, 21, 206, 98, 216, 31, 87, 112, 212, 224, 112, 123, 173, 203, 157, 153, 22, 83, 16, 61, 251, 179, 87, 155, 109, 255] };190;[29, 80, 44, d6, f7, 40, b5, 25, 41, a2, 71, 16, cb, 59, 2e, e1, ae, 15, 18, c9, 7c, 13, cc, 96, 18, 3e, 86, 38, 42, 6a, ec, aa, fb, 88, 81, 67, ba, 18, c7, bc, 2d, ec, c5, 63, 08, 9b, 47, 7c, 54, c0, 8d, 6c, 42, 02, ab, 60, b0, c3, ec, ca, 96, dc, 8a, 39, a4, 04, 64, 87, 4a, d4, 0c, d1, 92, da, 81, eb, 27, aa, f0, 96, 90, 05, 01, 4e, 0a, 18, 02, 18, a4, 91, d8, 97, 82, a1, 65, eb, 3b, 8e, 61, 6f, 68, 17, c1, 35, 98, 7a, 6c, 31, d3, d1, 67, 9c, c1, 09, 65, c5, e8, 56, b5, d4, fd, 9c, 48, 39, 14, b1, 3f, 72, cd, fa, ea, e6, 15, 8e, 02, f5, 6a, e3, b4, d7, 9e, dc, d8, 4c, 65, 0e, 80, 04, 4c, a3, d4, 0d, 2e, 01, b9, 6c, 7a, d4, f2, b0, de, 06, ba, 15, ce, 62, d8, 1f, 57, 70, d4, e0, 70, 7b, ad, cb, 9d, 99, 16, 53, 10, 3d, fb, b3, 57, 9b, 6d, ff, 00] +tcp_resp 5a8000005a40d8ea4a85162539af905b7407b937d4d2e69705449220ddd59b169fea012b5a9b23111efbff538f2d95a22d136d575448ea94fa155369f5dea727f3de473a4bd422669f57bd10efe9bc927a4503d8295f75384c911d3a02d4bc1c3cd77e83359ce8404946e8a2d044222c077c626612f1f099f7f6884c50d89fba3bdaa2bc12ce190cd2773c09d73c58df8eccecccafc53e7691e783ea150b6ce7ae376ce2121a80a109b381ae60a07f414c0aaeff79f7ea740d121c7d225e9ceae232bf30ed74235aca607e8a67e9947dfa3a7c3dff8bce72f328b23fe3a1d480000005b5e48f646c ConnectTcpResp { status: 90, error_code: 90, message: [234, 74, 133, 22, 37, 57, 175, 144, 91, 116, 7, 185, 55, 212, 210, 230, 151, 5, 68, 146, 32, 221, 213, 155, 22, 159, 234, 1, 43, 90, 155, 35, 17, 30, 251, 255, 83, 143, 45, 149, 162, 45, 19, 109, 87, 84, 72, 234, 148, 250, 21, 83, 105, 245, 222, 167, 39, 243, 222, 71, 58, 75, 212, 34, 102, 159, 87, 189, 16, 239, 233, 188, 146, 122, 69, 3, 216, 41, 95, 117, 56, 76, 145, 29, 58, 2, 212, 188, 28, 60, 215, 126, 131, 53, 156, 232, 64, 73, 70, 232, 162, 208, 68, 34, 44, 7, 124, 98, 102, 18, 241, 240, 153, 247, 246, 136, 76, 80, 216, 159, 186, 59, 218, 162, 188, 18, 206, 25, 12, 210, 119, 60, 9, 215, 60, 88, 223, 142, 204, 236, 204, 175, 197, 62, 118, 145, 231, 131, 234, 21, 11, 108, 231, 174, 55, 108, 226, 18, 26, 128, 161, 9, 179, 129, 174, 96, 160, 127, 65, 76, 10, 174, 255, 121, 247, 234, 116, 13, 18, 28, 125, 34, 94, 156, 234, 226, 50, 191, 48, 237, 116, 35, 90, 202, 96, 126, 138, 103, 233, 148, 125, 250, 58, 124, 61, 255, 139, 206, 114, 243, 40, 178, 63, 227, 161, 212] };232;[5a, 40, 5a, 40, d8, ea, 4a, 85, 16, 25, 39, af, 90, 5b, 74, 07, b9, 37, d4, d2, e6, 97, 05, 44, 92, 20, dd, d5, 9b, 16, 9f, ea, 01, 2b, 5a, 9b, 23, 11, 1e, fb, ff, 53, 8f, 2d, 95, a2, 2d, 13, 6d, 57, 54, 48, ea, 94, fa, 15, 53, 69, f5, de, a7, 27, f3, de, 47, 3a, 4b, d4, 22, 66, 9f, 57, bd, 10, ef, e9, bc, 92, 7a, 45, 03, d8, 29, 5f, 75, 38, 4c, 91, 1d, 3a, 02, d4, bc, 1c, 3c, d7, 7e, 83, 35, 9c, e8, 40, 49, 46, e8, a2, d0, 44, 22, 2c, 07, 7c, 62, 66, 12, f1, f0, 99, f7, f6, 88, 4c, 50, d8, 9f, ba, 3b, da, a2, bc, 12, ce, 19, 0c, d2, 77, 3c, 09, d7, 3c, 58, df, 8e, cc, ec, cc, af, c5, 3e, 76, 91, e7, 83, ea, 15, 0b, 6c, e7, ae, 37, 6c, e2, 12, 1a, 80, a1, 09, b3, 81, ae, 60, a0, 7f, 41, 4c, 0a, ae, ff, 79, f7, ea, 74, 0d, 12, 1c, 7d, 22, 5e, 9c, ea, e2, 32, bf, 30, ed, 74, 23, 5a, ca, 60, 7e, 8a, 67, e9, 94, 7d, fa, 3a, 7c, 3d, ff, 8b, ce, 72, f3, 28, b2, 3f, e3, a1, d4, 00] +tcp_resp 2e4206c0000000000000e5f3612cdc6bd4fa20d860f2dc8e9f3eb9254bebf524352165eaaa282f073bfd66316defd9699d4563888108ce7d92ad7adfb9397134fbe88d0681cfe7172163a9e3784c7eeeaab38631620c9a2a6e26d8d2418f8fd2e1ea3ab9216b32fc5e5aeab16e3e3c4fcd1b1c72688abeeb713b0c3d62e1506775d05f0a0a8dbc1a7d8a0181ecb44da3fc7d579f0e9234748c21edf367a25fab0ecb4dd631189044f735f5d156cd3d056742e8159430ea3def4cf444e938acb10ac429315ea15c385cdcbc729838315b0d0c6a39110afb7e7f0c51510740ecb156bd6636d483c4a135e863c42e3315db00fba1f7 ConnectTcpResp { status: 46, error_code: 518, message: [243, 97, 44, 220, 107, 212, 250, 32, 216, 96, 242, 220, 142, 159, 62, 185, 37, 75, 235, 245, 36, 53, 33, 101, 234, 170, 40, 47, 7, 59, 253, 102, 49, 109, 239, 217, 105, 157, 69, 99, 136, 129, 8, 206, 125, 146, 173, 122, 223, 185, 57, 113, 52, 251, 232, 141, 6, 129, 207, 231, 23, 33, 99, 169, 227, 120, 76, 126, 238, 170, 179, 134, 49, 98, 12, 154, 42, 110, 38, 216, 210, 65, 143, 143, 210, 225, 234, 58, 185, 33, 107, 50, 252, 94, 90, 234, 177, 110, 62, 60, 79, 205, 27, 28, 114, 104, 138, 190, 235, 113, 59, 12, 61, 98, 225, 80, 103, 117, 208, 95, 10, 10, 141, 188, 26, 125, 138, 1, 129, 236, 180, 77, 163, 252, 125, 87, 159, 14, 146, 52, 116, 140, 33, 237, 243, 103, 162, 95, 171, 14, 203, 77, 214, 49, 24, 144, 68, 247, 53, 245, 209, 86, 205, 61, 5, 103, 66, 232, 21, 148, 48, 234, 61, 239, 76, 244, 68, 233, 56, 172, 177, 10, 196, 41, 49, 94, 161, 92, 56, 92, 220, 188, 114, 152, 56, 49, 91, 13, 12, 106, 57, 17, 10, 251, 126, 127, 12, 81, 81, 7, 64, 236, 177, 86, 189, 102, 54, 212, 131, 196, 161, 53, 232, 99, 196, 46, 51, 21, 219] };241;[2e, 42, 06, 40, e5, f3, 61, 2c, dc, 6b, d4, fa, 20, d8, 60, f2, dc, 8e, 9f, 3e, b9, 25, 4b, eb, f5, 24, 35, 21, 65, ea, aa, 28, 2f, 07, 3b, fd, 66, 31, 6d, ef, d9, 69, 9d, 45, 63, 88, 81, 08, ce, 7d, 92, ad, 7a, df, b9, 39, 71, 34, fb, e8, 8d, 06, 81, cf, e7, 17, 21, 63, a9, e3, 78, 4c, 7e, ee, aa, b3, 86, 31, 62, 0c, 9a, 2a, 6e, 26, d8, d2, 41, 8f, 8f, d2, e1, ea, 3a, b9, 21, 6b, 32, fc, 5e, 5a, ea, b1, 6e, 3e, 3c, 4f, cd, 1b, 1c, 72, 68, 8a, be, eb, 71, 3b, 0c, 3d, 62, e1, 50, 67, 75, d0, 5f, 0a, 0a, 8d, bc, 1a, 7d, 8a, 01, 81, ec, b4, 4d, a3, fc, 7d, 57, 9f, 0e, 92, 34, 74, 8c, 21, ed, f3, 67, a2, 5f, ab, 0e, cb, 4d, d6, 31, 18, 90, 44, f7, 35, f5, d1, 56, cd, 3d, 05, 67, 42, e8, 15, 94, 30, ea, 3d, ef, 4c, f4, 44, e9, 38, ac, b1, 0a, c4, 29, 31, 5e, a1, 5c, 38, 5c, dc, bc, 72, 98, 38, 31, 5b, 0d, 0c, 6a, 39, 11, 0a, fb, 7e, 7f, 0c, 51, 51, 07, 40, ec, b1, 56, bd, 66, 36, d4, 83, c4, a1, 35, e8, 63, c4, 2e, 33, 15, db, 00] +tcp_resp 9d00404da7079920e8b01715a161fde8d1b654534b2db45d1b1a25724bfe6b2e45d6104e41764e2cdb22337455b9b7051d3cc166204ac109542c168353674cdfd5b342b1f0628508ddacb7e243b7c8aadb02c89f ConnectTcpResp { status: 157, error_code: 0, message: [167, 7, 153, 32, 232, 176, 23, 21, 161, 97, 253, 232, 209, 182, 84, 83, 75, 45, 180, 93, 27, 26, 37, 114, 75, 254, 107, 46, 69, 214, 16, 78, 65, 118, 78, 44, 219, 34, 51, 116, 85, 185, 183, 5, 29, 60, 193, 102, 32, 74, 193, 9, 84, 44, 22, 131, 83, 103, 76, 223, 213, 179, 66, 177, 240, 98, 133, 8, 221, 172, 183, 226, 67, 183, 200, 170, 219] };84;[9d, 00, 40, 4d, a7, 07, 99, 20, e8, b0, 17, 15, a1, 61, fd, e8, d1, b6, 54, 53, 4b, 2d, b4, 5d, 1b, 1a, 25, 72, 4b, fe, 6b, 2e, 45, d6, 10, 4e, 41, 76, 4e, 2c, db, 22, 33, 74, 55, b9, b7, 05, 1d, 3c, c1, 66, 20, 4a, c1, 09, 54, 2c, 16, 83, 53, 67, 4c, df, d5, b3, 42, b1, f0, 62, 85, 08, dd, ac, b7, e2, 43, b7, c8, aa, db, 00] +tcp_resp 4967f08000009a2a77acba4bd4a3511fe283f7f89962b1bf43ac460f292b7f77188ec0e6d7b09da8cf95f392fb181a35d142df536fb74937a9c1c926afac2eca5ce61bf660b2d1b17fc6ed4e463bcb594ee2fa62a00789ff3dd47067c79ced8d696da08e5e0b1eb8347de15b17d5d789608e8da0f18a73675b96b1a31699f054ad2e0c34fdc11e967ebaf626346e645eb330438647d9ff5b5821159a1c10ab38af4000 ConnectTcpResp { status: 73, error_code: 10224, message: [42, 119, 172, 186, 75, 212, 163, 81, 31, 226, 131, 247, 248, 153, 98, 177, 191, 67, 172, 70, 15, 41, 43, 127, 119, 24, 142, 192, 230, 215, 176, 157, 168, 207, 149, 243, 146, 251, 24, 26, 53, 209, 66, 223, 83, 111, 183, 73, 55, 169, 193, 201, 38, 175, 172, 46, 202, 92, 230, 27, 246, 96, 178, 209, 177, 127, 198, 237, 78, 70, 59, 203, 89, 78, 226, 250, 98, 160, 7, 137, 255, 61, 212, 112, 103, 199, 156, 237, 141, 105, 109, 160, 142, 94, 11, 30, 184, 52, 125, 225, 91, 23, 213, 215, 137, 96, 142, 141, 160, 241, 138, 115, 103, 91, 150, 177, 163, 22, 153, 240, 84, 173, 46, 12, 52, 253, 193, 30, 150, 126, 186, 246, 38, 52, 110, 100, 94, 179, 48, 67, 134, 71, 217, 255, 91, 88, 33, 21, 154, 28, 16, 171, 56, 175] };163;[49, 67, f0, 40, 9a, 2a, 77, ac, ba, 4b, d4, a3, 51, 1f, e2, 83, f7, f8, 99, 62, b1, bf, 43, ac, 46, 0f, 29, 2b, 7f, 77, 18, 8e, c0, e6, d7, b0, 9d, a8, cf, 95, f3, 92, fb, 18, 1a, 35, d1, 42, df, 53, 6f, b7, 49, 37, a9, c1, c9, 26, af, ac, 2e, ca, 5c, e6, 1b, f6, 60, b2, d1, b1, 7f, c6, ed, 4e, 46, 3b, cb, 59, 4e, e2, fa, 62, a0, 07, 89, ff, 3d, d4, 70, 67, c7, 9c, ed, 8d, 69, 6d, a0, 8e, 5e, 0b, 1e, b8, 34, 7d, e1, 5b, 17, d5, d7, 89, 60, 8e, 8d, a0, f1, 8a, 73, 67, 5b, 96, b1, a3, 16, 99, f0, 54, ad, 2e, 0c, 34, fd, c1, 1e, 96, 7e, ba, f6, 26, 34, 6e, 64, 5e, b3, 30, 43, 86, 47, d9, ff, 5b, 58, 21, 15, 9a, 1c, 10, ab, 38, af, 00] +tcp_resp 3d80000015c000000000000006ec40d84aad35c000000000000006012b5932e249 ConnectTcpResp { status: 61, error_code: 21, message: [236, 64, 216, 74, 173, 53] };33;[3d, 15, 06, ec, 40, d8, 4a, ad, 35, 00] +tcp_resp 158002c361c000000000000016349664facfa0ef62fc85825971100499e7dd0669f673c000000000000000 ConnectTcpResp { status: 21, error_code: 181089, message: [52, 150, 100, 250, 207, 160, 239, 98, 252, 133, 130, 89, 113, 16, 4, 153, 231, 221, 6, 105, 246, 115] };43;[15, 80, 02, c3, 61, 16, 34, 96, 64, fa, cf, a0, ef, 62, fc, 85, 82, 59, 71, 10, 04, 99, e7, dd, 06, 69, f6, 73, 00] +tcp_resp aa8000009540dbc7980e0513e2f61f38697570af138d3801c72848426f381ec527a84631625492f25f087cb8d5628e843382aa225c94978540938fa8fe3da53c097bd185d25fdc57e3b973abe24e526b2e23003f1a0f4b11e449c576ad9acd1dc2f9931fedb33afbd50d690bc78991f87713559a71ce19349754a4bd144cefc3f6ba810b4a68ccbc4490e2404f44908eddeb05bbd9317343711b0281e517773beabec732e9654fde62fa6559aa7ebf9504e06520568f872e46a2261d91cd6cae15ad4b8049935996c2ffbc4115a0fa0df1e5cfcb1f1c130a134ca4bf59b393a6b8714000 accept +udp_open 010001040808080800350000 UdpSessionOpen { session_id: 1, flags: 0, address_type: Ipv4, host: [8, 8, 8, 8], port: 53, idle_timeout_ms: 0 };12;[01, 00, 01, 04, 08, 08, 08, 08, 00, 35, 00, 00] +udp_open reject +udp_open 80 reject +udp_open 8002 reject +udp_open 800216 reject +udp_open 80021681 reject +udp_open 8002168180 reject +udp_open 80021681800e reject +udp_open 80021681800e09 reject +udp_open 80021681800e093e reject +udp_open 80021681800e093e03 reject +udp_open 80021681800e093e0340 reject +udp_open 80021681800e093e034042 reject +udp_open 80021681800e093e03404229 reject +udp_open 80021681800e093e0340422990 reject +udp_open 80021681800e093e0340422990d8 reject +udp_open 80021681800e093e0340422990d8e5 reject +udp_open 80021681800e093e0340422990d8e52b reject +udp_open 80021681800e093e0340422990d8e52beb reject +udp_open 80021681800e093e0340422990d8e52beb09 reject +udp_open 80021681800e093e0340422990d8e52beb09bf reject +udp_open 80021681800e093e0340422990d8e52beb09bf5a reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1b reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1bab reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaa reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0de reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1de reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded3 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded35878 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab166396 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16639675 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d2 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa3365 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33659324 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241f reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe448 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4481271 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d7 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a4 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f11 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f8840 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a30 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bf reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd6 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2b reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf7 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5e reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef8 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fb reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c0 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c000 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c0000000 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c000000000 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000000003 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000000003a4 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000000003a4c1 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000000003a4c1c0 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000000003a4c1c000 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000000003a4c1c00000 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000000003a4c1c0000000 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000000003a4c1c000000000 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000000003a4c1c00000000000 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000000003a4c1c0000000000000 reject +udp_open 80021681800e093e0340422990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836fbf1c00000000003a4c1c000000000000000 UdpSessionOpen { session_id: 136833, flags: 919870, address_type: Domain, host: [41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76, 163, 215, 132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54], port: 64497, idle_timeout_ms: 238785 };95;[80, 02, 16, 81, 80, 0e, 09, 3e, 03, 40, 42, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, a3, d7, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, fb, f1, 80, 03, a4, c1, 00] +udp_open 8005514485cd393e031cc3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b8aa88a59b78014000 UdpSessionOpen { session_id: 348484, flags: 97335614, address_type: Domain, host: [195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244, 107, 30, 239, 29, 38, 233, 184], port: 43656, idle_timeout_ms: 630945793 };46;[80, 05, 51, 44, 85, cd, 39, 3e, 03, 1c, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 6b, 1e, ef, 1d, 26, e9, b8, aa, 88, a5, 9b, 78, 01, 00] +udp_open 801c4dc8c02a5ef5efa6039a01800000a7825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48682d37a4c9d4896d14dcc8be6fcb3cae2a8ed41906905a00c79116c42f6efc3abeb588cd8433e48b916d29a040f1f56dfaae34040bfee40c54373755fff8070a31ca4e98ed392278b7de69abdbccd6ed00898000ad11c0000000000000004cdb61 UdpSessionOpen { session_id: 1854920, flags: 11926359402480538, address_type: Ipv4, host: [130, 93, 113, 102, 21, 200, 54, 126, 138, 121, 96, 231, 34, 98, 186, 205, 209, 72, 172, 95, 102, 63, 151, 112, 137, 150, 127, 105, 153, 50, 30, 82, 5, 84, 163, 148, 204, 77, 58, 12, 100, 228, 74, 221, 134, 136, 76, 55, 227, 50, 43, 251, 227, 156, 202, 123, 83, 167, 77, 217, 23, 113, 126, 173, 44, 101, 122, 211, 16, 172, 2, 236, 62, 75, 214, 150, 0, 200, 1, 18, 249, 1, 92, 94, 95, 29, 72, 104, 45, 55, 164, 201, 212, 137, 109, 20, 220, 200, 190, 111, 203, 60, 174, 42, 142, 212, 25, 6, 144, 90, 0, 199, 145, 22, 196, 47, 110, 252, 58, 190, 181, 136, 205, 132, 51, 228, 139, 145, 109, 41, 160, 64, 241, 245, 109, 250, 174, 52, 4, 11, 254, 228, 12, 84, 55, 55, 85, 255, 248, 7, 10, 49, 202, 78, 152, 237, 57, 34, 120, 183, 222, 105, 171, 219, 204, 214, 237], port: 137, idle_timeout_ms: 44305 };198;[80, 1c, 4d, c8, c0, 2a, 5e, f5, ef, a6, 03, 9a, 01, 40, a7, 82, 5d, 71, 66, 15, c8, 36, 7e, 8a, 79, 60, e7, 22, 62, ba, cd, d1, 48, ac, 5f, 66, 3f, 97, 70, 89, 96, 7f, 69, 99, 32, 1e, 52, 05, 54, a3, 94, cc, 4d, 3a, 0c, 64, e4, 4a, dd, 86, 88, 4c, 37, e3, 32, 2b, fb, e3, 9c, ca, 7b, 53, a7, 4d, d9, 17, 71, 7e, ad, 2c, 65, 7a, d3, 10, ac, 02, ec, 3e, 4b, d6, 96, 00, c8, 01, 12, f9, 01, 5c, 5e, 5f, 1d, 48, 68, 2d, 37, a4, c9, d4, 89, 6d, 14, dc, c8, be, 6f, cb, 3c, ae, 2a, 8e, d4, 19, 06, 90, 5a, 00, c7, 91, 16, c4, 2f, 6e, fc, 3a, be, b5, 88, cd, 84, 33, e4, 8b, 91, 6d, 29, a0, 40, f1, f5, 6d, fa, ae, 34, 04, 0b, fe, e4, 0c, 54, 37, 37, 55, ff, f8, 07, 0a, 31, ca, 4e, 98, ed, 39, 22, 78, b7, de, 69, ab, db, cc, d6, ed, 00, 89, 80, 00, ad, 11, 00] +udp_open 80028b74c00000e7d5d958cb03407941b28dceab810b061154399cb6cb22960706ecdc059f8ab72d06629b4d6881b73d140f46922fe7829f40b4905e000456b9d4cfa52935adb2d90b422e69297d8132b12d4bee0c567158668dce9817977065d567234e29dc1c0b073bf895cd7c66ace40ecab63c2d2a9be3487e51cd29f1ed51a93fe5a5c892b32edcc000054bc59e791880000000 UdpSessionOpen { session_id: 166772, flags: 995725236427, address_type: Domain, host: [65, 178, 141, 206, 171, 129, 11, 6, 17, 84, 57, 156, 182, 203, 34, 150, 7, 6, 236, 220, 5, 159, 138, 183, 45, 6, 98, 155, 77, 104, 129, 183, 61, 20, 15, 70, 146, 47, 231, 130, 159, 64, 180, 144, 94, 0, 4, 86, 185, 212, 207, 165, 41, 53, 173, 178, 217, 11, 66, 46, 105, 41, 125, 129, 50, 177, 45, 75, 238, 12, 86, 113, 88, 102, 141, 206, 152, 23, 151, 112, 101, 213, 103, 35, 78, 41, 220, 28, 11, 7, 59, 248, 149, 205, 124, 102, 172, 228, 14, 202, 182, 60, 45, 42, 155, 227, 72, 126, 81, 205, 41, 241, 237, 81, 169, 63, 229, 165, 200, 146, 179], port: 11996, idle_timeout_ms: 5822996183320 };150;[80, 02, 8b, 74, c0, 00, 00, e7, d5, d9, 58, cb, 03, 40, 79, 41, b2, 8d, ce, ab, 81, 0b, 06, 11, 54, 39, 9c, b6, cb, 22, 96, 07, 06, ec, dc, 05, 9f, 8a, b7, 2d, 06, 62, 9b, 4d, 68, 81, b7, 3d, 14, 0f, 46, 92, 2f, e7, 82, 9f, 40, b4, 90, 5e, 00, 04, 56, b9, d4, cf, a5, 29, 35, ad, b2, d9, 0b, 42, 2e, 69, 29, 7d, 81, 32, b1, 2d, 4b, ee, 0c, 56, 71, 58, 66, 8d, ce, 98, 17, 97, 70, 65, d5, 67, 23, 4e, 29, dc, 1c, 0b, 07, 3b, f8, 95, cd, 7c, 66, ac, e4, 0e, ca, b6, 3c, 2d, 2a, 9b, e3, 48, 7e, 51, cd, 29, f1, ed, 51, a9, 3f, e5, a5, c8, 92, b3, 2e, dc, c0, 00, 05, 4b, c5, 9e, 79, 18, 00] +udp_open 8062ebf656b404800000dc44f8de699a9723e60222de2997de191c1d712fffa0700dd5443f929102b81179b3a2f93ca80eddf35bce6aa609439c89860b0173935e59d0429831aedbee71abb4ded9744f25778f2c5e18c83a516e0f8c52a1325cd3df6225608e089cb79822ae3c4e964fc7fb440fc12d9199d03b0f93eec7afbbbd6354fc94f152f44291b4754d6941f0a709f23f7a075e2826813e113c9a06f49871ddb099ab2ad13be057c703b111585e8fe76d619f2dfb72c906ad202f604a64b4338f589eb76fd4f39162284f326cd28e0daa0fc04b7e0f07f875c4491f0876db23e7a935cfa48c40084000 UdpSessionOpen { session_id: 6482934, flags: 5812, address_type: Ipv6, host: [68, 248, 222, 105, 154, 151, 35, 230, 2, 34, 222, 41, 151, 222, 25, 28, 29, 113, 47, 255, 160, 112, 13, 213, 68, 63, 146, 145, 2, 184, 17, 121, 179, 162, 249, 60, 168, 14, 221, 243, 91, 206, 106, 166, 9, 67, 156, 137, 134, 11, 1, 115, 147, 94, 89, 208, 66, 152, 49, 174, 219, 238, 113, 171, 180, 222, 217, 116, 79, 37, 119, 143, 44, 94, 24, 200, 58, 81, 110, 15, 140, 82, 161, 50, 92, 211, 223, 98, 37, 96, 142, 8, 156, 183, 152, 34, 174, 60, 78, 150, 79, 199, 251, 68, 15, 193, 45, 145, 153, 208, 59, 15, 147, 238, 199, 175, 187, 189, 99, 84, 252, 148, 241, 82, 244, 66, 145, 180, 117, 77, 105, 65, 240, 167, 9, 242, 63, 122, 7, 94, 40, 38, 129, 62, 17, 60, 154, 6, 244, 152, 113, 221, 176, 153, 171, 42, 209, 59, 224, 87, 199, 3, 177, 17, 88, 94, 143, 231, 109, 97, 159, 45, 251, 114, 201, 6, 173, 32, 47, 96, 74, 100, 180, 51, 143, 88, 158, 183, 111, 212, 243, 145, 98, 40, 79, 50, 108, 210, 142, 13, 170, 15, 192, 75, 126, 15, 7, 248, 117, 196, 73, 31, 8, 118, 219, 35, 231, 169, 53, 207], port: 42124, idle_timeout_ms: 8 };237;[80, 62, eb, f6, 56, b4, 04, 40, dc, 44, f8, de, 69, 9a, 97, 23, e6, 02, 22, de, 29, 97, de, 19, 1c, 1d, 71, 2f, ff, a0, 70, 0d, d5, 44, 3f, 92, 91, 02, b8, 11, 79, b3, a2, f9, 3c, a8, 0e, dd, f3, 5b, ce, 6a, a6, 09, 43, 9c, 89, 86, 0b, 01, 73, 93, 5e, 59, d0, 42, 98, 31, ae, db, ee, 71, ab, b4, de, d9, 74, 4f, 25, 77, 8f, 2c, 5e, 18, c8, 3a, 51, 6e, 0f, 8c, 52, a1, 32, 5c, d3, df, 62, 25, 60, 8e, 08, 9c, b7, 98, 22, ae, 3c, 4e, 96, 4f, c7, fb, 44, 0f, c1, 2d, 91, 99, d0, 3b, 0f, 93, ee, c7, af, bb, bd, 63, 54, fc, 94, f1, 52, f4, 42, 91, b4, 75, 4d, 69, 41, f0, a7, 09, f2, 3f, 7a, 07, 5e, 28, 26, 81, 3e, 11, 3c, 9a, 06, f4, 98, 71, dd, b0, 99, ab, 2a, d1, 3b, e0, 57, c7, 03, b1, 11, 58, 5e, 8f, e7, 6d, 61, 9f, 2d, fb, 72, c9, 06, ad, 20, 2f, 60, 4a, 64, b4, 33, 8f, 58, 9e, b7, 6f, d4, f3, 91, 62, 28, 4f, 32, 6c, d2, 8e, 0d, aa, 0f, c0, 4b, 7e, 0f, 07, f8, 75, c4, 49, 1f, 08, 76, db, 23, e7, a9, 35, cf, a4, 8c, 08, 00] +udp_open 8191d516c0370c1fbbde619bbd8000008797bc7df349eebf00d7dbf32b7b4a8184c8e308239c4836e06e6f380ce1d0ac10416355ea789123d60736259222b981ebb7f3cd4831e326334a213ea9200cb0349e10298d37c30f1c7d2b7d48993335dbf42f5f671920826c8b20336523f338e439ffa35fc5b8e912eea23e9e2244c404deb1a55b145b518b6690a19107239e1ae2aa02ac8bb83612e4c0000514a57d1ad4c00000000000000840c2db22e04dad0a298455306074 reject +udp_open 86637e68c0016c687e7315f204800000a4fdd275a3086e36c82209de102a61b2521a8db3d8856f8f26e3cce9a378180da007ed46690447f9dbfdac36a6fb516443f6f8ed3df6922e909386f0d69d94ff84807fdf7f175f02c04c2b48c4391545f302f04acfd1107e3320759d09fdb3441b6f706af9a059f36b595a73942c77f0355f628d447143c8a4d62e22277d6a2638382f4cf8091dd248e79b017248944b89864107dbf685fcbe49684a8eba6596a100ae82f23e97c00005019ef9fe2c033fa8d8403bf5d364 UdpSessionOpen { session_id: 107183720, flags: 400671030580722, address_type: Ipv6, host: [253, 210, 117, 163, 8, 110, 54, 200, 34, 9, 222, 16, 42, 97, 178, 82, 26, 141, 179, 216, 133, 111, 143, 38, 227, 204, 233, 163, 120, 24, 13, 160, 7, 237, 70, 105, 4, 71, 249, 219, 253, 172, 54, 166, 251, 81, 100, 67, 246, 248, 237, 61, 246, 146, 46, 144, 147, 134, 240, 214, 157, 148, 255, 132, 128, 127, 223, 127, 23, 95, 2, 192, 76, 43, 72, 196, 57, 21, 69, 243, 2, 240, 74, 207, 209, 16, 126, 51, 32, 117, 157, 9, 253, 179, 68, 27, 111, 112, 106, 249, 160, 89, 243, 107, 89, 90, 115, 148, 44, 119, 240, 53, 95, 98, 141, 68, 113, 67, 200, 164, 214, 46, 34, 39, 125, 106, 38, 56, 56, 47, 76, 248, 9, 29, 210, 72, 231, 155, 1, 114, 72, 148, 75, 137, 134, 65, 7, 219, 246, 133, 252, 190, 73, 104, 74, 142, 186, 101, 150, 161, 0, 174, 130, 242], port: 16023, idle_timeout_ms: 5504520289836 };195;[86, 63, 7e, 68, c0, 01, 6c, 68, 7e, 73, 15, f2, 04, 40, a4, fd, d2, 75, a3, 08, 6e, 36, c8, 22, 09, de, 10, 2a, 61, b2, 52, 1a, 8d, b3, d8, 85, 6f, 8f, 26, e3, cc, e9, a3, 78, 18, 0d, a0, 07, ed, 46, 69, 04, 47, f9, db, fd, ac, 36, a6, fb, 51, 64, 43, f6, f8, ed, 3d, f6, 92, 2e, 90, 93, 86, f0, d6, 9d, 94, ff, 84, 80, 7f, df, 7f, 17, 5f, 02, c0, 4c, 2b, 48, c4, 39, 15, 45, f3, 02, f0, 4a, cf, d1, 10, 7e, 33, 20, 75, 9d, 09, fd, b3, 44, 1b, 6f, 70, 6a, f9, a0, 59, f3, 6b, 59, 5a, 73, 94, 2c, 77, f0, 35, 5f, 62, 8d, 44, 71, 43, c8, a4, d6, 2e, 22, 27, 7d, 6a, 26, 38, 38, 2f, 4c, f8, 09, 1d, d2, 48, e7, 9b, 01, 72, 48, 94, 4b, 89, 86, 41, 07, db, f6, 85, fc, be, 49, 68, 4a, 8e, ba, 65, 96, a1, 00, ae, 82, f2, 3e, 97, c0, 00, 05, 01, 9e, f9, fe, 2c, 00] +udp_open 80002d78580e8cc0000000000000e0c77c6d31efa1386799e942737721a21f11fb7dc3537fea049b46022c1b35e94d3e4df3e7bd7b77c0478621e9b68d05f26056b7dcba0d10fd36f177853ea5ecf64e1d1f3b0ebdce35069376735d0a7020a1e7e6b7aeec5c6e786d545a46e23b2916bc104f33c25262177da56b178762e599b12c606b256d4133f364f97e373df78a9b4a3116d3a2d7948fdfb8fe7b7241574f136235d96ee0961c25b4d2a6315fea3009dea32eddcfec7a30f014861e9822713d0d43407ddee1caa46a2c39d4b269ead41d9542b8004440b9cf813852dfe4a3a944b1194ebcb6f61a0868e12b6df2c1c967eb1fc53a8d9480000000 reject +udp_open 4007c00000000035394c0440bcabb2b4051c3b54a0ef9f1cca6c0013b0e0d2829140f72cb5d47c0add31b1c23fde5e9c17a297d1a34c3a30db4265cb082c8919d32ccb6a9a48ac18ce1d12b8bfbad1a0e58ad50963e8bbbe813ed69d003c99af6694f304c22848dafd9c618edeca65f053a022f8ef88a9158042e8ef218fae7f134905df35216f23dc5cd18d2bdb1ed3b1de0d1912792624d925037c6804c98ce21fd0c967a0cab946bb853b8ff747349c1bea4b2efe19e8cfdd39c3bb19c35e4c2bbd0b31adb1d3a13ec9800161e1c00000000000000680e53f6ea0aba168e287 UdpSessionOpen { session_id: 7, flags: 3488076, address_type: Ipv6, host: [171, 178, 180, 5, 28, 59, 84, 160, 239, 159, 28, 202, 108, 0, 19, 176, 224, 210, 130, 145, 64, 247, 44, 181, 212, 124, 10, 221, 49, 177, 194, 63, 222, 94, 156, 23, 162, 151, 209, 163, 76, 58, 48, 219, 66, 101, 203, 8, 44, 137, 25, 211, 44, 203, 106, 154, 72, 172, 24, 206, 29, 18, 184, 191, 186, 209, 160, 229, 138, 213, 9, 99, 232, 187, 190, 129, 62, 214, 157, 0, 60, 153, 175, 102, 148, 243, 4, 194, 40, 72, 218, 253, 156, 97, 142, 222, 202, 101, 240, 83, 160, 34, 248, 239, 136, 169, 21, 128, 66, 232, 239, 33, 143, 174, 127, 19, 73, 5, 223, 53, 33, 111, 35, 220, 92, 209, 141, 43, 219, 30, 211, 177, 222, 13, 25, 18, 121, 38, 36, 217, 37, 3, 124, 104, 4, 201, 140, 226, 31, 208, 201, 103, 160, 202, 185, 70, 187, 133, 59, 143, 247, 71, 52, 156, 27, 234, 75, 46, 254, 25, 232, 207, 221, 57, 195, 187, 25, 195, 94, 76, 43, 189, 11, 49, 173, 177, 211, 161], port: 16073, idle_timeout_ms: 90593 };221;[07, 80, 35, 39, 4c, 04, 40, bc, ab, b2, b4, 05, 1c, 3b, 54, a0, ef, 9f, 1c, ca, 6c, 00, 13, b0, e0, d2, 82, 91, 40, f7, 2c, b5, d4, 7c, 0a, dd, 31, b1, c2, 3f, de, 5e, 9c, 17, a2, 97, d1, a3, 4c, 3a, 30, db, 42, 65, cb, 08, 2c, 89, 19, d3, 2c, cb, 6a, 9a, 48, ac, 18, ce, 1d, 12, b8, bf, ba, d1, a0, e5, 8a, d5, 09, 63, e8, bb, be, 81, 3e, d6, 9d, 00, 3c, 99, af, 66, 94, f3, 04, c2, 28, 48, da, fd, 9c, 61, 8e, de, ca, 65, f0, 53, a0, 22, f8, ef, 88, a9, 15, 80, 42, e8, ef, 21, 8f, ae, 7f, 13, 49, 05, df, 35, 21, 6f, 23, dc, 5c, d1, 8d, 2b, db, 1e, d3, b1, de, 0d, 19, 12, 79, 26, 24, d9, 25, 03, 7c, 68, 04, c9, 8c, e2, 1f, d0, c9, 67, a0, ca, b9, 46, bb, 85, 3b, 8f, f7, 47, 34, 9c, 1b, ea, 4b, 2e, fe, 19, e8, cf, dd, 39, c3, bb, 19, c3, 5e, 4c, 2b, bd, 0b, 31, ad, b1, d3, a1, 3e, c9, 80, 01, 61, e1, 00] +udp_open c0000001000000308f28d7ff01408adb0c7a499d9173cef8755605b81f4de7a3d3a83db474c95f9ca6871fb7f32440ba1b10de500d0d259b5e52df7149ea99d44d5a9261837640e64ca491932cbe3953e3ff059159d4798b17ed87db84080468cdbe464687ab16d89ff2ddb657aa1e292401e6eadbf1f82e4b805b7c534d32cbde351b5cd8b0b48fc752e36ebb4c8b8ef7b57a4771dc86f1a5f019c234a107b598b7e700 reject +udp_open 81c1e14643090140d933aa23c5c74bbc6183f92c663919fb083692443427c77260111cac482c1346b28121f1a2cbe2b37067e3fe6fcb87c92f1e7ffa3b27be3408dff01ed962bf124abbd243f7015c49509d70c568629433cac03123405e39d0f9924c4624785ff92f3750420dd8d7120ae0877fbe0527d2d75dd4cc65b5876179054ee3e5ec265fa3878a1925a7db8154a680e7825d253769adf8e7986ee54c97b1d4d9a77fb755eaa841e446cc262d8ea5012b84f291c8255c4eed48cfd63bc7c4865b1c2b9c456b64b8417b93a9c8903932efd3b74a5b528d29c97428d2cbd63bad3b80da261180000000 UdpSessionOpen { session_id: 29483334, flags: 777, address_type: Ipv4, host: [51, 170, 35, 197, 199, 75, 188, 97, 131, 249, 44, 102, 57, 25, 251, 8, 54, 146, 68, 52, 39, 199, 114, 96, 17, 28, 172, 72, 44, 19, 70, 178, 129, 33, 241, 162, 203, 226, 179, 112, 103, 227, 254, 111, 203, 135, 201, 47, 30, 127, 250, 59, 39, 190, 52, 8, 223, 240, 30, 217, 98, 191, 18, 74, 187, 210, 67, 247, 1, 92, 73, 80, 157, 112, 197, 104, 98, 148, 51, 202, 192, 49, 35, 64, 94, 57, 208, 249, 146, 76, 70, 36, 120, 95, 249, 47, 55, 80, 66, 13, 216, 215, 18, 10, 224, 135, 127, 190, 5, 39, 210, 215, 93, 212, 204, 101, 181, 135, 97, 121, 5, 78, 227, 229, 236, 38, 95, 163, 135, 138, 25, 37, 167, 219, 129, 84, 166, 128, 231, 130, 93, 37, 55, 105, 173, 248, 231, 152, 110, 229, 76, 151, 177, 212, 217, 167, 127, 183, 85, 234, 168, 65, 228, 70, 204, 38, 45, 142, 165, 1, 43, 132, 242, 145, 200, 37, 92, 78, 237, 72, 207, 214, 59, 199, 196, 134, 91, 28, 43, 156, 69, 107, 100, 184, 65, 123, 147, 169, 200, 144, 57, 50, 239, 211, 183, 74, 91, 82, 141, 41, 201, 116, 40, 210, 203, 214, 59], port: 44347, idle_timeout_ms: 14296593 };236;[81, c1, e1, 46, 43, 09, 01, 40, d9, 33, aa, 23, c5, c7, 4b, bc, 61, 83, f9, 2c, 66, 39, 19, fb, 08, 36, 92, 44, 34, 27, c7, 72, 60, 11, 1c, ac, 48, 2c, 13, 46, b2, 81, 21, f1, a2, cb, e2, b3, 70, 67, e3, fe, 6f, cb, 87, c9, 2f, 1e, 7f, fa, 3b, 27, be, 34, 08, df, f0, 1e, d9, 62, bf, 12, 4a, bb, d2, 43, f7, 01, 5c, 49, 50, 9d, 70, c5, 68, 62, 94, 33, ca, c0, 31, 23, 40, 5e, 39, d0, f9, 92, 4c, 46, 24, 78, 5f, f9, 2f, 37, 50, 42, 0d, d8, d7, 12, 0a, e0, 87, 7f, be, 05, 27, d2, d7, 5d, d4, cc, 65, b5, 87, 61, 79, 05, 4e, e3, e5, ec, 26, 5f, a3, 87, 8a, 19, 25, a7, db, 81, 54, a6, 80, e7, 82, 5d, 25, 37, 69, ad, f8, e7, 98, 6e, e5, 4c, 97, b1, d4, d9, a7, 7f, b7, 55, ea, a8, 41, e4, 46, cc, 26, 2d, 8e, a5, 01, 2b, 84, f2, 91, c8, 25, 5c, 4e, ed, 48, cf, d6, 3b, c7, c4, 86, 5b, 1c, 2b, 9c, 45, 6b, 64, b8, 41, 7b, 93, a9, c8, 90, 39, 32, ef, d3, b7, 4a, 5b, 52, 8d, 29, c9, 74, 28, d2, cb, d6, 3b, ad, 3b, 80, da, 26, 11, 00] +udp_open c000000000000560c00000000000000cdc80000101dc9e8340c5ba1c37fcd40bf879995533a18e41c7733c83748c72e4a198db8ce4f183ce5b66014c865e58ac8d2131086ae83feeedc398d9b88363bc79892934a7e819d2cf0ea0a41c07b59a06a2791ca24314194d8c1a74e817c1b139cfe2daedaa3a190e69742df4489bc00400a8d0cf4440b1142c96d2ab0497ee6372806c8743b1ecec52a45511024058488c00dd81364732643b81dcf20bf823206672fb702f2c22e308de17fd7322d088999921870006b23ed44056e0bc3b6dc55b5787a938ff50485da1bc991985b29164997d339f4159d0ebf5d82de63ccda8d1f2f15a9f934a56f3e4db1ca35663161701cf464706193259507dc67d0dca619e396add065fcc807a1e61c00000000000000aa99000859fe49cbf853d reject +udp_open 80064dcec000000eb29a32b9038000008c64b778f1fd703a61a1a5661d0c8ffa679bb2a0d63f89bbdd867cc8e993cb3cef180175b61deda347c7d10cbf73664d99fc53c25c3d52163251e0927e021c98f76f48127d1e02a2378edcafb402131ac332d1e83d60aefe35bd40501a78b08031a1a309acf267f46473d6058b2e337a6a887509673329822b9bd838c2de468019edd22c10733ce00514be245d809cc0000192108a4bee40003d1ed1a1c7ed UdpSessionOpen { session_id: 413134, flags: 63125992121, address_type: Domain, host: [100, 183, 120, 241, 253, 112, 58, 97, 161, 165, 102, 29, 12, 143, 250, 103, 155, 178, 160, 214, 63, 137, 187, 221, 134, 124, 200, 233, 147, 203, 60, 239, 24, 1, 117, 182, 29, 237, 163, 71, 199, 209, 12, 191, 115, 102, 77, 153, 252, 83, 194, 92, 61, 82, 22, 50, 81, 224, 146, 126, 2, 28, 152, 247, 111, 72, 18, 125, 30, 2, 162, 55, 142, 220, 175, 180, 2, 19, 26, 195, 50, 209, 232, 61, 96, 174, 254, 53, 189, 64, 80, 26, 120, 176, 128, 49, 161, 163, 9, 172, 242, 103, 244, 100, 115, 214, 5, 139, 46, 51, 122, 106, 136, 117, 9, 103, 51, 41, 130, 43, 155, 216, 56, 194, 222, 70, 128, 25, 237, 210, 44, 16, 115, 60, 224, 5, 20, 190, 36, 93], port: 32924, idle_timeout_ms: 1726854351854 };169;[80, 06, 4d, ce, c0, 00, 00, 0e, b2, 9a, 32, b9, 03, 40, 8c, 64, b7, 78, f1, fd, 70, 3a, 61, a1, a5, 66, 1d, 0c, 8f, fa, 67, 9b, b2, a0, d6, 3f, 89, bb, dd, 86, 7c, c8, e9, 93, cb, 3c, ef, 18, 01, 75, b6, 1d, ed, a3, 47, c7, d1, 0c, bf, 73, 66, 4d, 99, fc, 53, c2, 5c, 3d, 52, 16, 32, 51, e0, 92, 7e, 02, 1c, 98, f7, 6f, 48, 12, 7d, 1e, 02, a2, 37, 8e, dc, af, b4, 02, 13, 1a, c3, 32, d1, e8, 3d, 60, ae, fe, 35, bd, 40, 50, 1a, 78, b0, 80, 31, a1, a3, 09, ac, f2, 67, f4, 64, 73, d6, 05, 8b, 2e, 33, 7a, 6a, 88, 75, 09, 67, 33, 29, 82, 2b, 9b, d8, 38, c2, de, 46, 80, 19, ed, d2, 2c, 10, 73, 3c, e0, 05, 14, be, 24, 5d, 80, 9c, c0, 00, 01, 92, 10, 8a, 4b, ee, 00] +udp_open 8000003d8000cf7f044012eb05eec1f0dde833b3f9787b4098b529cf31904bc0a0b25adb3e4b500039d9c2 UdpSessionOpen { session_id: 61, flags: 53119, address_type: Ipv6, host: [235, 5, 238, 193, 240, 221, 232, 51, 179, 249, 120, 123, 64, 152, 181, 41, 207, 49], port: 36939, idle_timeout_ms: 45232099568798544 };40;[3d, 80, 00, cf, 7f, 04, 12, eb, 05, ee, c1, f0, dd, e8, 33, b3, f9, 78, 7b, 40, 98, b5, 29, cf, 31, 90, 4b, c0, a0, b2, 5a, db, 3e, 4b, 50, 00] +udp_open 80b5914b80e112de0440c87914d4dd2dbc85e149b8cd3e7e17c302cd853c75effe80a0906501ce05fbc007c7c982a90dbba03d0fff7d4455e7cf363ffe21fc4ff0acd913caf851484038d8d358837b4f66adb9852c48e8acecc2025fe5c85b83df064d27b984a136d847fe0ee7f9923fccf69a5f6c184a70f6be47322f3dd3f54f37f398d176a1a2b3e75bb6e4d787422383ccd8dc66abecdcfc51a143eed6fd98eae4a1d7ebe3ffbba034ea4d98cda4e6532a90749882ca384df9f2d4630d74b9daa6b26b69c0b30e2c447e652a51b496928b2c17c0000000b72687a84000 UdpSessionOpen { session_id: 11899211, flags: 14750430, address_type: Ipv6, host: [121, 20, 212, 221, 45, 188, 133, 225, 73, 184, 205, 62, 126, 23, 195, 2, 205, 133, 60, 117, 239, 254, 128, 160, 144, 101, 1, 206, 5, 251, 192, 7, 199, 201, 130, 169, 13, 187, 160, 61, 15, 255, 125, 68, 85, 231, 207, 54, 63, 254, 33, 252, 79, 240, 172, 217, 19, 202, 248, 81, 72, 64, 56, 216, 211, 88, 131, 123, 79, 102, 173, 185, 133, 44, 72, 232, 172, 236, 194, 2, 95, 229, 200, 91, 131, 223, 6, 77, 39, 185, 132, 161, 54, 216, 71, 254, 14, 231, 249, 146, 63, 204, 246, 154, 95, 108, 24, 74, 112, 246, 190, 71, 50, 47, 61, 211, 245, 79, 55, 243, 152, 209, 118, 161, 162, 179, 231, 91, 182, 228, 215, 135, 66, 35, 131, 204, 216, 220, 102, 171, 236, 220, 252, 81, 161, 67, 238, 214, 253, 152, 234, 228, 161, 215, 235, 227, 255, 187, 160, 52, 234, 77, 152, 205, 164, 230, 83, 42, 144, 116, 152, 130, 202, 56, 77, 249, 242, 212, 99, 13, 116, 185, 218, 166, 178, 107, 105, 192, 179, 14, 44, 68, 126, 101, 42, 81, 180, 150, 146, 139], port: 11287, idle_timeout_ms: 3072755624 };223;[80, b5, 91, 4b, 80, e1, 12, de, 04, 40, c8, 79, 14, d4, dd, 2d, bc, 85, e1, 49, b8, cd, 3e, 7e, 17, c3, 02, cd, 85, 3c, 75, ef, fe, 80, a0, 90, 65, 01, ce, 05, fb, c0, 07, c7, c9, 82, a9, 0d, bb, a0, 3d, 0f, ff, 7d, 44, 55, e7, cf, 36, 3f, fe, 21, fc, 4f, f0, ac, d9, 13, ca, f8, 51, 48, 40, 38, d8, d3, 58, 83, 7b, 4f, 66, ad, b9, 85, 2c, 48, e8, ac, ec, c2, 02, 5f, e5, c8, 5b, 83, df, 06, 4d, 27, b9, 84, a1, 36, d8, 47, fe, 0e, e7, f9, 92, 3f, cc, f6, 9a, 5f, 6c, 18, 4a, 70, f6, be, 47, 32, 2f, 3d, d3, f5, 4f, 37, f3, 98, d1, 76, a1, a2, b3, e7, 5b, b6, e4, d7, 87, 42, 23, 83, cc, d8, dc, 66, ab, ec, dc, fc, 51, a1, 43, ee, d6, fd, 98, ea, e4, a1, d7, eb, e3, ff, bb, a0, 34, ea, 4d, 98, cd, a4, e6, 53, 2a, 90, 74, 98, 82, ca, 38, 4d, f9, f2, d4, 63, 0d, 74, b9, da, a6, b2, 6b, 69, c0, b3, 0e, 2c, 44, 7e, 65, 2a, 51, b4, 96, 92, 8b, 2c, 17, c0, 00, 00, 00, b7, 26, 87, a8, 00] +udp_open c0000000000f93fb80000000034100ef6c8b0cb5f2931764c49a978d386ddad1eea0b5b5c37e702c5d52cf0dcca392d7b090d5985c4f326dba59bcc2f082c4219cce531a126ba085e59253a89cac3e355f80fbd268148f123f31773cedde97849a26dd2edf4ef322341e14c431f8bce0f12367a30c0b9c5438863e1df7064caa1dbdeca852d3b91536c8878b90056548bdcd316279b15bfe2b5ee07693d9f2fe341944dd0efadb710b208f7714a405c03dc03a09cd45861220d203b1fb072c415c6a07afedca2bff6bd6745ff60259919cc4ef982f3ed12e369dc45053d8ed3a427d6b4be8a46178d86ea93088ec8bc32c73369d3d4b6f18913f25d7b5cf23eccae3c604a0f8b8b7837679b3d259ac7ca8c0095feb0660e84c00 reject +udp_open 01c00000457444a3cc04c00000000000002f1d6d213a7ccf0f15717c3ef210b683e87801c653ef6af4ed30ddc8ef277beabc39a544c935960f3c76b8f72bf0a840600dc0000003d6f73de3c000000000000000 UdpSessionOpen { session_id: 1, flags: 298303398860, address_type: Ipv6, host: [29, 109, 33, 58, 124, 207, 15, 21, 113, 124, 62, 242, 16, 182, 131, 232, 120, 1, 198, 83, 239, 106, 244, 237, 48, 221, 200, 239, 39, 123, 234, 188, 57, 165, 68, 201, 53, 150, 15, 60, 118, 184, 247, 43, 240, 168, 64], port: 24589, idle_timeout_ms: 16491429347 };83;[01, c0, 00, 00, 45, 74, 44, a3, cc, 04, 2f, 1d, 6d, 21, 3a, 7c, cf, 0f, 15, 71, 7c, 3e, f2, 10, b6, 83, e8, 78, 01, c6, 53, ef, 6a, f4, ed, 30, dd, c8, ef, 27, 7b, ea, bc, 39, a5, 44, c9, 35, 96, 0f, 3c, 76, b8, f7, 2b, f0, a8, 40, 60, 0d, c0, 00, 00, 03, d6, f7, 3d, e3, 00] +udp_open 802919bec0000005b7f0911b04800000335503e5757b0b57e6d8bfa70dd0ccf09ad742b9bc6704678be0cfb5eeae0ceebb902912cc6237d70869bced05f947632599829b442b9961cd8a800000083405fda931439ed1 UdpSessionOpen { session_id: 2693566, flags: 24560832795, address_type: Ipv6, host: [85, 3, 229, 117, 123, 11, 87, 230, 216, 191, 167, 13, 208, 204, 240, 154, 215, 66, 185, 188, 103, 4, 103, 139, 224, 207, 181, 238, 174, 12, 238, 187, 144, 41, 18, 204, 98, 55, 215, 8, 105, 188, 237, 5, 249, 71, 99, 37, 153, 130, 155], port: 17451, idle_timeout_ms: 425840010 };86;[80, 29, 19, be, c0, 00, 00, 05, b7, f0, 91, 1b, 04, 33, 55, 03, e5, 75, 7b, 0b, 57, e6, d8, bf, a7, 0d, d0, cc, f0, 9a, d7, 42, b9, bc, 67, 04, 67, 8b, e0, cf, b5, ee, ae, 0c, ee, bb, 90, 29, 12, cc, 62, 37, d7, 08, 69, bc, ed, 05, f9, 47, 63, 25, 99, 82, 9b, 44, 2b, 99, 61, cd, 8a, 00] +udp_open 84b3c37b806b8da40440ad2dddd3a04299c9653cbdefd0b6e8b29b8174b3a8d61a1775352d5590b443e70e3d18c3fe0693fc1a87e6d434a33c3cd177a3040a50a45d44a643e454626796aed84efe5ba633191e5e5ba9494ca56b50b93c39df1ef23612134025bcfd7d6c7738d04a192e298f944bd89d6c201cee8341047cc096d3b0af4e4788be8a4d937e6bc019377d6090b9a0ab813a509f38a62c5159e3e6629694af9570e348d624f7b947d55404ef01cb849d6e4a1dd524850a4215c000000000000005814a8b81ae UdpSessionOpen { session_id: 78889851, flags: 7048612, address_type: Ipv6, host: [45, 221, 211, 160, 66, 153, 201, 101, 60, 189, 239, 208, 182, 232, 178, 155, 129, 116, 179, 168, 214, 26, 23, 117, 53, 45, 85, 144, 180, 67, 231, 14, 61, 24, 195, 254, 6, 147, 252, 26, 135, 230, 212, 52, 163, 60, 60, 209, 119, 163, 4, 10, 80, 164, 93, 68, 166, 67, 228, 84, 98, 103, 150, 174, 216, 78, 254, 91, 166, 51, 25, 30, 94, 91, 169, 73, 76, 165, 107, 80, 185, 60, 57, 223, 30, 242, 54, 18, 19, 64, 37, 188, 253, 125, 108, 119, 56, 208, 74, 25, 46, 41, 143, 148, 75, 216, 157, 108, 32, 28, 238, 131, 65, 4, 124, 192, 150, 211, 176, 175, 78, 71, 136, 190, 138, 77, 147, 126, 107, 192, 25, 55, 125, 96, 144, 185, 160, 171, 129, 58, 80, 159, 56, 166, 44, 81, 89, 227, 230, 98, 150, 148, 175, 149, 112, 227, 72, 214, 36, 247, 185, 71, 213, 84, 4, 239, 1, 203, 132, 157, 110, 74, 29], port: 54564, idle_timeout_ms: 84558357 };203;[84, b3, c3, 7b, 80, 6b, 8d, a4, 04, 40, ad, 2d, dd, d3, a0, 42, 99, c9, 65, 3c, bd, ef, d0, b6, e8, b2, 9b, 81, 74, b3, a8, d6, 1a, 17, 75, 35, 2d, 55, 90, b4, 43, e7, 0e, 3d, 18, c3, fe, 06, 93, fc, 1a, 87, e6, d4, 34, a3, 3c, 3c, d1, 77, a3, 04, 0a, 50, a4, 5d, 44, a6, 43, e4, 54, 62, 67, 96, ae, d8, 4e, fe, 5b, a6, 33, 19, 1e, 5e, 5b, a9, 49, 4c, a5, 6b, 50, b9, 3c, 39, df, 1e, f2, 36, 12, 13, 40, 25, bc, fd, 7d, 6c, 77, 38, d0, 4a, 19, 2e, 29, 8f, 94, 4b, d8, 9d, 6c, 20, 1c, ee, 83, 41, 04, 7c, c0, 96, d3, b0, af, 4e, 47, 88, be, 8a, 4d, 93, 7e, 6b, c0, 19, 37, 7d, 60, 90, b9, a0, ab, 81, 3a, 50, 9f, 38, a6, 2c, 51, 59, e3, e6, 62, 96, 94, af, 95, 70, e3, 48, d6, 24, f7, b9, 47, d5, 54, 04, ef, 01, cb, 84, 9d, 6e, 4a, 1d, d5, 24, 85, 0a, 42, 15, 00] +udp_open 58f302048000007f0ae2f432fd3e3b2758db4ea1c55b17ee3de2dcbba20ad50d8768de9fbec1cd8da6cad135109efa7c3ec9178607e91958dd81df9c9818197ee0cfab02c22835e652b71454beab76a8c8315083f950c71bb7ae1b5808ed557ca40a4efd719ce0beb754c56640b5db3fc3e8957a3a8a39e667b6baa5a0b020c4724110dd82f483e8338000f813c000000000000005ff381a22ef UdpSessionOpen { session_id: 6387, flags: 2, address_type: Ipv6, host: [10, 226, 244, 50, 253, 62, 59, 39, 88, 219, 78, 161, 197, 91, 23, 238, 61, 226, 220, 187, 162, 10, 213, 13, 135, 104, 222, 159, 190, 193, 205, 141, 166, 202, 209, 53, 16, 158, 250, 124, 62, 201, 23, 134, 7, 233, 25, 88, 221, 129, 223, 156, 152, 24, 25, 126, 224, 207, 171, 2, 194, 40, 53, 230, 82, 183, 20, 84, 190, 171, 118, 168, 200, 49, 80, 131, 249, 80, 199, 27, 183, 174, 27, 88, 8, 237, 85, 124, 164, 10, 78, 253, 113, 156, 224, 190, 183, 84, 197, 102, 64, 181, 219, 63, 195, 232, 149, 122, 58, 138, 57, 230, 103, 182, 186, 165, 160, 176, 32, 196, 114, 65, 16, 221, 130, 244, 131], port: 59443, idle_timeout_ms: 63507 };154;[58, f3, 02, 04, 40, 7f, 0a, e2, f4, 32, fd, 3e, 3b, 27, 58, db, 4e, a1, c5, 5b, 17, ee, 3d, e2, dc, bb, a2, 0a, d5, 0d, 87, 68, de, 9f, be, c1, cd, 8d, a6, ca, d1, 35, 10, 9e, fa, 7c, 3e, c9, 17, 86, 07, e9, 19, 58, dd, 81, df, 9c, 98, 18, 19, 7e, e0, cf, ab, 02, c2, 28, 35, e6, 52, b7, 14, 54, be, ab, 76, a8, c8, 31, 50, 83, f9, 50, c7, 1b, b7, ae, 1b, 58, 08, ed, 55, 7c, a4, 0a, 4e, fd, 71, 9c, e0, be, b7, 54, c5, 66, 40, b5, db, 3f, c3, e8, 95, 7a, 3a, 8a, 39, e6, 67, b6, ba, a5, a0, b0, 20, c4, 72, 41, 10, dd, 82, f4, 83, e8, 33, 80, 00, f8, 13, 00] +udp_open c00000001217a758c00077c7b749a2d20180000014b3df51498cc2547b8e9d70f9bc0532e26ff5d4a89acec388a43f2b340ef540048ecb7d17 UdpSessionOpen { session_id: 303540056, flags: 131699657253586, address_type: Ipv4, host: [179, 223, 81, 73, 140, 194, 84, 123, 142, 157, 112, 249, 188, 5, 50, 226, 111, 245, 212, 168], port: 39630, idle_timeout_ms: 254633970161159925 };57;[92, 17, a7, 58, c0, 00, 77, c7, b7, 49, a2, d2, 01, 14, b3, df, 51, 49, 8c, c2, 54, 7b, 8e, 9d, 70, f9, bc, 05, 32, e2, 6f, f5, d4, a8, 9a, ce, c3, 88, a4, 3f, 2b, 34, 0e, f5, 00] +udp_open 0880004315c0092031c22ec7348f8eaa3affc16c63d6e1b0be5d03dab82d reject +udp_open 8000921ec00005cb2618eddc0440d7b1a707832e2e225d38cb42dfb889ec7acc9de755c818f1825e1443c5d4e6615bb0f89c4592442c8ab44e9564d604e3fed8103bd2302b26877bb1161f4da1f3f3fa1d76cc7d5065e35864f4c8d79b1ec23475f2b4a8909a846911ad9f94ecc6642c333949406d1fe0afa35a0a063ed032f25b6d5207cd8054afe21a9b172113cba0b5847542244641ff9fa7da4d6eac482a748a775d66a0d438253e7c93acd65da2d1532de33b97b490be9f9c416923ac7adf987dda9c999151fbfba706c68cb5ea852640a25c1a45c79a48238fdb54ff15f001000495ac4602806f332280000000 UdpSessionOpen { session_id: 37406, flags: 6370075667932, address_type: Ipv6, host: [177, 167, 7, 131, 46, 46, 34, 93, 56, 203, 66, 223, 184, 137, 236, 122, 204, 157, 231, 85, 200, 24, 241, 130, 94, 20, 67, 197, 212, 230, 97, 91, 176, 248, 156, 69, 146, 68, 44, 138, 180, 78, 149, 100, 214, 4, 227, 254, 216, 16, 59, 210, 48, 43, 38, 135, 123, 177, 22, 31, 77, 161, 243, 243, 250, 29, 118, 204, 125, 80, 101, 227, 88, 100, 244, 200, 215, 155, 30, 194, 52, 117, 242, 180, 168, 144, 154, 132, 105, 17, 173, 159, 148, 236, 198, 100, 44, 51, 57, 73, 64, 109, 31, 224, 175, 163, 90, 10, 6, 62, 208, 50, 242, 91, 109, 82, 7, 205, 128, 84, 175, 226, 26, 155, 23, 33, 19, 203, 160, 181, 132, 117, 66, 36, 70, 65, 255, 159, 167, 218, 77, 110, 172, 72, 42, 116, 138, 119, 93, 102, 160, 212, 56, 37, 62, 124, 147, 172, 214, 93, 162, 209, 83, 45, 227, 59, 151, 180, 144, 190, 159, 156, 65, 105, 35, 172, 122, 223, 152, 125, 218, 156, 153, 145, 81, 251, 251, 167, 6, 198, 140, 181, 234, 133, 38, 64, 162, 92, 26, 69, 199, 154, 72, 35, 143, 219, 84, 255, 21, 240, 1, 0, 4, 149, 172], port: 17922, idle_timeout_ms: 7287586 };240;[80, 00, 92, 1e, c0, 00, 05, cb, 26, 18, ed, dc, 04, 40, d7, b1, a7, 07, 83, 2e, 2e, 22, 5d, 38, cb, 42, df, b8, 89, ec, 7a, cc, 9d, e7, 55, c8, 18, f1, 82, 5e, 14, 43, c5, d4, e6, 61, 5b, b0, f8, 9c, 45, 92, 44, 2c, 8a, b4, 4e, 95, 64, d6, 04, e3, fe, d8, 10, 3b, d2, 30, 2b, 26, 87, 7b, b1, 16, 1f, 4d, a1, f3, f3, fa, 1d, 76, cc, 7d, 50, 65, e3, 58, 64, f4, c8, d7, 9b, 1e, c2, 34, 75, f2, b4, a8, 90, 9a, 84, 69, 11, ad, 9f, 94, ec, c6, 64, 2c, 33, 39, 49, 40, 6d, 1f, e0, af, a3, 5a, 0a, 06, 3e, d0, 32, f2, 5b, 6d, 52, 07, cd, 80, 54, af, e2, 1a, 9b, 17, 21, 13, cb, a0, b5, 84, 75, 42, 24, 46, 41, ff, 9f, a7, da, 4d, 6e, ac, 48, 2a, 74, 8a, 77, 5d, 66, a0, d4, 38, 25, 3e, 7c, 93, ac, d6, 5d, a2, d1, 53, 2d, e3, 3b, 97, b4, 90, be, 9f, 9c, 41, 69, 23, ac, 7a, df, 98, 7d, da, 9c, 99, 91, 51, fb, fb, a7, 06, c6, 8c, b5, ea, 85, 26, 40, a2, 5c, 1a, 45, c7, 9a, 48, 23, 8f, db, 54, ff, 15, f0, 01, 00, 04, 95, ac, 46, 02, 80, 6f, 33, 22, 00] +udp_open 800ea7d4725c01800000ef1744978309570bcc5ef361dbdcf78279a49b55bd12ad36a644d5ff25b2aed7d41747f597b0af107dc92b8764d63a60b3be39e00b2eb1675921e16738589aca3d650af306671080ed050e0fef093a1bfd289e395b458a7860b1a1a6bf771d3481f1abfee23047d287a57d16ff83e03b28a55a70281b1afded9d6dbd25f08a01ec929793d7c7f51e5fda2f9742996de561a9204f529e86a0dd1075de11667e6155220d71f36b5becf558f91910d643651f076b0d68d4ef61b631dac8cc77be3a43898140bf7aeef4f8137b674ac7e61701fd4d743117da6178fede80170d81518479d47af9bfb9871f864c9d2991fc7673f1c00000000000187880000000 UdpSessionOpen { session_id: 960468, flags: 12892, address_type: Ipv4, host: [23, 68, 151, 131, 9, 87, 11, 204, 94, 243, 97, 219, 220, 247, 130, 121, 164, 155, 85, 189, 18, 173, 54, 166, 68, 213, 255, 37, 178, 174, 215, 212, 23, 71, 245, 151, 176, 175, 16, 125, 201, 43, 135, 100, 214, 58, 96, 179, 190, 57, 224, 11, 46, 177, 103, 89, 33, 225, 103, 56, 88, 154, 202, 61, 101, 10, 243, 6, 103, 16, 128, 237, 5, 14, 15, 239, 9, 58, 27, 253, 40, 158, 57, 91, 69, 138, 120, 96, 177, 161, 166, 191, 119, 29, 52, 129, 241, 171, 254, 226, 48, 71, 210, 135, 165, 125, 22, 255, 131, 224, 59, 40, 165, 90, 112, 40, 27, 26, 253, 237, 157, 109, 189, 37, 240, 138, 1, 236, 146, 151, 147, 215, 199, 245, 30, 95, 218, 47, 151, 66, 153, 109, 229, 97, 169, 32, 79, 82, 158, 134, 160, 221, 16, 117, 222, 17, 102, 126, 97, 85, 34, 13, 113, 243, 107, 91, 236, 245, 88, 249, 25, 16, 214, 67, 101, 31, 7, 107, 13, 104, 212, 239, 97, 182, 49, 218, 200, 204, 119, 190, 58, 67, 137, 129, 64, 191, 122, 238, 244, 248, 19, 123, 103, 74, 199, 230, 23, 1, 253, 77, 116, 49, 23, 218, 97, 120, 254, 222, 128, 23, 13, 129, 81, 132, 121, 212, 122, 249, 191, 185, 135, 31, 134, 76, 157, 41, 145, 252, 118], port: 29681, idle_timeout_ms: 6264 };264;[80, 0e, a7, d4, 72, 5c, 01, 40, ef, 17, 44, 97, 83, 09, 57, 0b, cc, 5e, f3, 61, db, dc, f7, 82, 79, a4, 9b, 55, bd, 12, ad, 36, a6, 44, d5, ff, 25, b2, ae, d7, d4, 17, 47, f5, 97, b0, af, 10, 7d, c9, 2b, 87, 64, d6, 3a, 60, b3, be, 39, e0, 0b, 2e, b1, 67, 59, 21, e1, 67, 38, 58, 9a, ca, 3d, 65, 0a, f3, 06, 67, 10, 80, ed, 05, 0e, 0f, ef, 09, 3a, 1b, fd, 28, 9e, 39, 5b, 45, 8a, 78, 60, b1, a1, a6, bf, 77, 1d, 34, 81, f1, ab, fe, e2, 30, 47, d2, 87, a5, 7d, 16, ff, 83, e0, 3b, 28, a5, 5a, 70, 28, 1b, 1a, fd, ed, 9d, 6d, bd, 25, f0, 8a, 01, ec, 92, 97, 93, d7, c7, f5, 1e, 5f, da, 2f, 97, 42, 99, 6d, e5, 61, a9, 20, 4f, 52, 9e, 86, a0, dd, 10, 75, de, 11, 66, 7e, 61, 55, 22, 0d, 71, f3, 6b, 5b, ec, f5, 58, f9, 19, 10, d6, 43, 65, 1f, 07, 6b, 0d, 68, d4, ef, 61, b6, 31, da, c8, cc, 77, be, 3a, 43, 89, 81, 40, bf, 7a, ee, f4, f8, 13, 7b, 67, 4a, c7, e6, 17, 01, fd, 4d, 74, 31, 17, da, 61, 78, fe, de, 80, 17, 0d, 81, 51, 84, 79, d4, 7a, f9, bf, b9, 87, 1f, 86, 4c, 9d, 29, 91, fc, 76, 73, f1, 58, 78, 00] +udp_open 8ce169bc8000000f0480000081c97177416711639d337d79209317848ec370fa5e8fec3d88f102cd314687614463acd5817f578996479b97dc976a804f218231c0d47ec0b8ea0381a90140d1f103a4cc482ac50262593a81ca8036ededba3721c16e4f8f42b943f4631a4742585f5b3879f3fa78a8094dcc4c53912fd380ebf62d4521ec6f78b118ea7b151977a43ae5c001696f1554eff3c000000000000000c59ff0afd2 UdpSessionOpen { session_id: 216099260, flags: 15, address_type: Ipv6, host: [201, 113, 119, 65, 103, 17, 99, 157, 51, 125, 121, 32, 147, 23, 132, 142, 195, 112, 250, 94, 143, 236, 61, 136, 241, 2, 205, 49, 70, 135, 97, 68, 99, 172, 213, 129, 127, 87, 137, 150, 71, 155, 151, 220, 151, 106, 128, 79, 33, 130, 49, 192, 212, 126, 192, 184, 234, 3, 129, 169, 1, 64, 209, 241, 3, 164, 204, 72, 42, 197, 2, 98, 89, 58, 129, 202, 128, 54, 237, 237, 186, 55, 33, 193, 110, 79, 143, 66, 185, 67, 244, 99, 26, 71, 66, 88, 95, 91, 56, 121, 243, 250, 120, 168, 9, 77, 204, 76, 83, 145, 47, 211, 128, 235, 246, 45, 69, 33, 236, 111, 120, 177, 24, 234, 123, 21, 25, 119, 164], port: 15077, idle_timeout_ms: 397400796884979 };160;[8c, e1, 69, bc, 0f, 04, 40, 81, c9, 71, 77, 41, 67, 11, 63, 9d, 33, 7d, 79, 20, 93, 17, 84, 8e, c3, 70, fa, 5e, 8f, ec, 3d, 88, f1, 02, cd, 31, 46, 87, 61, 44, 63, ac, d5, 81, 7f, 57, 89, 96, 47, 9b, 97, dc, 97, 6a, 80, 4f, 21, 82, 31, c0, d4, 7e, c0, b8, ea, 03, 81, a9, 01, 40, d1, f1, 03, a4, cc, 48, 2a, c5, 02, 62, 59, 3a, 81, ca, 80, 36, ed, ed, ba, 37, 21, c1, 6e, 4f, 8f, 42, b9, 43, f4, 63, 1a, 47, 42, 58, 5f, 5b, 38, 79, f3, fa, 78, a8, 09, 4d, cc, 4c, 53, 91, 2f, d3, 80, eb, f6, 2d, 45, 21, ec, 6f, 78, b1, 18, ea, 7b, 15, 19, 77, a4, 3a, e5, c0, 01, 69, 6f, 15, 54, ef, f3, 00] +udp_open c000000100000011c000d8cfd0e527d004c00000000000005f62199b9a334938cea6b44ec9a790ca35912061c046139b79a2de5542744caf7bccf40fc202566ea328667f0e5ce974865e7b55732b77a38b871bf22e7e311ac78cd042e5a05aa5501e9c2d89fcf62d74af38ef88e5562b9a5a09b41089ddeaab74c00002fb357771f909bee5b9790931a79ed1 reject +udp_open 8beda289c0000000000000f401409ffa7b62e38b11ca4850b95107fbfd136aa27b2f0c4a71d67d39a00135fa3427208cccb550918996738f63d0c03b32ee60fcd08feb11f85c4e3f01f29371ad4d39ba70f6f70c4113b63fec8621251cff69bffbae588c0261ccf41bd969b26c1592b89bb2da2019e4a4c94e4477279bcf1c05946130bc594d2bdc7fd3f17c77505d0634151e8e261edae8274aa74c6796ab25a5d7fb9cfc9549cb085519646bb2e05241750052 UdpSessionOpen { session_id: 200123017, flags: 244, address_type: Ipv4, host: [250, 123, 98, 227, 139, 17, 202, 72, 80, 185, 81, 7, 251, 253, 19, 106, 162, 123, 47, 12, 74, 113, 214, 125, 57, 160, 1, 53, 250, 52, 39, 32, 140, 204, 181, 80, 145, 137, 150, 115, 143, 99, 208, 192, 59, 50, 238, 96, 252, 208, 143, 235, 17, 248, 92, 78, 63, 1, 242, 147, 113, 173, 77, 57, 186, 112, 246, 247, 12, 65, 19, 182, 63, 236, 134, 33, 37, 28, 255, 105, 191, 251, 174, 88, 140, 2, 97, 204, 244, 27, 217, 105, 178, 108, 21, 146, 184, 155, 178, 218, 32, 25, 228, 164, 201, 78, 68, 119, 39, 155, 207, 28, 5, 148, 97, 48, 188, 89, 77, 43, 220, 127, 211, 241, 124, 119, 80, 93, 6, 52, 21, 30, 142, 38, 30, 218, 232, 39, 74, 167, 76, 103, 150, 171, 37, 165, 215, 251, 156, 252, 149, 73, 203, 8, 85, 25, 100, 107, 178], port: 57426, idle_timeout_ms: 373 };179;[8b, ed, a2, 89, 40, f4, 01, 40, 9f, fa, 7b, 62, e3, 8b, 11, ca, 48, 50, b9, 51, 07, fb, fd, 13, 6a, a2, 7b, 2f, 0c, 4a, 71, d6, 7d, 39, a0, 01, 35, fa, 34, 27, 20, 8c, cc, b5, 50, 91, 89, 96, 73, 8f, 63, d0, c0, 3b, 32, ee, 60, fc, d0, 8f, eb, 11, f8, 5c, 4e, 3f, 01, f2, 93, 71, ad, 4d, 39, ba, 70, f6, f7, 0c, 41, 13, b6, 3f, ec, 86, 21, 25, 1c, ff, 69, bf, fb, ae, 58, 8c, 02, 61, cc, f4, 1b, d9, 69, b2, 6c, 15, 92, b8, 9b, b2, da, 20, 19, e4, a4, c9, 4e, 44, 77, 27, 9b, cf, 1c, 05, 94, 61, 30, bc, 59, 4d, 2b, dc, 7f, d3, f1, 7c, 77, 50, 5d, 06, 34, 15, 1e, 8e, 26, 1e, da, e8, 27, 4a, a7, 4c, 67, 96, ab, 25, a5, d7, fb, 9c, fc, 95, 49, cb, 08, 55, 19, 64, 6b, b2, e0, 52, 41, 75, 00] +udp_open 4eec80014cf801164fc455bbc24e5691e3181745e2fdfd7df415beb72ea0f319c00000074ad9e6fb8000000144 UdpSessionOpen { session_id: 3820, flags: 85240, address_type: Ipv4, host: [79, 196, 85, 187, 194, 78, 86, 145, 227, 24, 23, 69, 226, 253, 253, 125, 244, 21, 190, 183, 46, 160], port: 62233, idle_timeout_ms: 31320565499 };45;[4e, ec, 80, 01, 4c, f8, 01, 16, 4f, c4, 55, bb, c2, 4e, 56, 91, e3, 18, 17, 45, e2, fd, fd, 7d, f4, 15, be, b7, 2e, a0, f3, 19, c0, 00, 00, 07, 4a, d9, e6, fb, 00] +udp_open 649e8002a37504800000d4c40e193853b759c85f46ffe088c5cac6eda2329bf36d332f7f88ad4202a7b81ab742daf013d896db3a1beae72712d796248c114f1ff7482b3b656019bf03abe52d79f41073e3d4c3dc75739f73af5931c203049ade9b883bb9ea0a6da87d5262c22d86bb391b706e74193cb6cfaa7bc18e799e5e50e1d74b27b59bbd3d5d5b66c9c72e8802ea5d1e6dda96617e1c6d5c7926541872b000071fbc7a58089942e4912100f6614cece510ab37d5a879ad6c070cb43ebfba9766ec518a21bdb0e179c540175ff467c9e882bf97665c574840ddb53d6db941cccf31b31fe19a494000 UdpSessionOpen { session_id: 9374, flags: 172917, address_type: Ipv6, host: [196, 14, 25, 56, 83, 183, 89, 200, 95, 70, 255, 224, 136, 197, 202, 198, 237, 162, 50, 155, 243, 109, 51, 47, 127, 136, 173, 66, 2, 167, 184, 26, 183, 66, 218, 240, 19, 216, 150, 219, 58, 27, 234, 231, 39, 18, 215, 150, 36, 140, 17, 79, 31, 247, 72, 43, 59, 101, 96, 25, 191, 3, 171, 229, 45, 121, 244, 16, 115, 227, 212, 195, 220, 117, 115, 159, 115, 175, 89, 49, 194, 3, 4, 154, 222, 155, 136, 59, 185, 234, 10, 109, 168, 125, 82, 98, 194, 45, 134, 187, 57, 27, 112, 110, 116, 25, 60, 182, 207, 170, 123, 193, 142, 121, 158, 94, 80, 225, 215, 75, 39, 181, 155, 189, 61, 93, 91, 102, 201, 199, 46, 136, 2, 234, 93, 30, 109, 218, 150, 97, 126, 28, 109, 92, 121, 38, 84, 24, 114, 176, 0, 7, 31, 188, 122, 88, 8, 153, 66, 228, 145, 33, 0, 246, 97, 76, 236, 229, 16, 171, 55, 213, 168, 121, 173, 108, 7, 12, 180, 62, 191, 186, 151, 102, 236, 81, 138, 33, 189, 176, 225, 121, 197, 64, 23, 95, 244, 103, 201, 232, 130, 191, 151, 102, 92, 87, 72, 64, 221, 181, 61, 109], port: 47425, idle_timeout_ms: 923011094038026825 };235;[64, 9e, 80, 02, a3, 75, 04, 40, d4, c4, 0e, 19, 38, 53, b7, 59, c8, 5f, 46, ff, e0, 88, c5, ca, c6, ed, a2, 32, 9b, f3, 6d, 33, 2f, 7f, 88, ad, 42, 02, a7, b8, 1a, b7, 42, da, f0, 13, d8, 96, db, 3a, 1b, ea, e7, 27, 12, d7, 96, 24, 8c, 11, 4f, 1f, f7, 48, 2b, 3b, 65, 60, 19, bf, 03, ab, e5, 2d, 79, f4, 10, 73, e3, d4, c3, dc, 75, 73, 9f, 73, af, 59, 31, c2, 03, 04, 9a, de, 9b, 88, 3b, b9, ea, 0a, 6d, a8, 7d, 52, 62, c2, 2d, 86, bb, 39, 1b, 70, 6e, 74, 19, 3c, b6, cf, aa, 7b, c1, 8e, 79, 9e, 5e, 50, e1, d7, 4b, 27, b5, 9b, bd, 3d, 5d, 5b, 66, c9, c7, 2e, 88, 02, ea, 5d, 1e, 6d, da, 96, 61, 7e, 1c, 6d, 5c, 79, 26, 54, 18, 72, b0, 00, 07, 1f, bc, 7a, 58, 08, 99, 42, e4, 91, 21, 00, f6, 61, 4c, ec, e5, 10, ab, 37, d5, a8, 79, ad, 6c, 07, 0c, b4, 3e, bf, ba, 97, 66, ec, 51, 8a, 21, bd, b0, e1, 79, c5, 40, 17, 5f, f4, 67, c9, e8, 82, bf, 97, 66, 5c, 57, 48, 40, dd, b5, 3d, 6d, b9, 41, cc, cf, 31, b3, 1f, e1, 9a, 49, 00] +udp_open c000000000000009c00000198046371a574073c348382ad569edb6ae5ce72d3b5cb4566814af329d265fdbe23228194713f3b5023506036b0cb4bcf799732cb307aebd7f5c6aee3d3e126bacf2d0db6f8f370f891c2e62214922304753818a75584b44a6e5cb31369a825acb59a49b0a14b6c2334e342fa5c493178cc61cf6b3db50d64402500863c00005a3dd892722c000000000000000 reject +udp_open c000000000000003801ac26d04c000000000000071c3973d7c1644fef8015c4095d264fd3e8dcdfca1106e619766cabfd0c18343c4a308a51f840b2edc47337b1554cc7dba5a85268978f6a7a098eb8e6286a2170d58b3c42a909b2b8ea006be485f49c67696450e48ad90f86efd1134537c06140da88af33ac0e0c10af998354fa85c581de098f6c0000000005b18a400 UdpSessionOpen { session_id: 3, flags: 1753709, address_type: Ipv6, host: [195, 151, 61, 124, 22, 68, 254, 248, 1, 92, 64, 149, 210, 100, 253, 62, 141, 205, 252, 161, 16, 110, 97, 151, 102, 202, 191, 208, 193, 131, 67, 196, 163, 8, 165, 31, 132, 11, 46, 220, 71, 51, 123, 21, 84, 204, 125, 186, 90, 133, 38, 137, 120, 246, 167, 160, 152, 235, 142, 98, 134, 162, 23, 13, 88, 179, 196, 42, 144, 155, 43, 142, 160, 6, 190, 72, 95, 73, 198, 118, 150, 69, 14, 72, 173, 144, 248, 110, 253, 17, 52, 83, 124, 6, 20, 13, 168, 138, 243, 58, 192, 224, 193, 10, 249, 152, 53, 79, 168, 92, 88, 29, 224], port: 39158, idle_timeout_ms: 5970084 };145;[03, 80, 1a, c2, 6d, 04, 40, 71, c3, 97, 3d, 7c, 16, 44, fe, f8, 01, 5c, 40, 95, d2, 64, fd, 3e, 8d, cd, fc, a1, 10, 6e, 61, 97, 66, ca, bf, d0, c1, 83, 43, c4, a3, 08, a5, 1f, 84, 0b, 2e, dc, 47, 33, 7b, 15, 54, cc, 7d, ba, 5a, 85, 26, 89, 78, f6, a7, a0, 98, eb, 8e, 62, 86, a2, 17, 0d, 58, b3, c4, 2a, 90, 9b, 2b, 8e, a0, 06, be, 48, 5f, 49, c6, 76, 96, 45, 0e, 48, ad, 90, f8, 6e, fd, 11, 34, 53, 7c, 06, 14, 0d, a8, 8a, f3, 3a, c0, e0, c1, 0a, f9, 98, 35, 4f, a8, 5c, 58, 1d, e0, 98, f6, 80, 5b, 18, a4, 00] +udp_open 5f93800000160180000103509281ca419d02d2be251f7be03f2dd49dd3f51b2112a2b9216d245442db4b72305638a2d76a1e9a0ceab54a80c548dc082d4c44234b6aa28b6a8867a959018812b838d816553dd7eea2087c9623e306f6068c463de5b61040404b2a7b7cb9db3c14fd4b196d86eeb188bd4330680a3e83bc67dd473438b6b04a62c2b4c04961c7ba1f7bdb35639ce6f4a3c619bbd04641117c985154b0a5e161e3cad150363c94030c4eb29e04fbb4cf443e667b8f65dc908f95a7cbfb5a5c3d4cf7b7d3aed973aedad8c2264f119c163c9866b111d25c1ef1aefd0f8b2786901251aaa325b194eabcccdc099f20e25c415ff2bab4a76c40d67b4a5e59b3eec91aa719ee5f7a511d262448c000250258e45fb400 reject +udp_open 8a65daf440090180000066449db1f8b74077dee7cde1bb700713d325d2af9797dd1d3bb994672b53f44687164876474062ff85527a27b8cd375116f3eac6dc0f279e3c9a88266b71fbed0fbccb2d0cb9037211343c9ea59814905aefa1576a2c1e95971539cd8295d245092bfe38ce1b5abfccc00000016cc965868000000a2aa3136ce44f9100ba08 UdpSessionOpen { session_id: 174447348, flags: 9, address_type: Ipv4, host: [68, 157, 177, 248, 183, 64, 119, 222, 231, 205, 225, 187, 112, 7, 19, 211, 37, 210, 175, 151, 151, 221, 29, 59, 185, 148, 103, 43, 83, 244, 70, 135, 22, 72, 118, 71, 64, 98, 255, 133, 82, 122, 39, 184, 205, 55, 81, 22, 243, 234, 198, 220, 15, 39, 158, 60, 154, 136, 38, 107, 113, 251, 237, 15, 188, 203, 45, 12, 185, 3, 114, 17, 52, 60, 158, 165, 152, 20, 144, 90, 239, 161, 87, 106, 44, 30, 149, 151, 21, 57, 205, 130, 149, 210, 69, 9, 43, 254, 56, 206, 27, 90], port: 49100, idle_timeout_ms: 6120105350 };137;[8a, 65, da, f4, 09, 01, 40, 66, 44, 9d, b1, f8, b7, 40, 77, de, e7, cd, e1, bb, 70, 07, 13, d3, 25, d2, af, 97, 97, dd, 1d, 3b, b9, 94, 67, 2b, 53, f4, 46, 87, 16, 48, 76, 47, 40, 62, ff, 85, 52, 7a, 27, b8, cd, 37, 51, 16, f3, ea, c6, dc, 0f, 27, 9e, 3c, 9a, 88, 26, 6b, 71, fb, ed, 0f, bc, cb, 2d, 0c, b9, 03, 72, 11, 34, 3c, 9e, a5, 98, 14, 90, 5a, ef, a1, 57, 6a, 2c, 1e, 95, 97, 15, 39, cd, 82, 95, d2, 45, 09, 2b, fe, 38, ce, 1b, 5a, bf, cc, c0, 00, 00, 01, 6c, c9, 65, 86, 00] +udp_open c000000000000015c01949e769e6acfd03405062fd541889cf68d1636c36e3c4a8230566058685f3b066a16b0bad3fdff97d27692a6506402f4ea8d8c8c7df3ba2c8b6c827e484d67a33cb974a70dc43a9cda3ff30422e21dc8ec71c5d55a3c175d5a56f1ec0000005b02870530fd00705a9733d1ee17182732cd1ddfc UdpSessionOpen { session_id: 21, flags: 7118132680764669, address_type: Domain, host: [98, 253, 84, 24, 137, 207, 104, 209, 99, 108, 54, 227, 196, 168, 35, 5, 102, 5, 134, 133, 243, 176, 102, 161, 107, 11, 173, 63, 223, 249, 125, 39, 105, 42, 101, 6, 64, 47, 78, 168, 216, 200, 199, 223, 59, 162, 200, 182, 200, 39, 228, 132, 214, 122, 51, 203, 151, 74, 112, 220, 67, 169, 205, 163, 255, 48, 66, 46, 33, 220, 142, 199, 28, 93, 85, 163, 193, 117, 213, 165], port: 28446, idle_timeout_ms: 24430276691 };125;[15, c0, 19, 49, e7, 69, e6, ac, fd, 03, 40, 50, 62, fd, 54, 18, 89, cf, 68, d1, 63, 6c, 36, e3, c4, a8, 23, 05, 66, 05, 86, 85, f3, b0, 66, a1, 6b, 0b, ad, 3f, df, f9, 7d, 27, 69, 2a, 65, 06, 40, 2f, 4e, a8, d8, c8, c7, df, 3b, a2, c8, b6, c8, 27, e4, 84, d6, 7a, 33, cb, 97, 4a, 70, dc, 43, a9, cd, a3, ff, 30, 42, 2e, 21, dc, 8e, c7, 1c, 5d, 55, a3, c1, 75, d5, a5, 6f, 1e, c0, 00, 00, 05, b0, 28, 70, 53, 00] +udp_open 9455fe9dc0000004adf81858012a8e0b078256d4367d681f02b27e7ed8deefec652ac84b096bdd2ea1059afbea11055302cabfde6310fbfa2216c000000332658a32c000000000000000d5a2a4b4 UdpSessionOpen { session_id: 341180061, flags: 20098586712, address_type: Ipv4, host: [142, 11, 7, 130, 86, 212, 54, 125, 104, 31, 2, 178, 126, 126, 216, 222, 239, 236, 101, 42, 200, 75, 9, 107, 221, 46, 161, 5, 154, 251, 234, 17, 5, 83, 2, 202, 191, 222, 99, 16, 251, 250], port: 8726, idle_timeout_ms: 13730417202 };74;[94, 55, fe, 9d, c0, 00, 00, 04, ad, f8, 18, 58, 01, 2a, 8e, 0b, 07, 82, 56, d4, 36, 7d, 68, 1f, 02, b2, 7e, 7e, d8, de, ef, ec, 65, 2a, c8, 4b, 09, 6b, dd, 2e, a1, 05, 9a, fb, ea, 11, 05, 53, 02, ca, bf, de, 63, 10, fb, fa, 22, 16, c0, 00, 00, 03, 32, 65, 8a, 32, 00] +udp_open 87265c796e130440cbc4057ad41ad8d138a68ba1634cb1b1cac9b40dd375bb764eb54550d2cd636592e071337821551c5d8ffc6ff0dc4e717586e43fc73c505c00b6db40de7057aee55d30c2dab80b2d5a59f2618ab16989045b8ee8750f512ea80af79967cf3159656481eba5c3361a0f6df5e531fb4ae08ec453d83c33ec60143815e7eab8b73d67981c87bf8401dbb26955373f028cddd5880c59841155a15aedebd808512170e79e408750ea5fc69be90a9c6334472867a74b30ae697318e9b6a636392acdaeadbddc6804b2d6925445ed976ed4800057d54000 UdpSessionOpen { session_id: 119954553, flags: 11795, address_type: Ipv6, host: [196, 5, 122, 212, 26, 216, 209, 56, 166, 139, 161, 99, 76, 177, 177, 202, 201, 180, 13, 211, 117, 187, 118, 78, 181, 69, 80, 210, 205, 99, 101, 146, 224, 113, 51, 120, 33, 85, 28, 93, 143, 252, 111, 240, 220, 78, 113, 117, 134, 228, 63, 199, 60, 80, 92, 0, 182, 219, 64, 222, 112, 87, 174, 229, 93, 48, 194, 218, 184, 11, 45, 90, 89, 242, 97, 138, 177, 105, 137, 4, 91, 142, 232, 117, 15, 81, 46, 168, 10, 247, 153, 103, 207, 49, 89, 101, 100, 129, 235, 165, 195, 54, 26, 15, 109, 245, 229, 49, 251, 74, 224, 142, 196, 83, 216, 60, 51, 236, 96, 20, 56, 21, 231, 234, 184, 183, 61, 103, 152, 28, 135, 191, 132, 1, 219, 178, 105, 85, 55, 63, 2, 140, 221, 213, 136, 12, 89, 132, 17, 85, 161, 90, 237, 235, 216, 8, 81, 33, 112, 231, 158, 64, 135, 80, 234, 95, 198, 155, 233, 10, 156, 99, 52, 71, 40, 103, 167, 75, 48, 174, 105, 115, 24, 233, 182, 166, 54, 57, 42, 205, 174, 173, 189, 220, 104, 4, 178, 214, 146, 84, 69, 237, 151], port: 28372, idle_timeout_ms: 22485 };220;[87, 26, 5c, 79, 6e, 13, 04, 40, cb, c4, 05, 7a, d4, 1a, d8, d1, 38, a6, 8b, a1, 63, 4c, b1, b1, ca, c9, b4, 0d, d3, 75, bb, 76, 4e, b5, 45, 50, d2, cd, 63, 65, 92, e0, 71, 33, 78, 21, 55, 1c, 5d, 8f, fc, 6f, f0, dc, 4e, 71, 75, 86, e4, 3f, c7, 3c, 50, 5c, 00, b6, db, 40, de, 70, 57, ae, e5, 5d, 30, c2, da, b8, 0b, 2d, 5a, 59, f2, 61, 8a, b1, 69, 89, 04, 5b, 8e, e8, 75, 0f, 51, 2e, a8, 0a, f7, 99, 67, cf, 31, 59, 65, 64, 81, eb, a5, c3, 36, 1a, 0f, 6d, f5, e5, 31, fb, 4a, e0, 8e, c4, 53, d8, 3c, 33, ec, 60, 14, 38, 15, e7, ea, b8, b7, 3d, 67, 98, 1c, 87, bf, 84, 01, db, b2, 69, 55, 37, 3f, 02, 8c, dd, d5, 88, 0c, 59, 84, 11, 55, a1, 5a, ed, eb, d8, 08, 51, 21, 70, e7, 9e, 40, 87, 50, ea, 5f, c6, 9b, e9, 0a, 9c, 63, 34, 47, 28, 67, a7, 4b, 30, ae, 69, 73, 18, e9, b6, a6, 36, 39, 2a, cd, ae, ad, bd, dc, 68, 04, b2, d6, 92, 54, 45, ed, 97, 6e, d4, 80, 00, 57, d5, 00] +udp_open 866ddff8c0000006276eab2201800000c790be145aaa35d1a1fb61f44010caf4a3995f144ee2cc496f120b3e1c3380b35728c971611438d0a7a192b67e36fc026828af0d8525b2e0e8e3ee602d34160af700801d2f1b18c9d3f3a142f8528daccef0be2c56f56957557509fb46aec851c9fd6bdd592e8cea5e65ccb3ec89be6ece3ccf6d3f6a439a384165e129cef120ae8c66e9c791d83e3e9301f9eda1a52ab147063edd730f91424c66f6e8362beb131ddd4ec8028da046be6d3a1181e3ccd3a46369f2bd22062f958f686f1defb9c375c07e8c52c78d616cc000002aaddfb8e400fbdfc09f547e UdpSessionOpen { session_id: 107864056, flags: 26431367970, address_type: Ipv4, host: [144, 190, 20, 90, 170, 53, 209, 161, 251, 97, 244, 64, 16, 202, 244, 163, 153, 95, 20, 78, 226, 204, 73, 111, 18, 11, 62, 28, 51, 128, 179, 87, 40, 201, 113, 97, 20, 56, 208, 167, 161, 146, 182, 126, 54, 252, 2, 104, 40, 175, 13, 133, 37, 178, 224, 232, 227, 238, 96, 45, 52, 22, 10, 247, 0, 128, 29, 47, 27, 24, 201, 211, 243, 161, 66, 248, 82, 141, 172, 206, 240, 190, 44, 86, 245, 105, 87, 85, 117, 9, 251, 70, 174, 200, 81, 201, 253, 107, 221, 89, 46, 140, 234, 94, 101, 204, 179, 236, 137, 190, 110, 206, 60, 207, 109, 63, 106, 67, 154, 56, 65, 101, 225, 41, 206, 241, 32, 174, 140, 102, 233, 199, 145, 216, 62, 62, 147, 1, 249, 237, 161, 165, 42, 177, 71, 6, 62, 221, 115, 15, 145, 66, 76, 102, 246, 232, 54, 43, 235, 19, 29, 221, 78, 200, 2, 141, 160, 70, 190, 109, 58, 17, 129, 227, 204, 211, 164, 99, 105, 242, 189, 34, 6, 47, 149, 143, 104, 111, 29, 239, 185, 195, 117, 192, 126, 140, 82, 199, 141], port: 24940, idle_timeout_ms: 183305746660 };227;[86, 6d, df, f8, c0, 00, 00, 06, 27, 6e, ab, 22, 01, 40, c7, 90, be, 14, 5a, aa, 35, d1, a1, fb, 61, f4, 40, 10, ca, f4, a3, 99, 5f, 14, 4e, e2, cc, 49, 6f, 12, 0b, 3e, 1c, 33, 80, b3, 57, 28, c9, 71, 61, 14, 38, d0, a7, a1, 92, b6, 7e, 36, fc, 02, 68, 28, af, 0d, 85, 25, b2, e0, e8, e3, ee, 60, 2d, 34, 16, 0a, f7, 00, 80, 1d, 2f, 1b, 18, c9, d3, f3, a1, 42, f8, 52, 8d, ac, ce, f0, be, 2c, 56, f5, 69, 57, 55, 75, 09, fb, 46, ae, c8, 51, c9, fd, 6b, dd, 59, 2e, 8c, ea, 5e, 65, cc, b3, ec, 89, be, 6e, ce, 3c, cf, 6d, 3f, 6a, 43, 9a, 38, 41, 65, e1, 29, ce, f1, 20, ae, 8c, 66, e9, c7, 91, d8, 3e, 3e, 93, 01, f9, ed, a1, a5, 2a, b1, 47, 06, 3e, dd, 73, 0f, 91, 42, 4c, 66, f6, e8, 36, 2b, eb, 13, 1d, dd, 4e, c8, 02, 8d, a0, 46, be, 6d, 3a, 11, 81, e3, cc, d3, a4, 63, 69, f2, bd, 22, 06, 2f, 95, 8f, 68, 6f, 1d, ef, b9, c3, 75, c0, 7e, 8c, 52, c7, 8d, 61, 6c, c0, 00, 00, 2a, ad, df, b8, e4, 00] +udp_open 80060bd6c00000055a8de873014101f896fc53a438eacbf02892b04090af6d0ac7df15b8d9e7c4590ebbb82998525d512181ed44d3be3a21868b4053ec2418c4e932d52d3084bef42ba1e8b63f7edaa3bd44cddc457fc86cf2ff9beccc826cc2cd7f00e5372ecf29729a3d2375c1a70b2719810ad96a65e13dff59101d908dfdfaafb78f3ea28a6d4554b2eaa8d0382b36ff823654cd22e16c640d5b017cff1e711f0bacd158ee3aa694a79af8e9a7c30f3dc546c9786b263612ed3c957cfa22266c829e406884d6b199aff61d815d85b3d020e0f9a427ce0de0f6a20806fed1d9f273c435d56d419db849dd5729afc9a7c0b0222da75de6406e41acc3275d01f33f895a45ad4e268cf393d2c2bde7bd48fd4095c000000000000000 reject +udp_open 826f98ef40dd0140bd7533e6db080f524f58bbc3bde32089da6b1771b160b3c91cb542b6c0d8874f5614f3f775e09181467f4dc1daf095518ae57491875716f705b265843c161b995611177adac62e0f55df94a293a466d05c87991e82c4cbad570887ef351f409832082d75be0ce0ae64980bf5d783fdefdaba34f04c3dbc0333192180687a4f96967eb3a8e79e687de43a61884ecd321c7a91e7df5486c8aa965ab8f8e08b77fad93c2a83a9d54ff957be9d3a7ce831aa801f97aca2dc045e0e2d02d4e3535b37c033b629637e24964000 UdpSessionOpen { session_id: 40868079, flags: 221, address_type: Ipv4, host: [117, 51, 230, 219, 8, 15, 82, 79, 88, 187, 195, 189, 227, 32, 137, 218, 107, 23, 113, 177, 96, 179, 201, 28, 181, 66, 182, 192, 216, 135, 79, 86, 20, 243, 247, 117, 224, 145, 129, 70, 127, 77, 193, 218, 240, 149, 81, 138, 229, 116, 145, 135, 87, 22, 247, 5, 178, 101, 132, 60, 22, 27, 153, 86, 17, 23, 122, 218, 198, 46, 15, 85, 223, 148, 162, 147, 164, 102, 208, 92, 135, 153, 30, 130, 196, 203, 173, 87, 8, 135, 239, 53, 31, 64, 152, 50, 8, 45, 117, 190, 12, 224, 174, 100, 152, 11, 245, 215, 131, 253, 239, 218, 186, 52, 240, 76, 61, 188, 3, 51, 25, 33, 128, 104, 122, 79, 150, 150, 126, 179, 168, 231, 158, 104, 125, 228, 58, 97, 136, 78, 205, 50, 28, 122, 145, 231, 223, 84, 134, 200, 170, 150, 90, 184, 248, 224, 139, 119, 250, 217, 60, 42, 131, 169, 213, 79, 249, 87, 190, 157, 58, 124, 232, 49, 170, 128, 31, 151, 172, 162, 220, 4, 94, 14, 45, 2, 212, 227, 83], port: 23351, idle_timeout_ms: 14555512691369110 };210;[82, 6f, 98, ef, 40, dd, 01, 40, bd, 75, 33, e6, db, 08, 0f, 52, 4f, 58, bb, c3, bd, e3, 20, 89, da, 6b, 17, 71, b1, 60, b3, c9, 1c, b5, 42, b6, c0, d8, 87, 4f, 56, 14, f3, f7, 75, e0, 91, 81, 46, 7f, 4d, c1, da, f0, 95, 51, 8a, e5, 74, 91, 87, 57, 16, f7, 05, b2, 65, 84, 3c, 16, 1b, 99, 56, 11, 17, 7a, da, c6, 2e, 0f, 55, df, 94, a2, 93, a4, 66, d0, 5c, 87, 99, 1e, 82, c4, cb, ad, 57, 08, 87, ef, 35, 1f, 40, 98, 32, 08, 2d, 75, be, 0c, e0, ae, 64, 98, 0b, f5, d7, 83, fd, ef, da, ba, 34, f0, 4c, 3d, bc, 03, 33, 19, 21, 80, 68, 7a, 4f, 96, 96, 7e, b3, a8, e7, 9e, 68, 7d, e4, 3a, 61, 88, 4e, cd, 32, 1c, 7a, 91, e7, df, 54, 86, c8, aa, 96, 5a, b8, f8, e0, 8b, 77, fa, d9, 3c, 2a, 83, a9, d5, 4f, f9, 57, be, 9d, 3a, 7c, e8, 31, aa, 80, 1f, 97, ac, a2, dc, 04, 5e, 0e, 2d, 02, d4, e3, 53, 5b, 37, c0, 33, b6, 29, 63, 7e, 24, 96, 00] +udp_open 80591a94802a2e9c03c00000000000005465d051ff7abab2f3e1a791d2d09a0a99ec270ca4ae7c4f46adc0c4d51d4832351f403c7ae53058c6215c06dab444112a8b35536bd4c55d130ce32c42886d8773be5869d7ebb77b0146f4b01835e93d9d6254829354dac831610c531223fb80000000 UdpSessionOpen { session_id: 5839508, flags: 2764444, address_type: Domain, host: [101, 208, 81, 255, 122, 186, 178, 243, 225, 167, 145, 210, 208, 154, 10, 153, 236, 39, 12, 164, 174, 124, 79, 70, 173, 192, 196, 213, 29, 72, 50, 53, 31, 64, 60, 122, 229, 48, 88, 198, 33, 92, 6, 218, 180, 68, 17, 42, 139, 53, 83, 107, 212, 197, 93, 19, 12, 227, 44, 66, 136, 109, 135, 115, 190, 88, 105, 215, 235, 183, 123, 1, 70, 244, 176, 24, 53, 233, 61, 157, 98, 84, 130, 147], port: 21722, idle_timeout_ms: 590359731723445243 };115;[80, 59, 1a, 94, 80, 2a, 2e, 9c, 03, 40, 54, 65, d0, 51, ff, 7a, ba, b2, f3, e1, a7, 91, d2, d0, 9a, 0a, 99, ec, 27, 0c, a4, ae, 7c, 4f, 46, ad, c0, c4, d5, 1d, 48, 32, 35, 1f, 40, 3c, 7a, e5, 30, 58, c6, 21, 5c, 06, da, b4, 44, 11, 2a, 8b, 35, 53, 6b, d4, c5, 5d, 13, 0c, e3, 2c, 42, 88, 6d, 87, 73, be, 58, 69, d7, eb, b7, 7b, 01, 46, f4, b0, 18, 35, e9, 3d, 9d, 62, 54, 82, 93, 54, da, c8, 31, 61, 0c, 53, 12, 23, fb, 00] +udp_open 5b31806c55ac01406612340fcc411c958a3e801caa7848c4ae2bed87fc166cda8cb86999d1946a74d7ae30ac02ed035b3f283fa7979369b35e2122dd946950e7e126147bc9f3475dd9fdc96f943e7416ebb193193c7323a14adc32f5c0f8a4accd82f8b29dc9a4e0b8806710ed29ab88fc800241e8400032db UdpSessionOpen { session_id: 6961, flags: 7099820, address_type: Ipv4, host: [18, 52, 15, 204, 65, 28, 149, 138, 62, 128, 28, 170, 120, 72, 196, 174, 43, 237, 135, 252, 22, 108, 218, 140, 184, 105, 153, 209, 148, 106, 116, 215, 174, 48, 172, 2, 237, 3, 91, 63, 40, 63, 167, 151, 147, 105, 179, 94, 33, 34, 221, 148, 105, 80, 231, 225, 38, 20, 123, 201, 243, 71, 93, 217, 253, 201, 111, 148, 62, 116, 22, 235, 177, 147, 25, 60, 115, 35, 161, 74, 220, 50, 245, 192, 248, 164, 172, 205, 130, 248, 178, 157, 201, 164, 224, 184, 128, 103, 16, 237, 41, 171], port: 35068, idle_timeout_ms: 147944 };119;[5b, 31, 80, 6c, 55, ac, 01, 40, 66, 12, 34, 0f, cc, 41, 1c, 95, 8a, 3e, 80, 1c, aa, 78, 48, c4, ae, 2b, ed, 87, fc, 16, 6c, da, 8c, b8, 69, 99, d1, 94, 6a, 74, d7, ae, 30, ac, 02, ed, 03, 5b, 3f, 28, 3f, a7, 97, 93, 69, b3, 5e, 21, 22, dd, 94, 69, 50, e7, e1, 26, 14, 7b, c9, f3, 47, 5d, d9, fd, c9, 6f, 94, 3e, 74, 16, eb, b1, 93, 19, 3c, 73, 23, a1, 4a, dc, 32, f5, c0, f8, a4, ac, cd, 82, f8, b2, 9d, c9, a4, e0, b8, 80, 67, 10, ed, 29, ab, 88, fc, 80, 02, 41, e8, 00] +udp_open c0000000000eeda2806d49a504800001007cbb0f15c6289f859e915de8ce4c88b927ce7400d1de787d5f3256b298d9f1f5368bf0e70060e6d90248bd69195725073b7f723bc53840a2bdd3270b848fffdc57fc6c15656fd941ec41c82cefd8ee190ac5861e18c10ba282ea0621582d6370323d167d062a6bcfb685e41a9dccf7dffcf0d358d1ab92a751d52dffa9899e3c25d2ed27f0ba1435ac979b974b492d01faf0f93b8f6fd0f031fa79062c061e055ca222596c75783dfc9cc55f0176f40d72d05472b1f148dd3e01b1d1e12256c408ea9df51aa86c6edac0f7a83ede9f4e6215211254eceb160c0bbab5e4760a4e85ce56d370e7a860a9b667ed5469da0e6bb656482c0783d41d473d7f74051549260a044000 reject +udp_open c00000000000000180000002038000006a0c4090296a2f6d315cbe30603f841bccd8b7d5702f6aa47d5e537c5bb845e5fb89fb89ea9f779af91233dbe69abf88f9eaafea2ab1effe8b102a9171fb1ec1283a1c39b9be0bf7801db8dfe6801478dc1a1dfc56dddd2c409d5add0a83c1d64d866d9a002beda7f34fca283ec004651fedcfab5600 UdpSessionOpen { session_id: 1, flags: 2, address_type: Domain, host: [12, 64, 144, 41, 106, 47, 109, 49, 92, 190, 48, 96, 63, 132, 27, 204, 216, 183, 213, 112, 47, 106, 164, 125, 94, 83, 124, 91, 184, 69, 229, 251, 137, 251, 137, 234, 159, 119, 154, 249, 18, 51, 219, 230, 154, 191, 136, 249, 234, 175, 234, 42, 177, 239, 254, 139, 16, 42, 145, 113, 251, 30, 193, 40, 58, 28, 57, 185, 190, 11, 247, 128, 29, 184, 223, 230, 128, 20, 120, 220, 26, 29, 252, 86, 221, 221, 44, 64, 157, 90, 221, 10, 131, 193, 214, 77, 134, 109, 154, 0, 43, 237, 167, 243, 79, 202], port: 10302, idle_timeout_ms: 1237087715044182 };134;[01, 02, 03, 40, 6a, 0c, 40, 90, 29, 6a, 2f, 6d, 31, 5c, be, 30, 60, 3f, 84, 1b, cc, d8, b7, d5, 70, 2f, 6a, a4, 7d, 5e, 53, 7c, 5b, b8, 45, e5, fb, 89, fb, 89, ea, 9f, 77, 9a, f9, 12, 33, db, e6, 9a, bf, 88, f9, ea, af, ea, 2a, b1, ef, fe, 8b, 10, 2a, 91, 71, fb, 1e, c1, 28, 3a, 1c, 39, b9, be, 0b, f7, 80, 1d, b8, df, e6, 80, 14, 78, dc, 1a, 1d, fc, 56, dd, dd, 2c, 40, 9d, 5a, dd, 0a, 83, c1, d6, 4d, 86, 6d, 9a, 00, 2b, ed, a7, f3, 4f, ca, 28, 3e, c0, 04, 65, 1f, ed, cf, ab, 56, 00] +udp_open 404540000140fdb7a229d5b24e732cd2c84a71a19c109eb8a73ca2ace7053ee1499c2b8d0718dff2e9d41f3967108c21314cf6ed751be9f09b0810222a05969abf779e52ed84f6c286de1934e9d3a0d2e5019105a42f2155f82046734410aa980163320ae8255035fd1c73fb7bd825b8d8237f2a8d6f1ed4e0a1d5f7c77fa848e351841f708195e68556e11cfbaa92b69fdffa16b35eed59c9ac7f8d57ace5012e6db05d3cdf1e354f262ef8a038442db6d35d6cd1c51976c82109ae879881fc6ee1d0ca3c91ac73209ea5ba13729425bbbec48c7cd6b21974a55ffcbffb38047ce29eb77c5278e97edb36b0f1976e464388c091edf6fa2fa7251aa73b94df79cb65aeaac8d11100 UdpSessionOpen { session_id: 69, flags: 0, address_type: Ipv4, host: [183, 162, 41, 213, 178, 78, 115, 44, 210, 200, 74, 113, 161, 156, 16, 158, 184, 167, 60, 162, 172, 231, 5, 62, 225, 73, 156, 43, 141, 7, 24, 223, 242, 233, 212, 31, 57, 103, 16, 140, 33, 49, 76, 246, 237, 117, 27, 233, 240, 155, 8, 16, 34, 42, 5, 150, 154, 191, 119, 158, 82, 237, 132, 246, 194, 134, 222, 25, 52, 233, 211, 160, 210, 229, 1, 145, 5, 164, 47, 33, 85, 248, 32, 70, 115, 68, 16, 170, 152, 1, 99, 50, 10, 232, 37, 80, 53, 253, 28, 115, 251, 123, 216, 37, 184, 216, 35, 127, 42, 141, 111, 30, 212, 224, 161, 213, 247, 199, 127, 168, 72, 227, 81, 132, 31, 112, 129, 149, 230, 133, 86, 225, 28, 251, 170, 146, 182, 159, 223, 250, 22, 179, 94, 237, 89, 201, 172, 127, 141, 87, 172, 229, 1, 46, 109, 176, 93, 60, 223, 30, 53, 79, 38, 46, 248, 160, 56, 68, 45, 182, 211, 93, 108, 209, 197, 25, 118, 200, 33, 9, 174, 135, 152, 129, 252, 110, 225, 208, 202, 60, 145, 172, 115, 32, 158, 165, 186, 19, 114, 148, 37, 187, 190, 196, 140, 124, 214, 178, 25, 116, 165, 95, 252, 191, 251, 56, 4, 124, 226, 158, 183, 124, 82, 120, 233, 126, 219, 54, 176, 241, 151, 110, 70, 67, 136, 192, 145, 237, 246, 250, 47, 167, 37, 26, 167, 59, 148, 223, 121, 203, 101, 174, 170], port: 51409, idle_timeout_ms: 17 };264;[40, 45, 00, 01, 40, fd, b7, a2, 29, d5, b2, 4e, 73, 2c, d2, c8, 4a, 71, a1, 9c, 10, 9e, b8, a7, 3c, a2, ac, e7, 05, 3e, e1, 49, 9c, 2b, 8d, 07, 18, df, f2, e9, d4, 1f, 39, 67, 10, 8c, 21, 31, 4c, f6, ed, 75, 1b, e9, f0, 9b, 08, 10, 22, 2a, 05, 96, 9a, bf, 77, 9e, 52, ed, 84, f6, c2, 86, de, 19, 34, e9, d3, a0, d2, e5, 01, 91, 05, a4, 2f, 21, 55, f8, 20, 46, 73, 44, 10, aa, 98, 01, 63, 32, 0a, e8, 25, 50, 35, fd, 1c, 73, fb, 7b, d8, 25, b8, d8, 23, 7f, 2a, 8d, 6f, 1e, d4, e0, a1, d5, f7, c7, 7f, a8, 48, e3, 51, 84, 1f, 70, 81, 95, e6, 85, 56, e1, 1c, fb, aa, 92, b6, 9f, df, fa, 16, b3, 5e, ed, 59, c9, ac, 7f, 8d, 57, ac, e5, 01, 2e, 6d, b0, 5d, 3c, df, 1e, 35, 4f, 26, 2e, f8, a0, 38, 44, 2d, b6, d3, 5d, 6c, d1, c5, 19, 76, c8, 21, 09, ae, 87, 98, 81, fc, 6e, e1, d0, ca, 3c, 91, ac, 73, 20, 9e, a5, ba, 13, 72, 94, 25, bb, be, c4, 8c, 7c, d6, b2, 19, 74, a5, 5f, fc, bf, fb, 38, 04, 7c, e2, 9e, b7, 7c, 52, 78, e9, 7e, db, 36, b0, f1, 97, 6e, 46, 43, 88, c0, 91, ed, f6, fa, 2f, a7, 25, 1a, a7, 3b, 94, df, 79, cb, 65, ae, aa, c8, d1, 11, 00] +udp_open 8007e6548000b43d03800000999f009d363edb9e5ae5cbab0d342f8b6333e3adc353cc241a9e3382d074f2920510cc4b93dc3b56f27774c5593edbd6652813e391a8fac9b7a61ac73976b48fe217fd3de956873601ee9729c65756c75dec5baf7bddc04b1496604ee04cf943a8dee9b883dd7c8a391e13a70b316a0c4c64fe7f520439228396a736183ae4e8807f6275651a7306238880dcdaf6fee977cb94610dcf9e14fc7c378ec0e5be0735461f4480000000 UdpSessionOpen { session_id: 517716, flags: 46141, address_type: Domain, host: [159, 0, 157, 54, 62, 219, 158, 90, 229, 203, 171, 13, 52, 47, 139, 99, 51, 227, 173, 195, 83, 204, 36, 26, 158, 51, 130, 208, 116, 242, 146, 5, 16, 204, 75, 147, 220, 59, 86, 242, 119, 116, 197, 89, 62, 219, 214, 101, 40, 19, 227, 145, 168, 250, 201, 183, 166, 26, 199, 57, 118, 180, 143, 226, 23, 253, 61, 233, 86, 135, 54, 1, 238, 151, 41, 198, 87, 86, 199, 93, 236, 91, 175, 123, 221, 192, 75, 20, 150, 96, 78, 224, 76, 249, 67, 168, 222, 233, 184, 131, 221, 124, 138, 57, 30, 19, 167, 11, 49, 106, 12, 76, 100, 254, 127, 82, 4, 57, 34, 131, 150, 167, 54, 24, 58, 228, 232, 128, 127, 98, 117, 101, 26, 115, 6, 35, 136, 128, 220, 218, 246, 254, 233, 119, 203, 148, 97, 13, 207, 158, 20, 252, 124], port: 14222, idle_timeout_ms: 64666707834576708 };180;[80, 07, e6, 54, 80, 00, b4, 3d, 03, 40, 99, 9f, 00, 9d, 36, 3e, db, 9e, 5a, e5, cb, ab, 0d, 34, 2f, 8b, 63, 33, e3, ad, c3, 53, cc, 24, 1a, 9e, 33, 82, d0, 74, f2, 92, 05, 10, cc, 4b, 93, dc, 3b, 56, f2, 77, 74, c5, 59, 3e, db, d6, 65, 28, 13, e3, 91, a8, fa, c9, b7, a6, 1a, c7, 39, 76, b4, 8f, e2, 17, fd, 3d, e9, 56, 87, 36, 01, ee, 97, 29, c6, 57, 56, c7, 5d, ec, 5b, af, 7b, dd, c0, 4b, 14, 96, 60, 4e, e0, 4c, f9, 43, a8, de, e9, b8, 83, dd, 7c, 8a, 39, 1e, 13, a7, 0b, 31, 6a, 0c, 4c, 64, fe, 7f, 52, 04, 39, 22, 83, 96, a7, 36, 18, 3a, e4, e8, 80, 7f, 62, 75, 65, 1a, 73, 06, 23, 88, 80, dc, da, f6, fe, e9, 77, cb, 94, 61, 0d, cf, 9e, 14, fc, 7c, 37, 8e, c0, e5, be, 07, 35, 46, 1f, 44, 00] +udp_open 404ac000001b9038d3dd01c0000000000000115b8ba6bcfb0a40a1e9b88c01901a85c5ce283ac272e359a9c3dc5d00 UdpSessionOpen { session_id: 74, flags: 118383760349, address_type: Ipv4, host: [91, 139, 166, 188, 251, 10, 64, 161, 233, 184, 140, 1, 144, 26, 133, 197, 206], port: 10298, idle_timeout_ms: 176453309660650589 };47;[40, 4a, c0, 00, 00, 1b, 90, 38, d3, dd, 01, 11, 5b, 8b, a6, bc, fb, 0a, 40, a1, e9, b8, 8c, 01, 90, 1a, 85, c5, ce, 28, 3a, c2, 72, e3, 59, a9, c3, dc, 5d, 00] +udp_open 8300d610c0225e1259da7c0a0440f4c9c140521d43cfba2702d7747b8f022d7f2ced5983e1fad1263186b59cd92331e22fb3352cc2c6e9e0aa374218819ce3b43dd19c919f23849ef82b5a8c21dbd63f7cb7b487cb6945900f954a8a9fbeaa0c5d1b6da65c2fe18eb5868cc092f4a64b45328db28365e9b693c143632e9537036d6a997039a945c6086244d990de35b88aec1a523a3db7d666c8180076ca31cbe0507c67a23092eca6cbfca466db548731483abb996eeddefce24a13dcf361d919fdabdc72ff13a4b6583c2e2f20dc4169b042d3ca77c8711b979604ff73d136cdb1c889b883b5f9e204a850facd80e6876e54522ace000cedafcff4309f693daad9053aa381104d9e4000 UdpSessionOpen { session_id: 50386448, flags: 9673582118075402, address_type: Ipv6, host: [201, 193, 64, 82, 29, 67, 207, 186, 39, 2, 215, 116, 123, 143, 2, 45, 127, 44, 237, 89, 131, 225, 250, 209, 38, 49, 134, 181, 156, 217, 35, 49, 226, 47, 179, 53, 44, 194, 198, 233, 224, 170, 55, 66, 24, 129, 156, 227, 180, 61, 209, 156, 145, 159, 35, 132, 158, 248, 43, 90, 140, 33, 219, 214, 63, 124, 183, 180, 135, 203, 105, 69, 144, 15, 149, 74, 138, 159, 190, 170, 12, 93, 27, 109, 166, 92, 47, 225, 142, 181, 134, 140, 192, 146, 244, 166, 75, 69, 50, 141, 178, 131, 101, 233, 182, 147, 193, 67, 99, 46, 149, 55, 3, 109, 106, 153, 112, 57, 169, 69, 198, 8, 98, 68, 217, 144, 222, 53, 184, 138, 236, 26, 82, 58, 61, 183, 214, 102, 200, 24, 0, 118, 202, 49, 203, 224, 80, 124, 103, 162, 48, 146, 236, 166, 203, 252, 164, 102, 219, 84, 135, 49, 72, 58, 187, 153, 110, 237, 222, 252, 226, 74, 19, 220, 243, 97, 217, 25, 253, 171, 220, 114, 255, 19, 164, 182, 88, 60, 46, 47, 32, 220, 65, 105, 176, 66, 211, 202, 119, 200, 113, 27, 151, 150, 4, 255, 115, 209, 54, 205, 177, 200, 137, 184, 131, 181, 249, 226, 4, 168, 80, 250, 205, 128, 230, 135, 110, 84, 82, 42, 206, 0, 12, 237, 175, 207, 244, 48, 159, 105, 61, 170, 217, 5], port: 15011, idle_timeout_ms: 17845662 };267;[83, 00, d6, 10, c0, 22, 5e, 12, 59, da, 7c, 0a, 04, 40, f4, c9, c1, 40, 52, 1d, 43, cf, ba, 27, 02, d7, 74, 7b, 8f, 02, 2d, 7f, 2c, ed, 59, 83, e1, fa, d1, 26, 31, 86, b5, 9c, d9, 23, 31, e2, 2f, b3, 35, 2c, c2, c6, e9, e0, aa, 37, 42, 18, 81, 9c, e3, b4, 3d, d1, 9c, 91, 9f, 23, 84, 9e, f8, 2b, 5a, 8c, 21, db, d6, 3f, 7c, b7, b4, 87, cb, 69, 45, 90, 0f, 95, 4a, 8a, 9f, be, aa, 0c, 5d, 1b, 6d, a6, 5c, 2f, e1, 8e, b5, 86, 8c, c0, 92, f4, a6, 4b, 45, 32, 8d, b2, 83, 65, e9, b6, 93, c1, 43, 63, 2e, 95, 37, 03, 6d, 6a, 99, 70, 39, a9, 45, c6, 08, 62, 44, d9, 90, de, 35, b8, 8a, ec, 1a, 52, 3a, 3d, b7, d6, 66, c8, 18, 00, 76, ca, 31, cb, e0, 50, 7c, 67, a2, 30, 92, ec, a6, cb, fc, a4, 66, db, 54, 87, 31, 48, 3a, bb, 99, 6e, ed, de, fc, e2, 4a, 13, dc, f3, 61, d9, 19, fd, ab, dc, 72, ff, 13, a4, b6, 58, 3c, 2e, 2f, 20, dc, 41, 69, b0, 42, d3, ca, 77, c8, 71, 1b, 97, 96, 04, ff, 73, d1, 36, cd, b1, c8, 89, b8, 83, b5, f9, e2, 04, a8, 50, fa, cd, 80, e6, 87, 6e, 54, 52, 2a, ce, 00, 0c, ed, af, cf, f4, 30, 9f, 69, 3d, aa, d9, 05, 3a, a3, 81, 10, 4d, 9e, 00] +udp_open 9a13767cc13f78df28a76fc00441020b3beb3bae786db4700751bfddcd56cf66086ebb9fac54c28c1a6621205e7348313c0424abee1fb2fd7f46332c4ab200132b5b7acf662a4b9a10853a82c149af5d599a94ff082eb7acd18851df44bdb90537ae685fc53737f1d89cf94999ee348511904fb99ca5b485674d32ff76fe42686e77768bed546aa273e2c834fd73c6d2d61bfd552b20fdde2345a11b326bc132dcfc77f37af75608986a1ec108af61961a9b1728783de9b4e2851b75249ea5ded5f0a98cc12929e6b5b20ac012c188a4d19845db771e1a4107de6945bb97758ccef80fd2eaddfd18ce1a515a4c770fd2964a1ec541aab37909f01555f44703fbae86d09d89e3ad049951c098020afcea7d0780004000 reject +udp_open 995b0f710104c00000000000005a6690ecc4aad5619938290a4e17523a223c4da6556919dfbb139e53e931c94ec1577e124676df96d102abceedcd25c24fd949b33f3e4f864de173eca779cfbd40bb24f94c410d450d66bcd124906fec13fb63823ae46f4302bbcc76b7c0000000000004ad400cc239521740a4bb1ab7e37d37d6f6b3 UdpSessionOpen { session_id: 425398129, flags: 1, address_type: Ipv6, host: [102, 144, 236, 196, 170, 213, 97, 153, 56, 41, 10, 78, 23, 82, 58, 34, 60, 77, 166, 85, 105, 25, 223, 187, 19, 158, 83, 233, 49, 201, 78, 193, 87, 126, 18, 70, 118, 223, 150, 209, 2, 171, 206, 237, 205, 37, 194, 79, 217, 73, 179, 63, 62, 79, 134, 77, 225, 115, 236, 167, 121, 207, 189, 64, 187, 36, 249, 76, 65, 13, 69, 13, 102, 188, 209, 36, 144, 111, 236, 19, 251, 99, 130, 58, 228, 111, 67, 2, 187, 204], port: 30391, idle_timeout_ms: 1197 };128;[99, 5b, 0f, 71, 01, 04, 40, 5a, 66, 90, ec, c4, aa, d5, 61, 99, 38, 29, 0a, 4e, 17, 52, 3a, 22, 3c, 4d, a6, 55, 69, 19, df, bb, 13, 9e, 53, e9, 31, c9, 4e, c1, 57, 7e, 12, 46, 76, df, 96, d1, 02, ab, ce, ed, cd, 25, c2, 4f, d9, 49, b3, 3f, 3e, 4f, 86, 4d, e1, 73, ec, a7, 79, cf, bd, 40, bb, 24, f9, 4c, 41, 0d, 45, 0d, 66, bc, d1, 24, 90, 6f, ec, 13, fb, 63, 82, 3a, e4, 6f, 43, 02, bb, cc, 76, b7, 44, ad, 00] +udp_open 4acc8017a6340140febda06c83f250e42a61d6632a04c88a3a295281da08c153b5fb483903754cb5e45ce230b7886622e44111493dcd1880f2e731a0c9f1d1b1f381ab800a25dad3e8da87f4d49cb0d2103775714c9ba0723dddbec2d3d95d9e4a943827e5812fea4af16b3aa16247108990423f109153d514d2b7d9ec2c877bba926ec275bc0e35a0ebafe9575d248e5bbda7d0e6c11a41726b868def372a9b9b0eb7a6be5aef6c75a505131aa6635e0530133c0d7aec27f9993cff1bfa676ef304008c622956ddf98f17a6662978f5a75a37875511dc0cf70e279590a25ced79de8c2f425d10dc518f40dc9a81e6c4a3b650d16ead4ce84f443c34a1d5ac86dc11a98015ebb3181041b2c00000000000000a7d16dfb5996c9e18aadb UdpSessionOpen { session_id: 2764, flags: 1549876, address_type: Ipv4, host: [189, 160, 108, 131, 242, 80, 228, 42, 97, 214, 99, 42, 4, 200, 138, 58, 41, 82, 129, 218, 8, 193, 83, 181, 251, 72, 57, 3, 117, 76, 181, 228, 92, 226, 48, 183, 136, 102, 34, 228, 65, 17, 73, 61, 205, 24, 128, 242, 231, 49, 160, 201, 241, 209, 177, 243, 129, 171, 128, 10, 37, 218, 211, 232, 218, 135, 244, 212, 156, 176, 210, 16, 55, 117, 113, 76, 155, 160, 114, 61, 221, 190, 194, 211, 217, 93, 158, 74, 148, 56, 39, 229, 129, 47, 234, 74, 241, 107, 58, 161, 98, 71, 16, 137, 144, 66, 63, 16, 145, 83, 213, 20, 210, 183, 217, 236, 44, 135, 123, 186, 146, 110, 194, 117, 188, 14, 53, 160, 235, 175, 233, 87, 93, 36, 142, 91, 189, 167, 208, 230, 193, 26, 65, 114, 107, 134, 141, 239, 55, 42, 155, 155, 14, 183, 166, 190, 90, 239, 108, 117, 165, 5, 19, 26, 166, 99, 94, 5, 48, 19, 60, 13, 122, 236, 39, 249, 153, 60, 255, 27, 250, 103, 110, 243, 4, 0, 140, 98, 41, 86, 221, 249, 143, 23, 166, 102, 41, 120, 245, 167, 90, 55, 135, 85, 17, 220, 12, 247, 14, 39, 149, 144, 162, 92, 237, 121, 222, 140, 47, 66, 93, 16, 220, 81, 143, 64, 220, 154, 129, 230, 196, 163, 182, 80, 209, 110, 173, 76, 232, 79, 68, 60, 52, 161, 213, 172, 134, 220, 17, 169, 128, 21, 235, 179], port: 6160, idle_timeout_ms: 434 };285;[4a, cc, 80, 17, a6, 34, 01, 40, fe, bd, a0, 6c, 83, f2, 50, e4, 2a, 61, d6, 63, 2a, 04, c8, 8a, 3a, 29, 52, 81, da, 08, c1, 53, b5, fb, 48, 39, 03, 75, 4c, b5, e4, 5c, e2, 30, b7, 88, 66, 22, e4, 41, 11, 49, 3d, cd, 18, 80, f2, e7, 31, a0, c9, f1, d1, b1, f3, 81, ab, 80, 0a, 25, da, d3, e8, da, 87, f4, d4, 9c, b0, d2, 10, 37, 75, 71, 4c, 9b, a0, 72, 3d, dd, be, c2, d3, d9, 5d, 9e, 4a, 94, 38, 27, e5, 81, 2f, ea, 4a, f1, 6b, 3a, a1, 62, 47, 10, 89, 90, 42, 3f, 10, 91, 53, d5, 14, d2, b7, d9, ec, 2c, 87, 7b, ba, 92, 6e, c2, 75, bc, 0e, 35, a0, eb, af, e9, 57, 5d, 24, 8e, 5b, bd, a7, d0, e6, c1, 1a, 41, 72, 6b, 86, 8d, ef, 37, 2a, 9b, 9b, 0e, b7, a6, be, 5a, ef, 6c, 75, a5, 05, 13, 1a, a6, 63, 5e, 05, 30, 13, 3c, 0d, 7a, ec, 27, f9, 99, 3c, ff, 1b, fa, 67, 6e, f3, 04, 00, 8c, 62, 29, 56, dd, f9, 8f, 17, a6, 66, 29, 78, f5, a7, 5a, 37, 87, 55, 11, dc, 0c, f7, 0e, 27, 95, 90, a2, 5c, ed, 79, de, 8c, 2f, 42, 5d, 10, dc, 51, 8f, 40, dc, 9a, 81, e6, c4, a3, b6, 50, d1, 6e, ad, 4c, e8, 4f, 44, 3c, 34, a1, d5, ac, 86, dc, 11, a9, 80, 15, eb, b3, 18, 10, 41, b2, 00] +udp_open 8000018cc00000021a8a298a038000002a71ff8b61e0a6aef8288cac5a8624335112a0bfd30cb5c34086881be36bfcd93d8bb685dc0037a588de4b74b8c0191a23fae7d5b800 UdpSessionOpen { session_id: 396, flags: 9035196810, address_type: Domain, host: [113, 255, 139, 97, 224, 166, 174, 248, 40, 140, 172, 90, 134, 36, 51, 81, 18, 160, 191, 211, 12, 181, 195, 64, 134, 136, 27, 227, 107, 252, 217, 61, 139, 182, 133, 220, 0, 55, 165, 136, 222, 75], port: 29880, idle_timeout_ms: 7065616253441464 };70;[41, 8c, c0, 00, 00, 02, 1a, 8a, 29, 8a, 03, 2a, 71, ff, 8b, 61, e0, a6, ae, f8, 28, 8c, ac, 5a, 86, 24, 33, 51, 12, a0, bf, d3, 0c, b5, c3, 40, 86, 88, 1b, e3, 6b, fc, d9, 3d, 8b, b6, 85, dc, 00, 37, a5, 88, de, 4b, 74, b8, c0, 19, 1a, 23, fa, e7, d5, b8, 00] +udp_open c0000001000000018000ae8303c000000000000037afb5d9c4cb80bc227673abef2c98f3f13c7568df33c33a205ee7cdd153b51450e42be4d0a3f9d8367de79100d1f98b458d56819364e570c952c0000246af2b5ad84000662ba4cc6d reject +udp_open c0000000003a42a8c000000024e384ba0480000009a85fbaa961347ad202b2abc0000000000666db00 UdpSessionOpen { session_id: 3818152, flags: 618890426, address_type: Ipv6, host: [168, 95, 186, 169, 97, 52, 122, 210, 2], port: 45739, idle_timeout_ms: 419547 };41;[80, 3a, 42, a8, a4, e3, 84, ba, 04, 09, a8, 5f, ba, a9, 61, 34, 7a, d2, 02, b2, ab, 80, 06, 66, db, 00] +udp_open c000000100004f85c001fe5b37ace3380a409e118c411d8e0fbe00bdd0863f5a3621c6eaba7805915b8b2375508a23773a769913542fff46950543e85810961cf87a4f5425f66184df58611be5825dbafda26ca7314b24939fee8cffec929d47e72e444584535908ec4b20a9eac1a89a68eed3a63d65c92184b0ebd94ba48e5e4c7e5b4f3273a38d073880582372750d496d4548e2bcdd4f5e7667ff9d93fbfa560397772e6587f215fa0c80868b216c920b95c00000196dd7f86700 reject +udp_open 8003e734800003ad0440bce6936c64d987c915cb244ae1a706f4bc36bb1e44935c6cd17c3b4cfa9aba70cda7b939cb52d97f70ef53593eff40019b9eff39e9469840f24caf20f09c388d6090d1e9b00f261c68323daaf58d284cb1dcab88edea9acaa6fe83e2e79f329eba9a29b68744b25a82a24948f23852fc8c3bb5199533a207688867f00e85effcc53e8524fa2d82d2759d7de1d48bc2c11fdb384b9f5157e23bf9046223492239884c19fa9d7ef651213c9004b4c33277c0e7635eaafca8c4ad05f250fc660140eac000000000000000 UdpSessionOpen { session_id: 255796, flags: 941, address_type: Ipv6, host: [230, 147, 108, 100, 217, 135, 201, 21, 203, 36, 74, 225, 167, 6, 244, 188, 54, 187, 30, 68, 147, 92, 108, 209, 124, 59, 76, 250, 154, 186, 112, 205, 167, 185, 57, 203, 82, 217, 127, 112, 239, 83, 89, 62, 255, 64, 1, 155, 158, 255, 57, 233, 70, 152, 64, 242, 76, 175, 32, 240, 156, 56, 141, 96, 144, 209, 233, 176, 15, 38, 28, 104, 50, 61, 170, 245, 141, 40, 76, 177, 220, 171, 136, 237, 234, 154, 202, 166, 254, 131, 226, 231, 159, 50, 158, 186, 154, 41, 182, 135, 68, 178, 90, 130, 162, 73, 72, 242, 56, 82, 252, 140, 59, 181, 25, 149, 51, 162, 7, 104, 136, 103, 240, 14, 133, 239, 252, 197, 62, 133, 36, 250, 45, 130, 210, 117, 157, 125, 225, 212, 139, 194, 193, 31, 219, 56, 75, 159, 81, 87, 226, 59, 249, 4, 98, 35, 73, 34, 57, 136, 76, 25, 250, 157, 126, 246, 81, 33, 60, 144, 4, 180, 195, 50, 119, 192, 231, 99, 94, 170, 252, 168, 196, 173, 5, 242, 80, 252], port: 26113, idle_timeout_ms: 234 };211;[80, 03, e7, 34, 43, ad, 04, 40, bc, e6, 93, 6c, 64, d9, 87, c9, 15, cb, 24, 4a, e1, a7, 06, f4, bc, 36, bb, 1e, 44, 93, 5c, 6c, d1, 7c, 3b, 4c, fa, 9a, ba, 70, cd, a7, b9, 39, cb, 52, d9, 7f, 70, ef, 53, 59, 3e, ff, 40, 01, 9b, 9e, ff, 39, e9, 46, 98, 40, f2, 4c, af, 20, f0, 9c, 38, 8d, 60, 90, d1, e9, b0, 0f, 26, 1c, 68, 32, 3d, aa, f5, 8d, 28, 4c, b1, dc, ab, 88, ed, ea, 9a, ca, a6, fe, 83, e2, e7, 9f, 32, 9e, ba, 9a, 29, b6, 87, 44, b2, 5a, 82, a2, 49, 48, f2, 38, 52, fc, 8c, 3b, b5, 19, 95, 33, a2, 07, 68, 88, 67, f0, 0e, 85, ef, fc, c5, 3e, 85, 24, fa, 2d, 82, d2, 75, 9d, 7d, e1, d4, 8b, c2, c1, 1f, db, 38, 4b, 9f, 51, 57, e2, 3b, f9, 04, 62, 23, 49, 22, 39, 88, 4c, 19, fa, 9d, 7e, f6, 51, 21, 3c, 90, 04, b4, c3, 32, 77, c0, e7, 63, 5e, aa, fc, a8, c4, ad, 05, f2, 50, fc, 66, 01, 40, ea, 00] +udp_open 409f9a368ae703c0000000000000392309004dd274b8b9ed60b5a2130c2c715afb62ad9c7004bbdd3864f368f2aae5c9b363115453503c636c8dc2ee6d16549ea8b226414e621338129fc00033b78cf32e8dc000000000000000 UdpSessionOpen { session_id: 159, flags: 439782119, address_type: Domain, host: [35, 9, 0, 77, 210, 116, 184, 185, 237, 96, 181, 162, 19, 12, 44, 113, 90, 251, 98, 173, 156, 112, 4, 187, 221, 56, 100, 243, 104, 242, 170, 229, 201, 179, 99, 17, 84, 83, 80, 60, 99, 108, 141, 194, 238, 109, 22, 84, 158, 168, 178, 38, 65, 78, 98, 19, 56], port: 4767, idle_timeout_ms: 56863436779149 };90;[40, 9f, 9a, 36, 8a, e7, 03, 39, 23, 09, 00, 4d, d2, 74, b8, b9, ed, 60, b5, a2, 13, 0c, 2c, 71, 5a, fb, 62, ad, 9c, 70, 04, bb, dd, 38, 64, f3, 68, f2, aa, e5, c9, b3, 63, 11, 54, 53, 50, 3c, 63, 6c, 8d, c2, ee, 6d, 16, 54, 9e, a8, b2, 26, 41, 4e, 62, 13, 38, 12, 9f, c0, 00, 33, b7, 8c, f3, 2e, 8d, 00] +udp_open c00000006a2ef3c68004f7a90337e8aa4497e690e36986be6ca0cf34057edafc432d06176226dae286e9b5517243569e1b1e1ffcd69d05cfda022709a152b399b4758df5b5c53b80159c1200 UdpSessionOpen { session_id: 1781461958, flags: 325545, address_type: Domain, host: [232, 170, 68, 151, 230, 144, 227, 105, 134, 190, 108, 160, 207, 52, 5, 126, 218, 252, 67, 45, 6, 23, 98, 38, 218, 226, 134, 233, 181, 81, 114, 67, 86, 158, 27, 30, 31, 252, 214, 157, 5, 207, 218, 2, 39, 9, 161, 82, 179, 153, 180, 117, 141, 245, 181], port: 50491, idle_timeout_ms: 1416210 };76;[c0, 00, 00, 00, 6a, 2e, f3, c6, 80, 04, f7, a9, 03, 37, e8, aa, 44, 97, e6, 90, e3, 69, 86, be, 6c, a0, cf, 34, 05, 7e, da, fc, 43, 2d, 06, 17, 62, 26, da, e2, 86, e9, b5, 51, 72, 43, 56, 9e, 1b, 1e, 1f, fc, d6, 9d, 05, cf, da, 02, 27, 09, a1, 52, b3, 99, b4, 75, 8d, f5, b5, c5, 3b, 80, 15, 9c, 12, 00] +udp_open c0000000009dab3ec000002ca768fac401800000c334b2df238f37e10efc20f7d9baea9fe5c4f5220d65c50afeb50c420260ad82ca9834c211f5feffcbe15252a3530cc70a6098ec01aa61f5247aae5ee8fba3545b79b9c51567e6479f213a8ec0a823d252b668239dca297b96115df86e75421a057532baf77033e3288975f59dd476c52349eb26ed5aabee05d46f92cb1fdba20247becbb9056ac135ae09f08208eafac0729a161114523eb1820c43cee81a7e4b13b659f54d75f051d4d63b14ce6355d1b94118170257e341484000b30e3f7c670b4900ac4b404100 UdpSessionOpen { session_id: 10332990, flags: 191787236036, address_type: Ipv4, host: [52, 178, 223, 35, 143, 55, 225, 14, 252, 32, 247, 217, 186, 234, 159, 229, 196, 245, 34, 13, 101, 197, 10, 254, 181, 12, 66, 2, 96, 173, 130, 202, 152, 52, 194, 17, 245, 254, 255, 203, 225, 82, 82, 163, 83, 12, 199, 10, 96, 152, 236, 1, 170, 97, 245, 36, 122, 174, 94, 232, 251, 163, 84, 91, 121, 185, 197, 21, 103, 230, 71, 159, 33, 58, 142, 192, 168, 35, 210, 82, 182, 104, 35, 157, 202, 41, 123, 150, 17, 93, 248, 110, 117, 66, 26, 5, 117, 50, 186, 247, 112, 51, 227, 40, 137, 117, 245, 157, 212, 118, 197, 35, 73, 235, 38, 237, 90, 171, 238, 5, 212, 111, 146, 203, 31, 219, 162, 2, 71, 190, 203, 185, 5, 106, 193, 53, 174, 9, 240, 130, 8, 234, 250, 192, 114, 154, 22, 17, 20, 82, 62, 177, 130, 12, 67, 206, 232, 26, 126, 75, 19, 182, 89, 245, 77, 117, 240, 81, 212, 214, 59, 20, 206, 99, 85, 209, 185, 65, 24, 23, 2, 87, 227, 65, 72, 64, 0, 179, 14, 63, 124, 103, 11, 73, 0], port: 44107, idle_timeout_ms: 65 };221;[80, 9d, ab, 3e, c0, 00, 00, 2c, a7, 68, fa, c4, 01, 40, c3, 34, b2, df, 23, 8f, 37, e1, 0e, fc, 20, f7, d9, ba, ea, 9f, e5, c4, f5, 22, 0d, 65, c5, 0a, fe, b5, 0c, 42, 02, 60, ad, 82, ca, 98, 34, c2, 11, f5, fe, ff, cb, e1, 52, 52, a3, 53, 0c, c7, 0a, 60, 98, ec, 01, aa, 61, f5, 24, 7a, ae, 5e, e8, fb, a3, 54, 5b, 79, b9, c5, 15, 67, e6, 47, 9f, 21, 3a, 8e, c0, a8, 23, d2, 52, b6, 68, 23, 9d, ca, 29, 7b, 96, 11, 5d, f8, 6e, 75, 42, 1a, 05, 75, 32, ba, f7, 70, 33, e3, 28, 89, 75, f5, 9d, d4, 76, c5, 23, 49, eb, 26, ed, 5a, ab, ee, 05, d4, 6f, 92, cb, 1f, db, a2, 02, 47, be, cb, b9, 05, 6a, c1, 35, ae, 09, f0, 82, 08, ea, fa, c0, 72, 9a, 16, 11, 14, 52, 3e, b1, 82, 0c, 43, ce, e8, 1a, 7e, 4b, 13, b6, 59, f5, 4d, 75, f0, 51, d4, d6, 3b, 14, ce, 63, 55, d1, b9, 41, 18, 17, 02, 57, e3, 41, 48, 40, 00, b3, 0e, 3f, 7c, 67, 0b, 49, 00, ac, 4b, 40, 41, 00] +udp_open 78bd00032714a547e099874c37e39650b61d4244b6f65b085855228fa4466715b07bfa3665e79388ea571120646c403080000000 UdpSessionOpen { session_id: 14525, flags: 0, address_type: Domain, host: [20, 165, 71, 224, 153, 135, 76, 55, 227, 150, 80, 182, 29, 66, 68, 182, 246, 91, 8, 88, 85, 34, 143, 164, 70, 103, 21, 176, 123, 250, 54, 101, 231, 147, 136, 234, 87, 17, 32], port: 25708, idle_timeout_ms: 48 };52;[78, bd, 00, 03, 27, 14, a5, 47, e0, 99, 87, 4c, 37, e3, 96, 50, b6, 1d, 42, 44, b6, f6, 5b, 08, 58, 55, 22, 8f, a4, 46, 67, 15, b0, 7b, fa, 36, 65, e7, 93, 88, ea, 57, 11, 20, 64, 6c, 30, 00] +udp_open c0000001000cf5ccc0002c920f1ea37704800000f9034cc35f164c91926760e99d07e82eef9e3033b5e16e85ed6f87b9734bfb2829c64cb6b49b0feda0aa9348b861a1134cc896d8ae572e837a5dc00ff0c691c8d0bfe1bddda36a91de6108137b6eb806068431355f9f0d5504c19fe3da85421700686eb64a77a728b31819c510cadbf14c995403a1b95eefd42e0a2af228847079cc577b1ddd48cadf2d5222e9309f8d60a768498a0a8097f9aab5e6c089148493c0684a1727ef6f939fc330cfa1f5b2bb389f68fd09a0567dc08a55bc687e5cd466a1696cc579dc4cd9121ae07c82b9392b8e7e03fea26a371ecbd417a1b7b2264f51cc788a059798ff60ad7992524c0a08389e5fd308c9baa84168c0002c7dc5f0308cc00000000000000f4878237748db0fa24a0dd474218f7872 reject +udp_open 8283511ac2d4b14b4fa3ab120140cfdc0f534ed3073f14c381831c85350327ed555e1694ac34a0c141b73cce81ba71333960eed42cebe07df733e07378b07d294d4e394ec6815bea36a195d00d0d701f52ecd482b8bd494c11d3fb998590f9b1b51b191265945ebbcfdd6778d10a26d58fc65bfbb7b129c031826196beed3182088b90fdcaf822c2de90fbdee12a06a782833ea6c274f7ac83e01e831e014ea48948634511c9973f97dcdf448a617e573df879534aad52d111755d5928518cabf14e6d3b73cda68489a24e400ee9528dca53aaf68105475351072ac4db2a90cd400380000000 UdpSessionOpen { session_id: 42160410, flags: 203982820155304722, address_type: Ipv4, host: [220, 15, 83, 78, 211, 7, 63, 20, 195, 129, 131, 28, 133, 53, 3, 39, 237, 85, 94, 22, 148, 172, 52, 160, 193, 65, 183, 60, 206, 129, 186, 113, 51, 57, 96, 238, 212, 44, 235, 224, 125, 247, 51, 224, 115, 120, 176, 125, 41, 77, 78, 57, 78, 198, 129, 91, 234, 54, 161, 149, 208, 13, 13, 112, 31, 82, 236, 212, 130, 184, 189, 73, 76, 17, 211, 251, 153, 133, 144, 249, 177, 181, 27, 25, 18, 101, 148, 94, 187, 207, 221, 103, 120, 209, 10, 38, 213, 143, 198, 91, 251, 183, 177, 41, 192, 49, 130, 97, 150, 190, 237, 49, 130, 8, 139, 144, 253, 202, 248, 34, 194, 222, 144, 251, 222, 225, 42, 6, 167, 130, 131, 62, 166, 194, 116, 247, 172, 131, 224, 30, 131, 30, 1, 78, 164, 137, 72, 99, 69, 17, 201, 151, 63, 151, 220, 223, 68, 138, 97, 126, 87, 61, 248, 121, 83, 74, 173, 82, 209, 17, 117, 93, 89, 40, 81, 140, 171, 241, 78, 109, 59, 115, 205, 166, 132, 137, 162, 78, 64, 14, 233, 82, 141, 202, 83, 170, 246, 129, 5, 71, 83, 81, 7, 42, 196, 219, 42], port: 37069, idle_timeout_ms: 3 };230;[82, 83, 51, 1a, c2, d4, b1, 4b, 4f, a3, ab, 12, 01, 40, cf, dc, 0f, 53, 4e, d3, 07, 3f, 14, c3, 81, 83, 1c, 85, 35, 03, 27, ed, 55, 5e, 16, 94, ac, 34, a0, c1, 41, b7, 3c, ce, 81, ba, 71, 33, 39, 60, ee, d4, 2c, eb, e0, 7d, f7, 33, e0, 73, 78, b0, 7d, 29, 4d, 4e, 39, 4e, c6, 81, 5b, ea, 36, a1, 95, d0, 0d, 0d, 70, 1f, 52, ec, d4, 82, b8, bd, 49, 4c, 11, d3, fb, 99, 85, 90, f9, b1, b5, 1b, 19, 12, 65, 94, 5e, bb, cf, dd, 67, 78, d1, 0a, 26, d5, 8f, c6, 5b, fb, b7, b1, 29, c0, 31, 82, 61, 96, be, ed, 31, 82, 08, 8b, 90, fd, ca, f8, 22, c2, de, 90, fb, de, e1, 2a, 06, a7, 82, 83, 3e, a6, c2, 74, f7, ac, 83, e0, 1e, 83, 1e, 01, 4e, a4, 89, 48, 63, 45, 11, c9, 97, 3f, 97, dc, df, 44, 8a, 61, 7e, 57, 3d, f8, 79, 53, 4a, ad, 52, d1, 11, 75, 5d, 59, 28, 51, 8c, ab, f1, 4e, 6d, 3b, 73, cd, a6, 84, 89, a2, 4e, 40, 0e, e9, 52, 8d, ca, 53, aa, f6, 81, 05, 47, 53, 51, 07, 2a, c4, db, 2a, 90, cd, 03, 00] +udp_open 4014c12f973ecc4b19f203c0000000000000c2f3361bc518014581ad195bfdca8ae01a443d3e0be03d90e62bf70cf549835f9acc4df573165d5d7ccee0bb521c96b615267e5fdd5030c9c48518a360ccc0472f1e1ae353d9200df96f45ba20f11511396ec0b86d4b9b9a07d55523cfd48a3a1c72f25ff424c07ef49d5c9b680a751e8745dbd1b22be5374d98f1f2cb06b8a5ff6cf45737a9f8117fbe842e13a5e39f6b09f9dbfc992555de3b66a57803b57bd9c7cf34ba6729559ccebd9a0a023a90e5ee1b1f3ef64e41176d00a9ed53a2fbbbe426d3e7c0067cd03232e488400011fa UdpSessionOpen { session_id: 20, flags: 85453213914569202, address_type: Domain, host: [243, 54, 27, 197, 24, 1, 69, 129, 173, 25, 91, 253, 202, 138, 224, 26, 68, 61, 62, 11, 224, 61, 144, 230, 43, 247, 12, 245, 73, 131, 95, 154, 204, 77, 245, 115, 22, 93, 93, 124, 206, 224, 187, 82, 28, 150, 182, 21, 38, 126, 95, 221, 80, 48, 201, 196, 133, 24, 163, 96, 204, 192, 71, 47, 30, 26, 227, 83, 217, 32, 13, 249, 111, 69, 186, 32, 241, 21, 17, 57, 110, 192, 184, 109, 75, 155, 154, 7, 213, 85, 35, 207, 212, 138, 58, 28, 114, 242, 95, 244, 36, 192, 126, 244, 157, 92, 155, 104, 10, 117, 30, 135, 69, 219, 209, 178, 43, 229, 55, 77, 152, 241, 242, 203, 6, 184, 165, 255, 108, 244, 87, 55, 169, 248, 17, 127, 190, 132, 46, 19, 165, 227, 159, 107, 9, 249, 219, 252, 153, 37, 85, 222, 59, 102, 165, 120, 3, 181, 123, 217, 199, 207, 52, 186, 103, 41, 85, 156, 206, 189, 154, 10, 2, 58, 144, 229, 238, 27, 31, 62, 246, 78, 65, 23, 109, 0, 169, 237, 83, 162, 251, 187, 228, 38], port: 54247, idle_timeout_ms: 1826083497501832 };225;[14, c1, 2f, 97, 3e, cc, 4b, 19, f2, 03, 40, c2, f3, 36, 1b, c5, 18, 01, 45, 81, ad, 19, 5b, fd, ca, 8a, e0, 1a, 44, 3d, 3e, 0b, e0, 3d, 90, e6, 2b, f7, 0c, f5, 49, 83, 5f, 9a, cc, 4d, f5, 73, 16, 5d, 5d, 7c, ce, e0, bb, 52, 1c, 96, b6, 15, 26, 7e, 5f, dd, 50, 30, c9, c4, 85, 18, a3, 60, cc, c0, 47, 2f, 1e, 1a, e3, 53, d9, 20, 0d, f9, 6f, 45, ba, 20, f1, 15, 11, 39, 6e, c0, b8, 6d, 4b, 9b, 9a, 07, d5, 55, 23, cf, d4, 8a, 3a, 1c, 72, f2, 5f, f4, 24, c0, 7e, f4, 9d, 5c, 9b, 68, 0a, 75, 1e, 87, 45, db, d1, b2, 2b, e5, 37, 4d, 98, f1, f2, cb, 06, b8, a5, ff, 6c, f4, 57, 37, a9, f8, 11, 7f, be, 84, 2e, 13, a5, e3, 9f, 6b, 09, f9, db, fc, 99, 25, 55, de, 3b, 66, a5, 78, 03, b5, 7b, d9, c7, cf, 34, ba, 67, 29, 55, 9c, ce, bd, 9a, 0a, 02, 3a, 90, e5, ee, 1b, 1f, 3e, f6, 4e, 41, 17, 6d, 00, a9, ed, 53, a2, fb, bb, e4, 26, d3, e7, c0, 06, 7c, d0, 32, 32, e4, 88, 00] +udp_open 8000000ecd09700b50f1f731034065f59f8d8ac69305e34631bf5f143619d2ad7abea4c5c6c11649d73f299ed11f3e49d947db287542c1fa504c67bc5516d46d0d0a806f203a6001d5cd09814dd3250360dd59b45f58d002649d45126762e07f75a62dce2cd92032165687c52ede4635820694ed4fd6c0000011ee13ae124007bfd2b19d3a4c02 UdpSessionOpen { session_id: 14, flags: 939405191188444977, address_type: Domain, host: [245, 159, 141, 138, 198, 147, 5, 227, 70, 49, 191, 95, 20, 54, 25, 210, 173, 122, 190, 164, 197, 198, 193, 22, 73, 215, 63, 41, 158, 209, 31, 62, 73, 217, 71, 219, 40, 117, 66, 193, 250, 80, 76, 103, 188, 85, 22, 212, 109, 13, 10, 128, 111, 32, 58, 96, 1, 213, 205, 9, 129, 77, 211, 37, 3, 96, 221, 89, 180, 95, 88, 208, 2, 100, 157, 69, 18, 103, 98, 224, 127, 117, 166, 45, 206, 44, 217, 32, 50, 22, 86, 135, 197, 46, 222, 70, 53, 130, 6, 148, 237], port: 20438, idle_timeout_ms: 77008711186 };135;[0e, cd, 09, 70, 0b, 50, f1, f7, 31, 03, 40, 65, f5, 9f, 8d, 8a, c6, 93, 05, e3, 46, 31, bf, 5f, 14, 36, 19, d2, ad, 7a, be, a4, c5, c6, c1, 16, 49, d7, 3f, 29, 9e, d1, 1f, 3e, 49, d9, 47, db, 28, 75, 42, c1, fa, 50, 4c, 67, bc, 55, 16, d4, 6d, 0d, 0a, 80, 6f, 20, 3a, 60, 01, d5, cd, 09, 81, 4d, d3, 25, 03, 60, dd, 59, b4, 5f, 58, d0, 02, 64, 9d, 45, 12, 67, 62, e0, 7f, 75, a6, 2d, ce, 2c, d9, 20, 32, 16, 56, 87, c5, 2e, de, 46, 35, 82, 06, 94, ed, 4f, d6, c0, 00, 00, 11, ee, 13, ae, 12, 00] +udp_open c000000000000ab90001800000a1612ce5d57237f92e638cba2fdcadeb629abed0f912f0622ce4687f120f424d6a715dab237434641ee707a82014eed2608372abff9520564a91f1d07f1d1486b66961f041a5159aac1ff8742ae0f5b58abe06ec028474bca80ac04ee2cb4aa64b39c7759768bc602a6299311c771d2cb39a288445ae5cd3c46a336d69b65d2926cf9e634ef88d796f4586fc637e69073f06011ad05e7a0ca91e5b752dda2621fe63554f8b2b4b8b80000000 UdpSessionOpen { session_id: 2745, flags: 0, address_type: Ipv4, host: [97, 44, 229, 213, 114, 55, 249, 46, 99, 140, 186, 47, 220, 173, 235, 98, 154, 190, 208, 249, 18, 240, 98, 44, 228, 104, 127, 18, 15, 66, 77, 106, 113, 93, 171, 35, 116, 52, 100, 30, 231, 7, 168, 32, 20, 238, 210, 96, 131, 114, 171, 255, 149, 32, 86, 74, 145, 241, 208, 127, 29, 20, 134, 182, 105, 97, 240, 65, 165, 21, 154, 172, 31, 248, 116, 42, 224, 245, 181, 138, 190, 6, 236, 2, 132, 116, 188, 168, 10, 192, 78, 226, 203, 74, 166, 75, 57, 199, 117, 151, 104, 188, 96, 42, 98, 153, 49, 28, 119, 29, 44, 179, 154, 40, 132, 69, 174, 92, 211, 196, 106, 51, 109, 105, 182, 93, 41, 38, 207, 158, 99, 78, 248, 141, 121, 111, 69, 134, 252, 99, 126, 105, 7, 63, 6, 1, 26, 208, 94, 122, 12, 169, 30, 91, 117, 45, 218, 38, 33, 254, 99], port: 21839, idle_timeout_ms: 187386763 };185;[4a, b9, 00, 01, 40, a1, 61, 2c, e5, d5, 72, 37, f9, 2e, 63, 8c, ba, 2f, dc, ad, eb, 62, 9a, be, d0, f9, 12, f0, 62, 2c, e4, 68, 7f, 12, 0f, 42, 4d, 6a, 71, 5d, ab, 23, 74, 34, 64, 1e, e7, 07, a8, 20, 14, ee, d2, 60, 83, 72, ab, ff, 95, 20, 56, 4a, 91, f1, d0, 7f, 1d, 14, 86, b6, 69, 61, f0, 41, a5, 15, 9a, ac, 1f, f8, 74, 2a, e0, f5, b5, 8a, be, 06, ec, 02, 84, 74, bc, a8, 0a, c0, 4e, e2, cb, 4a, a6, 4b, 39, c7, 75, 97, 68, bc, 60, 2a, 62, 99, 31, 1c, 77, 1d, 2c, b3, 9a, 28, 84, 45, ae, 5c, d3, c4, 6a, 33, 6d, 69, b6, 5d, 29, 26, cf, 9e, 63, 4e, f8, 8d, 79, 6f, 45, 86, fc, 63, 7e, 69, 07, 3f, 06, 01, 1a, d0, 5e, 7a, 0c, a9, 1e, 5b, 75, 2d, da, 26, 21, fe, 63, 55, 4f, 8b, 2b, 4b, 8b, 00] +udp_open 8003aa62c0000000005a0c6801800000a056e31813a32c6392c62d6502a98427d110dc343d67ce809ea5d86696d810f429389a4da37cfa4d476823ba07d325588a4d0df4281cf64ff08f42e7d3a896232d68ec1b9388043d2e64c848e920c10bb5c4bfab22116ee0d2f50cd791a16625494ca492f0f7b00d58107be583ba4ce9772546ee97e481c9d7761e793602daed343c69bd659d4857385134cb7cb7ba0099e0284662228e996665021b033d534d4218f80400 UdpSessionOpen { session_id: 240226, flags: 5901416, address_type: Ipv4, host: [86, 227, 24, 19, 163, 44, 99, 146, 198, 45, 101, 2, 169, 132, 39, 209, 16, 220, 52, 61, 103, 206, 128, 158, 165, 216, 102, 150, 216, 16, 244, 41, 56, 154, 77, 163, 124, 250, 77, 71, 104, 35, 186, 7, 211, 37, 88, 138, 77, 13, 244, 40, 28, 246, 79, 240, 143, 66, 231, 211, 168, 150, 35, 45, 104, 236, 27, 147, 136, 4, 61, 46, 100, 200, 72, 233, 32, 193, 11, 181, 196, 191, 171, 34, 17, 110, 224, 210, 245, 12, 215, 145, 161, 102, 37, 73, 76, 164, 146, 240, 247, 176, 13, 88, 16, 123, 229, 131, 186, 76, 233, 119, 37, 70, 238, 151, 228, 129, 201, 215, 118, 30, 121, 54, 2, 218, 237, 52, 60, 105, 189, 101, 157, 72, 87, 56, 81, 52, 203, 124, 183, 186, 0, 153, 224, 40, 70, 98, 34, 142, 153, 102, 101, 2, 27, 3, 61, 83, 77, 66], port: 6392, idle_timeout_ms: 4 };181;[80, 03, aa, 62, 80, 5a, 0c, 68, 01, 40, a0, 56, e3, 18, 13, a3, 2c, 63, 92, c6, 2d, 65, 02, a9, 84, 27, d1, 10, dc, 34, 3d, 67, ce, 80, 9e, a5, d8, 66, 96, d8, 10, f4, 29, 38, 9a, 4d, a3, 7c, fa, 4d, 47, 68, 23, ba, 07, d3, 25, 58, 8a, 4d, 0d, f4, 28, 1c, f6, 4f, f0, 8f, 42, e7, d3, a8, 96, 23, 2d, 68, ec, 1b, 93, 88, 04, 3d, 2e, 64, c8, 48, e9, 20, c1, 0b, b5, c4, bf, ab, 22, 11, 6e, e0, d2, f5, 0c, d7, 91, a1, 66, 25, 49, 4c, a4, 92, f0, f7, b0, 0d, 58, 10, 7b, e5, 83, ba, 4c, e9, 77, 25, 46, ee, 97, e4, 81, c9, d7, 76, 1e, 79, 36, 02, da, ed, 34, 3c, 69, bd, 65, 9d, 48, 57, 38, 51, 34, cb, 7c, b7, ba, 00, 99, e0, 28, 46, 62, 22, 8e, 99, 66, 65, 02, 1b, 03, 3d, 53, 4d, 42, 18, f8, 04, 00] +udp_open c0000000000000964033030cf99b0184196bcdf3518fa46d290b400b00 UdpSessionOpen { session_id: 150, flags: 51, address_type: Domain, host: [249, 155, 1, 132, 25, 107, 205, 243, 81, 143, 164, 109], port: 10507, idle_timeout_ms: 11 };29;[40, 96, 33, 03, 0c, f9, 9b, 01, 84, 19, 6b, cd, f3, 51, 8f, a4, 6d, 29, 0b, 0b, 00] +udp_open 4065407a0140f98cb42222b004914a03fdbca88332be6795c624b20fb51694429e5de0f7ae0bebcee4a4967aa901a9a2f4eb1c5d14c7b68c45cd7283609a7d0c7cd04d32d3baece982cce65676804c011b42c9d2b584d84567b463fcaf833ee00daa38e9db031976e6af3a67593e2bd8a24572d5bf3cab43411d2c5d5bb595a2f6a28c9ddeca6952d4b2749a3bdd05a3ddd06eb8dca75a12a314f36a6f3d5e7983526bcbec3d4a384bd6276f267dd48e6722b7a1c950812a3f9b5bd5c1586210be1a77b69e21e13f12f88c683c35ebd628b126a8f7223250838fee20d454c848a54a247f63b706c5e5b2eb9de47a86b25f51eea1601bcdd2ff584937557a0f0bdd6a9adc9e98c00000000000000dd3c17ebee2d6861cb5f1eff94b UdpSessionOpen { session_id: 101, flags: 122, address_type: Ipv4, host: [140, 180, 34, 34, 176, 4, 145, 74, 3, 253, 188, 168, 131, 50, 190, 103, 149, 198, 36, 178, 15, 181, 22, 148, 66, 158, 93, 224, 247, 174, 11, 235, 206, 228, 164, 150, 122, 169, 1, 169, 162, 244, 235, 28, 93, 20, 199, 182, 140, 69, 205, 114, 131, 96, 154, 125, 12, 124, 208, 77, 50, 211, 186, 236, 233, 130, 204, 230, 86, 118, 128, 76, 1, 27, 66, 201, 210, 181, 132, 216, 69, 103, 180, 99, 252, 175, 131, 62, 224, 13, 170, 56, 233, 219, 3, 25, 118, 230, 175, 58, 103, 89, 62, 43, 216, 162, 69, 114, 213, 191, 60, 171, 67, 65, 29, 44, 93, 91, 181, 149, 162, 246, 162, 140, 157, 222, 202, 105, 82, 212, 178, 116, 154, 59, 221, 5, 163, 221, 208, 110, 184, 220, 167, 90, 18, 163, 20, 243, 106, 111, 61, 94, 121, 131, 82, 107, 203, 236, 61, 74, 56, 75, 214, 39, 111, 38, 125, 212, 142, 103, 34, 183, 161, 201, 80, 129, 42, 63, 155, 91, 213, 193, 88, 98, 16, 190, 26, 119, 182, 158, 33, 225, 63, 18, 248, 140, 104, 60, 53, 235, 214, 40, 177, 38, 168, 247, 34, 50, 80, 131, 143, 238, 32, 212, 84, 200, 72, 165, 74, 36, 127, 99, 183, 6, 197, 229, 178, 235, 157, 228, 122, 134, 178, 95, 81, 238, 161, 96, 27, 205, 210, 255, 88, 73, 55, 85, 122, 15, 11], port: 56682, idle_timeout_ms: 450666136 };283;[40, 65, 40, 7a, 01, 40, f9, 8c, b4, 22, 22, b0, 04, 91, 4a, 03, fd, bc, a8, 83, 32, be, 67, 95, c6, 24, b2, 0f, b5, 16, 94, 42, 9e, 5d, e0, f7, ae, 0b, eb, ce, e4, a4, 96, 7a, a9, 01, a9, a2, f4, eb, 1c, 5d, 14, c7, b6, 8c, 45, cd, 72, 83, 60, 9a, 7d, 0c, 7c, d0, 4d, 32, d3, ba, ec, e9, 82, cc, e6, 56, 76, 80, 4c, 01, 1b, 42, c9, d2, b5, 84, d8, 45, 67, b4, 63, fc, af, 83, 3e, e0, 0d, aa, 38, e9, db, 03, 19, 76, e6, af, 3a, 67, 59, 3e, 2b, d8, a2, 45, 72, d5, bf, 3c, ab, 43, 41, 1d, 2c, 5d, 5b, b5, 95, a2, f6, a2, 8c, 9d, de, ca, 69, 52, d4, b2, 74, 9a, 3b, dd, 05, a3, dd, d0, 6e, b8, dc, a7, 5a, 12, a3, 14, f3, 6a, 6f, 3d, 5e, 79, 83, 52, 6b, cb, ec, 3d, 4a, 38, 4b, d6, 27, 6f, 26, 7d, d4, 8e, 67, 22, b7, a1, c9, 50, 81, 2a, 3f, 9b, 5b, d5, c1, 58, 62, 10, be, 1a, 77, b6, 9e, 21, e1, 3f, 12, f8, 8c, 68, 3c, 35, eb, d6, 28, b1, 26, a8, f7, 22, 32, 50, 83, 8f, ee, 20, d4, 54, c8, 48, a5, 4a, 24, 7f, 63, b7, 06, c5, e5, b2, eb, 9d, e4, 7a, 86, b2, 5f, 51, ee, a1, 60, 1b, cd, d2, ff, 58, 49, 37, 55, 7a, 0f, 0b, dd, 6a, 9a, dc, 9e, 98, 00] +udp_open c00000000000044e0201800000a7c3391c9ea89620c8cfc2d9610f3774d0d0a148255fe78bc99fd1ae8e2af1a3a76db136553555727633b01b6fc69450773d3d508b1265e7ce1ac0561d230b5d0e772165c0c2b1199dbea518efa1fac41d0bc038bcd696faeca382038dc0c769fea470b8a1a83cb6665204fdf5ee44325734fc3b061bf27eeffc5a7803004413d4dae881462654e631960fa6c6828518b3ff37f189159b4f0164045a4afaf92fc31d8b076b7035123d76c0000000001107a080000000 UdpSessionOpen { session_id: 1102, flags: 2, address_type: Ipv4, host: [195, 57, 28, 158, 168, 150, 32, 200, 207, 194, 217, 97, 15, 55, 116, 208, 208, 161, 72, 37, 95, 231, 139, 201, 159, 209, 174, 142, 42, 241, 163, 167, 109, 177, 54, 85, 53, 85, 114, 118, 51, 176, 27, 111, 198, 148, 80, 119, 61, 61, 80, 139, 18, 101, 231, 206, 26, 192, 86, 29, 35, 11, 93, 14, 119, 33, 101, 192, 194, 177, 25, 157, 190, 165, 24, 239, 161, 250, 196, 29, 11, 192, 56, 188, 214, 150, 250, 236, 163, 130, 3, 141, 192, 199, 105, 254, 164, 112, 184, 161, 168, 60, 182, 102, 82, 4, 253, 245, 238, 68, 50, 87, 52, 252, 59, 6, 27, 242, 126, 239, 252, 90, 120, 3, 0, 68, 19, 212, 218, 232, 129, 70, 38, 84, 230, 49, 150, 15, 166, 198, 130, 133, 24, 179, 255, 55, 241, 137, 21, 155, 79, 1, 100, 4, 90, 74, 250, 249, 47, 195, 29, 139, 7, 107, 112, 53, 18], port: 15734, idle_timeout_ms: 1116064 };195;[44, 4e, 02, 01, 40, a7, c3, 39, 1c, 9e, a8, 96, 20, c8, cf, c2, d9, 61, 0f, 37, 74, d0, d0, a1, 48, 25, 5f, e7, 8b, c9, 9f, d1, ae, 8e, 2a, f1, a3, a7, 6d, b1, 36, 55, 35, 55, 72, 76, 33, b0, 1b, 6f, c6, 94, 50, 77, 3d, 3d, 50, 8b, 12, 65, e7, ce, 1a, c0, 56, 1d, 23, 0b, 5d, 0e, 77, 21, 65, c0, c2, b1, 19, 9d, be, a5, 18, ef, a1, fa, c4, 1d, 0b, c0, 38, bc, d6, 96, fa, ec, a3, 82, 03, 8d, c0, c7, 69, fe, a4, 70, b8, a1, a8, 3c, b6, 66, 52, 04, fd, f5, ee, 44, 32, 57, 34, fc, 3b, 06, 1b, f2, 7e, ef, fc, 5a, 78, 03, 00, 44, 13, d4, da, e8, 81, 46, 26, 54, e6, 31, 96, 0f, a6, c6, 82, 85, 18, b3, ff, 37, f1, 89, 15, 9b, 4f, 01, 64, 04, 5a, 4a, fa, f9, 2f, c3, 1d, 8b, 07, 6b, 70, 35, 12, 3d, 76, 80, 11, 07, a0, 00] +udp_open 800abff6f8f7156569b07f3a0140d729334e4560eb49aa86b363bbcea351e3de4eaaaead97693c67e77613c2523935284003f4a373b6d1a21d4a1a244ca9823b175c5066eb0a599efba6a02198f5fa0db8ce5a9297a3b4e7fa50bb33b58170b8f57c67540289bd894bb1e11cb95cc944970c021bf995a2899ad2c763556ac5ebf64458c6963cb8db143e1f668dd93b899083399c24f5c489732a85cb91a3420354197b334c7c43d50c4b2d358d19539738f05ecd4a815a4944648442fc13edf8bce409724a6398ab610fc6d642bf83830a3a53545848140118b2e12130ea51a78da900445ce52d28421c0780d71bff025c42 UdpSessionOpen { session_id: 704502, flags: 4104773110680551226, address_type: Ipv4, host: [41, 51, 78, 69, 96, 235, 73, 170, 134, 179, 99, 187, 206, 163, 81, 227, 222, 78, 170, 174, 173, 151, 105, 60, 103, 231, 118, 19, 194, 82, 57, 53, 40, 64, 3, 244, 163, 115, 182, 209, 162, 29, 74, 26, 36, 76, 169, 130, 59, 23, 92, 80, 102, 235, 10, 89, 158, 251, 166, 160, 33, 152, 245, 250, 13, 184, 206, 90, 146, 151, 163, 180, 231, 250, 80, 187, 51, 181, 129, 112, 184, 245, 124, 103, 84, 2, 137, 189, 137, 75, 177, 225, 28, 185, 92, 201, 68, 151, 12, 2, 27, 249, 149, 162, 137, 154, 210, 199, 99, 85, 106, 197, 235, 246, 68, 88, 198, 150, 60, 184, 219, 20, 62, 31, 102, 141, 217, 59, 137, 144, 131, 57, 156, 36, 245, 196, 137, 115, 42, 133, 203, 145, 163, 66, 3, 84, 25, 123, 51, 76, 124, 67, 213, 12, 75, 45, 53, 141, 25, 83, 151, 56, 240, 94, 205, 74, 129, 90, 73, 68, 100, 132, 66, 252, 19, 237, 248, 188, 228, 9, 114, 74, 99, 152, 171, 97, 15, 198, 214, 66, 191, 131, 131, 10, 58, 83, 84, 88, 72, 20, 1, 24, 178, 225, 33, 48, 234, 81, 167, 141, 169, 0, 68, 92, 229], port: 11560, idle_timeout_ms: 540 };242;[80, 0a, bf, f6, f8, f7, 15, 65, 69, b0, 7f, 3a, 01, 40, d7, 29, 33, 4e, 45, 60, eb, 49, aa, 86, b3, 63, bb, ce, a3, 51, e3, de, 4e, aa, ae, ad, 97, 69, 3c, 67, e7, 76, 13, c2, 52, 39, 35, 28, 40, 03, f4, a3, 73, b6, d1, a2, 1d, 4a, 1a, 24, 4c, a9, 82, 3b, 17, 5c, 50, 66, eb, 0a, 59, 9e, fb, a6, a0, 21, 98, f5, fa, 0d, b8, ce, 5a, 92, 97, a3, b4, e7, fa, 50, bb, 33, b5, 81, 70, b8, f5, 7c, 67, 54, 02, 89, bd, 89, 4b, b1, e1, 1c, b9, 5c, c9, 44, 97, 0c, 02, 1b, f9, 95, a2, 89, 9a, d2, c7, 63, 55, 6a, c5, eb, f6, 44, 58, c6, 96, 3c, b8, db, 14, 3e, 1f, 66, 8d, d9, 3b, 89, 90, 83, 39, 9c, 24, f5, c4, 89, 73, 2a, 85, cb, 91, a3, 42, 03, 54, 19, 7b, 33, 4c, 7c, 43, d5, 0c, 4b, 2d, 35, 8d, 19, 53, 97, 38, f0, 5e, cd, 4a, 81, 5a, 49, 44, 64, 84, 42, fc, 13, ed, f8, bc, e4, 09, 72, 4a, 63, 98, ab, 61, 0f, c6, d6, 42, bf, 83, 83, 0a, 3a, 53, 54, 58, 48, 14, 01, 18, b2, e1, 21, 30, ea, 51, a7, 8d, a9, 00, 44, 5c, e5, 2d, 28, 42, 1c, 00] +udp_open 800005c15bfd01c00000000000002f0fa5b707567d2a0a795c378b259031cd61d39ab266740f3e9430b0cb0083e0b0e8047ec555f447d59714d0967bd4abfde6c0005ed954a32eb980000009332f51f977f64fe7d0 UdpSessionOpen { session_id: 1473, flags: 7165, address_type: Ipv4, host: [15, 165, 183, 7, 86, 125, 42, 10, 121, 92, 55, 139, 37, 144, 49, 205, 97, 211, 154, 178, 102, 116, 15, 62, 148, 48, 176, 203, 0, 131, 224, 176, 232, 4, 126, 197, 85, 244, 71, 213, 151, 20, 208, 150, 123, 212, 171], port: 64998, idle_timeout_ms: 104287520894649 };85;[45, c1, 5b, fd, 01, 2f, 0f, a5, b7, 07, 56, 7d, 2a, 0a, 79, 5c, 37, 8b, 25, 90, 31, cd, 61, d3, 9a, b2, 66, 74, 0f, 3e, 94, 30, b0, cb, 00, 83, e0, b0, e8, 04, 7e, c5, 55, f4, 47, d5, 97, 14, d0, 96, 7b, d4, ab, fd, e6, c0, 00, 5e, d9, 54, a3, 2e, b9, 00] +udp_open 80004e76c0000b930e088f150440d6acf727bbdb5e0376d07e023607864be216bef358ac92c0b69ce3f661657f634f1209a45444b49e45e446dbd2d5c9abc875aa400999121bdc24d163192c5ea737338577f54d83f82708bc56c059def138dcf8cb04fa788b61abfaf6f41a69fcc99d57bca3513b6017ce433bdc126d77c9a2945f2808e4239cb9c248a97f31a81d243b9b44816891edcfb592c6034d5f76dc7dcf6119aa92367bf7e967c34f5d2434ab88f817a4ffc943fc9e1c6251721b8050462129af36f2e07dd488d01bd24180d8dcbdf45fb976f5e295d2274672c611c1b3120bf23d3b40004000 UdpSessionOpen { session_id: 20086, flags: 12726223539989, address_type: Ipv6, host: [172, 247, 39, 187, 219, 94, 3, 118, 208, 126, 2, 54, 7, 134, 75, 226, 22, 190, 243, 88, 172, 146, 192, 182, 156, 227, 246, 97, 101, 127, 99, 79, 18, 9, 164, 84, 68, 180, 158, 69, 228, 70, 219, 210, 213, 201, 171, 200, 117, 170, 64, 9, 153, 18, 27, 220, 36, 209, 99, 25, 44, 94, 167, 55, 51, 133, 119, 245, 77, 131, 248, 39, 8, 188, 86, 192, 89, 222, 241, 56, 220, 248, 203, 4, 250, 120, 139, 97, 171, 250, 246, 244, 26, 105, 252, 201, 157, 87, 188, 163, 81, 59, 96, 23, 206, 67, 59, 220, 18, 109, 119, 201, 162, 148, 95, 40, 8, 228, 35, 156, 185, 194, 72, 169, 127, 49, 168, 29, 36, 59, 155, 68, 129, 104, 145, 237, 207, 181, 146, 198, 3, 77, 95, 118, 220, 125, 207, 97, 25, 170, 146, 54, 123, 247, 233, 103, 195, 79, 93, 36, 52, 171, 136, 248, 23, 164, 255, 201, 67, 252, 158, 28, 98, 81, 114, 27, 128, 80, 70, 33, 41, 175, 54, 242, 224, 125, 212, 136, 208, 27, 210, 65, 128, 216, 220, 189, 244, 95, 185, 118, 245, 226, 149, 210, 39, 70, 114, 198, 17, 193, 179, 18, 11, 242], port: 15675, idle_timeout_ms: 0 };235;[80, 00, 4e, 76, c0, 00, 0b, 93, 0e, 08, 8f, 15, 04, 40, d6, ac, f7, 27, bb, db, 5e, 03, 76, d0, 7e, 02, 36, 07, 86, 4b, e2, 16, be, f3, 58, ac, 92, c0, b6, 9c, e3, f6, 61, 65, 7f, 63, 4f, 12, 09, a4, 54, 44, b4, 9e, 45, e4, 46, db, d2, d5, c9, ab, c8, 75, aa, 40, 09, 99, 12, 1b, dc, 24, d1, 63, 19, 2c, 5e, a7, 37, 33, 85, 77, f5, 4d, 83, f8, 27, 08, bc, 56, c0, 59, de, f1, 38, dc, f8, cb, 04, fa, 78, 8b, 61, ab, fa, f6, f4, 1a, 69, fc, c9, 9d, 57, bc, a3, 51, 3b, 60, 17, ce, 43, 3b, dc, 12, 6d, 77, c9, a2, 94, 5f, 28, 08, e4, 23, 9c, b9, c2, 48, a9, 7f, 31, a8, 1d, 24, 3b, 9b, 44, 81, 68, 91, ed, cf, b5, 92, c6, 03, 4d, 5f, 76, dc, 7d, cf, 61, 19, aa, 92, 36, 7b, f7, e9, 67, c3, 4f, 5d, 24, 34, ab, 88, f8, 17, a4, ff, c9, 43, fc, 9e, 1c, 62, 51, 72, 1b, 80, 50, 46, 21, 29, af, 36, f2, e0, 7d, d4, 88, d0, 1b, d2, 41, 80, d8, dc, bd, f4, 5f, b9, 76, f5, e2, 95, d2, 27, 46, 72, c6, 11, c1, b3, 12, 0b, f2, 3d, 3b, 00, 00] +udp_open 805aa7c0c00000d9e0cdf5b3f3800000ee4e758e340684b0c4979287ef275d84415822dc61889a00f539748047b6f9a0a512374dae6cf7213945db555ee05050034ee48d9ef69e34360a324657c7338a3a2367b5d8328b8fa72fff07cfdaa828c07825ef1cc0fa520aecbda6410afa1a323a48501dd7e0c8d28e178535aaf48a85560687a4f65942a27b093ab675d7133f9bb9bc79c94bbeaa442d3a16d1ea57b8bae1fff1b3289d16930b3b2b2b64ca400db34e00d4ce3803c498bed66ab0d130ced474aa8f76538489ceed2a223c8b46dfb9d8e5082d93174f77770ff40a2e806b16ae3d7f2fc58a873f768ba7118d40748f122bded1c60f7969a2d7518c3a318010585d0394940d reject +udp_open 8003f2aac00000006b38c0f60440b321b123474db9fb7c1cf31dda9f5fa5e9972bbaa06266aecdff95797c7fab47df8753a4daddae4a43e6c0c481e92f681ccbd24c409fd54007d6fb2d5831583e1a1610c41920a583dd0c7e12c3a4683d6a6f7077a1b9d480d51dc2007e2f93b740475f5e1dc7afd922223668e63349b82fe32929f7945511103d22037738841cfc60dc6842f9f517a522178f469a0258d2664a94309a9cc9b9e15abce203ffec5fa31ff8f188dd23e87e3d8c32ed5569aec42634fbebfd719ad1c4adbf8bc000000000000000 UdpSessionOpen { session_id: 258730, flags: 1798881526, address_type: Ipv6, host: [33, 177, 35, 71, 77, 185, 251, 124, 28, 243, 29, 218, 159, 95, 165, 233, 151, 43, 186, 160, 98, 102, 174, 205, 255, 149, 121, 124, 127, 171, 71, 223, 135, 83, 164, 218, 221, 174, 74, 67, 230, 192, 196, 129, 233, 47, 104, 28, 203, 210, 76, 64, 159, 213, 64, 7, 214, 251, 45, 88, 49, 88, 62, 26, 22, 16, 196, 25, 32, 165, 131, 221, 12, 126, 18, 195, 164, 104, 61, 106, 111, 112, 119, 161, 185, 212, 128, 213, 29, 194, 0, 126, 47, 147, 183, 64, 71, 95, 94, 29, 199, 175, 217, 34, 34, 54, 104, 230, 51, 73, 184, 47, 227, 41, 41, 247, 148, 85, 17, 16, 61, 34, 3, 119, 56, 132, 28, 252, 96, 220, 104, 66, 249, 245, 23, 165, 34, 23, 143, 70, 154, 2, 88, 210, 102, 74, 148, 48, 154, 156, 201, 185, 225, 90, 188, 226, 3, 255, 236, 95, 163, 31, 248, 241, 136, 221, 35, 232, 126, 61, 140, 50, 237, 85, 105, 174, 196, 38, 52], port: 64491, idle_timeout_ms: 4427490134420471691 };212;[80, 03, f2, aa, c0, 00, 00, 00, 6b, 38, c0, f6, 04, 40, b3, 21, b1, 23, 47, 4d, b9, fb, 7c, 1c, f3, 1d, da, 9f, 5f, a5, e9, 97, 2b, ba, a0, 62, 66, ae, cd, ff, 95, 79, 7c, 7f, ab, 47, df, 87, 53, a4, da, dd, ae, 4a, 43, e6, c0, c4, 81, e9, 2f, 68, 1c, cb, d2, 4c, 40, 9f, d5, 40, 07, d6, fb, 2d, 58, 31, 58, 3e, 1a, 16, 10, c4, 19, 20, a5, 83, dd, 0c, 7e, 12, c3, a4, 68, 3d, 6a, 6f, 70, 77, a1, b9, d4, 80, d5, 1d, c2, 00, 7e, 2f, 93, b7, 40, 47, 5f, 5e, 1d, c7, af, d9, 22, 22, 36, 68, e6, 33, 49, b8, 2f, e3, 29, 29, f7, 94, 55, 11, 10, 3d, 22, 03, 77, 38, 84, 1c, fc, 60, dc, 68, 42, f9, f5, 17, a5, 22, 17, 8f, 46, 9a, 02, 58, d2, 66, 4a, 94, 30, 9a, 9c, c9, b9, e1, 5a, bc, e2, 03, ff, ec, 5f, a3, 1f, f8, f1, 88, dd, 23, e8, 7e, 3d, 8c, 32, ed, 55, 69, ae, c4, 26, 34, fb, eb, fd, 71, 9a, d1, c4, ad, bf, 8b, 00] +udp_open 8c0528bf1703c000000000000084c58680005cf1ef71b34952d9aca5329eece17e79960616ef8ba68daba71ba16c12be32655ac1c5fbaeb5694c754874ad11b34f9f8f40e14b6d0762b68b9d331415c9276294f61190e25df9f0917aa751066713a8cf117ba9abbe9cd8eb649bb442af495ed867865b8076aa5eb7016c95a878ad95385fbe3a9f2684bf6f5ac6f7b5c1afdc5cbd80038f050087aee68dff197e UdpSessionOpen { session_id: 201664703, flags: 23, address_type: Domain, host: [197, 134, 128, 0, 92, 241, 239, 113, 179, 73, 82, 217, 172, 165, 50, 158, 236, 225, 126, 121, 150, 6, 22, 239, 139, 166, 141, 171, 167, 27, 161, 108, 18, 190, 50, 101, 90, 193, 197, 251, 174, 181, 105, 76, 117, 72, 116, 173, 17, 179, 79, 159, 143, 64, 225, 75, 109, 7, 98, 182, 139, 157, 51, 20, 21, 201, 39, 98, 148, 246, 17, 144, 226, 93, 249, 240, 145, 122, 167, 81, 6, 103, 19, 168, 207, 17, 123, 169, 171, 190, 156, 216, 235, 100, 155, 180, 66, 175, 73, 94, 216, 103, 134, 91, 128, 118, 170, 94, 183, 1, 108, 149, 168, 120, 173, 149, 56, 95, 190, 58, 159, 38, 132, 191, 111, 90, 198, 247, 181, 193, 175, 220], port: 23741, idle_timeout_ms: 233221 };153;[8c, 05, 28, bf, 17, 03, 40, 84, c5, 86, 80, 00, 5c, f1, ef, 71, b3, 49, 52, d9, ac, a5, 32, 9e, ec, e1, 7e, 79, 96, 06, 16, ef, 8b, a6, 8d, ab, a7, 1b, a1, 6c, 12, be, 32, 65, 5a, c1, c5, fb, ae, b5, 69, 4c, 75, 48, 74, ad, 11, b3, 4f, 9f, 8f, 40, e1, 4b, 6d, 07, 62, b6, 8b, 9d, 33, 14, 15, c9, 27, 62, 94, f6, 11, 90, e2, 5d, f9, f0, 91, 7a, a7, 51, 06, 67, 13, a8, cf, 11, 7b, a9, ab, be, 9c, d8, eb, 64, 9b, b4, 42, af, 49, 5e, d8, 67, 86, 5b, 80, 76, aa, 5e, b7, 01, 6c, 95, a8, 78, ad, 95, 38, 5f, be, 3a, 9f, 26, 84, bf, 6f, 5a, c6, f7, b5, c1, af, dc, 5c, bd, 80, 03, 8f, 05, 00] +udp_open 4c88cb00460c54b69fd00480000070fcb3e513a7958eeee871ecedf36d4451cbcda44566dea75fe7c75c826f3717a9a0c3af65841ee774fc1c6e3819801cb35986856dcb5df2aa0031d48a3bde47d48b308a214b54c32ec486340dc27bd68557b537ccb9b9b5c85b8caa5c21058b0ab1882bf0cb1a1190cee27021c3903ab1c7c6c0027e093e00e85300 UdpSessionOpen { session_id: 3208, flags: 792710553192013776, address_type: Ipv6, host: [252, 179, 229, 19, 167, 149, 142, 238, 232, 113, 236, 237, 243, 109, 68, 81, 203, 205, 164, 69, 102, 222, 167, 95, 231, 199, 92, 130, 111, 55, 23, 169, 160, 195, 175, 101, 132, 30, 231, 116, 252, 28, 110, 56, 25, 128, 28, 179, 89, 134, 133, 109, 203, 93, 242, 170, 0, 49, 212, 138, 59, 222, 71, 212, 139, 48, 138, 33, 75, 84, 195, 46, 196, 134, 52, 13, 194, 123, 214, 133, 87, 181, 55, 204, 185, 185, 181, 200, 91, 140, 170, 92, 33, 5, 139, 10, 177, 136, 43, 240, 203, 26, 17, 144, 206, 226, 112, 33, 195, 144, 58, 177], port: 51142, idle_timeout_ms: 701528113473619 };138;[4c, 88, cb, 00, 46, 0c, 54, b6, 9f, d0, 04, 40, 70, fc, b3, e5, 13, a7, 95, 8e, ee, e8, 71, ec, ed, f3, 6d, 44, 51, cb, cd, a4, 45, 66, de, a7, 5f, e7, c7, 5c, 82, 6f, 37, 17, a9, a0, c3, af, 65, 84, 1e, e7, 74, fc, 1c, 6e, 38, 19, 80, 1c, b3, 59, 86, 85, 6d, cb, 5d, f2, aa, 00, 31, d4, 8a, 3b, de, 47, d4, 8b, 30, 8a, 21, 4b, 54, c3, 2e, c4, 86, 34, 0d, c2, 7b, d6, 85, 57, b5, 37, cc, b9, b9, b5, c8, 5b, 8c, aa, 5c, 21, 05, 8b, 0a, b1, 88, 2b, f0, cb, 1a, 11, 90, ce, e2, 70, 21, c3, 90, 3a, b1, c7, c6, c0, 02, 7e, 09, 3e, 00, e8, 53, 00] +udp_open 80009841c100931727f6e96701c000000000000086759237bd260567ebb3f4aa11f5c30cd0243f7698c96901d288149df5dff01ab463725c8bc7104a19a585c9c167746caec4884a5bef4aa302bf4602cbc2aa4ff9343c42c90adbcd9a842c315f10e0609a37a614165588282d1e87107abda01667daa9d7e6130e66744597508620b5517dffdec38b222fab9166825de8817d1f8d4dc01ecb2a527a2a800000074000 UdpSessionOpen { session_id: 38977, flags: 72219321701951847, address_type: Ipv4, host: [117, 146, 55, 189, 38, 5, 103, 235, 179, 244, 170, 17, 245, 195, 12, 208, 36, 63, 118, 152, 201, 105, 1, 210, 136, 20, 157, 245, 223, 240, 26, 180, 99, 114, 92, 139, 199, 16, 74, 25, 165, 133, 201, 193, 103, 116, 108, 174, 196, 136, 74, 91, 239, 74, 163, 2, 191, 70, 2, 203, 194, 170, 79, 249, 52, 60, 66, 201, 10, 219, 205, 154, 132, 44, 49, 95, 16, 224, 96, 154, 55, 166, 20, 22, 85, 136, 40, 45, 30, 135, 16, 122, 189, 160, 22, 103, 218, 169, 215, 230, 19, 14, 102, 116, 69, 151, 80, 134, 32, 181, 81, 125, 255, 222, 195, 139, 34, 47, 171, 145, 102, 130, 93, 232, 129, 125, 31, 141, 77, 192, 30, 203, 42, 82], port: 31274, idle_timeout_ms: 7 };163;[80, 00, 98, 41, c1, 00, 93, 17, 27, f6, e9, 67, 01, 40, 86, 75, 92, 37, bd, 26, 05, 67, eb, b3, f4, aa, 11, f5, c3, 0c, d0, 24, 3f, 76, 98, c9, 69, 01, d2, 88, 14, 9d, f5, df, f0, 1a, b4, 63, 72, 5c, 8b, c7, 10, 4a, 19, a5, 85, c9, c1, 67, 74, 6c, ae, c4, 88, 4a, 5b, ef, 4a, a3, 02, bf, 46, 02, cb, c2, aa, 4f, f9, 34, 3c, 42, c9, 0a, db, cd, 9a, 84, 2c, 31, 5f, 10, e0, 60, 9a, 37, a6, 14, 16, 55, 88, 28, 2d, 1e, 87, 10, 7a, bd, a0, 16, 67, da, a9, d7, e6, 13, 0e, 66, 74, 45, 97, 50, 86, 20, b5, 51, 7d, ff, de, c3, 8b, 22, 2f, ab, 91, 66, 82, 5d, e8, 81, 7d, 1f, 8d, 4d, c0, 1e, cb, 2a, 52, 7a, 2a, 07, 00] +udp_open 4251c00000bc9bb00d54048000009f33478fa0359c1d0b78af63948b31fb1f895edb2fa68e546183fbe1bf563d4efa3dd8d46336edada9a8b48e8fb916a769e012389c6abcf351860f57bf8bb6f9be9d2751f01e3140b8e464eada79d13beffbe804535d824ee6ad6179bb16ad67ffbb3a877716561c413567f6c7c5839899a7bd981afc447701fdee3e606dbb5ccbae408a2c2fc3c97c8f2a89ce5ceedb80c394afa5c012337271e835f5f235a9e4751200021554a4e0ce30 UdpSessionOpen { session_id: 593, flags: 810065857876, address_type: Ipv6, host: [51, 71, 143, 160, 53, 156, 29, 11, 120, 175, 99, 148, 139, 49, 251, 31, 137, 94, 219, 47, 166, 142, 84, 97, 131, 251, 225, 191, 86, 61, 78, 250, 61, 216, 212, 99, 54, 237, 173, 169, 168, 180, 142, 143, 185, 22, 167, 105, 224, 18, 56, 156, 106, 188, 243, 81, 134, 15, 87, 191, 139, 182, 249, 190, 157, 39, 81, 240, 30, 49, 64, 184, 228, 100, 234, 218, 121, 209, 59, 239, 251, 232, 4, 83, 93, 130, 78, 230, 173, 97, 121, 187, 22, 173, 103, 255, 187, 58, 135, 119, 22, 86, 28, 65, 53, 103, 246, 199, 197, 131, 152, 153, 167, 189, 152, 26, 252, 68, 119, 1, 253, 238, 62, 96, 109, 187, 92, 203, 174, 64, 138, 44, 47, 195, 201, 124, 143, 42, 137, 206, 92, 238, 219, 128, 195, 148, 175, 165, 192, 18, 51, 114, 113, 232, 53, 245, 242, 53, 169], port: 58485, idle_timeout_ms: 18 };178;[42, 51, c0, 00, 00, bc, 9b, b0, 0d, 54, 04, 40, 9f, 33, 47, 8f, a0, 35, 9c, 1d, 0b, 78, af, 63, 94, 8b, 31, fb, 1f, 89, 5e, db, 2f, a6, 8e, 54, 61, 83, fb, e1, bf, 56, 3d, 4e, fa, 3d, d8, d4, 63, 36, ed, ad, a9, a8, b4, 8e, 8f, b9, 16, a7, 69, e0, 12, 38, 9c, 6a, bc, f3, 51, 86, 0f, 57, bf, 8b, b6, f9, be, 9d, 27, 51, f0, 1e, 31, 40, b8, e4, 64, ea, da, 79, d1, 3b, ef, fb, e8, 04, 53, 5d, 82, 4e, e6, ad, 61, 79, bb, 16, ad, 67, ff, bb, 3a, 87, 77, 16, 56, 1c, 41, 35, 67, f6, c7, c5, 83, 98, 99, a7, bd, 98, 1a, fc, 44, 77, 01, fd, ee, 3e, 60, 6d, bb, 5c, cb, ae, 40, 8a, 2c, 2f, c3, c9, 7c, 8f, 2a, 89, ce, 5c, ee, db, 80, c3, 94, af, a5, c0, 12, 33, 72, 71, e8, 35, f5, f2, 35, a9, e4, 75, 12, 00] +udp_open c000000000001f788004ac1b01c00000000000010088055df69466393f1abcb00f9a6aca4f86ae3bfe1688e86cc673b43e073896b6f4c9ecb8465f1250f594efa764745e3f4066e8754a8161e4d10aeea953b70b11f7949fd6e59408db40501fa2fc2931920a2a3eaabad2753b51b8c9dd4677d18e5225183b7ee73819210e6f363b7d68069e39c239d56c8b8a5ac6f43ab634f0c8e26a34cb78a52eee796349bfaa605135525380b69e4b86f272b82e8f30211bc21960da404095f797286858f2040506161a209f36f158b7130e767e3717c7c9beeb1c51f423f74eefbc2d72e1cf3fa7540f4c36c7818d4990f0f6c5fe2a11018a54273367259cabd748f4cb402df9e3f6815eae998afe2ef0b045896cd95b78c5bb55c000000067f87dc18000000c459296cd22cf76deb6301713 reject +udp_open c000000078d48f768ed5416d0440807b873d4d2e907f5b51f4d224a56d38fa72ce101067c858da17619e8b8fc3a6c4d9a4b99a3c35c58e25e491617cca10a3f09df20d3e10a8f894f9956d5a50079ed85bc0d412e9e4f9ed10b81ad400968df6fecb8512466472b51ae0925032a1f71c817da01b920272eea890ce6f05cd0a159e19af19e181ce2b12856445688afe4373c0001ca848141adb4000 UdpSessionOpen { session_id: 2027196278, flags: 248856941, address_type: Ipv6, host: [123, 135, 61, 77, 46, 144, 127, 91, 81, 244, 210, 36, 165, 109, 56, 250, 114, 206, 16, 16, 103, 200, 88, 218, 23, 97, 158, 139, 143, 195, 166, 196, 217, 164, 185, 154, 60, 53, 197, 142, 37, 228, 145, 97, 124, 202, 16, 163, 240, 157, 242, 13, 62, 16, 168, 248, 148, 249, 149, 109, 90, 80, 7, 158, 216, 91, 192, 212, 18, 233, 228, 249, 237, 16, 184, 26, 212, 0, 150, 141, 246, 254, 203, 133, 18, 70, 100, 114, 181, 26, 224, 146, 80, 50, 161, 247, 28, 129, 125, 160, 27, 146, 2, 114, 238, 168, 144, 206, 111, 5, 205, 10, 21, 158, 25, 175, 25, 225, 129, 206, 43, 18, 133, 100, 69, 104, 138, 254], port: 17267, idle_timeout_ms: 31509089360603 };155;[c0, 00, 00, 00, 78, d4, 8f, 76, 8e, d5, 41, 6d, 04, 40, 80, 7b, 87, 3d, 4d, 2e, 90, 7f, 5b, 51, f4, d2, 24, a5, 6d, 38, fa, 72, ce, 10, 10, 67, c8, 58, da, 17, 61, 9e, 8b, 8f, c3, a6, c4, d9, a4, b9, 9a, 3c, 35, c5, 8e, 25, e4, 91, 61, 7c, ca, 10, a3, f0, 9d, f2, 0d, 3e, 10, a8, f8, 94, f9, 95, 6d, 5a, 50, 07, 9e, d8, 5b, c0, d4, 12, e9, e4, f9, ed, 10, b8, 1a, d4, 00, 96, 8d, f6, fe, cb, 85, 12, 46, 64, 72, b5, 1a, e0, 92, 50, 32, a1, f7, 1c, 81, 7d, a0, 1b, 92, 02, 72, ee, a8, 90, ce, 6f, 05, cd, 0a, 15, 9e, 19, af, 19, e1, 81, ce, 2b, 12, 85, 64, 45, 68, 8a, fe, 43, 73, c0, 00, 1c, a8, 48, 14, 1a, db, 00] +udp_open 00c00000129831b2a4038000006bd2c7549e0137e9f1f97838e40334866a0754522689e02fbda771b3a6fe8f4bdb3171e20cf8314475e8d0cfa7090f5c8b89e584ef5790fc0704918e449ae5811028313ad3ba3c804df57da6fdf1c8224e388e391dec7a0a4502fe8003241ee58f3b869c282f45506672b6f19cb7c00000010cefcbb040060ca3b2c754b5 UdpSessionOpen { session_id: 0, flags: 79862805156, address_type: Domain, host: [210, 199, 84, 158, 1, 55, 233, 241, 249, 120, 56, 228, 3, 52, 134, 106, 7, 84, 82, 38, 137, 224, 47, 189, 167, 113, 179, 166, 254, 143, 75, 219, 49, 113, 226, 12, 248, 49, 68, 117, 232, 208, 207, 167, 9, 15, 92, 139, 137, 229, 132, 239, 87, 144, 252, 7, 4, 145, 142, 68, 154, 229, 129, 16, 40, 49, 58, 211, 186, 60, 128, 77, 245, 125, 166, 253, 241, 200, 34, 78, 56, 142, 57, 29, 236, 122, 10, 69, 2, 254, 128, 3, 36, 30, 229, 143, 59, 134, 156, 40, 47, 69, 80, 102, 114, 182, 241], port: 40119, idle_timeout_ms: 4512009136 };139;[00, c0, 00, 00, 12, 98, 31, b2, a4, 03, 40, 6b, d2, c7, 54, 9e, 01, 37, e9, f1, f9, 78, 38, e4, 03, 34, 86, 6a, 07, 54, 52, 26, 89, e0, 2f, bd, a7, 71, b3, a6, fe, 8f, 4b, db, 31, 71, e2, 0c, f8, 31, 44, 75, e8, d0, cf, a7, 09, 0f, 5c, 8b, 89, e5, 84, ef, 57, 90, fc, 07, 04, 91, 8e, 44, 9a, e5, 81, 10, 28, 31, 3a, d3, ba, 3c, 80, 4d, f5, 7d, a6, fd, f1, c8, 22, 4e, 38, 8e, 39, 1d, ec, 7a, 0a, 45, 02, fe, 80, 03, 24, 1e, e5, 8f, 3b, 86, 9c, 28, 2f, 45, 50, 66, 72, b6, f1, 9c, b7, c0, 00, 00, 01, 0c, ef, cb, b0, 00] +udp_open 80004396c792fb0b8beefdee0140fbe0a7d9165466c438811674b45222872b5bda1faf1a25b9af3ea3e20c5f19edbc13558d20fb22e83cf4a7dc7bba131d6c42c0b79c4f22f3e60f1a0418ee0e805fad3e0250b02a598a92f15de06356a7ead14fe8f2e51e3a35d3225320e1636a3c50d41ea0d30afbfb307c7d95b2031bd51adef996a0d409582c2ae1d3de6b2bc222f3cdaeb3653fb29e2b451889fa8067ccd0c16b7c8d661b254873dd26d94f8accb2a9dce295a40c2c17c4cf04b0321d62f90892bd7d0bffc0d0998dc26588d932984bef3fb349b4f7910c506e94d368d4aedaff8ab4f25f4925279d2b6c77e9d78ae28fa713cda27629b5f22956749e2294e67cffa06f3d0069d94cb1c099d277b513f11dc0000000000000082234a9986695e064 UdpSessionOpen { session_id: 17302, flags: 545774531876158958, address_type: Ipv4, host: [224, 167, 217, 22, 84, 102, 196, 56, 129, 22, 116, 180, 82, 34, 135, 43, 91, 218, 31, 175, 26, 37, 185, 175, 62, 163, 226, 12, 95, 25, 237, 188, 19, 85, 141, 32, 251, 34, 232, 60, 244, 167, 220, 123, 186, 19, 29, 108, 66, 192, 183, 156, 79, 34, 243, 230, 15, 26, 4, 24, 238, 14, 128, 95, 173, 62, 2, 80, 176, 42, 89, 138, 146, 241, 93, 224, 99, 86, 167, 234, 209, 79, 232, 242, 229, 30, 58, 53, 211, 34, 83, 32, 225, 99, 106, 60, 80, 212, 30, 160, 211, 10, 251, 251, 48, 124, 125, 149, 178, 3, 27, 213, 26, 222, 249, 150, 160, 212, 9, 88, 44, 42, 225, 211, 222, 107, 43, 194, 34, 243, 205, 174, 179, 101, 63, 178, 158, 43, 69, 24, 137, 250, 128, 103, 204, 208, 193, 107, 124, 141, 102, 27, 37, 72, 115, 221, 38, 217, 79, 138, 204, 178, 169, 220, 226, 149, 164, 12, 44, 23, 196, 207, 4, 176, 50, 29, 98, 249, 8, 146, 189, 125, 11, 255, 192, 208, 153, 141, 194, 101, 136, 217, 50, 152, 75, 239, 63, 179, 73, 180, 247, 145, 12, 80, 110, 148, 211, 104, 212, 174, 218, 255, 138, 180, 242, 95, 73, 37, 39, 157, 43, 108, 119, 233, 215, 138, 226, 143, 167, 19, 205, 162, 118, 41, 181, 242, 41, 86, 116, 158, 34, 148, 230, 124, 255, 160, 111, 61, 0, 105, 217], port: 19633, idle_timeout_ms: 43297083017654557 };292;[80, 00, 43, 96, c7, 92, fb, 0b, 8b, ee, fd, ee, 01, 40, fb, e0, a7, d9, 16, 54, 66, c4, 38, 81, 16, 74, b4, 52, 22, 87, 2b, 5b, da, 1f, af, 1a, 25, b9, af, 3e, a3, e2, 0c, 5f, 19, ed, bc, 13, 55, 8d, 20, fb, 22, e8, 3c, f4, a7, dc, 7b, ba, 13, 1d, 6c, 42, c0, b7, 9c, 4f, 22, f3, e6, 0f, 1a, 04, 18, ee, 0e, 80, 5f, ad, 3e, 02, 50, b0, 2a, 59, 8a, 92, f1, 5d, e0, 63, 56, a7, ea, d1, 4f, e8, f2, e5, 1e, 3a, 35, d3, 22, 53, 20, e1, 63, 6a, 3c, 50, d4, 1e, a0, d3, 0a, fb, fb, 30, 7c, 7d, 95, b2, 03, 1b, d5, 1a, de, f9, 96, a0, d4, 09, 58, 2c, 2a, e1, d3, de, 6b, 2b, c2, 22, f3, cd, ae, b3, 65, 3f, b2, 9e, 2b, 45, 18, 89, fa, 80, 67, cc, d0, c1, 6b, 7c, 8d, 66, 1b, 25, 48, 73, dd, 26, d9, 4f, 8a, cc, b2, a9, dc, e2, 95, a4, 0c, 2c, 17, c4, cf, 04, b0, 32, 1d, 62, f9, 08, 92, bd, 7d, 0b, ff, c0, d0, 99, 8d, c2, 65, 88, d9, 32, 98, 4b, ef, 3f, b3, 49, b4, f7, 91, 0c, 50, 6e, 94, d3, 68, d4, ae, da, ff, 8a, b4, f2, 5f, 49, 25, 27, 9d, 2b, 6c, 77, e9, d7, 8a, e2, 8f, a7, 13, cd, a2, 76, 29, b5, f2, 29, 56, 74, 9e, 22, 94, e6, 7c, ff, a0, 6f, 3d, 00, 69, d9, 4c, b1, c0, 99, d2, 77, b5, 13, f1, 1d, 00] +udp_open 800369c4c00000234b59dd5a014069a81632b0a14562388a9f0b87dccef3dd3c2c75697bf77b3f6f46b89e4d8420c77a79b2df2a6089c15fdc7e795f98219ef90dbb1ed3091a1d25309ccb79596a9f9a2ba1e01eab7c234ff713fb16db5d4e315290c78dc65abeda64131c8ab3e7f5bb240cd340d25027fe9c7dc000000000000af400 UdpSessionOpen { session_id: 223684, flags: 151588035930, address_type: Ipv4, host: [168, 22, 50, 176, 161, 69, 98, 56, 138, 159, 11, 135, 220, 206, 243, 221, 60, 44, 117, 105, 123, 247, 123, 63, 111, 70, 184, 158, 77, 132, 32, 199, 122, 121, 178, 223, 42, 96, 137, 193, 95, 220, 126, 121, 95, 152, 33, 158, 249, 13, 187, 30, 211, 9, 26, 29, 37, 48, 156, 203, 121, 89, 106, 159, 154, 43, 161, 224, 30, 171, 124, 35, 79, 247, 19, 251, 22, 219, 93, 78, 49, 82, 144, 199, 141, 198, 90, 190, 218, 100, 19, 28, 138, 179, 231, 245, 187, 36, 12, 211, 64, 210, 80, 39, 254], port: 40061, idle_timeout_ms: 2804 };131;[80, 03, 69, c4, c0, 00, 00, 23, 4b, 59, dd, 5a, 01, 40, 69, a8, 16, 32, b0, a1, 45, 62, 38, 8a, 9f, 0b, 87, dc, ce, f3, dd, 3c, 2c, 75, 69, 7b, f7, 7b, 3f, 6f, 46, b8, 9e, 4d, 84, 20, c7, 7a, 79, b2, df, 2a, 60, 89, c1, 5f, dc, 7e, 79, 5f, 98, 21, 9e, f9, 0d, bb, 1e, d3, 09, 1a, 1d, 25, 30, 9c, cb, 79, 59, 6a, 9f, 9a, 2b, a1, e0, 1e, ab, 7c, 23, 4f, f7, 13, fb, 16, db, 5d, 4e, 31, 52, 90, c7, 8d, c6, 5a, be, da, 64, 13, 1c, 8a, b3, e7, f5, bb, 24, 0c, d3, 40, d2, 50, 27, fe, 9c, 7d, 4a, f4, 00] +udp_open 9f656c22c00000000000003804c0000000000000b9d98f1b84ecafe91701ec906b0dd43398ee6beb556ade678fec468a801debd0030e25c011e12c67fa729d8d142233fc2ba49009436e500fa3e6189b610d31a9449298b7947d17f2654e9d2661899561163e0f613247b1d570940ca7a04736a4bd3c290c55526ebb977c9b97c64721e4641e351c923a8ff5e272bfd4ede9ebe272bf0cee21c0297aa72666ed3e821c71ea9d545da57eb7f32870e58ee813a71737ee3d7c7dd2933b9da5f4843405b1c94e03c18a0d4acf4b601fc0cdc000001346a97b9a40000583d0 UdpSessionOpen { session_id: 526740514, flags: 56, address_type: Ipv6, host: [217, 143, 27, 132, 236, 175, 233, 23, 1, 236, 144, 107, 13, 212, 51, 152, 238, 107, 235, 85, 106, 222, 103, 143, 236, 70, 138, 128, 29, 235, 208, 3, 14, 37, 192, 17, 225, 44, 103, 250, 114, 157, 141, 20, 34, 51, 252, 43, 164, 144, 9, 67, 110, 80, 15, 163, 230, 24, 155, 97, 13, 49, 169, 68, 146, 152, 183, 148, 125, 23, 242, 101, 78, 157, 38, 97, 137, 149, 97, 22, 62, 15, 97, 50, 71, 177, 213, 112, 148, 12, 167, 160, 71, 54, 164, 189, 60, 41, 12, 85, 82, 110, 187, 151, 124, 155, 151, 198, 71, 33, 228, 100, 30, 53, 28, 146, 58, 143, 245, 226, 114, 191, 212, 237, 233, 235, 226, 114, 191, 12, 238, 33, 192, 41, 122, 167, 38, 102, 237, 62, 130, 28, 113, 234, 157, 84, 93, 165, 126, 183, 243, 40, 112, 229, 142, 232, 19, 167, 23, 55, 238, 61, 124, 125, 210, 147, 59, 157, 165, 244, 132, 52, 5, 177, 201, 78, 3, 193, 138, 13, 74, 207, 75, 96, 31], port: 49357, idle_timeout_ms: 82789890970 };218;[9f, 65, 6c, 22, 38, 04, 40, b9, d9, 8f, 1b, 84, ec, af, e9, 17, 01, ec, 90, 6b, 0d, d4, 33, 98, ee, 6b, eb, 55, 6a, de, 67, 8f, ec, 46, 8a, 80, 1d, eb, d0, 03, 0e, 25, c0, 11, e1, 2c, 67, fa, 72, 9d, 8d, 14, 22, 33, fc, 2b, a4, 90, 09, 43, 6e, 50, 0f, a3, e6, 18, 9b, 61, 0d, 31, a9, 44, 92, 98, b7, 94, 7d, 17, f2, 65, 4e, 9d, 26, 61, 89, 95, 61, 16, 3e, 0f, 61, 32, 47, b1, d5, 70, 94, 0c, a7, a0, 47, 36, a4, bd, 3c, 29, 0c, 55, 52, 6e, bb, 97, 7c, 9b, 97, c6, 47, 21, e4, 64, 1e, 35, 1c, 92, 3a, 8f, f5, e2, 72, bf, d4, ed, e9, eb, e2, 72, bf, 0c, ee, 21, c0, 29, 7a, a7, 26, 66, ed, 3e, 82, 1c, 71, ea, 9d, 54, 5d, a5, 7e, b7, f3, 28, 70, e5, 8e, e8, 13, a7, 17, 37, ee, 3d, 7c, 7d, d2, 93, 3b, 9d, a5, f4, 84, 34, 05, b1, c9, 4e, 03, c1, 8a, 0d, 4a, cf, 4b, 60, 1f, c0, cd, c0, 00, 00, 13, 46, a9, 7b, 9a, 00] +udp_open c000000000003c03c00000057e88540f03800000d2ac257760f558184d831a17537bc5833d3ef3ca4cf30640b1e4dee9400c609b8e6a0a5425148d105ad5287d03cd7e25365ece8e0ba4c30abcb75acc1d80b4ba0f7b88b310d65686d7428d072373c9f9350603fa20a0500e6828f8ebf488112bc160598dd4956c3f6562494eb82e020291ebafdfbbc8ad434f0f2ab9deb4f10e714f7f90fb0a0317628216cde760af4d8e18aa6154067e6b020ee873d9dc63312649a0020afc105ce3d7c33de1adcd337d6100b6c794c1416f8775c73d6b8ef89f29165162435c271e0e03be2f27c4a02e547a84c9800001a740071ec003dd0c271f UdpSessionOpen { session_id: 15363, flags: 23597700111, address_type: Domain, host: [172, 37, 119, 96, 245, 88, 24, 77, 131, 26, 23, 83, 123, 197, 131, 61, 62, 243, 202, 76, 243, 6, 64, 177, 228, 222, 233, 64, 12, 96, 155, 142, 106, 10, 84, 37, 20, 141, 16, 90, 213, 40, 125, 3, 205, 126, 37, 54, 94, 206, 142, 11, 164, 195, 10, 188, 183, 90, 204, 29, 128, 180, 186, 15, 123, 136, 179, 16, 214, 86, 134, 215, 66, 141, 7, 35, 115, 201, 249, 53, 6, 3, 250, 32, 160, 80, 14, 104, 40, 248, 235, 244, 136, 17, 43, 193, 96, 89, 141, 212, 149, 108, 63, 101, 98, 73, 78, 184, 46, 2, 2, 145, 235, 175, 223, 187, 200, 173, 67, 79, 15, 42, 185, 222, 180, 241, 14, 113, 79, 127, 144, 251, 10, 3, 23, 98, 130, 22, 205, 231, 96, 175, 77, 142, 24, 170, 97, 84, 6, 126, 107, 2, 14, 232, 115, 217, 220, 99, 49, 38, 73, 160, 2, 10, 252, 16, 92, 227, 215, 195, 61, 225, 173, 205, 51, 125, 97, 0, 182, 199, 148, 193, 65, 111, 135, 117, 199, 61, 107, 142, 248, 159, 41, 22, 81, 98, 67, 92, 39, 30, 14, 3, 190, 47, 39, 196, 160, 46, 84, 122], port: 33993, idle_timeout_ms: 423 };246;[7c, 03, c0, 00, 00, 05, 7e, 88, 54, 0f, 03, 40, d2, ac, 25, 77, 60, f5, 58, 18, 4d, 83, 1a, 17, 53, 7b, c5, 83, 3d, 3e, f3, ca, 4c, f3, 06, 40, b1, e4, de, e9, 40, 0c, 60, 9b, 8e, 6a, 0a, 54, 25, 14, 8d, 10, 5a, d5, 28, 7d, 03, cd, 7e, 25, 36, 5e, ce, 8e, 0b, a4, c3, 0a, bc, b7, 5a, cc, 1d, 80, b4, ba, 0f, 7b, 88, b3, 10, d6, 56, 86, d7, 42, 8d, 07, 23, 73, c9, f9, 35, 06, 03, fa, 20, a0, 50, 0e, 68, 28, f8, eb, f4, 88, 11, 2b, c1, 60, 59, 8d, d4, 95, 6c, 3f, 65, 62, 49, 4e, b8, 2e, 02, 02, 91, eb, af, df, bb, c8, ad, 43, 4f, 0f, 2a, b9, de, b4, f1, 0e, 71, 4f, 7f, 90, fb, 0a, 03, 17, 62, 82, 16, cd, e7, 60, af, 4d, 8e, 18, aa, 61, 54, 06, 7e, 6b, 02, 0e, e8, 73, d9, dc, 63, 31, 26, 49, a0, 02, 0a, fc, 10, 5c, e3, d7, c3, 3d, e1, ad, cd, 33, 7d, 61, 00, b6, c7, 94, c1, 41, 6f, 87, 75, c7, 3d, 6b, 8e, f8, 9f, 29, 16, 51, 62, 43, 5c, 27, 1e, 0e, 03, be, 2f, 27, c4, a0, 2e, 54, 7a, 84, c9, 41, a7, 00] +udp_open 8000000b803983ae044090843a5d6ad560c7fd564b5c94853941bad88c0866b1a789d29b902a2a3e3ab1792ea8898d19619912d7149558e1ac55ed2fdc6696fc6ada5cbdd9b84ad4ccd6027e33405f8330e0391e2db36271716c22ef246ecf3b3c661fd780a459afd2497f4dbb469a0ad369e0e4535328bbba23e213ceb7adb42adccccc95d62e9d88f06417c4189f461dd16a136c47e8f070bfc2b0a340000be765a20a039ab13f0ca774 UdpSessionOpen { session_id: 11, flags: 3769262, address_type: Ipv6, host: [132, 58, 93, 106, 213, 96, 199, 253, 86, 75, 92, 148, 133, 57, 65, 186, 216, 140, 8, 102, 177, 167, 137, 210, 155, 144, 42, 42, 62, 58, 177, 121, 46, 168, 137, 141, 25, 97, 153, 18, 215, 20, 149, 88, 225, 172, 85, 237, 47, 220, 102, 150, 252, 106, 218, 92, 189, 217, 184, 74, 212, 204, 214, 2, 126, 51, 64, 95, 131, 48, 224, 57, 30, 45, 179, 98, 113, 113, 108, 34, 239, 36, 110, 207, 59, 60, 102, 31, 215, 128, 164, 89, 175, 210, 73, 127, 77, 187, 70, 154, 10, 211, 105, 224, 228, 83, 83, 40, 187, 186, 35, 226, 19, 206, 183, 173, 180, 42, 220, 204, 204, 149, 214, 46, 157, 136, 240, 100, 23, 196, 24, 159, 70, 29, 209, 106, 19, 108, 71, 232, 240, 112, 191, 194], port: 45219, idle_timeout_ms: 0 };171;[0b, 80, 39, 83, ae, 04, 40, 90, 84, 3a, 5d, 6a, d5, 60, c7, fd, 56, 4b, 5c, 94, 85, 39, 41, ba, d8, 8c, 08, 66, b1, a7, 89, d2, 9b, 90, 2a, 2a, 3e, 3a, b1, 79, 2e, a8, 89, 8d, 19, 61, 99, 12, d7, 14, 95, 58, e1, ac, 55, ed, 2f, dc, 66, 96, fc, 6a, da, 5c, bd, d9, b8, 4a, d4, cc, d6, 02, 7e, 33, 40, 5f, 83, 30, e0, 39, 1e, 2d, b3, 62, 71, 71, 6c, 22, ef, 24, 6e, cf, 3b, 3c, 66, 1f, d7, 80, a4, 59, af, d2, 49, 7f, 4d, bb, 46, 9a, 0a, d3, 69, e0, e4, 53, 53, 28, bb, ba, 23, e2, 13, ce, b7, ad, b4, 2a, dc, cc, cc, 95, d6, 2e, 9d, 88, f0, 64, 17, c4, 18, 9f, 46, 1d, d1, 6a, 13, 6c, 47, e8, f0, 70, bf, c2, b0, a3, 00, 00] +udp_open 4073c000003567eeb7e403410048886378cb78477816dc1356745567923958a3bbb3768c9be8fd79ddde69ecd0a1027cdf9474b48173c7edda14c7a4078ee3d5783cafd5895220d105a0bb2f2280e18a241695c555c9912a641d41adb439a8772ae9e1a50922c4889dd341299acc6e0cbc7fe55a88a68221152d60743fcb72cd3cbaa35c69fe10eb1b6cee1d3e16ea21be3d7d0402afb6713adf16b3f383f646b93a2c50e97a0bade5e30c4682ab1696c4aadc67ac671b1ebab35d9b590b6c61999c214566806d26f17fe727653dfa9e1986655551817c40c61d6847a04e767a09308e4d95c7e88c856f5e95b1adb72acfcdd77b1a92395caac32e0fbc1bb8aa8724d39aa65bad37d37ba6ce46a785c00000003577bc67400cc94fe65f27c319550c7fa995 reject +udp_open 008175333c041451311dc94824d6c62dc36d490605856f7065aed7db30c0000000430363170e01d337e174591c774638b13feffd UdpSessionOpen { session_id: 0, flags: 24458044, address_type: Ipv6, host: [81, 49, 29, 201, 72, 36, 214, 198, 45, 195, 109, 73, 6, 5, 133, 111, 112, 101, 174, 215], port: 56112, idle_timeout_ms: 1124295447 };52;[00, 81, 75, 33, 3c, 04, 14, 51, 31, 1d, c9, 48, 24, d6, c6, 2d, c3, 6d, 49, 06, 05, 85, 6f, 70, 65, ae, d7, db, 30, c0, 00, 00, 00, 43, 03, 63, 17, 00] +udp_open 80000000800065980340a9f117460dca1c1e99e98c43a8a113ecdbf2bcde608d9437f3abf6235a8758e69d0f2b6ea5eba43e5abda1c50576fd61f1d1268c92aeb95ab903da7f9f3183327b9164fa7812331267bd51a5200d9e486cfdf31a2d079f60927af6a7d0a554e83baa1423842c15b020ee91b040773b4a8cfba743e2598d1aec979ad865a8c39ef293942312e9777703c3fabb96113cffcef6e8836e872969c8e7bcc40891d2746a0cdb05752a2cab37e95776c40224eff02aa1e100 UdpSessionOpen { session_id: 0, flags: 26008, address_type: Domain, host: [241, 23, 70, 13, 202, 28, 30, 153, 233, 140, 67, 168, 161, 19, 236, 219, 242, 188, 222, 96, 141, 148, 55, 243, 171, 246, 35, 90, 135, 88, 230, 157, 15, 43, 110, 165, 235, 164, 62, 90, 189, 161, 197, 5, 118, 253, 97, 241, 209, 38, 140, 146, 174, 185, 90, 185, 3, 218, 127, 159, 49, 131, 50, 123, 145, 100, 250, 120, 18, 51, 18, 103, 189, 81, 165, 32, 13, 158, 72, 108, 253, 243, 26, 45, 7, 159, 96, 146, 122, 246, 167, 208, 165, 84, 232, 59, 170, 20, 35, 132, 44, 21, 176, 32, 238, 145, 176, 64, 119, 59, 74, 140, 251, 167, 67, 226, 89, 141, 26, 236, 151, 154, 216, 101, 168, 195, 158, 242, 147, 148, 35, 18, 233, 119, 119, 3, 195, 250, 187, 150, 17, 60, 255, 206, 246, 232, 131, 110, 135, 41, 105, 200, 231, 188, 196, 8, 145, 210, 116, 106, 12, 219, 5, 117, 42, 44, 171, 55, 233], port: 22390, idle_timeout_ms: 288833939050242529 };191;[00, 80, 00, 65, 98, 03, 40, a9, f1, 17, 46, 0d, ca, 1c, 1e, 99, e9, 8c, 43, a8, a1, 13, ec, db, f2, bc, de, 60, 8d, 94, 37, f3, ab, f6, 23, 5a, 87, 58, e6, 9d, 0f, 2b, 6e, a5, eb, a4, 3e, 5a, bd, a1, c5, 05, 76, fd, 61, f1, d1, 26, 8c, 92, ae, b9, 5a, b9, 03, da, 7f, 9f, 31, 83, 32, 7b, 91, 64, fa, 78, 12, 33, 12, 67, bd, 51, a5, 20, 0d, 9e, 48, 6c, fd, f3, 1a, 2d, 07, 9f, 60, 92, 7a, f6, a7, d0, a5, 54, e8, 3b, aa, 14, 23, 84, 2c, 15, b0, 20, ee, 91, b0, 40, 77, 3b, 4a, 8c, fb, a7, 43, e2, 59, 8d, 1a, ec, 97, 9a, d8, 65, a8, c3, 9e, f2, 93, 94, 23, 12, e9, 77, 77, 03, c3, fa, bb, 96, 11, 3c, ff, ce, f6, e8, 83, 6e, 87, 29, 69, c8, e7, bc, c4, 08, 91, d2, 74, 6a, 0c, db, 05, 75, 2a, 2c, ab, 37, e9, 57, 76, c4, 02, 24, ef, f0, 2a, a1, e1, 00] +udp_open 80000a9b8000b3760140a479f118f678301e7ccaf73392c79fad790bf875b265af706dade4d49b9234790724b4dd9b5f3e3090dfad493d1ae6da8127818f615b8341e639d895231f3993f8103b9587261153fdd3bcd2a8bb70d2fac74a08a4d7d7fadbc7e4c74ec75054e8699f9ea1083c4e247c02bb8007015f3b4cd46289d6cde5a7c241a309894a105d3ebd8309273d3aa9000f11a35a5f547173c39ffb30cabf8c59acd4dfe0ec213618714eab12b687b8528a4000 UdpSessionOpen { session_id: 2715, flags: 45942, address_type: Ipv4, host: [121, 241, 24, 246, 120, 48, 30, 124, 202, 247, 51, 146, 199, 159, 173, 121, 11, 248, 117, 178, 101, 175, 112, 109, 173, 228, 212, 155, 146, 52, 121, 7, 36, 180, 221, 155, 95, 62, 48, 144, 223, 173, 73, 61, 26, 230, 218, 129, 39, 129, 143, 97, 91, 131, 65, 230, 57, 216, 149, 35, 31, 57, 147, 248, 16, 59, 149, 135, 38, 17, 83, 253, 211, 188, 210, 168, 187, 112, 210, 250, 199, 74, 8, 164, 215, 215, 250, 219, 199, 228, 199, 78, 199, 80, 84, 232, 105, 159, 158, 161, 8, 60, 78, 36, 124, 2, 187, 128, 7, 1, 95, 59, 76, 212, 98, 137, 214, 205, 229, 167, 194, 65, 163, 9, 137, 74, 16, 93, 62, 189, 131, 9, 39, 61, 58, 169, 0, 15, 17, 163, 90, 95, 84, 113, 115, 195, 159, 251, 48, 202, 191, 140, 89, 172, 212, 223, 224, 236, 33, 54, 24, 113, 78, 171], port: 4790, idle_timeout_ms: 129520266 };183;[4a, 9b, 80, 00, b3, 76, 01, 40, a4, 79, f1, 18, f6, 78, 30, 1e, 7c, ca, f7, 33, 92, c7, 9f, ad, 79, 0b, f8, 75, b2, 65, af, 70, 6d, ad, e4, d4, 9b, 92, 34, 79, 07, 24, b4, dd, 9b, 5f, 3e, 30, 90, df, ad, 49, 3d, 1a, e6, da, 81, 27, 81, 8f, 61, 5b, 83, 41, e6, 39, d8, 95, 23, 1f, 39, 93, f8, 10, 3b, 95, 87, 26, 11, 53, fd, d3, bc, d2, a8, bb, 70, d2, fa, c7, 4a, 08, a4, d7, d7, fa, db, c7, e4, c7, 4e, c7, 50, 54, e8, 69, 9f, 9e, a1, 08, 3c, 4e, 24, 7c, 02, bb, 80, 07, 01, 5f, 3b, 4c, d4, 62, 89, d6, cd, e5, a7, c2, 41, a3, 09, 89, 4a, 10, 5d, 3e, bd, 83, 09, 27, 3d, 3a, a9, 00, 0f, 11, a3, 5a, 5f, 54, 71, 73, c3, 9f, fb, 30, ca, bf, 8c, 59, ac, d4, df, e0, ec, 21, 36, 18, 71, 4e, ab, 12, b6, 87, b8, 52, 8a, 00] +udp_open 800026b18410b3db0380000044216f208c3af1030da63c63bed7d2f16bcd06235be5c5d69161bd2f5dffa6f7ab52cbd96d7e1d1cd3604f581e387626043ec69ebf1c110e6611955637f8dd4490837f9350cd77c000003a69751b1200 UdpSessionOpen { session_id: 9905, flags: 68203483, address_type: Domain, host: [33, 111, 32, 140, 58, 241, 3, 13, 166, 60, 99, 190, 215, 210, 241, 107, 205, 6, 35, 91, 229, 197, 214, 145, 97, 189, 47, 93, 255, 166, 247, 171, 82, 203, 217, 109, 126, 29, 28, 211, 96, 79, 88, 30, 56, 118, 38, 4, 62, 198, 158, 191, 28, 17, 14, 102, 17, 149, 86, 55, 248, 221, 68, 144, 131, 127, 147, 80], port: 52599, idle_timeout_ms: 250877385490 };92;[66, b1, 84, 10, b3, db, 03, 40, 44, 21, 6f, 20, 8c, 3a, f1, 03, 0d, a6, 3c, 63, be, d7, d2, f1, 6b, cd, 06, 23, 5b, e5, c5, d6, 91, 61, bd, 2f, 5d, ff, a6, f7, ab, 52, cb, d9, 6d, 7e, 1d, 1c, d3, 60, 4f, 58, 1e, 38, 76, 26, 04, 3e, c6, 9e, bf, 1c, 11, 0e, 66, 11, 95, 56, 37, f8, dd, 44, 90, 83, 7f, 93, 50, cd, 77, c0, 00, 00, 3a, 69, 75, 1b, 12, 00] +udp_open 8000003a8d31378e0480000021ad2623a229dde151c50613502285ab5f5f32908189dcb7f418fe6d9af2da494543d5e0c00000000000002900 UdpSessionOpen { session_id: 58, flags: 221329294, address_type: Ipv6, host: [173, 38, 35, 162, 41, 221, 225, 81, 197, 6, 19, 80, 34, 133, 171, 95, 95, 50, 144, 129, 137, 220, 183, 244, 24, 254, 109, 154, 242, 218, 73, 69, 67], port: 54752, idle_timeout_ms: 41 };57;[3a, 8d, 31, 37, 8e, 04, 21, ad, 26, 23, a2, 29, dd, e1, 51, c5, 06, 13, 50, 22, 85, ab, 5f, 5f, 32, 90, 81, 89, dc, b7, f4, 18, fe, 6d, 9a, f2, da, 49, 45, 43, d5, e0, 29, 00] +udp_open c00000010000017ac006931f2e03dd9a044098eeb73d3fb97adb78efa3b39736e09ca68b4d58497ff9ea29fd3d78b1e9334a037c27a54d82183c83a51039d9cd945ed3a5b180a13ebcec9a16af49feae63cfc05e55e5a464ae35fe85c3b756295696caaf205a2906b2c0c77494794e73d2aec67cb404f7e8cbadb03454d75bfe6eaa6d9150f2929abe8e9c1d92b5749b7e3f880982ac93a9a10f6ec0e7b644057c7f5859ee9d528388c300b69d4fe300 reject +udp_open 07c000002991f7ccfe03800000eb85b02c22f2165fc827affe798e7a46604f01c24b329a27b73ec02c4eb8113a80e0863ad0aa353fced0cf996db8611eda40ea1b8579d31ad1434ec86152a6851313ef6b20c5abc38ba847f42c12e951ad6c557818e56a3aae0384929a13b5c8a6c4707226f7ffd2f14140af4b0c443d757fbf33fa50f3b2c1d13b44fc063dab0c9f517f8ec854cfe7c6b8f0e55dc58834d8ec9048bc7ea20ef28dd8e5aa0cb5055577261858a21beaa04e1828dfc8f757de203f333da2241176b1ea160668dc9fa9aa47ffc3fd20943f2a92849eabd10ba7b7e37a7eabd36bc63716d5b1c61fde87efd4d9aaa5e68c393289b753ef7fb3d617dfdbf1c000000000000000 UdpSessionOpen { session_id: 7, flags: 178542595326, address_type: Domain, host: [133, 176, 44, 34, 242, 22, 95, 200, 39, 175, 254, 121, 142, 122, 70, 96, 79, 1, 194, 75, 50, 154, 39, 183, 62, 192, 44, 78, 184, 17, 58, 128, 224, 134, 58, 208, 170, 53, 63, 206, 208, 207, 153, 109, 184, 97, 30, 218, 64, 234, 27, 133, 121, 211, 26, 209, 67, 78, 200, 97, 82, 166, 133, 19, 19, 239, 107, 32, 197, 171, 195, 139, 168, 71, 244, 44, 18, 233, 81, 173, 108, 85, 120, 24, 229, 106, 58, 174, 3, 132, 146, 154, 19, 181, 200, 166, 196, 112, 114, 38, 247, 255, 210, 241, 65, 64, 175, 75, 12, 68, 61, 117, 127, 191, 51, 250, 80, 243, 178, 193, 209, 59, 68, 252, 6, 61, 171, 12, 159, 81, 127, 142, 200, 84, 207, 231, 198, 184, 240, 229, 93, 197, 136, 52, 216, 236, 144, 72, 188, 126, 162, 14, 242, 141, 216, 229, 170, 12, 181, 5, 85, 119, 38, 24, 88, 162, 27, 234, 160, 78, 24, 40, 223, 200, 247, 87, 222, 32, 63, 51, 61, 162, 36, 17, 118, 177, 234, 22, 6, 104, 220, 159, 169, 170, 71, 255, 195, 253, 32, 148, 63, 42, 146, 132, 158, 171, 209, 11, 167, 183, 227, 122, 126, 171, 211, 107, 198, 55, 22, 213, 177, 198, 31, 222, 135, 239, 212, 217, 170, 165, 230, 140, 57, 50, 137], port: 46931, idle_timeout_ms: 3422651973929786353 };267;[07, c0, 00, 00, 29, 91, f7, cc, fe, 03, 40, eb, 85, b0, 2c, 22, f2, 16, 5f, c8, 27, af, fe, 79, 8e, 7a, 46, 60, 4f, 01, c2, 4b, 32, 9a, 27, b7, 3e, c0, 2c, 4e, b8, 11, 3a, 80, e0, 86, 3a, d0, aa, 35, 3f, ce, d0, cf, 99, 6d, b8, 61, 1e, da, 40, ea, 1b, 85, 79, d3, 1a, d1, 43, 4e, c8, 61, 52, a6, 85, 13, 13, ef, 6b, 20, c5, ab, c3, 8b, a8, 47, f4, 2c, 12, e9, 51, ad, 6c, 55, 78, 18, e5, 6a, 3a, ae, 03, 84, 92, 9a, 13, b5, c8, a6, c4, 70, 72, 26, f7, ff, d2, f1, 41, 40, af, 4b, 0c, 44, 3d, 75, 7f, bf, 33, fa, 50, f3, b2, c1, d1, 3b, 44, fc, 06, 3d, ab, 0c, 9f, 51, 7f, 8e, c8, 54, cf, e7, c6, b8, f0, e5, 5d, c5, 88, 34, d8, ec, 90, 48, bc, 7e, a2, 0e, f2, 8d, d8, e5, aa, 0c, b5, 05, 55, 77, 26, 18, 58, a2, 1b, ea, a0, 4e, 18, 28, df, c8, f7, 57, de, 20, 3f, 33, 3d, a2, 24, 11, 76, b1, ea, 16, 06, 68, dc, 9f, a9, aa, 47, ff, c3, fd, 20, 94, 3f, 2a, 92, 84, 9e, ab, d1, 0b, a7, b7, e3, 7a, 7e, ab, d3, 6b, c6, 37, 16, d5, b1, c6, 1f, de, 87, ef, d4, d9, aa, a5, e6, 8c, 39, 32, 89, b7, 53, ef, 7f, b3, d6, 17, df, db, f1, 00] +udp_open c0000000000000028000c3f301800000d8c121b03dccddf7a7b56ceba60f30596564c68d1e7436d1652016d2fb3c321bd600d86ead0e69365a8b1ea93773caa6995b89f2b7314887492f2a67f86c1f16c51d1ae4da81b65b0ddb07f559289c832268d3a00fa4dfd9afbdfd54f7db154539b91130c624ed45176cdd60f0c3bddc7a80dd024e1d940ccd7c250967320c6abbb7c6dbe983e20a14938c06684bf2114a30f0450900d9207b133bb2f4fcfa8c1142a1a965ab8f8839745e22cdc1506953cca24272bc1f25a9ef66563f8b8884d748711735bd818a284e66bb66b7770f76debbe7ee36e5139b13bbc00000000000081a40023a9a UdpSessionOpen { session_id: 2, flags: 50163, address_type: Ipv4, host: [193, 33, 176, 61, 204, 221, 247, 167, 181, 108, 235, 166, 15, 48, 89, 101, 100, 198, 141, 30, 116, 54, 209, 101, 32, 22, 210, 251, 60, 50, 27, 214, 0, 216, 110, 173, 14, 105, 54, 90, 139, 30, 169, 55, 115, 202, 166, 153, 91, 137, 242, 183, 49, 72, 135, 73, 47, 42, 103, 248, 108, 31, 22, 197, 29, 26, 228, 218, 129, 182, 91, 13, 219, 7, 245, 89, 40, 156, 131, 34, 104, 211, 160, 15, 164, 223, 217, 175, 189, 253, 84, 247, 219, 21, 69, 57, 185, 17, 48, 198, 36, 237, 69, 23, 108, 221, 96, 240, 195, 189, 220, 122, 128, 221, 2, 78, 29, 148, 12, 205, 124, 37, 9, 103, 50, 12, 106, 187, 183, 198, 219, 233, 131, 226, 10, 20, 147, 140, 6, 104, 75, 242, 17, 74, 48, 240, 69, 9, 0, 217, 32, 123, 19, 59, 178, 244, 252, 250, 140, 17, 66, 161, 169, 101, 171, 143, 136, 57, 116, 94, 34, 205, 193, 80, 105, 83, 204, 162, 66, 114, 188, 31, 37, 169, 239, 102, 86, 63, 139, 136, 132, 215, 72, 113, 23, 53, 189, 129, 138, 40, 78, 102, 187, 102, 183, 119, 15, 118, 222, 187, 231, 238, 54, 229, 19, 155], port: 5051, idle_timeout_ms: 2074 };247;[02, 80, 00, c3, f3, 01, 40, d8, c1, 21, b0, 3d, cc, dd, f7, a7, b5, 6c, eb, a6, 0f, 30, 59, 65, 64, c6, 8d, 1e, 74, 36, d1, 65, 20, 16, d2, fb, 3c, 32, 1b, d6, 00, d8, 6e, ad, 0e, 69, 36, 5a, 8b, 1e, a9, 37, 73, ca, a6, 99, 5b, 89, f2, b7, 31, 48, 87, 49, 2f, 2a, 67, f8, 6c, 1f, 16, c5, 1d, 1a, e4, da, 81, b6, 5b, 0d, db, 07, f5, 59, 28, 9c, 83, 22, 68, d3, a0, 0f, a4, df, d9, af, bd, fd, 54, f7, db, 15, 45, 39, b9, 11, 30, c6, 24, ed, 45, 17, 6c, dd, 60, f0, c3, bd, dc, 7a, 80, dd, 02, 4e, 1d, 94, 0c, cd, 7c, 25, 09, 67, 32, 0c, 6a, bb, b7, c6, db, e9, 83, e2, 0a, 14, 93, 8c, 06, 68, 4b, f2, 11, 4a, 30, f0, 45, 09, 00, d9, 20, 7b, 13, 3b, b2, f4, fc, fa, 8c, 11, 42, a1, a9, 65, ab, 8f, 88, 39, 74, 5e, 22, cd, c1, 50, 69, 53, cc, a2, 42, 72, bc, 1f, 25, a9, ef, 66, 56, 3f, 8b, 88, 84, d7, 48, 71, 17, 35, bd, 81, 8a, 28, 4e, 66, bb, 66, b7, 77, 0f, 76, de, bb, e7, ee, 36, e5, 13, 9b, 13, bb, 48, 1a, 00] +udp_open 86dc1d40c00020c19759d8b404c0000000000000bc3b84d8447f46c0a85f05ae4b67f2e167a8f9b1154d47e8065211cfcea8381fc41a359ea38d69bc90472c5febd6cda5e453e90dbd6c0a574810f41806038a3555e95670e579cb87b5ee36deb082b8b67aa929996105c31023add1916bcd3176ca4760ca4863f520514ba001ee81ae0d8e6bb21f00a3ba17abc259feb97f5277af87fe7cb8099e7ef4510ffdf26345e0fc23b855e8cb5a508613f06395a6c915d07b5f3b2658aab24979a807da78ca0556e3a93a051f938068316707209e54c72ffe05830ca703c000000000000005ab45ff0e2e UdpSessionOpen { session_id: 115088704, flags: 36015840024756, address_type: Ipv6, host: [59, 132, 216, 68, 127, 70, 192, 168, 95, 5, 174, 75, 103, 242, 225, 103, 168, 249, 177, 21, 77, 71, 232, 6, 82, 17, 207, 206, 168, 56, 31, 196, 26, 53, 158, 163, 141, 105, 188, 144, 71, 44, 95, 235, 214, 205, 165, 228, 83, 233, 13, 189, 108, 10, 87, 72, 16, 244, 24, 6, 3, 138, 53, 85, 233, 86, 112, 229, 121, 203, 135, 181, 238, 54, 222, 176, 130, 184, 182, 122, 169, 41, 153, 97, 5, 195, 16, 35, 173, 209, 145, 107, 205, 49, 118, 202, 71, 96, 202, 72, 99, 245, 32, 81, 75, 160, 1, 238, 129, 174, 13, 142, 107, 178, 31, 0, 163, 186, 23, 171, 194, 89, 254, 185, 127, 82, 119, 175, 135, 254, 124, 184, 9, 158, 126, 244, 81, 15, 253, 242, 99, 69, 224, 252, 35, 184, 85, 232, 203, 90, 80, 134, 19, 240, 99, 149, 166, 201, 21, 208, 123, 95, 59, 38, 88, 170, 178, 73, 121, 168, 7, 218, 120, 202, 5, 86, 227, 169, 58, 5, 31, 147, 128, 104, 49, 103, 7, 32], port: 40532, idle_timeout_ms: 517911781797832451 };232;[86, dc, 1d, 40, c0, 00, 20, c1, 97, 59, d8, b4, 04, 40, bc, 3b, 84, d8, 44, 7f, 46, c0, a8, 5f, 05, ae, 4b, 67, f2, e1, 67, a8, f9, b1, 15, 4d, 47, e8, 06, 52, 11, cf, ce, a8, 38, 1f, c4, 1a, 35, 9e, a3, 8d, 69, bc, 90, 47, 2c, 5f, eb, d6, cd, a5, e4, 53, e9, 0d, bd, 6c, 0a, 57, 48, 10, f4, 18, 06, 03, 8a, 35, 55, e9, 56, 70, e5, 79, cb, 87, b5, ee, 36, de, b0, 82, b8, b6, 7a, a9, 29, 99, 61, 05, c3, 10, 23, ad, d1, 91, 6b, cd, 31, 76, ca, 47, 60, ca, 48, 63, f5, 20, 51, 4b, a0, 01, ee, 81, ae, 0d, 8e, 6b, b2, 1f, 00, a3, ba, 17, ab, c2, 59, fe, b9, 7f, 52, 77, af, 87, fe, 7c, b8, 09, 9e, 7e, f4, 51, 0f, fd, f2, 63, 45, e0, fc, 23, b8, 55, e8, cb, 5a, 50, 86, 13, f0, 63, 95, a6, c9, 15, d0, 7b, 5f, 3b, 26, 58, aa, b2, 49, 79, a8, 07, da, 78, ca, 05, 56, e3, a9, 3a, 05, 1f, 93, 80, 68, 31, 67, 07, 20, 9e, 54, c7, 2f, fe, 05, 83, 0c, a7, 03, 00] +udp_open c000000003df9e6d40530180000099bdd35e68f4884bb53534ea7b2b9d9d53e28ba377b517d525a5c0f3831e2a92402afaab47dd2fe4d01dbaaf3ef959e049414b79bb22ee8e95709bf194e002d4e9d7678aa6c0fd94a6bc54c7912dcad8c36ec5834a0525b62b31f0092884d8cee97509a23528f905ec4fbfddbbdbefa2088ae77b6dd15daf38faa02aae3816d0193c319682fcda109dcdf2eccaefdef663871ac33bb52e13575c35bbc0433ad0be03984100 UdpSessionOpen { session_id: 64986733, flags: 83, address_type: Ipv4, host: [189, 211, 94, 104, 244, 136, 75, 181, 53, 52, 234, 123, 43, 157, 157, 83, 226, 139, 163, 119, 181, 23, 213, 37, 165, 192, 243, 131, 30, 42, 146, 64, 42, 250, 171, 71, 221, 47, 228, 208, 29, 186, 175, 62, 249, 89, 224, 73, 65, 75, 121, 187, 34, 238, 142, 149, 112, 155, 241, 148, 224, 2, 212, 233, 215, 103, 138, 166, 192, 253, 148, 166, 188, 84, 199, 145, 45, 202, 216, 195, 110, 197, 131, 74, 5, 37, 182, 43, 49, 240, 9, 40, 132, 216, 206, 233, 117, 9, 162, 53, 40, 249, 5, 236, 79, 191, 221, 187, 219, 239, 162, 8, 138, 231, 123, 109, 209, 93, 175, 56, 250, 160, 42, 174, 56, 22, 208, 25, 60, 49, 150, 130, 252, 218, 16, 157, 205, 242, 236, 202, 239, 222, 246, 99, 135, 26, 195, 59, 181, 46, 19, 87, 92], port: 13755, idle_timeout_ms: 18923491655129153 };179;[83, df, 9e, 6d, 40, 53, 01, 40, 99, bd, d3, 5e, 68, f4, 88, 4b, b5, 35, 34, ea, 7b, 2b, 9d, 9d, 53, e2, 8b, a3, 77, b5, 17, d5, 25, a5, c0, f3, 83, 1e, 2a, 92, 40, 2a, fa, ab, 47, dd, 2f, e4, d0, 1d, ba, af, 3e, f9, 59, e0, 49, 41, 4b, 79, bb, 22, ee, 8e, 95, 70, 9b, f1, 94, e0, 02, d4, e9, d7, 67, 8a, a6, c0, fd, 94, a6, bc, 54, c7, 91, 2d, ca, d8, c3, 6e, c5, 83, 4a, 05, 25, b6, 2b, 31, f0, 09, 28, 84, d8, ce, e9, 75, 09, a2, 35, 28, f9, 05, ec, 4f, bf, dd, bb, db, ef, a2, 08, 8a, e7, 7b, 6d, d1, 5d, af, 38, fa, a0, 2a, ae, 38, 16, d0, 19, 3c, 31, 96, 82, fc, da, 10, 9d, cd, f2, ec, ca, ef, de, f6, 63, 87, 1a, c3, 3b, b5, 2e, 13, 57, 5c, 35, bb, c0, 43, 3a, d0, be, 03, 98, 41, 00] +udp_open 47d9c00000f2538a7bbb0340707b9505fb461b1e7ea2fdbd1e4ab43534790f34229621d810b8dee350cb4fcee6ee42608958f8f858c95602a301fb4c6a3cb62292db2f4a824b485b78261d503a9ae8fb79c754580aab5a6b37fd47b8267f45997ed226b64bd4c61ef4e4de5e649e3bbc9ac4a2c4f2631281708fb25774c48dc0000000000c9cf60900005c0bae539a99ba UdpSessionOpen { session_id: 2009, flags: 1040783670203, address_type: Domain, host: [123, 149, 5, 251, 70, 27, 30, 126, 162, 253, 189, 30, 74, 180, 53, 52, 121, 15, 52, 34, 150, 33, 216, 16, 184, 222, 227, 80, 203, 79, 206, 230, 238, 66, 96, 137, 88, 248, 248, 88, 201, 86, 2, 163, 1, 251, 76, 106, 60, 182, 34, 146, 219, 47, 74, 130, 75, 72, 91, 120, 38, 29, 80, 58, 154, 232, 251, 121, 199, 84, 88, 10, 171, 90, 107, 55, 253, 71, 184, 38, 127, 69, 153, 126, 210, 38, 182, 75, 212, 198, 30, 244, 228, 222, 94, 100, 158, 59, 188, 154, 196, 162, 196, 242, 99, 18, 129, 112, 143, 178, 87, 116], port: 50317, idle_timeout_ms: 826614 };145;[47, d9, c0, 00, 00, f2, 53, 8a, 7b, bb, 03, 40, 70, 7b, 95, 05, fb, 46, 1b, 1e, 7e, a2, fd, bd, 1e, 4a, b4, 35, 34, 79, 0f, 34, 22, 96, 21, d8, 10, b8, de, e3, 50, cb, 4f, ce, e6, ee, 42, 60, 89, 58, f8, f8, 58, c9, 56, 02, a3, 01, fb, 4c, 6a, 3c, b6, 22, 92, db, 2f, 4a, 82, 4b, 48, 5b, 78, 26, 1d, 50, 3a, 9a, e8, fb, 79, c7, 54, 58, 0a, ab, 5a, 6b, 37, fd, 47, b8, 26, 7f, 45, 99, 7e, d2, 26, b6, 4b, d4, c6, 1e, f4, e4, de, 5e, 64, 9e, 3b, bc, 9a, c4, a2, c4, f2, 63, 12, 81, 70, 8f, b2, 57, 74, c4, 8d, 80, 0c, 9c, f6, 00] +udp_open bfb321c3c0000000000000010380000103bc785632d8ff279803c87b59b2d292790a93175f33fe49d90419ef5b1b805536ef039461644674ad67283100f124eb4473deaac5dd6d3497694dc33154f7159c2e603f55187da79a51553af929ed9e22990998866f77c5d324f2da561a0e64807ae00c2f570ad22ce910913cd49721aa7bb25d231fdabe5952ee15caf62b9b2697aad555ce6fb84c1aabcf537cf75497ab55de2205445bb8986eddd9cdad3abb8075c9e7164f24af606ff8dcb0c7190a9bbbccd222a733a90c8576d18c0afdb2640dea97cc2c0ded34c823f5104ab7be43557737f96507f66dfa193a64b399c3284f0056730613d697dcca2bc853e420c4ad120da9317119edeb9870140f47411bf6d58c56c000008cad1b913ec000000000000000f7defdaceab34f reject +udp_open c000000000008003c0003eb175c2ebf40140c5c6d36e2070d7878ff423778ac4a03ef471f8b50d9501aad9301c1e35117d1dedff21ad492e05dde9e4f802e16eec251fc2c21165643d692aec2e592c55327d4acb8b560949c0e75722ce7760bf4e73e1cf82e3a40c60c4245438f70d1dacc1dec1bf621a7cbfc76ef2104e68e8194dad6821e2f62ba60e24c3825f2e28fa061a32ef19b3690bfd0814e0dec1022d58269a5abaa61ad4635676617b370e127eea9b7bd11bafb1d0be093c304cf0624a78bc1015027e08f81d7e920d99599bfc425c4b20a6ea1a28c00000113127b77280000000c894 UdpSessionOpen { session_id: 32771, flags: 68931905842164, address_type: Ipv4, host: [198, 211, 110, 32, 112, 215, 135, 143, 244, 35, 119, 138, 196, 160, 62, 244, 113, 248, 181, 13, 149, 1, 170, 217, 48, 28, 30, 53, 17, 125, 29, 237, 255, 33, 173, 73, 46, 5, 221, 233, 228, 248, 2, 225, 110, 236, 37, 31, 194, 194, 17, 101, 100, 61, 105, 42, 236, 46, 89, 44, 85, 50, 125, 74, 203, 139, 86, 9, 73, 192, 231, 87, 34, 206, 119, 96, 191, 78, 115, 225, 207, 130, 227, 164, 12, 96, 196, 36, 84, 56, 247, 13, 29, 172, 193, 222, 193, 191, 98, 26, 124, 191, 199, 110, 242, 16, 78, 104, 232, 25, 77, 173, 104, 33, 226, 246, 43, 166, 14, 36, 195, 130, 95, 46, 40, 250, 6, 26, 50, 239, 25, 179, 105, 11, 253, 8, 20, 224, 222, 193, 2, 45, 88, 38, 154, 90, 186, 166, 26, 212, 99, 86, 118, 97, 123, 55, 14, 18, 126, 234, 155, 123, 209, 27, 175, 177, 208, 190, 9, 60, 48, 76, 240, 98, 74, 120, 188, 16, 21, 2, 126, 8, 248, 29, 126, 146, 13, 153, 89, 155, 252, 66, 92, 75, 32, 166, 234], port: 6696, idle_timeout_ms: 73839130482 };230;[80, 00, 80, 03, c0, 00, 3e, b1, 75, c2, eb, f4, 01, 40, c5, c6, d3, 6e, 20, 70, d7, 87, 8f, f4, 23, 77, 8a, c4, a0, 3e, f4, 71, f8, b5, 0d, 95, 01, aa, d9, 30, 1c, 1e, 35, 11, 7d, 1d, ed, ff, 21, ad, 49, 2e, 05, dd, e9, e4, f8, 02, e1, 6e, ec, 25, 1f, c2, c2, 11, 65, 64, 3d, 69, 2a, ec, 2e, 59, 2c, 55, 32, 7d, 4a, cb, 8b, 56, 09, 49, c0, e7, 57, 22, ce, 77, 60, bf, 4e, 73, e1, cf, 82, e3, a4, 0c, 60, c4, 24, 54, 38, f7, 0d, 1d, ac, c1, de, c1, bf, 62, 1a, 7c, bf, c7, 6e, f2, 10, 4e, 68, e8, 19, 4d, ad, 68, 21, e2, f6, 2b, a6, 0e, 24, c3, 82, 5f, 2e, 28, fa, 06, 1a, 32, ef, 19, b3, 69, 0b, fd, 08, 14, e0, de, c1, 02, 2d, 58, 26, 9a, 5a, ba, a6, 1a, d4, 63, 56, 76, 61, 7b, 37, 0e, 12, 7e, ea, 9b, 7b, d1, 1b, af, b1, d0, be, 09, 3c, 30, 4c, f0, 62, 4a, 78, bc, 10, 15, 02, 7e, 08, f8, 1d, 7e, 92, 0d, 99, 59, 9b, fc, 42, 5c, 4b, 20, a6, ea, 1a, 28, c0, 00, 00, 11, 31, 27, b7, 72, 00] +udp_open 8001b825800c6c210340e44069bc79985e197fb403d5a434cb9bbe0ea89eda27b187924c4fcbeb1d53ce282816d9ea253c7cc1a177342736ca7e0573982346792ce22e7f55d87952d9581b85eefad77b0bfa9fd946e9a46101b3d7dcbccacb60f8fa352a6a2d081abc5f717dd12c7f3060b44021231b8ebd3ac30734b426537bd78bc355eded2196d3bf011bdd05c70b7b420f8b2ced0ea18fdf8720d77dd9e196402b019729d4df270f3b9c6231de8c542d92da8d2eb191ea683197c91a170b73439370ba0b38a868d19841dc8e937cb059a98648ed4cfdd12cf3dbeb1253009f2545bf8cdd754e65d2d2afcb74de761cc091b1875b52c6b180000003500071 UdpSessionOpen { session_id: 112677, flags: 814113, address_type: Domain, host: [64, 105, 188, 121, 152, 94, 25, 127, 180, 3, 213, 164, 52, 203, 155, 190, 14, 168, 158, 218, 39, 177, 135, 146, 76, 79, 203, 235, 29, 83, 206, 40, 40, 22, 217, 234, 37, 60, 124, 193, 161, 119, 52, 39, 54, 202, 126, 5, 115, 152, 35, 70, 121, 44, 226, 46, 127, 85, 216, 121, 82, 217, 88, 27, 133, 238, 250, 215, 123, 11, 250, 159, 217, 70, 233, 164, 97, 1, 179, 215, 220, 188, 202, 203, 96, 248, 250, 53, 42, 106, 45, 8, 26, 188, 95, 113, 125, 209, 44, 127, 48, 96, 180, 64, 33, 35, 27, 142, 189, 58, 195, 7, 52, 180, 38, 83, 123, 215, 139, 195, 85, 237, 237, 33, 150, 211, 191, 1, 27, 221, 5, 199, 11, 123, 66, 15, 139, 44, 237, 14, 161, 143, 223, 135, 32, 215, 125, 217, 225, 150, 64, 43, 1, 151, 41, 212, 223, 39, 15, 59, 156, 98, 49, 222, 140, 84, 45, 146, 218, 141, 46, 177, 145, 234, 104, 49, 151, 201, 26, 23, 11, 115, 67, 147, 112, 186, 11, 56, 168, 104, 209, 152, 65, 220, 142, 147, 124, 176, 89, 169, 134, 72, 237, 76, 253, 209, 44, 243, 219, 235, 18, 83, 0, 159, 37, 69, 191, 140, 221, 117, 78, 101, 210, 210, 175, 203, 116, 222], port: 30236, idle_timeout_ms: 41009066533897905 };256;[80, 01, b8, 25, 80, 0c, 6c, 21, 03, 40, e4, 40, 69, bc, 79, 98, 5e, 19, 7f, b4, 03, d5, a4, 34, cb, 9b, be, 0e, a8, 9e, da, 27, b1, 87, 92, 4c, 4f, cb, eb, 1d, 53, ce, 28, 28, 16, d9, ea, 25, 3c, 7c, c1, a1, 77, 34, 27, 36, ca, 7e, 05, 73, 98, 23, 46, 79, 2c, e2, 2e, 7f, 55, d8, 79, 52, d9, 58, 1b, 85, ee, fa, d7, 7b, 0b, fa, 9f, d9, 46, e9, a4, 61, 01, b3, d7, dc, bc, ca, cb, 60, f8, fa, 35, 2a, 6a, 2d, 08, 1a, bc, 5f, 71, 7d, d1, 2c, 7f, 30, 60, b4, 40, 21, 23, 1b, 8e, bd, 3a, c3, 07, 34, b4, 26, 53, 7b, d7, 8b, c3, 55, ed, ed, 21, 96, d3, bf, 01, 1b, dd, 05, c7, 0b, 7b, 42, 0f, 8b, 2c, ed, 0e, a1, 8f, df, 87, 20, d7, 7d, d9, e1, 96, 40, 2b, 01, 97, 29, d4, df, 27, 0f, 3b, 9c, 62, 31, de, 8c, 54, 2d, 92, da, 8d, 2e, b1, 91, ea, 68, 31, 97, c9, 1a, 17, 0b, 73, 43, 93, 70, ba, 0b, 38, a8, 68, d1, 98, 41, dc, 8e, 93, 7c, b0, 59, a9, 86, 48, ed, 4c, fd, d1, 2c, f3, db, eb, 12, 53, 00, 9f, 25, 45, bf, 8c, dd, 75, 4e, 65, d2, d2, af, cb, 74, de, 76, 1c, c0, 91, b1, 87, 5b, 52, c6, b1, 00] +udp_open c000000000000015c00a783876abcb1304c00000000000003baeab687b201c22a0a5bf75db58f33dbe72d0360fcd0b54684126e40186b3fe8e10bb587443a6a255410d471a146b016c8bf3665322123996a29dc627a4c07619b2ee8a14eb8000000f18a40d6ddc45ead0f3fc98375dbb9b UdpSessionOpen { session_id: 21, flags: 2946933671578387, address_type: Ipv6, host: [174, 171, 104, 123, 32, 28, 34, 160, 165, 191, 117, 219, 88, 243, 61, 190, 114, 208, 54, 15, 205, 11, 84, 104, 65, 38, 228, 1, 134, 179, 254, 142, 16, 187, 88, 116, 67, 166, 162, 85, 65, 13, 71, 26, 20, 107, 1, 108, 139, 243, 102, 83, 34, 18, 57, 150, 162, 157, 198], port: 10148, idle_timeout_ms: 33242303548757227 };113;[15, c0, 0a, 78, 38, 76, ab, cb, 13, 04, 3b, ae, ab, 68, 7b, 20, 1c, 22, a0, a5, bf, 75, db, 58, f3, 3d, be, 72, d0, 36, 0f, cd, 0b, 54, 68, 41, 26, e4, 01, 86, b3, fe, 8e, 10, bb, 58, 74, 43, a6, a2, 55, 41, 0d, 47, 1a, 14, 6b, 01, 6c, 8b, f3, 66, 53, 22, 12, 39, 96, a2, 9d, c6, 27, a4, c0, 76, 19, b2, ee, 8a, 14, eb, 00] +udp_open 400140030dc0000000000000a466f4cd23f79e488418afe4400a8ac2234c848775b42e30aa0b1c1c2ebf185fd53c37bacfadccd22586bda092434dcff9e857a49cd8f211f8f2532f6f0945aff7263254418cc49db357abdcb0ccaf2c7c91ba246168c898214506fb39fd11e8074497da3a46783796810a13ac551ef430b7e20df62cde129bd251cc8b439981fa3ff9b7e271747d80e578025ae07d4961940aaf1094c07c24a8770a836b9f607c009b86035b5d9df8d4064000 reject +udp_open c0000000000003e4c000001781404efb0440d1a6e41df043b9780deaa95f9442e03bf989f46a5ac81ef1279d6efafae6aacbe91703c9ed25d8dec184ddb0164d2cd37f5f3c0ee9816a0471c41b771e04ba71ace06d48911d0c945fd28c8dea4f6630c311805c3bfa643db92e82aa8e24ff95c16006bb339a0bf5324173ab9003c8fff56500a3c7b5259fb4b3e775a6f200d45dc783ea6aeff1a299d00cbe5059ffcc23b206793ad645ae27b05a6b71936a70905fd8878b7f98a3f08b956d825c0a0184590f3ba88b72c36010ca7b22b995d526fa615a3c5e9fe9d819c235bf28ef971b226521c00000228f7ed9d380000000 UdpSessionOpen { session_id: 996, flags: 100952723195, address_type: Ipv6, host: [166, 228, 29, 240, 67, 185, 120, 13, 234, 169, 95, 148, 66, 224, 59, 249, 137, 244, 106, 90, 200, 30, 241, 39, 157, 110, 250, 250, 230, 170, 203, 233, 23, 3, 201, 237, 37, 216, 222, 193, 132, 221, 176, 22, 77, 44, 211, 127, 95, 60, 14, 233, 129, 106, 4, 113, 196, 27, 119, 30, 4, 186, 113, 172, 224, 109, 72, 145, 29, 12, 148, 95, 210, 140, 141, 234, 79, 102, 48, 195, 17, 128, 92, 59, 250, 100, 61, 185, 46, 130, 170, 142, 36, 255, 149, 193, 96, 6, 187, 51, 154, 11, 245, 50, 65, 115, 171, 144, 3, 200, 255, 245, 101, 0, 163, 199, 181, 37, 159, 180, 179, 231, 117, 166, 242, 0, 212, 93, 199, 131, 234, 106, 239, 241, 162, 153, 208, 12, 190, 80, 89, 255, 204, 35, 178, 6, 121, 58, 214, 69, 174, 39, 176, 90, 107, 113, 147, 106, 112, 144, 95, 216, 135, 139, 127, 152, 163, 240, 139, 149, 109, 130, 92, 10, 1, 132, 89, 15, 59, 168, 139, 114, 195, 96, 16, 202, 123, 34, 185, 149, 213, 38, 250, 97, 90, 60, 94, 159, 233, 216, 25, 194, 53, 191, 40, 239, 151, 27, 34], port: 25889, idle_timeout_ms: 148436343251 };242;[43, e4, c0, 00, 00, 17, 81, 40, 4e, fb, 04, 40, d1, a6, e4, 1d, f0, 43, b9, 78, 0d, ea, a9, 5f, 94, 42, e0, 3b, f9, 89, f4, 6a, 5a, c8, 1e, f1, 27, 9d, 6e, fa, fa, e6, aa, cb, e9, 17, 03, c9, ed, 25, d8, de, c1, 84, dd, b0, 16, 4d, 2c, d3, 7f, 5f, 3c, 0e, e9, 81, 6a, 04, 71, c4, 1b, 77, 1e, 04, ba, 71, ac, e0, 6d, 48, 91, 1d, 0c, 94, 5f, d2, 8c, 8d, ea, 4f, 66, 30, c3, 11, 80, 5c, 3b, fa, 64, 3d, b9, 2e, 82, aa, 8e, 24, ff, 95, c1, 60, 06, bb, 33, 9a, 0b, f5, 32, 41, 73, ab, 90, 03, c8, ff, f5, 65, 00, a3, c7, b5, 25, 9f, b4, b3, e7, 75, a6, f2, 00, d4, 5d, c7, 83, ea, 6a, ef, f1, a2, 99, d0, 0c, be, 50, 59, ff, cc, 23, b2, 06, 79, 3a, d6, 45, ae, 27, b0, 5a, 6b, 71, 93, 6a, 70, 90, 5f, d8, 87, 8b, 7f, 98, a3, f0, 8b, 95, 6d, 82, 5c, 0a, 01, 84, 59, 0f, 3b, a8, 8b, 72, c3, 60, 10, ca, 7b, 22, b9, 95, d5, 26, fa, 61, 5a, 3c, 5e, 9f, e9, d8, 19, c2, 35, bf, 28, ef, 97, 1b, 22, 65, 21, c0, 00, 00, 22, 8f, 7e, d9, d3, 00] +udp_open 806241a0c0001b41601d7f08034100768bdd1bec889d73fe5a4e4532afa2e5cf61bb732834f2e69793ea16c5e3edd2c76391664c06c5277719f2770c577fab9fd6deae7fd2ddbbbf2ec351e3d7fa3c10cc9407d46540010bff2a93dc327618dd5925bbef52f8768601826002cc9de59f9c19e0b77dd88a529581c84528f6151830f3b94b3c25e55508038e1de19cba4c3d6093f3f48e3d185bcfcf94ea8492a6a84a171c232d5ad1035af0bc8f277b37383bbcf9993d617aa6041e6d0f1268ef2d1455dd522f7abcfe3158681cd7347f3899bf2ac3deb08ef6159a8e91c50bfc20050926d0469ad12aeb3d1d314979e2bac4ed76a94318539c83a45f68ceb207497abb2e6da0e9fc648bf074523a862b03c018d79d338260ea800000000b6e9ab7 reject +udp_open 4004c00068ec039faa9003c000000000000075fca3319ac5a6a4d0ac0b135509108362e5d9e749deee730ef185abde5c957e77a3a0d93ccc65f530e5a51792bcb22b6ebca281832075059903f65a8cb9bcfa551505053e12755ea22fbaac33f73dece7e341bda5c929f507c5a33ded7fa2481e3bd541783b4b1e29566ad120e78f08c95a26a8e7e9b8038032d9b98000000039b588b0ce36 UdpSessionOpen { session_id: 4, flags: 115362882366096, address_type: Domain, host: [252, 163, 49, 154, 197, 166, 164, 208, 172, 11, 19, 85, 9, 16, 131, 98, 229, 217, 231, 73, 222, 238, 115, 14, 241, 133, 171, 222, 92, 149, 126, 119, 163, 160, 217, 60, 204, 101, 245, 48, 229, 165, 23, 146, 188, 178, 43, 110, 188, 162, 129, 131, 32, 117, 5, 153, 3, 246, 90, 140, 185, 188, 250, 85, 21, 5, 5, 62, 18, 117, 94, 162, 47, 186, 172, 51, 247, 61, 236, 231, 227, 65, 189, 165, 201, 41, 245, 7, 197, 163, 61, 237, 127, 162, 72, 30, 59, 213, 65, 120, 59, 75, 30, 41, 86, 106, 209, 32, 231, 143, 8, 201, 90, 38, 168, 231, 233], port: 47107, idle_timeout_ms: 3332537 };146;[04, c0, 00, 68, ec, 03, 9f, aa, 90, 03, 40, 75, fc, a3, 31, 9a, c5, a6, a4, d0, ac, 0b, 13, 55, 09, 10, 83, 62, e5, d9, e7, 49, de, ee, 73, 0e, f1, 85, ab, de, 5c, 95, 7e, 77, a3, a0, d9, 3c, cc, 65, f5, 30, e5, a5, 17, 92, bc, b2, 2b, 6e, bc, a2, 81, 83, 20, 75, 05, 99, 03, f6, 5a, 8c, b9, bc, fa, 55, 15, 05, 05, 3e, 12, 75, 5e, a2, 2f, ba, ac, 33, f7, 3d, ec, e7, e3, 41, bd, a5, c9, 29, f5, 07, c5, a3, 3d, ed, 7f, a2, 48, 1e, 3b, d5, 41, 78, 3b, 4b, 1e, 29, 56, 6a, d1, 20, e7, 8f, 08, c9, 5a, 26, a8, e7, e9, b8, 03, 80, 32, d9, b9, 00] +udp_open 8000942f413e01c0000000000000916586282c3664a2c38577e7a1145b46ae4c9dfecae3e2e799075bc0c9bcc473ff5cdce39c554d0509700351e34dead2d9e2d636ffa8357493fd937d019dfa7582228a4ff9f7d7673ad0b1517612e1c23aa2a2f701830b03cacd2f7ca2d4a5a5ce20a45db87dbd86800bbd62a0d2bc82f3ecd05ce7b5884db965ba15604e0b0bfd4f2ba99d5ac73f56295c37c92390400614edd4c009d6808fe58700c000000000000003632d58 UdpSessionOpen { session_id: 37935, flags: 318, address_type: Ipv4, host: [101, 134, 40, 44, 54, 100, 162, 195, 133, 119, 231, 161, 20, 91, 70, 174, 76, 157, 254, 202, 227, 226, 231, 153, 7, 91, 192, 201, 188, 196, 115, 255, 92, 220, 227, 156, 85, 77, 5, 9, 112, 3, 81, 227, 77, 234, 210, 217, 226, 214, 54, 255, 168, 53, 116, 147, 253, 147, 125, 1, 157, 250, 117, 130, 34, 138, 79, 249, 247, 215, 103, 58, 208, 177, 81, 118, 18, 225, 194, 58, 162, 162, 247, 1, 131, 11, 3, 202, 205, 47, 124, 162, 212, 165, 165, 206, 32, 164, 93, 184, 125, 189, 134, 128, 11, 189, 98, 160, 210, 188, 130, 243, 236, 208, 92, 231, 181, 136, 77, 185, 101, 186, 21, 96, 78, 11, 11, 253, 79, 43, 169, 157, 90, 199, 63, 86, 41, 92, 55, 201, 35, 144, 64, 6, 20], port: 60884, idle_timeout_ms: 2769122448738048 };181;[80, 00, 94, 2f, 41, 3e, 01, 40, 91, 65, 86, 28, 2c, 36, 64, a2, c3, 85, 77, e7, a1, 14, 5b, 46, ae, 4c, 9d, fe, ca, e3, e2, e7, 99, 07, 5b, c0, c9, bc, c4, 73, ff, 5c, dc, e3, 9c, 55, 4d, 05, 09, 70, 03, 51, e3, 4d, ea, d2, d9, e2, d6, 36, ff, a8, 35, 74, 93, fd, 93, 7d, 01, 9d, fa, 75, 82, 22, 8a, 4f, f9, f7, d7, 67, 3a, d0, b1, 51, 76, 12, e1, c2, 3a, a2, a2, f7, 01, 83, 0b, 03, ca, cd, 2f, 7c, a2, d4, a5, a5, ce, 20, a4, 5d, b8, 7d, bd, 86, 80, 0b, bd, 62, a0, d2, bc, 82, f3, ec, d0, 5c, e7, b5, 88, 4d, b9, 65, ba, 15, 60, 4e, 0b, 0b, fd, 4f, 2b, a9, 9d, 5a, c7, 3f, 56, 29, 5c, 37, c9, 23, 90, 40, 06, 14, ed, d4, c0, 09, d6, 80, 8f, e5, 87, 00, 00] +udp_open 8453e4840501c0000000000000534e5a0b9112aba0a8cf1fe2effab0989254455601a4b6d8defebb275c1b616458e2016fc8fb7f1268d47a098fa68841252daceda89092e167ac37475acbf2879d83aaacce33e11c1b701ef3f96b3269ccb88e6d3d3d2309957c8649214030957d UdpSessionOpen { session_id: 72606852, flags: 5, address_type: Ipv4, host: [78, 90, 11, 145, 18, 171, 160, 168, 207, 31, 226, 239, 250, 176, 152, 146, 84, 69, 86, 1, 164, 182, 216, 222, 254, 187, 39, 92, 27, 97, 100, 88, 226, 1, 111, 200, 251, 127, 18, 104, 212, 122, 9, 143, 166, 136, 65, 37, 45, 172, 237, 168, 144, 146, 225, 103, 172, 55, 71, 90, 203, 242, 135, 157, 131, 170, 172, 206, 51, 225, 28, 27, 112, 30, 243, 249, 107, 50, 105, 204, 184, 142, 109], port: 15677, idle_timeout_ms: 35 };110;[84, 53, e4, 84, 05, 01, 40, 53, 4e, 5a, 0b, 91, 12, ab, a0, a8, cf, 1f, e2, ef, fa, b0, 98, 92, 54, 45, 56, 01, a4, b6, d8, de, fe, bb, 27, 5c, 1b, 61, 64, 58, e2, 01, 6f, c8, fb, 7f, 12, 68, d4, 7a, 09, 8f, a6, 88, 41, 25, 2d, ac, ed, a8, 90, 92, e1, 67, ac, 37, 47, 5a, cb, f2, 87, 9d, 83, aa, ac, ce, 33, e1, 1c, 1b, 70, 1e, f3, f9, 6b, 32, 69, cc, b8, 8e, 6d, 3d, 3d, 23, 00] +udp_open c000000000c3e4f7802eb20e01c0000000000000697d2bb32b5b5470a861f6fb926efa99d7464eed0398e7907963e2e38a91f288d148ebde3d8cf335d53803388cebab2b72058387ac2ce1eb64d62e4113e9cb3225109993b59204d33b2817c0131c96a63d901dbd6e070b2fb9b1c5d39ea02f82e27cec97a072c9300924da3dc0604066c483adc600b2 UdpSessionOpen { session_id: 12838135, flags: 3060238, address_type: Ipv4, host: [125, 43, 179, 43, 91, 84, 112, 168, 97, 246, 251, 146, 110, 250, 153, 215, 70, 78, 237, 3, 152, 231, 144, 121, 99, 226, 227, 138, 145, 242, 136, 209, 72, 235, 222, 61, 140, 243, 53, 213, 56, 3, 56, 140, 235, 171, 43, 114, 5, 131, 135, 172, 44, 225, 235, 100, 214, 46, 65, 19, 233, 203, 50, 37, 16, 153, 147, 181, 146, 4, 211, 59, 40, 23, 192, 19, 28, 150, 166, 61, 144, 29, 189, 110, 7, 11, 47, 185, 177, 197, 211, 158, 160, 47, 130, 226, 124, 236, 151, 160, 114, 201, 48, 9, 36], port: 55869, idle_timeout_ms: 27092407892028870 };137;[80, c3, e4, f7, 80, 2e, b2, 0e, 01, 40, 69, 7d, 2b, b3, 2b, 5b, 54, 70, a8, 61, f6, fb, 92, 6e, fa, 99, d7, 46, 4e, ed, 03, 98, e7, 90, 79, 63, e2, e3, 8a, 91, f2, 88, d1, 48, eb, de, 3d, 8c, f3, 35, d5, 38, 03, 38, 8c, eb, ab, 2b, 72, 05, 83, 87, ac, 2c, e1, eb, 64, d6, 2e, 41, 13, e9, cb, 32, 25, 10, 99, 93, b5, 92, 04, d3, 3b, 28, 17, c0, 13, 1c, 96, a6, 3d, 90, 1d, bd, 6e, 07, 0b, 2f, b9, b1, c5, d3, 9e, a0, 2f, 82, e2, 7c, ec, 97, a0, 72, c9, 30, 09, 24, da, 3d, c0, 60, 40, 66, c4, 83, ad, c6, 00] +udp_open 804af9a4c01a46a4eec30d880340ec0ecb72cc1fb3f2e4ed8af1b6ac9d01ff3f5472ab22d271d45727179e3c329530f75d85f85d0c51bdf3055d92f37752e47865c38d55ccbae59331b3b2f9014956645e8f6c867fba0f83e27f0ad01de68119ac13d6615f77b6e2c10b944b091fe315bde697db3a1ddc3a982af26eabe11ba980c249fdd6485c70a0d761c1a631359a5852d06e6708e7ac6dcfc00fbde58866c6ddfacb08f6544bb3a762eca851afab691bb4ba30460d6e61733b6feeb0c52c82b957bd617e9f51c545ccc903ac1f9db4cc11bd3bd951e33871018694942b20b4d64b61ae408125ec58f76c7ebdd99148b0af5e54b5dcb59f8c3914e4800016ea4005ba48b2df12 UdpSessionOpen { session_id: 4913572, flags: 7396023588818312, address_type: Domain, host: [14, 203, 114, 204, 31, 179, 242, 228, 237, 138, 241, 182, 172, 157, 1, 255, 63, 84, 114, 171, 34, 210, 113, 212, 87, 39, 23, 158, 60, 50, 149, 48, 247, 93, 133, 248, 93, 12, 81, 189, 243, 5, 93, 146, 243, 119, 82, 228, 120, 101, 195, 141, 85, 204, 186, 229, 147, 49, 179, 178, 249, 1, 73, 86, 100, 94, 143, 108, 134, 127, 186, 15, 131, 226, 127, 10, 208, 29, 230, 129, 25, 172, 19, 214, 97, 95, 119, 182, 226, 193, 11, 148, 75, 9, 31, 227, 21, 189, 230, 151, 219, 58, 29, 220, 58, 152, 42, 242, 110, 171, 225, 27, 169, 128, 194, 73, 253, 214, 72, 92, 112, 160, 215, 97, 193, 166, 49, 53, 154, 88, 82, 208, 110, 103, 8, 231, 172, 109, 207, 192, 15, 189, 229, 136, 102, 198, 221, 250, 203, 8, 246, 84, 75, 179, 167, 98, 236, 168, 81, 175, 171, 105, 27, 180, 186, 48, 70, 13, 110, 97, 115, 59, 111, 238, 176, 197, 44, 130, 185, 87, 189, 97, 126, 159, 81, 197, 69, 204, 201, 3, 172, 31, 157, 180, 204, 17, 189, 59, 217, 81, 227, 56, 113, 1, 134, 148, 148, 43, 32, 180, 214, 75, 97, 174, 64, 129, 37, 236, 88, 247, 108, 126, 189, 217, 145, 72, 176, 175, 94, 84, 181, 220, 181, 159, 140, 57], port: 5348, idle_timeout_ms: 5866 };264;[80, 4a, f9, a4, c0, 1a, 46, a4, ee, c3, 0d, 88, 03, 40, ec, 0e, cb, 72, cc, 1f, b3, f2, e4, ed, 8a, f1, b6, ac, 9d, 01, ff, 3f, 54, 72, ab, 22, d2, 71, d4, 57, 27, 17, 9e, 3c, 32, 95, 30, f7, 5d, 85, f8, 5d, 0c, 51, bd, f3, 05, 5d, 92, f3, 77, 52, e4, 78, 65, c3, 8d, 55, cc, ba, e5, 93, 31, b3, b2, f9, 01, 49, 56, 64, 5e, 8f, 6c, 86, 7f, ba, 0f, 83, e2, 7f, 0a, d0, 1d, e6, 81, 19, ac, 13, d6, 61, 5f, 77, b6, e2, c1, 0b, 94, 4b, 09, 1f, e3, 15, bd, e6, 97, db, 3a, 1d, dc, 3a, 98, 2a, f2, 6e, ab, e1, 1b, a9, 80, c2, 49, fd, d6, 48, 5c, 70, a0, d7, 61, c1, a6, 31, 35, 9a, 58, 52, d0, 6e, 67, 08, e7, ac, 6d, cf, c0, 0f, bd, e5, 88, 66, c6, dd, fa, cb, 08, f6, 54, 4b, b3, a7, 62, ec, a8, 51, af, ab, 69, 1b, b4, ba, 30, 46, 0d, 6e, 61, 73, 3b, 6f, ee, b0, c5, 2c, 82, b9, 57, bd, 61, 7e, 9f, 51, c5, 45, cc, c9, 03, ac, 1f, 9d, b4, cc, 11, bd, 3b, d9, 51, e3, 38, 71, 01, 86, 94, 94, 2b, 20, b4, d6, 4b, 61, ae, 40, 81, 25, ec, 58, f7, 6c, 7e, bd, d9, 91, 48, b0, af, 5e, 54, b5, dc, b5, 9f, 8c, 39, 14, e4, 56, ea, 00] +udp_open c000000100000c45c0000000782e1c15b6c000000000000092bbc5a2086a0b940ea2283ba6192985f78b2d51f4700467bcb2ceee92b38db99c1afb75d2824882d382484cd944f303d7805fc922659f91d21995cb834d25c5184be56112a9186d5a41d1b5cfb9a6fcbe9e9c0c5d8e45d105e91d301a37fd17edd137fe65352c92a25861a868f6b46d16a184cc2cd4e979fccd5fd398128bf4749898733767e2997651478e84ac4cbdcf695242d0c0005b1145407bc54000 reject +udp_open 863d61a88006832d01c000000000000099fc57293a9c12b5b02541a27116cb592ee1ae1518c97c13cc96183e8638426aecaafb888167ba18c7bc2decc563089b477c54c08d6c4202ab60b0c3ecca96dc8a39a40464874ad40cd192da81eb27aaf0969005014e0a180218a491d89782a165eb3b8e616f6817c135987a6c31d3d1679cc10965c5e856b5d4fd9c483914b13f72cdfaeae6158e02f56ae3b4d79edcd84c650e80044ca3d40de52ec0134ad7b664d34e00 UdpSessionOpen { session_id: 104685992, flags: 426797, address_type: Ipv4, host: [252, 87, 41, 58, 156, 18, 181, 176, 37, 65, 162, 113, 22, 203, 89, 46, 225, 174, 21, 24, 201, 124, 19, 204, 150, 24, 62, 134, 56, 66, 106, 236, 170, 251, 136, 129, 103, 186, 24, 199, 188, 45, 236, 197, 99, 8, 155, 71, 124, 84, 192, 141, 108, 66, 2, 171, 96, 176, 195, 236, 202, 150, 220, 138, 57, 164, 4, 100, 135, 74, 212, 12, 209, 146, 218, 129, 235, 39, 170, 240, 150, 144, 5, 1, 78, 10, 24, 2, 24, 164, 145, 216, 151, 130, 161, 101, 235, 59, 142, 97, 111, 104, 23, 193, 53, 152, 122, 108, 49, 211, 209, 103, 156, 193, 9, 101, 197, 232, 86, 181, 212, 253, 156, 72, 57, 20, 177, 63, 114, 205, 250, 234, 230, 21, 142, 2, 245, 106, 227, 180, 215, 158, 220, 216, 76, 101, 14, 128, 4, 76, 163, 212, 13], port: 58670, idle_timeout_ms: 5430314895987534 };181;[86, 3d, 61, a8, 80, 06, 83, 2d, 01, 40, 99, fc, 57, 29, 3a, 9c, 12, b5, b0, 25, 41, a2, 71, 16, cb, 59, 2e, e1, ae, 15, 18, c9, 7c, 13, cc, 96, 18, 3e, 86, 38, 42, 6a, ec, aa, fb, 88, 81, 67, ba, 18, c7, bc, 2d, ec, c5, 63, 08, 9b, 47, 7c, 54, c0, 8d, 6c, 42, 02, ab, 60, b0, c3, ec, ca, 96, dc, 8a, 39, a4, 04, 64, 87, 4a, d4, 0c, d1, 92, da, 81, eb, 27, aa, f0, 96, 90, 05, 01, 4e, 0a, 18, 02, 18, a4, 91, d8, 97, 82, a1, 65, eb, 3b, 8e, 61, 6f, 68, 17, c1, 35, 98, 7a, 6c, 31, d3, d1, 67, 9c, c1, 09, 65, c5, e8, 56, b5, d4, fd, 9c, 48, 39, 14, b1, 3f, 72, cd, fa, ea, e6, 15, 8e, 02, f5, 6a, e3, b4, d7, 9e, dc, d8, 4c, 65, 0e, 80, 04, 4c, a3, d4, 0d, e5, 2e, c0, 13, 4a, d7, b6, 64, d3, 4e, 00] +udp_open 800611f0c006f1337e8e52237040e07badcb9d991653103dfbb3579b6dff14f108b6c9054b5aa387ab860ad80cea4a85162539af905b7407b937d4d2e69705449220ddd59b169fea012b5a9b23111efbff538f2d95a22d136d575448ea94fa155369f5dea727f3de473a4bd422669f57bd10efe9bc927a4503d8295f75384c911d3a02d4bc1c3cd77e83359ce8404946e8a2d044222c077c626612f1f099f7f6884c50d89fba3bdaa2bc12ce190cd2773c09d73c58df8eccecccafc53e7691e783ea150b6ce7ae376ce2121a80a109b381ae60a07f414c0aaeff79f7ea740d121c7d225e9ceae232bf30ed74235aca1e60c00000000efae85440043a7c3dff reject +udp_open 80000000c00000000000000004c0000000000000b2e3a1d4f1d59afcb5e48f646c2e4acc2ce533f3612cdc6bd4fa20d860f2dc8e9f3eb9254bebf524352165eaaa282f073bfd66316defd9699d4563888108ce7d92ad7adfb9397134fbe88d0681cfe7172163a9e3784c7eeeaab38631620c9a2a6e26d8d2418f8fd2e1ea3ab9216b32fc5e5aeab16e3e3c4fcd1b1c72688abeeb713b0c3d62e1506775d05f0a0a8dbc1a7d8a0081ecb44da3fc7d579f0e9234748c21edf367a25fab0ecb4dd631189044f735f587d1c00000082ed82169800000079430ea3def4cf444e938acb1 UdpSessionOpen { session_id: 0, flags: 0, address_type: Ipv6, host: [227, 161, 212, 241, 213, 154, 252, 181, 228, 143, 100, 108, 46, 74, 204, 44, 229, 51, 243, 97, 44, 220, 107, 212, 250, 32, 216, 96, 242, 220, 142, 159, 62, 185, 37, 75, 235, 245, 36, 53, 33, 101, 234, 170, 40, 47, 7, 59, 253, 102, 49, 109, 239, 217, 105, 157, 69, 99, 136, 129, 8, 206, 125, 146, 173, 122, 223, 185, 57, 113, 52, 251, 232, 141, 6, 129, 207, 231, 23, 33, 99, 169, 227, 120, 76, 126, 238, 170, 179, 134, 49, 98, 12, 154, 42, 110, 38, 216, 210, 65, 143, 143, 210, 225, 234, 58, 185, 33, 107, 50, 252, 94, 90, 234, 177, 110, 62, 60, 79, 205, 27, 28, 114, 104, 138, 190, 235, 113, 59, 12, 61, 98, 225, 80, 103, 117, 208, 95, 10, 10, 141, 188, 26, 125, 138, 0, 129, 236, 180, 77, 163, 252, 125, 87, 159, 14, 146, 52, 116, 140, 33, 237, 243, 103, 162, 95, 171, 14, 203, 77, 214, 49, 24, 144, 68, 247, 53, 245], port: 34769, idle_timeout_ms: 35145654633 };220;[00, 00, 04, 40, b2, e3, a1, d4, f1, d5, 9a, fc, b5, e4, 8f, 64, 6c, 2e, 4a, cc, 2c, e5, 33, f3, 61, 2c, dc, 6b, d4, fa, 20, d8, 60, f2, dc, 8e, 9f, 3e, b9, 25, 4b, eb, f5, 24, 35, 21, 65, ea, aa, 28, 2f, 07, 3b, fd, 66, 31, 6d, ef, d9, 69, 9d, 45, 63, 88, 81, 08, ce, 7d, 92, ad, 7a, df, b9, 39, 71, 34, fb, e8, 8d, 06, 81, cf, e7, 17, 21, 63, a9, e3, 78, 4c, 7e, ee, aa, b3, 86, 31, 62, 0c, 9a, 2a, 6e, 26, d8, d2, 41, 8f, 8f, d2, e1, ea, 3a, b9, 21, 6b, 32, fc, 5e, 5a, ea, b1, 6e, 3e, 3c, 4f, cd, 1b, 1c, 72, 68, 8a, be, eb, 71, 3b, 0c, 3d, 62, e1, 50, 67, 75, d0, 5f, 0a, 0a, 8d, bc, 1a, 7d, 8a, 00, 81, ec, b4, 4d, a3, fc, 7d, 57, 9f, 0e, 92, 34, 74, 8c, 21, ed, f3, 67, a2, 5f, ab, 0e, cb, 4d, d6, 31, 18, 90, 44, f7, 35, f5, 87, d1, c0, 00, 00, 08, 2e, d8, 21, 69, 00] +udp_open 40019e34db63dc407238315b0d0c6a39110afb7e7f0c51510740ecb156bd6636d483c4a135e863c42e3315db15485823fba1f79d7b60483b4d19a7079920e8b01715a161fde8d1b654534b2db45d1b1a25724bfe6b2e45d6104e41764e2cdb22337455b9b7051d3cc166204ac109542c168353674cdfd5b342b1f02a62401ac000000000000000 reject +udp_open 40ddc00014d289f78fbb0480000008c89f490fa4449a76352ac1f6650ad6ca4dc600 UdpSessionOpen { session_id: 221, flags: 22894490390459, address_type: Ipv6, host: [200, 159, 73, 15, 164, 68, 154, 118], port: 13610, idle_timeout_ms: 141411535536410054 };34;[40, dd, c0, 00, 14, d2, 89, f7, 8f, bb, 04, 08, c8, 9f, 49, 0f, a4, 44, 9a, 76, 35, 2a, c1, f6, 65, 0a, d6, ca, 4d, c6, 00] +udp_open c0000000000004c3c14ac6dcb9b39c55bf800000ac0f292b7f77188ec0e6d7b09da8cf95f392fb181a35d142df536fb74937a9c1c926afac2eca5ce61bf660b2d1b17fc6ed4e463bcb594ee2fa62a00789ff3dd47067c79ced8d696da08e5e0b1eb8347de15b17d5d789608e8da0f18a73675b96b1a31699f054ad2e0c34fdc11e967ebaf626346e645eb330438647d9ff5b5821159a1c10ab38af39d1d63dc1272a62000683ec40d84aad357106db24012b5932e2491532f4b516e7349664facfa2a0c0038287a1d0885140021004 reject +udp_open a13060d280f1198d03c0000000000000e1e8aa1748bb6e76db79c7980e0513e2f61f38697570af138d3801c72848426f381ec527a84631625492f25f087cb8d5628e843382aa225c94978540938fa8fe3da53c097bd185d25fdc57e3b973abe24e526b2e23003f1a0f4b11e449c576ad9acd1dc2f9931fedb33afbd50d690bc78991f87713559a71ce19349754a4bd144cefc3f6ba810b4a68ccbc4490e2404f44908eddeb05bbd9317343711b0281e517773beabec732e9654fde62fa6559aa7ebf9504e06520568f872e46a2261d91cd6cae15ad4b8049935996c2ffbc4115a0fa0df1e5cfcb1f1c130a134ca4bf59b3933fa682097b1dc000000000000000 UdpSessionOpen { session_id: 556818642, flags: 15800717, address_type: Domain, host: [232, 170, 23, 72, 187, 110, 118, 219, 121, 199, 152, 14, 5, 19, 226, 246, 31, 56, 105, 117, 112, 175, 19, 141, 56, 1, 199, 40, 72, 66, 111, 56, 30, 197, 39, 168, 70, 49, 98, 84, 146, 242, 95, 8, 124, 184, 213, 98, 142, 132, 51, 130, 170, 34, 92, 148, 151, 133, 64, 147, 143, 168, 254, 61, 165, 60, 9, 123, 209, 133, 210, 95, 220, 87, 227, 185, 115, 171, 226, 78, 82, 107, 46, 35, 0, 63, 26, 15, 75, 17, 228, 73, 197, 118, 173, 154, 205, 29, 194, 249, 147, 31, 237, 179, 58, 251, 213, 13, 105, 11, 199, 137, 145, 248, 119, 19, 85, 154, 113, 206, 25, 52, 151, 84, 164, 189, 20, 76, 239, 195, 246, 186, 129, 11, 74, 104, 204, 188, 68, 144, 226, 64, 79, 68, 144, 142, 221, 235, 5, 187, 217, 49, 115, 67, 113, 27, 2, 129, 229, 23, 119, 59, 234, 190, 199, 50, 233, 101, 79, 222, 98, 250, 101, 89, 170, 126, 191, 149, 4, 224, 101, 32, 86, 143, 135, 46, 70, 162, 38, 29, 145, 205, 108, 174, 21, 173, 75, 128, 73, 147, 89, 150, 194, 255, 188, 65, 21, 160, 250, 13, 241, 229, 207, 203, 31, 28, 19, 10, 19, 76, 164, 191, 89, 179, 147], port: 16294, idle_timeout_ms: 34175773 };256;[a1, 30, 60, d2, 80, f1, 19, 8d, 03, 40, e1, e8, aa, 17, 48, bb, 6e, 76, db, 79, c7, 98, 0e, 05, 13, e2, f6, 1f, 38, 69, 75, 70, af, 13, 8d, 38, 01, c7, 28, 48, 42, 6f, 38, 1e, c5, 27, a8, 46, 31, 62, 54, 92, f2, 5f, 08, 7c, b8, d5, 62, 8e, 84, 33, 82, aa, 22, 5c, 94, 97, 85, 40, 93, 8f, a8, fe, 3d, a5, 3c, 09, 7b, d1, 85, d2, 5f, dc, 57, e3, b9, 73, ab, e2, 4e, 52, 6b, 2e, 23, 00, 3f, 1a, 0f, 4b, 11, e4, 49, c5, 76, ad, 9a, cd, 1d, c2, f9, 93, 1f, ed, b3, 3a, fb, d5, 0d, 69, 0b, c7, 89, 91, f8, 77, 13, 55, 9a, 71, ce, 19, 34, 97, 54, a4, bd, 14, 4c, ef, c3, f6, ba, 81, 0b, 4a, 68, cc, bc, 44, 90, e2, 40, 4f, 44, 90, 8e, dd, eb, 05, bb, d9, 31, 73, 43, 71, 1b, 02, 81, e5, 17, 77, 3b, ea, be, c7, 32, e9, 65, 4f, de, 62, fa, 65, 59, aa, 7e, bf, 95, 04, e0, 65, 20, 56, 8f, 87, 2e, 46, a2, 26, 1d, 91, cd, 6c, ae, 15, ad, 4b, 80, 49, 93, 59, 96, c2, ff, bc, 41, 15, a0, fa, 0d, f1, e5, cf, cb, 1f, 1c, 13, 0a, 13, 4c, a4, bf, 59, b3, 93, 3f, a6, 82, 09, 7b, 1d, 00] +udp_open c0000001000000004108044100214521ba3bbfe86c0d8737892dbcffb88d8108993bc887780ab4f11bddabc5a9e0438a2129768d06c05650c92364e3785da37274b61b7b6c814ed5c6fc94241fd69152e6c23e07dc8d4e1f1f6504119111397eb51fb944cdfa0ccf84b4df26fefbc77dcea54786ca6eb1a6ebbc1473c18a72d0e23def77b1f53408114c80c1502b3862c34b5d2c298124b7dcb067024937c4a9130e2d452d60c033a07e28fbae57eb8e0d456117fd0b2ae774eb812518d426fda60e1eb7e632187c5248586cd5a818e3371155685bb8634ad6e630d26a9a41629dfbe3d2afcbab5943458e352dcb25d4e7445c169aa11dfe29e03a6b6bfa8e817d6d6b5e03e061701a30a81f1a34cc800000084000 reject +udp_open 8063526f81aeaad501c0000000000000e14ea73dd031429c7ea5665aff199e60caaf03872f244f3d5395e5f5eaa6b63d7e0082d78b667f214bc6fe967f608c4386be981cd91458c711623927dc9c162830d48cd962dfccca008108f357f724a7d7974456626227c038ae83fea7574e53aceb48e56a0af74f4811828b83947e1c1973cc9765e01c1eb4f2a69ff5c418e89bb77106ad7bc912005bbc7f413ae0c5a96334bff8dd1ee051d4fff706b151f50af4112da2cc12b51f7373f88a10f0a2e670d5cef983e103a7294dce03a439f3688a802880a0dc58aabcf3edb727aee22e048d03876b362141212df02e90341bbe1e9107c0a67e0910df2cae4000 UdpSessionOpen { session_id: 6509167, flags: 28224213, address_type: Ipv4, host: [78, 167, 61, 208, 49, 66, 156, 126, 165, 102, 90, 255, 25, 158, 96, 202, 175, 3, 135, 47, 36, 79, 61, 83, 149, 229, 245, 234, 166, 182, 61, 126, 0, 130, 215, 139, 102, 127, 33, 75, 198, 254, 150, 127, 96, 140, 67, 134, 190, 152, 28, 217, 20, 88, 199, 17, 98, 57, 39, 220, 156, 22, 40, 48, 212, 140, 217, 98, 223, 204, 202, 0, 129, 8, 243, 87, 247, 36, 167, 215, 151, 68, 86, 98, 98, 39, 192, 56, 174, 131, 254, 167, 87, 78, 83, 172, 235, 72, 229, 106, 10, 247, 79, 72, 17, 130, 139, 131, 148, 126, 28, 25, 115, 204, 151, 101, 224, 28, 30, 180, 242, 166, 159, 245, 196, 24, 232, 155, 183, 113, 6, 173, 123, 201, 18, 0, 91, 188, 127, 65, 58, 224, 197, 169, 99, 52, 191, 248, 221, 30, 224, 81, 212, 255, 247, 6, 177, 81, 245, 10, 244, 17, 45, 162, 204, 18, 181, 31, 115, 115, 248, 138, 16, 240, 162, 230, 112, 213, 206, 249, 131, 225, 3, 167, 41, 77, 206, 3, 164, 57, 243, 104, 138, 128, 40, 128, 160, 220, 88, 170, 188, 243, 237, 183, 39, 174, 226, 46, 4, 141, 3, 135, 107, 54, 33, 65, 33, 45, 240, 46, 144, 52, 27, 190, 30], port: 37127, idle_timeout_ms: 46863423536835758 };254;[80, 63, 52, 6f, 81, ae, aa, d5, 01, 40, e1, 4e, a7, 3d, d0, 31, 42, 9c, 7e, a5, 66, 5a, ff, 19, 9e, 60, ca, af, 03, 87, 2f, 24, 4f, 3d, 53, 95, e5, f5, ea, a6, b6, 3d, 7e, 00, 82, d7, 8b, 66, 7f, 21, 4b, c6, fe, 96, 7f, 60, 8c, 43, 86, be, 98, 1c, d9, 14, 58, c7, 11, 62, 39, 27, dc, 9c, 16, 28, 30, d4, 8c, d9, 62, df, cc, ca, 00, 81, 08, f3, 57, f7, 24, a7, d7, 97, 44, 56, 62, 62, 27, c0, 38, ae, 83, fe, a7, 57, 4e, 53, ac, eb, 48, e5, 6a, 0a, f7, 4f, 48, 11, 82, 8b, 83, 94, 7e, 1c, 19, 73, cc, 97, 65, e0, 1c, 1e, b4, f2, a6, 9f, f5, c4, 18, e8, 9b, b7, 71, 06, ad, 7b, c9, 12, 00, 5b, bc, 7f, 41, 3a, e0, c5, a9, 63, 34, bf, f8, dd, 1e, e0, 51, d4, ff, f7, 06, b1, 51, f5, 0a, f4, 11, 2d, a2, cc, 12, b5, 1f, 73, 73, f8, 8a, 10, f0, a2, e6, 70, d5, ce, f9, 83, e1, 03, a7, 29, 4d, ce, 03, a4, 39, f3, 68, 8a, 80, 28, 80, a0, dc, 58, aa, bc, f3, ed, b7, 27, ae, e2, 2e, 04, 8d, 03, 87, 6b, 36, 21, 41, 21, 2d, f0, 2e, 90, 34, 1b, be, 1e, 91, 07, c0, a6, 7e, 09, 10, df, 2c, ae, 00] +udp_open 844fd9b4c00003645d206be70480000080a4485db0a3a8b03c12d7d6d477bcdaccd7de0d49aae853e76e380401b324f579947bb5aaece46f947f10cf1b5053db8bb295874b1308d22e18493bda40391f5493308f8b4e2e835bf2bce3639c3daa3800b61d06d1a5259659e79d577fa2f655b3c3da0aa2fea5fd526babe629d84ade7721de1244f34266480d4f489ebc1c2a6ef3c0000ed2030ece2b400985615f3e3d2ed1981f UdpSessionOpen { session_id: 72341940, flags: 3729594018791, address_type: Ipv6, host: [164, 72, 93, 176, 163, 168, 176, 60, 18, 215, 214, 212, 119, 188, 218, 204, 215, 222, 13, 73, 170, 232, 83, 231, 110, 56, 4, 1, 179, 36, 245, 121, 148, 123, 181, 170, 236, 228, 111, 148, 127, 16, 207, 27, 80, 83, 219, 139, 178, 149, 135, 75, 19, 8, 210, 46, 24, 73, 59, 218, 64, 57, 31, 84, 147, 48, 143, 139, 78, 46, 131, 91, 242, 188, 227, 99, 156, 61, 170, 56, 0, 182, 29, 6, 209, 165, 37, 150, 89, 231, 157, 87, 127, 162, 246, 85, 179, 195, 218, 10, 162, 254, 165, 253, 82, 107, 171, 230, 41, 216, 74, 222, 119, 33, 222, 18, 68, 243, 66, 102, 72, 13, 79, 72, 158, 188, 28, 42], port: 28403, idle_timeout_ms: 16295157222955 };166;[84, 4f, d9, b4, c0, 00, 03, 64, 5d, 20, 6b, e7, 04, 40, 80, a4, 48, 5d, b0, a3, a8, b0, 3c, 12, d7, d6, d4, 77, bc, da, cc, d7, de, 0d, 49, aa, e8, 53, e7, 6e, 38, 04, 01, b3, 24, f5, 79, 94, 7b, b5, aa, ec, e4, 6f, 94, 7f, 10, cf, 1b, 50, 53, db, 8b, b2, 95, 87, 4b, 13, 08, d2, 2e, 18, 49, 3b, da, 40, 39, 1f, 54, 93, 30, 8f, 8b, 4e, 2e, 83, 5b, f2, bc, e3, 63, 9c, 3d, aa, 38, 00, b6, 1d, 06, d1, a5, 25, 96, 59, e7, 9d, 57, 7f, a2, f6, 55, b3, c3, da, 0a, a2, fe, a5, fd, 52, 6b, ab, e6, 29, d8, 4a, de, 77, 21, de, 12, 44, f3, 42, 66, 48, 0d, 4f, 48, 9e, bc, 1c, 2a, 6e, f3, c0, 00, 0e, d2, 03, 0e, ce, 2b, 00] +udp_open c0000000000105848000000001800000597c86033f694f7423f1d4779b6d262f36614d95827daa8cc2004229cbd0b3f186f6acc65d75e375e4a791dd661a0969e94ccea889b757cd8d3acc6483a99ce0c34426227dbdd1ecb3fb5fffef67ba4d5bd7b1d18a4973f83802988f80efdd1c00 UdpSessionOpen { session_id: 66948, flags: 0, address_type: Ipv4, host: [124, 134, 3, 63, 105, 79, 116, 35, 241, 212, 119, 155, 109, 38, 47, 54, 97, 77, 149, 130, 125, 170, 140, 194, 0, 66, 41, 203, 208, 179, 241, 134, 246, 172, 198, 93, 117, 227, 117, 228, 167, 145, 221, 102, 26, 9, 105, 233, 76, 206, 168, 137, 183, 87, 205, 141, 58, 204, 100, 131, 169, 156, 224, 195, 68, 38, 34, 125, 189, 209, 236, 179, 251, 95, 255, 239, 103, 186, 77, 91, 215, 177, 209, 138, 73, 115, 248, 56, 2], port: 39055, idle_timeout_ms: 15719708 };113;[80, 01, 05, 84, 00, 01, 40, 59, 7c, 86, 03, 3f, 69, 4f, 74, 23, f1, d4, 77, 9b, 6d, 26, 2f, 36, 61, 4d, 95, 82, 7d, aa, 8c, c2, 00, 42, 29, cb, d0, b3, f1, 86, f6, ac, c6, 5d, 75, e3, 75, e4, a7, 91, dd, 66, 1a, 09, 69, e9, 4c, ce, a8, 89, b7, 57, cd, 8d, 3a, cc, 64, 83, a9, 9c, e0, c3, 44, 26, 22, 7d, bd, d1, ec, b3, fb, 5f, ff, ef, 67, ba, 4d, 5b, d7, b1, d1, 8a, 49, 73, f8, 38, 02, 98, 8f, 80, ef, dd, 1c, 00] +udp_open c00000010000000680005ec3014041e2560cc947c3691ac719e71753d48470bd3c7b4a02d5387d67da0fdccc8517d3841bfe1a533a0952dcd81fe26e58fe2b8d6f1ca599d1bfb1ab3f345eabeb9213676e52800efd2900 reject +udp_open 80058d63cfd94ba87a5e06c803406c1d13f6c6abadd457be5d1d562c57ed533fe7693030af7c4019698d325fe996f618ff831ae210fb4e1bc0a69fd9bf5c78cd787616c0ed060bf933aeb1b77a7c7e20607be44214c6317fe2df6732e42c7aa3726cd30883a4910ee25549a9aef52e966ad0f9f2664cc0b6a112f6ed1980082aa54000 UdpSessionOpen { session_id: 363875, flags: 1142027167494702792, address_type: Domain, host: [29, 19, 246, 198, 171, 173, 212, 87, 190, 93, 29, 86, 44, 87, 237, 83, 63, 231, 105, 48, 48, 175, 124, 64, 25, 105, 141, 50, 95, 233, 150, 246, 24, 255, 131, 26, 226, 16, 251, 78, 27, 192, 166, 159, 217, 191, 92, 120, 205, 120, 118, 22, 192, 237, 6, 11, 249, 51, 174, 177, 183, 122, 124, 126, 32, 96, 123, 228, 66, 20, 198, 49, 127, 226, 223, 103, 50, 228, 44, 122, 163, 114, 108, 211, 8, 131, 164, 145, 14, 226, 85, 73, 169, 174, 245, 46, 150, 106, 208, 249, 242, 102, 76, 192, 182, 161, 18, 246], port: 60697, idle_timeout_ms: 535205 };131;[80, 05, 8d, 63, cf, d9, 4b, a8, 7a, 5e, 06, c8, 03, 40, 6c, 1d, 13, f6, c6, ab, ad, d4, 57, be, 5d, 1d, 56, 2c, 57, ed, 53, 3f, e7, 69, 30, 30, af, 7c, 40, 19, 69, 8d, 32, 5f, e9, 96, f6, 18, ff, 83, 1a, e2, 10, fb, 4e, 1b, c0, a6, 9f, d9, bf, 5c, 78, cd, 78, 76, 16, c0, ed, 06, 0b, f9, 33, ae, b1, b7, 7a, 7c, 7e, 20, 60, 7b, e4, 42, 14, c6, 31, 7f, e2, df, 67, 32, e4, 2c, 7a, a3, 72, 6c, d3, 08, 83, a4, 91, 0e, e2, 55, 49, a9, ae, f5, 2e, 96, 6a, d0, f9, f2, 66, 4c, c0, b6, a1, 12, f6, ed, 19, 80, 08, 2a, a5, 00] +udp_open c0000000a87bd6ae8034d7840640d07cd41f05fc2bb9c3254a348c32abdc5f3ef7b5de4dd515a0eb6bc63aa4126cdf4fae0900b775ae26b08d8e689d2321bbe6d3d52e36f9692a9437abe6d06235e2037566b4f817efbcfe4394c543eba8e21e0ce8d6ae9142b47037fcfcfceda5267534e7764fce6089ae743a863fcdc36c3e189f6c0760753c241db84cafab78267c222bde55737ce723ec2e3c754e105168da43acdca61ed13b50e021e6a4de1b8cc4f19ca25de88ebb1ae48cd081726d889a0c257b3e9ac3b8ae8742d7a7261825d0dae115e279ee5f212726d458a0704d1ede5f5507f299901a0cdb12ac003638b0db2d5f006d reject +udp_open 4075c00064acddd781bf04800000e72de99bb274c851210934667fdf1518e806a28485af429a0979065db79470129b7826e63ef066cfc08e9d5392c5b8e512045dfc65b8eb8d57828c18d7aac5ad17e2c2fabafd28d0b40fb8424fabe8310e010f5f9c63361699d329ee330d551882bad3a3a6c0752856e1503e66eb832813774605781b9d31a2417fb998dc3cbcf9e2e1cbb33f480a28216038f6d9c85f1c3a2c3784d53182146ddeaf354771a9126c58be357b8b805441c01b0fe091b3f7ee53387f8b189be597d38615f99d6e76dabc6745df2df8b7a14af98f2956d78e91392dfb3cb59720bc94c7740e2b0a17a279331d66ddea575dc000d9981f72839cc000000000000000 UdpSessionOpen { session_id: 117, flags: 110693619040703, address_type: Ipv6, host: [45, 233, 155, 178, 116, 200, 81, 33, 9, 52, 102, 127, 223, 21, 24, 232, 6, 162, 132, 133, 175, 66, 154, 9, 121, 6, 93, 183, 148, 112, 18, 155, 120, 38, 230, 62, 240, 102, 207, 192, 142, 157, 83, 146, 197, 184, 229, 18, 4, 93, 252, 101, 184, 235, 141, 87, 130, 140, 24, 215, 170, 197, 173, 23, 226, 194, 250, 186, 253, 40, 208, 180, 15, 184, 66, 79, 171, 232, 49, 14, 1, 15, 95, 156, 99, 54, 22, 153, 211, 41, 238, 51, 13, 85, 24, 130, 186, 211, 163, 166, 192, 117, 40, 86, 225, 80, 62, 102, 235, 131, 40, 19, 119, 70, 5, 120, 27, 157, 49, 162, 65, 127, 185, 152, 220, 60, 188, 249, 226, 225, 203, 179, 63, 72, 10, 40, 33, 96, 56, 246, 217, 200, 95, 28, 58, 44, 55, 132, 213, 49, 130, 20, 109, 222, 175, 53, 71, 113, 169, 18, 108, 88, 190, 53, 123, 139, 128, 84, 65, 192, 27, 15, 224, 145, 179, 247, 238, 83, 56, 127, 139, 24, 155, 229, 151, 211, 134, 21, 249, 157, 110, 118, 218, 188, 103, 69, 223, 45, 248, 183, 161, 74, 249, 143, 41, 86, 215, 142, 145, 57, 45, 251, 60, 181, 151, 32, 188, 148, 199, 116, 14, 43, 10, 23, 162, 121, 51, 29, 102, 221, 234], port: 22365, idle_timeout_ms: 239247385854876 };264;[40, 75, c0, 00, 64, ac, dd, d7, 81, bf, 04, 40, e7, 2d, e9, 9b, b2, 74, c8, 51, 21, 09, 34, 66, 7f, df, 15, 18, e8, 06, a2, 84, 85, af, 42, 9a, 09, 79, 06, 5d, b7, 94, 70, 12, 9b, 78, 26, e6, 3e, f0, 66, cf, c0, 8e, 9d, 53, 92, c5, b8, e5, 12, 04, 5d, fc, 65, b8, eb, 8d, 57, 82, 8c, 18, d7, aa, c5, ad, 17, e2, c2, fa, ba, fd, 28, d0, b4, 0f, b8, 42, 4f, ab, e8, 31, 0e, 01, 0f, 5f, 9c, 63, 36, 16, 99, d3, 29, ee, 33, 0d, 55, 18, 82, ba, d3, a3, a6, c0, 75, 28, 56, e1, 50, 3e, 66, eb, 83, 28, 13, 77, 46, 05, 78, 1b, 9d, 31, a2, 41, 7f, b9, 98, dc, 3c, bc, f9, e2, e1, cb, b3, 3f, 48, 0a, 28, 21, 60, 38, f6, d9, c8, 5f, 1c, 3a, 2c, 37, 84, d5, 31, 82, 14, 6d, de, af, 35, 47, 71, a9, 12, 6c, 58, be, 35, 7b, 8b, 80, 54, 41, c0, 1b, 0f, e0, 91, b3, f7, ee, 53, 38, 7f, 8b, 18, 9b, e5, 97, d3, 86, 15, f9, 9d, 6e, 76, da, bc, 67, 45, df, 2d, f8, b7, a1, 4a, f9, 8f, 29, 56, d7, 8e, 91, 39, 2d, fb, 3c, b5, 97, 20, bc, 94, c7, 74, 0e, 2b, 0a, 17, a2, 79, 33, 1d, 66, dd, ea, 57, 5d, c0, 00, d9, 98, 1f, 72, 83, 9c, 00] +udp_open 0ac006ee9af4950a0803800000c21617a3567191aba33a6138309819762668b8895f4eef478be8941383b223925283875cd47bec57cdad2fff20134d1027c3880256e92fcf95f2909850251cb90d53cd5d6ba9ce890460ab866fd3c2387630f0d51fa9089c7e9345da507c9a825a3d24c0a9544f9d3ef44637323331931e1912b776838d283268efb461dea2d78c9209054a48ed1d00aa0b9530ab827ec66e59d1e43eb693b04c0b01562e90fac427b3c58e692d1a37d8b8489f3749ddb5472123801221e6ca3e9114a307544a28e2cb3c1b420d009b260267a1258d UdpSessionOpen { session_id: 10, flags: 1951199156046344, address_type: Domain, host: [22, 23, 163, 86, 113, 145, 171, 163, 58, 97, 56, 48, 152, 25, 118, 38, 104, 184, 137, 95, 78, 239, 71, 139, 232, 148, 19, 131, 178, 35, 146, 82, 131, 135, 92, 212, 123, 236, 87, 205, 173, 47, 255, 32, 19, 77, 16, 39, 195, 136, 2, 86, 233, 47, 207, 149, 242, 144, 152, 80, 37, 28, 185, 13, 83, 205, 93, 107, 169, 206, 137, 4, 96, 171, 134, 111, 211, 194, 56, 118, 48, 240, 213, 31, 169, 8, 156, 126, 147, 69, 218, 80, 124, 154, 130, 90, 61, 36, 192, 169, 84, 79, 157, 62, 244, 70, 55, 50, 51, 49, 147, 30, 25, 18, 183, 118, 131, 141, 40, 50, 104, 239, 180, 97, 222, 162, 215, 140, 146, 9, 5, 74, 72, 237, 29, 0, 170, 11, 149, 48, 171, 130, 126, 198, 110, 89, 209, 228, 62, 182, 147, 176, 76, 11, 1, 86, 46, 144, 250, 196, 39, 179, 197, 142, 105, 45, 26, 55, 216, 184, 72, 159, 55, 73, 221, 181, 71, 33, 35, 128, 18, 33, 230, 202, 62, 145, 20, 163, 7, 84, 74, 40, 226, 203], port: 15387, idle_timeout_ms: 525 };213;[0a, c0, 06, ee, 9a, f4, 95, 0a, 08, 03, 40, c2, 16, 17, a3, 56, 71, 91, ab, a3, 3a, 61, 38, 30, 98, 19, 76, 26, 68, b8, 89, 5f, 4e, ef, 47, 8b, e8, 94, 13, 83, b2, 23, 92, 52, 83, 87, 5c, d4, 7b, ec, 57, cd, ad, 2f, ff, 20, 13, 4d, 10, 27, c3, 88, 02, 56, e9, 2f, cf, 95, f2, 90, 98, 50, 25, 1c, b9, 0d, 53, cd, 5d, 6b, a9, ce, 89, 04, 60, ab, 86, 6f, d3, c2, 38, 76, 30, f0, d5, 1f, a9, 08, 9c, 7e, 93, 45, da, 50, 7c, 9a, 82, 5a, 3d, 24, c0, a9, 54, 4f, 9d, 3e, f4, 46, 37, 32, 33, 31, 93, 1e, 19, 12, b7, 76, 83, 8d, 28, 32, 68, ef, b4, 61, de, a2, d7, 8c, 92, 09, 05, 4a, 48, ed, 1d, 00, aa, 0b, 95, 30, ab, 82, 7e, c6, 6e, 59, d1, e4, 3e, b6, 93, b0, 4c, 0b, 01, 56, 2e, 90, fa, c4, 27, b3, c5, 8e, 69, 2d, 1a, 37, d8, b8, 48, 9f, 37, 49, dd, b5, 47, 21, 23, 80, 12, 21, e6, ca, 3e, 91, 14, a3, 07, 54, 4a, 28, e2, cb, 3c, 1b, 42, 0d, 00] +udp_open 80016a2a80e3723a01800000a540d37d0628c642937788aa32e5b1fe2cebd6f79baa9083e32e9a05d2526046826b40f4acdce621fa3bb6b63d984aa7dd6d5f710747cca65bbce7a044a70faa0d559c56cc6407c2953ba1a44d29c0e575aee2857b749759c098aec715c7e9e91bc9a64f84b2ccbba344975972f93392764bedc08189e7f0c46eab3c621bf1d3d9d1e7353f64a9f80979393006f5be0f1964db0fef5c28eb168889da5bce13f4661a2e06e1a752b6d2c60af5d8265ee700 UdpSessionOpen { session_id: 92714, flags: 14905914, address_type: Ipv4, host: [64, 211, 125, 6, 40, 198, 66, 147, 119, 136, 170, 50, 229, 177, 254, 44, 235, 214, 247, 155, 170, 144, 131, 227, 46, 154, 5, 210, 82, 96, 70, 130, 107, 64, 244, 172, 220, 230, 33, 250, 59, 182, 182, 61, 152, 74, 167, 221, 109, 95, 113, 7, 71, 204, 166, 91, 188, 231, 160, 68, 167, 15, 170, 13, 85, 156, 86, 204, 100, 7, 194, 149, 59, 161, 164, 77, 41, 192, 229, 117, 174, 226, 133, 123, 116, 151, 89, 192, 152, 174, 199, 21, 199, 233, 233, 27, 201, 166, 79, 132, 178, 204, 187, 163, 68, 151, 89, 114, 249, 51, 146, 118, 75, 237, 192, 129, 137, 231, 240, 196, 110, 171, 60, 98, 27, 241, 211, 217, 209, 231, 53, 63, 100, 169, 248, 9, 121, 57, 48, 6, 245, 190, 15, 25, 100, 219, 15, 239, 92, 40, 235, 22, 136, 137, 218, 91, 206, 19, 244, 102, 26, 46, 6, 225, 167], port: 21174, idle_timeout_ms: 1352780789081071335 };189;[80, 01, 6a, 2a, 80, e3, 72, 3a, 01, 40, a5, 40, d3, 7d, 06, 28, c6, 42, 93, 77, 88, aa, 32, e5, b1, fe, 2c, eb, d6, f7, 9b, aa, 90, 83, e3, 2e, 9a, 05, d2, 52, 60, 46, 82, 6b, 40, f4, ac, dc, e6, 21, fa, 3b, b6, b6, 3d, 98, 4a, a7, dd, 6d, 5f, 71, 07, 47, cc, a6, 5b, bc, e7, a0, 44, a7, 0f, aa, 0d, 55, 9c, 56, cc, 64, 07, c2, 95, 3b, a1, a4, 4d, 29, c0, e5, 75, ae, e2, 85, 7b, 74, 97, 59, c0, 98, ae, c7, 15, c7, e9, e9, 1b, c9, a6, 4f, 84, b2, cc, bb, a3, 44, 97, 59, 72, f9, 33, 92, 76, 4b, ed, c0, 81, 89, e7, f0, c4, 6e, ab, 3c, 62, 1b, f1, d3, d9, d1, e7, 35, 3f, 64, a9, f8, 09, 79, 39, 30, 06, f5, be, 0f, 19, 64, db, 0f, ef, 5c, 28, eb, 16, 88, 89, da, 5b, ce, 13, f4, 66, 1a, 2e, 06, e1, a7, 52, b6, d2, c6, 0a, f5, d8, 26, 5e, e7, 00] +udp_open c000000000ad7ad0c05defd8b6e1b3a30440cc8c359a227fb92fa16a4a90eb08b34c26749d737ec352f40d0aca463b743d2f52dd0d0973a6e6d0c16efbe501af0bae45ee8f12e6fe20f4ef39602270b9eb19da9a2b6f63558823f747447a1d366ace2dbb34cbe499719eb456b534d48841d5766e1c9b9102580f1d531858a625b85232f3563a60fa09d42d4a876a9eb050251c3d05d9708253df60e816a4bee4c168d6d95c0d0af74fe4ca80cb7a6bbadc39f6c985c0f67d97b18def765feaff3504b3dc0c3a209395a939d40cf461ca195ea216f9c29a3b41a11c176f44f7fb2fc000000000058d3d80000000 UdpSessionOpen { session_id: 11369168, flags: 26440886894310307, address_type: Ipv6, host: [140, 53, 154, 34, 127, 185, 47, 161, 106, 74, 144, 235, 8, 179, 76, 38, 116, 157, 115, 126, 195, 82, 244, 13, 10, 202, 70, 59, 116, 61, 47, 82, 221, 13, 9, 115, 166, 230, 208, 193, 110, 251, 229, 1, 175, 11, 174, 69, 238, 143, 18, 230, 254, 32, 244, 239, 57, 96, 34, 112, 185, 235, 25, 218, 154, 43, 111, 99, 85, 136, 35, 247, 71, 68, 122, 29, 54, 106, 206, 45, 187, 52, 203, 228, 153, 113, 158, 180, 86, 181, 52, 212, 136, 65, 213, 118, 110, 28, 155, 145, 2, 88, 15, 29, 83, 24, 88, 166, 37, 184, 82, 50, 243, 86, 58, 96, 250, 9, 212, 45, 74, 135, 106, 158, 176, 80, 37, 28, 61, 5, 217, 112, 130, 83, 223, 96, 232, 22, 164, 190, 228, 193, 104, 214, 217, 92, 13, 10, 247, 79, 228, 202, 128, 203, 122, 107, 186, 220, 57, 246, 201, 133, 192, 246, 125, 151, 177, 141, 239, 118, 95, 234, 255, 53, 4, 179, 220, 12, 58, 32, 147, 149, 169, 57, 212, 12, 244, 97, 202, 25, 94, 162, 22, 249, 194, 154, 59, 65, 161, 28, 23, 111, 68, 247], port: 64303, idle_timeout_ms: 363837 };237;[80, ad, 7a, d0, c0, 5d, ef, d8, b6, e1, b3, a3, 04, 40, cc, 8c, 35, 9a, 22, 7f, b9, 2f, a1, 6a, 4a, 90, eb, 08, b3, 4c, 26, 74, 9d, 73, 7e, c3, 52, f4, 0d, 0a, ca, 46, 3b, 74, 3d, 2f, 52, dd, 0d, 09, 73, a6, e6, d0, c1, 6e, fb, e5, 01, af, 0b, ae, 45, ee, 8f, 12, e6, fe, 20, f4, ef, 39, 60, 22, 70, b9, eb, 19, da, 9a, 2b, 6f, 63, 55, 88, 23, f7, 47, 44, 7a, 1d, 36, 6a, ce, 2d, bb, 34, cb, e4, 99, 71, 9e, b4, 56, b5, 34, d4, 88, 41, d5, 76, 6e, 1c, 9b, 91, 02, 58, 0f, 1d, 53, 18, 58, a6, 25, b8, 52, 32, f3, 56, 3a, 60, fa, 09, d4, 2d, 4a, 87, 6a, 9e, b0, 50, 25, 1c, 3d, 05, d9, 70, 82, 53, df, 60, e8, 16, a4, be, e4, c1, 68, d6, d9, 5c, 0d, 0a, f7, 4f, e4, ca, 80, cb, 7a, 6b, ba, dc, 39, f6, c9, 85, c0, f6, 7d, 97, b1, 8d, ef, 76, 5f, ea, ff, 35, 04, b3, dc, 0c, 3a, 20, 93, 95, a9, 39, d4, 0c, f4, 61, ca, 19, 5e, a2, 16, f9, c2, 9a, 3b, 41, a1, 1c, 17, 6f, 44, f7, fb, 2f, 80, 05, 8d, 3d, 00] +udp_resp 0000008000753000 UdpSessionResp { status: 0, error_code: 0, message: [], idle_timeout_ms: 30000 };8;[00, 00, 00, 80, 00, 75, 30, 00] +udp_resp reject +udp_resp 00 reject +udp_resp 0000 reject +udp_resp 000040 reject +udp_resp 000040dd reject +udp_resp 000040dd6d reject +udp_resp 000040dd6dc6 reject +udp_resp 000040dd6dc6fb reject +udp_resp 000040dd6dc6fb42 reject +udp_resp 000040dd6dc6fb4244 reject +udp_resp 000040dd6dc6fb424429 reject +udp_resp 000040dd6dc6fb42442990 reject +udp_resp 000040dd6dc6fb42442990d8 reject +udp_resp 000040dd6dc6fb42442990d8e5 reject +udp_resp 000040dd6dc6fb42442990d8e52b reject +udp_resp 000040dd6dc6fb42442990d8e52beb reject +udp_resp 000040dd6dc6fb42442990d8e52beb09 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5a reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1b reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1bab reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaa reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0de reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1de reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded3 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded35878 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab166396 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab16639675 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d2 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa3365 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa33659324 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241f reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe448 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe4481271 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714c reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d7 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a4 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f11 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f8840 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a30 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bf reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd6 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2b reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf7 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5e reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef8 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f1 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f141 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b7 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d0 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05b reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbd reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdab reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c436 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e88 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5d reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d067 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a3814 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140e reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d3 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c0 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c09806 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f4 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1e reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b8 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b888 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b5 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b59 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b5934 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e6 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64b reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be1 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a7 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a79682 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d71 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d7166 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c836 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a79 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e7 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e722 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262ba reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacd reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd1 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f66 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f97 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f9770 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f97708996 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f69 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f699932 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e52 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e5205 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a3 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e4 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44a reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add8688 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e332 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322b reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfb reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe3 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39c reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a7 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74d reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd9 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd91771 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717e reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c65 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657a reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad3 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4b reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd6 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd696 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c8 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c801 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f901 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48c0 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48c000 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48c00000 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48c0000000 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48c000000000 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48c00000000000 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48c000000000007d reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48c000000000007de6 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48c000000000007de640 reject +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48c000000000007de64000 UdpSessionResp { status: 0, error_code: 0, message: [109, 198, 251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76, 163, 215, 132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244, 107, 30, 239, 29, 38, 233, 184, 136, 69, 242, 213, 229, 181, 139, 89, 52, 116, 230, 75, 225, 17, 167, 150, 130, 93, 113, 102, 21, 200, 54, 126, 138, 121, 96, 231, 34, 98, 186, 205, 209, 72, 172, 95, 102, 63, 151, 112, 137, 150, 127, 105, 153, 50, 30, 82, 5, 84, 163, 148, 204, 77, 58, 12, 100, 228, 74, 221, 134, 136, 76, 55, 227, 50, 43, 251, 227, 156, 202, 123, 83, 167, 77, 217, 23, 113, 126, 173, 44, 101, 122, 211, 16, 172, 2, 236, 62, 75, 214, 150, 0, 200, 1, 18, 249, 1, 92, 94, 95, 29, 72], idle_timeout_ms: 32230 };235;[00, 00, 40, dd, 6d, c6, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, a3, d7, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 6b, 1e, ef, 1d, 26, e9, b8, 88, 45, f2, d5, e5, b5, 8b, 59, 34, 74, e6, 4b, e1, 11, a7, 96, 82, 5d, 71, 66, 15, c8, 36, 7e, 8a, 79, 60, e7, 22, 62, ba, cd, d1, 48, ac, 5f, 66, 3f, 97, 70, 89, 96, 7f, 69, 99, 32, 1e, 52, 05, 54, a3, 94, cc, 4d, 3a, 0c, 64, e4, 4a, dd, 86, 88, 4c, 37, e3, 32, 2b, fb, e3, 9c, ca, 7b, 53, a7, 4d, d9, 17, 71, 7e, ad, 2c, 65, 7a, d3, 10, ac, 02, ec, 3e, 4b, d6, 96, 00, c8, 01, 12, f9, 01, 5c, 5e, 5f, 1d, 48, 80, 00, 7d, e6, 00] +udp_resp 000040dd6dc6fb42442990d8e52beb09bf5aec1babaaf0ded1ded358781f10ab1663967565d213fa336593241fe44812714ca3d784a45f117a2f88407a3038d8bfd62c8f2bf78e7f5ef836f14139b734e3d05bbdabe6e1d6c4364e881c20c3ba6d5dd1d0673d3a38140ed0d355c098067b91f46b1eef1d26e9b88845f2d5e5b58b593474e64be111a796825d716615c8367e8a7960e72262bacdd148ac5f663f977089967f6999321e520554a394cc4d3a0c64e44add86884c37e3322bfbe39cca7b53a74dd917717ead2c657ad310ac02ec3e4bd69600c80112f9015c5e5f1d48c000000000007de640006d UdpSessionResp { status: 0, error_code: 0, message: [109, 198, 251, 66, 68, 41, 144, 216, 229, 43, 235, 9, 191, 90, 236, 27, 171, 170, 240, 222, 209, 222, 211, 88, 120, 31, 16, 171, 22, 99, 150, 117, 101, 210, 19, 250, 51, 101, 147, 36, 31, 228, 72, 18, 113, 76, 163, 215, 132, 164, 95, 17, 122, 47, 136, 64, 122, 48, 56, 216, 191, 214, 44, 143, 43, 247, 142, 127, 94, 248, 54, 241, 65, 57, 183, 52, 227, 208, 91, 189, 171, 230, 225, 214, 196, 54, 78, 136, 28, 32, 195, 186, 109, 93, 209, 208, 103, 61, 58, 56, 20, 14, 208, 211, 85, 192, 152, 6, 123, 145, 244, 107, 30, 239, 29, 38, 233, 184, 136, 69, 242, 213, 229, 181, 139, 89, 52, 116, 230, 75, 225, 17, 167, 150, 130, 93, 113, 102, 21, 200, 54, 126, 138, 121, 96, 231, 34, 98, 186, 205, 209, 72, 172, 95, 102, 63, 151, 112, 137, 150, 127, 105, 153, 50, 30, 82, 5, 84, 163, 148, 204, 77, 58, 12, 100, 228, 74, 221, 134, 136, 76, 55, 227, 50, 43, 251, 227, 156, 202, 123, 83, 167, 77, 217, 23, 113, 126, 173, 44, 101, 122, 211, 16, 172, 2, 236, 62, 75, 214, 150, 0, 200, 1, 18, 249, 1, 92, 94, 95, 29, 72], idle_timeout_ms: 32230 };235;[00, 00, 40, dd, 6d, c6, fb, 42, 44, 29, 90, d8, e5, 2b, eb, 09, bf, 5a, ec, 1b, ab, aa, f0, de, d1, de, d3, 58, 78, 1f, 10, ab, 16, 63, 96, 75, 65, d2, 13, fa, 33, 65, 93, 24, 1f, e4, 48, 12, 71, 4c, a3, d7, 84, a4, 5f, 11, 7a, 2f, 88, 40, 7a, 30, 38, d8, bf, d6, 2c, 8f, 2b, f7, 8e, 7f, 5e, f8, 36, f1, 41, 39, b7, 34, e3, d0, 5b, bd, ab, e6, e1, d6, c4, 36, 4e, 88, 1c, 20, c3, ba, 6d, 5d, d1, d0, 67, 3d, 3a, 38, 14, 0e, d0, d3, 55, c0, 98, 06, 7b, 91, f4, 6b, 1e, ef, 1d, 26, e9, b8, 88, 45, f2, d5, e5, b5, 8b, 59, 34, 74, e6, 4b, e1, 11, a7, 96, 82, 5d, 71, 66, 15, c8, 36, 7e, 8a, 79, 60, e7, 22, 62, ba, cd, d1, 48, ac, 5f, 66, 3f, 97, 70, 89, 96, 7f, 69, 99, 32, 1e, 52, 05, 54, a3, 94, cc, 4d, 3a, 0c, 64, e4, 4a, dd, 86, 88, 4c, 37, e3, 32, 2b, fb, e3, 9c, ca, 7b, 53, a7, 4d, d9, 17, 71, 7e, ad, 2c, 65, 7a, d3, 10, ac, 02, ec, 3e, 4b, d6, 96, 00, c8, 01, 12, f9, 01, 5c, 5e, 5f, 1d, 48, 80, 00, 7d, e6, 00] +udp_resp 0000c00000000000006f3cae2a8ed41906905a00c79116c42f6efc3abeb588cd8433e48b916d29a040f1f56dfaae34040bfee40c54373755fff8070a31ca4e98ed392278b7de69abdbccd6ed89165eb4cbdf27f34cdb610cfbb98a6eb961a9f34e79c941b28dceab810b061154399cb6cb22960706ecdc059fc0000000e9175444800000006881b73d14 UdpSessionResp { status: 0, error_code: 0, message: [60, 174, 42, 142, 212, 25, 6, 144, 90, 0, 199, 145, 22, 196, 47, 110, 252, 58, 190, 181, 136, 205, 132, 51, 228, 139, 145, 109, 41, 160, 64, 241, 245, 109, 250, 174, 52, 4, 11, 254, 228, 12, 84, 55, 55, 85, 255, 248, 7, 10, 49, 202, 78, 152, 237, 57, 34, 120, 183, 222, 105, 171, 219, 204, 214, 237, 137, 22, 94, 180, 203, 223, 39, 243, 76, 219, 97, 12, 251, 185, 138, 110, 185, 97, 169, 243, 78, 121, 201, 65, 178, 141, 206, 171, 129, 11, 6, 17, 84, 57, 156, 182, 203, 34, 150, 7, 6, 236, 220, 5, 159], idle_timeout_ms: 3910620228 };133;[00, 00, 40, 6f, 3c, ae, 2a, 8e, d4, 19, 06, 90, 5a, 00, c7, 91, 16, c4, 2f, 6e, fc, 3a, be, b5, 88, cd, 84, 33, e4, 8b, 91, 6d, 29, a0, 40, f1, f5, 6d, fa, ae, 34, 04, 0b, fe, e4, 0c, 54, 37, 37, 55, ff, f8, 07, 0a, 31, ca, 4e, 98, ed, 39, 22, 78, b7, de, 69, ab, db, cc, d6, ed, 89, 16, 5e, b4, cb, df, 27, f3, 4c, db, 61, 0c, fb, b9, 8a, 6e, b9, 61, a9, f3, 4e, 79, c9, 41, b2, 8d, ce, ab, 81, 0b, 06, 11, 54, 39, 9c, b6, cb, 22, 96, 07, 06, ec, dc, 05, 9f, c0, 00, 00, 00, e9, 17, 54, 44, 00] +udp_resp 0080000000c000000000000092e7829f40b4905e000456b9d4cfa52935adb2d90b422e69297d8132b12d4bee0c567158668dce9817977065d567234e29dc1c0b073bf895cd7c66ace40ecab63c2d2a9be3487e51cd29f1ed51a93fe5a5c892b3dccdef9372f6478d38d1ce0428cddc2a44f8de699a9723e60222de2997de191c1d712fffa0700dd5443f929102b81179b3a2f93ca80eddf35bce6aa609439cc0000000000aa658c000000000000000 UdpSessionResp { status: 0, error_code: 0, message: [231, 130, 159, 64, 180, 144, 94, 0, 4, 86, 185, 212, 207, 165, 41, 53, 173, 178, 217, 11, 66, 46, 105, 41, 125, 129, 50, 177, 45, 75, 238, 12, 86, 113, 88, 102, 141, 206, 152, 23, 151, 112, 101, 213, 103, 35, 78, 41, 220, 28, 11, 7, 59, 248, 149, 205, 124, 102, 172, 228, 14, 202, 182, 60, 45, 42, 155, 227, 72, 126, 81, 205, 41, 241, 237, 81, 169, 63, 229, 165, 200, 146, 179, 220, 205, 239, 147, 114, 246, 71, 141, 56, 209, 206, 4, 40, 205, 220, 42, 68, 248, 222, 105, 154, 151, 35, 230, 2, 34, 222, 41, 151, 222, 25, 28, 29, 113, 47, 255, 160, 112, 13, 213, 68, 63, 146, 145, 2, 184, 17, 121, 179, 162, 249, 60, 168, 14, 221, 243, 91, 206, 106, 166, 9, 67, 156], idle_timeout_ms: 697944 };175;[00, 00, 40, 92, e7, 82, 9f, 40, b4, 90, 5e, 00, 04, 56, b9, d4, cf, a5, 29, 35, ad, b2, d9, 0b, 42, 2e, 69, 29, 7d, 81, 32, b1, 2d, 4b, ee, 0c, 56, 71, 58, 66, 8d, ce, 98, 17, 97, 70, 65, d5, 67, 23, 4e, 29, dc, 1c, 0b, 07, 3b, f8, 95, cd, 7c, 66, ac, e4, 0e, ca, b6, 3c, 2d, 2a, 9b, e3, 48, 7e, 51, cd, 29, f1, ed, 51, a9, 3f, e5, a5, c8, 92, b3, dc, cd, ef, 93, 72, f6, 47, 8d, 38, d1, ce, 04, 28, cd, dc, 2a, 44, f8, de, 69, 9a, 97, 23, e6, 02, 22, de, 29, 97, de, 19, 1c, 1d, 71, 2f, ff, a0, 70, 0d, d5, 44, 3f, 92, 91, 02, b8, 11, 79, b3, a2, f9, 3c, a8, 0e, dd, f3, 5b, ce, 6a, a6, 09, 43, 9c, 80, 0a, a6, 58, 00] +udp_resp 00004100aedbee71abb4ded9744f25778f2c5e18c83a516e0f8c52a1325cd3df6225608e089cb79822ae3c4e964fc7fb440fc12d9199d03b0f93eec7afbbbd6354fc94f152f44291b4754d6941f0a709f23f7a075e2826813e113c9a06f49871ddb099ab2ad13be057c703b111585e8fe76d619f2dfb72c906ad202f604a64b4338f589eb76fd4f39162284f326cd28e0daa0fc04b7e0f07f875c4491f0876db23e7a935cf8c5a485121a92b78c348b16bc348be5ebdf687fa97bc7df349eebf00d7dbf32b7b4a8184c8e308239c4836e06e6f380ce1d0ac10416355ea789123d60736259222b981ebb7f3cd4831e326334a213ea9200cb0349e10298d37c30f1c7d2b7dc0000000004ba3310b5f671920826c8b20336523 reject +udp_resp 0101c000000000000039a35fc5b8e912eea23e9e2244c404deb1a55b145b518b6690a19107239e1ae2aa02ac8bb836e4bc10bdd6c8c782ee40c2db22e04dad0a298455801b16628000000062c0 UdpSessionResp { status: 1, error_code: 1, message: [163, 95, 197, 184, 233, 18, 238, 162, 62, 158, 34, 68, 196, 4, 222, 177, 165, 91, 20, 91, 81, 139, 102, 144, 161, 145, 7, 35, 158, 26, 226, 170, 2, 172, 139, 184, 54, 228, 188, 16, 189, 214, 200, 199, 130, 238, 64, 194, 219, 34, 224, 77, 173, 10, 41, 132, 85], idle_timeout_ms: 1775202 };75;[01, 01, 39, a3, 5f, c5, b8, e9, 12, ee, a2, 3e, 9e, 22, 44, c4, 04, de, b1, a5, 5b, 14, 5b, 51, 8b, 66, 90, a1, 91, 07, 23, 9e, 1a, e2, aa, 02, ac, 8b, b8, 36, e4, bc, 10, bd, d6, c8, c7, 82, ee, 40, c2, db, 22, e0, 4d, ad, 0a, 29, 84, 55, 80, 1b, 16, 62, 00] +udp_resp 00800000004102a3086e36c82209de102a61b2521a8db3d8856f8f26e3cce9a378180da007ed46690447f9dbfdac36a6fb516443f6f8ed3df6922e909386f0d69d94ff84807fdf7f175f02c04c2b48c4391545f302f04acfd1107e3320759d09fdb3441b6f706af9a059f36b595a73942c77f0355f628d447143c8a4d62e22277d6a2638382f4cf8091dd248e79b017248944b89864107dbf685fcbe49684a8eba6596a100ae82f2974ee2ccd7a3a4bc853fa8d8403bf5d3646a569ca22bdab971748c9fe003c77c6d31efa1386799e942737721a21f11fb7dc3537fea049b46022c1b35e94d3e4df3e7bd7b77c0478621e9b68d05f26056b7dcba0d10fd36f177853ea5ecf64e1d1f4000400076735d reject +udp_resp 0101c0000000000000a1e6b7aeec5c6e786d545a46e23b2916bc104f33c25262177da56b178762e599b12c606b256d4133f364f97e373df78a9b4a3116d3a2d7948fdfb8fe7b7241574f136235d96ee0961c25b4d2a6315fea3009dea32eddcfec7a30f014861e9822713d0d43407ddee1caa46a2c39d4b269ead41d9542b8004440b9cf813852dfe4a3a944b1194ebcb6f61a0868e12b6dc145a506dfd0423712f2e914219b90bca8abb2c018d925f5f2d55f009f1cca6c0013b0 UdpSessionResp { status: 1, error_code: 1, message: [230, 183, 174, 236, 92, 110, 120, 109, 84, 90, 70, 226, 59, 41, 22, 188, 16, 79, 51, 194, 82, 98, 23, 125, 165, 107, 23, 135, 98, 229, 153, 177, 44, 96, 107, 37, 109, 65, 51, 243, 100, 249, 126, 55, 61, 247, 138, 155, 74, 49, 22, 211, 162, 215, 148, 143, 223, 184, 254, 123, 114, 65, 87, 79, 19, 98, 53, 217, 110, 224, 150, 28, 37, 180, 210, 166, 49, 95, 234, 48, 9, 222, 163, 46, 221, 207, 236, 122, 48, 240, 20, 134, 30, 152, 34, 113, 61, 13, 67, 64, 125, 222, 225, 202, 164, 106, 44, 57, 212, 178, 105, 234, 212, 29, 149, 66, 184, 0, 68, 64, 185, 207, 129, 56, 82, 223, 228, 163, 169, 68, 177, 25, 78, 188, 182, 246, 26, 8, 104, 225, 43, 109, 193, 69, 165, 6, 223, 208, 66, 55, 18, 242, 233, 20, 33, 155, 144, 188, 168, 171, 178], idle_timeout_ms: 6994156504405343 };180;[01, 01, 40, a1, e6, b7, ae, ec, 5c, 6e, 78, 6d, 54, 5a, 46, e2, 3b, 29, 16, bc, 10, 4f, 33, c2, 52, 62, 17, 7d, a5, 6b, 17, 87, 62, e5, 99, b1, 2c, 60, 6b, 25, 6d, 41, 33, f3, 64, f9, 7e, 37, 3d, f7, 8a, 9b, 4a, 31, 16, d3, a2, d7, 94, 8f, df, b8, fe, 7b, 72, 41, 57, 4f, 13, 62, 35, d9, 6e, e0, 96, 1c, 25, b4, d2, a6, 31, 5f, ea, 30, 09, de, a3, 2e, dd, cf, ec, 7a, 30, f0, 14, 86, 1e, 98, 22, 71, 3d, 0d, 43, 40, 7d, de, e1, ca, a4, 6a, 2c, 39, d4, b2, 69, ea, d4, 1d, 95, 42, b8, 00, 44, 40, b9, cf, 81, 38, 52, df, e4, a3, a9, 44, b1, 19, 4e, bc, b6, f6, 1a, 08, 68, e1, 2b, 6d, c1, 45, a5, 06, df, d0, 42, 37, 12, f2, e9, 14, 21, 9b, 90, bc, a8, ab, b2, c0, 18, d9, 25, f5, f2, d5, 5f, 00] +udp_resp 01400340f7b5d47c0add31b1c23fde5e9c17a297d1a34c3a30db4265cb082c8919d32ccb6a9a48ac18ce1d12b8bfbad1a0e58ad50963e8bbbe813ed69d003c99af6694f304c22848dafd9c618edeca65f053a022f8ef88a9158042e8ef218fae7f134905df35216f23dc5cd18d2bdb1ed3b1de0d1912792624d925037c6804c98ce21fd0c967a0cab946bb853b8ff747349c1bea4b2efe19e8cfdd39c3bb19c35e4c2bbd0b31adb1d3a1c9c0b43406a6cf4cec80e53f6ea0aba168e2872121a86b8574e1afc3978ac4db0c7a499d9173cef8755605b81f4de7a3d3a83db474c95f9ca6871fb7f32440ba1b10de500d0d259b5e52df7149ea99d44d5ac000009cd9b2991800a491932c UdpSessionResp { status: 1, error_code: 3, message: [181, 212, 124, 10, 221, 49, 177, 194, 63, 222, 94, 156, 23, 162, 151, 209, 163, 76, 58, 48, 219, 66, 101, 203, 8, 44, 137, 25, 211, 44, 203, 106, 154, 72, 172, 24, 206, 29, 18, 184, 191, 186, 209, 160, 229, 138, 213, 9, 99, 232, 187, 190, 129, 62, 214, 157, 0, 60, 153, 175, 102, 148, 243, 4, 194, 40, 72, 218, 253, 156, 97, 142, 222, 202, 101, 240, 83, 160, 34, 248, 239, 136, 169, 21, 128, 66, 232, 239, 33, 143, 174, 127, 19, 73, 5, 223, 53, 33, 111, 35, 220, 92, 209, 141, 43, 219, 30, 211, 177, 222, 13, 25, 18, 121, 38, 36, 217, 37, 3, 124, 104, 4, 201, 140, 226, 31, 208, 201, 103, 160, 202, 185, 70, 187, 133, 59, 143, 247, 71, 52, 156, 27, 234, 75, 46, 254, 25, 232, 207, 221, 57, 195, 187, 25, 195, 94, 76, 43, 189, 11, 49, 173, 177, 211, 161, 201, 192, 180, 52, 6, 166, 207, 76, 236, 128, 229, 63, 110, 160, 171, 161, 104, 226, 135, 33, 33, 168, 107, 133, 116, 225, 175, 195, 151, 138, 196, 219, 12, 122, 73, 157, 145, 115, 206, 248, 117, 86, 5, 184, 31, 77, 231, 163, 211, 168, 61, 180, 116, 201, 95, 156, 166, 135, 31, 183, 243, 36, 64, 186, 27, 16, 222, 80, 13, 13, 37, 155, 94, 82, 223, 113, 73, 234, 153, 212, 77, 90], idle_timeout_ms: 673667258648 };261;[01, 03, 40, f7, b5, d4, 7c, 0a, dd, 31, b1, c2, 3f, de, 5e, 9c, 17, a2, 97, d1, a3, 4c, 3a, 30, db, 42, 65, cb, 08, 2c, 89, 19, d3, 2c, cb, 6a, 9a, 48, ac, 18, ce, 1d, 12, b8, bf, ba, d1, a0, e5, 8a, d5, 09, 63, e8, bb, be, 81, 3e, d6, 9d, 00, 3c, 99, af, 66, 94, f3, 04, c2, 28, 48, da, fd, 9c, 61, 8e, de, ca, 65, f0, 53, a0, 22, f8, ef, 88, a9, 15, 80, 42, e8, ef, 21, 8f, ae, 7f, 13, 49, 05, df, 35, 21, 6f, 23, dc, 5c, d1, 8d, 2b, db, 1e, d3, b1, de, 0d, 19, 12, 79, 26, 24, d9, 25, 03, 7c, 68, 04, c9, 8c, e2, 1f, d0, c9, 67, a0, ca, b9, 46, bb, 85, 3b, 8f, f7, 47, 34, 9c, 1b, ea, 4b, 2e, fe, 19, e8, cf, dd, 39, c3, bb, 19, c3, 5e, 4c, 2b, bd, 0b, 31, ad, b1, d3, a1, c9, c0, b4, 34, 06, a6, cf, 4c, ec, 80, e5, 3f, 6e, a0, ab, a1, 68, e2, 87, 21, 21, a8, 6b, 85, 74, e1, af, c3, 97, 8a, c4, db, 0c, 7a, 49, 9d, 91, 73, ce, f8, 75, 56, 05, b8, 1f, 4d, e7, a3, d3, a8, 3d, b4, 74, c9, 5f, 9c, a6, 87, 1f, b7, f3, 24, 40, ba, 1b, 10, de, 50, 0d, 0d, 25, 9b, 5e, 52, df, 71, 49, ea, 99, d4, 4d, 5a, c0, 00, 00, 9c, d9, b2, 99, 18, 00] +udp_resp 01c000000000000002c0000000000000e3059159d4798b17ed87db84080468cdbe464687ab16d89ff2ddb657aa1e292401e6eadbf1f82e4b805b7c534d32cbde351b5cd8b0b48fc752e36ebb4c8b8ef7b57a4771dc86f1a519c93f92335862f6d73107fee4c6d94533aa23c5c74bbc6183f92c663919fb083692443427c77260111cac482c1346b28121f1a2cbe2b37067e3fe6fcb87c92f1e7ffa3b27be3408dff01ed962bf124abbd243f7015c49509d70c568629433cac03123405e39d0f9924c4624785ff92f3750420dd8d7120ae0877fbe0527d2d75dd4cc65b5876179054ee3e5ec265fa3878a1925a7db8154a680e782c002afb52db17a654000 UdpSessionResp { status: 1, error_code: 2, message: [5, 145, 89, 212, 121, 139, 23, 237, 135, 219, 132, 8, 4, 104, 205, 190, 70, 70, 135, 171, 22, 216, 159, 242, 221, 182, 87, 170, 30, 41, 36, 1, 230, 234, 219, 241, 248, 46, 75, 128, 91, 124, 83, 77, 50, 203, 222, 53, 27, 92, 216, 176, 180, 143, 199, 82, 227, 110, 187, 76, 139, 142, 247, 181, 122, 71, 113, 220, 134, 241, 165, 25, 201, 63, 146, 51, 88, 98, 246, 215, 49, 7, 254, 228, 198, 217, 69, 51, 170, 35, 197, 199, 75, 188, 97, 131, 249, 44, 102, 57, 25, 251, 8, 54, 146, 68, 52, 39, 199, 114, 96, 17, 28, 172, 72, 44, 19, 70, 178, 129, 33, 241, 162, 203, 226, 179, 112, 103, 227, 254, 111, 203, 135, 201, 47, 30, 127, 250, 59, 39, 190, 52, 8, 223, 240, 30, 217, 98, 191, 18, 74, 187, 210, 67, 247, 1, 92, 73, 80, 157, 112, 197, 104, 98, 148, 51, 202, 192, 49, 35, 64, 94, 57, 208, 249, 146, 76, 70, 36, 120, 95, 249, 47, 55, 80, 66, 13, 216, 215, 18, 10, 224, 135, 127, 190, 5, 39, 210, 215, 93, 212, 204, 101, 181, 135, 97, 121, 5, 78, 227, 229, 236, 38, 95, 163, 135, 138, 25, 37, 167, 219, 129, 84, 166, 128, 231, 130], idle_timeout_ms: 756142643968613 };254;[01, 02, 40, e3, 05, 91, 59, d4, 79, 8b, 17, ed, 87, db, 84, 08, 04, 68, cd, be, 46, 46, 87, ab, 16, d8, 9f, f2, dd, b6, 57, aa, 1e, 29, 24, 01, e6, ea, db, f1, f8, 2e, 4b, 80, 5b, 7c, 53, 4d, 32, cb, de, 35, 1b, 5c, d8, b0, b4, 8f, c7, 52, e3, 6e, bb, 4c, 8b, 8e, f7, b5, 7a, 47, 71, dc, 86, f1, a5, 19, c9, 3f, 92, 33, 58, 62, f6, d7, 31, 07, fe, e4, c6, d9, 45, 33, aa, 23, c5, c7, 4b, bc, 61, 83, f9, 2c, 66, 39, 19, fb, 08, 36, 92, 44, 34, 27, c7, 72, 60, 11, 1c, ac, 48, 2c, 13, 46, b2, 81, 21, f1, a2, cb, e2, b3, 70, 67, e3, fe, 6f, cb, 87, c9, 2f, 1e, 7f, fa, 3b, 27, be, 34, 08, df, f0, 1e, d9, 62, bf, 12, 4a, bb, d2, 43, f7, 01, 5c, 49, 50, 9d, 70, c5, 68, 62, 94, 33, ca, c0, 31, 23, 40, 5e, 39, d0, f9, 92, 4c, 46, 24, 78, 5f, f9, 2f, 37, 50, 42, 0d, d8, d7, 12, 0a, e0, 87, 7f, be, 05, 27, d2, d7, 5d, d4, cc, 65, b5, 87, 61, 79, 05, 4e, e3, e5, ec, 26, 5f, a3, 87, 8a, 19, 25, a7, db, 81, 54, a6, 80, e7, 82, c0, 02, af, b5, 2d, b1, 7a, 65, 00] +udp_resp 0140034097d4d9a77fb755eaa841e446cc262d8ea5012b84f291c8255c4eed48cfd63bc7c4865b1c2b9c456b64b8417b93a9c8903932efd3b74a5b528d29c97428d2cbd63b3b4075499a1683a97e2e3b82f70b7858dc2b599edc9e8340c5ba1c37fcd40bf879995533a18e41c7733c83748c72e4a198db8ce4f183ce5b66014c865e58ac8d2131086ae83feeedc398d9b88363bc79892934a7e819d2c02213382b493aeec00000000000000c9a06a2791ca24314194d8c1a UdpSessionResp { status: 1, error_code: 3, message: [212, 217, 167, 127, 183, 85, 234, 168, 65, 228, 70, 204, 38, 45, 142, 165, 1, 43, 132, 242, 145, 200, 37, 92, 78, 237, 72, 207, 214, 59, 199, 196, 134, 91, 28, 43, 156, 69, 107, 100, 184, 65, 123, 147, 169, 200, 144, 57, 50, 239, 211, 183, 74, 91, 82, 141, 41, 201, 116, 40, 210, 203, 214, 59, 59, 64, 117, 73, 154, 22, 131, 169, 126, 46, 59, 130, 247, 11, 120, 88, 220, 43, 89, 158, 220, 158, 131, 64, 197, 186, 28, 55, 252, 212, 11, 248, 121, 153, 85, 51, 161, 142, 65, 199, 115, 60, 131, 116, 140, 114, 228, 161, 152, 219, 140, 228, 241, 131, 206, 91, 102, 1, 76, 134, 94, 88, 172, 141, 33, 49, 8, 106, 232, 63, 238, 237, 195, 152, 217, 184, 131, 99, 188, 121, 137, 41, 52, 167, 232, 25, 210], idle_timeout_ms: 9591281173478126 };184;[01, 03, 40, 97, d4, d9, a7, 7f, b7, 55, ea, a8, 41, e4, 46, cc, 26, 2d, 8e, a5, 01, 2b, 84, f2, 91, c8, 25, 5c, 4e, ed, 48, cf, d6, 3b, c7, c4, 86, 5b, 1c, 2b, 9c, 45, 6b, 64, b8, 41, 7b, 93, a9, c8, 90, 39, 32, ef, d3, b7, 4a, 5b, 52, 8d, 29, c9, 74, 28, d2, cb, d6, 3b, 3b, 40, 75, 49, 9a, 16, 83, a9, 7e, 2e, 3b, 82, f7, 0b, 78, 58, dc, 2b, 59, 9e, dc, 9e, 83, 40, c5, ba, 1c, 37, fc, d4, 0b, f8, 79, 99, 55, 33, a1, 8e, 41, c7, 73, 3c, 83, 74, 8c, 72, e4, a1, 98, db, 8c, e4, f1, 83, ce, 5b, 66, 01, 4c, 86, 5e, 58, ac, 8d, 21, 31, 08, 6a, e8, 3f, ee, ed, c3, 98, d9, b8, 83, 63, bc, 79, 89, 29, 34, a7, e8, 19, d2, c0, 22, 13, 38, 2b, 49, 3a, ee, 00] +udp_resp 00004017b139cfe2daedaa3a190e69742df4489bc00400a8d0cf44c001a8d7046f2aad80000000 UdpSessionResp { status: 0, error_code: 0, message: [177, 57, 207, 226, 218, 237, 170, 58, 25, 14, 105, 116, 45, 244, 72, 155, 192, 4, 0, 168, 208, 207, 68], idle_timeout_ms: 467116422539949 };39;[00, 00, 17, b1, 39, cf, e2, da, ed, aa, 3a, 19, 0e, 69, 74, 2d, f4, 48, 9b, c0, 04, 00, a8, d0, cf, 44, c0, 01, a8, d7, 04, 6f, 2a, ad, 00] +udp_resp 018000000441026c8743b1ecec52a45511024058488c00dd81364732643b81dcf20bf823206672fb702f2c22e308de17fd7322d088999921870006b23ed44056e0bc3b6dc55b5787a938ff50485da1bc991985b29164997d339f4159d0ebf5d82de63ccda8d1f2f15a9f934a56f3e4db1ca35663161701cf464706193259507dc67d0dca619e396add06cc46f941a7ca77ffa99000859fe49cbf853d0a33c25539b8298c6a64b778f1fd703a61a1a5661d0c8ffa679bb2a0d63f89bbdd867cc8e993cb3cef180175b61deda347c7d10cbf73664d99fc53c25c3d52163251e0927e021c98f76f48127d1e02a2378edcafb402131ac332d1e83d60aefe35bd40501a78b08031a1a309ac80073e5e800000032e337a6a8875 reject +udp_resp 00c0000000000000004033822b9bd838c2de468019edd22c10733ce00514be245d9c07914b51e15c363d1ed1a1c7ed397b14a2cb85c1e1662312bdeb05ee8285f5e6c000000000000000 UdpSessionResp { status: 0, error_code: 0, message: [130, 43, 155, 216, 56, 194, 222, 70, 128, 25, 237, 210, 44, 16, 115, 60, 224, 5, 20, 190, 36, 93, 156, 7, 145, 75, 81, 225, 92, 54, 61, 30, 209, 161, 199, 237, 57, 123, 20, 162, 203, 133, 193, 225, 102, 35, 18, 189, 235, 5, 238], idle_timeout_ms: 42333670 };74;[00, 00, 33, 82, 2b, 9b, d8, 38, c2, de, 46, 80, 19, ed, d2, 2c, 10, 73, 3c, e0, 05, 14, be, 24, 5d, 9c, 07, 91, 4b, 51, e1, 5c, 36, 3d, 1e, d1, a1, c7, ed, 39, 7b, 14, a2, cb, 85, c1, e1, 66, 23, 12, bd, eb, 05, ee, 82, 85, f5, e6, 00] +udp_resp 00c000000000000000410029cf314ba725c0da3c06db39d9c2e2a1f64115e518c8ec7914d4dd2dbc85e149b8cd3e7e17c302cd853c75effe80a0906501ce05fbc007c7c982a90dbba03d0fff7d4455e7cf363ffe21fc4ff0acd913caf851484038d8d358837b4f66adb9852c48e8acecc2025fe5c85b83df064d27b984a136d847fe0ee7f9923fccf69a5f6c184a70f6be47322f3dd3f54f37f398d176a1a2b3e75bb6e4d787422383ccd8dc66abecdcfc51a143eed6fd98eae4a1d7ebe3ffbba034ea4d98cda4e6532a90749882ca384df9f2d4630d74b9daa6b26b69c0b30e2c447e652a51b496928b175c6a1e76a94bbd61f5cfe293b2bf4fdbfc2cef6c8b0cb5f2931764c49a978d38400100c37e702c5d reject +udp_resp 00c0000000000000000da392d7b090d5985c4f326dba591c00 UdpSessionResp { status: 0, error_code: 0, message: [163, 146, 215, 176, 144, 213, 152, 92, 79, 50, 109, 186, 89], idle_timeout_ms: 28 };25;[00, 00, 0d, a3, 92, d7, b0, 90, d5, 98, 5c, 4f, 32, 6d, ba, 59, 1c, 00] +udp_resp 028000000340a0e59253a89cac3e355f80fbd268148f123f31773cedde97849a26dd2edf4ef322341e14c431f8bce0f12367a30c0b9c5438863e1df7064caa1dbdeca852d3b91536c8878b90056548bdcd316279b15bfe2b5ee07693d9f2fe341944dd0efadb710b208f7714a405c03dc03a09cd45861220d203b1fb072c415c6a07afedca2bff6bd6745ff60259919cc4ef982f3ed12e369dc45053d8ed3a427d6b4be8a46178803df00d00 reject +udp_resp 0104800000739d3d4b6f18913f25d7b5cf23eccae3c604a0f8b8b7837679b3d259aca8a6e22b2f3cc686f76474b13e132f1b1d6d213a7ccf0f15717c3ef210b683e87801c653ef6af4ed30ddc8ef277beabc39a544c935960f3c76b8f72bf0a8400d38ad5863f309c782b3dec64e382d9fae33c25503e5757b8000001ac000000000000000 UdpSessionResp { status: 1, error_code: 4, message: [157, 61, 75, 111, 24, 145, 63, 37, 215, 181, 207, 35, 236, 202, 227, 198, 4, 160, 248, 184, 183, 131, 118, 121, 179, 210, 89, 172, 168, 166, 226, 43, 47, 60, 198, 134, 247, 100, 116, 177, 62, 19, 47, 27, 29, 109, 33, 58, 124, 207, 15, 21, 113, 124, 62, 242, 16, 182, 131, 232, 120, 1, 198, 83, 239, 106, 244, 237, 48, 221, 200, 239, 39, 123, 234, 188, 57, 165, 68, 201, 53, 150, 15, 60, 118, 184, 247, 43, 240, 168, 64, 13, 56, 173, 88, 99, 243, 9, 199, 130, 179, 222, 198, 78, 56, 45, 159, 174, 51, 194, 85, 3, 229, 117, 123], idle_timeout_ms: 26 };133;[01, 04, 40, 73, 9d, 3d, 4b, 6f, 18, 91, 3f, 25, d7, b5, cf, 23, ec, ca, e3, c6, 04, a0, f8, b8, b7, 83, 76, 79, b3, d2, 59, ac, a8, a6, e2, 2b, 2f, 3c, c6, 86, f7, 64, 74, b1, 3e, 13, 2f, 1b, 1d, 6d, 21, 3a, 7c, cf, 0f, 15, 71, 7c, 3e, f2, 10, b6, 83, e8, 78, 01, c6, 53, ef, 6a, f4, ed, 30, dd, c8, ef, 27, 7b, ea, bc, 39, a5, 44, c9, 35, 96, 0f, 3c, 76, b8, f7, 2b, f0, a8, 40, 0d, 38, ad, 58, 63, f3, 09, c7, 82, b3, de, c6, 4e, 38, 2d, 9f, ae, 33, c2, 55, 03, e5, 75, 7b, 1a, 00] +udp_resp 0000c00000000000009a42b9bc6704678be0cfb5eeae0ceebb902912cc6237d70869bced05f947632599829b2bfde2214fd826ec3405fda931439ed153582630beadbaad8d2dddd3a04299c9653cbdefd0b6e8b29b8174b3a8d61a1775352d5590b443e70e3d18c3fe0693fc1a87e6d434a33c3cd177a3040a50a45d44a643e454626796aed84efe5ba633191e5e5ba9494ca56b50b93c39df1ef23612134025bcfd7d6c5c9c4000 UdpSessionResp { status: 0, error_code: 0, message: [66, 185, 188, 103, 4, 103, 139, 224, 207, 181, 238, 174, 12, 238, 187, 144, 41, 18, 204, 98, 55, 215, 8, 105, 188, 237, 5, 249, 71, 99, 37, 153, 130, 155, 43, 253, 226, 33, 79, 216, 38, 236, 52, 5, 253, 169, 49, 67, 158, 209, 83, 88, 38, 48, 190, 173, 186, 173, 141, 45, 221, 211, 160, 66, 153, 201, 101, 60, 189, 239, 208, 182, 232, 178, 155, 129, 116, 179, 168, 214, 26, 23, 117, 53, 45, 85, 144, 180, 67, 231, 14, 61, 24, 195, 254, 6, 147, 252, 26, 135, 230, 212, 52, 163, 60, 60, 209, 119, 163, 4, 10, 80, 164, 93, 68, 166, 67, 228, 84, 98, 103, 150, 174, 216, 78, 254, 91, 166, 51, 25, 30, 94, 91, 169, 73, 76, 165, 107, 80, 185, 60, 57, 223, 30, 242, 54, 18, 19, 64, 37, 188, 253, 125, 108], idle_timeout_ms: 7324 };168;[00, 00, 40, 9a, 42, b9, bc, 67, 04, 67, 8b, e0, cf, b5, ee, ae, 0c, ee, bb, 90, 29, 12, cc, 62, 37, d7, 08, 69, bc, ed, 05, f9, 47, 63, 25, 99, 82, 9b, 2b, fd, e2, 21, 4f, d8, 26, ec, 34, 05, fd, a9, 31, 43, 9e, d1, 53, 58, 26, 30, be, ad, ba, ad, 8d, 2d, dd, d3, a0, 42, 99, c9, 65, 3c, bd, ef, d0, b6, e8, b2, 9b, 81, 74, b3, a8, d6, 1a, 17, 75, 35, 2d, 55, 90, b4, 43, e7, 0e, 3d, 18, c3, fe, 06, 93, fc, 1a, 87, e6, d4, 34, a3, 3c, 3c, d1, 77, a3, 04, 0a, 50, a4, 5d, 44, a6, 43, e4, 54, 62, 67, 96, ae, d8, 4e, fe, 5b, a6, 33, 19, 1e, 5e, 5b, a9, 49, 4c, a5, 6b, 50, b9, 3c, 39, df, 1e, f2, 36, 12, 13, 40, 25, bc, fd, 7d, 6c, 5c, 9c, 00] +udp_resp 0104404b9d6c201cee8341047cc096d3b0af4e4788be8a4d937e6bc019377d6090b9a0ab813a509f38a62c5159e3e6629694af9570e348d624f7b947d55404ef01cb849d6e4a1d2474f1461985cb8ec0000000000000e380000000 UdpSessionResp { status: 1, error_code: 4, message: [157, 108, 32, 28, 238, 131, 65, 4, 124, 192, 150, 211, 176, 175, 78, 71, 136, 190, 138, 77, 147, 126, 107, 192, 25, 55, 125, 96, 144, 185, 160, 171, 129, 58, 80, 159, 56, 166, 44, 81, 89, 227, 230, 98, 150, 148, 175, 149, 112, 227, 72, 214, 36, 247, 185, 71, 213, 84, 4, 239, 1, 203, 132, 157, 110, 74, 29, 36, 116, 241, 70, 25, 133, 203, 142], idle_timeout_ms: 227 };91;[01, 04, 40, 4b, 9d, 6c, 20, 1c, ee, 83, 41, 04, 7c, c0, 96, d3, b0, af, 4e, 47, 88, be, 8a, 4d, 93, 7e, 6b, c0, 19, 37, 7d, 60, 90, b9, a0, ab, 81, 3a, 50, 9f, 38, a6, 2c, 51, 59, e3, e6, 62, 96, 94, af, 95, 70, e3, 48, d6, 24, f7, b9, 47, d5, 54, 04, ef, 01, cb, 84, 9d, 6e, 4a, 1d, 24, 74, f1, 46, 19, 85, cb, 8e, 40, e3, 00] +udp_resp 0180000001800000e0d79a7f720ae2f432fd3e3b2758db4ea1c55b17ee3de2dcbba20ad50d8768de9fbec1cd8da6cad135109efa7c3ec9178607e91958dd81df9c9818197ee0cfab02c22835e652b71454beab76a8c8315083f950c71bb7ae1b5808ed557ca40a4efd719ce0beb754c56640b5db3fc3e8957a3a8a39e667b6baa5a0b020c4724110dd82f48333ab830024757b67ff381a22ef298edf7212e3f714cab3df51498cc2547b8e9d70f9bc0532e26ff5d4a8ce257c4127940d248ecb7d177f96b6c4255daec41dc0a709c02031c22ec7348f8eaaff154035c903b48adab82d2be991db143dfaf7874878c183f5c0000000000000002e2e22 UdpSessionResp { status: 1, error_code: 1, message: [215, 154, 127, 114, 10, 226, 244, 50, 253, 62, 59, 39, 88, 219, 78, 161, 197, 91, 23, 238, 61, 226, 220, 187, 162, 10, 213, 13, 135, 104, 222, 159, 190, 193, 205, 141, 166, 202, 209, 53, 16, 158, 250, 124, 62, 201, 23, 134, 7, 233, 25, 88, 221, 129, 223, 156, 152, 24, 25, 126, 224, 207, 171, 2, 194, 40, 53, 230, 82, 183, 20, 84, 190, 171, 118, 168, 200, 49, 80, 131, 249, 80, 199, 27, 183, 174, 27, 88, 8, 237, 85, 124, 164, 10, 78, 253, 113, 156, 224, 190, 183, 84, 197, 102, 64, 181, 219, 63, 195, 232, 149, 122, 58, 138, 57, 230, 103, 182, 186, 165, 160, 176, 32, 196, 114, 65, 16, 221, 130, 244, 131, 51, 171, 131, 0, 36, 117, 123, 103, 255, 56, 26, 34, 239, 41, 142, 223, 114, 18, 227, 247, 20, 202, 179, 223, 81, 73, 140, 194, 84, 123, 142, 157, 112, 249, 188, 5, 50, 226, 111, 245, 212, 168, 206, 37, 124, 65, 39, 148, 13, 36, 142, 203, 125, 23, 127, 150, 182, 196, 37, 93, 174, 196, 29, 192, 167, 9, 192, 32, 49, 194, 46, 199, 52, 143, 142, 170, 255, 21, 64, 53, 201, 3, 180, 138, 218, 184, 45, 43, 233, 145, 219, 20, 61], idle_timeout_ms: 4249013518780695541 };249;[01, 01, 40, e0, d7, 9a, 7f, 72, 0a, e2, f4, 32, fd, 3e, 3b, 27, 58, db, 4e, a1, c5, 5b, 17, ee, 3d, e2, dc, bb, a2, 0a, d5, 0d, 87, 68, de, 9f, be, c1, cd, 8d, a6, ca, d1, 35, 10, 9e, fa, 7c, 3e, c9, 17, 86, 07, e9, 19, 58, dd, 81, df, 9c, 98, 18, 19, 7e, e0, cf, ab, 02, c2, 28, 35, e6, 52, b7, 14, 54, be, ab, 76, a8, c8, 31, 50, 83, f9, 50, c7, 1b, b7, ae, 1b, 58, 08, ed, 55, 7c, a4, 0a, 4e, fd, 71, 9c, e0, be, b7, 54, c5, 66, 40, b5, db, 3f, c3, e8, 95, 7a, 3a, 8a, 39, e6, 67, b6, ba, a5, a0, b0, 20, c4, 72, 41, 10, dd, 82, f4, 83, 33, ab, 83, 00, 24, 75, 7b, 67, ff, 38, 1a, 22, ef, 29, 8e, df, 72, 12, e3, f7, 14, ca, b3, df, 51, 49, 8c, c2, 54, 7b, 8e, 9d, 70, f9, bc, 05, 32, e2, 6f, f5, d4, a8, ce, 25, 7c, 41, 27, 94, 0d, 24, 8e, cb, 7d, 17, 7f, 96, b6, c4, 25, 5d, ae, c4, 1d, c0, a7, 09, c0, 20, 31, c2, 2e, c7, 34, 8f, 8e, aa, ff, 15, 40, 35, c9, 03, b4, 8a, da, b8, 2d, 2b, e9, 91, db, 14, 3d, fa, f7, 87, 48, 78, c1, 83, f5, 00] +udp_resp 01c000000000000001c000000000000042b889ec7acc9de755c818f1825e1443c5d4e6615bb0f89c4592442c8ab44e9564d604e3fed8103bd2302b26877bb1161f4da1f3f3fa1d76cc7d5065e35864f4c8d79bc00000015448b2c180000000 UdpSessionResp { status: 1, error_code: 1, message: [184, 137, 236, 122, 204, 157, 231, 85, 200, 24, 241, 130, 94, 20, 67, 197, 212, 230, 97, 91, 176, 248, 156, 69, 146, 68, 44, 138, 180, 78, 149, 100, 214, 4, 227, 254, 216, 16, 59, 210, 48, 43, 38, 135, 123, 177, 22, 31, 77, 161, 243, 243, 250, 29, 118, 204, 125, 80, 101, 227, 88, 100, 244, 200, 215, 155], idle_timeout_ms: 5709017793 };95;[01, 01, 40, 42, b8, 89, ec, 7a, cc, 9d, e7, 55, c8, 18, f1, 82, 5e, 14, 43, c5, d4, e6, 61, 5b, b0, f8, 9c, 45, 92, 44, 2c, 8a, b4, 4e, 95, 64, d6, 04, e3, fe, d8, 10, 3b, d2, 30, 2b, 26, 87, 7b, b1, 16, 1f, 4d, a1, f3, f3, fa, 1d, 76, cc, 7d, 50, 65, e3, 58, 64, f4, c8, d7, 9b, c0, 00, 00, 01, 54, 48, b2, c1, 00] +udp_resp 00800000004069ad9f94ecc6642c333949406d1fe0afa35a0a063ed032f25b6d5207cd8054afe21a9b172113cba0b5847542244641ff9fa7da4d6eac482a748a775d66a0d438253e7c93acd65da2d1532de33b97b490be9f9c416923ac7adf987dda9c999151fbfba706c68cb5ea8526024000 UdpSessionResp { status: 0, error_code: 0, message: [173, 159, 148, 236, 198, 100, 44, 51, 57, 73, 64, 109, 31, 224, 175, 163, 90, 10, 6, 62, 208, 50, 242, 91, 109, 82, 7, 205, 128, 84, 175, 226, 26, 155, 23, 33, 19, 203, 160, 181, 132, 117, 66, 36, 70, 65, 255, 159, 167, 218, 77, 110, 172, 72, 42, 116, 138, 119, 93, 102, 160, 212, 56, 37, 62, 124, 147, 172, 214, 93, 162, 209, 83, 45, 227, 59, 151, 180, 144, 190, 159, 156, 65, 105, 35, 172, 122, 223, 152, 125, 218, 156, 153, 145, 81, 251, 251, 167, 6, 198, 140, 181, 234, 133, 38], idle_timeout_ms: 2 };115;[00, 00, 40, 69, ad, 9f, 94, ec, c6, 64, 2c, 33, 39, 49, 40, 6d, 1f, e0, af, a3, 5a, 0a, 06, 3e, d0, 32, f2, 5b, 6d, 52, 07, cd, 80, 54, af, e2, 1a, 9b, 17, 21, 13, cb, a0, b5, 84, 75, 42, 24, 46, 41, ff, 9f, a7, da, 4d, 6e, ac, 48, 2a, 74, 8a, 77, 5d, 66, a0, d4, 38, 25, 3e, 7c, 93, ac, d6, 5d, a2, d1, 53, 2d, e3, 3b, 97, b4, 90, be, 9f, 9c, 41, 69, 23, ac, 7a, df, 98, 7d, da, 9c, 99, 91, 51, fb, fb, a7, 06, c6, 8c, b5, ea, 85, 26, 02, 00] +udp_resp 0000c000000000000023db54ff15f001000495ac029e587d060efe4c38a4e4cdc3a18dcf68ef7e1744978309578073cf63c0000000000000018279a49b55bd12ad UdpSessionResp { status: 0, error_code: 0, message: [219, 84, 255, 21, 240, 1, 0, 4, 149, 172, 2, 158, 88, 125, 6, 14, 254, 76, 56, 164, 228, 205, 195, 161, 141, 207, 104, 239, 126, 23, 68, 151, 131, 9, 87], idle_timeout_ms: 7589731 };58;[00, 00, 23, db, 54, ff, 15, f0, 01, 00, 04, 95, ac, 02, 9e, 58, 7d, 06, 0e, fe, 4c, 38, a4, e4, cd, c3, a1, 8d, cf, 68, ef, 7e, 17, 44, 97, 83, 09, 57, 80, 73, cf, 63, 00] +udp_resp 01400180000025aed7d41747f597b0af107dc92b8764d63a60b3be39e00b2eb1675921e16738589aca3d650ac000000000000004000e0fef093a UdpSessionResp { status: 1, error_code: 1, message: [174, 215, 212, 23, 71, 245, 151, 176, 175, 16, 125, 201, 43, 135, 100, 214, 58, 96, 179, 190, 57, 224, 11, 46, 177, 103, 89, 33, 225, 103, 56, 88, 154, 202, 61, 101, 10], idle_timeout_ms: 4 };53;[01, 01, 25, ae, d7, d4, 17, 47, f5, 97, b0, af, 10, 7d, c9, 2b, 87, 64, d6, 3a, 60, b3, be, 39, e0, 0b, 2e, b1, 67, 59, 21, e1, 67, 38, 58, 9a, ca, 3d, 65, 0a, 04, 00] +udp_resp 00400080000028395b458a7860b1a1a6bf771d3481f1abfee23047d287a57d16ff83e03b28a55a70281b1afded9d6dc0abff5ab5da202c0197 UdpSessionResp { status: 0, error_code: 0, message: [57, 91, 69, 138, 120, 96, 177, 161, 166, 191, 119, 29, 52, 129, 241, 171, 254, 226, 48, 71, 210, 135, 165, 125, 22, 255, 131, 224, 59, 40, 165, 90, 112, 40, 27, 26, 253, 237, 157, 109], idle_timeout_ms: 48412986080632876 };57;[00, 00, 28, 39, 5b, 45, 8a, 78, 60, b1, a1, a6, bf, 77, 1d, 34, 81, f1, ab, fe, e2, 30, 47, d2, 87, a5, 7d, 16, ff, 83, e0, 3b, 28, a5, 5a, 70, 28, 1b, 1a, fd, ed, 9d, 6d, c0, ab, ff, 5a, b5, da, 20, 2c, 00] +udp_resp 0140048000005f2f9742996de561a9204f529e86a0dd1075de11667e6155220d71f36b5becf558f91910d643651f076b0d68d4ef61b631dac8cc77be3a43898140bf7aeef4f8137b674ac7e61701fd4d743117da6178fede80170d81518479d47af9bfb9871fc000001ee4b0a3b94000f1622d57d752 UdpSessionResp { status: 1, error_code: 4, message: [47, 151, 66, 153, 109, 229, 97, 169, 32, 79, 82, 158, 134, 160, 221, 16, 117, 222, 17, 102, 126, 97, 85, 34, 13, 113, 243, 107, 91, 236, 245, 88, 249, 25, 16, 214, 67, 101, 31, 7, 107, 13, 104, 212, 239, 97, 182, 49, 218, 200, 204, 119, 190, 58, 67, 137, 129, 64, 191, 122, 238, 244, 248, 19, 123, 103, 74, 199, 230, 23, 1, 253, 77, 116, 49, 23, 218, 97, 120, 254, 222, 128, 23, 13, 129, 81, 132, 121, 212, 122, 249, 191, 185, 135, 31], idle_timeout_ms: 132685800377 };112;[01, 04, 40, 5f, 2f, 97, 42, 99, 6d, e5, 61, a9, 20, 4f, 52, 9e, 86, a0, dd, 10, 75, de, 11, 66, 7e, 61, 55, 22, 0d, 71, f3, 6b, 5b, ec, f5, 58, f9, 19, 10, d6, 43, 65, 1f, 07, 6b, 0d, 68, d4, ef, 61, b6, 31, da, c8, cc, 77, be, 3a, 43, 89, 81, 40, bf, 7a, ee, f4, f8, 13, 7b, 67, 4a, c7, e6, 17, 01, fd, 4d, 74, 31, 17, da, 61, 78, fe, de, 80, 17, 0d, 81, 51, 84, 79, d4, 7a, f9, bf, b9, 87, 1f, c0, 00, 00, 1e, e4, b0, a3, b9, 00] +udp_resp 0104800000295a16ed8102c97177416711639d337d79209317848ec370fa5e8fec3d88f102cd314687614463acd581c0010c6c1187e94dc000000000000007dc976a804f2182 UdpSessionResp { status: 1, error_code: 4, message: [90, 22, 237, 129, 2, 201, 113, 119, 65, 103, 17, 99, 157, 51, 125, 121, 32, 147, 23, 132, 142, 195, 112, 250, 94, 143, 236, 61, 136, 241, 2, 205, 49, 70, 135, 97, 68, 99, 172, 213, 129], idle_timeout_ms: 295133266831693 };70;[01, 04, 29, 5a, 16, ed, 81, 02, c9, 71, 77, 41, 67, 11, 63, 9d, 33, 7d, 79, 20, 93, 17, 84, 8e, c3, 70, fa, 5e, 8f, ec, 3d, 88, f1, 02, cd, 31, 46, 87, 61, 44, 63, ac, d5, 81, c0, 01, 0c, 6c, 11, 87, e9, 4d, 00] +udp_resp 000040c0ea0381a90140d1f103a4cc482ac50262593a81ca8036ededba3721c16e4f8f42b943f4631a4742585f5b3879f3fa78a8094dcc4c53912fd380ebf62d4521ec6f78b118ea7b151977a4e53586a52c73af7dc59ff0afd22041351d30d1ddd2ec7c5f2b62199b9a334938cea6b44ec9a790ca35912061c046139b79a2de5542744caf7bccf40fc202566ea328667f0e5ce974865e7b55732b77a38b871bf22e7e311ac78cd042e5a05aa5501e9c2d89fcf62d74af38ef88e5562b9a5a09b41089ddc000000147957e0d400e640fbee5b9790931a79ed1ad9614 UdpSessionResp { status: 0, error_code: 0, message: [234, 3, 129, 169, 1, 64, 209, 241, 3, 164, 204, 72, 42, 197, 2, 98, 89, 58, 129, 202, 128, 54, 237, 237, 186, 55, 33, 193, 110, 79, 143, 66, 185, 67, 244, 99, 26, 71, 66, 88, 95, 91, 56, 121, 243, 250, 120, 168, 9, 77, 204, 76, 83, 145, 47, 211, 128, 235, 246, 45, 69, 33, 236, 111, 120, 177, 24, 234, 123, 21, 25, 119, 164, 229, 53, 134, 165, 44, 115, 175, 125, 197, 159, 240, 175, 210, 32, 65, 53, 29, 48, 209, 221, 210, 236, 124, 95, 43, 98, 25, 155, 154, 51, 73, 56, 206, 166, 180, 78, 201, 167, 144, 202, 53, 145, 32, 97, 192, 70, 19, 155, 121, 162, 222, 85, 66, 116, 76, 175, 123, 204, 244, 15, 194, 2, 86, 110, 163, 40, 102, 127, 14, 92, 233, 116, 134, 94, 123, 85, 115, 43, 119, 163, 139, 135, 27, 242, 46, 126, 49, 26, 199, 140, 208, 66, 229, 160, 90, 165, 80, 30, 156, 45, 137, 252, 246, 45, 116, 175, 56, 239, 136, 229, 86, 43, 154, 90, 9, 180, 16, 137, 221], idle_timeout_ms: 5495946765 };220;[00, 00, 40, c0, ea, 03, 81, a9, 01, 40, d1, f1, 03, a4, cc, 48, 2a, c5, 02, 62, 59, 3a, 81, ca, 80, 36, ed, ed, ba, 37, 21, c1, 6e, 4f, 8f, 42, b9, 43, f4, 63, 1a, 47, 42, 58, 5f, 5b, 38, 79, f3, fa, 78, a8, 09, 4d, cc, 4c, 53, 91, 2f, d3, 80, eb, f6, 2d, 45, 21, ec, 6f, 78, b1, 18, ea, 7b, 15, 19, 77, a4, e5, 35, 86, a5, 2c, 73, af, 7d, c5, 9f, f0, af, d2, 20, 41, 35, 1d, 30, d1, dd, d2, ec, 7c, 5f, 2b, 62, 19, 9b, 9a, 33, 49, 38, ce, a6, b4, 4e, c9, a7, 90, ca, 35, 91, 20, 61, c0, 46, 13, 9b, 79, a2, de, 55, 42, 74, 4c, af, 7b, cc, f4, 0f, c2, 02, 56, 6e, a3, 28, 66, 7f, 0e, 5c, e9, 74, 86, 5e, 7b, 55, 73, 2b, 77, a3, 8b, 87, 1b, f2, 2e, 7e, 31, 1a, c7, 8c, d0, 42, e5, a0, 5a, a5, 50, 1e, 9c, 2d, 89, fc, f6, 2d, 74, af, 38, ef, 88, e5, 56, 2b, 9a, 5a, 09, b4, 10, 89, dd, c0, 00, 00, 01, 47, 95, 7e, 0d, 00] +udp_resp 01c000000000000003c0000000000000db09fa7b62e38b11ca4850b95107fbfd136aa27b2f0c4a71d67d39a00135fa3427208cccb550918996738f63d0c03b32ee60fcd08feb11f85c4e3f01f29371ad4d39ba70f6f70c4113b63fec8621251cff69bffbae588c0261ccf41bd969b26c1592b89bb2da2019e4a4c94e4477279bcf1c05946130bc594d2bdc7fd3f17c77505d0634151e8e261edae8274aa74c6796ab25a5d7fb9cfc9549cb085519646bb25253dc45b1544a3952f89ec1a1d6e52a46508b16244fc455bbc24e5691e3181745e2fdfd7df415beb72ea019d4200bbe91e63a44b3bca821da0452c000468efa07437d4000b759c8 UdpSessionResp { status: 1, error_code: 3, message: [9, 250, 123, 98, 227, 139, 17, 202, 72, 80, 185, 81, 7, 251, 253, 19, 106, 162, 123, 47, 12, 74, 113, 214, 125, 57, 160, 1, 53, 250, 52, 39, 32, 140, 204, 181, 80, 145, 137, 150, 115, 143, 99, 208, 192, 59, 50, 238, 96, 252, 208, 143, 235, 17, 248, 92, 78, 63, 1, 242, 147, 113, 173, 77, 57, 186, 112, 246, 247, 12, 65, 19, 182, 63, 236, 134, 33, 37, 28, 255, 105, 191, 251, 174, 88, 140, 2, 97, 204, 244, 27, 217, 105, 178, 108, 21, 146, 184, 155, 178, 218, 32, 25, 228, 164, 201, 78, 68, 119, 39, 155, 207, 28, 5, 148, 97, 48, 188, 89, 77, 43, 220, 127, 211, 241, 124, 119, 80, 93, 6, 52, 21, 30, 142, 38, 30, 218, 232, 39, 74, 167, 76, 103, 150, 171, 37, 165, 215, 251, 156, 252, 149, 73, 203, 8, 85, 25, 100, 107, 178, 82, 83, 220, 69, 177, 84, 74, 57, 82, 248, 158, 193, 161, 214, 229, 42, 70, 80, 139, 22, 36, 79, 196, 85, 187, 194, 78, 86, 145, 227, 24, 23, 69, 226, 253, 253, 125, 244, 21, 190, 183, 46, 160, 25, 212, 32, 11, 190, 145, 230, 58, 68, 179, 188, 168, 33, 218, 4, 82], idle_timeout_ms: 77579894080381 };246;[01, 03, 40, db, 09, fa, 7b, 62, e3, 8b, 11, ca, 48, 50, b9, 51, 07, fb, fd, 13, 6a, a2, 7b, 2f, 0c, 4a, 71, d6, 7d, 39, a0, 01, 35, fa, 34, 27, 20, 8c, cc, b5, 50, 91, 89, 96, 73, 8f, 63, d0, c0, 3b, 32, ee, 60, fc, d0, 8f, eb, 11, f8, 5c, 4e, 3f, 01, f2, 93, 71, ad, 4d, 39, ba, 70, f6, f7, 0c, 41, 13, b6, 3f, ec, 86, 21, 25, 1c, ff, 69, bf, fb, ae, 58, 8c, 02, 61, cc, f4, 1b, d9, 69, b2, 6c, 15, 92, b8, 9b, b2, da, 20, 19, e4, a4, c9, 4e, 44, 77, 27, 9b, cf, 1c, 05, 94, 61, 30, bc, 59, 4d, 2b, dc, 7f, d3, f1, 7c, 77, 50, 5d, 06, 34, 15, 1e, 8e, 26, 1e, da, e8, 27, 4a, a7, 4c, 67, 96, ab, 25, a5, d7, fb, 9c, fc, 95, 49, cb, 08, 55, 19, 64, 6b, b2, 52, 53, dc, 45, b1, 54, 4a, 39, 52, f8, 9e, c1, a1, d6, e5, 2a, 46, 50, 8b, 16, 24, 4f, c4, 55, bb, c2, 4e, 56, 91, e3, 18, 17, 45, e2, fd, fd, 7d, f4, 15, be, b7, 2e, a0, 19, d4, 20, 0b, be, 91, e6, 3a, 44, b3, bc, a8, 21, da, 04, 52, c0, 00, 46, 8e, fa, 07, 43, 7d, 00] +udp_resp 00c0000000000000004088cac6eda2329bf36d332f7f88ad4202a7b81ab742daf013d896db3a1beae72712d796248c114f1ff7482b3b656019bf03abe52d79f41073e3d4c3dc75739f73af5931c203049ade9b883bb9ea0a6da87d5262c22d86bb391b706e74193cb6cfaa7bc18e799e5e50e1d74b27b59bbd3d5d5b66c9c72e8802ea5d1e6dda96617e1c6d5c7926541872b0c00000000003145980000000 UdpSessionResp { status: 0, error_code: 0, message: [202, 198, 237, 162, 50, 155, 243, 109, 51, 47, 127, 136, 173, 66, 2, 167, 184, 26, 183, 66, 218, 240, 19, 216, 150, 219, 58, 27, 234, 231, 39, 18, 215, 150, 36, 140, 17, 79, 31, 247, 72, 43, 59, 101, 96, 25, 191, 3, 171, 229, 45, 121, 244, 16, 115, 227, 212, 195, 220, 117, 115, 159, 115, 175, 89, 49, 194, 3, 4, 154, 222, 155, 136, 59, 185, 234, 10, 109, 168, 125, 82, 98, 194, 45, 134, 187, 57, 27, 112, 110, 116, 25, 60, 182, 207, 170, 123, 193, 142, 121, 158, 94, 80, 225, 215, 75, 39, 181, 155, 189, 61, 93, 91, 102, 201, 199, 46, 136, 2, 234, 93, 30, 109, 218, 150, 97, 126, 28, 109, 92, 121, 38, 84, 24, 114, 176], idle_timeout_ms: 201817 };159;[00, 00, 40, 88, ca, c6, ed, a2, 32, 9b, f3, 6d, 33, 2f, 7f, 88, ad, 42, 02, a7, b8, 1a, b7, 42, da, f0, 13, d8, 96, db, 3a, 1b, ea, e7, 27, 12, d7, 96, 24, 8c, 11, 4f, 1f, f7, 48, 2b, 3b, 65, 60, 19, bf, 03, ab, e5, 2d, 79, f4, 10, 73, e3, d4, c3, dc, 75, 73, 9f, 73, af, 59, 31, c2, 03, 04, 9a, de, 9b, 88, 3b, b9, ea, 0a, 6d, a8, 7d, 52, 62, c2, 2d, 86, bb, 39, 1b, 70, 6e, 74, 19, 3c, b6, cf, aa, 7b, c1, 8e, 79, 9e, 5e, 50, e1, d7, 4b, 27, b5, 9b, bd, 3d, 5d, 5b, 66, c9, c7, 2e, 88, 02, ea, 5d, 1e, 6d, da, 96, 61, 7e, 1c, 6d, 5c, 79, 26, 54, 18, 72, b0, 80, 03, 14, 59, 00] +udp_resp 018000000240e42100f6614cece510ab37d5a879ad6c070cb43ebfba9766ec518a21bdb0e179c540175ff467c9e882bf97665c574840ddb53d6d41519743e0a52c9da4ea2719ee82930e57507349c348382ad569edb6ae5ce72d3b5cb4566814af329d265fdbe23228194713f3b5023506036b0cb4bcf799732cb307aebd7f5c6aee3d3e126bacf2d0db6f8f370f891c2e62214922304753818a75584b44a6e5cb31369a825acb59a49b0a14b6c2334e342fa5c493178cc61cf6b3db50d644025063e74040180ff140a4cfac05f60a7163c3973d7c1644fef8015c4095d264fd3e8dcdfca1106e619766ca401b0308a51f UdpSessionResp { status: 1, error_code: 2, message: [33, 0, 246, 97, 76, 236, 229, 16, 171, 55, 213, 168, 121, 173, 108, 7, 12, 180, 62, 191, 186, 151, 102, 236, 81, 138, 33, 189, 176, 225, 121, 197, 64, 23, 95, 244, 103, 201, 232, 130, 191, 151, 102, 92, 87, 72, 64, 221, 181, 61, 109, 65, 81, 151, 67, 224, 165, 44, 157, 164, 234, 39, 25, 238, 130, 147, 14, 87, 80, 115, 73, 195, 72, 56, 42, 213, 105, 237, 182, 174, 92, 231, 45, 59, 92, 180, 86, 104, 20, 175, 50, 157, 38, 95, 219, 226, 50, 40, 25, 71, 19, 243, 181, 2, 53, 6, 3, 107, 12, 180, 188, 247, 153, 115, 44, 179, 7, 174, 189, 127, 92, 106, 238, 61, 62, 18, 107, 172, 242, 208, 219, 111, 143, 55, 15, 137, 28, 46, 98, 33, 73, 34, 48, 71, 83, 129, 138, 117, 88, 75, 68, 166, 229, 203, 49, 54, 154, 130, 90, 203, 89, 164, 155, 10, 20, 182, 194, 51, 78, 52, 47, 165, 196, 147, 23, 140, 198, 28, 246, 179, 219, 80, 214, 68, 2, 80, 99, 231, 64, 64, 24, 15, 241, 64, 164, 207, 172, 5, 246, 10, 113, 99, 195, 151, 61, 124, 22, 68, 254, 248, 1, 92, 64, 149, 210, 100, 253, 62, 141, 205, 252, 161, 16, 110, 97, 151, 102, 202], idle_timeout_ms: 27 };241;[01, 02, 40, e4, 21, 00, f6, 61, 4c, ec, e5, 10, ab, 37, d5, a8, 79, ad, 6c, 07, 0c, b4, 3e, bf, ba, 97, 66, ec, 51, 8a, 21, bd, b0, e1, 79, c5, 40, 17, 5f, f4, 67, c9, e8, 82, bf, 97, 66, 5c, 57, 48, 40, dd, b5, 3d, 6d, 41, 51, 97, 43, e0, a5, 2c, 9d, a4, ea, 27, 19, ee, 82, 93, 0e, 57, 50, 73, 49, c3, 48, 38, 2a, d5, 69, ed, b6, ae, 5c, e7, 2d, 3b, 5c, b4, 56, 68, 14, af, 32, 9d, 26, 5f, db, e2, 32, 28, 19, 47, 13, f3, b5, 02, 35, 06, 03, 6b, 0c, b4, bc, f7, 99, 73, 2c, b3, 07, ae, bd, 7f, 5c, 6a, ee, 3d, 3e, 12, 6b, ac, f2, d0, db, 6f, 8f, 37, 0f, 89, 1c, 2e, 62, 21, 49, 22, 30, 47, 53, 81, 8a, 75, 58, 4b, 44, a6, e5, cb, 31, 36, 9a, 82, 5a, cb, 59, a4, 9b, 0a, 14, b6, c2, 33, 4e, 34, 2f, a5, c4, 93, 17, 8c, c6, 1c, f6, b3, db, 50, d6, 44, 02, 50, 63, e7, 40, 40, 18, 0f, f1, 40, a4, cf, ac, 05, f6, 0a, 71, 63, c3, 97, 3d, 7c, 16, 44, fe, f8, 01, 5c, 40, 95, d2, 64, fd, 3e, 8d, cd, fc, a1, 10, 6e, 61, 97, 66, ca, 1b, 00] +udp_resp 00c000000000000006410354cc7dba5a85268978f6a7a098eb8e6286a2170d58b3c42a909b2b8ea006be485f49c67696450e48ad90f86efd1134537c06140da88af33ac0e0c10af998354fa85c581de0f62becaff30871a5b33f41be48e229228163ea509281ca419d02d2be251f7be03f2dd49dd3f51b2112a2b9216d245442db4b72305638a2d76a1e9a0ceab54a80c548dc082d4c44234b6aa28b6a8867a959018812b838d816553dd7eea2087c9623e306f6068c463de5b61040404b2a7b7cb9db3c14fd4b196d86eeb188bd4330680a3e83bc67dd473438b6b04a62c2b4c04961c7ba1f7bdb35639ce6f4a3c619bbd04641117c985154b0a5e161e3cad150363c94030c4eb29e04fbb4cf44c00049a88e6906e90595a7cbfb5a5c3d4cf7b7d3ae reject +udp_resp 018000000440dac2264f119c163c9866b111d25c1ef1aefd0f8b2786901251aaa325b194eabcccdc099f20e25c415ff2bab4a76c40d67b4a5e59b3eec91aa719ee5f7a511d2648ffcc11b430106c1f557c3319446652449db1f8b74077dee7cde1bb700713d325d2af9797dd1d3bb994672b53f44687164876474062ff85527a27b8cd375116f3eac6dc0f279e3c9a88266b71fbed0fbccb2d0cb9037211343c9ea59814905aefa1576a2c1e95971539cd8295d245092bfe38ce1b5acc2a3eeef63a4e942aa3136ce44f9100ba08d1c0667753a2e290560d506162fd541889cf6815c000000000000000 UdpSessionResp { status: 1, error_code: 4, message: [194, 38, 79, 17, 156, 22, 60, 152, 102, 177, 17, 210, 92, 30, 241, 174, 253, 15, 139, 39, 134, 144, 18, 81, 170, 163, 37, 177, 148, 234, 188, 204, 220, 9, 159, 32, 226, 92, 65, 95, 242, 186, 180, 167, 108, 64, 214, 123, 74, 94, 89, 179, 238, 201, 26, 167, 25, 238, 95, 122, 81, 29, 38, 72, 255, 204, 17, 180, 48, 16, 108, 31, 85, 124, 51, 25, 68, 102, 82, 68, 157, 177, 248, 183, 64, 119, 222, 231, 205, 225, 187, 112, 7, 19, 211, 37, 210, 175, 151, 151, 221, 29, 59, 185, 148, 103, 43, 83, 244, 70, 135, 22, 72, 118, 71, 64, 98, 255, 133, 82, 122, 39, 184, 205, 55, 81, 22, 243, 234, 198, 220, 15, 39, 158, 60, 154, 136, 38, 107, 113, 251, 237, 15, 188, 203, 45, 12, 185, 3, 114, 17, 52, 60, 158, 165, 152, 20, 144, 90, 239, 161, 87, 106, 44, 30, 149, 151, 21, 57, 205, 130, 149, 210, 69, 9, 43, 254, 56, 206, 27, 90, 204, 42, 62, 238, 246, 58, 78, 148, 42, 163, 19, 108, 228, 79, 145, 0, 186, 8, 209, 192, 102, 119, 83, 162, 226, 144, 86, 13, 80, 97, 98, 253, 84, 24, 137, 207, 104], idle_timeout_ms: 21 };234;[01, 04, 40, da, c2, 26, 4f, 11, 9c, 16, 3c, 98, 66, b1, 11, d2, 5c, 1e, f1, ae, fd, 0f, 8b, 27, 86, 90, 12, 51, aa, a3, 25, b1, 94, ea, bc, cc, dc, 09, 9f, 20, e2, 5c, 41, 5f, f2, ba, b4, a7, 6c, 40, d6, 7b, 4a, 5e, 59, b3, ee, c9, 1a, a7, 19, ee, 5f, 7a, 51, 1d, 26, 48, ff, cc, 11, b4, 30, 10, 6c, 1f, 55, 7c, 33, 19, 44, 66, 52, 44, 9d, b1, f8, b7, 40, 77, de, e7, cd, e1, bb, 70, 07, 13, d3, 25, d2, af, 97, 97, dd, 1d, 3b, b9, 94, 67, 2b, 53, f4, 46, 87, 16, 48, 76, 47, 40, 62, ff, 85, 52, 7a, 27, b8, cd, 37, 51, 16, f3, ea, c6, dc, 0f, 27, 9e, 3c, 9a, 88, 26, 6b, 71, fb, ed, 0f, bc, cb, 2d, 0c, b9, 03, 72, 11, 34, 3c, 9e, a5, 98, 14, 90, 5a, ef, a1, 57, 6a, 2c, 1e, 95, 97, 15, 39, cd, 82, 95, d2, 45, 09, 2b, fe, 38, ce, 1b, 5a, cc, 2a, 3e, ee, f6, 3a, 4e, 94, 2a, a3, 13, 6c, e4, 4f, 91, 00, ba, 08, d1, c0, 66, 77, 53, a2, e2, 90, 56, 0d, 50, 61, 62, fd, 54, 18, 89, cf, 68, 15, 00] +udp_resp 01800000024086f3b066a16b0bad3fdff97d27692a6506402f4ea8d8c8c7df3ba2c8b6c827e484d67a33cb974a70dc43a9cda3ff30422e21dc8ec71c5d55a3c175d5a51e2eeedf653f4c6940d00705a9733d1ee17182732cd1ddfc586369f78fb7e12a908e0b078256d4367d681f02b27e7ed8deefec652ac84b096bdd2ea1059afbea11055302cabfde6310fbc02438184daa6b914000 UdpSessionResp { status: 1, error_code: 2, message: [243, 176, 102, 161, 107, 11, 173, 63, 223, 249, 125, 39, 105, 42, 101, 6, 64, 47, 78, 168, 216, 200, 199, 223, 59, 162, 200, 182, 200, 39, 228, 132, 214, 122, 51, 203, 151, 74, 112, 220, 67, 169, 205, 163, 255, 48, 66, 46, 33, 220, 142, 199, 28, 93, 85, 163, 193, 117, 213, 165, 30, 46, 238, 223, 101, 63, 76, 105, 64, 208, 7, 5, 169, 115, 61, 30, 225, 113, 130, 115, 44, 209, 221, 252, 88, 99, 105, 247, 143, 183, 225, 42, 144, 142, 11, 7, 130, 86, 212, 54, 125, 104, 31, 2, 178, 126, 126, 216, 222, 239, 236, 101, 42, 200, 75, 9, 107, 221, 46, 161, 5, 154, 251, 234, 17, 5, 83, 2, 202, 191, 222, 99, 16, 251], idle_timeout_ms: 10194776194968465 };151;[01, 02, 40, 86, f3, b0, 66, a1, 6b, 0b, ad, 3f, df, f9, 7d, 27, 69, 2a, 65, 06, 40, 2f, 4e, a8, d8, c8, c7, df, 3b, a2, c8, b6, c8, 27, e4, 84, d6, 7a, 33, cb, 97, 4a, 70, dc, 43, a9, cd, a3, ff, 30, 42, 2e, 21, dc, 8e, c7, 1c, 5d, 55, a3, c1, 75, d5, a5, 1e, 2e, ee, df, 65, 3f, 4c, 69, 40, d0, 07, 05, a9, 73, 3d, 1e, e1, 71, 82, 73, 2c, d1, dd, fc, 58, 63, 69, f7, 8f, b7, e1, 2a, 90, 8e, 0b, 07, 82, 56, d4, 36, 7d, 68, 1f, 02, b2, 7e, 7e, d8, de, ef, ec, 65, 2a, c8, 4b, 09, 6b, dd, 2e, a1, 05, 9a, fb, ea, 11, 05, 53, 02, ca, bf, de, 63, 10, fb, c0, 24, 38, 18, 4d, aa, 6b, 91, 00] +udp_resp 0103800000d5a4b46be5b8c61f77d8125e61cbc5c4057ad41ad8d138a68ba1634cb1b1cac9b40dd375bb764eb54550d2cd636592e071337821551c5d8ffc6ff0dc4e717586e43fc73c505c00b6db40de7057aee55d30c2dab80b2d5a59f2618ab16989045b8ee8750f512ea80af79967cf3159656481eba5c3361a0f6df5e531fb4ae08ec453d83c33ec60143815e7eab8b73d67981c87bf8401dbb26955373f028cddd5880c59841155a15aedebd808512170e79e408750ea5fc69be90a9c6334472867a74b30ae697318e9b6a636392acdaeadbddc6804b2d692c00dbb712e3cf243400408202dac UdpSessionResp { status: 1, error_code: 3, message: [164, 180, 107, 229, 184, 198, 31, 119, 216, 18, 94, 97, 203, 197, 196, 5, 122, 212, 26, 216, 209, 56, 166, 139, 161, 99, 76, 177, 177, 202, 201, 180, 13, 211, 117, 187, 118, 78, 181, 69, 80, 210, 205, 99, 101, 146, 224, 113, 51, 120, 33, 85, 28, 93, 143, 252, 111, 240, 220, 78, 113, 117, 134, 228, 63, 199, 60, 80, 92, 0, 182, 219, 64, 222, 112, 87, 174, 229, 93, 48, 194, 218, 184, 11, 45, 90, 89, 242, 97, 138, 177, 105, 137, 4, 91, 142, 232, 117, 15, 81, 46, 168, 10, 247, 153, 103, 207, 49, 89, 101, 100, 129, 235, 165, 195, 54, 26, 15, 109, 245, 229, 49, 251, 74, 224, 142, 196, 83, 216, 60, 51, 236, 96, 20, 56, 21, 231, 234, 184, 183, 61, 103, 152, 28, 135, 191, 132, 1, 219, 178, 105, 85, 55, 63, 2, 140, 221, 213, 136, 12, 89, 132, 17, 85, 161, 90, 237, 235, 216, 8, 81, 33, 112, 231, 158, 64, 135, 80, 234, 95, 198, 155, 233, 10, 156, 99, 52, 71, 40, 103, 167, 75, 48, 174, 105, 115, 24, 233, 182, 166, 54, 57, 42, 205, 174, 173, 189, 220, 104, 4, 178, 214, 146], idle_timeout_ms: 3865269478683203 };233;[01, 03, 40, d5, a4, b4, 6b, e5, b8, c6, 1f, 77, d8, 12, 5e, 61, cb, c5, c4, 05, 7a, d4, 1a, d8, d1, 38, a6, 8b, a1, 63, 4c, b1, b1, ca, c9, b4, 0d, d3, 75, bb, 76, 4e, b5, 45, 50, d2, cd, 63, 65, 92, e0, 71, 33, 78, 21, 55, 1c, 5d, 8f, fc, 6f, f0, dc, 4e, 71, 75, 86, e4, 3f, c7, 3c, 50, 5c, 00, b6, db, 40, de, 70, 57, ae, e5, 5d, 30, c2, da, b8, 0b, 2d, 5a, 59, f2, 61, 8a, b1, 69, 89, 04, 5b, 8e, e8, 75, 0f, 51, 2e, a8, 0a, f7, 99, 67, cf, 31, 59, 65, 64, 81, eb, a5, c3, 36, 1a, 0f, 6d, f5, e5, 31, fb, 4a, e0, 8e, c4, 53, d8, 3c, 33, ec, 60, 14, 38, 15, e7, ea, b8, b7, 3d, 67, 98, 1c, 87, bf, 84, 01, db, b2, 69, 55, 37, 3f, 02, 8c, dd, d5, 88, 0c, 59, 84, 11, 55, a1, 5a, ed, eb, d8, 08, 51, 21, 70, e7, 9e, 40, 87, 50, ea, 5f, c6, 9b, e9, 0a, 9c, 63, 34, 47, 28, 67, a7, 4b, 30, ae, 69, 73, 18, e9, b6, a6, 36, 39, 2a, cd, ae, ad, bd, dc, 68, 04, b2, d6, 92, c0, 0d, bb, 71, 2e, 3c, f2, 43, 00] +udp_resp 01400240a1c72290be145aaa35d1a1fb61f44010caf4a3995f144ee2cc496f120b3e1c3380b35728c971611438d0a7a192b67e36fc026828af0d8525b2e0e8e3ee602d34160af700801d2f1b18c9d3f3a142f8528daccef0be2c56f56957557509fb46aec851c9fd6bdd592e8cea5e65ccb3ec89be6ece3ccf6d3f6a439a384165e129cef120ae8c66e9c791d83e3e9301f9eda1a52ab147063edd730f91424c66f6e8362beb800071a000a046be6d3a UdpSessionResp { status: 1, error_code: 2, message: [199, 34, 144, 190, 20, 90, 170, 53, 209, 161, 251, 97, 244, 64, 16, 202, 244, 163, 153, 95, 20, 78, 226, 204, 73, 111, 18, 11, 62, 28, 51, 128, 179, 87, 40, 201, 113, 97, 20, 56, 208, 167, 161, 146, 182, 126, 54, 252, 2, 104, 40, 175, 13, 133, 37, 178, 224, 232, 227, 238, 96, 45, 52, 22, 10, 247, 0, 128, 29, 47, 27, 24, 201, 211, 243, 161, 66, 248, 82, 141, 172, 206, 240, 190, 44, 86, 245, 105, 87, 85, 117, 9, 251, 70, 174, 200, 81, 201, 253, 107, 221, 89, 46, 140, 234, 94, 101, 204, 179, 236, 137, 190, 110, 206, 60, 207, 109, 63, 106, 67, 154, 56, 65, 101, 225, 41, 206, 241, 32, 174, 140, 102, 233, 199, 145, 216, 62, 62, 147, 1, 249, 237, 161, 165, 42, 177, 71, 6, 62, 221, 115, 15, 145, 66, 76, 102, 246, 232, 54, 43, 235], idle_timeout_ms: 29088 };171;[01, 02, 40, a1, c7, 22, 90, be, 14, 5a, aa, 35, d1, a1, fb, 61, f4, 40, 10, ca, f4, a3, 99, 5f, 14, 4e, e2, cc, 49, 6f, 12, 0b, 3e, 1c, 33, 80, b3, 57, 28, c9, 71, 61, 14, 38, d0, a7, a1, 92, b6, 7e, 36, fc, 02, 68, 28, af, 0d, 85, 25, b2, e0, e8, e3, ee, 60, 2d, 34, 16, 0a, f7, 00, 80, 1d, 2f, 1b, 18, c9, d3, f3, a1, 42, f8, 52, 8d, ac, ce, f0, be, 2c, 56, f5, 69, 57, 55, 75, 09, fb, 46, ae, c8, 51, c9, fd, 6b, dd, 59, 2e, 8c, ea, 5e, 65, cc, b3, ec, 89, be, 6e, ce, 3c, cf, 6d, 3f, 6a, 43, 9a, 38, 41, 65, e1, 29, ce, f1, 20, ae, 8c, 66, e9, c7, 91, d8, 3e, 3e, 93, 01, f9, ed, a1, a5, 2a, b1, 47, 06, 3e, dd, 73, 0f, 91, 42, 4c, 66, f6, e8, 36, 2b, eb, 80, 00, 71, a0, 00] +udp_resp 01c000000000000002c0000000000000cca46369f2bd22062f958f686f1defb9c375c07e8c52c78d6cf99d2f994ca856fbdfc09f547e6caa7cdcfd8e846b8c993189f896fc53a438eacbf02892b04090af6d0ac7df15b8d9e7c4590ebbb82998525d512181ed44d3be3a21868b4053ec2418c4e932d52d3084bef42ba1e8b63f7edaa3bd44cddc457fc86cf2ff9beccc826cc2cd7f00e5372ecf29729a3d2375c1a70b2719810ad96a65e13dff59101d908dfdfaafb78f3ea28a6d4554b2eaa8d0382b36ff823654cd22e16c640d5b017cff1e711f0bacd158ee3aa69402c0000000000000073dc546c9786b26 UdpSessionResp { status: 1, error_code: 2, message: [164, 99, 105, 242, 189, 34, 6, 47, 149, 143, 104, 111, 29, 239, 185, 195, 117, 192, 126, 140, 82, 199, 141, 108, 249, 157, 47, 153, 76, 168, 86, 251, 223, 192, 159, 84, 126, 108, 170, 124, 220, 253, 142, 132, 107, 140, 153, 49, 137, 248, 150, 252, 83, 164, 56, 234, 203, 240, 40, 146, 176, 64, 144, 175, 109, 10, 199, 223, 21, 184, 217, 231, 196, 89, 14, 187, 184, 41, 152, 82, 93, 81, 33, 129, 237, 68, 211, 190, 58, 33, 134, 139, 64, 83, 236, 36, 24, 196, 233, 50, 213, 45, 48, 132, 190, 244, 43, 161, 232, 182, 63, 126, 218, 163, 189, 68, 205, 220, 69, 127, 200, 108, 242, 255, 155, 236, 204, 130, 108, 194, 205, 127, 0, 229, 55, 46, 207, 41, 114, 154, 61, 35, 117, 193, 167, 11, 39, 25, 129, 10, 217, 106, 101, 225, 61, 255, 89, 16, 29, 144, 141, 253, 250, 175, 183, 143, 62, 162, 138, 109, 69, 84, 178, 234, 168, 208, 56, 43, 54, 255, 130, 54, 84, 205, 34, 225, 108, 100, 13, 91, 1, 124, 255, 30, 113, 31, 11, 172, 209, 88, 238, 58, 166, 148], idle_timeout_ms: 2 };237;[01, 02, 40, cc, a4, 63, 69, f2, bd, 22, 06, 2f, 95, 8f, 68, 6f, 1d, ef, b9, c3, 75, c0, 7e, 8c, 52, c7, 8d, 6c, f9, 9d, 2f, 99, 4c, a8, 56, fb, df, c0, 9f, 54, 7e, 6c, aa, 7c, dc, fd, 8e, 84, 6b, 8c, 99, 31, 89, f8, 96, fc, 53, a4, 38, ea, cb, f0, 28, 92, b0, 40, 90, af, 6d, 0a, c7, df, 15, b8, d9, e7, c4, 59, 0e, bb, b8, 29, 98, 52, 5d, 51, 21, 81, ed, 44, d3, be, 3a, 21, 86, 8b, 40, 53, ec, 24, 18, c4, e9, 32, d5, 2d, 30, 84, be, f4, 2b, a1, e8, b6, 3f, 7e, da, a3, bd, 44, cd, dc, 45, 7f, c8, 6c, f2, ff, 9b, ec, cc, 82, 6c, c2, cd, 7f, 00, e5, 37, 2e, cf, 29, 72, 9a, 3d, 23, 75, c1, a7, 0b, 27, 19, 81, 0a, d9, 6a, 65, e1, 3d, ff, 59, 10, 1d, 90, 8d, fd, fa, af, b7, 8f, 3e, a2, 8a, 6d, 45, 54, b2, ea, a8, d0, 38, 2b, 36, ff, 82, 36, 54, cd, 22, e1, 6c, 64, 0d, 5b, 01, 7c, ff, 1e, 71, 1f, 0b, ac, d1, 58, ee, 3a, a6, 94, 02, 00] +udp_resp 0040004095fa22266c829e406884d6b199aff61d815d85b3d020e0f9a427ce0de0f6a20806fed1d9f273c435d56d419db849dd5729afc9a7c0b0222da75de6406e41acc3275d01f33f895a45ad4e268cf393d2c2bde7bdfde84d75d9b7b3f227fab9ee619cbd557533e6db080f524f58bbc3bde32089da6b1771b160b3c91cb542b6c0d8874f5614f3f775e09181467f4dc1daf095518ae57491800006e04000 UdpSessionResp { status: 0, error_code: 0, message: [250, 34, 38, 108, 130, 158, 64, 104, 132, 214, 177, 153, 175, 246, 29, 129, 93, 133, 179, 208, 32, 224, 249, 164, 39, 206, 13, 224, 246, 162, 8, 6, 254, 209, 217, 242, 115, 196, 53, 213, 109, 65, 157, 184, 73, 221, 87, 41, 175, 201, 167, 192, 176, 34, 45, 167, 93, 230, 64, 110, 65, 172, 195, 39, 93, 1, 243, 63, 137, 90, 69, 173, 78, 38, 140, 243, 147, 210, 194, 189, 231, 189, 253, 232, 77, 117, 217, 183, 179, 242, 39, 250, 185, 238, 97, 156, 189, 85, 117, 51, 230, 219, 8, 15, 82, 79, 88, 187, 195, 189, 227, 32, 137, 218, 107, 23, 113, 177, 96, 179, 201, 28, 181, 66, 182, 192, 216, 135, 79, 86, 20, 243, 247, 117, 224, 145, 129, 70, 127, 77, 193, 218, 240, 149, 81, 138, 229, 116, 145], idle_timeout_ms: 1760 };160;[00, 00, 40, 95, fa, 22, 26, 6c, 82, 9e, 40, 68, 84, d6, b1, 99, af, f6, 1d, 81, 5d, 85, b3, d0, 20, e0, f9, a4, 27, ce, 0d, e0, f6, a2, 08, 06, fe, d1, d9, f2, 73, c4, 35, d5, 6d, 41, 9d, b8, 49, dd, 57, 29, af, c9, a7, c0, b0, 22, 2d, a7, 5d, e6, 40, 6e, 41, ac, c3, 27, 5d, 01, f3, 3f, 89, 5a, 45, ad, 4e, 26, 8c, f3, 93, d2, c2, bd, e7, bd, fd, e8, 4d, 75, d9, b7, b3, f2, 27, fa, b9, ee, 61, 9c, bd, 55, 75, 33, e6, db, 08, 0f, 52, 4f, 58, bb, c3, bd, e3, 20, 89, da, 6b, 17, 71, b1, 60, b3, c9, 1c, b5, 42, b6, c0, d8, 87, 4f, 56, 14, f3, f7, 75, e0, 91, 81, 46, 7f, 4d, c1, da, f0, 95, 51, 8a, e5, 74, 91, 46, e0, 00] +udp_resp 00008000003c1b995611177adac62e0f55df94a293a466d05c87991e82c4cbad570887ef351f409832082d75be0ce0ae64980bf5d783fdefdaba34f04c3dbc033319c00000020fe89437c000000000000000 UdpSessionResp { status: 0, error_code: 0, message: [27, 153, 86, 17, 23, 122, 218, 198, 46, 15, 85, 223, 148, 162, 147, 164, 102, 208, 92, 135, 153, 30, 130, 196, 203, 173, 87, 8, 135, 239, 53, 31, 64, 152, 50, 8, 45, 117, 190, 12, 224, 174, 100, 152, 11, 245, 215, 131, 253, 239, 218, 186, 52, 240, 76, 61, 188, 3, 51, 25], idle_timeout_ms: 8856835127 };82;[00, 00, 3c, 1b, 99, 56, 11, 17, 7a, da, c6, 2e, 0f, 55, df, 94, a2, 93, a4, 66, d0, 5c, 87, 99, 1e, 82, c4, cb, ad, 57, 08, 87, ef, 35, 1f, 40, 98, 32, 08, 2d, 75, be, 0c, e0, ae, 64, 98, 0b, f5, d7, 83, fd, ef, da, ba, 34, f0, 4c, 3d, bc, 03, 33, 19, c0, 00, 00, 02, 0f, e8, 94, 37, 00] +udp_resp 0104409e7de43a61884ecd321c7a91e7df5486c8aa965ab8f8e08b77fad93c2a83a9d54ff957be9d3a7ce831aa801f97aca2dc045e0e2d02d4e35337298bab1ad1a8657f3a9e479b88dd46e9542365d051ff7abab2f3e1a791d2d09a0a99ec270ca4ae7c4f46adc0c4d51d4832351f403c7ae53058c6215c06dab444112a8b35536bd4c55d130ce32c42886d8773be5869d7ebb77b0146f4b01835e93d9d62548293cd5361bcd9f205d0c000000000000000 UdpSessionResp { status: 1, error_code: 4, message: [125, 228, 58, 97, 136, 78, 205, 50, 28, 122, 145, 231, 223, 84, 134, 200, 170, 150, 90, 184, 248, 224, 139, 119, 250, 217, 60, 42, 131, 169, 213, 79, 249, 87, 190, 157, 58, 124, 232, 49, 170, 128, 31, 151, 172, 162, 220, 4, 94, 14, 45, 2, 212, 227, 83, 55, 41, 139, 171, 26, 209, 168, 101, 127, 58, 158, 71, 155, 136, 221, 70, 233, 84, 35, 101, 208, 81, 255, 122, 186, 178, 243, 225, 167, 145, 210, 208, 154, 10, 153, 236, 39, 12, 164, 174, 124, 79, 70, 173, 192, 196, 213, 29, 72, 50, 53, 31, 64, 60, 122, 229, 48, 88, 198, 33, 92, 6, 218, 180, 68, 17, 42, 139, 53, 83, 107, 212, 197, 93, 19, 12, 227, 44, 66, 136, 109, 135, 115, 190, 88, 105, 215, 235, 183, 123, 1, 70, 244, 176, 24, 53, 233, 61, 157, 98, 84, 130, 147], idle_timeout_ms: 960218609298310608 };178;[01, 04, 40, 9e, 7d, e4, 3a, 61, 88, 4e, cd, 32, 1c, 7a, 91, e7, df, 54, 86, c8, aa, 96, 5a, b8, f8, e0, 8b, 77, fa, d9, 3c, 2a, 83, a9, d5, 4f, f9, 57, be, 9d, 3a, 7c, e8, 31, aa, 80, 1f, 97, ac, a2, dc, 04, 5e, 0e, 2d, 02, d4, e3, 53, 37, 29, 8b, ab, 1a, d1, a8, 65, 7f, 3a, 9e, 47, 9b, 88, dd, 46, e9, 54, 23, 65, d0, 51, ff, 7a, ba, b2, f3, e1, a7, 91, d2, d0, 9a, 0a, 99, ec, 27, 0c, a4, ae, 7c, 4f, 46, ad, c0, c4, d5, 1d, 48, 32, 35, 1f, 40, 3c, 7a, e5, 30, 58, c6, 21, 5c, 06, da, b4, 44, 11, 2a, 8b, 35, 53, 6b, d4, c5, 5d, 13, 0c, e3, 2c, 42, 88, 6d, 87, 73, be, 58, 69, d7, eb, b7, 7b, 01, 46, f4, b0, 18, 35, e9, 3d, 9d, 62, 54, 82, 93, cd, 53, 61, bc, d9, f2, 05, d0, 00] +udp_resp 00c00000000000000040f2c56c09ae661c12340fcc411c958a3e801caa7848c4ae2bed87fc166cda8cb86999d1946a74d7ae30ac02ed035b3f283fa7979369b35e2122dd946950e7e126147bc9f3475dd9fdc96f943e7416ebb193193c7323a14adc32f5c0f8a4accd82f8b29dc9a4e0b8806710ed29abfc1bb50088f5ae5232db2dede99f3d9df5fb2b7974a27cbb0f15c6289f859e915de8ce4c88b927ce7400d1de787d5f3256b298d9f1f5368bf0e70060e6d90248bd69195725073b7f723bc53840a2bdd3270b848fffdc57fc6c15656fd941ec41c82cefd8ee190ac5861e18c10ba282ea0621582d6370323d167d062a6bcfb685e41a9dccf7dfc0000010d40eddd14000 UdpSessionResp { status: 0, error_code: 0, message: [197, 108, 9, 174, 102, 28, 18, 52, 15, 204, 65, 28, 149, 138, 62, 128, 28, 170, 120, 72, 196, 174, 43, 237, 135, 252, 22, 108, 218, 140, 184, 105, 153, 209, 148, 106, 116, 215, 174, 48, 172, 2, 237, 3, 91, 63, 40, 63, 167, 151, 147, 105, 179, 94, 33, 34, 221, 148, 105, 80, 231, 225, 38, 20, 123, 201, 243, 71, 93, 217, 253, 201, 111, 148, 62, 116, 22, 235, 177, 147, 25, 60, 115, 35, 161, 74, 220, 50, 245, 192, 248, 164, 172, 205, 130, 248, 178, 157, 201, 164, 224, 184, 128, 103, 16, 237, 41, 171, 252, 27, 181, 0, 136, 245, 174, 82, 50, 219, 45, 237, 233, 159, 61, 157, 245, 251, 43, 121, 116, 162, 124, 187, 15, 21, 198, 40, 159, 133, 158, 145, 93, 232, 206, 76, 136, 185, 39, 206, 116, 0, 209, 222, 120, 125, 95, 50, 86, 178, 152, 217, 241, 245, 54, 139, 240, 231, 0, 96, 230, 217, 2, 72, 189, 105, 25, 87, 37, 7, 59, 127, 114, 59, 197, 56, 64, 162, 189, 211, 39, 11, 132, 143, 255, 220, 87, 252, 108, 21, 101, 111, 217, 65, 236, 65, 200, 44, 239, 216, 238, 25, 10, 197, 134, 30, 24, 193, 11, 162, 130, 234, 6, 33, 88, 45, 99, 112, 50, 61, 22, 125, 6, 42, 107, 207, 182, 133, 228, 26, 157, 204, 247, 223], idle_timeout_ms: 72277220817 };263;[00, 00, 40, f2, c5, 6c, 09, ae, 66, 1c, 12, 34, 0f, cc, 41, 1c, 95, 8a, 3e, 80, 1c, aa, 78, 48, c4, ae, 2b, ed, 87, fc, 16, 6c, da, 8c, b8, 69, 99, d1, 94, 6a, 74, d7, ae, 30, ac, 02, ed, 03, 5b, 3f, 28, 3f, a7, 97, 93, 69, b3, 5e, 21, 22, dd, 94, 69, 50, e7, e1, 26, 14, 7b, c9, f3, 47, 5d, d9, fd, c9, 6f, 94, 3e, 74, 16, eb, b1, 93, 19, 3c, 73, 23, a1, 4a, dc, 32, f5, c0, f8, a4, ac, cd, 82, f8, b2, 9d, c9, a4, e0, b8, 80, 67, 10, ed, 29, ab, fc, 1b, b5, 00, 88, f5, ae, 52, 32, db, 2d, ed, e9, 9f, 3d, 9d, f5, fb, 2b, 79, 74, a2, 7c, bb, 0f, 15, c6, 28, 9f, 85, 9e, 91, 5d, e8, ce, 4c, 88, b9, 27, ce, 74, 00, d1, de, 78, 7d, 5f, 32, 56, b2, 98, d9, f1, f5, 36, 8b, f0, e7, 00, 60, e6, d9, 02, 48, bd, 69, 19, 57, 25, 07, 3b, 7f, 72, 3b, c5, 38, 40, a2, bd, d3, 27, 0b, 84, 8f, ff, dc, 57, fc, 6c, 15, 65, 6f, d9, 41, ec, 41, c8, 2c, ef, d8, ee, 19, 0a, c5, 86, 1e, 18, c1, 0b, a2, 82, ea, 06, 21, 58, 2d, 63, 70, 32, 3d, 16, 7d, 06, 2a, 6b, cf, b6, 85, e4, 1a, 9d, cc, f7, df, c0, 00, 00, 10, d4, 0e, dd, d1, 00] +udp_resp 03801085cf409e25d2ed27f0ba1435ac979b974b492d01faf0f93b8f6fd0f031fa79062c061e055ca222596c75783dfc9cc55f0176f40d72d05472b1f148dd3e01b1d1e12256c408ea9df51aa86c6edac0f7a83ede9f4e6215211254eceb160c0bbab5e4760a4e85ce56d370e7a860a9b667ed5469da0e6bb656482c0783d41d473d7f740515490a0c363895d9a47b4ab75b469e096a4a0c4090296a2f6d315cbe30603f8440a14000 reject +udp_resp 008000000040a45e537c5bb845e5fb89fb89ea9f779af91233dbe69abf88f9eaafea2ab1effe8b102a9171fb1ec1283a1c39b9be0bf7801db8dfe6801478dc1a1dfc56dddd2c409d5add0a83c1d64d866d9a002beda7f34fca3e0221736934023203172c15f5fc3307fd0cb7a229d5b24e732cd2c84a71a19c109eb8a73ca2ace7053ee1499c2b8d0718dff2e9d41f3967108c21314cf6ed751be9f09b0810222a05969abf779e52ed84f6c0128ef515ba283800a0d2e5 UdpSessionResp { status: 0, error_code: 0, message: [94, 83, 124, 91, 184, 69, 229, 251, 137, 251, 137, 234, 159, 119, 154, 249, 18, 51, 219, 230, 154, 191, 136, 249, 234, 175, 234, 42, 177, 239, 254, 139, 16, 42, 145, 113, 251, 30, 193, 40, 58, 28, 57, 185, 190, 11, 247, 128, 29, 184, 223, 230, 128, 20, 120, 220, 26, 29, 252, 86, 221, 221, 44, 64, 157, 90, 221, 10, 131, 193, 214, 77, 134, 109, 154, 0, 43, 237, 167, 243, 79, 202, 62, 2, 33, 115, 105, 52, 2, 50, 3, 23, 44, 21, 245, 252, 51, 7, 253, 12, 183, 162, 41, 213, 178, 78, 115, 44, 210, 200, 74, 113, 161, 156, 16, 158, 184, 167, 60, 162, 172, 231, 5, 62, 225, 73, 156, 43, 141, 7, 24, 223, 242, 233, 212, 31, 57, 103, 16, 140, 33, 49, 76, 246, 237, 117, 27, 233, 240, 155, 8, 16, 34, 42, 5, 150, 154, 191, 119, 158, 82, 237, 132, 246], idle_timeout_ms: 5223732863445048 };180;[00, 00, 40, a4, 5e, 53, 7c, 5b, b8, 45, e5, fb, 89, fb, 89, ea, 9f, 77, 9a, f9, 12, 33, db, e6, 9a, bf, 88, f9, ea, af, ea, 2a, b1, ef, fe, 8b, 10, 2a, 91, 71, fb, 1e, c1, 28, 3a, 1c, 39, b9, be, 0b, f7, 80, 1d, b8, df, e6, 80, 14, 78, dc, 1a, 1d, fc, 56, dd, dd, 2c, 40, 9d, 5a, dd, 0a, 83, c1, d6, 4d, 86, 6d, 9a, 00, 2b, ed, a7, f3, 4f, ca, 3e, 02, 21, 73, 69, 34, 02, 32, 03, 17, 2c, 15, f5, fc, 33, 07, fd, 0c, b7, a2, 29, d5, b2, 4e, 73, 2c, d2, c8, 4a, 71, a1, 9c, 10, 9e, b8, a7, 3c, a2, ac, e7, 05, 3e, e1, 49, 9c, 2b, 8d, 07, 18, df, f2, e9, d4, 1f, 39, 67, 10, 8c, 21, 31, 4c, f6, ed, 75, 1b, e9, f0, 9b, 08, 10, 22, 2a, 05, 96, 9a, bf, 77, 9e, 52, ed, 84, f6, c0, 12, 8e, f5, 15, ba, 28, 38, 00] +udp_resp 00c00000000000000541012046734410aa980163320ae8255035fd1c73fb7bd825b8d8237f2a8d6f1ed4e0a1d5f7c77fa848e351841f708195e68556e11cfbaa92b69fdffa16b35eed59c9ac7f8d57ace5012e6db05d3cdf1e354f262ef8a038442db6d35d6cd1c51976c82109ae879881fc6ee1d0ca3c91ac73209ea5ba13729425bbbec48c7cd6b21974a55ffcbffb38047ce29eb77c5278e97edb36b0f1976e464388c091edf6fa2fa7251aa73b94df79cb65aeaad1ff7a4cab74739c551dad0d100299929f009d363edb9e5ae5cbab0d342f8b6333e3adc353cc241a9e3382d074f2920510cc4b93dc3b56f27774c5593edbd6652813e391a8fac9b7a61ac73976b48fe217fd3de95687db4f52c046f8b020400056c75dec5baf7b reject +udp_resp 0000404b96604ee04cf943a8dee9b883dd7c8a391e13a70b316a0c4c64fe7f520439228396a736183ae4e8807f6275651a7306238880dcdaf6fee977cb94610dcf9e14fc7c8eadfda3b8e6401e9948405d00 UdpSessionResp { status: 0, error_code: 0, message: [150, 96, 78, 224, 76, 249, 67, 168, 222, 233, 184, 131, 221, 124, 138, 57, 30, 19, 167, 11, 49, 106, 12, 76, 100, 254, 127, 82, 4, 57, 34, 131, 150, 167, 54, 24, 58, 228, 232, 128, 127, 98, 117, 101, 26, 115, 6, 35, 136, 128, 220, 218, 246, 254, 233, 119, 203, 148, 97, 13, 207, 158, 20, 252, 124, 142, 173, 253, 163, 184, 230, 64, 30, 153, 72], idle_timeout_ms: 93 };82;[00, 00, 40, 4b, 96, 60, 4e, e0, 4c, f9, 43, a8, de, e9, b8, 83, dd, 7c, 8a, 39, 1e, 13, a7, 0b, 31, 6a, 0c, 4c, 64, fe, 7f, 52, 04, 39, 22, 83, 96, a7, 36, 18, 3a, e4, e8, 80, 7f, 62, 75, 65, 1a, 73, 06, 23, 88, 80, dc, da, f6, fe, e9, 77, cb, 94, 61, 0d, cf, 9e, 14, fc, 7c, 8e, ad, fd, a3, b8, e6, 40, 1e, 99, 48, 40, 5d, 00] +udp_resp 00c0000000000000008000008bbcfb0a40a1e9b88c01901a85c5ce3a87abe3aedc3da4ad006b1bc68cf495c9c140521d43cfba2702d7747b8f022d7f2ced5983e1fad1263186b59cd92331e22fb3352cc2c6e9e0aa374218819ce3b43dd19c919f23849ef82b5a8c21dbd63f7cb7b487cb6945900f954a8a9fbeaa0c5d1b6da65c2fe18eb5868cc092f4a64b45328db28365e9b693c14363c000000001a76278400070 UdpSessionResp { status: 0, error_code: 0, message: [188, 251, 10, 64, 161, 233, 184, 140, 1, 144, 26, 133, 197, 206, 58, 135, 171, 227, 174, 220, 61, 164, 173, 0, 107, 27, 198, 140, 244, 149, 201, 193, 64, 82, 29, 67, 207, 186, 39, 2, 215, 116, 123, 143, 2, 45, 127, 44, 237, 89, 131, 225, 250, 209, 38, 49, 134, 181, 156, 217, 35, 49, 226, 47, 179, 53, 44, 194, 198, 233, 224, 170, 55, 66, 24, 129, 156, 227, 180, 61, 209, 156, 145, 159, 35, 132, 158, 248, 43, 90, 140, 33, 219, 214, 63, 124, 183, 180, 135, 203, 105, 69, 144, 15, 149, 74, 138, 159, 190, 170, 12, 93, 27, 109, 166, 92, 47, 225, 142, 181, 134, 140, 192, 146, 244, 166, 75, 69, 50, 141, 178, 131, 101, 233, 182, 147, 193, 67, 99], idle_timeout_ms: 27746936 };162;[00, 00, 40, 8b, bc, fb, 0a, 40, a1, e9, b8, 8c, 01, 90, 1a, 85, c5, ce, 3a, 87, ab, e3, ae, dc, 3d, a4, ad, 00, 6b, 1b, c6, 8c, f4, 95, c9, c1, 40, 52, 1d, 43, cf, ba, 27, 02, d7, 74, 7b, 8f, 02, 2d, 7f, 2c, ed, 59, 83, e1, fa, d1, 26, 31, 86, b5, 9c, d9, 23, 31, e2, 2f, b3, 35, 2c, c2, c6, e9, e0, aa, 37, 42, 18, 81, 9c, e3, b4, 3d, d1, 9c, 91, 9f, 23, 84, 9e, f8, 2b, 5a, 8c, 21, db, d6, 3f, 7c, b7, b4, 87, cb, 69, 45, 90, 0f, 95, 4a, 8a, 9f, be, aa, 0c, 5d, 1b, 6d, a6, 5c, 2f, e1, 8e, b5, 86, 8c, c0, 92, f4, a6, 4b, 45, 32, 8d, b2, 83, 65, e9, b6, 93, c1, 43, 63, 81, a7, 62, 78, 00] +udp_resp 01400240c66244d990de35b88aec1a523a3db7d666c8180076ca31cbe0507c67a23092eca6cbfca466db548731483abb996eeddefce24a13dcf361d919fdabdc72ff13a4b6583c2e2f20dc4169b042d3ca77c8711b979604ff73d136cdb1c889b883b5f9e204a850facd80e6876e54522ace000cedafcff4309f693daad905a39365ba0cc58d17e42c06f76222d93f4836b10b3beb3bae786db4700751bfddcd56cf66086ebb9fac54c28c1a6621205e7348313c0424abee1fb2fd7f46332c4ab200132b5b7acf662a4b9ac001c3c6c2a90c704001599a94ff082e UdpSessionResp { status: 1, error_code: 2, message: [98, 68, 217, 144, 222, 53, 184, 138, 236, 26, 82, 58, 61, 183, 214, 102, 200, 24, 0, 118, 202, 49, 203, 224, 80, 124, 103, 162, 48, 146, 236, 166, 203, 252, 164, 102, 219, 84, 135, 49, 72, 58, 187, 153, 110, 237, 222, 252, 226, 74, 19, 220, 243, 97, 217, 25, 253, 171, 220, 114, 255, 19, 164, 182, 88, 60, 46, 47, 32, 220, 65, 105, 176, 66, 211, 202, 119, 200, 113, 27, 151, 150, 4, 255, 115, 209, 54, 205, 177, 200, 137, 184, 131, 181, 249, 226, 4, 168, 80, 250, 205, 128, 230, 135, 110, 84, 82, 42, 206, 0, 12, 237, 175, 207, 244, 48, 159, 105, 61, 170, 217, 5, 163, 147, 101, 186, 12, 197, 141, 23, 228, 44, 6, 247, 98, 34, 217, 63, 72, 54, 177, 11, 59, 235, 59, 174, 120, 109, 180, 112, 7, 81, 191, 221, 205, 86, 207, 102, 8, 110, 187, 159, 172, 84, 194, 140, 26, 102, 33, 32, 94, 115, 72, 49, 60, 4, 36, 171, 238, 31, 178, 253, 127, 70, 51, 44, 74, 178, 0, 19, 43, 91, 122, 207, 102, 42, 75, 154], idle_timeout_ms: 496733413510256 };214;[01, 02, 40, c6, 62, 44, d9, 90, de, 35, b8, 8a, ec, 1a, 52, 3a, 3d, b7, d6, 66, c8, 18, 00, 76, ca, 31, cb, e0, 50, 7c, 67, a2, 30, 92, ec, a6, cb, fc, a4, 66, db, 54, 87, 31, 48, 3a, bb, 99, 6e, ed, de, fc, e2, 4a, 13, dc, f3, 61, d9, 19, fd, ab, dc, 72, ff, 13, a4, b6, 58, 3c, 2e, 2f, 20, dc, 41, 69, b0, 42, d3, ca, 77, c8, 71, 1b, 97, 96, 04, ff, 73, d1, 36, cd, b1, c8, 89, b8, 83, b5, f9, e2, 04, a8, 50, fa, cd, 80, e6, 87, 6e, 54, 52, 2a, ce, 00, 0c, ed, af, cf, f4, 30, 9f, 69, 3d, aa, d9, 05, a3, 93, 65, ba, 0c, c5, 8d, 17, e4, 2c, 06, f7, 62, 22, d9, 3f, 48, 36, b1, 0b, 3b, eb, 3b, ae, 78, 6d, b4, 70, 07, 51, bf, dd, cd, 56, cf, 66, 08, 6e, bb, 9f, ac, 54, c2, 8c, 1a, 66, 21, 20, 5e, 73, 48, 31, 3c, 04, 24, ab, ee, 1f, b2, fd, 7f, 46, 33, 2c, 4a, b2, 00, 13, 2b, 5b, 7a, cf, 66, 2a, 4b, 9a, c0, 01, c3, c6, c2, a9, 0c, 70, 00] +udp_resp 004000c00000000000005144bdb90537ae685fc53737f1d89cf94999ee348511904fb99ca5b485674d32ff76fe42686e77768bed546aa273e2c834fd73c6d2d61bfd552b20fdde2345a11b326bc132dcfc77f37af75608986a1ec108c00003cb52aa2ecfc000000000000000 UdpSessionResp { status: 0, error_code: 0, message: [68, 189, 185, 5, 55, 174, 104, 95, 197, 55, 55, 241, 216, 156, 249, 73, 153, 238, 52, 133, 17, 144, 79, 185, 156, 165, 180, 133, 103, 77, 50, 255, 118, 254, 66, 104, 110, 119, 118, 139, 237, 84, 106, 162, 115, 226, 200, 52, 253, 115, 198, 210, 214, 27, 253, 85, 43, 32, 253, 222, 35, 69, 161, 27, 50, 107, 193, 50, 220, 252, 119, 243, 122, 247, 86, 8, 152, 106, 30, 193, 8], idle_timeout_ms: 4171800129231 };108;[00, 00, 40, 51, 44, bd, b9, 05, 37, ae, 68, 5f, c5, 37, 37, f1, d8, 9c, f9, 49, 99, ee, 34, 85, 11, 90, 4f, b9, 9c, a5, b4, 85, 67, 4d, 32, ff, 76, fe, 42, 68, 6e, 77, 76, 8b, ed, 54, 6a, a2, 73, e2, c8, 34, fd, 73, c6, d2, d6, 1b, fd, 55, 2b, 20, fd, de, 23, 45, a1, 1b, 32, 6b, c1, 32, dc, fc, 77, f3, 7a, f7, 56, 08, 98, 6a, 1e, c1, 08, c0, 00, 03, cb, 52, aa, 2e, cf, 00] +udp_resp 0000403db4e2851b75249ea5ded5f0a98cc12929e6b5b20ac012c188a4d19845db771e1a4107de6945bb97758ccef80fd2eaddfd18ce1a515a4c770fd2964a1ec5c00000dc45936faf4000 UdpSessionResp { status: 0, error_code: 0, message: [180, 226, 133, 27, 117, 36, 158, 165, 222, 213, 240, 169, 140, 193, 41, 41, 230, 181, 178, 10, 192, 18, 193, 136, 164, 209, 152, 69, 219, 119, 30, 26, 65, 7, 222, 105, 69, 187, 151, 117, 140, 206, 248, 15, 210, 234, 221, 253, 24, 206, 26, 81, 90, 76, 119, 15, 210, 150, 74, 30, 197], idle_timeout_ms: 946060095407 };75;[00, 00, 3d, b4, e2, 85, 1b, 75, 24, 9e, a5, de, d5, f0, a9, 8c, c1, 29, 29, e6, b5, b2, 0a, c0, 12, c1, 88, a4, d1, 98, 45, db, 77, 1e, 1a, 41, 07, de, 69, 45, bb, 97, 75, 8c, ce, f8, 0f, d2, ea, dd, fd, 18, ce, 1a, 51, 5a, 4c, 77, 0f, d2, 96, 4a, 1e, c5, c0, 00, 00, dc, 45, 93, 6f, af, 00] +udp_resp 01c000000000000001800000fb86d09d89e3ad049951c098020afcea7d8092f1c4982d2a8d7eb52bf6b4755a9b6690ecc4aad5619938290a4e17523a223c4da6556919dfbb139e53e931c94ec1577e124676df96d102abceedcd25c24fd949b33f3e4f864de173eca779cfbd40bb24f94c410d450d66bcd124906fec13fb63823ae46f4302bbccb77420938cfcfd1393c239521740a4bb1ab7e37d37d6f6b3a6c9bbdc4f56a14cd7d8fea8bda06c83f250e42a61d6632a04c88a3a295281da08c153b5fb483903754cb5e45ce230b7886622e44111493dcd1880f2e731a0c9f1d1b1f381ab800a25dad3e8da87f4d49cb0d2103775714c9ba0723dddbec2d3d95d9e4a943827e581c00107b8144cad50c000000000000000 UdpSessionResp { status: 1, error_code: 1, message: [134, 208, 157, 137, 227, 173, 4, 153, 81, 192, 152, 2, 10, 252, 234, 125, 128, 146, 241, 196, 152, 45, 42, 141, 126, 181, 43, 246, 180, 117, 90, 155, 102, 144, 236, 196, 170, 213, 97, 153, 56, 41, 10, 78, 23, 82, 58, 34, 60, 77, 166, 85, 105, 25, 223, 187, 19, 158, 83, 233, 49, 201, 78, 193, 87, 126, 18, 70, 118, 223, 150, 209, 2, 171, 206, 237, 205, 37, 194, 79, 217, 73, 179, 63, 62, 79, 134, 77, 225, 115, 236, 167, 121, 207, 189, 64, 187, 36, 249, 76, 65, 13, 69, 13, 102, 188, 209, 36, 144, 111, 236, 19, 251, 99, 130, 58, 228, 111, 67, 2, 187, 204, 183, 116, 32, 147, 140, 252, 253, 19, 147, 194, 57, 82, 23, 64, 164, 187, 26, 183, 227, 125, 55, 214, 246, 179, 166, 201, 187, 220, 79, 86, 161, 76, 215, 216, 254, 168, 189, 160, 108, 131, 242, 80, 228, 42, 97, 214, 99, 42, 4, 200, 138, 58, 41, 82, 129, 218, 8, 193, 83, 181, 251, 72, 57, 3, 117, 76, 181, 228, 92, 226, 48, 183, 136, 102, 34, 228, 65, 17, 73, 61, 205, 24, 128, 242, 231, 49, 160, 201, 241, 209, 177, 243, 129, 171, 128, 10, 37, 218, 211, 232, 218, 135, 244, 212, 156, 176, 210, 16, 55, 117, 113, 76, 155, 160, 114, 61, 221, 190, 194, 211, 217, 93, 158, 74, 148, 56, 39, 229, 129], idle_timeout_ms: 289962172656976 };280;[01, 01, 40, fb, 86, d0, 9d, 89, e3, ad, 04, 99, 51, c0, 98, 02, 0a, fc, ea, 7d, 80, 92, f1, c4, 98, 2d, 2a, 8d, 7e, b5, 2b, f6, b4, 75, 5a, 9b, 66, 90, ec, c4, aa, d5, 61, 99, 38, 29, 0a, 4e, 17, 52, 3a, 22, 3c, 4d, a6, 55, 69, 19, df, bb, 13, 9e, 53, e9, 31, c9, 4e, c1, 57, 7e, 12, 46, 76, df, 96, d1, 02, ab, ce, ed, cd, 25, c2, 4f, d9, 49, b3, 3f, 3e, 4f, 86, 4d, e1, 73, ec, a7, 79, cf, bd, 40, bb, 24, f9, 4c, 41, 0d, 45, 0d, 66, bc, d1, 24, 90, 6f, ec, 13, fb, 63, 82, 3a, e4, 6f, 43, 02, bb, cc, b7, 74, 20, 93, 8c, fc, fd, 13, 93, c2, 39, 52, 17, 40, a4, bb, 1a, b7, e3, 7d, 37, d6, f6, b3, a6, c9, bb, dc, 4f, 56, a1, 4c, d7, d8, fe, a8, bd, a0, 6c, 83, f2, 50, e4, 2a, 61, d6, 63, 2a, 04, c8, 8a, 3a, 29, 52, 81, da, 08, c1, 53, b5, fb, 48, 39, 03, 75, 4c, b5, e4, 5c, e2, 30, b7, 88, 66, 22, e4, 41, 11, 49, 3d, cd, 18, 80, f2, e7, 31, a0, c9, f1, d1, b1, f3, 81, ab, 80, 0a, 25, da, d3, e8, da, 87, f4, d4, 9c, b0, d2, 10, 37, 75, 71, 4c, 9b, a0, 72, 3d, dd, be, c2, d3, d9, 5d, 9e, 4a, 94, 38, 27, e5, 81, c0, 01, 07, b8, 14, 4c, ad, 50, 00] +udp_resp 01c000000000000003401090423f109153d514d2b7d9ec2c877bbac00031a7b65fe99c00a0ebafe957 UdpSessionResp { status: 1, error_code: 3, message: [144, 66, 63, 16, 145, 83, 213, 20, 210, 183, 217, 236, 44, 135, 123, 186], idle_timeout_ms: 54596389038492 };36;[01, 03, 10, 90, 42, 3f, 10, 91, 53, d5, 14, d2, b7, d9, ec, 2c, 87, 7b, ba, c0, 00, 31, a7, b6, 5f, e9, 9c, 00] +udp_resp 034006800000d0c11a41726b868def372a9b9b0eb7a6be5aef6c75a505131aa6635e0530133c0d7aec27f9993cff1bfa676ef304008c622956ddf98f17a6662978f5a75a37875511dc0cf70e279590a25ced79de8c2f425d10dc518f40dc9a81e6c4a3b650d16ead4ce84f443c34a1d5ac86dc11a98015ebb31097c0d0663a23eb7d16dfb5996c9e18aadb664f963011a6a72af271ff8b61e0a6aef8288cac5a8624335112a0bfd30cb5c34086881be36bfcd93d8bb685dc0037a588de4bb8140b659ff8a80ed013da3f7c12131d0137c7afb5d9c4cb80c6cd6896e59a9e32c000000000000000 reject +udp_resp 000040f33c7568df33c33a205ee7cdd153b51450e42be4d0a3f9d8367de79100d1f98b458d56819364e5705259c8b5239d266d662ba4cc6df93a77ccb8df1309a6a85fbaa961347ad202abb3275ffdd8812da9441e408e27de660a6d9e91118c411d8e0fbe00bdd0863f5a3621c6eaba7805915b8b2375508a23773a769913542fff46950543e85810961cf87a4f5425f66184df58611be5825dbafda26ca7314b24939fee8cffec929d47e72e444584535908ec4b20a9eac1a89a68eed3a63d65c92184b0ebd94ba48e5e4c7e5b4f3273a38d073880582372750d496d4548e2bcdd4f5e7667ff9d93fbfa560397772e6587f215fa0c808003827480000000 UdpSessionResp { status: 0, error_code: 0, message: [60, 117, 104, 223, 51, 195, 58, 32, 94, 231, 205, 209, 83, 181, 20, 80, 228, 43, 228, 208, 163, 249, 216, 54, 125, 231, 145, 0, 209, 249, 139, 69, 141, 86, 129, 147, 100, 229, 112, 82, 89, 200, 181, 35, 157, 38, 109, 102, 43, 164, 204, 109, 249, 58, 119, 204, 184, 223, 19, 9, 166, 168, 95, 186, 169, 97, 52, 122, 210, 2, 171, 179, 39, 95, 253, 216, 129, 45, 169, 68, 30, 64, 142, 39, 222, 102, 10, 109, 158, 145, 17, 140, 65, 29, 142, 15, 190, 0, 189, 208, 134, 63, 90, 54, 33, 198, 234, 186, 120, 5, 145, 91, 139, 35, 117, 80, 138, 35, 119, 58, 118, 153, 19, 84, 47, 255, 70, 149, 5, 67, 232, 88, 16, 150, 28, 248, 122, 79, 84, 37, 246, 97, 132, 223, 88, 97, 27, 229, 130, 93, 186, 253, 162, 108, 167, 49, 75, 36, 147, 159, 238, 140, 255, 236, 146, 157, 71, 231, 46, 68, 69, 132, 83, 89, 8, 236, 75, 32, 169, 234, 193, 168, 154, 104, 238, 211, 166, 61, 101, 201, 33, 132, 176, 235, 217, 75, 164, 142, 94, 76, 126, 91, 79, 50, 115, 163, 141, 7, 56, 128, 88, 35, 114, 117, 13, 73, 109, 69, 72, 226, 188, 221, 79, 94, 118, 103, 255, 157, 147, 251, 250, 86, 3, 151, 119, 46, 101, 135, 242, 21, 250, 12, 128], idle_timeout_ms: 230004 };255;[00, 00, 40, f3, 3c, 75, 68, df, 33, c3, 3a, 20, 5e, e7, cd, d1, 53, b5, 14, 50, e4, 2b, e4, d0, a3, f9, d8, 36, 7d, e7, 91, 00, d1, f9, 8b, 45, 8d, 56, 81, 93, 64, e5, 70, 52, 59, c8, b5, 23, 9d, 26, 6d, 66, 2b, a4, cc, 6d, f9, 3a, 77, cc, b8, df, 13, 09, a6, a8, 5f, ba, a9, 61, 34, 7a, d2, 02, ab, b3, 27, 5f, fd, d8, 81, 2d, a9, 44, 1e, 40, 8e, 27, de, 66, 0a, 6d, 9e, 91, 11, 8c, 41, 1d, 8e, 0f, be, 00, bd, d0, 86, 3f, 5a, 36, 21, c6, ea, ba, 78, 05, 91, 5b, 8b, 23, 75, 50, 8a, 23, 77, 3a, 76, 99, 13, 54, 2f, ff, 46, 95, 05, 43, e8, 58, 10, 96, 1c, f8, 7a, 4f, 54, 25, f6, 61, 84, df, 58, 61, 1b, e5, 82, 5d, ba, fd, a2, 6c, a7, 31, 4b, 24, 93, 9f, ee, 8c, ff, ec, 92, 9d, 47, e7, 2e, 44, 45, 84, 53, 59, 08, ec, 4b, 20, a9, ea, c1, a8, 9a, 68, ee, d3, a6, 3d, 65, c9, 21, 84, b0, eb, d9, 4b, a4, 8e, 5e, 4c, 7e, 5b, 4f, 32, 73, a3, 8d, 07, 38, 80, 58, 23, 72, 75, 0d, 49, 6d, 45, 48, e2, bc, dd, 4f, 5e, 76, 67, ff, 9d, 93, fb, fa, 56, 03, 97, 77, 2e, 65, 87, f2, 15, fa, 0c, 80, 80, 03, 82, 74, 00] +udp_resp 0101c000000000000065503eb6370edabce1e6936c64d987c915cb244ae1a706f4bc36bb1e44935c6cd17c3b4cfa9aba70cda7b939cb52d97f70ef53593eff40019b9eff39e9469840f24caf20f09c388d6090d1e9b00f261c68323daaf58d284cb1dcab88edea9acaa6fe83e2e79f800000008000000944b25a82a24948f238 UdpSessionResp { status: 1, error_code: 1, message: [80, 62, 182, 55, 14, 218, 188, 225, 230, 147, 108, 100, 217, 135, 201, 21, 203, 36, 74, 225, 167, 6, 244, 188, 54, 187, 30, 68, 147, 92, 108, 209, 124, 59, 76, 250, 154, 186, 112, 205, 167, 185, 57, 203, 82, 217, 127, 112, 239, 83, 89, 62, 255, 64, 1, 155, 158, 255, 57, 233, 70, 152, 64, 242, 76, 175, 32, 240, 156, 56, 141, 96, 144, 209, 233, 176, 15, 38, 28, 104, 50, 61, 170, 245, 141, 40, 76, 177, 220, 171, 136, 237, 234, 154, 202, 166, 254, 131, 226, 231, 159], idle_timeout_ms: 0 };128;[01, 01, 40, 65, 50, 3e, b6, 37, 0e, da, bc, e1, e6, 93, 6c, 64, d9, 87, c9, 15, cb, 24, 4a, e1, a7, 06, f4, bc, 36, bb, 1e, 44, 93, 5c, 6c, d1, 7c, 3b, 4c, fa, 9a, ba, 70, cd, a7, b9, 39, cb, 52, d9, 7f, 70, ef, 53, 59, 3e, ff, 40, 01, 9b, 9e, ff, 39, e9, 46, 98, 40, f2, 4c, af, 20, f0, 9c, 38, 8d, 60, 90, d1, e9, b0, 0f, 26, 1c, 68, 32, 3d, aa, f5, 8d, 28, 4c, b1, dc, ab, 88, ed, ea, 9a, ca, a6, fe, 83, e2, e7, 9f, 00, 00] +udp_resp 0101403b199533a207688867f00e85effcc53e8524fa2d82d2759d7de1d48bc2c11fdb384b9f5157e23bf9046223492239884c19fa9d7ef651213c9004b4c38dbf43cf80000003a8c4ad05f250fc UdpSessionResp { status: 1, error_code: 1, message: [25, 149, 51, 162, 7, 104, 136, 103, 240, 14, 133, 239, 252, 197, 62, 133, 36, 250, 45, 130, 210, 117, 157, 125, 225, 212, 139, 194, 193, 31, 219, 56, 75, 159, 81, 87, 226, 59, 249, 4, 98, 35, 73, 34, 57, 136, 76, 25, 250, 157, 126, 246, 81, 33, 60, 144, 4, 180, 195], idle_timeout_ms: 230638543 };74;[01, 01, 3b, 19, 95, 33, a2, 07, 68, 88, 67, f0, 0e, 85, ef, fc, c5, 3e, 85, 24, fa, 2d, 82, d2, 75, 9d, 7d, e1, d4, 8b, c2, c1, 1f, db, 38, 4b, 9f, 51, 57, e2, 3b, f9, 04, 62, 23, 49, 22, 39, 88, 4c, 19, fa, 9d, 7e, f6, 51, 21, 3c, 90, 04, b4, c3, 8d, bf, 43, cf, 00] +udp_resp 0104800000cf5fcbb661e090aef9123239c72309004dd274b8b9ed60b5a2130c2c715afb62ad9c7004bbdd3864f368f2aae5c9b363115453503c636c8dc2ee6d16549ea8b226414e6213389fcf9edcb8d30891f2a14fdfc8aa373ce8aa4497e690e36986be6ca0cf34057edafc432d06176226dae286e9b5517243569e1b1e1ffcd69d05cfda022709a152b399b4758df5b53b396dfc1eeca665867d0fa3c654bfb512c37a34b2df238f37e10efc20f7d9baea9fe5c4f5220d65c50afeb50c420260ad82ca9834c211f5feffcbe15252a3530cc70ac00003a44b429b4780000000 UdpSessionResp { status: 1, error_code: 4, message: [95, 203, 182, 97, 224, 144, 174, 249, 18, 50, 57, 199, 35, 9, 0, 77, 210, 116, 184, 185, 237, 96, 181, 162, 19, 12, 44, 113, 90, 251, 98, 173, 156, 112, 4, 187, 221, 56, 100, 243, 104, 242, 170, 229, 201, 179, 99, 17, 84, 83, 80, 60, 99, 108, 141, 194, 238, 109, 22, 84, 158, 168, 178, 38, 65, 78, 98, 19, 56, 159, 207, 158, 220, 184, 211, 8, 145, 242, 161, 79, 223, 200, 170, 55, 60, 232, 170, 68, 151, 230, 144, 227, 105, 134, 190, 108, 160, 207, 52, 5, 126, 218, 252, 67, 45, 6, 23, 98, 38, 218, 226, 134, 233, 181, 81, 114, 67, 86, 158, 27, 30, 31, 252, 214, 157, 5, 207, 218, 2, 39, 9, 161, 82, 179, 153, 180, 117, 141, 245, 181, 59, 57, 109, 252, 30, 236, 166, 101, 134, 125, 15, 163, 198, 84, 191, 181, 18, 195, 122, 52, 178, 223, 35, 143, 55, 225, 14, 252, 32, 247, 217, 186, 234, 159, 229, 196, 245, 34, 13, 101, 197, 10, 254, 181, 12, 66, 2, 96, 173, 130, 202, 152, 52, 194, 17, 245, 254, 255, 203, 225, 82, 82, 163, 83, 12, 199, 10], idle_timeout_ms: 4004172176199 };225;[01, 04, 40, cf, 5f, cb, b6, 61, e0, 90, ae, f9, 12, 32, 39, c7, 23, 09, 00, 4d, d2, 74, b8, b9, ed, 60, b5, a2, 13, 0c, 2c, 71, 5a, fb, 62, ad, 9c, 70, 04, bb, dd, 38, 64, f3, 68, f2, aa, e5, c9, b3, 63, 11, 54, 53, 50, 3c, 63, 6c, 8d, c2, ee, 6d, 16, 54, 9e, a8, b2, 26, 41, 4e, 62, 13, 38, 9f, cf, 9e, dc, b8, d3, 08, 91, f2, a1, 4f, df, c8, aa, 37, 3c, e8, aa, 44, 97, e6, 90, e3, 69, 86, be, 6c, a0, cf, 34, 05, 7e, da, fc, 43, 2d, 06, 17, 62, 26, da, e2, 86, e9, b5, 51, 72, 43, 56, 9e, 1b, 1e, 1f, fc, d6, 9d, 05, cf, da, 02, 27, 09, a1, 52, b3, 99, b4, 75, 8d, f5, b5, 3b, 39, 6d, fc, 1e, ec, a6, 65, 86, 7d, 0f, a3, c6, 54, bf, b5, 12, c3, 7a, 34, b2, df, 23, 8f, 37, e1, 0e, fc, 20, f7, d9, ba, ea, 9f, e5, c4, f5, 22, 0d, 65, c5, 0a, fe, b5, 0c, 42, 02, 60, ad, 82, ca, 98, 34, c2, 11, f5, fe, ff, cb, e1, 52, 52, a3, 53, 0c, c7, 0a, c0, 00, 03, a4, 4b, 42, 9b, 47, 00] +udp_resp 00008000007a5ee8fba3545b79b9c51567e6479f213a8ec0a823d252b668239dca297b96115df86e75421a057532baf77033e3288975f59dd476c52349eb26ed5aabee05d46f92cb1fdba20247becbb9056ac135ae09f08208eafac0729a161114523eb1820c43cee81a7e4b13b659f54d75f051d4d63b14ce6355d1b9411817c00000000000036300 UdpSessionResp { status: 0, error_code: 0, message: [94, 232, 251, 163, 84, 91, 121, 185, 197, 21, 103, 230, 71, 159, 33, 58, 142, 192, 168, 35, 210, 82, 182, 104, 35, 157, 202, 41, 123, 150, 17, 93, 248, 110, 117, 66, 26, 5, 117, 50, 186, 247, 112, 51, 227, 40, 137, 117, 245, 157, 212, 118, 197, 35, 73, 235, 38, 237, 90, 171, 238, 5, 212, 111, 146, 203, 31, 219, 162, 2, 71, 190, 203, 185, 5, 106, 193, 53, 174, 9, 240, 130, 8, 234, 250, 192, 114, 154, 22, 17, 20, 82, 62, 177, 130, 12, 67, 206, 232, 26, 126, 75, 19, 182, 89, 245, 77, 117, 240, 81, 212, 214, 59, 20, 206, 99, 85, 209, 185, 65, 24, 23], idle_timeout_ms: 867 };137;[00, 00, 40, 7a, 5e, e8, fb, a3, 54, 5b, 79, b9, c5, 15, 67, e6, 47, 9f, 21, 3a, 8e, c0, a8, 23, d2, 52, b6, 68, 23, 9d, ca, 29, 7b, 96, 11, 5d, f8, 6e, 75, 42, 1a, 05, 75, 32, ba, f7, 70, 33, e3, 28, 89, 75, f5, 9d, d4, 76, c5, 23, 49, eb, 26, ed, 5a, ab, ee, 05, d4, 6f, 92, cb, 1f, db, a2, 02, 47, be, cb, b9, 05, 6a, c1, 35, ae, 09, f0, 82, 08, ea, fa, c0, 72, 9a, 16, 11, 14, 52, 3e, b1, 82, 0c, 43, ce, e8, 1a, 7e, 4b, 13, b6, 59, f5, 4d, 75, f0, 51, d4, d6, 3b, 14, ce, 63, 55, d1, b9, 41, 18, 17, 43, 63, 00] +udp_resp 01c000000000000003c00000000000006749004b5e3e601fec09b981d5183c5a271c14a547e099874c37e39650b61d4244b6f65b085855228fa4466715b07bfa3665e79388ea5711206c066e25535af6dfe92f6714920a024d6ff966034cc35f164c91926760e99d07e82eef9e3033b5e16e85ed6f87b973430b06b49b0feda0aa UdpSessionResp { status: 1, error_code: 3, message: [73, 0, 75, 94, 62, 96, 31, 236, 9, 185, 129, 213, 24, 60, 90, 39, 28, 20, 165, 71, 224, 153, 135, 76, 55, 227, 150, 80, 182, 29, 66, 68, 182, 246, 91, 8, 88, 85, 34, 143, 164, 70, 103, 21, 176, 123, 250, 54, 101, 231, 147, 136, 234, 87, 17, 32, 108, 6, 110, 37, 83, 90, 246, 223, 233, 47, 103, 20, 146, 10, 2, 77, 111, 249, 102, 3, 76, 195, 95, 22, 76, 145, 146, 103, 96, 233, 157, 7, 232, 46, 239, 158, 48, 51, 181, 225, 110, 133, 237, 111, 135, 185, 115], idle_timeout_ms: 779 };129;[01, 03, 40, 67, 49, 00, 4b, 5e, 3e, 60, 1f, ec, 09, b9, 81, d5, 18, 3c, 5a, 27, 1c, 14, a5, 47, e0, 99, 87, 4c, 37, e3, 96, 50, b6, 1d, 42, 44, b6, f6, 5b, 08, 58, 55, 22, 8f, a4, 46, 67, 15, b0, 7b, fa, 36, 65, e7, 93, 88, ea, 57, 11, 20, 6c, 06, 6e, 25, 53, 5a, f6, df, e9, 2f, 67, 14, 92, 0a, 02, 4d, 6f, f9, 66, 03, 4c, c3, 5f, 16, 4c, 91, 92, 67, 60, e9, 9d, 07, e8, 2e, ef, 9e, 30, 33, b5, e1, 6e, 85, ed, 6f, 87, b9, 73, 43, 0b, 00] +udp_resp 01014061134cc896d8ae572e837a5dc00ff0c691c8d0bfe1bddda36a91de6108137b6eb806068431355f9f0d5504c19fe3da85421700686eb64a77a728b31819c510cadbf14c995403a1b95eefd42e0a2af228847079cc577b1ddd48cadf2d5222e9309f8d800046098000000a97f9aab5e6c089148493 UdpSessionResp { status: 1, error_code: 1, message: [19, 76, 200, 150, 216, 174, 87, 46, 131, 122, 93, 192, 15, 240, 198, 145, 200, 208, 191, 225, 189, 221, 163, 106, 145, 222, 97, 8, 19, 123, 110, 184, 6, 6, 132, 49, 53, 95, 159, 13, 85, 4, 193, 159, 227, 218, 133, 66, 23, 0, 104, 110, 182, 74, 119, 167, 40, 179, 24, 25, 197, 16, 202, 219, 241, 76, 153, 84, 3, 161, 185, 94, 239, 212, 46, 10, 42, 242, 40, 132, 112, 121, 204, 87, 123, 29, 221, 72, 202, 223, 45, 82, 34, 233, 48, 159, 141], idle_timeout_ms: 17929 };119;[01, 01, 40, 61, 13, 4c, c8, 96, d8, ae, 57, 2e, 83, 7a, 5d, c0, 0f, f0, c6, 91, c8, d0, bf, e1, bd, dd, a3, 6a, 91, de, 61, 08, 13, 7b, 6e, b8, 06, 06, 84, 31, 35, 5f, 9f, 0d, 55, 04, c1, 9f, e3, da, 85, 42, 17, 00, 68, 6e, b6, 4a, 77, a7, 28, b3, 18, 19, c5, 10, ca, db, f1, 4c, 99, 54, 03, a1, b9, 5e, ef, d4, 2e, 0a, 2a, f2, 28, 84, 70, 79, cc, 57, 7b, 1d, dd, 48, ca, df, 2d, 52, 22, e9, 30, 9f, 8d, 80, 00, 46, 09, 00] +udp_resp 03c000000000000002c00000000000006f9fc330cfa1f5b2bb389f68fd09a0567dc08a55bc687e5cd466a1696cc579dc4cd9121ae07c82b9392b8e7e03fea26a371ecbd417a1b7b2264f51cc788a059798ff60ad7992524c0a08389e5fd308c9baa8686e4bf3a0afc332094878237748db0fa24a0dd474218f7872be05198a94c00000000003bfafc00000000000000c4ed3073f14c381831c853503 reject +udp_resp 004000800000551694ac34a0c141b73cce81ba71333960eed42cebe07df733e07378b07d294d4e394ec6815bea36a195d00d0d701f52ecd482b8bd494c11d3fb998590f9b1b51b191265945ebbcfdd6778d10a26d58fc65bfbb7b129c0e3228ae0043918800000003182088b90 UdpSessionResp { status: 0, error_code: 0, message: [22, 148, 172, 52, 160, 193, 65, 183, 60, 206, 129, 186, 113, 51, 57, 96, 238, 212, 44, 235, 224, 125, 247, 51, 224, 115, 120, 176, 125, 41, 77, 78, 57, 78, 198, 129, 91, 234, 54, 161, 149, 208, 13, 13, 112, 31, 82, 236, 212, 130, 184, 189, 73, 76, 17, 211, 251, 153, 133, 144, 249, 177, 181, 27, 25, 18, 101, 148, 94, 187, 207, 221, 103, 120, 209, 10, 38, 213, 143, 198, 91, 251, 183, 177, 41], idle_timeout_ms: 63932799572523288 };104;[00, 00, 40, 55, 16, 94, ac, 34, a0, c1, 41, b7, 3c, ce, 81, ba, 71, 33, 39, 60, ee, d4, 2c, eb, e0, 7d, f7, 33, e0, 73, 78, b0, 7d, 29, 4d, 4e, 39, 4e, c6, 81, 5b, ea, 36, a1, 95, d0, 0d, 0d, 70, 1f, 52, ec, d4, 82, b8, bd, 49, 4c, 11, d3, fb, 99, 85, 90, f9, b1, b5, 1b, 19, 12, 65, 94, 5e, bb, cf, dd, 67, 78, d1, 0a, 26, d5, 8f, c6, 5b, fb, b7, b1, 29, c0, e3, 22, 8a, e0, 04, 39, 18, 00] +udp_resp 020140de2a06a782833ea6c274f7ac83e01e831e014ea48948634511c9973f97dcdf448a617e573df879534aad52d111755d5928518cabf14e6d3b73cda68489a24e400ee9528dca53aaf68105475351072ac4db2acd98fd7538722a14174589e1b1d97283c9c22ff3361bc518014581ad195bfdca8ae01a443d3e0be03d90e62bf70cf549835f9acc4df573165d5d7ccee0bb521c96b615267e5fdd5030c9c48518a360ccc0472f1e1ae353d9200df96f45ba20f11511396ec0b86d4b9b9a07d55523cfd48a3a1c72f25ff424c07ef49d5c9b680a751e8745dbd1b22be5374d98f18003595f4000 reject +udp_resp 01c0000000000000014037f8117fbe842e13a5e39f6b09f9dbfc992555de3b66a57803b57bd9c7cf34ba6729559ccebd9a0a023a90e5ee1b1f3ef64e41176d00a9edc0000074d757e20900ed24122bf5f65a UdpSessionResp { status: 1, error_code: 1, message: [248, 17, 127, 190, 132, 46, 19, 165, 227, 159, 107, 9, 249, 219, 252, 153, 37, 85, 222, 59, 102, 165, 120, 3, 181, 123, 217, 199, 207, 52, 186, 103, 41, 85, 156, 206, 189, 154, 10, 2, 58, 144, 229, 238, 27, 31, 62, 246, 78, 65, 23, 109, 0, 169, 237], idle_timeout_ms: 501829067273 };75;[01, 01, 37, f8, 11, 7f, be, 84, 2e, 13, a5, e3, 9f, 6b, 09, f9, db, fc, 99, 25, 55, de, 3b, 66, a5, 78, 03, b5, 7b, d9, c7, cf, 34, ba, 67, 29, 55, 9c, ce, bd, 9a, 0a, 02, 3a, 90, e5, ee, 1b, 1f, 3e, f6, 4e, 41, 17, 6d, 00, a9, ed, c0, 00, 00, 74, d7, 57, e2, 09, 00] +udp_resp 0280000003410020655df59f8d8ac69305e34631bf5f143619d2ad7abea4c5c6c11649d73f299ed11f3e49d947db287542c1fa504c67bc5516d46d0d0a806f203a6001d5cd09814dd3250360dd59b45f58d002649d45126762e07f75a62dce2cd92032165687c52ede4635820694edd6254b0166c7f12fbfd2b19d3a4c02ee939beb07dea81b5376a1da612ce5d57237f92e638cba2fdcadeb629abed0f912f0622ce4687f120f424d6a715dab237434641ee707a82014eed2608372abff9520564a91f1d07f1d1486b66961f041a5159aac1ff8742ae0f5b58abe06ec028474bca80ac04ee2cb4aa64b39c7759768bc602a6299311c771d2cb39a288445ae5cd3c46a336d69b6c2fee374329ab89480000000 reject +udp_resp 014001c0000000000000794586fc637e69073f06011ad05e7a0ca91e5b752dda2621fe634f5b503cd2b2751f3dc0fe63a7cfa00256e31813a32c6392c62d6502a98427d110dc343d67ce809ea5d86696d810f429389a4da37cfa4d476823ba07d325588a4d0df4281cf64ff08f42e7d3a896232d68ec1b9388043d2e64c848e920c10bb5c00008e73f30bdc480000001d2 UdpSessionResp { status: 1, error_code: 1, message: [69, 134, 252, 99, 126, 105, 7, 63, 6, 1, 26, 208, 94, 122, 12, 169, 30, 91, 117, 45, 218, 38, 33, 254, 99, 79, 91, 80, 60, 210, 178, 117, 31, 61, 192, 254, 99, 167, 207, 160, 2, 86, 227, 24, 19, 163, 44, 99, 146, 198, 45, 101, 2, 169, 132, 39, 209, 16, 220, 52, 61, 103, 206, 128, 158, 165, 216, 102, 150, 216, 16, 244, 41, 56, 154, 77, 163, 124, 250, 77, 71, 104, 35, 186, 7, 211, 37, 88, 138, 77, 13, 244, 40, 28, 246, 79, 240, 143, 66, 231, 211, 168, 150, 35, 45, 104, 236, 27, 147, 136, 4, 61, 46, 100, 200, 72, 233, 32, 193, 11, 181], idle_timeout_ms: 9789290626500 };145;[01, 01, 40, 79, 45, 86, fc, 63, 7e, 69, 07, 3f, 06, 01, 1a, d0, 5e, 7a, 0c, a9, 1e, 5b, 75, 2d, da, 26, 21, fe, 63, 4f, 5b, 50, 3c, d2, b2, 75, 1f, 3d, c0, fe, 63, a7, cf, a0, 02, 56, e3, 18, 13, a3, 2c, 63, 92, c6, 2d, 65, 02, a9, 84, 27, d1, 10, dc, 34, 3d, 67, ce, 80, 9e, a5, d8, 66, 96, d8, 10, f4, 29, 38, 9a, 4d, a3, 7c, fa, 4d, 47, 68, 23, ba, 07, d3, 25, 58, 8a, 4d, 0d, f4, 28, 1c, f6, 4f, f0, 8f, 42, e7, d3, a8, 96, 23, 2d, 68, ec, 1b, 93, 88, 04, 3d, 2e, 64, c8, 48, e9, 20, c1, 0b, b5, c0, 00, 08, e7, 3f, 30, bd, c4, 00] +udp_resp 0140044066494ca492f0f7b00d58107be583ba4ce9772546ee97e481c9d7761e793602daed343c69bd659d4857385134cb7cb7ba0099e0284662228e996665021b033d534d42f8ec63c0d3981f35611f07ad8ead1c004e0c8cf99b0184196bcdf3518fa46d0bb837ed1d60c0000000000000084000 UdpSessionResp { status: 1, error_code: 4, message: [73, 76, 164, 146, 240, 247, 176, 13, 88, 16, 123, 229, 131, 186, 76, 233, 119, 37, 70, 238, 151, 228, 129, 201, 215, 118, 30, 121, 54, 2, 218, 237, 52, 60, 105, 189, 101, 157, 72, 87, 56, 81, 52, 203, 124, 183, 186, 0, 153, 224, 40, 70, 98, 34, 142, 153, 102, 101, 2, 27, 3, 61, 83, 77, 66, 248, 236, 99, 192, 211, 152, 31, 53, 97, 31, 7, 173, 142, 173, 28, 0, 78, 12, 140, 249, 155, 1, 132, 25, 107, 205, 243, 81, 143, 164, 109, 11, 184, 55, 237, 29, 96], idle_timeout_ms: 8 };117;[01, 04, 40, 66, 49, 4c, a4, 92, f0, f7, b0, 0d, 58, 10, 7b, e5, 83, ba, 4c, e9, 77, 25, 46, ee, 97, e4, 81, c9, d7, 76, 1e, 79, 36, 02, da, ed, 34, 3c, 69, bd, 65, 9d, 48, 57, 38, 51, 34, cb, 7c, b7, ba, 00, 99, e0, 28, 46, 62, 22, 8e, 99, 66, 65, 02, 1b, 03, 3d, 53, 4d, 42, f8, ec, 63, c0, d3, 98, 1f, 35, 61, 1f, 07, ad, 8e, ad, 1c, 00, 4e, 0c, 8c, f9, 9b, 01, 84, 19, 6b, cd, f3, 51, 8f, a4, 6d, 0b, b8, 37, ed, 1d, 60, 08, 00] +udp_resp 008000000040f98cb42222b004914a03fdbca88332be6795c624b20fb51694429e5de0f7ae0bebcee4a4967aa901a9a2f4eb1c5d14c7b68c45cd7283609a7d0c7cd04d32d3baece982cce65676804c011b42c9d2b584d84567b463fcaf833ee00daa38e9db031976e6af3a67593e2bd8a24572d5bf3cab43411d2c5d5bb595a2f6a28c9ddeca6952d4b2749a3bdd05a3ddd06eb8dca75a12a314f36a6f3d5e7983526bcbec3d4a384bd6276f267dd48e6722b7a1c950812a3f9b5bd5c1586210be1a77b69e21e13f12f88c683c35ebd628b126a8f7223250838fee20d454c848a54a247f63b706c5e5b2eb9de47a86b25f51eea1601bcdd2ff584937557a0f0bc13b61301557fe144001c2 UdpSessionResp { status: 0, error_code: 0, message: [140, 180, 34, 34, 176, 4, 145, 74, 3, 253, 188, 168, 131, 50, 190, 103, 149, 198, 36, 178, 15, 181, 22, 148, 66, 158, 93, 224, 247, 174, 11, 235, 206, 228, 164, 150, 122, 169, 1, 169, 162, 244, 235, 28, 93, 20, 199, 182, 140, 69, 205, 114, 131, 96, 154, 125, 12, 124, 208, 77, 50, 211, 186, 236, 233, 130, 204, 230, 86, 118, 128, 76, 1, 27, 66, 201, 210, 181, 132, 216, 69, 103, 180, 99, 252, 175, 131, 62, 224, 13, 170, 56, 233, 219, 3, 25, 118, 230, 175, 58, 103, 89, 62, 43, 216, 162, 69, 114, 213, 191, 60, 171, 67, 65, 29, 44, 93, 91, 181, 149, 162, 246, 162, 140, 157, 222, 202, 105, 82, 212, 178, 116, 154, 59, 221, 5, 163, 221, 208, 110, 184, 220, 167, 90, 18, 163, 20, 243, 106, 111, 61, 94, 121, 131, 82, 107, 203, 236, 61, 74, 56, 75, 214, 39, 111, 38, 125, 212, 142, 103, 34, 183, 161, 201, 80, 129, 42, 63, 155, 91, 213, 193, 88, 98, 16, 190, 26, 119, 182, 158, 33, 225, 63, 18, 248, 140, 104, 60, 53, 235, 214, 40, 177, 38, 168, 247, 34, 50, 80, 131, 143, 238, 32, 212, 84, 200, 72, 165, 74, 36, 127, 99, 183, 6, 197, 229, 178, 235, 157, 228, 122, 134, 178, 95, 81, 238, 161, 96, 27, 205, 210, 255, 88, 73, 55, 85, 122, 15, 11], idle_timeout_ms: 88771476808269332 };267;[00, 00, 40, f9, 8c, b4, 22, 22, b0, 04, 91, 4a, 03, fd, bc, a8, 83, 32, be, 67, 95, c6, 24, b2, 0f, b5, 16, 94, 42, 9e, 5d, e0, f7, ae, 0b, eb, ce, e4, a4, 96, 7a, a9, 01, a9, a2, f4, eb, 1c, 5d, 14, c7, b6, 8c, 45, cd, 72, 83, 60, 9a, 7d, 0c, 7c, d0, 4d, 32, d3, ba, ec, e9, 82, cc, e6, 56, 76, 80, 4c, 01, 1b, 42, c9, d2, b5, 84, d8, 45, 67, b4, 63, fc, af, 83, 3e, e0, 0d, aa, 38, e9, db, 03, 19, 76, e6, af, 3a, 67, 59, 3e, 2b, d8, a2, 45, 72, d5, bf, 3c, ab, 43, 41, 1d, 2c, 5d, 5b, b5, 95, a2, f6, a2, 8c, 9d, de, ca, 69, 52, d4, b2, 74, 9a, 3b, dd, 05, a3, dd, d0, 6e, b8, dc, a7, 5a, 12, a3, 14, f3, 6a, 6f, 3d, 5e, 79, 83, 52, 6b, cb, ec, 3d, 4a, 38, 4b, d6, 27, 6f, 26, 7d, d4, 8e, 67, 22, b7, a1, c9, 50, 81, 2a, 3f, 9b, 5b, d5, c1, 58, 62, 10, be, 1a, 77, b6, 9e, 21, e1, 3f, 12, f8, 8c, 68, 3c, 35, eb, d6, 28, b1, 26, a8, f7, 22, 32, 50, 83, 8f, ee, 20, d4, 54, c8, 48, a5, 4a, 24, 7f, 63, b7, 06, c5, e5, b2, eb, 9d, e4, 7a, 86, b2, 5f, 51, ee, a1, 60, 1b, cd, d2, ff, 58, 49, 37, 55, 7a, 0f, 0b, c1, 3b, 61, 30, 15, 57, fe, 14, 00] +udp_resp 0080000000800000e2861cb5f1eff94badfce357c7ffe0c35e32a7fac3391c9ea89620c8cfc2d9610f3774d0d0a148255fe78bc99fd1ae8e2af1a3a76db136553555727633b01b6fc69450773d3d508b1265e7ce1ac0561d230b5d0e772165c0c2b1199dbea518efa1fac41d0bc038bcd696faeca382038dc0c769fea470b8a1a83cb6665204fdf5ee44325734fc3b061bf27eeffc5a7803004413d4dae881462654e631960fa6c6828518b3ff37f189159b4f0164045a4afaf92fc31d8b076b70351276d28077e6a2004ac25a06e99d50d73529334e4560eb49aa86b363bbcea351e3de4eaaaead97693c8000006680000000 UdpSessionResp { status: 0, error_code: 0, message: [134, 28, 181, 241, 239, 249, 75, 173, 252, 227, 87, 199, 255, 224, 195, 94, 50, 167, 250, 195, 57, 28, 158, 168, 150, 32, 200, 207, 194, 217, 97, 15, 55, 116, 208, 208, 161, 72, 37, 95, 231, 139, 201, 159, 209, 174, 142, 42, 241, 163, 167, 109, 177, 54, 85, 53, 85, 114, 118, 51, 176, 27, 111, 198, 148, 80, 119, 61, 61, 80, 139, 18, 101, 231, 206, 26, 192, 86, 29, 35, 11, 93, 14, 119, 33, 101, 192, 194, 177, 25, 157, 190, 165, 24, 239, 161, 250, 196, 29, 11, 192, 56, 188, 214, 150, 250, 236, 163, 130, 3, 141, 192, 199, 105, 254, 164, 112, 184, 161, 168, 60, 182, 102, 82, 4, 253, 245, 238, 68, 50, 87, 52, 252, 59, 6, 27, 242, 126, 239, 252, 90, 120, 3, 0, 68, 19, 212, 218, 232, 129, 70, 38, 84, 230, 49, 150, 15, 166, 198, 130, 133, 24, 179, 255, 55, 241, 137, 21, 155, 79, 1, 100, 4, 90, 74, 250, 249, 47, 195, 29, 139, 7, 107, 112, 53, 18, 118, 210, 128, 119, 230, 162, 0, 74, 194, 90, 6, 233, 157, 80, 215, 53, 41, 51, 78, 69, 96, 235, 73, 170, 134, 179, 99, 187, 206, 163, 81, 227, 222, 78, 170, 174, 173, 151, 105, 60], idle_timeout_ms: 102 };243;[00, 00, 40, e2, 86, 1c, b5, f1, ef, f9, 4b, ad, fc, e3, 57, c7, ff, e0, c3, 5e, 32, a7, fa, c3, 39, 1c, 9e, a8, 96, 20, c8, cf, c2, d9, 61, 0f, 37, 74, d0, d0, a1, 48, 25, 5f, e7, 8b, c9, 9f, d1, ae, 8e, 2a, f1, a3, a7, 6d, b1, 36, 55, 35, 55, 72, 76, 33, b0, 1b, 6f, c6, 94, 50, 77, 3d, 3d, 50, 8b, 12, 65, e7, ce, 1a, c0, 56, 1d, 23, 0b, 5d, 0e, 77, 21, 65, c0, c2, b1, 19, 9d, be, a5, 18, ef, a1, fa, c4, 1d, 0b, c0, 38, bc, d6, 96, fa, ec, a3, 82, 03, 8d, c0, c7, 69, fe, a4, 70, b8, a1, a8, 3c, b6, 66, 52, 04, fd, f5, ee, 44, 32, 57, 34, fc, 3b, 06, 1b, f2, 7e, ef, fc, 5a, 78, 03, 00, 44, 13, d4, da, e8, 81, 46, 26, 54, e6, 31, 96, 0f, a6, c6, 82, 85, 18, b3, ff, 37, f1, 89, 15, 9b, 4f, 01, 64, 04, 5a, 4a, fa, f9, 2f, c3, 1d, 8b, 07, 6b, 70, 35, 12, 76, d2, 80, 77, e6, a2, 00, 4a, c2, 5a, 06, e9, 9d, 50, d7, 35, 29, 33, 4e, 45, 60, eb, 49, aa, 86, b3, 63, bb, ce, a3, 51, e3, de, 4e, aa, ae, ad, 97, 69, 3c, 40, 66, 00] +udp_resp 0040002803f4a373b6d1a21d4a1a244ca9823b175c5066eb0a599efba6a02198f5fa0db8ce5a9297a3b4e7fac0000000006461334000 UdpSessionResp { status: 0, error_code: 0, message: [3, 244, 163, 115, 182, 209, 162, 29, 74, 26, 36, 76, 169, 130, 59, 23, 92, 80, 102, 235, 10, 89, 158, 251, 166, 160, 33, 152, 245, 250, 13, 184, 206, 90, 146, 151, 163, 180, 231, 250], idle_timeout_ms: 6578483 };54;[00, 00, 28, 03, f4, a3, 73, b6, d1, a2, 1d, 4a, 1a, 24, 4c, a9, 82, 3b, 17, 5c, 50, 66, eb, 0a, 59, 9e, fb, a6, a0, 21, 98, f5, fa, 0d, b8, ce, 5a, 92, 97, a3, b4, e7, fa, 80, 64, 61, 33, 00] +udp_resp 03044089894bb1e11cb95cc944970c021bf995a2899ad2c763556ac5ebf64458c6963cb8db143e1f668dd93b899083399c24f5c489732a85cb91a3420354197b334c7c43d50c4b2d358d19539738f05ecd4a815a4944648442fc13edf8bce409724a6398ab610fc6d642bf83830a3a53545848140118b2e12130ea51a78da900445ce528969860f52700b780d780f7bb9f80000000 reject +udp_resp 0180000001c000000000000006459baddb2fdfc000bc4f53ef91b34006795c378b259031cd UdpSessionResp { status: 1, error_code: 1, message: [69, 155, 173, 219, 47, 223], idle_timeout_ms: 207048896647603 };35;[01, 01, 06, 45, 9b, ad, db, 2f, df, c0, 00, bc, 4f, 53, ef, 91, b3, 00] +udp_resp 008000000041020f3e9430b0cb0083e0b0e8047ec555f447d59714d0967bd4abe6e535e596898a11332f51f977f64fe7d0b3e164f89b4b2ad6f4acf727bbdb5e0376d07e023607864be216bef358ac92c0b69ce3f661657f634f1209a45444b49e45e446dbd2d5c9abc875aa400999121bdc24d163192c5ea737338577f54d83f82708bc56c059def138dcf8cb04fa788b61abfaf6f41a69fcc99d57bca3513b6017ce433bdc126d77c9a2945f2808e4239cb9c248a97f31a81d243b9b44816891edcfb592c6034d5f76dc7dcf6119aa92367bf7e967c34f5d2434ab88f817a4ffc943fc9e1c6251721b8050462129af36f2e07dd488d01bd24180d8dcbdf45fb976f5e295d2274672c04f48e7e7ac506580000000 reject +udp_resp 01400429855a20d88f5df0f6abb798f390eec64e758e340684b0c4979287ef275d84415822dc61889a00f539748007784f4000 UdpSessionResp { status: 1, error_code: 4, message: [133, 90, 32, 216, 143, 93, 240, 246, 171, 183, 152, 243, 144, 238, 198, 78, 117, 142, 52, 6, 132, 176, 196, 151, 146, 135, 239, 39, 93, 132, 65, 88, 34, 220, 97, 136, 154, 0, 245, 57, 116], idle_timeout_ms: 489551 };51;[01, 04, 29, 85, 5a, 20, d8, 8f, 5d, f0, f6, ab, b7, 98, f3, 90, ee, c6, 4e, 75, 8e, 34, 06, 84, b0, c4, 97, 92, 87, ef, 27, 5d, 84, 41, 58, 22, dc, 61, 88, 9a, 00, f5, 39, 74, 80, 07, 78, 4f, 00] +udp_resp 00c0000000000000064039db555ee05050034ee48d9ef69e34360a324657c7338a3a2367b5d8328b8fa72fff07cfdaa828c07825ef1cc0fa520aecbda6410afa1a323a48c0000000000008f600 reject +udp_resp 00c0000000000000004085aaf48a85560687a4f65942a27b093ab675d7133f9bb9bc79c94bbeaa442d3a16d1ea57b8bae1fff1b3289d16930b3b2b2b64ca400db34e00d4ce3803c498bed66ab0d130ced474aa8f76538489ceed2a223c8b46dfb9d8e5082d93174f77770ff40a2e806b16ae3d7f2fc58a873f768ba7118d40748f122bded1c60f7969a2d7518c318d6fc000000000a3891700 UdpSessionResp { status: 0, error_code: 0, message: [170, 244, 138, 133, 86, 6, 135, 164, 246, 89, 66, 162, 123, 9, 58, 182, 117, 215, 19, 63, 155, 185, 188, 121, 201, 75, 190, 170, 68, 45, 58, 22, 209, 234, 87, 184, 186, 225, 255, 241, 179, 40, 157, 22, 147, 11, 59, 43, 43, 100, 202, 64, 13, 179, 78, 0, 212, 206, 56, 3, 196, 152, 190, 214, 106, 176, 209, 48, 206, 212, 116, 170, 143, 118, 83, 132, 137, 206, 237, 42, 34, 60, 139, 70, 223, 185, 216, 229, 8, 45, 147, 23, 79, 119, 119, 15, 244, 10, 46, 128, 107, 22, 174, 61, 127, 47, 197, 138, 135, 63, 118, 139, 167, 17, 141, 64, 116, 143, 18, 43, 222, 209, 198, 15, 121, 105, 162, 215, 81, 140, 49, 141, 111], idle_timeout_ms: 10717463 };153;[00, 00, 40, 85, aa, f4, 8a, 85, 56, 06, 87, a4, f6, 59, 42, a2, 7b, 09, 3a, b6, 75, d7, 13, 3f, 9b, b9, bc, 79, c9, 4b, be, aa, 44, 2d, 3a, 16, d1, ea, 57, b8, ba, e1, ff, f1, b3, 28, 9d, 16, 93, 0b, 3b, 2b, 2b, 64, ca, 40, 0d, b3, 4e, 00, d4, ce, 38, 03, c4, 98, be, d6, 6a, b0, d1, 30, ce, d4, 74, aa, 8f, 76, 53, 84, 89, ce, ed, 2a, 22, 3c, 8b, 46, df, b9, d8, e5, 08, 2d, 93, 17, 4f, 77, 77, 0f, f4, 0a, 2e, 80, 6b, 16, ae, 3d, 7f, 2f, c5, 8a, 87, 3f, 76, 8b, a7, 11, 8d, 40, 74, 8f, 12, 2b, de, d1, c6, 0f, 79, 69, a2, d7, 51, 8c, 31, 8d, 6f, 80, a3, 89, 17, 00] +udp_resp 0042c8c00000000000008c9821b123474db9fb7c1cf31dda9f5fa5e9972bbaa06266aecdff95797c7fab47df8753a4daddae4a43e6c0c481e92f681ccbd24c409fd54007d6fb2d5831583e1a1610c41920a583dd0c7e12c3a4683d6a6f7077a1b9d480d51dc2007e2f93b740475f5e1dc7afd922223668e63349b82fe32929f7945511103d22037738841cfc60dc6842f9f517a522178f800012c580000000 reject +udp_resp 0101409ac9b9e15abce203ffec5fa31ff8f188dd23e87e3d8c32ed5569aec42634eb9e2da9be1b20a8e4d18a544a649a00308413c58680005cf1ef71b34952d9aca5329eece17e79960616ef8ba68daba71ba16c12be32655ac1c5fbaeb5694c754874ad11b34f9f8f40e14b6d0762b68b9d331415c9276294f61190e25df9f0917aa751066713a8cf117ba9abbe9cd8eb649bb442af495ed867865b8076c0000774168c1eb200 UdpSessionResp { status: 1, error_code: 1, message: [201, 185, 225, 90, 188, 226, 3, 255, 236, 95, 163, 31, 248, 241, 136, 221, 35, 232, 126, 61, 140, 50, 237, 85, 105, 174, 196, 38, 52, 235, 158, 45, 169, 190, 27, 32, 168, 228, 209, 138, 84, 74, 100, 154, 0, 48, 132, 19, 197, 134, 128, 0, 92, 241, 239, 113, 179, 73, 82, 217, 172, 165, 50, 158, 236, 225, 126, 121, 150, 6, 22, 239, 139, 166, 141, 171, 167, 27, 161, 108, 18, 190, 50, 101, 90, 193, 197, 251, 174, 181, 105, 76, 117, 72, 116, 173, 17, 179, 79, 159, 143, 64, 225, 75, 109, 7, 98, 182, 139, 157, 51, 20, 21, 201, 39, 98, 148, 246, 17, 144, 226, 93, 249, 240, 145, 122, 167, 81, 6, 103, 19, 168, 207, 17, 123, 169, 171, 190, 156, 216, 235, 100, 155, 180, 66, 175, 73, 94, 216, 103, 134, 91, 128, 118], idle_timeout_ms: 8195175882418 };167;[01, 01, 40, 9a, c9, b9, e1, 5a, bc, e2, 03, ff, ec, 5f, a3, 1f, f8, f1, 88, dd, 23, e8, 7e, 3d, 8c, 32, ed, 55, 69, ae, c4, 26, 34, eb, 9e, 2d, a9, be, 1b, 20, a8, e4, d1, 8a, 54, 4a, 64, 9a, 00, 30, 84, 13, c5, 86, 80, 00, 5c, f1, ef, 71, b3, 49, 52, d9, ac, a5, 32, 9e, ec, e1, 7e, 79, 96, 06, 16, ef, 8b, a6, 8d, ab, a7, 1b, a1, 6c, 12, be, 32, 65, 5a, c1, c5, fb, ae, b5, 69, 4c, 75, 48, 74, ad, 11, b3, 4f, 9f, 8f, 40, e1, 4b, 6d, 07, 62, b6, 8b, 9d, 33, 14, 15, c9, 27, 62, 94, f6, 11, 90, e2, 5d, f9, f0, 91, 7a, a7, 51, 06, 67, 13, a8, cf, 11, 7b, a9, ab, be, 9c, d8, eb, 64, 9b, b4, 42, af, 49, 5e, d8, 67, 86, 5b, 80, 76, c0, 00, 07, 74, 16, 8c, 1e, b2, 00] +udp_resp 0380063d7b8000009f84bf6f5ac6f7b5c1afdcbdb481c00cc8969787aee68dff197e4c5c313b02bcec708efcb3e513a7958eeee871ecedf36d4451cbcda44566dea75fe7c75c826f3717a9a0c3af65841ee774fc1c6e3819801cb35986856dcb5df2aa0031d48a3bde47d48b308a214b54c32ec486340dc27bd68557b537ccb9b9b5c85b8caa5c21058b0ab1882bf0cb1a1190cee27021c3903ab1c600e141a14809428e50b271db7c8980000000 reject +udp_resp 01c000000000000003800000bd0567ebb3f4aa11f5c30cd0243f7698c96901d288149df5dff01ab463725c8bc7104a19a585c9c167746caec4884a5bef4aa302bf4602cbc2aa4ff9343c42c90adbcd9a842c315f10e0609a37a614165588282d1e87107abda01667daa9d7e6130e66744597508620b5517dffdec38b222fab9166825de8817d1f8d4dc01ecb2a522a8add4a71f9d8f9cef4332560579f2a33478fa0359c1d0b78af63948b31fb1f895edb2fa68e546183fbe1bf563d4efa3dd8d46336edada9a8b48e8fc00000000006485000 UdpSessionResp { status: 1, error_code: 3, message: [5, 103, 235, 179, 244, 170, 17, 245, 195, 12, 208, 36, 63, 118, 152, 201, 105, 1, 210, 136, 20, 157, 245, 223, 240, 26, 180, 99, 114, 92, 139, 199, 16, 74, 25, 165, 133, 201, 193, 103, 116, 108, 174, 196, 136, 74, 91, 239, 74, 163, 2, 191, 70, 2, 203, 194, 170, 79, 249, 52, 60, 66, 201, 10, 219, 205, 154, 132, 44, 49, 95, 16, 224, 96, 154, 55, 166, 20, 22, 85, 136, 40, 45, 30, 135, 16, 122, 189, 160, 22, 103, 218, 169, 215, 230, 19, 14, 102, 116, 69, 151, 80, 134, 32, 181, 81, 125, 255, 222, 195, 139, 34, 47, 171, 145, 102, 130, 93, 232, 129, 125, 31, 141, 77, 192, 30, 203, 42, 82, 42, 138, 221, 74, 113, 249, 216, 249, 206, 244, 51, 37, 96, 87, 159, 42, 51, 71, 143, 160, 53, 156, 29, 11, 120, 175, 99, 148, 139, 49, 251, 31, 137, 94, 219, 47, 166, 142, 84, 97, 131, 251, 225, 191, 86, 61, 78, 250, 61, 216, 212, 99, 54, 237, 173, 169, 168, 180, 142, 143], idle_timeout_ms: 411728 };211;[01, 03, 40, bd, 05, 67, eb, b3, f4, aa, 11, f5, c3, 0c, d0, 24, 3f, 76, 98, c9, 69, 01, d2, 88, 14, 9d, f5, df, f0, 1a, b4, 63, 72, 5c, 8b, c7, 10, 4a, 19, a5, 85, c9, c1, 67, 74, 6c, ae, c4, 88, 4a, 5b, ef, 4a, a3, 02, bf, 46, 02, cb, c2, aa, 4f, f9, 34, 3c, 42, c9, 0a, db, cd, 9a, 84, 2c, 31, 5f, 10, e0, 60, 9a, 37, a6, 14, 16, 55, 88, 28, 2d, 1e, 87, 10, 7a, bd, a0, 16, 67, da, a9, d7, e6, 13, 0e, 66, 74, 45, 97, 50, 86, 20, b5, 51, 7d, ff, de, c3, 8b, 22, 2f, ab, 91, 66, 82, 5d, e8, 81, 7d, 1f, 8d, 4d, c0, 1e, cb, 2a, 52, 2a, 8a, dd, 4a, 71, f9, d8, f9, ce, f4, 33, 25, 60, 57, 9f, 2a, 33, 47, 8f, a0, 35, 9c, 1d, 0b, 78, af, 63, 94, 8b, 31, fb, 1f, 89, 5e, db, 2f, a6, 8e, 54, 61, 83, fb, e1, bf, 56, 3d, 4e, fa, 3d, d8, d4, 63, 36, ed, ad, a9, a8, b4, 8e, 8f, 80, 06, 48, 50, 00] +udp_resp 008000000040f3860f57bf8bb6f9be9d2751f01e3140b8e464eada79d13beffbe804535d824ee6ad6179bb16ad67ffbb3a877716561c413567f6c7c5839899a7bd981afc447701fdee3e606dbb5ccbae408a2c2fc3c97c8f2a89ce5ceedb80c394afa5c012337271e835f5f235a975d4ef444a0c97af021554a4e0ce304b01178f0bf3ed09c9c780e788055df69466393f1abcb00f9a6aca4f86ae3bfe1688e86cc673b43e073896b6f4c9ecb8465f1250f594efa764745e3f4066e8754a8161e4d10aeea953b70b11f7949fd6e59408db40501fa2fc2931920a2a3eaabad2753b51b8c9dd4677d18e5225183b7ee73819210e6f363b7d68069ec0000037597fb51d005ac6 UdpSessionResp { status: 0, error_code: 0, message: [134, 15, 87, 191, 139, 182, 249, 190, 157, 39, 81, 240, 30, 49, 64, 184, 228, 100, 234, 218, 121, 209, 59, 239, 251, 232, 4, 83, 93, 130, 78, 230, 173, 97, 121, 187, 22, 173, 103, 255, 187, 58, 135, 119, 22, 86, 28, 65, 53, 103, 246, 199, 197, 131, 152, 153, 167, 189, 152, 26, 252, 68, 119, 1, 253, 238, 62, 96, 109, 187, 92, 203, 174, 64, 138, 44, 47, 195, 201, 124, 143, 42, 137, 206, 92, 238, 219, 128, 195, 148, 175, 165, 192, 18, 51, 114, 113, 232, 53, 245, 242, 53, 169, 117, 212, 239, 68, 74, 12, 151, 175, 2, 21, 84, 164, 224, 206, 48, 75, 1, 23, 143, 11, 243, 237, 9, 201, 199, 128, 231, 136, 5, 93, 246, 148, 102, 57, 63, 26, 188, 176, 15, 154, 106, 202, 79, 134, 174, 59, 254, 22, 136, 232, 108, 198, 115, 180, 62, 7, 56, 150, 182, 244, 201, 236, 184, 70, 95, 18, 80, 245, 148, 239, 167, 100, 116, 94, 63, 64, 102, 232, 117, 74, 129, 97, 228, 209, 10, 238, 169, 83, 183, 11, 17, 247, 148, 159, 214, 229, 148, 8, 219, 64, 80, 31, 162, 252, 41, 49, 146, 10, 42, 62, 170, 186, 210, 117, 59, 81, 184, 201, 221, 70, 119, 209, 142, 82, 37, 24, 59, 126, 231, 56, 25, 33, 14, 111, 54, 59, 125, 104, 6, 158], idle_timeout_ms: 237724742941 };259;[00, 00, 40, f3, 86, 0f, 57, bf, 8b, b6, f9, be, 9d, 27, 51, f0, 1e, 31, 40, b8, e4, 64, ea, da, 79, d1, 3b, ef, fb, e8, 04, 53, 5d, 82, 4e, e6, ad, 61, 79, bb, 16, ad, 67, ff, bb, 3a, 87, 77, 16, 56, 1c, 41, 35, 67, f6, c7, c5, 83, 98, 99, a7, bd, 98, 1a, fc, 44, 77, 01, fd, ee, 3e, 60, 6d, bb, 5c, cb, ae, 40, 8a, 2c, 2f, c3, c9, 7c, 8f, 2a, 89, ce, 5c, ee, db, 80, c3, 94, af, a5, c0, 12, 33, 72, 71, e8, 35, f5, f2, 35, a9, 75, d4, ef, 44, 4a, 0c, 97, af, 02, 15, 54, a4, e0, ce, 30, 4b, 01, 17, 8f, 0b, f3, ed, 09, c9, c7, 80, e7, 88, 05, 5d, f6, 94, 66, 39, 3f, 1a, bc, b0, 0f, 9a, 6a, ca, 4f, 86, ae, 3b, fe, 16, 88, e8, 6c, c6, 73, b4, 3e, 07, 38, 96, b6, f4, c9, ec, b8, 46, 5f, 12, 50, f5, 94, ef, a7, 64, 74, 5e, 3f, 40, 66, e8, 75, 4a, 81, 61, e4, d1, 0a, ee, a9, 53, b7, 0b, 11, f7, 94, 9f, d6, e5, 94, 08, db, 40, 50, 1f, a2, fc, 29, 31, 92, 0a, 2a, 3e, aa, ba, d2, 75, 3b, 51, b8, c9, dd, 46, 77, d1, 8e, 52, 25, 18, 3b, 7e, e7, 38, 19, 21, 0e, 6f, 36, 3b, 7d, 68, 06, 9e, c0, 00, 00, 37, 59, 7f, b5, 1d, 00] +udp_resp 018000000334c8e26a34cb78a52eee796349bfaa605135525380b69e4b86f272b82e8f30211bc21960da404095f797286858f2040506161a209f878ba4dbc000000000000000 UdpSessionResp { status: 1, error_code: 3, message: [200, 226, 106, 52, 203, 120, 165, 46, 238, 121, 99, 73, 191, 170, 96, 81, 53, 82, 83, 128, 182, 158, 75, 134, 242, 114, 184, 46, 143, 48, 33, 27, 194, 25, 96, 218, 64, 64, 149, 247, 151, 40, 104, 88, 242, 4, 5, 6, 22, 26, 32, 159], idle_timeout_ms: 126592219 };70;[01, 03, 34, c8, e2, 6a, 34, cb, 78, a5, 2e, ee, 79, 63, 49, bf, aa, 60, 51, 35, 52, 53, 80, b6, 9e, 4b, 86, f2, 72, b8, 2e, 8f, 30, 21, 1b, c2, 19, 60, da, 40, 40, 95, f7, 97, 28, 68, 58, f2, 04, 05, 06, 16, 1a, 20, 9f, 87, 8b, a4, db, 00] +udp_resp 01c000000000000004800000c9eb1c51f423f74eefbc2d72e1cf3fa7540f4c36c7818d4990f0f6c5fe2a11018a54273367259cabd748f4cb402df9e3f6815eae998afe2ef0b045896cd95b78c555f5076ed28c26fe459296cd22cf76deb630171353efb1ac6601e480e17b873d4d2e907f5b51f4d224a56d38fa72ce101067c858da17619e8b8fc3a6c4d9a4b99a3c35c58e25e491617cca10a3f09df20d3e10a8f894f9956d5a50079ed85bc0d412e9e4f9ed10b81ad400968df6fecb8512466472b51ae0925032a1f71c817da01b920272eea890cec000005360e7f6914000 UdpSessionResp { status: 1, error_code: 4, message: [235, 28, 81, 244, 35, 247, 78, 239, 188, 45, 114, 225, 207, 63, 167, 84, 15, 76, 54, 199, 129, 141, 73, 144, 240, 246, 197, 254, 42, 17, 1, 138, 84, 39, 51, 103, 37, 156, 171, 215, 72, 244, 203, 64, 45, 249, 227, 246, 129, 94, 174, 153, 138, 254, 46, 240, 176, 69, 137, 108, 217, 91, 120, 197, 85, 245, 7, 110, 210, 140, 38, 254, 69, 146, 150, 205, 34, 207, 118, 222, 182, 48, 23, 19, 83, 239, 177, 172, 102, 1, 228, 128, 225, 123, 135, 61, 77, 46, 144, 127, 91, 81, 244, 210, 36, 165, 109, 56, 250, 114, 206, 16, 16, 103, 200, 88, 218, 23, 97, 158, 139, 143, 195, 166, 196, 217, 164, 185, 154, 60, 53, 197, 142, 37, 228, 145, 97, 124, 202, 16, 163, 240, 157, 242, 13, 62, 16, 168, 248, 148, 249, 149, 109, 90, 80, 7, 158, 216, 91, 192, 212, 18, 233, 228, 249, 237, 16, 184, 26, 212, 0, 150, 141, 246, 254, 203, 133, 18, 70, 100, 114, 181, 26, 224, 146, 80, 50, 161, 247, 28, 129, 125, 160, 27, 146, 2, 114, 238, 168, 144, 206], idle_timeout_ms: 358108100241 };224;[01, 04, 40, c9, eb, 1c, 51, f4, 23, f7, 4e, ef, bc, 2d, 72, e1, cf, 3f, a7, 54, 0f, 4c, 36, c7, 81, 8d, 49, 90, f0, f6, c5, fe, 2a, 11, 01, 8a, 54, 27, 33, 67, 25, 9c, ab, d7, 48, f4, cb, 40, 2d, f9, e3, f6, 81, 5e, ae, 99, 8a, fe, 2e, f0, b0, 45, 89, 6c, d9, 5b, 78, c5, 55, f5, 07, 6e, d2, 8c, 26, fe, 45, 92, 96, cd, 22, cf, 76, de, b6, 30, 17, 13, 53, ef, b1, ac, 66, 01, e4, 80, e1, 7b, 87, 3d, 4d, 2e, 90, 7f, 5b, 51, f4, d2, 24, a5, 6d, 38, fa, 72, ce, 10, 10, 67, c8, 58, da, 17, 61, 9e, 8b, 8f, c3, a6, c4, d9, a4, b9, 9a, 3c, 35, c5, 8e, 25, e4, 91, 61, 7c, ca, 10, a3, f0, 9d, f2, 0d, 3e, 10, a8, f8, 94, f9, 95, 6d, 5a, 50, 07, 9e, d8, 5b, c0, d4, 12, e9, e4, f9, ed, 10, b8, 1a, d4, 00, 96, 8d, f6, fe, cb, 85, 12, 46, 64, 72, b5, 1a, e0, 92, 50, 32, a1, f7, 1c, 81, 7d, a0, 1b, 92, 02, 72, ee, a8, 90, ce, c0, 00, 00, 53, 60, e7, f6, 91, 00] +udp_resp 01400440e1ce2b12856445688afe73fd8fd79b29b1bcd75f2c38ad1d68d4326b56d2c7549e0137e9f1f97838e40334866a0754522689e02fbda771b3a6fe8f4bdb3171e20cf8314475e8d0cfa7090f5c8b89e584ef5790fc0704918e449ae5811028313ad3ba3c804df57da6fdf1c8224e388e391dec7a0a4502fe8003241ee58f3b869c282f45506672b6f1b768e22d75f6e9e60ca3b2c754b51d68e1e5d1e6d1fb19e0a7d9165466c438811674b45222872b5bda1faf1a25b9af3ea3e20c5f19edbc13558d20fb22e83cf4a7dc7bba131d6c42c0b79c4f22f3e60f1a0418ee0e805fad3e02c2619b3e4918cff2800000005d UdpSessionResp { status: 1, error_code: 4, message: [206, 43, 18, 133, 100, 69, 104, 138, 254, 115, 253, 143, 215, 155, 41, 177, 188, 215, 95, 44, 56, 173, 29, 104, 212, 50, 107, 86, 210, 199, 84, 158, 1, 55, 233, 241, 249, 120, 56, 228, 3, 52, 134, 106, 7, 84, 82, 38, 137, 224, 47, 189, 167, 113, 179, 166, 254, 143, 75, 219, 49, 113, 226, 12, 248, 49, 68, 117, 232, 208, 207, 167, 9, 15, 92, 139, 137, 229, 132, 239, 87, 144, 252, 7, 4, 145, 142, 68, 154, 229, 129, 16, 40, 49, 58, 211, 186, 60, 128, 77, 245, 125, 166, 253, 241, 200, 34, 78, 56, 142, 57, 29, 236, 122, 10, 69, 2, 254, 128, 3, 36, 30, 229, 143, 59, 134, 156, 40, 47, 69, 80, 102, 114, 182, 241, 183, 104, 226, 45, 117, 246, 233, 230, 12, 163, 178, 199, 84, 181, 29, 104, 225, 229, 209, 230, 209, 251, 25, 224, 167, 217, 22, 84, 102, 196, 56, 129, 22, 116, 180, 82, 34, 135, 43, 91, 218, 31, 175, 26, 37, 185, 175, 62, 163, 226, 12, 95, 25, 237, 188, 19, 85, 141, 32, 251, 34, 232, 60, 244, 167, 220, 123, 186, 19, 29, 108, 66, 192, 183, 156, 79, 34, 243, 230, 15, 26, 4, 24, 238, 14, 128, 95, 173, 62, 2], idle_timeout_ms: 171588952633430002 };242;[01, 04, 40, e1, ce, 2b, 12, 85, 64, 45, 68, 8a, fe, 73, fd, 8f, d7, 9b, 29, b1, bc, d7, 5f, 2c, 38, ad, 1d, 68, d4, 32, 6b, 56, d2, c7, 54, 9e, 01, 37, e9, f1, f9, 78, 38, e4, 03, 34, 86, 6a, 07, 54, 52, 26, 89, e0, 2f, bd, a7, 71, b3, a6, fe, 8f, 4b, db, 31, 71, e2, 0c, f8, 31, 44, 75, e8, d0, cf, a7, 09, 0f, 5c, 8b, 89, e5, 84, ef, 57, 90, fc, 07, 04, 91, 8e, 44, 9a, e5, 81, 10, 28, 31, 3a, d3, ba, 3c, 80, 4d, f5, 7d, a6, fd, f1, c8, 22, 4e, 38, 8e, 39, 1d, ec, 7a, 0a, 45, 02, fe, 80, 03, 24, 1e, e5, 8f, 3b, 86, 9c, 28, 2f, 45, 50, 66, 72, b6, f1, b7, 68, e2, 2d, 75, f6, e9, e6, 0c, a3, b2, c7, 54, b5, 1d, 68, e1, e5, d1, e6, d1, fb, 19, e0, a7, d9, 16, 54, 66, c4, 38, 81, 16, 74, b4, 52, 22, 87, 2b, 5b, da, 1f, af, 1a, 25, b9, af, 3e, a3, e2, 0c, 5f, 19, ed, bc, 13, 55, 8d, 20, fb, 22, e8, 3c, f4, a7, dc, 7b, ba, 13, 1d, 6c, 42, c0, b7, 9c, 4f, 22, f3, e6, 0f, 1a, 04, 18, ee, 0e, 80, 5f, ad, 3e, 02, c2, 61, 9b, 3e, 49, 18, cf, f2, 00] +udp_resp 01c000000000000003c000000000000101e8f2e51e3a35d3225320e1636a3c50d41ea0d30afbfb307c7d95b2031bd51adef996a0d409582c2ae1d3de6b2bc222f3cdaeb3653fb29e2b451889fa8067ccd0c16b7c8d661b254873dd26d94f8accb2a9dce295a40c2c17c4cf04b0321d62f90892bd7d0bffc0d0998dc26588d932984bef3fb349b4f7910c506e94d368d4aedaff8ab4f25f4925279d2b6c77e9d78ae28fa713cda27629b5f22956749e2294e67cffa06f3d0069d9b1e6d09a66f85b182234a9986695e06454b2135a308a714cf7e66935a81632b0a14562388a9f0b87dccef3dd3c2c75697bf77b3f6f46b89e4d8420c77a79b2df2a6089c15fdc7e795f98219ef90dbb1ed3091a1d25309ccbc000000d9e26093f80000000 reject +udp_resp 0000c00000000000001e7c234ff713fb16db5d4e315290c78dc65abeda64131c8ab3e7f5bb240cd3c0000015af1424de80000000 UdpSessionResp { status: 0, error_code: 0, message: [124, 35, 79, 247, 19, 251, 22, 219, 93, 78, 49, 82, 144, 199, 141, 198, 90, 190, 218, 100, 19, 28, 138, 179, 231, 245, 187, 36, 12, 211], idle_timeout_ms: 93131646174 };52;[00, 00, 1e, 7c, 23, 4f, f7, 13, fb, 16, db, 5d, 4e, 31, 52, 90, c7, 8d, c6, 5a, be, da, 64, 13, 1c, 8a, b3, e7, f5, bb, 24, 0c, d3, c0, 00, 00, 15, af, 14, 24, de, 00] +udp_resp 03416dc00000000000002d304bdbb9ffd98f1b84ecafe91701ec906b0dd43398ee6beb556ade678fec468a801debd0030e25c011e12c67fa85ac1be180000000 reject +udp_resp 00c00000000000000040a409436e500fa3e6189b610d31a9449298b7947d17f2654e9d2661899561163e0f613247b1d570940ca7a04736a4bd3c290c55526ebb977c9b97c64721e4641e351c923a8ff5e272bfd4ede9ebe272bf0cee21c0297aa72666ed3e821c71ea9d545da57eb7f32870e58ee813a71737ee3d7c7dd2933b9da5f4843405b1c94e03c18a0d4acf4b601fcd9b619f66414b030583d015efec2f2b54a1f064dad2aeac257760f558c000000000001549c000000000000000 UdpSessionResp { status: 0, error_code: 0, message: [9, 67, 110, 80, 15, 163, 230, 24, 155, 97, 13, 49, 169, 68, 146, 152, 183, 148, 125, 23, 242, 101, 78, 157, 38, 97, 137, 149, 97, 22, 62, 15, 97, 50, 71, 177, 213, 112, 148, 12, 167, 160, 71, 54, 164, 189, 60, 41, 12, 85, 82, 110, 187, 151, 124, 155, 151, 198, 71, 33, 228, 100, 30, 53, 28, 146, 58, 143, 245, 226, 114, 191, 212, 237, 233, 235, 226, 114, 191, 12, 238, 33, 192, 41, 122, 167, 38, 102, 237, 62, 130, 28, 113, 234, 157, 84, 93, 165, 126, 183, 243, 40, 112, 229, 142, 232, 19, 167, 23, 55, 238, 61, 124, 125, 210, 147, 59, 157, 165, 244, 132, 52, 5, 177, 201, 78, 3, 193, 138, 13, 74, 207, 75, 96, 31, 205, 155, 97, 159, 102, 65, 75, 3, 5, 131, 208, 21, 239, 236, 47, 43, 84, 161, 240, 100, 218, 210, 174, 172, 37, 119, 96, 245, 88], idle_timeout_ms: 5449 };191;[00, 00, 40, a4, 09, 43, 6e, 50, 0f, a3, e6, 18, 9b, 61, 0d, 31, a9, 44, 92, 98, b7, 94, 7d, 17, f2, 65, 4e, 9d, 26, 61, 89, 95, 61, 16, 3e, 0f, 61, 32, 47, b1, d5, 70, 94, 0c, a7, a0, 47, 36, a4, bd, 3c, 29, 0c, 55, 52, 6e, bb, 97, 7c, 9b, 97, c6, 47, 21, e4, 64, 1e, 35, 1c, 92, 3a, 8f, f5, e2, 72, bf, d4, ed, e9, eb, e2, 72, bf, 0c, ee, 21, c0, 29, 7a, a7, 26, 66, ed, 3e, 82, 1c, 71, ea, 9d, 54, 5d, a5, 7e, b7, f3, 28, 70, e5, 8e, e8, 13, a7, 17, 37, ee, 3d, 7c, 7d, d2, 93, 3b, 9d, a5, f4, 84, 34, 05, b1, c9, 4e, 03, c1, 8a, 0d, 4a, cf, 4b, 60, 1f, cd, 9b, 61, 9f, 66, 41, 4b, 03, 05, 83, d0, 15, ef, ec, 2f, 2b, 54, a1, f0, 64, da, d2, ae, ac, 25, 77, 60, f5, 58, 55, 49, 00] +udp_resp 014004800000f34cf30640b1e4dee9400c609b8e6a0a5425148d105ad5287d03cd7e25365ece8e0ba4c30abcb75acc1d80b4ba0f7b88b310d65686d7428d072373c9f9350603fa20a0500e6828f8ebf488112bc160598dd4956c3f6562494eb82e020291ebafdfbbc8ad434f0f2ab9deb4f10e714f7f90fb0a0317628216cde760af4d8e18aa6154067e6b020ee873d9dc63312649a0020afc105ce3d7c33de1adcd337d6100b6c794c1416f8775c73d6b8ef89f29165162435c271e0e03be2f27c4a02e547ac9074af629e79d8b1ec003dd0c271ffacdfe4430081490f5843a5d6ad560c7fd564b5c94853941bad88c0866b1a789d29b902a2ac000000e2317145680000000 UdpSessionResp { status: 1, error_code: 4, message: [76, 243, 6, 64, 177, 228, 222, 233, 64, 12, 96, 155, 142, 106, 10, 84, 37, 20, 141, 16, 90, 213, 40, 125, 3, 205, 126, 37, 54, 94, 206, 142, 11, 164, 195, 10, 188, 183, 90, 204, 29, 128, 180, 186, 15, 123, 136, 179, 16, 214, 86, 134, 215, 66, 141, 7, 35, 115, 201, 249, 53, 6, 3, 250, 32, 160, 80, 14, 104, 40, 248, 235, 244, 136, 17, 43, 193, 96, 89, 141, 212, 149, 108, 63, 101, 98, 73, 78, 184, 46, 2, 2, 145, 235, 175, 223, 187, 200, 173, 67, 79, 15, 42, 185, 222, 180, 241, 14, 113, 79, 127, 144, 251, 10, 3, 23, 98, 130, 22, 205, 231, 96, 175, 77, 142, 24, 170, 97, 84, 6, 126, 107, 2, 14, 232, 115, 217, 220, 99, 49, 38, 73, 160, 2, 10, 252, 16, 92, 227, 215, 195, 61, 225, 173, 205, 51, 125, 97, 0, 182, 199, 148, 193, 65, 111, 135, 117, 199, 61, 107, 142, 248, 159, 41, 22, 81, 98, 67, 92, 39, 30, 14, 3, 190, 47, 39, 196, 160, 46, 84, 122, 201, 7, 74, 246, 41, 231, 157, 139, 30, 192, 3, 221, 12, 39, 31, 250, 205, 254, 68, 48, 8, 20, 144, 245, 132, 58, 93, 106, 213, 96, 199, 253, 86, 75, 92, 148, 133, 57, 65, 186, 216, 140, 8, 102, 177, 167, 137, 210, 155, 144, 42, 42], idle_timeout_ms: 60718257238 };262;[01, 04, 40, f3, 4c, f3, 06, 40, b1, e4, de, e9, 40, 0c, 60, 9b, 8e, 6a, 0a, 54, 25, 14, 8d, 10, 5a, d5, 28, 7d, 03, cd, 7e, 25, 36, 5e, ce, 8e, 0b, a4, c3, 0a, bc, b7, 5a, cc, 1d, 80, b4, ba, 0f, 7b, 88, b3, 10, d6, 56, 86, d7, 42, 8d, 07, 23, 73, c9, f9, 35, 06, 03, fa, 20, a0, 50, 0e, 68, 28, f8, eb, f4, 88, 11, 2b, c1, 60, 59, 8d, d4, 95, 6c, 3f, 65, 62, 49, 4e, b8, 2e, 02, 02, 91, eb, af, df, bb, c8, ad, 43, 4f, 0f, 2a, b9, de, b4, f1, 0e, 71, 4f, 7f, 90, fb, 0a, 03, 17, 62, 82, 16, cd, e7, 60, af, 4d, 8e, 18, aa, 61, 54, 06, 7e, 6b, 02, 0e, e8, 73, d9, dc, 63, 31, 26, 49, a0, 02, 0a, fc, 10, 5c, e3, d7, c3, 3d, e1, ad, cd, 33, 7d, 61, 00, b6, c7, 94, c1, 41, 6f, 87, 75, c7, 3d, 6b, 8e, f8, 9f, 29, 16, 51, 62, 43, 5c, 27, 1e, 0e, 03, be, 2f, 27, c4, a0, 2e, 54, 7a, c9, 07, 4a, f6, 29, e7, 9d, 8b, 1e, c0, 03, dd, 0c, 27, 1f, fa, cd, fe, 44, 30, 08, 14, 90, f5, 84, 3a, 5d, 6a, d5, 60, c7, fd, 56, 4b, 5c, 94, 85, 39, 41, ba, d8, 8c, 08, 66, b1, a7, 89, d2, 9b, 90, 2a, 2a, c0, 00, 00, 0e, 23, 17, 14, 56, 00] +udp_resp 00400040199912d7149558e1ac55ed2fdc6696fc6ada5cbdd9b84ad4ccd6c0000000005a63fbc000000000000000 UdpSessionResp { status: 0, error_code: 0, message: [153, 18, 215, 20, 149, 88, 225, 172, 85, 237, 47, 220, 102, 150, 252, 106, 218, 92, 189, 217, 184, 74, 212, 204, 214], idle_timeout_ms: 5923835 };46;[00, 00, 19, 99, 12, d7, 14, 95, 58, e1, ac, 55, ed, 2f, dc, 66, 96, fc, 6a, da, 5c, bd, d9, b8, 4a, d4, cc, d6, 80, 5a, 63, fb, 00] +udp_resp 004000c00000000000002d6271716c22ef246ecf3b3c661fd780a459afd2497f4dbb469a0ad369e0e4535328bbba23e213ceb7adb42adccc80006d4a4000 UdpSessionResp { status: 0, error_code: 0, message: [98, 113, 113, 108, 34, 239, 36, 110, 207, 59, 60, 102, 31, 215, 128, 164, 89, 175, 210, 73, 127, 77, 187, 70, 154, 10, 211, 105, 224, 228, 83, 83, 40, 187, 186, 35, 226, 19, 206, 183, 173, 180, 42, 220, 204], idle_timeout_ms: 27978 };62;[00, 00, 2d, 62, 71, 71, 6c, 22, ef, 24, 6e, cf, 3b, 3c, 66, 1f, d7, 80, a4, 59, af, d2, 49, 7f, 4d, bb, 46, 9a, 0a, d3, 69, e0, e4, 53, 53, 28, bb, ba, 23, e2, 13, ce, b7, ad, b4, 2a, dc, cc, 80, 00, 6d, 4a, 00] +udp_resp 000017189f461dd16a136c47e8f070bfc2a37519cd391b1c5be78002046b400aa774c05d39994d8d5ae0755d9454 UdpSessionResp { status: 0, error_code: 0, message: [24, 159, 70, 29, 209, 106, 19, 108, 71, 232, 240, 112, 191, 194, 163, 117, 25, 205, 57, 27, 28, 91, 231], idle_timeout_ms: 132203 };42;[00, 00, 17, 18, 9f, 46, 1d, d1, 6a, 13, 6c, 47, e8, f0, 70, bf, c2, a3, 75, 19, cd, 39, 1b, 1c, 5b, e7, 80, 02, 04, 6b, 00] +udp_resp 0104c0000000000000787816dc1356745567923958a3bbb3768c9be8fd79ddde69ecd0a1027cdf9474b48173c7edda14c7a4078ee3d5783cafd5895220d105a0bb2f2280e18a241695c555c9912a641d41adb439a8772ae9e1a50922c4889dd341299acc6e0cbc7fe55a88a68221152d60743fcb72cd3cbaa35c69fe10eb1b6cee1dc0000006b1c54bde400e02afb6713adf16b3f383f646b93a2c50e97a UdpSessionResp { status: 1, error_code: 4, message: [120, 22, 220, 19, 86, 116, 85, 103, 146, 57, 88, 163, 187, 179, 118, 140, 155, 232, 253, 121, 221, 222, 105, 236, 208, 161, 2, 124, 223, 148, 116, 180, 129, 115, 199, 237, 218, 20, 199, 164, 7, 142, 227, 213, 120, 60, 175, 213, 137, 82, 32, 209, 5, 160, 187, 47, 34, 128, 225, 138, 36, 22, 149, 197, 85, 201, 145, 42, 100, 29, 65, 173, 180, 57, 168, 119, 42, 233, 225, 165, 9, 34, 196, 136, 157, 211, 65, 41, 154, 204, 110, 12, 188, 127, 229, 90, 136, 166, 130, 33, 21, 45, 96, 116, 63, 203, 114, 205, 60, 186, 163, 92, 105, 254, 16, 235, 27, 108, 238, 29], idle_timeout_ms: 28752301022 };154;[01, 04, 40, 78, 78, 16, dc, 13, 56, 74, 55, 67, 92, 39, 58, a3, bb, b3, 76, 8c, 9b, e8, fd, 79, dd, de, 69, ec, d0, a1, 02, 7c, df, 94, 74, b4, 81, 73, c7, ed, da, 14, c7, a4, 07, 8e, e3, d5, 78, 3c, af, d5, 89, 52, 20, d1, 05, a0, bb, 2f, 22, 80, e1, 8a, 24, 16, 95, c5, 55, c9, 91, 2a, 64, 1d, 41, ad, b4, 39, a8, 77, 2a, e9, e1, a5, 09, 22, c4, 88, 9d, d3, 41, 29, 9a, cc, 6e, 0c, bc, 7f, e5, 5a, 88, a6, 82, 21, 15, 2d, 60, 74, 3f, cb, 72, cd, 3c, ba, a3, 5c, 69, fe, 10, eb, 1b, 6c, ee, 1d, c0, 00, 00, 06, b1, c5, 4b, de, 00] +udp_resp 004000c0000000000000e50c4682ab1696c4aadc67ac671b1ebab35d9b590b6c61999c214566806d26f17fe727653dfa9e1986655551817c40c61d6847a04e767a09308e4d95c7e88c856f5e95b1adb72acfcdd77b1a92395caac32e0fbc1bb8aa8724d39aa65bad37d37ba6ce4685a7c38fb1dc6d9dc94fe65f27c319550c7fa9956a4f6cdcf27e2c143851311dc94824d6c62dc36d490605856f7065aed7302119fe15ee3c9701d337e174591c774638b13feffd0e069f7623195c491d48a934f117460dca1c1e99e98c43a8a113ecdbf2bcde608d9437f3abf6235a8758e69d0f2b6ea5eba43e5abda1c50576fd61c00000613ebf7304800000025ab9 UdpSessionResp { status: 0, error_code: 0, message: [12, 70, 130, 171, 22, 150, 196, 170, 220, 103, 172, 103, 27, 30, 186, 179, 93, 155, 89, 11, 108, 97, 153, 156, 33, 69, 102, 128, 109, 38, 241, 127, 231, 39, 101, 61, 250, 158, 25, 134, 101, 85, 81, 129, 124, 64, 198, 29, 104, 71, 160, 78, 118, 122, 9, 48, 142, 77, 149, 199, 232, 140, 133, 111, 94, 149, 177, 173, 183, 42, 207, 205, 215, 123, 26, 146, 57, 92, 170, 195, 46, 15, 188, 27, 184, 170, 135, 36, 211, 154, 166, 91, 173, 55, 211, 123, 166, 206, 70, 133, 167, 195, 143, 177, 220, 109, 157, 201, 79, 230, 95, 39, 195, 25, 85, 12, 127, 169, 149, 106, 79, 108, 220, 242, 126, 44, 20, 56, 81, 49, 29, 201, 72, 36, 214, 198, 45, 195, 109, 73, 6, 5, 133, 111, 112, 101, 174, 215, 48, 33, 25, 254, 21, 238, 60, 151, 1, 211, 55, 225, 116, 89, 28, 119, 70, 56, 177, 63, 239, 253, 14, 6, 159, 118, 35, 25, 92, 73, 29, 72, 169, 52, 241, 23, 70, 13, 202, 28, 30, 153, 233, 140, 67, 168, 161, 19, 236, 219, 242, 188, 222, 96, 141, 148, 55, 243, 171, 246, 35, 90, 135, 88, 230, 157, 15, 43, 110, 165, 235, 164, 62, 90, 189, 161, 197, 5, 118, 253, 97], idle_timeout_ms: 417664561924 };254;[00, 00, 40, e5, 0c, 46, 82, ab, 16, 96, c4, aa, dc, 67, ac, 67, 1b, 1e, ba, b3, 5d, 9b, 59, 0b, 6c, 61, 99, 9c, 21, 45, 66, 80, 6d, 26, f1, 7f, e7, 27, 65, 3d, fa, 9e, 19, 86, 65, 55, 51, 81, 7c, 40, c6, 1d, 68, 47, a0, 4e, 76, 7a, 09, 30, 8e, 4d, 95, c7, e8, 8c, 85, 6f, 5e, 95, b1, ad, b7, 2a, cf, cd, d7, 7b, 1a, 92, 39, 5c, aa, c3, 2e, 0f, bc, 1b, b8, aa, 87, 24, d3, 9a, a6, 5b, ad, 37, d3, 7b, a6, ce, 46, 85, a7, c3, 8f, b1, dc, 6d, 9d, c9, 4f, e6, 5f, 27, c3, 19, 55, 0c, 7f, a9, 95, 6a, 4f, 6c, dc, f2, 7e, 2c, 14, 38, 51, 31, 1d, c9, 48, 24, d6, c6, 2d, c3, 6d, 49, 06, 05, 85, 6f, 70, 65, ae, d7, 30, 21, 19, fe, 15, ee, 3c, 97, 01, d3, 37, e1, 74, 59, 1c, 77, 46, 38, b1, 3f, ef, fd, 0e, 06, 9f, 76, 23, 19, 5c, 49, 1d, 48, a9, 34, f1, 17, 46, 0d, ca, 1c, 1e, 99, e9, 8c, 43, a8, a1, 13, ec, db, f2, bc, de, 60, 8d, 94, 37, f3, ab, f6, 23, 5a, 87, 58, e6, 9d, 0f, 2b, 6e, a5, eb, a4, 3e, 5a, bd, a1, c5, 05, 76, fd, 61, c0, 00, 00, 61, 3e, bf, 73, 04, 00] +udp_resp 01c000000000000004800000837b9164fa7812331267bd51a5200d9e486cfdf31a2d079f60927af6a7d0a554e83baa1423842c15b020ee91b040773b4a8cfba743e2598d1aec979ad865a8c39ef293942312e9777703c3fabb96113cffcef6e8836e872969c8e7bcc40891d2746a0cdb05752a2cab37e976be10bcb87c732bfbda8bf86691a47d79f118f678301e7ccac000000044e3f27e400f0bf875b265af706dade4d49b923479 UdpSessionResp { status: 1, error_code: 4, message: [123, 145, 100, 250, 120, 18, 51, 18, 103, 189, 81, 165, 32, 13, 158, 72, 108, 253, 243, 26, 45, 7, 159, 96, 146, 122, 246, 167, 208, 165, 84, 232, 59, 170, 20, 35, 132, 44, 21, 176, 32, 238, 145, 176, 64, 119, 59, 74, 140, 251, 167, 67, 226, 89, 141, 26, 236, 151, 154, 216, 101, 168, 195, 158, 242, 147, 148, 35, 18, 233, 119, 119, 3, 195, 250, 187, 150, 17, 60, 255, 206, 246, 232, 131, 110, 135, 41, 105, 200, 231, 188, 196, 8, 145, 210, 116, 106, 12, 219, 5, 117, 42, 44, 171, 55, 233, 118, 190, 16, 188, 184, 124, 115, 43, 251, 218, 139, 248, 102, 145, 164, 125, 121, 241, 24, 246, 120, 48, 30, 124, 202], idle_timeout_ms: 1155789438 };169;[01, 04, 40, 83, 7b, 91, 64, fa, 78, 12, 33, 12, 67, bd, 51, a5, 20, 0d, 9e, 48, 6c, fd, f3, 1a, 2d, 07, 9f, 60, 92, 7a, f6, a7, d0, a5, 54, e8, 3b, aa, 14, 23, 84, 2c, 15, b0, 20, ee, 91, b0, 40, 77, 3b, 4a, 8c, fb, a7, 43, e2, 59, 8d, 1a, ec, 97, 9a, d8, 65, a8, c3, 9e, f2, 93, 94, 23, 12, e9, 77, 77, 03, c3, fa, bb, 96, 11, 3c, ff, ce, f6, e8, 83, 6e, 87, 29, 69, c8, e7, bc, c4, 08, 91, d2, 74, 6a, 0c, db, 05, 75, 2a, 2c, ab, 37, e9, 76, be, 10, bc, b8, 7c, 73, 2b, fb, da, 8b, f8, 66, 91, a4, 7d, 79, f1, 18, f6, 78, 30, 1e, 7c, ca, c0, 00, 00, 00, 44, e3, f2, 7e, 00] +udp_resp 000040b49b5f3e3090dfad493d1ae6da8127818f615b8341e639d895231f3993f8103b9587261153fdd3bcd2a8bb70d2fac74a08a4d7d7fadbc7e4c74ec75054e8699f9ea1083c4e247c02bb8007015f3b4cd46289d6cde5a7c241a309894a105d3ebd8309273d3aa9000f11a35a5f547173c39ffb30cabf8c59acd4dfe0ec213618714eabb69c76ae499975f8ae174a8a098b6da14c014446216f208c3af1030da63c63bed7d2f16bcd06235be5c5d69161bd2f5dffa6f7c000fea2e26ad4a34000 UdpSessionResp { status: 0, error_code: 0, message: [155, 95, 62, 48, 144, 223, 173, 73, 61, 26, 230, 218, 129, 39, 129, 143, 97, 91, 131, 65, 230, 57, 216, 149, 35, 31, 57, 147, 248, 16, 59, 149, 135, 38, 17, 83, 253, 211, 188, 210, 168, 187, 112, 210, 250, 199, 74, 8, 164, 215, 215, 250, 219, 199, 228, 199, 78, 199, 80, 84, 232, 105, 159, 158, 161, 8, 60, 78, 36, 124, 2, 187, 128, 7, 1, 95, 59, 76, 212, 98, 137, 214, 205, 229, 167, 194, 65, 163, 9, 137, 74, 16, 93, 62, 189, 131, 9, 39, 61, 58, 169, 0, 15, 17, 163, 90, 95, 84, 113, 115, 195, 159, 251, 48, 202, 191, 140, 89, 172, 212, 223, 224, 236, 33, 54, 24, 113, 78, 171, 182, 156, 118, 174, 73, 153, 117, 248, 174, 23, 74, 138, 9, 139, 109, 161, 76, 1, 68, 70, 33, 111, 32, 140, 58, 241, 3, 13, 166, 60, 99, 190, 215, 210, 241, 107, 205, 6, 35, 91, 229, 197, 214, 145, 97, 189, 47, 93, 255, 166, 247], idle_timeout_ms: 279975536809123 };194;[00, 00, 40, b4, 9b, 5f, 3e, 30, 90, df, ad, 49, 3d, 1a, e6, da, 81, 27, 81, 8f, 61, 5b, 83, 41, e6, 39, d8, 95, 23, 1f, 39, 93, f8, 10, 3b, 95, 87, 26, 11, 53, fd, d3, bc, d2, a8, bb, 70, d2, fa, c7, 4a, 08, a4, d7, d7, fa, db, c7, e4, c7, 4e, c7, 50, 54, e8, 69, 9f, 9e, a1, 08, 3c, 4e, 24, 7c, 02, bb, 80, 07, 01, 5f, 3b, 4c, d4, 62, 89, d6, cd, e5, a7, c2, 41, a3, 09, 89, 4a, 10, 5d, 3e, bd, 83, 09, 27, 3d, 3a, a9, 00, 0f, 11, a3, 5a, 5f, 54, 71, 73, c3, 9f, fb, 30, ca, bf, 8c, 59, ac, d4, df, e0, ec, 21, 36, 18, 71, 4e, ab, b6, 9c, 76, ae, 49, 99, 75, f8, ae, 17, 4a, 8a, 09, 8b, 6d, a1, 4c, 01, 44, 46, 21, 6f, 20, 8c, 3a, f1, 03, 0d, a6, 3c, 63, be, d7, d2, f1, 6b, cd, 06, 23, 5b, e5, c5, d6, 91, 61, bd, 2f, 5d, ff, a6, f7, c0, 00, fe, a2, e2, 6a, d4, a3, 00] +udp_resp 00c000000000000000404f1e387626043ec69ebf1c110e6611955637f8dd4490837f9350776d757343dcf7b65dc218156445213ead2623a229dde151c50613502285ab5f5f32908189dcb7f418fe6d9af2da494543e0907243764332800000055b855f25d6 UdpSessionResp { status: 0, error_code: 0, message: [30, 56, 118, 38, 4, 62, 198, 158, 191, 28, 17, 14, 102, 17, 149, 86, 55, 248, 221, 68, 144, 131, 127, 147, 80, 119, 109, 117, 115, 67, 220, 247, 182, 93, 194, 24, 21, 100, 69, 33, 62, 173, 38, 35, 162, 41, 221, 225, 81, 197, 6, 19, 80, 34, 133, 171, 95, 95, 50, 144, 129, 137, 220, 183, 244, 24, 254, 109, 154, 242, 218, 73, 69, 67, 224, 144, 114, 67, 118], idle_timeout_ms: 818 };101;[00, 00, 40, 4f, 1e, 38, 76, 26, 04, 3e, c6, 9e, bf, 1c, 11, 0e, 66, 11, 95, 56, 37, f8, dd, 44, 90, 83, 7f, 93, 50, 77, 6d, 75, 73, 43, dc, f7, b6, 5d, c2, 18, 15, 64, 45, 21, 3e, ad, 26, 23, a2, 29, dd, e1, 51, c5, 06, 13, 50, 22, 85, ab, 5f, 5f, 32, 90, 81, 89, dc, b7, f4, 18, fe, 6d, 9a, f2, da, 49, 45, 43, e0, 90, 72, 43, 76, 43, 32, 00] +udp_resp 018000000140b73fb97adb78efa3b39736e09ca68b4d58497ff9ea29fd3d78b1e9334a037c27a54d82183c83a51039d9cd945ed3a5b180a13ebcec9a16af49feae63cfc05e55e5a464ae35fe85c3b756295696caaf205a2906b2c0c77494794e73d2aec67cb404f7e8cbadb03454d75bfe6eaa6d9150f2929abe8e9c1d92b5749b7e3f880982ac93a9a10f6ec0e7b644057c7f5859ee9d528388c3009d062fb4b800ac52acd3f00ec720c1eb1a85b02c22f2165fc827affe798e7a46604fc0000096406749a980000000 UdpSessionResp { status: 1, error_code: 1, message: [63, 185, 122, 219, 120, 239, 163, 179, 151, 54, 224, 156, 166, 139, 77, 88, 73, 127, 249, 234, 41, 253, 61, 120, 177, 233, 51, 74, 3, 124, 39, 165, 77, 130, 24, 60, 131, 165, 16, 57, 217, 205, 148, 94, 211, 165, 177, 128, 161, 62, 188, 236, 154, 22, 175, 73, 254, 174, 99, 207, 192, 94, 85, 229, 164, 100, 174, 53, 254, 133, 195, 183, 86, 41, 86, 150, 202, 175, 32, 90, 41, 6, 178, 192, 199, 116, 148, 121, 78, 115, 210, 174, 198, 124, 180, 4, 247, 232, 203, 173, 176, 52, 84, 215, 91, 254, 110, 170, 109, 145, 80, 242, 146, 154, 190, 142, 156, 29, 146, 181, 116, 155, 126, 63, 136, 9, 130, 172, 147, 169, 161, 15, 110, 192, 231, 182, 68, 5, 124, 127, 88, 89, 238, 157, 82, 131, 136, 195, 0, 157, 6, 47, 180, 184, 0, 172, 82, 172, 211, 240, 14, 199, 32, 193, 235, 26, 133, 176, 44, 34, 242, 22, 95, 200, 39, 175, 254, 121, 142, 122, 70, 96, 79], idle_timeout_ms: 645325605289 };202;[01, 01, 40, b7, 3f, b9, 7a, db, 78, ef, a3, b3, 97, 36, e0, 9c, a6, 8b, 4d, 58, 49, 7f, f9, ea, 29, fd, 3d, 78, b1, e9, 33, 4a, 03, 7c, 27, a5, 4d, 82, 18, 3c, 83, a5, 10, 39, d9, cd, 94, 5e, d3, a5, b1, 80, a1, 3e, bc, ec, 9a, 16, af, 49, fe, ae, 63, cf, c0, 5e, 55, e5, a4, 64, ae, 35, fe, 85, c3, b7, 56, 29, 56, 96, ca, af, 20, 5a, 29, 06, b2, c0, c7, 74, 94, 79, 4e, 73, d2, ae, c6, 7c, b4, 04, f7, e8, cb, ad, b0, 34, 54, d7, 5b, fe, 6e, aa, 6d, 91, 50, f2, 92, 9a, be, 8e, 9c, 1d, 92, b5, 74, 9b, 7e, 3f, 88, 09, 82, ac, 93, a9, a1, 0f, 6e, c0, e7, b6, 44, 05, 7c, 7f, 58, 59, ee, 9d, 52, 83, 88, c3, 00, 9d, 06, 2f, b4, b8, 00, ac, 52, ac, d3, f0, 0e, c7, 20, c1, eb, 1a, 85, b0, 2c, 22, f2, 16, 5f, c8, 27, af, fe, 79, 8e, 7a, 46, 60, 4f, c0, 00, 00, 96, 40, 67, 49, a9, 00] +udp_resp 0000404e113a80e0863ad0aa353fced0cf996db8611eda40ea1b8579d31ad1434ec86152a6851313ef6b20c5abc38ba847f42c12e951ad6c557818e56a3aae0384929a13b5c8a6c4707226f7ffd2f14140af58d64000 UdpSessionResp { status: 0, error_code: 0, message: [17, 58, 128, 224, 134, 58, 208, 170, 53, 63, 206, 208, 207, 153, 109, 184, 97, 30, 218, 64, 234, 27, 133, 121, 211, 26, 209, 67, 78, 200, 97, 82, 166, 133, 19, 19, 239, 107, 32, 197, 171, 195, 139, 168, 71, 244, 44, 18, 233, 81, 173, 108, 85, 120, 24, 229, 106, 58, 174, 3, 132, 146, 154, 19, 181, 200, 166, 196, 112, 114, 38, 247, 255, 210, 241, 65, 64, 175], idle_timeout_ms: 6358 };86;[00, 00, 40, 4e, 11, 3a, 80, e0, 86, 3a, d0, aa, 35, 3f, ce, d0, cf, 99, 6d, b8, 61, 1e, da, 40, ea, 1b, 85, 79, d3, 1a, d1, 43, 4e, c8, 61, 52, a6, 85, 13, 13, ef, 6b, 20, c5, ab, c3, 8b, a8, 47, f4, 2c, 12, e9, 51, ad, 6c, 55, 78, 18, e5, 6a, 3a, ae, 03, 84, 92, 9a, 13, b5, c8, a6, c4, 70, 72, 26, f7, ff, d2, f1, 41, 40, af, 58, d6, 00] +udp_resp 0180000004c000000000000050b2c1d13b44fc063dab0c9f517f8ec854cfe7c6b8f0e55dc58834d8ec9048bc7ea20ef28dd8e5aa0cb5055577261858a21beaa04e1828dfc8f757de203f333da2241176b1ea160668dc9fa9aa47ffc3fdc5e7201f78fa2b8c02abd1 UdpSessionResp { status: 1, error_code: 4, message: [178, 193, 209, 59, 68, 252, 6, 61, 171, 12, 159, 81, 127, 142, 200, 84, 207, 231, 198, 184, 240, 229, 93, 197, 136, 52, 216, 236, 144, 72, 188, 126, 162, 14, 242, 141, 216, 229, 170, 12, 181, 5, 85, 119, 38, 24, 88, 162, 27, 234, 160, 78, 24, 40, 223, 200, 247, 87, 222, 32, 63, 51, 61, 162, 36, 17, 118, 177, 234, 22, 6, 104, 220, 159, 169, 170, 71, 255, 195, 253], idle_timeout_ms: 425344009355537292 };104;[01, 04, 40, 50, b2, c1, d1, 3b, 44, fc, 06, 3d, ab, 0c, 9f, 51, 7f, 8e, c8, 54, cf, e7, c6, b8, f0, e5, 5d, c5, 88, 34, d8, ec, 90, 48, bc, 7e, a2, 0e, f2, 8d, d8, e5, aa, 0c, b5, 05, 55, 77, 26, 18, 58, a2, 1b, ea, a0, 4e, 18, 28, df, c8, f7, 57, de, 20, 3f, 33, 3d, a2, 24, 11, 76, b1, ea, 16, 06, 68, dc, 9f, a9, aa, 47, ff, c3, fd, c5, e7, 20, 1f, 78, fa, 2b, 8c, 00] +udp_resp 00c0000000000000008000007aabd36bc63716d5b1c61fde87efd4d9aaa5e68c3932895310c58c56db261f94e1377b3e09487d66d8b2c121b03dccddf7a7b56ceba60f30596564c68d1e7436d1652016d2fb3c321bd600d86ead0e69365a8b1ea93773caa6995b89f2b7314887492f2a67f86c1f16c51d1ae4da81b65b0ddb07f559289c832268c000000000000793c000000000000000 UdpSessionResp { status: 0, error_code: 0, message: [171, 211, 107, 198, 55, 22, 213, 177, 198, 31, 222, 135, 239, 212, 217, 170, 165, 230, 140, 57, 50, 137, 83, 16, 197, 140, 86, 219, 38, 31, 148, 225, 55, 123, 62, 9, 72, 125, 102, 216, 178, 193, 33, 176, 61, 204, 221, 247, 167, 181, 108, 235, 166, 15, 48, 89, 101, 100, 198, 141, 30, 116, 54, 209, 101, 32, 22, 210, 251, 60, 50, 27, 214, 0, 216, 110, 173, 14, 105, 54, 90, 139, 30, 169, 55, 115, 202, 166, 153, 91, 137, 242, 183, 49, 72, 135, 73, 47, 42, 103, 248, 108, 31, 22, 197, 29, 26, 228, 218, 129, 182, 91, 13, 219, 7, 245, 89, 40, 156, 131, 34, 104], idle_timeout_ms: 1939 };151;[00, 00, 40, 7a, ab, d3, 6b, c6, 37, 16, d5, b1, c6, 1f, de, 87, ef, d4, d9, aa, a5, e6, 8c, 39, 32, 89, 53, 10, c5, 8c, 56, db, 26, 1f, 94, e1, 37, 7b, 3e, 09, 48, 7d, 66, d8, b2, c1, 21, b0, 3d, cc, dd, f7, a7, b5, 6c, eb, a6, 0f, 30, 59, 65, 64, c6, 8d, 1e, 74, 36, d1, 65, 20, 16, d2, fb, 3c, 32, 1b, d6, 00, d8, 6e, ad, 0e, 69, 36, 5a, 8b, 1e, a9, 37, 73, ca, a6, 99, 5b, 89, f2, b7, 31, 48, 87, 49, 2f, 2a, 67, f8, 6c, 1f, 16, c5, 1d, 1a, e4, da, 81, b6, 5b, 0d, db, 07, f5, 59, 28, 9c, 83, 22, 68, 47, 93, 00] +udp_resp 014002c000000000000054db154539b91130c624ed45176cdd60f0c3bddc7a80dd024e1d940ccd7c250967320c6abbb7c6dbe983e20a14938c06684bf2114a30f0450900d9207b133bb2f4fcfa8c1142a1a965ab8f8839745e22cdc150695380000fd700 UdpSessionResp { status: 1, error_code: 2, message: [219, 21, 69, 57, 185, 17, 48, 198, 36, 237, 69, 23, 108, 221, 96, 240, 195, 189, 220, 122, 128, 221, 2, 78, 29, 148, 12, 205, 124, 37, 9, 103, 50, 12, 106, 187, 183, 198, 219, 233, 131, 226, 10, 20, 147, 140, 6, 104, 75, 242, 17, 74, 48, 240, 69, 9, 0, 217, 32, 123, 19, 59, 178, 244, 252, 250, 140, 17, 66, 161, 169, 101, 171, 143, 136, 57, 116, 94, 34, 205, 193, 80, 105, 83], idle_timeout_ms: 4055 };100;[01, 02, 40, 54, db, 15, 45, 39, b9, 11, 30, c6, 24, ed, 45, 17, 6c, dd, 60, f0, c3, bd, dc, 7a, 80, dd, 02, 4e, 1d, 94, 0c, cd, 7c, 25, 09, 67, 32, 0c, 6a, bb, b7, c6, db, e9, 83, e2, 0a, 14, 93, 8c, 06, 68, 4b, f2, 11, 4a, 30, f0, 45, 09, 00, d9, 20, 7b, 13, 3b, b2, f4, fc, fa, 8c, 11, 42, a1, a9, 65, ab, 8f, 88, 39, 74, 5e, 22, cd, c1, 50, 69, 53, 4f, d7, 00] +udp_resp 03c000000000000037c00000000000008448711735bd818a284e66bb66b7770f76debbe7ee36e5139bbb28eb575972c12e3a9aa6cb0ca865da7cbcff3b84d8447f46c0a85f05ae4b67f2e167a8f9b1154d47e8065211cfcea8381fc41a359ea38d69bc90472c5febd6cda5e453e90dbd6c0a574810f41806038a3555e95670e579cb87b5ee36deb082b8b67aa929996105c31023adc0000000000005ce4000 reject +udp_resp 010440ca63f520514ba001ee81ae0d8e6bb21f00a3ba17abc259feb97f5277af87fe7cb8099e7ef4510ffdf26345e0fc23b855e8cb5a508613f06395a6c915d07b5f3b2658aab24979a807da78ca0556e3a93a051f9380683167072054d668dd54356364ab45ff0e2e6e908b038fc864bf8cb399eebdd35e68f4884bb53534ea7b2b9d9d53e28ba377b517d525a5c0f3831e2a92402afaab47dd2fe4d01dbaaf3ef959e049414b79bb22ee8e95709bf194e002d4e9d7678aa6c0fd94a6bc54c7912dcad8c36ec5834a0525b62b3180007c7b0075 UdpSessionResp { status: 1, error_code: 4, message: [99, 245, 32, 81, 75, 160, 1, 238, 129, 174, 13, 142, 107, 178, 31, 0, 163, 186, 23, 171, 194, 89, 254, 185, 127, 82, 119, 175, 135, 254, 124, 184, 9, 158, 126, 244, 81, 15, 253, 242, 99, 69, 224, 252, 35, 184, 85, 232, 203, 90, 80, 134, 19, 240, 99, 149, 166, 201, 21, 208, 123, 95, 59, 38, 88, 170, 178, 73, 121, 168, 7, 218, 120, 202, 5, 86, 227, 169, 58, 5, 31, 147, 128, 104, 49, 103, 7, 32, 84, 214, 104, 221, 84, 53, 99, 100, 171, 69, 255, 14, 46, 110, 144, 139, 3, 143, 200, 100, 191, 140, 179, 153, 238, 189, 211, 94, 104, 244, 136, 75, 181, 53, 52, 234, 123, 43, 157, 157, 83, 226, 139, 163, 119, 181, 23, 213, 37, 165, 192, 243, 131, 30, 42, 146, 64, 42, 250, 171, 71, 221, 47, 228, 208, 29, 186, 175, 62, 249, 89, 224, 73, 65, 75, 121, 187, 34, 238, 142, 149, 112, 155, 241, 148, 224, 2, 212, 233, 215, 103, 138, 166, 192, 253, 148, 166, 188, 84, 199, 145, 45, 202, 216, 195, 110, 197, 131, 74, 5, 37, 182, 43, 49], idle_timeout_ms: 31867 };211;[01, 04, 40, ca, 63, f5, 20, 51, 4b, a0, 01, ee, 81, ae, 0d, 8e, 6b, b2, 1f, 00, a3, ba, 17, ab, c2, 59, fe, b9, 7f, 52, 77, af, 87, fe, 7c, b8, 09, 9e, 7e, f4, 51, 0f, fd, f2, 63, 45, e0, fc, 23, b8, 55, e8, cb, 5a, 50, 86, 13, f0, 63, 95, a6, c9, 15, d0, 7b, 5f, 3b, 26, 58, aa, b2, 49, 79, a8, 07, da, 78, ca, 05, 56, e3, a9, 3a, 05, 1f, 93, 80, 68, 31, 67, 07, 20, 54, d6, 68, dd, 54, 35, 63, 64, ab, 45, ff, 0e, 2e, 6e, 90, 8b, 03, 8f, c8, 64, bf, 8c, b3, 99, ee, bd, d3, 5e, 68, f4, 88, 4b, b5, 35, 34, ea, 7b, 2b, 9d, 9d, 53, e2, 8b, a3, 77, b5, 17, d5, 25, a5, c0, f3, 83, 1e, 2a, 92, 40, 2a, fa, ab, 47, dd, 2f, e4, d0, 1d, ba, af, 3e, f9, 59, e0, 49, 41, 4b, 79, bb, 22, ee, 8e, 95, 70, 9b, f1, 94, e0, 02, d4, e9, d7, 67, 8a, a6, c0, fd, 94, a6, bc, 54, c7, 91, 2d, ca, d8, c3, 6e, c5, 83, 4a, 05, 25, b6, 2b, 31, 80, 00, 7c, 7b, 00] +udp_resp 0102054fbfddbbdb803ea174c000000000000000d15daf38fa UdpSessionResp { status: 1, error_code: 2, message: [79, 191, 221, 187, 219], idle_timeout_ms: 4104564 };20;[01, 02, 05, 4f, bf, dd, bb, db, 80, 3e, a1, 74, 00] +udp_resp 008000000040ae16d0193c319682fcda109dcdf2eccaefdef663871ac33bb52e13575cbbcf44a72f6c3bed0b541e3ad3fb70597b9505fb461b1e7ea2fdbd1e4ab43534790f34229621d810b8dee350cb4fcee6ee42608958f8f858c95602a301fb4c6a3cb62292db2f4a824b485b78261d503a9ae8fb79c754580aab5a6b37fd47b8267f45997ed226b64bd4c61ef4e4de5e649e3bbc9ac4a2c4f2631281708fb257748d8ab3175ba9bc9500005c0bae539a99ba7a41fdc000000000000000ba48638a UdpSessionResp { status: 0, error_code: 0, message: [22, 208, 25, 60, 49, 150, 130, 252, 218, 16, 157, 205, 242, 236, 202, 239, 222, 246, 99, 135, 26, 195, 59, 181, 46, 19, 87, 92, 187, 207, 68, 167, 47, 108, 59, 237, 11, 84, 30, 58, 211, 251, 112, 89, 123, 149, 5, 251, 70, 27, 30, 126, 162, 253, 189, 30, 74, 180, 53, 52, 121, 15, 52, 34, 150, 33, 216, 16, 184, 222, 227, 80, 203, 79, 206, 230, 238, 66, 96, 137, 88, 248, 248, 88, 201, 86, 2, 163, 1, 251, 76, 106, 60, 182, 34, 146, 219, 47, 74, 130, 75, 72, 91, 120, 38, 29, 80, 58, 154, 232, 251, 121, 199, 84, 88, 10, 171, 90, 107, 55, 253, 71, 184, 38, 127, 69, 153, 126, 210, 38, 182, 75, 212, 198, 30, 244, 228, 222, 94, 100, 158, 59, 188, 154, 196, 162, 196, 242, 99, 18, 129, 112, 143, 178, 87, 116, 141, 138, 179, 23, 91, 169, 188, 149, 0, 0, 92, 11, 174, 83, 154, 153, 186, 122], idle_timeout_ms: 509 };191;[00, 00, 40, ae, 16, d0, 19, 3c, 31, 96, 82, fc, da, 10, 9d, cd, f2, ec, ca, ef, de, f6, 63, 87, 1a, c3, 3b, b5, 2e, 13, 57, 5c, bb, cf, 44, a7, 2f, 6c, 3b, ed, 0b, 54, 1e, 3a, d3, fb, 70, 59, 7b, 95, 05, fb, 46, 1b, 1e, 7e, a2, fd, bd, 1e, 4a, b4, 35, 34, 79, 0f, 34, 22, 96, 21, d8, 10, b8, de, e3, 50, cb, 4f, ce, e6, ee, 42, 60, 89, 58, f8, f8, 58, c9, 56, 02, a3, 01, fb, 4c, 6a, 3c, b6, 22, 92, db, 2f, 4a, 82, 4b, 48, 5b, 78, 26, 1d, 50, 3a, 9a, e8, fb, 79, c7, 54, 58, 0a, ab, 5a, 6b, 37, fd, 47, b8, 26, 7f, 45, 99, 7e, d2, 26, b6, 4b, d4, c6, 1e, f4, e4, de, 5e, 64, 9e, 3b, bc, 9a, c4, a2, c4, f2, 63, 12, 81, 70, 8f, b2, 57, 74, 8d, 8a, b3, 17, 5b, a9, bc, 95, 00, 00, 5c, 0b, ae, 53, 9a, 99, ba, 7a, 41, fd, 00] +udp_resp 0080000000c0000000000000d8279803c87b59b2d292790a93175f33fe49d90419ef5b1b805536ef039461644674ad67283100f124eb4473deaac5dd6d3497694dc33154f7159c2e603f55187da79a51553af929ed9e22990998866f77c5d324f2da561a0e64807ae00c2f570ad22ce910913cd49721aa7bb25d231fdabe5952ee15caf62b9b2697aad555ce6fb84c1aabcf537cf75497ab55de2205445bb8986eddd9cdad3abb8075c9e7164f24af606ff8dcb0c7190a9bbbccd222a733a90c8576d18c0afdb2640dea97cc2c0ded34c823f5104ab7be43557737f96507f66dfa193a64b380092cce80000000 UdpSessionResp { status: 0, error_code: 0, message: [39, 152, 3, 200, 123, 89, 178, 210, 146, 121, 10, 147, 23, 95, 51, 254, 73, 217, 4, 25, 239, 91, 27, 128, 85, 54, 239, 3, 148, 97, 100, 70, 116, 173, 103, 40, 49, 0, 241, 36, 235, 68, 115, 222, 170, 197, 221, 109, 52, 151, 105, 77, 195, 49, 84, 247, 21, 156, 46, 96, 63, 85, 24, 125, 167, 154, 81, 85, 58, 249, 41, 237, 158, 34, 153, 9, 152, 134, 111, 119, 197, 211, 36, 242, 218, 86, 26, 14, 100, 128, 122, 224, 12, 47, 87, 10, 210, 44, 233, 16, 145, 60, 212, 151, 33, 170, 123, 178, 93, 35, 31, 218, 190, 89, 82, 238, 21, 202, 246, 43, 155, 38, 151, 170, 213, 85, 206, 111, 184, 76, 26, 171, 207, 83, 124, 247, 84, 151, 171, 85, 222, 34, 5, 68, 91, 184, 152, 110, 221, 217, 205, 173, 58, 187, 128, 117, 201, 231, 22, 79, 36, 175, 96, 111, 248, 220, 176, 199, 25, 10, 155, 187, 204, 210, 34, 167, 51, 169, 12, 133, 118, 209, 140, 10, 253, 178, 100, 13, 234, 151, 204, 44, 13, 237, 52, 200, 35, 245, 16, 74, 183, 190, 67, 85, 119, 55, 249, 101, 7, 246, 109, 250, 25, 58, 100, 179], idle_timeout_ms: 601294 };237;[00, 00, 40, d8, 27, 98, 03, c8, 7b, 59, b2, d2, 92, 79, 0a, 93, 17, 5f, 33, fe, 49, d9, 04, 19, ef, 5b, 1b, 80, 55, 36, ef, 03, 94, 61, 64, 46, 74, ad, 67, 28, 31, 00, f1, 24, eb, 44, 73, de, aa, c5, dd, 6d, 34, 97, 69, 4d, c3, 31, 54, f7, 15, 9c, 2e, 60, 3f, 55, 18, 7d, a7, 9a, 51, 55, 3a, f9, 29, ed, 9e, 22, 99, 09, 98, 86, 6f, 77, c5, d3, 24, f2, da, 56, 1a, 0e, 64, 80, 7a, e0, 0c, 2f, 57, 0a, d2, 2c, e9, 10, 91, 3c, d4, 97, 21, aa, 7b, b2, 5d, 23, 1f, da, be, 59, 52, ee, 15, ca, f6, 2b, 9b, 26, 97, aa, d5, 55, ce, 6f, b8, 4c, 1a, ab, cf, 53, 7c, f7, 54, 97, ab, 55, de, 22, 05, 44, 5b, b8, 98, 6e, dd, d9, cd, ad, 3a, bb, 80, 75, c9, e7, 16, 4f, 24, af, 60, 6f, f8, dc, b0, c7, 19, 0a, 9b, bb, cc, d2, 22, a7, 33, a9, 0c, 85, 76, d1, 8c, 0a, fd, b2, 64, 0d, ea, 97, cc, 2c, 0d, ed, 34, c8, 23, f5, 10, 4a, b7, be, 43, 55, 77, 37, f9, 65, 07, f6, 6d, fa, 19, 3a, 64, b3, 80, 09, 2c, ce, 00] +udp_resp 00c000000000000000c0000000000000d6dcca2bc853e420c4ad120da9317119edeb9870140f47411bf6d556eaf2b1df13397ff7defdaceab34f9cd1db212f15e7c5dcc6d36e2070d7878ff423778ac4a03ef471f8b50d9501aad9301c1e35117d1dedff21ad492e05dde9e4f802e16eec251fc2c21165643d692aec2e592c55327d4acb8b560949c0e75722ce7760bf4e73e1cf82e3a40c60c4245438f70d1dacc1dec1bf621a7cbfc76ef2104e68e8194dad6821e2f62ba60e24c3825f2e28fa061a32ef19b3690bfd0814e0dec1022d58269a5abaa61ad4635676617b370e127eea9b7bd11bc0023de1320719d44000 UdpSessionResp { status: 0, error_code: 0, message: [220, 202, 43, 200, 83, 228, 32, 196, 173, 18, 13, 169, 49, 113, 25, 237, 235, 152, 112, 20, 15, 71, 65, 27, 246, 213, 86, 234, 242, 177, 223, 19, 57, 127, 247, 222, 253, 172, 234, 179, 79, 156, 209, 219, 33, 47, 21, 231, 197, 220, 198, 211, 110, 32, 112, 215, 135, 143, 244, 35, 119, 138, 196, 160, 62, 244, 113, 248, 181, 13, 149, 1, 170, 217, 48, 28, 30, 53, 17, 125, 29, 237, 255, 33, 173, 73, 46, 5, 221, 233, 228, 248, 2, 225, 110, 236, 37, 31, 194, 194, 17, 101, 100, 61, 105, 42, 236, 46, 89, 44, 85, 50, 125, 74, 203, 139, 86, 9, 73, 192, 231, 87, 34, 206, 119, 96, 191, 78, 115, 225, 207, 130, 227, 164, 12, 96, 196, 36, 84, 56, 247, 13, 29, 172, 193, 222, 193, 191, 98, 26, 124, 191, 199, 110, 242, 16, 78, 104, 232, 25, 77, 173, 104, 33, 226, 246, 43, 166, 14, 36, 195, 130, 95, 46, 40, 250, 6, 26, 50, 239, 25, 179, 105, 11, 253, 8, 20, 224, 222, 193, 2, 45, 88, 38, 154, 90, 186, 166, 26, 212, 99, 86, 118, 97, 123, 55, 14, 18, 126, 234, 155, 123, 209, 27], idle_timeout_ms: 630987369683412 };241;[00, 00, 40, d6, dc, ca, 2b, c8, 53, e4, 20, c4, ad, 12, 0d, a9, 31, 71, 19, ed, eb, 98, 70, 14, 0f, 47, 41, 1b, f6, d5, 56, ea, f2, b1, df, 13, 39, 7f, f7, de, fd, ac, ea, b3, 4f, 9c, d1, db, 21, 2f, 15, e7, c5, dc, c6, d3, 6e, 20, 70, d7, 87, 8f, f4, 23, 77, 8a, c4, a0, 3e, f4, 71, f8, b5, 0d, 95, 01, aa, d9, 30, 1c, 1e, 35, 11, 7d, 1d, ed, ff, 21, ad, 49, 2e, 05, dd, e9, e4, f8, 02, e1, 6e, ec, 25, 1f, c2, c2, 11, 65, 64, 3d, 69, 2a, ec, 2e, 59, 2c, 55, 32, 7d, 4a, cb, 8b, 56, 09, 49, c0, e7, 57, 22, ce, 77, 60, bf, 4e, 73, e1, cf, 82, e3, a4, 0c, 60, c4, 24, 54, 38, f7, 0d, 1d, ac, c1, de, c1, bf, 62, 1a, 7c, bf, c7, 6e, f2, 10, 4e, 68, e8, 19, 4d, ad, 68, 21, e2, f6, 2b, a6, 0e, 24, c3, 82, 5f, 2e, 28, fa, 06, 1a, 32, ef, 19, b3, 69, 0b, fd, 08, 14, e0, de, c1, 02, 2d, 58, 26, 9a, 5a, ba, a6, 1a, d4, 63, 56, 76, 61, 7b, 37, 0e, 12, 7e, ea, 9b, 7b, d1, 1b, c0, 02, 3d, e1, 32, 07, 19, d4, 00] +udp_resp 018000000340bc15027e08f81d7e920d99599bfc425c4b20a6ea289c07d770d60582c8949001874edab608a455f8e4144069bc79985e197fb403d5a434cb9bbe0ea89eda27b187924c4fcbeb1d53ce282816d9ea253c7cc1a177342736ca7e0573982346792ce22e7f55d87952d9581b85eefad77b0bfa9fd946e9a46101b3d7dcbccacb60f8fa352a6a2d081abc5f717dd12c7f3060b44021231b8ebd3ac30734b426537bd78bc355eded2196d3bf011bdd05c70b7b420f8b2ced0ea18fdf8720d77d80000c1dc000000000000000 UdpSessionResp { status: 1, error_code: 3, message: [21, 2, 126, 8, 248, 29, 126, 146, 13, 153, 89, 155, 252, 66, 92, 75, 32, 166, 234, 40, 156, 7, 215, 112, 214, 5, 130, 200, 148, 144, 1, 135, 78, 218, 182, 8, 164, 85, 248, 228, 20, 64, 105, 188, 121, 152, 94, 25, 127, 180, 3, 213, 164, 52, 203, 155, 190, 14, 168, 158, 218, 39, 177, 135, 146, 76, 79, 203, 235, 29, 83, 206, 40, 40, 22, 217, 234, 37, 60, 124, 193, 161, 119, 52, 39, 54, 202, 126, 5, 115, 152, 35, 70, 121, 44, 226, 46, 127, 85, 216, 121, 82, 217, 88, 27, 133, 238, 250, 215, 123, 11, 250, 159, 217, 70, 233, 164, 97, 1, 179, 215, 220, 188, 202, 203, 96, 248, 250, 53, 42, 106, 45, 8, 26, 188, 95, 113, 125, 209, 44, 127, 48, 96, 180, 64, 33, 35, 27, 142, 189, 58, 195, 7, 52, 180, 38, 83, 123, 215, 139, 195, 85, 237, 237, 33, 150, 211, 191, 1, 27, 221, 5, 199, 11, 123, 66, 15, 139, 44, 237, 14, 161, 143, 223, 135, 32, 215, 125], idle_timeout_ms: 3101 };207;[01, 03, 40, bc, 15, 02, 7e, 08, f8, 1d, 7e, 92, 0d, 99, 59, 9b, fc, 42, 5c, 4b, 20, a6, ea, 28, 9c, 07, d7, 70, d6, 05, 82, c8, 94, 90, 01, 87, 4e, da, b6, 08, a4, 55, f8, e4, 14, 40, 69, bc, 79, 98, 5e, 19, 7f, b4, 03, d5, a4, 34, cb, 9b, be, 0e, a8, 9e, da, 27, b1, 87, 92, 4c, 4f, cb, eb, 1d, 53, ce, 28, 28, 16, d9, ea, 25, 3c, 7c, c1, a1, 77, 34, 27, 36, ca, 7e, 05, 73, 98, 23, 46, 79, 2c, e2, 2e, 7f, 55, d8, 79, 52, d9, 58, 1b, 85, ee, fa, d7, 7b, 0b, fa, 9f, d9, 46, e9, a4, 61, 01, b3, d7, dc, bc, ca, cb, 60, f8, fa, 35, 2a, 6a, 2d, 08, 1a, bc, 5f, 71, 7d, d1, 2c, 7f, 30, 60, b4, 40, 21, 23, 1b, 8e, bd, 3a, c3, 07, 34, b4, 26, 53, 7b, d7, 8b, c3, 55, ed, ed, 21, 96, d3, bf, 01, 1b, dd, 05, c7, 0b, 7b, 42, 0f, 8b, 2c, ed, 0e, a1, 8f, df, 87, 20, d7, 7d, 4c, 1d, 00] +udp_resp 0102c0000000000000df0f3b9c6231de8c542d92da8d2eb191ea683197c91a170b73439370ba0b38a868d19841dc8e937cb059a98648ed4cfdd12cf3dbeb1253009f2545bf8cdd754e65d2d2afcb74de1cc7f03f03a39a645000717d003b7c2bf7bd3bebaeab687b201c22a0a5bf75db58f33dbe72d0360fcd0b54684126e40186b3fe8e10bb587443a6a255410d471a146b016c8bf3665322123996a29dc6a4afa8dcc09f8e9118a40d6ddc45ead0f3fc98375dbb9bbe1a8595222fc555e30dd9a4ab66f4cd23f79e488418afe4400a8ac2234c848775b42e30aa0b1c1c2ebf185fd53c37bacfadcc8000001900 UdpSessionResp { status: 1, error_code: 2, message: [15, 59, 156, 98, 49, 222, 140, 84, 45, 146, 218, 141, 46, 177, 145, 234, 104, 49, 151, 201, 26, 23, 11, 115, 67, 147, 112, 186, 11, 56, 168, 104, 209, 152, 65, 220, 142, 147, 124, 176, 89, 169, 134, 72, 237, 76, 253, 209, 44, 243, 219, 235, 18, 83, 0, 159, 37, 69, 191, 140, 221, 117, 78, 101, 210, 210, 175, 203, 116, 222, 28, 199, 240, 63, 3, 163, 154, 100, 80, 0, 113, 125, 0, 59, 124, 43, 247, 189, 59, 235, 174, 171, 104, 123, 32, 28, 34, 160, 165, 191, 117, 219, 88, 243, 61, 190, 114, 208, 54, 15, 205, 11, 84, 104, 65, 38, 228, 1, 134, 179, 254, 142, 16, 187, 88, 116, 67, 166, 162, 85, 65, 13, 71, 26, 20, 107, 1, 108, 139, 243, 102, 83, 34, 18, 57, 150, 162, 157, 198, 164, 175, 168, 220, 192, 159, 142, 145, 24, 164, 13, 109, 220, 69, 234, 208, 243, 252, 152, 55, 93, 187, 155, 190, 26, 133, 149, 34, 47, 197, 85, 227, 13, 217, 164, 171, 102, 244, 205, 35, 247, 158, 72, 132, 24, 175, 228, 64, 10, 138, 194, 35, 76, 132, 135, 117, 180, 46, 48, 170, 11, 28, 28, 46, 191, 24, 95, 213, 60, 55, 186, 207, 173, 204], idle_timeout_ms: 25 };238;[01, 02, 40, df, 0f, 3b, 9c, 62, 31, de, 8c, 54, 2d, 92, da, 8d, 2e, b1, 91, ea, 68, 31, 97, c9, 1a, 17, 0b, 73, 43, 93, 70, ba, 0b, 38, a8, 68, d1, 98, 41, dc, 8e, 93, 7c, b0, 59, a9, 86, 48, ed, 4c, fd, d1, 2c, f3, db, eb, 12, 53, 00, 9f, 25, 45, bf, 8c, dd, 75, 4e, 65, d2, d2, af, cb, 74, de, 1c, c7, f0, 3f, 03, a3, 9a, 64, 50, 00, 71, 7d, 00, 3b, 7c, 2b, f7, bd, 3b, eb, ae, ab, 68, 7b, 20, 1c, 22, a0, a5, bf, 75, db, 58, f3, 3d, be, 72, d0, 36, 0f, cd, 0b, 54, 68, 41, 26, e4, 01, 86, b3, fe, 8e, 10, bb, 58, 74, 43, a6, a2, 55, 41, 0d, 47, 1a, 14, 6b, 01, 6c, 8b, f3, 66, 53, 22, 12, 39, 96, a2, 9d, c6, a4, af, a8, dc, c0, 9f, 8e, 91, 18, a4, 0d, 6d, dc, 45, ea, d0, f3, fc, 98, 37, 5d, bb, 9b, be, 1a, 85, 95, 22, 2f, c5, 55, e3, 0d, d9, a4, ab, 66, f4, cd, 23, f7, 9e, 48, 84, 18, af, e4, 40, 0a, 8a, c2, 23, 4c, 84, 87, 75, b4, 2e, 30, aa, 0b, 1c, 1c, 2e, bf, 18, 5f, d5, 3c, 37, ba, cf, ad, cc, 19, 00] +udp_resp 010740a4d8f211f8f2532f6f0945aff7263254418cc49db357abdcb0ccaf2c7c91ba246168c898214506fb39fd11e8074497da3a46783796810a13ac551ef430b7e20df62cde129bd251cc8b439981fa3ff9b7e271747d80e578025ae07d4961940aaf1094c07c24a8770a836b9f607c009b86035ddaa509fd3d129abb333dc910ffd120a6e41df043b9780deaa95f9442e03bf989f46a5ac81ef1279d6efafae6aacbe91703c9edc000266d4b5e46ef00 reject +udp_resp 014003c00000000000002c7f5f3c0ee9816a0471c41b771e04ba71ace06d48911d0c945fd28c8dea4f6630c311805c3bfa643db92e82aac00000e7ddf8aa1a4000bb339a0bf532 UdpSessionResp { status: 1, error_code: 3, message: [127, 95, 60, 14, 233, 129, 106, 4, 113, 196, 27, 119, 30, 4, 186, 113, 172, 224, 109, 72, 145, 29, 12, 148, 95, 210, 140, 141, 234, 79, 102, 48, 195, 17, 128, 92, 59, 250, 100, 61, 185, 46, 130, 170], idle_timeout_ms: 995861506586 };65;[01, 03, 2c, 7f, 5f, 3c, 0e, e9, 81, 6a, 04, 71, c4, 1b, 77, 1e, 04, ba, 71, ac, e0, 6d, 48, 91, 1d, 0c, 94, 5f, d2, 8c, 8d, ea, 4f, 66, 30, c3, 11, 80, 5c, 3b, fa, 64, 3d, b9, 2e, 82, aa, c0, 00, 00, e7, dd, f8, aa, 1a, 00] +udp_resp 00c00000000000000340ff6500a3c7b5259fb4b3e775a6f200d45dc783ea6aeff1a299d00cbe5059ffcc23b206793ad645ae27b05a6b71936a70905fd8878b7f98a3f08b956d825c0a0184590f3ba88b72c36010ca7b22b995d526fa615a3c5e9fe9d819c235bf28ef971b2221ac5eb967f26a5cca835c743460594c4588a8768bdd1bec889d73fe5a4e4532afa2e5cf61bb732834f2e69793ea16c5e3edd2c76391664c06c5277719f2770c577fab9fd6deae7fd2ddbbbf2ec351e3d7fa3c10cc9407d46540010bff2a93dc327618dd5925bbef52f8768601826002cc9de59f9c19e0b77dd88a529581c84528f6151830f3b94b3c25e55508038e1de19cba4c3d6093f3f48e3d185bcf0000a84a171c232d reject +udp_resp 00400080000003f0bc8fc000000020436b6ac000000000000000 UdpSessionResp { status: 0, error_code: 0, message: [240, 188, 143], idle_timeout_ms: 541289322 };26;[00, 00, 03, f0, bc, 8f, a0, 43, 6b, 6a, 00] +udp_resp 0180000005800000046d0f1268e49966d9ff2fa34b4000 reject +udp_resp 010340fe58681cd7347f3899bf2ac3deb08ef6159a8e91c50bfc20050926d0469ad12aeb3d1d314979e2bac4ed76a94318539c83a45f68ceb207497abb2e6da0e9fc648bf074523a86037fb01b996286840b6e9ab728724186caaf1075cbfca3319ac5a6a4d0ac0b135509108362e5d9e749deee730ef185abde5c957e77a3a0d93ccc65f530e5a51792bcb22b6ebca281832075059903f65a8cb9bcfa551505053e12755ea22fbaac33f73dece7e341bda5c929f507c5a33ded7fa2481e3bd541783b4b1e29566ad120e78f08c95a26a8e7e903936db52b9ed87639b588b0ce368c1fe04565fc5ce0269991776586282c3664a2c38577e7a1145b46ae4c9dfecae3c00000076c851b310bbcc473ff5cdce39c554d05 UdpSessionResp { status: 1, error_code: 3, message: [88, 104, 28, 215, 52, 127, 56, 153, 191, 42, 195, 222, 176, 142, 246, 21, 154, 142, 145, 197, 11, 252, 32, 5, 9, 38, 208, 70, 154, 209, 42, 235, 61, 29, 49, 73, 121, 226, 186, 196, 237, 118, 169, 67, 24, 83, 156, 131, 164, 95, 104, 206, 178, 7, 73, 122, 187, 46, 109, 160, 233, 252, 100, 139, 240, 116, 82, 58, 134, 3, 127, 176, 27, 153, 98, 134, 132, 11, 110, 154, 183, 40, 114, 65, 134, 202, 175, 16, 117, 203, 252, 163, 49, 154, 197, 166, 164, 208, 172, 11, 19, 85, 9, 16, 131, 98, 229, 217, 231, 73, 222, 238, 115, 14, 241, 133, 171, 222, 92, 149, 126, 119, 163, 160, 217, 60, 204, 101, 245, 48, 229, 165, 23, 146, 188, 178, 43, 110, 188, 162, 129, 131, 32, 117, 5, 153, 3, 246, 90, 140, 185, 188, 250, 85, 21, 5, 5, 62, 18, 117, 94, 162, 47, 186, 172, 51, 247, 61, 236, 231, 227, 65, 189, 165, 201, 41, 245, 7, 197, 163, 61, 237, 127, 162, 72, 30, 59, 213, 65, 120, 59, 75, 30, 41, 86, 106, 209, 32, 231, 143, 8, 201, 90, 38, 168, 231, 233, 3, 147, 109, 181, 43, 158, 216, 118, 57, 181, 136, 176, 206, 54, 140, 31, 224, 69, 101, 252, 92, 224, 38, 153, 145, 119, 101, 134, 40, 44, 54, 100, 162, 195, 133, 119, 231, 161, 20, 91, 70, 174, 76, 157, 254, 202, 227], idle_timeout_ms: 31885433649 };278;[01, 03, 40, fe, 58, 68, 1c, d7, 34, 7f, 38, 99, bf, 2a, c3, de, b0, 8e, f6, 15, 9a, 8e, 91, c5, 0b, fc, 20, 05, 09, 26, d0, 46, 9a, d1, 2a, eb, 3d, 1d, 31, 49, 79, e2, ba, c4, ed, 76, a9, 43, 18, 53, 9c, 83, a4, 5f, 68, ce, b2, 07, 49, 7a, bb, 2e, 6d, a0, e9, fc, 64, 8b, f0, 74, 52, 3a, 86, 03, 7f, b0, 1b, 99, 62, 86, 84, 0b, 6e, 9a, b7, 28, 72, 41, 86, ca, af, 10, 75, cb, fc, a3, 31, 9a, c5, a6, a4, d0, ac, 0b, 13, 55, 09, 10, 83, 62, e5, d9, e7, 49, de, ee, 73, 0e, f1, 85, ab, de, 5c, 95, 7e, 77, a3, a0, d9, 3c, cc, 65, f5, 30, e5, a5, 17, 92, bc, b2, 2b, 6e, bc, a2, 81, 83, 20, 75, 05, 99, 03, f6, 5a, 8c, b9, bc, fa, 55, 15, 05, 05, 3e, 12, 75, 5e, a2, 2f, ba, ac, 33, f7, 3d, ec, e7, e3, 41, bd, a5, c9, 29, f5, 07, c5, a3, 3d, ed, 7f, a2, 48, 1e, 3b, d5, 41, 78, 3b, 4b, 1e, 29, 56, 6a, d1, 20, e7, 8f, 08, c9, 5a, 26, a8, e7, e9, 03, 93, 6d, b5, 2b, 9e, d8, 76, 39, b5, 88, b0, ce, 36, 8c, 1f, e0, 45, 65, fc, 5c, e0, 26, 99, 91, 77, 65, 86, 28, 2c, 36, 64, a2, c3, 85, 77, e7, a1, 14, 5b, 46, ae, 4c, 9d, fe, ca, e3, c0, 00, 00, 07, 6c, 85, 1b, 31, 00] +udp_resp 018000e2c280000101d636ffa8357493fd937d019dfa7582228a4ff9f7d7673ad0b1517612e1c23aa2a2f701830b03cacd2f7ca2d4a5a5ce20a45db87dbd86800bbd62a0d2bc82f3ecd05ce7b5884db965ba15604e0b0bfd4f2ba99d5ac73f56295c37c92390400614d491f02bb1839359632d583881e23ef5804d53b34e5a0b9112aba0a8cf1fe2effab0989254455601a4b6d8defebb275c1b616458e2016fc8fb7f1268d47a098fa68841252daceda89092e167ac37475acbf2879d83aaacce33e11c1b701ef3f96b3269ccb88e6d3d25b15c5d19683a957c8649214030957dd02f50f32039a877adcb69937d2bb32b5b5470a861f6fb926efa99d7464eed0398e7907963e2e38a91c0000a497694c295c000000000000000 reject +udp_resp 000040f3d53803388cebab2b72058387ac2ce1eb64d62e4113e9cb3225109993b59204d33b2817c0131c96a63d901dbd6e070b2fb9b1c5d39ea02f82e27cec97a072c93009243d1ce00f5798f831b2e9b3d54380226fecf80ecb72cc1fb3f2e4ed8af1b6ac9d01ff3f5472ab22d271d45727179e3c329530f75d85f85d0c51bdf3055d92f37752e47865c38d55ccbae59331b3b2f9014956645e8f6c867fba0f83e27f0ad01de68119ac13d6615f77b6e2c10b944b091fe315bde697db3a1ddc3a982af26eabe11ba980c249fdd6485c70a0d761c1a631359a5852d06e6708e7ac6dcfc00fbde58866c6ddfacb08f6544bb3a762eca851c0000013e3953b0e00 UdpSessionResp { status: 0, error_code: 0, message: [213, 56, 3, 56, 140, 235, 171, 43, 114, 5, 131, 135, 172, 44, 225, 235, 100, 214, 46, 65, 19, 233, 203, 50, 37, 16, 153, 147, 181, 146, 4, 211, 59, 40, 23, 192, 19, 28, 150, 166, 61, 144, 29, 189, 110, 7, 11, 47, 185, 177, 197, 211, 158, 160, 47, 130, 226, 124, 236, 151, 160, 114, 201, 48, 9, 36, 61, 28, 224, 15, 87, 152, 248, 49, 178, 233, 179, 213, 67, 128, 34, 111, 236, 248, 14, 203, 114, 204, 31, 179, 242, 228, 237, 138, 241, 182, 172, 157, 1, 255, 63, 84, 114, 171, 34, 210, 113, 212, 87, 39, 23, 158, 60, 50, 149, 48, 247, 93, 133, 248, 93, 12, 81, 189, 243, 5, 93, 146, 243, 119, 82, 228, 120, 101, 195, 141, 85, 204, 186, 229, 147, 49, 179, 178, 249, 1, 73, 86, 100, 94, 143, 108, 134, 127, 186, 15, 131, 226, 127, 10, 208, 29, 230, 129, 25, 172, 19, 214, 97, 95, 119, 182, 226, 193, 11, 148, 75, 9, 31, 227, 21, 189, 230, 151, 219, 58, 29, 220, 58, 152, 42, 242, 110, 171, 225, 27, 169, 128, 194, 73, 253, 214, 72, 92, 112, 160, 215, 97, 193, 166, 49, 53, 154, 88, 82, 208, 110, 103, 8, 231, 172, 109, 207, 192, 15, 189, 229, 136, 102, 198, 221, 250, 203, 8, 246, 84, 75, 179, 167, 98, 236, 168, 81], idle_timeout_ms: 85422586638 };256;[00, 00, 40, f3, d5, 38, 03, 38, 8c, eb, ab, 2b, 72, 05, 83, 87, ac, 2c, e1, eb, 64, d6, 2e, 41, 13, e9, cb, 32, 25, 10, 99, 93, b5, 92, 04, d3, 3b, 28, 17, c0, 13, 1c, 96, a6, 3d, 90, 1d, bd, 6e, 07, 0b, 2f, b9, b1, c5, d3, 9e, a0, 2f, 82, e2, 7c, ec, 97, a0, 72, c9, 30, 09, 24, 3d, 1c, e0, 0f, 57, 98, f8, 31, b2, e9, b3, d5, 43, 80, 22, 6f, ec, f8, 0e, cb, 72, cc, 1f, b3, f2, e4, ed, 8a, f1, b6, ac, 9d, 01, ff, 3f, 54, 72, ab, 22, d2, 71, d4, 57, 27, 17, 9e, 3c, 32, 95, 30, f7, 5d, 85, f8, 5d, 0c, 51, bd, f3, 05, 5d, 92, f3, 77, 52, e4, 78, 65, c3, 8d, 55, cc, ba, e5, 93, 31, b3, b2, f9, 01, 49, 56, 64, 5e, 8f, 6c, 86, 7f, ba, 0f, 83, e2, 7f, 0a, d0, 1d, e6, 81, 19, ac, 13, d6, 61, 5f, 77, b6, e2, c1, 0b, 94, 4b, 09, 1f, e3, 15, bd, e6, 97, db, 3a, 1d, dc, 3a, 98, 2a, f2, 6e, ab, e1, 1b, a9, 80, c2, 49, fd, d6, 48, 5c, 70, a0, d7, 61, c1, a6, 31, 35, 9a, 58, 52, d0, 6e, 67, 08, e7, ac, 6d, cf, c0, 0f, bd, e5, 88, 66, c6, dd, fa, cb, 08, f6, 54, 4b, b3, a7, 62, ec, a8, 51, c0, 00, 00, 13, e3, 95, 3b, 0e, 00] +udp_resp 0180000002c0000000000000736feeb0c52c82b957bd617e9f51c545ccc903ac1f9db4cc11bd3bd951e33871018694942b20b4d64b61ae408125ec58f76c7ebdd99148b0af5e54b5dcb59f8c39e44affa2c325cd31ba48b2df12021c807ccf8a4b832cb60f9223bbc5a2086a0b940ea2283ba6192985f78b2d51f4700467bcb280000000400d1afb75d2824882d382484cd944 UdpSessionResp { status: 1, error_code: 2, message: [111, 238, 176, 197, 44, 130, 185, 87, 189, 97, 126, 159, 81, 197, 69, 204, 201, 3, 172, 31, 157, 180, 204, 17, 189, 59, 217, 81, 227, 56, 113, 1, 134, 148, 148, 43, 32, 180, 214, 75, 97, 174, 64, 129, 37, 236, 88, 247, 108, 126, 189, 217, 145, 72, 176, 175, 94, 84, 181, 220, 181, 159, 140, 57, 228, 74, 255, 162, 195, 37, 205, 49, 186, 72, 178, 223, 18, 2, 28, 128, 124, 207, 138, 75, 131, 44, 182, 15, 146, 35, 187, 197, 162, 8, 106, 11, 148, 14, 162, 40, 59, 166, 25, 41, 133, 247, 139, 45, 81, 244, 112, 4, 103, 188, 178], idle_timeout_ms: 0 };147;[01, 02, 40, 73, 6f, ee, b0, c5, 2c, 82, b9, 57, bd, 61, 7e, 9f, 51, c5, 45, cc, c9, 03, ac, 1f, 9d, b4, cc, 11, bd, 3b, d9, 51, e3, 38, 71, 01, 86, 94, 94, 2b, 20, b4, d6, 4b, 61, ae, 40, 81, 25, ec, 58, f7, 6c, 7e, bd, d9, 91, 48, b0, af, 5e, 54, b5, dc, b5, 9f, 8c, 39, e4, 4a, ff, a2, c3, 25, cd, 31, ba, 48, b2, df, 12, 02, 1c, 80, 7c, cf, 8a, 4b, 83, 2c, b6, 0f, 92, 23, bb, c5, a2, 08, 6a, 0b, 94, 0e, a2, 28, 3b, a6, 19, 29, 85, f7, 8b, 2d, 51, f4, 70, 04, 67, bc, b2, 00, 00] +udp_resp 01c000000000000004c000000000000080c922659f91d21995cb834d25c5184be56112a9186d5a41d1b5cfb9a6fcbe9e9c0c5d8e45d105e91d301a37fd17edd137fe65352c92a25861a868f6b46d16a184cc2cd4e979fccd5fd398128bf4749898733767e2997651478e84ac4cbdcf6952d0403c89983d5953f6444bc665a89977fc57293a9c12b5b02541a27116cb592ec000007136657d744000 UdpSessionResp { status: 1, error_code: 4, message: [201, 34, 101, 159, 145, 210, 25, 149, 203, 131, 77, 37, 197, 24, 75, 229, 97, 18, 169, 24, 109, 90, 65, 209, 181, 207, 185, 166, 252, 190, 158, 156, 12, 93, 142, 69, 209, 5, 233, 29, 48, 26, 55, 253, 23, 237, 209, 55, 254, 101, 53, 44, 146, 162, 88, 97, 168, 104, 246, 180, 109, 22, 161, 132, 204, 44, 212, 233, 121, 252, 205, 95, 211, 152, 18, 139, 244, 116, 152, 152, 115, 55, 103, 226, 153, 118, 81, 71, 142, 132, 172, 76, 189, 207, 105, 82, 208, 64, 60, 137, 152, 61, 89, 83, 246, 68, 75, 198, 101, 168, 153, 119, 252, 87, 41, 58, 156, 18, 181, 176, 37, 65, 162, 113, 22, 203, 89, 46], idle_timeout_ms: 486243925364 };155;[01, 04, 40, 80, c9, 22, 65, 9f, 91, d2, 19, 95, cb, 83, 4d, 25, c5, 18, 4b, e5, 61, 12, a9, 18, 6d, 5a, 41, d1, b5, cf, b9, a6, fc, be, 9e, 9c, 0c, 5d, 8e, 45, d1, 05, e9, 1d, 30, 1a, 37, fd, 17, ed, d1, 37, fe, 65, 35, 2c, 92, a2, 58, 61, a8, 68, f6, b4, 6d, 16, a1, 84, cc, 2c, d4, e9, 79, fc, cd, 5f, d3, 98, 12, 8b, f4, 74, 98, 98, 73, 37, 67, e2, 99, 76, 51, 47, 8e, 84, ac, 4c, bd, cf, 69, 52, d0, 40, 3c, 89, 98, 3d, 59, 53, f6, 44, 4b, c6, 65, a8, 99, 77, fc, 57, 29, 3a, 9c, 12, b5, b0, 25, 41, a2, 71, 16, cb, 59, 2e, c0, 00, 00, 71, 36, 65, 7d, 74, 00] +udp_resp 00800000008000003e38426aecaafb888167ba18c7bc2decc563089b477c54c08d6c4202ab60b0c3ecca96dc8a39a40464874ad40cd192da81eb27aaf0969005014e0a180218a4c000000045c46b974000 UdpSessionResp { status: 0, error_code: 0, message: [56, 66, 106, 236, 170, 251, 136, 129, 103, 186, 24, 199, 188, 45, 236, 197, 99, 8, 155, 71, 124, 84, 192, 141, 108, 66, 2, 171, 96, 176, 195, 236, 202, 150, 220, 138, 57, 164, 4, 100, 135, 74, 212, 12, 209, 146, 218, 129, 235, 39, 170, 240, 150, 144, 5, 1, 78, 10, 24, 2, 24, 164], idle_timeout_ms: 1170500503 };81;[00, 00, 3e, 38, 42, 6a, ec, aa, fb, 88, 81, 67, ba, 18, c7, bc, 2d, ec, c5, 63, 08, 9b, 47, 7c, 54, c0, 8d, 6c, 42, 02, ab, 60, b0, c3, ec, ca, 96, dc, 8a, 39, a4, 04, 64, 87, 4a, d4, 0c, d1, 92, da, 81, eb, 27, aa, f0, 96, 90, 05, 01, 4e, 0a, 18, 02, 18, a4, c0, 00, 00, 00, 45, c4, 6b, 97, 00] +udp_resp 0180000004c0000000000000616817c135987a6c31d3d1679cc10965c5e856b5d4fd9c483914b13f72cdfaeae6158e02f56ae3b4d79edcd84c650e80044ca3d40d2e00b96c7ad4f2b0de06ba15ce62d81f5770d4e0707badcb9d991653103dfbb3579b6dff14f108b6c9054b5aa3cb53e191dfad3e9a00 UdpSessionResp { status: 1, error_code: 4, message: [104, 23, 193, 53, 152, 122, 108, 49, 211, 209, 103, 156, 193, 9, 101, 197, 232, 86, 181, 212, 253, 156, 72, 57, 20, 177, 63, 114, 205, 250, 234, 230, 21, 142, 2, 245, 106, 227, 180, 215, 158, 220, 216, 76, 101, 14, 128, 4, 76, 163, 212, 13, 46, 0, 185, 108, 122, 212, 242, 176, 222, 6, 186, 21, 206, 98, 216, 31, 87, 112, 212, 224, 112, 123, 173, 203, 157, 153, 22, 83, 16, 61, 251, 179, 87, 155, 109, 255, 20, 241, 8, 182, 201, 5, 75, 90, 163], idle_timeout_ms: 816243974123372186 };119;[01, 04, 40, 61, 68, 17, c1, 35, 98, 7a, 6c, 31, d3, d1, 67, 9c, c1, 09, 65, c5, e8, 56, b5, d4, fd, 9c, 48, 39, 14, b1, 3f, 72, cd, fa, ea, e6, 15, 8e, 02, f5, 6a, e3, b4, d7, 9e, dc, d8, 4c, 65, 0e, 80, 04, 4c, a3, d4, 0d, 2e, 00, b9, 6c, 7a, d4, f2, b0, de, 06, ba, 15, ce, 62, d8, 1f, 57, 70, d4, e0, 70, 7b, ad, cb, 9d, 99, 16, 53, 10, 3d, fb, b3, 57, 9b, 6d, ff, 14, f1, 08, b6, c9, 05, 4b, 5a, a3, cb, 53, e1, 91, df, ad, 3e, 9a, 00] +udp_resp 02400540af5b7407b937d4d2e69705449220ddd59b169fea012b5a9b23111efbff538f2d95a22d136d575448ea94fa155369f5dea727f3de473a4bd422669f57bd10efe9bc927a4503d8295f75384c911d3a02d4bc1c3cd77e83359ce8404946e8a2d044222c077c626612f1f099f7f6884c50d89fba3bdaa2bc12ce190cd2773c09d73c58df8eccecccafc53e7691e783ea150b6ce7ae376ce2121a80a109b381ae60a07f414c0aaeff79f7ea740d121c7d225ec000000077760f49c00000000000000074235aca60 reject +udp_resp 00800000004067947dfa3a7c3dff8bce72f328b23fe3a1d4f1d59afcb5e48f646c2e4acc2ce533f3612cdc6bd4fa20d860f2dc8e9f3eb9254bebf524352165eaaa282f073bfd66316defd9699d4563888108ce7d92ad7adfb9397134fbe88d0681cfe7172163a9e3784c7eeeaab3c00001c6c72a624d8000000026d8d2418f8f UdpSessionResp { status: 0, error_code: 0, message: [148, 125, 250, 58, 124, 61, 255, 139, 206, 114, 243, 40, 178, 63, 227, 161, 212, 241, 213, 154, 252, 181, 228, 143, 100, 108, 46, 74, 204, 44, 229, 51, 243, 97, 44, 220, 107, 212, 250, 32, 216, 96, 242, 220, 142, 159, 62, 185, 37, 75, 235, 245, 36, 53, 33, 101, 234, 170, 40, 47, 7, 59, 253, 102, 49, 109, 239, 217, 105, 157, 69, 99, 136, 129, 8, 206, 125, 146, 173, 122, 223, 185, 57, 113, 52, 251, 232, 141, 6, 129, 207, 231, 23, 33, 99, 169, 227, 120, 76, 126, 238, 170, 179], idle_timeout_ms: 1953256596045 };122;[00, 00, 40, 67, 94, 7d, fa, 3a, 7c, 3d, ff, 8b, ce, 72, f3, 28, b2, 3f, e3, a1, d4, f1, d5, 9a, fc, b5, e4, 8f, 64, 6c, 2e, 4a, cc, 2c, e5, 33, f3, 61, 2c, dc, 6b, d4, fa, 20, d8, 60, f2, dc, 8e, 9f, 3e, b9, 25, 4b, eb, f5, 24, 35, 21, 65, ea, aa, 28, 2f, 07, 3b, fd, 66, 31, 6d, ef, d9, 69, 9d, 45, 63, 88, 81, 08, ce, 7d, 92, ad, 7a, df, b9, 39, 71, 34, fb, e8, 8d, 06, 81, cf, e7, 17, 21, 63, a9, e3, 78, 4c, 7e, ee, aa, b3, c0, 00, 01, c6, c7, 2a, 62, 4d, 00] +udp_resp 0180000003c00000000000002132fc5e5aeab16e3e3c4fcd1b1c72688abeeb713b0c3d62e1506775d05f0a0a8dbcc000000c064c3a514000 UdpSessionResp { status: 1, error_code: 3, message: [50, 252, 94, 90, 234, 177, 110, 62, 60, 79, 205, 27, 28, 114, 104, 138, 190, 235, 113, 59, 12, 61, 98, 225, 80, 103, 117, 208, 95, 10, 10, 141, 188], idle_timeout_ms: 51645266513 };56;[01, 03, 21, 32, fc, 5e, 5a, ea, b1, 6e, 3e, 3c, 4f, cd, 1b, 1c, 72, 68, 8a, be, eb, 71, 3b, 0c, 3d, 62, e1, 50, 67, 75, d0, 5f, 0a, 0a, 8d, bc, c0, 00, 00, 0c, 06, 4c, 3a, 51, 00] +udp_resp 00400040a37d579f0e9234748c21edf367a25fab0ecb4dd631189044f735f5d156cd3d056742e8159430ea3def4cf444e938acb10ac429315ea15c385cdcbc729838315b0d0c6a39110afb7e7f0c51510740ecb156bd6636d483c4a135e863c42e3315db15485823fba1f79d7b60483b4d19a7079920e8b01715a161fde8d1b654534b2db45d1b1a25724bfe6b2e45d6104e41764e2cdb22337455b9b7051d3cc166204ac109542ccd365710d494f8f8c00000000000000cb342b1f0628508ddacb7e243 UdpSessionResp { status: 0, error_code: 0, message: [125, 87, 159, 14, 146, 52, 116, 140, 33, 237, 243, 103, 162, 95, 171, 14, 203, 77, 214, 49, 24, 144, 68, 247, 53, 245, 209, 86, 205, 61, 5, 103, 66, 232, 21, 148, 48, 234, 61, 239, 76, 244, 68, 233, 56, 172, 177, 10, 196, 41, 49, 94, 161, 92, 56, 92, 220, 188, 114, 152, 56, 49, 91, 13, 12, 106, 57, 17, 10, 251, 126, 127, 12, 81, 81, 7, 64, 236, 177, 86, 189, 102, 54, 212, 131, 196, 161, 53, 232, 99, 196, 46, 51, 21, 219, 21, 72, 88, 35, 251, 161, 247, 157, 123, 96, 72, 59, 77, 25, 167, 7, 153, 32, 232, 176, 23, 21, 161, 97, 253, 232, 209, 182, 84, 83, 75, 45, 180, 93, 27, 26, 37, 114, 75, 254, 107, 46, 69, 214, 16, 78, 65, 118, 78, 44, 219, 34, 51, 116, 85, 185, 183, 5, 29, 60, 193, 102, 32, 74, 193, 9, 84, 44], idle_timeout_ms: 952044101033064696 };196;[00, 00, 40, a3, 7d, 57, 9f, 0e, 92, 34, 74, 8c, 21, ed, f3, 67, a2, 5f, ab, 0e, cb, 4d, d6, 31, 18, 90, 44, f7, 35, f5, d1, 56, cd, 3d, 05, 67, 42, e8, 15, 94, 30, ea, 3d, ef, 4c, f4, 44, e9, 38, ac, b1, 0a, c4, 29, 31, 5e, a1, 5c, 38, 5c, dc, bc, 72, 98, 38, 31, 5b, 0d, 0c, 6a, 39, 11, 0a, fb, 7e, 7f, 0c, 51, 51, 07, 40, ec, b1, 56, bd, 66, 36, d4, 83, c4, a1, 35, e8, 63, c4, 2e, 33, 15, db, 15, 48, 58, 23, fb, a1, f7, 9d, 7b, 60, 48, 3b, 4d, 19, a7, 07, 99, 20, e8, b0, 17, 15, a1, 61, fd, e8, d1, b6, 54, 53, 4b, 2d, b4, 5d, 1b, 1a, 25, 72, 4b, fe, 6b, 2e, 45, d6, 10, 4e, 41, 76, 4e, 2c, db, 22, 33, 74, 55, b9, b7, 05, 1d, 3c, c1, 66, 20, 4a, c1, 09, 54, 2c, cd, 36, 57, 10, d4, 94, f8, f8, 00] +udp_resp 03400280000008c89f490fa4449a76622b0b511fe283f7f89962b1bf43 reject +udp_resp 0080000000400f2b7f77188ec0e6d7b09da8cf95f392c009b4cb38e53aa74000536fb74937a9c1 UdpSessionResp { status: 0, error_code: 0, message: [43, 127, 119, 24, 142, 192, 230, 215, 176, 157, 168, 207, 149, 243, 146], idle_timeout_ms: 2732059716303527 };32;[00, 00, 0f, 2b, 7f, 77, 18, 8e, c0, e6, d7, b0, 9d, a8, cf, 95, f3, 92, c0, 09, b4, cb, 38, e5, 3a, a7, 00] +udp_resp 0080000007800001001bf660b2d1b17fc6ed4e463bcb594ee2fa62a00789ff3dd47067c79ced8d696da08e5e0b1eb8347de15b17d5d789608e8da0f18a73675b96b1a31699f054ad2e0c34fdc11e967ebaf626346e645eb330438647d9ff5b5821159a1c10ab38af39d1d63dc1272a62000683ec40d84aad357106db24012b5932e2491532f4b516e7349664facfa0ef62fc85825971100499e7dd0669f673e12be8aa1748bb6e76db79c7980e0513e2f61f38697570af138d3801c72848426f381ec527a84631625492f25f087cb8d5628e843382aa225c94978540938fa8fe3da53c097bd185d25fdc57e3b973abe24e526b2e23003f1a0f4b11e449c576ad9acd1dc2f9931fedb3c000453cbf903e2c4000 reject +udp_hdr 000000010001000001 UdpMsgHdr { session_id: 1, packet_id: 1, flags: 0, frag_id: 0, frag_count: 1 };[00, 00, 00, 01, 00, 01, 00, 00, 01] +udp_hdr reject +udp_hdr a0 reject +udp_hdr a0dd reject +udp_hdr a0ddad reject +udp_hdr a0ddad6d reject +udp_hdr a0ddad6dc6 reject +udp_hdr a0ddad6dc6fb reject +udp_hdr a0ddad6dc6fb42 reject +udp_hdr a0ddad6dc6fb4244 reject +udp_hdr a0ddad6dc6fb424429 UdpMsgHdr { session_id: 2698882413, packet_id: 50939, flags: 66, frag_id: 68, frag_count: 41 };[a0, dd, ad, 6d, c6, fb, 42, 44, 29] +udp_hdr a0ddad6dc6fb42442990 UdpMsgHdr { session_id: 2698882413, packet_id: 50939, flags: 66, frag_id: 68, frag_count: 41 };[a0, dd, ad, 6d, c6, fb, 42, 44, 29] +udp_hdr e52beb09bf5aec1bab UdpMsgHdr { session_id: 3844860681, packet_id: 48986, flags: 236, frag_id: 27, frag_count: 171 };[e5, 2b, eb, 09, bf, 5a, ec, 1b, ab] +udp_hdr f0ded1ded358781f10ab16 UdpMsgHdr { session_id: 4041134558, packet_id: 54104, flags: 120, frag_id: 31, frag_count: 16 };[f0, de, d1, de, d3, 58, 78, 1f, 10] +udp_hdr 967565d213fa336593241fe4 UdpMsgHdr { session_id: 2524276178, packet_id: 5114, flags: 51, frag_id: 101, frag_count: 147 };[96, 75, 65, d2, 13, fa, 33, 65, 93] +udp_hdr 12714ca3d784a45f11 UdpMsgHdr { session_id: 309415075, packet_id: 55172, flags: 164, frag_id: 95, frag_count: 17 };[12, 71, 4c, a3, d7, 84, a4, 5f, 11] +udp_hdr 2f88407a3038d8bfd62c8f UdpMsgHdr { session_id: 797458554, packet_id: 12344, flags: 216, frag_id: 191, frag_count: 214 };[2f, 88, 40, 7a, 30, 38, d8, bf, d6] +udp_hdr f78e7f5ef836f14139b734e3 UdpMsgHdr { session_id: 4153311070, packet_id: 63542, flags: 241, frag_id: 65, frag_count: 57 };[f7, 8e, 7f, 5e, f8, 36, f1, 41, 39] +udp_hdr 5bbdabe6e1d6c4364e UdpMsgHdr { session_id: 1539156966, packet_id: 57814, flags: 196, frag_id: 54, frag_count: 78 };[5b, bd, ab, e6, e1, d6, c4, 36, 4e] +udp_hdr 1c20c3ba6d5dd1d067 UdpMsgHdr { session_id: 471909306, packet_id: 27997, flags: 209, frag_id: 208, frag_count: 103 };[1c, 20, c3, ba, 6d, 5d, d1, d0, 67] +udp_hdr 3a38140ed0d355c098067b91f46b UdpMsgHdr { session_id: 976753678, packet_id: 53459, flags: 85, frag_id: 192, frag_count: 152 };[3a, 38, 14, 0e, d0, d3, 55, c0, 98] +udp_hdr ef1d26e9b88845f2d5e5b58b593474 UdpMsgHdr { session_id: 4011665129, packet_id: 47240, flags: 69, frag_id: 242, frag_count: 213 };[ef, 1d, 26, e9, b8, 88, 45, f2, d5] +udp_hdr 4be111a796825d716615c8367e8a79 UdpMsgHdr { session_id: 1273041319, packet_id: 38530, flags: 93, frag_id: 113, frag_count: 102 };[4b, e1, 11, a7, 96, 82, 5d, 71, 66] +udp_hdr e72262bacdd148ac5f UdpMsgHdr { session_id: 3877790394, packet_id: 52689, flags: 72, frag_id: 172, frag_count: 95 };[e7, 22, 62, ba, cd, d1, 48, ac, 5f] +udp_hdr 3f977089967f6999321e520554a394 UdpMsgHdr { session_id: 1066889353, packet_id: 38527, flags: 105, frag_id: 153, frag_count: 50 };[3f, 97, 70, 89, 96, 7f, 69, 99, 32] +udp_hdr 4d3a0c64e44add86884c37e332 UdpMsgHdr { session_id: 1295649892, packet_id: 58442, flags: 221, frag_id: 134, frag_count: 136 };[4d, 3a, 0c, 64, e4, 4a, dd, 86, 88] +udp_hdr fbe39cca7b53a74dd917717e UdpMsgHdr { session_id: 4225998026, packet_id: 31571, flags: 167, frag_id: 77, frag_count: 217 };[fb, e3, 9c, ca, 7b, 53, a7, 4d, d9] +udp_hdr 2c657ad310ac02ec3e4bd69600c8 UdpMsgHdr { session_id: 744848083, packet_id: 4268, flags: 2, frag_id: 236, frag_count: 62 };[2c, 65, 7a, d3, 10, ac, 02, ec, 3e] +udp_hdr 12f9015c5e5f1d48682d UdpMsgHdr { session_id: 318308700, packet_id: 24159, flags: 29, frag_id: 72, frag_count: 104 };[12, f9, 01, 5c, 5e, 5f, 1d, 48, 68] +udp_hdr a4c9d4896d14dcc8be6fcb3cae2a8ed4 UdpMsgHdr { session_id: 2764690569, packet_id: 27924, flags: 220, frag_id: 200, frag_count: 190 };[a4, c9, d4, 89, 6d, 14, dc, c8, be] +udp_hdr 06905a00c79116c42f6e UdpMsgHdr { session_id: 110123520, packet_id: 51089, flags: 22, frag_id: 196, frag_count: 47 };[06, 90, 5a, 00, c7, 91, 16, c4, 2f] +udp_hdr 3abeb588cd8433e48b916d29a0 UdpMsgHdr { session_id: 985576840, packet_id: 52612, flags: 51, frag_id: 228, frag_count: 139 };[3a, be, b5, 88, cd, 84, 33, e4, 8b] +udp_hdr f1f56dfaae34040bfe UdpMsgHdr { session_id: 4059393530, packet_id: 44596, flags: 4, frag_id: 11, frag_count: 254 };[f1, f5, 6d, fa, ae, 34, 04, 0b, fe] +udp_hdr 0c54373755fff8070a31ca4e98 UdpMsgHdr { session_id: 206845751, packet_id: 22015, flags: 248, frag_id: 7, frag_count: 10 };[0c, 54, 37, 37, 55, ff, f8, 07, 0a] +udp_hdr 392278b7de69abdbccd6ed89165e UdpMsgHdr { session_id: 958560439, packet_id: 56937, flags: 171, frag_id: 219, frag_count: 204 };[39, 22, 78, b7, de, 69, ab, db, cc] +udp_hdr cbdf27f34cdb610cfbb98a6eb9 UdpMsgHdr { session_id: 3420399603, packet_id: 19675, flags: 97, frag_id: 12, frag_count: 251 };[cb, df, 27, f3, 4c, db, 61, 0c, fb] +udp_hdr a9f34e79c941b28dceab UdpMsgHdr { session_id: 2851294841, packet_id: 51521, flags: 178, frag_id: 141, frag_count: 206 };[a9, f3, 4e, 79, c9, 41, b2, 8d, ce] +udp_hdr 0b061154399cb6cb2296 UdpMsgHdr { session_id: 184947028, packet_id: 14748, flags: 182, frag_id: 203, frag_count: 34 };[0b, 06, 11, 54, 39, 9c, b6, cb, 22] +udp_hdr 06ecdc059f8ab72d06629b4d6881b73d UdpMsgHdr { session_id: 116186117, packet_id: 40842, flags: 183, frag_id: 45, frag_count: 6 };[06, ec, dc, 05, 9f, 8a, b7, 2d, 06] +udp_hdr 0f46922fe7829f40b4905e0004 UdpMsgHdr { session_id: 256283183, packet_id: 59266, flags: 159, frag_id: 64, frag_count: 180 };[0f, 46, 92, 2f, e7, 82, 9f, 40, b4] +udp_hdr b9d4cfa52935adb2d90b422e69297d UdpMsgHdr { session_id: 3117731749, packet_id: 10549, flags: 173, frag_id: 178, frag_count: 217 };[b9, d4, cf, a5, 29, 35, ad, b2, d9] +udp_hdr 32b12d4bee0c56715866 UdpMsgHdr { session_id: 850472267, packet_id: 60940, flags: 86, frag_id: 113, frag_count: 88 };[32, b1, 2d, 4b, ee, 0c, 56, 71, 58] +udp_hdr ce9817977065d567234e29dc1c0b UdpMsgHdr { session_id: 3466074007, packet_id: 28773, flags: 213, frag_id: 103, frag_count: 35 };[ce, 98, 17, 97, 70, 65, d5, 67, 23] +udp_hdr 3bf895cd7c66ace40ecab63c2d2a9be3 UdpMsgHdr { session_id: 1006147021, packet_id: 31846, flags: 172, frag_id: 228, frag_count: 14 };[3b, f8, 95, cd, 7c, 66, ac, e4, 0e] +udp_hdr 7e51cd29f1ed51a93f UdpMsgHdr { session_id: 2119290153, packet_id: 61933, flags: 81, frag_id: 169, frag_count: 63 };[7e, 51, cd, 29, f1, ed, 51, a9, 3f] +udp_hdr a5c892b3dccdef9372f6478d38d1 UdpMsgHdr { session_id: 2781385395, packet_id: 56525, flags: 239, frag_id: 147, frag_count: 114 };[a5, c8, 92, b3, dc, cd, ef, 93, 72] +udp_hdr 0428cddc2a44f8de699a9723e60222 UdpMsgHdr { session_id: 69783004, packet_id: 10820, flags: 248, frag_id: 222, frag_count: 105 };[04, 28, cd, dc, 2a, 44, f8, de, 69] +udp_hdr 2997de191c1d712fffa0700dd5443f UdpMsgHdr { session_id: 697818649, packet_id: 7197, flags: 113, frag_id: 47, frag_count: 255 };[29, 97, de, 19, 1c, 1d, 71, 2f, ff] +udp_hdr 9102b81179b3a2f93ca80e UdpMsgHdr { session_id: 2432874513, packet_id: 31155, flags: 162, frag_id: 249, frag_count: 60 };[91, 02, b8, 11, 79, b3, a2, f9, 3c] +udp_hdr f35bce6aa609439c89860b017393 UdpMsgHdr { session_id: 4082880106, packet_id: 42505, flags: 67, frag_id: 156, frag_count: 137 };[f3, 5b, ce, 6a, a6, 09, 43, 9c, 89] +udp_hdr 59d0429831aedbee71abb4ded9744f UdpMsgHdr { session_id: 1506820760, packet_id: 12718, flags: 219, frag_id: 238, frag_count: 113 };[59, d0, 42, 98, 31, ae, db, ee, 71] +udp_hdr 778f2c5e18c83a516e0f8c52a132 UdpMsgHdr { session_id: 2005871710, packet_id: 6344, flags: 58, frag_id: 81, frag_count: 110 };[77, 8f, 2c, 5e, 18, c8, 3a, 51, 6e] +udp_hdr d3df6225608e089cb79822ae3c UdpMsgHdr { session_id: 3554632229, packet_id: 24718, flags: 8, frag_id: 156, frag_count: 183 };[d3, df, 62, 25, 60, 8e, 08, 9c, b7] +udp_hdr 964fc7fb440fc12d9199d03b0f93ee UdpMsgHdr { session_id: 2521810939, packet_id: 17423, flags: 193, frag_id: 45, frag_count: 145 };[96, 4f, c7, fb, 44, 0f, c1, 2d, 91] +udp_hdr afbbbd6354fc94f152f44291b4754d69 UdpMsgHdr { session_id: 2948316515, packet_id: 21756, flags: 148, frag_id: 241, frag_count: 82 };[af, bb, bd, 63, 54, fc, 94, f1, 52] +udp_hdr f0a709f23f7a075e2826 UdpMsgHdr { session_id: 4037478898, packet_id: 16250, flags: 7, frag_id: 94, frag_count: 40 };[f0, a7, 09, f2, 3f, 7a, 07, 5e, 28] +udp_hdr 3e113c9a06f49871ddb0 UdpMsgHdr { session_id: 1041317018, packet_id: 1780, flags: 152, frag_id: 113, frag_count: 221 };[3e, 11, 3c, 9a, 06, f4, 98, 71, dd] +udp_hdr ab2ad13be057c703b111 UdpMsgHdr { session_id: 2871710011, packet_id: 57431, flags: 199, frag_id: 3, frag_count: 177 };[ab, 2a, d1, 3b, e0, 57, c7, 03, b1] +udp_hdr 5e8fe76d619f2dfb72 UdpMsgHdr { session_id: 1586489197, packet_id: 24991, flags: 45, frag_id: 251, frag_count: 114 };[5e, 8f, e7, 6d, 61, 9f, 2d, fb, 72] +udp_hdr 06ad202f604a64b4338f UdpMsgHdr { session_id: 112009263, packet_id: 24650, flags: 100, frag_id: 180, frag_count: 51 };[06, ad, 20, 2f, 60, 4a, 64, b4, 33] +udp_hdr 9eb76fd4f39162284f UdpMsgHdr { session_id: 2662821844, packet_id: 62353, flags: 98, frag_id: 40, frag_count: 79 };[9e, b7, 6f, d4, f3, 91, 62, 28, 4f] +udp_hdr 6cd28e0daa0fc04b7e0f07 UdpMsgHdr { session_id: 1825738253, packet_id: 43535, flags: 192, frag_id: 75, frag_count: 126 };[6c, d2, 8e, 0d, aa, 0f, c0, 4b, 7e] +udp_hdr 75c4491f0876db23e7 UdpMsgHdr { session_id: 1975798047, packet_id: 2166, flags: 219, frag_id: 35, frag_count: 231 };[75, c4, 49, 1f, 08, 76, db, 23, e7] +udp_hdr 35cf8c5a485121a92b78 UdpMsgHdr { session_id: 902794330, packet_id: 18513, flags: 33, frag_id: 169, frag_count: 43 };[35, cf, 8c, 5a, 48, 51, 21, a9, 2b] +udp_hdr 48b16bc348be5ebdf687fa97 UdpMsgHdr { session_id: 1219587011, packet_id: 18622, flags: 94, frag_id: 189, frag_count: 246 };[48, b1, 6b, c3, 48, be, 5e, bd, f6] +udp_hdr 7df349eebf00d7dbf32b7b4a81 UdpMsgHdr { session_id: 2113096174, packet_id: 48896, flags: 215, frag_id: 219, frag_count: 243 };[7d, f3, 49, ee, bf, 00, d7, db, f3] +udp_hdr c8e308239c4836e06e6f380ce1 UdpMsgHdr { session_id: 3370321955, packet_id: 40008, flags: 54, frag_id: 224, frag_count: 110 };[c8, e3, 08, 23, 9c, 48, 36, e0, 6e] +udp_hdr ac10416355ea789123 UdpMsgHdr { session_id: 2886746467, packet_id: 21994, flags: 120, frag_id: 145, frag_count: 35 };[ac, 10, 41, 63, 55, ea, 78, 91, 23] +udp_hdr 0736259222b981ebb7f3cd4831e326 UdpMsgHdr { session_id: 120989074, packet_id: 8889, flags: 129, frag_id: 235, frag_count: 183 };[07, 36, 25, 92, 22, b9, 81, eb, b7] +udp_hdr 4a213ea9200cb0349e10298d UdpMsgHdr { session_id: 1243692713, packet_id: 8204, flags: 176, frag_id: 52, frag_count: 158 };[4a, 21, 3e, a9, 20, 0c, b0, 34, 9e] +udp_hdr c30f1c7d2b7d48993335dbf42f5f6719 UdpMsgHdr { session_id: 3272547453, packet_id: 11133, flags: 72, frag_id: 153, frag_count: 51 };[c3, 0f, 1c, 7d, 2b, 7d, 48, 99, 33] +udp_hdr 826c8b20336523f338 UdpMsgHdr { session_id: 2188151584, packet_id: 13157, flags: 35, frag_id: 243, frag_count: 56 };[82, 6c, 8b, 20, 33, 65, 23, f3, 38] +udp_hdr 39ffa35fc5b8e912eea23e9e22 UdpMsgHdr { session_id: 973054815, packet_id: 50616, flags: 233, frag_id: 18, frag_count: 238 };[39, ff, a3, 5f, c5, b8, e9, 12, ee] +udp_hdr c404deb1a55b145b518b6690a1 UdpMsgHdr { session_id: 3288653489, packet_id: 42331, flags: 20, frag_id: 91, frag_count: 81 };[c4, 04, de, b1, a5, 5b, 14, 5b, 51] +udp_hdr 07239e1ae2aa02ac8bb8 UdpMsgHdr { session_id: 119774746, packet_id: 58026, flags: 2, frag_id: 172, frag_count: 139 };[07, 23, 9e, 1a, e2, aa, 02, ac, 8b] +udp_hdr e4bc10bdd6c8c782ee40c2db22e04d UdpMsgHdr { session_id: 3837530301, packet_id: 54984, flags: 199, frag_id: 130, frag_count: 238 };[e4, bc, 10, bd, d6, c8, c7, 82, ee] +udp_hdr 0a29845530607429de147262c0d0 UdpMsgHdr { session_id: 170493013, packet_id: 12384, flags: 116, frag_id: 41, frag_count: 222 };[0a, 29, 84, 55, 30, 60, 74, 29, de] +udp_hdr cafdd275a3086e36c82209de10 UdpMsgHdr { session_id: 3405632117, packet_id: 41736, flags: 110, frag_id: 54, frag_count: 200 };[ca, fd, d2, 75, a3, 08, 6e, 36, c8] +udp_hdr 61b2521a8db3d8856f8f26 UdpMsgHdr { session_id: 1639076378, packet_id: 36275, flags: 216, frag_id: 133, frag_count: 111 };[61, b2, 52, 1a, 8d, b3, d8, 85, 6f] +udp_hdr cce9a378180da007ed466904 UdpMsgHdr { session_id: 3437863800, packet_id: 6157, flags: 160, frag_id: 7, frag_count: 237 };[cc, e9, a3, 78, 18, 0d, a0, 07, ed] +udp_hdr f9dbfdac36a6fb516443f6f8ed3df692 UdpMsgHdr { session_id: 4191944108, packet_id: 13990, flags: 251, frag_id: 81, frag_count: 100 };[f9, db, fd, ac, 36, a6, fb, 51, 64] +udp_hdr 909386f0d69d94ff84807fdf7f175f UdpMsgHdr { session_id: 2425587440, packet_id: 54941, flags: 148, frag_id: 255, frag_count: 132 };[90, 93, 86, f0, d6, 9d, 94, ff, 84] +udp_hdr c04c2b48c4391545f302f0 UdpMsgHdr { session_id: 3226217288, packet_id: 50233, flags: 21, frag_id: 69, frag_count: 243 };[c0, 4c, 2b, 48, c4, 39, 15, 45, f3] +udp_hdr cfd1107e3320759d09fdb3 UdpMsgHdr { session_id: 3486584958, packet_id: 13088, flags: 117, frag_id: 157, frag_count: 9 };[cf, d1, 10, 7e, 33, 20, 75, 9d, 09] +udp_hdr 1b6f706af9a059f36b595a7394 UdpMsgHdr { session_id: 460288106, packet_id: 63904, flags: 89, frag_id: 243, frag_count: 107 };[1b, 6f, 70, 6a, f9, a0, 59, f3, 6b] +udp_hdr 77f0355f628d447143c8a4d62e UdpMsgHdr { session_id: 2012231007, packet_id: 25229, flags: 68, frag_id: 113, frag_count: 67 };[77, f0, 35, 5f, 62, 8d, 44, 71, 43] +udp_hdr 277d6a2638382f4cf8091d UdpMsgHdr { session_id: 662530598, packet_id: 14392, flags: 47, frag_id: 76, frag_count: 248 };[27, 7d, 6a, 26, 38, 38, 2f, 4c, f8] +udp_hdr 48e79b017248944b898641 UdpMsgHdr { session_id: 1223138049, packet_id: 29256, flags: 148, frag_id: 75, frag_count: 137 };[48, e7, 9b, 01, 72, 48, 94, 4b, 89] +udp_hdr dbf685fcbe49684a8eba6596a100ae82 UdpMsgHdr { session_id: 3690366460, packet_id: 48713, flags: 104, frag_id: 74, frag_count: 142 };[db, f6, 85, fc, be, 49, 68, 4a, 8e] +udp_hdr 974ee2ccd7a3a4bc853fa8 UdpMsgHdr { session_id: 2538529484, packet_id: 55203, flags: 164, frag_id: 188, frag_count: 133 };[97, 4e, e2, cc, d7, a3, a4, bc, 85] +udp_hdr 403bf5d3646a569ca2 UdpMsgHdr { session_id: 1077671379, packet_id: 25706, flags: 86, frag_id: 156, frag_count: 162 };[40, 3b, f5, d3, 64, 6a, 56, 9c, a2] +udp_hdr dab971748c9fe003c77c6d31 UdpMsgHdr { session_id: 3669586292, packet_id: 35999, flags: 224, frag_id: 3, frag_count: 199 };[da, b9, 71, 74, 8c, 9f, e0, 03, c7] +udp_hdr a1386799e942737721a21f11fb7dc353 UdpMsgHdr { session_id: 2704828313, packet_id: 59714, flags: 115, frag_id: 119, frag_count: 33 };[a1, 38, 67, 99, e9, 42, 73, 77, 21] +udp_hdr ea049b46022c1b35e94d3e4df3e7bd7b UdpMsgHdr { session_id: 3926170438, packet_id: 556, flags: 27, frag_id: 53, frag_count: 233 };[ea, 04, 9b, 46, 02, 2c, 1b, 35, e9] +udp_hdr c0478621e9b68d05f26056b7dcba0d10 UdpMsgHdr { session_id: 3225912865, packet_id: 59830, flags: 141, frag_id: 5, frag_count: 242 };[c0, 47, 86, 21, e9, b6, 8d, 05, f2] +udp_hdr 36f177853ea5ecf64e1d1f3b0ebd UdpMsgHdr { session_id: 921794437, packet_id: 16037, flags: 236, frag_id: 246, frag_count: 78 };[36, f1, 77, 85, 3e, a5, ec, f6, 4e] +udp_hdr 35069376735d0a7020a1e7e6b7aeec UdpMsgHdr { session_id: 889623414, packet_id: 29533, flags: 10, frag_id: 112, frag_count: 32 };[35, 06, 93, 76, 73, 5d, 0a, 70, 20] +udp_hdr 6e786d545a46e23b2916bc104f UdpMsgHdr { session_id: 1853386068, packet_id: 23110, flags: 226, frag_id: 59, frag_count: 41 };[6e, 78, 6d, 54, 5a, 46, e2, 3b, 29] +udp_hdr c25262177da56b178762e599 UdpMsgHdr { session_id: 3260178967, packet_id: 32165, flags: 107, frag_id: 23, frag_count: 135 };[c2, 52, 62, 17, 7d, a5, 6b, 17, 87] +udp_hdr 2c606b256d4133f364f9 UdpMsgHdr { session_id: 744516389, packet_id: 27969, flags: 51, frag_id: 243, frag_count: 100 };[2c, 60, 6b, 25, 6d, 41, 33, f3, 64] +udp_hdr 373df78a9b4a3116d3a2d7948fdfb8 UdpMsgHdr { session_id: 926807946, packet_id: 39754, flags: 49, frag_id: 22, frag_count: 211 };[37, 3d, f7, 8a, 9b, 4a, 31, 16, d3] +udp_hdr 7b7241574f136235d96ee0961c25b4 UdpMsgHdr { session_id: 2071085399, packet_id: 20243, flags: 98, frag_id: 53, frag_count: 217 };[7b, 72, 41, 57, 4f, 13, 62, 35, d9] +udp_hdr a6315fea3009dea32eddcf UdpMsgHdr { session_id: 2788253674, packet_id: 12297, flags: 222, frag_id: 163, frag_count: 46 };[a6, 31, 5f, ea, 30, 09, de, a3, 2e] +udp_hdr 7a30f014861e9822713d0d4340 UdpMsgHdr { session_id: 2050027540, packet_id: 34334, flags: 152, frag_id: 34, frag_count: 113 };[7a, 30, f0, 14, 86, 1e, 98, 22, 71] +udp_hdr dee1caa46a2c39d4b269ead41d95 UdpMsgHdr { session_id: 3739339428, packet_id: 27180, flags: 57, frag_id: 212, frag_count: 178 };[de, e1, ca, a4, 6a, 2c, 39, d4, b2] +udp_hdr b8004440b9cf813852dfe4 UdpMsgHdr { session_id: 3087025216, packet_id: 47567, flags: 129, frag_id: 56, frag_count: 82 };[b8, 00, 44, 40, b9, cf, 81, 38, 52] +udp_hdr a944b1194ebcb6f61a0868e1 UdpMsgHdr { session_id: 2839851289, packet_id: 20156, flags: 182, frag_id: 246, frag_count: 26 };[a9, 44, b1, 19, 4e, bc, b6, f6, 1a] +udp_hdr 6dc145a506dfd0423712f2e9 UdpMsgHdr { session_id: 1841382821, packet_id: 1759, flags: 208, frag_id: 66, frag_count: 55 };[6d, c1, 45, a5, 06, df, d0, 42, 37] +udp_hdr 219b90bca8abb2b4051c3b54a0 UdpMsgHdr { session_id: 563843260, packet_id: 43179, flags: 178, frag_id: 180, frag_count: 5 };[21, 9b, 90, bc, a8, ab, b2, b4, 05] +udp_hdr 9f1cca6c0013b0e0d2829140f72cb5d4 UdpMsgHdr { session_id: 2669464172, packet_id: 19, flags: 176, frag_id: 224, frag_count: 210 };[9f, 1c, ca, 6c, 00, 13, b0, e0, d2] +udp_hdr 0add31b1c23fde5e9c17a297d1 UdpMsgHdr { session_id: 182268337, packet_id: 49727, flags: 222, frag_id: 94, frag_count: 156 };[0a, dd, 31, b1, c2, 3f, de, 5e, 9c] +udp_hdr 4c3a30db4265cb082c8919d3 UdpMsgHdr { session_id: 1278882011, packet_id: 16997, flags: 203, frag_id: 8, frag_count: 44 };[4c, 3a, 30, db, 42, 65, cb, 08, 2c] +udp_hdr cb6a9a48ac18ce1d12b8bfbad1 UdpMsgHdr { session_id: 3412761160, packet_id: 44056, flags: 206, frag_id: 29, frag_count: 18 };[cb, 6a, 9a, 48, ac, 18, ce, 1d, 12] +udp_hdr e58ad50963e8bbbe81 UdpMsgHdr { session_id: 3851080969, packet_id: 25576, flags: 187, frag_id: 190, frag_count: 129 };[e5, 8a, d5, 09, 63, e8, bb, be, 81] +udp_hdr d69d003c99af6694f304c22848dafd UdpMsgHdr { session_id: 3600613436, packet_id: 39343, flags: 102, frag_id: 148, frag_count: 243 };[d6, 9d, 00, 3c, 99, af, 66, 94, f3] +udp_hdr 618edeca65f053a022f8ef88a9 UdpMsgHdr { session_id: 1636753098, packet_id: 26096, flags: 83, frag_id: 160, frag_count: 34 };[61, 8e, de, ca, 65, f0, 53, a0, 22] +udp_hdr 8042e8ef218fae7f134905df3521 UdpMsgHdr { session_id: 2151868655, packet_id: 8591, flags: 174, frag_id: 127, frag_count: 19 };[80, 42, e8, ef, 21, 8f, ae, 7f, 13] +udp_hdr 23dc5cd18d2bdb1ed3b1de0d19127926 UdpMsgHdr { session_id: 601644241, packet_id: 36139, flags: 219, frag_id: 30, frag_count: 211 };[23, dc, 5c, d1, 8d, 2b, db, 1e, d3] +udp_hdr d925037c6804c98ce21fd0c967 UdpMsgHdr { session_id: 3643081596, packet_id: 26628, flags: 201, frag_id: 140, frag_count: 226 };[d9, 25, 03, 7c, 68, 04, c9, 8c, e2] +udp_hdr cab946bb853b8ff747 UdpMsgHdr { session_id: 3401139899, packet_id: 34107, flags: 143, frag_id: 247, frag_count: 71 };[ca, b9, 46, bb, 85, 3b, 8f, f7, 47] +udp_hdr 9c1bea4b2efe19e8cfdd39c3bb UdpMsgHdr { session_id: 2619075147, packet_id: 12030, flags: 25, frag_id: 232, frag_count: 207 };[9c, 1b, ea, 4b, 2e, fe, 19, e8, cf] +udp_hdr c35e4c2bbd0b31adb1d3 UdpMsgHdr { session_id: 3277737003, packet_id: 48395, flags: 49, frag_id: 173, frag_count: 177 };[c3, 5e, 4c, 2b, bd, 0b, 31, ad, b1] +udp_hdr c9c0b43406a6cf4cec80 UdpMsgHdr { session_id: 3384849460, packet_id: 1702, flags: 207, frag_id: 76, frag_count: 236 };[c9, c0, b4, 34, 06, a6, cf, 4c, ec] +udp_hdr 3f6ea0aba168e2872121a86b8574 UdpMsgHdr { session_id: 1064214699, packet_id: 41320, flags: 226, frag_id: 135, frag_count: 33 };[3f, 6e, a0, ab, a1, 68, e2, 87, 21] +udp_hdr afc3978ac4db0c7a499d UdpMsgHdr { session_id: 2948831114, packet_id: 50395, flags: 12, frag_id: 122, frag_count: 73 };[af, c3, 97, 8a, c4, db, 0c, 7a, 49] +udp_hdr 73cef8755605b81f4de7 UdpMsgHdr { session_id: 1942943861, packet_id: 22021, flags: 184, frag_id: 31, frag_count: 77 };[73, ce, f8, 75, 56, 05, b8, 1f, 4d] +udp_hdr d3a83db474c95f9ca6871fb7 UdpMsgHdr { session_id: 3551018420, packet_id: 29897, flags: 95, frag_id: 156, frag_count: 166 };[d3, a8, 3d, b4, 74, c9, 5f, 9c, a6] +udp_hdr 2440ba1b10de500d0d259b5e UdpMsgHdr { session_id: 608221723, packet_id: 4318, flags: 80, frag_id: 13, frag_count: 13 };[24, 40, ba, 1b, 10, de, 50, 0d, 0d] +udp_hdr df7149ea99d44d5a926183 UdpMsgHdr { session_id: 3748743658, packet_id: 39380, flags: 77, frag_id: 90, frag_count: 146 };[df, 71, 49, ea, 99, d4, 4d, 5a, 92] +udp_hdr 40e64ca491932cbe3953e3ff059159 UdpMsgHdr { session_id: 1088834724, packet_id: 37267, flags: 44, frag_id: 190, frag_count: 57 };[40, e6, 4c, a4, 91, 93, 2c, be, 39] +udp_hdr 798b17ed87db84080468cdbe46 UdpMsgHdr { session_id: 2039158765, packet_id: 34779, flags: 132, frag_id: 8, frag_count: 4 };[79, 8b, 17, ed, 87, db, 84, 08, 04] +udp_hdr 87ab16d89ff2ddb657aa1e292401e6 UdpMsgHdr { session_id: 2276136664, packet_id: 40946, flags: 221, frag_id: 182, frag_count: 87 };[87, ab, 16, d8, 9f, f2, dd, b6, 57] +udp_hdr dbf1f82e4b805b7c534d32 UdpMsgHdr { session_id: 3690068014, packet_id: 19328, flags: 91, frag_id: 124, frag_count: 83 };[db, f1, f8, 2e, 4b, 80, 5b, 7c, 53] +udp_hdr de351b5cd8b0b48fc752e36e UdpMsgHdr { session_id: 3728022364, packet_id: 55472, flags: 180, frag_id: 143, frag_count: 199 };[de, 35, 1b, 5c, d8, b0, b4, 8f, c7] +udp_hdr 4c8b8ef7b57a4771dc86f1a5 UdpMsgHdr { session_id: 1284214519, packet_id: 46458, flags: 71, frag_id: 113, frag_count: 220 };[4c, 8b, 8e, f7, b5, 7a, 47, 71, dc] +udp_hdr c93f92335862f6d73107 UdpMsgHdr { session_id: 3376386611, packet_id: 22626, flags: 246, frag_id: 215, frag_count: 49 };[c9, 3f, 92, 33, 58, 62, f6, d7, 31] +udp_hdr e4c6d94533aa23c5c74bbc6183f92c UdpMsgHdr { session_id: 3838236997, packet_id: 13226, flags: 35, frag_id: 197, frag_count: 199 };[e4, c6, d9, 45, 33, aa, 23, c5, c7] +udp_hdr 3919fb083692443427c77260111cac UdpMsgHdr { session_id: 958003976, packet_id: 13970, flags: 68, frag_id: 52, frag_count: 39 };[39, 19, fb, 08, 36, 92, 44, 34, 27] +udp_hdr 2c1346b28121f1a2cb UdpMsgHdr { session_id: 739460786, packet_id: 33057, flags: 241, frag_id: 162, frag_count: 203 };[2c, 13, 46, b2, 81, 21, f1, a2, cb] +varint None +varint 00 Some((0, 1)) +varint 01 Some((1, 1)) +varint 05 Some((5, 1)) +varint 3f Some((63, 1)) +varint 40 None +varint 4025 Some((37, 2)) +varint 4040 Some((64, 2)) +varint 7f None +varint 7fff Some((16383, 2)) +varint 80 None +varint 8000 None +varint 800040 None +varint 80004000 Some((16384, 4)) +varint bf None +varint bfff None +varint bfffff None +varint bfffffff Some((1073741823, 4)) +varint c0 None +varint c000 None +varint c00000 None +varint c0000000 None +varint c000000040 None +varint c00000004000 None +varint c0000000400000 None +varint c000000040000000 Some((1073741824, 8)) +varint ff None +varint ffff None +varint ffffff None +varint ffffffff None +varint ffffffffff None +varint ffffffffffff None +varint ffffffffffffff None +varint ffffffffffffffff Some((4611686018427387903, 8)) diff --git a/crates/mqproxy/Cargo.toml b/crates/mqproxy/Cargo.toml index 05848782..6a9f582f 100644 --- a/crates/mqproxy/Cargo.toml +++ b/crates/mqproxy/Cargo.toml @@ -14,3 +14,8 @@ mq-h3 = { path = "../mq-h3" } mq-linux = { path = "../mq-linux" } clap = { workspace = true } log = { workspace = true } + +[dev-dependencies] +mq-wire = { path = "../mq-wire" } +signal-hook = { workspace = true } +mio = { workspace = true } diff --git a/crates/mqproxy/examples/udp_echo.rs b/crates/mqproxy/examples/udp_echo.rs new file mode 100644 index 00000000..a6a38497 --- /dev/null +++ b/crates/mqproxy/examples/udp_echo.rs @@ -0,0 +1,49 @@ +//! UDP echo peer for e2e_udp.sh. +use clap::Parser; +use std::{ + io, + net::UdpSocket, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + time::Duration, +}; + +#[derive(Parser)] +struct Args { + #[arg(long, value_parser = clap::value_parser!(u16).range(1..))] + port: u16, + #[arg(long, default_value = "65535", value_parser = clap::value_parser!(u16).range(1..))] + max_size: u16, +} + +fn main() -> io::Result<()> { + let args = Args::parse(); + let socket = UdpSocket::bind(("127.0.0.1", args.port))?; + socket.set_read_timeout(Some(Duration::from_millis(100)))?; + let stop = Arc::new(AtomicBool::new(false)); + for sig in [signal_hook::consts::SIGINT, signal_hook::consts::SIGTERM] { + signal_hook::flag::register(sig, stop.clone())?; + } + println!("udp_echo: bound {}", socket.local_addr()?); + let mut buf = vec![0; usize::from(args.max_size)]; + while !stop.load(Ordering::Relaxed) { + match socket.recv_from(&mut buf) { + Ok((n, peer)) => { + if let Err(e) = socket.send_to(&buf[..n], peer) { + eprintln!("udp_echo: {e}"); + } + } + Err(e) + if matches!( + e.kind(), + io::ErrorKind::WouldBlock + | io::ErrorKind::TimedOut + | io::ErrorKind::Interrupted + ) => {} + Err(e) => return Err(e), + } + } + Ok(()) +} diff --git a/crates/mqproxy/examples/udpsocks.rs b/crates/mqproxy/examples/udpsocks.rs new file mode 100644 index 00000000..1b6963ff --- /dev/null +++ b/crates/mqproxy/examples/udpsocks.rs @@ -0,0 +1,288 @@ +//! SOCKS5 UDP echo client and single-peer benchmark forwarder. +use clap::Parser; +use mq_proxy::udp::socks5udp::{self, Dst}; +use mq_wire::frames::AddrType; +use std::{ + error::Error, + io::{self, Read, Write}, + net::{Ipv4Addr, SocketAddr, TcpStream, UdpSocket}, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + time::Duration, +}; +type Result = std::result::Result>; + +#[derive(Parser)] +struct Args { + #[arg(long)] + proxy: SocketAddr, + #[arg(long)] + target: String, + #[arg(long)] + send: Option, + #[arg(long, default_value = "1", value_parser = clap::value_parser!(u32).range(1..))] + count: u32, + #[arg(long, default_value = "3000", value_parser = clap::value_parser!(u64).range(1..))] + timeout_ms: u64, + #[arg(long)] + verbose: bool, + #[arg(long, conflicts_with_all = ["send", "count"], value_parser = clap::value_parser!(u16).range(1..))] + listen: Option, +} + +fn payload(arg: Option<&str>) -> Result> { + if let Some(path) = arg { + match std::fs::read(path) { + Ok(raw) => { + let hex: Vec<_> = raw + .into_iter() + .filter(|b| !b.is_ascii_whitespace()) + .collect(); + if hex.len() % 2 != 0 || hex.len() > 2 * 65535 { + return Err("invalid hex payload length".into()); + } + return hex + .as_chunks::<2>() + .0 + .iter() + .map(|pair| Ok(u8::from_str_radix(std::str::from_utf8(pair)?, 16)?)) + .collect(); + } + Err(e) if e.kind() == io::ErrorKind::NotFound => {} + Err(e) => return Err(e.into()), + } + } + let n: u16 = arg.unwrap_or("8").parse()?; + if n == 0 { + return Err("payload size must be 1-65535".into()); + } + Ok((0..n).map(|i| i as u8).collect()) +} + +fn header(target: &str) -> Result> { + let (host, port) = target.rsplit_once(':').ok_or("target must be host:port")?; + let port: u16 = port.parse()?; + if host.is_empty() || host.len() > 255 || port == 0 { + return Err("invalid target".into()); + } + let ip = host.parse::().ok().map(|v| v.octets()); + let dst = Dst { + atype: if ip.is_some() { + AddrType::Ipv4 + } else { + AddrType::Domain + }, + addr: ip.as_ref().map_or(host.as_bytes(), |v| v.as_slice()), + port, + }; + let mut out = Vec::new(); + socks5udp::build(&mut out, &dst); + Ok(out) +} + +fn associate(args: &Args) -> Result<(TcpStream, UdpSocket)> { + let timeout = Duration::from_millis(args.timeout_ms); + let mut tcp = TcpStream::connect_timeout(&args.proxy, timeout)?; + tcp.set_read_timeout(Some(timeout))?; + tcp.set_write_timeout(Some(timeout))?; + tcp.write_all(&[5, 1, 0])?; + let mut method = [0; 2]; + tcp.read_exact(&mut method)?; + if method != [5, 0] { + return Err("SOCKS5 method rejected".into()); + } + tcp.write_all(&[5, 3, 0, 1, 0, 0, 0, 0, 0, 0])?; + let mut reply = [0; 10]; + tcp.read_exact(&mut reply[..4])?; + if reply[..4] != [5, 0, 0, 1] { + return Err("ASSOCIATE rejected or non-IPv4 relay".into()); + } + tcp.read_exact(&mut reply[4..])?; + let port = u16::from_be_bytes([reply[8], reply[9]]); + if port == 0 { + return Err("ASSOCIATE returned port zero".into()); + } + let ip = Ipv4Addr::new(reply[4], reply[5], reply[6], reply[7]); + let udp = UdpSocket::bind(("127.0.0.1", 0))?; + udp.connect((ip, port))?; + udp.set_read_timeout(Some(timeout))?; + Ok((tcp, udp)) +} + +fn forward(args: &Args, tcp: TcpStream, udp: UdpSocket, header: &[u8], port: u16) -> Result<()> { + use mio::{Events, Interest, Poll, Token}; + tcp.set_nonblocking(true)?; + udp.set_nonblocking(true)?; + let local = UdpSocket::bind(("127.0.0.1", port))?; + local.set_nonblocking(true)?; + let mut tcp = mio::net::TcpStream::from_std(tcp); + let mut udp = mio::net::UdpSocket::from_std(udp); + let mut local = mio::net::UdpSocket::from_std(local); + let mut poll = Poll::new()?; + poll.registry() + .register(&mut tcp, Token(0), Interest::READABLE)?; + poll.registry() + .register(&mut udp, Token(1), Interest::READABLE)?; + poll.registry() + .register(&mut local, Token(2), Interest::READABLE)?; + let stop = Arc::new(AtomicBool::new(false)); + for sig in [signal_hook::consts::SIGINT, signal_hook::consts::SIGTERM] { + signal_hook::flag::register(sig, stop.clone())?; + } + let mut events = Events::with_capacity(8); + let mut rx = vec![0; 65535]; + let mut tx = Vec::with_capacity(65535); + // ponytail: one sticky peer per benchmark process; use a peer map if multi-client tests need it. + let mut peer = None; + let (mut wrap_fail, mut unwrap_fail, mut no_peer_drop) = (0u64, 0u64, 0u64); + eprintln!("udpsocks: forwarding 127.0.0.1:{port} <-> SOCKS5 UDP relay"); + let result = (|| -> io::Result<()> { + while !stop.load(Ordering::Relaxed) { + match poll.poll(&mut events, Some(Duration::from_millis(100))) { + Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, + r => r?, + } + for event in &events { + loop { + let result = match event.token() { + Token(0) => match tcp.read(&mut rx[..1]) { + Ok(0) => return Ok(()), + other => other, + }, + Token(1) => udp.recv(&mut rx).inspect(|&n| { + if let Some(peer) = peer { + if let Some((_, off)) = socks5udp::parse(&rx[..n]) { + if local.send_to(&rx[off..n], peer).is_err() { + unwrap_fail += 1; + } + } else { + unwrap_fail += 1; + } + } else { + no_peer_drop += 1; + } + }), + _ => local.recv_from(&mut rx).map(|(n, from)| { + if peer.is_none() { + peer = Some(from); + if args.verbose { + eprintln!("udpsocks: learned peer {from}"); + } + } + tx.clear(); + tx.extend_from_slice(header); + tx.extend_from_slice(&rx[..n]); + if tx.len() > 65507 || udp.send(&tx).is_err() { + wrap_fail += 1; + } + n + }), + }; + match result { + Ok(_) => {} + Err(e) if e.kind() == io::ErrorKind::WouldBlock => break, + Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + } + } + } + } + Ok(()) + })(); + eprintln!( + "udpsocks: forwarder stats wrap_fail={wrap_fail} unwrap_fail={unwrap_fail} no_peer_drop={no_peer_drop}" + ); + Ok(result?) +} + +fn run(args: Args) -> Result<()> { + let mut packet = header(&args.target)?; + let data = payload(args.send.as_deref())?; + let (tcp, udp) = associate(&args)?; + if let Some(port) = args.listen { + return forward(&args, tcp, udp, &packet, port); + } + packet.extend_from_slice(&data); + if packet.len() > 65507 { + return Err("datagram too large".into()); + } + let mut rx = vec![0; 65535]; + let mut failed = 0; + for i in 0..args.count { + let result = (|| -> Result<()> { + udp.send(&packet)?; + let n = udp.recv(&mut rx)?; + let (_, off) = socks5udp::parse(&rx[..n]).ok_or("invalid SOCKS5 UDP header")?; + let body = &rx[off..n]; + let hex: String = body.iter().map(|b| format!("{b:02x}")).collect(); + println!("{hex}"); + if body != data { + return Err("MISMATCH".into()); + } + if args.verbose { + eprintln!("udpsocks: OK {} bytes (iter {})", body.len(), i + 1); + } else { + eprintln!("OK {} bytes", body.len()); + } + Ok(()) + })(); + if let Err(e) = result { + eprintln!("udpsocks: {e} (iter {})", i + 1); + failed += 1; + } + } + if failed > 0 { + return Err(format!("{failed}/{} responses failed", args.count).into()); + } + Ok(()) +} + +fn main() { + if let Err(e) = run(Args::parse()) { + eprintln!("udpsocks: {e}"); + std::process::exit(1); + } +} + +#[test] +fn headers_and_arguments() { + assert_eq!( + header("127.0.0.1:53").unwrap(), + [0, 0, 0, 1, 127, 0, 0, 1, 0, 53] + ); + let h = header("example.org:53").unwrap(); + assert_eq!(socks5udp::parse(&h).unwrap().0.addr, b"example.org"); + for target in [":53", "example.org:0", "example.org:65536"] { + assert!(header(target).is_err()); + } + assert_eq!(payload(Some("3")).unwrap(), [0, 1, 2]); + assert!(payload(Some("0")).is_err()); + assert!( + Args::try_parse_from([ + "udpsocks", + "--proxy", + "127.0.0.1:1080", + "--target", + "a:53", + "--listen", + "1234" + ]) + .is_ok() + ); + assert!( + Args::try_parse_from([ + "udpsocks", + "--proxy", + "127.0.0.1:1080", + "--target", + "a:53", + "--listen", + "1234", + "--count", + "1" + ]) + .is_err() + ); +} diff --git a/crates/mqproxy/src/cli.rs b/crates/mqproxy/src/cli.rs index 18430476..4739a2cd 100644 --- a/crates/mqproxy/src/cli.rs +++ b/crates/mqproxy/src/cli.rs @@ -30,6 +30,8 @@ pub struct Exit { /// spec §6.4: the parsed and validated command line. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Resolved { + /// Optional process label appended to log lines. + pub instance_id: Option, pub mode: Mode, /// `--config`, already applied (spec §6.4: defaults < file < CLI). pub config: Option, @@ -87,6 +89,9 @@ pub struct Client { arg_required_else_help = true )] struct Cli { + /// Process label appended to logs (1-64 ASCII letters, digits, dot, underscore or dash). + #[arg(long, global = true, value_parser = instance_id)] + instance_id: Option, #[command(subcommand)] cmd: Cmd, } @@ -269,7 +274,7 @@ pub fn parse(argv: &[&str]) -> Result { code: e.exit_code(), message: e.render().to_string(), })?; - match cli.cmd { + let mut resolved = match cli.cmd { Cmd::Version => Err(Exit { code: 0, message: format!("mqproxy {}\n", env!("CARGO_PKG_VERSION")), @@ -282,7 +287,23 @@ pub fn parse(argv: &[&str]) -> Result { let f = config::load(a.config.as_deref(), false)?; client(a, f).map_err(|m| usage_error("client", m)) } + }?; + resolved.instance_id = cli.instance_id; + Ok(resolved) +} + +fn instance_id(s: &str) -> Result { + if s.is_empty() + || s.len() > 64 + || !s + .bytes() + .all(|b| b.is_ascii_alphanumeric() || b"._-".contains(&b)) + { + return Err( + "instance ID must be 1-64 ASCII letters, digits, dot, underscore or dash".into(), + ); } + Ok(s.to_owned()) } /// Exit 2 with the message and the subcommand's usage, like clap's own errors. @@ -350,6 +371,7 @@ fn server(a: ServerArgs, f: FileConfig) -> Result { }; let listen = ip_port("--listen", &listen)?; Ok(Resolved { + instance_id: None, mode: Mode::Server(Server { config: ServerConfig { token, @@ -450,6 +472,7 @@ fn client(a: ClientArgs, f: FileConfig) -> Result { }) .transpose()?; Ok(Resolved { + instance_id: None, mode: Mode::Client(Client { config: ClientConfig { server, diff --git a/crates/mqproxy/src/logger.rs b/crates/mqproxy/src/logger.rs index 9c24721c..623b2077 100644 --- a/crates/mqproxy/src/logger.rs +++ b/crates/mqproxy/src/logger.rs @@ -4,6 +4,13 @@ use log::{Level, LevelFilter, Log, Metadata, Record}; use std::io::Write; +static INSTANCE_ID: std::sync::OnceLock = std::sync::OnceLock::new(); + +/// Set once at startup, before emitting process logs. +pub fn set_instance_id(id: String) { + let _ = INSTANCE_ID.set(id); +} + struct Stderr; impl Log for Stderr { @@ -14,7 +21,11 @@ impl Log for Stderr { fn log(&self, r: &Record) { if self.enabled(r.metadata()) { // One write per line, so lines from other threads do not interleave. - let _ = writeln!(std::io::stderr().lock(), "{}", line(r.level(), r.args())); + let _ = writeln!( + std::io::stderr().lock(), + "{}", + line(r.level(), r.args(), INSTANCE_ID.get().map(String::as_str)) + ); } } @@ -22,14 +33,17 @@ impl Log for Stderr { } /// C's prefixes: ERROR, WARN, INFO, DEBUG (`Trace` never passes the INFO level). -fn line(level: Level, msg: &std::fmt::Arguments) -> String { +fn line(level: Level, msg: &std::fmt::Arguments, instance: Option<&str>) -> String { let prefix = match level { Level::Error => "ERROR", Level::Warn => "WARN", Level::Info => "INFO", Level::Debug | Level::Trace => "DEBUG", }; - format!("[{prefix}] {msg}") + match instance { + Some(id) => format!("[{prefix}] {msg} instance_id={id}"), + None => format!("[{prefix}] {msg}"), + } } /// Installs the logger; the max level is INFO, as C. @@ -45,10 +59,17 @@ mod tests { #[test] fn format_matches_c() { - assert_eq!(line(Level::Info, &format_args!("a {}", 1)), "[INFO] a 1"); - assert_eq!(line(Level::Warn, &format_args!("w")), "[WARN] w"); - assert_eq!(line(Level::Error, &format_args!("e")), "[ERROR] e"); - assert_eq!(line(Level::Debug, &format_args!("d")), "[DEBUG] d"); + assert_eq!( + line(Level::Info, &format_args!("message"), Some("edge-1")), + "[INFO] message instance_id=edge-1" + ); + assert_eq!( + line(Level::Info, &format_args!("a {}", 1), None), + "[INFO] a 1" + ); + assert_eq!(line(Level::Warn, &format_args!("w"), None), "[WARN] w"); + assert_eq!(line(Level::Error, &format_args!("e"), None), "[ERROR] e"); + assert_eq!(line(Level::Debug, &format_args!("d"), None), "[DEBUG] d"); assert!(!Stderr.enabled(&Metadata::builder().level(Level::Debug).build())); } } diff --git a/crates/mqproxy/src/main.rs b/crates/mqproxy/src/main.rs index 222a4894..1b025dcc 100644 --- a/crates/mqproxy/src/main.rs +++ b/crates/mqproxy/src/main.rs @@ -12,6 +12,9 @@ fn main() { let argv: Vec<&str> = args.iter().map(String::as_str).collect(); match mqproxy::cli::parse(&argv) { Ok(mut resolved) => { + if let Some(id) = resolved.instance_id.clone() { + mqproxy::logger::set_instance_id(id); + } // spec §8: the test-only connect timeout knob, read once here. if let mqproxy::cli::Mode::Server(s) = &mut resolved.mode && let Some(g) = &mut s.config.gateway diff --git a/crates/mqproxy/tests/cli.rs b/crates/mqproxy/tests/cli.rs index d7141ba4..1178b20e 100644 --- a/crates/mqproxy/tests/cli.rs +++ b/crates/mqproxy/tests/cli.rs @@ -1117,3 +1117,20 @@ fn listen_addr_in_use_exits_1() { p.lines ); } + +#[test] +fn instance_label_is_optional_and_rejects_log_injection() { + for base in [SERVER, CLIENT] { + assert_eq!(parse(base, &[]).unwrap().instance_id, None); + assert_eq!( + parse(base, &["--instance-id", "edge-1.a_b"]) + .unwrap() + .instance_id + .as_deref(), + Some("edge-1.a_b") + ); + for bad in ["", "a b", "x\n[INFO] forged", "a=b", &"x".repeat(65)] { + assert!(parse(base, &["--instance-id", bad]).is_err()); + } + } +} diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt deleted file mode 100644 index ba053f2a..00000000 --- a/fuzz/CMakeLists.txt +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright (c) 2026 mp0rta -# -# Standalone libFuzzer project for the wire/ingress parse surface. -# Deliberately its OWN project() so configuring it never triggers the root -# CMakeLists.txt's find_package(CURL REQUIRED) / xquic wiring — the in-scope -# parsers depend only on libc, so no submodule build is needed. -# -# cmake -S fuzz -B build-fuzz -DCMAKE_C_COMPILER=clang -# cmake --build build-fuzz -cmake_minimum_required(VERSION 3.10) -project(mqproxy_fuzz LANGUAGES C) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) - -if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang") - message(FATAL_ERROR "fuzz/ requires clang (libFuzzer). Configure with -DCMAKE_C_COMPILER=clang.") -endif() - -set(SRC ${CMAKE_CURRENT_SOURCE_DIR}/../src) -set(FUZZ_FLAGS -g -O1 -fsanitize=fuzzer,address,undefined -fno-omit-frame-pointer) - -# define_fuzzer( ) -function(define_fuzzer name harness) - add_executable(${name} ${harness} ${ARGN}) - target_include_directories(${name} PRIVATE ${SRC}) - target_compile_options(${name} PRIVATE ${FUZZ_FLAGS}) - target_link_options(${name} PRIVATE ${FUZZ_FLAGS}) -endfunction() - -set(WIRE ${SRC}/wire/mq_wire.c ${SRC}/wire/mq_varint.c) - -define_fuzzer(fuzz_varint fuzz_varint.c ${SRC}/wire/mq_varint.c) - -define_fuzzer(fuzz_wire_auth_req fuzz_wire_auth_req.c ${WIRE}) -define_fuzzer(fuzz_wire_auth_resp fuzz_wire_auth_resp.c ${WIRE}) -define_fuzzer(fuzz_wire_connect_tcp_req fuzz_wire_connect_tcp_req.c ${WIRE}) -define_fuzzer(fuzz_wire_connect_tcp_resp fuzz_wire_connect_tcp_resp.c ${WIRE}) -define_fuzzer(fuzz_wire_udp_session_open fuzz_wire_udp_session_open.c ${WIRE}) -define_fuzzer(fuzz_wire_udp_session_resp fuzz_wire_udp_session_resp.c ${WIRE}) -define_fuzzer(fuzz_udp_msg_hdr fuzz_udp_msg_hdr.c ${SRC}/wire/mq_udp_msg.c) - -# fuzz/scripts/gen_wire_seeds.c is NOT a build target — it is a standalone helper -# compiled by hand (see its header comment) only when (re)generating wire seeds. - -set(INGRESS ${SRC}/ingress/mq_socks5.c ${WIRE}) -define_fuzzer(fuzz_socks5_feed fuzz_socks5_feed.c ${INGRESS}) -define_fuzzer(fuzz_socks5_udp_hdr fuzz_socks5_udp_hdr.c ${INGRESS}) -define_fuzzer(fuzz_http_connect fuzz_http_connect.c ${SRC}/ingress/mq_http_connect.c ${WIRE}) -define_fuzzer(fuzz_clienthello fuzz_clienthello.c ${SRC}/ingress/mq_clienthello.c) diff --git a/fuzz/README.md b/fuzz/README.md index 03385e00..61bfbe45 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -1,65 +1,18 @@ -# mqproxy fuzzing +# Parser corpus replay -In-tree [libFuzzer](https://llvm.org/docs/LibFuzzer.html) harnesses over the -wire/ingress byte-parsing attack surface (the decoders that consume -attacker-controlled bytes). Each harness compiles only the `src/*.c` -translation units it needs — no xquic / libevent / boringssl / curl — so this is -a **standalone CMake project** that builds in seconds without a submodule build. +`fuzz/corpus` preserves the legacy C seeds. The maintained harnesses are the +Cargo fuzz workspaces under `crates/mq-wire/fuzz`, `crates/mq-proxy/fuzz` and +`crates/mq-http/fuzz`. The Rust workflow replays these seeds and explores each +target. For example: -## Build - -Requires clang (libFuzzer + ASan/UBSan): - -```bash -cmake -S fuzz -B build-fuzz -DCMAKE_C_COMPILER=clang -cmake --build build-fuzz -``` - -All harness binaries land in `build-fuzz/fuzz_*`. - -## Targets - -| binary | parser under test | -|---|---| -| `fuzz_varint` | `mq_varint_decode` | -| `fuzz_wire_auth_req` / `_resp` | `mq_decode_auth_req` / `_resp` | -| `fuzz_wire_connect_tcp_req` / `_resp` | `mq_decode_connect_tcp_req` / `_resp` | -| `fuzz_wire_udp_session_open` / `_resp` | `mq_decode_udp_session_open` / `_resp` | -| `fuzz_udp_msg_hdr` | `mq_udp_msg_decode_hdr` | -| `fuzz_socks5_feed` | `mq_socks5_feed` (greeting + request) | -| `fuzz_socks5_udp_hdr` | `mq_socks5_parse_udp_hdr` | -| `fuzz_http_connect` | `mq_http_connect_parse` | -| `fuzz_clienthello` | `mq_clienthello_parse` (TLS ClientHello SNI/ALPN peek) | - -## Run - -Replay the committed corpus (deterministic — this is the CI regression gate): - -```bash -./build-fuzz/ fuzz/corpus//* +```sh +cargo +nightly fuzz run --fuzz-dir crates/mq-wire/fuzz varint fuzz/corpus/varint -- -runs=0 ``` -Explore for new bugs (time-boxed): - -```bash -./build-fuzz/ fuzz/corpus/ -max_total_time=30 -``` - -Corpus dir name = binary name minus the `fuzz_` prefix -(`fuzz_wire_auth_req` → `fuzz/corpus/wire_auth_req/`). - -## On a crash (regression discipline) - -```bash -./build-fuzz/ -minimize_crash=1 -runs=10000 # minimize -cp fuzz/corpus// # commit the reproducer -``` - -Then fix the bug. The committed reproducer is replayed on every CI run, so a -crash found once can never silently return. - -## Seed (re)generation - -Wire seeds are produced by `fuzz/scripts/gen_wire_seeds.c` (a non-built helper). -To regenerate, compile and run it **per the command in its header comment** — -that comment is the single source of truth; it is not duplicated here. +C codec compatibility is frozen in `crates/mq-wire/tests/data/c-codec-vectors.txt`. +The 1,892 rows were checked against the C decoder/encoder at `cd41d699` before +removal: committed corpora, deterministic generated inputs (seed +`0x5BE0_CD19_137E_2179`), truncations, and varint boundaries. Each full row +checks fields, bytes consumed and canonical encoding. Outside C's representable +string/error-code domain, rows check acceptance only, matching the old differential +contract. Replay with `cargo test -p mq-wire --test c_golden`. diff --git a/fuzz/fuzz_clienthello.c b/fuzz/fuzz_clienthello.c deleted file mode 100644 index a728eb8f..00000000 --- a/fuzz/fuzz_clienthello.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "ingress/mq_clienthello.h" -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - mq_clienthello_t out; - (void)mq_clienthello_parse(data, size, &out); - return 0; -} diff --git a/fuzz/fuzz_http_connect.c b/fuzz/fuzz_http_connect.c deleted file mode 100644 index 00dd3c36..00000000 --- a/fuzz/fuzz_http_connect.c +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "ingress/mq_http_connect.h" -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - mq_http_target_t out = {0}; - size_t header_len = 0; - (void)mq_http_connect_parse(data, size, &out, &header_len); - return 0; -} diff --git a/fuzz/fuzz_socks5_feed.c b/fuzz/fuzz_socks5_feed.c deleted file mode 100644 index 827bb612..00000000 --- a/fuzz/fuzz_socks5_feed.c +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -// -// Drives mq_socks5_feed exactly as src/ingress/mq_listener.c does: init the -// parser per call, feed bytes, and on GREETING_DONE advance past the consumed -// greeting into the request phase. The loop is bounded by the buffer length so -// a fuzz input can never wedge the harness itself. -#include "ingress/mq_socks5.h" -#include -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - mq_socks5_parser_t p; - mq_socks5_parser_init(&p); - - /* mutable working copy: the listener consumes from an in-place buffer */ - uint8_t buf[4096]; - if (size > sizeof buf) size = sizeof buf; - memcpy(buf, data, size); - - size_t off = 0; - for (size_t guard = 0; guard <= size; guard++) { - size_t consumed = 0; - mq_socks5_req_t req = {0}; - mq_socks5_status_t st = - mq_socks5_feed(&p, buf + off, size - off, &consumed, &req); - off += consumed; - if (st == MQ_SOCKS5_NEED_MORE) break; /* would wait for more bytes */ - if (st == MQ_SOCKS5_GREETING_DONE) { - if (consumed == 0) break; /* no progress guard */ - continue; /* advance into request phase */ - } - break; /* REQUEST_DONE / ASSOCIATE_DONE / UNSUPPORTED* / ERROR are terminal */ - } - return 0; -} diff --git a/fuzz/fuzz_socks5_udp_hdr.c b/fuzz/fuzz_socks5_udp_hdr.c deleted file mode 100644 index 1e2780c3..00000000 --- a/fuzz/fuzz_socks5_udp_hdr.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "ingress/mq_socks5.h" -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - mq_socks5_udp_hdr_t out = {0}; - (void)mq_socks5_parse_udp_hdr(data, size, &out); - return 0; -} diff --git a/fuzz/fuzz_udp_msg_hdr.c b/fuzz/fuzz_udp_msg_hdr.c deleted file mode 100644 index 2b966d49..00000000 --- a/fuzz/fuzz_udp_msg_hdr.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "wire/mq_udp_msg.h" -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - mq_udp_msg_hdr_t out = {0}; - (void)mq_udp_msg_decode_hdr(data, size, &out); - return 0; -} diff --git a/fuzz/fuzz_varint.c b/fuzz/fuzz_varint.c deleted file mode 100644 index 63c21c9a..00000000 --- a/fuzz/fuzz_varint.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "wire/mq_varint.h" -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - uint64_t out = 0; - (void)mq_varint_decode(data, size, &out); - return 0; -} diff --git a/fuzz/fuzz_wire_auth_req.c b/fuzz/fuzz_wire_auth_req.c deleted file mode 100644 index 4e688936..00000000 --- a/fuzz/fuzz_wire_auth_req.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "wire/mq_wire.h" -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - mq_auth_req_t out = {0}; - (void)mq_decode_auth_req(data, size, &out); - return 0; -} diff --git a/fuzz/fuzz_wire_auth_resp.c b/fuzz/fuzz_wire_auth_resp.c deleted file mode 100644 index 1fba87b9..00000000 --- a/fuzz/fuzz_wire_auth_resp.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "wire/mq_wire.h" -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - mq_auth_resp_t out = {0}; - (void)mq_decode_auth_resp(data, size, &out); - return 0; -} diff --git a/fuzz/fuzz_wire_connect_tcp_req.c b/fuzz/fuzz_wire_connect_tcp_req.c deleted file mode 100644 index a4b8beaa..00000000 --- a/fuzz/fuzz_wire_connect_tcp_req.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "wire/mq_wire.h" -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - mq_connect_tcp_req_t out = {0}; - (void)mq_decode_connect_tcp_req(data, size, &out); - return 0; -} diff --git a/fuzz/fuzz_wire_connect_tcp_resp.c b/fuzz/fuzz_wire_connect_tcp_resp.c deleted file mode 100644 index 3463ab35..00000000 --- a/fuzz/fuzz_wire_connect_tcp_resp.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "wire/mq_wire.h" -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - mq_connect_tcp_resp_t out = {0}; - (void)mq_decode_connect_tcp_resp(data, size, &out); - return 0; -} diff --git a/fuzz/fuzz_wire_udp_session_open.c b/fuzz/fuzz_wire_udp_session_open.c deleted file mode 100644 index 9918c649..00000000 --- a/fuzz/fuzz_wire_udp_session_open.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "wire/mq_wire.h" -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - mq_udp_session_open_t out = {0}; - (void)mq_decode_udp_session_open(data, size, &out); - return 0; -} diff --git a/fuzz/fuzz_wire_udp_session_resp.c b/fuzz/fuzz_wire_udp_session_resp.c deleted file mode 100644 index d624c45d..00000000 --- a/fuzz/fuzz_wire_udp_session_resp.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "wire/mq_wire.h" -#include -#include - -int -LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - mq_udp_session_resp_t out = {0}; - (void)mq_decode_udp_session_resp(data, size, &out); - return 0; -} diff --git a/fuzz/scripts/gen_wire_seeds.c b/fuzz/scripts/gen_wire_seeds.c deleted file mode 100644 index b1e61fcf..00000000 --- a/fuzz/scripts/gen_wire_seeds.c +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -// -// NOT part of the CMake build. Regenerate wire seeds (run from repo ROOT): -// clang -g -O1 -Isrc fuzz/scripts/gen_wire_seeds.c \ -// src/wire/mq_wire.c src/wire/mq_varint.c src/wire/mq_udp_msg.c -o -// /tmp/gen_wire_seeds -// for d in wire_auth_req wire_auth_resp wire_connect_tcp_req wire_connect_tcp_resp \ -// wire_udp_session_open wire_udp_session_resp udp_msg_hdr; do mkdir -p -// fuzz/corpus/$d; done -// /tmp/gen_wire_seeds -// -// Emits a minimal valid frame + one boundary frame per wire type, by running the -// real encoders. Seeds only bootstrap libFuzzer coverage; they are not golden -// fixtures (the fuzzer mutates them), so a stale seed at worst loses a little -// coverage and never breaks the gate. -#include "wire/mq_udp_msg.h" -#include "wire/mq_wire.h" -#include -#include - -static void -dump(const char *path, const uint8_t *b, int n) -{ - if (n < 0) { - fprintf(stderr, "encode failed: %s\n", path); - return; - } - FILE *f = fopen(path, "wb"); - if (!f) { - perror(path); - return; - } - fwrite(b, 1, (size_t)n, f); - fclose(f); -} - -int -main(void) -{ - uint8_t buf[1024]; - - /* --- AUTH_REQUEST --- */ - mq_auth_req_t ar = {.version = 1, .features = 0}; - strcpy(ar.client_id, "c1"); - strcpy(ar.auth_token, "t1"); - dump("fuzz/corpus/wire_auth_req/min", buf, mq_encode_auth_req(buf, sizeof buf, &ar)); - mq_auth_req_t ar2 = {.version = 0xFFFFFFFF, .features = 0xDEADBEEF}; - memset(ar2.client_id, 'A', sizeof ar2.client_id - 1); - memset(ar2.auth_token, 'B', sizeof ar2.auth_token - 1); - dump("fuzz/corpus/wire_auth_req/maxlen", buf, - mq_encode_auth_req(buf, sizeof buf, &ar2)); - - /* --- AUTH_RESPONSE --- */ - mq_auth_resp_t aresp = { - .status = MQ_STATUS_OK, .error_code = MQ_AUTH_OK, .features = MQ_FEAT_UDP_RELAY}; - strcpy(aresp.server_id, "s1"); - dump("fuzz/corpus/wire_auth_resp/ok", buf, - mq_encode_auth_resp(buf, sizeof buf, &aresp)); - mq_auth_resp_t aresp_err = { - .status = MQ_STATUS_ERROR, .error_code = MQ_AUTH_TOKEN_EXPIRED, .features = 0}; - strcpy(aresp_err.server_id, "s1"); - dump("fuzz/corpus/wire_auth_resp/err", buf, - mq_encode_auth_resp(buf, sizeof buf, &aresp_err)); - - /* --- CONNECT_TCP_REQUEST --- */ - mq_connect_tcp_req_t creq = { - .flags = 0, .address_type = MQ_ADDR_IPV4, .host_len = 4, .port = 80}; - memcpy(creq.host, (uint8_t[]){127, 0, 0, 1}, 4); - dump("fuzz/corpus/wire_connect_tcp_req/ipv4", buf, - mq_encode_connect_tcp_req(buf, sizeof buf, &creq)); - mq_connect_tcp_req_t creq_dom = { - .flags = 0, .address_type = MQ_ADDR_DOMAIN, .port = 443}; - const char *dom = "example.com"; - creq_dom.host_len = strlen(dom); - memcpy(creq_dom.host, dom, creq_dom.host_len); - dump("fuzz/corpus/wire_connect_tcp_req/domain", buf, - mq_encode_connect_tcp_req(buf, sizeof buf, &creq_dom)); - - /* --- CONNECT_TCP_RESPONSE --- */ - mq_connect_tcp_resp_t cresp = { - .status = MQ_STATUS_OK, .error_code = MQ_TCP_OK, .message_len = 0}; - dump("fuzz/corpus/wire_connect_tcp_resp/ok", buf, - mq_encode_connect_tcp_resp(buf, sizeof buf, &cresp)); - mq_connect_tcp_resp_t cresp_err = {.status = MQ_STATUS_ERROR, - .error_code = MQ_TCP_CONN_REFUSED}; - const char *msg = "connection refused"; - cresp_err.message_len = strlen(msg); - memcpy(cresp_err.message, msg, cresp_err.message_len); - dump("fuzz/corpus/wire_connect_tcp_resp/err", buf, - mq_encode_connect_tcp_resp(buf, sizeof buf, &cresp_err)); - - /* --- UDP_SESSION_OPEN --- */ - mq_udp_session_open_t uopen = {.session_id = 1, - .flags = 0, - .address_type = MQ_ADDR_IPV4, - .host_len = 4, - .port = 53, - .idle_timeout_ms = 0}; - memcpy(uopen.host, (uint8_t[]){8, 8, 8, 8}, 4); - dump("fuzz/corpus/wire_udp_session_open/ipv4", buf, - mq_encode_udp_session_open(buf, sizeof buf, &uopen)); - - /* --- UDP_SESSION_RESPONSE --- */ - mq_udp_session_resp_t uresp = {.status = MQ_STATUS_OK, - .error_code = MQ_UDP_OK, - .message_len = 0, - .idle_timeout_ms = 30000}; - dump("fuzz/corpus/wire_udp_session_resp/ok", buf, - mq_encode_udp_session_resp(buf, sizeof buf, &uresp)); - - /* --- UDP_MSG_HDR (fixed 9-byte header) --- */ - mq_udp_msg_hdr_t h = { - .session_id = 1, .packet_id = 1, .flags = 0, .frag_id = 0, .frag_count = 1}; - uint8_t hbuf[MQ_UDP_MSG_HDR]; - int hn = mq_udp_msg_encode_hdr(hbuf, &h); - dump("fuzz/corpus/udp_msg_hdr/min", hbuf, hn == 0 ? (int)MQ_UDP_MSG_HDR : -1); - - return 0; -} diff --git a/packaging/build.py b/packaging/build.py new file mode 100644 index 00000000..2654efab --- /dev/null +++ b/packaging/build.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Build native Rust .deb and tar artifacts; run on each target architecture.""" +import json +import os +from pathlib import Path +import shutil +import subprocess +import tarfile +import tempfile + +ROOT = Path(__file__).resolve().parents[1] + + +def output(*args, **kwargs): + return subprocess.check_output(args, text=True, **kwargs).strip() + + +def build(): + os.chdir(ROOT) + os.umask(0o022) # Package permissions must not inherit the builder's umask. + subprocess.run(["cargo", "build", "--release", "--locked", "-p", "mqproxy"], check=True) + metadata = json.loads(output("cargo", "metadata", "--locked", "--format-version", "1")) + package = next(p for p in metadata["packages"] if p["name"] == "mqproxy") + version = package["version"] + arch = output("dpkg", "--print-architecture") + if arch not in ("amd64", "arm64"): + raise SystemExit(f"unsupported release architecture: {arch}") + binary = Path(metadata["target_directory"]) / "release/mqproxy" + machine = output("readelf", "-h", str(binary)) + assert ("X86-64" if arch == "amd64" else "AArch64") in machine, "binary architecture mismatch" + dist = ROOT / "target/dist" + dist.mkdir(parents=True, exist_ok=True) + # Keep each build isolated, including dpkg-shlibdeps' source control file. + with tempfile.TemporaryDirectory(prefix="mqproxy-package-") as tmp: + work = Path(tmp) + stage = work / "stage" + doc = stage / "usr/share/doc/mqproxy" + + def install(src, dst, mode=0o644): + dst = stage / dst + dst.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(src, dst) + dst.chmod(mode) + + install(binary, "usr/bin/mqproxy", 0o755) + subprocess.run(["strip", str(stage / "usr/bin/mqproxy")], check=True) + for unit in (ROOT / "packaging/systemd").glob("*.in"): + install(unit, f"usr/lib/systemd/system/{unit.stem}") + for kind in ("sysusers.d", "tmpfiles.d"): + install(ROOT / f"packaging/{kind}/mqproxy.conf", f"usr/lib/{kind}/mqproxy.conf") + for name in ("LICENSE", "NOTICE", "server.conf.example", "client.conf.example"): + install(ROOT / name, f"usr/share/doc/mqproxy/{name}") + for name, source in (("xquic", "third_party/xquic/LICENSE"), + ("boringssl", "third_party/xquic/third_party/boringssl/LICENSE")): + install(ROOT / source, f"usr/share/doc/mqproxy/third-party/{name}.txt") + # Include the locked production/build dependency notices, not a hand-maintained list. + tree = output("cargo", "tree", "--locked", "-p", "mqproxy", "--edges", "normal,build", + "--prefix", "none", "--format", "{p}") + used = {tuple(line.split()[:2]) for line in tree.splitlines()} + inventory = [] + for p in sorted(metadata["packages"], key=lambda p: (p["name"], p["version"])): + if not p["source"] or (p["name"], "v" + p["version"]) not in used: + continue + source = Path(p["manifest_path"]).parent + notices = [f for f in source.iterdir() if f.is_file() + and f.name.lower().startswith(("license", "licence", "copying", "notice", "copyright"))] + if p["license_file"]: + notices.append(source / p["license_file"]) + # asn1-rs-impl 0.2.0 omits its workspace license files from the archive. + # It is Apache-2.0 OR MIT; distribute under Apache-2.0 with its metadata. + if p["name"] == "asn1-rs-impl" and p["version"] == "0.2.0" and not notices: + install(ROOT / "LICENSE", f"usr/share/doc/mqproxy/third-party/asn1-rs-impl-0.2.0/LICENSE-APACHE") + notices = [source / "Cargo.toml"] + if not notices: + raise RuntimeError(f"missing license text for {p['name']} {p['version']}") + for f in set(notices): + install(f, f"usr/share/doc/mqproxy/third-party/{p['name']}-{p['version']}/{f.name}") + inventory.append(f"{p['name']} {p['version']}: {p['license'] or p['license_file']}") + (doc / "third-party/RUST-DEPENDENCIES.txt").write_text("\n".join(inventory) + "\n") + subprocess.run(["bash", str(ROOT / "tests/packaging/verify_install.sh"), str(stage)], check=True) + # tar carries the same installed tree, without Debian control metadata. + tar = dist / f"mqproxy_{version}_{arch}.tar.gz" + def root_owner(info): + info.uid = info.gid = 0 + info.uname = info.gname = "root" + return info + + with tarfile.open(tar, "w:gz") as archive: + archive.add(stage / "usr", arcname="usr", filter=root_owner) + (work / "debian").mkdir() + (work / "debian/control").write_text("Source: mqproxy\nSection: net\nPriority: optional\nMaintainer: mp0rta\n\nPackage: mqproxy\nArchitecture: any\nDescription: Multipath QUIC application proxy\n") + deps = output("dpkg-shlibdeps", "-O", "-e" + str(stage / "usr/bin/mqproxy"), cwd=work) + deps = next(line.removeprefix("shlibs:Depends=") for line in deps.splitlines() + if line.startswith("shlibs:Depends=")) + control = stage / "DEBIAN" + control.mkdir() + size = sum(f.stat().st_size for f in (stage / "usr").rglob("*") if f.is_file()) + (control / "control").write_text( + f"Package: mqproxy\nVersion: {version}\nArchitecture: {arch}\n" + f"Maintainer: mp0rta\nSection: net\nPriority: optional\nDepends: {deps}\n" + f"Installed-Size: {(size + 1023) // 1024}\nHomepage: https://github.com/mp0rta/mqproxy\n" + "Description: Multipath QUIC application proxy/accelerator\n") + for script in ("postinst", "postrm"): + install(ROOT / f"packaging/{script}", f"DEBIAN/{script}", 0o755) + deb = dist / f"mqproxy_{version}_{arch}.deb" + subprocess.run(["dpkg-deb", "--root-owner-group", "--build", str(stage), str(deb)], check=True) + subprocess.run(["python3", str(ROOT / "tests/packaging/verify_archives.py"), + str(tar), str(deb)], check=True) + print(f"Built {deb}\nBuilt {tar}") + + +if __name__ == "__main__": + build() diff --git a/packaging/postinst b/packaging/postinst index 19c73454..2d8b5a9a 100755 --- a/packaging/postinst +++ b/packaging/postinst @@ -1,6 +1,6 @@ #!/bin/sh set -e -# CPack DEB ships no debhelper, so sysusers.d/tmpfiles.d are not auto-applied. +# The native DEB ships no debhelper, so sysusers.d/tmpfiles.d are not auto-applied. # Create the mqproxy system user and runtime dirs the systemd units depend on. # || true so a host without systemd (e.g. a container) never aborts dpkg. systemd-sysusers /usr/lib/sysusers.d/mqproxy.conf 2>/dev/null || true diff --git a/packaging/systemd/mqproxy-client@.service.in b/packaging/systemd/mqproxy-client@.service.in index d68560f5..4f96f481 100644 --- a/packaging/systemd/mqproxy-client@.service.in +++ b/packaging/systemd/mqproxy-client@.service.in @@ -8,7 +8,7 @@ StartLimitBurst=3 [Service] Type=simple -ExecStart=@CMAKE_INSTALL_PREFIX@/bin/mqproxy client --config /etc/mqproxy/%i.conf +ExecStart=/usr/bin/mqproxy client --config /etc/mqproxy/%i.conf --instance-id %i Restart=on-failure RestartSec=2 diff --git a/packaging/systemd/mqproxy-server@.service.in b/packaging/systemd/mqproxy-server@.service.in index 2294d293..14d70865 100644 --- a/packaging/systemd/mqproxy-server@.service.in +++ b/packaging/systemd/mqproxy-server@.service.in @@ -8,7 +8,7 @@ StartLimitBurst=3 [Service] Type=simple -ExecStart=@CMAKE_INSTALL_PREFIX@/bin/mqproxy server --config /etc/mqproxy/%i.conf +ExecStart=/usr/bin/mqproxy server --config /etc/mqproxy/%i.conf --instance-id %i Restart=on-failure RestartSec=2 diff --git a/scripts/build-h3-curl.sh b/scripts/build-h3-curl.sh deleted file mode 100755 index 9a73e52f..00000000 --- a/scripts/build-h3-curl.sh +++ /dev/null @@ -1,261 +0,0 @@ -#!/usr/bin/env bash -# -# build-h3-curl.sh — build an HTTP/3-capable libcurl (ngtcp2 + nghttp3) on top -# of mqproxy's already-pinned BoringSSL, into third_party/h3-curl/install. -# -# WHY THIS EXISTS: -# The system libcurl (8.5.0 on Ubuntu) has NO HTTP/3 — so mqproxy's -# origin-protocol selection (`X-Mq-Origin-Protocol: h3`) silently falls back -# to the default. To actually speak h3 to an origin, libcurl must be built -# with the ngtcp2 (QUIC) + nghttp3 (HTTP/3) stack against a QUIC-capable TLS -# library. We reuse the BoringSSL that scripts/build-xquic.sh already builds -# (xquic also needs BoringSSL), so there is exactly one TLS build in the tree. -# -# This is an OPT-IN build. The default mqproxy build links the system libcurl -# and does not need this script. Configure mqproxy against the produced curl -# with: -# cmake -S . -B build \ -# -DXQUIC_BUILD_DIR="$(pwd)/third_party/xquic/build" \ -# -DMQPROXY_H3_CURL="$(pwd)/third_party/h3-curl/install" -# -# DISCOVERY NOTES (why the explicit paths below): -# The pinned BoringSSL is built STATIC-ONLY (libssl.a / libcrypto.a under -# .../boringssl/build), is NEVER installed, and ships NO openssl.pc. So: -# * ngtcp2's cmake cannot auto-find it → we pass -DBORINGSSL_INCLUDE_DIR / -# -DBORINGSSL_LIBRARIES explicitly. -# * curl's configure cannot auto-find it via pkg-config → we feed it -# OPENSSL_CFLAGS / OPENSSL_LIBS explicitly. (PKG_CONFIG_PATH only helps -# discover ngtcp2/nghttp3, which DO emit .pc files into $PREFIX.) -# BoringSSL is built -fPIC (build-xquic.sh), so linking its static .a into a -# SHARED libcurl.so is valid — that is the precondition for --enable-shared. -# We build libcurl SHARED so the .so ABSORBS the whole static QUIC+TLS stack; -# the mqproxy link then stays a single -lcurl (no transitive .a ordering). -# -# USAGE: -# scripts/build-h3-curl.sh [--clean] -# -# REQUIREMENTS: cmake, make, cc/c++, git, pkg-config, tar + network (clones -# ngtcp2/nghttp3 and downloads the curl release tarball on first run). -# NOTE: the curl RELEASE tarball ships a pre-generated ./configure, so -# autoconf/autoreconf are NOT required. ngtcp2/nghttp3 use cmake. -# Not run by CI by default — it is the privileged/one-time h3 bootstrap. -# -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -cd "$REPO_ROOT" - -NPROC=$(nproc 2>/dev/null || echo 4) - -# ---------- Pinned versions (mutually compatible stable releases) ---------- -# ngtcp2 1.11.x and nghttp3 1.8.x are contemporaneous releases that interoperate; -# curl 8.13.0 supports that ngtcp2/nghttp3 API. Bump these together. -NGHTTP3_TAG="v1.8.0" -NGTCP2_TAG="v1.11.0" -CURL_VER="8.13.0" - -# ---------- Paths ---------- -H3_DIR="$REPO_ROOT/third_party/h3-curl" -SRC_DIR="$H3_DIR/src" -PREFIX="$H3_DIR/install" - -XQUIC_DIR="$REPO_ROOT/third_party/xquic" -BSSL_DIR="$XQUIC_DIR/third_party/boringssl" -BSSL_BUILD="$BSSL_DIR/build" -BSSL_INCLUDE="$BSSL_DIR/include" - -# ---------- Options ---------- - -if [ "${1:-}" = "--clean" ]; then - echo "Cleaning h3-curl build directories..." - rm -rf "$H3_DIR" - shift -fi - -# ---------- Dependency checks ---------- - -err=0 -for cmd in cmake make cc c++ git pkg-config tar; do - if ! command -v "$cmd" &>/dev/null; then - echo "ERROR: '$cmd' not found. Please install it." - err=1 - fi -done -if [ "$err" -ne 0 ]; then - exit 1 -fi - -# ---------- 0. Reuse the pinned BoringSSL (build it if absent) ---------- - -if [ ! -f "$BSSL_BUILD/libssl.a" ] || [ ! -f "$BSSL_BUILD/libcrypto.a" ]; then - echo "=== BoringSSL static libs not found — running build-xquic.sh to produce them ===" - "$SCRIPT_DIR/build-xquic.sh" -fi -if [ ! -f "$BSSL_BUILD/libssl.a" ] || [ ! -f "$BSSL_BUILD/libcrypto.a" ]; then - echo "ERROR: BoringSSL static libs still missing at $BSSL_BUILD after build-xquic.sh." >&2 - exit 1 -fi -if [ ! -f "$BSSL_INCLUDE/openssl/ssl.h" ]; then - echo "ERROR: BoringSSL headers missing at $BSSL_INCLUDE (no openssl/ssl.h)." >&2 - exit 1 -fi - -BSSL_COMMIT="$(git -C "$BSSL_DIR" rev-parse HEAD 2>/dev/null || echo '')" -echo "=== Using BoringSSL at $BSSL_DIR (commit $BSSL_COMMIT) ===" -echo " include: $BSSL_INCLUDE" -echo " libs: $BSSL_BUILD/libssl.a ; $BSSL_BUILD/libcrypto.a" - -mkdir -p "$SRC_DIR" "$PREFIX" -export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig:$PREFIX/lib64/pkgconfig:${PKG_CONFIG_PATH:-}" - -# ---------- 1. nghttp3 (no TLS dependency) ---------- - -NGHTTP3_SRC="$SRC_DIR/nghttp3" -if [ ! -f "$NGHTTP3_SRC/CMakeLists.txt" ]; then - echo "=== Cloning nghttp3 ($NGHTTP3_TAG) ===" - git clone --recursive --depth 1 --branch "$NGHTTP3_TAG" \ - https://github.com/ngtcp2/nghttp3.git "$NGHTTP3_SRC" -fi -if [ ! -f "$PREFIX/lib/pkgconfig/libnghttp3.pc" ] && \ - [ ! -f "$PREFIX/lib64/pkgconfig/libnghttp3.pc" ]; then - echo "=== Building nghttp3 ===" - cmake -S "$NGHTTP3_SRC" -B "$NGHTTP3_SRC/build" \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="$PREFIX" \ - -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ - -DENABLE_LIB_ONLY=ON \ - -DENABLE_SHARED_LIB=OFF \ - -DENABLE_STATIC_LIB=ON - cmake --build "$NGHTTP3_SRC/build" -j"$NPROC" - cmake --install "$NGHTTP3_SRC/build" -else - echo "=== nghttp3 already installed in $PREFIX — skipping ===" -fi - -# ---------- 2. ngtcp2 (BoringSSL crypto backend, explicit paths) ---------- - -NGTCP2_SRC="$SRC_DIR/ngtcp2" -if [ ! -f "$NGTCP2_SRC/CMakeLists.txt" ]; then - echo "=== Cloning ngtcp2 ($NGTCP2_TAG) ===" - git clone --recursive --depth 1 --branch "$NGTCP2_TAG" \ - https://github.com/ngtcp2/ngtcp2.git "$NGTCP2_SRC" -fi -if [ ! -f "$PREFIX/lib/pkgconfig/libngtcp2.pc" ] && \ - [ ! -f "$PREFIX/lib64/pkgconfig/libngtcp2.pc" ]; then - echo "=== Building ngtcp2 (BoringSSL crypto backend) ===" - # BoringSSL is static + un-installed + no .pc, so feed ngtcp2's cmake the - # paths explicitly (semicolon-separated CMake list for the two .a files). - set +e - cmake -S "$NGTCP2_SRC" -B "$NGTCP2_SRC/build" \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="$PREFIX" \ - -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ - -DENABLE_LIB_ONLY=ON \ - -DENABLE_SHARED_LIB=OFF \ - -DENABLE_STATIC_LIB=ON \ - -DENABLE_OPENSSL=OFF \ - -DENABLE_BORINGSSL=ON \ - -DBORINGSSL_INCLUDE_DIR="$BSSL_INCLUDE" \ - -DBORINGSSL_LIBRARIES="$BSSL_BUILD/libssl.a;$BSSL_BUILD/libcrypto.a" - cfg_rc=$? - set -e - if [ "$cfg_rc" -ne 0 ]; then - echo >&2 "" - echo >&2 "ERROR: ngtcp2 ($NGTCP2_TAG) cmake REJECTED the pinned BoringSSL" - echo >&2 " ($BSSL_COMMIT at $BSSL_DIR) — almost always a QUIC-API mismatch." - echo >&2 " Bump the BoringSSL commit (scripts/build-xquic.sh + ci.yml cache keys) to" - echo >&2 " one compatible with both xquic AND ngtcp2 $NGTCP2_TAG, re-run build-xquic.sh," - echo >&2 " then re-run this. Not continuing — the libcurl would lack working HTTP/3." - exit 1 - fi - cmake --build "$NGTCP2_SRC/build" -j"$NPROC" - cmake --install "$NGTCP2_SRC/build" -else - echo "=== ngtcp2 already installed in $PREFIX — skipping ===" -fi - -# Sanity: the BoringSSL crypto helper must have been built (it is the proof the -# BoringSSL backend was actually enabled, not silently skipped). -if ! ls "$PREFIX"/lib*/libngtcp2_crypto_boringssl.a >/dev/null 2>&1; then - echo "ERROR: libngtcp2_crypto_boringssl.a not found in $PREFIX — the BoringSSL" >&2 - echo " crypto backend did not build. Cannot link an h3 libcurl." >&2 - exit 1 -fi - -# ---------- 3. libcurl (SHARED, with ngtcp2 + nghttp3 over BoringSSL) ---------- - -CURL_SRC="$SRC_DIR/curl-$CURL_VER" -CURL_TARBALL="$SRC_DIR/curl-$CURL_VER.tar.gz" -if [ ! -f "$CURL_SRC/configure" ]; then - if [ ! -f "$CURL_TARBALL" ]; then - echo "=== Downloading curl $CURL_VER release tarball (ships ./configure) ===" - # Release tarball has a pre-generated configure → no autoreconf needed. - curl -fSL -o "$CURL_TARBALL" \ - "https://github.com/curl/curl/releases/download/curl-${CURL_VER//./_}/curl-$CURL_VER.tar.gz" - fi - echo "=== Extracting curl $CURL_VER ===" - tar -C "$SRC_DIR" -xzf "$CURL_TARBALL" -fi - -# Stage a conventional OpenSSL-layout dir for BoringSSL (lib/ + include/), via -# symlinks (zero copy). EMPIRICAL DISCOVERY (config.log): the env form -# OPENSSL_CFLAGS/OPENSSL_LIBS is consumed ONLY when curl finds OpenSSL via -# pkg-config; BoringSSL has no .pc, so `--with-openssl` (bare) fell back to a -# pathless `-lssl -lcrypto` and curl's QUIC probe link failed with -# "undefined reference to SSL_set_quic_use_legacy_codepoint" — the symbol IS in -# libssl.a, the linker just had no -L to find it. `--with-openssl=` makes -# curl add `-L/lib -I/include`, which resolves it. (Plan Step 1.4: -# "configure's own error dictates the staged-prefix alternative.") -BSSL_STAGE="$H3_DIR/boringssl-stage" -mkdir -p "$BSSL_STAGE/lib" -ln -sf "$BSSL_BUILD/libssl.a" "$BSSL_STAGE/lib/libssl.a" -ln -sf "$BSSL_BUILD/libcrypto.a" "$BSSL_STAGE/lib/libcrypto.a" -ln -sfn "$BSSL_INCLUDE" "$BSSL_STAGE/include" - -if [ ! -x "$PREFIX/bin/curl" ] || ! "$PREFIX/bin/curl" --version 2>/dev/null | grep -qi HTTP3; then - echo "=== Building libcurl $CURL_VER (--enable-shared, h3 via ngtcp2/nghttp3) ===" - # ngtcp2/nghttp3 are found via PKG_CONFIG_PATH ($PREFIX/lib*/pkgconfig). - # BoringSSL is fed as a path prefix (see staging note above). - # BoringSSL's libssl.a/libcrypto.a are C++ ARCHIVES (built from .cc) that - # reference the C++ runtime (__cxa_*, std::exception vtables, operator - # delete, __gxx_personality_v0). curl's autoconf probes link with the C - # driver and no C++ runtime, so "checking for SSL_connect in -lssl" fails - # with a wall of undefined C++ symbols → "OpenSSL libs not found". Feed the - # C++ runtime + pthread via LIBS so the probes (and the final libcurl.so) - # link. (This tree's other targets likewise link `stdc++` — CMakeLists.txt.) - ( - cd "$CURL_SRC" - ./configure \ - --prefix="$PREFIX" \ - --enable-shared \ - --disable-static \ - --with-openssl="$BSSL_STAGE" \ - --with-ngtcp2 \ - --with-nghttp3 \ - --without-libpsl \ - LIBS="-lstdc++ -lpthread" - make -j"$NPROC" - make install - ) -else - echo "=== libcurl already built with HTTP3 in $PREFIX — skipping ===" -fi - -# ---------- 4. Verify HTTP/3 is actually present ---------- - -echo "" -echo "=== Verifying $PREFIX/bin/curl reports HTTP3 ===" -if ! "$PREFIX/bin/curl" --version | grep -qi HTTP3; then - echo "ERROR: built curl does NOT report HTTP3 in its features line:" >&2 - "$PREFIX/bin/curl" --version >&2 - exit 1 -fi -"$PREFIX/bin/curl" --version | head -2 - -echo "" -echo "Build complete: $PREFIX/lib/libcurl.so (HTTP3 enabled)" -echo "Configure mqproxy with:" -echo " cmake -S \"$REPO_ROOT\" -B \"$REPO_ROOT/build\" \\" -echo " -DXQUIC_BUILD_DIR=\"$XQUIC_DIR/build\" \\" -echo " -DMQPROXY_H3_CURL=\"$PREFIX\"" diff --git a/scripts/build-xquic.sh b/scripts/build-xquic.sh index 393ebe1c..0459ceb8 100755 --- a/scripts/build-xquic.sh +++ b/scripts/build-xquic.sh @@ -1,35 +1,7 @@ #!/usr/bin/env bash -# -# build-xquic.sh — build mqproxy's in-tree xquic submodule with qlog enabled. -# -# WHY THIS EXISTS: -# mqproxy currently links the xquic build that lives under the SIBLING mqvpn -# checkout (mqvpn/third_party/xquic/build). That coupling is a stopgap. The -# clean end-state is for mqproxy to build its OWN submodule -# (third_party/xquic, pinned to the same commit as mqvpn) so the two repos -# are decoupled. This script is that decoupling: it builds -# third_party/xquic into third_party/xquic/build/libxquic.so. -# -# It mirrors mqvpn/build.sh's BoringSSL + xquic steps, with one critical -# addition: -DXQC_ENABLE_EVENT_LOG=ON. -# -# WHY -DXQC_ENABLE_EVENT_LOG=ON IS REQUIRED: -# The qlog-based milestone 1-B benchmark (tests/integration/e2e_multipath.sh) -# and the unit test `test_qlog_blocked` both assert on qlog EXTRA-importance -# events (frames_processed, xqc_parse_*_blocked_frame). Those events are only -# emitted when xquic is compiled with XQC_ENABLE_EVENT_LOG=ON. A stock xquic -# build will make those assertions vacuous (empty qlog) or fail outright. -# => Any xquic you point mqproxy at for those tests MUST have this flag on. -# -# USAGE: -# scripts/build-xquic.sh [--clean] -# Then configure mqproxy against the produced build: -# cmake -S . -B build \ -# -DXQUIC_BUILD_DIR="$(pwd)/third_party/xquic/build" -# -# REQUIREMENTS: cmake, make, cc, git, time + network (submodule init on first -# run). Not run by CI automatically — it is the privileged/one-time bootstrap. -# +# Build the pinned xquic fork for its standalone CUnit suite and diagnostics. +# The Rust application builds its own static xquic/BoringSSL through Cargo. +# Requires cmake, make, C/C++, Go and git. Usage: scripts/build-xquic.sh [--clean] set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -118,6 +90,3 @@ make -C "$XQUIC_BUILD" -j"$NPROC" echo "" echo "Build complete: $XQUIC_BUILD/libxquic.so" -echo "Configure mqproxy with:" -echo " cmake -S \"$REPO_ROOT\" -B \"$REPO_ROOT/build\" \\" -echo " -DXQUIC_BUILD_DIR=\"$XQUIC_BUILD\"" diff --git a/scripts/ci_benchmarks/ci_bench_env.sh b/scripts/ci_benchmarks/ci_bench_env.sh index 17c833c5..b2971e75 100755 --- a/scripts/ci_benchmarks/ci_bench_env.sh +++ b/scripts/ci_benchmarks/ci_bench_env.sh @@ -22,7 +22,7 @@ CI_BENCH_ENV_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${CI_BENCH_ENV_DIR}/../../" && pwd)" # ── Binary paths (overridable) ── -MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/build/mqproxy}" +MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/target/release/mqproxy}" MQPROXY_CERT="${MQPROXY_CERT:-${REPO_ROOT}/tests/certs/test.crt}" MQPROXY_KEY="${MQPROXY_KEY:-${REPO_ROOT}/tests/certs/test.key}" diff --git a/scripts/ci_benchmarks/ci_bench_gateway.sh b/scripts/ci_benchmarks/ci_bench_gateway.sh index e80618a4..733e1a8f 100755 --- a/scripts/ci_benchmarks/ci_bench_gateway.sh +++ b/scripts/ci_benchmarks/ci_bench_gateway.sh @@ -1,38 +1,9 @@ #!/usr/bin/env bash -# ci_bench_gateway.sh — HTTP gateway benchmark, Rust vs C (spec §10.6). -# -# Topology: ci_bench_env.sh's two netns, both paths, NO netem (unshaped). The -# server netns hosts the mqproxy gateway server and the Go TLS/H1 origin -# (bench_origin_server.go on 127.0.0.1, an 8 MiB blob plus the PUT/POST /sink -# upload sink); the client netns hosts a gateway-only mqproxy client and the -# curl load, every transfer a `POST /_mqproxy/fetch` with `X-Mq-Target`. -# -# Cells: {download, upload} × P ∈ {1, 4, 16}. Each run starts a fresh server + -# client pair and keeps P curl loops busy for DURATION seconds, one 8 MiB -# transfer after another; no transfer starts after the deadline and the last -# ones run to completion (none is cancelled: cancelling a download mid-body -# crashes the C server inside libcurl), so the window is t0 .. last transfer -# done. Metrics per run: throughput (Mbps) and bits per CPU-second of each -# mqproxy process and of both together (/proc//stat utime+stime deltas, -# all threads). C runs first, then Rust, REPEAT runs per cell, median per -# cell. The ratio table is Rust / C. -# -# Output: ci_bench_results/gateway_.json -# Exit: non-zero when any cell's throughput ratio is below GATE (0.95), when a -# transfer failed, a worker left no output or a process died, when a cell -# moved nothing, or when any of the 6 cells lacks REPEAT runs of either -# binary. bits/CPU-s is reported, not gated. -# -# Usage: sudo MQPROXY_BIN_C=build/mqproxy MQPROXY_BIN_RUST=target/release/mqproxy \ -# bash scripts/ci_benchmarks/ci_bench_gateway.sh -# -# Env: -# MQPROXY_BIN_C, MQPROXY_BIN_RUST the two binaries (defaults: build/mqproxy, -# target/release/mqproxy) -# MQPROXY_CERT/KEY tunnel TLS cert/key (default: tests/certs/test.*) -# REPEAT runs per cell (default: 3) -# DURATION seconds per run (default: 10) -# CI_BENCH_RESULTS output directory (default: ci_bench_results/) +# Rust HTTP gateway benchmark: download/upload x P={1,4,16}, unshaped two paths. +# Each cell must complete REPEAT positive-throughput runs with no failed transfers. +# Reports throughput and bits per CPU-second. C comparison ended with SP5. +# Usage: sudo bash scripts/ci_benchmarks/ci_bench_gateway.sh [path/to/mqproxy] +# Env: REPEAT=3, DURATION=10, CI_BENCH_RESULTS=ci_bench_results/ set -uo pipefail @@ -40,8 +11,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=scripts/ci_benchmarks/ci_bench_env.sh source "${SCRIPT_DIR}/ci_bench_env.sh" -BIN_C="$(realpath -m "${MQPROXY_BIN_C:-${REPO_ROOT}/build/mqproxy}")" -BIN_RUST="$(realpath -m "${MQPROXY_BIN_RUST:-${REPO_ROOT}/target/release/mqproxy}")" +BIN_RUST="$(realpath -m "${1:-${MQPROXY_BIN_RUST:-${MQPROXY_BIN}}}")" ORIGIN_CERT="${REPO_ROOT}/tests/certs/origin.crt" ORIGIN_KEY="${REPO_ROOT}/tests/certs/origin.key" ORIGIN_CA="${REPO_ROOT}/tests/certs/origin-ca.crt" @@ -51,7 +21,6 @@ REPEAT="${REPEAT:-3}" case "${REPEAT}" in ''|*[!0-9]*|0) echo "error: REPEAT must be an integer >= 1 (got '${REPEAT}')" >&2; exit 1 ;; esac -GATE=0.95 DIRECTIONS=(download upload) STREAM_COUNTS=(1 4 16) GW_PORT=18080 @@ -72,7 +41,7 @@ if [ "$(id -u)" -ne 0 ]; then echo "error: requires root (netns)" >&2 exit 1 fi -for f in "${BIN_C}" "${BIN_RUST}"; do +for f in "${BIN_RUST}"; do [ -x "${f}" ] || { echo "error: binary not found or not executable: ${f}" >&2; exit 1; } done for f in "${MQPROXY_CERT}" "${MQPROXY_KEY}" "${ORIGIN_CERT}" "${ORIGIN_KEY}" "${ORIGIN_CA}"; do @@ -183,8 +152,7 @@ start_gateway() { echo "" echo "================================================================" -echo " CI Gateway Benchmark (Rust vs C)" -echo " C: ${BIN_C}" +echo " CI Gateway Benchmark (Rust)" echo " Rust: ${BIN_RUST}" echo " Params: ${DURATION}s, P=${STREAM_COUNTS[*]}, REPEAT=${REPEAT}, no netem, 8 MiB transfers" echo " Commit: ${CI_BENCH_COMMIT}" @@ -195,68 +163,66 @@ RAW="${_CB_WORK_DIR}/raw.tsv" : > "${RAW}" export CB_TOKEN TARGET_DL TARGET_UL FETCH_URL _CB_WORK_DIR -for impl in c rust; do - bin="${BIN_C}"; [ "${impl}" = rust ] && bin="${BIN_RUST}" - for dir in "${DIRECTIONS[@]}"; do - for p in "${STREAM_COUNTS[@]}"; do - for rep in $(seq 1 "${REPEAT}"); do - # A fresh pair per run: no run inherits another's state (or crash). - run="${impl}-${dir}-P${p}-${rep}" - start_gateway "${bin}" "${run}" || exit 1 - out="${_CB_WORK_DIR}/xfer-${run}.txt" - : > "${out}" - c0=$(cpu_ticks "${_CB_CLIENT_PID}"); s0=$(cpu_ticks "${_CB_SERVER_PID}") - t0=$(date +%s%N) - end=$(( t0 + DURATION * 1000000000 )) - pids=() - for w in $(seq 1 "${p}"); do - ip netns exec "${CB_NS_CLIENT}" bash -c "$(declare -f load_worker); load_worker \"\$@\"" _ \ - "${dir}" "${end}" "${out}.${w}" & - pids+=($!) - done - wait "${pids[@]}" - t1=$(date +%s%N) - c1=$(cpu_ticks "${_CB_CLIENT_PID}"); s1=$(cpu_ticks "${_CB_SERVER_PID}") - # A worker that left no output file counts as a failure. - missing=0 - for w in $(seq 1 "${p}"); do - [ -f "${out}.${w}" ] || missing=$(( missing + 1 )) - done - cat "${out}".* > "${out}" 2>/dev/null - # Count completed 200s; anything else is a failure. - read -r nbytes ok errs < <(awk -v d="${dir}" ' - { b = (d == "download") ? $2 : $3 } - $4 == 0 && $1 == 200 { s += b; n++; next } - { e++ } - END { printf "%d %d %d\n", s, n, e }' "${out}") - if [ "${missing}" -gt 0 ]; then - echo "error: ${run}: ${missing} of ${p} workers left no output" >&2 - errs=$(( errs + missing )) - fi - if ! kill -0 "${_CB_SERVER_PID}" 2>/dev/null || ! kill -0 "${_CB_CLIENT_PID}" 2>/dev/null; then - echo "error: ${run}: an mqproxy process died during the run" >&2 - tail -5 "${_CB_WORK_DIR}/server-${run}.log" "${_CB_WORK_DIR}/client-${run}.log" >&2 - errs=$(( errs + 1 )) - fi - printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "${impl}" "${dir}" "${p}" "${rep}" \ - "${nbytes}" "$(( t1 - t0 ))" "$(( c1 - c0 ))" "$(( s1 - s0 ))" "${errs}" >> "${RAW}" - echo " ${impl} ${dir} P=${p} rep=${rep}: $(( nbytes * 8 * 1000 / (t1 - t0) )) Mbps," \ - "${ok} complete, ${errs} failed, client_ticks=$(( c1 - c0 )) server_ticks=$(( s1 - s0 ))" - ci_bench_stop_proxy +impl=rust +bin="${BIN_RUST}" +for dir in "${DIRECTIONS[@]}"; do + for p in "${STREAM_COUNTS[@]}"; do + for rep in $(seq 1 "${REPEAT}"); do + # A fresh pair per run: no run inherits another's state (or crash). + run="${impl}-${dir}-P${p}-${rep}" + start_gateway "${bin}" "${run}" || exit 1 + out="${_CB_WORK_DIR}/xfer-${run}.txt" + : > "${out}" + c0=$(cpu_ticks "${_CB_CLIENT_PID}"); s0=$(cpu_ticks "${_CB_SERVER_PID}") + t0=$(date +%s%N) + end=$(( t0 + DURATION * 1000000000 )) + pids=() + for w in $(seq 1 "${p}"); do + ip netns exec "${CB_NS_CLIENT}" bash -c "$(declare -f load_worker); load_worker \"\$@\"" _ \ + "${dir}" "${end}" "${out}.${w}" & + pids+=($!) done + wait "${pids[@]}" + t1=$(date +%s%N) + c1=$(cpu_ticks "${_CB_CLIENT_PID}"); s1=$(cpu_ticks "${_CB_SERVER_PID}") + # A worker that left no output file counts as a failure. + missing=0 + for w in $(seq 1 "${p}"); do + [ -f "${out}.${w}" ] || missing=$(( missing + 1 )) + done + cat "${out}".* > "${out}" 2>/dev/null + # Count completed 200s; anything else is a failure. + read -r nbytes ok errs < <(awk -v d="${dir}" ' + { b = (d == "download") ? $2 : $3 } + $4 == 0 && $1 == 200 { s += b; n++; next } + { e++ } + END { printf "%d %d %d\n", s, n, e }' "${out}") + if [ "${missing}" -gt 0 ]; then + echo "error: ${run}: ${missing} of ${p} workers left no output" >&2 + errs=$(( errs + missing )) + fi + if ! kill -0 "${_CB_SERVER_PID}" 2>/dev/null || ! kill -0 "${_CB_CLIENT_PID}" 2>/dev/null; then + echo "error: ${run}: an mqproxy process died during the run" >&2 + tail -5 "${_CB_WORK_DIR}/server-${run}.log" "${_CB_WORK_DIR}/client-${run}.log" >&2 + errs=$(( errs + 1 )) + fi + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "${impl}" "${dir}" "${p}" "${rep}" \ + "${nbytes}" "$(( t1 - t0 ))" "$(( c1 - c0 ))" "$(( s1 - s0 ))" "${errs}" >> "${RAW}" + echo " ${impl} ${dir} P=${p} rep=${rep}: $(( nbytes * 8 * 1000 / (t1 - t0) )) Mbps," \ + "${ok} complete, ${errs} failed, client_ticks=$(( c1 - c0 )) server_ticks=$(( s1 - s0 ))" + ci_bench_stop_proxy done done done - -# ── JSON output + ratio table + gate ── +# ── JSON output + completion gate ── OUTPUT_FILE="${CI_BENCH_RESULTS}/gateway_$(date -u '+%Y%m%d_%H%M%S').json" python3 - "${RAW}" "${OUTPUT_FILE}" < 0 for r in runs) + ok = ok and good + print("%s: %.2f Mbps %s" % (name, cell.get("median_throughput_mbps", 0), "PASS" if good else "FAIL")) output = { "test": "gateway", "commit": "${CI_BENCH_COMMIT}", "timestamp": "$(date -u '+%Y-%m-%dT%H:%M:%SZ')", - "binaries": {"c": "${BIN_C}", "rust": "${BIN_RUST}"}, + "binaries": {"rust": "${BIN_RUST}"}, "duration_sec": ${DURATION}, "repeat": ${REPEAT}, "transfer_bytes": ${BLOB_BYTES}, "clk_tck": tck, - "gate": gate, "results": results, - "ratios": ratios, "pass": ok, } with open(out, "w") as f: @@ -323,7 +271,7 @@ rc=$? echo "" echo "================================================================" -[ "${rc}" -eq 0 ] && echo " Gateway Benchmark PASS (every throughput ratio >= ${GATE})" \ +[ "${rc}" -eq 0 ] && echo " Gateway Benchmark PASS (all cells completed, no failed transfers)" \ || echo " Gateway Benchmark FAIL" echo "================================================================" exit "${rc}" diff --git a/scripts/ci_benchmarks/ci_bench_idle.sh b/scripts/ci_benchmarks/ci_bench_idle.sh index 9e1d367b..68fa8bca 100755 --- a/scripts/ci_benchmarks/ci_bench_idle.sh +++ b/scripts/ci_benchmarks/ci_bench_idle.sh @@ -14,7 +14,7 @@ # Usage: sudo bash scripts/ci_benchmarks/ci_bench_idle.sh [path/to/mqproxy] # # Env: -# MQPROXY_BIN path to mqproxy binary (default: build/mqproxy) +# MQPROXY_BIN path to mqproxy binary (default: target/release/mqproxy) # MQPROXY_CERT/KEY TLS cert/key (default: tests/certs/test.*) # BENCH_LABEL name used in the output file (default: binary's parent dir) # IDLE_SEC idle window in seconds (default: 10) diff --git a/scripts/ci_benchmarks/ci_bench_mitm.sh b/scripts/ci_benchmarks/ci_bench_mitm.sh index 62e4927b..8158e16c 100755 --- a/scripts/ci_benchmarks/ci_bench_mitm.sh +++ b/scripts/ci_benchmarks/ci_bench_mitm.sh @@ -31,11 +31,11 @@ # Output: ci_bench_results/mitm_rust_vs_c_.json # # Usage: sudo bash scripts/ci_benchmarks/ci_bench_mitm.sh [path/to/mqproxy] -# sudo MQPROXY_BIN_C=build/mqproxy MQPROXY_BIN_RUST=target/release/mqproxy \ +# sudo MQPROXY_BIN_C=/path/to/legacy/mqproxy MQPROXY_BIN_RUST=target/release/mqproxy \ # bash scripts/ci_benchmarks/ci_bench_mitm.sh # # Env: -# MQPROXY_BIN path to mqproxy binary (default: build/mqproxy) +# MQPROXY_BIN path to mqproxy binary (default: target/release/mqproxy) # MQPROXY_BIN_C the C binary in Rust-vs-C mode (default: MQPROXY_BIN) # MQPROXY_BIN_RUST the Rust binary; set to enable Rust-vs-C mode # REPEAT runs per cell in Rust-vs-C mode (default: 3) @@ -49,7 +49,7 @@ set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../../" && pwd)" -MQPROXY_BIN="${1:-${MQPROXY_BIN_C:-${MQPROXY_BIN:-${REPO_ROOT}/build/mqproxy}}}" +MQPROXY_BIN="${1:-${MQPROXY_BIN_C:-${MQPROXY_BIN:-${REPO_ROOT}/target/release/mqproxy}}}" BIN_RUST="${MQPROXY_BIN_RUST:-}" MQPROXY_CERT="${MQPROXY_CERT:-${REPO_ROOT}/tests/certs/test.crt}" MQPROXY_KEY="${MQPROXY_KEY:-${REPO_ROOT}/tests/certs/test.key}" diff --git a/scripts/ci_benchmarks/ci_bench_multi_stream.sh b/scripts/ci_benchmarks/ci_bench_multi_stream.sh index 4e473684..c1b518df 100755 --- a/scripts/ci_benchmarks/ci_bench_multi_stream.sh +++ b/scripts/ci_benchmarks/ci_bench_multi_stream.sh @@ -10,7 +10,7 @@ # Usage: sudo bash scripts/ci_benchmarks/ci_bench_multi_stream.sh [path/to/mqproxy] # # Env: -# MQPROXY_BIN path to mqproxy binary (default: build/mqproxy) +# MQPROXY_BIN path to mqproxy binary (default: target/release/mqproxy) # MQPROXY_CERT/KEY TLS cert/key (default: tests/certs/test.*) # CI_BENCH_RESULTS output directory (default: ci_bench_results/) diff --git a/scripts/ci_benchmarks/ci_bench_tcp_proxy.sh b/scripts/ci_benchmarks/ci_bench_tcp_proxy.sh index 240bfb2d..76276d0d 100755 --- a/scripts/ci_benchmarks/ci_bench_tcp_proxy.sh +++ b/scripts/ci_benchmarks/ci_bench_tcp_proxy.sh @@ -14,7 +14,7 @@ # Usage: sudo bash scripts/ci_benchmarks/ci_bench_tcp_proxy.sh [path/to/mqproxy] # # Env: -# MQPROXY_BIN path to mqproxy binary (default: build/mqproxy) +# MQPROXY_BIN path to mqproxy binary (default: target/release/mqproxy) # MQPROXY_CERT TLS cert (default: tests/certs/test.crt) # MQPROXY_KEY TLS key (default: tests/certs/test.key) # CI_BENCH_RESULTS output directory (default: ci_bench_results/) diff --git a/scripts/ci_benchmarks/ci_bench_unshaped.sh b/scripts/ci_benchmarks/ci_bench_unshaped.sh index c2ef1f45..fb95c39c 100755 --- a/scripts/ci_benchmarks/ci_bench_unshaped.sh +++ b/scripts/ci_benchmarks/ci_bench_unshaped.sh @@ -17,7 +17,7 @@ # Usage: sudo bash scripts/ci_benchmarks/ci_bench_unshaped.sh [path/to/mqproxy] # # Env: -# MQPROXY_BIN path to mqproxy binary (default: build/mqproxy) +# MQPROXY_BIN path to mqproxy binary (default: target/release/mqproxy) # MQPROXY_CERT/KEY TLS cert/key (default: tests/certs/test.*) # BENCH_LABEL name used in the output file (default: binary's parent dir, # e.g. "build" for C, "release" for target/release) diff --git a/scripts/ci_benchmarks/test_gateway_summary.py b/scripts/ci_benchmarks/test_gateway_summary.py new file mode 100644 index 00000000..cfacaa10 --- /dev/null +++ b/scripts/ci_benchmarks/test_gateway_summary.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +"""Exercise the benchmark's report gate without root, network or a running proxy.""" +from pathlib import Path +import subprocess +import tempfile + +script = Path(__file__).with_name("ci_bench_gateway.sh").read_text() +report = script.split('< -#include -#include -#include /* SIZE_MAX, UINT32_MAX */ -#include -#include -#include -#include /* stat() for perms check */ - -#ifdef _MSC_VER -# define strcasecmp _stricmp -#endif - -void -mq_config_defaults(mq_file_config_t *cfg) -{ - memset(cfg, 0, sizeof(*cfg)); - cfg->max_conns = 16; - cfg->gateway_enabled = 1; - cfg->udp_enabled = 1; - cfg->udp_idle_timeout_s = 60; - cfg->reconnect = 1; - cfg->keepalive_idle_s = 30; - cfg->reconnect_max_backoff_s = 30; - snprintf(cfg->client_id, sizeof(cfg->client_id), "%s", "mqproxy"); - /* tproxy defaults: mode=redirect, fwmark=1, table=100, skip_uid=-1 (geteuid()) */ - snprintf(cfg->tproxy_mode, sizeof(cfg->tproxy_mode), "%s", "redirect"); - cfg->tproxy_fwmark = 1; - cfg->tproxy_table = 100; - cfg->tproxy_dport = 443; - cfg->tproxy_skip_uid = -1; -} - -/* ---- helpers (templated from mqvpn/src/config.c) ---- */ - -static char * -trim(char *s) -{ - while (isspace((unsigned char)*s)) - s++; - if (*s == '\0') return s; - char *end = s + strlen(s) - 1; - while (end > s && isspace((unsigned char)*end)) - *end-- = '\0'; - return s; -} - -static int -parse_bool(const char *v) -{ - return strcmp(v, "true") == 0 || strcmp(v, "yes") == 0 || strcmp(v, "1") == 0; -} - -/* strict long; returns 0 ok / -1 bad */ -static int -parse_long_strict(const char *v, long *out) -{ - if (!v || *v == '\0') return -1; - errno = 0; - char *end = NULL; - long x = strtol(v, &end, 10); - if (end == v || *end != '\0' || errno == ERANGE) return -1; - *out = x; - return 0; -} - -enum { - SEC_NONE = 0, - SEC_INTERFACE, - SEC_SERVER, - SEC_TLS, - SEC_AUTH, - SEC_MULTIPATH, - SEC_GATEWAY, - SEC_UDP, - SEC_INGRESS, - SEC_METRICS, - SEC_LOG, - SEC_MITM -}; - -static int -parse_section(const char *n) -{ - if (strcasecmp(n, "Interface") == 0) return SEC_INTERFACE; - if (strcasecmp(n, "Server") == 0) return SEC_SERVER; - if (strcasecmp(n, "TLS") == 0) return SEC_TLS; - if (strcasecmp(n, "Auth") == 0) return SEC_AUTH; - if (strcasecmp(n, "Multipath") == 0) return SEC_MULTIPATH; - if (strcasecmp(n, "Gateway") == 0) return SEC_GATEWAY; - if (strcasecmp(n, "UDP") == 0) return SEC_UDP; - if (strcasecmp(n, "Ingress") == 0) return SEC_INGRESS; - if (strcasecmp(n, "Metrics") == 0) return SEC_METRICS; - if (strcasecmp(n, "Log") == 0) return SEC_LOG; - if (strcasecmp(n, "Mitm") == 0) return SEC_MITM; - return -1; -} - -/* warn + skip for a key that belongs to the other mode */ -#define WRONG_MODE(sec) \ - do { \ - MQ_LOGW("%s:%d: key '%s' in [%s] not valid for this mode; ignoring", path, \ - lineno, key, sec); \ - } while (0) - -static void -handle_kv(mq_file_config_t *cfg, int section, const char *key, const char *val, - int lineno, const char *path, int is_server) -{ -/* CSTR copies a string field; LONGV parses bounded ints leniently. - * Booleans use parse_bool() inline (no macro needed). */ -#define CSTR(field) snprintf(cfg->field, sizeof(cfg->field), "%s", val) -#define LONGV(field, lo, hi) \ - do { \ - long _v; \ - if (parse_long_strict(val, &_v) < 0 || _v < (lo) || _v > (hi)) \ - MQ_LOGW("%s:%d: invalid %s '%s'; keeping default", path, lineno, key, val); \ - else \ - cfg->field = _v; \ - } while (0) - - switch (section) { - case SEC_INTERFACE: - if (strcasecmp(key, "Listen") == 0) { - if (is_server) - CSTR(listen); - else - WRONG_MODE("Interface"); - } else if (strcasecmp(key, "MaxConns") == 0) { - /* Mirror the CLI's unsigned-domain guard (cli/main.c --max-conns): - * compare in unsigned long long to stay correct where long is 32-bit. */ - if (!is_server) - WRONG_MODE("Interface"); - else { - long _v; - if (parse_long_strict(val, &_v) < 0 || _v < 0 || - (unsigned long long)_v > (unsigned long long)UINT32_MAX) - MQ_LOGW("%s:%d: invalid MaxConns '%s'; keeping default", path, lineno, - val); - else - cfg->max_conns = _v; - } - } else if (strcasecmp(key, "KeepaliveIdle") == 0) { - if (!is_server) - LONGV(keepalive_idle_s, 0, LONG_MAX); - else - WRONG_MODE("Interface"); - } else if (strcasecmp(key, "Reconnect") == 0) { - if (!is_server) - cfg->reconnect = parse_bool(val); - else - WRONG_MODE("Interface"); - } else if (strcasecmp(key, "ReconnectMaxBackoff") == 0) { - if (!is_server) - LONGV(reconnect_max_backoff_s, 1, LONG_MAX); - else - WRONG_MODE("Interface"); - } else - MQ_LOGW("%s:%d: unknown key '%s' in [Interface]", path, lineno, key); - break; - case SEC_SERVER: /* client-side: the server the client dials */ - if (is_server) - WRONG_MODE("Server"); - else if (strcasecmp(key, "Address") == 0) - CSTR(server_addr); - else if (strcasecmp(key, "ClientId") == 0) - CSTR(client_id); - else - MQ_LOGW("%s:%d: unknown key '%s' in [Server]", path, lineno, key); - break; - case SEC_TLS: - if (!is_server) - WRONG_MODE("TLS"); - else if (strcasecmp(key, "Cert") == 0) - CSTR(cert); - else if (strcasecmp(key, "Key") == 0) - CSTR(key); /* cfg->key (TLS field) */ - else - MQ_LOGW("%s:%d: unknown key '%s' in [TLS]", path, lineno, key); - break; - case SEC_AUTH: - if (strcasecmp(key, "Key") == 0) - CSTR(token); - else - MQ_LOGW("%s:%d: unknown key '%s' in [Auth]", path, lineno, key); - break; - case SEC_MULTIPATH: - if (strcasecmp(key, "CC") == 0) - CSTR(cc); - else if (strcasecmp(key, "Scheduler") == 0) - CSTR(scheduler); - else if (strcasecmp(key, "Path") == 0) { - if (is_server) { - WRONG_MODE("Multipath"); - } else if (cfg->n_paths < MQ_CONFIG_MAX_PATHS) { - snprintf(cfg->paths[cfg->n_paths], sizeof(cfg->paths[0]), "%s", val); - cfg->n_paths++; - } else - MQ_LOGW("%s:%d: max %d paths; ignoring '%s'", path, lineno, - MQ_CONFIG_MAX_PATHS, val); - } else - MQ_LOGW("%s:%d: unknown key '%s' in [Multipath]", path, lineno, key); - break; - case SEC_GATEWAY: - if (!is_server) - WRONG_MODE("Gateway"); - else if (strcasecmp(key, "Enabled") == 0) - cfg->gateway_enabled = parse_bool(val); - else if (strcasecmp(key, "Masquerade") == 0) - cfg->masquerade = parse_bool(val); - else if (strcasecmp(key, "OriginCA") == 0) - CSTR(origin_ca); - else if (strcasecmp(key, "CacheMaxBytes") == 0) { - /* mirror CLI SIZE_MAX guard (cli/main.c --cache-max-bytes). NOTE: - * parse_long_strict uses strtol/long; on the 64-bit target this is - * identical to the CLI's strtoll. On a 32-bit-long platform it would - * reject byte counts > LONG_MAX the CLI accepts — acceptable (rejects - * more, never accepts what the CLI refuses; spec §5.2 floor holds). */ - long v; - if (parse_long_strict(val, &v) < 0 || v < 0 || - (unsigned long long)v > (unsigned long long)SIZE_MAX) - MQ_LOGW("%s:%d: invalid CacheMaxBytes '%s'; keeping default", path, - lineno, val); - else - cfg->cache_max_bytes = (size_t)v; - } else - MQ_LOGW("%s:%d: unknown key '%s' in [Gateway]", path, lineno, key); - break; - case SEC_UDP: - if (!is_server) - WRONG_MODE("UDP"); - else if (strcasecmp(key, "Enabled") == 0) - cfg->udp_enabled = parse_bool(val); - else if (strcasecmp(key, "IdleTimeout") == 0) - LONGV(udp_idle_timeout_s, 1, LONG_MAX); - else - MQ_LOGW("%s:%d: unknown key '%s' in [UDP]", path, lineno, key); - break; - case SEC_INGRESS: - if (is_server) - WRONG_MODE("Ingress"); - else if (strcasecmp(key, "Socks5") == 0) - CSTR(socks5); - else if (strcasecmp(key, "HttpConnect") == 0) - CSTR(http_connect); - else if (strcasecmp(key, "Gateway") == 0) - CSTR(gw_listen); - else if (strcasecmp(key, "TProxy") == 0) - CSTR(tproxy); - else if (strcasecmp(key, "Mode") == 0) - CSTR(tproxy_mode); - else if (strcasecmp(key, "Fwmark") == 0) - /* capped at INT_MAX: applied as int (mq_tproxy_setup_t.fwmark). */ - LONGV(tproxy_fwmark, 1, INT_MAX); - else if (strcasecmp(key, "Table") == 0) - LONGV(tproxy_table, 1, 65535); - else if (strcasecmp(key, "Dport") == 0) - LONGV(tproxy_dport, 1, 65535); - else if (strcasecmp(key, "SetupRedirect") == 0) - cfg->setup_redirect = parse_bool(val); - else if (strcasecmp(key, "SkipUid") == 0) - /* capped at INT_MAX: narrowed to uid_t at apply; reserves (uid_t)-1 - * as the internal "use geteuid()" sentinel, not a user value. */ - LONGV(tproxy_skip_uid, -1, INT_MAX); - else - MQ_LOGW("%s:%d: unknown key '%s' in [Ingress]", path, lineno, key); - break; - case SEC_METRICS: - if (strcasecmp(key, "Interval") == 0) - LONGV(metrics_interval_s, 0, LONG_MAX); - else if (strcasecmp(key, "PerRequest") == 0) { - if (is_server) - cfg->request_metrics = parse_bool(val); - else - WRONG_MODE("Metrics"); - } else - MQ_LOGW("%s:%d: unknown key '%s' in [Metrics]", path, lineno, key); - break; - case SEC_LOG: - if (strcasecmp(key, "QLog") == 0) - CSTR(qlog); - else - MQ_LOGW("%s:%d: unknown key '%s' in [Log]", path, lineno, key); - break; - case SEC_MITM: /* client-only (Phase 7 Slice 3) */ - if (is_server) - WRONG_MODE("Mitm"); - else if (strcasecmp(key, "Enabled") == 0) - cfg->mitm_enabled = parse_bool(val); - else if (strcasecmp(key, "CACert") == 0) - CSTR(ca_cert); - else if (strcasecmp(key, "CAKey") == 0) - CSTR(ca_key); - else if (strcasecmp(key, "IgnoreHosts") == 0) { - /* repeatable: mirrors [Multipath] Path accumulation */ - if (cfg->n_ignore_hosts < MQ_CONFIG_MAX_IGNORE_HOSTS) { - snprintf(cfg->ignore_hosts[cfg->n_ignore_hosts], - sizeof(cfg->ignore_hosts[0]), "%s", val); - cfg->n_ignore_hosts++; - } else - MQ_LOGW("%s:%d: max %d ignore hosts; ignoring '%s'", path, lineno, - MQ_CONFIG_MAX_IGNORE_HOSTS, val); - } else - MQ_LOGW("%s:%d: unknown key '%s' in [Mitm]", path, lineno, key); - break; - default: MQ_LOGW("%s:%d: key '%s' outside any section", path, lineno, key); break; - } -#undef CSTR -#undef LONGV -} - -int -mq_config_load(mq_file_config_t *cfg, const char *path, int is_server) -{ - FILE *fp = fopen(path, "r"); - if (!fp) { - MQ_LOGE("config: cannot open '%s'", path); - return -1; - } - fseek(fp, 0, SEEK_END); - long sz = ftell(fp); - if (sz < 0) { - fclose(fp); - return -1; - } - rewind(fp); - char *buf = malloc((size_t)sz + 1); - if (!buf) { - fclose(fp); - return -1; - } - size_t got = fread(buf, 1, (size_t)sz, fp); - fclose(fp); - buf[got] = '\0'; - - int lineno = 0, section = SEC_NONE; - for (char *line = strtok(buf, "\n"); line; line = strtok(NULL, "\n")) { - lineno++; - char *t = trim(line); - if (*t == '\0' || *t == '#' || *t == ';') continue; - if (*t == '[') { - char *end = strchr(t, ']'); - if (!end) { - MQ_LOGW("%s:%d: malformed section header", path, lineno); - continue; - } - *end = '\0'; - int sec = parse_section(t + 1); - if (sec < 0) { - MQ_LOGW("%s:%d: unknown section '%s'", path, lineno, t + 1); - section = SEC_NONE; - } else - section = sec; - continue; - } - char *eq = strchr(t, '='); - if (!eq) { - MQ_LOGW("%s:%d: malformed line (no '=')", path, lineno); - continue; - } - *eq = '\0'; - char *k = trim(t), *v = trim(eq + 1); - if (*v == '\0') continue; /* empty value: treat as not set */ - handle_kv(cfg, section, k, v, lineno, path, is_server); - } - free(buf); - return 0; -} - -int -mq_config_perms_insecure(const char *path) -{ - struct stat st; - if (stat(path, &st) != 0) return 0; /* can't check -> don't warn */ - return (st.st_mode & 0077) ? 1 : 0; -} diff --git a/src/config/mq_config.h b/src/config/mq_config.h deleted file mode 100644 index af8d521b..00000000 --- a/src/config/mq_config.h +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// mq_config.h — WireGuard-style INI config for mqproxy (server + client). -// Templated on mqvpn/src/config.c conventions; NOT linked to mqvpn. -#ifndef MQ_CONFIG_H -#define MQ_CONFIG_H - -#include -#include - -#define MQ_CONFIG_MAX_PATHS 8 /* must equal MQ_MAX_EXTRA_PATHS (cli/main.c) */ -/* must equal MQ_IGNORE_HOSTS_MAX (mitm/mq_ignore_hosts.h); kept in sync */ -#define MQ_CONFIG_MAX_IGNORE_HOSTS 256 - -typedef struct mq_file_config_t { - /* shared */ - char token[256]; /* [Auth] Key */ - char cc[16]; /* [Multipath] CC */ - char scheduler[16]; /* [Multipath] Scheduler */ - char qlog[256]; /* [Log] QLog */ - long metrics_interval_s; /* [Metrics] Interval (seconds; 0 = off) */ - - /* server */ - char listen[280]; /* [Interface] Listen */ - long max_conns; /* [Interface] MaxConns */ - char cert[256]; /* [TLS] Cert */ - char key[256]; /* [TLS] Key */ - int gateway_enabled; /* [Gateway] Enabled */ - int masquerade; /* [Gateway] Masquerade */ - char origin_ca[256]; /* [Gateway] OriginCA */ - size_t cache_max_bytes; /* [Gateway] CacheMaxBytes */ - int udp_enabled; /* [UDP] Enabled */ - long udp_idle_timeout_s; /* [UDP] IdleTimeout */ - int request_metrics; /* [Metrics] PerRequest */ - - /* client */ - char server_addr[280]; /* [Server] Address */ - char client_id[64]; /* [Server] ClientId */ - char socks5[280]; /* [Ingress] Socks5 */ - char http_connect[280]; /* [Ingress] HttpConnect */ - char gw_listen[280]; /* [Ingress] Gateway */ - char paths[MQ_CONFIG_MAX_PATHS][64]; /* [Multipath] Path (repeatable) */ - int n_paths; - long keepalive_idle_s; /* [Interface] KeepaliveIdle */ - int reconnect; /* [Interface] Reconnect */ - long reconnect_max_backoff_s; /* [Interface] ReconnectMaxBackoff */ - char tproxy[280]; /* [Ingress] TProxy (ip:port string) */ - char tproxy_mode[16]; /* [Ingress] Mode ("redirect" | "tproxy") */ - long tproxy_fwmark; /* [Ingress] Fwmark (default 1) */ - long tproxy_table; /* [Ingress] Table (default 100) */ - long tproxy_dport; /* [Ingress] Dport — captured TCP dport (default 443) */ - int setup_redirect; /* [Ingress] SetupRedirect (default false) */ - long tproxy_skip_uid; /* [Ingress] SkipUid (default -1 = geteuid()) */ - - /* client — MITM (Phase 7 Slice 3) */ - int mitm_enabled; /* [Mitm] Enabled */ - char ca_cert[256]; /* [Mitm] CACert */ - char ca_key[256]; /* [Mitm] CAKey */ - /* [Mitm] IgnoreHosts (repeatable); 256-wide holds a normalized host, - * mq_ignore_hosts re-normalizes on add. */ - char ignore_hosts[MQ_CONFIG_MAX_IGNORE_HOSTS][256]; - int n_ignore_hosts; -} mq_file_config_t; - -/* Fill cfg with the same defaults as the CLI locals. */ -void mq_config_defaults(mq_file_config_t *cfg); - -/* Overlay INI file at path onto cfg. is_server selects which mode's keys are - * accepted; other-mode keys warn-and-skip. Returns 0 on success, -1 if the - * file cannot be opened/read (caller should treat as fatal). Bad values inside - * a readable file warn and are skipped (not fatal). */ -int mq_config_load(mq_file_config_t *cfg, const char *path, int is_server); - -/* Spec §5.4 secret hygiene: returns 1 if path is readable by group or other - * (st_mode & 0077), else 0 (incl. on stat failure — never warn about something - * we cannot check). Pure + testable; caller emits the chmod-0600 warning. */ -int mq_config_perms_insecure(const char *path); - -#endif /* MQ_CONFIG_H */ diff --git a/src/gateway/mq_fetch_listener.c b/src/gateway/mq_fetch_listener.c deleted file mode 100644 index 97f85aa3..00000000 --- a/src/gateway/mq_fetch_listener.c +++ /dev/null @@ -1,763 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_fetch_listener.c — local fetch-API listener (POST /_mqproxy/fetch). - * - * Mirrors mq_listener's socket/bind/listen + EV_READ accept loop, but each - * connection is a single request/response (Connection: close). After the head - * parses, body bytes are streamed to the gateway via mq_fetch_cbs_t with - * backpressure both ways; the gateway writes the response back through the - * per-conn handle ops. - * - * See mq_fetch_listener.h for the fd/handle ownership contract and the request - * lifecycle. - */ -#include "gateway/mq_fetch_listener.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "util/mq_log.h" - -/* The request method+path we accept; anything else is a listener-owned 404. */ -#define MQ_FETCH_METHOD "POST" -#define MQ_FETCH_PATH "/_mqproxy/fetch" - -/* Output high/low watermarks for the response side. When queued output exceeds - * the high watermark, mq_fetch_conn_write returns 0 (the gateway stops feeding) - * and the drain cb fires once the queue falls below the low watermark. */ -#define MQ_FETCH_OUT_HIGHWATER (256 * 1024) -#define MQ_FETCH_OUT_LOWWATER (64 * 1024) - -/* Hard ceiling on the queued output buffer. A write that would push the queue - * (or its required capacity) above this is rejected with -1: it bounds memory - * for a stalled/slow client and is well above the high watermark, so a - * well-behaved gateway that stops on the 0 (high-watermark) return never hits - * it. */ -#define MQ_FETCH_OUT_MAX (4u * 1024 * 1024) - -/* Read chunk size for body streaming. */ -#define MQ_FETCH_RD_CHUNK 16384 - -struct mq_fetch_listener_s { - struct event_base *base; - int listen_fd; - struct event *accept_ev; - uint16_t local_port; - mq_fetch_cbs_t cbs; - void *user; - struct mq_fetch_conn *conns; /* live per-conn list (intrusive) */ -}; - -/* Per-connection state. Phases: - * HEAD — accumulating the request head into rxbuf. - * BODY — head accepted by on_request; streaming body bytes to on_body. - * RESP — body done (or handler rejected): gateway owns the write side; we - * just drain output and wait for finish/abort. - */ -typedef enum { MQ_FC_HEAD, MQ_FC_BODY, MQ_FC_RESP } mq_fc_phase_t; - -struct mq_fetch_conn { - mq_fetch_listener_t *l; - int fd; /* accepted app socket; -1 once closed */ - struct event *read_ev; - struct event *write_ev; /* on-demand EV_WRITE while output is queued */ - mq_fc_phase_t phase; - - /* HEAD-phase read buffer (capped at MQ_HTTP1_MAX_HEAD). */ - uint8_t *rxbuf; - size_t rxlen; - size_t rxcap; - - /* BODY-phase accounting. */ - int64_t body_total; /* content_length to deliver */ - int64_t body_delivered; /* bytes handed to on_body so far */ - void *req_ctx; /* handler's per-request context */ - - int read_paused; /* gateway asked us to stop reading (backpressure) */ - int detached; /* finish/abort/on_aborted done: no cbs, handle dead */ - int closing; /* finish requested: close once output drains */ - - /* Growable output queue (response bytes from the gateway). */ - uint8_t *out; - size_t out_len; /* bytes queued (unsent) */ - size_t out_off; /* bytes already written from out[0..] */ - size_t out_cap; - int hw_hit; /* high watermark was hit since the last drain */ - - void (*drain_cb)(void *); - void *drain_user; - - struct mq_fetch_conn *next; -}; - -/* ── per-conn list bookkeeping ──────────────────────────────────────────── */ - -static void -conn_unlink(mq_fetch_listener_t *l, struct mq_fetch_conn *c) -{ - struct mq_fetch_conn **pp = &l->conns; - while (*pp) { - if (*pp == c) { - *pp = c->next; - return; - } - pp = &(*pp)->next; - } -} - -/* Tear down a per-conn state: free events, close the fd (listener always owns - * it), free buffers, unlink, free. Never fires cbs. */ -static void -conn_destroy(struct mq_fetch_conn *c) -{ - if (!c) return; - conn_unlink(c->l, c); - if (c->read_ev) { - event_free(c->read_ev); - c->read_ev = NULL; - } - if (c->write_ev) { - event_free(c->write_ev); - c->write_ev = NULL; - } - if (c->fd >= 0) { - close(c->fd); - c->fd = -1; - } - free(c->rxbuf); - free(c->out); - free(c); -} - -/* ── output queue / write side ──────────────────────────────────────────── */ - -/* Queued-but-unsent output bytes. */ -static size_t -out_pending(const struct mq_fetch_conn *c) -{ - return c->out_len - c->out_off; -} - -/* Append len bytes to the output queue, growing as needed. Returns 0 on - * rejection (overflow guard / hard ceiling / OOM). The caller treats 0 as a - * fatal write error (handle dies). */ -static int -out_append(struct mq_fetch_conn *c, const void *p, size_t len) -{ - if (len == 0) return 1; - /* Compact consumed prefix when it dominates, to bound growth. */ - if (c->out_off > 0 && c->out_off == c->out_len) { - c->out_len = 0; - c->out_off = 0; - } else if (c->out_off >= MQ_FETCH_OUT_LOWWATER) { - memmove(c->out, c->out + c->out_off, c->out_len - c->out_off); - c->out_len -= c->out_off; - c->out_off = 0; - } - /* Overflow + ceiling guard: reject before any arithmetic can wrap size_t. - * c->out_len is already <= MQ_FETCH_OUT_MAX (this guard ran on every prior - * append), so out_len + len cannot wrap once len is bounded below. */ - if (len > MQ_FETCH_OUT_MAX - c->out_len) return 0; - size_t need = c->out_len + len; - if (need > c->out_cap) { - size_t ncap = c->out_cap ? c->out_cap : 4096; - /* Double until it fits, guarding the doubling against size_t wrap. - * `need <= MQ_FETCH_OUT_MAX` bounds the loop. */ - while (ncap < need) { - if (ncap > SIZE_MAX / 2) { - ncap = need; /* cannot double further; use the exact size */ - break; - } - ncap *= 2; - } - uint8_t *nb = realloc(c->out, ncap); - if (!nb) return 0; - c->out = nb; - c->out_cap = ncap; - } - memcpy(c->out + c->out_len, p, len); - c->out_len += len; - return 1; -} - -/* Try to flush queued output to the socket. Returns -1 on hard error (caller - * destroys the conn), 0 otherwise. Arms/disarms the EV_WRITE event and fires - * the drain cb when crossing below the low watermark after a high-water hit. - * If closing and the queue empties, the conn is destroyed (returns -1 sentinel - * via *destroyed). */ -static int -out_flush(struct mq_fetch_conn *c, int *destroyed) -{ - if (destroyed) *destroyed = 0; - while (out_pending(c) > 0) { - ssize_t n = send(c->fd, c->out + c->out_off, out_pending(c), MSG_NOSIGNAL); - if (n > 0) { - c->out_off += (size_t)n; - continue; - } - if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - /* Socket full: arm EV_WRITE to resume on the writable edge. */ - if (c->write_ev) event_add(c->write_ev, NULL); - break; - } - if (n < 0 && errno == EINTR) continue; - /* Hard error. */ - return -1; - } - - if (out_pending(c) == 0) { - c->out_len = 0; - c->out_off = 0; - if (c->write_ev) event_del(c->write_ev); - if (c->closing) { - /* finish() flushed: close now. The handle is already detached. */ - conn_destroy(c); - if (destroyed) *destroyed = 1; - return 0; - } - } - - /* Drain notification: crossed below the low watermark after a high-water - * hit. Only meaningful before detach/closing. */ - if (c->hw_hit && !c->detached && !c->closing && - out_pending(c) <= MQ_FETCH_OUT_LOWWATER) { - c->hw_hit = 0; - if (c->drain_cb) c->drain_cb(c->drain_user); - } - return 0; -} - -static void -on_writable(evutil_socket_t fd, short what, void *user) -{ - (void)fd; - (void)what; - struct mq_fetch_conn *c = (struct mq_fetch_conn *)user; - int destroyed = 0; - if (out_flush(c, &destroyed) < 0 && !destroyed) { - conn_destroy(c); - } -} - -/* ── listener-owned direct error replies ────────────────────────────────── */ - -/* Build and queue "\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", - * then flush + close. Never fires cbs. */ -static void -reply_error_and_close(struct mq_fetch_conn *c, int code, const char *reason) -{ - char buf[128]; - int o = 0, n; - n = mq_http1_write_status(buf + o, sizeof(buf) - (size_t)o, code, reason); - if (n > 0) o += n; - n = mq_http1_write_header(buf + o, sizeof(buf) - (size_t)o, "Connection", "close"); - if (n > 0) o += n; - n = mq_http1_write_header(buf + o, sizeof(buf) - (size_t)o, "Content-Length", "0"); - if (n > 0) o += n; - if ((size_t)o + 2 <= sizeof(buf)) { - buf[o++] = '\r'; - buf[o++] = '\n'; - } - /* Best-effort blocking-ish send; the head buffer is tiny so a single send - * almost always takes it. Then close regardless. */ - (void)send(c->fd, buf, (size_t)o, MSG_NOSIGNAL); - conn_destroy(c); -} - -/* ── body streaming ─────────────────────────────────────────────────────── */ - -/* Deliver body bytes from [p, p+len) to on_body, honoring Content-Length. - * Returns: - * 1 delivered (body may now be complete); see *done, - * 0 on_body asked to pause (read paused); *done set, - * -1 the conn was destroyed (oversize abort) — caller MUST stop. - * - * Only the in-bounds portion (up to remaining Content-Length) is delivered to - * on_body in a single call, so cbs observe EXACTLY Content-Length bytes. Any - * bytes in this chunk beyond Content-Length are a protocol violation by the - * peer, but the request body is fully received: on_body_done has fired and the - * gateway owns the write side. We do NOT fire on_aborted (its contract is "peer - * died BEFORE Content-Length") and we do NOT destroy the conn from here (that - * would discard a response the gateway may be mid-flushing); we simply stop - * reading and leave teardown to the gateway's finish()/abort(). - * - * NOTE: *done is set when the caller must NOT touch `c` afterward — either the - * read is paused, or the body completed (on_body_done may have finished/aborted - * the conn), or the conn was destroyed. */ -static int -deliver_body(struct mq_fetch_conn *c, const uint8_t *p, size_t len, int *done) -{ - mq_fetch_listener_t *l = c->l; - if (done) *done = 0; - if (len == 0) return 1; - - int64_t remain = c->body_total - c->body_delivered; - size_t take = len; - if ((int64_t)take > remain) { - take = (size_t)remain; /* deliver only up to Content-Length */ - /* Extra bytes follow Content-Length: a peer protocol violation, but the - * body is complete. We stop reading after on_body_done (below) rather - * than aborting. */ - } - - if (take > 0 && l->cbs.on_body) { - int rc = l->cbs.on_body(c->req_ctx, p, take); - c->body_delivered += (int64_t)take; - if (rc == -1) { - /* Backpressure: the chunk IS consumed (consume-on-deliver - * contract); on_body just wants us to stop reading more body. - * Pause; the gateway resumes via the handle. (A paused chunk never - * coincides with oversize: take==remain only when oversize, and a - * pause there still leaves body incomplete=false — but we honor the - * pause first; the abort, if any, is moot once we stop reading.) */ - c->read_paused = 1; - if (c->read_ev) event_del(c->read_ev); - if (done) *done = 1; - return 0; - } - } else { - c->body_delivered += (int64_t)take; - } - - if (c->body_delivered >= c->body_total) { - /* Exactly Content-Length delivered. Move to RESP, then on_body_done — - * the gateway now owns the write side and will finish/abort. - * - * If this chunk also carried trailing bytes past Content-Length, those - * are a peer protocol violation, but the request is fully received: we - * just stop reading (and let the RESP-phase read event, if still armed, - * discard any further bytes). We must NOT fire on_aborted and must NOT - * destroy the conn here — the gateway may be mid-response. *done was set - * above so the caller stops touching `c`. - * - * on_body_done itself may finish/abort (detach) the conn; after it - * returns we must not touch `c` unconditionally, so stop reading only - * while still attached. */ - c->phase = MQ_FC_RESP; - if (done) *done = 1; - if (l->cbs.on_body_done) l->cbs.on_body_done(c->req_ctx); - /* Do not touch `c` past here unless we know it survived; the caller has - * *done set and will not loop. */ - } - return 1; -} - -/* ── HEAD-phase parse + dispatch ────────────────────────────────────────── */ - -/* Process whatever is in rxbuf during HEAD phase. Returns -1 if the conn was - * destroyed (caller must stop), 0 if it survives (may now be in BODY/RESP). */ -static int -drive_head(struct mq_fetch_conn *c) -{ - mq_fetch_listener_t *l = c->l; - - mq_http1_req_t req; - mq_http1_status_t st = mq_http1_parse_req(c->rxbuf, c->rxlen, &req); - - if (st == MQ_HTTP1_NEED_MORE) { - return 0; /* keep reading */ - } - if (st == MQ_HTTP1_BAD) { - reply_error_and_close(c, 400, "Bad Request"); - return -1; - } - - /* DONE: validate method+path, then policy. */ - if (strcmp(req.method, MQ_FETCH_METHOD) != 0 || - strcmp(req.path, MQ_FETCH_PATH) != 0) { - reply_error_and_close(c, 404, "Not Found"); - return -1; - } - if (req.has_chunked_te) { - reply_error_and_close(c, 411, "Length Required"); - return -1; - } - - /* Accepted request. Hand it to the gateway. */ - c->phase = MQ_FC_BODY; - c->body_total = req.content_length > 0 ? req.content_length : 0; - c->body_delivered = 0; - - void *ctx = NULL; - int rc = 0; - if (l->cbs.on_request) { - rc = l->cbs.on_request(&req, c, l->user, &ctx); - } - c->req_ctx = ctx; - - if (rc == -1) { - /* Handler wrote its own error reply via the handle ops. Flush + close; - * no body streamed, no further cbs. */ - c->detached = 1; - c->closing = 1; - c->phase = MQ_FC_RESP; - if (c->read_ev) event_del(c->read_ev); - int destroyed = 0; - if (out_flush(c, &destroyed) < 0 && !destroyed) { - conn_destroy(c); - } - return -1; /* c may be alive (closing) but HEAD drive is done */ - } - - /* Stream any body bytes already buffered past the head. */ - size_t head_len = req.head_len; - const uint8_t *body = c->rxbuf + head_len; - size_t body_buffered = c->rxlen - head_len; - - if (c->body_total <= 0) { - /* No body: on_body_done immediately. The read event stays armed so the - * RESP branch notices peer EOF (normal here — the gateway writes the - * reply via the handle). on_body_done may finish/abort the conn, so - * stop the HEAD drive afterward (do not touch c). */ - c->phase = MQ_FC_RESP; - if (l->cbs.on_body_done) l->cbs.on_body_done(c->req_ctx); - return -1; /* body complete: HEAD drive done, do not loop */ - } - - if (body_buffered > 0) { - int done = 0; - int dr = deliver_body(c, body, body_buffered, &done); - if (dr == -1) return -1; /* destroyed */ - if (done) return -1; /* paused or body complete: do not loop on c */ - } - return 0; -} - -/* ── read event ─────────────────────────────────────────────────────────── */ - -static int -ensure_rx_space(struct mq_fetch_conn *c, size_t want) -{ - if (c->rxlen + want <= c->rxcap) return 1; - size_t ncap = c->rxcap ? c->rxcap : 4096; - while (ncap < c->rxlen + want) - ncap *= 2; - if (ncap > MQ_HTTP1_MAX_HEAD) ncap = MQ_HTTP1_MAX_HEAD; - if (c->rxlen + want > ncap) return 0; /* would exceed head cap */ - uint8_t *nb = realloc(c->rxbuf, ncap); - if (!nb) return 0; - c->rxbuf = nb; - c->rxcap = ncap; - return 1; -} - -static void -on_readable(evutil_socket_t fd, short what, void *user) -{ - (void)what; - struct mq_fetch_conn *c = (struct mq_fetch_conn *)user; - - for (;;) { - if (c->read_paused) return; /* gateway asked us to stop */ - - if (c->phase == MQ_FC_HEAD) { - if (!ensure_rx_space(c, MQ_FETCH_RD_CHUNK)) { - /* Head exceeds cap with no terminator: treat as bad request. */ - reply_error_and_close(c, 400, "Bad Request"); - return; - } - ssize_t n = recv(fd, c->rxbuf + c->rxlen, c->rxcap - c->rxlen, 0); - if (n > 0) { - c->rxlen += (size_t)n; - if (drive_head(c) < 0) return; /* destroyed (or closing) */ - continue; - } - if (n == 0) { - /* Peer closed before completing the head: just close. No cbs - * fired yet (still HEAD). */ - conn_destroy(c); - return; - } - if (errno == EAGAIN || errno == EWOULDBLOCK) return; - if (errno == EINTR) continue; - conn_destroy(c); - return; - } - - if (c->phase == MQ_FC_BODY) { - uint8_t buf[MQ_FETCH_RD_CHUNK]; - ssize_t n = recv(fd, buf, sizeof(buf), 0); - if (n > 0) { - int done = 0; - int dr = deliver_body(c, buf, (size_t)n, &done); - if (dr == -1) return; /* destroyed (oversize abort) */ - if (done) return; /* paused or body complete: stop reading */ - continue; - } - if (n == 0) { - /* Peer EOF before content_length bytes: abort (NOT done). */ - c->detached = 1; - if (c->l->cbs.on_aborted) c->l->cbs.on_aborted(c->req_ctx); - conn_destroy(c); - return; - } - if (errno == EAGAIN || errno == EWOULDBLOCK) return; - if (errno == EINTR) continue; - /* read error before CL: treat as abort. */ - c->detached = 1; - if (c->l->cbs.on_aborted) c->l->cbs.on_aborted(c->req_ctx); - conn_destroy(c); - return; - } - - /* MQ_FC_RESP: body fully delivered; the gateway owns the write side. - * Peer EOF here is normal (it finished uploading). We drain/discard any - * trailing reads but otherwise wait for finish/abort. */ - { - uint8_t buf[1024]; - ssize_t n = recv(fd, buf, sizeof(buf), 0); - if (n > 0) { - /* Unexpected extra bytes after a complete request; ignore them - * (keep-alive unsupported). Keep draining. */ - continue; - } - if (n == 0) { - /* Peer closed its write side. If the gateway has already - * detached (finish/abort) the conn is gone; otherwise stop - * reading and let the gateway finish writing the response. The - * gateway will call finish/abort to close. */ - if (c->read_ev) event_del(c->read_ev); - return; - } - if (errno == EAGAIN || errno == EWOULDBLOCK) return; - if (errno == EINTR) continue; - if (c->read_ev) event_del(c->read_ev); - return; - } - } -} - -/* ── accept event ───────────────────────────────────────────────────────── */ - -static int -set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - if (fl < 0) return -1; - return fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -static void -on_accept(evutil_socket_t lfd, short what, void *user) -{ - (void)what; - mq_fetch_listener_t *l = (mq_fetch_listener_t *)user; - - for (;;) { - int fd = accept(lfd, NULL, NULL); - if (fd < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) return; - if (errno == EINTR) continue; - MQ_LOGW("mq_fetch_listener: accept failed: %s", strerror(errno)); - return; - } - if (set_nonblock(fd) != 0) { - close(fd); - continue; - } - - struct mq_fetch_conn *c = calloc(1, sizeof(*c)); - if (!c) { - close(fd); - continue; - } - c->l = l; - c->fd = fd; - c->phase = MQ_FC_HEAD; - c->body_total = 0; - c->next = l->conns; - l->conns = c; - - c->read_ev = event_new(l->base, fd, EV_READ | EV_PERSIST, on_readable, c); - c->write_ev = event_new(l->base, fd, EV_WRITE | EV_PERSIST, on_writable, c); - if (!c->read_ev || !c->write_ev) { - conn_destroy(c); - continue; - } - event_add(c->read_ev, NULL); - } -} - -/* ── construction ───────────────────────────────────────────────────────── */ - -mq_fetch_listener_t * -mq_fetch_listener_new(struct event_base *base, const char *ip, uint16_t port, - const mq_fetch_cbs_t *cbs, void *user) -{ - if (!base || !ip || !cbs) return NULL; - - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) return NULL; - - int one = 1; - (void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - if (inet_pton(AF_INET, ip, &sa.sin_addr) != 1) { - close(fd); - return NULL; - } - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return NULL; - } - if (listen(fd, 64) != 0) { - close(fd); - return NULL; - } - if (set_nonblock(fd) != 0) { - close(fd); - return NULL; - } - - struct sockaddr_in bound; - socklen_t blen = sizeof(bound); - uint16_t lport = 0; - if (getsockname(fd, (struct sockaddr *)&bound, &blen) == 0) { - lport = ntohs(bound.sin_port); - } - - mq_fetch_listener_t *l = calloc(1, sizeof(*l)); - if (!l) { - close(fd); - return NULL; - } - l->base = base; - l->listen_fd = fd; - l->local_port = lport; - l->cbs = *cbs; - l->user = user; - l->conns = NULL; - - l->accept_ev = event_new(base, fd, EV_READ | EV_PERSIST, on_accept, l); - if (!l->accept_ev) { - close(fd); - free(l); - return NULL; - } - event_add(l->accept_ev, NULL); - return l; -} - -uint16_t -mq_fetch_listener_port(const mq_fetch_listener_t *l) -{ - return l ? l->local_port : 0; -} - -void -mq_fetch_listener_free(mq_fetch_listener_t *l) -{ - if (!l) return; - if (l->accept_ev) { - event_free(l->accept_ev); - l->accept_ev = NULL; - } - if (l->listen_fd >= 0) { - close(l->listen_fd); - l->listen_fd = -1; - } - while (l->conns) { - conn_destroy(l->conns); - } - free(l); -} - -/* ── per-connection handle ops ──────────────────────────────────────────── */ - -void -mq_fetch_conn_pause_read(void *handle) -{ - struct mq_fetch_conn *c = (struct mq_fetch_conn *)handle; - if (!c || c->detached) return; - c->read_paused = 1; - if (c->read_ev) event_del(c->read_ev); -} - -void -mq_fetch_conn_resume_read(void *handle) -{ - struct mq_fetch_conn *c = (struct mq_fetch_conn *)handle; - if (!c || c->detached) return; - if (!c->read_paused) return; - c->read_paused = 0; - if (c->read_ev) { - /* Re-enable reading, then schedule a deferred read kick rather than - * calling on_readable() inline. A gateway that resumes from inside - * on_body would otherwise recurse into the read loop (unbounded - * recursion) and could touch a conn freed deeper in the stack. Posting - * EV_READ defers the kick to the next loop turn, where any ready bytes - * are processed safely after the current callback unwinds. */ - event_add(c->read_ev, NULL); - event_active(c->read_ev, EV_READ, 0); - } -} - -int -mq_fetch_conn_write(void *handle, const void *p, size_t len) -{ - struct mq_fetch_conn *c = (struct mq_fetch_conn *)handle; - if (!c || c->detached || c->fd < 0) return -1; - if (!out_append(c, p, len)) return -1; - - int destroyed = 0; - if (out_flush(c, &destroyed) < 0) { - if (!destroyed) conn_destroy(c); - return -1; - } - if (destroyed) return -1; /* shouldn't happen unless closing, but be safe */ - - if (out_pending(c) > MQ_FETCH_OUT_HIGHWATER) { - c->hw_hit = 1; - return 0; /* high watermark: gateway stops feeding */ - } - return 1; /* accepted */ -} - -void -mq_fetch_conn_set_drain_cb(void *handle, void (*fn)(void *), void *u) -{ - struct mq_fetch_conn *c = (struct mq_fetch_conn *)handle; - if (!c || c->detached) return; - c->drain_cb = fn; - c->drain_user = u; -} - -void -mq_fetch_conn_finish(void *handle) -{ - struct mq_fetch_conn *c = (struct mq_fetch_conn *)handle; - if (!c || c->detached) return; - /* Detach FIRST: no cbs may fire after finish. Then flush remaining output; - * close once drained (out_flush destroys the conn when closing+empty). */ - c->detached = 1; - c->closing = 1; - c->drain_cb = NULL; - if (c->read_ev) event_del(c->read_ev); - int destroyed = 0; - if (out_flush(c, &destroyed) < 0 && !destroyed) { - conn_destroy(c); - } -} - -void -mq_fetch_conn_abort(void *handle) -{ - struct mq_fetch_conn *c = (struct mq_fetch_conn *)handle; - if (!c || c->detached) return; - c->detached = 1; - c->drain_cb = NULL; - conn_destroy(c); -} diff --git a/src/gateway/mq_fetch_listener.h b/src/gateway/mq_fetch_listener.h deleted file mode 100644 index 29214b05..00000000 --- a/src/gateway/mq_fetch_listener.h +++ /dev/null @@ -1,214 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_fetch_listener.h — local fetch-API listener (POST /_mqproxy/fetch). - * - * A loopback HTTP/1.1 accept loop that the gateway client uses to receive - * fetch requests from a local-but-untrusted peer (the app / SDK). It binds and - * listens a non-blocking TCP socket on ip:port and runs a libevent EV_READ - * accept loop, mirroring mq_listener's socket/accept machinery. - * - * Unlike mq_listener (1 connection = a long-lived CONNECT tunnel), here ONE - * connection = ONE request: every response carries `Connection: close` and - * keep-alive is NOT supported in the MVP. After the request head parses, the - * BODY is streamed to the gateway client through callbacks, with backpressure - * in BOTH directions. - * - * Request lifecycle: - * 1. Accumulate bytes into a head buffer until mq_http1_parse_req returns - * DONE/BAD (capped at MQ_HTTP1_MAX_HEAD). - * 2. Listener-owned direct error replies (the cbs are NEVER invoked): - * - parse BAD -> 400 Bad Request - * - method+path not "POST /_mqproxy/fetch" -> 404 Not Found - * - Transfer-Encoding lists chunked -> 411 Length Required - * Each reply is "\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", - * flushed, then the connection is closed. - * 3. Valid request -> cbs->on_request(req, handle, user, &req_ctx). - * - return -1 => the handler already wrote its own error reply via the - * handle ops; the listener just flushes and closes (no body streamed). - * - return 0 => the listener streams exactly content_length body bytes - * to cbs->on_body, then cbs->on_body_done. content_length <= 0 calls - * on_body_done immediately after on_request returns. - * 4. cbs->on_body returning -1 => the listener pauses reading (backpressure); - * the gateway resumes it via mq_fetch_conn_resume_read(handle). - * 5. Peer EOF/error BEFORE content_length bytes are delivered => on_aborted - * (NOT on_body_done). After on_body_done, peer EOF is normal: the listener - * waits for the gateway to write the response via the handle ops. - * 6. Body bytes exceeding content_length are a peer protocol violation, but - * the body is fully received: on_body_done has already fired and the - * gateway owns the write side. The listener does NOT fire on_aborted and - * does NOT tear the connection down from the read path; it just stops - * reading and leaves teardown to the gateway's finish()/abort() (so a - * response the gateway is mid-flushing is not discarded). - * - * DESIGN NOTE — handle vs fd: the converged plan's on_request signature passes - * an `int fd`, but the per-connection handle ops below need the listener's - * per-conn object, not a raw fd. Per the sanctioned plan-review interpretation, - * on_request receives a `void *handle` (the opaque per-conn handle) instead; - * the accepted fd stays internal to the listener. The gateway drives the - * connection ONLY through the handle ops. - * - * Ownership of the accepted fd / handle: - * - The listener OWNS the accepted fd for the connection's whole life and is - * the only party that ever closes it. The gateway never sees the fd. - * - The `handle` is valid from the on_request call until the gateway calls - * mq_fetch_conn_finish() or mq_fetch_conn_abort(), OR until on_aborted - * fires. After ANY of those, the handle is DEAD: the gateway MUST drop it - * and never call a handle op again. The listener guarantees that NO cbs - * fire after finish()/abort() (it sets a 'detached' flag), so finish/abort - * are the gateway's clean hand-back of the connection. - * - on_aborted is the listener telling the gateway the peer died mid-body; - * the gateway must drop the handle (it is already being torn down) and MUST - * NOT call finish/abort on it. - * - * This module knows nothing about xquic; it speaks libevent + sockets and the - * mq_fetch_cbs_t boundary only. - * - * KNOWN LIMITATION — per-request vs per-process memory: each request's buffers - * are individually bounded (the request head is capped at MQ_HTTP1_MAX_HEAD, the - * download spill buffer at 256 KiB, and the per-connection output queue at the - * 4 MiB hard ceiling above), so no single request can grow memory without limit. - * The per-PROCESS SUM of those buffers across many concurrent requests is NOT - * bounded, however. This is acceptable for the Phase 2 single-trusted-client - * scope (one local app drives a small number of in-flight fetches); a global - * memory budget / concurrency cap is deferred to the multi-client hardening work - * (Phase 5). Relatedly, the listener enforces NO read/idle timeout: a peer that - * dribbles the request head or holds a half-sent Content-Length body open forever - * (slowloris / CL-hold) ties up a connection indefinitely. This is a conscious - * single-trusted-client scope decision; an idle/read timeout is revisited in the - * same Phase 5 hardening work. - */ -#ifndef MQ_GATEWAY_MQ_FETCH_LISTENER_H -#define MQ_GATEWAY_MQ_FETCH_LISTENER_H - -#include -#include - -#include "gateway/mq_http1.h" - -struct event_base; - -typedef struct mq_fetch_listener_s mq_fetch_listener_t; - -/* Gateway-supplied callbacks. All are invoked from the event loop. None may - * free the listener. - * - * ── HANDLE-OP RE-ENTRANCY CONTRACT (which ops are safe from which callback) ── - * The listener touches the connection object AFTER on_request and on_body - * return (drive_head reads c->body_total + calls deliver_body after on_request; - * deliver_body does `c->body_delivered += take` immediately after on_body - * returns — see mq_fetch_listener.c). finish()/abort() DETACH and may DESTROY - * the connection, so calling them from inside on_request or on_body would make - * those post-return touches a use-after-free. Therefore: - * - * - mq_fetch_conn_finish / mq_fetch_conn_abort MUST NOT be called from within - * on_request or on_body. Use the RETURN VALUES instead: return -1 from - * on_request = "I already replied via the handle ops" (listener flushes + - * closes); return -1 from on_body = "consumed, but pause reading". - * - finish/abort ARE safe from on_body_done and from any later (RESP-phase) - * point, and from OUTSIDE any callback (the normal hand-back). on_body_done - * is the listener's LAST touch of the connection on the read path: it sets - * the *done out-param (deliver_body, drive_head) BEFORE invoking on_body_done - * and does not touch `c` afterward, so finish/abort there is safe — this is - * the normal echo path (reply + finish from on_body_done). - * - mq_fetch_conn_pause_read / resume_read / write / set_drain_cb are safe - * ANYWHERE (they early-return when the handle is detached; resume_read - * defers its read kick, so it is even safe from inside on_body). - * - on_aborted is terminal: the handle is already being torn down — drop it - * and call NO handle op (not even finish/abort). */ -typedef struct { - /* The request head parsed (valid POST /_mqproxy/fetch). The listener will - * then stream body bytes to on_body. Return 0 = accepted (listener - * proceeds to stream the body); return -1 = the handler already wrote its - * own error reply (the listener just flushes and closes). *req_ctx is the - * handler's per-request context, threaded into the later callbacks. - * - * MUST NOT call finish/abort (the listener touches the conn after this - * returns — UAF); reply-and-close is signalled by returning -1 after - * writing via the handle ops. write/pause_read/resume_read/set_drain_cb are - * safe here. - * - * `req`'s header slices point into the listener's read buffer and are valid - * only for the duration of this call — copy anything you need to keep. */ - int (*on_request)(const mq_http1_req_t *req, void *handle, void *user, - void **req_ctx); - - /* A chunk of body bytes [p, p+len). The chunk is ALWAYS consumed by this - * call (the listener never re-delivers it). Return value signals only - * backpressure: 0 = keep streaming; -1 = consumed-but-now-full, so the - * listener stops reading further body until the gateway calls - * mq_fetch_conn_resume_read(). p is valid only for this call. - * - * MUST NOT call finish/abort (the listener touches the conn after this - * returns — UAF); use the -1 return to pause. write/pause_read/resume_read/ - * set_drain_cb are safe here. */ - int (*on_body)(void *req_ctx, const uint8_t *p, size_t len); - - /* Exactly content_length body bytes have been delivered to on_body. The - * listener will not touch the connection on the read path after this returns - * (the read path's *done discipline guarantees it), so finish/abort ARE safe - * here — this is the normal response-phase hand-back. */ - void (*on_body_done)(void *req_ctx); - - /* The local peer died (EOF/error) before content_length bytes arrived. The - * handle is being torn down: drop it, do not call any handle op. */ - void (*on_aborted)(void *req_ctx); -} mq_fetch_cbs_t; - -/* Create a fetch-API listener bound to ip:port (port 0 => ephemeral). `base` - * is borrowed (must outlive the listener). `cbs` is copied; `user` is opaque - * and threaded into on_request. Returns NULL on bad args / bind / listen - * failure. */ -mq_fetch_listener_t *mq_fetch_listener_new(struct event_base *base, const char *ip, - uint16_t port, const mq_fetch_cbs_t *cbs, - void *user); - -/* The bound local TCP port in host byte order (useful for ephemeral binds in - * tests). Returns 0 if unknown. */ -uint16_t mq_fetch_listener_port(const mq_fetch_listener_t *l); - -/* Free the listener: stop accepting, close the listen socket, and tear down - * every live connection (closing its fd). Safe on NULL. */ -void mq_fetch_listener_free(mq_fetch_listener_t *l); - -/* ── per-connection handle ops (called by the gateway client) ───────────────── - * - * `handle` is the opaque per-conn handle delivered to on_request. All ops are - * no-ops (or return an error) once the connection is detached (after - * finish/abort/on_aborted) — but the gateway MUST treat the handle as dead and - * stop using it after finish/abort/on_aborted regardless. */ - -/* Stop reading body bytes from the peer (backpressure from the gateway). */ -void mq_fetch_conn_pause_read(void *handle); - -/* Resume reading body bytes after a pause. The read kick is DEFERRED to the - * next event-loop turn (it does not re-enter the read path synchronously), so - * it is safe to call this from within on_body — there is no recursion into the - * read loop and no use-after-free window if a later callback tears the conn - * down. */ -void mq_fetch_conn_resume_read(void *handle); - -/* Append response bytes to the connection's output. Returns: - * >0 accepted (queued/sent), - * 0 high-watermark hit — the output buffer is full; STOP feeding and wait - * for the drain callback (set via mq_fetch_conn_set_drain_cb) to fire, - * -1 error (handle dead / write failure / the write would push the queued - * output above the internal hard ceiling, currently 4 MiB). - * - * A gateway that honors the 0 (high-watermark) return never reaches the hard - * ceiling; the ceiling only bounds memory for a misbehaving caller. */ -int mq_fetch_conn_write(void *handle, const void *p, size_t len); - -/* Register a callback fired when the output buffer drains below the low - * watermark after a high-watermark hit (the gateway may resume writing). */ -void mq_fetch_conn_set_drain_cb(void *handle, void (*fn)(void *), void *u); - -/* Flush any queued output, then close the connection. After this the handle is - * dead. No cbs fire afterward. */ -void mq_fetch_conn_finish(void *handle); - -/* Immediately close the connection (mid-body error path), discarding queued - * output. After this the handle is dead. No cbs fire afterward. */ -void mq_fetch_conn_abort(void *handle); - -#endif /* MQ_GATEWAY_MQ_FETCH_LISTENER_H */ diff --git a/src/gateway/mq_gw_cache.c b/src/gateway/mq_gw_cache.c deleted file mode 100644 index 791db87a..00000000 --- a/src/gateway/mq_gw_cache.c +++ /dev/null @@ -1,244 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "gateway/mq_gw_cache.h" - -#include -#include - -/* Minimal in-memory LRU response cache (design 2026-06-12-cache-integration). - * Single-threaded (no locks); clock-free (callers pass now_ms). The store is a - * doubly-linked LRU list (MRU at head, LRU at tail) with a LINEAR key search — - * the cache is byte-bounded and per-instance small, so O(n) lookup is fine - * (a hash index is a documented future optimization). */ - -struct mq_gw_cache_s { - size_t max_bytes; /* hard total byte budget */ - size_t max_obj_bytes; /* per-object cap (refuse larger) */ - size_t cur_bytes; /* sum of all entries' size_bytes */ - mq_gw_cache_entry_t *head; /* MRU */ - mq_gw_cache_entry_t *tail; /* LRU */ -}; - -/* --------------------------------------------------------------------------- - * Entry helpers - * ------------------------------------------------------------------------- */ - -/* Free every heap allocation owned by an entry, then the entry itself. - * NULL-safe on the entry and on every sub-allocation (free(NULL) is a no-op). */ -static void -entry_free(mq_gw_cache_entry_t *e) -{ - if (!e) return; - free(e->key); - if (e->hdrs) { - for (size_t i = 0; i < e->n_hdrs; i++) { - free(e->hdrs[i].name); - free(e->hdrs[i].value); - } - free(e->hdrs); - } - free(e->body); - free(e); -} - -/* Detach an entry from the LRU list (does NOT free it, does NOT touch cur_bytes). */ -static void -unlink_entry(mq_gw_cache_t *c, mq_gw_cache_entry_t *e) -{ - if (e->lru_prev) - e->lru_prev->lru_next = e->lru_next; - else - c->head = e->lru_next; /* e was the head */ - if (e->lru_next) - e->lru_next->lru_prev = e->lru_prev; - else - c->tail = e->lru_prev; /* e was the tail */ - e->lru_prev = e->lru_next = NULL; -} - -/* Link an entry at the head (MRU). Caller adjusts cur_bytes. */ -static void -link_head(mq_gw_cache_t *c, mq_gw_cache_entry_t *e) -{ - e->lru_prev = NULL; - e->lru_next = c->head; - if (c->head) c->head->lru_prev = e; - c->head = e; - if (!c->tail) c->tail = e; -} - -/* Remove + free an entry, fixing cur_bytes. */ -static void -drop_entry(mq_gw_cache_t *c, mq_gw_cache_entry_t *e) -{ - unlink_entry(c, e); - c->cur_bytes -= e->size_bytes; - entry_free(e); -} - -/* --------------------------------------------------------------------------- - * Public API - * ------------------------------------------------------------------------- */ - -mq_gw_cache_t * -mq_gw_cache_new(size_t max_bytes, size_t max_obj_bytes) -{ - if (max_bytes == 0) return NULL; /* disabled */ - mq_gw_cache_t *c = calloc(1, sizeof(*c)); - if (!c) return NULL; - c->max_bytes = max_bytes; - c->max_obj_bytes = max_obj_bytes; - return c; -} - -void -mq_gw_cache_free(mq_gw_cache_t *c) -{ - if (!c) return; - mq_gw_cache_entry_t *e = c->head; - while (e) { - mq_gw_cache_entry_t *next = e->lru_next; - entry_free(e); - e = next; - } - free(c); -} - -const mq_gw_cache_entry_t * -mq_gw_cache_lookup(mq_gw_cache_t *c, const char *key, uint64_t now_ms) -{ - if (!c || !key) return NULL; - for (mq_gw_cache_entry_t *e = c->head; e; e = e->lru_next) { - if (strcmp(e->key, key) != 0) continue; - if (now_ms < e->stored_at_ms + e->ttl_ms) { - /* fresh → promote to MRU and return a borrowed view */ - if (e != c->head) { - unlink_entry(c, e); - link_head(c, e); - } - return e; - } - /* found but expired → drop it, report miss */ - drop_entry(c, e); - return NULL; - } - return NULL; -} - -int -mq_gw_cache_insert(mq_gw_cache_t *c, const char *key, int status, - const mq_gw_cache_hdr_t *hdrs, size_t n_hdrs, const uint8_t *body, - size_t body_len, uint64_t ttl_ms, uint64_t now_ms) -{ - if (!c || !key) return -1; - - size_t klen = strlen(key); - /* Early per-object-cap reject BEFORE the deep-copy: header bytes only add - * more, so (klen + 1 + body_len) > max_obj_bytes is a conservative-safe - * lower bound — avoids mallocing then freeing a too-large blob. The exact - * size (with headers) is re-checked after the copy below. */ - if (klen + 1 + body_len > c->max_obj_bytes) return -1; - - /* Build a deep-copied entry; any failure on the way frees what we've - * allocated so far (entry_free is NULL-safe on partial entries). */ - mq_gw_cache_entry_t *e = calloc(1, sizeof(*e)); - if (!e) return -1; - - /* Byte accounting is PAYLOAD-only: key + headers + body. The fixed - * per-entry struct overhead (sizeof(*e)) is deliberately EXCLUDED so that - * max_bytes / max_obj_bytes mean "payload bytes" — what an operator setting - * --cache-max-bytes expects, and what the unit tests assume (test_lru_evict - * sizes two ~28-byte payload entries into a 64-byte cap; including the - * 88-byte struct would refuse every insert). The plan's draft formula listed - * a +sizeof(entry) term that contradicts the verbatim test; the test is the - * ground truth, so payload-only accounting is used. */ - size_t size = klen + 1 + body_len; - - e->status = status; - e->stored_at_ms = now_ms; - e->ttl_ms = ttl_ms; - e->body_len = body_len; - - e->key = malloc(klen + 1); - if (!e->key) { - entry_free(e); - return -1; - } - memcpy(e->key, key, klen + 1); - - if (n_hdrs > 0 && hdrs) { - e->hdrs = calloc(n_hdrs, sizeof(*e->hdrs)); - if (!e->hdrs) { - entry_free(e); - return -1; - } - /* n_hdrs is set incrementally so a mid-loop failure only frees the - * names/values copied so far. */ - for (size_t i = 0; i < n_hdrs; i++) { - size_t nl = strlen(hdrs[i].name); - size_t vl = strlen(hdrs[i].value); - e->hdrs[i].name = malloc(nl + 1); - e->hdrs[i].value = malloc(vl + 1); - if (!e->hdrs[i].name || !e->hdrs[i].value) { - free(e->hdrs[i].name); - free(e->hdrs[i].value); - e->hdrs[i].name = e->hdrs[i].value = NULL; - e->n_hdrs = i; - entry_free(e); - return -1; - } - memcpy(e->hdrs[i].name, hdrs[i].name, nl + 1); - memcpy(e->hdrs[i].value, hdrs[i].value, vl + 1); - size += nl + 1 + vl + 1; - } - e->n_hdrs = n_hdrs; - } - - if (body_len > 0) { - e->body = malloc(body_len); - if (!e->body) { - entry_free(e); - return -1; - } - if (body) memcpy(e->body, body, body_len); - } - - e->size_bytes = size; - - /* Single object too large for the per-object cap → refuse, store nothing. */ - if (size > c->max_obj_bytes) { - entry_free(e); - return -1; - } - - /* Duplicate key replaces the old entry (drop it before sizing/eviction). */ - for (mq_gw_cache_entry_t *o = c->head; o; o = o->lru_next) { - if (strcmp(o->key, key) == 0) { - drop_entry(c, o); - break; - } - } - - /* Evict LRU until the newcomer fits the total budget. (size <= max_obj_bytes - * <= max_bytes is the caller's invariant via set_cache, but even if not, the - * per-object cap already refused anything that can never fit.) */ - while (c->tail && c->cur_bytes + size > c->max_bytes) - drop_entry(c, c->tail); - - link_head(c, e); - c->cur_bytes += size; - return 0; -} - -size_t -mq_gw_cache_bytes(const mq_gw_cache_t *c) -{ - return c ? c->cur_bytes : 0; -} - -size_t -mq_gw_cache_max_obj_bytes(const mq_gw_cache_t *c) -{ - return c ? c->max_obj_bytes : 0; -} diff --git a/src/gateway/mq_gw_cache.h b/src/gateway/mq_gw_cache.h deleted file mode 100644 index 44a2369b..00000000 --- a/src/gateway/mq_gw_cache.h +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#ifndef MQ_GW_CACHE_H -#define MQ_GW_CACHE_H -#include -#include - -/* Minimal in-memory LRU HTTP response cache for the gateway server (design - * 2026-06-12-cache-integration). Single-threaded (the libevent loop) — NO locks. - * Clock-free: callers pass a monotonic now_ms (CLOCK_MONOTONIC ms). */ - -typedef struct mq_gw_cache_hdr_s { - char *name; /* lowercase header name (NUL-term) */ - char *value; /* header value (NUL-term) */ -} mq_gw_cache_hdr_t; - -typedef struct mq_gw_cache_entry_s mq_gw_cache_entry_t; -struct mq_gw_cache_entry_s { - char *key; /* "GET " (NUL-term) */ - int status; /* HTTP status (200) */ - mq_gw_cache_hdr_t *hdrs; - size_t n_hdrs; - uint8_t *body; - size_t body_len; - uint64_t stored_at_ms; - uint64_t ttl_ms; - size_t size_bytes; /* key + hdrs + body accounting */ - mq_gw_cache_entry_t *lru_prev, *lru_next; /* MRU at head */ -}; - -typedef struct mq_gw_cache_s mq_gw_cache_t; - -/* max_bytes 0 ⇒ returns NULL (disabled). max_obj_bytes is the per-object cap. */ -mq_gw_cache_t *mq_gw_cache_new(size_t max_bytes, size_t max_obj_bytes); -void mq_gw_cache_free(mq_gw_cache_t *c); - -/* Lookup a FRESH entry (drops it if expired). Returns a BORROWED const view or NULL. - * The caller must COPY the body before yielding the loop (copy-on-hit — the entry can be - * evicted by a later insert). Moves the entry to MRU on hit. */ -const mq_gw_cache_entry_t *mq_gw_cache_lookup(mq_gw_cache_t *c, const char *key, - uint64_t now_ms); - -/* Build + insert an entry from copied inputs (the cache deep-copies all of them; the - * caller keeps ownership of its buffers). Evicts LRU until it fits; refuses (returns -1, - * stores nothing) if the single object exceeds max_obj_bytes. Returns 0 on insert. A - * duplicate key replaces the old entry. */ -int mq_gw_cache_insert(mq_gw_cache_t *c, const char *key, int status, - const mq_gw_cache_hdr_t *hdrs, size_t n_hdrs, const uint8_t *body, - size_t body_len, uint64_t ttl_ms, uint64_t now_ms); - -size_t mq_gw_cache_bytes(const mq_gw_cache_t *c); /* current total (tests/diag) */ -size_t -mq_gw_cache_max_obj_bytes(const mq_gw_cache_t *c); /* per-object cap (store gate) */ - -#endif diff --git a/src/gateway/mq_gw_client.c b/src/gateway/mq_gw_client.c deleted file mode 100644 index 3a6f4179..00000000 --- a/src/gateway/mq_gw_client.c +++ /dev/null @@ -1,1462 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_gw_client.c — client-side fetch→H3 bridge. See mq_gw_client.h. - * - * ───────────────────────────────────────────────────────────────────────────── - * PER-REQUEST OWNERSHIP (mq_gw_req_t) - * ───────────────────────────────────────────────────────────────────────────── - * This is the protocol-agnostic NEUTRAL CORE: it knows nothing about HTTP/1.1 or - * the fetch listener. The local protocol lives entirely behind the sink boundary - * (mq_gw_sink_ops_t), implemented today by mq_gw_fetch_adapter.c. Each request - * owns one mq_gw_req_t referenced from two independent sides that can each die - * first or race: - * - * LOCAL side (the sink: sink + sink_user): set on accept (mq_gw_client_req_ - * begin); cleared when we terminate the response (sink->resp_finish/resp_abort) - * OR when the adapter tells us the local peer died (mq_gw_client_req_aborted). - * After any of those the sink is DEAD — we null sink/sink_user and never touch - * it again. The sink_user the adapter threads back to us stays valid until then. - * - * TUNNEL side (the mq_h3_req): set on accept (mq_h3_req_open); cleared in the - * H3 on_close callback (the wrapper is freed right after). After on_close we - * null it and never touch it again. - * - * The mq_gw_req_t is freed when BOTH sides are detached. We track this with two - * flags (local_dead, h3_dead) and free in gw_req_maybe_free() once both are set. - * Every termination path routes through exactly one of: - * - * - local terminates first: we mq_h3_req_reset (if h3 still live) → h3 on_close - * fires later → both dead → free. We set local_dead immediately and clear the - * sink so no further sink op runs. - * - tunnel terminates first (h3 on_close): we drive sink->resp_finish/resp_abort - * (if local still live) → that detaches local → both dead → free. on_close - * sets h3_dead and nulls the req pointer. - * - both race: idempotent flags; maybe_free runs only when both set, so the - * struct is freed exactly once regardless of order. - * - * mq_h3 guarantees on_close fires exactly once, and the adapter guarantees no - * sink op is delivered after we detach (resp_finish/resp_abort/req_aborted are - * each terminal). So each side flips its flag exactly once. - * - * One subtlety: when we drive a terminal sink op from inside an H3 callback, it - * does NOT synchronously re-enter the core (the adapter is terminal). So no - * reentrancy hazard there. Conversely, when the LOCAL side dies we learn it - * either via mq_gw_client_req_aborted (explicit) or via a sink op returning -1 — - * we treat a -1 from any sink write/resume as "local dead" and reset the tunnel. - * - * SINK INVARIANT (Task 4): `!local_dead` ⟹ `sink != NULL && sink_user != NULL`. - * The sink is set once at req_begin (mq_gw_client_req_begin) and cleared in - * lockstep with local_dead at every local-side termination site. Code that has - * already established `!local_dead` (e.g. download_pump, which returns early if - * local_dead) may call sink ops WITHOUT a redundant NULL guard. - */ -#include "gateway/mq_gw_client.h" - -#include -#include -#include -#include -#include - -#include - -#include "gateway/mq_gw_headers.h" -#include "util/mq_backoff.h" -#include "util/mq_log.h" - -/* Bound on the internal upload spill buffer (junction #1): bytes the local peer - * delivered that H3 could not immediately accept. When non-empty we pause the - * listener; we flush from on_write and resume_read once drained. */ -#define MQ_GW_UPLOAD_SPILL_MAX (256 * 1024) - -/* recv_body scratch size for the download path (junction #2). */ -#define MQ_GW_DOWNLOAD_CHUNK 16384 - -/* Max number of forwarded request headers (request line pseudo-headers + - * x-mq-auth + x-mq-class + ordinary headers). MQ_HTTP1_MAX_HEADERS bounds the - * incoming set; add a margin for the pseudo-headers we synthesize. */ -#define MQ_GW_MAX_SEND_HDRS (MQ_HTTP1_MAX_HEADERS + 8) - -/* Per-forwarded-header arena slot caps (name / value). A slot holds cap-1 bytes - * + NUL, so a name >= NAME_CAP or value >= VAL_CAP would be truncated. These bound - * BOTH the gw_on_request reject check (fail closed before opening the H3 request) - * AND the forwarding arena declarations — keep them in lockstep here so the two - * sites can never drift. */ -#define MQ_GW_HDR_NAME_CAP 128 -#define MQ_GW_HDR_VAL_CAP 1024 - -/* Poll interval (ms) for the mp-ready deferral timer (mirrors mq_client). */ -#define MQ_GW_MP_POLL_MS 50 -#define MQ_GW_MAX_PATHS 8 -#define MQ_GW_IP_MAX 64 - -/* Base delay (ms) for the reconnect exponential backoff (mirrors mq_client). */ -#define MQ_GW_RECONNECT_BASE_MS 250 - -/* ── per-request state ──────────────────────────────────────────────────────*/ - -/* This is the definition of the opaque mq_gw_xreq_t (typedef'd in - * mq_gw_intake.h as `struct mq_gw_xreq_s`). The neutral core owns only the - * tunnel state and the sink boundary; all protocol-specific fields (H1 handle, - * resp_chunked, etc.) live in the adapter (mq_gw_fetch_adapter.c). */ -struct mq_gw_xreq_s { - mq_gw_client_t *cli; - - /* ── neutral intake (sink) side ── */ - const mq_gw_sink_ops_t *sink; /* adapter callbacks (NULL == intake detached) */ - void *sink_user; /* adapter per-request cookie */ - - /* local (sink) side liveness flag */ - int local_dead; - - /* tunnel (H3) side */ - mq_h3_req_t *req; /* NULL once on_close fired (dead) */ - int h3_dead; - - /* ── upload (junction #1) ── */ - int upload_fin_sent; /* the request's fin has been sent on the H3 side */ - int upload_done; /* on_body_done fired (listener will deliver no more) */ - int paused; /* we returned -1 to pause the listener read */ - uint8_t *spill; /* heap spill buffer (allocated lazily) */ - size_t spill_len; /* valid bytes in spill */ - size_t spill_off; /* bytes already flushed from spill */ - - /* ── download (junction #2) ── */ - int resp_started; /* we called sink->resp_head (response started) */ - int read_deferred; /* adapter output highwater; stop recv_body until drain */ - int resp_status; /* parsed :status (for diagnostics) */ - - struct mq_gw_xreq_s *next; /* intrusive list of live requests on the client */ -}; - -/* In-file alias so the internal code can spell the struct `mq_gw_req_t`. */ -typedef struct mq_gw_xreq_s mq_gw_req_t; - -/* ── client state ───────────────────────────────────────────────────────────*/ - -struct mq_gw_client_s { - mq_transport_t *t; /* borrowed */ - mq_runtime_t *rt; /* borrowed */ - mq_h3_t *h3; /* borrowed */ - mq_cc_t cc; - - char token[256]; - - struct sockaddr_in peer; - socklen_t peerlen; - - mq_h3_conn_t *conn; /* the gateway tunnel conn (borrowed; freed by mq_h3) */ - int conn_up; /* 1 between established and closed */ - - mq_gw_req_t *reqs; /* intrusive list of live per-request states */ - - /* mp-poll timer (mirrors mq_client) */ - char extra_paths[MQ_GW_MAX_PATHS][MQ_GW_IP_MAX]; - size_t n_extra_paths; - size_t added_paths; - struct event *mp_timer; - - /* Keepalive: > 0 enables xquic PING keepalive with this idle timeout. Passed - * to every mq_h3_connect (initial + reconnect). */ - uint64_t keepalive_idle_ms; - - /* Reconnect controller (Phase 5b, mirrors mq_client). reconnect_ev is created - * ONCE in mq_gw_client_new (when enabled) and freed ONLY in mq_gw_client_free; - * it is a one-shot timer re-armed across reconnect cycles, so a stop must NOT - * free it. shutting_down is set FIRST by mq_gw_client_free so a late conn close - * takes the terminal path (no reconnect-after-free). The gateway has NO - * connection-level auth latch (auth is per-request via x-mq-auth), so the - * reconnect-success hook is `established` (conn_up=1), not an auth-OK event. */ - int shutting_down; - int reconnect_enabled; - uint64_t reconnect_max_backoff_ms; - struct event *reconnect_ev; - unsigned reconnect_attempts; -}; - -/* ── forward decls ──────────────────────────────────────────────────────────*/ -static void gw_req_unlink(mq_gw_client_t *c, mq_gw_req_t *r); -static void gw_req_maybe_free(mq_gw_req_t *r); -static void gw_local_dead(mq_gw_req_t *r); -static void gw_local_finish(mq_gw_req_t *r); -static void gw_local_abort(mq_gw_req_t *r); -static void h3_on_read(mq_h3_req_t *hr, int flag, void *user); -static void h3_on_write(mq_h3_req_t *hr, void *user); -static void h3_on_close(mq_h3_req_t *hr, void *user); -static void upload_pump(mq_gw_req_t *r); -static void download_pump(mq_gw_req_t *r); -static void gw_local_error_req(mq_gw_req_t *r, int code, const char *xmq); -/* Reconnect controller + the re-issuable connect body (defined below). */ -static int gw_issue_connect(mq_gw_client_t *c); -static void gw_reconnect_arm(mq_gw_client_t *c); -static void gw_reconnect_stop(mq_gw_client_t *c); -static void gw_mp_timer_arm(mq_gw_client_t *c); -static void gw_mp_timer_stop(mq_gw_client_t *c); - -/* ── small helpers ──────────────────────────────────────────────────────────*/ - -/* Case-insensitive equality for a slice vs a NUL-terminated lowercase literal. */ -static int -slice_ieq(const char *s, size_t sl, const char *lit) -{ - size_t ll = strlen(lit); - if (sl != ll) return 0; - for (size_t i = 0; i < sl; i++) { - char a = s[i]; - if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a'); - if (a != lit[i]) return 0; - } - return 1; -} - -/* ── neutral header-list helpers (mq_h3_header_t, NUL-terminated) ────────────*/ - -/* Find header `name` (case-insensitive) in a neutral list; NULL if absent. - * *out_vl receives strlen(value). */ -static const char * -nfind_hdr(const mq_h3_header_t *hs, size_t n, const char *name, size_t *out_vl) -{ - for (size_t i = 0; i < n; i++) { - if (slice_ieq(hs[i].name, strlen(hs[i].name), name)) { - if (out_vl) *out_vl = strlen(hs[i].value); - return hs[i].value; - } - } - return NULL; -} - -/* Returns 1 if the neutral list carries the same X-Mq-* name twice or more - * (case-insensitive). Mirrors mq_gw_has_dup_xmq for the neutral representation. */ -static int -nhas_dup_xmq(const mq_h3_header_t *hs, size_t n) -{ - for (size_t i = 0; i < n; i++) { - size_t il = strlen(hs[i].name); - if (!slice_ieq(hs[i].name, il < 5 ? il : 5, "x-mq-")) continue; - for (size_t j = i + 1; j < n; j++) { - /* Both names come from the wire with arbitrary case; slice_ieq only - * folds its first arg, so compare both sides case-insensitively here - * (matches the original mq_gw_has_dup_xmq which lc()'d both). */ - size_t jl = strlen(hs[j].name); - if (jl != il) continue; - size_t k = 0; - for (; k < il; k++) { - char a = hs[i].name[k], b = hs[j].name[k]; - if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a'); - if (b >= 'A' && b <= 'Z') b = (char)(b - 'A' + 'a'); - if (a != b) break; - } - if (k == il) return 1; - } - } - return 0; -} - -/* ── per-request lifecycle ──────────────────────────────────────────────────*/ - -static void -gw_req_unlink(mq_gw_client_t *c, mq_gw_req_t *r) -{ - mq_gw_req_t **pp = &c->reqs; - while (*pp) { - if (*pp == r) { - *pp = r->next; - return; - } - pp = &(*pp)->next; - } -} - -/* Free the per-request state once BOTH sides are detached. Idempotent guard via - * the two flags. */ -static void -gw_req_maybe_free(mq_gw_req_t *r) -{ - if (!r->local_dead || !r->h3_dead) return; - gw_req_unlink(r->cli, r); - free(r->spill); - free(r); -} - -/* The LOCAL side terminated (adapter output dead: resp_body returned -1, or - * local peer died mid-upload). Detach the sink and, if the tunnel is still - * live, reset it so its on_close eventually frees the struct. Does NOT call - * any sink op — the adapter already knows the local side is dead. */ -static void -gw_local_dead(mq_gw_req_t *r) -{ - if (r->local_dead) return; - r->local_dead = 1; - r->sink = NULL; - r->sink_user = NULL; - if (!r->h3_dead && r->req) { - /* Mid-stream local death: reset the H3 request (truncated). on_close - * fires later and finishes the teardown. */ - mq_h3_req_reset(r->req); - } - gw_req_maybe_free(r); -} - -/* Finish the local side cleanly (response fully relayed). Calls the sink's - * resp_finish, then marks local done and frees if both sides are dead. */ -static void -gw_local_finish(mq_gw_req_t *r) -{ - if (r->local_dead) return; - const mq_gw_sink_ops_t *sink = r->sink; - void *su = r->sink_user; - r->local_dead = 1; - r->sink = NULL; - r->sink_user = NULL; - if (sink) sink->resp_finish(su); - gw_req_maybe_free(r); -} - -/* Abort the local side (mid-stream truncation visible to the local peer). - * Calls the sink's resp_abort, then marks local done and frees if both dead. */ -static void -gw_local_abort(mq_gw_req_t *r) -{ - if (r->local_dead) return; - const mq_gw_sink_ops_t *sink = r->sink; - void *su = r->sink_user; - r->local_dead = 1; - r->sink = NULL; - r->sink_user = NULL; - if (sink) sink->resp_abort(su); - gw_req_maybe_free(r); -} - -/* ── neutral intake: prevalidate + req_begin (core) ─────────────────────────*/ - -/* Phase 1: header-only checks that run BEFORE the adapter parses the fetch - * envelope's target/method — duplicate X-Mq-* control headers, then X-Mq-Auth - * presence + "Bearer " format. Returns MQ_GW_OK or the reject reason - * (+ *status). No tunnel touch. */ -mq_gw_reject_reason_t -mq_gw_client_prevalidate(mq_gw_client_t *c, const mq_h3_header_t *headers, size_t n, - int *status) -{ - (void)c; - /* 1. duplicate X-Mq-* control headers → 400. */ - if (nhas_dup_xmq(headers, n)) { - if (status) *status = 400; - return MQ_GW_REJ_DUP_CONTROL; - } - /* 2. X-Mq-Auth present + "Bearer " format check (client-side). */ - size_t auth_vl = 0; - const char *auth = nfind_hdr(headers, n, "x-mq-auth", &auth_vl); - if (!auth) { - if (status) *status = 400; - return MQ_GW_REJ_MISSING_AUTH; - } - { - const char *pfx = "Bearer "; - size_t pl = strlen(pfx); - if (auth_vl <= pl || strncmp(auth, pfx, pl) != 0) { - if (status) *status = 400; - return MQ_GW_REJ_BAD_AUTH; - } - } - return MQ_GW_OK; -} - -/* Phase 2: begin a request (head already split + prevalidated). The core does, - * IN ORDER: X-Mq-Origin-Protocol(400), X-Mq-Cache(400), header-size(400), tunnel - * liveness(502); then opens the H3 request, forwards pseudo + x-mq-* + ordinary - * headers, and streams the body via the sink. On reject sets *err_status + - * *reason and returns NULL (the adapter renders the error). */ -mq_gw_xreq_t * -mq_gw_client_req_begin(mq_gw_client_t *c, const mq_gw_req_head_t *head, - const mq_gw_sink_ops_t *sink, void *sink_user, int *err_status, - mq_gw_reject_reason_t *reason) -{ - const mq_h3_header_t *H = head->headers; - size_t HN = head->n_headers; - - /* 4a. X-Mq-Origin-Protocol if present: validate via the shared parser. A present, - * non-empty, UNRECOGNIZED token is a caller bug → 400. Absent / empty → DEFAULT - * (no preference; not re-emitted below). */ - size_t xop_vl = 0; - const char *xop = nfind_hdr(H, HN, "x-mq-origin-protocol", &xop_vl); - mq_http_ver_t xop_ver = MQ_HTTP_VER_DEFAULT; - if (xop && xop_vl > 0) { - xop_ver = mq_gw_parse_http_ver(xop, xop_vl); - if (xop_ver == MQ_HTTP_VER_DEFAULT) { - *err_status = 400; - *reason = MQ_GW_REJ_BAD_ORIGIN_PROTO; - return NULL; - } - } - - /* 4a'. X-Mq-Cache if present: validate via the shared parser. A present, non-empty, - * unparseable TTL is a caller bug → 400. Absent / empty → no caching. */ - size_t xmc_vl = 0; - const char *xmc = nfind_hdr(H, HN, "x-mq-cache", &xmc_vl); - unsigned xmc_ttl = 0; - if (xmc && xmc_vl > 0) { - xmc_ttl = mq_gw_parse_cache_ttl(xmc, xmc_vl); - if (xmc_ttl == 0) { - *err_status = 400; - *reason = MQ_GW_REJ_BAD_CACHE_TTL; - return NULL; - } - } - - /* 4b. Reject (BEFORE opening the H3 request) any header that would not fit the - * forwarding arena, instead of silently clamping it. A clamped X-Mq-Auth can't - * authenticate anyway, and a clamped forwarded value is a corruption / smuggling - * surface; fail closed with 400 header-too-long. */ - int forward_cookie = 0; - { - const char *fc = nfind_hdr(H, HN, "x-mq-forward-cookie", NULL); - if (fc) forward_cookie = slice_ieq(fc, strlen(fc), "true"); - } - size_t auth_vl = 0; - const char *auth = nfind_hdr(H, HN, "x-mq-auth", &auth_vl); - size_t xae_vl = 0; - const char *xae = nfind_hdr(H, HN, "x-mq-accept-encoding", &xae_vl); - int inject_ae = (xae && xae_vl > 0); /* present AND non-empty (empty => no-op) */ - { - if (auth_vl >= MQ_GW_HDR_VAL_CAP) { - *err_status = 400; - *reason = MQ_GW_REJ_HEADER_TOO_LONG; - return NULL; - } - size_t xcl_vl = 0; - const char *xcl = nfind_hdr(H, HN, "x-mq-class", &xcl_vl); - if (xcl && xcl_vl >= MQ_GW_HDR_VAL_CAP) { - *err_status = 400; - *reason = MQ_GW_REJ_HEADER_TOO_LONG; - return NULL; - } - if (inject_ae && xae_vl >= MQ_GW_HDR_VAL_CAP) { - *err_status = 400; - *reason = MQ_GW_REJ_HEADER_TOO_LONG; - return NULL; - } - for (size_t i = 0; i < HN; i++) { - const char *n = H[i].name; - size_t nl = strlen(n); - if (mq_gw_strip_client(n, nl, forward_cookie)) continue; - if (nl >= MQ_GW_HDR_NAME_CAP || strlen(H[i].value) >= MQ_GW_HDR_VAL_CAP) { - *err_status = 400; - *reason = MQ_GW_REJ_HEADER_TOO_LONG; - return NULL; - } - } - } - - /* 5. tunnel must be up. */ - if (!c->conn_up || !c->conn) { - *err_status = 502; - *reason = MQ_GW_REJ_TUNNEL_UNAVAIL; - return NULL; - } - - /* Accept: open the H3 request. */ - mq_h3_req_t *hr = mq_h3_req_open(c->conn); - if (!hr) { - *err_status = 502; - *reason = MQ_GW_REJ_TUNNEL_UNAVAIL; - return NULL; - } - - mq_gw_req_t *r = calloc(1, sizeof(*r)); - if (!r) { - mq_h3_req_reset(hr); - *err_status = 502; - *reason = MQ_GW_REJ_INTERNAL; - return NULL; - } - r->cli = c; - r->sink = sink; - r->sink_user = sink_user; - r->req = hr; - r->resp_status = 0; - - /* fin = no body. Per mq_gw_intake.h: content_length == 0 → NO body (fin on - * headers); -1 → streaming body (unknown length, e.g. a chunked H2 upload); - * > 0 → known-length body. ONLY 0 means bodyless — both -1 and > 0 keep the - * stream OPEN (no fin) and expect req_body / req_body_done (codex-1 High: the - * old `<= 0` test FIN'd a -1 streaming upload before its body). - * - * NOTE for the H1 fetch ingress: a bodyless fetch request reaches the listener - * with Content-Length ABSENT (parsed as -1). The fetch adapter NORMALIZES that - * to 0 before req_begin (see mq_gw_fetch_adapter.c head.content_length), so a - * bodyless fetch request arrives here as 0 → fin-on-headers (unchanged). The - * fetch path never delivers a true streaming/unknown-length body (it rejects - * chunked Transfer-Encoding with 411), so -1 only ever arrives from the H2 MITM - * adapter, where it correctly means "DATA follows". */ - int no_body = (head->content_length == 0); - - /* Build the forwarded header list. NUL-terminated C strings are required by - * mq_h3_req_send_headers, so copy each (name,value) into a per-request arena. - * The arena lives only for the duration of this call (send_headers copies the - * iovecs synchronously). */ - mq_h3_header_t hs[MQ_GW_MAX_SEND_HDRS]; - static const size_t NS = MQ_GW_HDR_NAME_CAP, VS = MQ_GW_HDR_VAL_CAP; - char *namebuf = malloc(MQ_GW_MAX_SEND_HDRS * NS); - char *valbuf = malloc(MQ_GW_MAX_SEND_HDRS * VS); - if (!namebuf || !valbuf) { - free(namebuf); - free(valbuf); - mq_h3_req_reset(hr); - r->req = NULL; - r->h3_dead = 1; - free(r); - *err_status = 502; - *reason = MQ_GW_REJ_INTERNAL; - return NULL; - } - size_t nh = 0; - - /* :method / :scheme / :authority / :path (pseudo-headers first). */ - hs[nh].name = ":method"; - hs[nh].value = head->method; - nh++; - hs[nh].name = ":scheme"; - hs[nh].value = head->scheme; - nh++; - hs[nh].name = ":authority"; - hs[nh].value = head->authority; - nh++; - hs[nh].name = ":path"; - hs[nh].value = head->path; - nh++; - - /* x-mq-auth: forward the ORIGINAL header value (server verifies). */ - { - char *nb = namebuf + nh * NS; - char *vb = valbuf + nh * VS; - size_t vl = auth_vl < VS - 1 ? auth_vl : VS - 1; - memcpy(nb, "x-mq-auth", 10); - memcpy(vb, auth, vl); - vb[vl] = '\0'; - hs[nh].name = nb; - hs[nh].value = vb; - nh++; - } - - /* x-mq-class if present. */ - { - size_t cl_vl = 0; - const char *cls = nfind_hdr(H, HN, "x-mq-class", &cl_vl); - if (cls && nh < MQ_GW_MAX_SEND_HDRS) { - char *nb = namebuf + nh * NS; - char *vb = valbuf + nh * VS; - size_t vl = cl_vl < VS - 1 ? cl_vl : VS - 1; - memcpy(nb, "x-mq-class", 11); - memcpy(vb, cls, vl); - vb[vl] = '\0'; - hs[nh].name = nb; - hs[nh].value = vb; - nh++; - } - } - - /* x-mq-origin-protocol: re-emit the RAW validated token only when a recognized - * choice was parsed (DEFAULT = no preference, not conveyed). */ - if (xop_ver != MQ_HTTP_VER_DEFAULT && nh < MQ_GW_MAX_SEND_HDRS) { - char *nb = namebuf + nh * NS; - char *vb = valbuf + nh * VS; - size_t vl = xop_vl < VS - 1 ? xop_vl : VS - 1; - memcpy(nb, "x-mq-origin-protocol", 21); - memcpy(vb, xop, vl); - vb[vl] = '\0'; - hs[nh].name = nb; - hs[nh].value = vb; - nh++; - } - - /* x-mq-cache: re-emit the RAW validated TTL token only when a valid opt-in TTL - * was parsed (absent / empty = no caching, not conveyed). */ - if (xmc_ttl != 0 && nh < MQ_GW_MAX_SEND_HDRS) { - char *nb = namebuf + nh * NS; - char *vb = valbuf + nh * VS; - size_t vl = xmc_vl < VS - 1 ? xmc_vl : VS - 1; - memcpy(nb, "x-mq-cache", 11); - memcpy(vb, xmc, vl); - vb[vl] = '\0'; - hs[nh].name = nb; - hs[nh].value = vb; - nh++; - } - - /* accept-encoding: emit when X-Mq-Accept-Encoding opt-in is present. */ - if (inject_ae && nh < MQ_GW_MAX_SEND_HDRS) { - char *nb = namebuf + nh * NS; - char *vb = valbuf + nh * VS; - size_t vl = xae_vl < VS - 1 ? xae_vl : VS - 1; - memcpy(nb, "accept-encoding", 16); - memcpy(vb, xae, vl); - vb[vl] = '\0'; - hs[nh].name = nb; - hs[nh].value = vb; - nh++; - } - - /* content-length: re-emit the recomputed value over the tunnel when the - * request has a known body length (CL > 0). The ORIGINAL Content-Length header - * is stripped (mq_gw_strip_client) and we emit our own validated value so the - * value the server sees is the one this client committed to streaming. CL == 0 - * means fin-on-headers / no body (no_body above), so no content-length is - * needed in that case. */ - if (head->content_length > 0 && nh < MQ_GW_MAX_SEND_HDRS) { - char *nb = namebuf + nh * NS; - char *vb = valbuf + nh * VS; - memcpy(nb, "content-length", 15); - int vn = snprintf(vb, VS, "%lld", (long long)head->content_length); - if (vn > 0 && (size_t)vn < VS) { - hs[nh].name = nb; - hs[nh].value = vb; - nh++; - } - } - - /* Remaining request headers, EXCEPT those stripped client-side (hop-by-hop, - * X-Mq-*, Host, Content-Length, Cookie). Lowercase the name (H3 requires - * lowercase field names). */ - for (size_t i = 0; i < HN && nh < MQ_GW_MAX_SEND_HDRS; i++) { - const char *n = H[i].name; - size_t nl = strlen(n); - if (mq_gw_strip_client(n, nl, forward_cookie)) continue; - if (inject_ae && slice_ieq(n, nl, "accept-encoding")) continue; - char *nb = namebuf + nh * NS; - char *vb = valbuf + nh * VS; - size_t cnl = nl < NS - 1 ? nl : NS - 1; - for (size_t j = 0; j < cnl; j++) { - char ch = n[j]; - if (ch >= 'A' && ch <= 'Z') ch = (char)(ch - 'A' + 'a'); - nb[j] = ch; - } - nb[cnl] = '\0'; - size_t hvl = strlen(H[i].value); - size_t vl = hvl < VS - 1 ? hvl : VS - 1; - memcpy(vb, H[i].value, vl); - vb[vl] = '\0'; - hs[nh].name = nb; - hs[nh].value = vb; - nh++; - } - - /* Wire up H3 callbacks before sending so on_close cannot be lost. */ - mq_h3_req_set_cbs(hr, h3_on_read, h3_on_write, h3_on_close, r); - - long sh = mq_h3_req_send_headers(hr, hs, nh, no_body ? 1 : 0); - free(namebuf); - free(valbuf); - - if (sh < 0) { - /* Hard send error: reset H3, synthesize 502. Detach the callbacks FIRST or - * the deferred on_close would run on freed memory. r is not yet linked. */ - mq_h3_req_set_cbs(hr, NULL, NULL, NULL, NULL); - mq_h3_req_reset(hr); - r->req = NULL; - r->h3_dead = 1; - free(r); - *err_status = 502; - *reason = MQ_GW_REJ_TUNNEL_UNAVAIL; - return NULL; - } - if (sh == 0) { - /* EAGAIN on the header send (0 == NOTHING accepted). Essentially - * unreachable; we fail closed with 502 rather than carry retry complexity. */ - MQ_LOGW("mq_gw_client: header send EAGAIN (flow-control); failing closed"); - mq_h3_req_set_cbs(hr, NULL, NULL, NULL, NULL); - mq_h3_req_reset(hr); - r->req = NULL; - r->h3_dead = 1; - free(r); - *err_status = 502; - *reason = MQ_GW_REJ_TUNNEL_UNAVAIL; - return NULL; - } - - if (no_body) r->upload_fin_sent = 1; - - /* Link the live request. */ - r->next = c->reqs; - c->reqs = r; - return r; -} - - -/* ── upload (junction #1) ───────────────────────────────────────────────────*/ - -/* Try to push as much of the spill buffer into H3 as it will take. When the - * spill drains AND the upload is done, send the fin. Returns nothing; updates - * paused/resume bookkeeping. */ -static void -upload_pump(mq_gw_req_t *r) -{ - if (r->h3_dead || !r->req) return; - - /* Flush spill first. fin rides the LAST spill byte iff the local side has - * signalled body-done (no more chunks will arrive) and this segment is the - * tail. */ - while (r->spill && r->spill_off < r->spill_len) { - size_t avail = r->spill_len - r->spill_off; - int last = (r->upload_done && !r->upload_fin_sent); - long acc = - mq_h3_req_send_body(r->req, r->spill + r->spill_off, avail, last ? 1 : 0); - if (acc < 0) { - /* H3 hard error: reset; on_close handles the rest. */ - mq_h3_req_reset(r->req); - return; - } - if (acc == 0) break; /* still blocked; wait for next on_write */ - r->spill_off += (size_t)acc; - if (last && (size_t)acc == avail) - r->upload_fin_sent = 1; /* fin went out with the last byte */ - } - - /* Spill drained? Reset the buffer + (maybe) resume the adapter input. */ - if (r->spill && r->spill_off >= r->spill_len) { - r->spill_len = 0; - r->spill_off = 0; - if (r->paused && !r->local_dead && r->sink) { - r->paused = 0; - r->sink->resume_read(r->sink_user); - } - } - - /* All body delivered (on_body_done) and spill empty but fin not yet sent - * (e.g. the spill was already empty when body-done fired, or the fin could - * not ride a body byte) → send a bare fin. xqc_h3_request_finish on a - * 0-length tail does not consume flow control, so EAGAIN here is effectively - * unreachable; treat a non-negative return as success. */ - if (r->upload_done && !r->upload_fin_sent && - (!r->spill || r->spill_off >= r->spill_len)) { - long fr = mq_h3_req_finish(r->req); - if (fr < 0) { - mq_h3_req_reset(r->req); - return; - } - r->upload_fin_sent = 1; - } -} - -/* Neutral upload intake: feed `len` body bytes toward the tunnel. Returns 0 to - * keep reading, -1 to pause (the bytes ARE consumed — the adapter must not - * re-deliver them). Body of the former gw_on_body. */ -int -mq_gw_client_req_body(mq_gw_xreq_t *r, const uint8_t *p, size_t len) -{ - if (r->local_dead) return 0; - if (r->h3_dead || !r->req) { - /* Tunnel gone but listener still feeding: drop bytes, ask for pause to - * stop further delivery; the eventual local teardown handles cleanup. */ - return -1; - } - - /* Append the new chunk after any spill that is still pending. The listener - * contract: a -1 return means "pause AFTER consuming this chunk" — so we - * MUST buffer the chunk if H3 cannot take all of it. */ - size_t off = 0; - - /* If nothing is buffered, try to send directly first (fast path). */ - if (!r->spill || r->spill_off >= r->spill_len) { - long acc = mq_h3_req_send_body(r->req, p, len, 0); - if (acc < 0) { - mq_h3_req_reset(r->req); - return -1; - } - off = (size_t)(acc > 0 ? acc : 0); - if (off >= len) return 0; /* fully accepted; no pause needed */ - } - - /* Buffer the remainder [p+off, p+len). Compact any drained spill first. */ - if (r->spill && r->spill_off > 0) { - memmove(r->spill, r->spill + r->spill_off, r->spill_len - r->spill_off); - r->spill_len -= r->spill_off; - r->spill_off = 0; - } - size_t need = len - off; - size_t newlen = r->spill_len + need; - if (newlen > MQ_GW_UPLOAD_SPILL_MAX) { - /* Over the bound: still consume (contract) but the buffer is full. We - * grow up to the bound and keep what fits; anything beyond is a hard - * backpressure failure → reset the request rather than lose bytes - * silently. In practice the listener pauses on our -1 before we reach - * here, so this is a defensive ceiling. */ - MQ_LOGW("mq_gw_client: upload spill ceiling hit (%zu); resetting request", - newlen); - mq_h3_req_reset(r->req); - return -1; - } - if (!r->spill) { - r->spill = malloc(MQ_GW_UPLOAD_SPILL_MAX); - if (!r->spill) { - mq_h3_req_reset(r->req); - return -1; - } - r->spill_len = 0; - r->spill_off = 0; - } - memcpy(r->spill + r->spill_len, p + off, need); - r->spill_len += need; - - /* Buffer non-empty → pause the listener (consume-on-deliver honored). */ - r->paused = 1; - return -1; -} - -/* Neutral upload intake: the adapter will deliver no more body bytes. Body of - * the former gw_on_body_done. */ -void -mq_gw_client_req_body_done(mq_gw_xreq_t *r) -{ - r->upload_done = 1; - if (r->local_dead || r->h3_dead || !r->req) return; - /* Flush any spill and send the fin if everything is out. */ - upload_pump(r); -} - -/* Neutral intake: the adapter's input side died mid-request (truncated). Reset - * the H3 request (never finish) and detach the sink side. The adapter is - * already tearing down — do NOT call any sink op (the adapter handles its own - * cleanup). */ -void -mq_gw_client_req_aborted(mq_gw_xreq_t *r) -{ - r->sink = NULL; - r->sink_user = NULL; - if (r->local_dead) { - gw_req_maybe_free(r); - return; - } - r->local_dead = 1; - if (!r->h3_dead && r->req) mq_h3_req_reset(r->req); - gw_req_maybe_free(r); -} - -/* Neutral intake: the adapter's output drained below its low watermark — resume - * relaying the response body (replaces the listener drain_cb notification). */ -void -mq_gw_client_req_drained(mq_gw_xreq_t *r) -{ - if (r->local_dead) return; - if (r->read_deferred) { - r->read_deferred = 0; - download_pump(r); - } -} - -/* ── download (junction #2): H3 response → adapter SINK ───────────────────── - * - * The core extracts the H3 response head into a NEUTRAL (status, header-list, - * body_mode) tuple and drives the adapter sink (resp_head/resp_body/resp_finish/ - * resp_abort). The in-file H1 sink (below) renders today's exact HTTP/1.1 bytes - * (status line, Connection: close, chunked framing) so observable output is - * byte-identical; a future H2/H3 adapter renders natively. */ - -/* Per-response header caps. The HARD limit on the response head is the H1 sink's - * 8192-byte render buffer (h1_resp_head); MQ_GW_RESP_MAX_HDRS / _ARENA are sized - * so that buffer fills FIRST. This reproduces the pre-refactor behavior, which had - * per-field caps + an 8192 render buffer and NO header-COUNT cap — an origin - * sending many small headers (e.g. 100+ Set-Cookie) must relay, not 502. */ -#define MQ_GW_RESP_NAME_CAP 128 -#define MQ_GW_RESP_VAL_CAP 2048 -#define MQ_GW_RESP_MAX_HDRS 2048 -#define MQ_GW_RESP_ARENA 16384 - -/* Header-capture context for recv_headers (download). Collects the response - * headers into a NEUTRAL mq_h3_header_t list (pointers into a shared NUL- - * terminated arena) instead of serializing HTTP/1.1 inline — the sink renders. - * Slots/arena are sized generously so the sink's 8192-byte buffer is the binding - * limit (not a count cap), matching the original byte-bounded behavior. */ -typedef struct { - mq_gw_req_t *r; - int status; - int has_cl; /* response carried a content-length */ - mq_h3_header_t hs[MQ_GW_RESP_MAX_HDRS]; - size_t nh; - char arena[MQ_GW_RESP_ARENA]; /* name\0 value\0 ... for all collected headers */ - size_t arena_off; - int ok; /* head fit + well-formed */ -} dl_hdr_ctx_t; - -static void -dl_each_header(const char *n, size_t nl, const char *v, size_t vl, void *u) -{ - dl_hdr_ctx_t *ctx = (dl_hdr_ctx_t *)u; - if (!ctx->ok) return; - - /* :status pseudo-header → status line. An H3 :status is EXACTLY three ASCII - * digits (RFC 9114/9113 response semantics). A hostile peer that sends e.g. - * ":status: 99999999999" must not be accumulated into a signed int (overflow - * is UB → UBSan abort); reject anything that is not 3 digits as an - * upstream-protocol failure (ctx->ok=0 → caller synthesizes 502). */ - if (nl == 7 && memcmp(n, ":status", 7) == 0) { - if (vl != 3 || v[0] < '0' || v[0] > '9' || v[1] < '0' || v[1] > '9' || - v[2] < '0' || v[2] > '9') { - ctx->ok = 0; - return; - } - int code = (v[0] - '0') * 100 + (v[1] - '0') * 10 + (v[2] - '0'); - if (code < 100 || code > 599) code = 502; - ctx->status = code; - return; - } - - /* Other pseudo-headers (none expected on a response) → drop. */ - if (nl > 0 && n[0] == ':') return; - - /* Strip hop-by-hop. KEEP x-mq-* diagnostic headers (design: X-Mq-Origin- - * Protocol etc. SHOULD reach the local client). */ - if (mq_gw_strip_hop(n, nl)) return; - - /* content-length → passthrough (and remember we have one). */ - if (slice_ieq(n, nl, "content-length")) ctx->has_cl = 1; - - /* Build NUL-terminated name/value (bounded). An origin response header NAME or - * VALUE that would not fit must FAIL the response (ctx->ok = 0 → caller takes - * the failure path), not be silently truncated: a clipped value corrupts the - * downstream response, and origin headers this large are pathological — failing - * closed beats corrupting. */ - if (nl >= MQ_GW_RESP_NAME_CAP || vl >= MQ_GW_RESP_VAL_CAP) { - ctx->ok = 0; - return; - } - /* Fail closed if we run out of header slots or arena space. Both are sized so - * the sink's 8192-byte render buffer fills first, so in practice the binding - * limit is the byte buffer (matching pre-refactor behavior), not a count. */ - if (ctx->nh >= MQ_GW_RESP_MAX_HDRS) { - ctx->ok = 0; - return; - } - if (ctx->arena_off + nl + 1 + vl + 1 > sizeof(ctx->arena)) { - ctx->ok = 0; - return; - } - char *nb = ctx->arena + ctx->arena_off; - memcpy(nb, n, nl); - nb[nl] = '\0'; - ctx->arena_off += nl + 1; - char *vb = ctx->arena + ctx->arena_off; - memcpy(vb, v, vl); - vb[vl] = '\0'; - ctx->arena_off += vl + 1; - ctx->hs[ctx->nh].name = nb; - ctx->hs[ctx->nh].value = vb; - ctx->nh++; -} - -/* Extract the H3 response head and drive the sink's resp_head. Returns 0 on - * success (head delivered), -1 on a hard failure (caller aborts). */ -static int -download_start_response(mq_gw_req_t *r) -{ - dl_hdr_ctx_t *ctx = malloc(sizeof(*ctx)); - if (!ctx) return -1; - memset(ctx, 0, sizeof(*ctx)); - ctx->r = r; - ctx->ok = 1; - - int fin = 0; - int n = mq_h3_req_recv_headers(r->req, dl_each_header, ctx, &fin); - if (n < 0 || !ctx->ok || ctx->status == 0) { - /* recv error / malformed / no :status → malformed response. */ - free(ctx); - return -1; - } - r->resp_status = ctx->status; - - /* No content-length → unknown length (the H1 sink frames the body chunked). */ - mq_gw_body_mode_t mode = ctx->has_cl ? MQ_GW_BODY_CONTENT_LENGTH : MQ_GW_BODY_STREAM; - - int hr = r->sink->resp_head(r->sink_user, ctx->status, ctx->hs, ctx->nh, mode); - free(ctx); - if (hr < 0) return -1; - r->resp_started = 1; - - /* If headers carried fin (no body), finish immediately. Use gw_local_finish - * (not a direct resp_finish call) so that r->local_dead + r->sink are cleared - * atomically — gw_local_finish sets local_dead=1 BEFORE calling resp_finish, - * ensuring h3_on_close (which fires LATER on the same event loop iteration) - * sees local_dead and skips any further sink op. A direct resp_finish call - * here would free the adapter's per-request state without clearing r->sink / - * r->sink_user, causing h3_on_close to call resp_abort on freed memory. */ - if (fin) gw_local_finish(r); - return 0; -} - -/* Drain H3 body and feed it to the sink. Honors the sink highwater: resp_body - * returning 0 sets read_deferred and stops; the drain notification re-kicks. */ -static void -download_pump(mq_gw_req_t *r) -{ - if (r->local_dead || r->h3_dead || !r->req) return; - if (r->read_deferred) return; /* waiting for sink drain */ - - uint8_t buf[MQ_GW_DOWNLOAD_CHUNK]; - for (;;) { - int fin = 0; - long n = mq_h3_req_recv_body(r->req, buf, sizeof(buf), &fin); - if (n < 0) { - /* Hard recv error mid-download → abort (truncation must be visible). */ - gw_local_abort(r); - return; - } - if (n > 0) { - int wr = r->sink->resp_body(r->sink_user, buf, (size_t)n); - if (wr < 0) { - /* Sink/local output dead → tear down the request (resets H3). */ - gw_local_dead(r); - return; - } - if (wr == 0) { - r->read_deferred = 1; - if (!fin) return; - } - } - if (fin) { - gw_local_finish(r); - return; - } - if (n == 0) return; /* no more body available right now */ - } -} - -/* ── H3 request callbacks ───────────────────────────────────────────────────*/ - -static void -h3_on_read(mq_h3_req_t *hr, int flag, void *user) -{ - (void)hr; - mq_gw_req_t *r = (mq_gw_req_t *)user; - if (r->local_dead) { - /* Local gone: keep draining/discarding so the stream can finish, but do - * not write anywhere. We already reset on local death, so just return. */ - return; - } - - if (flag & (XQC_REQ_NOTIFY_READ_HEADER | XQC_REQ_NOTIFY_READ_TRAILER)) { - if (!r->resp_started) { - /* First (and for us only) header section → build local response. */ - if (download_start_response(r) != 0) { - /* Malformed/short response head → abort if started else 502. - * NOTE: download_start_response only ever returns non-zero BEFORE - * it sets r->resp_started (every failure path returns earlier), so - * the resp_started branch is defensive-only / currently - * unreachable; it is kept to stay correct if that ordering ever - * changes (e.g. a post-head write failure path is added). */ - if (r->resp_started) - gw_local_abort(r); - else - gw_local_error_req(r, 502, "upstream-protocol"); - return; - } - } else { - /* Trailer section: drain + ignore (we already sent Connection:close - * and either CL or chunked framing; trailers are not forwarded in the - * MVP). recv to keep xquic's flow moving. */ - int tfin = 0; - mq_h3_req_recv_headers(r->req, NULL, NULL, &tfin); - } - } - - if (r->local_dead) return; - - /* Drain any available body. */ - download_pump(r); -} - -static void -h3_on_write(mq_h3_req_t *hr, void *user) -{ - (void)hr; - mq_gw_req_t *r = (mq_gw_req_t *)user; - if (r->h3_dead || !r->req) return; - /* A previously-blocked send can resume: flush upload spill / fin. */ - upload_pump(r); -} - -static void -h3_on_close(mq_h3_req_t *hr, void *user) -{ - (void)hr; - mq_gw_req_t *r = (mq_gw_req_t *)user; - r->h3_dead = 1; - r->req = NULL; /* freed right after this returns */ - - if (!r->local_dead) { - /* Tunnel closed. If the response never started, synthesize 502 via the - * sink; else the local side must see a truncation (abort, not a fake - * clean finish). Detach the sink INLINE (do NOT call gw_local_* helpers - * — they call gw_req_maybe_free, which would free r out from under the - * final maybe_free below → use-after-free). */ - const mq_gw_sink_ops_t *sink = r->sink; - void *su = r->sink_user; - r->local_dead = 1; - r->sink = NULL; - r->sink_user = NULL; - if (sink) { - if (!r->resp_started) { - /* Synthesize 502 with X-Mq-Error: upstream-reset. */ - mq_h3_header_t eh[2]; - eh[0].name = "X-Mq-Error"; - eh[0].value = "upstream-reset"; - eh[1].name = "Content-Length"; - eh[1].value = "0"; - int hr2 = sink->resp_head(su, 502, eh, 2, MQ_GW_BODY_CONTENT_LENGTH); - if (hr2 >= 0) - sink->resp_finish(su); - else - sink->resp_abort(su); - } else { - sink->resp_abort(su); - } - } - } - gw_req_maybe_free(r); /* both sides now dead → frees r exactly once */ -} - -/* Synthesize an error response via the sink on a request whose local side is - * still live (used from the read path when the response head is malformed). */ -static void -gw_local_error_req(mq_gw_req_t *r, int code, const char *xmq) -{ - if (r->local_dead || !r->sink) return; - const mq_gw_sink_ops_t *sink = r->sink; - void *su = r->sink_user; - r->local_dead = 1; - r->sink = NULL; - r->sink_user = NULL; - mq_h3_header_t eh[2]; - eh[0].name = "X-Mq-Error"; - eh[0].value = (char *)xmq; - eh[1].name = "Content-Length"; - eh[1].value = "0"; - int hr = sink->resp_head(su, code, eh, 2, MQ_GW_BODY_CONTENT_LENGTH); - if (hr >= 0) - sink->resp_finish(su); - else - sink->resp_abort(su); - if (!r->h3_dead && r->req) mq_h3_req_reset(r->req); - gw_req_maybe_free(r); -} - -/* ── conn state ─────────────────────────────────────────────────────────────*/ - -static void -gw_conn_state(mq_h3_conn_t *c, int established, void *user) -{ - (void)c; - mq_gw_client_t *cli = (mq_gw_client_t *)user; - if (established) { - cli->conn_up = 1; - MQ_LOGI("mq_gw_client: tunnel conn established"); - /* Back to SERVING: reset the backoff counter + disarm the reconnect timer - * so the next loss starts from base again. The gateway has no auth latch, - * so `established` IS the reconnect-success hook. */ - cli->reconnect_attempts = 0; - gw_reconnect_stop(cli); - /* Re-arm multipath for this (possibly reconnected) conn: reset added_paths - * so the mp-timer re-adds every registered extra path on the new conn. - * Harmless on the first establish (added_paths is 0; arm is a no-op when - * n_extra_paths == 0). */ - cli->added_paths = 0; - gw_mp_timer_arm(cli); - } else { - MQ_LOGI("mq_gw_client: tunnel conn closed"); - /* In-flight requests get their own per-request on_close from mq_h3, which - * handles their local teardown. Nothing to do here for them. */ - - /* Decide reconnect vs terminal from shutting_down + reconnect_enabled ONLY. - * reconnect_ev != NULL also guards the timer existing. */ - int reconnect_now = - (!cli->shutting_down && cli->reconnect_enabled && cli->reconnect_ev != NULL); - - if (reconnect_now) { - /* Do NOT go terminal: tear down the dead conn's per-conn state and arm - * the backoff timer to re-establish. conn stays NULL through the backoff - * window; gw_issue_connect re-wires a fresh conn. */ - cli->conn_up = 0; - cli->conn = NULL; /* the conn wrapper is freed after this returns */ - cli->added_paths = 0; - gw_mp_timer_stop(cli); - gw_reconnect_arm(cli); - } else { - /* Terminal (shutting_down or reconnect disabled): legacy behavior. */ - cli->conn_up = 0; - cli->conn = NULL; /* the conn wrapper is freed after this returns */ - } - } -} - -/* ── mp-poll timer (mirrors mq_client) ──────────────────────────────────────*/ - -static void -gw_mp_timer_stop(mq_gw_client_t *c) -{ - if (c->mp_timer) { - event_free(c->mp_timer); - c->mp_timer = NULL; - } -} - -static void -gw_mp_timer_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - mq_gw_client_t *c = (mq_gw_client_t *)arg; - if (!c->conn) { - gw_mp_timer_stop(c); - return; - } - if (!mq_h3_conn_mp_ready(c->conn)) return; - while (c->added_paths < c->n_extra_paths) { - const char *ip = c->extra_paths[c->added_paths]; - int pid = mq_h3_conn_add_path(c->conn, ip, 0); - if (pid >= 0) - MQ_LOGI("mq_gw_client: extra path up: bind %s -> path_id %d", ip, pid); - else - MQ_LOGW("mq_gw_client: failed to add extra path bind %s (rc=%d)", ip, pid); - c->added_paths++; - } - gw_mp_timer_stop(c); -} - -static void -gw_mp_timer_arm(mq_gw_client_t *c) -{ - if (c->mp_timer || c->added_paths >= c->n_extra_paths) return; - struct event_base *base = mq_runtime_base(c->rt); - if (!base) return; - c->mp_timer = event_new(base, -1, EV_PERSIST, gw_mp_timer_cb, c); - if (!c->mp_timer) return; - struct timeval tv = {.tv_sec = 0, .tv_usec = MQ_GW_MP_POLL_MS * 1000}; - event_add(c->mp_timer, &tv); -} - -/* ── public API ─────────────────────────────────────────────────────────────*/ - -/* Issue the (re-)connect: open a fresh H3 tunnel conn with keepalive and wire the - * state callback. Re-invoked by the reconnect timer. Returns 0 on success, -1 on - * failure (the caller re-arms backoff). */ -static int -gw_issue_connect(mq_gw_client_t *c) -{ - c->conn = mq_h3_connect(c->h3, (struct sockaddr *)&c->peer, c->peerlen, c->cc, - c->keepalive_idle_ms, gw_conn_state, c); - if (!c->conn) { - MQ_LOGE("mq_gw_client: tunnel connect failed"); - return -1; - } - return 0; -} - -/* ── Reconnect controller (Phase 5b, mirrors mq_client) ────────────────────── - * - * reconnect_ev is created ONCE in mq_gw_client_new and freed ONLY in - * mq_gw_client_free; it is a one-shot timer re-armed across reconnect cycles, so - * stop() must NOT free it (unlike the mp-timer). */ - -/* Disarm the backoff timer (idempotent). Does NOT free reconnect_ev. */ -static void -gw_reconnect_stop(mq_gw_client_t *c) -{ - if (c->reconnect_ev) { - evtimer_del(c->reconnect_ev); - } -} - -/* Arm the one-shot backoff timer for the next reconnect attempt. Exponential - * (base 250 ms, cap reconnect_max_backoff_ms) with half-jitter into [d/2, d] so - * the re-arm cannot land near zero and spin. */ -static void -gw_reconnect_arm(mq_gw_client_t *c) -{ - if (!c->reconnect_ev) { - return; - } - c->reconnect_attempts++; - uint64_t d = mq_backoff_ms(MQ_GW_RECONNECT_BASE_MS, c->reconnect_max_backoff_ms, - c->reconnect_attempts); - uint64_t j = d / 2 + (uint64_t)(random() % (long)(d / 2 + 1)); - struct timeval tv = {.tv_sec = (time_t)(j / 1000), - .tv_usec = (suseconds_t)((j % 1000) * 1000)}; - evtimer_add(c->reconnect_ev, &tv); -} - -/* Backoff timer fired: re-issue the connect. On a synchronous connect failure - * re-arm with the next backoff rather than going terminal — an always-on device - * must self-heal across long outages. */ -static void -gw_reconnect_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - mq_gw_client_t *c = (mq_gw_client_t *)arg; - if (gw_issue_connect(c) != 0) { - gw_reconnect_arm(c); - } -} - -mq_gw_client_t * -mq_gw_client_new(mq_transport_t *t, mq_runtime_t *rt, mq_h3_t *h3, const char *server_ip, - uint16_t server_port, const char *token, mq_cc_t cc, - uint64_t keepalive_idle_ms, int reconnect_enabled, - uint64_t reconnect_max_backoff_ms) -{ - if (!t || !rt || !h3 || !server_ip || !token) return NULL; - - mq_gw_client_t *c = calloc(1, sizeof(*c)); - if (!c) return NULL; - c->t = t; - c->rt = rt; - c->h3 = h3; - c->cc = cc; - snprintf(c->token, sizeof(c->token), "%s", token); - - c->keepalive_idle_ms = keepalive_idle_ms; - c->reconnect_enabled = reconnect_enabled ? 1 : 0; - if (reconnect_max_backoff_ms < 1000) { - reconnect_max_backoff_ms = 1000; - } - c->reconnect_max_backoff_ms = reconnect_max_backoff_ms; - - memset(&c->peer, 0, sizeof(c->peer)); - c->peer.sin_family = AF_INET; - c->peer.sin_port = htons(server_port); - if (inet_pton(AF_INET, server_ip, &c->peer.sin_addr) != 1) { - free(c); - return NULL; - } - c->peerlen = sizeof(c->peer); - - /* Create the reconnect backoff timer ONCE (when enabled), BEFORE the eager - * connect so the timer exists if the first connect's conn later closes. It is - * one-shot, armed only from the close handler, disarmed on establish + on - * shutdown, and freed only in mq_gw_client_free. */ - if (c->reconnect_enabled) { - c->reconnect_ev = evtimer_new(mq_runtime_base(c->rt), gw_reconnect_cb, c); - if (!c->reconnect_ev) { - MQ_LOGW("mq_gw_client: reconnect timer alloc failed (reconnect disabled)"); - } - } - - /* Eager connect (mirror mq_client), using keepalive. */ - if (gw_issue_connect(c) != 0) { - if (c->reconnect_ev) { - event_free(c->reconnect_ev); - c->reconnect_ev = NULL; - } - free(c); - return NULL; - } - return c; -} - -const char * -mq_gw_client_token(const mq_gw_client_t *c) -{ - if (!c) return NULL; - return c->token; -} - -void -mq_gw_client_dump_stats(mq_gw_client_t *c) -{ - if (!c || !c->conn) return; - mq_h3_conn_dump_stats(c->conn); -} - -int -mq_gw_client_add_paths(mq_gw_client_t *c, const char *const *ips, size_t n) -{ - if (!c || (n > 0 && !ips)) return -1; - size_t accepted = 0; - for (size_t i = 0; i < n; i++) { - if (!ips[i]) continue; - if (c->n_extra_paths >= MQ_GW_MAX_PATHS) { - MQ_LOGW("mq_gw_client: extra-path capacity %d reached; ignoring %s", - MQ_GW_MAX_PATHS, ips[i]); - break; - } - snprintf(c->extra_paths[c->n_extra_paths], MQ_GW_IP_MAX, "%s", ips[i]); - c->n_extra_paths++; - accepted++; - } - gw_mp_timer_arm(c); - return (int)accepted; -} - -void -mq_gw_client_free(mq_gw_client_t *c) -{ - if (!c) return; - /* Mark teardown FIRST so a late conn close takes the terminal path (no - * reconnect-after-free). Then disarm + free the backoff timer. */ - c->shutting_down = 1; - gw_reconnect_stop(c); - if (c->reconnect_ev) { - event_free(c->reconnect_ev); - c->reconnect_ev = NULL; - } - gw_mp_timer_stop(c); - - /* DETACH the conn-state callback first. gw_client_free runs while the H3 - * engine is still live (sanctioned order), so the conn's close transition - * still fires LATER, during mq_h3_free / engine teardown — by then `c` is - * freed. Clearing on_state(user) here stops gw_conn_state from touching the - * freed gw_client. (The conn itself is borrowed; mq_h3 frees it.) */ - if (c->conn) mq_h3_conn_set_state_cb(c->conn, NULL, NULL); - - /* Tear down in-flight requests. SANCTIONED TEARDOWN ORDER (see header): - * gw_client_free runs FIRST, while the H3 engine is STILL LIVE — so touching - * r->req here is valid. For each live request we: - * 1. DETACH its H3 callbacks (set_cbs NULL) so the deferred on_close that - * mq_h3_req_reset schedules cannot re-enter h3_on_close on the - * mq_gw_req_t we are about to free (that would be a UAF + double free). - * 2. RESET the H3 request (truncate the tunnel side cleanly). - * 3. ABORT the local side via the sink (the local peer sees truncation). - * 4. Free the per-request state ourselves (we own it; both sides detached). - * Because we cleared the callbacks, the subsequent mq_h3_free engine teardown - * fires no callback back into us — our wrappers and r are already gone. - * - * CRITICAL — null EVERY request's H3 callbacks in a SEPARATE FIRST PASS, then - * reset every request's stream via a SINGLE connection close, and only THEN - * free the per-request states (codex-1 High / streaming-upload teardown UAF). - * Why all three steps are separated: - * - * - Nulling cbs first means no h3_on_close can fire during teardown, so no - * gw_req is surprise-freed/unlinked out from under the loop (the loop owns - * the whole list). - * - But xqc_h3_request_close() on ONE still-open stream (e.g. an in-flight - * streaming POST the server already remote-reset) can SYNCHRONOUSLY drive - * xqc_h3_request_destroy → mq_h3_request_close_notify, which free()s the - * mq_h3_req_t wrapper of OTHER streams too. So a per-request reset loop - * that derefs each r->req would touch a wrapper a sibling's close already - * freed → UAF. We therefore close the CONNECTION ONCE (mq_h3_conn_close - * resets all live streams in one shot; the freed wrappers fire no - * h3_on_close because cbs are nulled) and NEVER deref r->req again. - * - The final pass frees only the gw_req_t states (and their spill); it does - * not touch r->req (already reclaimed by the conn close above). - * - * Only observable once a streaming request can still be in-flight at shutdown - * — the all-GET fetch path always finished + freed each request before - * teardown, so c->reqs was empty and none of this ran. */ - for (mq_gw_req_t *p = c->reqs; p; p = p->next) { - if (!p->h3_dead && p->req) mq_h3_req_set_cbs(p->req, NULL, NULL, NULL, NULL); - } - /* Reset all live streams at once via a single connection close (avoids the - * sibling-cascade dangling-wrapper UAF of a per-request reset loop). */ - if (c->conn) mq_h3_conn_close(c->conn); - mq_gw_req_t *r = c->reqs; - while (r) { - mq_gw_req_t *next = r->next; - if (!r->local_dead && r->sink) { - r->sink->resp_abort(r->sink_user); - } - free(r->spill); - free(r); - r = next; - } - c->reqs = NULL; - free(c); -} diff --git a/src/gateway/mq_gw_client.h b/src/gateway/mq_gw_client.h deleted file mode 100644 index 0ddfb5f1..00000000 --- a/src/gateway/mq_gw_client.h +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_gw_client.h — client-side bridge between the local fetch-API listener - * (mq_fetch_listener) and an HTTP/3-over-MPQUIC tunnel to the gateway server - * (mq_h3). Task 3.2 of the Phase 2 HTTP Gateway plan. - * - * One fetch request (POST /_mqproxy/fetch on the local listener) becomes one - * H3 request on a single, eagerly-established gateway connection. The bridge: - * - * - validates the X-Mq-* control headers (auth format, target, method, no - * duplicate control headers) and the tunnel's liveness, answering 4xx/502 - * directly on the local connection on any failure; - * - on accept, opens an H3 request and forwards :method/:scheme/:authority/ - * :path + x-mq-auth + x-mq-class + the remaining non-stripped request - * headers, then streams the upload body with backpressure in BOTH directions; - * - relays the H3 response back onto the local connection, framing the body as - * chunked when the response carries no content-length, and surfacing - * mid-stream truncation as an abort (never a fake clean finish). - * - * LAYERING: this module owns the proxy semantics; it speaks the mq_fetch_cbs_t - * boundary (listener side) and the mq_h3 boundary (tunnel side) only. It never - * touches sockets directly — the listener owns the local fd, the transport + - * runtime own the UDP paths. - * - * OWNERSHIP / LIFETIME: - * - t / rt / h3 are BORROWED (the caller owns them). - * - SANCTIONED TEARDOWN ORDER (MANDATORY): - * - * mq_gw_client_free(gw) → mq_h3_free(h3) → mq_transport_free(t) - * - * gw_client MUST be freed FIRST, while the H3 engine is STILL LIVE. Doing so - * lets mq_gw_client_free detach each in-flight request's H3 callbacks - * (mq_h3_req_set_cbs(NULL,...)) and reset it against a live engine — safe, - * no UAF. If mq_h3_free ran first, the per-request wrappers would already be - * destroyed and any gw_client touch of r->req (set_cbs/reset) would be a UAF. - * mq_h3_free BEFORE mq_transport_free is the separate mq_h3.h contract. - * - Per-request state (mq_gw_xreq_t, internal) is owned by the bridge and freed - * exactly once on EVERY termination order — see mq_gw_client.c for the - * ownership rules across local-first / tunnel-first / racing teardown. - */ -#ifndef MQ_GATEWAY_MQ_GW_CLIENT_H -#define MQ_GATEWAY_MQ_GW_CLIENT_H - -#include -#include - -#include "gateway/mq_fetch_listener.h" -#include "gateway/mq_gw_intake.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" /* mq_cc_t */ -#include "transport/mq_h3.h" -#include "transport/mq_transport.h" - -typedef struct mq_gw_client_s mq_gw_client_t; - -/* Create a gateway client bridging fetch requests to an H3 tunnel. - * - * t / rt : the sans-io transport + its runtime (BORROWED; must outlive the - * gw_client). rt owns the libevent base used for the mp-poll timer. - * h3 : the mq_h3 ctx for `t` (BORROWED). On a pure client the caller may - * pass NULL server hooks to mq_h3_init. - * server_ip / server_port : the tunnel peer (the gateway server). - * token : the shared token EXPECTED in the X-Mq-Auth header. The bridge only - * FORMAT-checks the incoming header against "Bearer " - * client-side; the server performs the authoritative verification. - * The string is copied. - * cc : congestion control for the tunnel conn. - * keepalive_idle_ms : QUIC PING keepalive idle timeout for the tunnel conn - * (> 0 enables keepalive + sets the post-handshake idle timeout; - * 0 disables). Mirrors mq_client_set_keepalive. - * reconnect_enabled : 1 re-establishes the tunnel conn on loss with an - * exponential backoff (mirrors mq_client); 0 keeps the legacy - * terminal-on-close behavior. - * reconnect_max_backoff_ms : backoff cap (floored to 1000 ms). Ignored when - * reconnect is disabled. - * - * Because the connection is established EAGERLY in the constructor (there is no - * separate start), keepalive + reconnect are constructor args (not setters). - * - * Establishes the gateway H3 connection EAGERLY (mirrors mq_client's eager - * connect). Returns NULL on bad args / OOM / connect failure. */ -mq_gw_client_t *mq_gw_client_new(mq_transport_t *t, mq_runtime_t *rt, mq_h3_t *h3, - const char *server_ip, uint16_t server_port, - const char *token, mq_cc_t cc, - uint64_t keepalive_idle_ms, int reconnect_enabled, - uint64_t reconnect_max_backoff_ms); - -/* Register up to `n` extra local bind IPs to bring up as additional MPQUIC - * paths on the tunnel conn once it becomes multipath-ready. Mirrors - * mq_client_add_paths (deferred via a recurring mp-poll timer on rt's base). - * The IP strings are copied. Returns the number accepted (clamped), or -1 on - * bad args. */ -int mq_gw_client_add_paths(mq_gw_client_t *c, const char *const *ips, size_t n); - -/* Snapshot + log the gateway tunnel conn's per-path byte counters (spec §23.1), - * for e2e benchmarks that need to confirm both MPQUIC paths carried traffic on - * the GATEWAY conn (the mq_client TCP-core conn is dumped separately via - * mq_conn_dump_stats). Delegates to mq_h3_conn_dump_stats on the tunnel conn. - * No-op if the conn is not (yet) up. Smoke-safe on NULL. */ -void mq_gw_client_dump_stats(mq_gw_client_t *c); - -/* Free the gateway client: tear down any in-flight requests (detaching their H3 - * callbacks, resetting their H3 requests, aborting their local handles, dropping - * their per-request state) and the mp-poll timer. Does NOT free t / rt / h3. - * Safe on NULL. - * - * MUST be called BEFORE mq_h3_free (see the SANCTIONED TEARDOWN ORDER above): - * it touches r->req on live in-flight requests, which is only valid while the H3 - * engine still exists. */ -void mq_gw_client_free(mq_gw_client_t *c); - -/* ── Neutral intake boundary (Phase 7 MITM, Tasks 3–4) ────────────────────── - * - * Protocol-agnostic request boundary onto the gateway tunnel. Adapters (e.g. - * mq_gw_fetch_adapter for H1, future H2/H3 MITM adapters) plug into this - * boundary so the core tunnel-forwarding + response-relay logic is shared. - * - * Reject ORDER is observable (tests/e2e assert X-Mq-Error byte-for-byte) and is - * split across two phases to preserve it: - * prevalidate → dup-control-header, X-Mq-Auth (missing/format) - * [adapter parses the fetch envelope: X-Mq-Target/Method] - * req_begin → X-Mq-Origin-Protocol, X-Mq-Cache, header-size, tunnel liveness - */ - -/* Phase 1 of intake: header-only checks that, in the CURRENT code, run BEFORE the fetch - * envelope's target/method parse — duplicate-control-header, then X-Mq-Auth format. - * Returns MQ_GW_OK or the reject reason (+ *status). The adapter calls this FIRST so - * observable reject ORDER is preserved: dup -> auth -> [adapter parses target/method] -> - * req_begin. */ -mq_gw_reject_reason_t mq_gw_client_prevalidate(mq_gw_client_t *c, - const mq_h3_header_t *headers, size_t n, - int *status); - -/* Phase 2: begin a request (head already split + prevalidated). On acceptance returns the - * handle. On rejection returns NULL and sets *err_status AND *reason. Core checks here IN - * ORDER: X-Mq-Origin-Protocol(400), X-Mq-Cache(400), header-size(400), tunnel - * liveness(502). */ -mq_gw_xreq_t *mq_gw_client_req_begin(mq_gw_client_t *c, const mq_gw_req_head_t *head, - const mq_gw_sink_ops_t *sink, void *sink_user, - int *err_status, mq_gw_reject_reason_t *reason); -/* Return the RAW shared token stored at construction (the value EXPECTED in - * X-Mq-Auth, WITHOUT the "Bearer " prefix). Borrowed, NUL-terminated, valid for - * the client's lifetime. Used by the H2 MITM adapter's production submit vtable - * (mq_gw_h2_submit_ops_gwc) to inject "Bearer " on demuxed requests. - * Safe on NULL (returns NULL). */ -const char *mq_gw_client_token(const mq_gw_client_t *c); - -int mq_gw_client_req_body(mq_gw_xreq_t *r, const uint8_t *p, - size_t len); /* 0 go, -1 pause (consumed) */ -void mq_gw_client_req_body_done(mq_gw_xreq_t *r); -void mq_gw_client_req_aborted(mq_gw_xreq_t *r); -void mq_gw_client_req_drained( - mq_gw_xreq_t *r); /* adapter output drained below low watermark */ - -#endif /* MQ_GATEWAY_MQ_GW_CLIENT_H */ diff --git a/src/gateway/mq_gw_fetch_adapter.c b/src/gateway/mq_gw_fetch_adapter.c deleted file mode 100644 index 140659be..00000000 --- a/src/gateway/mq_gw_fetch_adapter.c +++ /dev/null @@ -1,468 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_gw_fetch_adapter.c — HTTP/1.1 fetch-listener adapter over the neutral - * mq_gw_client intake boundary. See mq_gw_fetch_adapter.h. - * - * ───────────────────────────────────────────────────────────────────────────── - * ADAPTER_REQ OWNERSHIP - * ───────────────────────────────────────────────────────────────────────────── - * Each accepted fetch request owns one adapter_req_t (ar). Allocated in - * adp_on_request on success; freed by exactly ONE of: - * - * (A) Normal response path: the core drives the download to completion and - * calls adp_resp_finish(ar) or adp_resp_abort(ar). These free ar. - * mq_gw_client_req_body_done and adp_on_body_done do NOT free ar. - * - * (B) Local-peer-died path: adp_on_aborted fires before the response is - * complete. mq_gw_client_req_aborted nulls r->sink / r->sink_user - * WITHOUT calling any sink op (it just resets H3 and eventually frees r - * once H3 on_close fires). ar is therefore freed by adp_on_aborted - * itself, after calling req_aborted. - * - * Paths A and B are MUTUALLY EXCLUSIVE (the core's local_dead flag + the - * listener's per-connection detach ensure on_aborted cannot fire after - * resp_finish/resp_abort, and vice versa). So ar is freed exactly once. */ -#include "gateway/mq_gw_fetch_adapter.h" - -#include -#include -#include - -#include "gateway/mq_gw_headers.h" -#include "gateway/mq_gw_intake.h" -#include "gateway/mq_http1.h" -#include "util/mq_log.h" - -/* ── per-request adapter state ──────────────────────────────────────────────*/ - -typedef struct { - void *handle; /* mq_fetch_listener per-conn handle (NULL once dead) */ - mq_gw_xreq_t *core; /* the core per-request state (valid until resp_finish/abort) */ - int resp_chunked; /* response is framed as Transfer-Encoding: chunked */ -} adapter_req_t; - -/* ── adapter struct ─────────────────────────────────────────────────────────*/ - -struct mq_gw_fetch_adapter { - mq_gw_client_t *core; /* borrowed */ -}; - -/* ── helpers (moved verbatim from the former H1 path in mq_gw_client.c) ────*/ - -/* Serialize a complete HTTP/1.1 error response into buf. Returns byte length, - * or 0 if it did not fit. Used by gw_reject_write (on_request reject path). */ -static size_t -gw_build_error(char *buf, size_t cap, int code, const char *reason, const char *xmq_error) -{ - int o = 0, n; - n = mq_http1_write_status(buf + o, cap - o, code, reason); - if (n <= 0) return 0; - o += n; - n = mq_http1_write_header(buf + o, cap - o, "Connection", "close"); - if (n <= 0) return 0; - o += n; - n = mq_http1_write_header(buf + o, cap - o, "Content-Length", "0"); - if (n <= 0) return 0; - o += n; - n = mq_http1_write_header(buf + o, cap - o, "X-Mq-Error", xmq_error); - if (n <= 0) return 0; - o += n; - if ((size_t)o + 2 > cap) return 0; - buf[o++] = '\r'; - buf[o++] = '\n'; - return (size_t)o; -} - -/* on_request REJECTION path: write the error response. The listener's - * on_request==-1 contract flushes + closes the connection itself, so we MUST - * NOT call mq_fetch_conn_finish here. Write only. */ -static void -gw_reject_write(void *handle, int code, const char *reason, const char *xmq_error) -{ - char buf[512]; - size_t n = gw_build_error(buf, sizeof(buf), code, reason, xmq_error); - if (n) mq_fetch_conn_write(handle, buf, n); -} - -/* Map mq_gw_reject_reason_t → X-Mq-Error string: now the SHARED helper - * mq_gw_reject_xmq (mq_gw_intake.h, implemented in mq_gw_headers.c) so the H1 and - * H2 adapters render byte-identical strings. The former static gw_reason_xmq was - * extracted there (Slice 3 Task 8). */ - -/* H1 reason → status-line reason phrase (moved from mq_gw_client.c). */ -static const char * -gw_status_phrase(int code) -{ - return code == 502 ? "Bad Gateway" : "Bad Request"; -} - -/* Case-insensitive equality: slice vs NUL-terminated lowercase literal. */ -static int -slice_ieq_local(const char *s, size_t sl, const char *lit) -{ - size_t ll = strlen(lit); - if (sl != ll) return 0; - for (size_t i = 0; i < sl; i++) { - char a = s[i]; - if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a'); - if (a != lit[i]) return 0; - } - return 1; -} - -/* Find header value slice (case-insensitive) in mq_http1_req_t. */ -static const char * -find_hdr_local(const mq_http1_req_t *req, const char *name, size_t *out_vl) -{ - for (size_t i = 0; i < req->nh; i++) { - if (slice_ieq_local(req->h[i].n, req->h[i].nl, name)) { - if (out_vl) *out_vl = req->h[i].vl; - return req->h[i].v; - } - } - return NULL; -} - -/* ── local drain cb ─────────────────────────────────────────────────────────*/ - -/* Local output drained below the low watermark → notify the core so the - * download read path can resume. */ -static void -local_drain_cb(void *user) -{ - adapter_req_t *ar = (adapter_req_t *)user; - mq_gw_client_req_drained(ar->core); -} - -/* ── H1 sink callbacks ────────────────────────────────────────────────────── - * - * Invoked BY the core when the H3 response arrives. Render HTTP/1.1 response - * bytes via mq_fetch_conn_* on ar->handle — byte-identical to the former - * inline rendering in mq_gw_client.c. These callbacks MUST NOT call back into - * the core: the core manages local_dead and gw_req_maybe_free; the sink's - * only responsibility is to render + deliver the response to the local peer. */ - -static int -adp_resp_head(void *u, int status, const mq_h3_header_t *hs, size_t n, - mq_gw_body_mode_t body_mode) -{ - adapter_req_t *ar = (adapter_req_t *)u; - if (!ar->handle) return -1; - - char head[8192]; - size_t head_len = 0; - - int sn = mq_http1_write_status(head, sizeof(head), status, ""); - if (sn <= 0) return -1; - head_len += (size_t)sn; - - for (size_t i = 0; i < n; i++) { - int n2 = mq_http1_write_header(head + head_len, sizeof(head) - head_len, - hs[i].name, hs[i].value); - if (n2 <= 0) return -1; - head_len += (size_t)n2; - } - - /* Unknown length → chunked framing for the body. */ - if (body_mode == MQ_GW_BODY_STREAM) { - ar->resp_chunked = 1; - int n2 = mq_http1_write_header(head + head_len, sizeof(head) - head_len, - "Transfer-Encoding", "chunked"); - if (n2 <= 0) return -1; - head_len += (size_t)n2; - } - - /* Connection: close (one request per local connection). */ - { - int n2 = mq_http1_write_header(head + head_len, sizeof(head) - head_len, - "Connection", "close"); - if (n2 <= 0) return -1; - head_len += (size_t)n2; - } - - /* Terminating blank line. */ - if (head_len + 2 > sizeof(head)) return -1; - head[head_len++] = '\r'; - head[head_len++] = '\n'; - - /* Register the drain cb so a highwater hit during body relay re-kicks the - * download read path. */ - mq_fetch_conn_set_drain_cb(ar->handle, local_drain_cb, ar); - int wr = mq_fetch_conn_write(ar->handle, head, head_len); - if (wr < 0) return -1; - return 1; -} - -static int -adp_resp_body(void *u, const uint8_t *p, size_t len) -{ - adapter_req_t *ar = (adapter_req_t *)u; - if (!ar->handle) return -1; - - if (ar->resp_chunked) { - /* Frame the chunk: "\r\n\r\n". */ - char hdr[32]; - int hn = snprintf(hdr, sizeof(hdr), "%lx\r\n", (unsigned long)len); - if (hn <= 0) return -1; - int wr = mq_fetch_conn_write(ar->handle, hdr, (size_t)hn); - if (wr < 0) return -1; - wr = mq_fetch_conn_write(ar->handle, p, len); - if (wr < 0) return -1; - int wr2 = mq_fetch_conn_write(ar->handle, "\r\n", 2); - if (wr2 < 0) return -1; - if (wr == 0 || wr2 == 0) return 0; /* highwater → defer */ - return 1; - } - - int wr = mq_fetch_conn_write(ar->handle, p, len); - if (wr < 0) return -1; - return wr == 0 ? 0 : 1; -} - -static void -adp_resp_finish(void *u) -{ - adapter_req_t *ar = (adapter_req_t *)u; - /* Chunked response → write the terminating zero-length chunk. */ - if (ar->resp_chunked && ar->handle) { - char term[8]; - size_t tn = mq_http1_chunk_frame(term, sizeof(term), NULL, 0); - if (tn > 0) mq_fetch_conn_write(ar->handle, term, tn); - } - if (ar->handle) mq_fetch_conn_finish(ar->handle); - ar->handle = NULL; - free(ar); -} - -static void -adp_resp_abort(void *u) -{ - adapter_req_t *ar = (adapter_req_t *)u; - if (ar->handle) mq_fetch_conn_abort(ar->handle); - ar->handle = NULL; - free(ar); -} - -static void -adp_resume_read(void *u) -{ - adapter_req_t *ar = (adapter_req_t *)u; - if (ar->handle) mq_fetch_conn_resume_read(ar->handle); -} - -static const mq_gw_sink_ops_t g_h1_sink = { - .resp_head = adp_resp_head, - .resp_body = adp_resp_body, - .resp_finish = adp_resp_finish, - .resp_abort = adp_resp_abort, - .resume_read = adp_resume_read, -}; - -/* ── fetch listener callbacks (mq_fetch_cbs_t) ────────────────────────────── - * - * Translate the HTTP/1.1 mq_fetch_listener events into the neutral - * mq_gw_client intake boundary. Observable reject ORDER is preserved exactly: - * dup-control-header → X-Mq-Auth → X-Mq-Target → X-Mq-Method - * → [req_begin: origin-proto / cache / size / tunnel] */ - -static int -adp_on_request(const mq_http1_req_t *req, void *handle, void *user, void **req_ctx) -{ - mq_gw_fetch_adapter_t *a = (mq_gw_fetch_adapter_t *)user; - *req_ctx = NULL; - - /* Build a NEUTRAL, NUL-terminated copy of the request headers so the core's - * strlen-based size checks observe the true lengths. */ - size_t hn = req->nh; - mq_h3_header_t *H = NULL; - char *namearena = NULL, *valarena = NULL; - if (hn > 0) { - H = calloc(hn, sizeof(*H)); - size_t names_sz = 0, vals_sz = 0; - for (size_t i = 0; i < hn; i++) { - names_sz += req->h[i].nl + 1; - vals_sz += req->h[i].vl + 1; - } - namearena = malloc(names_sz ? names_sz : 1); - valarena = malloc(vals_sz ? vals_sz : 1); - if (!H || !namearena || !valarena) { - free(H); - free(namearena); - free(valarena); - gw_reject_write(handle, 502, "Bad Gateway", "internal-error"); - return -1; - } - char *np = namearena, *vp = valarena; - for (size_t i = 0; i < hn; i++) { - memcpy(np, req->h[i].n, req->h[i].nl); - np[req->h[i].nl] = '\0'; - H[i].name = np; - np += req->h[i].nl + 1; - memcpy(vp, req->h[i].v, req->h[i].vl); - vp[req->h[i].vl] = '\0'; - H[i].value = vp; - vp += req->h[i].vl + 1; - } - } - - int status = 0; - - /* Phase 1 (header-only): dup-control-header, X-Mq-Auth. */ - mq_gw_reject_reason_t pre = mq_gw_client_prevalidate(a->core, H, hn, &status); - if (pre != MQ_GW_OK) { - gw_reject_write(handle, status, gw_status_phrase(status), mq_gw_reject_xmq(pre)); - free(H); - free(namearena); - free(valarena); - return -1; - } - - /* Fetch envelope (fetch-specific): X-Mq-Target. */ - size_t tgt_vl = 0; - const char *tgt = find_hdr_local(req, "x-mq-target", &tgt_vl); - mq_gw_target_t target; - if (!tgt || mq_gw_parse_target(tgt, tgt_vl, &target) != 0) { - gw_reject_write(handle, 400, "Bad Request", "bad-target"); - free(H); - free(namearena); - free(valarena); - return -1; - } - - /* X-Mq-Method if present; default GET. */ - char method[16]; - size_t mth_vl = 0; - const char *mth = find_hdr_local(req, "x-mq-method", &mth_vl); - if (mth) { - if (mq_gw_parse_method(mth, mth_vl, method) != 0) { - gw_reject_write(handle, 400, "Bad Request", "bad-method"); - free(H); - free(namearena); - free(valarena); - return -1; - } - } else { - memcpy(method, "GET", 4); - } - - /* Allocate the per-request adapter state BEFORE req_begin so we have a live - * ar to pass as sink_user. */ - adapter_req_t *ar = calloc(1, sizeof(*ar)); - if (!ar) { - free(H); - free(namearena); - free(valarena); - gw_reject_write(handle, 502, "Bad Gateway", "internal-error"); - return -1; - } - ar->handle = handle; - - /* Phase 2 (core): origin-proto / cache / header-size / tunnel + open+forward. */ - mq_gw_req_head_t head; - head.method = method; - head.scheme = target.scheme; - head.authority = target.authority; - head.path = target.path; - head.headers = H; - head.n_headers = hn; - /* NORMALIZE bodyless → 0 (codex-1 High boundary contract). The H1 parser sets - * content_length = -1 when the Content-Length header is ABSENT, which for the - * fetch ingress means NO body (a bodyless GET/POST). But mq_gw_intake.h defines - * content_length == -1 as "streaming body, unknown length" — and req_begin now - * treats ONLY 0 as bodyless (fin on headers). The fetch path NEVER has a true - * streaming/unknown-length body (chunked Transfer-Encoding is rejected with 411 - * upstream), so map the parser's absent-CL (-1) to 0 here. Without this a - * bodyless fetch request would (post-fix) be left open waiting for a body that - * never arrives. A known length (> 0) passes through unchanged. */ - head.content_length = req->content_length > 0 ? req->content_length : 0; - - int err_status = 0; - mq_gw_reject_reason_t reason = MQ_GW_OK; - mq_gw_xreq_t *r = - mq_gw_client_req_begin(a->core, &head, &g_h1_sink, ar, &err_status, &reason); - - free(H); - free(namearena); - free(valarena); - - if (!r) { - free(ar); - gw_reject_write(handle, err_status, gw_status_phrase(err_status), - mq_gw_reject_xmq(reason)); - return -1; - } - - ar->core = r; - *req_ctx = ar; - return 0; -} - -static int -adp_on_body(void *req_ctx, const uint8_t *p, size_t len) -{ - adapter_req_t *ar = (adapter_req_t *)req_ctx; - return mq_gw_client_req_body(ar->core, p, len); -} - -static void -adp_on_body_done(void *req_ctx) -{ - adapter_req_t *ar = (adapter_req_t *)req_ctx; - mq_gw_client_req_body_done(ar->core); - /* ar is freed when the core calls adp_resp_finish or adp_resp_abort (paths A - * above). Do NOT free ar here — the response is still in progress. */ -} - -static void -adp_on_aborted(void *req_ctx) -{ - adapter_req_t *ar = (adapter_req_t *)req_ctx; - /* The listener handle is being torn down — null it so no sink op ever touches - * it. Do this BEFORE calling req_aborted so even a synchronous internal abort - * notification (if any) sees handle=NULL. */ - ar->handle = NULL; - /* mq_gw_client_req_aborted nulls r->sink / r->sink_user WITHOUT calling any - * sink op, then tears down the H3 side and eventually frees r. ar will NOT be - * freed by any sink callback (path B above), so we own it here. */ - mq_gw_client_req_aborted(ar->core); - free(ar); -} - -static const mq_fetch_cbs_t g_adp_cbs = { - .on_request = adp_on_request, - .on_body = adp_on_body, - .on_body_done = adp_on_body_done, - .on_aborted = adp_on_aborted, -}; - -/* ── public API ─────────────────────────────────────────────────────────────*/ - -mq_gw_fetch_adapter_t * -mq_gw_fetch_adapter_new(mq_gw_client_t *core) -{ - if (!core) return NULL; - mq_gw_fetch_adapter_t *a = calloc(1, sizeof(*a)); - if (!a) return NULL; - a->core = core; - return a; -} - -const mq_fetch_cbs_t * -mq_gw_fetch_adapter_cbs(mq_gw_fetch_adapter_t *a) -{ - (void)a; - return &g_adp_cbs; -} - -void * -mq_gw_fetch_adapter_user(mq_gw_fetch_adapter_t *a) -{ - return a; -} - -void -mq_gw_fetch_adapter_free(mq_gw_fetch_adapter_t *a) -{ - free(a); -} diff --git a/src/gateway/mq_gw_fetch_adapter.h b/src/gateway/mq_gw_fetch_adapter.h deleted file mode 100644 index 47230fcc..00000000 --- a/src/gateway/mq_gw_fetch_adapter.h +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_gw_fetch_adapter.h — HTTP/1.1 fetch-listener adapter over the neutral - * mq_gw_client intake boundary. - * - * Wraps an mq_gw_client_t (the protocol-agnostic core) with a mq_fetch_cbs_t - * implementation so the mq_fetch_listener can be wired to the gateway client - * without the core knowing about HTTP/1.1 framing, handle ops, or chunked - * encoding. The adapter: - * - * - Implements on_request / on_body / on_body_done / on_aborted as a - * mq_fetch_cbs_t (to be passed to mq_fetch_listener_new). - * - Builds a neutral mq_h3_header_t list from the mq_http1_req_t, calls - * mq_gw_client_prevalidate, parses the X-Mq-Target / X-Mq-Method fetch - * envelope, and calls mq_gw_client_req_begin with an H1 sink. - * - The H1 sink (internal) renders HTTP/1.1 response bytes (status line, - * Transfer-Encoding: chunked / Connection: close, chunk framing) via the - * mq_fetch_conn_* handle ops — byte-identical to the former inline path. - * - * LIFETIME: mq_gw_fetch_adapter_t does NOT take ownership of the core, the - * fetch listener, or the H3 engine — it only borrows them. Per the SANCTIONED - * teardown order (see cli/main.c, where the rationale is spelled out), the core - * (mq_gw_client) is freed FIRST, while the H3 engine is still live, so a late - * gateway conn-close callback lands as a no-op into the dying gw_client instead - * of a use-after-free. The adapter does not dereference the core in - * mq_gw_fetch_adapter_free, so freeing it after the core is safe: - * - * mq_gw_client_free(core) -- FIRST, while mq_h3 engine still live - * mq_fetch_listener_free(listener) - * mq_gw_fetch_adapter_free(adapter) -- does not touch the freed core - * mq_h3_free(engine) - */ -#pragma once -#include "gateway/mq_fetch_listener.h" -#include "gateway/mq_gw_client.h" - -typedef struct mq_gw_fetch_adapter mq_gw_fetch_adapter_t; - -/* Create an adapter wrapping `core`. Returns NULL on OOM. */ -mq_gw_fetch_adapter_t *mq_gw_fetch_adapter_new(mq_gw_client_t *core); - -/* The mq_fetch_cbs_t vtable to pass to mq_fetch_listener_new. Points to a - * static immutable struct (valid for the program lifetime). */ -const mq_fetch_cbs_t *mq_gw_fetch_adapter_cbs(mq_gw_fetch_adapter_t *a); - -/* The `user` pointer to pass alongside mq_gw_fetch_adapter_cbs() into - * mq_fetch_listener_new (it is the mq_gw_fetch_adapter_t itself). */ -void *mq_gw_fetch_adapter_user(mq_gw_fetch_adapter_t *a); - -/* Free the adapter. Does NOT free the core. Safe on NULL. */ -void mq_gw_fetch_adapter_free(mq_gw_fetch_adapter_t *a); diff --git a/src/gateway/mq_gw_h2_adapter.c b/src/gateway/mq_gw_h2_adapter.c deleted file mode 100644 index 6d81f8d2..00000000 --- a/src/gateway/mq_gw_h2_adapter.c +++ /dev/null @@ -1,1071 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors - -/* mq_gw_h2_adapter.c — HTTP/2 (nghttp2) server adapter over the neutral - * mq_gw_client intake boundary. See mq_gw_h2_adapter.h. - * - * SKELETON SCOPE (Task 5): create the nghttp2 SERVER session, submit the §5.2 - * SETTINGS, and plumb recv / want_write to the byte transport. No request - * demux, header policy, response path or body handling yet (Tasks 6/7/8). - * - * The production wrapper mq_gw_h2_submit_ops_gwc() is NOT defined here — it is - * built in Task 11 (its first live use), because it needs mq_gw_client_token() - * which Task 6 adds. Declaring it in the header keeps the API complete without a - * forward reference to a not-yet-existing symbol. */ -#include "gateway/mq_gw_h2_adapter.h" - -#include -#include -#include -#include - -/* ── §5.2 resource limits (NORMATIVE) ──────────────────────────────────────── */ - -/* Cap concurrent streams to bound per-connection state / stream-flood DoS. */ -#define MQ_H2_MAX_CONCURRENT_STREAMS 128 -/* HTTP/2 default frame size; do NOT advertise larger (smaller read amplification - * surface). 16384 = the protocol minimum/default. */ -#define MQ_H2_MAX_FRAME_SIZE 16384 -/* Bound the HPACK dynamic table (4 KiB) so a decoder-state-bloat attack is - * capped. */ -#define MQ_H2_HEADER_TABLE_SIZE 4096 -/* Cumulative (name+value+per-field-overhead) cap for a single inbound header - * block (16 KiB), advertised as SETTINGS_MAX_HEADER_LIST_SIZE. - * - * This SETTINGS entry is a COOPERATIVE HINT to the peer only: nghttp2 1.59.0 - * does NOT auto-enforce it on inbound header blocks (the recv/HPACK-inflate path - * never compares decoded header-list size against local_settings; the inflater - * caps only the HPACK dynamic table). The symbol that would enforce it, - * nghttp2_option_set_max_header_list_size, is absent from 1.59.0. - * - * Therefore inbound header-bomb rejection is NOT provided by advertising this - * setting — it must be enforced by application-side cumulative header-size - * accounting in the on_header callback. That accounting is added in Task 6. */ -#define MQ_H2_MAX_HEADER_LIST_SIZE 16384 - -/* Per RFC 7540 §6.5.2 each header field costs name_len + value_len + 32 toward - * the header-list size. We enforce the SAME cap we advertise. */ -#define MQ_H2_FIELD_OVERHEAD 32 - -/* Max regular (non-pseudo, non-x-mq-) headers we forward, PLUS the two injected - * controls (x-mq-auth, x-mq-forward-cookie). A header block whose cumulative - * size stays <= 16 KiB cannot hold an unbounded count of fields (each costs >= - * 32 bytes), so this comfortably covers any block that survives the size cap. */ -#define MQ_H2_MAX_REQ_HDRS 256 - -/* §5.2 per-stream response (download) send-queue cap. The adapter buffers - * response body bytes the core hands it (resp_body) until nghttp2's data - * provider drains them onto the wire. Bounding it applies download backpressure: - * once full, resp_body returns 0 (highwater) and the core stops reading the H3 - * tunnel until the queue drains below the cap, at which point the adapter calls - * submit->req_drained(xreq) to resume the download pump (rigor IMP-1). 64 KiB is - * a few nghttp2 max-frame-size (16 KiB) DATA frames in flight — enough to keep - * the wire busy without unbounded per-stream memory under a stalled client. */ -#define MQ_H2_RESP_QUEUE_CAP (64u * 1024u) - -/* Max regular response headers we frame back (browser-bound). Same generous - * bound as the request side; the core's response heads are small. */ -#define MQ_H2_MAX_RESP_HDRS 256 - -/* §5.2 inbound (upload) backpressure buffer cap. When a stream's submit->req_body - * returns -1 (pause), the adapter STOPS feeding nghttp2 and parks the not-yet- - * processed input bytes here until the core releases the pause via - * sink->resume_read. See the UPLOAD BACKPRESSURE block below for the full design; - * 256 KiB comfortably exceeds the H2 connection flow-control window (nghttp2's - * default 64 KiB), so under a well-behaved peer the buffer never approaches the - * cap before HTTP/2 flow control throttles the browser. Overflow (a peer that - * floods past its window) is a fatal protocol/DoS condition → tear the - * connection down (return -1 from recv) rather than grow memory unbounded. */ -#define MQ_H2_UPLOAD_PAUSE_CAP (256u * 1024u) - -/* ── adapter struct ─────────────────────────────────────────────────────────── */ - -struct mq_h2_stream_s; /* fwd */ - -struct mq_gw_h2_adapter { - nghttp2_session *session; /* server session */ - - /* Submission boundary (BORROWED). Stored for Tasks 6/7/8; unused by the - * skeleton beyond holding the references. */ - const mq_gw_submit_ops_t *submit; - void *submit_user; - - /* Plaintext writer (BORROWED). */ - ssize_t (*send_cb)(void *io, const uint8_t *p, size_t n); - void *io; - - /* Intrusive list of LIVE per-stream contexts. nghttp2_session_del does NOT - * invoke on_stream_close for streams still open at teardown, so the adapter - * tracks them and frees any survivors in mq_gw_h2_adapter_free. */ - struct mq_h2_stream_s *streams; - - /* ── UPLOAD BACKPRESSURE (Task 8) — adapter-level input gate ─────────────── - * - * DESIGN (approach A: nghttp2 input pause + bounded re-feed buffer): - * - * When on_data_chunk_recv hands a chunk to submit->req_body and the core - * returns -1 (PAUSE — the chunk IS consumed by the core; -1 only means "stop - * feeding me more", per the mq_gw_intake.h req_body contract), the callback - * returns NGHTTP2_ERR_PAUSE. nghttp2_session_mem_recv then stops and returns - * the byte count it consumed SO FAR (a positive value that may be < n). The - * unconsumed tail [r, n) is NOT lost: mq_gw_h2_adapter_recv parks it in - * `pending_in` and sets `input_paused`. While paused, recv parks any further - * input into the same bounded buffer (and does NOT feed nghttp2 — so nghttp2 - * sends no WINDOW_UPDATE and the browser is throttled by H2 flow control). - * - * sink->resume_read clears `input_paused` and RE-FEEDS the parked bytes - * through nghttp2_session_mem_recv (which re-drives on_data_chunk_recv for the - * buffered DATA), looping until the buffer drains or it pauses again. - * - * INVARIANT: no upload body byte is ever lost (every byte either reaches - * req_body or sits in pending_in awaiting re-feed), and a stalled tunnel - * (req_body=-1) throttles the browser without unbounded growth (the buffer is - * bounded by MQ_H2_UPLOAD_PAUSE_CAP; H2 flow control keeps a well-behaved peer - * far below it). - * - * NOTE: nghttp2_session_resume_data() is the OUTBOUND deferred-data-provider - * resume; it is NOT used for inbound upload resume (re-feed is the correct - * mechanism). The pause is connection-wide: even though we advertise - * MQ_H2_MAX_CONCURRENT_STREAMS so a browser CAN multiplex, all H2 streams on - * this conn share a SINGLE downstream MPQUIC tunnel — so when that tunnel - * cannot accept upload bytes (any req_body returns -1), backpressuring every - * stream is aligned with the real bottleneck. The (acceptable for v1) cost is - * cross-stream head-of-line blocking of uploads while the pause holds. */ - uint8_t pending_in[MQ_H2_UPLOAD_PAUSE_CAP]; - size_t pending_len; - int input_paused; /* a req_body returned -1; do not feed nghttp2 until resumed */ - int input_fatal; /* pending_in overflowed (peer flooded past its window) */ - - /* WRITABLE-NOTIFY (optional; mq_gw_h2_adapter_set_writable_cb). Fired when a - * sink op enqueues outbound frames OUTSIDE a recv/want_write turn so the io - * layer can SCHEDULE a deferred want_write. Deferred-safe (never re-entrant). - * NULL ⇒ the caller pulls want_write itself (unit-test seam). */ - void (*writable_cb)(void *user); - void *writable_user; -}; - -/* ── per-stream demux context (Task 6) ────────────────────────────────────── - * - * Created in on_begin_headers, attached as the nghttp2 stream user-data, - * populated header-by-header in on_header, materialized in on_frame_recv - * (END_HEADERS), and freed in on_stream_close. - * - * SECURITY (untrusted browser input): - * - All name/value bytes are COPIED out of nghttp2's transient buffers into - * `arena` as NUL-terminated C strings. The boundary uses strlen/strncmp, so - * a slice carrying an embedded NUL is REJECTED (codex H1) — never truncated. - * - The cumulative header-list size (RFC 7540 §6.5.2: name+value+32 per field) - * is accumulated across the whole block; exceeding MQ_H2_MAX_HEADER_LIST_SIZE - * trips `rejected` and RSTs the stream BEFORE materialization (inbound - * header-bomb defense — nghttp2 1.59.0 does NOT auto-enforce the advertised - * SETTINGS_MAX_HEADER_LIST_SIZE; see the #define above). `arena` is sized to - * that same cap, so the bomb defense also caps arena growth. - * - §4.5 header policy: EVERY browser-supplied x-mq-* header is dropped - * (total/unconditional) in on_header BEFORE the two controls are injected at - * materialization, so a page cannot smuggle a control. - */ -typedef struct mq_h2_stream_s { - mq_gw_h2_adapter_t *a; - struct mq_h2_stream_s *next; /* adapter->streams intrusive list */ - - /* Bump arena for NUL-terminated header-string copies. Bounded by the - * header-list cap so the header-bomb defense also caps memory. */ - char arena[MQ_H2_MAX_HEADER_LIST_SIZE]; - size_t arena_off; - - /* Pseudo-headers (point into arena once set). */ - const char *method, *scheme, *authority, *path; - - /* Regular (non-pseudo, non-x-mq-) browser headers, copies into arena. */ - mq_h3_header_t hdrs[MQ_H2_MAX_REQ_HDRS]; - size_t n_hdrs; - - /* Cumulative inbound header-list size accounting (RFC 7540 §6.5.2). */ - size_t hdr_list_size; - - int rejected; /* a fatal per-stream condition (RST on END_HEADERS / set now) */ - - /* The core handle once req_begin succeeds; NULL until then / on reject. */ - mq_gw_xreq_t *xreq; - - int32_t stream_id; - int materialized; /* req_begin already driven (guards re-entry) */ - int upload_done; /* req_body_done already fired (END_STREAM seen; guards re-entry) */ - int headers_end_stream; /* END_STREAM was set ON THE HEADERS frame → bodyless - * request (content_length 0). Cleared (has body → - * content_length -1 streaming) otherwise. Captured in - * on_frame_recv before materialize_request. */ - - /* ── response (download) path (Task 7) ────────────────────────────────── - * - * Per-stream FIFO ring of response body bytes the core handed us via - * resp_body but nghttp2's data provider has not yet drained onto the wire. - * resp_head submits the response with a data provider that pulls from here; - * resp_finish sets resp_eof so the provider emits END_STREAM once drained. - * - * Backpressure (rigor IMP-1): resp_body returns 0 (highwater) at the cap and - * sets sq_deferred; once want_write drains the ring below the cap we call - * submit->req_drained(xreq) exactly once per highwater episode to resume the - * core's download pump. */ - uint8_t sendq[MQ_H2_RESP_QUEUE_CAP]; - size_t sq_head; /* index of the oldest queued byte */ - size_t sq_len; /* bytes currently queued */ - int resp_started; /* resp_head submitted (data provider installed) */ - int resp_eof; /* resp_finish seen → provider emits END_STREAM when drained */ - int sq_deferred; /* resp_body hit highwater; owe a req_drained on drain */ -} mq_h2_stream_t; - -/* Copy a (ptr,len) slice into the stream arena as a NUL-terminated string. - * REJECTS an embedded NUL (codex H1): the boundary measures with strlen, so a - * truncated-at-NUL value would smuggle/lose bytes. Returns the arena pointer, or - * NULL on overflow / embedded NUL (caller sets `rejected`). */ -static const char * -arena_dup(mq_h2_stream_t *s, const uint8_t *p, size_t len) -{ - if (s->arena_off + len + 1 > sizeof(s->arena)) return NULL; /* bomb cap */ - for (size_t i = 0; i < len; i++) { - if (p[i] == '\0') return NULL; /* embedded NUL — reject, never truncate */ - } - char *dst = s->arena + s->arena_off; - memcpy(dst, p, len); - dst[len] = '\0'; - s->arena_off += len + 1; - return dst; -} - -/* fwd (defined in the recv section) — used by the resume_read re-feed loop. */ -static int park_pending(mq_gw_h2_adapter_t *a, const uint8_t *p, size_t len); - -/* Case-insensitive ASCII prefix test: does `name` (len nl) start with "x-mq-"? */ -static int -name_has_xmq_prefix(const uint8_t *name, size_t nl) -{ - static const char pfx[] = {'x', '-', 'm', 'q', '-'}; - if (nl < sizeof(pfx)) return 0; - for (size_t i = 0; i < sizeof(pfx); i++) { - unsigned char c = name[i]; - if (c >= 'A' && c <= 'Z') c = (unsigned char)(c - 'A' + 'a'); - if (c != (unsigned char)pfx[i]) return 0; - } - return 1; -} - -/* Fire the writable-notify cb (if set) so the io layer schedules a deferred - * want_write. Used by the RESPONSE-side sink ops, which enqueue outbound frames - * on the async core→adapter path (no recv/want_write turn to drain them). */ -static void -notify_writable(mq_gw_h2_adapter_t *a) -{ - if (a->writable_cb) a->writable_cb(a->writable_user); -} - -/* ── demux callbacks (Task 6) ───────────────────────────────────────────────*/ - -/* Drive a per-stream RST (TEMPORAL_CALLBACK_FAILURE semantics) and mark the - * stream rejected. Used for the header-bomb / NUL / malformed cases. We submit - * an explicit RST_STREAM so the chosen error code reaches the peer, then signal - * the on_header / mem_recv path to stop processing this stream. - * - * NOTE for Task 8: the full X-Mq-Error error-RESPONSE rendering (an H2 response - * with a body) for reject *reasons* is finalized via a shared helper there. For - * the cases handled HERE (header-bomb / embedded-NUL / missing pseudo-header) we - * cannot reliably send a response body mid-header-block, so RST_STREAM is the - * correct, spec-clean signal (consistent with MQ_GW_REJ_HEADER_TOO_LONG intent). - */ -static void -stream_reject(mq_h2_stream_t *s, uint32_t error_code) -{ - if (s->rejected) return; - s->rejected = 1; - nghttp2_submit_rst_stream(s->a->session, NGHTTP2_FLAG_NONE, s->stream_id, error_code); -} - -/* Render an H2 ERROR RESPONSE for a reject *reason* known after END_HEADERS - * (prevalidate / req_begin verdicts). Unlike stream_reject (a bare RST used for - * mid-header-block failures where no response can be framed), this submits a - * complete header-only response: :status + x-mq-error: with - * END_STREAM, so the browser sees the same diagnostic the H1 fetch adapter - * renders (§5/§5.2; strings byte-identical via the shared mq_gw_reject_xmq). The - * stream is then marked rejected so no further demux/body touches it. - * - * `status` is the verdict's HTTP status (the boundary fills it; 0 → default 400 - * for the 4xx reasons / nothing reasonable, so we floor to 400). */ -static void -stream_reject_response(mq_h2_stream_t *s, int status, mq_gw_reject_reason_t reason) -{ - if (s->rejected) return; - s->rejected = 1; - - if (status < 100 || status > 999) status = 400; /* defensive floor */ - char status_str[4]; - status_str[0] = (char)('0' + (status / 100) % 10); - status_str[1] = (char)('0' + (status / 10) % 10); - status_str[2] = (char)('0' + status % 10); - status_str[3] = '\0'; - - const char *xmq = mq_gw_reject_xmq(reason); /* shared, byte-identical literal */ - - nghttp2_nv nva[2]; - nva[0].name = (uint8_t *)":status"; - nva[0].namelen = 7; - nva[0].value = (uint8_t *)status_str; - nva[0].valuelen = 3; - nva[0].flags = NGHTTP2_NV_FLAG_NONE; - nva[1].name = (uint8_t *)"x-mq-error"; - nva[1].namelen = 10; - nva[1].value = (uint8_t *)xmq; - nva[1].valuelen = strlen(xmq); - nva[1].flags = NGHTTP2_NV_FLAG_NONE; - - /* No data provider → nghttp2 sets END_STREAM on the HEADERS frame. If submit - * fails (OOM), fall back to a RST so the browser still sees a clean failure. */ - if (nghttp2_submit_response(s->a->session, s->stream_id, nva, 2, NULL) != 0) - nghttp2_submit_rst_stream(s->a->session, NGHTTP2_FLAG_NONE, s->stream_id, - NGHTTP2_INTERNAL_ERROR); -} - -static int -on_begin_headers(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) -{ - mq_gw_h2_adapter_t *a = (mq_gw_h2_adapter_t *)user_data; - - /* Only request HEADERS open a stream context. */ - if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - return 0; - } - - mq_h2_stream_t *s = calloc(1, sizeof(*s)); - if (!s) return NGHTTP2_ERR_CALLBACK_FAILURE; /* OOM → tear the connection down */ - s->a = a; - s->stream_id = frame->hd.stream_id; - - if (nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, s) != 0) { - free(s); - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - s->next = a->streams; - a->streams = s; - return 0; -} - -/* Unlink a stream context from the adapter's live list. */ -static void -stream_unlink(mq_gw_h2_adapter_t *a, mq_h2_stream_t *s) -{ - mq_h2_stream_t **pp = &a->streams; - while (*pp) { - if (*pp == s) { - *pp = s->next; - return; - } - pp = &(*pp)->next; - } -} - -static int -on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, - size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, - void *user_data) -{ - (void)flags; - (void)user_data; - if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - return 0; - } - mq_h2_stream_t *s = (mq_h2_stream_t *)nghttp2_session_get_stream_user_data( - session, frame->hd.stream_id); - if (!s) return 0; /* no context (e.g. already torn down) — ignore */ - if (s->rejected) return 0; /* already RST'd this stream — drop remaining fields */ - - /* Inbound header-bomb accounting (RFC 7540 §6.5.2): name+value+32 per field, - * accumulated across the WHOLE block. nghttp2 1.59.0 does not enforce the - * advertised SETTINGS_MAX_HEADER_LIST_SIZE on inbound blocks, so we do it - * here and RST before the block can grow memory unbounded. */ - s->hdr_list_size += namelen + valuelen + MQ_H2_FIELD_OVERHEAD; - if (s->hdr_list_size > MQ_H2_MAX_HEADER_LIST_SIZE) { - stream_reject(s, NGHTTP2_ENHANCE_YOUR_CALM); - return 0; - } - - /* §4.5 step 1: drop EVERY browser-supplied x-mq-* header, total and - * unconditional. Skip BEFORE copying so the page cannot smuggle a control or - * inflate the arena with controls we ignore. (Pseudo-headers start with ':' - * and never match this prefix.) */ - if (name_has_xmq_prefix(name, namelen)) return 0; - - /* Copy NAME + VALUE into the arena as NUL-terminated strings, rejecting any - * embedded NUL (codex H1). */ - const char *cn = arena_dup(s, name, namelen); - if (!cn) { - stream_reject(s, NGHTTP2_ENHANCE_YOUR_CALM); - return 0; - } - const char *cv = arena_dup(s, value, valuelen); - if (!cv) { - stream_reject(s, NGHTTP2_ENHANCE_YOUR_CALM); - return 0; - } - - /* Pseudo-headers → request head. nghttp2 already validated that pseudo-headers - * precede regular ones and are known request pseudos; we map the four we use - * and ignore any others (none expected for a request). */ - if (namelen > 0 && name[0] == ':') { - if (namelen == 7 && memcmp(name, ":method", 7) == 0) - s->method = cv; - else if (namelen == 7 && memcmp(name, ":scheme", 7) == 0) - s->scheme = cv; - else if (namelen == 10 && memcmp(name, ":authority", 10) == 0) - s->authority = cv; - else if (namelen == 5 && memcmp(name, ":path", 5) == 0) - s->path = cv; - /* else: unknown pseudo — copied but not referenced (dropped). */ - return 0; - } - - /* Regular browser header — forward (§4.5 step 2). */ - if (s->n_hdrs >= MQ_H2_MAX_REQ_HDRS) { - /* Unreachable under the 16 KiB cap (each field costs >= 32 bytes), but - * fail closed rather than overflow. */ - stream_reject(s, NGHTTP2_ENHANCE_YOUR_CALM); - return 0; - } - s->hdrs[s->n_hdrs].name = cn; - s->hdrs[s->n_hdrs].value = cv; - s->n_hdrs++; - return 0; -} - -/* ── response path: sink ops → H2 HEADERS/DATA + download backpressure (Task 7) ─ - * - * The core (production: mq_gw_client via the Task 11 submit wrapper; tests: the - * stub acting as the core) drives these to deliver the H3 response back out as - * H2. sink_user is the per-stream context (mq_h2_stream_t) set at req_begin. */ - -/* Dequeue up to `cap` bytes from the stream's response FIFO into `dst`. Returns - * the count copied. Handles the ring wrap. */ -static size_t -sendq_pop(mq_h2_stream_t *s, uint8_t *dst, size_t cap) -{ - size_t n = s->sq_len < cap ? s->sq_len : cap; - if (n == 0) return 0; - size_t first = MQ_H2_RESP_QUEUE_CAP - s->sq_head; /* until the ring wraps */ - if (first > n) first = n; - memcpy(dst, s->sendq + s->sq_head, first); - if (n > first) memcpy(dst + first, s->sendq, n - first); /* wrapped tail */ - s->sq_head = (s->sq_head + n) % MQ_H2_RESP_QUEUE_CAP; - s->sq_len -= n; - return n; -} - -/* nghttp2 data provider: pull response body bytes from the per-stream FIFO. - * Empty + not finished → DEFER (resumed via nghttp2_session_resume_data in - * resp_body / on drain). Empty + finished → EOF (nghttp2 sets END_STREAM since - * we send no trailers). */ -static ssize_t -resp_data_read(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, - uint32_t *data_flags, nghttp2_data_source *source, void *user_data) -{ - (void)session; - (void)stream_id; - (void)user_data; - mq_h2_stream_t *s = (mq_h2_stream_t *)source->ptr; - - size_t n = sendq_pop(s, buf, length); - if (n == 0) { - if (s->resp_eof) { - *data_flags |= NGHTTP2_DATA_FLAG_EOF; /* → END_STREAM (no trailers) */ - return 0; - } - return NGHTTP2_ERR_DEFERRED; /* nothing queued yet; resume on next enqueue */ - } - /* If the whole tail is queued AND the core signalled EOF, mark END_STREAM on - * this final non-empty frame to avoid an extra empty DATA frame. */ - if (s->resp_eof && s->sq_len == 0) *data_flags |= NGHTTP2_DATA_FLAG_EOF; - return (ssize_t)n; -} - -/* sink->resp_head: submit the response HEADERS with a data provider that pulls - * the body from the per-stream FIFO. body_mode is advisory for H2 (nghttp2 owns - * DATA framing); the core's head already carries any Content-Length it wants - * forwarded, so we just render :status + the supplied headers. Returns 1 on - * success, -1 on a hard failure (the core maps -1 → abort). */ -static int -mq_h2_resp_head(void *u, int status, const mq_h3_header_t *hs, size_t n, - mq_gw_body_mode_t body_mode) -{ - (void)body_mode; - mq_h2_stream_t *s = (mq_h2_stream_t *)u; - if (!s || s->rejected) return -1; - if (n > MQ_H2_MAX_RESP_HDRS) return -1; - - /* :status as a string (3 ASCII digits for any HTTP status). */ - char status_str[4]; - if (status < 100 || status > 999) return -1; - status_str[0] = (char)('0' + (status / 100) % 10); - status_str[1] = (char)('0' + (status / 10) % 10); - status_str[2] = (char)('0' + status % 10); - status_str[3] = '\0'; - - nghttp2_nv nva[MQ_H2_MAX_RESP_HDRS + 1]; - size_t nv = 0; - nva[nv].name = (uint8_t *)":status"; - nva[nv].namelen = 7; - nva[nv].value = (uint8_t *)status_str; - nva[nv].valuelen = 3; - nva[nv].flags = NGHTTP2_NV_FLAG_NONE; - nv++; - for (size_t i = 0; i < n; i++) { - nva[nv].name = (uint8_t *)hs[i].name; - nva[nv].namelen = strlen(hs[i].name); - nva[nv].value = (uint8_t *)hs[i].value; - nva[nv].valuelen = strlen(hs[i].value); - nva[nv].flags = NGHTTP2_NV_FLAG_NONE; - nv++; - } - - nghttp2_data_provider prd; - prd.source.ptr = s; - prd.read_callback = resp_data_read; - - if (nghttp2_submit_response(s->a->session, s->stream_id, nva, nv, &prd) != 0) - return -1; - s->resp_started = 1; - notify_writable(s->a); /* async core path — schedule a flush to the transport */ - return 1; -} - -/* sink->resp_body: enqueue body bytes into the per-stream FIFO and resume the - * data provider. Contract (mq_gw_intake.h): >0 accepted byte count / 0 highwater - * (queue at cap; NOT enqueued — the core retries after req_drained) / -1 dead. - * Returns the FULL accepted count on the accept path so the core does not - * spuriously set read_deferred. */ -static int -mq_h2_resp_body(void *u, const uint8_t *p, size_t len) -{ - mq_h2_stream_t *s = (mq_h2_stream_t *)u; - if (!s || s->rejected) return -1; - if (len == 0) return 0; /* nothing to do; treat as a no-op accept */ - - /* WHOLE-CHUNK-OR-HIGHWATER (NOT partial). The intake.h contract says ">0 - * accept / 0 highwater", but the production consumer (mq_gw_client.c - * download_pump) treats ANY wr > 0 as full-chunk acceptance — it does NOT - * compare the return against the chunk length, so a partial accept (0 < wr < - * len) would silently DROP the unaccepted tail. The core's chunk is bounded - * (MQ_GW_DOWNLOAD_CHUNK = 16 KiB) and our cap is 64 KiB, so refusing a chunk - * that does not fit entirely is safe and lossless: the core defers it and - * retries the WHOLE chunk after req_drained. */ - size_t room = MQ_H2_RESP_QUEUE_CAP - s->sq_len; - if (len > room) { - /* Won't fit in one piece → highwater. Do NOT enqueue any of it. Owe a - * req_drained once the FIFO drains below the cap. */ - s->sq_deferred = 1; - return 0; - } - size_t tail = (s->sq_head + s->sq_len) % MQ_H2_RESP_QUEUE_CAP; - size_t first = MQ_H2_RESP_QUEUE_CAP - tail; /* until the ring wraps */ - if (first > len) first = len; - memcpy(s->sendq + tail, p, first); - if (len > first) memcpy(s->sendq, p + first, len - first); /* wrap */ - s->sq_len += len; - - /* Re-arm the (possibly deferred) data provider so the new bytes flush. */ - nghttp2_session_resume_data(s->a->session, s->stream_id); - notify_writable(s->a); /* async core path — schedule a flush to the transport */ - return (int)len; -} - -/* sink->resp_finish: mark EOF. MUST tolerate being called while the send buffer - * is still draining (codex M-1: the core's download_pump falls through to finish - * when fin coincides with a highwater chunk). We do NOT assert an empty queue — - * the EOF flag rides independently and resp_data_read emits END_STREAM once the - * FIFO drains. */ -static void -mq_h2_resp_finish(void *u) -{ - mq_h2_stream_t *s = (mq_h2_stream_t *)u; - if (!s) return; - /* Even on the rejected early-return path we MUST drop the borrowed xreq: the - * core has signalled completion and may free it regardless of whether the - * stream was already RST'd, so resp_finish must symmetrically NULL it (mirror - * resp_abort, which NULLs unconditionally). Otherwise a rejected-then-finished - * stream lingering in a->streams would call req_aborted on freed memory at - * teardown (UAF). The END_STREAM-emit below is gated on !rejected. */ - if (s->rejected) { - s->xreq = NULL; - return; - } - s->resp_eof = 1; - /* Resume in case the provider had deferred on an empty queue: now it can emit - * the (possibly empty) END_STREAM DATA frame. */ - if (s->resp_started) nghttp2_session_resume_data(s->a->session, s->stream_id); - notify_writable(s->a); /* async core path — schedule the END_STREAM flush */ - /* TERMINAL: the core (gwc) has signalled clean completion and may free the - * xreq the instant this returns (mq_gw_client gw_local_finish sets local_dead - * then a later h3_on_close → gw_req_maybe_free frees it). Drop our borrowed - * handle so nothing — on_stream_close, adapter_free, req_drained — ever calls - * a req_* op on the freed xreq (the H2-side drain uses `s`, not s->xreq, so - * the END_STREAM emit above is unaffected). Without this, a stream still in - * a->streams at MITM teardown would call req_aborted on freed memory (UAF). */ - s->xreq = NULL; -} - -/* sink->resp_abort: mid-stream failure → RST_STREAM (never a fake clean finish). - * If the response had not started we still RST: the browser sees a reset, which - * is the correct signal for an aborted/truncated response. */ -static void -mq_h2_resp_abort(void *u) -{ - mq_h2_stream_t *s = (mq_h2_stream_t *)u; - if (!s) return; - if (!s->rejected) { - s->rejected = 1; - nghttp2_submit_rst_stream(s->a->session, NGHTTP2_FLAG_NONE, s->stream_id, - NGHTTP2_INTERNAL_ERROR); - notify_writable(s->a); /* async core path — schedule the RST flush */ - } - /* TERMINAL (mirror resp_finish): the core has signalled a mid-stream failure - * and owns the xreq's death from here (it detaches its sink + frees the xreq - * via gw_req_maybe_free). Drop our borrowed handle so adapter_free / - * on_stream_close never call req_aborted on the freed xreq (UAF). */ - s->xreq = NULL; -} - -/* sink->resume_read: UPLOAD backpressure release (browser→origin). The core has - * room again; clear the pause and re-feed the parked input bytes through nghttp2 - * (which re-drives on_data_chunk_recv → req_body). If req_body pauses again - * mid-drain, mem_recv stops on the prefix and we re-park the unconsumed tail and - * stay paused — bytes are never lost or re-delivered. Loops until the buffer - * drains or the upload re-pauses. */ -static void -mq_h2_resume_read(void *u) -{ - mq_h2_stream_t *s = (mq_h2_stream_t *)u; - if (!s) return; - mq_gw_h2_adapter_t *a = s->a; - if (a->input_fatal) return; - - a->input_paused = 0; - - /* Drain the parked buffer IN PLACE. Each pass feeds the whole current buffer - * (pending_in[0, pending_len)); on a pause, mem_recv reports a short consume r - * and re-arms input_paused, so we compact the unconsumed tail [r, plen) back - * down to offset 0 and stop. mem_recv only re-drives on_data_chunk_recv during - * the feed (recv is not re-entered), so nothing else writes pending_in here — - * the in-place memmove is safe and never loses or duplicates a byte. */ - while (a->pending_len > 0 && !a->input_paused && !a->input_fatal) { - size_t plen = a->pending_len; - - ssize_t r = nghttp2_session_mem_recv(a->session, a->pending_in, plen); - if (r < 0) { - a->input_fatal = 1; /* fatal parse error — surfaced by next recv */ - return; - } - if ((size_t)r != plen) { - /* Paused (or unexpected shortfall) mid-drain: compact the tail. */ - if (!a->input_paused) { - a->input_fatal = 1; - return; - } - size_t tail = plen - (size_t)r; - memmove(a->pending_in, a->pending_in + (size_t)r, tail); - a->pending_len = tail; - return; /* re-armed paused; loop guard would stop us anyway */ - } - /* Whole buffer consumed: clear and loop (resume_read may have been a no-op - * re-pause-free drain). */ - a->pending_len = 0; - } -} - -static const mq_gw_sink_ops_t mq_h2_sink = { - .resp_head = mq_h2_resp_head, - .resp_body = mq_h2_resp_body, - .resp_finish = mq_h2_resp_finish, - .resp_abort = mq_h2_resp_abort, - .resume_read = mq_h2_resume_read, -}; - -/* Build the FINAL header list (forwarded browser headers + the two injected - * controls) and drive the boundary: prevalidate → req_begin. Stores the returned - * xreq on the stream. On any reject the stream is RST (full X-Mq-Error response - * rendering is Task 8). */ -static void -materialize_request(mq_h2_stream_t *s) -{ - mq_gw_h2_adapter_t *a = s->a; - - if (s->materialized) return; - s->materialized = 1; - - /* A required pseudo-header missing → malformed request. */ - if (!s->method || !*s->method || !s->scheme || !*s->scheme || !s->authority || - !*s->authority || !s->path || !*s->path) { - stream_reject(s, NGHTTP2_PROTOCOL_ERROR); - return; - } - - /* §4.5 step 3: inject EXACTLY two controls AFTER the strip. Build the final - * array = forwarded browser headers + x-mq-auth + x-mq-forward-cookie. */ - mq_h3_header_t final[MQ_H2_MAX_REQ_HDRS + 2]; - size_t nf = 0; - for (size_t i = 0; i < s->n_hdrs; i++) - final[nf++] = s->hdrs[i]; - - /* x-mq-auth: "Bearer " + raw token. Strip an existing "Bearer " on the token - * to avoid doubling (codex H3); mq_gw_client_prevalidate REQUIRES the prefix. - * The token storage must outlive req_begin — stash the formatted value in the - * stream arena. */ - const char *raw = a->submit->auth_token ? a->submit->auth_token(a->submit_user) : ""; - if (!raw) raw = ""; - { - static const char bearer[] = "Bearer "; - const size_t bl = sizeof(bearer) - 1; /* 7 */ - /* Strip a leading case-sensitive "Bearer " (the boundary re-adds exactly - * one; doubling would corrupt the secret). */ - if (strncmp(raw, bearer, bl) == 0) raw += bl; - size_t rl = strlen(raw); - if (s->arena_off + bl + rl + 1 > sizeof(s->arena)) { - stream_reject(s, NGHTTP2_ENHANCE_YOUR_CALM); - return; - } - char *vb = s->arena + s->arena_off; - memcpy(vb, bearer, bl); - memcpy(vb + bl, raw, rl); - vb[bl + rl] = '\0'; - s->arena_off += bl + rl + 1; - final[nf].name = "x-mq-auth"; - final[nf].value = vb; - nf++; - } - /* x-mq-forward-cookie: true (single cookie-forward mechanism; req_begin has no - * forward_cookie param). */ - final[nf].name = "x-mq-forward-cookie"; - final[nf].value = "true"; - nf++; - - /* prevalidate on the FINAL head: dup-control + auth format. Because the strip - * is total, only the injected x-mq-auth exists → no dup; "Bearer " - * passes the format check. */ - int status = 0; - mq_gw_reject_reason_t reason = - a->submit->prevalidate(a->submit_user, final, nf, &status); - if (reason != MQ_GW_OK) { - stream_reject_response(s, status, reason); /* :status + x-mq-error response */ - return; - } - - mq_gw_req_head_t head = {0}; - head.method = s->method; - head.scheme = s->scheme; - head.authority = s->authority; - head.path = s->path; - head.headers = final; - head.n_headers = nf; - /* Body framing (codex-1 High): the HEADERS frame's END_STREAM flag tells us - * whether a body follows. END_STREAM on HEADERS → bodyless → content_length 0 - * (the core fins on headers). No END_STREAM → a DATA body follows → - * content_length -1 (streaming/unknown length per mq_gw_intake.h; the core must - * NOT fin the headers and instead waits for req_body / req_body_done). - * - * We deliberately use -1 (streaming) rather than parsing a browser-supplied - * content-length: H2 uploads need no content-length header (DATA framing is - * self-delimiting via END_STREAM) and -1 is the correct "has body, length - * unknown" signal for the core, keeping the policy simple and robust against a - * lying browser content-length. */ - head.content_length = s->headers_end_stream ? 0 : -1; - - /* Bind the per-stream response sink (Task 7). The boundary stores `sink` + - * `sink_user` and later drives resp_head/resp_body/resp_finish/resp_abort on - * the response; sink_user is THIS per-stream context (s), which carries the - * nghttp2 stream_id + the response FIFO the sink ops operate on. */ - int err_status = 0; - reason = MQ_GW_OK; - mq_gw_xreq_t *xreq = - a->submit->req_begin(a->submit_user, &head, &mq_h2_sink, s, &err_status, &reason); - if (!xreq) { - stream_reject_response(s, err_status, reason); /* :status + x-mq-error response */ - return; - } - s->xreq = xreq; - /* nghttp2 stream user-data already points at `s`; the core handle rides on - * it (s->xreq). Task 7 wires the response sink to find s (and s->xreq). */ -} - -/* Signal the core that the upload body is complete (END_STREAM seen). Fired once - * per stream, only if the request actually materialized (has an xreq). */ -static void -finish_upload(mq_h2_stream_t *s) -{ - if (s->upload_done) return; - s->upload_done = 1; - if (s->xreq && s->a->submit->req_body_done) s->a->submit->req_body_done(s->xreq); -} - -/* on_data_chunk_recv: forward request-body bytes to the core (upload path). - * - * Contract (mq_gw_intake.h req_body): the bytes are ALWAYS consumed by the core; - * 0 = accepted (keep reading), -1 = pause (consumed, but stop feeding more). On - * -1 we return NGHTTP2_ERR_PAUSE so nghttp2_session_mem_recv stops and reports - * how much it consumed — the recv wrapper parks the unconsumed tail and the - * adapter stays paused until sink->resume_read re-feeds it (see the UPLOAD - * BACKPRESSURE block on the adapter struct). */ -static int -on_data_chunk_recv(nghttp2_session *session, uint8_t flags, int32_t stream_id, - const uint8_t *data, size_t len, void *user_data) -{ - (void)flags; - (void)user_data; - mq_h2_stream_t *s = - (mq_h2_stream_t *)nghttp2_session_get_stream_user_data(session, stream_id); - if (!s) return 0; /* no context — ignore */ - if (s->rejected) return 0; /* stream already RST'd — drop the body */ - if (!s->xreq) return 0; /* not materialized (e.g. reject pending) — drop */ - if (len == 0) return 0; - - if (s->a->submit->req_body) { - int rc = s->a->submit->req_body(s->xreq, data, len); - if (rc < 0) { - /* PAUSE: the core consumed these bytes but wants no more for now. - * Stop nghttp2 here; mem_recv returns the consumed prefix and the - * recv wrapper parks the tail. resume_read re-feeds. */ - s->a->input_paused = 1; - return NGHTTP2_ERR_PAUSE; - } - } - return 0; -} - -static int -on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) -{ - (void)user_data; - - /* DATA frame with END_STREAM → upload body complete. */ - if (frame->hd.type == NGHTTP2_DATA) { - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - mq_h2_stream_t *s = (mq_h2_stream_t *)nghttp2_session_get_stream_user_data( - session, frame->hd.stream_id); - if (s && !s->rejected) finish_upload(s); - } - return 0; - } - - if (frame->hd.type != NGHTTP2_HEADERS) return 0; - if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) return 0; - if (!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS)) return 0; - - mq_h2_stream_t *s = (mq_h2_stream_t *)nghttp2_session_get_stream_user_data( - session, frame->hd.stream_id); - if (!s) return 0; - if (s->rejected) return 0; /* already RST'd (bomb / NUL) — do not materialize */ - - /* Capture whether the request is bodyless BEFORE materializing: END_STREAM on - * the HEADERS frame ⇒ no DATA follows ⇒ content_length 0 (codex-1 High). */ - s->headers_end_stream = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) ? 1 : 0; - - materialize_request(s); - - /* Body-less request (END_STREAM on HEADERS): the upload is already complete. - * Fire req_body_done so the core sends fin and proceeds. Only if it actually - * materialized (materialize_request may have rejected it). */ - if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) && !s->rejected && s->xreq) - finish_upload(s); - return 0; -} - -static int -on_stream_close(nghttp2_session *session, int32_t stream_id, uint32_t error_code, - void *user_data) -{ - (void)error_code; - (void)user_data; - mq_h2_stream_t *s = - (mq_h2_stream_t *)nghttp2_session_get_stream_user_data(session, stream_id); - if (!s) return 0; - /* If a request was begun, tell the core the local side is gone (Task 7 owns - * the full upload/response lifecycle; header-only requests just abort). */ - if (s->xreq && s->a->submit->req_aborted) s->a->submit->req_aborted(s->xreq); - nghttp2_session_set_stream_user_data(session, stream_id, NULL); - stream_unlink(s->a, s); - free(s); - return 0; -} - -/* ── public API ────────────────────────────────────────────────────────────── */ - -mq_gw_h2_adapter_t * -mq_gw_h2_adapter_new(const mq_gw_submit_ops_t *submit, void *submit_user, - ssize_t (*send_cb)(void *io, const uint8_t *p, size_t n), void *io) -{ - if (!submit || !send_cb) return NULL; - - mq_gw_h2_adapter_t *a = calloc(1, sizeof(*a)); - if (!a) return NULL; - a->submit = submit; - a->submit_user = submit_user; - a->send_cb = send_cb; - a->io = io; - - /* Session callbacks. Task 6 registers the request-demux callbacks - * (on_begin_headers / on_header / on_frame_recv / on_stream_close); the body - * (on_data_chunk) + response path are Tasks 7/8. */ - nghttp2_session_callbacks *cbs = NULL; - if (nghttp2_session_callbacks_new(&cbs) != 0) { - free(a); - return NULL; - } - nghttp2_session_callbacks_set_on_begin_headers_callback(cbs, on_begin_headers); - nghttp2_session_callbacks_set_on_header_callback(cbs, on_header); - nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv); - nghttp2_session_callbacks_set_on_data_chunk_recv_callback(cbs, on_data_chunk_recv); - nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close); - - int rc = nghttp2_session_server_new(&a->session, cbs, a); - nghttp2_session_callbacks_del(cbs); - if (rc != 0) { - free(a); - return NULL; - } - - /* Submit the §5.2 SETTINGS. MAX_FRAME_SIZE is the default; advertising it - * explicitly documents intent and locks the value. MAX_HEADER_LIST_SIZE is - * the header-bomb guard (see the #define). */ - const nghttp2_settings_entry iv[] = { - {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, MQ_H2_MAX_CONCURRENT_STREAMS}, - {NGHTTP2_SETTINGS_MAX_FRAME_SIZE, MQ_H2_MAX_FRAME_SIZE}, - {NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, MQ_H2_HEADER_TABLE_SIZE}, - {NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, MQ_H2_MAX_HEADER_LIST_SIZE}, - }; - if (nghttp2_submit_settings(a->session, NGHTTP2_FLAG_NONE, iv, - sizeof(iv) / sizeof(iv[0])) != 0) { - nghttp2_session_del(a->session); - free(a); - return NULL; - } - - return a; -} - -/* Park `len` bytes from `p` into the bounded upload-pause buffer. Returns 0 on - * success, -1 if it would overflow the cap (fatal — the peer flooded past its H2 - * flow-control window). */ -static int -park_pending(mq_gw_h2_adapter_t *a, const uint8_t *p, size_t len) -{ - if (len == 0) return 0; - if (a->pending_len + len > sizeof(a->pending_in)) { - a->input_fatal = 1; - return -1; - } - memcpy(a->pending_in + a->pending_len, p, len); - a->pending_len += len; - return 0; -} - -int -mq_gw_h2_adapter_recv(mq_gw_h2_adapter_t *a, const uint8_t *p, size_t n) -{ - if (!a || a->input_fatal) return -1; - - /* While the upload is paused (a stream's req_body returned -1), do NOT feed - * nghttp2 — park the new bytes so the browser is throttled by H2 flow control - * (nghttp2 emits no WINDOW_UPDATE while we hold input). resume_read re-feeds - * the parked bytes. Bounded: overflow → fatal (see MQ_H2_UPLOAD_PAUSE_CAP). */ - if (a->input_paused) return park_pending(a, p, n) == 0 ? 0 : -1; - - ssize_t r = nghttp2_session_mem_recv(a->session, p, n); - if (r < 0) return -1; - - /* A short positive return means on_data_chunk_recv returned NGHTTP2_ERR_PAUSE - * (upload backpressure): nghttp2 consumed [0, r) and stopped. The unconsumed - * tail [r, n) is NOT lost — park it for re-feed on resume_read. (Absent a - * pause, mem_recv consumes the whole buffer, so r == n and nothing is - * parked.) */ - if ((size_t)r != n) { - if (!a->input_paused) return -1; /* shortfall without a pause → fatal */ - if (park_pending(a, p + (size_t)r, n - (size_t)r) != 0) return -1; - } - return 0; -} - -/* Drain the nghttp2 send queue fully through send_cb. Returns 0 on success, -1 - * on a fatal/writer error. The data provider (resp_data_read) pops response body - * bytes from each stream's FIFO during this, so the FIFOs shrink here. */ -static int -flush_session(mq_gw_h2_adapter_t *a) -{ - for (;;) { - const uint8_t *data = NULL; - ssize_t n = nghttp2_session_mem_send(a->session, &data); - if (n < 0) return -1; /* fatal */ - if (n == 0) return 0; /* nothing more to write */ - ssize_t w = a->send_cb(a->io, data, (size_t)n); - if (w < 0 || (size_t)w != (size_t)n) return -1; /* writer failed/partial */ - } -} - -int -mq_gw_h2_adapter_want_write(mq_gw_h2_adapter_t *a) -{ - if (!a) return -1; - - /* Outer loop: flush, then resume any download-deferred streams whose FIFO - * has drained below the cap. req_drained re-pumps the core, which may enqueue - * more response bytes (resp_body → resume_data) and so can GROW the FIFO; if - * it did, we flush again. Termination is NOT "the FIFO shrinks each pass": - * each resumed pass clears at least one sq_deferred flag, and a flag is only - * re-armed when resp_body hits highwater. Because resp_body appends in 16 KiB - * chunks against a 64 KiB cap, a single resume admits at most a few chunks - * before re-deferring, so flags are re-armed strictly slower than they are - * cleared — a converging process that drains and quiesces. */ - for (;;) { - if (flush_session(a) != 0) return -1; - - int resumed = 0; - for (mq_h2_stream_t *s = a->streams; s; s = s->next) { - /* Resume the core's download pump once our FIFO has room again. A - * full FIFO (== cap) stays paused; any free byte releases it. */ - if (s->sq_deferred && s->sq_len < MQ_H2_RESP_QUEUE_CAP && s->xreq && - !s->rejected && a->submit->req_drained) { - s->sq_deferred = 0; /* clear BEFORE the callback (it may re-defer) */ - a->submit->req_drained(s->xreq); - resumed = 1; - } - } - if (!resumed) return 0; /* quiescent: nothing flushed, nothing to resume */ - } -} - -void -mq_gw_h2_adapter_set_writable_cb(mq_gw_h2_adapter_t *a, void (*cb)(void *user), - void *user) -{ - if (!a) return; - a->writable_cb = cb; - a->writable_user = user; -} - -void -mq_gw_h2_adapter_free(mq_gw_h2_adapter_t *a) -{ - if (!a) return; - /* nghttp2_session_del does NOT fire on_stream_close for streams still open at - * teardown — free any survivors here (abort their core handle first, mirroring - * on_stream_close). */ - mq_h2_stream_t *s = a->streams; - while (s) { - mq_h2_stream_t *next = s->next; - if (s->xreq && a->submit->req_aborted) a->submit->req_aborted(s->xreq); - free(s); - s = next; - } - a->streams = NULL; - nghttp2_session_del(a->session); - free(a); -} - -/* The production submit wrapper (mq_gw_h2_submit_ops_gwc + mq_gw_h2_submit_gwc_*) - * is DELIBERATELY in a SEPARATE TU (mq_gw_h2_submit_gwc.c): it references - * mq_gw_client_* (→ xquic), whereas THIS adapter TU is pure nghttp2. Keeping them - * apart preserves the adapter's xquic-free unit test (test_gw_h2_adapter binds a - * capturing stub, never the gwc wrapper). */ diff --git a/src/gateway/mq_gw_h2_adapter.h b/src/gateway/mq_gw_h2_adapter.h deleted file mode 100644 index eb3ebb58..00000000 --- a/src/gateway/mq_gw_h2_adapter.h +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors - -/* mq_gw_h2_adapter.h — HTTP/2 (nghttp2) server adapter over the neutral - * mq_gw_client intake boundary (Phase 7 MITM, Slice 3). - * - * The MITM data path terminates TLS (ALPN=h2) and feeds the resulting plaintext - * H2 frames into this adapter. The adapter runs an nghttp2 SERVER session: it - * decodes the browser's HTTP/2 requests, drives them onto the shared gateway - * tunnel via the submit vtable, and frames the responses back out as H2. - * - * SKELETON SCOPE (Task 5): nghttp2 server session creation + §5.2 resource - * limits (SETTINGS) + the recv / want_write plumbing to the byte transport. No - * request demux, header policy, response path or body handling yet — those are - * Tasks 6/7/8. - * - * ── TEST SEAM (submit vtable) ─────────────────────────────────────────────── - * mq_gw_client_t is opaque and mq_gw_client_req_begin opens a request on a LIVE - * H3 tunnel (sockets), so the adapter cannot be unit-tested against the concrete - * client. The adapter therefore drives a small SUBMISSION vtable: production - * binds it to thin wrappers over mq_gw_client_* (mq_gw_h2_submit_ops_gwc, built - * in Task 11); tests bind it to a capturing stub (no tunnel). This mirrors the - * sink direction of mq_gw_intake.h. - */ -#ifndef MQ_GATEWAY_MQ_GW_H2_ADAPTER_H -#define MQ_GATEWAY_MQ_GW_H2_ADAPTER_H - -#include -#include -#include /* ssize_t */ - -#include "gateway/mq_gw_intake.h" /* mq_gw_req_head_t, mq_gw_sink_ops_t, mq_gw_xreq_t, mq_gw_reject_reason_t */ -#include "transport/mq_h3.h" /* mq_h3_header_t */ - -typedef struct mq_gw_h2_adapter mq_gw_h2_adapter_t; - -/* Submission boundary the adapter drives — production = thin wrappers over - * mq_gw_client_*; tests = capturing stub (no tunnel). Mirrors mq_gw_intake.h's - * sink direction. - * - * Only prevalidate/req_begin/auth_token truly need the gwc (carried as `u`); the - * four mq_gw_xreq_t*-keyed ops ride the same seam so the stub can observe - * upload/drain/abort without --wrap. */ -typedef struct { - mq_gw_reject_reason_t (*prevalidate)(void *u, const mq_h3_header_t *h, size_t n, - int *status); - mq_gw_xreq_t *(*req_begin)(void *u, const mq_gw_req_head_t *head, - const mq_gw_sink_ops_t *sink, void *sink_user, - int *err_status, mq_gw_reject_reason_t *reason); - int (*req_body)(mq_gw_xreq_t *r, const uint8_t *p, - size_t len); /* 0=accepted, -1=pause */ - void (*req_body_done)(mq_gw_xreq_t *r); - void (*req_drained)(mq_gw_xreq_t *r); /* DOWNLOAD resume — clears read_deferred */ - void (*req_aborted)( - mq_gw_xreq_t *r); /* adapter→core "local gone"; NO sink callback */ - const char *(*auth_token)(void *u); /* raw token; adapter prepends "Bearer " */ -} mq_gw_submit_ops_t; - -/* Create an H2 server adapter. - * - * submit / submit_user : the submission vtable + its user (BORROWED — the - * vtable and user must outlive the adapter). Production passes a wrapper - * holding the gwc; tests pass a capturing stub. - * send_cb / io : plaintext writer. The adapter calls send_cb to emit - * the H2 bytes it wants written to the (TLS) transport. Returns bytes - * accepted (>=0) or <0 on a hard error. `io` is the writer's user pointer. - * - * Submits the §5.2 SETTINGS frame immediately so the handshake completes on the - * first want_write drain. Returns NULL on bad args / OOM. */ -mq_gw_h2_adapter_t * -mq_gw_h2_adapter_new(const mq_gw_submit_ops_t *submit, void *submit_user, - ssize_t (*send_cb)(void *io, const uint8_t *p, size_t n), void *io); - -/* Feed `n` plaintext bytes from the TLS layer into the session. Returns 0 on - * success, -1 on a fatal protocol/parse error (the caller should tear down). */ -int mq_gw_h2_adapter_recv(mq_gw_h2_adapter_t *a, const uint8_t *p, size_t n); - -/* Drain any pending outbound frames through send_cb. Returns 0 on success, -1 on - * a fatal error. (Named want_write to mirror the nghttp2 wants_write notion: the - * caller invokes it whenever the session may have data to write.) */ -int mq_gw_h2_adapter_want_write(mq_gw_h2_adapter_t *a); - -/* Register a WRITABLE-NOTIFY callback (optional). The adapter fires it whenever a - * RESPONSE-side event enqueues new outbound frames OUTSIDE the caller's recv / - * want_write turn — i.e. from a sink op (resp_head/resp_body/resp_finish/ - * resp_abort) or an error-response render. These run on the async core→adapter - * path (e.g. a tunnel response arriving on a DIFFERENT event than the local-fd - * read), so without this notify nothing would drive the bytes to the transport - * until the next inbound event — a deadlock the peer never breaks (it is waiting - * for the response). The callback MUST be deferred-safe: it should only SCHEDULE - * a later want_write (e.g. arm a one-shot writable event), NEVER call want_write - * re-entrantly (the sink op may be nested inside the core's download pump, and a - * re-entrant want_write would recurse through req_drained). `user` is opaque. - * NULL cb clears it. Unset by default (the unit-test seam leaves it NULL and - * pulls want_write itself, so the adapter stays transport-agnostic). */ -void mq_gw_h2_adapter_set_writable_cb(mq_gw_h2_adapter_t *a, void (*cb)(void *user), - void *user); - -/* Free the adapter and its nghttp2 session. Does NOT free submit/submit_user/io - * (all borrowed). Safe on NULL. */ -void mq_gw_h2_adapter_free(mq_gw_h2_adapter_t *a); - -/* ── production submit vtable bound to a gwc (Task 11) ─────────────────────── - * - * The adapter drives the submit vtable, not mq_gw_client_* directly. Production - * binds it via this pair: the STATIC vtable (mq_gw_h2_submit_ops_gwc) whose ops - * thin-wrap mq_gw_client_*, plus a per-conn WRAPPER (mq_gw_h2_submit_gwc_t) that - * carries the shared mq_gw_client_t* as the vtable's `submit_user`. */ -typedef struct mq_gw_h2_submit_gwc mq_gw_h2_submit_gwc_t; -struct mq_gw_client_s; /* fwd — the wrapper borrows it */ - -/* The static production vtable (thin wrappers over mq_gw_client_*). The `u` - * argument each op receives is the wrapper below. */ -const mq_gw_submit_ops_t *mq_gw_h2_submit_ops_gwc(void); - -/* Allocate a wrapper carrying the SHARED gwc (BORROWED — not freed by - * mq_gw_h2_submit_gwc_free). The orchestrator passes the wrapper as `submit_user` - * to mq_gw_h2_adapter_new and owns its lifetime (one wrapper per MITM conn; freed - * in the conn's teardown AFTER the adapter is freed). Returns NULL on NULL gwc / - * OOM. */ -mq_gw_h2_submit_gwc_t *mq_gw_h2_submit_gwc_new(struct mq_gw_client_s *gwc); - -/* Free a wrapper. Does NOT free the borrowed gwc. Safe on NULL. */ -void mq_gw_h2_submit_gwc_free(mq_gw_h2_submit_gwc_t *w); - -#endif /* MQ_GATEWAY_MQ_GW_H2_ADAPTER_H */ diff --git a/src/gateway/mq_gw_h2_submit_gwc.c b/src/gateway/mq_gw_h2_submit_gwc.c deleted file mode 100644 index 41381de9..00000000 --- a/src/gateway/mq_gw_h2_submit_gwc.c +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors - -/* mq_gw_h2_submit_gwc.c — PRODUCTION binding of the H2 adapter's submit vtable - * (mq_gw_submit_ops_t) to a live mq_gw_client_t (Phase 7 MITM, Slice 3 Task 11). - * - * The mq_gw_h2_adapter is protocol-agnostic: it drives the submit vtable, not - * mq_gw_client_* directly, so its unit test can bind a capturing stub with NO - * tunnel. This file is the PRODUCTION binding — thin wrappers over mq_gw_client_*. - * - * It lives in a SEPARATE TU from mq_gw_h2_adapter.c ON PURPOSE: it references - * mq_gw_client_* (which pulls in the xquic transport), whereas the adapter TU is - * pure nghttp2. Splitting them keeps test_gw_h2_adapter xquic-free (the static - * archive only drags this object into a link that actually references the gwc - * wrapper — i.e. the MITM orchestrator + the live CLI, never the adapter test). - * - * Only prevalidate / req_begin / auth_token need the gwc (carried as the wrapper - * `u`); the four mq_gw_xreq_t*-keyed ops (req_body / req_body_done / req_drained / - * req_aborted) ignore `u` and forward to the per-request functions directly. - * - * LIFETIME: the wrapper instance is allocated + owned by the MITM orchestrator - * (mq_mitm_conn) — one wrapper per MITM connection (freed in the conn's teardown, - * AFTER the adapter is freed). The gwc it points at is SHARED (borrowed); the - * wrapper merely carries the pointer so the static vtable's ops can recover it. - * The vtable itself is a single static const shared by all conns. */ -#include "gateway/mq_gw_h2_adapter.h" - -#include - -#include "gateway/mq_gw_client.h" - -struct mq_gw_h2_submit_gwc { - mq_gw_client_t *gwc; /* BORROWED — shared across all MITM conns */ -}; - -static mq_gw_reject_reason_t -gwc_prevalidate(void *u, const mq_h3_header_t *h, size_t n, int *status) -{ - struct mq_gw_h2_submit_gwc *w = (struct mq_gw_h2_submit_gwc *)u; - return mq_gw_client_prevalidate(w->gwc, h, n, status); -} - -static mq_gw_xreq_t * -gwc_req_begin(void *u, const mq_gw_req_head_t *head, const mq_gw_sink_ops_t *sink, - void *sink_user, int *err_status, mq_gw_reject_reason_t *reason) -{ - struct mq_gw_h2_submit_gwc *w = (struct mq_gw_h2_submit_gwc *)u; - return mq_gw_client_req_begin(w->gwc, head, sink, sink_user, err_status, reason); -} - -static int -gwc_req_body(mq_gw_xreq_t *r, const uint8_t *p, size_t len) -{ - return mq_gw_client_req_body(r, p, len); -} - -static void -gwc_req_body_done(mq_gw_xreq_t *r) -{ - mq_gw_client_req_body_done(r); -} - -static void -gwc_req_drained(mq_gw_xreq_t *r) -{ - mq_gw_client_req_drained(r); -} - -static void -gwc_req_aborted(mq_gw_xreq_t *r) -{ - mq_gw_client_req_aborted(r); -} - -static const char * -gwc_auth_token(void *u) -{ - struct mq_gw_h2_submit_gwc *w = (struct mq_gw_h2_submit_gwc *)u; - return mq_gw_client_token(w->gwc); /* raw token; adapter prepends "Bearer " */ -} - -static const mq_gw_submit_ops_t g_gwc_ops = { - .prevalidate = gwc_prevalidate, - .req_begin = gwc_req_begin, - .req_body = gwc_req_body, - .req_body_done = gwc_req_body_done, - .req_drained = gwc_req_drained, - .req_aborted = gwc_req_aborted, - .auth_token = gwc_auth_token, -}; - -const mq_gw_submit_ops_t * -mq_gw_h2_submit_ops_gwc(void) -{ - return &g_gwc_ops; -} - -mq_gw_h2_submit_gwc_t * -mq_gw_h2_submit_gwc_new(struct mq_gw_client_s *gwc) -{ - if (!gwc) return NULL; - mq_gw_h2_submit_gwc_t *w = calloc(1, sizeof(*w)); - if (!w) return NULL; - w->gwc = gwc; - return w; -} - -void -mq_gw_h2_submit_gwc_free(mq_gw_h2_submit_gwc_t *w) -{ - free(w); /* does NOT free the borrowed gwc */ -} diff --git a/src/gateway/mq_gw_headers.c b/src/gateway/mq_gw_headers.c deleted file mode 100644 index 20362090..00000000 --- a/src/gateway/mq_gw_headers.c +++ /dev/null @@ -1,433 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "gateway/mq_gw_headers.h" - -#include -#include - -#include "gateway/mq_gw_intake.h" /* mq_gw_reject_reason_t / mq_gw_reject_xmq */ - -/* Map a reject reason → its X-Mq-Error string. SHARED across gateway adapters - * (H1 fetch + H2 MITM) via the mq_gw_intake.h declaration so the rendered error - * strings cannot drift between protocols (tests/e2e assert these 10 literals - * byte-for-byte — do NOT alter them). - * - * Extracted from the former static gw_reason_xmq in mq_gw_fetch_adapter.c - * (Slice 3 Task 8, codex M-3 / DRY). It lives HERE rather than in mq_gw_client.c - * (the plan's first choice) because the H2 adapter unit test links the adapter - * .o out of the static mqproxy archive with ONLY nghttp2 (no libevent/xquic; see - * CMakeLists test_gw_h2_adapter): referencing a symbol in mq_gw_client.c would - * drag that whole object's event/xquic undefined refs into the leaf test and - * break the link. mq_gw_headers.c is an already-compiled gateway TU that is a - * pure leaf (no event/xquic), so the shared helper stays linkable from both the - * fetch path AND the isolated H2 test — same parsimony (no new file), correct - * isolation. */ -const char * -mq_gw_reject_xmq(mq_gw_reject_reason_t reason) -{ - switch (reason) { - case MQ_GW_REJ_DUP_CONTROL: return "duplicate-control-header"; - case MQ_GW_REJ_MISSING_AUTH: return "missing-auth"; - case MQ_GW_REJ_BAD_AUTH: return "bad-auth-format"; - case MQ_GW_REJ_BAD_TARGET: return "bad-target"; - case MQ_GW_REJ_BAD_METHOD: return "bad-method"; - case MQ_GW_REJ_BAD_ORIGIN_PROTO: return "bad-origin-protocol"; - case MQ_GW_REJ_BAD_CACHE_TTL: return "bad-cache-ttl"; - case MQ_GW_REJ_HEADER_TOO_LONG: return "header-too-long"; - case MQ_GW_REJ_TUNNEL_UNAVAIL: return "tunnel-unavailable"; - case MQ_GW_REJ_INTERNAL: return "internal-error"; - default: return "internal-error"; - } -} - -/* The MQ_GW_CURL_* constants in mq_gw_headers.h are mirrors of CURLE_* values - * so that Chunk-3 unit tests compile without libcurl. These asserts pin the - * mirrors to the real libcurl values; any future divergence is caught at - * compile time rather than silently producing wrong HTTP status codes. */ -_Static_assert(MQ_GW_CURL_RESOLVE == CURLE_COULDNT_RESOLVE_HOST, - "MQ_GW_CURL_RESOLVE must equal CURLE_COULDNT_RESOLVE_HOST"); -_Static_assert(MQ_GW_CURL_CONNECT == CURLE_COULDNT_CONNECT, - "MQ_GW_CURL_CONNECT must equal CURLE_COULDNT_CONNECT"); -_Static_assert(MQ_GW_CURL_TIMEDOUT == CURLE_OPERATION_TIMEDOUT, - "MQ_GW_CURL_TIMEDOUT must equal CURLE_OPERATION_TIMEDOUT"); -_Static_assert(MQ_GW_CURL_PEER_VERIFY == CURLE_PEER_FAILED_VERIFICATION, - "MQ_GW_CURL_PEER_VERIFY must equal CURLE_PEER_FAILED_VERIFICATION"); - -/* --------------------------------------------------------------------------- - * Small ASCII helpers (locale-independent; we never want locale to influence - * what counts as a token char / how a name is folded). - * ------------------------------------------------------------------------- */ -static char -lc(char c) -{ - return (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c; -} - -static char -uc(char c) -{ - return (c >= 'a' && c <= 'z') ? (char)(c - 32) : c; -} - -/* Case-insensitive compare of an (n,nl) name against a NUL-terminated literal. - * Returns 1 if equal. */ -static int -name_eq(const char *n, size_t nl, const char *lit) -{ - if (nl != strlen(lit)) return 0; - for (size_t i = 0; i < nl; i++) - if (lc(n[i]) != lc(lit[i])) return 0; - return 1; -} - -/* Case-insensitive prefix test: does (n,nl) start with NUL-terminated pfx? */ -static int -name_has_prefix(const char *n, size_t nl, const char *pfx) -{ - size_t pl = strlen(pfx); - if (nl < pl) return 0; - for (size_t i = 0; i < pl; i++) - if (lc(n[i]) != lc(pfx[i])) return 0; - return 1; -} - -/* RFC 7230 token char: tchar = "!#$%&'*+-.^_`|~" / DIGIT / ALPHA */ -int -mq_gw_is_tchar(unsigned char c) -{ - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) - return 1; - switch (c) { - case '!': - case '#': - case '$': - case '%': - case '&': - case '\'': - case '*': - case '+': - case '-': - case '.': - case '^': - case '_': - case '`': - case '|': - case '~': return 1; - default: return 0; - } -} - -/* A byte we forbid inside authority / path: controls (<0x20), DEL (0x7f), or - * SP. The caller handles '#' (fragment) and '@' (userinfo) separately. */ -static int -is_forbidden_uri_byte(unsigned char c) -{ - return c < 0x20 || c == 0x7f || c == ' '; -} - -/* --------------------------------------------------------------------------- - * Target parsing - * ------------------------------------------------------------------------- */ -int -mq_gw_parse_target(const char *s, size_t len, mq_gw_target_t *out) -{ - /* scheme (lowercase, fixed) */ - const char *scheme; - size_t off; - if (len >= 8 && memcmp(s, "https://", 8) == 0) { - scheme = "https"; - off = 8; - } else if (len >= 7 && memcmp(s, "http://", 7) == 0) { - scheme = "http"; - off = 7; - } else { - return -1; - } - - /* authority = bytes up to first '/' or '?' (or end). - * Bracket tracking is only honoured when '[' appears at the very first - * byte of the authority (IPv6 literal). A '[' anywhere else is illegal - * in a host and must cause the whole target to be rejected. */ - size_t a_start = off; - size_t i = off; - int in_brackets = 0; - int bracket_err = 0; /* set if '[' seen at a non-zero authority offset */ - for (; i < len; i++) { - char c = s[i]; - if (c == '[') { - if (i == a_start) - in_brackets = 1; - else - bracket_err = 1; /* stray '[' — mark and keep scanning */ - } else if (c == ']') { - in_brackets = 0; - } else if (!in_brackets && (c == '/' || c == '?')) { - break; - } - } - if (bracket_err) return -1; - - size_t a_len = i - a_start; - if (a_len == 0 || a_len > 255) return -1; - - const char *auth = s + a_start; - - /* Reject userinfo, forbidden bytes, and any '['/']' that are not part - * of a well-formed leading IPv6 literal (i.e. auth[0] == '[' with a - * matching ']'). A lone ']' when the authority is not bracketed is - * illegal. */ - int is_bracketed = (auth[0] == '['); - for (size_t k = 0; k < a_len; k++) { - unsigned char c = (unsigned char)auth[k]; - if (c == '@') return -1; /* userinfo — credential-injection surface */ - if (c == '#') return -1; /* fragment cannot appear in authority */ - if (is_forbidden_uri_byte(c)) return -1; - /* '[' at k>0 was already caught by bracket_err above (the scan loop - * continued past a stray '[' only if bracket_err was set, which - * triggered an early return). Guard here too for safety. */ - if (c == '[' && k != 0) return -1; - /* ']' is only legal inside a bracketed IPv6 literal. */ - if (c == ']' && !is_bracketed) return -1; - } - - /* Validate host:port split. For a bracketed IPv6 literal "[...]", the - * colon scan starts after ']'; otherwise split on the LAST ':'. */ - size_t colon_scan_from = 0; - if (is_bracketed) { - /* find closing ']' */ - size_t rb = 0; - int found = 0; - for (size_t k = 1; k < a_len; k++) { - if (auth[k] == ']') { - rb = k; - found = 1; - break; - } - } - if (!found || rb == 1) return -1; /* unterminated or empty "[]" */ - colon_scan_from = rb + 1; - /* after ']' only an optional ":port" may follow */ - if (colon_scan_from < a_len && auth[colon_scan_from] != ':') return -1; - } - - /* locate port colon (last ':' at/after colon_scan_from) */ - size_t pc = a_len; /* a_len == "no port" */ - for (size_t k = colon_scan_from; k < a_len; k++) - if (auth[k] == ':') pc = k; - if (pc != a_len) { - /* port present: must be non-empty, digits only */ - if (pc + 1 >= a_len) return -1; /* "host:" with empty port */ - for (size_t k = pc + 1; k < a_len; k++) - if (auth[k] < '0' || auth[k] > '9') return -1; - /* host part must be non-empty (e.g. ":8443" is invalid) */ - if (pc == 0) return -1; - /* non-bracketed host must not itself contain ':' (e.g. "h:80:90") */ - if (!is_bracketed) { - for (size_t k = 0; k < pc; k++) - if (auth[k] == ':') return -1; - } - } - - /* path+query = the rest (from index i to end). */ - const char *rest = s + i; - size_t rest_len = len - i; - - /* reject fragment and forbidden bytes in path+query */ - for (size_t k = 0; k < rest_len; k++) { - unsigned char c = (unsigned char)rest[k]; - if (c == '#') return -1; /* fragment — caller bug */ - if (is_forbidden_uri_byte(c)) return -1; - } - - /* Build the canonical path string. */ - char pathbuf[1024]; - size_t pl; - if (rest_len == 0) { - pathbuf[0] = '/'; - pl = 1; - } else if (rest[0] == '?') { - /* query only → prepend "/" */ - if (rest_len + 1 > sizeof(pathbuf) - 1) return -1; - pathbuf[0] = '/'; - memcpy(pathbuf + 1, rest, rest_len); - pl = rest_len + 1; - } else { - /* starts with '/' */ - if (rest_len > sizeof(pathbuf) - 1) return -1; - memcpy(pathbuf, rest, rest_len); - pl = rest_len; - } - /* pl is now <= 1023; NUL terminates within bounds */ - - /* Commit to *out only after every check passed. */ - memcpy(out->scheme, scheme, strlen(scheme) + 1); - memcpy(out->authority, auth, a_len); - out->authority[a_len] = '\0'; - memcpy(out->path, pathbuf, pl); - out->path[pl] = '\0'; - return 0; -} - -/* --------------------------------------------------------------------------- - * Method parsing - * ------------------------------------------------------------------------- */ -int -mq_gw_parse_method(const char *s, size_t len, char out[16]) -{ - if (len == 0 || len > 15) return -1; - char tmp[16]; - for (size_t i = 0; i < len; i++) { - if (!mq_gw_is_tchar((unsigned char)s[i])) return -1; - tmp[i] = uc(s[i]); - } - memcpy(out, tmp, len); - out[len] = '\0'; - return 0; -} - -/* --------------------------------------------------------------------------- - * Origin HTTP version selection - * ------------------------------------------------------------------------- */ -mq_http_ver_t -mq_gw_parse_http_ver(const char *v, size_t vl) -{ - if (name_eq(v, vl, "h3")) return MQ_HTTP_VER_H3; - if (name_eq(v, vl, "h2")) return MQ_HTTP_VER_H2; - if (name_eq(v, vl, "h1")) return MQ_HTTP_VER_H1; - return MQ_HTTP_VER_DEFAULT; -} - -unsigned -mq_gw_parse_cache_ttl(const char *v, size_t vl) -{ - if (!v || vl == 0) return 0; - unsigned long n = 0; - for (size_t i = 0; i < vl; i++) { - char c = v[i]; - if (c < '0' || c > '9') return 0; /* non-digit (incl. sign/space) */ - if (n > (MQ_GW_CACHE_TTL_MAX / 10) + 1) return 0; /* early overflow guard */ - n = n * 10 + (unsigned long)(c - '0'); - } - if (n < 1 || n > MQ_GW_CACHE_TTL_MAX) return 0; - return (unsigned)n; -} - -/* --------------------------------------------------------------------------- - * Strip predicates - * ------------------------------------------------------------------------- */ -int -mq_gw_strip_hop(const char *n, size_t nl) -{ - static const char *const hop[] = { - "Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", - "TE", "Trailer", "Transfer-Encoding", "Upgrade", - }; - for (size_t i = 0; i < sizeof(hop) / sizeof(hop[0]); i++) - if (name_eq(n, nl, hop[i])) return 1; - return 0; -} - -int -mq_gw_strip_client(const char *n, size_t nl, int forward_cookie) -{ - if (mq_gw_strip_hop(n, nl)) return 1; - if (name_has_prefix(n, nl, "X-Mq-")) return 1; - if (name_eq(n, nl, "Host")) return 1; - if (name_eq(n, nl, "Content-Length")) return 1; - if (!forward_cookie && name_eq(n, nl, "Cookie")) return 1; - return 0; -} - -int -mq_gw_strip_server(const char *n, size_t nl) -{ - if (mq_gw_strip_hop(n, nl)) return 1; - if (name_has_prefix(n, nl, "X-Mq-")) return 1; - return 0; -} - -/* --------------------------------------------------------------------------- - * Duplicate X-Mq-* detection - * ------------------------------------------------------------------------- */ -int -mq_gw_has_dup_xmq(const mq_http1_req_t *req) -{ - for (size_t i = 0; i < req->nh; i++) { - if (!name_has_prefix(req->h[i].n, req->h[i].nl, "X-Mq-")) continue; - for (size_t j = i + 1; j < req->nh; j++) { - if (req->h[i].nl != req->h[j].nl) continue; - int eq = 1; - for (size_t k = 0; k < req->h[i].nl; k++) - if (lc(req->h[i].n[k]) != lc(req->h[j].n[k])) { - eq = 0; - break; - } - if (eq) return 1; - } - } - return 0; -} - -int -mq_gw_forward_cookie_requested(const mq_http1_req_t *req) -{ - for (size_t i = 0; i < req->nh; i++) { - if (!name_eq(req->h[i].n, req->h[i].nl, "X-Mq-Forward-Cookie")) continue; - /* Values reach us already OWS-trimmed: mq_http1_parse_req (the only producer of - * mq_http1_req_t) strips leading/trailing SP/HTAB before storing v/vl, so no trim - * is needed here. name_eq is a generic length-checked case-insensitive - * slice==cstr. */ - return name_eq(req->h[i].v, req->h[i].vl, "true"); - } - return 0; /* header absent */ -} - -/* --------------------------------------------------------------------------- - * Control-byte rejection - * ------------------------------------------------------------------------- */ -int -mq_gw_hdr_name_ok(const char *s, size_t sl) -{ - for (size_t i = 0; i < sl; i++) { - unsigned char c = (unsigned char)s[i]; - if (c < 0x20 || c == 0x7f) return 0; /* controls (incl. TAB) + DEL */ - } - return 1; -} - -int -mq_gw_hdr_value_ok(const char *s, size_t sl) -{ - for (size_t i = 0; i < sl; i++) { - unsigned char c = (unsigned char)s[i]; - if (c == 0x09) continue; /* HTAB is permitted in values */ - if (c < 0x20 || c == 0x7f) return 0; /* other controls + DEL */ - } - return 1; -} - -int -mq_gw_uri_field_ok(const char *s, size_t sl) -{ - /* is_forbidden_uri_byte already covers SP, all controls (<0x20, includes - * CR/LF), and 0x7f. */ - for (size_t i = 0; i < sl; i++) - if (is_forbidden_uri_byte((unsigned char)s[i])) return 0; - return 1; -} - -/* --------------------------------------------------------------------------- - * curl result code → HTTP status (design §10.1) - * ------------------------------------------------------------------------- */ -int -mq_gw_status_from_curl(int curl_code) -{ - switch (curl_code) { - case MQ_GW_CURL_TIMEDOUT: return 504; - case MQ_GW_CURL_RESOLVE: - case MQ_GW_CURL_CONNECT: - case MQ_GW_CURL_PEER_VERIFY: - default: return 502; - } -} diff --git a/src/gateway/mq_gw_headers.h b/src/gateway/mq_gw_headers.h deleted file mode 100644 index 44736b69..00000000 --- a/src/gateway/mq_gw_headers.h +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_GW_HEADERS_H -#define MQ_GW_HEADERS_H -#include - -#include "gateway/mq_http1.h" - -/* Pure, allocation-free header-policy / target-parsing / error-mapping helpers - * shared by the gateway CLIENT (it tunnels a local fetch request) and the - * gateway SERVER (it replays that request to an origin). Nothing here touches - * sockets, libcurl, or the transport; every function is a deterministic - * function of its inputs so it can be exhaustively unit-tested. - * - * Design references: spec §14.2 / §14.5 (strip rules), §18.3 (error mapping), - * design doc §7 (header policy) / §10 (curl error → HTTP status). - * §14.2 X-Mq-Cache: opt-in response-cache TTL seconds (mq_gw_parse_cache_ttl). - */ - -/* --------------------------------------------------------------------------- - * Target parsing (X-Mq-Target → scheme / authority / path+query) - * ------------------------------------------------------------------------- */ -typedef struct { - char scheme[8]; /* "http" | "https" (NUL-terminated) */ - char authority[256]; /* host[:port], non-empty (NUL-terminated) */ - char path[1024]; /* path + query; "/" if empty (NUL-terminated) */ -} mq_gw_target_t; - -/* Parse the X-Mq-Target value into scheme / authority / path+query. - * Returns 0 on success (filling *out), -1 on any rejection. - * - * Accepted form: ("http://" | "https://") authority [ path ] [ "?" query ] - * - * Deliberately strict (this value crosses the local trust boundary and is - * turned into an outbound fetch URL — a lenient parser here is an SSRF / - * credential-injection / request-smuggling surface): - * - * - scheme: lowercase "http://" or "https://" ONLY. Mixed/upper case - * ("HTTPS://") is REJECTED — callers emit the target, so requiring the - * canonical lowercase form keeps the contract unambiguous and avoids - * scheme-confusion games. - * - authority: bytes up to the first '/' or '?' (or end). Must be non-empty - * and <= 255 bytes. NO '@' anywhere (userinfo is rejected outright — it is - * a credential-injection surface and we never want to carry creds in the - * URL). No control bytes (<0x20 or 0x7f) and no space. If a port is - * present it must be digits-only and non-empty; the host:port split is on - * the LAST ':' UNLESS the host is a bracketed IPv6 literal "[....]", in - * which case the colon scan starts after the closing ']'. - * - path+query: everything after the authority. If empty → "/". If it starts - * with '?' (no path segment) → "/" + query. <= 1023 bytes. No control - * bytes (<0x20 or 0x7f) and no space. - * - fragment ("#..."): REJECTED. A fetch target carrying a fragment is a - * caller bug (fragments are client-side only and never sent on the wire); - * rejecting it surfaces the bug instead of silently forwarding it. - */ -int mq_gw_parse_target(const char *s, size_t len, mq_gw_target_t *out); - -/* --------------------------------------------------------------------------- - * Origin HTTP version selection - * ------------------------------------------------------------------------- */ - -/* Requested origin HTTP version. DEFAULT = libcurl's own choice (TCP h2/h1 - * ALPN, NO QUIC attempt). H3 is honored only if libcurl was built with HTTP/3 - * (else falls back to DEFAULT). */ -typedef enum { - MQ_HTTP_VER_DEFAULT = 0, - MQ_HTTP_VER_H1, - MQ_HTTP_VER_H2, - MQ_HTTP_VER_H3 -} mq_http_ver_t; - -/* Parse an X-Mq-Origin-Protocol token (case-insensitive "h1"/"h2"/"h3") → mq_http_ver_t. - * Absent / empty / unrecognized → MQ_HTTP_VER_DEFAULT. Callers that must REJECT an - * invalid value test for DEFAULT on a non-empty input (the client does); the server - * treats DEFAULT as "no preference". */ -mq_http_ver_t mq_gw_parse_http_ver(const char *v, size_t vl); - -/* Parse an X-Mq-Cache value: strict decimal seconds in [1, 31536000] (1 year). - * Returns the seconds on success, or 0 for - * absent/empty/non-numeric/zero/out-of-range/overflow (0 is the "no caching" sentinel — - * callers treat >0 as opted-in). */ -#define MQ_GW_CACHE_TTL_MAX 31536000u -unsigned mq_gw_parse_cache_ttl(const char *v, size_t vl); - -/* --------------------------------------------------------------------------- - * Token-char classification - * ------------------------------------------------------------------------- */ - -/* RFC 7230 token char (tchar): DIGIT / ALPHA / "!#$%&'*+-.^_`|~". Returns 1 if c - * is a valid token byte, 0 otherwise. Shared by the HTTP/1 request parser (field - * names) and the method/target validators. */ -int mq_gw_is_tchar(unsigned char c); - -/* --------------------------------------------------------------------------- - * Method parsing - * ------------------------------------------------------------------------- */ - -/* Validate and canonicalise an HTTP method. RFC 7230 token chars only; - * ASCII-lowercased letters are uppercased; result must be 1..15 chars. - * Writes a NUL-terminated method into out[16] and returns 0, or returns -1 - * (leaving out unspecified) on empty / too-long / non-token input. */ -int mq_gw_parse_method(const char *s, size_t len, char out[16]); - -/* --------------------------------------------------------------------------- - * Strip predicates (case-insensitive header-name match; 1 = strip the header) - * - * hop-by-hop (RFC 7230 §6.1, stripped in BOTH directions): - * Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, - * Trailer, Transfer-Encoding, Upgrade - * client → tunnel (mq_gw_strip_client): hop-by-hop + X-Mq-* (any) + Host + - * Content-Length + Cookie (UNLESS forward_cookie != 0 — the X-Mq-Forward-Cookie - * opt-in; see mq_gw_forward_cookie_requested) - * server → origin (mq_gw_strip_server): hop-by-hop + X-Mq-* (any) - * - * NOTE: Authorization is intentionally NOT in any set — it is default-forwarded - * to the origin so the caller can authenticate to the target. Cookie is stripped - * client-side only (not server-side) and only when forward_cookie == 0; - * Content-Length is stripped client-side but is NOT hop-by-hop. - * ------------------------------------------------------------------------- */ -int mq_gw_strip_client(const char *n, size_t nl, int forward_cookie); -int mq_gw_strip_server(const char *n, size_t nl); -int mq_gw_strip_hop(const char *n, size_t nl); - -/* Returns 1 if the parsed request carries the same X-Mq-* header name two or - * more times (a duplicate control header → caller should answer 400). Only - * X-Mq-* names are considered; ordinary duplicate headers (e.g. two Accept) - * are legal and ignored here. Name comparison is case-insensitive. */ -int mq_gw_has_dup_xmq(const mq_http1_req_t *req); - -/* Returns 1 iff the request carries X-Mq-Forward-Cookie whose value (already OWS-trimmed - * by mq_http1_parse_req) case-insensitively equals "true" — the spec §14.2 opt-in to - * forward the Cookie header upstream. Absent / "false" / "1" / "" / anything else → 0. - * (A duplicate X-Mq-Forward-Cookie is rejected upstream by mq_gw_has_dup_xmq; this reads - * the first match.) */ -int mq_gw_forward_cookie_requested(const mq_http1_req_t *req); - -/* --------------------------------------------------------------------------- - * Control-byte rejection (defense-in-depth against header / request smuggling) - * - * A forwarded header NAME or VALUE that carries control bytes is a smuggling / - * response-splitting surface once it is replayed to the origin (or serialized - * back into an HTTP/1 response). Both helpers return 1 when the (s,sl) slice is - * clean and 0 when it contains a forbidden byte. - * - * mq_gw_hdr_name_ok — rejects any byte < 0x20 (controls incl. TAB) or 0x7f. - * mq_gw_hdr_value_ok — rejects any byte < 0x20 EXCEPT 0x09 (HTAB is permitted - * in field values per RFC 7230), and rejects 0x7f. - * ------------------------------------------------------------------------- */ -int mq_gw_hdr_name_ok(const char *s, size_t sl); -int mq_gw_hdr_value_ok(const char *s, size_t sl); - -/* Returns 1 if (s,sl) is free of SP, CR, LF, other control bytes (<0x20), and - * 0x7f — the strictness mq_gw_parse_target applies to authority/path, exposed so - * the gateway server can re-validate the :authority / :path pseudo-headers it - * receives over the tunnel before building an outbound URL. */ -int mq_gw_uri_field_ok(const char *s, size_t sl); - -/* --------------------------------------------------------------------------- - * libcurl result code → HTTP status (design §10.1) - * - * Mirrored constants so this translation unit compiles WITHOUT libcurl. The - * curl client (Chunk 4) adds _Static_asserts that each mirror equals the real - * CURLE_* value, so any future divergence is caught at compile time. - * ------------------------------------------------------------------------- */ -#define MQ_GW_CURL_RESOLVE 6 /* mirrors CURLE_COULDNT_RESOLVE_HOST */ -#define MQ_GW_CURL_CONNECT 7 /* mirrors CURLE_COULDNT_CONNECT */ -#define MQ_GW_CURL_TIMEDOUT 28 /* mirrors CURLE_OPERATION_TIMEDOUT */ -#define MQ_GW_CURL_PEER_VERIFY 60 /* mirrors CURLE_PEER_FAILED_VERIFICATION */ - -/* Map a curl result code to an HTTP status for the synthesized error response: - * RESOLVE(6)->502, CONNECT(7)->502, TIMEDOUT(28)->504, PEER_VERIFY(60)->502, - * anything else -> 502. - * This must NOT be called with 0 (CURLE_OK) — success has no error status; if - * it is, the defensive result is 502. */ -int mq_gw_status_from_curl(int curl_code); - -#endif diff --git a/src/gateway/mq_gw_intake.h b/src/gateway/mq_gw_intake.h deleted file mode 100644 index 9e2ff22d..00000000 --- a/src/gateway/mq_gw_intake.h +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -#ifndef MQ_GW_INTAKE_H -#define MQ_GW_INTAKE_H - -#include -#include -#include "transport/mq_h3.h" /* mq_h3_header_t */ - -/* How the response body will be framed for the adapter. */ -typedef enum { - MQ_GW_BODY_CONTENT_LENGTH = 0, /* relay as-is; length known */ - MQ_GW_BODY_STREAM = 1, /* unknown length (H1 renders chunked; H3 needs nothing) */ -} mq_gw_body_mode_t; - -/* Parsed, adapter-validated request head handed to the core. */ -typedef struct { - const char *method, *scheme, *authority, *path; /* non-NULL, non-empty */ - const mq_h3_header_t *headers; - size_t n_headers; /* regular headers, envelope-stripped */ - int64_t content_length; /* -1 unknown (stream); >=0 known */ -} mq_gw_req_head_t; - -/* Core -> adapter callbacks (replaces direct mq_fetch_conn_* calls). */ -typedef struct { - int (*resp_head)(void *u, int status, const mq_h3_header_t *hs, size_t n, - mq_gw_body_mode_t body_mode); - int (*resp_body)(void *u, const uint8_t *p, - size_t len); /* >0 accept, 0 highwater, -1 dead */ - void (*resp_finish)(void *u); - void (*resp_abort)(void *u); /* mid-stream failure — never a fake clean finish */ - void (*resume_read)(void *u); /* upload backpressure released */ -} mq_gw_sink_ops_t; - -/* Opaque per-request core handle (was mq_gw_req_t). */ -typedef struct mq_gw_xreq_s mq_gw_xreq_t; - -/* Reject reason — maps 1:1 to the existing X-Mq-Error strings (which tests/e2e assert - * byte-for-byte). The CORE returns this enum; the ADAPTER maps it to (status, X-Mq-Error - * text) so rendered errors stay identical. Values mirror gw_reject_write call sites in - * mq_gw_client.c (verified): */ -typedef enum { - MQ_GW_OK = 0, - MQ_GW_REJ_DUP_CONTROL, /* 400 "duplicate-control-header" */ - MQ_GW_REJ_MISSING_AUTH, /* 400 "missing-auth" */ - MQ_GW_REJ_BAD_AUTH, /* 400 "bad-auth-format" */ - MQ_GW_REJ_BAD_TARGET, /* 400 "bad-target" (adapter-detected, fetch envelope) */ - MQ_GW_REJ_BAD_METHOD, /* 400 "bad-method" (adapter-detected, fetch envelope) */ - MQ_GW_REJ_BAD_ORIGIN_PROTO, /* 400 "bad-origin-protocol" */ - MQ_GW_REJ_BAD_CACHE_TTL, /* 400 "bad-cache-ttl" */ - MQ_GW_REJ_HEADER_TOO_LONG, /* 400 "header-too-long" */ - MQ_GW_REJ_TUNNEL_UNAVAIL, /* 502 "tunnel-unavailable" */ - MQ_GW_REJ_INTERNAL, /* 502 "internal-error" */ -} mq_gw_reject_reason_t; - -/* Map a reject reason → its X-Mq-Error string (byte-identical across adapters; - * tests/e2e assert these verbatim). Returns a borrowed, NUL-terminated literal - * (never NULL — an unknown reason maps to "internal-error"). Shared by every - * gateway adapter (H1 fetch + H2 MITM) so the rendered error strings cannot - * drift between protocols. Implemented in mq_gw_headers.c (a leaf gateway TU, kept - * dependency-light so the nghttp2-only adapter test links it without dragging in - * xquic). */ -const char *mq_gw_reject_xmq(mq_gw_reject_reason_t reason); - -#endif /* MQ_GW_INTAKE_H */ diff --git a/src/gateway/mq_gw_metrics.c b/src/gateway/mq_gw_metrics.c deleted file mode 100644 index a9b92971..00000000 --- a/src/gateway/mq_gw_metrics.c +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "gateway/mq_gw_metrics.h" -#include -#include - -/* Append a logfmt quoted field: key="value" with capping + quote-escaping. - * Writes into buf[off..cap). Returns new offset, or -1 on overflow. cap_value - * bounds the value length (excluding escapes); on cap an ellipsis marker "…" - * is appended before the closing quote so truncation is visible. */ -static int -append_quoted(char *buf, size_t cap, int off, const char *key, const char *val, - size_t cap_value) -{ - if (off < 0) return -1; - int n = snprintf(buf + off, (off < (int)cap) ? cap - off : 0, " %s=\"", key); - if (n < 0 || off + n >= (int)cap) return -1; - off += n; - size_t written = 0; - int capped = 0; - for (const char *p = val; *p; p++) { - if (written >= cap_value) { - capped = 1; - break; - } - char esc[3]; - int el; - if (*p == '"' || *p == '\\') { - esc[0] = '\\'; - esc[1] = *p; - el = 2; - } else { - esc[0] = *p; - el = 1; - } - if (off + el >= (int)cap) return -1; - memcpy(buf + off, esc, el); - off += el; - written++; - } - if (capped) { - const char *ell = "\xE2\x80\xA6"; /* U+2026 … */ - int el = 3; - if (off + el >= (int)cap) return -1; - memcpy(buf + off, ell, el); - off += el; - } - if (off + 1 >= (int)cap) return -1; - buf[off++] = '"'; - return off; -} - -int -mq_gw_format_req_line(char *buf, size_t cap, const char *cid_hex, uint64_t sid, - const mq_gw_req_metrics_t *m) -{ - if (!buf || !cid_hex || !m) return -1; - int off = snprintf(buf, cap, "mq.req cid=%s sid=%llu method=%s status=%d", cid_hex, - (unsigned long long)sid, m->method, m->status); - if (off < 0 || off >= (int)cap) return -1; - - off = - append_quoted(buf, cap, off, "authority", m->authority, MQ_GW_REQ_AUTHORITY_CAP); - off = append_quoted(buf, cap, off, "path", m->path, MQ_GW_REQ_PATH_CAP); - if (off < 0) return -1; - - int n = snprintf(buf + off, cap - off, - " req_bytes=%llu resp_bytes=%llu ttfb_ms=%d duration_ms=%d" - " origin_protocol=%s origin_tls=%s content_encoding=%s cache=%s" - " origin_reuse=%d origin_connect_ms=%d mp_state=%d completion_ms=%d", - (unsigned long long)m->req_bytes, (unsigned long long)m->resp_bytes, - m->ttfb_ms, m->duration_ms, m->origin_protocol, m->origin_tls, - m->content_encoding, m->cache, m->origin_reuse, m->origin_connect_ms, - m->mp_state, m->completion_ms); - if (n < 0 || off + n >= (int)cap) return -1; - off += n; - - off = append_quoted(buf, cap, off, "reset", m->reset_reason, MQ_GW_REQ_RESET_CAP); - if (off < 0) return -1; - if (off < (int)cap) buf[off] = '\0'; - return off; -} diff --git a/src/gateway/mq_gw_metrics.h b/src/gateway/mq_gw_metrics.h deleted file mode 100644 index b2baa92d..00000000 --- a/src/gateway/mq_gw_metrics.h +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_GW_METRICS_H -#define MQ_GW_METRICS_H - -#include -#include - -/* mq.req line buffer cap. Holds the fixed keys plus the capped authority/path - * (see *_CAP below). 1024 is comfortable. Note: each *_CAP bounds INPUT - * characters; a quoted field can emit up to ~2x its cap in bytes when every - * character needs escaping (e.g. all-quotes value). A -1 (caller drops the - * line) is therefore reachable on adversarial input; expected-rare for normal - * URL authority/path. */ -#define MQ_GW_REQ_LINE_CAP 1024 -#define MQ_GW_REQ_AUTHORITY_CAP 128 -#define MQ_GW_REQ_PATH_CAP 256 -#define MQ_GW_REQ_RESET_CAP 64 /* max input chars for reset_reason field */ - -/* All values are already gathered by the caller; no xquic/libcurl/libevent - * dependency, so this is a pure-unit test target. String fields must be - * non-NULL ("-" / "" for absent). Numeric -1 denotes "unknown". */ -typedef struct { - const char *method; /* "GET"/"POST"/... or "-" */ - int status; /* HTTP status sent to client; 0 if none */ - const char *authority; /* target host[:port] or "-" */ - const char *path; /* target path, query already stripped, or "-" */ - uint64_t req_bytes; /* request body bytes received from client */ - uint64_t resp_bytes; /* response body bytes sent to client */ - int ttfb_ms; /* -1 if unknown */ - int duration_ms; /* -1 if unknown */ - const char *origin_protocol; /* "h3"/"h2"/"h1"/"none" */ - const char *origin_tls; /* "ok"/"verify_fail"/"connect_fail"/"na" */ - const char - *content_encoding; /* origin response Content-Encoding: gzip/br/identity/none */ - const char *cache; /* "bypass" now; "hit"/"miss" in Phase 6 */ - int origin_reuse; /* 1 when the origin connection was reused, else 0 */ - int origin_connect_ms; /* origin conn setup ms; 0 reuse; -1 unknown */ - int mp_state; /* xqc_request_stats_t.mp_state 0..3 */ - int completion_ms; /* -1 if unknown */ - const char *reset_reason; /* "" if clean close */ -} mq_gw_req_metrics_t; - -/* Render a logfmt "mq.req ..." line into buf. cid_hex is the connection cid as - * a string — callers pass "-" today (5c log lines carry no cid, so there is - * nothing to correlate a hex cid against yet); the param is retained so Phase 6 - * can wire a real cid without a signature change. sid is the request's MPQUIC - * stream id (the live within-conn correlation key). authority/path/reset_reason - * are quoted and capped; embedded quotes are escaped. Returns bytes written - * (>0) or -1 on truncation. */ -int mq_gw_format_req_line(char *buf, size_t cap, const char *cid_hex, uint64_t sid, - const mq_gw_req_metrics_t *m); - -#endif /* MQ_GW_METRICS_H */ diff --git a/src/gateway/mq_gw_server.c b/src/gateway/mq_gw_server.c deleted file mode 100644 index 65e1f34b..00000000 --- a/src/gateway/mq_gw_server.c +++ /dev/null @@ -1,1857 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_gw_server.c — server-side H3→origin execution bridge. See mq_gw_server.h. - * - * ───────────────────────────────────────────────────────────────────────────── - * PER-REQUEST OWNERSHIP (mq_gw_req_t) — dual-flag, exactly-once discipline - * ───────────────────────────────────────────────────────────────────────────── - * Each accepted H3 request owns one mq_gw_req_t, referenced from two independent - * sides that can each die first or race (mirrors mq_gw_client's local/h3 split): - * - * H3 side (the mq_h3_req): set on on_new_req; cleared in h3_on_close (the - * wrapper is freed right after). After on_close we null r->req (h3_dead=1). - * - * ORIGIN side (the mq_origin_req): set on mq_origin_start; cleared either when - * on_done fires (the origin completed/errored — the request is freed right - * after on_done returns) or when WE call mq_origin_abort. Either way we null - * r->oreq and set origin_dead=1. - * - * The mq_gw_req_t is freed by gw_req_maybe_free() once BOTH flags are set. Each - * side flips its flag exactly once: - * - H3 closes first (client reset / conn died): h3_on_close → mq_origin_abort - * (origin side dies, NO on_done) → both dead → free. on_close is an mq_h3 - * callback, NOT a curl callback, so mq_origin_abort is safe there (per the - * re-entrancy contract in mq_origin_curl.h). - * - origin terminates first (on_done): we finish/reset the H3 request → its - * on_close fires later → both dead → free. on_done sets origin_dead + nulls - * r->oreq. We MUST NOT touch r->oreq after on_done. - * - both race: idempotent flags; maybe_free runs only when both set. - * - * BODY auth/validation failures (403/400) and origin-start failures synthesize - * an H3 response and finish/reset BEFORE the origin side ever exists. There we - * set origin_dead=1 up front so the single remaining flag is the H3 side. - * - * Re-entrancy note: mq_origin's on_status/on_header/on_body/on_done callbacks - * run from inside curl. We never call mq_origin_abort from on_done (forbidden), - * and the download/upload pumping calls only mq_h3_req_* (transport side) and - * mq_origin_resume_* (deferred, always safe) from those curl callbacks. - */ -#include "gateway/mq_gw_server.h" - -#include -#include -#include -#include -#include - -#include -#include - -#include "gateway/mq_gw_cache.h" -#include "gateway/mq_gw_headers.h" -#include "gateway/mq_gw_metrics.h" -#include "gateway/mq_origin_curl.h" -#include "util/mq_ct.h" -#include "util/mq_log.h" - -/* Bound on the H3→origin upload spill (junction #3): body bytes recv'd from the - * H3 request that curl's pull_body has not yet drained. */ -#define MQ_GWS_UPLOAD_SPILL_MAX (256 * 1024) - -/* recv_body scratch size for draining the H3 request body. */ -#define MQ_GWS_RECV_CHUNK 16384 - -/* Max forwarded origin request headers (the request's non-stripped headers). - * xqc delivers a bounded header set; 64 is comfortable for the MVP. */ -#define MQ_GWS_MAX_HDRS 64 - -/* Monotonic millisecond clock for the cache's freshness/LRU accounting (the - * cache itself is clock-free — callers inject now_ms). A file-static rather than - * a shared util header (only the cache store/lookup sites need it). */ -static uint64_t -gw_now_ms(void) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u; -} - -/* ── TLS outcome (origin_tls field) ─────────────────────────────────────────*/ - -typedef enum { - MQ_TLS_NA = 0, /* non-TLS origin (http://) or origin not reached */ - MQ_TLS_OK, /* https + curl OK + cert verified */ - MQ_TLS_VERIFY_FAIL, /* cert rejected by curl */ - MQ_TLS_CONNECT_FAIL, /* any other transport/handshake failure on https */ -} mq_tls_result_t; - -static const char *tls_token(mq_tls_result_t t) __attribute__((unused)); -static const char * -tls_token(mq_tls_result_t t) -{ - switch (t) { - case MQ_TLS_OK: return "ok"; - case MQ_TLS_VERIFY_FAIL: return "verify_fail"; - case MQ_TLS_CONNECT_FAIL: return "connect_fail"; - default: return "na"; - } -} - -/* ── per-request state ──────────────────────────────────────────────────────*/ - -typedef struct mq_gw_req_s { - mq_gw_server_t *srv; - - /* H3 (transport) side */ - mq_h3_req_t *req; /* NULL once on_close fired (h3_dead) */ - int h3_dead; - int authed; /* request presented a valid x-mq-auth Bearer token */ - - /* ORIGIN (curl) side */ - mq_origin_req_t *oreq; /* NULL once on_done fired OR we aborted (origin_dead) */ - int origin_dead; - - /* request parse / forwarding */ - int has_body; /* the H3 request arrived without fin (a body follows) */ - int h3_body_fin; /* the H3 request body reached fin (no more upload bytes) */ - - /* ── upload (junction #3): H3 body → curl pull_body ── */ - uint8_t *spill; /* heap spill buffer (lazily allocated) */ - size_t spill_len; /* valid bytes in spill */ - size_t spill_off; /* bytes already drained by pull_body */ - int pull_paused; /* pull_body returned 0 (PAUSE); resume when spill refills */ - - /* ── download (junction #4): origin response → H3 response ── */ - int resp_headers_sent; /* we sent the H3 response header section */ - int resp_status; /* origin http_status (from on_status) */ - long resp_http_ver; /* CURLINFO_HTTP_VERSION (set on first body/on_done) */ - - /* ── cacheability (Phase 6) ── */ - unsigned cache_ttl_s; /* request X-Mq-Cache opt-in TTL (0 = not opted in) */ - int has_authorization; /* request carried an Authorization header */ - int cc_uncacheable; /* response Cache-Control: no-store/private/no-cache */ - long cc_max_age; /* response max-age/s-maxage (smaller of the two); -1 absent */ - int has_set_cookie; /* response carried a Set-Cookie header */ - int has_vary; /* response carried a Vary header */ - - /* ── cache HIT replay (Phase 6) — copy-on-hit: the source entry can be evicted - * by a LATER request's insert across the send turns this request yields on, so - * we deep-copy the entry's body into req-owned storage (cache_body) and stage - * its header strings into the EXISTING inline r->hdrs[] arrays (they are bounded - * by the same 128/1024 arena caps the entry passed through on store, so the copy - * is lossless — no separate header backing buffer is needed). The body is - * replayed in 16 KiB slices by download_flush's from_cache loop. */ - int from_cache; /* this request is being served from the cache (no origin) */ - uint8_t *cache_body; /* owned copy of the cached body (NULL = none/empty) */ - size_t cache_body_len; /* total cached body length */ - size_t cache_off; /* replay cursor into cache_body */ - char cache_key[8 + 256 + 1024 + 8]; /* "GET " (built in gw_dispatch when caching - * may apply; reused for lookup + store) */ - int is_get; /* request method is GET (cache gate) */ - - /* ── cache STORE (Phase 6) — accumulate a cacheable miss's body, capped at the - * cache's max_obj_bytes; on a clean 200 done we insert. Overflow / non-cacheable - * frees store_buf and clears `storing` (keep streaming to the client). */ - uint8_t *store_buf; /* growable body accumulator (NULL until first cacheable body) */ - size_t store_len; /* bytes accumulated */ - size_t store_cap; /* current capacity of store_buf */ - int storing; /* 1 while accumulating a cacheable miss */ - - enum { MQ_CACHE_BYPASS = 0, MQ_CACHE_HIT, MQ_CACHE_MISS } cache_state; - - int origin_is_tls; /* origin scheme == https (set in gw_dispatch, Task 5) */ - mq_tls_result_t origin_tls; /* TLS verify outcome, set in origin_on_done */ - char content_encoding[24]; /* origin response Content-Encoding value; "" => none */ - int origin_reuse; /* 0 fresh / not-yet-done; 1 when the origin conn was reused */ - int origin_connect_ms; /* origin TCP+TLS setup ms; 0 on reuse; -1 unknown */ - - /* observability (mq.req); captured in gw_dispatch */ - char method[16]; - char authority[256]; - char path[512]; - - int recv_paused; /* on_body returned 0 (curl download paused); resume on drain */ - int origin_done_ok; /* on_done(OK) fired; send the response fin once pend drains */ - int finished; /* we sent the response fin (clean completion) */ - - /* Accumulated response headers (captured in on_header, flushed with status on - * the first on_body or on_done). NUL-terminated name/value pairs. */ - struct { - char name[128]; - char value[1024]; - } hdrs[MQ_GWS_MAX_HDRS]; - int n_hdrs; - int hdrs_overflow; /* dropped a header (response header count > MQ_GWS_MAX_HDRS). - * Fail closed: checked in download_send_headers alongside - * hdr_oversized — synthesize 502 + x-mq-error: upstream-protocol - * before any response header reaches the H3 client. Post-body - * trailers may set this after resp_headers_sent, but that path is - * already gated by `if (!r->resp_headers_sent)` (same as - * hdr_oversized), so trailers cannot trigger a spurious 502. */ - int hdr_oversized; /* an ORIGIN response header NAME/VALUE did not fit its - * arena slot (>127 name / >1023 value). Fail closed: - * checked in download_send_headers before any response - * header reaches the H3 client (a silently clipped - * origin header would corrupt the downstream response). */ - - /* Pending download body byte that curl handed us but H3 could not accept - * (send returned 0). We hold ONE chunk and resume the curl download via - * mq_origin_resume_body once H3's on_write drains it. */ - uint8_t pend[MQ_GWS_RECV_CHUNK]; - size_t pend_len; - size_t pend_off; - - struct mq_gw_req_s *next; /* intrusive list of live requests */ -} mq_gw_req_t; - -/* ── server state ───────────────────────────────────────────────────────────*/ - -struct mq_gw_server_s { - mq_transport_t *t; /* borrowed */ - mq_runtime_t *rt; /* borrowed */ - mq_h3_t *h3; /* owned-by-creation but freed by the caller (see header) */ - mq_origin_t *origin; - - char token[256]; - size_t token_len; - - unsigned requests; /* accepted H3 requests (observability) */ - - /* Most-recently-accepted H3 tunnel conn (Phase 5c observability). Set - * unconditionally in gw_on_new_conn, cleared (if still itself) in - * gw_srv_conn_state on close, and detached in mq_gw_server_free. Mirrors the - * TCP server's last_conn (mq_server.c). Per-instance: one client conn. */ - mq_h3_conn_t *last_conn; - - mq_gw_req_t *reqs; /* intrusive list of live per-request states */ - - int request_metrics; /* emit mq.req per-request line (opt-in; default 0) */ - int masquerade; /* return bare 404 to unauthenticated requests (opt-in; default 0) */ - - /* In-memory origin response cache (opt-in via --cache-max-bytes; NULL when - * disabled = default). Owned: freed in mq_gw_server_free. */ - mq_gw_cache_t *cache; -}; - -/* ── forward decls ──────────────────────────────────────────────────────────*/ -static void gw_req_unlink(mq_gw_server_t *s, mq_gw_req_t *r); -static void gw_req_maybe_free(mq_gw_req_t *r); -static void h3_on_read(mq_h3_req_t *hr, int flag, void *user); -static void h3_on_write(mq_h3_req_t *hr, void *user); -static void h3_on_close(mq_h3_req_t *hr, void *user); -static void download_flush(mq_gw_req_t *r); -static unsigned gw_response_cacheable(const mq_gw_req_t *r); - -/* ── small helpers ──────────────────────────────────────────────────────────*/ - -/* Case-insensitive equality for a slice vs a NUL-terminated lowercase literal. */ -static int -slice_ieq(const char *s, size_t sl, const char *lit) -{ - size_t ll = strlen(lit); - if (sl != ll) return 0; - for (size_t i = 0; i < sl; i++) { - char a = s[i]; - if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a'); - if (a != lit[i]) return 0; - } - return 1; -} - -/* Sanitize an attacker-controlled string for log emission: cap at `maxlen` - * chars and replace non-printable bytes (<0x20 or >=0x7f) with '?'. Writes a - * NUL-terminated result into `out` (which must have capacity maxlen+1). */ -static void -sanitize_for_log(char *out, size_t maxlen, const char *s, size_t slen) -{ - size_t n = slen < maxlen ? slen : maxlen; - for (size_t i = 0; i < n; i++) { - unsigned char c = (unsigned char)s[i]; - out[i] = (c >= 0x20 && c < 0x7f) ? (char)c : '?'; - } - out[n] = '\0'; -} - -/* http_ver → wire protocol token for x-mq-origin-protocol. */ -static const char * -http_ver_token(long ver) -{ - switch (ver) { - case CURL_HTTP_VERSION_1_1: return "http/1.1"; - case CURL_HTTP_VERSION_2: return "h2"; - case CURL_HTTP_VERSION_3: return "h3"; - case CURL_HTTP_VERSION_1_0: return "http/1.0"; - default: return "http/1.1"; - } -} - -/* ── per-request lifecycle ──────────────────────────────────────────────────*/ - -static void -gw_req_unlink(mq_gw_server_t *s, mq_gw_req_t *r) -{ - mq_gw_req_t **pp = &s->reqs; - while (*pp) { - if (*pp == r) { - *pp = r->next; - return; - } - pp = &(*pp)->next; - } -} - -static void -gw_req_maybe_free(mq_gw_req_t *r) -{ - if (!r->origin_dead || !r->h3_dead) return; - gw_req_unlink(r->srv, r); - free(r->spill); - free(r->cache_body); /* copy-on-hit body (NULL when not a HIT) */ - free(r->store_buf); /* STORE accumulator (NULL unless a cacheable miss aborted) */ - free(r); -} - -/* Synthesize and send an H3 error response (status + x-mq-error + content-length: - * 0), then finish the request. Used for auth/validation failures and origin - * errors that occur BEFORE any response header has been sent. This does NOT - * touch the origin side: most callers (auth/validation rejects) run before the - * origin ever exists and leave origin_dead==0 — h3_on_close flips origin_dead - * unconditionally once oreq==NULL (no live origin without an oreq), so the H3 - * side finishing naturally → on_close → maybe_free reclaims the state. (The - * origin-error caller, origin_on_done, has already set origin_dead itself.) */ -static void -gw_send_error(mq_gw_req_t *r, const char *status, const char *xmq_error) -{ - if (r->h3_dead || !r->req) return; - /* masquerade: an UNAUTHENTICATED peer gets a generic 404 with NO x-mq-* - * headers (indistinguishable from a vanilla H3 server returning 404 for an - * unknown path). authed==1 (post-auth rejects: bad-target, 502, ...) falls - * through to the diagnostic path below. Mirrors the send/reset/finish - * bookkeeping of the normal path; msh==0 (EAGAIN) is treated as sent. */ - if (r->srv->masquerade && !r->authed) { - mq_h3_header_t m404[] = {{":status", "404"}, {"content-length", "0"}}; - r->resp_status = 404; - long msh = mq_h3_req_send_headers(r->req, m404, 2, /*fin=*/1); - if (msh < 0) { - mq_h3_req_reset(r->req); - return; - } - r->resp_headers_sent = 1; - r->finished = 1; - return; - } - /* :status is always 3 digits in practice; 32 keeps gcc's -Wformat-truncation - * (which sizes this by the longest caller literal at -O2) satisfied. */ - char st[32]; - snprintf(st, sizeof(st), "%s", status); - mq_h3_header_t hs[] = { - {":status", st}, - {"x-mq-error", xmq_error}, - {"content-length", "0"}, - }; - /* Record the synthetic status so error rows report a non-zero status in mq.req - * (B2: gw_send_error was the only send path that never stored resp_status). */ - r->resp_status = (int)strtol( - status, NULL, - 10); /* intentional: mq.req `status` = status sent to client; synthetic error - replaces any earlier origin status (e.g. a 502 over an oversized-header - 200) — that is correct, not a clobber bug */ - long sh = mq_h3_req_send_headers(r->req, hs, 3, /*fin=*/1); - if (sh < 0) { - /* Hard send failure: reset; on_close finishes teardown. */ - mq_h3_req_reset(r->req); - return; - } - /* sh==0 (header EAGAIN) is effectively unreachable for a small header block on - * a fresh stream; if it ever happens, the request will be reset by teardown. - * Mark resp_headers_sent so no later path re-sends. */ - r->resp_headers_sent = 1; - r->finished = 1; -} - -/* ── upload (junction #3): H3 request body → curl pull_body ──────────────────*/ - -/* Drain H3 request body into the spill buffer (bounded). Sets h3_body_fin when - * the request finishes. Pauses recv implicitly by NOT reading further once the - * spill is full (xquic re-notifies on the next READ_BODY once we drain). */ -static void -upload_recv(mq_gw_req_t *r) -{ - if (r->h3_dead || !r->req) return; - - for (;;) { - /* Compact drained bytes to make room. */ - if (r->spill && r->spill_off > 0) { - memmove(r->spill, r->spill + r->spill_off, r->spill_len - r->spill_off); - r->spill_len -= r->spill_off; - r->spill_off = 0; - } - if (r->spill_len >= MQ_GWS_UPLOAD_SPILL_MAX) { - /* Buffer full: stop reading. The next pull_body drain re-arms us - * (xquic re-delivers READ_BODY as the stream window reopens). */ - break; - } - if (!r->spill) { - r->spill = malloc(MQ_GWS_UPLOAD_SPILL_MAX); - if (!r->spill) { - /* OOM: reset the request (truncates the origin upload too). */ - mq_h3_req_reset(r->req); - return; - } - r->spill_len = 0; - r->spill_off = 0; - } - size_t room = MQ_GWS_UPLOAD_SPILL_MAX - r->spill_len; - int fin = 0; - long n = mq_h3_req_recv_body(r->req, r->spill + r->spill_len, room, &fin); - if (n < 0) { - mq_h3_req_reset(r->req); - return; - } - if (n > 0) r->spill_len += (size_t)n; - if (fin) { - r->h3_body_fin = 1; - break; - } - if (n == 0) break; /* no more body available right now */ - } - - /* Newly buffered bytes (or fin) → resume a paused curl upload. */ - if (r->pull_paused && !r->origin_dead && r->oreq && - (r->spill_off < r->spill_len || r->h3_body_fin)) { - r->pull_paused = 0; - mq_origin_resume_pull(r->oreq); - } -} - -/* curl pull_body: drain the spill into curl's buffer. 0 = PAUSE (spill empty, - * H3 fin not yet seen), -1 = EOF (spill empty AND H3 fin seen). Runs inside a - * curl callback — only mq_h3_req_recv_body (transport recv) is touched here. */ -static long -origin_pull_body(uint8_t *buf, size_t cap, void *u) -{ - mq_gw_req_t *r = (mq_gw_req_t *)u; - - /* Top up the spill from the H3 side first (recv is safe from a curl cb). */ - upload_recv(r); - - size_t avail = - (r->spill && r->spill_len > r->spill_off) ? r->spill_len - r->spill_off : 0; - if (avail == 0) { - if (r->h3_body_fin) return -1; /* EOF */ - r->pull_paused = 1; - return 0; /* PAUSE: resumed from upload_recv when more arrives */ - } - size_t take = avail < cap ? avail : cap; - memcpy(buf, r->spill + r->spill_off, take); - r->spill_off += take; - return (long)take; -} - -/* ── download (junction #4): origin response → H3 response ───────────────────*/ - -/* Build + send the H3 response header section from the accumulated origin - * headers (status + headers minus hop-by-hop + x-mq-origin-protocol). Sent once, - * lazily, on the first on_body or on on_done. Returns 0 on success, -1 on a hard - * H3 send failure (caller resets). */ -static int -download_send_headers(mq_gw_req_t *r) -{ - if (r->resp_headers_sent) return 0; - if (r->h3_dead || !r->req) return -1; - - /* Fail closed on an oversized ORIGIN response header (recorded in on_header) - * or a response header-count overflow (> MQ_GWS_MAX_HDRS, also recorded in - * on_header). In both cases no header has reached the H3 client yet, so we - * synthesize a clean 502 + x-mq-error: upstream-protocol instead of - * forwarding a corrupted/truncated header set. gw_send_error sends the fin - * and sets finished; the caller must NOT also reset (it guards the reset on - * !r->finished). */ - if (r->hdr_oversized || r->hdrs_overflow) { - gw_send_error(r, "502", "upstream-protocol"); - return -1; - } - - mq_h3_header_t hs[MQ_GWS_MAX_HDRS + 2]; - size_t nh = 0; - - char st[16]; - int code = (r->resp_status >= 100 && r->resp_status <= 599) ? r->resp_status : 502; - snprintf(st, sizeof(st), "%d", code); - hs[nh].name = ":status"; - hs[nh].value = st; - nh++; - - /* x-mq-origin-protocol is a diagnostic of the NEGOTIATED origin protocol. A - * cache HIT contacted NO origin, so synthesizing one here would emit a FALSE - * `http/1.1` (http_ver_token's NONE default) — the response-version bug fixed - * in commit 5136c46. Omit it on the HIT path (design §7). */ - if (!r->from_cache) { - hs[nh].name = "x-mq-origin-protocol"; - hs[nh].value = http_ver_token(r->resp_http_ver); - nh++; - } - - for (int i = 0; i < r->n_hdrs && nh < (size_t)(MQ_GWS_MAX_HDRS + 2); i++) { - hs[nh].name = r->hdrs[i].name; - hs[nh].value = r->hdrs[i].value; - nh++; - } - - long sh = mq_h3_req_send_headers(r->req, hs, nh, /*fin=*/0); - if (sh < 0) return -1; - /* sh==0 (header-block EAGAIN) is effectively unreachable for a response header - * block on an established stream; treat non-negative as sent. */ - r->resp_headers_sent = 1; - return 0; -} - -/* Try to flush the pending download chunk into H3. Sets send_blocked if H3 could - * not take it all (curl stays paused); clears + resumes curl when fully drained. - * Sends the response fin when the origin is done AND nothing is pending. */ -static void -download_flush(mq_gw_req_t *r) -{ - if (r->h3_dead || !r->req) return; - if (!r->resp_headers_sent) return; /* headers must precede body */ - - if (r->from_cache) { - /* drive the whole in-memory body, yielding only on real H3 backpressure */ - while (r->cache_off < r->cache_body_len || r->pend_off < r->pend_len) { - if (r->pend_off >= r->pend_len && - r->cache_off < r->cache_body_len) { /* refill from cache */ - size_t rem = r->cache_body_len - r->cache_off; - size_t n = - rem < MQ_GWS_RECV_CHUNK - ? rem - : MQ_GWS_RECV_CHUNK; /* inline clamp (no MIN macro in tree) */ - memcpy(r->pend, r->cache_body + r->cache_off, n); - r->cache_off += n; - r->pend_len = n; - r->pend_off = 0; - } - long acc = mq_h3_req_send_body(r->req, r->pend + r->pend_off, - r->pend_len - r->pend_off, /*fin=*/0); - if (acc < 0) { - if (!r->finished) mq_h3_req_reset(r->req); - return; - } /* hard error → reset, as curl path */ - r->pend_off += (size_t)acc; - if (r->pend_off < r->pend_len) - return; /* H3 blocked → h3_on_write re-drives later */ - } - if (mq_h3_req_send_body(r->req, NULL, 0, /*fin=*/1) < 0) { - if (!r->finished) mq_h3_req_reset(r->req); - return; - } - r->finished = 1; /* mirror the curl path's post-finish flag */ - return; /* never fall through to the curl tail */ - } - - while (r->pend_off < r->pend_len) { - long acc = mq_h3_req_send_body(r->req, r->pend + r->pend_off, - r->pend_len - r->pend_off, /*fin=*/0); - if (acc < 0) { - mq_h3_req_reset(r->req); - return; - } - if (acc == 0) return; /* still blocked; wait for the next on_write */ - r->pend_off += (size_t)acc; - } - /* Pending fully drained. */ - r->pend_len = 0; - r->pend_off = 0; - if (r->recv_paused) { - r->recv_paused = 0; - /* We had returned 0 to curl (a chunk raced an undrained pend). Resume the - * paused download (deferred; safe from anywhere). */ - if (!r->origin_dead && r->oreq) mq_origin_resume_body(r->oreq); - } - /* The origin completed (on_done OK) but its tail chunk was still buffered when - * on_done ran: now that pend is empty, send the deferred response fin. */ - if (r->origin_done_ok && !r->finished) { - long fr = mq_h3_req_finish(r->req); - if (fr < 0) - mq_h3_req_reset(r->req); - else - r->finished = 1; - } -} - -/* Case-insensitive search for `needle` (lowercase literal) in the value slice - * [v, v+vl). Returns a pointer to the match (within v) or NULL. Local + simple - * (the value is short; this runs once per cache-control header). */ -static const char * -ci_memmem(const char *v, size_t vl, const char *needle) -{ - size_t nl = strlen(needle); - if (nl == 0 || nl > vl) return NULL; - for (size_t i = 0; i + nl <= vl; i++) { - size_t j = 0; - for (; j < nl; j++) { - char a = v[i + j]; - if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a'); - if (a != needle[j]) break; - } - if (j == nl) return v + i; - } - return NULL; -} - -/* Token-boundary test for a valueless Cache-Control directive (no-store / - * private / no-cache). `ci_memmem` matches substrings, so a header value like - * `x-private-data` or `no-cacheable` would falsely set cc_uncacheable. Require a - * delimiter (start-of-value / ',' / SP / HTAB) immediately BEFORE the match and a - * terminator (end / ',' / SP / HTAB / ';') immediately AFTER — these directives - * carry no `=value`, so an `=` after the token is NOT a valid boundary. Scans all - * occurrences (a later token may be a clean match even if an earlier one is a - * substring). */ -static int -cc_has_token(const char *v, size_t vl, const char *tok) -{ - size_t tl = strlen(tok); - const char *end = v + vl; - for (const char *p = v; (p = ci_memmem(p, (size_t)(end - p), tok)) != NULL; p += tl) { - char before = (p > v) ? p[-1] : ','; /* synthesize a delimiter at start */ - const char *a = p + tl; - char after = (a < end) ? *a : ','; /* synthesize a delimiter at end */ - int bok = (before == ',' || before == ' ' || before == '\t'); - int aok = (after == ',' || after == ' ' || after == '\t' || after == ';'); - if (bok && aok) return 1; - } - return 0; -} - -/* Parse the delta-seconds at `p` (just past a `…age=`), within the slice ending - * at `end`. Returns the non-negative value, or -1 if no leading digit. Stops at - * the first non-digit (',' / ' ' / end). Caps at INT32_MAX (still "cacheable"). */ -static long -cc_parse_delta(const char *p, const char *end) -{ - if (p >= end || *p < '0' || *p > '9') return -1; - long n = 0; - for (; p < end && *p >= '0' && *p <= '9'; p++) { - n = n * 10 + (*p - '0'); - if (n > 2147483647L) return 2147483647L; /* clamp; still "cacheable, large" */ - } - return n; -} - -/* Fold one parsed delta into cc_max_age, keeping the SMALLER of any already-set - * value and this one (the more conservative freshness bound). */ -static void -cc_take_min_age(mq_gw_req_t *r, long d) -{ - if (d >= 0 && (r->cc_max_age < 0 || d < r->cc_max_age)) r->cc_max_age = d; -} - -/* Scan a response Cache-Control value for the cacheability-relevant directives - * and fold them into r: no-store/private/no-cache ⇒ cc_uncacheable; max-age= / - * s-maxage= ⇒ cc_max_age (the smaller of the two when both are present). The - * `s-maxage=` token contains `max-age=` at offset 2, so each `max-age=` match is - * accepted only when it is NOT the tail of an `s-maxage=` (the prior char is not - * the 's' of "s-max"); both deltas still fold into the min. */ -static void -cc_scan(mq_gw_req_t *r, const char *v, size_t vl) -{ - if (cc_has_token(v, vl, "no-store") || cc_has_token(v, vl, "private") || - cc_has_token(v, vl, "no-cache")) - r->cc_uncacheable = 1; - const char *end = v + vl; - /* All `s-maxage=` occurrences. */ - for (const char *p = v; (p = ci_memmem(p, (size_t)(end - p), "s-maxage=")) != NULL;) { - cc_take_min_age(r, cc_parse_delta(p + 9, end)); - p += 9; - } - /* All `max-age=` occurrences that are NOT the tail of an `s-maxage=` (i.e. not - * immediately preceded by an 's'/'S'). */ - for (const char *p = v; (p = ci_memmem(p, (size_t)(end - p), "max-age=")) != NULL;) { - char prev = (p > v) ? p[-1] : '\0'; - if (prev != 's' && prev != 'S') cc_take_min_age(r, cc_parse_delta(p + 8, end)); - p += 8; - } -} - -/* curl on_status: stash the final status code. */ -static void -origin_on_status(int http_status, void *u) -{ - mq_gw_req_t *r = (mq_gw_req_t *)u; - r->resp_status = http_status; -} - -/* curl on_header: accumulate non-hop-by-hop response headers (already trimmed by - * mq_origin). Names are lowercased (H3 requires lowercase field names). */ -static void -origin_on_header(const char *n, size_t nl, const char *v, size_t vl, void *u) -{ - mq_gw_req_t *r = (mq_gw_req_t *)u; - /* Drop-class checks must run BEFORE the capacity check: a dropped header - * must not count toward the capacity limit. */ - if (mq_gw_strip_hop(n, nl)) return; - /* Drop any x-mq-origin-protocol the origin itself sent — we synthesize ours - * from the negotiated http_ver, and a duplicate pseudo-ish diagnostic would - * be confusing. */ - if (slice_ieq(n, nl, "x-mq-origin-protocol")) return; - if (slice_ieq(n, nl, "content-encoding")) { - size_t cap = sizeof(r->content_encoding) - 1; - size_t cn = vl < cap ? vl : cap; - memcpy(r->content_encoding, v, cn); - r->content_encoding[cn] = '\0'; - } - /* Cacheability capture (Phase 6; set but unread until the STORE/HIT task). - * Captured BEFORE the arena-capacity check so a full header set still gates - * cacheability correctly. These headers are ALSO forwarded normally below. */ - if (slice_ieq(n, nl, "set-cookie")) - r->has_set_cookie = 1; - else if (slice_ieq(n, nl, "vary")) - r->has_vary = 1; - else if (slice_ieq(n, nl, "cache-control")) - cc_scan(r, v, vl); - if (r->n_hdrs >= MQ_GWS_MAX_HDRS) { - r->hdrs_overflow = 1; - return; - } - - /* An origin response header NAME or VALUE that would not fit its arena slot - * must FAIL the response (download_send_headers turns this into a 502 - * upstream-protocol before any header reaches the H3 client), NOT be silently - * clamped: a clipped value corrupts the downstream response, and origin - * headers this large are pathological — failing closed beats corrupting. - * Mirrors the client-side dl_each_header fail-closed posture. on_header is a - * void curl callback, so we cannot signal here; record the condition and act - * at the next safe point (download_send_headers). */ - if (nl >= sizeof(r->hdrs[0].name) || vl >= sizeof(r->hdrs[0].value)) { - r->hdr_oversized = 1; - return; - } - int i = r->n_hdrs++; - size_t cnl = nl; - for (size_t j = 0; j < cnl; j++) { - char ch = n[j]; - if (ch >= 'A' && ch <= 'Z') ch = (char)(ch - 'A' + 'a'); - r->hdrs[i].name[j] = ch; - } - r->hdrs[i].name[cnl] = '\0'; - memcpy(r->hdrs[i].value, v, vl); - r->hdrs[i].value[vl] = '\0'; -} - -/* STORE accumulation: append `len` body bytes to r->store_buf while this miss is - * being cached. Lazily starts storing on the first chunk if the response is - * cacheable. Capped at the cache's per-object limit (max_obj_bytes) — on overflow - * (or OOM) we free the buffer and stop storing, but KEEP streaming to the client - * (a store failure never breaks the request). Called once per ACCEPTED chunk. */ -static void -store_accumulate(mq_gw_req_t *r, const uint8_t *p, size_t len) -{ - mq_gw_server_t *s = r->srv; - if (r->from_cache || !s->cache) return; /* HIT replay or cache disabled */ - - /* Decide once, on the first accepted chunk, whether this response is cacheable. - * All inputs are known by now (status from on_status, CC/cookie/vary from - * on_header, both before the first on_body). */ - if (!r->storing && r->store_buf == NULL && r->store_len == 0) { - if (gw_response_cacheable(r) == 0) return; /* not cacheable → never store */ - r->storing = 1; - } - if (!r->storing) return; /* a prior overflow disabled storing */ - if (len == 0) return; - - size_t cap_max = mq_gw_cache_max_obj_bytes(s->cache); - if (r->store_len + len > cap_max) { - /* Object exceeds the per-object cap (the insert would refuse it anyway) → - * stop storing now and reclaim the buffer; keep streaming to the client. */ - free(r->store_buf); - r->store_buf = NULL; - r->store_len = 0; - r->store_cap = 0; - r->storing = 0; - return; - } - if (r->store_len + len > r->store_cap) { - size_t ncap = r->store_cap ? r->store_cap * 2 : MQ_GWS_RECV_CHUNK; - while (ncap < r->store_len + len) - ncap *= 2; - if (ncap > cap_max) ncap = cap_max; /* never grow past the per-object cap */ - uint8_t *nb = realloc(r->store_buf, ncap); - if (!nb) { /* OOM → stop storing, keep streaming */ - free(r->store_buf); - r->store_buf = NULL; - r->store_len = 0; - r->store_cap = 0; - r->storing = 0; - return; - } - r->store_buf = nb; - r->store_cap = ncap; - } - memcpy(r->store_buf + r->store_len, p, len); - r->store_len += len; -} - -/* curl on_body: send response headers (if not yet) then the body chunk over H3. - * Returns the bytes accepted: len (accept all, copying any unsent remainder into - * the pending buffer + pausing) or 0 (pause) on a hard H3 block. Runs inside a - * curl callback — only mq_h3_req_* (transport send) is touched. */ -static long -origin_on_body(const uint8_t *p, size_t len, void *u) -{ - mq_gw_req_t *r = (mq_gw_req_t *)u; - if (r->h3_dead || !r->req) return 0; /* H3 gone: pause (teardown handles it) */ - - if (!r->resp_headers_sent) { - /* Capture the negotiated origin HTTP version NOW: CURLINFO_HTTP_VERSION is - * valid once headers are parsed, and the synthesized x-mq-origin-protocol - * response header (download_send_headers) must report the REAL version. - * on_done re-reads it later for the mq.req metrics line; both agree. Without - * this, resp_http_ver stays NONE here → http_ver_token defaults to "http/1.1", - * mislabeling every h2/h3 origin response (on_done fires too late — the - * response headers are already on the wire by the first body chunk). */ - if (r->oreq) r->resp_http_ver = mq_origin_http_ver(r->oreq); - if (download_send_headers(r) != 0) { - /* download_send_headers may have already synthesized a clean error - * response (oversized origin header → 502 + fin); only reset if it - * did NOT finish cleanly. */ - if (!r->finished) mq_h3_req_reset(r->req); - return 0; - } - } - - /* BACKPRESSURE MODEL (one chunk in flight at a time): - * - If `pend` already holds an undrained chunk, we cannot take more → - * return 0 to PAUSE curl. We are now recv-paused; download_flush() resumes - * curl (mq_origin_resume_body) once `pend` drains. curl re-delivers the - * SAME chunk it offered now after resume, so returning 0 loses nothing. - * - Otherwise try to send directly. xqc may partially accept; we copy the - * UNSENT remainder into `pend` and `return len` (accept the whole chunk - * from curl's view). The remainder is flushed by download_flush() from the - * H3 on_write notify. We do NOT pause curl in this case — curl proceeds to - * the next chunk, which the pend-full guard above pauses if it races the - * drain. - * curl's WRITEFUNCTION buffer is CURL_MAX_WRITE_SIZE (16 KiB) == the pend - * buffer size, so a full chunk's remainder always fits. */ - if (r->pend_off < r->pend_len) { - r->recv_paused = 1; - return 0; /* PAUSE: resumed from download_flush once pend drains */ - } - - /* Past the pend-full pause we are COMMITTED to accepting this whole chunk (both - * remaining returns yield `len`), so accumulate it for the cache exactly once — - * a paused-then-redelivered chunk never reaches here twice. */ - store_accumulate(r, p, len); - - long acc = mq_h3_req_send_body(r->req, p, len, /*fin=*/0); - if (acc < 0) { - mq_h3_req_reset(r->req); - return 0; - } - size_t off = (size_t)(acc > 0 ? acc : 0); - if (off >= len) return (long)len; /* fully accepted */ - - size_t rem = len - off; - if (rem > sizeof(r->pend)) { - /* Defensive: a chunk larger than the pend buffer cannot be stashed and we - * have already sent `off` bytes, so re-delivery would duplicate. Treat as a - * hard error (unreachable with the 16 KiB == 16 KiB sizing above). */ - MQ_LOGW("mq_gw_server: download chunk %zu exceeds pend buffer; resetting", len); - mq_h3_req_reset(r->req); - return 0; - } - memcpy(r->pend, p + off, rem); - r->pend_len = rem; - r->pend_off = 0; - return (long)len; /* accept all; the remainder is buffered for on_write */ -} - -/* STORE finalize: on a clean cacheable miss, deep-copy the accumulated body + - * response headers into the cache. Builds a temporary mq_gw_cache_hdr_t[] view of - * the inline r->hdrs[] (the cache deep-copies, so the temp array + store_buf are - * freed right after). A cache insert failure NEVER affects the already-delivered - * response. Frees store_buf unconditionally (it is consumed here). */ -static void -store_finalize(mq_gw_req_t *r) -{ - mq_gw_server_t *s = r->srv; - if (!r->storing || !s->cache) { - /* Not storing (empty body / overflow-disabled / non-cacheable / disabled): - * nothing to insert. store_buf is NULL in all these cases, but free defensively. - */ - free(r->store_buf); - r->store_buf = NULL; - r->store_len = 0; - r->store_cap = 0; - r->storing = 0; - return; - } - - unsigned ttl_s = gw_response_cacheable(r); /* re-check + compute the effective ttl */ - if (ttl_s > 0 && r->cache_key[0] != '\0') { - mq_gw_cache_hdr_t tmp[MQ_GWS_MAX_HDRS]; - size_t nh = 0; - for (int i = 0; i < r->n_hdrs && nh < MQ_GWS_MAX_HDRS; i++) { - tmp[nh].name = r->hdrs[i].name; - tmp[nh].value = r->hdrs[i].value; - nh++; - } - /* Insert is best-effort: a -1 (per-object cap / OOM) leaves the cache - * unchanged and the response already delivered — never a request failure. */ - (void)mq_gw_cache_insert(s->cache, r->cache_key, 200, tmp, nh, r->store_buf, - r->store_len, (uint64_t)ttl_s * 1000u, gw_now_ms()); - } - free(r->store_buf); - r->store_buf = NULL; - r->store_len = 0; - r->store_cap = 0; - r->storing = 0; -} - -/* curl on_done: terminal. result==CURLE_OK → finish; error → 502/mapped status - * (if headers not yet sent) or RESET (if already sent). The origin side is freed - * right after this returns — null r->oreq + set origin_dead FIRST. */ -static void -origin_on_done(int curl_result, long http_ver, long ssl_verify, int origin_reuse, - int origin_connect_ms, void *u) -{ - mq_gw_req_t *r = (mq_gw_req_t *)u; - r->oreq = NULL; /* freed right after on_done returns */ - r->origin_dead = 1; - r->resp_http_ver = http_ver; - r->origin_reuse = origin_reuse; - r->origin_connect_ms = origin_connect_ms; - /* TLS outcome — only meaningful for an https origin. A plain http:// origin - * (scheme_ok accepts both, mq_gw_server.c:751) has NO TLS result → MQ_TLS_NA, - * even on success. ok = https + curl OK + verify passed; verify_fail = cert - * rejected; connect_fail = any other transport failure on an https origin. - * origin_is_tls is set in gw_dispatch (Task 5); defaults 0 until then. */ - if (!r->origin_is_tls) - r->origin_tls = MQ_TLS_NA; - else if (curl_result == 0 && ssl_verify == 0) - r->origin_tls = MQ_TLS_OK; - else if (ssl_verify != 0 || curl_result == CURLE_PEER_FAILED_VERIFICATION) - r->origin_tls = MQ_TLS_VERIFY_FAIL; - else - r->origin_tls = MQ_TLS_CONNECT_FAIL; - - if (r->h3_dead || !r->req) { - gw_req_maybe_free(r); - return; - } - - if (curl_result == 0) { - /* Success. Send headers if we never got a body (no-body response). */ - if (!r->resp_headers_sent) { - if (download_send_headers(r) != 0) { - /* May have synthesized a clean error (oversized origin header → - * 502 + fin); only reset if it did NOT finish cleanly. */ - if (!r->finished) mq_h3_req_reset(r->req); - gw_req_maybe_free(r); - return; - } - } - /* Mark the origin done so the fin is sent once any buffered tail drains. */ - r->origin_done_ok = 1; - /* STORE the cacheable miss (best-effort; all body chunks have been - * accumulated by now — on_done fires after the last on_body). Consumes + - * frees store_buf. Done before the flush; insert never touches the H3 side. */ - store_finalize(r); - /* Flush any pending body; download_flush sends the fin when pend empties - * (immediately here if nothing is buffered). */ - download_flush(r); - gw_req_maybe_free(r); - return; - } - - /* Error. */ - if (r->resp_headers_sent) { - /* Headers already on the wire → truncation must be visible. RESET, never - * a fake clean fin (absolute rule). */ - mq_h3_req_reset(r->req); - } else { - int code = mq_gw_status_from_curl(curl_result); - char status[16]; - snprintf(status, sizeof(status), "%d", code); - char xmq[32]; - snprintf(xmq, sizeof(xmq), "curl:%d", curl_result); - gw_send_error(r, status, xmq); - } - gw_req_maybe_free(r); -} - -/* ── request parse + origin start ───────────────────────────────────────────*/ - -/* Header-capture context: pseudo-headers + x-mq-auth/class + forwarded headers. */ -typedef struct { - mq_gw_req_t *r; - - /* pseudo-headers (NUL-terminated). */ - char method[16]; - char scheme[8]; - char authority[256]; - char path[1024]; - int has_method, has_scheme, has_authority, has_path; - /* A pseudo-header value that did NOT fit its buffer was silently truncated by - * copy_z. Silent truncation of :authority / :path changes the outbound target - * (a redirection / SSRF surface); a clipped :method becomes a different verb. - * Capture the overlong condition so gw_dispatch rejects rather than acts on a - * truncated value. */ - int method_overlong, scheme_overlong, authority_overlong, path_overlong; - - /* auth + class */ - char auth[512]; - int has_auth; - char cls[128]; - int has_class; - - mq_http_ver_t origin_http_version; /* from x-mq-origin-protocol; DEFAULT if absent */ - - /* cache control (Phase 6). cache_ttl_s = parsed X-Mq-Cache opt-in TTL (0 = - * absent/not opted in); has_authorization = request carried an Authorization - * header (a cacheability gate — the response is per-credential). */ - unsigned cache_ttl_s; - int has_authorization; - - /* forwarded origin headers (minus x-mq-* / hop-by-hop / pseudo). */ - mq_h3_header_t fwd[MQ_GWS_MAX_HDRS]; - char fwd_name[MQ_GWS_MAX_HDRS][128]; - char fwd_val[MQ_GWS_MAX_HDRS][1024]; - size_t n_fwd; - - /* content-length from the request headers (for INFILESIZE), -1 if absent. */ - int64_t content_length; - int cl_seen; /* a content-length header was parsed (duplicate detection) */ - int bad_cl; /* content-length was empty / non-digit / overflowed / duplicated - * → reject the whole request with 400 (mirrors mq_http1's strict - * parse; a silently-unset CL would mis-frame the upload). */ - - int bad; /* a header could not be stored (overflow) — diagnostic */ - int bad_header; /* a forwarded header name/value carried control bytes → - * reject the whole request with 400 (smuggling posture). */ -} req_hdr_ctx_t; - -static void -copy_z(char *dst, size_t dstcap, const char *s, size_t sl) -{ - size_t n = sl < dstcap - 1 ? sl : dstcap - 1; - memcpy(dst, s, n); - dst[n] = '\0'; -} - -static void -req_each_header(const char *n, size_t nl, const char *v, size_t vl, void *u) -{ - req_hdr_ctx_t *ctx = (req_hdr_ctx_t *)u; - - /* Pseudo-headers. */ - if (nl > 0 && n[0] == ':') { - if (slice_ieq(n, nl, ":method")) { - if (vl >= sizeof(ctx->method)) ctx->method_overlong = 1; - copy_z(ctx->method, sizeof(ctx->method), v, vl); - ctx->has_method = 1; - } else if (slice_ieq(n, nl, ":scheme")) { - if (vl >= sizeof(ctx->scheme)) ctx->scheme_overlong = 1; - copy_z(ctx->scheme, sizeof(ctx->scheme), v, vl); - ctx->has_scheme = 1; - } else if (slice_ieq(n, nl, ":authority")) { - if (vl >= sizeof(ctx->authority)) ctx->authority_overlong = 1; - copy_z(ctx->authority, sizeof(ctx->authority), v, vl); - ctx->has_authority = 1; - } else if (slice_ieq(n, nl, ":path")) { - if (vl >= sizeof(ctx->path)) ctx->path_overlong = 1; - copy_z(ctx->path, sizeof(ctx->path), v, vl); - ctx->has_path = 1; - } - /* other pseudo-headers: ignore */ - return; - } - - if (slice_ieq(n, nl, "x-mq-auth")) { - copy_z(ctx->auth, sizeof(ctx->auth), v, vl); - ctx->has_auth = 1; - return; - } - if (slice_ieq(n, nl, "x-mq-class")) { - copy_z(ctx->cls, sizeof(ctx->cls), v, vl); - ctx->has_class = 1; - return; - } - if (slice_ieq(n, nl, "x-mq-origin-protocol")) { - /* Reuse the shared parser. Client already validated; DEFAULT here = defensive - * no-op. */ - ctx->origin_http_version = mq_gw_parse_http_ver(v, vl); - return; /* control header — do NOT forward to origin */ - } - if (slice_ieq(n, nl, "x-mq-cache")) { - /* Cache opt-in TTL (the client validated + re-emitted this). 0 = invalid/ - * absent → not opted in. Control header — auto-stripped, not forwarded. */ - ctx->cache_ttl_s = mq_gw_parse_cache_ttl(v, vl); - return; - } - if (slice_ieq(n, nl, "authorization")) { - /* NOTE the credential for the cacheability gate (a per-credential - * response must not be cached) but STILL forward it to the origin — do - * NOT return here; fall through to the forward path below. */ - ctx->has_authorization = 1; - } - /* Remember content-length for the upload framing, but do NOT forward it to - * the origin: CURLOPT_INFILESIZE_LARGE is the sole framing source, so - * forwarding CL as a request header would create a divergence surface for - * CL-based request-smuggling (defense mirrors mq_gw_strip_client's CL rule). */ - if (slice_ieq(n, nl, "content-length")) { - /* A SECOND content-length (even identical) is a framing ambiguity → reject - * (mirrors mq_http1: a duplicate CL is a request-smuggling surface). */ - if (ctx->cl_seen) { - ctx->bad_cl = 1; - return; - } - ctx->cl_seen = 1; - /* Strict decimal into [0, INT64_MAX]; empty / non-digit / overflow → reject - * rather than silently leaving content_length unset (which would fall back - * to chunked and mis-frame a bodied request). Mirrors the INT64_MAX/10 - * overflow guard in mq_http1.c parse_content_length. */ - int64_t cl = 0; - int ok = vl > 0; - for (size_t i = 0; i < vl; i++) { - if (v[i] < '0' || v[i] > '9') { - ok = 0; - break; - } - int d = v[i] - '0'; - if (cl > (int64_t)922337203685477580LL || - (cl == (int64_t)922337203685477580LL && d > 7)) { - ok = 0; - break; - } - cl = cl * 10 + d; - } - if (ok) - ctx->content_length = cl; - else - ctx->bad_cl = 1; - return; /* strip from forwarded set */ - } - - /* Strip server→origin (x-mq-*) and hop-by-hop. Forward the rest. */ - if (mq_gw_strip_server(n, nl) || mq_gw_strip_hop(n, nl)) return; - /* Defense-in-depth: reject (whole request → 400) any forwarded header whose - * NAME or VALUE carries control bytes. A name/value smuggling a CR/LF would - * be a request-splitting surface once replayed to the origin. HTAB is - * permitted in values only (mirrors D3 / mq_gw_parse_target strictness). */ - if (!mq_gw_hdr_name_ok(n, nl) || !mq_gw_hdr_value_ok(v, vl)) { - ctx->bad_header = 1; - return; - } - /* A forwarded NAME/VALUE that would not fit its arena slot must be REJECTED, - * not silently truncated by copy_z: a clipped header changes what reaches the - * origin (and a truncated value is a smuggling surface). Reuse the bad-header - * 400 path. nl/vl are the FULL slice lengths; the slots hold cap-1 + NUL. */ - if (nl >= sizeof(ctx->fwd_name[0]) || vl >= sizeof(ctx->fwd_val[0])) { - ctx->bad_header = 1; - return; - } - if (ctx->n_fwd >= MQ_GWS_MAX_HDRS) { - ctx->bad = 1; - return; - } - size_t i = ctx->n_fwd++; - copy_z(ctx->fwd_name[i], sizeof(ctx->fwd_name[i]), n, nl); - copy_z(ctx->fwd_val[i], sizeof(ctx->fwd_val[i]), v, vl); - ctx->fwd[i].name = ctx->fwd_name[i]; - ctx->fwd[i].value = ctx->fwd_val[i]; -} - -/* Validate a scheme is exactly "http" or "https" (lowercase). */ -static int -scheme_ok(const char *s) -{ - return strcmp(s, "http") == 0 || strcmp(s, "https") == 0; -} - -/* Build the cache key "GET " into buf. GET-only (the caller gates on method). - * Returns the written length (excl. NUL), or 0 on truncation/failure. */ -static size_t -gw_cache_key(const char *url, char *buf, size_t cap) -{ - int n = snprintf(buf, cap, "GET %s", url); - if (n <= 0 || (size_t)n >= cap) { - if (cap > 0) buf[0] = '\0'; - return 0; - } - return (size_t)n; -} - -/* Cacheability predicate for the ORIGIN response that ran (the STORE gate, and the - * ttl source). Returns the storable TTL in SECONDS (>0) iff every rule holds, else - * 0 (not cacheable). Rules: GET, 200, opted in (cache_ttl_s>0), no Authorization, - * no no-store/private/no-cache, no Set-Cookie, no Vary, and the effective ttl - * (min of the request opt-in and any response max-age/s-maxage) is > 0 — so a - * `max-age=0` response is NOT cacheable. */ -static unsigned -gw_response_cacheable(const mq_gw_req_t *r) -{ - if (!r->is_get) return 0; - if (r->resp_status != 200) return 0; - if (r->cache_ttl_s == 0) return 0; - if (r->has_authorization) return 0; - if (r->cc_uncacheable) return 0; - if (r->has_set_cookie) return 0; - if (r->has_vary) return 0; - unsigned ttl_s = r->cache_ttl_s; - if (r->cc_max_age >= 0 && (unsigned long)r->cc_max_age < ttl_s) - ttl_s = (unsigned)r->cc_max_age; /* the more conservative bound (incl. 0) */ - return ttl_s; /* 0 ⇒ not cacheable (e.g. max-age=0) */ -} - -/* COPY-ON-HIT + replay: serve `e` (a fresh borrowed cache entry) as this request's - * response. The entry can be evicted by a LATER request's insert across the send - * turns we yield on, so we DEEP-COPY the body into r->cache_body and stage the - * header strings into the inline r->hdrs[] arrays BEFORE sending anything. Then we - * send the headers and kick off the from_cache body replay (download_flush drives - * the whole in-memory body, yielding only on real H3 backpressure; h3_on_write - * re-drives). - * - * Returns 0 when the HIT was HANDLED (the response is in flight, OR the request is - * already being torn down by a hard H3 send failure — either way the caller must - * NOT start an origin). Returns -1 ONLY on a pre-send OOM (the body deep-copy - * failed and NOTHING was sent), where the caller safely degrades to a normal - * proxied response. On -1 all partial state is cleared. */ -static int -gw_serve_from_cache(mq_gw_req_t *r, const mq_gw_cache_entry_t *e) -{ - if (r->h3_dead || !r->req) return -1; - - /* Deep-copy the body first (the only heap we own on the HIT path). Done BEFORE - * any send so a -1 (OOM) leaves the H3 side untouched → safe to degrade. */ - uint8_t *body = NULL; - if (e->body_len > 0) { - body = malloc(e->body_len); - if (!body) return -1; /* OOM → degrade; nothing sent yet */ - memcpy(body, e->body, e->body_len); - } - - /* Stage the cached headers into the inline r->hdrs[] arrays. The entry's header - * strings passed the SAME 128/1024 arena caps on store (origin_on_header), so - * copy_z here is lossless; cap defensively at MQ_GWS_MAX_HDRS regardless. */ - r->n_hdrs = 0; - for (size_t i = 0; i < e->n_hdrs && r->n_hdrs < MQ_GWS_MAX_HDRS; i++) { - int j = r->n_hdrs++; - copy_z(r->hdrs[j].name, sizeof(r->hdrs[j].name), e->hdrs[i].name, - strlen(e->hdrs[i].name)); - copy_z(r->hdrs[j].value, sizeof(r->hdrs[j].value), e->hdrs[i].value, - strlen(e->hdrs[i].value)); - } - - r->resp_status = e->status; - r->cache_body = body; /* owned; freed at both teardown sites */ - r->cache_body_len = e->body_len; - r->cache_off = 0; - r->from_cache = 1; - - /* Send the response header section (the x-mq-origin-protocol diagnostic is - * suppressed for from_cache in download_send_headers). From here on the request - * is COMMITTED to the HIT path: even a hard H3 send failure tears the request - * down (reset) rather than degrading — falling back to the origin after bytes - * (or a reset) hit the wire is not possible. So we return 0 (handled) in all - * post-send outcomes; the from_cache replay state + cache_body remain owned and - * are freed at teardown. */ - if (download_send_headers(r) != 0) { - /* Hard H3 send failure (sh<0) OR a synthesized clean error (oversized header - * → 502 fin; not reachable for cached headers but handled defensively). - * Mirror origin_on_body: reset only if the error path did NOT already finish - * cleanly. Leave from_cache state intact (download_flush is a no-op once - * r->req is gone / finished); teardown frees cache_body once. */ - if (!r->finished) mq_h3_req_reset(r->req); - return 0; - } - /* Prime the body replay (the from_cache outer loop fills pend on first iter). - * h3_on_write re-drives on H3 backpressure. */ - download_flush(r); - return 0; -} - -/* on_new_req has fired and headers are ready: parse, auth, validate, start the - * origin request. Synthesizes 403/400/5xx on failure. */ -static void -gw_dispatch(mq_gw_req_t *r) -{ - mq_gw_server_t *s = r->srv; - - req_hdr_ctx_t ctx; - memset(&ctx, 0, sizeof(ctx)); - ctx.r = r; - ctx.content_length = -1; - - int fin = 0; - int nh = mq_h3_req_recv_headers(r->req, req_each_header, &ctx, &fin); - if (nh < 0) { - /* No headers / hard error: synthesize 400. */ - gw_send_error(r, "400", "bad-request"); - return; - } - /* A forwarded header carried control bytes → reject (smuggling posture). */ - if (ctx.bad_header) { - gw_send_error(r, "400", "bad-header"); - return; - } - /* The forwardable-header COUNT exceeded the per-request arena (ctx.bad set in - * req_each_header). Forwarding now would replay a SILENTLY TRUNCATED header - * set to the origin — fail closed with 400 rather than corrupt the request. */ - if (ctx.bad) { - gw_send_error(r, "400", "bad-request"); - return; - } - /* content-length was empty / non-digit / overflowed / duplicated → reject - * rather than mis-frame the upload (strict, mirrors mq_http1). */ - if (ctx.bad_cl) { - gw_send_error(r, "400", "bad-request"); - return; - } - /* fin on the header section → no request body. */ - r->has_body = fin ? 0 : 1; - if (fin) r->h3_body_fin = 1; - - /* AUTH: x-mq-auth must be "Bearer " with constant-time token compare. */ - { - const char *pfx = "Bearer "; - size_t pl = strlen(pfx); - size_t al = ctx.has_auth ? strlen(ctx.auth) : 0; - int ok = ctx.has_auth && al > pl && memcmp(ctx.auth, pfx, pl) == 0; - if (ok) { - const char *tok = ctx.auth + pl; - size_t tl = al - pl; - ok = mq_ct_equal(tok, tl, s->token, s->token_len); - } - if (!ok) { - gw_send_error(r, "403", "auth-failed"); - return; - } - } - /* Past the auth gate: this request presented a valid token. Marks the - * boundary for masquerade — post-auth rejects keep x-mq-error diagnostics. */ - r->authed = 1; - - /* x-mq-class: accept + log only (design: forward+log). - * Sanitize before logging: the value is attacker-controlled (log-injection). */ - if (ctx.has_class) { - char cls_safe[65]; /* 64 visible chars + NUL */ - sanitize_for_log(cls_safe, 64, ctx.cls, strlen(ctx.cls)); - MQ_LOGI("mq_gw_server: x-mq-class='%s'", cls_safe); - } - - /* Validate pseudo-headers. An overlong :method (silently clipped above) is a - * verb-confusion surface; reject it with the same 400 as a missing/empty one. */ - if (!ctx.has_method || !ctx.has_scheme || !ctx.has_authority || !ctx.has_path || - ctx.method[0] == '\0' || ctx.authority[0] == '\0' || ctx.path[0] == '\0' || - ctx.path[0] != '/' || !scheme_ok(ctx.scheme) || ctx.method_overlong || - ctx.scheme_overlong) { - gw_send_error(r, "400", "bad-request"); - return; - } - /* Validate + canonicalise :method with the SAME strictness as the client side - * (mq_gw_parse_method: RFC 7230 token chars only, <=15, ASCII-uppercased). - * curl does NOT sanitize CURLOPT_CUSTOMREQUEST, so an unvalidated method from - * a direct H3 peer (e.g. "GE T" / "GET\r") is a request-line-injection surface. - * Overwrite ctx.method in place with the canonical (uppercased) form. */ - { - char canon[16]; - if (mq_gw_parse_method(ctx.method, strlen(ctx.method), canon) != 0) { - gw_send_error(r, "400", "bad-request"); - return; - } - memcpy(ctx.method, canon, sizeof(ctx.method)); - } - /* An overlong :authority / :path was silently truncated by copy_z — that - * changes the outbound target (redirection / SSRF surface), so reject. */ - if (ctx.authority_overlong || ctx.path_overlong) { - gw_send_error(r, "400", "bad-target"); - return; - } - /* Re-validate :authority / :path strictness on this side of the tunnel - * (defense-in-depth, mirrors mq_gw_parse_target): reject SP, CR, LF, other - * control bytes, and 0x7f before these are folded into an outbound URL. */ - if (!mq_gw_uri_field_ok(ctx.authority, strlen(ctx.authority)) || - !mq_gw_uri_field_ok(ctx.path, strlen(ctx.path))) { - gw_send_error(r, "400", "bad-target"); - return; - } - - /* Build the origin URL = scheme://authority + path. */ - char url[8 + 256 + 1024 + 4]; - int un = snprintf(url, sizeof(url), "%s://%s%s", ctx.scheme, ctx.authority, ctx.path); - if (un <= 0 || (size_t)un >= sizeof(url)) { - gw_send_error(r, "400", "bad-target"); - return; - } - - /* Observability capture (best-effort; safe even if the request later aborts). - * ctx.scheme is validated by scheme_ok() above and NUL-terminated by copy_z. */ - snprintf(r->method, sizeof(r->method), "%s", ctx.method); - snprintf(r->authority, sizeof(r->authority), "%s", ctx.authority); - snprintf( - r->path, sizeof(r->path), "%.*s", (int)sizeof(r->path) - 1, - ctx.path); /* r->path is 512 B, ctx.path is 1024 B: a 513–1023 B path is the - full value at the origin URL (built above) but is silently - truncated in this metrics-only copy. The %.*s precision bound - caps output to fit so -Werror=format-truncation can prove it. */ - r->origin_is_tls = (strcmp(ctx.scheme, "https") == 0); /* gates origin_tls (NEW-3) */ - - /* Cacheability inputs from the request side (Phase 6; the response-side flags - * are captured later in origin_on_header). */ - r->cache_ttl_s = ctx.cache_ttl_s; - r->has_authorization = ctx.has_authorization; - r->is_get = (strcmp(ctx.method, "GET") == 0); - - /* CACHE HIT path (Phase 6) — check the cache BEFORE starting the origin. A hit - * serves the stored 200 from memory and contacts NO origin. Gated on: caching - * enabled, opted in (X-Mq-Cache), GET, and no Authorization (a per-credential - * response is never served from a shared cache). A cache failure NEVER breaks - * the request — on any error we fall through to a normal proxied response. */ - if (s->cache && r->cache_ttl_s > 0 && r->is_get && !r->has_authorization) { - /* Eligible for caching → this is at least a MISS (flips to HIT below, or - * stays MISS once the origin runs). The store-on-miss happens in - * origin_on_done; the key built here is reused there. */ - r->cache_state = MQ_CACHE_MISS; - if (gw_cache_key(url, r->cache_key, sizeof(r->cache_key)) > 0) { - const mq_gw_cache_entry_t *e = - mq_gw_cache_lookup(s->cache, r->cache_key, gw_now_ms()); - if (e && gw_serve_from_cache(r, e) == 0) { - /* HIT: response is now in flight from memory; no origin started. - * origin_dead stays 0 here → h3_on_close flips it (oreq==NULL, the - * reject precedent) → gw_req_maybe_free reclaims. */ - r->cache_state = MQ_CACHE_HIT; - return; - } - /* lookup miss OR a copy-on-hit OOM: degrade to a normal proxied response - * (cache_state stays MISS). gw_serve_from_cache cleared any partial state. */ - } else { - /* Key too long to cache (pathological URL): leave cache_key empty so the - * store path skips insert; still a normal proxied response. */ - r->cache_key[0] = '\0'; - } - } - - /* Upload framing: body present → known CL (>=0) or chunked sentinel. */ - int64_t upload_len = -1; /* no body */ - if (r->has_body) { - upload_len = - ctx.content_length >= 0 ? ctx.content_length : MQ_ORIGIN_UPLOAD_CHUNKED; - } - - mq_origin_cbs_t cbs; - memset(&cbs, 0, sizeof(cbs)); - cbs.on_status = origin_on_status; - cbs.on_header = origin_on_header; - cbs.on_body = origin_on_body; - cbs.pull_body = origin_pull_body; - cbs.on_done = origin_on_done; - - r->oreq = mq_origin_start(s->origin, url, ctx.method, ctx.fwd, ctx.n_fwd, upload_len, - ctx.origin_http_version, &cbs, r); - if (!r->oreq) { - /* Could not start the origin request → 502. Origin side never existed. */ - r->origin_dead = 1; - gw_send_error(r, "502", "origin-start-failed"); - return; - } - /* origin_dead stays 0; on_done or our abort flips it. The request is now - * live on both sides. If the request body already has bytes buffered in - * the stream (rare), pump them so curl's first pull sees them; pull_body - * also tops up on demand. */ -} - -/* ── H3 request callbacks ───────────────────────────────────────────────────*/ - -static void -h3_on_read(mq_h3_req_t *hr, int flag, void *user) -{ - (void)hr; - mq_gw_req_t *r = (mq_gw_req_t *)user; - if (r->h3_dead || !r->req) return; - - if (flag & XQC_REQ_NOTIFY_READ_HEADER) { - /* First (and only) request header section → parse, auth, dispatch. */ - gw_dispatch(r); - /* If dispatch already finished/reset the request (error path), stop. */ - if (r->h3_dead || !r->req || r->finished) return; - } - - if (flag & XQC_REQ_NOTIFY_READ_TRAILER) { - /* Drain + ignore trailers (not forwarded in the MVP). */ - int tfin = 0; - mq_h3_req_recv_headers(r->req, NULL, NULL, &tfin); - if (tfin) r->h3_body_fin = 1; - } - - /* Body bytes available (or empty-fin): feed the upload spill. Only relevant - * once the origin request is live (otherwise we have nothing to feed). */ - if (flag & (XQC_REQ_NOTIFY_READ_BODY | XQC_REQ_NOTIFY_READ_EMPTY_FIN)) { - if (!r->origin_dead) upload_recv(r); - } -} - -static void -h3_on_write(mq_h3_req_t *hr, void *user) -{ - (void)hr; - mq_gw_req_t *r = (mq_gw_req_t *)user; - if (r->h3_dead || !r->req) return; - /* A previously flow-control-blocked response-body send can resume. */ - if (r->pend_off < r->pend_len || r->recv_paused) download_flush(r); -} - -/* ── mq.req metrics emission ───────────────────────────────────────────────── - * Stage-0 (Task 3b) finding, verified live against test_gw_server downloads: - * the SEND-side stamps are the server's response timings and are reliably - * populated > h3r_begin_time on a clean close: - * - h3r_header_send_time → ttfb_ms (e.g. +0.8ms..+1.9ms after begin) - * - stream_fin_send_time → duration_ms (e.g. +2.0ms) - * - stream_fin_ack_time → completion_ms (e.g. +2.1ms) - * recv_body_size/send_body_size carry the request/response body sizes. - * On an error/reset close, fin_send/fin_ack come back 0 (ms_since → -1, guarded) - * and stream_err is nonzero. The receive-side stream_fin_time/h3r_end_time - * measure the REQUEST upload finishing, NOT the response — not used here. - * h3r_header_send_time is reliable, so the Step-3b TTFB fallback is NOT needed. */ -static int -ms_since(xqc_usec_t begin, xqc_usec_t later) -{ - if (begin == 0 || later == 0 || later < begin) return -1; - return (int)((later - begin) / 1000); -} - -/* Copy `in` up to the first '?' (drop query) into out[0..cap). */ -static void -emit_path_strip_query(const char *in, char *out, size_t cap) -{ - size_t n = 0; - for (const char *p = in; *p && *p != '?' && n + 1 < cap; p++) - out[n++] = *p; - out[n] = '\0'; -} - -/* metrics-only origin protocol token: h3/h2/h1/none. Do NOT reuse http_ver_token - * (it returns "http/1.1"/"http/1.0" for H1, which violates the mq.req schema). - * NONE/unknown → "none" so an origin that never negotiated a version (start-failure - * or H3-first abort, where resp_http_ver stays at the NONE default) is not mislabeled. */ -static const char * -origin_proto_token(long ver) -{ - switch (ver) { - case CURL_HTTP_VERSION_3: return "h3"; - case CURL_HTTP_VERSION_2: return "h2"; - case CURL_HTTP_VERSION_1_1: - case CURL_HTTP_VERSION_1_0: return "h1"; - default: return "none"; /* incl. CURL_HTTP_VERSION_NONE */ - } -} - -static void -gw_emit_req_metrics(mq_gw_req_t *r, mq_h3_req_t *hr) -{ - if (!r->srv->request_metrics) return; - xqc_request_stats_t st; - int have = (mq_h3_req_get_stats(hr, &st) == 0); - - char pathbuf[MQ_GW_REQ_PATH_CAP + 1]; - emit_path_strip_query(r->path[0] ? r->path : "-", pathbuf, sizeof(pathbuf)); - - /* origin_tls: origin_on_done set it for a COMPLETED origin (MQ_TLS_NA for a plain - * http:// origin). If the origin is still live at close (oreq != NULL and not yet - * dead), this request is closing with the origin in-flight (H3-first abort, which - * fires no on_done) → no completed/verified origin. For a TLS origin that means - * connect_fail; for a plain http:// origin there is no TLS handshake to fail, so - * report "na" (same value origin_on_done would have set). */ - const char *otls = (r->oreq && !r->origin_dead) - ? (r->origin_is_tls ? "connect_fail" : "na") - : tls_token(r->origin_tls); - - mq_gw_req_metrics_t m = { - .method = r->method[0] ? r->method : "-", - .status = r->resp_status, /* client-visible status; set on BOTH the - origin path (on_status) and gw_send_error - (Task 5) so error rows are not status=0 */ - .authority = r->authority[0] ? r->authority : "-", - .path = pathbuf, - .req_bytes = have ? st.recv_body_size : 0, /* server RECEIVES request body */ - .resp_bytes = have ? st.send_body_size : 0, /* server SENDS response body */ - /* SERVER-side response timings use SEND-side stamps (verified in Stage-0): - * h3r_*send_time / stream_fin_send_time / stream_fin_ack_time. The receive-side - * stream_fin_time ("receiving transport fin") / h3r_end_time measure the - * REQUEST upload finishing, NOT the response — do not use them here. */ - .ttfb_ms = have ? ms_since(st.h3r_begin_time, st.h3r_header_send_time) : -1, - .duration_ms = have ? ms_since(st.h3r_begin_time, st.stream_fin_send_time) : -1, - .completion_ms = have ? ms_since(st.h3r_begin_time, st.stream_fin_ack_time) : -1, - .origin_protocol = - origin_proto_token(r->resp_http_ver), /* NONE-default → "none" */ - .origin_tls = otls, - .content_encoding = r->content_encoding[0] ? r->content_encoding : "none", - .cache = r->cache_state == MQ_CACHE_HIT ? "hit" - : r->cache_state == MQ_CACHE_MISS - ? "miss" - : "bypass", /* not opted in / disabled / - non-GET / Authorization */ - .origin_reuse = r->origin_reuse, /* Phase 6: real (was honest 0) */ - .origin_connect_ms = r->origin_connect_ms, /* Phase 6: 0 reuse / -1 unknown */ - .mp_state = have ? st.mp_state : 0, - .reset_reason = (have && st.stream_err) - ? (st.stream_close_msg ? st.stream_close_msg : "stream-err") - : "", - }; - - /* cid=- : 5c log lines carry no cid, so there is nothing to correlate a hex - * cid against today. sid (the MPQUIC stream id) correlates within a conn. */ - char line[MQ_GW_REQ_LINE_CAP]; - uint64_t sid = mq_h3_req_stream_id(hr); /* Task 3.5 accessor */ - if (mq_gw_format_req_line(line, sizeof(line), "-", sid, &m) > 0) - MQ_LOGI("%s", line); - else - MQ_LOGW("mq.req line truncated (dropped)"); /* MQ_LOGW exists, mq_log.h:28 */ -} - -static void -h3_on_close(mq_h3_req_t *hr, void *user) -{ - mq_gw_req_t *r = (mq_gw_req_t *)user; - gw_emit_req_metrics(r, hr); - r->h3_dead = 1; - r->req = NULL; /* freed right after this returns */ - - /* Flip the origin side dead so gw_req_maybe_free can reclaim the state. - * Two cases, handled uniformly: - * - origin live (oreq != NULL): client reset / conn died before the origin - * completed → abort it. on_close is an mq_h3 callback — NOT a curl - * callback — so mq_origin_abort is safe here (re-entrancy contract). - * abort fires NO on_done, so we flip origin_dead ourselves. - * - origin NEVER existed (oreq == NULL): the request was rejected pre-origin - * (403/400/bad-header/bad-target) and the synthetic-error path did not run - * gw_dispatch's origin_dead=1 (those rejects leave it 0). The origin side - * cannot be live without an oreq, so flip origin_dead unconditionally — - * otherwise maybe_free never runs and the state leaks in s->reqs. */ - if (!r->origin_dead) { - if (r->oreq) { - mq_origin_abort(r->oreq); - r->oreq = NULL; - } - r->origin_dead = 1; - } - gw_req_maybe_free(r); -} - -/* ── mq_h3 server hooks ─────────────────────────────────────────────────────*/ - -/* Conn-state hook for accepted H3 tunnel conns: clear the observability slot on - * close if it still points at this conn (a later accept may have replaced it), - * so the metrics tick never dereferences a freed h3 conn. Mirrors the TCP - * server's clear-if-self guard (mq_server.c). `established` is unused (we only - * act on close). */ -static void -gw_srv_conn_state(mq_h3_conn_t *c, int established, void *user) -{ - if (established) { - return; - } - mq_gw_server_t *s = (mq_gw_server_t *)user; - if (s->last_conn == c) { - s->last_conn = NULL; - } -} - -static void -gw_on_new_conn(mq_h3_conn_t *c, void *user) -{ - mq_gw_server_t *s = (mq_gw_server_t *)user; - /* Track the most-recent accepted conn for the periodic metrics dump, and - * install a state cb to clear it on close (UAF guard). Per-request state is - * still allocated lazily in on_new_req. - * - * DETACH the cb from the previously-tracked conn before overwriting, so AT - * MOST ONE live conn ever carries gw_srv_conn_state(s). Otherwise an older - * conn that is still live when a newer one is accepted would keep the cb, and - * mq_h3_free's engine teardown (which runs AFTER mq_gw_server_free frees `s`) - * would fire gw_srv_conn_state into the freed `s` — a use-after-free. With - * this, mq_gw_server_free's single detach of last_conn fully covers it. */ - if (s->last_conn && s->last_conn != c) { - mq_h3_conn_set_state_cb(s->last_conn, NULL, NULL); - } - s->last_conn = c; - mq_h3_conn_set_state_cb(c, gw_srv_conn_state, s); -} - -static void -gw_on_new_req(mq_h3_req_t *hr, void *user) -{ - mq_gw_server_t *s = (mq_gw_server_t *)user; - s->requests++; - - mq_gw_req_t *r = calloc(1, sizeof(*r)); - if (!r) { - MQ_LOGE("mq_gw_server: OOM allocating per-request state; resetting"); - mq_h3_req_reset(hr); - return; - } - r->srv = s; - r->req = hr; - r->resp_http_ver = - CURL_HTTP_VERSION_NONE; /* "none" until on_done sets the real version */ - r->cc_max_age = -1; /* -1 = absent (calloc zeroes the other cache flags) */ - r->origin_reuse = 0; - r->origin_connect_ms = -1; - - /* Link before wiring callbacks so teardown can find it. */ - r->next = s->reqs; - s->reqs = r; - - mq_h3_req_set_cbs(hr, h3_on_read, h3_on_write, h3_on_close, r); - - /* Headers may already be pending (xquic can deliver on_new_req with the - * header section ready). The on_read notify drives the parse; do not call - * recv here to avoid double-draining. */ -} - -/* ── public API ─────────────────────────────────────────────────────────────*/ - -mq_gw_server_t * -mq_gw_server_new(mq_transport_t *t, mq_runtime_t *rt, const char *token, - const char *ca_file, long connect_timeout_s) -{ - if (!t || !rt || !token) return NULL; - - mq_gw_server_t *s = calloc(1, sizeof(*s)); - if (!s) return NULL; - s->t = t; - s->rt = rt; - snprintf(s->token, sizeof(s->token), "%s", token); - s->token_len = strnlen(s->token, sizeof(s->token)); - - struct event_base *base = mq_runtime_base(rt); - if (!base) { - free(s); - return NULL; - } - s->origin = mq_origin_new(base, ca_file, connect_timeout_s); - if (!s->origin) { - free(s); - return NULL; - } - - /* The gw_server is the H3 hooks owner — init mq_h3 with our own hooks. */ - s->h3 = mq_h3_init(t, gw_on_new_conn, gw_on_new_req, s); - if (!s->h3) { - mq_origin_free(s->origin); - free(s); - return NULL; - } - return s; -} - -mq_h3_t * -mq_gw_server_h3(const mq_gw_server_t *s) -{ - return s ? s->h3 : NULL; -} - -mq_h3_conn_t * -mq_gw_server_active_conn(const mq_gw_server_t *s) -{ - return s ? s->last_conn : NULL; -} - -unsigned -mq_gw_server_requests(const mq_gw_server_t *s) -{ - return s ? s->requests : 0; -} - -unsigned -mq_gw_server_live_reqs(const mq_gw_server_t *s) -{ - if (!s) return 0; - unsigned n = 0; - for (const mq_gw_req_t *r = s->reqs; r; r = r->next) - n++; - return n; -} - -void -mq_gw_server_set_request_metrics(mq_gw_server_t *s, int on) -{ - if (s) s->request_metrics = on ? 1 : 0; -} - -void -mq_gw_server_set_masquerade(mq_gw_server_t *s, int on) -{ - if (s) s->masquerade = on ? 1 : 0; -} - -void -mq_gw_server_set_cache(mq_gw_server_t *s, size_t max_bytes) -{ - if (!s) return; - if (s->cache) { - mq_gw_cache_free(s->cache); - s->cache = NULL; - } - if (max_bytes > 0) { - size_t obj = max_bytes < (4u << 20) ? max_bytes : (4u << 20); /* min(N,4MiB) */ - if (obj < MQ_GWS_RECV_CHUNK) obj = MQ_GWS_RECV_CHUNK; /* floor at a chunk */ - s->cache = mq_gw_cache_new(max_bytes, obj); - } -} - -void -mq_gw_server_free(mq_gw_server_t *s) -{ - if (!s) return; - - /* DETACH the conn-state cb on the tracked conn FIRST. gw_server_free runs - * while the H3 engine is still live (sanctioned order); the subsequent - * mq_h3_free destroys remaining conns and fires their close-notify — by then - * `s` is freed, so a live gw_srv_conn_state(s) would be a use-after-free. - * Mirrors the mq_gw_client_free detach. (Per-instance: one live conn.) */ - if (s->last_conn) { - mq_h3_conn_set_state_cb(s->last_conn, NULL, NULL); - s->last_conn = NULL; - } - - /* Tear down in-flight requests. SANCTIONED ORDER (see header): gw_server_free - * runs FIRST, while the H3 engine is STILL LIVE, so touching r->req is valid. - * For each live request: - * 1. ABORT the origin side (no on_done; the curl handle is reaped now). - * 2. DETACH the H3 callbacks so the deferred on_close mq_h3_req_reset - * schedules cannot re-enter h3_on_close on the mq_gw_req_t we free here. - * 3. RESET the H3 request (truncate the tunnel side). - * 4. Free the per-request state (we own it; both sides detached). */ - mq_gw_req_t *r = s->reqs; - while (r) { - mq_gw_req_t *next = r->next; - if (!r->origin_dead && r->oreq) { - mq_origin_abort(r->oreq); - r->oreq = NULL; - r->origin_dead = 1; - } - if (!r->h3_dead && r->req) { - mq_h3_req_set_cbs(r->req, NULL, NULL, NULL, NULL); - mq_h3_req_reset(r->req); - r->req = NULL; - r->h3_dead = 1; - } - free(r->spill); - free(r->cache_body); /* copy-on-hit body — a HIT can be live at shutdown */ - free(r->store_buf); /* STORE accumulator — a cacheable miss can be in flight */ - free(r); - r = next; - } - s->reqs = NULL; - - /* All requests aborted → safe to free the origin (no live easy handles). */ - if (s->origin) mq_origin_free(s->origin); - - /* Free the response cache (NULL when disabled). All in-flight requests are - * already reclaimed above, so no live HIT borrows the entries. */ - if (s->cache) mq_gw_cache_free(s->cache); - - /* Do NOT free s->h3 here — the caller frees it (mq_gw_server_h3) in the - * sanctioned order, before mq_transport_free. */ - free(s); -} diff --git a/src/gateway/mq_gw_server.h b/src/gateway/mq_gw_server.h deleted file mode 100644 index ace5e8c4..00000000 --- a/src/gateway/mq_gw_server.h +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_gw_server.h — server-side H3→origin execution bridge (Phase 2 Task 4.3). - * - * The mirror of mq_gw_client: where the client tunnels a local fetch request as - * an H3 request to the gateway, the SERVER receives that H3 request, replays it - * to the upstream origin via mq_origin_curl, and streams the origin's response - * back over the SAME H3 request. One in-flight H3 request ⇄ one origin request. - * - * Per accepted H3 request (mq_h3 on_new_req): - * - recv the request headers; extract the :method/:scheme/:authority/:path - * pseudo-headers + x-mq-auth + x-mq-class. - * - AUTH: x-mq-auth must be "Bearer ", constant-time compared to the - * configured token (shared mq_ct_equal, util/mq_ct.h). Failure → synthesized - * 403 + x-mq-error: auth-failed (no origin contact). - * - validate the pseudo-headers (scheme http|https); bad → 400 + x-mq-error. - * - build origin URL = scheme://authority + path, forward the request headers - * minus x-mq-* (mq_gw_strip_server) minus hop-by-hop (mq_gw_strip_hop), and - * start the origin request. Upload body (if any) is pulled from an H3-side - * recv spill; download body is streamed back, with backpressure in both - * directions. On origin error (before response headers are sent) the bridge - * synthesizes status = mq_gw_status_from_curl(result) + x-mq-error:curl:; - * on an error AFTER headers are already on the wire it RESETS the H3 request - * (a truncation must be visible — never a fake clean fin). - * - * ───────────────────────────────────────────────────────────────────────────── - * OWNERSHIP / LIFETIME / TEARDOWN ORDER (MANDATORY) - * ───────────────────────────────────────────────────────────────────────────── - * - t / rt are BORROWED (the caller owns them). - * - mq_gw_server_new calls mq_h3_init ITSELF (the gw_server must be the hooks - * owner — a pre-inited mq_h3 would be circular, since the hooks need the - * gw_server). The created mq_h3 is exposed via mq_gw_server_h3() so the CLI - * and the test fixture can free it in the correct order. - * - * - mq_gw_server_free does NOT free the mq_h3 it created. It tears down every - * in-flight request (aborts the origin, detaches the H3 req callbacks) and - * frees the origin client, then returns. The caller MUST then free the mq_h3 - * and the transport. SANCTIONED TEARDOWN ORDER (consistent with gw_client): - * - * mq_gw_server_free(s) → mq_h3_free(mq_gw_server_h3(s)…captured) → - * mq_transport_free(t) - * - * gw_server_free MUST run FIRST, while the H3 engine is STILL LIVE: it touches - * r->req on live in-flight requests (mq_h3_req_set_cbs(NULL,...)), which is - * only valid while the engine exists. If mq_h3_free ran first, the per-request - * wrappers would already be destroyed and any touch would be a UAF. - * NOTE: capture the h3 handle (mq_gw_server_h3(s)) BEFORE mq_gw_server_free — - * the handle accessor reads the gw_server struct, which free() releases. - * - * ───────────────────────────────────────────────────────────────────────────── - * KNOWN LIMITATION — pre-auth per-stream memory (mirrors mq_fetch_listener.h) - * ───────────────────────────────────────────────────────────────────────────── - * Each opened H3 stream allocates its per-request state (mq_gw_req_t — ~90 KB: the - * inline response-header arena (64 * (128+1024) ~= 72 KB) plus the 16 KB pend - * buffer; the spill buffer is allocated lazily only for a bodied upload) in - * on_new_req, BEFORE the request headers are parsed and BEFORE auth runs. The - * COUNT of concurrent - * per-request states is bounded only by xquic's per-connection stream limit - * (1024), so a single tokenless peer that opens streams without ever sending a - * valid (or any) auth header can pin on the order of ~90 MB of per-request state - * pre-auth. No origin is contacted without a successful Bearer-token auth, so this - * is purely a memory-amplification surface, not an SSRF/relay one. This is - * acceptable for the Phase 2 single-trusted-client scope; a global per-process - * memory budget / concurrency cap (the D6 multi-client hardening item) is deferred - * to Phase 5, alongside the symmetric mq_fetch_listener.h per-process-sum limit. - */ -#ifndef MQ_GATEWAY_MQ_GW_SERVER_H -#define MQ_GATEWAY_MQ_GW_SERVER_H - -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_h3.h" -#include "transport/mq_transport.h" - -typedef struct mq_gw_server_s mq_gw_server_t; - -/* Create a gateway server. - * - * t / rt : the sans-io server transport + its runtime (BORROWED; must outlive - * the gw_server). rt owns the libevent base used to drive mq_origin. - * token : the shared token EXPECTED in each request's X-Mq-Auth header - * ("Bearer "), verified by constant-time compare. Copied. - * ca_file: (nullable) CURLOPT_CAINFO for origin TLS verification (always on). - * connect_timeout_s : origin connect timeout, per request. - * - * Calls mq_h3_init(t, …its own hooks…) internally. Returns NULL on bad args / - * OOM / mq_h3_init failure / mq_origin_new failure. */ -mq_gw_server_t *mq_gw_server_new(mq_transport_t *t, mq_runtime_t *rt, const char *token, - const char *ca_file, long connect_timeout_s); - -/* The mq_h3 the gw_server created (for teardown ordering / CLI use). Borrowed — - * do NOT free it before mq_gw_server_free. Returns NULL on a NULL arg. */ -mq_h3_t *mq_gw_server_h3(const mq_gw_server_t *s); - -/* The most-recently-accepted H3 tunnel conn, or NULL if none is live (Phase 5c - * observability hook; mirrors mq_server_active_conn). Cleared to NULL when that - * conn closes, so it is never dangling. Per-instance deployments have one client - * conn; with multiple simultaneous gateway clients only the newest is tracked. - * Pass the result to mq_h3_conn_dump_stats for periodic per-path metrics. */ -mq_h3_conn_t *mq_gw_server_active_conn(const mq_gw_server_t *s); - -/* Free the gateway server: abort every in-flight origin request, detach the H3 - * request callbacks, free the origin client and the gw_server. Does NOT free the - * mq_h3 it created (see the SANCTIONED TEARDOWN ORDER above), nor t / rt. Safe on - * NULL. MUST be called BEFORE mq_h3_free. */ -void mq_gw_server_free(mq_gw_server_t *s); - -/* Count of H3 requests the server has accepted (observability / test hook). - * Bumped once per on_new_req (including auth-rejected requests — they are - * accepted at the H3 layer before auth runs). 0 on a NULL arg. */ -unsigned mq_gw_server_requests(const mq_gw_server_t *s); - -/* Count of per-request states currently LIVE (linked in the server's intrusive - * request list) — i.e. allocated but not yet reclaimed by the dual-flag - * exactly-once teardown (observability / test hook; mirrors - * mq_gw_server_requests). After every in-flight request completes and the loop - * is pumped this returns 0; a non-zero residual under wrong-token / rejected - * traffic is a per-request state leak. 0 on a NULL arg. */ -unsigned mq_gw_server_live_reqs(const mq_gw_server_t *s); - -/* Enable per-request mq.req metrics emission (opt-in; default off). Idempotent. - * Pass on=1 to enable, on=0 to disable. Safe on NULL (no-op). */ -void mq_gw_server_set_request_metrics(mq_gw_server_t *s, int on); - -/* When on, the gateway answers UNAUTHENTICATED requests with a bare 404 (no - * x-mq-* headers) instead of 403 auth-failed, so a probe sees a generic H3 - * server. Authenticated requests keep full diagnostics. Opt-in; default off. - * Idempotent. Safe on NULL (no-op). */ -void mq_gw_server_set_masquerade(mq_gw_server_t *s, int on); - -/* Enable (or reconfigure) the in-memory origin response cache, bounded to - * `max_bytes` total. max_bytes == 0 disables it (frees any existing cache, the - * default). The per-object cap is min(max_bytes, 4 MiB) floored at one recv - * chunk. Opt-in; off by default. Idempotent. Safe on NULL (no-op). */ -void mq_gw_server_set_cache(mq_gw_server_t *s, size_t max_bytes); - -#endif /* MQ_GATEWAY_MQ_GW_SERVER_H */ diff --git a/src/gateway/mq_http1.c b/src/gateway/mq_http1.c deleted file mode 100644 index 2031d3a9..00000000 --- a/src/gateway/mq_http1.c +++ /dev/null @@ -1,302 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "gateway/mq_http1.h" - -#include "gateway/mq_gw_headers.h" /* mq_gw_is_tchar (shared token-char class) */ -#include -#include - -/* --------------------------------------------------------------------------- - * Character classes (RFC 7230) - * - * tchar (token char) is shared with the header-policy / target-parsing layer; - * see mq_gw_is_tchar in mq_gw_headers.h. There is intentionally a single - * definition so the HTTP/1 field-name parser and the method/target validators - * can never drift apart. - * ------------------------------------------------------------------------- */ - -static char -lc(char c) -{ - return (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c; -} - -/* Case-insensitive equality of a slice against a NUL-terminated lowercase key. */ -static int -slice_ci_eq(const char *s, size_t sl, const char *key) -{ - size_t kl = strlen(key); - if (sl != kl) return 0; - for (size_t i = 0; i < sl; i++) - if (lc(s[i]) != key[i]) return 0; - return 1; -} - -/* Find end (one past last byte) of first "\r\n\r\n". Returns 0 if not found - * within [0,len). */ -static size_t -find_head_end(const uint8_t *buf, size_t len) -{ - for (size_t i = 0; i + 4 <= len; i++) { - if (buf[i] == '\r' && buf[i + 1] == '\n' && buf[i + 2] == '\r' && - buf[i + 3] == '\n') - return i + 4; - } - return 0; -} - -/* --------------------------------------------------------------------------- - * Content-Length parse: strict decimal into [0, INT64_MAX]. Returns 0 on - * success (writes *out), -1 on any error (empty / non-digit / overflow). - * Leading zeros are accepted by design: the numeric value is unambiguous - * ("007" == 7), and rejecting them would add complexity with no security gain - * on this trust boundary. - * ------------------------------------------------------------------------- */ -static int -parse_content_length(const char *s, size_t sl, int64_t *out) -{ - if (sl == 0) return -1; - int64_t v = 0; - for (size_t i = 0; i < sl; i++) { - char c = s[i]; - if (c < '0' || c > '9') return -1; - int d = c - '0'; - /* overflow guard: v*10 + d must stay <= INT64_MAX (9223372036854775807) */ - if (v > (int64_t)922337203685477580LL || - (v == (int64_t)922337203685477580LL && d > 7)) - return -1; - v = v * 10 + d; - } - *out = v; - return 0; -} - -/* Does an OWS-trimmed Transfer-Encoding value list "chunked" (case-insensitive) - * as one of its comma-separated tokens? */ -static int -te_lists_chunked(const char *s, size_t sl) -{ - size_t i = 0; - while (i < sl) { - /* skip leading OWS / commas */ - while (i < sl && (s[i] == ' ' || s[i] == '\t' || s[i] == ',')) - i++; - size_t start = i; - while (i < sl && s[i] != ',') - i++; - /* token = s[start..i); trim trailing OWS */ - size_t end = i; - while (end > start && (s[end - 1] == ' ' || s[end - 1] == '\t')) - end--; - if (slice_ci_eq(s + start, end - start, "chunked")) return 1; - } - return 0; -} - -mq_http1_status_t -mq_http1_parse_req(const uint8_t *buf, size_t len, mq_http1_req_t *out) -{ - size_t hend = find_head_end(buf, len); - if (hend == 0) { - /* No terminator yet. If we've already buffered more than the cap with no - * terminator, give up rather than buffer unboundedly. */ - if (len > MQ_HTTP1_MAX_HEAD) return MQ_HTTP1_BAD; - return MQ_HTTP1_NEED_MORE; - } - if (hend > MQ_HTTP1_MAX_HEAD) return MQ_HTTP1_BAD; - - memset(out, 0, sizeof(*out)); - out->content_length = -1; - - const char *p = (const char *)buf; - - /* ---- request line: ends at first CRLF within [0,hend) ---- */ - size_t line_end = 0; - for (size_t i = 0; i + 1 < hend; i++) { - if (p[i] == '\r' && p[i + 1] == '\n') { - line_end = i; - break; - } - } - /* hend contains CRLFCRLF, so a CRLF exists; line_end==0 means empty line. */ - - /* METHOD SP PATH SP HTTP/x.y */ - size_t m_end = 0; - while (m_end < line_end && p[m_end] != ' ') - m_end++; - if (m_end == 0 || m_end >= line_end) return MQ_HTTP1_BAD; /* no method/path */ - if (m_end > 15) return MQ_HTTP1_BAD; /* method too long */ - for (size_t i = 0; i < m_end; i++) - if (!mq_gw_is_tchar((unsigned char)p[i])) return MQ_HTTP1_BAD; - - size_t path_start = m_end + 1; - size_t path_end = path_start; - while (path_end < line_end && p[path_end] != ' ') - path_end++; - if (path_end >= line_end) return MQ_HTTP1_BAD; /* no version field */ - if (path_end == path_start) return MQ_HTTP1_BAD; /* empty path */ - if (p[path_start] != '/') return MQ_HTTP1_BAD; /* origin-form must start '/' */ - - size_t path_len = path_end - path_start; - if (path_len >= sizeof(out->path)) return MQ_HTTP1_BAD; /* +1 for NUL */ - - /* Reject any control byte (< 0x20) or DEL (0x7f) in the path span. - * A NUL byte would silently truncate the NUL-terminated out->path, so - * downstream strlen consumers would see a different path than the wire - * (NUL-injection / path-confusion vector). Other bytes < 0x20 and 0x7f - * are in the same injection class as the NUL/CR guard on header values. */ - for (size_t i = path_start; i < path_end; i++) { - unsigned char b = (unsigned char)p[i]; - if (b < 0x20 || b == 0x7f) return MQ_HTTP1_BAD; - } - - /* Method span: mq_gw_is_tchar() already rejects every byte < 0x20 and 0x7f - * (including NUL), so no additional check is needed here. */ - - /* Version field: validated only as "HTTP/"-prefixed and non-empty; the - * gateway never branches on the minor version number (HTTP/1.0 vs 1.1 - * distinction is handled by the caller, not here). */ - size_t v_start = path_end + 1; - if (v_start >= line_end) return MQ_HTTP1_BAD; - if (line_end - v_start < 5 || memcmp(p + v_start, "HTTP/", 5) != 0) - return MQ_HTTP1_BAD; - - memcpy(out->method, p, m_end); - out->method[m_end] = '\0'; - memcpy(out->path, p + path_start, path_len); - out->path[path_len] = '\0'; - - /* ---- header lines: from (line_end+2) up to hend-2 (the blank CRLF) ---- */ - size_t pos = line_end + 2; - size_t headers_end = hend - 2; /* index of the final blank line's CR */ - - int seen_cl = 0; - while (pos < headers_end) { - /* obs-fold: a header line MUST NOT start with SP/HTAB. This is a - * request-smuggling vector (ambiguous re-folding between hops), so we - * reject outright rather than unfold. */ - if (p[pos] == ' ' || p[pos] == '\t') return MQ_HTTP1_BAD; - - /* find CRLF ending this line */ - size_t le = pos; - while (le + 1 < hend && !(p[le] == '\r' && p[le + 1] == '\n')) - le++; - if (le + 1 >= hend) return MQ_HTTP1_BAD; /* unterminated header line */ - /* [pos, le) is the header line bytes */ - - if (out->nh >= MQ_HTTP1_MAX_HEADERS) return MQ_HTTP1_BAD; - - /* name = token up to ':' */ - size_t colon = pos; - while (colon < le && p[colon] != ':') - colon++; - if (colon >= le) return MQ_HTTP1_BAD; /* no colon */ - if (colon == pos) return MQ_HTTP1_BAD; /* empty name */ - for (size_t i = pos; i < colon; i++) - if (!mq_gw_is_tchar((unsigned char)p[i])) return MQ_HTTP1_BAD; - - const char *nm = p + pos; - size_t nml = colon - pos; - - /* value = everything after ':', OWS-trimmed. */ - size_t vs = colon + 1; - size_t ve = le; - while (vs < ve && (p[vs] == ' ' || p[vs] == '\t')) - vs++; - while (ve > vs && (p[ve - 1] == ' ' || p[ve - 1] == '\t')) - ve--; - - /* Reject bare CR or NUL anywhere in the (pre-trim) value span — both are - * smuggling / header-injection vectors. (LF can't appear: the line ends - * at CRLF.) */ - for (size_t i = colon + 1; i < le; i++) - if (p[i] == '\0' || p[i] == '\r') return MQ_HTTP1_BAD; - - const char *vv = p + vs; - size_t vvl = ve - vs; - - out->h[out->nh].n = nm; - out->h[out->nh].nl = nml; - out->h[out->nh].v = vv; - out->h[out->nh].vl = vvl; - out->nh++; - - /* Content-Length: parse; multiple CL headers (even identical) -> BAD. - * Strictness is intentional on this trust boundary. */ - if (slice_ci_eq(nm, nml, "content-length")) { - if (seen_cl) return MQ_HTTP1_BAD; - seen_cl = 1; - int64_t cl; - if (parse_content_length(vv, vvl, &cl) != 0) return MQ_HTTP1_BAD; - out->content_length = cl; - } else if (slice_ci_eq(nm, nml, "transfer-encoding")) { - if (te_lists_chunked(vv, vvl)) out->has_chunked_te = 1; - } - - pos = le + 2; /* past the CRLF */ - } - - out->head_len = hend; - return MQ_HTTP1_DONE; -} - -/* --------------------------------------------------------------------------- - * Response serializers - * ------------------------------------------------------------------------- */ - -/* Size the formatted output first (snprintf to a 0-cap buffer returns the - * length it WOULD write, without touching dst), then only format into dst if - * the whole thing — including the trailing NUL snprintf needs — fits. This - * guarantees "nothing written" on failure, so dst is left byte-for-byte - * untouched. Returns bytes written (excluding NUL) or -1 if it won't fit. */ -int -mq_http1_write_status(char *dst, size_t cap, int code, const char *reason) -{ - int need = snprintf(NULL, 0, "HTTP/1.1 %d %s\r\n", code, reason); - if (need < 0 || (size_t)need + 1 > cap) return -1; - snprintf(dst, cap, "HTTP/1.1 %d %s\r\n", code, reason); - return need; -} - -int -mq_http1_write_header(char *dst, size_t cap, const char *n_, const char *v) -{ - /* Defense-in-depth for the download path: the value (and, less plausibly, - * the name) may carry bytes that originate from the origin's response. A CR - * or LF embedded here would split the serialized response (header injection - * / response splitting); a NUL would truncate it. Reject rather than emit a - * malformed/dangerous header section. (NUL cannot appear inside these C - * strings, but we check defensively in case callers ever pass slices.) */ - for (const char *p = n_; *p; p++) - if (*p == '\r' || *p == '\n') return -1; - for (const char *p = v; *p; p++) - if (*p == '\r' || *p == '\n') return -1; - - int need = snprintf(NULL, 0, "%s: %s\r\n", n_, v); - if (need < 0 || (size_t)need + 1 > cap) return -1; - snprintf(dst, cap, "%s: %s\r\n", n_, v); - return need; -} - -size_t -mq_http1_chunk_frame(char *dst, size_t cap, const uint8_t *p, size_t len) -{ - /* "\r\n\r\n" */ - char hex[20]; /* enough for 64-bit hex */ - int hl = snprintf(hex, sizeof hex, "%zx", len); - if (hl < 0) return 0; - size_t total = (size_t)hl + 2 + len + 2; - if (cap < total) return 0; - - size_t o = 0; - memcpy(dst + o, hex, (size_t)hl); - o += (size_t)hl; - dst[o++] = '\r'; - dst[o++] = '\n'; - if (len) memcpy(dst + o, p, len); - o += len; - dst[o++] = '\r'; - dst[o++] = '\n'; - return o; -} diff --git a/src/gateway/mq_http1.h b/src/gateway/mq_http1.h deleted file mode 100644 index 5dcb76d7..00000000 --- a/src/gateway/mq_http1.h +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_HTTP1_H -#define MQ_HTTP1_H -#include -#include - -/* Minimal, allocation-free HTTP/1.1 request-head parser + response serializer. - * - * This feeds the gateway client's local "fetch API" listener - * (POST /_mqproxy/fetch). It sits on a TRUST BOUNDARY: the bytes come from a - * local-but-untrusted client, so the parser is deliberately strict and rejects - * request-smuggling-prone constructs rather than trying to be lenient: - * - * - obs-fold (header continuation lines starting with SP/HTAB) -> BAD - * - bare CR or NUL inside a header value -> BAD - * - multiple Content-Length headers (even identical) -> BAD - * - non-numeric / overflowing Content-Length -> BAD - * - * Header (name,value) pairs are returned as SLICES into the caller's buffer; - * nothing is copied and no allocations are performed. The caller MUST keep the - * source buffer alive (and unmodified) for as long as it uses the slices. - * - * Duplicate header NAMES are not collapsed: every header line is preserved as a - * slice, in arrival order. Upstream (mq_gw_headers) performs duplicate-name - * detection on the X-Mq-* control headers. - */ - -/* Max size of the request head (request line + headers + the blank line). A - * request whose terminating CRLFCRLF is not seen within this many bytes is - * rejected as BAD (bound on buffering / DoS surface). 16 KiB. */ -#define MQ_HTTP1_MAX_HEAD (16 * 1024) - -#define MQ_HTTP1_MAX_HEADERS 64 - -typedef enum { - MQ_HTTP1_NEED_MORE = 0, /* terminator not yet seen and head still < max */ - MQ_HTTP1_DONE = 1, /* a complete, valid request head; *out is filled */ - MQ_HTTP1_BAD = 2 /* malformed / rejected (see strictness notes above) */ -} mq_http1_status_t; - -typedef struct { - char method[16]; /* NUL-terminated; RFC 7230 token, <= 15 chars */ - char path[1024]; /* NUL-terminated; origin-form, must start with '/' */ - - /* Header (name,value) slices into the caller's buffer. NOT copied. Values - * are OWS-trimmed; names are verified token-only. */ - struct { - const char *n; - size_t nl; - const char *v; - size_t vl; - } h[MQ_HTTP1_MAX_HEADERS]; - size_t nh; - - int64_t content_length; /* parsed Content-Length, or -1 if absent */ - int has_chunked_te; /* 1 if a Transfer-Encoding header listed "chunked" */ - size_t head_len; /* bytes up to AND INCLUDING the terminating blank line */ -} mq_http1_req_t; - -/* Parse a request head from a single contiguous buffer. The caller accumulates - * bytes and passes the whole buffer each call (like mq_http_connect_parse). - * Returns NEED_MORE if no CRLFCRLF terminator yet (and head still under the - * cap), DONE with *out filled on success, or BAD on any rejection. - * - * On CL + TE both present: both content_length and has_chunked_te are set; the - * parser does NOT reject this -- the caller (fetch listener) decides policy - * (e.g. 400). On has_chunked_te set, the caller typically answers 411. */ -mq_http1_status_t mq_http1_parse_req(const uint8_t *buf, size_t len, mq_http1_req_t *out); - -/* Response serializers. Each returns the number of bytes written, or a negative - * value (status/header) / 0 (chunk_frame) if cap is too small -- in which case - * nothing is written. */ - -/* "HTTP/1.1 \r\n" */ -int mq_http1_write_status(char *dst, size_t cap, int code, const char *reason); - -/* ": \r\n" */ -int mq_http1_write_header(char *dst, size_t cap, const char *n, const char *v); - -/* "\r\n\r\n". Returns bytes written, or 0 if cap is - * too small to hold the whole frame. - * - * len==0 emits the "0\r\n\r\n" last-chunk terminator (5 bytes). Callers - * framing a normal body chunk MUST NOT pass len==0 unless they intend to - * signal end-of-chunked-body. */ -size_t mq_http1_chunk_frame(char *dst, size_t cap, const uint8_t *p, size_t len); - -#endif diff --git a/src/gateway/mq_origin_curl.c b/src/gateway/mq_origin_curl.c deleted file mode 100644 index e990f78f..00000000 --- a/src/gateway/mq_origin_curl.c +++ /dev/null @@ -1,717 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_origin_curl.c — libcurl-multi origin client on libevent. See - * mq_origin_curl.h for the integration pattern, ownership, and lifetime. - * - * This is the canonical curl_multi_socket_action ("hiperfifo") glue: - * - * - sock_cb (CURLMOPT_SOCKETFUNCTION): curl tells us which fd it wants to - * read/write; we maintain a per-socket struct event (created lazily, kept - * via curl_multi_assign so curl hands it back on the next interest change) - * and arm/disarm it to match. The event's callback runs - * curl_multi_socket_action(fd, ev_bitmask) then reaps completions. - * - timer_cb (CURLMOPT_TIMERFUNCTION): curl asks us to call socket_action with - * CURL_SOCKET_TIMEOUT after `timeout_ms`; we (re)arm a single evtimer. -1 - * means "no timeout" (disarm). 0 means "as soon as possible" — we still go - * through a 0-delay timer so the action runs OUTSIDE the curl callback. - * - * After every socket_action we drain curl_multi_info_read for CURLMSG_DONE and - * fire on_done → cleanup. on_done is the LAST touch of the request. - */ -#include "gateway/mq_origin_curl.h" - -#include -#include - -#include -#include - -#include "util/mq_log.h" - -/* ── h3-ready runtime detection (design §5.3) ─────────────────────────────── - * curl_global_init runs once process-wide; we also probe curl_version_info for - * HTTP/3 support and remember it so a per-request MQ_HTTP_VER_H3 selection is - * honored only when this libcurl can actually do HTTP/3 (else it falls back to - * the default). System curl 8.5.0 has no HTTP3 — the flag just stays 0 there. */ -static int g_curl_inited; -static int g_curl_has_http3; - -static void -origin_global_init(void) -{ - if (g_curl_inited) return; - g_curl_inited = 1; - curl_global_init(CURL_GLOBAL_DEFAULT); - - curl_version_info_data *vi = curl_version_info(CURLVERSION_NOW); - if (vi) { - g_curl_has_http3 = (vi->features & CURL_VERSION_HTTP3) ? 1 : 0; - MQ_LOGI("mq_origin: libcurl %s (HTTP3=%s)", vi->version ? vi->version : "?", - g_curl_has_http3 ? "yes" : "no"); - } else { - MQ_LOGW("mq_origin: curl_version_info returned NULL"); - } -} - -/* ── per-socket event tracking ────────────────────────────────────────────── - * One sock_ctx per fd curl is interested in. Created on the first interest for - * an fd and re-associated via curl_multi_assign so sock_cb finds it again. */ -typedef struct { - mq_origin_t *o; - curl_socket_t fd; - struct event *ev; /* EV_READ/EV_WRITE | EV_PERSIST, armed to match curl */ -} sock_ctx_t; - -struct mq_origin_s { - struct event_base *base; - CURLM *multi; - struct event *timer_ev; /* single evtimer driven by CURLMOPT_TIMERFUNCTION */ - char *ca_file; /* CURLOPT_CAINFO; NULL → curl default bundle */ - long connect_timeout_s; - int still_running; /* curl's running-handle count from socket_action */ -}; - -struct mq_origin_req_s { - mq_origin_t *o; - CURL *easy; - struct curl_slist *hdrs; /* request header list (freed at teardown) */ - mq_origin_cbs_t cbs; - void *u; - - int upload; /* 1 if this request uploads a body via pull_body */ - - /* Header-section parse state (HEADERFUNCTION). curl delivers headers one - * line at a time, including the status line(s) and the blank terminator. - * We skip 1xx sections entirely and emit on_status once for the final. */ - int status_emitted; /* on_status fired for the final section */ - int in_1xx; /* current section is an informational (1xx) one */ - - /* Pause-state tracking. curl_easy_pause takes the DESIRED pause bitmask (the - * directions that should REMAIN paused), so to resume one direction we clear - * its bit and re-issue the remaining mask. write_cb/read_cb set these when - * they return PAUSE; resume_* clear them. */ - int paused_recv; /* download paused (on_body returned 0) */ - int paused_send; /* upload paused (pull_body returned 0) */ - - /* Deferred-unpause plumbing: calling curl_easy_pause from outside a curl - * callback can re-enter callbacks synchronously, so resume_* sets a flag and - * activates a 0-delay event; the event handler does the real easy_pause. */ - struct event *resume_ev; - int want_resume; /* a resume is pending; on_resume_ev recomputes the mask */ -}; - -/* ── completion reaping ─────────────────────────────────────────────────────*/ - -/* Tear down a request: remove its easy handle from the multi, free the slist - * and per-request events, free the struct. Never fires on_done. */ -static void -req_destroy(mq_origin_req_t *r) -{ - if (!r) return; - if (r->easy) { - curl_multi_remove_handle(r->o->multi, r->easy); - curl_easy_cleanup(r->easy); - r->easy = NULL; - } - if (r->hdrs) { - curl_slist_free_all(r->hdrs); - r->hdrs = NULL; - } - if (r->resume_ev) { - event_free(r->resume_ev); - r->resume_ev = NULL; - } - free(r); -} - -/* Drain DONE messages from the multi handle: for each, recover the request via - * CURLINFO_PRIVATE, capture the negotiated HTTP version, fire on_done, then - * destroy the request. on_done is the LAST touch of r. */ -static void -check_multi_info(mq_origin_t *o) -{ - CURLMsg *m; - int pending; - while ((m = curl_multi_info_read(o->multi, &pending)) != NULL) { - if (m->msg != CURLMSG_DONE) continue; - - CURL *easy = m->easy_handle; - CURLcode result = m->data.result; - - mq_origin_req_t *r = NULL; - curl_easy_getinfo(easy, CURLINFO_PRIVATE, &r); - - long http_ver = 0; - curl_easy_getinfo(easy, CURLINFO_HTTP_VERSION, &http_ver); - long ssl_verify = 0; - curl_easy_getinfo(easy, CURLINFO_SSL_VERIFYRESULT, &ssl_verify); - - long num_connects = 0; - CURLcode nc = curl_easy_getinfo(easy, CURLINFO_NUM_CONNECTS, &num_connects); - curl_off_t appconnect_us = 0; - CURLcode ac = curl_easy_getinfo(easy, CURLINFO_APPCONNECT_TIME_T, &appconnect_us); - curl_off_t connect_us = 0; - CURLcode cc = curl_easy_getinfo(easy, CURLINFO_CONNECT_TIME_T, &connect_us); - - /* origin_reuse: NUM_CONNECTS==0 means no NEW connection was opened => reused. - * Gate on CURLE_OK for BOTH the transfer (result) AND the NUM_CONNECTS getinfo - * (nc): a request that failed before connecting ALSO reports 0 connects, and a - * getinfo failure would leave num_connects at its 0 init (FALSELY reading as - * reuse). origin_connect_ms = origin connection SETUP time in ms: TCP+TLS for an - * https origin (APPCONNECT, measured from start through the TLS handshake), and - * TCP-only for a plain http:// origin (APPCONNECT is 0 there — no TLS leg — so - * fall back to CONNECT_TIME so a fresh http connection is NOT reported as - * 0/reuse). 0 on reuse (no new connection); -1 when unknown / getinfo failed. */ - int origin_reuse = 0; - int origin_connect_ms = -1; - if (result == CURLE_OK && nc == CURLE_OK) { - origin_reuse = (num_connects == 0) ? 1 : 0; - if (origin_reuse) - origin_connect_ms = 0; - else if (ac == CURLE_OK && appconnect_us > 0) - origin_connect_ms = (int)(appconnect_us / 1000); /* TCP+TLS (https) */ - else if (cc == CURLE_OK) - origin_connect_ms = (int)(connect_us / 1000); /* TCP only (plain http) */ - } - - if (r) { - mq_origin_cbs_t cbs = r->cbs; - void *u = r->u; - /* Destroy BEFORE the callback so the request (and its easy handle) - * is gone; on_done is the owner's cue to free its own `u`. The easy - * handle is removed from the multi inside req_destroy, which is safe - * here (we are between info_read calls, not mid-action). */ - req_destroy(r); - if (cbs.on_done) - cbs.on_done((int)result, http_ver, ssl_verify, origin_reuse, - origin_connect_ms, u); - } - } -} - -/* ── socket_action driver ───────────────────────────────────────────────────*/ - -/* Run curl_multi_socket_action for `fd`/`ev_bitmask` (or CURL_SOCKET_TIMEOUT), - * then reap any completions. */ -static void -origin_action(mq_origin_t *o, curl_socket_t fd, int ev_bitmask) -{ - CURLMcode rc = curl_multi_socket_action(o->multi, fd, ev_bitmask, &o->still_running); - if (rc != CURLM_OK) { - MQ_LOGW("mq_origin: curl_multi_socket_action: %s", curl_multi_strerror(rc)); - } - check_multi_info(o); -} - -/* libevent callback for a curl-owned socket. */ -static void -on_socket_ev(evutil_socket_t fd, short what, void *arg) -{ - sock_ctx_t *sc = (sock_ctx_t *)arg; - int flags = 0; - if (what & EV_READ) flags |= CURL_CSELECT_IN; - if (what & EV_WRITE) flags |= CURL_CSELECT_OUT; - origin_action(sc->o, fd, flags); -} - -/* libevent callback for the curl multi timeout. */ -static void -on_timer_ev(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - mq_origin_t *o = (mq_origin_t *)arg; - origin_action(o, CURL_SOCKET_TIMEOUT, 0); -} - -/* CURLMOPT_SOCKETFUNCTION: curl announces (or revokes) read/write interest for - * a socket. We mirror that onto a per-socket struct event. */ -static int -sock_cb(CURL *easy, curl_socket_t fd, int action, void *userp, void *socketp) -{ - (void)easy; - mq_origin_t *o = (mq_origin_t *)userp; - sock_ctx_t *sc = (sock_ctx_t *)socketp; - - if (action == CURL_POLL_REMOVE) { - if (sc) { - if (sc->ev) { - event_del(sc->ev); - event_free(sc->ev); - } - free(sc); - curl_multi_assign(o->multi, fd, NULL); - } - return 0; - } - - if (!sc) { - sc = (sock_ctx_t *)calloc(1, sizeof(*sc)); - if (!sc) return -1; - sc->o = o; - sc->fd = fd; - curl_multi_assign(o->multi, fd, sc); - } - - short kind = EV_PERSIST; - if (action == CURL_POLL_IN) - kind |= EV_READ; - else if (action == CURL_POLL_OUT) - kind |= EV_WRITE; - else if (action == CURL_POLL_INOUT) - kind |= EV_READ | EV_WRITE; - - /* Re-create the event when the interest set changes (the simplest correct - * approach: event flags are fixed at event_new time). */ - if (sc->ev) { - event_del(sc->ev); - event_free(sc->ev); - sc->ev = NULL; - } - sc->ev = event_new(o->base, fd, kind, on_socket_ev, sc); - if (!sc->ev) return -1; - event_add(sc->ev, NULL); - return 0; -} - -/* CURLMOPT_TIMERFUNCTION: curl asks for a single timeout. timeout_ms == -1 means - * disarm; otherwise (re)arm the evtimer. 0 is honored as a near-immediate timer - * so the resulting socket_action runs outside this callback. */ -static int -timer_cb(CURLM *multi, long timeout_ms, void *userp) -{ - (void)multi; - mq_origin_t *o = (mq_origin_t *)userp; - - if (timeout_ms < 0) { - if (o->timer_ev) event_del(o->timer_ev); - return 0; - } - struct timeval tv; - tv.tv_sec = timeout_ms / 1000; - tv.tv_usec = (timeout_ms % 1000) * 1000; - if (o->timer_ev) { - event_del(o->timer_ev); - event_add(o->timer_ev, &tv); - } - return 0; -} - -/* ── construction / teardown ────────────────────────────────────────────────*/ - -mq_origin_t * -mq_origin_new(struct event_base *base, const char *ca_file, long connect_timeout_s) -{ - if (!base) return NULL; - origin_global_init(); - - mq_origin_t *o = (mq_origin_t *)calloc(1, sizeof(*o)); - if (!o) return NULL; - o->base = base; - o->connect_timeout_s = connect_timeout_s; - if (ca_file) { - size_t n = strlen(ca_file) + 1; - o->ca_file = (char *)malloc(n); - if (!o->ca_file) { - free(o); - return NULL; - } - memcpy(o->ca_file, ca_file, n); - } - - o->multi = curl_multi_init(); - if (!o->multi) { - free(o->ca_file); - free(o); - return NULL; - } - o->timer_ev = evtimer_new(base, on_timer_ev, o); - if (!o->timer_ev) { - curl_multi_cleanup(o->multi); - free(o->ca_file); - free(o); - return NULL; - } - - curl_multi_setopt(o->multi, CURLMOPT_SOCKETFUNCTION, sock_cb); - curl_multi_setopt(o->multi, CURLMOPT_SOCKETDATA, o); - curl_multi_setopt(o->multi, CURLMOPT_TIMERFUNCTION, timer_cb); - curl_multi_setopt(o->multi, CURLMOPT_TIMERDATA, o); - return o; -} - -void -mq_origin_free(mq_origin_t *o) -{ - if (!o) return; - if (o->timer_ev) { - event_del(o->timer_ev); - event_free(o->timer_ev); - } - if (o->multi) { - /* Any per-socket events still registered are torn down by curl issuing - * CURL_POLL_REMOVE during cleanup of remaining handles; the gateway is - * expected to have aborted/completed all requests first. */ - curl_multi_cleanup(o->multi); - } - free(o->ca_file); - free(o); -} - -/* ── per-request callbacks (curl easy options) ──────────────────────────────*/ - -/* WRITEFUNCTION: response body. Hand bytes to on_body; if the owner accepts 0, - * pause the download (CURL_WRITEFUNC_PAUSE). Otherwise the owner must accept the - * whole chunk (the documented contract); a partial-but-nonzero accept is a short - * write and curl aborts the transfer. */ -static size_t -write_cb(char *ptr, size_t size, size_t nmemb, void *userp) -{ - mq_origin_req_t *r = (mq_origin_req_t *)userp; - size_t len = size * nmemb; - if (len == 0) return 0; - if (!r->cbs.on_body) return len; /* discard */ - - long accepted = r->cbs.on_body((const uint8_t *)ptr, len, r->u); - if (accepted == 0) { - r->paused_recv = 1; - return CURL_WRITEFUNC_PAUSE; - } - if (accepted < 0 || (size_t)accepted != len) return 0; /* short write → error */ - return len; -} - -/* READFUNCTION: upload source. pull_body returns >0 bytes, 0 = pause - * (CURL_READFUNC_PAUSE), -1 = EOF (return 0 to signal end of upload). */ -static size_t -read_cb(char *buffer, size_t size, size_t nitems, void *userp) -{ - mq_origin_req_t *r = (mq_origin_req_t *)userp; - size_t cap = size * nitems; - if (!r->cbs.pull_body) return 0; /* nothing to upload → EOF */ - - long got = r->cbs.pull_body((uint8_t *)buffer, cap, r->u); - if (got == 0) { - r->paused_send = 1; - return CURL_READFUNC_PAUSE; - } - if (got < 0) return 0; /* EOF */ - if ((size_t)got > cap) got = (long)cap; - return (size_t)got; -} - -/* Trim leading/trailing HTTP whitespace (SP/HTAB) from [p, p+len). */ -static void -trim_ows(const char **p, size_t *len) -{ - const char *s = *p; - size_t n = *len; - while (n > 0 && (s[0] == ' ' || s[0] == '\t')) { - s++; - n--; - } - while (n > 0 && (s[n - 1] == ' ' || s[n - 1] == '\t')) { - n--; - } - *p = s; - *len = n; -} - -/* Parse an HTTP status line into its 3-digit code. Returns the code (100..599) - * or -1 if the line is not a status line. A status line looks like - * "HTTP/x.y SSS reason" or "HTTP/2 SSS ..." (curl normalises HTTP/3 similarly). - * Exposed (non-static) so the header-parse path is unit-testable without a live - * 1xx origin (evhttp can't easily emit 100-continue). */ -int -mq_origin_parse_status_line(const char *line, size_t len) -{ - if (len < 5 || memcmp(line, "HTTP/", 5) != 0) return -1; - /* Skip the version token up to the first space. */ - size_t i = 5; - while (i < len && line[i] != ' ') - i++; - while (i < len && line[i] == ' ') - i++; - if (i + 3 > len) return -1; - if (line[i] < '0' || line[i] > '9' || line[i + 1] < '0' || line[i + 1] > '9' || - line[i + 2] < '0' || line[i + 2] > '9') - return -1; - int code = (line[i] - '0') * 100 + (line[i + 1] - '0') * 10 + (line[i + 2] - '0'); - if (code < 100 || code > 599) return -1; - return code; -} - -/* HEADERFUNCTION: one header line at a time (incl. status lines and the blank - * terminator). curl invokes this for every header section, so a 1xx section - * (e.g. 100-continue) arrives BEFORE the final one. We skip 1xx sections wholly: - * their status line sets in_1xx, their header lines are ignored, and their blank - * terminator just clears in_1xx. The final (non-1xx) status line emits on_status - * once; its subsequent header lines emit on_header. */ -static size_t -header_cb(char *buffer, size_t size, size_t nitems, void *userp) -{ - mq_origin_req_t *r = (mq_origin_req_t *)userp; - size_t len = size * nitems; - size_t raw = len; - - /* Strip the trailing CRLF (or lone LF) for parsing. */ - size_t l = len; - while (l > 0 && (buffer[l - 1] == '\r' || buffer[l - 1] == '\n')) - l--; - - /* Blank line: end of a header section. */ - if (l == 0) { - r->in_1xx = 0; /* a skipped 1xx section ends; the final section's blank - * line is harmless (status already emitted). */ - return raw; - } - - int code = mq_origin_parse_status_line(buffer, l); - if (code >= 0) { - /* A status line. */ - if (code >= 100 && code < 200) { - r->in_1xx = 1; /* skip this whole informational section */ - return raw; - } - r->in_1xx = 0; - if (!r->status_emitted) { - r->status_emitted = 1; - if (r->cbs.on_status) r->cbs.on_status(code, r->u); - } - return raw; - } - - /* A field line. Ignore it if we're inside a skipped 1xx section, or before - * the final status line was seen (defensive — shouldn't happen). */ - if (r->in_1xx || !r->status_emitted) return raw; - - /* Split "name: value". */ - const char *colon = memchr(buffer, ':', l); - if (!colon) return raw; /* not a field line (folded/obs) — ignore */ - const char *name = buffer; - size_t nl = (size_t)(colon - buffer); - const char *val = colon + 1; - size_t vl = l - nl - 1; - trim_ows(&val, &vl); - /* Field names have no surrounding OWS by spec; trim defensively anyway. */ - trim_ows(&name, &nl); - if (nl == 0) return raw; - - if (r->cbs.on_header) r->cbs.on_header(name, nl, val, vl, r->u); - return raw; -} - -/* ── deferred unpause ───────────────────────────────────────────────────────*/ - -/* Apply the (now-updated) pause state to the easy handle. curl_easy_pause takes - * the bitmask of directions that should REMAIN paused; resume_* already cleared - * the bit(s) being resumed, so we just re-issue the current paused_* state. */ -static void -on_resume_ev(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - mq_origin_req_t *r = (mq_origin_req_t *)arg; - if (!r->want_resume) return; - r->want_resume = 0; - if (!r->easy) return; - - int mask = 0; - if (r->paused_recv) mask |= CURLPAUSE_RECV; - if (r->paused_send) mask |= CURLPAUSE_SEND; - /* mask == CURLPAUSE_CONT (0) un-pauses everything. */ - curl_easy_pause(r->easy, mask); - /* easy_pause may have produced new socket activity / a new timeout; kick the - * multi so curl re-evaluates the transfer immediately. */ - origin_action(r->o, CURL_SOCKET_TIMEOUT, 0); -} - -static void -schedule_resume(mq_origin_req_t *r) -{ - r->want_resume = 1; - if (!r->resume_ev) { - r->resume_ev = evtimer_new(r->o->base, on_resume_ev, r); - if (!r->resume_ev) { - /* Fallback: unpause inline (documented risk). Better than a stall. */ - on_resume_ev(-1, 0, r); - return; - } - } - struct timeval z = {0, 0}; - evtimer_add(r->resume_ev, &z); -} - -void -mq_origin_resume_body(mq_origin_req_t *r) -{ - if (!r) return; - r->paused_recv = 0; /* download is no longer paused */ - schedule_resume(r); -} - -void -mq_origin_resume_pull(mq_origin_req_t *r) -{ - if (!r) return; - r->paused_send = 0; /* upload is no longer paused */ - schedule_resume(r); -} - -long -mq_origin_http_ver(const mq_origin_req_t *r) -{ - long ver = CURL_HTTP_VERSION_NONE; - if (r && r->easy) curl_easy_getinfo(r->easy, CURLINFO_HTTP_VERSION, &ver); - return ver; -} - -/* ── start / abort ──────────────────────────────────────────────────────────*/ - -mq_origin_req_t * -mq_origin_start(mq_origin_t *o, const char *url, const char *method, - const mq_h3_header_t *hs, size_t n, int64_t upload_len, - mq_http_ver_t http_version, const mq_origin_cbs_t *cbs, void *u) -{ - if (!o || !url || !method || !cbs) return NULL; - - mq_origin_req_t *r = (mq_origin_req_t *)calloc(1, sizeof(*r)); - if (!r) return NULL; - r->o = o; - r->cbs = *cbs; - r->u = u; - /* Upload if a known length (>=0) OR the chunked sentinel. Any other negative - * means no request body. */ - r->upload = (upload_len >= 0 || upload_len == MQ_ORIGIN_UPLOAD_CHUNKED); - - r->easy = curl_easy_init(); - if (!r->easy) { - free(r); - return NULL; - } - - /* Build the request header list. Always suppress Expect: — the 100-continue - * dance breaks streaming uploads (curl would wait for a 100 before sending - * the body, stalling the pull source). Headers are forwarded verbatim. */ - struct curl_slist *hdrs = NULL; - /* A small stack buffer covers the common case; oversize lines (a big - * Authorization / Cookie the gateway must forward verbatim) heap-allocate - * exactly the needed size rather than being silently dropped. */ - char stackline[1024]; - for (size_t i = 0; i < n; i++) { - if (!hs[i].name) continue; - const char *v = hs[i].value ? hs[i].value : ""; - /* Compute the exact length: strlen(name) + ": " + strlen(value). */ - size_t need = strlen(hs[i].name) + 2 + strlen(v) + 1; /* +1 for NUL */ - char *line = stackline; - char *heapline = NULL; - if (need > sizeof(stackline)) { - heapline = (char *)malloc(need); - if (!heapline) { - curl_slist_free_all(hdrs); - curl_easy_cleanup(r->easy); - free(r); - return NULL; - } - line = heapline; - } - int wn = snprintf(line, need, "%s: %s", hs[i].name, v); - if (wn <= 0 || (size_t)wn >= need) { - free(heapline); - continue; /* defensive: should not happen given `need` is exact */ - } - struct curl_slist *nl = curl_slist_append(hdrs, line); - free(heapline); - if (!nl) { - curl_slist_free_all(hdrs); - curl_easy_cleanup(r->easy); - free(r); - return NULL; - } - hdrs = nl; - } - { - struct curl_slist *nl = curl_slist_append(hdrs, "Expect:"); - if (!nl) { - curl_slist_free_all(hdrs); - curl_easy_cleanup(r->easy); - free(r); - return NULL; - } - hdrs = nl; - } - r->hdrs = hdrs; - - CURL *e = r->easy; - curl_easy_setopt(e, CURLOPT_URL, url); - curl_easy_setopt(e, CURLOPT_CUSTOMREQUEST, method); - curl_easy_setopt(e, CURLOPT_HTTPHEADER, hdrs); - curl_easy_setopt(e, CURLOPT_FOLLOWLOCATION, 0L); - curl_easy_setopt(e, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(e, CURLOPT_CONNECTTIMEOUT, o->connect_timeout_s); - curl_easy_setopt(e, CURLOPT_PRIVATE, r); - - /* TLS verification stays DEFAULT ON; only point at a custom CA if given. */ - if (o->ca_file) curl_easy_setopt(e, CURLOPT_CAINFO, o->ca_file); - - /* Raw passthrough: do NOT advertise Accept-Encoding (no transparent - * decompression — the gateway forwards the origin's bytes verbatim). */ - - /* Origin HTTP version: DEFAULT leaves libcurl to choose (TCP h2/h1 ALPN, no QUIC). - * H3 only if this libcurl has HTTP/3; otherwise fall back to DEFAULT (graceful). */ - switch (http_version) { - case MQ_HTTP_VER_H1: - curl_easy_setopt(e, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_1_1); - break; - case MQ_HTTP_VER_H2: - curl_easy_setopt(e, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); - break; - case MQ_HTTP_VER_H3: - if (g_curl_has_http3) - curl_easy_setopt(e, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_3); - /* else: leave default (graceful fallback) */ - break; - case MQ_HTTP_VER_DEFAULT: - default: break; /* libcurl default */ - } - - curl_easy_setopt(e, CURLOPT_WRITEFUNCTION, write_cb); - curl_easy_setopt(e, CURLOPT_WRITEDATA, r); - curl_easy_setopt(e, CURLOPT_HEADERFUNCTION, header_cb); - curl_easy_setopt(e, CURLOPT_HEADERDATA, r); - - if (r->upload) { - curl_easy_setopt(e, CURLOPT_UPLOAD, 1L); - /* Known length → set INFILESIZE (curl frames an exact-length body). - * Chunked sentinel → leave INFILESIZE unset so curl uploads with chunked - * Transfer-Encoding, pulling until pull_body signals EOF (-1). */ - if (upload_len >= 0) - curl_easy_setopt(e, CURLOPT_INFILESIZE_LARGE, (curl_off_t)upload_len); - curl_easy_setopt(e, CURLOPT_READFUNCTION, read_cb); - curl_easy_setopt(e, CURLOPT_READDATA, r); - } - - CURLMcode mc = curl_multi_add_handle(o->multi, e); - if (mc != CURLM_OK) { - MQ_LOGW("mq_origin: curl_multi_add_handle: %s", curl_multi_strerror(mc)); - curl_slist_free_all(hdrs); - r->hdrs = NULL; - curl_easy_cleanup(e); - r->easy = NULL; - free(r); - return NULL; - } - /* curl_multi_add_handle arms a 0ms timeout via timer_cb; the loop will drive - * the transfer from there. */ - return r; -} - -void -mq_origin_abort(mq_origin_req_t *r) -{ - if (!r) return; - /* No on_done. req_destroy removes the handle from the multi (which triggers - * CURL_POLL_REMOVE for its sockets) and frees everything. */ - req_destroy(r); -} diff --git a/src/gateway/mq_origin_curl.h b/src/gateway/mq_origin_curl.h deleted file mode 100644 index 512e5f60..00000000 --- a/src/gateway/mq_origin_curl.h +++ /dev/null @@ -1,203 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_origin_curl.h — the libcurl-multi origin client, integrated into the - * libevent loop (design §5.3 / §7.4 / §10, Phase 2 Task 4.2). - * - * The gateway SERVER (Task 4.3) receives an H3 request, then replays it to the - * upstream origin through THIS module. One mq_origin_t owns a single - * curl_multi handle bound to a struct event_base via the standard - * curl_multi_socket_action ("hiperfifo") pattern: - * - * - CURLMOPT_SOCKETFUNCTION maps curl's per-socket interest (read/write) onto - * libevent EV_READ/EV_WRITE events; an event fires → - * curl_multi_socket_action(fd) → completions are reaped via - * curl_multi_info_read. - * - CURLMOPT_TIMERFUNCTION drives a single evtimer; when it expires → - * curl_multi_socket_action(CURL_SOCKET_TIMEOUT). - * - * Each in-flight request is an mq_origin_req_t carrying a CURL easy handle. The - * easy handle's CURLOPT_PRIVATE points back at the mq_origin_req_t so a DONE - * message can be reaped to the right request. - * - * ───────────────────────────────────────────────────────────────────────────── - * OWNERSHIP / LIFETIME - * ───────────────────────────────────────────────────────────────────────────── - * - mq_origin_start returns a borrowed pointer. The owner (the gateway server) - * stashes its per-request state in `u`. `u` MUST outlive the request. - * - Exactly ONE of two terminal events ends a request: - * (a) on_done(result, http_ver, ssl_verify, origin_reuse, origin_connect_ms, u) - * fires — the request completed (success - * or error). This is the LAST callback and the LAST time mqproxy - * touches the mq_origin_req_t: it is freed immediately after on_done - * returns. The owner frees its own `u` state from inside on_done. - * (b) mq_origin_abort(r) — the owner cancels. on_done does NOT fire; the - * request is removed from the multi handle and destroyed synchronously. - * After abort the pointer is dangling; the owner must not use it. - * - mq_origin_free destroys the multi handle and the timer event. The owner - * MUST abort or let complete all in-flight requests before freeing the - * origin (free does not walk live requests — it asserts none remain in the - * sense that the multi handle is torn down; outstanding easy handles would - * leak, so the gateway server tracks and aborts them). - * - * ───────────────────────────────────────────────────────────────────────────── - * CALLBACK RE-ENTRANCY CONTRACT (which control ops are legal from inside a cb) - * ───────────────────────────────────────────────────────────────────────────── - * - mq_origin_resume_body / mq_origin_resume_pull: ALWAYS safe from inside any - * callback (on_status / on_header / on_body / pull_body / on_done) AND from - * outside. They never call curl_easy_pause synchronously — they only set a - * flag and arm a 0-delay event, so the actual unpause runs on the next loop - * turn, outside every curl callback. (Calling resume from on_done is a no-op - * race-loser but harmless: the request is torn down right after on_done.) - * - * - mq_origin_abort: may be called from inside on_status / on_header / on_body - * / pull_body — i.e. a SELF-abort of the request whose callback is running. - * Curl supports curl_multi_remove_handle from within a transfer callback, and - * mq_origin_abort performs exactly that (synchronously freeing the request). - * HARD RULE: after a self-abort the callback MUST return IMMEDIATELY and must - * NOT touch the request pointer `r` or its owner context `u` again — both are - * freed the instant mq_origin_abort returns. (on_body should return the value - * it intends; the safest is to abort then `return len` so curl does not also - * flag a short write, but since the handle is already removed the return value - * is moot.) Do NOT call mq_origin_abort from inside on_done — on_done already - * IS the terminal teardown (the request is freed right after it returns); a - * second abort would be a double free. - * - * - mq_origin_start: NOT re-entrancy-special, but note it may be called from - * inside on_done (e.g. to chain a follow-up request) since the completing - * request is fully destroyed before on_done runs. - */ -#ifndef MQ_GATEWAY_MQ_ORIGIN_CURL_H -#define MQ_GATEWAY_MQ_ORIGIN_CURL_H - -#include -#include - -#include "gateway/mq_gw_headers.h" /* mq_http_ver_t (origin HTTP version selection) */ -#include "transport/mq_h3.h" /* mq_h3_header_t (reused for request headers) */ - -struct event_base; - -typedef struct mq_origin_s mq_origin_t; -typedef struct mq_origin_req_s mq_origin_req_t; - -/* Per-request callbacks. `u` is the owner context passed to mq_origin_start. */ -typedef struct { - /* The final (non-1xx) response header section is complete; http_status is - * the status code. Fires exactly once per request. 1xx sections (incl. a - * 100-continue) are skipped entirely and never surface here. */ - void (*on_status)(int http_status, void *u); - - /* One response header (name length nl, value length vl). Name/value are - * borrowed for the duration of the call. The value is whitespace-trimmed. - * Fires only for the final header section, after on_status. */ - void (*on_header)(const char *n, size_t nl, const char *v, size_t vl, void *u); - - /* Response body bytes. Returns the number of bytes ACCEPTED by the owner. - * Returning 0 PAUSES the download (curl stops calling on_body until - * mq_origin_resume_body). Returning < len without 0 is treated by curl as a - * short write → request error. The simplest contract: accept all `len` - * (return len) or accept none (return 0 to pause). */ - long (*on_body)(const uint8_t *p, size_t len, void *u); - - /* Upload source (only used when upload_len >= 0). Fill up to `cap` bytes - * into buf and return the count: > 0 = that many body bytes, 0 = PAUSE the - * upload (resume via mq_origin_resume_pull), -1 = end of upload (EOF). */ - long (*pull_body)(uint8_t *buf, size_t cap, void *u); - - /* Terminal callback. curl_result is the CURLE_* code (0 == CURLE_OK). - * http_ver is the negotiated CURLINFO_HTTP_VERSION value (e.g. - * CURL_HTTP_VERSION_1_1). ssl_verify is CURLINFO_SSL_VERIFYRESULT (0 = - * verified ok; nonzero = verify failure). On a connect/handshake failure - * curl_result is nonzero and ssl_verify may be unset (0) — classify by - * curl_result first. origin_reuse is 1 iff the connection was reused - * (CURLINFO_NUM_CONNECTS==0 on CURLE_OK), 0 otherwise. origin_connect_ms - * is the origin connection setup time in ms: APPCONNECT_TIME_T/1000 (TCP+TLS) - * for an https origin, or CONNECT_TIME_T/1000 (TCP only) for a plain http - * origin where APPCONNECT is 0; 0 on reuse, -1 when unknown (failed before - * connect, or getinfo error). Sub-millisecond setups round to 0. This is the - * LAST touch of the request: the mq_origin_req_t is freed right after - * this returns. Fires for completion and error, but NOT for - * mq_origin_abort. */ - void (*on_done)(int curl_result, long http_ver, long ssl_verify, int origin_reuse, - int origin_connect_ms, void *u); -} mq_origin_cbs_t; - -/* Create an origin client bound to `base`. ca_file (nullable) sets CURLOPT_CAINFO - * for TLS verification; verification ALWAYS stays on (never disabled). - * connect_timeout_s sets CURLOPT_CONNECTTIMEOUT per request. Returns NULL on - * failure. Calls curl_global_init once process-wide (idempotent guard). */ -mq_origin_t *mq_origin_new(struct event_base *base, const char *ca_file, - long connect_timeout_s); - -/* Destroy the origin: tears down the multi handle and the timer event. All - * in-flight requests must already be terminated (on_done) or aborted. */ -void mq_origin_free(mq_origin_t *o); - -/* Sentinel for mq_origin_start's upload_len: a request body IS present but its - * length is not known up front (no Content-Length). curl uploads it with - * chunked Transfer-Encoding (CURLOPT_UPLOAD=1, no INFILESIZE), pulling bytes - * from cbs->pull_body until pull_body returns -1 (EOF). Distinct from a plain - * negative (no body): only this exact value enables the bodied-but-lengthless - * upload, so existing `upload_len < 0 == no body` callers are unaffected. */ -#define MQ_ORIGIN_UPLOAD_CHUNKED ((int64_t) - 2) - -/* Start a request. url is the absolute origin URL; method is the HTTP method - * verb. hs[0..n) are request headers (NUL-terminated name/value C strings, - * mq_h3_header_t). - * - * PRECONDITION: every header NAME and VALUE must be free of CR and LF (and NUL, - * implied by C-string termination). curl serializes "name: value\r\n" onto the - * wire verbatim, so a CR/LF in a caller-supplied value would split the request - * (header injection / request smuggling). The gateway server enforces this - * upstream (mq_gw_hdr_name_ok / mq_gw_hdr_value_ok reject control bytes before - * a header reaches here). As a SECOND line of defense, libcurl >= 7.84.0 itself - * strips CR/LF from header values passed via CURLOPT_HTTPHEADER — this version - * floor is load-bearing: on an older libcurl the caller-side check above is the - * only barrier. - * - * upload semantics by upload_len: - * >= 0 → upload exactly that many bytes (sets INFILESIZE) - * sourced from cbs->pull_body. - * MQ_ORIGIN_UPLOAD_CHUNKED → upload a body of unknown length via chunked TE, - * sourced from cbs->pull_body (EOF = pull -1). - * any other negative (e.g. -1) → no request body. - * http_version selects the origin HTTP version: MQ_HTTP_VER_DEFAULT leaves - * libcurl's own ALPN choice (TCP h2/h1, no QUIC); H1/H2 force that version; - * MQ_HTTP_VER_H3 is honored only on an HTTP/3-capable libcurl, else it silently - * falls back to DEFAULT (see mq_http_ver_t). - * cbs/u carry the response callbacks and owner context. Returns the request, or - * NULL on failure (no callback fires on a NULL return). */ -mq_origin_req_t *mq_origin_start(mq_origin_t *o, const char *url, const char *method, - const mq_h3_header_t *hs, size_t n, int64_t upload_len, - mq_http_ver_t http_version, const mq_origin_cbs_t *cbs, - void *u); - -/* Un-PAUSE the download side after on_body returned 0. Safe to call any time the - * request is live; the actual curl_easy_pause runs deferred (next loop turn) to - * avoid re-entering curl callbacks synchronously. */ -void mq_origin_resume_body(mq_origin_req_t *r); - -/* Un-PAUSE the upload side after pull_body returned 0. Deferred like - * mq_origin_resume_body. */ -void mq_origin_resume_pull(mq_origin_req_t *r); - -/* The negotiated origin HTTP version (CURLINFO_HTTP_VERSION). Valid once the - * response headers have been parsed — i.e. from on_status / on_header / on_body - * onward. Returns CURL_HTTP_VERSION_NONE before that or on error. The on_done - * callback already delivers the final value; this getter lets the owner read it - * EARLIER (during on_body) to synthesize a response-side diagnostic before the - * response headers go on the wire (after which on_done would be too late). */ -long mq_origin_http_ver(const mq_origin_req_t *r); - -/* Cancel a request: remove it from the multi handle and destroy it WITHOUT - * firing on_done. The pointer is dangling afterward. */ -void mq_origin_abort(mq_origin_req_t *r); - -/* Parse an HTTP status line ("HTTP/x.y SSS reason") and return its 3-digit code - * (100..599), or -1 if the line is not a status line. Exposed for unit-testing - * the 1xx-skip path directly: a live origin (evhttp) cannot easily emit a - * 100-continue, so the test drives this parser with synthetic status lines. */ -int mq_origin_parse_status_line(const char *line, size_t len); - -#endif /* MQ_GATEWAY_MQ_ORIGIN_CURL_H */ diff --git a/src/ingress/mq_clienthello.c b/src/ingress/mq_clienthello.c deleted file mode 100644 index e94b7fbb..00000000 --- a/src/ingress/mq_clienthello.c +++ /dev/null @@ -1,420 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// mq_clienthello_parse — bounds-checked TLS ClientHello SNI/ALPN peek. -// -// SECURITY: `buf`/`len` is attacker-controlled network input. Every length and -// offset read is guarded against `len`; the parser NEVER reads past `len` and -// NEVER allocates. A claimed inner length that runs past `len` while the -// enclosing record is itself incomplete => NEED_MORE (read more). A length that -// overruns a COMPLETE enclosing field (record fragment / handshake body) is a -// structural impossibility => INVALID. -// -// REASSEMBLY (RFC 8446 §5.1): a single handshake message (the ClientHello) MAY -// be fragmented across multiple consecutive TLS handshake records -// (content_type==22). We therefore present the handshake to the parser as a -// LOGICAL byte stream spanning all such records IN PLACE — no allocation: the -// cursor walks records and transparently skips each 5-byte record header when a -// read crosses a fragment boundary. The caller accumulates raw bytes into its -// own bounded (8 KiB) buffer and re-calls us on the growing buffer, so a -// handshake that is not yet fully present across the buffered records => NEED_MORE. -// A non-handshake record (content_type != 22) appearing while the ClientHello is -// still incomplete is a protocol violation => INVALID. -#include "ingress/mq_clienthello.h" -#include - -#define MQ_CH_CAP 8192u /* hard upper bound on accumulated bytes */ -#define MQ_CH_SNI_MAX 253u /* DNS hostname max; +1 NUL must fit sni[256] */ -#define TLS_CONTENT_HANDSHAKE 22 -#define TLS_HS_CLIENT_HELLO 1 -#define TLS_EXT_SERVER_NAME 0x0000 -#define TLS_EXT_ALPN 0x0010 -#define TLS_SNI_HOST_NAME 0x00 - -/* A bounds-checked cursor over [base, base+limit). Every read advances `pos` - * only after confirming `limit` covers it; reads never touch base+limit or - * beyond. `limit` is the count of bytes KNOWN to be present (<= len). Used for - * the inner sub-parses (extensions, SNI/ALPN entries) that operate over a single - * already-validated contiguous span. */ -typedef struct { - const uint8_t *base; - size_t limit; - size_t pos; -} cur_t; - -/* True iff `n` more bytes are available from the current position. */ -static int -cur_have(const cur_t *c, size_t n) -{ - return n <= c->limit - c->pos; /* c->pos <= c->limit always holds */ -} - -static uint8_t -cur_u8(cur_t *c) -{ - return c->base[c->pos++]; -} - -static uint16_t -cur_u16(cur_t *c) -{ - uint16_t v = (uint16_t)((c->base[c->pos] << 8) | c->base[c->pos + 1]); - c->pos += 2; - return v; -} - -/* ---- record-spanning handshake reader ---- - * - * `hs_t` presents the handshake-record fragments of `buf` as one logical byte - * stream, copying needed bytes into a small fixed scratch as it reads so that a - * field straddling a record boundary is read seamlessly. It NEVER reads past - * `len` and NEVER allocates. State machine results are encoded by the readers: - * a short read returns an error code via the caller's bounds checks. - * - * buf,len the full raw accumulated buffer (hard bound). - * rec_off offset in buf of the CURRENT record header (content_type). - * frag_pos bytes of the current record's fragment already consumed. - * frag_len the current record's declared fragment length. - * incomplete set when more raw bytes are needed to satisfy a read - * (record header or fragment not fully present) => NEED_MORE. - * bad set when a structural impossibility is hit (non-handshake - * record mid-message, zero-length fragment) => INVALID. */ -typedef struct { - const uint8_t *buf; - size_t len; - size_t rec_off; - size_t frag_pos; - size_t frag_len; - int incomplete; - int bad; -} hs_t; - -/* Position the reader at the first handshake record. The first record header is - * validated by the caller before this is invoked, so here we only seed state. */ -static void -hs_init(hs_t *h, const uint8_t *buf, size_t len, size_t rec_off, size_t frag_len) -{ - h->buf = buf; - h->len = len; - h->rec_off = rec_off; - h->frag_len = frag_len; - h->frag_pos = 0; - h->incomplete = 0; - h->bad = 0; -} - -/* Ensure the current record has at least one unconsumed fragment byte. If the - * current fragment is exhausted, advance to the NEXT record: parse its 5-byte - * header, require content_type==22, and require its declared fragment to be - * present in `buf`. Sets h->incomplete (need more bytes) or h->bad (protocol - * violation) on failure. Returns 1 if a fragment byte is available, else 0. */ -static int -hs_ensure(hs_t *h) -{ - if (h->bad || h->incomplete) { - return 0; - } - while (h->frag_pos == h->frag_len) { - /* Current fragment drained — try to enter the next record. */ - size_t next = h->rec_off + 5 + h->frag_len; - /* next == h->len means the buffered records ended exactly on a fragment - * boundary with the handshake still unsatisfied => need more records. */ - if (next + 5 > h->len) { - h->incomplete = 1; /* next record header not fully present yet */ - return 0; - } - if (h->buf[next] != TLS_CONTENT_HANDSHAKE) { - h->bad = 1; /* non-handshake record mid-ClientHello => INVALID */ - return 0; - } - size_t flen = ((size_t)h->buf[next + 3] << 8) | h->buf[next + 4]; - if (flen == 0) { - h->bad = 1; /* zero-length record fragment is structurally invalid */ - return 0; - } - if (next + 5 + flen > MQ_CH_CAP) { - h->bad = 1; /* record layer would exceed the hard cap */ - return 0; - } - if (next + 5 + flen > h->len) { - h->incomplete = 1; /* fragment not fully present yet => need more */ - return 0; - } - h->rec_off = next; - h->frag_len = flen; - h->frag_pos = 0; - } - return 1; -} - -/* Read `n` logical handshake bytes into `dst` (which must hold `n`). - * Returns 1 on success; on failure returns 0 with h->incomplete or h->bad set. - * Spans record boundaries transparently — used both for the small fixed-size - * header/length reads and for copying the whole handshake body out. */ -static int -hs_read(hs_t *h, uint8_t *dst, size_t n) -{ - size_t got = 0; - while (got < n) { - if (!hs_ensure(h)) { - return 0; - } - size_t avail = h->frag_len - h->frag_pos; - size_t take = n - got < avail ? n - got : avail; - memcpy(dst + got, h->buf + h->rec_off + 5 + h->frag_pos, take); - h->frag_pos += take; - got += take; - } - return 1; -} - -static int -hs_u8(hs_t *h, uint8_t *out) -{ - return hs_read(h, out, 1); -} - -static int -hs_u24(hs_t *h, size_t *out) -{ - uint8_t b[3]; - if (!hs_read(h, b, 3)) { - return 0; - } - *out = ((size_t)b[0] << 16) | ((size_t)b[1] << 8) | (size_t)b[2]; - return 1; -} - -mq_ch_result_t -mq_clienthello_parse(const uint8_t *buf, size_t len, mq_clienthello_t *out) -{ - /* `out` is always zeroed first so callers may read it after any result. */ - if (out) { - memset(out, 0, sizeof(*out)); - } - if (buf == NULL || out == NULL) { - return MQ_CH_INVALID; - } - /* DoS / runaway guard: if we have accumulated more than the cap and still - * cannot complete, refuse rather than loop. */ - if (len > MQ_CH_CAP) { - return MQ_CH_INVALID; - } - - /* ---- TLS record layer ---- */ - /* Need 5-byte record header: content_type(1) version(2) length(2). */ - if (len < 5) { - /* A handshake record begins with 0x16; if the first byte is present - * and not 0x16 this is not a TLS handshake flow at all. */ - if (len >= 1 && buf[0] != TLS_CONTENT_HANDSHAKE) { - return MQ_CH_NOT_TLS; - } - return MQ_CH_NEED_MORE; - } - if (buf[0] != TLS_CONTENT_HANDSHAKE) { - return MQ_CH_NOT_TLS; - } - /* buf[1..2] legacy_version — not validated (TLS 1.3 keeps it at 0x0301). */ - size_t frag_len = ((size_t)buf[3] << 8) | buf[4]; - if (frag_len == 0) { - return MQ_CH_INVALID; - } - /* The record fragment is the enclosing field for the handshake. If fewer - * than frag_len bytes are present, the record is incomplete => NEED_MORE - * (never read past what we have). The total record size cannot exceed the - * cap either. */ - if (5 + frag_len > MQ_CH_CAP) { - return MQ_CH_INVALID; - } - if (len < 5 + frag_len) { - return MQ_CH_NEED_MORE; - } - - /* ---- Handshake layer (reassembled across handshake records) ---- - * The handshake message MAY span several content_type==22 records. Walk the - * logical handshake byte stream via `hs_t`, which transparently skips record - * headers and surfaces "need more bytes" vs "structurally invalid". */ - hs_t h; - hs_init(&h, buf, len, /*rec_off=*/0, frag_len); - - uint8_t msg_type; - if (!hs_u8(&h, &msg_type)) { - if (h.bad) return MQ_CH_INVALID; - return MQ_CH_NEED_MORE; /* header straddles into a not-yet-present record */ - } - if (msg_type != TLS_HS_CLIENT_HELLO) { - return MQ_CH_INVALID; /* valid TLS record but not a ClientHello */ - } - size_t hs_len; - if (!hs_u24(&h, &hs_len)) { - if (h.bad) return MQ_CH_INVALID; - return MQ_CH_NEED_MORE; - } - /* LOAD-BEARING bound for the body[MQ_CH_CAP] copy below: hs_len is a 24-bit - * field (up to ~16M), so reject any declared handshake body that cannot fit - * the fixed scratch BEFORE copying. A body structurally too large to ever fit - * is INVALID; one merely not fully buffered yet is NEED_MORE (distinguished by - * walking the records). On a 64-bit target 4+hs_len cannot wrap. */ - if (4 + hs_len > MQ_CH_CAP) { - return MQ_CH_INVALID; - } - /* Copy the (possibly record-spanning) handshake body into a fixed scratch so - * the contiguous extension parser below is unchanged. Allocation-free: the - * body is bounded by MQ_CH_CAP (checked just above). */ - uint8_t body[MQ_CH_CAP]; - if (!hs_read(&h, body, hs_len)) { - if (h.bad) return MQ_CH_INVALID; - return MQ_CH_NEED_MORE; /* body not yet fully present across records */ - } - - /* The reassembled body is now a complete, contiguous enclosing field: inner - * overruns of it are structural impossibilities => INVALID. */ - cur_t c = {.base = body, .limit = hs_len, .pos = 0}; - - /* ---- ClientHello body ---- */ - /* legacy_version(2) + random(32) */ - if (!cur_have(&c, 2 + 32)) { - return MQ_CH_INVALID; - } - c.pos += 2 + 32; - /* session_id: u8 length + bytes */ - if (!cur_have(&c, 1)) { - return MQ_CH_INVALID; - } - { - uint8_t sid = cur_u8(&c); - if (!cur_have(&c, sid)) { - return MQ_CH_INVALID; - } - c.pos += sid; - } - /* cipher_suites: u16 length + bytes */ - if (!cur_have(&c, 2)) { - return MQ_CH_INVALID; - } - { - uint16_t cs = cur_u16(&c); - if (!cur_have(&c, cs)) { - return MQ_CH_INVALID; - } - c.pos += cs; - } - /* compression_methods: u8 length + bytes */ - if (!cur_have(&c, 1)) { - return MQ_CH_INVALID; - } - { - uint8_t cm = cur_u8(&c); - if (!cur_have(&c, cm)) { - return MQ_CH_INVALID; - } - c.pos += cm; - } - /* extensions: optional. TLS <1.2 ClientHello may legally omit the block. - * If nothing follows compression, there are simply no extensions => OK with - * empty sni / no alpn. */ - if (!cur_have(&c, 2)) { - if (c.pos == c.limit) { - return MQ_CH_OK; /* well-formed body, no extensions */ - } - return MQ_CH_INVALID; /* trailing byte(s) too short to be a length */ - } - uint16_t ext_total = cur_u16(&c); - if (!cur_have(&c, ext_total)) { - return MQ_CH_INVALID; /* extensions block overruns the handshake body */ - } - /* Bound the cursor to exactly the extensions block. */ - c.limit = c.pos + ext_total; - - /* ---- iterate extensions ---- */ - while (c.pos < c.limit) { - if (!cur_have(&c, 4)) { - return MQ_CH_INVALID; /* dangling bytes too short for ext header */ - } - uint16_t ext_type = cur_u16(&c); - uint16_t ext_len = cur_u16(&c); - if (!cur_have(&c, ext_len)) { - return MQ_CH_INVALID; /* extension data overruns the ext block */ - } - /* Sub-cursor scoped to this extension's data. */ - size_t ext_start = c.pos; - size_t ext_end = c.pos + ext_len; - - if (ext_type == TLS_EXT_SERVER_NAME && ext_len > 0) { - cur_t e = {.base = c.base, .limit = ext_end, .pos = ext_start}; - /* ServerNameList: u16 list length, then entries. */ - if (cur_have(&e, 2)) { - uint16_t list_len = cur_u16(&e); - size_t list_end = e.pos + list_len; - if (list_end <= ext_end) { - e.limit = list_end; - /* Take the first host_name (type 0) entry. Inner - * malformations here are NON-FATAL: the enclosing - * server_name extension framing is already length-validated - * (ext_len / list_end bounded above), so a truncated sub- - * field degrades to empty-SNI rather than rejecting the - * whole flow. Hence `break` (leave sni empty), not return. */ - while (e.pos < e.limit) { - if (!cur_have(&e, 1)) { - break; - } - uint8_t name_type = cur_u8(&e); - if (!cur_have(&e, 2)) { - break; - } - uint16_t name_len = cur_u16(&e); - if (!cur_have(&e, name_len)) { - break; - } - if (name_type == TLS_SNI_HOST_NAME) { - if (name_len > 0 && name_len <= MQ_CH_SNI_MAX) { - memcpy(out->sni, e.base + e.pos, name_len); - out->sni[name_len] = '\0'; - } - /* else: over-long / empty host_name → leave sni - * empty (skip), per the >253 reject rule. */ - break; /* first host_name wins */ - } - e.pos += name_len; - } - } - } - } else if (ext_type == TLS_EXT_ALPN) { - out->has_alpn = 1; - cur_t e = {.base = c.base, .limit = ext_end, .pos = ext_start}; - /* ProtocolNameList: u16 list length, then length-prefixed names. */ - if (cur_have(&e, 2)) { - uint16_t list_len = cur_u16(&e); - size_t list_end = e.pos + list_len; - if (list_end <= ext_end) { - e.limit = list_end; - /* As with SNI above, inner malformations are NON-FATAL: the - * ALPN extension framing is already length-validated, so a - * truncated protocol-name sub-field degrades to no-h2 rather - * than rejecting the whole flow. Hence `break`, not return. */ - while (e.pos < e.limit) { - if (!cur_have(&e, 1)) { - break; - } - uint8_t pn_len = cur_u8(&e); - if (!cur_have(&e, pn_len)) { - break; - } - /* Exact 2-byte "h2" — length-prefixed, so no substring - * confusion with "h2c"/"h3". */ - if (pn_len == 2 && e.base[e.pos] == 'h' && - e.base[e.pos + 1] == '2') { - out->has_h2 = 1; - } - e.pos += pn_len; - } - } - } - } - - /* Advance to the next extension regardless of inner parse outcome. */ - c.pos = ext_end; - } - - return MQ_CH_OK; -} diff --git a/src/ingress/mq_clienthello.h b/src/ingress/mq_clienthello.h deleted file mode 100644 index 95994cf2..00000000 --- a/src/ingress/mq_clienthello.h +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -#ifndef MQ_CLIENTHELLO_H -#define MQ_CLIENTHELLO_H -#include -#include - -/* Result of peeking at an (possibly partial) TLS ClientHello byte stream. - * MQ_CH_OK sni (may be empty) + alpn flags extracted. - * MQ_CH_NEED_MORE record/handshake incomplete — caller should read more bytes. - * MQ_CH_NOT_TLS first byte is not a TLS handshake record (e.g. plaintext - * HTTP, SSLv2). HARD-FAIL: this is not a TLS flow. - * MQ_CH_INVALID structurally malformed TLS (impossible field, overrun of a - * COMPLETE enclosing field, or 8 KiB cap exceeded). HARD-FAIL. */ -typedef enum { - MQ_CH_OK = 0, - MQ_CH_NEED_MORE = 1, - MQ_CH_NOT_TLS = -1, - MQ_CH_INVALID = -2 -} mq_ch_result_t; - -typedef struct { - char sni[256]; /* NUL-terminated server_name (host_name type 0); empty if absent */ - int has_h2; /* 1 if the ALPN offer list contains the exact token "h2" */ - int has_alpn; /* 1 if an ALPN extension was present at all */ -} mq_clienthello_t; - -/* Parse a (possibly partial) TLS plaintext byte stream. buf/len = the bytes - * accumulated from the client so far. Pure function: no allocation, never reads - * past `len`, never blocks. `out` is fully populated only on MQ_CH_OK (and is - * always zero-initialised first, so it is safe to read after any result). The - * raw host_name bytes are copied verbatim (NOT lowercased/normalised — the - * caller normalises via mq_mitm_normalize_sni). An over-long host_name (>253) - * is skipped, leaving sni empty. */ -mq_ch_result_t mq_clienthello_parse(const uint8_t *buf, size_t len, - mq_clienthello_t *out); - -#endif diff --git a/src/ingress/mq_http_connect.c b/src/ingress/mq_http_connect.c deleted file mode 100644 index f36682ec..00000000 --- a/src/ingress/mq_http_connect.c +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "ingress/mq_http_connect.h" -#include -#include - -/* Find the end (one past the last byte) of the first "\r\n\r\n" terminator in - buf[0..len). Returns 0 if not found. */ -static size_t -find_header_end(const uint8_t *buf, size_t len) -{ - if (len < 4) return 0; - for (size_t i = 0; i + 4 <= len; i++) { - if (buf[i] == '\r' && buf[i + 1] == '\n' && buf[i + 2] == '\r' && - buf[i + 3] == '\n') { - return i + 4; - } - } - return 0; -} - -/* Parse a decimal port from text[0..len). Returns 0 on any error (0 is never a - valid CONNECT port). */ -static uint16_t -parse_port(const char *text, size_t len) -{ - if (len == 0 || len > 5) return 0; - uint32_t v = 0; - for (size_t i = 0; i < len; i++) { - char c = text[i]; - if (c < '0' || c > '9') return 0; - v = v * 10 + (uint32_t)(c - '0'); - } - if (v < 1 || v > 65535) return 0; - return (uint16_t)v; -} - -mq_http_status_t -mq_http_connect_parse(const uint8_t *buf, size_t len, mq_http_target_t *out, - size_t *header_len) -{ - size_t hend = find_header_end(buf, len); - if (hend == 0) return MQ_HTTP_NEED_MORE; - - /* The request line ends at the first CRLF within [0, hend). */ - size_t line_end = 0; /* index of '\r' ending the request line */ - for (size_t i = 0; i + 1 < hend; i++) { - if (buf[i] == '\r' && buf[i + 1] == '\n') { - line_end = i; - break; - } - } - /* hend always contains a CRLF, so line_end is valid (could be 0 only for an - empty first line, which has no method => BAD). */ - - const char *line = (const char *)buf; - size_t llen = line_end; - - /* METHOD SP TARGET SP HTTP/x.y */ - /* find first space (end of method) */ - size_t m_end = 0; - while (m_end < llen && line[m_end] != ' ') - m_end++; - if (m_end == 0 || m_end >= llen) return MQ_HTTP_BAD; /* no method or no target */ - - /* method check */ - if (m_end != 7 || memcmp(line, "CONNECT", 7) != 0) return MQ_HTTP_UNSUPPORTED; - - /* target starts after the space */ - size_t t_start = m_end + 1; - /* find next space (end of target / start of version) */ - size_t t_end = t_start; - while (t_end < llen && line[t_end] != ' ') - t_end++; - if (t_end >= llen) return MQ_HTTP_BAD; /* no version field => malformed line */ - if (t_end == t_start) return MQ_HTTP_BAD; /* empty target */ - - /* version field must be non-empty and look like HTTP/ */ - size_t v_start = t_end + 1; - if (v_start >= llen) return MQ_HTTP_BAD; - if (llen - v_start < 5 || memcmp(line + v_start, "HTTP/", 5) != 0) return MQ_HTTP_BAD; - - /* target = host:port (authority-form). Split on the LAST colon. */ - const char *target = line + t_start; - size_t tlen = t_end - t_start; - - size_t colon = tlen; /* index of last colon within target, or tlen if none */ - for (size_t i = tlen; i > 0; i--) { - if (target[i - 1] == ':') { - colon = i - 1; - break; - } - } - if (colon == tlen) return MQ_HTTP_BAD; /* no port */ - - const char *host = target; - size_t host_len = colon; - const char *port_text = target + colon + 1; - size_t port_len = tlen - colon - 1; - - uint16_t port = parse_port(port_text, port_len); - if (port == 0) return MQ_HTTP_BAD; - - if (host_len == 0) return MQ_HTTP_BAD; - - /* Bracketed IPv6 literal: [ ... ] - If the host begins with '[', it MUST end with ']'; otherwise it is a - malformed authority (unclosed bracket) and we reject it immediately. - We also reject if the bracketed inner text is not a valid IPv6 address. */ - if (host[0] == '[') { - if (host[host_len - 1] != ']') return MQ_HTTP_BAD; - size_t inner_len = host_len - 2; - if (inner_len == 0 || inner_len > 45) return MQ_HTTP_BAD; - char tmp[46]; - memcpy(tmp, host + 1, inner_len); - tmp[inner_len] = '\0'; - uint8_t raw[16]; - if (inet_pton(AF_INET6, tmp, raw) != 1) return MQ_HTTP_BAD; - out->atype = MQ_ADDR_IPV6; - memcpy(out->host, raw, 16); - out->host_len = 16; - out->port = port; - *header_len = hend; - return MQ_HTTP_CONNECT_DONE; - } - - if (host_len > MQ_MAX_HOST) return MQ_HTTP_BAD; - - /* Try dotted-quad IPv4. inet_pton needs a NUL-terminated string; an IPv4 - literal is at most 15 chars. */ - if (host_len <= 15) { - char tmp[16]; - memcpy(tmp, host, host_len); - tmp[host_len] = '\0'; - uint8_t raw[4]; - if (inet_pton(AF_INET, tmp, raw) == 1) { - out->atype = MQ_ADDR_IPV4; - memcpy(out->host, raw, 4); - out->host_len = 4; - out->port = port; - *header_len = hend; - return MQ_HTTP_CONNECT_DONE; - } - } - - /* Otherwise: domain name text. */ - out->atype = MQ_ADDR_DOMAIN; - memcpy(out->host, host, host_len); - out->host_len = host_len; - out->port = port; - *header_len = hend; - return MQ_HTTP_CONNECT_DONE; -} - -size_t -mq_http_build_200(char *out, size_t cap) -{ - static const char msg[] = "HTTP/1.1 200 Connection Established\r\n\r\n"; - size_t n = sizeof(msg) - 1; - if (cap < n) return 0; - memcpy(out, msg, n); - return n; -} - -const char * -mq_http_status_line(mq_tcp_err_t err) -{ - switch (err) { - case MQ_TCP_TIMEOUT: return "HTTP/1.1 504 Gateway Timeout\r\n\r\n"; - case MQ_TCP_POLICY_DENIED: return "HTTP/1.1 403 Forbidden\r\n\r\n"; - case MQ_TCP_DNS_FAILED: - case MQ_TCP_CONN_REFUSED: - case MQ_TCP_OK: - default: return "HTTP/1.1 502 Bad Gateway\r\n\r\n"; - } -} diff --git a/src/ingress/mq_http_connect.h b/src/ingress/mq_http_connect.h deleted file mode 100644 index 0c9ee2bd..00000000 --- a/src/ingress/mq_http_connect.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_HTTP_CONNECT_H -#define MQ_HTTP_CONNECT_H -#include "wire/mq_wire.h" -#include -#include - -typedef enum { - MQ_HTTP_NEED_MORE, /* headers not yet complete */ - MQ_HTTP_CONNECT_DONE, /* parsed a CONNECT request; out valid */ - MQ_HTTP_BAD, /* malformed request line */ - MQ_HTTP_UNSUPPORTED /* not a CONNECT method */ -} mq_http_status_t; - -typedef struct { - mq_addr_type_t atype; /* DOMAIN, or IPV6 for a bracketed literal, or IPV4 for - dotted-quad */ - uint8_t host[MQ_MAX_HOST]; - size_t host_len; /* for DOMAIN: the hostname text bytes; - for IPV4: 4 raw bytes; for IPV6: 16 raw bytes */ - uint16_t port; -} mq_http_target_t; - -/* Feed accumulated request bytes (caller keeps the running buffer and passes the whole - thing each call). Parses from a single contiguous buffer; returns NEED_MORE if no - CRLFCRLF yet. On CONNECT_DONE, *out is filled and *header_len is set to the total - bytes of the request head (through the final CRLFCRLF). */ -mq_http_status_t mq_http_connect_parse(const uint8_t *buf, size_t len, - mq_http_target_t *out, size_t *header_len); - -/* response builders */ -size_t mq_http_build_200(char *out, size_t cap); /* returns len written */ -const char *mq_http_status_line(mq_tcp_err_t err); -#endif diff --git a/src/ingress/mq_ingress.h b/src/ingress/mq_ingress.h deleted file mode 100644 index f739b26c..00000000 --- a/src/ingress/mq_ingress.h +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_INGRESS_H -#define MQ_INGRESS_H -#include "wire/mq_wire.h" -#include -#include -/* delivered asynchronously when the proxy core has a result for a tcp_open */ -typedef void (*mq_tcp_open_cb)(int ok, mq_tcp_err_t err, void *user); -/* implemented later by mq_client; ingress calls this and never touches xquic. - * - * prebuf/prebuf_len carry app bytes that arrived in the SAME read as the request - * head (e.g. a TLS ClientHello pipelined behind the SOCKS5/HTTP CONNECT request). - * The ingress has already consumed them off local_fd, so they cannot be re-read - * from the socket; the core MUST relay them toward the origin ahead of any fresh - * local_fd reads or they are silently dropped. prebuf is borrowed (valid only - * for the duration of the call); the core copies what it needs. prebuf_len may - * be 0 (prebuf then NULL or ignored). */ -typedef void (*mq_tcp_open_fn)(void *core, const uint8_t *host, size_t host_len, - mq_addr_type_t atype, uint16_t port, int local_fd, - const uint8_t *prebuf, size_t prebuf_len, void *user, - mq_tcp_open_cb cb); - -/* UDP session boundary. The ingress layer (mq_udp_assoc) has no knowledge of - * xquic; all QUIC interaction is hidden behind these function-pointer hooks. - * - * open returns a session handle SYNCHRONOUSLY (local allocation only — it does - * not wait for auth or RESP). NULL means immediate failure (session limit hit / - * negative-cache hit / pre-auth queue full). A non-NULL handle may be passed to - * send immediately (optimistic send). - * Remote-side failures and terminations (RESP error / stream close / idle - * timeout) are reported back via on_err AT MOST ONCE; after that the handle is - * invalid (the caller MUST NOT call send or close — close is unnecessary because - * the core has already freed the session). Caller-initiated termination uses - * close. - * - * CALLBACK SUPPRESSION CONTRACT — required to prevent use-after-free: - * - After close returns, on_rx and on_err for that session are NEVER called - * again (close completes callback detachment synchronously). - * - The same guarantee holds if close is called re-entrantly during an on_err - * or on_rx dispatch (the core marks the session as closing and suppresses - * all further user callbacks for it). - * - Deferred events that arrive immediately after close (RESP error, idle - * expiry, stream close notify) are silently discarded inside the core. */ -typedef void (*mq_udp_rx_fn)(const uint8_t *payload, size_t len, void *user); -typedef void (*mq_udp_err_fn)(void *session, mq_udp_err_t err, void *user); -typedef void *(*mq_udp_open_fn)(void *core, const uint8_t *host, size_t host_len, - mq_addr_type_t atype, uint16_t port, mq_udp_rx_fn on_rx, - mq_udp_err_fn on_err, void *user); -typedef void (*mq_udp_send_fn)(void *session, const uint8_t *payload, size_t len); -typedef void (*mq_udp_close_fn)(void *session); -#endif diff --git a/src/ingress/mq_listener.c b/src/ingress/mq_listener.c deleted file mode 100644 index 1c377696..00000000 --- a/src/ingress/mq_listener.c +++ /dev/null @@ -1,586 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_listener.c — shared ingress TCP accept loop for SOCKS5 + HTTP CONNECT. - * - * One generic listener type backs both protocols. The protocol-specific logic - * is captured by a `drive` function pointer (set at construction) that, given a - * per-connection state and its accumulated read buffer, advances the parser and - * decides what to do: keep reading, send an intermediate reply, hand off to - * open_fn, or reject + close. This keeps the socket/accept/lifetime machinery - * in one place and the SOCKS5 vs HTTP differences small and isolated. - * - * SOCKS5 CMD UDP ASSOCIATE is a fourth drive outcome: on MQ_SOCKS5_ASSOCIATE_DONE - * the accepted control fd is MOVED into a new mq_udp_assoc (which takes fd - * ownership) and the transient parse state is freed WITHOUT closing the fd; the - * listener keeps the assoc in an intrusive list (l->assocs) for sweep/reap. See - * mq_listener.h for the full fd-ownership contract (incl. the ASSOCIATE bullet). - */ -#include "ingress/mq_listener.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ingress/mq_http_connect.h" -#include "ingress/mq_socks5.h" -#include "ingress/mq_udp_assoc.h" -#include "util/mq_log.h" - -/* Max bytes we buffer for a single request head before giving up (protects - * against a slowloris / oversized greeting). 8 KiB comfortably covers a SOCKS5 - * request (~262 B) and a reasonable HTTP CONNECT head. */ -#define MQ_LISTENER_RXCAP 8192 - -struct mq_conn_state; /* fwd */ - -/* Outcome of one drive() pass. */ -typedef enum { - MQ_DRIVE_NEED_MORE, /* keep reading */ - MQ_DRIVE_HANDOFF, /* open_fn was called; fd ownership transferred */ - MQ_DRIVE_CLOSE /* reject/error: close the accepted fd */ -} mq_drive_result_t; - -/* Protocol drive callback: process whatever is in c->rxbuf[0..rxlen). It may - * write intermediate replies to c->fd (e.g. SOCKS5 method reply) and update - * parser state. Returns one of mq_drive_result_t. On MQ_DRIVE_HANDOFF the - * callback MUST have invoked l->open_fn (which takes fd ownership). */ -typedef mq_drive_result_t (*mq_drive_fn)(struct mq_conn_state *c); - -/* open-result callbacks (defined below; referenced by the drive functions). */ -static void socks5_open_result(int ok, mq_tcp_err_t err, void *user); -static void http_open_result(int ok, mq_tcp_err_t err, void *user); - -struct mq_listener_s { - struct event_base *base; - int listen_fd; - struct event *accept_ev; - uint16_t local_port; - char bind_ip[46]; /* saved for ASSOCIATE UDP socket bind (INET6_ADDRSTRLEN) */ - mq_tcp_open_fn open_fn; - void *core; - mq_drive_fn drive; - /* UDP relay boundary (NULL open => no local ASSOCIATE support). The UDP - * `core` differs from the TCP `core` (it is the concrete relay table, the - * mq_udp_cli_t*), so it is stored separately. */ - mq_udp_open_fn udp_open; - mq_udp_send_fn udp_send; - mq_udp_close_fn udp_close; - void *udp_core; - /* Remote UDP-relay availability tri-state (-1 undetermined / 0 no / 1 yes). */ - int udp_avail; - /* Live per-conn states, singly linked, so free() can reap them. */ - struct mq_conn_state *conns; - /* Live UDP associations (intrusive doubly-linked list head). */ - mq_udp_assoc_t *assocs; -}; - -struct mq_conn_state { - mq_listener_t *l; - int fd; /* accepted app socket; -1 once handed to open_fn */ - struct event *read_ev; - uint8_t rxbuf[MQ_LISTENER_RXCAP]; - size_t rxlen; - mq_socks5_parser_t s5; /* only used for the SOCKS5 drive */ - int handed_off; /* 1 once open_fn was called (fd owned by core) */ - struct mq_conn_state *next; -}; - -/* ── per-conn list bookkeeping ──────────────────────────────────────────── */ - -static void -conn_unlink(mq_listener_t *l, struct mq_conn_state *c) -{ - struct mq_conn_state **pp = &l->conns; - while (*pp) { - if (*pp == c) { - *pp = c->next; - return; - } - pp = &(*pp)->next; - } -} - -/* Drop the listener's read interest on the accepted fd without closing it or - * freeing the state. Used at hand-off: the core now owns the fd, so the - * listener must stop its EV_READ event (which would otherwise keep firing on a - * fd the relay drives). The state itself survives until the open-result cb. */ -static void -conn_detach_read(struct mq_conn_state *c) -{ - if (c->read_ev) { - event_free(c->read_ev); - c->read_ev = NULL; - } -} - -/* Tear down a per-conn state. If close_fd is set AND the fd has not been handed - * to open_fn, close it (listener still owns it). Always frees the read event - * and the state and unlinks it. */ -static void -conn_destroy(struct mq_conn_state *c, int close_fd) -{ - if (!c) return; - conn_unlink(c->l, c); - if (c->read_ev) { - event_free(c->read_ev); - c->read_ev = NULL; - } - if (close_fd && !c->handed_off && c->fd >= 0) { - close(c->fd); - } - c->fd = -1; - free(c); -} - -/* ── SOCKS5 drive ───────────────────────────────────────────────────────── */ - -static mq_drive_result_t -drive_socks5(struct mq_conn_state *c) -{ - mq_listener_t *l = c->l; - - /* Feed whatever we have; the parser consumes greeting then request across - * one or more passes. We loop so a single read containing both greeting and - * request advances both. */ - for (;;) { - size_t consumed = 0; - mq_socks5_req_t req; - memset(&req, 0, sizeof(req)); - mq_socks5_status_t st = - mq_socks5_feed(&c->s5, c->rxbuf, c->rxlen, &consumed, &req); - - switch (st) { - case MQ_SOCKS5_NEED_MORE: return MQ_DRIVE_NEED_MORE; - - case MQ_SOCKS5_GREETING_DONE: { - /* Drop consumed greeting bytes, send the method reply, loop to try - * the request (it may already be buffered). */ - memmove(c->rxbuf, c->rxbuf + consumed, c->rxlen - consumed); - c->rxlen -= consumed; - uint8_t mr[2]; - size_t n = mq_socks5_build_method_reply(mr, /*accepted=*/1); - if (send(c->fd, mr, n, MSG_NOSIGNAL) != (ssize_t)n) { - return MQ_DRIVE_CLOSE; - } - continue; - } - - case MQ_SOCKS5_REQUEST_DONE: { - /* Hand off: the core owns the fd from here. The success/error reply - * is written by socks5_open_result on the (still-open) fd. Drop our - * read interest first — the core (relay) now drives the fd, and - * open_fn's result cb may fire asynchronously (later turn). - * - * Bytes the app pipelined after the request (already in rxbuf, past - * `consumed`) are handed to the core as a prebuffer so they reach the - * origin and are not dropped. */ - c->handed_off = 1; - int fd = c->fd; - const uint8_t *prebuf = c->rxbuf + consumed; - size_t prebuf_len = c->rxlen - consumed; - conn_detach_read(c); - l->open_fn(l->core, req.host, req.host_len, req.atype, req.port, fd, prebuf, - prebuf_len, c, socks5_open_result); - return MQ_DRIVE_HANDOFF; - } - - case MQ_SOCKS5_ASSOCIATE_DONE: { - /* CMD UDP ASSOCIATE. Admit only if this ingress has a UDP relay - * boundary (udp_open quad non-NULL) AND remote availability != 0. - * Otherwise REP 0x07 (command not supported) + close. */ - if (!l->udp_open || l->udp_avail == 0) { - uint8_t cr[10]; - size_t n = mq_socks5_build_connect_reply(cr, 0x07); - (void)send(c->fd, cr, n, MSG_NOSIGNAL); - return MQ_DRIVE_CLOSE; - } - /* Move the accepted (control) fd into a new assoc. The assoc owns - * the fd from here (its TCP-EOF watch tears the relay down); we drop - * our read interest and free the transient parse state WITHOUT - * closing the fd. The assoc writes the ASSOCIATE success reply. */ - int fd = c->fd; - conn_detach_read(c); - mq_udp_assoc_t *a = mq_udp_assoc_new(l->base, fd, l->bind_ip, l->udp_open, - l->udp_send, l->udp_close, l->udp_core); - if (!a) { - /* Bind/socket failure: reply general failure and close the fd. */ - uint8_t cr[10]; - size_t n = mq_socks5_build_connect_reply(cr, 0x01); - (void)send(fd, cr, n, MSG_NOSIGNAL); - close(fd); - c->fd = -1; /* already closed; conn_destroy must not re-close */ - c->handed_off = 1; - conn_destroy(c, /*close_fd=*/0); - return MQ_DRIVE_HANDOFF; - } - mq_udp_assoc_list_push(&l->assocs, a); - /* The fd is now owned by the assoc; our state must not close it. */ - c->handed_off = 1; - c->fd = -1; - conn_destroy(c, /*close_fd=*/0); - return MQ_DRIVE_HANDOFF; - } - - case MQ_SOCKS5_UNSUPPORTED: { - /* Greeting phase: no acceptable auth method => method reply 0xFF - * (NO ACCEPTABLE METHODS), per RFC 1928. */ - uint8_t mr[2]; - size_t n = mq_socks5_build_method_reply(mr, /*accepted=*/0); - (void)send(c->fd, mr, n, MSG_NOSIGNAL); - return MQ_DRIVE_CLOSE; - } - - case MQ_SOCKS5_UNSUPPORTED_CMD: /* REP 0x07: command not supported */ - case MQ_SOCKS5_UNSUPPORTED_ATYP: { /* REP 0x08: address type not supported */ - uint8_t cr[10]; - uint8_t rep = (st == MQ_SOCKS5_UNSUPPORTED_CMD) ? 0x07 : 0x08; - size_t n = mq_socks5_build_connect_reply(cr, rep); - (void)send(c->fd, cr, n, MSG_NOSIGNAL); - return MQ_DRIVE_CLOSE; - } - - case MQ_SOCKS5_ERROR: - default: return MQ_DRIVE_CLOSE; - } - } -} - -/* ── HTTP CONNECT drive ─────────────────────────────────────────────────── */ - -static mq_drive_result_t -drive_http(struct mq_conn_state *c) -{ - mq_listener_t *l = c->l; - - mq_http_target_t tgt; - memset(&tgt, 0, sizeof(tgt)); - size_t header_len = 0; - mq_http_status_t st = mq_http_connect_parse(c->rxbuf, c->rxlen, &tgt, &header_len); - - switch (st) { - case MQ_HTTP_NEED_MORE: return MQ_DRIVE_NEED_MORE; - - case MQ_HTTP_CONNECT_DONE: { - int fd = c->fd; - c->handed_off = 1; - /* Bytes the app pipelined after the CONNECT head (already in rxbuf, past - * header_len) are handed to the core as a prebuffer so they reach the - * origin and are not dropped. */ - const uint8_t *prebuf = c->rxbuf + header_len; - size_t prebuf_len = c->rxlen - header_len; - conn_detach_read(c); - l->open_fn(l->core, tgt.host, tgt.host_len, tgt.atype, tgt.port, fd, prebuf, - prebuf_len, c, http_open_result); - return MQ_DRIVE_HANDOFF; - } - - case MQ_HTTP_UNSUPPORTED: { - const char *line = "HTTP/1.1 405 Method Not Allowed\r\n\r\n"; - (void)send(c->fd, line, strlen(line), MSG_NOSIGNAL); - return MQ_DRIVE_CLOSE; - } - - case MQ_HTTP_BAD: - default: { - const char *line = "HTTP/1.1 400 Bad Request\r\n\r\n"; - (void)send(c->fd, line, strlen(line), MSG_NOSIGNAL); - return MQ_DRIVE_CLOSE; - } - } -} - -/* ── open-result callbacks (write app-side reply on local_fd) ─────────────── - * - * The core owns local_fd at this point. On ok=1 the relay has not closed it; on - * ok=0 the core closes it AFTER this callback returns, so writing the error - * reply here still reaches the app. We do NOT close local_fd in either case. */ - -static void -socks5_open_result(int ok, mq_tcp_err_t err, void *user) -{ - struct mq_conn_state *c = (struct mq_conn_state *)user; - int fd = c->fd; /* still valid; core hasn't closed it yet */ - uint8_t cr[10]; - uint8_t rep = ok ? 0x00 : mq_socks5_reply_code(err); - size_t n = mq_socks5_build_connect_reply(cr, rep); - (void)send(fd, cr, n, MSG_NOSIGNAL); - /* The fd is owned by the core; do not close. Free our parse state (we no - * longer read from fd). */ - conn_destroy(c, /*close_fd=*/0); -} - -static void -http_open_result(int ok, mq_tcp_err_t err, void *user) -{ - struct mq_conn_state *c = (struct mq_conn_state *)user; - int fd = c->fd; - if (ok) { - char buf[64]; - size_t n = mq_http_build_200(buf, sizeof(buf)); - (void)send(fd, buf, n, MSG_NOSIGNAL); - } else { - const char *line = mq_http_status_line(err); - (void)send(fd, line, strlen(line), MSG_NOSIGNAL); - } - conn_destroy(c, /*close_fd=*/0); -} - -/* ── read event ─────────────────────────────────────────────────────────── */ - -static void -on_readable(evutil_socket_t fd, short what, void *user) -{ - (void)what; - struct mq_conn_state *c = (struct mq_conn_state *)user; - - for (;;) { - if (c->rxlen >= sizeof(c->rxbuf)) { - /* Request head too large; treat as protocol error. */ - conn_destroy(c, /*close_fd=*/1); - return; - } - ssize_t n = recv(fd, c->rxbuf + c->rxlen, sizeof(c->rxbuf) - c->rxlen, 0); - if (n > 0) { - c->rxlen += (size_t)n; - mq_drive_result_t r = c->l->drive(c); - if (r == MQ_DRIVE_HANDOFF) { - /* open_fn was called; the drive already detached our read event - * (conn_detach_read) before the call. `c` may now be freed (if - * the result cb fired synchronously) OR still alive awaiting an - * async cb. Either way the core owns fd and we must not touch - * `c` or fd again here. */ - return; - } - if (r == MQ_DRIVE_CLOSE) { - conn_destroy(c, /*close_fd=*/1); - return; - } - /* NEED_MORE: keep reading in this loop. */ - continue; - } - if (n == 0) { - /* Peer closed before completing the request. */ - conn_destroy(c, /*close_fd=*/1); - return; - } - /* n < 0 */ - if (errno == EAGAIN || errno == EWOULDBLOCK) { - return; /* nothing more right now */ - } - if (errno == EINTR) { - continue; - } - conn_destroy(c, /*close_fd=*/1); - return; - } -} - -/* ── accept event ───────────────────────────────────────────────────────── */ - -static int -set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - if (fl < 0) return -1; - return fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -static void -on_accept(evutil_socket_t lfd, short what, void *user) -{ - (void)what; - mq_listener_t *l = (mq_listener_t *)user; - - for (;;) { - int fd = accept(lfd, NULL, NULL); - if (fd < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) return; - if (errno == EINTR) continue; - MQ_LOGW("mq_listener: accept failed: %s", strerror(errno)); - return; - } - if (set_nonblock(fd) != 0) { - close(fd); - continue; - } - - struct mq_conn_state *c = calloc(1, sizeof(*c)); - if (!c) { - close(fd); - continue; - } - c->l = l; - c->fd = fd; - mq_socks5_parser_init(&c->s5); - c->next = l->conns; - l->conns = c; - - c->read_ev = event_new(l->base, fd, EV_READ | EV_PERSIST, on_readable, c); - if (!c->read_ev) { - conn_destroy(c, /*close_fd=*/1); - continue; - } - event_add(c->read_ev, NULL); - } -} - -/* ── construction ───────────────────────────────────────────────────────── */ - -static mq_listener_t * -listener_new(struct event_base *base, const char *bind_ip, uint16_t port, - mq_tcp_open_fn open_fn, void *core, mq_drive_fn drive, - mq_udp_open_fn udp_open, mq_udp_send_fn udp_send, mq_udp_close_fn udp_close, - void *udp_core) -{ - if (!base || !bind_ip || !open_fn || !drive) return NULL; - - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) return NULL; - - int one = 1; - (void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - if (inet_pton(AF_INET, bind_ip, &sa.sin_addr) != 1) { - close(fd); - return NULL; - } - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return NULL; - } - if (listen(fd, 64) != 0) { - close(fd); - return NULL; - } - if (set_nonblock(fd) != 0) { - close(fd); - return NULL; - } - - struct sockaddr_in bound; - socklen_t blen = sizeof(bound); - uint16_t lport = 0; - if (getsockname(fd, (struct sockaddr *)&bound, &blen) == 0) { - lport = ntohs(bound.sin_port); - } - - mq_listener_t *l = calloc(1, sizeof(*l)); - if (!l) { - close(fd); - return NULL; - } - l->base = base; - l->listen_fd = fd; - l->local_port = lport; - snprintf(l->bind_ip, sizeof(l->bind_ip), "%s", bind_ip); - l->open_fn = open_fn; - l->core = core; - l->drive = drive; - l->udp_open = udp_open; - l->udp_send = udp_send; - l->udp_close = udp_close; - l->udp_core = udp_core; - l->udp_avail = -1; /* undetermined: optimistically admit ASSOCIATE */ - l->conns = NULL; - l->assocs = NULL; - - l->accept_ev = event_new(base, fd, EV_READ | EV_PERSIST, on_accept, l); - if (!l->accept_ev) { - close(fd); - free(l); - return NULL; - } - event_add(l->accept_ev, NULL); - return l; -} - -mq_listener_t * -mq_socks5_listener_new(struct event_base *base, const char *bind_ip, uint16_t port, - mq_tcp_open_fn open_fn, void *core, mq_udp_open_fn udp_open, - mq_udp_send_fn udp_send, mq_udp_close_fn udp_close, void *udp_core) -{ - return listener_new(base, bind_ip, port, open_fn, core, drive_socks5, udp_open, - udp_send, udp_close, udp_core); -} - -mq_listener_t * -mq_http_connect_listener_new(struct event_base *base, const char *bind_ip, uint16_t port, - mq_tcp_open_fn open_fn, void *core, mq_udp_open_fn udp_open, - mq_udp_send_fn udp_send, mq_udp_close_fn udp_close, - void *udp_core) -{ - /* HTTP CONNECT has no ASSOCIATE path; the udp quad is accepted for ctor - * symmetry and forced NULL. */ - (void)udp_open; - (void)udp_send; - (void)udp_close; - (void)udp_core; - return listener_new(base, bind_ip, port, open_fn, core, drive_http, NULL, NULL, NULL, - NULL); -} - -uint16_t -mq_listener_local_port(const mq_listener_t *l) -{ - return l ? l->local_port : 0; -} - -void -mq_listener_set_udp_availability(mq_listener_t *l, int avail) -{ - if (!l) return; /* public contract: HTTP-only client leaves socks5_l NULL */ - l->udp_avail = avail; - if (avail != 0) return; - - /* Unavailable: relay can no longer proceed. Sweep every live assoc and tear - * it down — closing the TCP control connection is the relay-terminated - * signal (RFC 1928 §7). This also reaps pre-auth assocs that never sent UDP. - * mq_udp_assoc_free unlinks the assoc from l->assocs (self-removal), so we - * capture next before freeing. */ - mq_udp_assoc_t *a = l->assocs; - while (a) { - mq_udp_assoc_t *next = mq_udp_assoc_list_next(a); - mq_udp_assoc_free(a); /* closes sessions + control fd + unlinks */ - a = next; - } - /* assocs list is now empty (all self-removed). */ -} - -void -mq_listener_free(mq_listener_t *l) -{ - if (!l) return; - if (l->accept_ev) { - event_free(l->accept_ev); - l->accept_ev = NULL; - } - if (l->listen_fd >= 0) { - close(l->listen_fd); - l->listen_fd = -1; - } - /* Reap any still-pending parse states (not yet handed off). */ - while (l->conns) { - conn_destroy(l->conns, /*close_fd=*/1); - } - /* Reap any surviving UDP associations (graph A path 3): their tunnel - * sessions were already killed by mq_transport_free (on_err → dead), so - * close_fn is a no-op here; this frees the assoc shells (UDP/TCP fds, - * events). mq_udp_assoc_free self-removes from l->assocs. */ - while (l->assocs) { - mq_udp_assoc_free(l->assocs); - } - free(l); -} diff --git a/src/ingress/mq_listener.h b/src/ingress/mq_listener.h deleted file mode 100644 index e19674eb..00000000 --- a/src/ingress/mq_listener.h +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_listener.h — ingress TCP accept listeners (SOCKS5 / HTTP CONNECT). - * - * A listener binds+listens a non-blocking TCP socket on bind_ip:port and runs - * a libevent EV_READ accept loop. Each accepted connection gets a small - * per-conn parse state that drives the protocol parser (SOCKS5 greeting/request - * or HTTP CONNECT) until it has a target, then calls the supplied open_fn - * (mq_tcp_open_fn) to hand the connection to the proxy core. - * - * Ownership of the accepted fd: - * - The listener OWNS the accepted fd from accept() until the moment it calls - * open_fn. On any pre-tcp_open failure (parse error, unsupported request, - * read error) the listener writes the appropriate reject reply (when the - * protocol defines one) and closes the accepted fd itself. - * - Once open_fn is called, the proxy core (mq_client) OWNS the fd and closes - * it on every terminal outcome. The listener MUST NOT close it afterwards. - * The open-result callback writes the protocol success/error reply to the - * (still-open) fd; the core closes it after the callback returns on error, - * and on relay completion on success. - * - SOCKS5 CMD UDP ASSOCIATE (0x03) is a third outcome: on MQ_SOCKS5_ASSOCIATE_DONE - * the accepted (control) fd is MOVED into a freshly created mq_udp_assoc, and - * the transient parse state is freed (without closing the fd). The assoc now - * owns the control fd (its EOF tears the association down) plus a bound UDP - * relay socket. The listener keeps an intrusive list of live assocs (see - * mq_udp_assoc.{c,h}) for: (1) self-removal on TCP-EOF teardown, (2) - * availability-sweep teardown when UDP becomes unavailable, (3) reaping any - * survivors at mq_listener_free. ASSOCIATE is admitted only when the - * udp_open quad is non-NULL AND availability != 0 (see set_udp_availability). - * - * This module knows nothing about xquic; it only speaks libevent + sockets and - * the mq_tcp_open_fn / mq_udp_open_fn boundaries. - */ -#ifndef MQ_INGRESS_MQ_LISTENER_H -#define MQ_INGRESS_MQ_LISTENER_H - -#include - -#include "ingress/mq_ingress.h" - -struct event_base; - -typedef struct mq_listener_s mq_listener_t; - -/* Create a SOCKS5 ingress listener bound to bind_ip:port (port 0 => ephemeral). - * base is borrowed (must outlive the listener). open_fn/core are the proxy-core - * TCP boundary; they are invoked once per accepted connection that parses a valid - * CONNECT target. - * - * udp_open/udp_send/udp_close are the UDP relay boundary (mq_udp_open_fn) used to - * service CMD UDP ASSOCIATE. Passing them all NULL means "this ingress has no - * local UDP relay support" => every ASSOCIATE is refused with REP 0x07. (This is - * distinct from the remote-side availability tri-state — see - * mq_listener_set_udp_availability — which governs an ingress that CAN relay but - * whose server may not, yet.) - * - * udp_core is the `core` passed to udp_open (mq_ingress.h) — the concrete relay - * table (mq_udp_cli_t*), which is DISTINCT from the TCP `core` (mq_client_t*), so - * it is a separate parameter. NULL when udp_open is NULL. - * Returns NULL on bad args / bind / listen failure. */ -mq_listener_t *mq_socks5_listener_new(struct event_base *base, const char *bind_ip, - uint16_t port, mq_tcp_open_fn open_fn, void *core, - mq_udp_open_fn udp_open, mq_udp_send_fn udp_send, - mq_udp_close_fn udp_close, void *udp_core); - -/* Create an HTTP CONNECT ingress listener. Same shape as the SOCKS5 variant. The - * HTTP listener does not speak SOCKS5, so the udp_open quad is accepted only for - * ctor symmetry and is always forced NULL (no ASSOCIATE path exists here). */ -mq_listener_t *mq_http_connect_listener_new(struct event_base *base, const char *bind_ip, - uint16_t port, mq_tcp_open_fn open_fn, - void *core, mq_udp_open_fn udp_open, - mq_udp_send_fn udp_send, - mq_udp_close_fn udp_close, void *udp_core); - -/* Set the remote UDP-relay availability tri-state consulted at ASSOCIATE time: - * -1 = undetermined (pre-auth window): optimistically admit ASSOCIATE - * 0 = unavailable: refuse ASSOCIATE with REP 0x07, AND immediately sweep — - * close every live assoc's TCP control connection (relay end, RFC 1928 - * §7), including pre-auth assocs that have not sent UDP yet - * 1 = available: admit ASSOCIATE - * Initial value is -1. `l == NULL` is a no-op (public contract: an HTTP-only - * client leaves socks5_l == NULL while the CLI glue still calls this). */ -void mq_listener_set_udp_availability(mq_listener_t *l, int avail); - -/* The bound local TCP port in host byte order (useful for ephemeral binds in - * tests). Returns 0 if unknown. */ -uint16_t mq_listener_local_port(const mq_listener_t *l); - -/* Free the listener: stop accepting, close the listen socket, and close every - * accepted fd still owned by the listener (i.e. those not yet handed to - * open_fn). Safe on NULL. */ -void mq_listener_free(mq_listener_t *l); - -#endif /* MQ_INGRESS_MQ_LISTENER_H */ diff --git a/src/ingress/mq_origdst.c b/src/ingress/mq_origdst.c deleted file mode 100644 index 56b0bf47..00000000 --- a/src/ingress/mq_origdst.c +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -#include "ingress/mq_origdst.h" -#include -#include /* SO_ORIGINAL_DST */ -#include -#include - -int -mq_origdst_to_target(const struct sockaddr_storage *ss, uint8_t *host, size_t cap, - size_t *host_len, int *atype, uint16_t *port) -{ - if (ss->ss_family == AF_INET) { - if (cap < 4) return -1; - const struct sockaddr_in *s4 = (const struct sockaddr_in *)ss; - memcpy(host, &s4->sin_addr, 4); - *host_len = 4; - *atype = MQ_ADDR_IPV4; - *port = ntohs(s4->sin_port); - return 0; - } - if (ss->ss_family == AF_INET6) { - if (cap < 16) return -1; - const struct sockaddr_in6 *s6 = (const struct sockaddr_in6 *)ss; - memcpy(host, &s6->sin6_addr, 16); - *host_len = 16; - *atype = MQ_ADDR_IPV6; - *port = ntohs(s6->sin6_port); - return 0; - } - return -1; -} - -int -mq_origdst_from_fd_tproxy(int fd, struct sockaddr_storage *out) -{ - socklen_t len = sizeof(*out); - if (getsockname(fd, (struct sockaddr *)out, &len) < 0) return -1; - return 0; -} - -int -mq_origdst_from_fd_redirect(int fd, struct sockaddr_storage *out) -{ - struct sockaddr_in s4; - socklen_t len = sizeof(s4); - if (getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &s4, &len) < 0) return -1; - memset(out, 0, sizeof(*out)); - memcpy(out, &s4, sizeof(s4)); - return 0; -} diff --git a/src/ingress/mq_origdst.h b/src/ingress/mq_origdst.h deleted file mode 100644 index 4cfe7844..00000000 --- a/src/ingress/mq_origdst.h +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -#ifndef MQ_ORIGDST_H -#define MQ_ORIGDST_H -#include /* struct sockaddr_storage */ -#include -#include "wire/mq_wire.h" /* MQ_ADDR_IPV4 / MQ_ADDR_IPV6 */ - -/* Convert an original-destination sockaddr into the binary (host_bytes, atype, port) - * the mq_tcp_open_fn / CONNECT_TCP wire contract expects. host receives the RAW - * address bytes (4 for IPv4, 16 for IPv6) — NOT a printable string. Returns 0 on - * success, -1 on unsupported family or cap < needed. */ -int mq_origdst_to_target(const struct sockaddr_storage *ss, uint8_t *host, size_t cap, - size_t *host_len, int *atype, uint16_t *port); - -/* Thin syscall wrappers (not unit-tested; need a live socket). v1 is IPv4-only. */ -int mq_origdst_from_fd_tproxy(int fd, struct sockaddr_storage *out); /* getsockname */ -int mq_origdst_from_fd_redirect( - int fd, struct sockaddr_storage *out); /* getsockopt SO_ORIGINAL_DST */ -#endif diff --git a/src/ingress/mq_socks5.c b/src/ingress/mq_socks5.c deleted file mode 100644 index 76bf74a2..00000000 --- a/src/ingress/mq_socks5.c +++ /dev/null @@ -1,244 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "ingress/mq_socks5.h" -#include - -#define SOCKS5_VER 0x05 -#define SOCKS5_CMD_CONNECT 0x01 -#define SOCKS5_CMD_ASSOCIATE 0x03 -#define SOCKS5_METHOD_NOAUTH 0x00 - -void -mq_socks5_parser_init(mq_socks5_parser_t *p) -{ - p->phase = 0; -} - -/* Parse the greeting from buf[0..len). Sets *consumed. - Bytes: VER(1) | NMETHODS(1) | METHODS(n). */ -static mq_socks5_status_t -parse_greeting(mq_socks5_parser_t *p, const uint8_t *buf, size_t len, size_t *consumed) -{ - *consumed = 0; - if (len < 2) return MQ_SOCKS5_NEED_MORE; - if (buf[0] != SOCKS5_VER) return MQ_SOCKS5_ERROR; - - size_t nmethods = buf[1]; - size_t total = 2 + nmethods; - if (len < total) return MQ_SOCKS5_NEED_MORE; - - int has_noauth = 0; - for (size_t i = 0; i < nmethods; i++) { - if (buf[2 + i] == SOCKS5_METHOD_NOAUTH) { - has_noauth = 1; - break; - } - } - - if (!has_noauth) return MQ_SOCKS5_UNSUPPORTED; - *consumed = total; - p->phase = 1; - return MQ_SOCKS5_GREETING_DONE; -} - -/* Parse the CONNECT/ASSOCIATE request from buf[0..len). Sets *consumed and fills out. - Bytes: VER(1) | CMD(1) | RSV(1) | ATYP(1) | DST.ADDR | DST.PORT(2 BE). */ -static mq_socks5_status_t -parse_request(const uint8_t *buf, size_t len, size_t *consumed, mq_socks5_req_t *out) -{ - *consumed = 0; - if (len < 4) return MQ_SOCKS5_NEED_MORE; - if (buf[0] != SOCKS5_VER) return MQ_SOCKS5_ERROR; - if (buf[1] != SOCKS5_CMD_CONNECT && buf[1] != SOCKS5_CMD_ASSOCIATE) - return MQ_SOCKS5_UNSUPPORTED_CMD; /* BIND => REP 0x07 */ - /* buf[2] RSV ignored (RFC 1928 says 0x00, but be permissive on receive) */ - - uint8_t atype = buf[3]; - size_t addr_off = 4; - size_t addr_len; - mq_addr_type_t mapped; - - switch (atype) { - case MQ_ADDR_IPV4: - addr_len = 4; - mapped = MQ_ADDR_IPV4; - break; - case MQ_ADDR_IPV6: - addr_len = 16; - mapped = MQ_ADDR_IPV6; - break; - case MQ_ADDR_DOMAIN: { - if (len < addr_off + 1) return MQ_SOCKS5_NEED_MORE; /* need the length byte */ - size_t dlen = buf[addr_off]; - addr_off += 1; /* consume LEN byte */ - addr_len = dlen; - mapped = MQ_ADDR_DOMAIN; - break; - } - default: return MQ_SOCKS5_UNSUPPORTED_ATYP; /* unknown ATYP => REP 0x08 */ - } - - size_t total = addr_off + addr_len + 2; /* +2 for port */ - if (len < total) return MQ_SOCKS5_NEED_MORE; - - /* exact fit (MQ_MAX_HOST == 255; no NUL terminator — host_len carries the length) */ - out->atype = mapped; - out->host_len = addr_len; - if (addr_len > 0) memcpy(out->host, buf + addr_off, addr_len); - out->port = - (uint16_t)((buf[addr_off + addr_len] << 8) | buf[addr_off + addr_len + 1]); - - *consumed = total; - return (buf[1] == SOCKS5_CMD_ASSOCIATE) ? MQ_SOCKS5_ASSOCIATE_DONE - : MQ_SOCKS5_REQUEST_DONE; -} - -mq_socks5_status_t -mq_socks5_feed(mq_socks5_parser_t *p, const uint8_t *buf, size_t len, size_t *consumed, - mq_socks5_req_t *out) -{ - if (p->phase == 0) return parse_greeting(p, buf, len, consumed); - return parse_request(buf, len, consumed, out); -} - -size_t -mq_socks5_build_method_reply(uint8_t out[2], int accepted) -{ - out[0] = SOCKS5_VER; - out[1] = accepted ? 0x00 : 0xFF; - return 2; -} - -/* Shared 10-byte reply builder: VER=5 REP=rep RSV=0 ATYP=1 BND.ADDR BND.PORT. */ -static size_t -build_reply10(uint8_t out[10], uint8_t rep, uint32_t bnd_ip_be, uint16_t bnd_port) -{ - out[0] = SOCKS5_VER; - out[1] = rep; - out[2] = 0x00; /* RSV */ - out[3] = 0x01; /* ATYP = IPv4 */ - out[4] = (uint8_t)(bnd_ip_be >> 24); - out[5] = (uint8_t)(bnd_ip_be >> 16); - out[6] = (uint8_t)(bnd_ip_be >> 8); - out[7] = (uint8_t)(bnd_ip_be); - out[8] = (uint8_t)(bnd_port >> 8); - out[9] = (uint8_t)(bnd_port); - return 10; -} - -size_t -mq_socks5_build_connect_reply(uint8_t out[10], uint8_t rep) -{ - return build_reply10(out, rep, 0, 0); -} - -size_t -mq_socks5_build_associate_reply(uint8_t out[10], uint32_t bnd_ip_host, uint16_t bnd_port) -{ - return build_reply10(out, 0x00, bnd_ip_host, bnd_port); -} - -uint8_t -mq_socks5_reply_code(mq_tcp_err_t err) -{ - switch (err) { - case MQ_TCP_OK: return 0x00; - case MQ_TCP_DNS_FAILED: return 0x04; /* host unreachable */ - case MQ_TCP_CONN_REFUSED: return 0x05; /* connection refused */ - case MQ_TCP_TIMEOUT: return 0x06; /* TTL expired */ - case MQ_TCP_POLICY_DENIED: return 0x02; /* connection not allowed by ruleset */ - } - return 0x01; /* general SOCKS server failure */ -} - -/* Parse UDP encapsulation header (RFC 1928 §7): - * RSV(2B) | FRAG(1B) | ATYP(1B) | DST.ADDR | DST.PORT(2B BE) - * - * Returns bytes consumed (>= 0) and fills *out, or: - * -1 malformed / truncated / RSV != 0x0000 (strict — untrusted input) - * -2 FRAG != 0 (fragmented datagram; caller must drop) - */ -int -mq_socks5_parse_udp_hdr(const uint8_t *buf, size_t len, mq_socks5_udp_hdr_t *out) -{ - /* Minimum: RSV(2) + FRAG(1) + ATYP(1) + IPv4(4) + PORT(2) = 10 bytes */ - if (len < 4) return -1; - - /* RSV must be 0x0000 (strict reject for untrusted input) */ - if (buf[0] != 0x00 || buf[1] != 0x00) return -1; - - /* FRAG: non-zero means fragmented datagram — caller must drop */ - if (buf[2] != 0x00) return -2; - - uint8_t atype = buf[3]; - size_t addr_off = 4; - size_t addr_len; - mq_addr_type_t mapped; - - switch (atype) { - case MQ_ADDR_IPV4: - addr_len = 4; - mapped = MQ_ADDR_IPV4; - break; - case MQ_ADDR_IPV6: - addr_len = 16; - mapped = MQ_ADDR_IPV6; - break; - case MQ_ADDR_DOMAIN: { - if (len < addr_off + 1) return -1; /* need the length byte */ - size_t dlen = buf[addr_off]; - addr_off += 1; - addr_len = dlen; - mapped = MQ_ADDR_DOMAIN; - break; - } - default: return -1; /* unknown ATYP */ - } - - size_t total = addr_off + addr_len + 2; /* +2 for port */ - if (len < total) return -1; - - /* exact fit (MQ_MAX_HOST == 255; no NUL terminator — host_len carries the length) */ - out->dst.atype = mapped; - out->dst.host_len = addr_len; - if (addr_len > 0) memcpy(out->dst.host, buf + addr_off, addr_len); - out->dst.port = - (uint16_t)((buf[addr_off + addr_len] << 8) | buf[addr_off + addr_len + 1]); - out->hdr_len = total; - - return (int)total; -} - -/* Write UDP encapsulation header (RSV=0, FRAG=0) for the given src address. - * Returns bytes written, or -1 if cap is too small. - */ -int -mq_socks5_build_udp_hdr(uint8_t *out, size_t cap, const mq_socks5_req_t *src) -{ - /* Calculate required size first */ - size_t addr_off = 4; /* RSV(2) + FRAG(1) + ATYP(1) */ - size_t addr_len = src->host_len; - size_t extra_len = 0; /* domain prefix byte */ - - if (src->atype == MQ_ADDR_DOMAIN) extra_len = 1; - - size_t needed = addr_off + extra_len + addr_len + 2; - if (cap < needed) return -1; - - out[0] = 0x00; /* RSV high */ - out[1] = 0x00; /* RSV low */ - out[2] = 0x00; /* FRAG */ - out[3] = (uint8_t)src->atype; /* ATYP */ - - size_t pos = 4; - if (src->atype == MQ_ADDR_DOMAIN) { - out[pos++] = (uint8_t)addr_len; /* LEN byte */ - } - if (addr_len > 0) memcpy(out + pos, src->host, addr_len); - pos += addr_len; - out[pos] = (uint8_t)(src->port >> 8); - out[pos + 1] = (uint8_t)(src->port); - - return (int)needed; -} diff --git a/src/ingress/mq_socks5.h b/src/ingress/mq_socks5.h deleted file mode 100644 index 44a68a7c..00000000 --- a/src/ingress/mq_socks5.h +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_SOCKS5_H -#define MQ_SOCKS5_H -#include "wire/mq_wire.h" -#include -#include - -typedef enum { - MQ_SOCKS5_NEED_MORE, /* incomplete; feed more bytes */ - MQ_SOCKS5_GREETING_DONE, /* parsed greeting; caller should send method reply */ - MQ_SOCKS5_REQUEST_DONE, /* parsed a CONNECT request; out fields valid */ - MQ_SOCKS5_UNSUPPORTED, /* greeting: no acceptable auth method; caller sends method - reply 0xFF (NO ACCEPTABLE METHODS) */ - MQ_SOCKS5_UNSUPPORTED_CMD, /* request CMD not CONNECT; caller replies REP 0x07 - (command not supported), RFC 1928 §6 */ - MQ_SOCKS5_UNSUPPORTED_ATYP, /* request ATYP not supported; caller replies REP 0x08 - (address type not supported), RFC 1928 §6 */ - MQ_SOCKS5_ERROR, /* malformed */ - MQ_SOCKS5_ASSOCIATE_DONE /* CMD 0x03 parsed; out is DST (0.0.0.0:0 allowed) */ -} mq_socks5_status_t; - -typedef struct { - int phase; /* 0 = expect greeting, 1 = expect request */ -} mq_socks5_parser_t; - -typedef struct { - mq_addr_type_t atype; - uint8_t host[MQ_MAX_HOST]; /* IPv4: 4 raw bytes; IPv6: 16 raw; domain: name bytes */ - size_t host_len; - uint16_t port; -} mq_socks5_req_t; - -void mq_socks5_parser_init(mq_socks5_parser_t *p); - -/* Feed bytes; consumes from buf; sets *consumed to bytes used. Returns a status. - On GREETING_DONE: greeting parsed (caller sends method reply, then call again - with request bytes). On REQUEST_DONE or ASSOCIATE_DONE: *out is filled. */ -mq_socks5_status_t mq_socks5_feed(mq_socks5_parser_t *p, const uint8_t *buf, size_t len, - size_t *consumed, mq_socks5_req_t *out); - -/* reply builders */ -size_t mq_socks5_build_method_reply(uint8_t out[2], int accepted); -size_t mq_socks5_build_connect_reply(uint8_t out[10], uint8_t rep); -/* ASSOCIATE success reply: VER=5 REP=0 RSV=0 ATYP=1 BND.ADDR=bnd_ip_host - BND.PORT=bnd_port. bnd_ip_host is the IPv4 address in host byte order; caller converts - from network order with ntohl(). bnd_port is the port in host order. Always writes - exactly 10 bytes. */ -size_t mq_socks5_build_associate_reply(uint8_t out[10], uint32_t bnd_ip_host, - uint16_t bnd_port); -uint8_t mq_socks5_reply_code(mq_tcp_err_t err); - -/* UDP encapsulation header (RFC 1928 §7): RSV(2B) | FRAG(1B) | ATYP(1B) | DST | PORT(2B). - Returns bytes consumed (>= 0), -1 on malformed/truncated, -2 if FRAG != 0 (caller - drop). Strict: RSV != 0x0000 is rejected (-1). */ -typedef struct { - mq_socks5_req_t dst; - size_t hdr_len; -} mq_socks5_udp_hdr_t; - -int mq_socks5_parse_udp_hdr(const uint8_t *buf, size_t len, mq_socks5_udp_hdr_t *out); -/* Write UDP encapsulation header with RSV=0 FRAG=0 for given src address. - Returns bytes written, or -1 if cap is too small. */ -int mq_socks5_build_udp_hdr(uint8_t *out, size_t cap, const mq_socks5_req_t *src); - -#endif diff --git a/src/ingress/mq_tproxy.c b/src/ingress/mq_tproxy.c deleted file mode 100644 index 2f9bb2fd..00000000 --- a/src/ingress/mq_tproxy.c +++ /dev/null @@ -1,248 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors - -/* mq_tproxy.c — transparent-capture TCP listener. - * - * Mirrors mq_listener.c's accept-loop pattern but with no protocol parser: - * the original destination is obtained directly from the kernel on accept, - * then the fd is handed immediately to the proxy core via mq_tcp_open_fn. - * - * fd-ownership contract (mirrors mq_listener.h): - * - The listener OWNS the accepted fd from accept() up to the moment - * open_fn is called. If orig-dst recovery fails (pre-handoff), the - * listener closes cfd itself and returns. - * - Once open_fn is called the core (mq_client) OWNS the fd and closes - * it on every terminal outcome. The listener MUST NOT close it after - * calling open_fn — not in on_accept, not in tproxy_open_result. - * - tproxy_open_result is a no-op on fd ownership: transparent capture has - * no in-band reply to write (unlike SOCKS5/HTTP CONNECT), so the body - * only needs to handle per-conn state cleanup. Since no per-conn state - * is allocated (user=NULL passed to open_fn), the callback is empty - * except for the suppressed close. - */ - -#include "ingress/mq_tproxy.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ingress/mq_origdst.h" -#include "util/mq_log.h" - -struct mq_tproxy_listener { - struct event_base *base; - int listen_fd; - struct event *accept_ev; - uint16_t local_port; - mq_capture_mode_t mode; - mq_tcp_open_fn open_fn; - void *core; -}; - -/* ── open-result callback ────────────────────────────────────────────────── - * - * The core owns local_fd at this point. On ok=1 the relay is live; on ok=0 - * the core closes it AFTER this callback returns. We do NOT close local_fd - * in either case — the ownership contract is identical to http_open_result - * in mq_listener.c. - * - * Transparent capture has no in-band reply (no SOCKS5 REP, no HTTP 200), so - * there is nothing to write. No per-conn state was allocated (user==NULL), - * so there is nothing to free either. */ -static void -tproxy_open_result(int ok, mq_tcp_err_t err, void *user) -{ - /* The fd is owned by the core; do NOT close it here. */ - (void)ok; - (void)err; - (void)user; /* NULL — no per-conn state allocated */ -} - -/* ── helpers ───────────────────────────────────────────────────────────── */ - -static int -set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - if (fl < 0) return -1; - return fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -/* ── accept event ───────────────────────────────────────────────────────── */ - -static void -on_accept(evutil_socket_t lfd, short what, void *user) -{ - (void)what; - struct mq_tproxy_listener *l = (struct mq_tproxy_listener *)user; - - for (;;) { - int cfd = accept(lfd, NULL, NULL); - if (cfd < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) return; - if (errno == EINTR) continue; - MQ_LOGW("mq_tproxy: accept failed: %s", strerror(errno)); - return; - } - - /* Set the accepted fd non-blocking before any further work. */ - if (set_nonblock(cfd) != 0) { - MQ_LOGW("mq_tproxy: set_nonblock failed: %s", strerror(errno)); - close(cfd); - continue; - } - - /* Recover original destination — listener still owns cfd here. */ - struct sockaddr_storage od; - int rc = (l->mode == MQ_CAPTURE_TPROXY) ? mq_origdst_from_fd_tproxy(cfd, &od) - : mq_origdst_from_fd_redirect(cfd, &od); - if (rc < 0) { - MQ_LOGW("mq_tproxy: orig-dst recovery failed: %s", strerror(errno)); - close(cfd); /* pre-handoff: listener owns cfd */ - continue; - } - - uint8_t host[16]; - size_t host_len; - int atype; - uint16_t port; - if (mq_origdst_to_target(&od, host, sizeof(host), &host_len, &atype, &port) < 0) { - MQ_LOGW("mq_tproxy: unsupported address family from orig-dst"); - close(cfd); /* pre-handoff: listener owns cfd */ - continue; - } - - /* Log the recovered original destination — the key diagnostic for - * orig-dst issues. At DEBUG so it does not flood production logs with one - * line per captured connection (consistent with mq_listener.c, which does - * not INFO-log accepts); operators enable debug to inspect capture. */ - char ipbuf[INET6_ADDRSTRLEN] = "?"; - inet_ntop(atype == MQ_ADDR_IPV6 ? AF_INET6 : AF_INET, host, ipbuf, sizeof(ipbuf)); - MQ_LOGD("mq_tproxy: captured connection -> %s:%u", ipbuf, (unsigned)port); - - /* Hand off to the core — from this point the CORE owns cfd. - * Do NOT close cfd after this call (on success OR failure path). */ - l->open_fn(l->core, host, host_len, (mq_addr_type_t)atype, port, cfd, NULL, 0, - NULL, tproxy_open_result); - /* After open_fn returns, cfd is owned by the core; do not touch it. */ - } -} - -/* ── construction ───────────────────────────────────────────────────────── */ - -mq_tproxy_listener_t * -mq_tproxy_listener_new(struct event_base *base, const char *bind_ip, uint16_t port, - mq_capture_mode_t mode, mq_tcp_open_fn open_fn, void *core) -{ - if (!base || !bind_ip || !open_fn) return NULL; - - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) { - MQ_LOGE("mq_tproxy: socket: %s", strerror(errno)); - return NULL; - } - - int one = 1; - (void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - - /* IP_TRANSPARENT must be set BEFORE bind for TPROXY mode. - * This requires CAP_NET_ADMIN; fail clearly if the privilege is absent. */ - if (mode == MQ_CAPTURE_TPROXY) { - if (setsockopt(fd, SOL_IP, IP_TRANSPARENT, &one, sizeof(one)) != 0) { - MQ_LOGE("mq_tproxy: IP_TRANSPARENT setsockopt failed (CAP_NET_ADMIN " - "required): %s", - strerror(errno)); - close(fd); - return NULL; - } - } - - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - if (inet_pton(AF_INET, bind_ip, &sa.sin_addr) != 1) { - MQ_LOGE("mq_tproxy: invalid bind_ip '%s'", bind_ip); - close(fd); - return NULL; - } - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - MQ_LOGE("mq_tproxy: bind %s:%u failed: %s", bind_ip, (unsigned)port, - strerror(errno)); - close(fd); - return NULL; - } - if (listen(fd, 64) != 0) { - MQ_LOGE("mq_tproxy: listen failed: %s", strerror(errno)); - close(fd); - return NULL; - } - if (set_nonblock(fd) != 0) { - MQ_LOGE("mq_tproxy: set_nonblock(listen_fd) failed: %s", strerror(errno)); - close(fd); - return NULL; - } - - /* Query actual bound port (support ephemeral port 0). */ - struct sockaddr_in bound; - socklen_t blen = sizeof(bound); - uint16_t lport = 0; - if (getsockname(fd, (struct sockaddr *)&bound, &blen) == 0) { - lport = ntohs(bound.sin_port); - } - - struct mq_tproxy_listener *l = calloc(1, sizeof(*l)); - if (!l) { - close(fd); - return NULL; - } - l->base = base; - l->listen_fd = fd; - l->local_port = lport; - l->mode = mode; - l->open_fn = open_fn; - l->core = core; - - l->accept_ev = event_new(base, fd, EV_READ | EV_PERSIST, on_accept, l); - if (!l->accept_ev) { - close(fd); - free(l); - return NULL; - } - event_add(l->accept_ev, NULL); - return l; -} - -/* ── accessors ──────────────────────────────────────────────────────────── */ - -uint16_t -mq_tproxy_listener_port(const mq_tproxy_listener_t *l) -{ - return l ? l->local_port : 0; -} - -/* ── teardown ───────────────────────────────────────────────────────────── */ - -void -mq_tproxy_listener_free(mq_tproxy_listener_t *l) -{ - if (!l) return; - if (l->accept_ev) { - event_free(l->accept_ev); - l->accept_ev = NULL; - } - if (l->listen_fd >= 0) { - close(l->listen_fd); - l->listen_fd = -1; - } - free(l); -} diff --git a/src/ingress/mq_tproxy.h b/src/ingress/mq_tproxy.h deleted file mode 100644 index e44876cc..00000000 --- a/src/ingress/mq_tproxy.h +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -#ifndef MQ_TPROXY_H -#define MQ_TPROXY_H - -/* mq_tproxy.h — transparent-capture TCP listener (TPROXY / REDIRECT). - * - * Accepts kernel-redirected TCP connections, learns the original destination - * from the kernel (via getsockname for TPROXY, SO_ORIGINAL_DST for REDIRECT), - * and immediately hands the accepted fd to the proxy core via mq_tcp_open_fn. - * No protocol is spoken toward the client; the relay is fully opaque. - * - * Capture modes: - * MQ_CAPTURE_REDIRECT — iptables nat REDIRECT (single-host OUTPUT or gateway - * PREROUTING); the original destination is recovered via SO_ORIGINAL_DST. - * Does NOT require IP_TRANSPARENT on the listen socket. - * - * MQ_CAPTURE_TPROXY — iptables mangle TPROXY (router PREROUTING, foreign - * destinations); the listen socket must have IP_TRANSPARENT set BEFORE - * bind (requires CAP_NET_ADMIN). The original destination is the local - * address reported by getsockname on the accepted fd. - * - * Because IP_TRANSPARENT requires CAP_NET_ADMIN there is no unprivileged unit - * test for the listener constructor. The pure orig-dst logic is tested by - * test_origdst; the live end-to-end path is covered by the Task 9 NET_ADMIN - * e2e suite. - * - * v1 is IPv4-only (AF_INET socket). - */ - -#include -#include - -#include "ingress/mq_ingress.h" /* mq_tcp_open_fn */ - -typedef enum { - MQ_CAPTURE_REDIRECT = 0, /* nat REDIRECT; orig-dst via SO_ORIGINAL_DST */ - MQ_CAPTURE_TPROXY = 1, /* mangle TPROXY; orig-dst via getsockname */ -} mq_capture_mode_t; - -typedef struct mq_tproxy_listener mq_tproxy_listener_t; - -/* Create a transparent-capture listener bound to bind_ip:port (port 0 => - * ephemeral; retrieve with mq_tproxy_listener_port). - * - * mode selects the kernel capture mechanism; in MQ_CAPTURE_TPROXY mode the - * socket is given IP_TRANSPARENT before bind — this requires CAP_NET_ADMIN and - * will return NULL with a logged error if the setsockopt fails. - * - * base is borrowed (must outlive the listener). open_fn/core are the - * proxy-core TCP boundary invoked for every successfully accepted connection. - * - * Returns NULL on bad args / privilege failure / bind / listen failure. */ -mq_tproxy_listener_t *mq_tproxy_listener_new(struct event_base *base, const char *bind_ip, - uint16_t port, mq_capture_mode_t mode, - mq_tcp_open_fn open_fn, void *core); - -/* The bound local TCP port in host byte order (ephemeral-bind support). - * Returns 0 if unknown or l is NULL. */ -uint16_t mq_tproxy_listener_port(const mq_tproxy_listener_t *l); - -/* Stop accepting, close the listen socket, free the listener. - * Safe on NULL. */ -void mq_tproxy_listener_free(mq_tproxy_listener_t *l); - -#endif /* MQ_TPROXY_H */ diff --git a/src/ingress/mq_tproxy_setup.c b/src/ingress/mq_tproxy_setup.c deleted file mode 100644 index 424648a7..00000000 --- a/src/ingress/mq_tproxy_setup.c +++ /dev/null @@ -1,348 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors - -/* mq_tproxy_setup.c — optional firewall-rule installer/uninstaller. - * - * Modelled on mqvpn/src/platform/linux/routing.c's run_ip_cmd pattern: - * fork + execvp + waitpid, no shell involvement. - * - * REDIRECT mode (nft only): - * nft add table ip mqproxy - * nft add chain ip mqproxy out '{ type nat hook output priority -100 ; }' - * nft add rule ip mqproxy out meta skuid return - * nft add rule ip mqproxy out meta l4proto tcp tcp dport redirect to : - * - * Cleanup: nft delete table ip mqproxy - * - * TPROXY mode (ip rule + ip route + nft): - * ip rule add fwmark lookup - * ip route add local 0.0.0.0/0 dev lo table
- * nft add table ip mqproxy - * nft add chain ip mqproxy div '{ type filter hook prerouting priority -150 ; }' - * nft add rule ip mqproxy div meta l4proto tcp meta skuid return - * nft add rule ip mqproxy div meta l4proto tcp tcp dport - * tproxy to : meta mark set accept - * - * Cleanup: - * nft delete table ip mqproxy - * ip rule del fwmark lookup
- * ip route del local 0.0.0.0/0 dev lo table
- */ - -#include "ingress/mq_tproxy_setup.h" -#include "util/mq_log.h" - -#include -#include -#include -#include -#include -#include -#include - -struct mq_tproxy_setup { - mq_capture_mode_t mode; - char bind_ip[64]; /* informational */ - uint16_t listener_port; - uint16_t dport; - uid_t skip_uid; - int fwmark; - int table; -}; - -/* ── command execution ────────────────────────────────────────────────────── - * - * run_cmd: fork + execvp(prog, argv) + waitpid. - * Returns 0 on success (exit status 0), -1 on fork failure, exec failure, - * wait interruption, or non-zero exit. On any failure the error is logged - * before returning. - * - * The argv array must be NULL-terminated. prog is both the executable name - * (resolved via PATH) and argv[0]. This matches the mqvpn routing.c style. - */ -static int -run_cmd(const char *prog, const char *const argv[]) -{ - /* Log the full command at INFO level so the operator can audit. */ - { - char buf[512]; - int off = 0; - for (int i = 0; argv[i] && off < (int)sizeof(buf) - 2; i++) { - if (i > 0) buf[off++] = ' '; - int n = snprintf(buf + off, sizeof(buf) - (size_t)off, "%s", argv[i]); - if (n > 0) off += n; - } - buf[off] = '\0'; - MQ_LOGI("mq_tproxy_setup: run: %s", buf); - } - - pid_t pid = fork(); - if (pid < 0) { - MQ_LOGE("mq_tproxy_setup: fork failed: %s", strerror(errno)); - return -1; - } - if (pid == 0) { - /* This runs privileged (root / CAP_NET_ADMIN) and resolves `nft`/`ip` - * via PATH. Pin a known-safe PATH so a polluted service environment - * cannot substitute an attacker-controlled `nft`/`ip` binary. Standard - * system sbin/bin dirs cover both Debian and split-/usr layouts without - * hardcoding a distro-specific absolute path. */ - setenv("PATH", "/usr/sbin:/usr/bin:/sbin:/bin", 1); - execvp(prog, (char *const *)argv); - /* execvp only returns on failure */ - _exit(127); - } - int status = 0; - while (waitpid(pid, &status, 0) < 0) { - if (errno != EINTR) { - MQ_LOGE("mq_tproxy_setup: waitpid: %s", strerror(errno)); - return -1; - } - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - if (WIFEXITED(status)) - MQ_LOGE("mq_tproxy_setup: '%s' exited %d", prog, WEXITSTATUS(status)); - else - MQ_LOGE("mq_tproxy_setup: '%s' killed by signal %d", prog, WTERMSIG(status)); - return -1; - } - return 0; -} - -/* ── REDIRECT install ───────────────────────────────────────────────────── */ - -/* Best-effort, partial-state-tolerated: on a mid-sequence command failure we set - * rc=-1 but CONTINUE issuing the remaining commands (and the caller logs the - * failure). uninstall is idempotent and removes whatever did get installed, so a - * partial install is cleaned up at teardown rather than left to accumulate. */ -static int -install_redirect(const mq_tproxy_setup_t *s) -{ - char uid_str[32], port_str[32], dport_str[32], redir_str[32]; - snprintf(uid_str, sizeof(uid_str), "%lu", (unsigned long)s->skip_uid); - snprintf(port_str, sizeof(port_str), "%u", (unsigned)s->listener_port); - snprintf(dport_str, sizeof(dport_str), "%u", (unsigned)s->dport); - snprintf(redir_str, sizeof(redir_str), ":%u", (unsigned)s->listener_port); - - int rc = 0; - - /* nft add table ip mqproxy */ - { - const char *const a[] = {"nft", "add", "table", "ip", "mqproxy", NULL}; - if (run_cmd("nft", a) != 0) rc = -1; - } - - /* nft add chain ip mqproxy out '{ type nat hook output priority -100 ; }' */ - { - const char *const a[] = {"nft", - "add", - "chain", - "ip", - "mqproxy", - "out", - "{ type nat hook output priority -100 ; }", - NULL}; - if (run_cmd("nft", a) != 0) rc = -1; - } - - /* nft add rule ip mqproxy out meta skuid return */ - { - const char *const a[] = {"nft", "add", "rule", "ip", "mqproxy", "out", - "meta", "skuid", uid_str, "return", NULL}; - if (run_cmd("nft", a) != 0) rc = -1; - } - - /* nft add rule ip mqproxy out meta l4proto tcp tcp dport redirect to : - */ - { - const char *const a[] = { - "nft", "add", "rule", "ip", "mqproxy", "out", "meta", "l4proto", - "tcp", "tcp", "dport", dport_str, "redirect", "to", redir_str, NULL}; - if (run_cmd("nft", a) != 0) rc = -1; - } - - if (rc != 0) { - MQ_LOGE("mq_tproxy_setup: REDIRECT install failed for dport=%s -> :%s", dport_str, - port_str); - } else { - MQ_LOGI("mq_tproxy_setup: REDIRECT rules installed " - "(uid=%s exempted, dport=%s -> :%s)", - uid_str, dport_str, port_str); - } - return rc; -} - -/* ── TPROXY install ─────────────────────────────────────────────────────── */ - -static int -install_tproxy(const mq_tproxy_setup_t *s) -{ - char uid_str[32], port_str[32], dport_str[32], redir_str[32]; - char mark_str[32], table_str[32]; - snprintf(uid_str, sizeof(uid_str), "%lu", (unsigned long)s->skip_uid); - snprintf(port_str, sizeof(port_str), "%u", (unsigned)s->listener_port); - snprintf(dport_str, sizeof(dport_str), "%u", (unsigned)s->dport); - snprintf(redir_str, sizeof(redir_str), ":%u", (unsigned)s->listener_port); - snprintf(mark_str, sizeof(mark_str), "%d", s->fwmark); - snprintf(table_str, sizeof(table_str), "%d", s->table); - - int rc = 0; - - /* ip rule add fwmark lookup
*/ - { - const char *const a[] = {"ip", "rule", "add", "fwmark", - mark_str, "lookup", table_str, NULL}; - if (run_cmd("ip", a) != 0) rc = -1; - } - - /* ip route add local 0.0.0.0/0 dev lo table
*/ - { - const char *const a[] = {"ip", "route", "add", "local", "0.0.0.0/0", - "dev", "lo", "table", table_str, NULL}; - if (run_cmd("ip", a) != 0) rc = -1; - } - - /* nft add table ip mqproxy */ - { - const char *const a[] = {"nft", "add", "table", "ip", "mqproxy", NULL}; - if (run_cmd("nft", a) != 0) rc = -1; - } - - /* nft add chain ip mqproxy div '{ type filter hook prerouting priority -150 ; }' */ - { - const char *const a[] = {"nft", - "add", - "chain", - "ip", - "mqproxy", - "div", - "{ type filter hook prerouting priority -150 ; }", - NULL}; - if (run_cmd("nft", a) != 0) rc = -1; - } - - /* nft add rule ip mqproxy div meta l4proto tcp meta skuid return */ - { - const char *const a[] = {"nft", "add", "rule", "ip", "mqproxy", - "div", "meta", "l4proto", "tcp", "meta", - "skuid", uid_str, "return", NULL}; - if (run_cmd("nft", a) != 0) rc = -1; - } - - /* nft add rule ip mqproxy div meta l4proto tcp tcp dport - * tproxy to : meta mark set accept */ - { - const char *const a[] = { - "nft", "add", "rule", "ip", "mqproxy", "div", "meta", - "l4proto", "tcp", "tcp", "dport", dport_str, "tproxy", "to", - redir_str, "meta", "mark", "set", mark_str, "accept", NULL}; - if (run_cmd("nft", a) != 0) rc = -1; - } - - if (rc != 0) { - MQ_LOGE("mq_tproxy_setup: TPROXY install failed " - "(mark=%s table=%s dport=%s -> :%s)", - mark_str, table_str, dport_str, port_str); - } else { - MQ_LOGI("mq_tproxy_setup: TPROXY rules installed " - "(uid=%s exempted, mark=%s, table=%s, dport=%s -> :%s)", - uid_str, mark_str, table_str, dport_str, port_str); - } - return rc; -} - -/* ── REDIRECT uninstall ─────────────────────────────────────────────────── */ - -static void -uninstall_redirect(const mq_tproxy_setup_t *s) -{ - (void)s; /* table name is fixed ("mqproxy"); no per-instance state needed */ - - /* nft delete table ip mqproxy */ - const char *const a[] = {"nft", "delete", "table", "ip", "mqproxy", NULL}; - if (run_cmd("nft", a) != 0) - MQ_LOGW("mq_tproxy_setup: REDIRECT cleanup: nft delete table failed (tolerated)"); - else - MQ_LOGI("mq_tproxy_setup: REDIRECT rules removed"); -} - -/* ── TPROXY uninstall ───────────────────────────────────────────────────── */ - -static void -uninstall_tproxy(const mq_tproxy_setup_t *s) -{ - char mark_str[32], table_str[32]; - snprintf(mark_str, sizeof(mark_str), "%d", s->fwmark); - snprintf(table_str, sizeof(table_str), "%d", s->table); - - /* nft delete table ip mqproxy */ - { - const char *const a[] = {"nft", "delete", "table", "ip", "mqproxy", NULL}; - if (run_cmd("nft", a) != 0) - MQ_LOGW( - "mq_tproxy_setup: TPROXY cleanup: nft delete table failed (tolerated)"); - } - - /* ip rule del fwmark lookup
*/ - { - const char *const a[] = {"ip", "rule", "del", "fwmark", - mark_str, "lookup", table_str, NULL}; - if (run_cmd("ip", a) != 0) - MQ_LOGW("mq_tproxy_setup: TPROXY cleanup: ip rule del failed (tolerated)"); - } - - /* ip route del local 0.0.0.0/0 dev lo table
*/ - { - const char *const a[] = {"ip", "route", "del", "local", "0.0.0.0/0", - "dev", "lo", "table", table_str, NULL}; - if (run_cmd("ip", a) != 0) - MQ_LOGW("mq_tproxy_setup: TPROXY cleanup: ip route del failed (tolerated)"); - } - - MQ_LOGI("mq_tproxy_setup: TPROXY rules cleanup done (mark=%s table=%s)", mark_str, - table_str); -} - -/* ── public API ─────────────────────────────────────────────────────────── */ - -mq_tproxy_setup_t * -mq_tproxy_setup_new(mq_capture_mode_t mode, const char *bind_ip, uint16_t listener_port, - uint16_t dport, uid_t skip_uid, int fwmark, int table) -{ - mq_tproxy_setup_t *s = calloc(1, sizeof(*s)); - if (!s) return NULL; - - s->mode = mode; - s->listener_port = listener_port; - s->dport = dport; - s->skip_uid = (skip_uid == (uid_t)-1) ? geteuid() : skip_uid; - s->fwmark = fwmark; - s->table = table; - if (bind_ip) snprintf(s->bind_ip, sizeof(s->bind_ip), "%s", bind_ip); - - return s; -} - -int -mq_tproxy_setup_install(mq_tproxy_setup_t *s) -{ - if (!s) return -1; - if (s->mode == MQ_CAPTURE_TPROXY) return install_tproxy(s); - return install_redirect(s); -} - -void -mq_tproxy_setup_uninstall(mq_tproxy_setup_t *s) -{ - if (!s) return; - if (s->mode == MQ_CAPTURE_TPROXY) - uninstall_tproxy(s); - else - uninstall_redirect(s); -} - -void -mq_tproxy_setup_free(mq_tproxy_setup_t *s) -{ - free(s); -} diff --git a/src/ingress/mq_tproxy_setup.h b/src/ingress/mq_tproxy_setup.h deleted file mode 100644 index ff384c91..00000000 --- a/src/ingress/mq_tproxy_setup.h +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -#ifndef MQ_TPROXY_SETUP_H -#define MQ_TPROXY_SETUP_H - -/* mq_tproxy_setup.h — optional kernel firewall helper for transparent capture. - * - * Installs / uninstalls the nftables + ip-rule/ip-route rules needed to - * redirect TCP traffic into the mq_tproxy_listener. Requires root or - * CAP_NET_ADMIN + CAP_NET_RAW. Use is OPTIONAL — operators who manage their - * own firewall rules can skip this entirely and just wire --tproxy. - * - * REDIRECT mode: - * nft: output-hook nat REDIRECT → listener-port (skip owner UID) - * - * TPROXY mode: - * ip rule + ip route: mark-based local routing - * nft: prerouting-hook filter TPROXY → listener-port (mark set, skip UID) - * - * All state (table name, mark, etc.) is owned by the mq_tproxy_setup_t - * handle. install / uninstall are idempotent at the nft/ip level (nft - * delete-table is silent if the table does not exist; ip rule/route del - * errors are logged and tolerated). - */ - -#include -#include - -#include "ingress/mq_tproxy.h" /* mq_capture_mode_t */ - -typedef struct mq_tproxy_setup mq_tproxy_setup_t; - -/* Allocate a setup handle. - * mode — capture mode (REDIRECT or TPROXY) - * bind_ip — listener bind address (informational; used in log only) - * listener_port — TCP port the mq_tproxy_listener is bound to - * dport — destination port to capture (e.g. 443) - * skip_uid — UID whose outbound traffic is NOT redirected (the proxy - * process itself); pass (uid_t)-1 to use geteuid() - * fwmark — packet mark for TPROXY routing (ignored in REDIRECT mode) - * table — ip routing table for TPROXY (ignored in REDIRECT mode) - * - * Returns NULL on allocation failure. */ -mq_tproxy_setup_t *mq_tproxy_setup_new(mq_capture_mode_t mode, const char *bind_ip, - uint16_t listener_port, uint16_t dport, - uid_t skip_uid, int fwmark, int table); - -/* Run the install commands. - * Returns 0 if all commands succeeded, -1 if any failed (errors logged). */ -int mq_tproxy_setup_install(mq_tproxy_setup_t *s); - -/* Run the uninstall/cleanup commands (idempotent; errors are logged, not - * propagated — teardown must not prevent process exit). Safe on NULL. */ -void mq_tproxy_setup_uninstall(mq_tproxy_setup_t *s); - -/* Free the handle. Does NOT run uninstall — call mq_tproxy_setup_uninstall - * first if rules were installed. Safe on NULL. */ -void mq_tproxy_setup_free(mq_tproxy_setup_t *s); - -#endif /* MQ_TPROXY_SETUP_H */ diff --git a/src/ingress/mq_udp_assoc.c b/src/ingress/mq_udp_assoc.c deleted file mode 100644 index acfc8340..00000000 --- a/src/ingress/mq_udp_assoc.c +++ /dev/null @@ -1,532 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_udp_assoc.c — SOCKS5 UDP ASSOCIATE edge. See mq_udp_assoc.h. */ -#include "ingress/mq_udp_assoc.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ingress/mq_socks5.h" -#include "util/mq_log.h" - -/* Per-assoc DST session map: fixed linear table. A single association's set of - * distinct destinations is small (one client app), so 64 entries with a linear - * scan is ample; full => drop the datagram (it retries naturally). */ -#define MQ_UDP_ASSOC_MAX_DST 64 - -/* Negative cache window: suppress re-OPEN of a DST that failed (remote OPEN - * error) for this many milliseconds. */ -#define MQ_UDP_ASSOC_NEGCACHE_MS 2000 - -/* Largest datagram we accept/relay (encap header + payload). MTU-ish; the relay - * itself fragments nothing. */ -#define MQ_UDP_ASSOC_DGRAM_CAP 65535 - -struct mq_dst_entry { - int used; - /* DST key: atyp + raw host bytes (host_len) + port. */ - mq_addr_type_t atype; - uint8_t host[MQ_MAX_HOST]; - size_t host_len; - uint16_t port; - /* Relay session handle from open_fn (NULL while none / after death). */ - void *session; - int dead; /* on_err received: handle invalid, never close it. */ - uint64_t failed_at; /* monotonic ms of last remote OPEN failure (neg cache); - 0 = no negative cache active. */ -}; - -/* Per-DST `user` carried across the open_fn boundary into on_rx/on_err. One per - * slot, kept parallel to the DST table so the pointer stays valid for the whole - * session lifetime and is freed with the assoc. */ -struct rx_ctx { - struct mq_udp_assoc *a; - mq_addr_type_t atype; - uint8_t host[MQ_MAX_HOST]; - size_t host_len; - uint16_t port; -}; - -struct mq_udp_assoc { - struct event_base *base; - int tcp_fd; /* SOCKS5 control connection; EOF/error => teardown. */ - struct event *tcp_ev; - int udp_fd; /* relay socket bound bind_ip:ephemeral. */ - struct event *udp_ev; - uint16_t udp_port; /* host order */ - - /* boundary */ - mq_udp_open_fn open_fn; - mq_udp_send_fn send_fn; - mq_udp_close_fn close_fn; - void *core; - - /* Source learning (RFC 1928 §7): locked after the first datagram from the - * TCP peer's IP. */ - struct sockaddr_in tcp_peer; /* learned at ctor (getpeername); IP filter. */ - int client_locked; - struct sockaddr_in client_addr; /* exact IP:port the client sends from. */ - - struct mq_dst_entry dst[MQ_UDP_ASSOC_MAX_DST]; - struct rx_ctx rxslot[MQ_UDP_ASSOC_MAX_DST]; /* parallel to dst[] */ - - /* intrusive list owned by the listener (head lives on mq_listener_s). */ - mq_udp_assoc_t **list_head; /* &listener->assocs (for self-removal) */ - mq_udp_assoc_t *prev; - mq_udp_assoc_t *next; - int tearing_down; /* re-entrancy guard for teardown */ -}; - -void -mq_udp_assoc_list_push(mq_udp_assoc_t **head, mq_udp_assoc_t *a) -{ - if (!head || !a) return; - a->list_head = head; - a->prev = NULL; - a->next = *head; - if (*head) (*head)->prev = a; - *head = a; -} - -mq_udp_assoc_t * -mq_udp_assoc_list_next(mq_udp_assoc_t *a) -{ - return a ? a->next : NULL; -} - -/* Unlink `a` from its list (if linked). Safe to call repeatedly. */ -static void -list_unlink(mq_udp_assoc_t *a) -{ - if (!a->list_head) return; /* not linked */ - if (a->prev) { - a->prev->next = a->next; - } else if (*a->list_head == a) { - *a->list_head = a->next; - } - if (a->next) a->next->prev = a->prev; - a->prev = NULL; - a->next = NULL; - a->list_head = NULL; -} - -static uint64_t -now_ms(void) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; -} - -static int -set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - if (fl < 0) return -1; - return fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -/* ── DST map ────────────────────────────────────────────────────────────── */ - -static int -dst_key_eq(const struct mq_dst_entry *e, const mq_socks5_req_t *dst) -{ - return e->used && e->atype == dst->atype && e->host_len == dst->host_len && - e->port == dst->port && - (dst->host_len == 0 || memcmp(e->host, dst->host, dst->host_len) == 0); -} - -static struct mq_dst_entry * -dst_find(mq_udp_assoc_t *a, const mq_socks5_req_t *dst) -{ - for (size_t i = 0; i < MQ_UDP_ASSOC_MAX_DST; i++) { - if (dst_key_eq(&a->dst[i], dst)) return &a->dst[i]; - } - return NULL; -} - -static struct mq_dst_entry * -dst_alloc(mq_udp_assoc_t *a, const mq_socks5_req_t *dst) -{ - struct mq_dst_entry *slot = NULL; - for (size_t i = 0; i < MQ_UDP_ASSOC_MAX_DST; i++) { - if (!a->dst[i].used) { - slot = &a->dst[i]; - break; - } - } - /* No free slot: reclaim a dead entry whose negative-cache window has expired - * (failed_at == 0 means a non-OPEN-failure close, which is never cached). - * Without this sweep, an assoc that churns through > MQ_UDP_ASSOC_MAX_DST - * destinations could never open a new one even when all old entries are - * long dead. */ - if (!slot) { - uint64_t now = now_ms(); - for (size_t i = 0; i < MQ_UDP_ASSOC_MAX_DST; i++) { - struct mq_dst_entry *e = &a->dst[i]; - if (e->dead && - (e->failed_at == 0 || now - e->failed_at >= MQ_UDP_ASSOC_NEGCACHE_MS)) { - slot = e; - break; - } - } - } - if (!slot) { - return NULL; /* table full of live/cached entries => caller drops */ - } - memset(slot, 0, sizeof(*slot)); - slot->used = 1; - slot->atype = dst->atype; - slot->host_len = dst->host_len; - if (dst->host_len) memcpy(slot->host, dst->host, dst->host_len); - slot->port = dst->port; - return slot; -} - -/* Map a relay session handle back to its DST entry (for on_rx/on_err). */ -static struct mq_dst_entry * -dst_by_session(mq_udp_assoc_t *a, void *session) -{ - if (!session) return NULL; - for (size_t i = 0; i < MQ_UDP_ASSOC_MAX_DST; i++) { - if (a->dst[i].used && !a->dst[i].dead && a->dst[i].session == session) { - return &a->dst[i]; - } - } - return NULL; -} - -/* ── reverse direction (relay → client) ─────────────────────────────────── */ - -static void -on_rx(const uint8_t *payload, size_t len, void *user) -{ - struct rx_ctx *rc = (struct rx_ctx *)user; - mq_udp_assoc_t *a = rc->a; - if (!a->client_locked) return; /* no client to send to yet */ - - /* Build encap header with the DST as the source address, then payload. */ - mq_socks5_req_t src; - memset(&src, 0, sizeof(src)); - src.atype = rc->atype; - src.host_len = rc->host_len; - if (rc->host_len) memcpy(src.host, rc->host, rc->host_len); - src.port = rc->port; - - /* 65535-byte stack: leaf event-callback frame; transient stack beats per-assoc - * resident scratch for many-idle-assoc deployments (deliberate divergence). */ - uint8_t out[MQ_UDP_ASSOC_DGRAM_CAP]; - int hlen = mq_socks5_build_udp_hdr(out, sizeof(out), &src); - if (hlen < 0) return; - if ((size_t)hlen + len > sizeof(out)) return; /* oversized; drop */ - if (len) memcpy(out + hlen, payload, len); - - (void)sendto(a->udp_fd, out, (size_t)hlen + len, MSG_NOSIGNAL, - (struct sockaddr *)&a->client_addr, sizeof(a->client_addr)); -} - -static void -on_err(void *session, mq_udp_err_t err, void *user) -{ - struct rx_ctx *rc = (struct rx_ctx *)user; - mq_udp_assoc_t *a = rc->a; - struct mq_dst_entry *e = dst_by_session(a, session); - if (!e) return; /* already reaped / unknown — late event, ignore */ - - /* Negative cache only for remote OPEN failures (RESP error decode). A - * normal close / idle timeout (MQ_UDP_CLOSED) just retires the entry so a - * future datagram re-opens immediately. */ - if (err != MQ_UDP_CLOSED) { - e->failed_at = now_ms(); - } - /* Contract: handle invalid after on_err; never call close_fn on it. Mark - * dead and detach the session pointer. The rx_ctx stays attached to the - * entry (freed when the entry is reused or the assoc dies). */ - e->dead = 1; - e->session = NULL; -} - -/* ── datagram receive (client → relay) ──────────────────────────────────── */ - -static void handle_client_datagram(mq_udp_assoc_t *a, const uint8_t *buf, size_t len, - const struct sockaddr_in *from); - -static void -on_udp_readable(evutil_socket_t fd, short what, void *user) -{ - (void)what; - mq_udp_assoc_t *a = (mq_udp_assoc_t *)user; - for (;;) { - /* 65535-byte stack: leaf event-callback frame; transient stack beats per-assoc - * resident scratch for many-idle-assoc deployments (deliberate divergence). */ - uint8_t buf[MQ_UDP_ASSOC_DGRAM_CAP]; - struct sockaddr_in from; - socklen_t flen = sizeof(from); - ssize_t n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &flen); - if (n < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) return; - if (errno == EINTR) continue; - return; - } - if (flen < (socklen_t)sizeof(from) || from.sin_family != AF_INET) { - continue; /* IPv4 only on this socket */ - } - handle_client_datagram(a, buf, (size_t)n, &from); - } -} - -static void -handle_client_datagram(mq_udp_assoc_t *a, const uint8_t *buf, size_t len, - const struct sockaddr_in *from) -{ - /* Source learning + filter. */ - if (!a->client_locked) { - /* First datagram must come from the TCP peer's IP (any port). */ - if (from->sin_addr.s_addr != a->tcp_peer.sin_addr.s_addr) { - return; /* spoofed source; drop */ - } - a->client_addr = *from; - a->client_locked = 1; - } else { - /* Exact IP:port match only. */ - if (from->sin_addr.s_addr != a->client_addr.sin_addr.s_addr || - from->sin_port != a->client_addr.sin_port) { - return; /* drop */ - } - } - - /* Parse the SOCKS5 UDP encapsulation header. */ - mq_socks5_udp_hdr_t hdr; - memset(&hdr, 0, sizeof(hdr)); - int r = mq_socks5_parse_udp_hdr(buf, len, &hdr); - if (r < 0) return; /* malformed (-1) or FRAG != 0 (-2): drop */ - - const uint8_t *payload = buf + hdr.hdr_len; - size_t payload_len = len - hdr.hdr_len; - - struct mq_dst_entry *e = dst_find(a, &hdr.dst); - if (e) { - if (e->dead) { - /* Negative cache: suppress re-open within the window. */ - if (e->failed_at != 0 && now_ms() - e->failed_at < MQ_UDP_ASSOC_NEGCACHE_MS) { - return; /* drop */ - } - /* Window expired (or close, no neg cache): retire and re-open. */ - e->used = 0; - e->session = NULL; - e->dead = 0; - e->failed_at = 0; - e = NULL; - } else if (e->session) { - /* Live session: optimistic send. */ - a->send_fn(e->session, payload, payload_len); - return; - } else { - /* Used but no live session and not dead: shouldn't happen; reset. */ - e->used = 0; - e = NULL; - } - } - - /* No (live) entry: allocate one and open a session. */ - if (!e) { - e = dst_alloc(a, &hdr.dst); - if (!e) return; /* table full: drop, no cache */ - } - - /* Stash the DST in the rx_ctx kept parallel to this slot. */ - size_t idx = (size_t)(e - a->dst); - struct rx_ctx *rc = &a->rxslot[idx]; - rc->a = a; - rc->atype = hdr.dst.atype; - rc->host_len = hdr.dst.host_len; - if (hdr.dst.host_len) memcpy(rc->host, hdr.dst.host, hdr.dst.host_len); - rc->port = hdr.dst.port; - - void *session = a->open_fn(a->core, hdr.dst.host, hdr.dst.host_len, hdr.dst.atype, - hdr.dst.port, on_rx, on_err, rc); - if (!session) { - /* NULL = transient (session limit / pre-auth queue full / availability - * undetermined): drop this datagram only, do NOT cache. Retire the - * just-allocated entry so the next datagram retries. */ - e->used = 0; - return; - } - e->session = session; - e->dead = 0; - e->failed_at = 0; - - /* Optimistic send right away. */ - a->send_fn(session, payload, payload_len); -} - -/* ── TCP control connection (EOF watch) ─────────────────────────────────── */ - -static void -assoc_teardown(mq_udp_assoc_t *a) -{ - if (!a) return; - if (a->tearing_down) return; - a->tearing_down = 1; - - /* Close every live relay session (caller-initiated terminate). Dead entries - * (on_err already fired) must NOT be closed — boundary contract. */ - for (size_t i = 0; i < MQ_UDP_ASSOC_MAX_DST; i++) { - struct mq_dst_entry *e = &a->dst[i]; - if (e->used && !e->dead && e->session) { - a->close_fn(e->session); - } - e->session = NULL; - e->used = 0; - } - - /* Self-remove from the listener's active list. */ - list_unlink(a); -} - -static void -on_tcp_event(evutil_socket_t fd, short what, void *user) -{ - (void)what; - mq_udp_assoc_t *a = (mq_udp_assoc_t *)user; - /* The control connection carries no application data after ASSOCIATE; any - * readable event is EOF or an error => tear the whole association down - * (RFC 1928 §7). Drain to confirm. */ - uint8_t scratch[256]; - ssize_t n = recv(fd, scratch, sizeof(scratch), 0); - if (n > 0) { - /* Unexpected data on the control channel; the client should not send - * anything. Ignore it but keep the assoc alive (be permissive). */ - return; - } - if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) { - /* mq_udp_assoc_free runs assoc_teardown (close live sessions + unlink - * from the listener list) then frees the shell. */ - mq_udp_assoc_free(a); - return; - } - /* EAGAIN/EINTR: spurious wakeup; ignore. */ -} - -/* ── construction / teardown ────────────────────────────────────────────── */ - -mq_udp_assoc_t * -mq_udp_assoc_new(struct event_base *base, int tcp_fd, const char *bind_ip, - mq_udp_open_fn open_fn, mq_udp_send_fn send_fn, mq_udp_close_fn close_fn, - void *core) -{ - if (!base || tcp_fd < 0 || !bind_ip || !open_fn || !send_fn || !close_fn) { - return NULL; - } - - mq_udp_assoc_t *a = calloc(1, sizeof(*a)); - if (!a) return NULL; - a->base = base; - a->tcp_fd = tcp_fd; - a->udp_fd = -1; - a->open_fn = open_fn; - a->send_fn = send_fn; - a->close_fn = close_fn; - a->core = core; - - /* Learn the TCP peer's IP for source filtering. */ - socklen_t plen = sizeof(a->tcp_peer); - if (getpeername(tcp_fd, (struct sockaddr *)&a->tcp_peer, &plen) != 0 || - a->tcp_peer.sin_family != AF_INET) { - free(a); - return NULL; - } - - /* Bind the UDP relay socket on bind_ip:ephemeral. */ - int ufd = socket(AF_INET, SOCK_DGRAM, 0); - if (ufd < 0) { - free(a); - return NULL; - } - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = 0; /* ephemeral */ - if (inet_pton(AF_INET, bind_ip, &sa.sin_addr) != 1) { - close(ufd); - free(a); - return NULL; - } - if (bind(ufd, (struct sockaddr *)&sa, sizeof(sa)) != 0 || set_nonblock(ufd) != 0) { - close(ufd); - free(a); - return NULL; - } - a->udp_fd = ufd; - - /* Determine the UDP port and the BND.ADDR we advertise. */ - struct sockaddr_in ubound; - socklen_t ulen = sizeof(ubound); - if (getsockname(ufd, (struct sockaddr *)&ubound, &ulen) == 0) { - a->udp_port = ntohs(ubound.sin_port); - } - - /* BND.ADDR: the IP the client can reach the UDP socket at. If bind_ip is a - * concrete address, use it; if it is the wildcard 0.0.0.0, the standard - * approach is the TCP socket's local address (the interface the client - * already reached us on). BND.PORT is always the UDP socket's port. */ - uint32_t bnd_ip_be = ubound.sin_addr.s_addr; /* = bind_ip (network order) */ - if (bnd_ip_be == htonl(INADDR_ANY)) { - struct sockaddr_in local; - socklen_t llen = sizeof(local); - if (getsockname(tcp_fd, (struct sockaddr *)&local, &llen) == 0 && - local.sin_family == AF_INET) { - bnd_ip_be = local.sin_addr.s_addr; - } - } - - /* Write the ASSOCIATE success reply on the control connection. */ - uint8_t rep[10]; - size_t rn = mq_socks5_build_associate_reply(rep, ntohl(bnd_ip_be), a->udp_port); - if (send(tcp_fd, rep, rn, MSG_NOSIGNAL) != (ssize_t)rn) { - close(ufd); - a->udp_fd = -1; - free(a); - return NULL; - } - - /* Wire events: UDP readable (relay) + TCP readable (EOF watch). */ - a->udp_ev = event_new(base, ufd, EV_READ | EV_PERSIST, on_udp_readable, a); - a->tcp_ev = event_new(base, tcp_fd, EV_READ | EV_PERSIST, on_tcp_event, a); - if (!a->udp_ev || !a->tcp_ev) { - if (a->udp_ev) event_free(a->udp_ev); - if (a->tcp_ev) event_free(a->tcp_ev); - close(ufd); - free(a); - return NULL; - } - event_add(a->udp_ev, NULL); - event_add(a->tcp_ev, NULL); - - return a; -} - -void -mq_udp_assoc_free(mq_udp_assoc_t *a) -{ - if (!a) return; - /* Close any sessions still live (e.g. listener-free path where TCP-EOF - * never ran). assoc_teardown is idempotent. */ - assoc_teardown(a); - - if (a->udp_ev) event_free(a->udp_ev); - if (a->tcp_ev) event_free(a->tcp_ev); - if (a->udp_fd >= 0) close(a->udp_fd); - if (a->tcp_fd >= 0) close(a->tcp_fd); - free(a); -} diff --git a/src/ingress/mq_udp_assoc.h b/src/ingress/mq_udp_assoc.h deleted file mode 100644 index 1d4a2252..00000000 --- a/src/ingress/mq_udp_assoc.h +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_udp_assoc.h — SOCKS5 UDP ASSOCIATE edge (RFC 1928 §7). - * - * One mq_udp_assoc_t backs a single accepted SOCKS5 control connection that - * issued CMD UDP ASSOCIATE (0x03). The listener hands it the TCP control fd - * (whose EOF/error tears the whole association down) and a freshly bound UDP - * socket on bind_ip:ephemeral; the assoc writes the ASSOCIATE success reply - * (REP=0, BND.ADDR:BND.PORT = the address the client can reach the UDP socket - * at) onto the TCP fd. - * - * On a client datagram (UDP readable): - * - source learning: the first datagram from the TCP peer's IP (any port) - * locks IP:port; afterwards only an exact IP:port match is accepted, all - * others are silently dropped (RFC 1928 §7 source filtering). - * - parse the encapsulation header (mq_socks5_parse_udp_hdr): FRAG != 0 or - * malformed => drop. - * - look up / create a per-DST relay session (open_fn boundary, mq_ingress.h). - * open returns a handle synchronously or NULL; optimistic send is allowed - * immediately after open. on_rx for the reverse direction re-encapsulates - * (mq_socks5_build_udp_hdr with the DST as source) and sendto()s the - * learned client addr. - * - * The open_fn/send_fn/close_fn/core quad is the mq_udp_open_fn boundary; the - * assoc never touches xquic. See mq_ingress.h for the at-most-once on_err and - * callback-suppression-after-close contract this module relies on. - */ -#ifndef MQ_INGRESS_MQ_UDP_ASSOC_H -#define MQ_INGRESS_MQ_UDP_ASSOC_H - -#include - -#include "ingress/mq_ingress.h" - -struct event_base; - -typedef struct mq_udp_assoc mq_udp_assoc_t; - -/* Accept an ASSOCIATE: take ownership of tcp_fd (EV_READ EOF watch), bind a UDP - * socket on bind_ip:ephemeral, and write the SOCKS5 success reply onto tcp_fd. - * open_fn/send_fn/close_fn/core are the mq_udp_open_fn boundary. base is - * borrowed (must outlive the assoc). Returns NULL on bad args / socket / bind - * failure (caller then closes tcp_fd and rejects). */ -mq_udp_assoc_t *mq_udp_assoc_new(struct event_base *base, int tcp_fd, const char *bind_ip, - mq_udp_open_fn open_fn, mq_udp_send_fn send_fn, - mq_udp_close_fn close_fn, void *core); - -/* Tear down: close every live relay session (close_fn), free the UDP socket, - * the TCP control fd, and all events. Idempotent-safe via the listener path. - * Safe on NULL. */ -void mq_udp_assoc_free(mq_udp_assoc_t *a); - -/* ── intrusive list (owned by the listener) ────────────────────────────────── - * The listener keeps the head; the assoc owns the prev/next links and a - * back-pointer to the head so it can self-remove on TCP-EOF teardown. */ - -/* Link `a` at the head of *head (the listener's active-assoc list). */ -void mq_udp_assoc_list_push(mq_udp_assoc_t **head, mq_udp_assoc_t *a); -/* The next assoc in the list (for iteration). NULL at the tail. */ -mq_udp_assoc_t *mq_udp_assoc_list_next(mq_udp_assoc_t *a); - -#endif /* MQ_INGRESS_MQ_UDP_ASSOC_H */ diff --git a/src/mitm/mq_ignore_hosts.c b/src/mitm/mq_ignore_hosts.c deleted file mode 100644 index 7cfc4eb9..00000000 --- a/src/mitm/mq_ignore_hosts.c +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// mq_ignore_hosts — bounded host list with exact + leading-dot-suffix matching. -// See mq_ignore_hosts.h for the entry-normalization vs match-input contract. -#include "mitm/mq_ignore_hosts.h" - -#include -#include -#include - -// A normalized DNS name is <= 253 chars (RFC 1035 presentation form). A suffix -// entry additionally carries a leading '.', so the longest stored pattern is -// 1 ('.') + 253 + 1 (NUL) = 255 bytes. 256 leaves a byte of slack. -#define MQ_IGNORE_HOST_LEN 256 -#define MQ_DNS_NAME_MAX 253 - -struct mq_ignore_hosts { - char entries[MQ_IGNORE_HOSTS_MAX][MQ_IGNORE_HOST_LEN]; - size_t count; -}; - -mq_ignore_hosts_t * -mq_ignore_hosts_new(void) -{ - return calloc(1, sizeof(struct mq_ignore_hosts)); -} - -void -mq_ignore_hosts_free(mq_ignore_hosts_t *l) -{ - free(l); -} - -int -mq_ignore_hosts_add(mq_ignore_hosts_t *l, const char *pattern) -{ - if (l == NULL || pattern == NULL) return -1; - if (l->count >= MQ_IGNORE_HOSTS_MAX) return -1; - - size_t plen = strlen(pattern); - if (plen == 0) return -1; - - // Strip a single trailing dot (FQDN root). A leading dot is preserved as - // the suffix marker, so only strip from the end, never collapsing the - // leading-dot marker of a name like ".com." down past its own ".". - if (pattern[plen - 1] == '.') plen--; - if (plen == 0) return -1; // pattern was just "." (or "..") → bare marker, no host - - // The "host" portion (after any leading suffix-marker dot) must be a - // non-empty DNS name; reject ".", which after the trailing-strip above - // would leave nothing but the marker. - size_t host_off = (pattern[0] == '.') ? 1 : 0; - if (host_off >= plen) return -1; // e.g. just "." → marker with no host - - size_t host_len = plen - host_off; - if (host_len > MQ_DNS_NAME_MAX) return -1; // over-long host portion - - // plen excludes the (stripped) trailing dot; it still fits with NUL since - // plen <= 1 (marker) + 253 = 254 < MQ_IGNORE_HOST_LEN. - char *dst = l->entries[l->count]; - for (size_t i = 0; i < plen; i++) - dst[i] = (char)tolower((unsigned char)pattern[i]); - dst[plen] = '\0'; - - l->count++; - return 0; -} - -int -mq_ignore_hosts_match(const mq_ignore_hosts_t *l, const char *normalized_sni) -{ - if (l == NULL || normalized_sni == NULL) return 0; - - size_t slen = strlen(normalized_sni); - for (size_t i = 0; i < l->count; i++) { - const char *pat = l->entries[i]; - if (pat[0] == '.') { - // Suffix entry: the SNI must be STRICTLY longer than the entry and - // end with it (the leading '.' is part of pat, so "x.apple.com" - // ends with ".apple.com" but "apple.com" does not, and - // "notapple.com" ends with "apple.com" but the byte at the suffix - // boundary is 't', not '.', so the compare fails). - size_t plen = strlen(pat); - if (slen > plen && strcmp(normalized_sni + (slen - plen), pat) == 0) return 1; - } else { - if (strcmp(normalized_sni, pat) == 0) return 1; - } - } - return 0; -} diff --git a/src/mitm/mq_ignore_hosts.h b/src/mitm/mq_ignore_hosts.h deleted file mode 100644 index d9118eb4..00000000 --- a/src/mitm/mq_ignore_hosts.h +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// mq_ignore_hosts — Phase 7 Slice 3 MITM "leave this flow opaque" host list. -// Operators populate it from config [Mitm] IgnoreHosts / CLI --ignore-host(s); -// the live MITM orchestrator asks "should I splice this connection through -// untouched instead of terminating + forging?" once it has the ClientHello SNI. -// -// Two entry kinds: -// exact "signal.org" — matches ONLY the apex "signal.org". -// suffix ".apple.com" — matches every strict subdomain ("x.apple.com", -// "a.b.apple.com") but NOT the apex "apple.com". -// -// Entry normalization (mq_ignore_hosts_add) uses this module's OWN rules: -// lowercase + strip a single trailing dot, but PRESERVE a leading '.' (it is -// the suffix marker). Entries are deliberately NOT run through -// mq_mitm_normalize_sni, which rejects a leading dot as an empty label. -// -// The SNI handed to mq_ignore_hosts_match is ALREADY normalized by the caller -// via mq_mitm_normalize_sni (lowercase, no leading/trailing dot), so match() -// only compares — it does not re-normalize. -#ifndef MQ_IGNORE_HOSTS_H -#define MQ_IGNORE_HOSTS_H - -// Maximum number of entries in one list (bounded to cap memory + match cost). -#define MQ_IGNORE_HOSTS_MAX 256 - -typedef struct mq_ignore_hosts mq_ignore_hosts_t; - -// Allocate an empty list. Returns NULL on allocation failure. -mq_ignore_hosts_t *mq_ignore_hosts_new(void); - -// Add one pattern (normalized with this module's rules). Returns 0 on success, -// <0 on invalid input (NULL/empty/bare "."/over-long) or when the list is full. -int mq_ignore_hosts_add(mq_ignore_hosts_t *l, const char *pattern); - -// Returns 1 if `normalized_sni` (already mq_mitm_normalize_sni-normalized by the -// caller) should be left OPAQUE, else 0. NULL list or NULL sni → 0. -int mq_ignore_hosts_match(const mq_ignore_hosts_t *l, const char *normalized_sni); - -// Free the list. mq_ignore_hosts_free(NULL) is a no-op. -void mq_ignore_hosts_free(mq_ignore_hosts_t *l); - -#endif // MQ_IGNORE_HOSTS_H diff --git a/src/mitm/mq_mitm_conn.c b/src/mitm/mq_mitm_conn.c deleted file mode 100644 index 83d895f6..00000000 --- a/src/mitm/mq_mitm_conn.c +++ /dev/null @@ -1,1068 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// mq_mitm_conn — LIVE per-connection MITM orchestrator (Phase 7 Slice 3). -// See mq_mitm_conn.h for the contract. -// -// LIFECYCLE of a captured TCP connection: -// 1. DRAIN: read attacker-controlled leading bytes off local_fd into a bounded -// 8 KiB buffer, parse as a TLS ClientHello (mq_clienthello), under a -// complete-ClientHello deadline. Any malformed/oversized/slow input -// HARD-FAILS closed. (Task 10.) -// -// ── deadline_ev LIFECYCLE (M-5 / codex-3) ──────────────────────────────────── -// One reusable one-shot timer field (deadline_ev) bounds the two pre-LIVE phases, -// each with its OWN fresh budget so a slow drain cannot eat the handshake's clock: -// (a) DRAIN deadline : armed at accept (mq_mitm_conn_open), bounds the -// ClientHello drain. Fires → conn_close. -// (b) HANDSHAKE deadline: RE-ARMED FRESH at the drain→handshake bridge -// (mitm_handshake_begin), bounds the MITM TLS handshake. -// A genuinely slow drain (e.g. 4 s) MUST NOT subtract from -// the handshake budget, so the timer is re-armed from zero -// rather than left running. Both (a) and (b) are HARD -// deadlines: NOT reset on I/O progress (that would let a -// dribbling slow-loris handshake stay alive forever). -// then LIVE idle timer : on handshake SUCCESS deadline_ev is cancelled and the -// SEPARATE idle_ev (re-armed on activity) takes over. -// deadline_ev is freed in conn_free_events on EVERY teardown path (UAF safety). -// 2. DECIDE: normalize the SNI, ask mq_ignore_hosts. -// HIT → OPAQUE: hand the drained bytes + ORIGINAL orig-dst to the relay. -// MISS → MITM: terminate TLS with a forged per-SNI leaf, bridge H2 onto -// the gateway tunnel (this file, Task 11). -// -// ── MITM HANDSHAKE / BIO DESIGN (Task 11, chosen + documented) ─────────────── -// The orchestrator OWNS local_fd and does raw recv()/send() on it. The SSL talks -// to a MEMORY BIO PAIR (BIO_new_bio_pair): SSL_set_bio gives the SSL its end -// (`ssl_bio`); the orchestrator holds the other end (`app_bio`) and shuttles -// bytes both ways. This matches the existing codebase (test_mitm_core.c) and the -// adapter's send_cb/recv byte model, and — crucially — means NO BIO ever owns the -// fd, so the BIO_NOCLOSE concern is moot: the orchestrator closes the fd itself. -// -// Encrypted-byte flow: -// fd → recv() → BIO_write(app_bio) → SSL sees ciphertext to read -// SSL writes ciphertext → BIO_read(app_bio) → send() → fd -// The drained ClientHello (c->buf) is BIO_write'n into app_bio BEFORE the first -// SSL_accept so the handshake sees those bytes first; subsequent ciphertext comes -// from recv(fd). Plaintext H2 frames are pumped via SSL_read → adapter recv, and -// the adapter's send_cb does SSL_write → drain app_bio → send(fd). -// -// ── TEARDOWN ORDER (§5.1, codex H8) ────────────────────────────────────────── -// drive req_aborted per in-flight stream (via mq_gw_h2_adapter_free, which -// already iterates its live-stream list and calls submit->req_aborted on -// each xreq — the adapter→core "local gone" signal, NO sink callback; -// streams that already completed cleanly are EXEMPT: resp_finish (and -// resp_abort) NULL the borrowed xreq, so only genuinely in-flight streams -// are aborted — a finished stream is never req_aborted'd) → -// free the H2 adapter (nghttp2 session) → -// SSL_free(ssl) (frees ssl_bio) → BIO_free(app_bio) → -// close(local_fd) → unlink from registry → free conn ctx → free submit wrapper. -// -// ── RE-ENTRANCY MODEL ──────────────────────────────────────────────────────── -// A teardown can be triggered from inside a libevent callback (e.g. SSL_read -// error during the read pump, or send() failure during the write pump). Freeing -// the conn ctx right there would free state whose stack frame is still running -// (the event callback, the adapter, the SSL). So conn_close() is SPLIT: -// - it sets c->dying (idempotent: a second trigger is a no-op), -// - unlinks the conn from the live registry, -// - frees the read event so no further callback can re-enter, -// - moves the conn onto ctx->pending_free, -// - and activates ctx->free_ev (a zero-timeout libevent event). -// ctx->free_ev's callback runs at the NEXT loop iteration (libevent never runs an -// active event re-entrantly inside the callback that activated it), AFTER every -// in-flight stack frame has unwound, and only THEN performs the real teardown -// (adapter_free → SSL_free → close → free). mq_mitm_ctx_free drains both the live -// registry AND the pending-free list synchronously (it runs outside the loop). -// -// THREADING: single-threaded libevent data plane (1 client/process). - -#include "mitm/mq_mitm_conn.h" - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "gateway/mq_gw_h2_adapter.h" -#include "ingress/mq_clienthello.h" -#include "util/mq_log.h" - -// Bounded per-conn drain buffer. Mirrors mq_clienthello's own 8 KiB cap: a real -// ClientHello (with SNI + a few extensions) is well under this; the parser -// itself reports MQ_CH_INVALID past 8 KiB, so this is the hard ceiling. -#define MQ_MITM_CH_BUF_MAX 8192 - -// Pre-LIVE HARD deadline (seconds). Used TWICE, each time with a fresh budget -// (see the deadline_ev lifecycle note in the file header): once for the DRAIN -// (complete-ClientHello) phase, then re-armed for the MITM TLS HANDSHAKE phase. -// A genuine ClientHello arrives within one round-trip; the handshake is one more -// flight. 5s per phase is generous for high-latency/lossy links yet bounds a -// slow-loris that dribbles bytes (or sends none) to keep the fd + its event/timer -// pinned forever. Re-arming (not sharing one clock) means a slow drain cannot kill -// an otherwise-valid handshake mid-flight. -#define MQ_MITM_CH_DEADLINE_SEC 5 - -// LIVE-phase idle timeout (H-1). Once the (cheap) h2 handshake completes the -// drain deadline is gone; without this a client could complete the handshake then -// idle forever, pinning local_fd + the SSL + the 64 KiB×2 BIO pair + the nghttp2 -// adapter + a registry slot (fd/memory exhaustion DoS — a malicious app on the -// captured device can open many such conns). This re-armed idle timer reclaims a -// conn with NO I/O progress for the window. 60s is long enough to span keep-alive -// idle between browsing requests, short enough to reclaim dead conns promptly. -#define MQ_MITM_LIVE_IDLE_SEC 60 - -// Mem-BIO-pair ring size. 64 KiB matches test_mitm_core.c and comfortably holds a -// full TLS flight (ClientHello + the server's Certificate/CertVerify/Finished). -#define MQ_MITM_BIO_RING 65536 - -// Scratch sizes for the per-pump ciphertext/plaintext transfers. -#define MQ_MITM_PUMP_CHUNK 16384 - -// ── Context ──────────────────────────────────────────────────────────────── - -struct mq_mitm_ctx { - mq_mitm_core_t *core; // BORROWED — MITM crypto core - mq_ignore_hosts_t *ign; // BORROWED — opaque-routing list (may be NULL) - struct mq_gw_client_s *gwc; // BORROWED — gateway tunnel (MITM→H2 path) - mq_tcp_open_fn opaque_open; // existing relay entry (mq_client) - void *opaque_core; // mq_client core for opaque_open - struct event_base *base; // BORROWED — libevent base - - // Live conn registry (singly linked). A conn stays registered through its - // WHOLE life: drain phase AND post-handshake live MITM. Opaque/hard-fail - // conns are removed when they leave this orchestrator. On ctx_free, every - // conn still here is torn down (§5.1). - struct mq_mitm_conn *conns; - - // Deferred-free queue (re-entrancy safety — see the file header). Conns whose - // teardown was requested from inside a callback land here and are reaped by - // free_ev at the next loop iteration. - struct mq_mitm_conn *pending_free; - struct event *free_ev; // zero-timeout event that drains pending_free - - // Test-only MITM-start hook (see header). NULL → production handshake. - void (*mitm_hook)(void *, int, const uint8_t *, size_t, const char *); - void *mitm_hook_user; -}; - -// ── Per-connection state ───────────────────────────────────────────────────── - -typedef enum { - MQ_MITM_PHASE_DRAIN = 0, // accumulating the ClientHello - MQ_MITM_PHASE_HANDSHAKE = 1, // SSL_accept in progress - MQ_MITM_PHASE_LIVE = 2, // handshake done; pumping TLS↔adapter -} mq_mitm_phase_t; - -struct mq_mitm_conn { - mq_mitm_ctx_t *ctx; - struct mq_mitm_conn *next; // registry / pending_free link - - mq_mitm_phase_t phase; - int dying; // teardown requested; idempotent guard (re-entrancy) - - int local_fd; // OWNED (closed on teardown) - struct event *read_ev; // EV_READ|EV_PERSIST on local_fd - struct event *write_ev; // EV_WRITE (one-shot, re-armed when send() blocks) - struct event *deadline_ev; // one-shot HARD deadline: bounds DRAIN, then - // RE-ARMED FRESH to bound the HANDSHAKE (M-5/codex-3) - struct event *idle_ev; // LIVE-phase idle timer (H-1; re-armed on I/O) - - // Original recovered orig-dst params (forwarded UNCHANGED on the opaque path). - uint8_t orig_host[16]; - size_t orig_host_len; - mq_addr_type_t orig_atype; - uint16_t orig_port; - - uint8_t buf[MQ_MITM_CH_BUF_MAX]; // accumulated ClientHello bytes - size_t buf_len; - - // ── TLS termination state (handshake + live) ──────────────────────────── - SSL *ssl; // OWNED (SSL_free in teardown; frees ssl_bio) - BIO *app_bio; // OWNED — the orchestrator's end of the mem-BIO pair - - // Ciphertext write-holdover. flush_ciphertext() pulls ciphertext out of - // app_bio and send()s it; on a partial/blocked send the UNSENT tail is parked - // here (BIO_read is destructive, so the bytes cannot go back into the BIO) and - // retried on EV_WRITE. wbuf is sized to one pump chunk; flush stops pulling - // from the BIO while a holdover is pending, so it never overflows. - uint8_t wbuf[MQ_MITM_PUMP_CHUNK]; - size_t wbuf_off; // next unsent byte - size_t wbuf_len; // total parked bytes (unsent = [wbuf_off, wbuf_len)) - - // ── adapter bridge (live phase) ───────────────────────────────────────── - mq_gw_h2_adapter_t *adapter; // OWNED (freed FIRST in teardown) - mq_gw_h2_submit_gwc_t *submit_w; // OWNED — production submit wrapper -}; - -// ── registry helpers ───────────────────────────────────────────────────────── - -static void -registry_add(mq_mitm_ctx_t *ctx, struct mq_mitm_conn *c) -{ - c->next = ctx->conns; - ctx->conns = c; -} - -static void -registry_remove(mq_mitm_ctx_t *ctx, struct mq_mitm_conn *c) -{ - struct mq_mitm_conn **pp = &ctx->conns; - while (*pp) { - if (*pp == c) { - *pp = c->next; - c->next = NULL; - return; - } - pp = &(*pp)->next; - } -} - -// Free all of a conn's libevent objects. Idempotent (NULLs as it goes). -static void -conn_free_events(struct mq_mitm_conn *c) -{ - if (c->read_ev) { - event_free(c->read_ev); // del + free in one - c->read_ev = NULL; - } - if (c->write_ev) { - event_free(c->write_ev); - c->write_ev = NULL; - } - if (c->deadline_ev) { - event_free(c->deadline_ev); // cancels the pending one-shot timer - c->deadline_ev = NULL; - } - if (c->idle_ev) { - event_free(c->idle_ev); // cancels the pending LIVE idle timer (H-1) - c->idle_ev = NULL; - } -} - -// REAL teardown — runs OUTSIDE any in-flight callback (from free_ev or from -// mq_mitm_ctx_free). Performs the §5.1 ordered teardown. The conn is assumed -// already unlinked from the live registry (conn_close does that) and its events -// freed (conn_close frees them too, so no callback re-enters); here we finish. -static void -conn_destroy(struct mq_mitm_conn *c) -{ - conn_free_events(c); // belt-and-suspenders (already done in conn_close) - - // 1. Drive req_aborted per in-flight stream + free the H2 adapter. The - // adapter's free path iterates its own live-stream list and calls - // submit->req_aborted(xreq) on each (adapter→core "local gone"; NO sink - // callback) then RSTs/frees them — exactly the codex-H8 direction. - if (c->adapter) { - mq_gw_h2_adapter_free(c->adapter); - c->adapter = NULL; - } - // 2. SSL_free frees the SSL's end of the BIO pair (ssl_bio); our end - // (app_bio) is freed separately. - if (c->ssl) { - SSL_free(c->ssl); - c->ssl = NULL; - } - if (c->app_bio) { - BIO_free(c->app_bio); - c->app_bio = NULL; - } - // 3. Close the orchestrator-owned fd (never a BIO's job here). - if (c->local_fd >= 0) { - close(c->local_fd); - c->local_fd = -1; - } - // 4. Free the production submit wrapper (borrows the shared gwc; gwc not - // freed here). - if (c->submit_w) { - mq_gw_h2_submit_gwc_free(c->submit_w); - c->submit_w = NULL; - } - free(c); -} - -// Request teardown. RE-ENTRANCY-SAFE: callable from inside a libevent/SSL/adapter -// callback. Sets dying (idempotent), unlinks from the live registry, frees the -// I/O events immediately (so no further callback can fire into this conn), then -// DEFERS the real teardown to the next loop iteration via free_ev. The deferred -// reap (conn_destroy) runs after the current stack unwinds — never freeing state -// still on the stack. -static void -conn_close(struct mq_mitm_conn *c) -{ - if (c->dying) return; // already queued - c->dying = 1; - - mq_mitm_ctx_t *ctx = c->ctx; - registry_remove(ctx, c); - - // Cancel ALL I/O events NOW so neither the read pump, write pump nor the - // deadline can re-enter this (about-to-be-freed) conn. (codex M-5: the - // deadline timer is cancelled on every teardown path.) - conn_free_events(c); - - // Queue for deferred reap. - c->next = ctx->pending_free; - ctx->pending_free = c; - if (ctx->free_ev) event_active(ctx->free_ev, 0, 0); -} - -// free_ev callback — drains the pending-free queue at a safe point (next loop -// iteration, after every callback that requested a teardown has returned). -static void -on_free_pending(evutil_socket_t fd, short what, void *user) -{ - (void)fd; - (void)what; - mq_mitm_ctx_t *ctx = (mq_mitm_ctx_t *)user; - while (ctx->pending_free) { - struct mq_mitm_conn *c = ctx->pending_free; - ctx->pending_free = c->next; - conn_destroy(c); - } -} - -// ── live MITM: TLS pumps ↔ H2 adapter ───────────────────────────────────────── -// -// Encrypted-byte plumbing. The orchestrator owns local_fd; the SSL talks to a -// mem-BIO pair whose application end is c->app_bio (see the file header). - -// Forward decls (the pumps reference each other / teardown). -static int flush_ciphertext(struct mq_mitm_conn *c); -static int pump_outbound(struct mq_mitm_conn *c); -static void on_local_io(evutil_socket_t fd, short what, void *user); -static ssize_t adapter_ssl_send(void *io, const uint8_t *p, size_t n); -static void on_idle(evutil_socket_t fd, short what, void *user); // H-1 LIVE idle - -// Arm the one-shot EV_WRITE event so flush_ciphertext is retried when the socket -// becomes writable again. Safe to call repeatedly (event_add on an armed one-shot -// is idempotent). -static void -arm_write(struct mq_mitm_conn *c) -{ - if (c->write_ev) event_add(c->write_ev, NULL); -} - -// (Re-)arm the LIVE-phase idle timer (H-1). event_add on an already-pending -// evtimer RESETS its expiry, so calling this on each I/O-progress point cheaply -// slides the deadline forward. Guarded on `dying` (like arm_write) so a teardown -// in progress never re-arms a timer into an about-to-be-freed conn. -static void -arm_idle(struct mq_mitm_conn *c) -{ - if (c->dying || !c->idle_ev) return; - struct timeval tv = {.tv_sec = MQ_MITM_LIVE_IDLE_SEC, .tv_usec = 0}; - event_add(c->idle_ev, &tv); -} - -// Adapter writable-notify (mq_gw_h2_adapter_set_writable_cb). A RESPONSE arriving -// asynchronously over the shared gwc tunnel drives the adapter's sink ops on a -// DIFFERENT event than this conn's local-fd read, so the enqueued H2 response -// frames would otherwise sit undrained until the next inbound byte from the -// browser — which never comes (the browser is waiting for the response) → the -// request hangs and the browser times out. The adapter calls this the instant a -// sink op enqueues; we just ARM the one-shot EV_WRITE so on_local_io runs -// flush_ciphertext + pump_outbound on the next loop tick (deferred, NOT -// re-entrant — the sink op may be nested inside the gwc download pump). -static void -on_adapter_writable(void *user) -{ - struct mq_mitm_conn *c = (struct mq_mitm_conn *)user; - arm_write(c); -} - -// Drain ciphertext from app_bio to local_fd. Lossless under partial sends: a send -// that blocks parks the UNSENT tail in c->wbuf (BIO_read is destructive, so the -// bytes can't go back) and arms EV_WRITE. While a holdover is pending we do NOT -// pull more from the BIO. Returns 0 on progress/would-block, -1 on a hard error. -static int -flush_ciphertext(struct mq_mitm_conn *c) -{ - for (;;) { - // 1. Flush any parked holdover first. - while (c->wbuf_off < c->wbuf_len) { - ssize_t w = send(c->local_fd, c->wbuf + c->wbuf_off, - c->wbuf_len - c->wbuf_off, MSG_NOSIGNAL); - if (w > 0) { - c->wbuf_off += (size_t)w; - continue; - } - if (w < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - arm_write(c); - return 0; // still blocked; retry on EV_WRITE - } - if (w < 0 && errno == EINTR) continue; - return -1; // hard send error - } - c->wbuf_off = c->wbuf_len = 0; - - // 2. Pull the next ciphertext chunk out of the BIO. - int pend = BIO_read(c->app_bio, c->wbuf, (int)sizeof(c->wbuf)); - if (pend <= 0) - return 0; // nothing buffered (BIO_should_retry on a pair BIO - // just means empty here; SSL refills it) - c->wbuf_off = 0; - c->wbuf_len = (size_t)pend; - // loop: the holdover-flush at the top now sends [0, pend). - } -} - -// Run SSL_accept until it completes, blocks, or fails. Feeds the read BIO from -// recv() as needed and drains the write BIO to send(). Transitions to LIVE on -// success (asserting ALPN=h2). Returns 0 to stay (handshake or live), -1 to tear -// down. -static int -drive_handshake(struct mq_mitm_conn *c) -{ - for (;;) { - int r = SSL_accept(c->ssl); - // Always push out any handshake ciphertext the SSL produced. - if (flush_ciphertext(c) != 0) return -1; - if (r == 1) break; // handshake complete - - int e = SSL_get_error(c->ssl, r); - if (e == SSL_ERROR_WANT_READ) { - // SSL needs more ciphertext; it will arrive via on_local_io's recv→BIO - // feed. Return and wait for EV_READ. - return 0; - } - if (e == SSL_ERROR_WANT_WRITE) { - // The write BIO is full (we just flushed); wait for writability. - arm_write(c); - return 0; - } - MQ_LOGD("mq_mitm_conn: SSL_accept failed (err=%d) — hard-fail close", e); - return -1; - } - - // Handshake complete — REQUIRE ALPN h2 (browser must speak h2 for this slice). - const uint8_t *alpn = NULL; - unsigned alpn_len = 0; - SSL_get0_alpn_selected(c->ssl, &alpn, &alpn_len); - if (alpn_len != 2 || memcmp(alpn, "h2", 2) != 0) { - MQ_LOGD("mq_mitm_conn: negotiated ALPN is not h2 — hard-fail close"); - return -1; - } - - // Cancel the (re-armed handshake) deadline (M-5): the handshake is done. The - // LIVE idle timer (idle_ev) takes over below. See the deadline_ev lifecycle - // note in the file header. - if (c->deadline_ev) { - event_free(c->deadline_ev); - c->deadline_ev = NULL; - } - - // Build the production submit wrapper (binds the static gwc vtable to the - // shared gwc) + the H2 adapter. send_cb = adapter_ssl_send (SSL_write + flush). - c->submit_w = mq_gw_h2_submit_gwc_new(c->ctx->gwc); - if (!c->submit_w) { - MQ_LOGW("mq_mitm_conn: submit-wrapper alloc failed — hard-fail close"); - return -1; - } - c->adapter = - mq_gw_h2_adapter_new(mq_gw_h2_submit_ops_gwc(), c->submit_w, adapter_ssl_send, c); - if (!c->adapter) { - MQ_LOGW("mq_mitm_conn: H2 adapter alloc failed — hard-fail close"); - return -1; - } - // Wire the async-response write-trigger: when a tunnel response enqueues H2 - // frames (sink op on a different event than the local-fd read), schedule a - // deferred pump_outbound via the one-shot EV_WRITE. Without this the response - // never reaches the browser (it sits in the adapter's send queue → hang). - mq_gw_h2_adapter_set_writable_cb(c->adapter, on_adapter_writable, c); - - // Arm the LIVE-phase idle timer (H-1). The DRAIN deadline was cancelled above; - // from here the conn is reclaimed if no I/O makes progress for the idle window. - c->idle_ev = evtimer_new(c->ctx->base, on_idle, c); - if (!c->idle_ev) { - MQ_LOGW("mq_mitm_conn: idle-timer alloc failed — hard-fail close"); - return -1; - } - arm_idle(c); - - c->phase = MQ_MITM_PHASE_LIVE; - MQ_LOGD("mq_mitm_conn: handshake complete (ALPN=h2) — live MITM bridge up"); - - // Drain the adapter's initial SETTINGS frame out to the browser. - if (pump_outbound(c) != 0) return -1; - return 0; -} - -// adapter send_cb: write plaintext H2 bytes into the SSL, then drain the resulting -// ciphertext to the fd. Returns `n` on full acceptance, -1 on a hard error. -// SSL_write here is all-or-nothing for the byte count we hand it (mem BIO never -// short-writes plaintext); WANT_WRITE means the ciphertext BIO is momentarily full -// — we flush and report acceptance (the plaintext is buffered inside the SSL). -static ssize_t -adapter_ssl_send(void *io, const uint8_t *p, size_t n) -{ - struct mq_mitm_conn *c = (struct mq_mitm_conn *)io; - if (n == 0) return 0; - int w = SSL_write(c->ssl, p, (int)n); - if (w <= 0) { - int e = SSL_get_error(c->ssl, w); - if (e == SSL_ERROR_WANT_READ || e == SSL_ERROR_WANT_WRITE) { - // Could not take the plaintext right now; flush what ciphertext exists. - // The send_cb contract has no "partial" return (only n or -1); a 0 would - // be read as a fatal short-write. With a 64 KiB BIO vs 16 KiB frames this - // is unreachable, so we fail closed on WANT_*. - (void)flush_ciphertext(c); - } - return -1; - } - if (flush_ciphertext(c) != 0) return -1; - return (ssize_t)w; -} - -// Push any pending adapter output (and resume download backpressure), then flush -// the resulting ciphertext. Returns 0 / -1. -static int -pump_outbound(struct mq_mitm_conn *c) -{ - if (!c->adapter) return 0; - if (mq_gw_h2_adapter_want_write(c->adapter) != 0) return -1; - return flush_ciphertext(c); -} - -// Read ciphertext off the fd → BIO, then SSL_read plaintext → adapter. Loops until -// the socket would block. Returns 0 (stay) / -1 (tear down) / 1 (peer EOF). -static int -pump_inbound(struct mq_mitm_conn *c) -{ - uint8_t cipher[MQ_MITM_PUMP_CHUNK]; - for (;;) { - ssize_t n = recv(c->local_fd, cipher, sizeof(cipher), 0); - if (n > 0) { - // Feed ALL received ciphertext into the SSL's read BIO. A pair BIO can - // momentarily be full if the SSL hasn't consumed prior bytes; with a - // 64 KiB ring and 16 KiB reads + an immediate SSL_read drain below this - // does not happen in practice — treat a short BIO_write as fatal. - int wb = BIO_write(c->app_bio, cipher, (int)n); - if (wb != (int)n) { - MQ_LOGD("mq_mitm_conn: read-BIO full — hard-fail"); - return -1; - } - } else if (n == 0) { - return 1; // peer closed - } else { - if (errno == EAGAIN || errno == EWOULDBLOCK) break; - if (errno == EINTR) continue; - MQ_LOGD("mq_mitm_conn: recv error: %s — hard-fail", strerror(errno)); - return -1; - } - - // Drain plaintext H2 frames out of the SSL into the adapter. - for (;;) { - uint8_t plain[MQ_MITM_PUMP_CHUNK]; - int pr = SSL_read(c->ssl, plain, (int)sizeof(plain)); - if (pr > 0) { - if (mq_gw_h2_adapter_recv(c->adapter, plain, (size_t)pr) != 0) { - MQ_LOGD("mq_mitm_conn: H2 adapter recv fatal — hard-fail"); - return -1; - } - continue; - } - int e = SSL_get_error(c->ssl, pr); - if (e == SSL_ERROR_WANT_READ) break; // need more ciphertext - if (e == SSL_ERROR_WANT_WRITE) { - // A renegotiation-style write need; flush and continue. - if (flush_ciphertext(c) != 0) return -1; - break; - } - // TLS close_notify. A peer half-close (FIN/close_notify) is - // deliberately treated as a full-close here (acceptable for the - // h2/browser MITM case), like the WANT_WRITE-fatal choice above. - if (e == SSL_ERROR_ZERO_RETURN) return 1; - MQ_LOGD("mq_mitm_conn: SSL_read error (err=%d) — hard-fail", e); - return -1; - } - - // The adapter may have produced output (WINDOW_UPDATE / responses). - if (pump_outbound(c) != 0) return -1; - } - // Flush anything still queued and return. - if (pump_outbound(c) != 0) return -1; - return 0; -} - -// Unified local-fd event callback (drain phase uses on_readable below; the LIVE -// phase + handshake use this one). EV_READ feeds the inbound pump; EV_WRITE -// retries the ciphertext flush. Teardown is deferred (conn_close), so it is safe -// to call here even though we are inside a libevent callback. -static void -on_local_io(evutil_socket_t fd, short what, void *user) -{ - (void)fd; - struct mq_mitm_conn *c = (struct mq_mitm_conn *)user; - if (c->dying) return; - - if (what & EV_WRITE) { - if (flush_ciphertext(c) != 0) { - conn_close(c); - return; - } - // If the handshake was waiting on writability, advance it. - if (c->phase == MQ_MITM_PHASE_HANDSHAKE) { - if (drive_handshake(c) != 0) { - conn_close(c); - return; - } - } else if (c->phase == MQ_MITM_PHASE_LIVE) { - // Draining the socket holdover may have re-opened headroom for a - // download that was deferred under backpressure (Task 7). Re-pump the - // adapter so want_write clears sq_deferred and fires req_drained, - // resuming the gateway pump — mirroring the pump_inbound tail. Without - // this the download stalls until the next inbound packet re-enters. - if (pump_outbound(c) != 0) { - conn_close(c); - return; - } - // I/O made progress (writability + outbound pump) → slide the idle - // deadline forward (H-1). Guarded on dying inside arm_idle. - arm_idle(c); - } - } - - if (what & EV_READ) { - if (c->phase == MQ_MITM_PHASE_HANDSHAKE) { - // Pull ciphertext into the read BIO, then advance the handshake. - uint8_t cipher[MQ_MITM_PUMP_CHUNK]; - for (;;) { - ssize_t n = recv(c->local_fd, cipher, sizeof(cipher), 0); - if (n > 0) { - if (BIO_write(c->app_bio, cipher, (int)n) != (int)n) { - conn_close(c); - return; - } - continue; - } - if (n == 0) { - MQ_LOGD("mq_mitm_conn: peer closed during handshake — hard-fail"); - conn_close(c); - return; - } - if (errno == EAGAIN || errno == EWOULDBLOCK) break; - if (errno == EINTR) continue; - MQ_LOGD("mq_mitm_conn: recv error in handshake — hard-fail"); - conn_close(c); - return; - } - if (drive_handshake(c) != 0) { - conn_close(c); - return; - } - } else if (c->phase == MQ_MITM_PHASE_LIVE) { - int r = pump_inbound(c); - if (r != 0) { // -1 fatal or 1 peer EOF → tear down - conn_close(c); - return; - } - // Inbound I/O made progress (read pump returned "stay") → slide the - // idle deadline forward (H-1). - arm_idle(c); - } - } -} - -// Begin the live MITM handshake: build the SSL + mem-BIO pair, seed the drained -// ClientHello into the read BIO, install the unified EV_READ|EV_WRITE callback, -// and kick off SSL_accept. The drain-phase read_ev/deadline are still valid here -// (the deadline is cancelled when the handshake completes). Returns 0 on success -// (conn becomes HANDSHAKE/LIVE) or -1 (caller hard-fails). On -1 the SSL/BIO are -// freed by the caller's conn_close path. -static int -mitm_handshake_begin(struct mq_mitm_conn *c) -{ - mq_mitm_ctx_t *ctx = c->ctx; - - c->ssl = mq_mitm_core_new_ssl(ctx->core); - if (!c->ssl) { - MQ_LOGW("mq_mitm_conn: mq_mitm_core_new_ssl failed — hard-fail"); - return -1; - } - - BIO *ssl_bio = NULL, *app_bio = NULL; - if (BIO_new_bio_pair(&ssl_bio, MQ_MITM_BIO_RING, &app_bio, MQ_MITM_BIO_RING) != 1) { - MQ_LOGW("mq_mitm_conn: BIO_new_bio_pair failed — hard-fail"); - return -1; // c->ssl freed by conn_close - } - // SSL takes ownership of ssl_bio (freed by SSL_free); we own app_bio. - SSL_set_bio(c->ssl, ssl_bio, ssl_bio); - c->app_bio = app_bio; - - // Seed the drained ClientHello so SSL_accept sees it before any recv(). - if (c->buf_len) { - if (BIO_write(c->app_bio, c->buf, (int)c->buf_len) != (int)c->buf_len) { - MQ_LOGW("mq_mitm_conn: failed to seed ClientHello into BIO — hard-fail"); - return -1; - } - } - - // Swap the drain-phase read event for the unified handshake/live callback. - if (c->read_ev) { - event_free(c->read_ev); - c->read_ev = NULL; - } - c->read_ev = event_new(ctx->base, c->local_fd, EV_READ | EV_PERSIST, on_local_io, c); - c->write_ev = event_new(ctx->base, c->local_fd, EV_WRITE, on_local_io, c); - if (!c->read_ev || !c->write_ev || event_add(c->read_ev, NULL) != 0) { - MQ_LOGW("mq_mitm_conn: handshake event setup failed — hard-fail"); - return -1; - } - - c->phase = MQ_MITM_PHASE_HANDSHAKE; - - // RE-ARM the deadline FRESH for the handshake phase (M-5 / codex-3). The same - // deadline_ev bounded the DRAIN; evtimer_add on a pending one-shot RESETS its - // expiry, so the handshake gets a full MQ_MITM_CH_DEADLINE_SEC budget that does - // NOT inherit however long the drain already burned. Still a HARD deadline: it - // is deliberately NOT re-armed on handshake I/O progress (that would let a - // dribbling slow-loris handshake live forever) — unlike the LIVE idle timer. - // On handshake SUCCESS drive_handshake cancels this and arms idle_ev instead. - // deadline_ev stays freed by conn_free_events on every teardown path (UAF safe). - if (c->deadline_ev) { - struct timeval dl = {.tv_sec = MQ_MITM_CH_DEADLINE_SEC, .tv_usec = 0}; - if (evtimer_add(c->deadline_ev, &dl) != 0) { - MQ_LOGW("mq_mitm_conn: failed to re-arm handshake deadline — hard-fail"); - return -1; - } - } - - return drive_handshake(c); -} - -// ── MITM start seam ────────────────────────────────────────────────────────── -// -// Production: terminate TLS + bridge H2 onto the gateway tunnel. A test may -// inject mitm_hook to observe branch selection without the real handshake. -static void -mitm_start(struct mq_mitm_conn *c, const char *normalized_sni) -{ - mq_mitm_ctx_t *ctx = c->ctx; - if (ctx->mitm_hook) { - ctx->mitm_hook(ctx->mitm_hook_user, c->local_fd, c->buf, c->buf_len, - normalized_sni); - // The hook observes selection; it does not take fd ownership. Tear the - // conn down (close fd) so the test path leaves no dangling resources. - conn_close(c); - return; - } - - // No core (mis-config) → cannot forge. Fail closed. - if (!ctx->core || !ctx->gwc) { - MQ_LOGW("mq_mitm_conn: MITM selected but core/gwc unavailable — hard-fail"); - conn_close(c); - return; - } - - MQ_LOGD("mq_mitm_conn: MITM terminate for SNI '%s'", - normalized_sni ? normalized_sni : "?"); - if (mitm_handshake_begin(c) != 0) { - conn_close(c); - return; - } - // conn stays registered + alive (HANDSHAKE/LIVE); do NOT touch it further here. -} - -// ── decision seam (Task 4 unit-testable) ──────────────────────────────────── -// -// Given a conn with a COMPLETE, parsed ClientHello buffered, decide opaque vs -// MITM and dispatch. Consumes the conn on the FAIL/OPAQUE paths (and the MITM -// HARD-FAIL paths); on a successful MITM start the conn lives on. The caller must -// not touch `c` after a FAIL/OPAQUE return. Returns the route taken. -static mq_mitm_route_t -mitm_conn_decide(struct mq_mitm_conn *c, const mq_clienthello_t *ch) -{ - mq_mitm_ctx_t *ctx = c->ctx; - - // No SNI → cannot key a forged leaf; hard-fail (see header rationale). - if (ch->sni[0] == '\0') { - MQ_LOGD("mq_mitm_conn: ClientHello has no SNI — hard-fail close"); - conn_close(c); - return MQ_MITM_ROUTE_FAIL; - } - - // Normalize the SNI. Rejects IP literals / wildcards / empty labels. - char norm[256]; - if (mq_mitm_normalize_sni(ch->sni, strlen(ch->sni), norm) != 0) { - // L-1: the raw pre-normalization SNI is attacker-controlled and may carry - // terminal escapes / control bytes; never echo it verbatim. Normalization - // just failed, so there is no safe normalized form — log only its length. - MQ_LOGD("mq_mitm_conn: SNI (len=%zu) failed normalization — hard-fail close", - strlen(ch->sni)); - conn_close(c); - return MQ_MITM_ROUTE_FAIL; - } - - // Ignore-hosts hit → OPAQUE. Replay the drained ClientHello as prebuf to the - // existing relay, forwarding the ORIGINAL orig-dst params UNCHANGED (codex H6). - // fd ownership transfers to the opaque entry. - if (mq_ignore_hosts_match(ctx->ign, norm)) { - MQ_LOGD("mq_mitm_conn: SNI '%s' in ignore-hosts → opaque relay", norm); - int fd = c->local_fd; - // Detach fd + cancel events/timer + unlink, but keep `c` alive across the - // opaque_open call so its drained buffer can be borrowed for the call. - registry_remove(ctx, c); - conn_free_events(c); - c->local_fd = -1; // ownership transfers to the opaque entry - ctx->opaque_open(ctx->opaque_core, c->orig_host, c->orig_host_len, c->orig_atype, - c->orig_port, fd, c->buf, c->buf_len, NULL, NULL); - free(c); // never entered the deferred path; no events armed - return MQ_MITM_ROUTE_OPAQUE; - } - - // Miss → MITM (terminate + forge). mitm_start either advances to HANDSHAKE/LIVE - // (conn lives) or hard-fails (conn_close). - mitm_start(c, norm); - return MQ_MITM_ROUTE_MITM; -} - -// TEST-ONLY entry into the decision seam without sockets (see header). -mq_mitm_route_t -mq_mitm_conn_decide_for_test(mq_mitm_ctx_t *ctx, const uint8_t *buf, size_t len, - const uint8_t *host, size_t host_len, mq_addr_type_t atype, - uint16_t port, int local_fd) -{ - struct mq_mitm_conn *c = calloc(1, sizeof(*c)); - if (!c) return MQ_MITM_ROUTE_FAIL; - c->ctx = ctx; - c->local_fd = local_fd; - c->orig_atype = atype; - c->orig_port = port; - c->orig_host_len = host_len <= sizeof(c->orig_host) ? host_len : sizeof(c->orig_host); - if (host && c->orig_host_len) memcpy(c->orig_host, host, c->orig_host_len); - c->buf_len = len <= sizeof(c->buf) ? len : sizeof(c->buf); - if (buf && c->buf_len) memcpy(c->buf, buf, c->buf_len); - registry_add(ctx, c); - - mq_clienthello_t ch; - mq_ch_result_t r = mq_clienthello_parse(c->buf, c->buf_len, &ch); - if (r != MQ_CH_OK) { - conn_close(c); - return MQ_MITM_ROUTE_FAIL; - } - return mitm_conn_decide(c, &ch); -} - -// ── deadline timer ─────────────────────────────────────────────────────────── - -static void -on_deadline(evutil_socket_t fd, short what, void *user) -{ - (void)fd; - (void)what; - struct mq_mitm_conn *c = (struct mq_mitm_conn *)user; - MQ_LOGD("mq_mitm_conn: ClientHello deadline (%ds) exceeded — hard-fail close", - MQ_MITM_CH_DEADLINE_SEC); - conn_close(c); -} - -// LIVE-phase idle timer callback (H-1). Fires when no I/O has made progress for -// MQ_MITM_LIVE_IDLE_SEC. Routes teardown through conn_close (deferred-free, -// re-entrancy-safe) — exactly like on_deadline — NOT a direct free. -static void -on_idle(evutil_socket_t fd, short what, void *user) -{ - (void)fd; - (void)what; - struct mq_mitm_conn *c = (struct mq_mitm_conn *)user; - MQ_LOGD("mq_mitm_conn: LIVE idle timeout (%ds, no I/O) — hard-fail close", - MQ_MITM_LIVE_IDLE_SEC); - conn_close(c); -} - -// ── drain read event ───────────────────────────────────────────────────────── - -static void -on_readable(evutil_socket_t fd, short what, void *user) -{ - (void)what; - struct mq_mitm_conn *c = (struct mq_mitm_conn *)user; - if (c->dying) return; - - for (;;) { - if (c->buf_len >= sizeof(c->buf)) { - MQ_LOGD("mq_mitm_conn: ClientHello exceeded %d-byte cap — hard-fail", - MQ_MITM_CH_BUF_MAX); - conn_close(c); - return; - } - ssize_t n = recv(fd, c->buf + c->buf_len, sizeof(c->buf) - c->buf_len, 0); - if (n > 0) { - c->buf_len += (size_t)n; - mq_clienthello_t ch; - mq_ch_result_t r = mq_clienthello_parse(c->buf, c->buf_len, &ch); - if (r == MQ_CH_NEED_MORE) continue; // keep reading - if (r != MQ_CH_OK) { - MQ_LOGD("mq_mitm_conn: ClientHello parse rejected (%d) — hard-fail", - (int)r); - conn_close(c); - return; - } - // Complete ClientHello — decide + dispatch. On FAIL/OPAQUE the conn is - // consumed; on MITM it advances to HANDSHAKE/LIVE (still registered). - // Either way, do NOT touch `c` or `fd` afterwards. - (void)mitm_conn_decide(c, &ch); - return; - } - if (n == 0) { - MQ_LOGD("mq_mitm_conn: peer closed mid-ClientHello — hard-fail"); - conn_close(c); - return; - } - if (errno == EAGAIN || errno == EWOULDBLOCK) return; // wait for more - if (errno == EINTR) continue; - MQ_LOGD("mq_mitm_conn: recv error: %s — hard-fail", strerror(errno)); - conn_close(c); - return; - } -} - -// ── public API ─────────────────────────────────────────────────────────────── - -mq_mitm_ctx_t * -mq_mitm_ctx_new(mq_mitm_core_t *core, mq_ignore_hosts_t *ign, struct mq_gw_client_s *gwc, - mq_tcp_open_fn opaque_open, void *opaque_core, struct event_base *base) -{ - if (!opaque_open || !base) return NULL; - mq_mitm_ctx_t *ctx = calloc(1, sizeof(*ctx)); - if (!ctx) return NULL; - ctx->core = core; - ctx->ign = ign; - ctx->gwc = gwc; - ctx->opaque_open = opaque_open; - ctx->opaque_core = opaque_core; - ctx->base = base; - // Deferred-free event: zero-timeout, fires at the next loop iteration to reap - // conns whose teardown was requested inside a callback (re-entrancy safety). - ctx->free_ev = event_new(base, -1, 0, on_free_pending, ctx); - if (!ctx->free_ev) { - free(ctx); - return NULL; - } - return ctx; -} - -void -mq_mitm_ctx_free(mq_mitm_ctx_t *ctx) -{ - if (!ctx) return; - // §5.1: drain the live registry first. Each conn (drain-phase OR live MITM) is - // torn down in order. We run OUTSIDE the event loop here, so conn_close queues - // onto pending_free; drain that synchronously after. (conn_destroy runs the - // adapter_free→SSL_free→close→free order.) - while (ctx->conns) { - conn_close(ctx->conns); - } - // Reap everything conn_close queued (free_ev will never fire — the loop is - // done — so drain by hand). - while (ctx->pending_free) { - struct mq_mitm_conn *c = ctx->pending_free; - ctx->pending_free = c->next; - conn_destroy(c); - } - if (ctx->free_ev) event_free(ctx->free_ev); - free(ctx); -} - -void -mq_mitm_conn_open(void *ctx_v, const uint8_t *host, size_t host_len, mq_addr_type_t atype, - uint16_t port, int local_fd, const uint8_t *prebuf, size_t prebuf_len, - void *user, mq_tcp_open_cb cb) -{ - mq_mitm_ctx_t *ctx = (mq_mitm_ctx_t *)ctx_v; - - // We OWN local_fd from here (tproxy contract). On any setup failure we close - // it and report via cb where provided. MQ_TCP_TIMEOUT is used as a stand-in - // for "local setup failure" — the mq_tcp_status_t enum has no dedicated - // resource/internal code, and the tproxy open-result cb is a no-op anyway. - struct mq_mitm_conn *c = calloc(1, sizeof(*c)); - if (!c) { - if (local_fd >= 0) close(local_fd); - if (cb) cb(0, MQ_TCP_TIMEOUT, user); // stand-in: local setup failure - return; - } - c->ctx = ctx; - c->local_fd = local_fd; - c->orig_atype = atype; - c->orig_port = port; - c->orig_host_len = host_len <= sizeof(c->orig_host) ? host_len : sizeof(c->orig_host); - if (host && c->orig_host_len) memcpy(c->orig_host, host, c->orig_host_len); - - if (prebuf && prebuf_len) { - c->buf_len = prebuf_len <= sizeof(c->buf) ? prebuf_len : sizeof(c->buf); - memcpy(c->buf, prebuf, c->buf_len); - } - - c->read_ev = event_new(ctx->base, local_fd, EV_READ | EV_PERSIST, on_readable, c); - c->deadline_ev = evtimer_new(ctx->base, on_deadline, c); - if (!c->read_ev || !c->deadline_ev) { - MQ_LOGW("mq_mitm_conn: event allocation failed — closing"); - conn_free_events(c); - if (local_fd >= 0) close(local_fd); - free(c); - if (cb) cb(0, MQ_TCP_TIMEOUT, user); // stand-in: local setup failure - return; - } - - struct timeval dl = {.tv_sec = MQ_MITM_CH_DEADLINE_SEC, .tv_usec = 0}; - if (event_add(c->read_ev, NULL) != 0 || evtimer_add(c->deadline_ev, &dl) != 0) { - MQ_LOGW("mq_mitm_conn: event_add failed — closing"); - conn_free_events(c); - if (local_fd >= 0) close(local_fd); - free(c); - if (cb) cb(0, MQ_TCP_TIMEOUT, user); // stand-in: local setup failure - return; - } - - registry_add(ctx, c); - (void)cb; // success path: draining continues in on_readable / on_deadline -} - -void -mq_mitm_ctx_set_mitm_hook_for_test(mq_mitm_ctx_t *ctx, - void (*hook)(void *, int, const uint8_t *, size_t, - const char *), - void *hook_user) -{ - if (!ctx) return; - ctx->mitm_hook = hook; - ctx->mitm_hook_user = hook_user; -} - -int -mq_mitm_conn_make_live_for_test(mq_mitm_ctx_t *ctx, struct mq_gw_h2_adapter *adapter, - struct ssl_st *ssl, int local_fd) -{ - if (!ctx) return -1; - struct mq_mitm_conn *c = calloc(1, sizeof(*c)); - if (!c) return -1; - c->ctx = ctx; - c->phase = MQ_MITM_PHASE_LIVE; - c->local_fd = local_fd; - c->ssl = (SSL *)ssl; - c->adapter = (mq_gw_h2_adapter_t *)adapter; - // No submit wrapper / app_bio in this seam — the test owns adapter construction - // (it binds its own stub submit vtable) and may pass a NULL/real SSL. Teardown - // exercises adapter_free (→ req_aborted per stream) → SSL_free → close → free. - registry_add(ctx, c); - return 0; -} - -int -mq_mitm_conn_arm_idle_now_for_test(mq_mitm_ctx_t *ctx) -{ - if (!ctx) return -1; - int n = 0; - for (struct mq_mitm_conn *c = ctx->conns; c; c = c->next) { - if (!c->idle_ev) c->idle_ev = evtimer_new(ctx->base, on_idle, c); - if (!c->idle_ev) return -1; - struct timeval zero = {0, 0}; // expire on the very next loop iteration - event_add(c->idle_ev, &zero); - n++; - } - return n; -} diff --git a/src/mitm/mq_mitm_conn.h b/src/mitm/mq_mitm_conn.h deleted file mode 100644 index 7bca9103..00000000 --- a/src/mitm/mq_mitm_conn.h +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// mq_mitm_conn — Phase 7 Slice 3 LIVE per-connection MITM orchestrator. -// -// Installed as the tproxy/redirect open_fn under --mitm. For each captured TCP -// connection it: -// 1. DRAINS the (attacker-controlled) leading bytes off local_fd into a -// bounded 8 KiB buffer, parsing them as a TLS ClientHello (mq_clienthello), -// under a complete-ClientHello deadline timer (MQ_MITM_CH_DEADLINE_SEC). -// Any non-TLS / malformed / no-SNI / deadline-exceeded input HARD-FAILS -// (close the fd, no data forwarded) — this is the security boundary for -// pre-handshake bytes. -// 2. NORMALIZES the SNI (mq_mitm_normalize_sni) and asks mq_ignore_hosts: -// - HIT → OPAQUE: replay the drained buffer (as prebuf) to the existing -// opaque entry (mq_client flow→MPQUIC), forwarding the ORIGINAL -// recovered orig-dst params UNCHANGED (host/atype/port). The SNI -// is NEVER substituted as the host. -// - MISS → MITM: terminate TLS with a forged per-SNI leaf and bridge H2 -// onto the gateway tunnel (Task 11 — currently a stub seam). -// -// fd OWNERSHIP: once mq_mitm_conn_open is invoked the orchestrator OWNS local_fd -// (tproxy contract). On the opaque path it transfers fd ownership to the opaque -// entry; on hard-fail it closes the fd; on the MITM path it retains it (Task 11). -// -// THREADING: single-threaded libevent data plane (1 client/process). All state -// is touched only from the event base's thread. -#ifndef MQ_MITM_CONN_H -#define MQ_MITM_CONN_H - -#include -#include - -#include "ingress/mq_ingress.h" /* mq_tcp_open_fn / mq_tcp_open_cb */ -#include "mitm/mq_ignore_hosts.h" -#include "mitm/mq_mitm_core.h" -#include "wire/mq_wire.h" /* mq_addr_type_t */ - -struct event_base; -struct mq_gw_client_s; - -typedef struct mq_mitm_ctx mq_mitm_ctx_t; - -// Create the orchestrator context. -// core : MITM crypto core (CA + per-SNI leaf forge). BORROWED. -// ign : ignore-hosts list (opaque routing). BORROWED; may be NULL -// (then nothing is opaque — every TLS flow is MITM'd). -// gwc : gateway client tunnel for the MITM (H2→tunnel) path. BORROWED; -// consumed by Task 11. -// opaque_open : the existing tproxy/relay entry (mq_client's mq_tcp_open_fn) -// used to splice ignore-hosts flows through untouched. -// opaque_core : the void* core that opaque_open expects (mq_client). -// base : libevent base for the drain EV_READ + deadline timer. BORROWED. -// Returns NULL on bad args (base/opaque_open NULL) or OOM. -mq_mitm_ctx_t *mq_mitm_ctx_new(mq_mitm_core_t *core, mq_ignore_hosts_t *ign, - struct mq_gw_client_s *gwc, mq_tcp_open_fn opaque_open, - void *opaque_core, struct event_base *base); - -// Tear down: drain the live-conn registry first (§5.1 — closes any conns still -// in the drain phase, freeing their events/timers/fds), then free ctx. Safe on -// NULL. Does NOT free core/ign/gwc/opaque_core (all BORROWED). -void mq_mitm_ctx_free(mq_mitm_ctx_t *ctx); - -// mq_tcp_open_fn — invoked by the tproxy listener for each accepted fd. `ctx` is -// an mq_mitm_ctx_t*. prebuf is NULL/0 from tproxy (ClientHello still on the -// socket); the orchestrator drains it off local_fd itself. host/host_len/atype/ -// port are the RECOVERED ORIGINAL destination and are forwarded UNCHANGED on the -// opaque path. cb is the listener's open-result callback (no-op for tproxy). -void mq_mitm_conn_open(void *ctx, const uint8_t *host, size_t host_len, - mq_addr_type_t atype, uint16_t port, int local_fd, - const uint8_t *prebuf, size_t prebuf_len, void *user, - mq_tcp_open_cb cb); - -// ── Test seam (Task 4) ───────────────────────────────────────────────────── -// The decision logic is factored out of the libevent I/O so it can be unit -// tested without sockets/TLS. Given a ctx + a fully-drained ClientHello buffer + -// the original orig-dst params, decide opaque-vs-MITM and dispatch. Returns the -// branch taken (for tests). The buffer is borrowed. -// -// The MITM branch is reached via an injectable hook (mq_mitm_ctx_set_mitm_hook): -// in production it is the real handshake (Task 11); in tests it captures that the -// MITM branch was selected. When no hook is set the production stub runs. -typedef enum { - MQ_MITM_ROUTE_OPAQUE = 0, // ignore-hosts hit → opaque entry - MQ_MITM_ROUTE_MITM = 1, // miss → terminate + forge (Task 11) - MQ_MITM_ROUTE_FAIL = -1 // no/invalid SNI → hard-fail (caller closes fd) -} mq_mitm_route_t; - -// TEST-ONLY entry into the decision seam without sockets (Task 4 / 10). Builds a -// transient conn around a caller-supplied fd + drained buffer + orig-dst, parses, -// and dispatches. Returns the route. tests/test_mitm_conn.c passes fd=-1 + a stub -// opaque_open. (Declared here, not extern-in-test, so the test sees the real -// mq_mitm_route_t signature — carry-forward fix #1.) -mq_mitm_route_t mq_mitm_conn_decide_for_test(mq_mitm_ctx_t *ctx, const uint8_t *buf, - size_t len, const uint8_t *host, - size_t host_len, mq_addr_type_t atype, - uint16_t port, int local_fd); - -// Test-only MITM-start hook: invoked instead of the production mitm_start() body -// when the route is MITM, so a unit test can observe branch selection without the -// real TLS handshake. NULL restores the production path. The conn is identified -// by its local_fd + drained-buffer pointer (passed through `user`). -void mq_mitm_ctx_set_mitm_hook_for_test(mq_mitm_ctx_t *ctx, - void (*hook)(void *hook_user, int local_fd, - const uint8_t *buf, size_t len, - const char *normalized_sni), - void *hook_user); - -// ── Live teardown test seam (Task 11) ─────────────────────────────────────── -// Construct a conn DIRECTLY in the post-handshake (live MITM) state, WITHOUT a -// real TLS handshake, so the registry + ordered teardown + re-entrancy + the -// req_aborted-per-in-flight-stream direction can be unit-tested with a fake -// SSL/socket pair + stub submit vtable. -// -// adapter : a live mq_gw_h2_adapter_t* (built by the test against its stub -// submit vtable + fake send_cb). OWNED by the conn after this call -// (freed in teardown). May be NULL (no adapter → still exercises -// the SSL_free/close/unlink/free path). -// ssl : a server-mode SSL* (the test may pass one built from mq_mitm_core -// with a mem-BIO pair, or NULL). OWNED by the conn (SSL_free'd in -// teardown). -// local_fd : OWNED by the conn (close()'d in teardown). Pass -1 for none. -// -// The conn is registered live; mq_mitm_ctx_free (or an explicit close) tears it -// down in the §5.1 order. Returns 0 on success, -1 on OOM (caller frees the -// adapter/ssl/fd it passed in on failure). -struct mq_gw_h2_adapter; -struct ssl_st; -int mq_mitm_conn_make_live_for_test(mq_mitm_ctx_t *ctx, struct mq_gw_h2_adapter *adapter, - struct ssl_st *ssl, int local_fd); - -// ── LIVE-phase idle-timeout test seam (H-1) ────────────────────────────────── -// Arm the LIVE idle timer (allocating idle_ev if needed) on EVERY conn currently -// in the live registry with a ZERO timeout, so the next event_base loop iteration -// fires on_idle for real — exercising the production idle→conn_close→deferred-free -// teardown path under ASan/LSan. After this returns the caller should run -// event_base_loop(base, EVLOOP_NONBLOCK) to drive expiry + the deferred reap. -// Returns the number of conns armed (0 if the registry was empty), -1 on bad arg. -int mq_mitm_conn_arm_idle_now_for_test(mq_mitm_ctx_t *ctx); - -#endif // MQ_MITM_CONN_H diff --git a/src/mitm/mq_mitm_core.c b/src/mitm/mq_mitm_core.c deleted file mode 100644 index 1f60ac55..00000000 --- a/src/mitm/mq_mitm_core.c +++ /dev/null @@ -1,778 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// mq_mitm_core — Phase 7 Slice 2 MITM crypto core. I/O-free crypto module: -// loads + validates a CA, owns the shared P-256 leaf key (forge/cache/SSL_CTX -// land in later Slice 2 tasks). NOT wired into the live tproxy path (Slice 3). -#include "mitm/mq_mitm_core.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MQ_DNS_NAME_MAX 253 /* RFC 1035 presentation-form name limit */ -#define MQ_DNS_LABEL_MAX 63 /* RFC 1035 label limit */ - -#define MQ_MITM_LEAF_SERIAL_BITS 64 /* random leaf serial width */ -#define MQ_MITM_LEAF_BACKDATE_SEC 3600 /* notBefore clock-skew tolerance */ - -#define MQ_MITM_CACHE_SKEW_SEC 30 /* treat a leaf within SKEW of not_after as stale */ - -// One bounded-LRU cache entry: a forged leaf keyed by its normalized SNI. -// REFCOUNT OWNERSHIP: the entry holds ONE X509 ref on `leaf` (taken via -// X509_up_ref / inherited from forge_leaf at insert). cache_get_or_forge hands -// the CALLER a SEPARATE ref. The cache's ref is released only by eviction or by -// destroy (X509_free). MRU is the head of the doubly-linked list; LRU is the tail. -typedef struct mq_cache_entry { - char norm_sni[256]; // normalized DNS name key - X509 *leaf; // cache-owned ref (one) - time_t not_after; // expiry instant (forge-time now + leaf_ttl_sec) - struct mq_cache_entry *prev; // toward MRU/head - struct mq_cache_entry *next; // toward LRU/tail -} mq_cache_entry_t; - -struct mq_mitm_core { - // Lifetime refcount. Starts at 1 (the create()/destroy() owner ref). Each SSL - // built by mq_mitm_core_new_ssl takes ANOTHER ref (stashed in SSL ex-data) and - // releases it from the SSL ex-data free callback on SSL_free. The core's - // resources are torn down only when the count reaches 0, so destroy() may run - // while SSLs are still alive without dangling the core pointer they hold. - // Non-atomic: the 1-client/process model means one owner thread (same - // assumption as the lazy SSL_CTX build below). - int refcount; - X509 *ca_cert; - EVP_PKEY *ca_key; - EVP_PKEY *leaf_key; // shared across all forged leaves - size_t cache_size; - int leaf_ttl_sec; - time_t (*now_fn)(void); - SSL_CTX *ssl_ctx; // created lazily in new_ssl (Task 6); NULL here - // Bounded LRU leaf cache (Task 5). Intrusive doubly-linked list, MRU at head, - // LRU at tail; linear scan (cache_size <= 256, small — no hashing). `count` - // tracks current entries. All NULL/0 when cache_size == 0 (caching disabled). - mq_cache_entry_t *lru_head; - mq_cache_entry_t *lru_tail; - size_t cache_count; -}; - -static time_t -mq_default_now(void) -{ - return time(NULL); -} - -// Normalize an SNI host name for use as a forge key / SAN entry. On success -// returns 0 and writes a NUL-terminated, lowercased DNS name (<= 253 chars) -// into out. Returns -1 for: NULL/empty input, total length > 253, any label -// > 63 bytes, empty label (e.g. "a..b" or leading dot), an IP literal -// (IPv4/IPv6, via inet_pton), a wildcard (any '*'), or any byte outside -// [A-Za-z0-9.-]. A single trailing dot is stripped before validation. -int -mq_mitm_normalize_sni(const char *sni, size_t sni_len, char out[256]) -{ - if (!sni || sni_len == 0) return -1; - - // Strip a single trailing dot (root label), then re-check for emptiness. - if (sni[sni_len - 1] == '.') sni_len--; - if (sni_len == 0) return -1; - - // Total length cap (DNS presentation name, excluding the stripped dot). - if (sni_len > MQ_DNS_NAME_MAX) return -1; - - // Reject wildcards and any byte outside [A-Za-z0-9.-]; lowercase ASCII; - // enforce per-label length (1..63, no empty labels). - char tmp[MQ_DNS_NAME_MAX + 1]; - size_t label_len = 0; - for (size_t i = 0; i < sni_len; i++) { - unsigned char ch = (unsigned char)sni[i]; - if (ch == '.') { - if (label_len == 0) return -1; // empty label (leading/double dot) - label_len = 0; - tmp[i] = '.'; - continue; - } - if (ch == '*') return -1; // wildcard - char c; - if (ch >= 'A' && ch <= 'Z') { - c = (char)(ch - 'A' + 'a'); - } else if ((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-') { - c = (char)ch; - } else { - return -1; // any other byte (control chars, '_', etc.) - } - if (++label_len > MQ_DNS_LABEL_MAX) return -1; // label too long - tmp[i] = c; - } - // Reject a trailing empty label. The single-dot strip above only removes ONE - // root dot, so multi-dot suffixes ("example.com..") arrive here with the loop - // having reset label_len to 0 on the final '.'. This MUST be a runtime check, - // not an assert: SNI is attacker-controlled and reaches this on the live - // cert-selection path — an assert would abort the data plane (debug) or, under - // NDEBUG, let a trailing-dot name through as a forge key / SAN (normalization - // bypass → cache amplification). - if (label_len == 0) return -1; - tmp[sni_len] = '\0'; - - // Reject IP literals — they must not be treated as DNS names. inet_pton on - // the lowercased copy covers IPv4 (and IPv6, which the charset filter above - // already rejects via ':' but we check defensively). - unsigned char addr[16]; - if (inet_pton(AF_INET, tmp, addr) == 1) return -1; - if (inet_pton(AF_INET6, tmp, addr) == 1) return -1; - - memcpy(out, tmp, sni_len + 1); - return 0; -} - -// Password callback that always declines: an encrypted PEM (which needs a -// passphrase) fails to decode rather than prompting on a tty. -static int -no_password_cb(char *buf, int size, int rw, void *u) -{ - (void)buf; - (void)size; - (void)rw; - (void)u; - return 0; -} - -// Read a PEM file with TOCTOU-free safety checks. Opens with O_NOFOLLOW (reject -// symlinks) + O_CLOEXEC, then fstat()s the fd (not the path) to reject -// non-regular files. When `require_private_perms` is set (the CA *key*), the -// fstat also rejects any group/other access bits and non-owner files; the CA -// *cert* is public material, so it loads with the symlink/cloexec protection -// only (no perms gate — the cert is routinely group/world-readable). -// On success returns a memory BIO holding the file contents (caller frees). -static BIO * -read_pem_file_safely(const char *path, int require_private_perms) -{ - int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); - if (fd < 0) return NULL; - - struct stat st; - if (fstat(fd, &st) != 0) { - close(fd); - return NULL; - } - if (!S_ISREG(st.st_mode)) { - close(fd); - return NULL; - } - if (require_private_perms && - ((st.st_mode & (S_IRWXG | S_IRWXO)) != 0 || st.st_uid != geteuid())) { - close(fd); - return NULL; - } - - // Read the whole file. CA cert/key PEMs are small; cap defensively at 1 MiB. - BIO *mem = BIO_new(BIO_s_mem()); - if (!mem) { - close(fd); - return NULL; - } - // M-1: this scratch may transiently hold raw CA private-key PEM bytes (the - // crown-jewel MITM secret). Cleanse it on EVERY exit path so it does not - // linger in a freed/stack frame recoverable from a core dump. The mem-BIO - // itself is cleansed by the caller (it owns the assembled secret bytes). - unsigned char buf[4096]; - size_t total = 0; - for (;;) { - ssize_t r = read(fd, buf, sizeof buf); - if (r < 0) { - OPENSSL_cleanse(buf, sizeof buf); - BIO_free(mem); - close(fd); - return NULL; - } - if (r == 0) break; - total += (size_t)r; - if (total > (1u << 20)) { - OPENSSL_cleanse(buf, sizeof buf); - BIO_free(mem); - close(fd); - return NULL; - } - if (BIO_write(mem, buf, (int)r) != r) { - OPENSSL_cleanse(buf, sizeof buf); - BIO_free(mem); - close(fd); - return NULL; - } - } - OPENSSL_cleanse(buf, sizeof buf); - close(fd); - return mem; -} - -// Zeroize a memory BIO's backing buffer before freeing it, so secret bytes (the -// CA private-key PEM) don't linger in freed heap (M-1). For a non-mem BIO or -// NULL this degrades to a plain BIO_free. -static void -bio_free_cleanse(BIO *b) -{ - if (!b) return; - char *data = NULL; - long len = BIO_get_mem_data(b, &data); - if (data && len > 0) OPENSSL_cleanse(data, (size_t)len); - BIO_free(b); -} - -mq_mitm_core_t * -mq_mitm_core_create(const char *ca_cert_pem_path, const char *ca_key_pem_path, - const mq_mitm_opts_t *opts) -{ - if (!ca_cert_pem_path || !ca_key_pem_path) return NULL; - - mq_mitm_core_t *core = NULL; - EVP_PKEY *ca_key = NULL; - X509 *ca_cert = NULL; - EVP_PKEY *leaf_key = NULL; - EVP_PKEY_CTX *kctx = NULL; - BIO *key_bio = NULL; - BIO *cert_bio = NULL; - - // --- 1. Load the CA private key with key-file safety checks first --- - // (perms gate runs BEFORE PEM parse — see MINOR-2 in the plan.) - key_bio = read_pem_file_safely(ca_key_pem_path, 1 /*require_private_perms*/); - if (!key_bio) goto fail; - ca_key = PEM_read_bio_PrivateKey(key_bio, NULL, no_password_cb, NULL); - if (!ca_key) goto fail; // unparseable, or encrypted (no_password_cb declines) - - // --- 2. Load the CA certificate. Route through the SAME safe-open helper as - // the key (O_NOFOLLOW|O_CLOEXEC, TOCTOU-free) but WITHOUT the perms gate: the - // cert is public material and routinely group/world-readable. --- - cert_bio = read_pem_file_safely(ca_cert_pem_path, 0 /*require_private_perms*/); - if (!cert_bio) goto fail; - ca_cert = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL); - if (!ca_cert) goto fail; - - // --- 3. CA eligibility (I4: ca_cert is a MUTABLE handle; the accessors - // below lazily compute the extension flags, so call them in this order). --- - // X509_check_ca() also triggers flag computation; do it first. - if (X509_check_ca(ca_cert) < 1) - goto fail; // not a CA (CA:FALSE or no basicConstraints) - uint32_t exflags = X509_get_extension_flags(ca_cert); - if (exflags & EXFLAG_INVALID) goto fail; // malformed/contradictory extensions - // X509_check_ca() treats an X509v1 cert (which has NO extensions) as - // CA-eligible. Require a real basicConstraints extension so a v1 / no-BCONS - // cert can't be loaded as a signing CA — i.e. demand explicit CA:TRUE. - if (!(exflags & EXFLAG_BCONS)) goto fail; - // keyUsage stays CONDITIONAL on purpose: RFC 5280 §4.2.1.3 makes keyUsage - // OPTIONAL, and an absent keyUsage permits all usages (incl. cert signing). - // We reject only a PRESENT keyUsage that withholds keyCertSign — tightening - // this to "keyUsage mandatory" would wrongly reject conformant CAs that omit it. - if (exflags & EXFLAG_KUSAGE) { - if (!(X509_get_key_usage(ca_cert) & KU_KEY_CERT_SIGN)) - goto fail; // keyUsage present but cannot sign certs - } - // NAME_CONSTRAINTS unsupported in v1 — reject if present. - { - void *nc = X509_get_ext_d2i(ca_cert, NID_name_constraints, NULL, NULL); - if (nc) { - NAME_CONSTRAINTS_free((NAME_CONSTRAINTS *)nc); - goto fail; - } - } - - // --- 4. key <-> cert match --- - if (X509_check_private_key(ca_cert, ca_key) != 1) goto fail; - - // --- 5. Shared P-256 leaf key (C2: EVP_EC_gen is ABSENT here — use the - // EVP_PKEY_CTX paramgen path). --- - kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); - if (!kctx || EVP_PKEY_keygen_init(kctx) <= 0 || - EVP_PKEY_CTX_set_ec_paramgen_curve_nid(kctx, NID_X9_62_prime256v1) <= 0 || - EVP_PKEY_keygen(kctx, &leaf_key) <= 0) - goto fail; - EVP_PKEY_CTX_free(kctx); - kctx = NULL; - - // --- 6. Assemble the core --- - core = calloc(1, sizeof *core); - if (!core) goto fail; - core->refcount = 1; // owner ref, released by mq_mitm_core_destroy - core->ca_cert = ca_cert; - core->ca_key = ca_key; - core->leaf_key = leaf_key; - core->ssl_ctx = NULL; - core->now_fn = mq_default_now; - - size_t cache_size = 256; - int leaf_ttl_sec = 86400; - if (opts) { - cache_size = opts->cache_size; - leaf_ttl_sec = opts->leaf_ttl_sec; - } - if (leaf_ttl_sec < 60) - leaf_ttl_sec = 60; - else if (leaf_ttl_sec > 86400 * 30) - leaf_ttl_sec = 86400 * 30; - if (cache_size > 256) cache_size = 256; - core->cache_size = cache_size; - core->leaf_ttl_sec = leaf_ttl_sec; - - bio_free_cleanse(key_bio); // M-1: zeroize raw CA-key PEM bytes before free - BIO_free(cert_bio); // cert is public — plain free - return core; - -fail: - if (kctx) EVP_PKEY_CTX_free(kctx); - if (leaf_key) EVP_PKEY_free(leaf_key); - if (ca_cert) X509_free(ca_cert); - if (ca_key) EVP_PKEY_free(ca_key); - if (cert_bio) BIO_free(cert_bio); - if (key_bio) bio_free_cleanse(key_bio); // M-1: zeroize on the fail path too - return NULL; -} - -// Add a v3 extension to `leaf` from a config string (e.g. "critical,CA:FALSE") -// using the X509V3 config-string mechanism. Returns 0 on success, -1 on failure. -static int -add_ext_conf(X509 *leaf, int nid, const char *value) -{ - X509V3_CTX v3ctx; - X509V3_set_ctx_nodb(&v3ctx); - // issuer = leaf's own issuer (already set), subject = leaf — sufficient for - // basicConstraints / keyUsage / extendedKeyUsage which don't reference names. - X509V3_set_ctx(&v3ctx, leaf, leaf, NULL, NULL, 0); - // Use the nconf variant (conf=NULL): the thin X509V3_EXT_conf_nid wrapper is - // not present in the vendored BoringSSL static archive. - X509_EXTENSION *ext = X509V3_EXT_nconf_nid(NULL, &v3ctx, nid, (char *)value); - if (!ext) return -1; - int rc = X509_add_ext(leaf, ext, -1); - X509_EXTENSION_free(ext); - return rc == 1 ? 0 : -1; -} - -// Build a per-SNI leaf per the spec §3 profile, signed by the CA. `ku_str` is the -// keyUsage extension config string ("critical,digitalSignature" in production). -// The forge_leaf production wrapper pins the spec keyUsage; the parameter exists -// so the test suite can prove the SSL-server purpose gate is load-bearing. -// Returns a new X509* owned by the caller, or NULL on failure (no leaks). -static X509 * -forge_leaf_ku(mq_mitm_core_t *core, const char *norm_sni, const char *ku_str) -{ - if (!core || !norm_sni) return NULL; - - X509 *leaf = NULL; - GENERAL_NAME *gen = NULL; - GENERAL_NAMES *gens = NULL; - ASN1_IA5STRING *ia5 = NULL; - BIGNUM *bn = NULL; - ASN1_INTEGER *serial = NULL; - X509_NAME *subj = NULL; - int ok = 0; - - leaf = X509_new(); - if (!leaf) goto done; - if (X509_set_version(leaf, 2) != 1) goto done; // v3 - - // Issuer = CA subject name. - if (X509_set_issuer_name(leaf, X509_get_subject_name(core->ca_cert)) != 1) goto done; - - // Subject CN = fixed literal (NOT the SNI — DNS names can exceed CN limit). - subj = X509_get_subject_name(leaf); - if (!subj) goto done; - if (X509_NAME_add_entry_by_NID(subj, NID_commonName, MBSTRING_ASC, - (const unsigned char *)"mqproxy-mitm", -1, -1, 0) != 1) - goto done; - - // Public key = shared leaf key. - if (X509_set_pubkey(leaf, core->leaf_key) != 1) goto done; - - // Serial = positive random (~64 bits). - bn = BN_new(); - if (!bn) goto done; - if (BN_rand(bn, MQ_MITM_LEAF_SERIAL_BITS, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ANY) != 1) - goto done; - serial = BN_to_ASN1_INTEGER(bn, NULL); - if (!serial) goto done; - if (X509_set_serialNumber(leaf, serial) != 1) goto done; - - // Validity: notBefore = now-1h, notAfter = now + leaf_ttl_sec. - { - time_t now = core->now_fn(); - if (!X509_time_adj_ex(X509_getm_notBefore(leaf), 0, -MQ_MITM_LEAF_BACKDATE_SEC, - &now)) - goto done; - if (!X509_time_adj_ex(X509_getm_notAfter(leaf), 0, core->leaf_ttl_sec, &now)) - goto done; - } - - // SAN: DNS = normalized SNI, built via GENERAL_NAME/GEN_DNS (NOT a conf string). - gens = sk_GENERAL_NAME_new_null(); - if (!gens) goto done; - gen = GENERAL_NAME_new(); - if (!gen) goto done; - ia5 = ASN1_IA5STRING_new(); - if (!ia5) goto done; - if (ASN1_STRING_set(ia5, norm_sni, -1) != 1) goto done; - GENERAL_NAME_set0_value(gen, GEN_DNS, ia5); - ia5 = NULL; // ownership transferred to gen - if (!sk_GENERAL_NAME_push(gens, gen)) goto done; - gen = NULL; // ownership transferred to gens - if (X509_add1_ext_i2d(leaf, NID_subject_alt_name, gens, 0, 0) != 1) goto done; - - // basicConstraints / keyUsage / extendedKeyUsage. - if (add_ext_conf(leaf, NID_basic_constraints, "critical,CA:FALSE") != 0) goto done; - if (add_ext_conf(leaf, NID_key_usage, ku_str) != 0) goto done; - if (add_ext_conf(leaf, NID_ext_key_usage, "serverAuth") != 0) goto done; - - // Sign with the CA key. - if (X509_sign(leaf, core->ca_key, EVP_sha256()) == 0) goto done; - - ok = 1; - -done: - if (gens) GENERAL_NAMES_free(gens); // frees any remaining pushed GENERAL_NAMEs - if (gen) GENERAL_NAME_free(gen); // only if not yet pushed - if (ia5) ASN1_IA5STRING_free(ia5); // only if ownership not transferred - if (serial) ASN1_INTEGER_free(serial); - if (bn) BN_free(bn); - if (!ok && leaf) { - X509_free(leaf); - leaf = NULL; - } - return leaf; -} - -// Production forge: spec §3 keyUsage = critical, digitalSignature. -static X509 * -forge_leaf(mq_mitm_core_t *core, const char *norm_sni) -{ - return forge_leaf_ku(core, norm_sni, "critical,digitalSignature"); -} - -// --- Bounded LRU leaf cache (Task 5) --- - -// Unlink `e` from the intrusive list (does not free it). -static void -cache_unlink(mq_mitm_core_t *core, mq_cache_entry_t *e) -{ - if (e->prev) - e->prev->next = e->next; - else - core->lru_head = e->next; - if (e->next) - e->next->prev = e->prev; - else - core->lru_tail = e->prev; - e->prev = e->next = NULL; -} - -// Push `e` at the MRU (head) of the list. -static void -cache_push_front(mq_mitm_core_t *core, mq_cache_entry_t *e) -{ - e->prev = NULL; - e->next = core->lru_head; - if (core->lru_head) core->lru_head->prev = e; - core->lru_head = e; - if (!core->lru_tail) core->lru_tail = e; -} - -// Free one entry: release its single cache-owned X509 ref + the node. -static void -cache_entry_free(mq_cache_entry_t *e) -{ - if (!e) return; - if (e->leaf) X509_free(e->leaf); - free(e); -} - -// Drop and free the LRU (tail) entry. Caller ensures cache is non-empty. -static void -cache_evict_lru(mq_mitm_core_t *core) -{ - mq_cache_entry_t *victim = core->lru_tail; - if (!victim) return; - cache_unlink(core, victim); - cache_entry_free(victim); - core->cache_count--; -} - -// Bounded LRU leaf cache keyed by normalized SNI, TTL-aware. -// -// REFCOUNT CONTRACT: returns a NEW X509 ref the CALLER owns and MUST X509_free. -// - HIT (fresh): X509_up_ref the cached leaf, move it to MRU, return that ref. -// The cache keeps its own ref. -// - HIT (expired / within SKEW of not_after): evict the stale entry, fall to MISS. -// - MISS: forge once (ref=1). If caching is enabled and we can store it, take a -// SECOND ref (X509_up_ref) for the cache and hand the ORIGINAL ref to the -// caller; otherwise (cache disabled, or storage allocation failed) hand the -// original ref straight to the caller and store nothing. Either way the caller -// gets exactly one ref and nothing leaks. -static X509 * -cache_get_or_forge(mq_mitm_core_t *core, const char *norm_sni) -{ - if (!core || !norm_sni) return NULL; - - time_t now = core->now_fn(); - - // Caching disabled → always forge, never store. forge_leaf returns ref=1, - // which is exactly the caller's ref. - if (core->cache_size == 0) return forge_leaf(core, norm_sni); - - // Linear scan for a hit. - for (mq_cache_entry_t *e = core->lru_head; e; e = e->next) { - if (strcmp(e->norm_sni, norm_sni) != 0) continue; - // Fresh only if comfortably before not_after. - if (now < e->not_after - MQ_MITM_CACHE_SKEW_SEC) { - cache_unlink(core, e); // move to MRU - cache_push_front(core, e); - (void)X509_up_ref(e->leaf); // caller's ref; cache keeps its own - // (never fails in practice — internal lock) - return e->leaf; - } - // Stale → evict and fall through to a fresh forge. - cache_unlink(core, e); - cache_entry_free(e); - core->cache_count--; - break; - } - - // MISS → forge (ref=1, the caller's ref). - X509 *leaf = forge_leaf(core, norm_sni); - if (!leaf) return NULL; - - // Try to store a SECOND ref. On any allocation failure, skip caching and - // still return the caller's ref (degrade gracefully, never leak/double-free). - mq_cache_entry_t *e = calloc(1, sizeof *e); - if (e) { - size_t n = strlen(norm_sni); - if (n < sizeof e->norm_sni) { - memcpy(e->norm_sni, norm_sni, n + 1); - e->leaf = leaf; - (void)X509_up_ref(e->leaf); // cache-owned ref (caller keeps the original) - // (never fails in practice — internal lock) - e->not_after = now + core->leaf_ttl_sec; - if (core->cache_count >= core->cache_size) cache_evict_lru(core); - cache_push_front(core, e); - core->cache_count++; - } else { - free(e); // pathological key (can't happen: norm_sni <= 253); skip caching - } - } - return leaf; -} - -// TEST-ONLY hooks (no header; forward-declared extern in the test). -X509 * -mq_mitm_cache_get_or_forge_for_test(mq_mitm_core_t *core, const char *norm_sni) -{ - return cache_get_or_forge(core, norm_sni); -} - -X509 * -mq_mitm_forge_for_test(mq_mitm_core_t *core, const char *norm_sni) -{ - return forge_leaf(core, norm_sni); -} - -X509 * -mq_mitm_forge_ku_for_test(mq_mitm_core_t *core, const char *norm_sni, const char *ku_str) -{ - return forge_leaf_ku(core, norm_sni, ku_str); -} - -// --- Task 6: server SSL_CTX + select-certificate callback + ALPN h2 --- - -// Process-wide SSL ex-data slot used to stash the mq_mitm_core_t* on each SSL so -// the user-data-less select-certificate callback can recover it. Registered ONCE -// (pthread_once); never per-core. -1 means "not yet registered". -static int g_core_idx = -1; -static pthread_once_t g_core_idx_once = PTHREAD_ONCE_INIT; - -// Defined below (near destroy); registered as the ex-data free callback so each -// SSL releases its core ref on SSL_free. -static void core_ssl_ex_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int index, - long argl, void *argp); - -static void -init_core_idx(void) -{ - g_core_idx = SSL_get_ex_new_index(0, NULL, NULL, NULL, core_ssl_ex_free); -} - -// Server-advertised ALPN protocol list (length-prefixed wire form): just "h2". -static const uint8_t k_alpn_h2[] = {0x02, 'h', '2'}; - -// ALPN selection: negotiate h2 if the client offers it, otherwise fatal alert. -// MINOR-3: SSL_select_next_proto's out param is uint8_t** while the cb signature -// gives us const uint8_t**; bridge with a one-line cast (don't fight -Werror). -static int -alpn_select_cb(SSL *ssl, const uint8_t **out, uint8_t *outlen, const uint8_t *in, - unsigned int inlen, void *arg) -{ - (void)ssl; - (void)arg; - // Documented arg order: SSL_select_next_proto(out, out_len, peer, peer_len, - // supported, supported_len) selects from `supported` (OURS) the first entry - // the peer offered. Pass the client's list as `peer` and our h2-only list as - // `supported`. (Today our list is a single proto so the intersection is - // order-insensitive, but keep the positions correct for when it grows.) - int rc = SSL_select_next_proto((uint8_t **)out, outlen, in, inlen, k_alpn_h2, - (unsigned int)sizeof k_alpn_h2); - if (rc == OPENSSL_NPN_NEGOTIATED) return SSL_TLSEXT_ERR_OK; - return SSL_TLSEXT_ERR_ALERT_FATAL; // OPENSSL_NPN_NO_OVERLAP (no h2 offered) -} - -// select-certificate callback: runs the no-ALPN guard, normalizes the SNI, -// forges/caches the leaf, and attaches it (+ the shared leaf key). Any failure -// returns ssl_select_cert_error so no half-configured connection proceeds. -static enum ssl_select_cert_result_t -select_certificate_cb(const SSL_CLIENT_HELLO *ch) -{ - mq_mitm_core_t *core = SSL_get_ex_data(ch->ssl, g_core_idx); - if (!core) return ssl_select_cert_error; - - // No-ALPN guard: refuse handshakes that don't offer ALPN at all (closes the - // "TLS completes with no ALPN" hole). Returns 1 if the extension is present. - const uint8_t *alpn_ext = NULL; // presence-only guard; the protocol contents are - // negotiated in alpn_select_cb - size_t alpn_len = 0; - if (SSL_early_callback_ctx_extension_get( - ch, TLSEXT_TYPE_application_layer_protocol_negotiation, &alpn_ext, - &alpn_len) == 0) - return ssl_select_cert_error; - - // SNI is mandatory: we forge per host name. - const char *sni = SSL_get_servername(ch->ssl, TLSEXT_NAMETYPE_host_name); - if (!sni) return ssl_select_cert_error; - - char norm[256]; - if (mq_mitm_normalize_sni(sni, strlen(sni), norm) != 0) return ssl_select_cert_error; - - // Forge/cache the leaf (caller-owned ref). - X509 *leaf = cache_get_or_forge(core, norm); - if (!leaf) return ssl_select_cert_error; - - // Attach leaf + shared key. SSL_use_certificate takes its OWN ref, so drop - // our ref afterwards (avoid leak) regardless of success. - // Order matters: SSL_use_certificate silently drops an inconsistent private key, so - // attach the cert first and the matching key last. - int used = (SSL_use_certificate(ch->ssl, leaf) == 1) && - (SSL_use_PrivateKey(ch->ssl, core->leaf_key) == 1); - X509_free(leaf); - if (!used) return ssl_select_cert_error; - - return ssl_select_cert_success; -} - -// Build the shared server SSL_CTX with TLS 1.2 floor + both callbacks. Returns -// the ctx or NULL on failure (no half-built ctx leaks). Lazily invoked. -static SSL_CTX * -build_ssl_ctx(void) -{ - SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); - if (!ctx) return NULL; - if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) != 1) { - SSL_CTX_free(ctx); - return NULL; - } - SSL_CTX_set_select_certificate_cb(ctx, select_certificate_cb); - SSL_CTX_set_alpn_select_cb(ctx, alpn_select_cb, NULL); - return ctx; -} - -SSL * -mq_mitm_core_new_ssl(mq_mitm_core_t *core) -{ - if (!core) return NULL; - - pthread_once(&g_core_idx_once, init_core_idx); - if (g_core_idx < 0) return NULL; // ex-data registration failed - - // Lazily build the shared SSL_CTX on first use. - // Lazy ctx build is not thread-safe: assumes a single owner thread per core (the - // 1-client/process MITM deployment model). The process-wide ex-data index IS - // pthread_once-guarded. - if (!core->ssl_ctx) { - core->ssl_ctx = build_ssl_ctx(); - if (!core->ssl_ctx) return NULL; - } - - SSL *s = SSL_new(core->ssl_ctx); - if (!s) return NULL; - // Hand this SSL its own core ref BEFORE stashing the pointer, so the ref the - // ex-data free callback will release on SSL_free is already accounted for. - core->refcount++; - if (SSL_set_ex_data(s, g_core_idx, core) != 1) { - core->refcount--; // ex-data slot not set → callback won't fire for it - SSL_free(s); - return NULL; - } - SSL_set_accept_state(s); - return s; -} - -// Drop one ref; tear down only when the last ref goes away. Shared by the public -// destroy() (owner ref) and the SSL ex-data free callback (per-SSL ref). -static void -core_release(mq_mitm_core_t *core) -{ - if (--core->refcount > 0) return; - // Order: SSL_CTX first, then cache, then keys/cert. Each cache entry holds - // one X509 ref on its leaf; release them all before freeing the keys/cert. - // (SSL_CTX_free here only drops the core's own ctx ref — any SSL still alive - // holds its own ref, so the ctx outlives this call until that SSL is freed.) - if (core->ssl_ctx) SSL_CTX_free(core->ssl_ctx); - for (mq_cache_entry_t *e = core->lru_head; e;) { - mq_cache_entry_t *next = e->next; - cache_entry_free(e); - e = next; - } - core->lru_head = core->lru_tail = NULL; - core->cache_count = 0; - if (core->leaf_key) EVP_PKEY_free(core->leaf_key); - if (core->ca_key) EVP_PKEY_free(core->ca_key); - if (core->ca_cert) X509_free(core->ca_cert); - free(core); -} - -// SSL ex-data free callback: releases the per-SSL core ref on SSL_free. `ptr` is -// the stashed core (NULL if the slot was never set — see SSL_set_ex_data failure -// path in mq_mitm_core_new_ssl). Per the BoringSSL contract, `parent`/`ad` are -// NULL and must not be touched; `core` is a separate object and is safe to use. -static void -core_ssl_ex_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int index, long argl, - void *argp) -{ - (void)parent; - (void)ad; - (void)index; - (void)argl; - (void)argp; - if (ptr) core_release((mq_mitm_core_t *)ptr); -} - -void -mq_mitm_core_destroy(mq_mitm_core_t *core) -{ - if (!core) return; - core_release(core); // drops the owner ref (actual teardown when refcount==0) -} - -void -mq_mitm_core_set_clock_for_test(mq_mitm_core_t *core, time_t (*now_fn)(void)) -{ - if (!core) return; - core->now_fn = now_fn ? now_fn : mq_default_now; -} diff --git a/src/mitm/mq_mitm_core.h b/src/mitm/mq_mitm_core.h deleted file mode 100644 index 5ae57bc8..00000000 --- a/src/mitm/mq_mitm_core.h +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// mq_mitm_core — Phase 7 Slice 2 MITM crypto core. I/O-free: owns CA + per-SNI -// leaf forge + LRU cache + a server-mode SSL_CTX. The CALLER owns the socket/BIO -// and pumps the handshake. NOT wired into the live tproxy path (Slice 3). -#ifndef MQ_MITM_CORE_H -#define MQ_MITM_CORE_H - -#include -#include -#include - -typedef struct mq_mitm_core mq_mitm_core_t; - -typedef struct { - size_t cache_size; // forged-leaf LRU capacity; 0 disables caching (always forge) - int leaf_ttl_sec; // forged-leaf validity = now-1h .. now+ttl; clamped to [60, - // 86400*30] -} mq_mitm_opts_t; - -// Load CA from PEM paths. Validates CA eligibility (CA:TRUE; keyCertSign when -// keyUsage present; EXFLAG_INVALID clear; NAME_CONSTRAINTS rejected), key<->cert -// match, and key-file safety (regular file, owner-only perms, no symlink, not -// encrypted). Generates the single shared P-256 leaf key. Returns NULL on any -// failure. opts==NULL uses defaults (cache_size=256, leaf_ttl_sec=86400). -mq_mitm_core_t *mq_mitm_core_create(const char *ca_cert_pem_path, - const char *ca_key_pem_path, - const mq_mitm_opts_t *opts); - -// Returns a server-mode SSL* with the select-certificate callback (no-ALPN guard -// + SNI normalize + forge/cache + attach) and h2 ALPN selector installed. The -// core pointer is stashed on the SSL via ex_data so the user-data-less callback -// can recover it. OWNERSHIP: caller owns the returned SSL* and MUST SSL_free it -// before mq_mitm_core_destroy(). Attaching BIO(s) via SSL_set_bio transfers BIO -// ownership to the SSL. Returns NULL on any setup failure (allocation, ex-data -// registration, or SSL_CTX build). -SSL *mq_mitm_core_new_ssl(mq_mitm_core_t *core); - -void mq_mitm_core_destroy(mq_mitm_core_t *core); - -// Normalize an SNI host name for use as a forge key / ignore-hosts lookup key. -// On success returns 0 and writes a NUL-terminated, lowercased DNS name (<= 253 -// chars, no trailing/leading dot) into out. Returns -1 for: NULL/empty input, -// total length > 253, any label > 63 bytes, an empty label (e.g. "a..b" or a -// leading dot), an IP literal (IPv4/IPv6), a wildcard ('*'), or any byte outside -// [A-Za-z0-9.-]. A single trailing dot is stripped before validation. The live -// MITM orchestrator (mq_mitm_conn) calls this before mq_ignore_hosts_match. -int mq_mitm_normalize_sni(const char *sni, size_t sni_len, char out[256]); - -// TEST-ONLY: override the clock used for leaf validity + cache expiry. Pass NULL -// to restore the default (time(NULL)). Not for production use. -void mq_mitm_core_set_clock_for_test(mq_mitm_core_t *core, time_t (*now_fn)(void)); - -#endif // MQ_MITM_CORE_H diff --git a/src/proxy/mq_client.c b/src/proxy/mq_client.c deleted file mode 100644 index 0688581e..00000000 --- a/src/proxy/mq_client.c +++ /dev/null @@ -1,1173 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_client.c — proxy client + control-stream AUTH handshake. - * - * Flow: - * mq_client_start -> mq_conn_connect (ALPN "mqproxy-tcp/1") - * on ESTABLISHED -> open control stream, send AUTH_REQUEST (no FIN) - * on control stream readable -> accumulate bytes, retry mq_decode_auth_resp - * until it succeeds; report via on_auth. - * - * The control stream stays open after auth so Tasks 12/13 can reuse it. - */ -#include "proxy/mq_client.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "proxy/mq_flow.h" -#include "proxy/mq_framebuf.h" -#include "proxy/mq_udp_session.h" -#include "transport/mq_stream.h" -#include "util/mq_backoff.h" -#include "util/mq_log.h" -#include "wire/mq_wire.h" - -#define MQ_CLIENT_ALPN "mqproxy-tcp/1" - -/* The buffered AUTH_RESPONSE / CONNECT_TCP_RESPONSE is bounded by - * MQ_FRAMEBUF_CAP (512): both frames are tiny (status + error + small fields), - * well under that. Exceeding the bound without a valid decode is malformed - * (Phase 1: fail closed). */ - -/* Bound on pre-auth queued tcp_open requests. Beyond this we fail-fast new - * opens (cb with CONN_REFUSED) rather than grow unboundedly. */ -#define MQ_CLIENT_QUEUE_MAX 256 - -/* Extra (non-primary) local-bind IPs the CLI's --path args request. Each is - * brought up as its own MPQUIC path once the conn is multipath-ready. */ -#define MQ_CLIENT_MAX_PATHS 8 -/* Max length of a stored bind IP literal (v4/v6). */ -#define MQ_CLIENT_IP_MAX 64 -/* Poll interval (ms) for the mp-ready deferral timer. */ -#define MQ_CLIENT_MP_POLL_MS 50 -/* Base delay (ms) for the reconnect exponential backoff. */ -#define MQ_CLIENT_RECONNECT_BASE_MS 250 - -typedef struct mq_client_open_s mq_client_open_t; - -/* A queued (pre-auth) tcp_open request. The host/port/atype/local_fd/cb/user are - * captured verbatim and replayed once auth succeeds. */ -struct mq_client_open_s { - uint8_t host[MQ_MAX_HOST]; - size_t host_len; - mq_addr_type_t atype; - uint16_t port; - int local_fd; - /* App bytes the ingress read coalesced with the request head (owned copy, - * freed on drain/fail); replayed into the flow's B-side prebuffer. */ - uint8_t *early; - size_t early_len; - void *user; - mq_tcp_open_cb cb; - mq_client_open_t *next; -}; - -/* An in-flight tcp_open: a data stream awaiting / relaying after the - * CONNECT_TCP_RESPONSE. Two phases: - * 1. Header: buffer stream bytes until CONNECT_TCP_RESPONSE decodes. - * On OK → fire cb(ok), start an mq_flow relay (stream ⇄ local_fd). - * On ERROR/malformed → fire cb(err), close stream + local_fd. - * 2. Relaying: mq_flow drives the relay; on completion/teardown the flow - * closes the stream + local_fd and reaps this node. */ -struct mq_client_data_s { - struct mq_client_s *cli; - mq_flow_t *flow; /* relay node (owns stream + local_fd once relaying) */ - mq_stream_t *stream; /* header phase: the data stream (also held by flow) */ - int local_fd; /* app socket; handed to the flow on relay start */ - int reaped; - int resp_done; /* the response was decoded + cb fired */ - - /* App bytes the ingress read coalesced with the request head (owned copy, - * freed once injected into the flow's B-side prebuffer or on reap). */ - uint8_t *early; - size_t early_len; - - void *user; - mq_tcp_open_cb cb; - - mq_framebuf_t rx; - - struct mq_client_data_s *next; -}; -typedef struct mq_client_data_s mq_client_data_t; - -struct mq_client_s { - mq_transport_t *transport; - mq_runtime_t *rt; - mq_cc_t cc; /* congestion control for this client's conn */ - char server_ip[64]; - uint16_t server_port; - - mq_auth_req_t req; /* prebuilt AUTH_REQUEST fields */ - - mq_conn_t *conn; - mq_stream_t *ctrl; /* control stream */ - - mq_client_on_auth_fn on_auth; - void *on_auth_user; - mq_conn_on_state_fn on_state; - void *on_state_user; - - /* AUTH_RESPONSE accumulation buffer. */ - mq_framebuf_t rx; - - int authed; - int auth_reported; /* on_auth fired exactly once */ - int closed; /* connection reached CLOSED: no new opens */ - - /* AUTH_RESPONSE.features saved at auth time (for mq_client_udp_available). */ - uint64_t auth_features; - - /* Per-connection UDP relay session table (client role). Created in - * mq_client_start; driven on auth/conn-close; freed in mq_client_free. */ - mq_udp_cli_t *udp; - - /* Pre-auth queue (FIFO) + count. */ - mq_client_open_t *queue_head; - mq_client_open_t *queue_tail; - size_t queue_len; - - /* Active in-flight / relaying data streams. */ - mq_client_data_t *data_head; - - /* Keepalive: if > 0, enables xquic PING keepalive with this idle timeout. */ - uint64_t keepalive_idle_ms; - - /* Deferred extra paths (Task 19, CLI --path). Stored at mq_client_add_paths; - * added via mq_conn_add_path once the conn is multipath-ready. A recurring - * libevent timer (pending_paths > 0) polls mp-ready and adds them. */ - char extra_paths[MQ_CLIENT_MAX_PATHS][MQ_CLIENT_IP_MAX]; - size_t n_extra_paths; /* total registered */ - size_t added_paths; /* how many have been brought up so far */ - struct event *mp_timer; /* armed while paths remain to add */ - - /* Reconnect controller (Phase 5b). reconnect_ev is created ONCE in - * mq_client_start (when enabled) and freed ONLY in mq_client_free; it is a - * one-shot timer re-armed across reconnect cycles, so it must survive a stop. - * shutting_down is set FIRST by mq_client_free so a late MQ_CONN_CLOSED takes - * the terminal path (no reconnect-after-free). */ - int shutting_down; - int reconnect_enabled; - uint64_t reconnect_max_backoff_ms; - struct event *reconnect_ev; - unsigned reconnect_attempts; -}; - -/* Forward decl: the mp-ready deferral timer teardown is referenced from - * client_on_state (CLOSED) but defined below near the other path-add helpers. */ -static void client_mp_timer_stop(mq_client_t *c); -static void client_mp_timer_arm(mq_client_t *c); - -/* Forward decls: the reconnect controller is referenced from client_on_state / - * client_report_auth but its arm path needs client_issue_connect (defined below). - * client_issue_connect itself is declared here so client_reconnect_cb can call it. */ -static int client_issue_connect(mq_client_t *c); -static void client_reconnect_arm(mq_client_t *c); -static void client_reconnect_stop(mq_client_t *c); - -/* Bridge mq_conn's datagram callback (mq_conn_t* + user) to the UDP relay - * client role's dispatch (mq_udp_cli_t* first arg). user is the mq_client_t*. */ -static void -client_on_datagram(mq_conn_t *conn, const uint8_t *data, size_t len, void *user) -{ - (void)conn; - mq_client_t *c = (mq_client_t *)user; - if (c && c->udp) { - mq_udp_cli_on_datagram(c->udp, data, len); - } -} - -/* ── in-flight data-stream nodes ─────────────────────────────────────────── */ - -static void -client_data_unlink(mq_client_data_t *d) -{ - mq_client_t *c = d->cli; - mq_client_data_t **pp = &c->data_head; - while (*pp) { - if (*pp == d) { - *pp = d->next; - return; - } - pp = &(*pp)->next; - } -} - -/* Reap a data node exactly once. In the relay phase the flow owns the stream + - * local_fd and closes both; pre-relay we close them directly here. Idempotent. */ -static void -client_data_reap(mq_client_data_t *d) -{ - if (!d || d->reaped) { - return; - } - d->reaped = 1; - - if (d->flow) { - mq_flow_t *fl = d->flow; - d->flow = NULL; - d->stream = NULL; - d->local_fd = -1; - mq_flow_reap(fl); - } else { - if (d->stream) { - mq_stream_set_cbs(d->stream, NULL, NULL, NULL, NULL); - mq_stream_close(d->stream); - d->stream = NULL; - } - if (d->local_fd >= 0) { - close(d->local_fd); - d->local_fd = -1; - } - } - free(d->early); - d->early = NULL; - d->early_len = 0; - client_data_unlink(d); - free(d); -} - -/* Flow reap callback: the relay closed stream + local_fd. Drop our pointers and - * reap the node. Guarded by d->reaped against the srv-style re-entry. */ -static void -client_flow_on_reap(mq_flow_t *fl, void *user) -{ - (void)fl; - mq_client_data_t *d = (mq_client_data_t *)user; - d->flow = NULL; - d->stream = NULL; - d->local_fd = -1; - client_data_reap(d); -} - -/* Fire the open's cb exactly once. */ -static void -client_data_report(mq_client_data_t *d, int ok, mq_tcp_err_t err) -{ - if (d->resp_done) { - return; - } - d->resp_done = 1; - if (d->cb) { - d->cb(ok, err, d->user); - } -} - -/* Header phase: stream readable — buffer bytes, decode CONNECT_TCP_RESPONSE. */ -static void -client_data_header_readable(mq_stream_t *s, void *user) -{ - mq_client_data_t *d = (mq_client_data_t *)user; - - int fin = 0; - if (mq_framebuf_fill(s, &d->rx, &fin) < 0) { - /* Hard error / RESET before a response: fail the open. */ - client_data_report(d, 0, MQ_TCP_CONN_REFUSED); - client_data_reap(d); - return; - } - - mq_connect_tcp_resp_t resp; - memset(&resp, 0, sizeof(resp)); - int consumed = mq_decode_connect_tcp_resp(d->rx.buf, d->rx.len, &resp); - if (consumed < 0) { - if (d->rx.len >= sizeof(d->rx.buf) || fin) { - /* Oversized or stream FIN'd without a valid response: malformed. */ - MQ_LOGW("mq_client: CONNECT_TCP_RESPONSE malformed/oversized"); - client_data_report(d, 0, MQ_TCP_CONN_REFUSED); - client_data_reap(d); - } - return; /* otherwise need more bytes */ - } - - if (resp.status != MQ_STATUS_OK) { - /* Error response: report the server's code, close stream + local_fd. */ - client_data_report(d, 0, resp.error_code); - client_data_reap(d); - return; - } - - /* OK. Report success first (the ingress sends its app-side success reply), - * then start the relay binding the stream ⇄ local_fd. */ - client_data_report(d, 1, MQ_TCP_OK); - - struct event_base *base = mq_runtime_base(d->cli->rt); - d->flow = mq_flow_new(&d->flow, base, d->stream, d->local_fd, client_flow_on_reap, d); - if (!d->flow) { - MQ_LOGE("mq_client: flow alloc failed"); - client_data_reap(d); - return; - } - /* The flow owns the stream + local_fd now. */ - d->local_fd = -1; - - /* CRITICAL: while buffering the response we may have pulled payload bytes - * that trailed the CONNECT_TCP_RESPONSE in the same read into rx.buf. The - * relay reads fresh from the stream, so hand these leftover bytes to the - * flow's prebuffer or they would be silently dropped (truncating the - * download by exactly the trailing amount). */ - size_t consumed_sz = (size_t)consumed; - if (d->rx.len > consumed_sz) { - if (mq_flow_prebuffer(d->flow, d->rx.buf + consumed_sz, - d->rx.len - consumed_sz) != 0) { - MQ_LOGE("mq_client: prebuffer failed"); - client_data_reap(d); - return; - } - } - - /* Symmetric uplink case: app bytes the ingress read coalesced with the - * SOCKS5/HTTP CONNECT request. Hand them to the flow's B-side prebuffer so - * they reach the origin ahead of fresh local_fd reads (else the first upload - * bytes — e.g. a TLS ClientHello — are silently dropped). */ - if (d->early_len > 0) { - if (mq_flow_prebuffer_b(d->flow, d->early, d->early_len) != 0) { - MQ_LOGE("mq_client: B-side prebuffer failed"); - client_data_reap(d); - return; - } - free(d->early); - d->early = NULL; - d->early_len = 0; - } - - if (mq_flow_begin_relay(d->flow) != 0) { - MQ_LOGE("mq_client: begin relay failed"); - client_data_reap(d); - } -} - -/* Header-phase stream close: the stream closed before a response decoded. */ -static void -client_data_header_closed(mq_stream_t *s, void *user) -{ - (void)s; - mq_client_data_t *d = (mq_client_data_t *)user; - d->stream = NULL; /* the transport frees it after this returns */ - /* If no response was decoded yet, fail the open. */ - client_data_report(d, 0, MQ_TCP_CONN_REFUSED); - client_data_reap(d); -} - -/* Issue an authed tcp_open: open a data stream, send CONNECT_TCP_REQUEST, and - * buffer the response. local_fd ownership transfers to the new node (closed on - * any terminal outcome). On a synchronous failure the cb fires and local_fd is - * closed here. early/early_len are app bytes that arrived coalesced with the - * request head; they are copied and replayed into the flow's B-side prebuffer - * once the relay starts (borrowed for the duration of this call). */ -static void -client_issue_open(mq_client_t *c, const uint8_t *host, size_t host_len, - mq_addr_type_t atype, uint16_t port, int local_fd, const uint8_t *early, - size_t early_len, void *user, mq_tcp_open_cb cb) -{ - mq_stream_t *s = mq_conn_open_stream(c->conn); - if (!s) { - MQ_LOGE("mq_client: open data stream failed"); - if (cb) cb(0, MQ_TCP_CONN_REFUSED, user); - if (local_fd >= 0) close(local_fd); - return; - } - - mq_client_data_t *d = calloc(1, sizeof(*d)); - if (!d) { - MQ_LOGE("mq_client: OOM allocating data node"); - mq_stream_close(s); - if (cb) cb(0, MQ_TCP_CONN_REFUSED, user); - if (local_fd >= 0) close(local_fd); - return; - } - d->cli = c; - d->stream = s; - d->local_fd = local_fd; - d->user = user; - d->cb = cb; - if (early_len > 0 && early) { - d->early = malloc(early_len); - if (!d->early) { - MQ_LOGE("mq_client: OOM copying coalesced bytes"); - mq_stream_close(s); - free(d); - if (cb) cb(0, MQ_TCP_CONN_REFUSED, user); - if (local_fd >= 0) close(local_fd); - return; - } - memcpy(d->early, early, early_len); - d->early_len = early_len; - } - d->next = c->data_head; - c->data_head = d; - - mq_stream_set_cbs(s, client_data_header_readable, NULL, client_data_header_closed, d); - - mq_connect_tcp_req_t req; - memset(&req, 0, sizeof(req)); - req.flags = 0; - req.address_type = atype; - if (host_len > MQ_MAX_HOST) host_len = MQ_MAX_HOST; - memcpy(req.host, host, host_len); - req.host_len = host_len; - req.port = port; - - uint8_t buf[512]; - /* Prepend the data-stream type discriminator (design §5.2). Byte 0 is - * MQ_STREAM_TYPE_CONNECT_TCP (0x01) encoded as a 1-byte varint; the - * CONNECT_TCP_REQUEST frame follows immediately at offset 1. Both are - * sent in one mq_stream_send call so the server's header phase sees a - * contiguous buffer and does not have to handle a split discriminator. */ - buf[0] = (uint8_t)MQ_STREAM_TYPE_CONNECT_TCP; - int n = mq_encode_connect_tcp_req(buf + 1, sizeof(buf) - 1, &req); - if (n < 0) { - MQ_LOGE("mq_client: encode CONNECT_TCP_REQUEST failed"); - client_data_report(d, 0, MQ_TCP_CONN_REFUSED); - client_data_reap(d); - return; - } - /* No FIN: keep the stream open for the response + bidirectional relay. */ - (void)mq_stream_send(s, buf, (size_t)(1 + n), /*fin=*/0); - - /* Bytes may already be buffered; try decoding now. */ - client_data_header_readable(s, d); -} - -/* Drain the pre-auth queue (auth succeeded). */ -static void -client_drain_queue(mq_client_t *c) -{ - mq_client_open_t *q = c->queue_head; - c->queue_head = c->queue_tail = NULL; - c->queue_len = 0; - while (q) { - mq_client_open_t *next = q->next; - client_issue_open(c, q->host, q->host_len, q->atype, q->port, q->local_fd, - q->early, q->early_len, q->user, q->cb); - free(q->early); - free(q); - q = next; - } -} - -/* Fail + free the whole pre-auth queue (connection died before auth). */ -static void -client_fail_queue(mq_client_t *c, mq_tcp_err_t err) -{ - mq_client_open_t *q = c->queue_head; - c->queue_head = c->queue_tail = NULL; - c->queue_len = 0; - while (q) { - mq_client_open_t *next = q->next; - if (q->cb) q->cb(0, err, q->user); - if (q->local_fd >= 0) close(q->local_fd); - free(q->early); - free(q); - q = next; - } -} - -static void -client_report_auth(mq_client_t *c, int ok, mq_auth_err_t err) -{ - if (c->auth_reported) { - return; - } - c->auth_reported = 1; - c->authed = ok ? 1 : 0; - if (c->on_auth) { - c->on_auth(ok, err, c->on_auth_user); - } - /* Drain queued opens now that the auth outcome is known. On success they are - * issued; on failure they are failed (cb + close local_fd). */ - if (ok) { - /* Back to SERVING: reset the backoff counter + disarm the reconnect timer - * so the next loss starts from base again. */ - c->reconnect_attempts = 0; - client_reconnect_stop(c); - client_drain_queue(c); - } else { - client_fail_queue(c, MQ_TCP_CONN_REFUSED); - } - - /* Drive the UDP relay glue with the now-known auth + capability outcome: - * issue pending sessions' streams (ok && available) or fail them closed - * (auth failure / capability denied). mq_client_udp_available() consults the - * saved features + datagram mss; here authed is already set above. */ - if (c->udp) { - int avail = (mq_client_udp_available(c) == 1); - mq_udp_cli_on_auth(c->udp, ok, avail); - } -} - -/* Control-stream readable: pull bytes, retry decode. */ -static void -client_ctrl_readable(mq_stream_t *s, void *user) -{ - mq_client_t *c = (mq_client_t *)user; - if (c->auth_reported) { - /* Auth already settled; drain to keep the stream from stalling. */ - uint8_t scratch[256]; - int fin = 0; - while (mq_stream_recv(s, scratch, sizeof(scratch), &fin) > 0) {} - return; - } - - mq_framebuf_fill(s, &c->rx, NULL); - - mq_auth_resp_t resp; - memset(&resp, 0, sizeof(resp)); - int consumed = mq_decode_auth_resp(c->rx.buf, c->rx.len, &resp); - if (consumed >= 0) { - int ok = (resp.status == MQ_STATUS_OK); - /* Save the advertised features BEFORE reporting so mq_client_udp_available - * (and the UDP glue inside client_report_auth) sees them. */ - c->auth_features = resp.features; - client_report_auth(c, ok, resp.error_code); - return; - } - - /* decode failed: could be "need more" or malformed. Phase 1: once we have - * accumulated past a sane bound without a valid frame, treat as malformed. */ - if (c->rx.len >= sizeof(c->rx.buf)) { - MQ_LOGW("mq_client: AUTH_RESPONSE exceeded %d bytes, treating as malformed", - MQ_FRAMEBUF_CAP); - client_report_auth(c, 0, MQ_AUTH_FAILED); - } -} - -/* Connection state: on ESTABLISHED, open control stream + send AUTH_REQUEST. */ -static void -client_on_state(mq_conn_t *conn, mq_conn_state_t st, void *user) -{ - mq_client_t *c = (mq_client_t *)user; - - if (st == MQ_CONN_ESTABLISHED) { - c->ctrl = mq_conn_open_stream(conn); - if (!c->ctrl) { - MQ_LOGE("mq_client: failed to open control stream"); - client_report_auth(c, 0, MQ_AUTH_FAILED); - goto forward; - } - mq_stream_set_cbs(c->ctrl, client_ctrl_readable, NULL, NULL, c); - - uint8_t buf[512]; - int n = mq_encode_auth_req(buf, sizeof(buf), &c->req); - if (n < 0) { - MQ_LOGE("mq_client: encode AUTH_REQUEST failed"); - client_report_auth(c, 0, MQ_AUTH_FAILED); - goto forward; - } - /* No FIN: keep the control stream open for the response. */ - long sent = mq_stream_send(c->ctrl, buf, (size_t)n, /*fin=*/0); - if (sent < 0 || (size_t)sent != (size_t)n) { - MQ_LOGE("mq_client: send AUTH_REQUEST short/failed (%ld of %d)", sent, n); - client_report_auth(c, 0, MQ_AUTH_FAILED); - goto forward; - } - - /* Re-arm multipath for this (possibly reconnected) conn: reset added_paths - * so the mp-timer re-adds every registered extra path on the new conn. - * Harmless on the first start (added_paths is already 0 and arm is a no-op - * when n_extra_paths == 0). */ - c->added_paths = 0; - client_mp_timer_arm(c); - } else if (st == MQ_CONN_CLOSED) { - /* Reap every active data node: fail any in-flight open whose response - * never arrived (cb with an error), then close stream + local_fd once. - * In-flight flows do NOT survive a tunnel loss (irreducible — design - * §Non-goal); only NEW opens resume after reconnect. Iterate defensively - * against the self-unlink. This runs identically on both branches. */ - while (c->data_head) { - mq_client_data_t *d = c->data_head; - client_data_report(d, 0, MQ_TCP_CONN_REFUSED); - client_data_reap(d); - } - - /* Decide reconnect vs terminal from shutting_down + reconnect_enabled - * ONLY (NOT auth_reported/authed — those are reset below on the reconnect - * path). reconnect_ev != NULL also guards the timer existing. */ - int reconnect_now = - (!c->shutting_down && c->reconnect_enabled && c->reconnect_ev != NULL); - - if (reconnect_now) { - /* Carry the pre-auth/serving open queue across to the next attempt: - * do NOT report auth failure and do NOT fail the queue. Instead reset - * the per-conn one-shot latches + conn-bound buffers so the next auth - * cycle runs fresh (design §Per-conn state reset). */ - c->auth_reported = 0; - c->authed = 0; - c->auth_features = 0; - c->closed = 0; - c->ctrl = NULL; - c->rx.len = 0; - - /* UDP: fail live/pending sessions w/ MQ_UDP_CLOSED + null the conn, - * then clear the latch/mss cache so the table is ready for a fresh - * auth cycle. conn stays NULL through the backoff window; - * client_issue_connect re-wires it (mq_udp_cli_set_conn) at the - * re-issued connect. */ - if (c->udp) { - mq_udp_cli_on_conn_close(c->udp); - mq_udp_cli_reset(c->udp); - } - - c->conn = NULL; - /* No more paths can be added on the dead conn; the new ESTABLISHED - * re-arms after resetting added_paths. */ - client_mp_timer_stop(c); - - /* Arm the backoff timer to re-establish the conn. */ - client_reconnect_arm(c); - } else { - /* Terminal (shutting_down or reconnect disabled): today's behavior. */ - c->conn = NULL; - c->ctrl = NULL; - c->closed = 1; - /* The conn is gone: no more paths can be added. Disarm the deferral - * timer so it does not dereference the freed conn. */ - client_mp_timer_stop(c); - /* If the connection died before auth settled, report failure (this - * also fails any pre-auth queued opens). If auth already succeeded, - * the queue is empty, but fail it defensively in case opens were - * queued in a race. */ - client_report_auth(c, 0, MQ_AUTH_FAILED); - client_fail_queue(c, MQ_TCP_CONN_REFUSED); - - /* Fail every live/pending UDP relay session with MQ_UDP_CLOSED (a - * non-OPEN-failure close — the assoc must not negative-cache on it). - * If the conn died before auth, client_report_auth above already - * failed the pending UDP sessions with POLICY_DENIED (auth failure); - * on_conn_close is then a no-op (cli already settled). */ - if (c->udp) { - mq_udp_cli_on_conn_close(c->udp); - } - } - } - -forward: - if (c->on_state) { - c->on_state(conn, st, c->on_state_user); - } -} - -mq_client_t * -mq_client_new(mq_transport_t *t, mq_runtime_t *rt, const char *server_ip, - uint16_t server_port, const char *client_id, const char *auth_token, - mq_cc_t cc) -{ - if (!t || !rt || !server_ip || !client_id || !auth_token) { - return NULL; - } - mq_client_t *c = calloc(1, sizeof(*c)); - if (!c) { - return NULL; - } - c->transport = t; - c->rt = rt; - c->cc = cc; - c->server_port = server_port; - snprintf(c->server_ip, sizeof(c->server_ip), "%s", server_ip); - - c->req.version = 1; - c->req.features = 0; - snprintf(c->req.client_id, sizeof(c->req.client_id), "%s", client_id); - /* auth_token is not NUL-guaranteed-meaningful but encoded as a string field; - * use the NUL-terminated length. */ - snprintf(c->req.auth_token, sizeof(c->req.auth_token), "%s", auth_token); - c->keepalive_idle_ms = 30000; /* default 30 s; 0 = disable */ - /* Reconnect on by default (design §Config); 30 s backoff cap. */ - c->reconnect_enabled = 1; - c->reconnect_max_backoff_ms = 30000; - return c; -} - -void -mq_client_set_reconnect(mq_client_t *c, int enabled, uint64_t max_backoff_ms) -{ - if (!c) { - return; - } - c->reconnect_enabled = enabled ? 1 : 0; - if (max_backoff_ms < 1000) { - max_backoff_ms = 1000; - } - c->reconnect_max_backoff_ms = max_backoff_ms; -} - -void -mq_client_set_keepalive(mq_client_t *c, uint64_t idle_ms) -{ - if (!c) { - return; - } - c->keepalive_idle_ms = idle_ms; -} - -void -mq_client_set_on_auth(mq_client_t *c, mq_client_on_auth_fn fn, void *user) -{ - if (!c) { - return; - } - c->on_auth = fn; - c->on_auth_user = user; -} - -void -mq_client_set_on_state(mq_client_t *c, mq_conn_on_state_fn fn, void *user) -{ - if (!c) { - return; - } - c->on_state = fn; - c->on_state_user = user; -} - -/* Build sockaddr + conn settings, call mq_conn_connect, wire on_state, and - * (if c->udp is already live from a previous start) re-wire the UDP table and - * datagram callback onto the new conn. On the first start c->udp is NULL, so - * the re-wire block is skipped; mq_client_start creates the table afterwards. - * Returns 0 on success, -1 on failure. */ -static int -client_issue_connect(mq_client_t *c) -{ - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(c->server_port); - if (inet_pton(AF_INET, c->server_ip, &sa.sin_addr) != 1) { - MQ_LOGE("mq_client: bad server ip '%s'", c->server_ip); - return -1; - } - - xqc_conn_settings_t settings; - memset(&settings, 0, sizeof(settings)); - settings.proto_version = XQC_VERSION_V1; - settings.pacing_on = 1; - settings.max_pkt_out_size = 1200; - /* Enable QUIC DATAGRAM (Phase 3 UDP relay carrier) on this mqproxy-tcp/1 - * conn. u16 field; max advertises the largest frame we accept (xquic caps - * the effective payload to the path MTU regardless). */ - settings.max_datagram_frame_size = 65535; - /* PING keepalive: send periodic PINGs to keep the idle connection alive and - * detect peer loss. Disabled when keepalive_idle_ms == 0. Only the post- - * handshake idle timeout is set here; init_idle_time_out is left at the - * xquic default (pre-handshake idle, intentional). */ - settings.ping_on = (c->keepalive_idle_ms > 0) ? 1 : 0; - if (c->keepalive_idle_ms > 0) { - settings.idle_time_out = c->keepalive_idle_ms; - } - /* Multipath + aggregate-BDP flow-control windows (see mq_conn.h). */ - mq_conn_apply_mp_settings(&settings, /*is_server=*/0, c->cc); - - c->conn = mq_conn_connect(c->transport, (struct sockaddr *)&sa, sizeof(sa), - MQ_CLIENT_ALPN, &settings, c); - if (!c->conn) { - MQ_LOGE("mq_client: connect failed"); - return -1; - } - mq_conn_set_on_state(c->conn, client_on_state, c); - - /* On reconnect (c->udp already exists), re-wire the UDP session table and - * datagram callback onto the new conn. On first start c->udp is NULL here - * (mq_client_start creates it after this call), so the block is a no-op. */ - if (c->udp) { - mq_udp_cli_set_conn(c->udp, c->conn); - mq_conn_set_on_datagram(c->conn, client_on_datagram, c); - } - - return 0; -} - -/* ── Reconnect controller (Phase 5b) ───────────────────────────────────────── - * - * reconnect_ev is created ONCE in mq_client_start and freed ONLY in - * mq_client_free; it is a one-shot timer re-armed across reconnect cycles, so - * stop() must NOT free it (unlike the mp-timer). */ - -/* Disarm the backoff timer (idempotent). Does NOT free reconnect_ev — it must - * survive for the next reconnect cycle (freed only in mq_client_free). */ -static void -client_reconnect_stop(mq_client_t *c) -{ - if (c->reconnect_ev) { - evtimer_del(c->reconnect_ev); - } -} - -/* Arm the one-shot backoff timer for the next reconnect attempt. Exponential - * (base 250 ms, cap reconnect_max_backoff_ms) with full jitter + a floor: - * the delay is randomised into [d/2, d] so the re-arm cannot land near zero and - * spin. */ -static void -client_reconnect_arm(mq_client_t *c) -{ - if (!c->reconnect_ev) { - return; - } - c->reconnect_attempts++; - uint64_t d = mq_backoff_ms(MQ_CLIENT_RECONNECT_BASE_MS, c->reconnect_max_backoff_ms, - c->reconnect_attempts); - /* jitter into [d/2, d] (half-jitter with a d/2 floor to avoid near-zero re-spin). */ - uint64_t j = d / 2 + (uint64_t)(random() % (long)(d / 2 + 1)); - struct timeval tv = {.tv_sec = (time_t)(j / 1000), - .tv_usec = (suseconds_t)((j % 1000) * 1000)}; - evtimer_add(c->reconnect_ev, &tv); -} - -/* Backoff timer fired: re-issue the connect. If the connect itself fails - * (mq_conn_connect returned NULL), re-arm with the next backoff rather than - * going terminal — an always-on device must self-heal across long outages. */ -static void -client_reconnect_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - mq_client_t *c = (mq_client_t *)arg; - if (client_issue_connect(c) != 0) { - client_reconnect_arm(c); - } -} - -int -mq_client_start(mq_client_t *c) -{ - if (!c) { - return -1; - } - - /* Register the client ALPN (no server-side hooks). Idempotency: registering - * twice on one engine is not supported, so this assumes one client per - * engine, which matches the proxy's usage. */ - if (mq_conn_register_alpn(c->transport, MQ_CLIENT_ALPN, NULL, NULL, NULL) != 0) { - MQ_LOGE("mq_client: register_alpn failed"); - return -1; - } - - /* Create the reconnect backoff timer ONCE (when enabled). It is one-shot, - * armed only from the CLOSED handler, disarmed on reaching SERVING + on - * shutdown, and freed only in mq_client_free. */ - if (c->reconnect_enabled) { - c->reconnect_ev = evtimer_new(mq_runtime_base(c->rt), client_reconnect_cb, c); - if (!c->reconnect_ev) { - MQ_LOGW("mq_client: reconnect timer alloc failed (reconnect disabled)"); - } - } - - if (client_issue_connect(c) != 0) { - return -1; - } - - /* Per-connection UDP relay session table (client role) + datagram dispatch. - * Created here so opens may be queued pre-auth; freed in mq_client_free. - * Non-fatal on OOM: the client still serves TCP, and UDP opens fail with a - * NULL handle (mq_udp_cli_open on a NULL core returns NULL). */ - c->udp = mq_udp_cli_new(c->conn, mq_runtime_base(c->rt)); - if (c->udp) { - mq_conn_set_on_datagram(c->conn, client_on_datagram, c); - } else { - MQ_LOGW("mq_client: UDP relay session table alloc failed (TCP-only)"); - } - return 0; -} - -int -mq_client_is_authed(const mq_client_t *c) -{ - return c ? c->authed : 0; -} - -mq_conn_t * -mq_client_conn(const mq_client_t *c) -{ - return c ? c->conn : NULL; -} - -/* ── Multipath: bring up deferred extra paths once mp-ready (Task 19) ───────── */ - -/* Disarm + free the mp-ready deferral timer (idempotent). */ -static void -client_mp_timer_stop(mq_client_t *c) -{ - if (c->mp_timer) { - event_free(c->mp_timer); - c->mp_timer = NULL; - } -} - -/* Recurring timer: once the conn is multipath-ready, add every still-pending - * extra path (each on an ephemeral local UDP port), then disarm. Disarms early - * if the connection went away. */ -static void -client_mp_timer_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - mq_client_t *c = (mq_client_t *)arg; - - if (c->closed || !c->conn) { - client_mp_timer_stop(c); - return; - } - if (!mq_conn_mp_ready(c->conn)) { - return; /* not ready yet; the persistent timer will fire again */ - } - - while (c->added_paths < c->n_extra_paths) { - const char *ip = c->extra_paths[c->added_paths]; - int pid = mq_conn_add_path(c->conn, ip, /*local_port=0 -> ephemeral*/ 0); - /* mq_conn_add_path returns the new path_id (>= 0) on success, or the - * documented failure sentinel -1. Treat any non-negative id as success - * so a path_id of 0 is not mis-logged as a failure. */ - if (pid >= 0) { - MQ_LOGI("mq_client: extra path up: bind %s -> path_id %d", ip, pid); - } else { - MQ_LOGW("mq_client: failed to add extra path bind %s (path_id rc=%d)", ip, - pid); - } - c->added_paths++; /* advance regardless: do not spin on a bad bind */ - } - /* All pending paths processed — disarm. */ - client_mp_timer_stop(c); -} - -/* Arm the recurring mp-ready timer if there is pending work and it is not yet - * armed. Safe to call repeatedly. */ -static void -client_mp_timer_arm(mq_client_t *c) -{ - if (c->mp_timer || c->added_paths >= c->n_extra_paths) { - return; - } - struct event_base *base = mq_runtime_base(c->rt); - if (!base) { - return; - } - c->mp_timer = event_new(base, -1, EV_PERSIST, client_mp_timer_cb, c); - if (!c->mp_timer) { - MQ_LOGE("mq_client: failed to create mp-ready timer"); - return; - } - struct timeval tv = {.tv_sec = 0, .tv_usec = MQ_CLIENT_MP_POLL_MS * 1000}; - event_add(c->mp_timer, &tv); -} - -int -mq_client_add_paths(mq_client_t *c, const char *const *ips, size_t n) -{ - if (!c || (n > 0 && !ips)) { - return -1; - } - size_t accepted = 0; - for (size_t i = 0; i < n; i++) { - if (!ips[i]) { - continue; - } - if (c->n_extra_paths >= MQ_CLIENT_MAX_PATHS) { - MQ_LOGW("mq_client: extra-path capacity %d reached; ignoring %s", - MQ_CLIENT_MAX_PATHS, ips[i]); - break; - } - snprintf(c->extra_paths[c->n_extra_paths], MQ_CLIENT_IP_MAX, "%s", ips[i]); - c->n_extra_paths++; - accepted++; - } - /* Arm the deferral timer if the runtime base is available (after - * mq_client_new it is). Harmless if there is no pending work. */ - client_mp_timer_arm(c); - return (int)accepted; -} - -/* Enqueue a pre-auth tcp_open. Returns 0 on success, -1 if the queue is full. */ -static int -client_enqueue_open(mq_client_t *c, const uint8_t *host, size_t host_len, - mq_addr_type_t atype, uint16_t port, int local_fd, - const uint8_t *early, size_t early_len, void *user, mq_tcp_open_cb cb) -{ - if (c->queue_len >= MQ_CLIENT_QUEUE_MAX) { - return -1; - } - mq_client_open_t *q = calloc(1, sizeof(*q)); - if (!q) { - return -1; - } - if (early_len > 0 && early) { - q->early = malloc(early_len); - if (!q->early) { - free(q); - return -1; - } - memcpy(q->early, early, early_len); - q->early_len = early_len; - } - if (host_len > MQ_MAX_HOST) host_len = MQ_MAX_HOST; - memcpy(q->host, host, host_len); - q->host_len = host_len; - q->atype = atype; - q->port = port; - q->local_fd = local_fd; - q->user = user; - q->cb = cb; - q->next = NULL; - if (c->queue_tail) { - c->queue_tail->next = q; - } else { - c->queue_head = q; - } - c->queue_tail = q; - c->queue_len++; - return 0; -} - -void -mq_client_tcp_open(void *core, const uint8_t *host, size_t host_len, mq_addr_type_t atype, - uint16_t port, int local_fd, const uint8_t *prebuf, size_t prebuf_len, - void *user, mq_tcp_open_cb cb) -{ - mq_client_t *c = (mq_client_t *)core; - if (!c || !host) { - if (cb) cb(0, MQ_TCP_CONN_REFUSED, user); - if (local_fd >= 0) close(local_fd); - return; - } - /* Admission (Phase 5b reconnect-aware ordering): - * closed (terminal) → fail fast. - * not authed → enqueue (pre-auth OR reconnect window: conn - * may be NULL; client_enqueue_open does not - * dereference c->conn — it only queues). The - * queue drains on the next auth OK (carried - * across a reconnect loss). - * authed but no conn (race) → fail fast (narrow authed-mid-teardown guard; - * do not simplify away). - * authed + conn → issue immediately. */ - if (c->closed) { - if (cb) cb(0, MQ_TCP_CONN_REFUSED, user); - if (local_fd >= 0) close(local_fd); - } else if (!c->authed) { - if (client_enqueue_open(c, host, host_len, atype, port, local_fd, prebuf, - prebuf_len, user, cb) != 0) { - MQ_LOGW("mq_client: tcp_open queue full, rejecting"); - if (cb) cb(0, MQ_TCP_CONN_REFUSED, user); - if (local_fd >= 0) close(local_fd); - } - } else if (!c->conn) { - if (cb) cb(0, MQ_TCP_CONN_REFUSED, user); - if (local_fd >= 0) close(local_fd); - } else { - client_issue_open(c, host, host_len, atype, port, local_fd, prebuf, prebuf_len, - user, cb); - } -} - -mq_tcp_open_fn -mq_client_tcp_open_fn(void) -{ - return mq_client_tcp_open; -} - -void * -mq_client_tcp_open_core(mq_client_t *c) -{ - return c; -} - -/* ── UDP relay boundary getters + capability ─────────────────────────────── */ - -mq_udp_open_fn -mq_client_udp_open_fn(void) -{ - return mq_udp_cli_open; -} - -mq_udp_send_fn -mq_client_udp_send_fn(void) -{ - return mq_udp_cli_send; -} - -mq_udp_close_fn -mq_client_udp_close_fn(void) -{ - return mq_udp_cli_close; -} - -void * -mq_client_udp_open_core(mq_client_t *c) -{ - /* The mq_udp_open_fn `core` is the mq_udp_cli_t* (the concrete relay table), - * not the mq_client_t* — open/send/close operate directly on the cli role. - * Returns NULL before mq_client_start (no conn / no table yet). */ - return c ? c->udp : NULL; -} - -int -mq_client_udp_available(const mq_client_t *c) -{ - if (!c) { - return 0; - } - if (!c->authed) { - /* Pre-auth (or auth not yet settled): undetermined — admit optimistically. - * Once CLOSED without auth, authed stays 0 and we report unavailable via - * the auth-failure path; callers gate new opens on mq_client_conn() too. */ - if (c->auth_reported) { - return 0; /* auth settled as failure */ - } - return -1; - } - /* Authed: capability = server advertised MQ_FEAT_UDP_RELAY AND the conn's - * datagram channel is usable (mss > 0). */ - if (!(c->auth_features & MQ_FEAT_UDP_RELAY)) { - return 0; - } - if (!c->conn || mq_conn_datagram_mss(c->conn) == 0) { - return 0; - } - return 1; -} - -void -mq_client_free(mq_client_t *c) -{ - if (!c) { - return; - } - /* (1) Mark teardown FIRST so a late MQ_CONN_CLOSED takes the terminal path - * (no reconnect-after-free). (2) Disarm the backoff timer, then (3) free it. - * (4) Detach the conn state callback so a late CLOSED (the engine frees the - * conn after us, in some teardown orders) cannot re-enter client_on_state and - * dereference the freed c. Only then run the existing teardown. */ - c->shutting_down = 1; - client_reconnect_stop(c); - if (c->reconnect_ev) { - event_free(c->reconnect_ev); - c->reconnect_ev = NULL; - } - if (c->conn) { - mq_conn_set_on_state(c->conn, NULL, NULL); - } - /* Disarm the mp-ready deferral timer (no-op if already stopped). */ - client_mp_timer_stop(c); - /* Fail any still-queued opens (fires their cb with CONN_REFUSED). In - * practice the queue is already empty by free-time (drained/failed on the - * prior CLOSED); this is defensive. The active data nodes are reaped via - * the conn CLOSED path before the engine frees the conn; by the time - * mq_client_free runs they should be gone. Fail defensively in case free - * is called without a prior CLOSED. */ - client_fail_queue(c, MQ_TCP_CONN_REFUSED); - while (c->data_head) { - client_data_report(c->data_head, 0, MQ_TCP_CONN_REFUSED); - client_data_reap(c->data_head); - } - /* Free the UDP relay session table (reaps any remaining sessions WITHOUT - * firing on_err — the owner is tearing down, not a remote failure). */ - mq_udp_cli_free(c->udp); - c->udp = NULL; - free(c); -} diff --git a/src/proxy/mq_client.h b/src/proxy/mq_client.h deleted file mode 100644 index 6026304f..00000000 --- a/src/proxy/mq_client.h +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_client.h — proxy client: connects to an mqproxy server and performs the - * control-stream AUTH handshake. - * - * The client wraps one mq_conn to the server. On ESTABLISHED it opens the - * control stream (the first client-initiated bidi stream), sends an - * AUTH_REQUEST, and waits for the AUTH_RESPONSE on the same stream. The auth - * outcome is reported via mq_client_set_on_auth. - * - * This is the first capability of the client; Tasks 12/13 extend it with TCP - * data streams. The control stream is kept open after auth so later - * capabilities can reuse the connection. - */ -#ifndef MQ_PROXY_MQ_CLIENT_H -#define MQ_PROXY_MQ_CLIENT_H - -#include - -#include "ingress/mq_ingress.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_transport.h" -#include "wire/mq_wire.h" - -typedef struct mq_client_s mq_client_t; - -/* Auth result callback: ok!=0 iff the server accepted (status==OK); err is the - * server-reported error code (meaningful when ok==0). Fires exactly once per - * connection. */ -typedef void (*mq_client_on_auth_fn)(int ok, mq_auth_err_t err, void *user); - -/* Create a client targeting server_ip:server_port, authenticating as client_id - * with auth_token. `t` is the sans-io transport (xquic/streams/conn); `rt` is - * the runtime that owns the libevent base used for flow relays + the mp-ready - * poll timer. The strings are copied. Returns NULL on bad args / OOM. */ -mq_client_t *mq_client_new(mq_transport_t *t, mq_runtime_t *rt, const char *server_ip, - uint16_t server_port, const char *client_id, - const char *auth_token, mq_cc_t cc); - -/* Configure QUIC idle timeout for keepalive: idle_ms sets the connection - * idle timeout (0 = disable keepalive / use xquic default). When enabled, - * xquic sends keepalive PINGs at a fixed ~15s cadence to hold the connection - * open. Values below ~15000 ms are counterproductive (the PING cannot refresh - * a shorter idle timeout). Default is 30000 (30 s). Must be called before - * mq_client_start. NULL-safe. */ -void mq_client_set_keepalive(mq_client_t *c, uint64_t idle_ms); - -/* Configure in-process reconnect (Phase 5b): when enabled (default), a tunnel - * loss arms a jittered exponential-backoff timer that re-establishes the conn - * and re-auths, returning the client to SERVING without dropping its listeners. - * max_backoff_ms caps the backoff (default 30000); it is floored to 1000ms to - * prevent a zero-backoff busy-spin. When disabled, MQ_CONN_CLOSED is terminal - * (today's behavior; for tests / supervised-restart deployments). - * Must be called before mq_client_start. NULL-safe. */ -void mq_client_set_reconnect(mq_client_t *c, int enabled, uint64_t max_backoff_ms); - -/* Register the auth-result callback (call before mq_client_start). */ -void mq_client_set_on_auth(mq_client_t *c, mq_client_on_auth_fn fn, void *user); - -/* Register a connection-state callback (established/closed) forwarded from the - * underlying mq_conn. Optional; useful to observe peer-initiated close. */ -void mq_client_set_on_state(mq_client_t *c, mq_conn_on_state_fn fn, void *user); - -/* Initiate the connection to the server. Returns 0 on success, -1 on failure. */ -int mq_client_start(mq_client_t *c); - -/* ── Multipath: request extra paths (Task 19) ──────────────────────────────── - * - * Register up to `n` extra local bind IPs to bring up as additional MPQUIC - * paths once the underlying connection becomes multipath-ready (xquic has fired - * ready_to_create_path_notify, i.e. cids are exchanged). The IP strings are - * copied. Because path creation can only happen after mp-ready, this DEFERS: - * after mq_client_start the client arms a short recurring libevent timer that - * polls mq_conn_mp_ready and calls mq_conn_add_path for each pending IP once - * ready (each on an ephemeral local UDP port), then disarms. - * - * Call after mq_client_new and before/after mq_client_start (the timer is armed - * on the runtime's base, which must exist). Returns the number of IPs accepted - * (clamped to the internal max), or -1 on bad args. */ -int mq_client_add_paths(mq_client_t *c, const char *const *ips, size_t n); - -/* 1 once the server accepted auth, else 0. */ -int mq_client_is_authed(const mq_client_t *c); - -/* The underlying mq_conn (valid after mq_client_start, until close), or NULL. */ -mq_conn_t *mq_client_conn(const mq_client_t *c); - -/* ── TCP open (the ingress→core boundary, mq_ingress.h) ────────────────────── - * - * Open a proxied TCP connection to host:port on behalf of a local app socket - * (local_fd). Implements mq_tcp_open_fn: `core` is the mq_client_t* (see - * mq_client_tcp_open_core / mq_client_tcp_open_fn). Behaviour: - * - * - If not yet authed, the request is QUEUED (bounded) and drained when auth - * succeeds. - * - When authed, a new bidi stream is opened carrying CONNECT_TCP_REQUEST; - * the CONNECT_TCP_RESPONSE is buffered (bounded) and decoded. - * · status OK → cb(1, MQ_TCP_OK, user), then a relay binds the stream - * ⇄ local_fd (the client owns local_fd from here, closing - * it on relay completion / teardown). - * · status ERROR → cb(0, error_code, user); the client closes the stream - * and CLOSES local_fd itself (ownership note below). - * - * local_fd ownership: the client takes ownership of local_fd the moment - * mq_client_tcp_open is invoked. On EVERY terminal outcome (success-then-relay - * completion, error response, malformed response, queue overflow, connection - * teardown) the client closes local_fd exactly once. The ingress must NOT close - * local_fd after calling tcp_open; it only writes its app-side reply (driven by - * the cb's ok flag) — on ok=0 the ingress sends its error reply on its OWN app - * socket, which is distinct from local_fd here in Phase 1 the ingress hands the - * very socket it will relay, so for ok=0 the client closing local_fd also - * terminates the app connection, which is the desired SOCKS/HTTP error - * behaviour. - * - * prebuf/prebuf_len carry app bytes the ingress already read off local_fd - * coalesced with the request head (see mq_tcp_open_fn). They are copied and - * relayed to the origin ahead of any fresh local_fd reads; prebuf is borrowed - * for the call only. prebuf_len may be 0. */ -void mq_client_tcp_open(void *core, const uint8_t *host, size_t host_len, - mq_addr_type_t atype, uint16_t port, int local_fd, - const uint8_t *prebuf, size_t prebuf_len, void *user, - mq_tcp_open_cb cb); - -/* The function pointer + core pointer for the ingress (Task 14) to call without - * knowing the concrete client type. core is the mq_client_t*. */ -mq_tcp_open_fn mq_client_tcp_open_fn(void); -void *mq_client_tcp_open_core(mq_client_t *c); - -/* ── UDP relay (the ingress→core boundary, mq_ingress.h) ───────────────────── - * - * The client owns a per-connection UDP relay session table (mq_udp_cli_t). The - * function pointers + core pointer below let the UDP ingress (mq_udp_assoc, - * Task 6.3) open/send/close UDP relay sessions without knowing the concrete - * client type — mirroring the TCP getter style above. core is the mq_client_t*. - */ -mq_udp_open_fn mq_client_udp_open_fn(void); -mq_udp_send_fn mq_client_udp_send_fn(void); -mq_udp_close_fn mq_client_udp_close_fn(void); -void *mq_client_udp_open_core(mq_client_t *c); - -/* UDP relay capability tri-state (the CLI's on_auth glue consults this — Task - * 6.3/6.4 — to decide whether to admit ASSOCIATE; the ingress never calls the - * core directly): - * -1 = undetermined (pre-auth — optimistically admit) - * 0 = unavailable (authed but no MQ_FEAT_UDP_RELAY, or datagram mss == 0) - * 1 = available (authed + feature bit + mss > 0) - * The server advertises MQ_FEAT_UDP_RELAY in AUTH_RESPONSE.features; the client - * saves it at auth time and combines it with mq_conn_datagram_mss() > 0. */ -int mq_client_udp_available(const mq_client_t *c); - -/* Free the client. Does not close the connection on its own; callers tear down - * the engine/paths which closes outstanding conns. Safe on NULL. */ -void mq_client_free(mq_client_t *c); - -#endif /* MQ_PROXY_MQ_CLIENT_H */ diff --git a/src/proxy/mq_defrag.c b/src/proxy/mq_defrag.c deleted file mode 100644 index e005f3fe..00000000 --- a/src/proxy/mq_defrag.c +++ /dev/null @@ -1,285 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "proxy/mq_defrag.h" - -#include -#include -#include -#include - -/* ---- constants ---------------------------------------------------------- */ - -#define DEFRAG_SLOTS 4 /* number of concurrent reassembly slots */ -#define DEFRAG_MAX_FRAGS 255 /* frag_count is u8, max value 255 */ -#define DEFRAG_MAX_TOTAL 65535U /* max reassembled packet size (bytes) */ - -/* Bitmap storage: 256 bits = 32 bytes covers all possible frag_id values - * (0..254 for frag_count up to 255). */ -#define DEFRAG_BITMAP_BYTES 32 -_Static_assert(DEFRAG_BITMAP_BYTES * 8 >= DEFRAG_MAX_FRAGS, - "bitmap must cover all frag ids"); - -/* ---- per-fragment storage ----------------------------------------------- */ - -typedef struct { - uint8_t *data; /* malloc'd copy of the fragment payload */ - size_t len; -} defrag_frag_t; - -/* ---- reassembly slot ---------------------------------------------------- */ - -typedef struct { - int active; /* 1 if this slot is in use */ - uint16_t packet_id; - uint8_t frag_count; /* expected number of fragments */ - uint8_t frags_received; /* count of distinct frags seen so far */ - size_t total_len; /* accumulated byte count (incremental 65535 check) */ - uint64_t use_seq; /* LRU: updated on every feed touching this slot */ - - /* Per-frag storage: lazily allocated array of frag_count pointers. */ - defrag_frag_t *frags; /* malloc'd[frag_count] or NULL before first frag */ - - /* Bitmap: bit i is set when frag_id==i has been received. */ - uint8_t bitmap[DEFRAG_BITMAP_BYTES]; -} defrag_slot_t; - -/* ---- top-level defragmenter -------------------------------------------- */ - -struct mq_defrag { - defrag_slot_t slots[DEFRAG_SLOTS]; - uint64_t seq; /* monotonically increasing use counter */ -}; - -/* ---- helpers ------------------------------------------------------------ */ - -static int -bitmap_test(const uint8_t bm[DEFRAG_BITMAP_BYTES], uint8_t bit) -{ - return (bm[bit >> 3] >> (bit & 7)) & 1; -} - -static void -bitmap_set(uint8_t bm[DEFRAG_BITMAP_BYTES], uint8_t bit) -{ - bm[bit >> 3] |= (uint8_t)(1u << (bit & 7)); -} - -/* Free all frag copies stored in slot and reset it to inactive. */ -static void -slot_reset(defrag_slot_t *s) -{ - if (s->frags) { - for (int i = 0; i < (int)s->frag_count; i++) { - free(s->frags[i].data); - s->frags[i].data = NULL; - s->frags[i].len = 0; - } - free(s->frags); - s->frags = NULL; - } - memset(s->bitmap, 0, DEFRAG_BITMAP_BYTES); - s->active = 0; - s->packet_id = 0; - s->frag_count = 0; - s->frags_received = 0; - s->total_len = 0; - s->use_seq = 0; -} - -/* Find an active slot with the given packet_id. Returns pointer or NULL. */ -static defrag_slot_t * -slot_find(mq_defrag_t *d, uint16_t packet_id) -{ - for (int i = 0; i < DEFRAG_SLOTS; i++) { - if (d->slots[i].active && d->slots[i].packet_id == packet_id) return &d->slots[i]; - } - return NULL; -} - -/* Find a free slot. Returns pointer or NULL if all slots are active. */ -static defrag_slot_t * -slot_find_free(mq_defrag_t *d) -{ - for (int i = 0; i < DEFRAG_SLOTS; i++) { - if (!d->slots[i].active) return &d->slots[i]; - } - return NULL; -} - -/* Evict the LRU slot (lowest use_seq among active slots), reset it, and - * return a pointer to it (now free). Must only be called when all slots are - * active. */ -static defrag_slot_t * -slot_evict_lru(mq_defrag_t *d) -{ - defrag_slot_t *lru = &d->slots[0]; - for (int i = 1; i < DEFRAG_SLOTS; i++) { - if (d->slots[i].use_seq < lru->use_seq) lru = &d->slots[i]; - } - slot_reset(lru); - return lru; -} - -/* Allocate and initialise a new slot for the given packet_id / frag_count. - * Returns the slot on success, NULL on OOM. */ -static defrag_slot_t * -slot_open(mq_defrag_t *d, uint16_t packet_id, uint8_t frag_count, uint64_t seq) -{ - defrag_slot_t *s = slot_find_free(d); - if (!s) s = slot_evict_lru(d); - - /* Allocate per-frag storage array (frag_count entries). */ - s->frags = calloc((size_t)frag_count, sizeof(defrag_frag_t)); - if (!s->frags) return NULL; - - s->active = 1; - s->packet_id = packet_id; - s->frag_count = frag_count; - s->frags_received = 0; - s->total_len = 0; - s->use_seq = seq; - memset(s->bitmap, 0, DEFRAG_BITMAP_BYTES); - - return s; -} - -/* Concatenate all stored fragments (in frag_id order) into a single malloc'd - * buffer. Returns the buffer via out/out_len, then resets the slot. */ -static int -slot_assemble(defrag_slot_t *s, uint8_t **out, size_t *out_len) -{ - size_t total = s->total_len; - /* Allocate at least 1 byte so the pointer is always non-NULL and freeable - * even when total == 0 (shouldn't happen for multi-frag, but be safe). */ - uint8_t *buf = malloc(total > 0 ? total : 1); - if (!buf) return -1; - - size_t off = 0; - for (int i = 0; i < (int)s->frag_count; i++) { - if (s->frags[i].len > 0) { - memcpy(buf + off, s->frags[i].data, s->frags[i].len); - off += s->frags[i].len; - } - } - - *out = buf; - *out_len = total; - slot_reset(s); - return 0; -} - -/* ---- public API --------------------------------------------------------- */ - -mq_defrag_t * -mq_defrag_new(void) -{ - mq_defrag_t *d = calloc(1, sizeof *d); - return d; -} - -void -mq_defrag_free(mq_defrag_t *d) -{ - if (!d) return; - for (int i = 0; i < DEFRAG_SLOTS; i++) - slot_reset(&d->slots[i]); - free(d); -} - -int -mq_defrag_feed(mq_defrag_t *d, const mq_udp_msg_hdr_t *h, const uint8_t *p, size_t len, - uint8_t **out, size_t *out_len) -{ - /* --- input validation --- */ - if (h->frag_count == 0) return -1; - if (h->frag_id >= h->frag_count) return -1; - - /* --- fast path: single-frag packet --- */ - if (h->frag_count == 1) { - /* malloc(len+1): the +1 guarantees a non-NULL, freeable pointer even - * when len==0. Only len bytes are exposed to the caller (out_len=len). */ - uint8_t *buf = malloc(len + 1); - if (!buf) return -1; - if (len > 0) memcpy(buf, p, len); - *out = buf; - *out_len = len; - return 1; - } - - /* --- multi-frag path --- */ - uint64_t seq = ++d->seq; - - defrag_slot_t *s = slot_find(d, h->packet_id); - - if (s) { - /* Existing slot: check frag_count consistency. */ - if (s->frag_count != h->frag_count) { - /* Mismatch: drop the entire slot. */ - slot_reset(s); - return -1; - } - /* Update LRU timestamp. */ - s->use_seq = seq; - } else { - /* New packet_id: open a slot (evicts LRU if all full). */ - s = slot_open(d, h->packet_id, h->frag_count, seq); - if (!s) return -1; /* OOM */ - } - - /* Duplicate check: if the bit is already set, ignore silently. - * - * KNOWN LIMITATION (packet_id wrap): identity is the 16-bit packet_id only. - * If a sender leaves a fragment incomplete in a slot, that slot survives - * until LRU-evicted by 4 other *multi-frag* packet_ids (single-frag packets - * are passthrough and never touch slots). If the sender's packet_id counter - * wraps (65536 packets) before that happens AND a new fragmented packet - * reuses the stale slot's packet_id with the same frag_count, a fresh frag - * could be mistaken for a duplicate, mixing stale + fresh data on assembly. - * A length compare here would NOT help (frag 0 of any multi-frag packet is - * exactly mss_payload bytes, so lengths match). A robust fix needs a wire - * generation/nonce (the reserved `flags` byte) — deferred. Impact is bounded: - * client->server it is self-inflicted (a hostile client can only garble its - * own UDP, gaining nothing over just sending different bytes); server->client - * packet_id is mqproxy-assigned and not peer-controlled; and for honest - * traffic the LRU + the joint precondition (near-zero intervening - * fragmentation across a full 65536-packet wrap + exact id+frag_count match) - * makes it astronomically rare on an already-unreliable UDP transport. - * See docs/impl-notes/memos/2026-06-07-defrag-packet-id-wrap.md. */ - if (bitmap_test(s->bitmap, h->frag_id)) return 0; - - /* Incremental 65535 check: would adding this fragment overflow? */ - if (s->total_len + len > DEFRAG_MAX_TOTAL) { - slot_reset(s); - return -1; - } - - /* Copy the fragment payload (caller's buffer is transient). */ - uint8_t *frag_copy = NULL; - if (len > 0) { - frag_copy = malloc(len); - if (!frag_copy) { - slot_reset(s); - return -1; /* OOM */ - } - memcpy(frag_copy, p, len); - } - - s->frags[h->frag_id].data = frag_copy; - s->frags[h->frag_id].len = len; - bitmap_set(s->bitmap, h->frag_id); - s->frags_received++; /* counts DISTINCT frag_ids only: duplicate check above prevents - increment */ - s->total_len += len; - - /* Check if all fragments have arrived. */ - if (s->frags_received == s->frag_count) { - if (slot_assemble(s, out, out_len) != 0) { - slot_reset(s); /* OOM: free all stored frags so slot doesn't get stuck */ - return -1; - } - return 1; - } - - return 0; -} diff --git a/src/proxy/mq_defrag.h b/src/proxy/mq_defrag.h deleted file mode 100644 index df05fb28..00000000 --- a/src/proxy/mq_defrag.h +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_DEFRAG_H -#define MQ_DEFRAG_H -#include -#include - -#include "wire/mq_udp_msg.h" - -/* Datagram defragmenter — per-session, pure logic (no I/O, no clocks). - * - * Internally keeps 4 reassembly slots keyed by packet_id, evicting the - * least-recently-used slot when all 4 are occupied and a new packet_id arrives. - * - * Caller is responsible for demultiplexing by session_id before calling - * mq_defrag_feed(); session_id in the header is ignored by this module. */ -typedef struct mq_defrag mq_defrag_t; - -/* Allocate a new defragmenter. Returns NULL on OOM. */ -mq_defrag_t *mq_defrag_new(void); - -/* Free all resources owned by d. Safe to call with NULL. */ -void mq_defrag_free(mq_defrag_t *d); - -/* Feed one fragment into the defragmenter. - * - * h — decoded header for this fragment (session_id ignored) - * p — payload bytes for this fragment (caller's buffer, may be transient) - * len — length of p - * out — set to newly malloc'd reassembled packet on return 1; caller frees - * out_len — set to reassembled length on return 1 - * - * Returns: - * 1 Packet complete: *out (malloc'd, caller must free) and *out_len are set. - * For frag_count==1 the assembly loop is skipped; *out is malloc(len+1) - * (minimum 1 byte to guarantee a non-NULL freeable pointer, extra byte - * never exposed) and *out_len == len. - * 0 Fragment accepted; packet not yet complete (or duplicate fragment ignored). - * -1 Fragment rejected; slot dropped if applicable. Counted by caller. - * - * Rejection conditions (return -1): - * frag_count == 0 - * frag_id >= frag_count - * frag_count mismatch vs the slot's recorded frag_count (whole slot dropped) - * reassembled total would exceed 65535 bytes (whole slot dropped) - * - * Duplicate fragment (bitmap already set for frag_id): silently ignored, - * returns 0 (not -1). */ -int mq_defrag_feed(mq_defrag_t *d, const mq_udp_msg_hdr_t *h, const uint8_t *p, - size_t len, uint8_t **out, size_t *out_len); - -#endif /* MQ_DEFRAG_H */ diff --git a/src/proxy/mq_flow.c b/src/proxy/mq_flow.c deleted file mode 100644 index d3a4ff38..00000000 --- a/src/proxy/mq_flow.c +++ /dev/null @@ -1,530 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_flow.c — shared QUIC-stream ⇄ TCP-fd relay glue. See mq_flow.h. */ -#include "proxy/mq_flow.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "proxy/mq_relay.h" -#include "util/mq_log.h" - -/* Per-flow node. Lifecycle phases: - * 1. Pre-relay: stream attached, fd being connected by the owner (relay==NULL). - * 2. Relaying: relay live; persistent EV_READ + on-demand EV_WRITE on the fd - * and the stream read/write callbacks drive the relay edges. - * 3. Reaped: stream closed, fd closed, events freed, relay freed, unlinked. - */ -struct mq_flow_s { - mq_flow_t **list_head; /* owner's intrusive list head */ - struct event_base *base; - - mq_stream_t *stream; /* A side; NULL'd by the stream on_close callback */ - int fd; /* B side TCP socket, -1 once closed */ - struct event *rd_ev; /* persistent EV_READ on fd (relay phase) */ - struct event *wr_ev; /* on-demand EV_WRITE on fd */ - mq_relay_t *relay; - - int reaped; - int graceful; /* stream finished/owes a FIN: do NOT RESET on reap */ - int fin_sent; /* a FIN was already sent on the QUIC stream (idempotency) */ - int pending_reap; /* a direction hit EOF (simple-close): reap after this edge */ - int a_fin_armed; /* B (fd) source EOF'd: coalesce FIN onto the final A write */ - - /* Bytes pulled off the stream by the owner BEFORE the relay started (e.g. the - * download payload that trailed the CONNECT_TCP_RESPONSE in the same read). - * flow_a_read drains these FIRST so they reach B (the fd) and are not lost. */ - unsigned char *pre; - size_t pre_len; - size_t pre_off; - - /* Symmetric B-side prebuffer: bytes pulled off the fd by the owner BEFORE the - * relay started (e.g. app bytes the ingress read coalesced with the - * SOCKS5/HTTP CONNECT request). flow_b_read drains these FIRST so they reach - * A (the stream) and are not lost. */ - unsigned char *pre_b; - size_t pre_b_len; - size_t pre_b_off; - - mq_flow_on_reap_fn on_reap; - void *user; - - mq_flow_t *next; -}; - -/* ── list ─────────────────────────────────────────────────────────────────── */ -static void -flow_unlink(mq_flow_t *f) -{ - mq_flow_t **pp = f->list_head; - while (*pp) { - if (*pp == f) { - *pp = f->next; - return; - } - pp = &(*pp)->next; - } -} - -/* ── reap ─────────────────────────────────────────────────────────────────── */ -void -mq_flow_reap(mq_flow_t *f) -{ - if (!f || f->reaped) { - return; - } - f->reaped = 1; - - if (f->rd_ev) { - event_free(f->rd_ev); - f->rd_ev = NULL; - } - if (f->wr_ev) { - event_free(f->wr_ev); - f->wr_ev = NULL; - } - if (f->fd >= 0) { - close(f->fd); - f->fd = -1; - } - if (f->stream) { - /* Detach our callbacks so the imminent stream close_notify does not call - * back into a freed node. If graceful, a FIN was already sent (or owed) - * and we must NOT RESET (RESET would discard un-acked stream data); - * otherwise abort with RESET_STREAM. The mq_stream is freed by the - * transport's stream_close_notify. */ - mq_stream_set_cbs(f->stream, NULL, NULL, NULL, NULL); - if (!f->graceful) { - mq_stream_close(f->stream); - } - f->stream = NULL; - } - if (f->relay) { - mq_relay_free(f->relay); - f->relay = NULL; - } - if (f->pre) { - free(f->pre); - f->pre = NULL; - f->pre_len = f->pre_off = 0; - } - if (f->pre_b) { - free(f->pre_b); - f->pre_b = NULL; - f->pre_b_len = f->pre_b_off = 0; - } - - /* Unlink from the owner's list BEFORE on_reap: on_reap may free the owner - * object that holds *list_head (e.g. a protocol node whose `flow` field is - * our list root), so we must not dereference list_head afterward. */ - flow_unlink(f); - if (f->on_reap) { - f->on_reap(f, f->user); - } - free(f); -} - -/* If a direction EOF requested a simple-close reap during the edge that just - * ran, perform it now (after the relay/libevent callback has fully unwound, so - * reaping is free of use-after-free). */ -static void -flow_drain_pending(mq_flow_t *f) -{ - if (f->pending_reap && !f->reaped) { - mq_flow_reap(f); - } -} - -/* Stop watching the B-side fd for read/write edges. Called when the origin - * direction finishes (B EOF) so the level-triggered EV_READ on a half-closed fd - * cannot re-fire forever (the busy-spin), and on any reap. */ -static void -flow_stop_fd_events(mq_flow_t *f) -{ - if (f->rd_ev) { - event_del(f->rd_ev); - } - if (f->wr_ev) { - event_del(f->wr_ev); - } -} - -/* Send a clean FIN on the QUIC stream (idempotent). The relay guarantees the - * B->A buffer is drained when this fires, so a zero-length FIN just closes the - * send direction; the peer then sees stream EOF (never a RESET). Marks the node - * graceful so reap will NOT RESET. */ -static void -flow_send_fin(mq_flow_t *f) -{ - if (f->fin_sent) { - return; - } - f->fin_sent = 1; - f->graceful = 1; - if (f->stream) { - long n = mq_stream_send(f->stream, NULL, 0, /*fin=*/1); - if (n < 0) { - MQ_LOGW("mq_flow: stream FIN send failed"); - } - } -} - -/* ── relay callbacks ──────────────────────────────────────────────────────── */ -/* Per-direction EOF: propagate a clean shutdown for the finished direction (FIN - * to A on B EOF; SHUT_WR to B on A FIN), stop watching the dead fd so it cannot - * busy-spin, and request a reap (simple-close: either direction EOF closes - * both). MUST NOT free here — the relay/libevent callback that triggered this - * may still touch the node. The reap runs from flow_drain_pending. */ -static void -flow_relay_dir_eof(mq_relay_t *r, mq_relay_dir_t dir, void *user) -{ - (void)r; - mq_flow_t *f = (mq_flow_t *)user; - if (dir == MQ_RELAY_DIR_BA) { - /* B (fd) -> A (stream) finished: fd recv hit EOF and B->A is flushed. - * Stop the now level-triggered fd read event to kill the busy-spin, then - * FIN the QUIC stream so A observes a clean EOF with all bytes. */ - flow_stop_fd_events(f); - flow_send_fin(f); - } else { - /* A (stream) -> B (fd) finished: the stream's read side hit FIN and A->B - * is flushed. Half-close the fd's write side so the app sees EOF, stop - * the fd read events (closing both under simple-close), and FIN the - * stream's WRITE side so the bidi stream closes cleanly from this end - * (e.g. the client end of a download, which never sent data itself). - * flow_send_fin marks the node graceful so reap will not RESET. */ - if (f->fd >= 0) { - shutdown(f->fd, SHUT_WR); - } - flow_stop_fd_events(f); - f->graceful = 1; - } - f->pending_reap = 1; -} - -/* on_done: BOTH directions finished or a hard error. We do NOT free here (the - * relay touches *r after on_done returns); request a deferred reap. On the clean - * both-EOF path flow_relay_dir_eof already FIN'd and marked graceful, so reap - * will not RESET; on a hard error graceful stays 0 → RESET. */ -static void -flow_relay_done(mq_relay_t *r, void *user) -{ - (void)r; - mq_flow_t *f = (mq_flow_t *)user; - f->pending_reap = 1; -} - -/* ── relay I/O adapters ───────────────────────────────────────────────────── */ -/* A side = QUIC stream. */ -static long -flow_a_read(void *io, unsigned char *buf, size_t cap, int *eof, int *wb) -{ - mq_flow_t *f = (mq_flow_t *)io; - if (!f->stream) { - *eof = 1; - return 0; - } - /* Drain owner-prebuffered stream bytes first (see mq_flow_t::pre). */ - if (f->pre && f->pre_off < f->pre_len) { - size_t avail = f->pre_len - f->pre_off; - size_t take = avail < cap ? avail : cap; - memcpy(buf, f->pre + f->pre_off, take); - f->pre_off += take; - if (f->pre_off >= f->pre_len) { - free(f->pre); - f->pre = NULL; - f->pre_len = f->pre_off = 0; - } - return (long)take; - } - - int fin = 0; - long n = mq_stream_recv(f->stream, buf, cap, &fin); - if (n < 0) { - return -1; - } - if (fin) { - *eof = 1; - } - if (n == 0 && !fin) { - *wb = 1; - } - return n; -} - -static long -flow_a_write(void *io, const unsigned char *buf, size_t len, int *wb) -{ - mq_flow_t *f = (mq_flow_t *)io; - if (!f->stream) { - return -1; - } - /* If B's source has EOF'd, this B->A flush carries the stream's final bytes. - * Coalesce the FIN onto a write that xquic accepts IN FULL (n == len): xquic - * only commits the FIN with the data when the whole buffer is taken, so on a - * partial/blocked accept we send fin=0 and retry (the relay re-calls us). */ - int want_fin = (f->a_fin_armed && len > 0) ? 1 : 0; - long n = mq_stream_send(f->stream, buf, len, want_fin); - if (n < 0) { - return -1; - } - if (want_fin && n == (long)len) { - f->fin_sent = 1; /* the FIN went out coalesced with the last data */ - f->graceful = 1; - } - if (n == 0 && len > 0) { - *wb = 1; /* flow-control blocked; resumes on stream on_writable */ - } - return n; -} - -/* B side = TCP fd. */ -static long -flow_b_read(void *io, unsigned char *buf, size_t cap, int *eof, int *wb) -{ - mq_flow_t *f = (mq_flow_t *)io; - if (f->fd < 0) { - *eof = 1; - return 0; - } - /* Drain owner-prebuffered fd bytes first (see mq_flow_t::pre_b). */ - if (f->pre_b && f->pre_b_off < f->pre_b_len) { - size_t avail = f->pre_b_len - f->pre_b_off; - size_t take = avail < cap ? avail : cap; - memcpy(buf, f->pre_b + f->pre_b_off, take); - f->pre_b_off += take; - if (f->pre_b_off >= f->pre_b_len) { - free(f->pre_b); - f->pre_b = NULL; - f->pre_b_len = f->pre_b_off = 0; - } - return (long)take; - } - ssize_t n = recv(f->fd, buf, cap, 0); - if (n == 0) { - *eof = 1; - /* B (fd) source is done: the remaining B->A bytes are the last data the - * stream will carry. Arm FIN coalescing so the final flow_a_write that - * drains them carries fin=1 (avoids a standalone zero-length FIN being - * attached at a flow-control-blocked offset, which truncates data still - * buffered in xquic — the demo always sends fin WITH the last bytes). */ - f->a_fin_armed = 1; - return 0; - } - if (n < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - *wb = 1; - return 0; - } - return -1; - } - return (long)n; -} - -static long -flow_b_write(void *io, const unsigned char *buf, size_t len, int *wb) -{ - mq_flow_t *f = (mq_flow_t *)io; - if (f->fd < 0) { - return -1; - } - ssize_t n = send(f->fd, buf, len, MSG_NOSIGNAL); - if (n < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - *wb = 1; - if (f->wr_ev) { - event_add(f->wr_ev, NULL); /* get a writable edge to resume */ - } - return 0; - } - return -1; - } - return (long)n; -} - -/* ── edge wiring ──────────────────────────────────────────────────────────── */ -static void -flow_stream_readable(mq_stream_t *s, void *user) -{ - (void)s; - mq_flow_t *f = (mq_flow_t *)user; - if (f->relay) { - mq_relay_on_a_readable(f->relay); - flow_drain_pending(f); - } -} - -static void -flow_stream_writable(mq_stream_t *s, void *user) -{ - (void)s; - mq_flow_t *f = (mq_flow_t *)user; - if (f->relay) { - mq_relay_on_a_writable(f->relay); - flow_drain_pending(f); - } -} - -static void -flow_stream_closed(mq_stream_t *s, void *user) -{ - (void)s; - mq_flow_t *f = (mq_flow_t *)user; - f->stream = NULL; /* the transport frees it after this returns */ - mq_flow_reap(f); -} - -static void -flow_fd_readable_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - mq_flow_t *f = (mq_flow_t *)arg; - if (f->relay) { - mq_relay_on_b_readable(f->relay); - flow_drain_pending(f); - } -} - -static void -flow_fd_writable_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - mq_flow_t *f = (mq_flow_t *)arg; - if (f->relay) { - mq_relay_on_b_writable(f->relay); - flow_drain_pending(f); - } -} - -/* ── public ───────────────────────────────────────────────────────────────── */ -mq_flow_t * -mq_flow_new(mq_flow_t **list_head, struct event_base *base, mq_stream_t *stream, int fd, - mq_flow_on_reap_fn on_reap, void *user) -{ - if (!list_head || !base) { - return NULL; - } - mq_flow_t *f = calloc(1, sizeof(*f)); - if (!f) { - return NULL; - } - f->list_head = list_head; - f->base = base; - f->stream = stream; - f->fd = fd; - f->on_reap = on_reap; - f->user = user; - f->next = *list_head; - *list_head = f; - return f; -} - -int -mq_flow_prebuffer(mq_flow_t *f, const void *data, size_t len) -{ - if (!f || f->relay) { - return -1; /* must be set before mq_flow_begin_relay */ - } - if (len == 0) { - return 0; - } - unsigned char *p = malloc(len); - if (!p) { - return -1; - } - memcpy(p, data, len); - /* Replace any prior prebuffer (single-use at relay start). */ - free(f->pre); - f->pre = p; - f->pre_len = len; - f->pre_off = 0; - return 0; -} - -int -mq_flow_prebuffer_b(mq_flow_t *f, const void *data, size_t len) -{ - if (!f || f->relay) { - return -1; /* must be set before mq_flow_begin_relay */ - } - if (len == 0) { - return 0; - } - unsigned char *p = malloc(len); - if (!p) { - return -1; - } - memcpy(p, data, len); - /* Replace any prior prebuffer (single-use at relay start). */ - free(f->pre_b); - f->pre_b = p; - f->pre_b_len = len; - f->pre_b_off = 0; - return 0; -} - -int -mq_flow_begin_relay(mq_flow_t *f) -{ - if (!f || !f->stream || f->fd < 0) { - return -1; - } - - mq_relay_cfg_t cfg; - memset(&cfg, 0, sizeof(cfg)); - cfg.a_io = f; - cfg.b_io = f; - cfg.a_read = flow_a_read; - cfg.a_write = flow_a_write; - cfg.b_read = flow_b_read; - cfg.b_write = flow_b_write; - cfg.on_dir_eof = flow_relay_dir_eof; - cfg.on_done = flow_relay_done; - cfg.user = f; - - f->relay = mq_relay_new(&cfg); - if (!f->relay) { - MQ_LOGE("mq_flow: relay alloc failed"); - return -1; - } - - f->rd_ev = event_new(f->base, f->fd, EV_READ | EV_PERSIST, flow_fd_readable_cb, f); - f->wr_ev = event_new(f->base, f->fd, EV_WRITE, flow_fd_writable_cb, f); - if (!f->rd_ev || !f->wr_ev) { - MQ_LOGE("mq_flow: fd event alloc failed"); - mq_relay_free(f->relay); - f->relay = NULL; - if (f->rd_ev) { - event_free(f->rd_ev); - f->rd_ev = NULL; - } - if (f->wr_ev) { - event_free(f->wr_ev); - f->wr_ev = NULL; - } - return -1; - } - event_add(f->rd_ev, NULL); - - /* Re-wire the stream callbacks for the relay phase. */ - mq_stream_set_cbs(f->stream, flow_stream_readable, flow_stream_writable, - flow_stream_closed, f); - - /* Pump both directions once: any bytes already buffered on the stream/fd, - * plus to register interest. A direction may already be at EOF here, so - * honor a pending simple-close reap afterward. */ - mq_relay_on_a_readable(f->relay); - mq_relay_on_b_readable(f->relay); - flow_drain_pending(f); - return 0; -} diff --git a/src/proxy/mq_flow.h b/src/proxy/mq_flow.h deleted file mode 100644 index b07703f4..00000000 --- a/src/proxy/mq_flow.h +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_flow.h — shared QUIC-stream ⇄ TCP-fd relay glue. - * - * A "flow" binds one bidirectional QUIC stream (the A side, driven via - * mq_stream_recv/_send) to one TCP socket fd (the B side, driven via - * recv()/send(MSG_NOSIGNAL)) through an mq_relay. It encapsulates the - * hard-won completion / half-close / teardown logic that both the proxy - * server (origin fd) and the proxy client (local app fd) need identically: - * - * - graceful zero-length FIN on the QUIC stream when the fd hits EOF (so the - * peer observes a clean stream EOF with ALL bytes delivered — never a - * RESET that would truncate un-acked STREAM data); - * - event_del of the dead fd's read/write events on that direction's EOF, to - * kill the level-triggered busy-spin on a half-closed fd; - * - deferred reap (pending_reap + drain-after-edge) so the node is never - * freed underneath a relay/libevent callback still on the stack (no UAF); - * - per-flow teardown that closes stream + fd exactly once, frees events and - * the relay, and unlinks the node from its owner's active-flow list. - * - * The owner (server or client) handles the protocol-specific phases (decode the - * CONNECT_TCP request/response, dial / open the fd) and then hands the flow a - * connected fd plus the attached stream and calls mq_flow_begin_relay() to - * enter the relaying phase. - * - * Ownership of an mq_flow node: the owner keeps an intrusive list head - * (mq_flow_t*) and links/unlinks nodes via this module. On connection teardown - * the owner reaps every node in its list (mq_flow_reap, idempotent). - */ -#ifndef MQ_PROXY_MQ_FLOW_H -#define MQ_PROXY_MQ_FLOW_H - -#include - -#include "transport/mq_stream.h" - -struct event_base; - -typedef struct mq_flow_s mq_flow_t; - -/* Allocate a flow node and link it at the head of *list_head. The stream is the - * A side (already attached to the owning conn); fd is the B side TCP socket - * (must be non-blocking) or -1 if not yet connected. base is the libevent base - * used to register the fd read/write events when the relay starts. on_reap (may - * be NULL) is invoked from mq_flow_reap AFTER the node has closed its stream/fd - * and unlinked itself but BEFORE it is freed, so the owner can drop any back - * pointer; user is passed through to on_reap. - * - * Returns NULL on OOM (caller still owns the stream/fd in that case). */ -typedef void (*mq_flow_on_reap_fn)(mq_flow_t *flow, void *user); - -mq_flow_t *mq_flow_new(mq_flow_t **list_head, struct event_base *base, - mq_stream_t *stream, int fd, mq_flow_on_reap_fn on_reap, - void *user); - -/* Inject `len` bytes the owner already pulled off the A-side stream BEFORE the - * relay started (e.g. payload that trailed a decoded response frame in the same - * read). The relay drains these toward B (the fd) ahead of any fresh stream - * reads, so they are not lost. Copies the data. Must be called before - * mq_flow_begin_relay. Returns 0 on success, -1 on bad state / OOM. */ -int mq_flow_prebuffer(mq_flow_t *flow, const void *data, size_t len); - -/* Symmetric to mq_flow_prebuffer but for the B side: inject `len` bytes the - * owner already pulled off the B-side fd BEFORE the relay started (e.g. app - * bytes the ingress read coalesced with the SOCKS5/HTTP CONNECT request). The - * relay drains these toward A (the stream) ahead of any fresh fd reads, so they - * are not lost. Copies the data. Must be called before mq_flow_begin_relay. - * Returns 0 on success, -1 on bad state / OOM. */ -int mq_flow_prebuffer_b(mq_flow_t *flow, const void *data, size_t len); - -/* Enter the relaying phase: build the relay, register a persistent EV_READ and - * an on-demand EV_WRITE on the fd, re-wire the stream callbacks to drive the - * relay edges, and pump both directions once (forwarding any bytes already - * buffered on the stream or fd, and honoring an immediate EOF). The stream must - * be set and the fd must be connected & non-blocking. - * - * Returns 0 on success. On failure (relay/event alloc) returns -1 and the flow - * is left intact for the caller to fail+reap (the caller's own error path); - * the caller decides how to notify the peer. - * - * After a successful return the flow drives itself to completion via libevent - * and the stream callbacks; on completion it self-requests a deferred reap and - * invokes on_reap. */ -int mq_flow_begin_relay(mq_flow_t *flow); - -/* Reap the flow exactly once: detach stream callbacks, close stream (RESET only - * if not graceful) + fd, free events + relay, run on_reap, unlink from the - * list, and free the node. Safe from any phase and idempotent. */ -void mq_flow_reap(mq_flow_t *flow); - -#endif /* MQ_PROXY_MQ_FLOW_H */ diff --git a/src/proxy/mq_framebuf.c b/src/proxy/mq_framebuf.c deleted file mode 100644 index 980b3a4e..00000000 --- a/src/proxy/mq_framebuf.c +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_framebuf.c — see mq_framebuf.h. */ -#include "proxy/mq_framebuf.h" - -int -mq_framebuf_fill(mq_stream_t *s, mq_framebuf_t *fb, int *fin) -{ - if (!s || !fb) { - return -1; - } - - for (;;) { - if (fb->len >= sizeof(fb->buf)) { - break; /* buffer full: stop; caller decides malformed after decode */ - } - int f = 0; - long n = mq_stream_recv(s, fb->buf + fb->len, sizeof(fb->buf) - fb->len, &f); - if (n < 0) { - /* Hard error / RESET before a frame. Match the historical client - * data-header behavior: report it without consulting fin. */ - return -1; - } - if (n > 0) { - fb->len += (size_t)n; - } - if (f && fin) { - *fin = 1; - } - if (n == 0) { - break; /* EAGAIN: nothing more right now */ - } - } - - return 0; -} diff --git a/src/proxy/mq_framebuf.h b/src/proxy/mq_framebuf.h deleted file mode 100644 index bb52f701..00000000 --- a/src/proxy/mq_framebuf.h +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_framebuf.h — bounded frame-accumulation buffer for control/header decode. - * - * The proxy client and server each need to pull bytes off a freshly-opened - * QUIC stream into a small fixed buffer and retry decoding a single - * length-prefixed frame (AUTH_REQUEST/RESPONSE, CONNECT_TCP_REQUEST/RESPONSE) - * as more bytes arrive, treating a full buffer without a valid frame as - * malformed. This value-type helper owns only the buffer + length + the fill - * loop; the caller still performs the decode, the overflow⇒malformed decision, - * and (on success) computes the trailing-byte offset for mq_flow_prebuffer. - */ -#ifndef MQ_FRAMEBUF_H -#define MQ_FRAMEBUF_H - -#include -#include - -#include "transport/mq_stream.h" - -/* Bound for an accumulated control/header frame. AUTH and CONNECT_TCP frames - * (client id + token / host + port + small fields) sit comfortably under this; - * exceeding it without a decode is treated by the caller as malformed. */ -#define MQ_FRAMEBUF_CAP 512 - -/* A zero-initialized mq_framebuf_t (e.g. via calloc) is already empty. */ -typedef struct { - uint8_t buf[MQ_FRAMEBUF_CAP]; - size_t len; -} mq_framebuf_t; - -/* Drain currently-available stream bytes into fb (up to MQ_FRAMEBUF_CAP), - * looping while the stream keeps returning data. The caller then attempts a - * decode on fb->buf / fb->len and, on success, may consume + reset. - * - * If non-NULL, *fin is set to 1 (and never cleared) when a stream FIN is - * observed during the fill; the caller may treat a FIN-without-decode as - * malformed. - * - * Returns 0 on success (the caller then attempts a decode on fb->buf/fb->len — - * whether or not new bytes arrived), or -1 on a hard stream error (e.g. RESET) - * before a frame. Callers that fail hard on a pre-frame error check for <0; - * callers that historically retried the decode regardless may ignore the return. - * - * The buffer-full and "full without a valid frame ⇒ malformed" decisions stay - * with the caller (after its decode attempt), preserving each site's policy. */ -int mq_framebuf_fill(mq_stream_t *s, mq_framebuf_t *fb, int *fin); - -#endif /* MQ_FRAMEBUF_H */ diff --git a/src/proxy/mq_relay.c b/src/proxy/mq_relay.c deleted file mode 100644 index bd74a4c2..00000000 --- a/src/proxy/mq_relay.c +++ /dev/null @@ -1,182 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "proxy/mq_relay.h" -#include "util/mq_buf.h" - -#include - -struct mq_relay { - mq_relay_cfg_t cfg; - mq_buf_t ab; /* A -> B */ - mq_buf_t ba; /* B -> A */ - int a_eof; /* A's source hit EOF */ - int b_eof; /* B's source hit EOF */ - int ab_eof_signaled; /* on_dir_eof(AB) already fired */ - int ba_eof_signaled; /* on_dir_eof(BA) already fired */ - int done; /* on_done already fired */ - int error; /* a hard error occurred */ -}; - -mq_relay_t * -mq_relay_new(const mq_relay_cfg_t *cfg) -{ - mq_relay_t *r = (mq_relay_t *)calloc(1, sizeof(*r)); - if (!r) return NULL; - /* calloc zeroed everything: buffers (r=w=0), a_eof/b_eof, done, error. */ - r->cfg = *cfg; - return r; -} - -void -mq_relay_free(mq_relay_t *r) -{ - free(r); -} - -/* A direction is finished when its source is at EOF and its buffer is drained. */ -static int -dir_finished(int src_eof, const mq_buf_t *buf) -{ - return src_eof && mq_buf_len(buf) == 0; -} - -/* - * Pump a single direction: read from (read_io, read_fn) into `buf`, write - * `buf` out to (write_io, write_fn). Honors backpressure: never reads when - * the buffer is full, never spins on would-block. Terminates an iteration - * when neither a read nor a write made progress. - * - * Sets *r->error on hard error (read or write returns -1). - */ -static void -pump_dir(mq_relay_t *r, void *read_io, mq_io_read_fn read_fn, void *write_io, - mq_io_write_fn write_fn, mq_buf_t *buf, int *src_eof) -{ - for (;;) { - int progress = 0; - - /* --- read into buffer, if room and source still open --- */ - if (!*src_eof && mq_buf_space(buf) > 0) { - int eof = 0, wb = 0; - long n = - read_fn(read_io, mq_buf_write_ptr(buf), mq_buf_space(buf), &eof, &wb); - if (n < 0) { - r->error = 1; - return; - } - if (n > 0) { - mq_buf_commit(buf, (size_t)n); - progress = 1; - } - if (eof) *src_eof = 1; - /* wb with n==0 just means nothing to read now; stop trying to read */ - } - - /* --- write buffered bytes out --- */ - if (mq_buf_len(buf) > 0) { - int wb = 0; - long n = write_fn(write_io, mq_buf_read_ptr(buf), mq_buf_len(buf), &wb); - if (n < 0) { - r->error = 1; - return; - } - if (n > 0) { - mq_buf_consume(buf, (size_t)n); - progress = 1; - } - if (wb) { - /* destination is full; stop writing this direction. It resumes - on the destination's writable edge. Don't keep looping. */ - return; - } - } - - if (!progress) return; /* nothing read, nothing written: avoid spinning */ - } -} - -static void -maybe_done(mq_relay_t *r) -{ - if (r->done) return; - - /* On a hard error, skip the per-direction notifications and go straight to - * the (reaping) on_done with r->error set. on_done may free r — touch nothing - * of *r afterward. */ - if (r->error) { - r->done = 1; - if (r->cfg.on_done) r->cfg.on_done(r, r->cfg.user); - return; - } - - /* Per-direction clean-EOF notifications fire the moment a direction's source - * has EOF'd AND its buffered bytes are fully flushed to the sink. The owner - * uses these to propagate a clean shutdown (FIN on the peer) and to drop the - * dead source's read interest (killing any level-triggered busy-spin). - * - * Contract: on_dir_eof is a pure side-effect notification — the owner MUST - * NOT free the relay from it. (The owner can reap AFTER the edge call that - * triggered this returns.) That keeps the both-directions completion model - * below intact for the relay's own unit tests, while letting an owner apply - * a simple-close policy out of band. */ - if (!r->ab_eof_signaled && dir_finished(r->a_eof, &r->ab)) { - r->ab_eof_signaled = 1; - if (r->cfg.on_dir_eof) r->cfg.on_dir_eof(r, MQ_RELAY_DIR_AB, r->cfg.user); - } - if (!r->ba_eof_signaled && dir_finished(r->b_eof, &r->ba)) { - r->ba_eof_signaled = 1; - if (r->cfg.on_dir_eof) r->cfg.on_dir_eof(r, MQ_RELAY_DIR_BA, r->cfg.user); - } - - /* Both directions finished (source EOF + buffer drained): the relay is done. - * on_done may free r — touch nothing of *r afterward. */ - if (dir_finished(r->a_eof, &r->ab) && dir_finished(r->b_eof, &r->ba)) { - r->done = 1; - if (r->cfg.on_done) r->cfg.on_done(r, r->cfg.user); - } -} - -/* A->B direction: read from A, write to B. */ -static void -pump_ab(mq_relay_t *r) -{ - if (r->done) return; - pump_dir(r, r->cfg.a_io, r->cfg.a_read, r->cfg.b_io, r->cfg.b_write, &r->ab, - &r->a_eof); - maybe_done(r); -} - -/* B->A direction: read from B, write to A. */ -static void -pump_ba(mq_relay_t *r) -{ - if (r->done) return; - pump_dir(r, r->cfg.b_io, r->cfg.b_read, r->cfg.a_io, r->cfg.a_write, &r->ba, - &r->b_eof); - maybe_done(r); -} - -void -mq_relay_on_a_readable(mq_relay_t *r) -{ - pump_ab(r); /* A is the source for A->B */ -} - -void -mq_relay_on_a_writable(mq_relay_t *r) -{ - pump_ba(r); /* A is the destination for B->A */ -} - -void -mq_relay_on_b_readable(mq_relay_t *r) -{ - pump_ba(r); /* B is the source for B->A */ -} - -void -mq_relay_on_b_writable(mq_relay_t *r) -{ - pump_ab(r); /* B is the destination for A->B */ -} diff --git a/src/proxy/mq_relay.h b/src/proxy/mq_relay.h deleted file mode 100644 index c7dd923e..00000000 --- a/src/proxy/mq_relay.h +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_RELAY_H -#define MQ_RELAY_H -#include - -/* read: return >=0 bytes read; set *eof=1 on clean EOF; set *wb=1 if it would block - (no bytes now). return -1 on hard error. (eof and wb are out-params, preset to 0.) - write: return >=0 bytes written; set *wb=1 if it would block. return -1 on hard error. - */ -typedef long (*mq_io_read_fn)(void *io, unsigned char *buf, size_t cap, int *eof, - int *wb); -typedef long (*mq_io_write_fn)(void *io, const unsigned char *buf, size_t len, int *wb); - -typedef struct mq_relay mq_relay_t; -typedef void (*mq_relay_done_fn)(mq_relay_t *r, void *user); - -/* Which relay direction reached a clean EOF, reported via on_dir_eof. */ -typedef enum { - MQ_RELAY_DIR_AB = 0, /* A->B: A's source (e.g. QUIC stream) hit FIN/EOF */ - MQ_RELAY_DIR_BA = 1, /* B->A: B's source (e.g. origin fd) hit EOF */ -} mq_relay_dir_t; - -typedef void (*mq_relay_dir_eof_fn)(mq_relay_t *r, mq_relay_dir_t dir, void *user); - -typedef struct { - void *a_io, *b_io; - mq_io_read_fn a_read, b_read; - mq_io_write_fn a_write, b_write; - /* Called AT MOST ONCE per direction, the moment that direction's source has - * hit a clean EOF AND its buffered bytes are fully flushed to the sink. The - * owner uses this to propagate a clean shutdown (e.g. FIN on the peer) and to - * remove the dead source's read interest. Not called on hard error. */ - mq_relay_dir_eof_fn on_dir_eof; - mq_relay_done_fn - on_done; /* called EXACTLY ONCE when both directions finished OR on hard error */ - void *user; -} mq_relay_cfg_t; - -mq_relay_t *mq_relay_new(const mq_relay_cfg_t *cfg); -void mq_relay_free(mq_relay_t *r); - -/* Edge handlers — the owner calls these when an endpoint becomes readable/writable. - Each pumps as much as possible without blocking, honoring backpressure. */ -void mq_relay_on_a_readable(mq_relay_t *r); -void mq_relay_on_a_writable(mq_relay_t *r); -void mq_relay_on_b_readable(mq_relay_t *r); -void mq_relay_on_b_writable(mq_relay_t *r); -#endif diff --git a/src/proxy/mq_server.c b/src/proxy/mq_server.c deleted file mode 100644 index 4737a82e..00000000 --- a/src/proxy/mq_server.c +++ /dev/null @@ -1,804 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_server.c — proxy server + control-stream AUTH handshake. - * - * Per accepted connection: an mq_srv_conn state struct is allocated in - * on_new_conn and hung off the mq_conn owner slot (mq_conn_set_user). It is - * freed when the connection closes (state callback MQ_CONN_CLOSED). - * - * The FIRST bidi stream on a connection is the control stream: we accumulate - * its bytes, decode AUTH_REQUEST, constant-time-compare the token to the - * configured one, reply AUTH_RESPONSE, and on failure close the connection. - * Later (non-control) streams are left unhandled here — Task 12 wires data - * streams. The auth-attempt counter is bumped exactly once per control stream. - */ -#include "proxy/mq_server.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "proxy/mq_flow.h" -#include "proxy/mq_framebuf.h" -#include "proxy/mq_udp_session.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_stream.h" -#include "transport/mq_transport.h" -#include "util/mq_ct.h" -#include "util/mq_log.h" -#include "wire/mq_varint.h" -#include "wire/mq_wire.h" - -#define MQ_SERVER_ALPN "mqproxy-tcp/1" -#define MQ_SERVER_ID "mqproxy-server" - -/* Origin connect deadline. A non-blocking connect to a blackholed target would - * otherwise pin an fd + QUIC stream + data node until the kernel TCP timeout - * (~2 min). Cap it so a captured client cannot exhaust resources by dialing dead - * hosts. Internal backstop (not operator-tuned); 15 s is well under the kernel - * default and above any healthy origin's connect latency. */ -#define MQ_SRV_CONNECT_TIMEOUT_MS 15000 - -/* The buffered AUTH_REQUEST / CONNECT_TCP_REQUEST is bounded by MQ_FRAMEBUF_CAP - * (512): the full frame is at most version + client_id(<=64) + auth_token(<=256) - * + host/port + small fields, comfortably under that. Exceeding the bound without - * a decode is malformed. */ - -typedef struct mq_srv_conn_s mq_srv_conn_t; -typedef struct mq_srv_data_s mq_srv_data_t; - -/* Per-connection state, hung off mq_conn's owner slot. */ -struct mq_srv_conn_s { - mq_server_t *server; - int authed; - int control_stream_seen; /* the first bidi stream was claimed as control */ - int auth_done; /* AUTH_RESPONSE sent (control stream settled) */ - mq_stream_t *ctrl; - - mq_framebuf_t rx; - - /* Intrusive singly-linked list of active data streams / relays. */ - mq_srv_data_t *data_head; - - /* Per-connection UDP relay state (Chunk 5). Created in on_new_conn from the - * server's udp settings; owns the session table + 0x02 OPEN handling. */ - mq_udp_srv_t *udp; -}; - -/* Per-data-stream state: one CONNECT_TCP request + its origin fd + relay. - * - * The stream⇄fd relay machinery (I/O adapters, fd events, completion/FIN/ - * half-close/deferred-reap) lives in the shared mq_flow module; this node owns - * the protocol-specific phases on top of it. - * - * Lifecycle phases: - * 1. Header accumulation: buffer bytes until CONNECT_TCP_REQUEST decodes. - * 2. Connecting: non-blocking connect() in flight; a one-shot EV_WRITE event - * on the origin fd fires when the connect completes (or errors). - * 3. Relaying: mq_flow drives the relay (mq_flow_begin_relay). - * 4. Reaped: flow reaped (closes stream + fd once), node unlinked + freed. - */ -struct mq_srv_data_s { - mq_srv_conn_t *sc; - mq_flow_t *flow; /* shared stream⇄fd relay node (owns stream + fd) */ - mq_stream_t *stream; /* header phase: the data stream (also held by flow) */ - int fd; /* origin socket during connect; handed to flow on relay */ - struct event *connect_ev; /* one-shot EV_WRITE to detect connect completion */ - int reaped; - int graceful; /* pre-relay error path sent a FIN'd response: do NOT RESET */ - - mq_framebuf_t rx; - size_t hdr_consumed; /* bytes of rx.buf the CONNECT_TCP_REQUEST occupied */ - - mq_srv_data_t *next; -}; - -struct mq_server_s { - mq_transport_t *transport; - mq_runtime_t *rt; - char auth_token[256]; - size_t auth_token_len; - unsigned auth_attempts; - uint64_t udp_idle_timeout_ms; /* idle timeout for UDP relay sessions (Chunk 5) */ - int udp_enabled; /* whether to advertise MQ_FEAT_UDP_RELAY */ - /* Observability: the UDP relay state of the most-recently-accepted conn. - * Set in on_new_conn, cleared when that conn closes. Single-conn observation - * hook (see mq_server_last_udp_srv). */ - mq_udp_srv_t *last_udp; - mq_conn_t *last_conn; /* most-recent accepted conn (Phase 5c observability) */ -}; - - -static void -srv_send_resp(mq_stream_t *s, mq_status_t status, mq_auth_err_t err, uint64_t features) -{ - mq_auth_resp_t resp; - memset(&resp, 0, sizeof(resp)); - resp.status = status; - resp.error_code = err; - resp.features = features; - snprintf(resp.server_id, sizeof(resp.server_id), "%s", MQ_SERVER_ID); - - uint8_t buf[512]; - int n = mq_encode_auth_resp(buf, sizeof(buf), &resp); - if (n < 0) { - MQ_LOGE("mq_server: encode AUTH_RESPONSE failed"); - return; - } - /* No FIN: keep the control stream open (reused by later capabilities). */ - long sent = mq_stream_send(s, buf, (size_t)n, /*fin=*/0); - if (sent < 0 || (size_t)sent != (size_t)n) { - MQ_LOGW("mq_server: AUTH_RESPONSE send short/failed (%ld of %d)", sent, n); - } -} - -/* Control stream readable: accumulate, decode AUTH_REQUEST, authenticate. */ -static void -srv_ctrl_readable(mq_stream_t *s, void *user) -{ - mq_srv_conn_t *sc = (mq_srv_conn_t *)user; - mq_server_t *srv = sc->server; - - if (sc->auth_done) { - /* Already settled; drain to avoid stalling the stream. */ - uint8_t scratch[256]; - int fin = 0; - while (mq_stream_recv(s, scratch, sizeof(scratch), &fin) > 0) {} - return; - } - - mq_framebuf_fill(s, &sc->rx, NULL); - - mq_auth_req_t req; - memset(&req, 0, sizeof(req)); - int consumed = mq_decode_auth_req(sc->rx.buf, sc->rx.len, &req); - if (consumed < 0) { - if (sc->rx.len >= sizeof(sc->rx.buf)) { - /* Malformed / oversized: count the attempt, reject, close. */ - srv->auth_attempts++; - sc->auth_done = 1; - MQ_LOGW("mq_server: AUTH_REQUEST malformed/oversized, rejecting"); - srv_send_resp(s, MQ_STATUS_ERROR, MQ_AUTH_FAILED, 0); - mq_conn_close((mq_conn_t *)mq_stream_conn(s)); - } - return; /* otherwise: need more bytes */ - } - - /* A full AUTH_REQUEST decoded — this is the (single) auth attempt. */ - srv->auth_attempts++; - sc->auth_done = 1; - - /* Constant-time token compare. auth_token is a fixed-size field; compare - * over its NUL-terminated content length. */ - size_t tok_len = strnlen(req.auth_token, sizeof(req.auth_token)); - int ok = mq_ct_equal(req.auth_token, tok_len, srv->auth_token, srv->auth_token_len); - - if (ok) { - sc->authed = 1; - /* Open the datagram auth gate so mq_udp_srv_on_datagram accepts frames - * for this connection (DATAGRAM frames bypass the 0x02 stream pre-auth - * guard — this is the sole auth boundary for inbound datagrams). */ - mq_udp_srv_set_authed(sc->udp, 1); - uint64_t feat = srv->udp_enabled ? MQ_FEAT_UDP_RELAY : 0; - srv_send_resp(s, MQ_STATUS_OK, MQ_AUTH_OK, feat); - MQ_LOGI("mq_server: auth OK for client_id='%s'", req.client_id); - } else { - srv_send_resp(s, MQ_STATUS_ERROR, MQ_AUTH_FAILED, 0); - MQ_LOGW("mq_server: auth FAILED for client_id='%s'", req.client_id); - mq_conn_close((mq_conn_t *)mq_stream_conn(s)); - } -} - -/* ── data streams: CONNECT_TCP → origin dial → relay ─────────────────────── */ - -/* Send a CONNECT_TCP_RESPONSE on the data stream. fin!=0 closes the send - * direction after the response (used on the error path so the response is not - * abandoned by a RESET_STREAM — RESET discards un-acked stream data). */ -static void -srv_send_tcp_resp(mq_stream_t *s, mq_status_t status, mq_tcp_err_t err, int fin) -{ - mq_connect_tcp_resp_t resp; - memset(&resp, 0, sizeof(resp)); - resp.status = status; - resp.error_code = err; - resp.message[0] = '\0'; - - uint8_t buf[512]; - int n = mq_encode_connect_tcp_resp(buf, sizeof(buf), &resp); - if (n < 0) { - MQ_LOGE("mq_server: encode CONNECT_TCP_RESPONSE failed"); - return; - } - long sent = mq_stream_send(s, buf, (size_t)n, fin); - if (sent < 0 || (size_t)sent != (size_t)n) { - MQ_LOGW("mq_server: CONNECT_TCP_RESPONSE send short/failed (%ld of %d)", sent, n); - } -} - -/* Map a connect()/getaddrinfo errno to a CONNECT_TCP error code. */ -static mq_tcp_err_t -srv_map_errno(int e) -{ - switch (e) { - case ECONNREFUSED: return MQ_TCP_CONN_REFUSED; - case ETIMEDOUT: return MQ_TCP_TIMEOUT; - default: return MQ_TCP_CONN_REFUSED; - } -} - -/* Unlink a data-stream node from its conn's list. */ -static void -srv_data_unlink(mq_srv_data_t *d) -{ - mq_srv_conn_t *sc = d->sc; - mq_srv_data_t **pp = &sc->data_head; - while (*pp) { - if (*pp == d) { - *pp = d->next; - return; - } - pp = &(*pp)->next; - } -} - -/* Reap a data stream exactly once: reap its flow (which closes stream + fd, - * frees events + relay), free the connect event, unlink and free the node. Safe - * from any phase. Re-entrancy: reaping the flow may invoke srv_flow_on_reap - * (which sets d->flow = NULL); the d->reaped guard makes the whole thing - * idempotent. */ -static void -srv_data_reap(mq_srv_data_t *d) -{ - if (!d || d->reaped) { - return; - } - d->reaped = 1; - - if (d->connect_ev) { - event_free(d->connect_ev); - d->connect_ev = NULL; - } - if (d->flow) { - /* Relay phase: the flow owns the stream + fd; reaping it closes both. */ - mq_flow_t *fl = d->flow; - d->flow = NULL; - d->stream = NULL; - d->fd = -1; - mq_flow_reap(fl); - } else { - /* Pre-relay (header / connect phase): no flow yet. Close the stream/fd - * directly. On the graceful error path the FIN'd response was already - * sent — do NOT RESET (RESET would discard the un-acked response). */ - if (d->stream) { - mq_stream_set_cbs(d->stream, NULL, NULL, NULL, NULL); - if (!d->graceful) { - mq_stream_close(d->stream); - } - d->stream = NULL; - } - if (d->fd >= 0) { - close(d->fd); - d->fd = -1; - } - } - srv_data_unlink(d); - free(d); -} - -/* Flow reap callback: the shared relay node has closed stream + fd and is about - * to free itself. Drop our pointer and reap the protocol node. Guarded by - * d->reaped so a reap initiated from srv_data_reap (which nulls d->flow first) - * does not recurse. */ -static void -srv_flow_on_reap(mq_flow_t *fl, void *user) -{ - (void)fl; - mq_srv_data_t *d = (mq_srv_data_t *)user; - d->flow = NULL; - d->stream = NULL; - d->fd = -1; - srv_data_reap(d); -} - -/* Error path (pre-relay only): send CONNECT_TCP_RESPONSE(ERROR, err) with FIN, - * then reap the node gracefully (no RESET). The stream finishes naturally, - * delivering the response to the client before close. */ -static void -srv_data_fail(mq_srv_data_t *d, mq_tcp_err_t err) -{ - if (d->stream) { - srv_send_tcp_resp(d->stream, MQ_STATUS_ERROR, err, /*fin=*/1); - d->graceful = 1; - } - srv_data_reap(d); -} - -/* Transition a connected fd into the relay phase: create the shared flow (taking - * ownership of the stream + fd), send CONNECT_TCP_RESPONSE(OK), and start - * relaying. The flow re-wires the stream callbacks and pumps both directions - * once (forwarding any bytes the client sent past the CONNECT_TCP_REQUEST). */ -static void -srv_data_begin_relay(mq_srv_data_t *d) -{ - mq_server_t *srv = d->sc->server; - struct event_base *base = mq_runtime_base(srv->rt); - - d->flow = mq_flow_new(&d->flow, base, d->stream, d->fd, srv_flow_on_reap, d); - if (!d->flow) { - MQ_LOGE("mq_server: flow alloc failed"); - srv_data_fail(d, MQ_TCP_CONN_REFUSED); - return; - } - /* The flow owns the stream + fd now; clear our pre-relay handles so reap - * goes through the flow. */ - d->fd = -1; - - /* Hand any client bytes that trailed the CONNECT_TCP_REQUEST (already pulled - * into rx.buf during header buffering) to the flow's prebuffer so they reach - * the origin and are not lost. */ - if (d->rx.len > d->hdr_consumed) { - if (mq_flow_prebuffer(d->flow, d->rx.buf + d->hdr_consumed, - d->rx.len - d->hdr_consumed) != 0) { - MQ_LOGE("mq_server: prebuffer failed"); - srv_data_reap(d); - return; - } - } - - /* Send the success response BEFORE starting the relay so it precedes any - * relayed origin bytes on the stream. */ - srv_send_tcp_resp(d->stream, MQ_STATUS_OK, MQ_TCP_OK, /*fin=*/0); - - if (mq_flow_begin_relay(d->flow) != 0) { - MQ_LOGE("mq_server: begin relay failed"); - srv_data_reap(d); - } -} - -/* Origin connect-completion one-shot: check SO_ERROR. */ -static void -srv_connect_done_cb(evutil_socket_t fd, short what, void *arg) -{ - mq_srv_data_t *d = (mq_srv_data_t *)arg; - - /* The one-shot connect_ev has fired; it is no longer pending. */ - if (d->connect_ev) { - event_free(d->connect_ev); - d->connect_ev = NULL; - } - - /* Deadline hit before the connect completed: fail the open with TIMEOUT so - * the origin fd + stream + node are released instead of pinned until the - * kernel TCP timeout. */ - if (what & EV_TIMEOUT) { - MQ_LOGW("mq_server: origin connect timed out"); - srv_data_fail(d, MQ_TCP_TIMEOUT); - return; - } - - int soerr = 0; - socklen_t sl = sizeof(soerr); - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &soerr, &sl) != 0) { - soerr = errno ? errno : ECONNREFUSED; - } - if (soerr != 0) { - MQ_LOGW("mq_server: connect failed: %s", strerror(soerr)); - srv_data_fail(d, srv_map_errno(soerr)); - return; - } - if (!d->stream) { - /* Stream went away while connecting. */ - srv_data_reap(d); - return; - } - srv_data_begin_relay(d); -} - -/* Resolve the CONNECT_TCP target into a sockaddr. Returns 0 on success and - * fills *ss / *sslen; on failure returns -1 and sets *err. */ -static int -srv_resolve_target(const mq_connect_tcp_req_t *req, struct sockaddr_storage *ss, - socklen_t *sslen, mq_tcp_err_t *err) -{ - memset(ss, 0, sizeof(*ss)); - if (req->address_type == MQ_ADDR_IPV4) { - if (req->host_len != 4) { - *err = MQ_TCP_DNS_FAILED; - return -1; - } - struct sockaddr_in *sin = (struct sockaddr_in *)ss; - sin->sin_family = AF_INET; - memcpy(&sin->sin_addr, req->host, 4); - sin->sin_port = htons(req->port); - *sslen = sizeof(*sin); - return 0; - } - if (req->address_type == MQ_ADDR_IPV6) { - if (req->host_len != 16) { - *err = MQ_TCP_DNS_FAILED; - return -1; - } - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; - sin6->sin6_family = AF_INET6; - memcpy(&sin6->sin6_addr, req->host, 16); - sin6->sin6_port = htons(req->port); - *sslen = sizeof(*sin6); - return 0; - } - /* DOMAIN: Phase 1 uses BLOCKING getaddrinfo (acceptable for now; Phase 2 - * moves DNS off the event loop). */ - char hostz[MQ_MAX_HOST + 1]; - if (req->host_len > MQ_MAX_HOST) { - *err = MQ_TCP_DNS_FAILED; - return -1; - } - memcpy(hostz, req->host, req->host_len); - hostz[req->host_len] = '\0'; - char portz[8]; - snprintf(portz, sizeof(portz), "%u", (unsigned)req->port); - - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - struct addrinfo *res = NULL; - int gai = getaddrinfo(hostz, portz, &hints, &res); - if (gai != 0 || !res) { - if (res) freeaddrinfo(res); - *err = MQ_TCP_DNS_FAILED; - return -1; - } - memcpy(ss, res->ai_addr, res->ai_addrlen); - *sslen = res->ai_addrlen; - freeaddrinfo(res); - return 0; -} - -/* Begin dialing the origin for a decoded CONNECT_TCP request. */ -static void -srv_data_dial(mq_srv_data_t *d, const mq_connect_tcp_req_t *req) -{ - struct sockaddr_storage ss; - socklen_t sslen = 0; - mq_tcp_err_t rerr = MQ_TCP_DNS_FAILED; - if (srv_resolve_target(req, &ss, &sslen, &rerr) != 0) { - srv_data_fail(d, rerr); - return; - } - - int fd = socket(ss.ss_family, SOCK_STREAM, 0); - if (fd < 0) { - srv_data_fail(d, MQ_TCP_CONN_REFUSED); - return; - } - int fl = fcntl(fd, F_GETFL, 0); - if (fl < 0 || fcntl(fd, F_SETFL, fl | O_NONBLOCK) < 0) { - close(fd); - srv_data_fail(d, MQ_TCP_CONN_REFUSED); - return; - } - d->fd = fd; - - int rc = connect(fd, (struct sockaddr *)&ss, sslen); - if (rc == 0) { - /* Immediate connect (loopback may complete synchronously). */ - srv_data_begin_relay(d); - return; - } - if (errno != EINPROGRESS) { - int e = errno; - MQ_LOGW("mq_server: connect: %s", strerror(e)); - srv_data_fail(d, srv_map_errno(e)); - return; - } - /* In progress: watch for writability to learn the outcome. */ - struct event_base *base = mq_runtime_base(d->sc->server->rt); - d->connect_ev = event_new(base, fd, EV_WRITE, srv_connect_done_cb, d); - if (!d->connect_ev) { - srv_data_fail(d, MQ_TCP_CONN_REFUSED); - return; - } - /* Arm with a deadline: fires on writable (connect done) OR timeout, once. */ - struct timeval tv = {.tv_sec = MQ_SRV_CONNECT_TIMEOUT_MS / 1000, - .tv_usec = (MQ_SRV_CONNECT_TIMEOUT_MS % 1000) * 1000}; - event_add(d->connect_ev, &tv); -} - -/* Data stream readable while still buffering the CONNECT_TCP_REQUEST header. - * - * Wire layout (design §5.2): - * [discriminator varint] [request frame...] - * - * The discriminator is decoded on every retry together with the request frame - * (cheap: always 1 byte for currently-defined values). This avoids any extra - * per-stream state to track "discriminator already consumed". */ -static void -srv_data_header_readable(mq_stream_t *s, void *user) -{ - mq_srv_data_t *d = (mq_srv_data_t *)user; - - mq_framebuf_fill(s, &d->rx, NULL); - - /* Step 1: decode the stream-type discriminator. */ - uint64_t stream_type = 0; - int disc_len = mq_varint_decode(d->rx.buf, d->rx.len, &stream_type); - if (disc_len < 0) { - /* Not even 1 byte yet (or buffer overflow without a discriminator). */ - if (d->rx.len >= sizeof(d->rx.buf)) { - MQ_LOGW("mq_server: data stream header malformed/oversized, resetting"); - srv_data_reap(d); - } - return; /* need more bytes */ - } - - /* Step 2: dispatch on stream type. */ - if (stream_type == MQ_STREAM_TYPE_CONNECT_TCP) { - mq_connect_tcp_req_t req; - int consumed = mq_decode_connect_tcp_req(d->rx.buf + disc_len, - d->rx.len - (size_t)disc_len, &req); - if (consumed < 0) { - if (d->rx.len >= sizeof(d->rx.buf)) { - MQ_LOGW("mq_server: CONNECT_TCP_REQUEST malformed/oversized, resetting"); - srv_data_reap(d); - } - return; /* need more bytes */ - } - - /* Header complete. hdr_consumed accounts for discriminator + request. */ - d->hdr_consumed = (size_t)disc_len + (size_t)consumed; - srv_data_dial(d, &req); - - } else if (stream_type == MQ_STREAM_TYPE_UDP_SESSION) { - /* UDP_SESSION (0x02): hand the stream to the per-conn UDP relay, which - * decodes OPEN → resolves → dials → sends RESP. The client sends the - * discriminator + OPEN in one send (mq_client), so mq_framebuf_fill - * above already drained the OPEN body out of the stream into d->rx; it - * now sits at d->rx.buf + disc_len. Forward it as carry-over so the - * relay's framebuf is seeded and a single-send OPEN is not stalled. */ - mq_srv_conn_t *sc = d->sc; - const uint8_t *carry = d->rx.buf + disc_len; - size_t carry_len = d->rx.len - (size_t)disc_len; - - /* Detach this node from the data-stream machinery: the UDP relay takes - * ownership of the stream. Drop our header callbacks, mark reaped, and - * unlink + free the node WITHOUT closing the stream (the relay owns it - * now). */ - mq_stream_set_cbs(s, NULL, NULL, NULL, NULL); - d->stream = NULL; - d->reaped = 1; - srv_data_unlink(d); - - if (sc->udp) { - mq_udp_srv_attach_stream(sc->udp, s, carry, carry_len); - } else { - MQ_LOGW("mq_server: UDP relay disabled (no udp state), resetting"); - mq_stream_close(s); - } - free(d); - - } else { - MQ_LOGW("mq_server: unknown stream type 0x%02x, resetting", - (unsigned)stream_type); - mq_stream_close(s); - } -} - -/* Header-phase stream close: the stream closed before the relay started (no - * flow yet). Drop our pointer and reap. (Once the relay starts, the flow owns - * the stream-close callback.) */ -static void -srv_data_header_closed(mq_stream_t *s, void *user) -{ - (void)s; - mq_srv_data_t *d = (mq_srv_data_t *)user; - d->stream = NULL; /* the transport frees it after this returns */ - srv_data_reap(d); -} - -/* Attach a new data stream: allocate state, link it, start header buffering. */ -static void -srv_attach_data_stream(mq_srv_conn_t *sc, mq_stream_t *s) -{ - mq_srv_data_t *d = calloc(1, sizeof(*d)); - if (!d) { - MQ_LOGE("mq_server: OOM allocating data-stream state"); - mq_stream_close(s); - return; - } - d->sc = sc; - d->stream = s; - d->fd = -1; - d->next = sc->data_head; - sc->data_head = d; - - mq_stream_set_cbs(s, srv_data_header_readable, NULL, srv_data_header_closed, d); - - /* Bytes may already be buffered in the stream; try decoding immediately. */ - srv_data_header_readable(s, d); -} - -/* Connection-state hook installed on each accepted conn: reaps active data - * relays then frees per-conn state on close. */ -static void -srv_conn_state(mq_conn_t *c, mq_conn_state_t st, void *user) -{ - if (st == MQ_CONN_CLOSED) { - mq_srv_conn_t *sc = (mq_srv_conn_t *)user; - /* Reap every active relay (each closes its stream+fd once, frees - * events/relay, unlinks itself). Iterate defensively against the - * self-unlink. */ - while (sc->data_head) { - srv_data_reap(sc->data_head); - } - /* Tear down all UDP sessions (fd close + event free + defrag free) and - * free the per-conn UDP relay state. Sessions hold 0x02 stream pointers; - * the conn is closing so those streams are gone — mq_udp_srv_free reaps - * each session's resources (it does NOT touch the dead streams beyond a - * RESET that the closing conn drops). */ - if (sc->server->last_udp == sc->udp) { - sc->server->last_udp = NULL; - } - if (sc->server->last_conn == c) { - sc->server->last_conn = NULL; - } - mq_udp_srv_free(sc->udp); - sc->udp = NULL; - free(sc); - } -} - -/* Datagram callback trampoline: adapts mq_conn_on_datagram_fn (which carries - * the mq_conn_t* and a void* user) to mq_udp_srv_on_datagram (which takes the - * mq_udp_srv_t* as its first arg and has no conn pointer). */ -static void -srv_on_datagram_cb(mq_conn_t *c, const uint8_t *data, size_t len, void *user) -{ - (void)c; - mq_udp_srv_on_datagram((mq_udp_srv_t *)user, data, len); -} - -/* on_new_conn: allocate per-conn state, attach it to the conn owner slot. */ -static void -srv_on_new_conn(mq_conn_t *c, void *user) -{ - mq_server_t *srv = (mq_server_t *)user; - mq_srv_conn_t *sc = calloc(1, sizeof(*sc)); - if (!sc) { - MQ_LOGE("mq_server: OOM allocating per-conn state"); - mq_conn_close(c); - return; - } - sc->server = srv; - /* Per-conn UDP relay state (Chunk 5). enabled mirrors !--no-udp; the idle - * timeout is the server-configured value (per-session effective = min with - * the client's OPEN request). Allocation failure just disables UDP for this - * conn (sc->udp stays NULL ⇒ 0x02 streams get reset). */ - sc->udp = mq_udp_srv_new(c, mq_runtime_base(srv->rt), srv->udp_idle_timeout_ms, - srv->udp_enabled); - srv->last_udp = sc->udp; /* observability: most-recent conn's UDP relay state */ - srv->last_conn = c; /* observability: most-recent accepted conn */ - mq_conn_set_user(c, sc); - mq_conn_set_on_state(c, srv_conn_state, sc); - /* Wire the connection-level DATAGRAM callback so tunnel→target datagrams - * are dispatched to the UDP relay. The auth gate inside - * mq_udp_srv_on_datagram drops all frames until mq_udp_srv_set_authed(1). - * If sc->udp is NULL (OOM) the callback is not registered (safe no-op). */ - if (sc->udp) { - mq_conn_set_on_datagram(c, srv_on_datagram_cb, sc->udp); - } -} - -/* on_new_stream: the FIRST stream on a connection is the control stream. */ -static void -srv_on_new_stream(mq_stream_t *s, void *user) -{ - (void)user; - mq_conn_t *c = (mq_conn_t *)mq_stream_conn(s); - if (!c) { - MQ_LOGW("mq_server: stream with no owning conn; ignoring"); - return; - } - mq_srv_conn_t *sc = (mq_srv_conn_t *)mq_conn_user(c); - if (!sc) { - MQ_LOGW("mq_server: stream on conn with no state; ignoring"); - return; - } - - if (!sc->control_stream_seen) { - sc->control_stream_seen = 1; - sc->ctrl = s; - mq_stream_set_cbs(s, srv_ctrl_readable, NULL, NULL, sc); - return; - } - - /* Non-control (data) stream. Pre-auth guard: a data stream may only be - * served on an authenticated connection (design §4.2/4.3). If auth has not - * succeeded yet, RESET the stream rather than buffer indefinitely or dial. */ - if (!sc->authed) { - MQ_LOGW("mq_server: data stream %llu before auth; resetting", - (unsigned long long)mq_stream_id(s)); - mq_stream_close(s); - return; - } - - srv_attach_data_stream(sc, s); -} - -mq_server_t * -mq_server_new(mq_transport_t *t, mq_runtime_t *rt, const char *auth_token, mq_cc_t cc, - uint64_t udp_idle_timeout_ms, int udp_enabled) -{ - if (!t || !rt || !auth_token) { - return NULL; - } - mq_server_t *s = calloc(1, sizeof(*s)); - if (!s) { - return NULL; - } - s->transport = t; - s->rt = rt; - snprintf(s->auth_token, sizeof(s->auth_token), "%s", auth_token); - s->auth_token_len = strnlen(s->auth_token, sizeof(s->auth_token)); - s->udp_idle_timeout_ms = udp_idle_timeout_ms; - s->udp_enabled = udp_enabled; - - if (mq_conn_register_alpn(t, MQ_SERVER_ALPN, srv_on_new_conn, srv_on_new_stream, s) != - 0) { - MQ_LOGE("mq_server: register_alpn failed"); - free(s); - return NULL; - } - - /* Apply multipath + aggregate-BDP flow-control windows to every accepted - * connection. The server is a pure responder (the client creates paths), - * but it must advertise enable_multipath + the larger path-id grant so the - * client's paths can be created and validated. */ - xqc_conn_settings_t settings; - memset(&settings, 0, sizeof(settings)); - settings.proto_version = XQC_VERSION_V1; - settings.pacing_on = 1; - /* Enable QUIC DATAGRAM (Phase 3 UDP relay carrier) — symmetric with the - * client. u16 field (xquic.h). */ - settings.max_datagram_frame_size = 65535; - mq_conn_apply_mp_settings(&settings, /*is_server=*/1, cc); - xqc_server_set_conn_settings(mq_transport_xqc(t), &settings); - - return s; -} - -unsigned -mq_server_auth_attempts(const mq_server_t *s) -{ - return s ? s->auth_attempts : 0; -} - -mq_udp_srv_t * -mq_server_last_udp_srv(const mq_server_t *s) -{ - return s ? s->last_udp : NULL; -} - -mq_conn_t * -mq_server_active_conn(const mq_server_t *s) -{ - return s ? s->last_conn : NULL; -} - -void -mq_server_free(mq_server_t *s) -{ - free(s); -} diff --git a/src/proxy/mq_server.h b/src/proxy/mq_server.h deleted file mode 100644 index 371e72ee..00000000 --- a/src/proxy/mq_server.h +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_server.h — proxy server: accepts mqproxy connections and authenticates - * each on its control stream. - * - * mq_server_new registers the "mqproxy-tcp/1" ALPN hooks on a server engine. - * For each accepted connection it tracks a small per-conn state and treats the - * FIRST bidi stream as the control stream: it decodes AUTH_REQUEST, compares - * the token in constant time against the configured token, and replies with - * AUTH_RESPONSE. On failure it closes the connection. Later (non-control) - * streams are NOT auth-checked here (Task 12 handles data streams). - * - * Per-conn state home: hung off the mq_conn owner slot (mq_conn_set_user / - * mq_conn_user), which starts NULL for server-accepted conns. - */ -#ifndef MQ_PROXY_MQ_SERVER_H -#define MQ_PROXY_MQ_SERVER_H - -#include "proxy/mq_udp_session.h" /* mq_udp_srv_t (observability accessor) */ -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" /* mq_cc_t */ -#include "transport/mq_transport.h" - -typedef struct mq_server_s mq_server_t; - -/* Create a server bound to transport `t` (xquic/streams/conn) and runtime `rt` - * (libevent base for origin-fd relays) that expects `auth_token`. Registers the - * ALPN + on_new_conn/on_new_stream hooks. The token string is copied. - * - * udp_idle_timeout_ms: idle timeout for UDP relay sessions in milliseconds - * (stored for Chunk 5 consumption; pass 60000 for the default). A value of 0 - * is NOT validated here (the CLI rejects 0 — Task 6.4); passing 0 yields a - * degenerate immediately-expiring idle timer. - * udp_enabled: if non-zero, advertise MQ_FEAT_UDP_RELAY in AUTH_RESPONSE.features. - * - * Returns NULL on bad args / OOM / ALPN registration failure. */ -mq_server_t *mq_server_new(mq_transport_t *t, mq_runtime_t *rt, const char *auth_token, - mq_cc_t cc, uint64_t udp_idle_timeout_ms, int udp_enabled); - -/* Total number of control-stream auth attempts processed across all - * connections (test/observability hook). */ -unsigned mq_server_auth_attempts(const mq_server_t *s); - -/* Observability accessor: the UDP relay state of the most-recently-accepted - * connection (or NULL if none accepted yet / that conn had no UDP state / it - * has since closed). Intended for single-connection in-process observation - * (integration tests read its counters via mq_udp_srv_counters). With one - * client per server instance the "most recent" conn is unambiguous. The pointer - * is owned by the connection and dangles after that conn closes — do not retain - * it across a conn teardown. */ -mq_udp_srv_t *mq_server_last_udp_srv(const mq_server_t *s); - -/* The most-recently-accepted tunnel conn, or NULL if none is live. Observability - * hook (mirrors last_udp / mq_server_last_udp_srv). Per-instance deployments have - * one client conn; multi-conn servers see only the newest here. The returned - * pointer is cleared to NULL when that conn closes, so it is never dangling. */ -mq_conn_t *mq_server_active_conn(const mq_server_t *s); - -/* Free the server. Safe on NULL. (Per-conn state is freed as conns close.) */ -void mq_server_free(mq_server_t *s); - -#endif /* MQ_PROXY_MQ_SERVER_H */ diff --git a/src/proxy/mq_udp_session.c b/src/proxy/mq_udp_session.c deleted file mode 100644 index c572aba0..00000000 --- a/src/proxy/mq_udp_session.c +++ /dev/null @@ -1,1923 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_udp_session.c — server-side UDP relay session table + OPEN handling. - * - * See mq_udp_session.h for the ownership / lifecycle contract. - * - * Task 5.1 delivered: session table, OPEN decode → resolve → dial → RESP, the - * 0x02 stream lifecycle (stream-close ⇒ session teardown), the per-session idle - * timer, and the three-way reap (stream close / idle expiry / conn close) - * guarded so each session's fd / event / defrag are freed exactly once. - * - * Task 5.2 (this file): datagram relay paths (tunnel→target and - * target→tunnel), pre-OPEN buffer, auth gate, MSS cache, drop counters, and - * stats dump. - * - * ── MSS cache policy ──────────────────────────────────────────────────────── - * mq_conn_datagram_mss() does a calloc/free (xqc_conn_get_stats) per call, so - * we MUST NOT call it per datagram or per frag emit. We cache the mss on - * mq_udp_srv (conn-level, not per session — one value per QUIC connection). - * Cache refresh policy (cached_mss == 0 OR refresh_counter == 0): - * - initialised to 0 (unknown). - * - refreshed when: cached_mss == 0 (never fetched yet), or after each - * MQ_MSS_REFRESH_INTERVAL emits (64 emits), to pick up path changes. - * - if the refreshed value is still 0 (peer unsupported / conn gone) the - * datagram or split is skipped and drops_send_fail is incremented. - * - * ── frag counter semantics ────────────────────────────────────────────────── - * frags_sent: incremented by the number of frags successfully sent - * ONLY when ctx.frags_ok > 1 after a split (i.e. at least - * two frags were actually delivered). Single-frag - * passthrough does NOT count. Proves the multi-frag emit - * path ran. - * frags_reassembled: incremented once per COMPLETED reassembly whose - * frag_count>1 (i.e. a multi-frag packet was fully - * reassembled from defrag). Single-frag feed that returns 1 - * does NOT count. Proves the defrag path ran end-to-end. - * - * ── pre-OPEN buffer ───────────────────────────────────────────────────────── - * When a datagram arrives for a session_id that has no live session yet (the - * OPEN is still in flight), we buffer the FULL datagram bytes (including the - * 9-byte header) so flush can re-run the identical decode→defrag path. Caps: - * 16 entries AND 32 KiB total payload per connection. Eviction: oldest first - * (FIFO ring; preopen_evictions++). TTL: 250 ms, swept lazily on each new - * arrival and at flush time. - * - * ── target→tunnel recv loop ───────────────────────────────────────────────── - * recv into a MQ_UDP_MAX_PAYLOAD-byte heap buffer; loop until EAGAIN, bounded - * to 16 packets per wakeup. Persistent EV_READ re-fires for leftovers. - * Per-emit assembly uses one scratch buffer (MQ_UDP_MSG_HDR + MQ_UDP_MAX_PAYLOAD - * bytes) allocated once on - * mq_udp_srv (NOT per packet, NOT on stack) to avoid per-frag heap churn. - */ -#include "proxy/mq_udp_session.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "proxy/mq_defrag.h" -#include "proxy/mq_framebuf.h" -#include "transport/mq_conn.h" -#include "transport/mq_stream.h" -#include "util/mq_log.h" -#include "wire/mq_udp_msg.h" -#include "wire/mq_wire.h" - -/* ═══════════════════════════════════════════════════════════════════════════ - * ─── server role ─── (mq_udp_srv_*) - * - * Per-accepted-connection session table + OPEN handling. See the file header - * above. The client role (mq_udp_cli_*) lives at the bottom of this file. - * ═══════════════════════════════════════════════════════════════════════════ */ - -/* ── constants ─────────────────────────────────────────────────────────────── */ - -/* pre-OPEN buffer caps per connection */ -#define MQ_PREOPEN_MAX_ENTRIES 16 -#define MQ_PREOPEN_MAX_BYTES (32 * 1024) -/* TTL for pre-OPEN entries in milliseconds */ -#define MQ_PREOPEN_TTL_MS 250 - -/* recv loop: max packets per EV_READ wakeup */ -#define MQ_UDP_RECV_LOOP_MAX 16 - -/* maximum UDP payload length (IPv4/IPv6 max datagram payload) */ -#define MQ_UDP_MAX_PAYLOAD 65535 - -/* MSS cache refresh interval in emit-count units (carries per direction count) */ -#define MQ_MSS_REFRESH_INTERVAL 64 - -/* ── monotonic milliseconds (local helper) ────────────────────────────────── - * - * No exported helper exists in this codebase for CLOCK_MONOTONIC-based ms. - * Note: the transport layer uses gettimeofday for xquic timers (see - * mq_transport.c comment) — that constraint only applies to xquic callbacks. - * For our TTL/idle purposes CLOCK_MONOTONIC is correct and preferred. */ -static uint64_t -srv_mono_ms(void) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (uint64_t)ts.tv_sec * 1000u + (uint64_t)(ts.tv_nsec / 1000000); -} - -/* ── pre-OPEN buffer entry ────────────────────────────────────────────────── - * - * Stores the FULL datagram bytes (including 9-byte header) so flush re-runs - * the identical decode→defrag path as a live hit. */ -typedef struct { - uint8_t *data; /* malloc'd copy of full datagram (hdr + payload) */ - size_t len; /* length of data */ - uint32_t session_id; /* decoded from the stored header (for sid-match lookup) */ - uint64_t arrived_ms; /* srv_mono_ms() at insertion time (for TTL) */ -} mq_preopen_entry_t; - -/* Fixed-array FIFO ring of MQ_PREOPEN_MAX_ENTRIES entries. head is the - * oldest entry index, count is the number of live entries. Eviction removes - * the entry at head (FIFO oldest-first). */ -typedef struct { - mq_preopen_entry_t entries[MQ_PREOPEN_MAX_ENTRIES]; - size_t head; /* index of oldest entry */ - size_t count; /* number of live entries (0..MQ_PREOPEN_MAX_ENTRIES) */ - size_t total_bytes; /* sum of all live entry data lengths */ -} mq_preopen_buf_t; - -/* ── per-session state ───────────────────────────────────────────────────── - * - * Lives in the parent's `sessions` array; `used` marks a slot live. - * The 0x02 stream is the session's control handle: its close (or the conn - * close, or an idle expiry) tears the session down. */ -typedef struct { - int used; - uint32_t session_id; - mq_stream_t *stream; /* 0x02 control stream (NULL after close-notify) */ - int fd; /* connected UDP socket */ - struct event *rd_ev; /* persistent EV_READ on fd */ - struct event *idle_ev; /* idle evtimer (re-armed on bidirectional activity) */ - mq_defrag_t *defrag; /* lazy: allocated on first fragmented inbound packet */ - uint64_t idle_timeout_ms; /* effective = min(client requested, server default) */ - uint16_t next_packet_id; /* server→client packet_id counter */ - int reaped; /* reap-once guard (Known risk 7) */ - int graceful; /* an arm-failure RESP(ERROR) was sent with FIN: - * reap must NOT RESET (RESET drops the un-acked RESP) */ - mq_udp_srv_t *owner; /* back-pointer for callbacks */ -} mq_udp_sess_t; - -/* ── mq_udp_srv: per-connection UDP relay state ─────────────────────────── */ - -struct mq_udp_srv { - mq_conn_t *conn; - struct event_base *base; - uint64_t idle_timeout_ms; /* server-configured idle timeout */ - int enabled; /* mirrors !--no-udp */ - int authed; /* set by mq_udp_srv_set_authed on auth success */ - - /* Session table */ - mq_udp_sess_t sessions[MQ_UDP_SRV_MAX_SESSIONS]; - size_t live_count; - - /* Pre-OPEN buffer (one per conn, shared across all session_ids) */ - mq_preopen_buf_t preopen; - - /* MSS cache (conn-level; see file header for policy). - * cached_mss: last fetched value from mq_conn_datagram_mss(); 0 = unknown. - * mss_emit_countdown: counts down from MQ_MSS_REFRESH_INTERVAL; when it - * reaches 0 the cache is refreshed before the next emit. Initialised to - * 0 so the first emit always fetches. */ - size_t cached_mss; - unsigned mss_emit_countdown; - - /* Per-conn scratch buffers (allocated once, reused per operation): - * recv_buf: MQ_UDP_MAX_PAYLOAD bytes for recvfrom() - * emit_buf: MQ_UDP_MSG_HDR + MQ_UDP_MAX_PAYLOAD bytes for assembling - * hdr+slice before datagram_send - * Both allocated in mq_udp_srv_new; freed in mq_udp_srv_free. */ - uint8_t *recv_buf; /* MQ_UDP_MAX_PAYLOAD bytes */ - uint8_t *emit_buf; /* MQ_UDP_MSG_HDR + MQ_UDP_MAX_PAYLOAD bytes */ - - /* Drop / activity counters (uint32_t, %u, design §9.2) */ - uint32_t drops_send_fail; /* datagram_send returned -1, or split failed */ - uint32_t drops_oversize; /* recv'd UDP payload > mss - MQ_UDP_MSG_HDR */ - uint32_t defrag_drops; /* mq_defrag_feed returned -1 */ - uint32_t preopen_evictions; /* pre-OPEN buffer evictions (cap or byte overflow) */ - uint32_t drops_preauth; /* datagrams dropped because !authed or !enabled */ - - /* Observation counters (prove the frag path ran; see file header) */ - uint32_t frags_sent; /* frags emitted from multi-frag splits only */ - uint32_t frags_reassembled; /* completed multi-frag reassemblies */ -}; - -/* ── helpers ─────────────────────────────────────────────────────────────── */ - -static mq_udp_sess_t * -srv_find_session(mq_udp_srv_t *u, uint32_t sid) -{ - for (size_t i = 0; i < MQ_UDP_SRV_MAX_SESSIONS; i++) { - if (u->sessions[i].used && u->sessions[i].session_id == sid) { - return &u->sessions[i]; - } - } - return NULL; -} - -static mq_udp_sess_t * -srv_alloc_session(mq_udp_srv_t *u) -{ - for (size_t i = 0; i < MQ_UDP_SRV_MAX_SESSIONS; i++) { - if (!u->sessions[i].used) { - return &u->sessions[i]; - } - } - return NULL; -} - -/* Re-arm the session's idle timer. Must be called on every bidirectional - * activity (defrag-complete send AND target recv). The timer is edge-triggered - * (one-shot), so evtimer_add every time. */ -static void -srv_idle_rearm(mq_udp_sess_t *sess) -{ - if (!sess->idle_ev) { - return; - } - /* Use the per-session effective timeout (= min(client requested, server - * default), set at open success), NOT the connection-wide server default — - * a client that negotiated a shorter idle timeout must keep it across every - * re-arm, not just the initial one. */ - struct timeval tv; - tv.tv_sec = (time_t)(sess->idle_timeout_ms / 1000); - tv.tv_usec = (suseconds_t)((sess->idle_timeout_ms % 1000) * 1000); - /* evtimer_add on an already-pending timer re-arms it (libevent docs: - * adding a pending event re-schedules it). */ - evtimer_add(sess->idle_ev, &tv); -} - -/* ── MSS cache ────────────────────────────────────────────────────────────── - * - * Returns 0 if mss is not usable (peer unsupported / conn gone). Otherwise - * returns the payload bytes available per datagram frame (mss - MQ_UDP_MSG_HDR). - * Updates u->cached_mss and u->mss_emit_countdown. */ -static size_t -srv_get_mss_payload(mq_udp_srv_t *u) -{ - /* Refresh when cache is empty OR countdown expired */ - if (u->cached_mss == 0 || u->mss_emit_countdown == 0) { - u->cached_mss = mq_conn_datagram_mss(u->conn); - u->mss_emit_countdown = MQ_MSS_REFRESH_INTERVAL; - } - if (u->cached_mss <= MQ_UDP_MSG_HDR) { - return 0; /* not usable */ - } - return u->cached_mss - MQ_UDP_MSG_HDR; -} - -/* ── pre-OPEN buffer operations ─────────────────────────────────────────── */ - -/* Sweep and drop entries older than MQ_PREOPEN_TTL_MS. Called lazily on each - * new arrival and at flush time (no timer needed — design §6.2). */ -static void -srv_preopen_sweep_ttl(mq_preopen_buf_t *pb, uint64_t now_ms) -{ - while (pb->count > 0) { - mq_preopen_entry_t *oldest = &pb->entries[pb->head]; - if (now_ms - oldest->arrived_ms <= MQ_PREOPEN_TTL_MS) { - break; /* remaining entries are newer — FIFO order guarantees this */ - } - free(oldest->data); - oldest->data = NULL; - pb->total_bytes -= oldest->len; - pb->head = (pb->head + 1) % MQ_PREOPEN_MAX_ENTRIES; - pb->count--; - } -} - -/* Insert a new pre-OPEN entry. Evicts oldest if either cap is exceeded. - * data/len are the FULL datagram bytes to copy. session_id and arrived_ms - * are pre-decoded/stamped by the caller. */ -static void -srv_preopen_insert(mq_udp_srv_t *u, uint32_t sid, const uint8_t *data, size_t len, - uint64_t now_ms) -{ - mq_preopen_buf_t *pb = &u->preopen; - - /* A single datagram larger than the whole byte cap can never fit; reject it - * outright (otherwise the eviction loop, gated on count > 0, would store it - * into an empty buffer and blow past the cap). */ - if (len > MQ_PREOPEN_MAX_BYTES) { - u->preopen_evictions++; - return; - } - - /* Evict oldest entries until both caps are satisfied AFTER insertion. - * The byte test is written overflow-safe (len <= cap is guaranteed above, - * so cap - len does not underflow). */ - while (pb->count > 0 && (pb->count >= MQ_PREOPEN_MAX_ENTRIES || - pb->total_bytes > MQ_PREOPEN_MAX_BYTES - len)) { - mq_preopen_entry_t *oldest = &pb->entries[pb->head]; - free(oldest->data); - oldest->data = NULL; - pb->total_bytes -= oldest->len; - pb->head = (pb->head + 1) % MQ_PREOPEN_MAX_ENTRIES; - pb->count--; - u->preopen_evictions++; - } - - /* Allocate and copy. */ - uint8_t *copy = malloc(len); - if (!copy) { - /* OOM: treat as eviction drop (silently skip insertion). */ - u->preopen_evictions++; - return; - } - memcpy(copy, data, len); - - size_t slot = (pb->head + pb->count) % MQ_PREOPEN_MAX_ENTRIES; - pb->entries[slot].data = copy; - pb->entries[slot].len = len; - pb->entries[slot].session_id = sid; - pb->entries[slot].arrived_ms = now_ms; - pb->total_bytes += len; - pb->count++; -} - -/* Free all entries in the pre-OPEN buffer (used at teardown). */ -static void -srv_preopen_free_all(mq_preopen_buf_t *pb) -{ - for (size_t i = 0; i < MQ_PREOPEN_MAX_ENTRIES; i++) { - if (pb->entries[i].data) { - free(pb->entries[i].data); - pb->entries[i].data = NULL; - } - } - pb->head = 0; - pb->count = 0; - pb->total_bytes = 0; -} - -/* ── defrag + send: shared by live-hit path and flush path ───────────────── - * - * Feeds one complete datagram (hdr + payload already decoded into hdr/payload/ - * plen) through defrag. On reassembly completion, sends the reassembled UDP - * packet to the session's fd. Re-arms the idle timer on success. */ -static void -srv_defrag_and_send(mq_udp_srv_t *u, mq_udp_sess_t *sess, const mq_udp_msg_hdr_t *hdr, - const uint8_t *payload, size_t plen) -{ - /* Lazy defrag allocation. */ - if (!sess->defrag) { - sess->defrag = mq_defrag_new(); - if (!sess->defrag) { - u->drops_send_fail++; - return; - } - } - - uint8_t *out = NULL; - size_t out_len = 0; - int rc = mq_defrag_feed(sess->defrag, hdr, payload, plen, &out, &out_len); - - if (rc < 0) { - u->defrag_drops++; - return; - } - if (rc == 0) { - return; /* fragment accepted, packet not yet complete */ - } - - /* rc == 1: packet complete — out is malloc'd, caller must free. */ - - /* Count completed multi-frag reassemblies (proves the defrag path ran). */ - if (hdr->frag_count > 1) { - u->frags_reassembled++; - } - - /* Send to target UDP socket. */ - ssize_t sent = send(sess->fd, out, out_len, 0); - free(out); - if (sent < 0) { - u->drops_send_fail++; - return; - } - - /* Activity: re-arm idle timer. */ - srv_idle_rearm(sess); -} - -/* ── pre-OPEN flush ─────────────────────────────────────────────────────── */ - -/* Called when a session transitions to OPEN-complete (see Task 5.1 flow). - * Walks the pre-OPEN buffer for matching sid, applies TTL filter (250ms), - * feeds live entries through the same decode→defrag path, then removes them - * from the buffer. */ -static void -srv_preopen_flush(mq_udp_srv_t *u, mq_udp_sess_t *sess) -{ - mq_preopen_buf_t *pb = &u->preopen; - uint64_t now_ms = srv_mono_ms(); - - /* Walk all live entries in FIFO order (head … head+count-1 mod MAX). */ - size_t i = 0; - size_t remaining = pb->count; - size_t new_head = pb->head; - size_t new_count = 0; - - /* We compact the ring in place: entries we keep are shifted to the front. */ - mq_preopen_entry_t kept[MQ_PREOPEN_MAX_ENTRIES]; - size_t kept_count = 0; - size_t kept_bytes = 0; - - for (i = 0; i < remaining; i++) { - size_t idx = (pb->head + i) % MQ_PREOPEN_MAX_ENTRIES; - mq_preopen_entry_t *e = &pb->entries[idx]; - - if (e->session_id == sess->session_id) { - /* Apply TTL filter before feeding. */ - if (now_ms - e->arrived_ms <= MQ_PREOPEN_TTL_MS) { - /* Decode and feed. */ - mq_udp_msg_hdr_t hdr; - if (e->len >= MQ_UDP_MSG_HDR && - mq_udp_msg_decode_hdr(e->data, e->len, &hdr) == 0) { - const uint8_t *payload = e->data + MQ_UDP_MSG_HDR; - size_t plen = e->len - MQ_UDP_MSG_HDR; - srv_defrag_and_send(u, sess, &hdr, payload, plen); - } - } - free(e->data); - e->data = NULL; - /* do not keep */ - } else { - /* Keep this entry (different sid). MOVE ownership out of the source - * slot: kept[] takes the data pointer and the source slot is cleared - * so the duplicate owning pointer cannot be double-freed later by - * srv_preopen_free_all (which walks all physical slots). */ - kept[kept_count] = *e; - kept_count++; - kept_bytes += e->len; - e->data = NULL; - e->len = 0; - } - } - - /* Rebuild the ring from the kept entries. */ - for (i = 0; i < kept_count; i++) { - pb->entries[i] = kept[i]; - } - pb->head = 0; - pb->count = kept_count; - pb->total_bytes = kept_bytes; - - (void)new_head; - (void)new_count; -} - -/* ── Send a UDP_SESSION_RESP on the OPEN stream ─────────────────────────── */ - -/* Send a UDP_SESSION_RESP on the OPEN stream. On the error path the codec maps - * MQ_STATUS_ERROR ⇔ codes 1-4; OK ⇔ MQ_UDP_OK. message is empty. - * - * fin selects the close semantics, mirroring mq_server.c srv_send_tcp_resp: - * fin=0 — success: keep the stream open as the session control handle. - * fin=1 — error: close the send direction so the RESP is the stream's final - * frame. The caller MUST NOT mq_stream_close() afterwards: a RESET - * (xqc_stream_close → xqc_send_queue_drop_stream_frame_packets) would - * DROP the not-yet-flushed RESP (engine flush is deferred inside a read - * callback), so the client would see only RESET, never the error code. - * The FIN terminates the stream; the peer's close completes teardown. */ -/* Returns 1 if the full RESP frame was accepted by the stream, 0 otherwise - * (encode failure, flow-control EAGAIN, or short/failed send). Callers that - * rely on a graceful FIN to DELIVER the RESP must fall back to a RESET when - * this returns 0 — otherwise the client hangs waiting for a RESP that never - * went out. */ -static int -srv_send_udp_resp(mq_stream_t *s, mq_status_t status, mq_udp_err_t err, - uint64_t idle_timeout_ms, int fin) -{ - mq_udp_session_resp_t resp; - memset(&resp, 0, sizeof(resp)); - resp.status = status; - resp.error_code = err; - resp.message[0] = '\0'; - resp.message_len = 0; - resp.idle_timeout_ms = idle_timeout_ms; - - uint8_t buf[512]; - int n = mq_encode_udp_session_resp(buf, sizeof(buf), &resp); - if (n < 0) { - MQ_LOGE("mq_udp_srv: encode UDP_SESSION_RESP failed"); - return 0; - } - long sent = mq_stream_send(s, buf, (size_t)n, fin); - if (sent < 0 || (size_t)sent != (size_t)n) { - MQ_LOGW("mq_udp_srv: UDP_SESSION_RESP send short/failed (%ld of %d)", sent, n); - return 0; - } - return 1; -} - -/* ── target resolution ───────────────────────────────────────────────────── */ - -/* Resolve the OPEN target into a connected-able sockaddr (SOCK_DGRAM). Mirrors - * mq_server.c srv_resolve_target but tailored to UDP: it requests - * hints.ai_socktype = SOCK_DGRAM and returns mq_udp_err_t. A minimal copy is - * cleaner than exporting the TCP static (which returns mq_tcp_err_t and would - * force an error-type translation + a new shared header dependency). */ -static int -srv_resolve_udp_target(const mq_udp_session_open_t *open, struct sockaddr_storage *ss, - socklen_t *sslen, mq_udp_err_t *err) -{ - memset(ss, 0, sizeof(*ss)); - if (open->address_type == MQ_ADDR_IPV4) { - if (open->host_len != 4) { - *err = MQ_UDP_DNS_FAILED; - return -1; - } - struct sockaddr_in *sin = (struct sockaddr_in *)ss; - sin->sin_family = AF_INET; - memcpy(&sin->sin_addr, open->host, 4); - sin->sin_port = htons(open->port); - *sslen = sizeof(*sin); - return 0; - } - if (open->address_type == MQ_ADDR_IPV6) { - if (open->host_len != 16) { - *err = MQ_UDP_DNS_FAILED; - return -1; - } - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; - sin6->sin6_family = AF_INET6; - memcpy(&sin6->sin6_addr, open->host, 16); - sin6->sin6_port = htons(open->port); - *sslen = sizeof(*sin6); - return 0; - } - /* DOMAIN: BLOCKING getaddrinfo (accepted debt, design §2). */ - char hostz[MQ_MAX_HOST + 1]; - if (open->host_len > MQ_MAX_HOST) { - *err = MQ_UDP_DNS_FAILED; - return -1; - } - memcpy(hostz, open->host, open->host_len); - hostz[open->host_len] = '\0'; - char portz[8]; - snprintf(portz, sizeof(portz), "%u", (unsigned)open->port); - - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - struct addrinfo *res = NULL; - int gai = getaddrinfo(hostz, portz, &hints, &res); - if (gai != 0 || !res) { - if (res) freeaddrinfo(res); - *err = MQ_UDP_DNS_FAILED; - return -1; - } - memcpy(ss, res->ai_addr, res->ai_addrlen); - *sslen = res->ai_addrlen; - freeaddrinfo(res); - return 0; -} - -/* ── session teardown (reap-once, three callers) ─────────────────────────── */ - -/* Known risk 7: stream-close (client-initiated), idle-timer expiry, and conn - * close all free the same session. The `reaped` guard makes this idempotent; - * fd / events / defrag are freed exactly once. The stream is RESET here ONLY - * when it is still live AND the teardown did not originate from the stream's own - * close-notify (which nulls sess->stream first) AND it is not a graceful - * teardown (an error RESP was already FIN'd — a RESET would drop it), mirroring - * mq_server's flow / srv_data_reap pattern. In every case the callbacks are - * detached first so the eventual close-notify cannot touch the freed slot. */ -static void -srv_reap_session(mq_udp_sess_t *sess) -{ - if (!sess || !sess->used || sess->reaped) { - return; - } - sess->reaped = 1; - MQ_LOGI("mq_udp_srv: session %u closed", sess->session_id); - - if (sess->idle_ev) { - event_free(sess->idle_ev); - sess->idle_ev = NULL; - } - if (sess->rd_ev) { - event_free(sess->rd_ev); - sess->rd_ev = NULL; - } - if (sess->fd >= 0) { - close(sess->fd); - sess->fd = -1; - } - if (sess->defrag) { - mq_defrag_free(sess->defrag); - sess->defrag = NULL; - } - if (sess->stream) { - /* Still live (teardown came from idle expiry or conn close, NOT the - * stream's own close-notify). Drop our callbacks so the eventual - * close-notify (from the FIN / peer close, or conn teardown) cannot fire - * on the freed slot. On the graceful path an error RESP was already sent - * with FIN — a RESET would drop it, so leave the close to the FIN / peer - * (the transport frees the stream at conn teardown either way). */ - mq_stream_set_cbs(sess->stream, NULL, NULL, NULL, NULL); - if (!sess->graceful) { - mq_stream_close(sess->stream); - } - sess->stream = NULL; - } - - mq_udp_srv_t *u = sess->owner; - if (u && u->live_count > 0) { - u->live_count--; - } - /* Clear the slot (used=0 frees it for reuse). */ - memset(sess, 0, sizeof(*sess)); - sess->fd = -1; -} - -/* Idle timer expiry: tear the session down. */ -static void -srv_idle_expired_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - mq_udp_sess_t *sess = (mq_udp_sess_t *)arg; - MQ_LOGI("mq_udp_srv: session %u idle-expired", sess->session_id); - srv_reap_session(sess); -} - -/* ── target→tunnel: EV_READ callback ────────────────────────────────────── */ - -/* emit callback for mq_udp_msg_split: assemble hdr-bytes + slice into - * u->emit_buf and send as one datagram frame. user is mq_udp_srv_t*. */ -typedef struct { - mq_udp_srv_t *u; - uint32_t frags_ok; /* number of frags successfully sent in this split */ -} emit_ctx_t; - -static void -srv_split_emit_cb(const mq_udp_msg_hdr_t *h, const uint8_t *p, size_t len, void *user) -{ - emit_ctx_t *ctx = (emit_ctx_t *)user; - mq_udp_srv_t *u = ctx->u; - - /* Assemble: encode header into emit_buf, then copy slice payload. */ - if (mq_udp_msg_encode_hdr(u->emit_buf, h) != 0) { - u->drops_send_fail++; - return; - } - memcpy(u->emit_buf + MQ_UDP_MSG_HDR, p, len); - - int rc = mq_conn_datagram_send(u->conn, u->emit_buf, MQ_UDP_MSG_HDR + len); - if (rc != 0) { - u->drops_send_fail++; - /* Absolute rule: do NOT abort remaining frags — continue (design §8). */ - return; - } - - ctx->frags_ok++; - /* Decrement MSS countdown on each successful emit. */ - if (u->mss_emit_countdown > 0) { - u->mss_emit_countdown--; - } -} - -/* Persistent EV_READ on a session's UDP socket: recv target→tunnel. */ -static void -srv_udp_readable_cb(evutil_socket_t evfd, short what, void *arg) -{ - (void)what; - mq_udp_sess_t *sess = (mq_udp_sess_t *)arg; - mq_udp_srv_t *u = sess->owner; - - /* Loop up to MQ_UDP_RECV_LOOP_MAX packets per wakeup; EV_PERSIST re-fires - * for any left in the socket buffer. */ - for (int pkts = 0; pkts < MQ_UDP_RECV_LOOP_MAX; pkts++) { - ssize_t n = recv(evfd, u->recv_buf, MQ_UDP_MAX_PAYLOAD, 0); - if (n < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - break; /* socket drained */ - } - /* Other error: count and continue; EV_PERSIST will retry. */ - u->drops_send_fail++; - break; - } - /* n == 0 is NOT EOF on a connected datagram socket — UDP has no EOF; it - * is a legitimate zero-length datagram. Relay it (mq_udp_msg_split emits - * one 0-byte frag), so empty UDP probes/keepalives reach the client. - * The loop is bounded by MQ_UDP_RECV_LOOP_MAX + the EAGAIN break above. */ - size_t plen = (size_t)n; - - /* Get (possibly cached) MSS payload capacity. */ - size_t mss_payload = srv_get_mss_payload(u); - if (mss_payload == 0) { - u->drops_send_fail++; - continue; - } - - /* Reject oversize: plen must fit in one or more fragments but the frag - * count is u8 (max 255). mq_udp_msg_split will guard this too, but we - * also apply the "mss <= MQ_UDP_MSG_HDR" check here: if plen alone > - * mss - MQ_UDP_MSG_HDR per frag the packet cannot be split into ≤255 - * frags. We delegate the 255-frag overflow to mq_udp_msg_split (-1) and - * count that as drops_oversize. */ - emit_ctx_t ctx; - ctx.u = u; - ctx.frags_ok = 0; - - int split_rc = - mq_udp_msg_split(sess->session_id, sess->next_packet_id, u->recv_buf, plen, - mss_payload, srv_split_emit_cb, &ctx); - if (split_rc != 0) { - u->drops_oversize++; - continue; - } - - sess->next_packet_id++; - - /* Count frags_sent for multi-frag splits only (proves the path ran). */ - if (ctx.frags_ok > 1) { - u->frags_sent += ctx.frags_ok; - } - - /* Activity: re-arm idle timer on successful send. */ - if (ctx.frags_ok > 0) { - srv_idle_rearm(sess); - } - } -} - -/* The 0x02 stream's close-notify: the client closed/reset the session control - * stream. Null our pointer (the transport frees the stream after this returns) - * and reap the session. */ -static void -srv_stream_closed_cb(mq_stream_t *s, void *user) -{ - (void)s; - mq_udp_sess_t *sess = (mq_udp_sess_t *)user; - sess->stream = NULL; /* transport frees it after this returns */ - srv_reap_session(sess); -} - -/* The 0x02 stream readable after RESP: drain to avoid stalling it. */ -static void -srv_stream_readable_cb(mq_stream_t *s, void *user) -{ - (void)user; - uint8_t scratch[256]; - int fin = 0; - while (mq_stream_recv(s, scratch, sizeof(scratch), &fin) > 0) {} -} - -/* ── OPEN handling ──────────────────────────────────────────────────────── */ - -/* The 0x02 stream's readable callback during the OPEN phase: accumulate + - * decode UDP_SESSION_OPEN (after the already-consumed 0x02 discriminator), - * then resolve → dial → RESP. The pending-open state lives in a transient - * heap struct hung off the stream's user pointer until the session is created - * (or the stream errors). */ -typedef struct { - mq_udp_srv_t *u; - mq_stream_t *stream; - mq_framebuf_t rx; - int settled; /* OPEN processed: ignore further reads */ -} mq_udp_open_t; - -static void -srv_open_free(mq_udp_open_t *op) -{ - free(op); -} - -/* Pending-open stream close: the client gave up before OPEN completed. Free the - * transient open state (no session was created yet). */ -static void -srv_open_closed_cb(mq_stream_t *s, void *user) -{ - (void)s; - mq_udp_open_t *op = (mq_udp_open_t *)user; - srv_open_free(op); -} - -/* Reject an OPEN before a session exists. Two shapes, mirroring the TCP path's - * graceful-vs-RESET split (mq_server.c srv_data_fail / srv_data_reap): - * - * send_resp != 0 (POLICY_DENIED / DNS_FAILED / SOCKET_FAILED / SESSION_LIMIT): - * send RESP(ERROR, err) with FIN, then detach callbacks and free the - * pending-open state. We do NOT mq_stream_close(): a RESET - * (xqc_send_queue_drop_stream_frame_packets) would drop the not-yet-flushed - * RESP — the engine flush is deferred inside this read callback — so the - * client would see only RESET, never the error code. The FIN terminates the - * send direction; the peer's close (or conn teardown) frees the stream. - * Because the callbacks are detached, the later close-notify cannot fire - * srv_open_closed_cb on the freed op (and the conn never frees op: it is a - * transient owned only here, freed exactly once below). - * - * send_resp == 0 (duplicate-SID / malformed OPEN — no decodable session to - * RESP to): there is nothing to deliver, so RESET is correct and cheaper. - * Detach callbacks, mq_stream_close (RESET), free op. */ -static void -srv_open_reject(mq_udp_open_t *op, int send_resp, mq_udp_err_t err) -{ - if (send_resp) { - /* Graceful: FIN'd RESP, no RESET (would drop the un-acked RESP). But if - * the RESP could not be sent (flow-control/short), the FIN carries no - * RESP and the client would hang — fall back to a RESET so it gets a - * connection-level signal (cli_stream_closed_cb -> on_err -> assoc retry). */ - int ok = srv_send_udp_resp(op->stream, MQ_STATUS_ERROR, err, 0, /*fin=*/1); - mq_stream_set_cbs(op->stream, NULL, NULL, NULL, NULL); - if (!ok) { - mq_stream_close(op->stream); - } - } else { - /* Silent reset: no RESP owed, RESET the stream. */ - mq_stream_set_cbs(op->stream, NULL, NULL, NULL, NULL); - mq_stream_close(op->stream); - } - srv_open_free(op); -} - -/* Finalize a successful OPEN: create the session, send RESP(OK), hand the stream - * over from the pending-open state to the session. - * - * After RESP is sent, flush any pre-OPEN buffered datagrams for this sid. */ -static void -srv_open_success(mq_udp_open_t *op, const mq_udp_session_open_t *open, - struct sockaddr_storage *ss, socklen_t sslen, uint64_t eff_idle_ms) -{ - mq_udp_srv_t *u = op->u; - - int fd = socket(ss->ss_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (fd < 0) { - MQ_LOGW("mq_udp_srv: socket: %s", strerror(errno)); - srv_open_reject(op, /*send_resp=*/1, MQ_UDP_SOCKET_FAILED); - return; - } - int fl = fcntl(fd, F_GETFL, 0); - if (fl < 0 || fcntl(fd, F_SETFL, fl | O_NONBLOCK) < 0 || - connect(fd, (struct sockaddr *)ss, sslen) != 0) { - MQ_LOGW("mq_udp_srv: connect: %s", strerror(errno)); - close(fd); - srv_open_reject(op, /*send_resp=*/1, MQ_UDP_SOCKET_FAILED); - return; - } - - mq_udp_sess_t *sess = srv_alloc_session(u); - if (!sess) { - /* Lost the slot between the cap check and here (cannot happen single- - * threaded, but fail closed). */ - close(fd); - srv_open_reject(op, /*send_resp=*/1, MQ_UDP_SESSION_LIMIT); - return; - } - - memset(sess, 0, sizeof(*sess)); - sess->used = 1; - sess->session_id = open->session_id; - sess->stream = op->stream; - sess->fd = fd; - sess->next_packet_id = 0; - sess->idle_timeout_ms = eff_idle_ms; /* per-session effective (min) value */ - sess->owner = u; - u->live_count++; - - /* Persistent EV_READ on the UDP socket (target→tunnel). */ - sess->rd_ev = event_new(u->base, fd, EV_READ | EV_PERSIST, srv_udp_readable_cb, sess); - if (!sess->rd_ev || event_add(sess->rd_ev, NULL) != 0) { - MQ_LOGE("mq_udp_srv: EV_READ arm failed"); - /* Graceful: FIN'd error RESP so the client learns SOCKET_FAILED, then - * reap WITHOUT a RESET (which would drop the un-acked RESP). The stream - * still carries the pending-open callbacks (user = op, freed below); - * detach them before reap so the later close-notify cannot fire - * srv_open_closed_cb on the freed op. sess->graceful makes reap skip the - * RESET; reap still closes fd + frees rd_ev + decrements live_count + - * nulls the stream + resets the slot. If the RESP could not be sent, - * graceful=0 so reap RESETs instead (client gets a signal, no hang). */ - int ok = srv_send_udp_resp(op->stream, MQ_STATUS_ERROR, MQ_UDP_SOCKET_FAILED, 0, - /*fin=*/1); - mq_stream_set_cbs(op->stream, NULL, NULL, NULL, NULL); - sess->graceful = ok; - srv_reap_session(sess); - srv_open_free(op); - return; - } - - /* Re-point the stream callbacks at the live-session handlers. The session - * now owns the stream; the pending-open state is freed. */ - mq_stream_set_cbs(op->stream, srv_stream_readable_cb, NULL, srv_stream_closed_cb, - sess); - - /* Arm the idle timer to the per-session effective (min) value. Re-armed - * on every bidirectional activity. */ - { - struct timeval tv; - tv.tv_sec = (time_t)(eff_idle_ms / 1000); - tv.tv_usec = (suseconds_t)((eff_idle_ms % 1000) * 1000); - sess->idle_ev = evtimer_new(u->base, srv_idle_expired_cb, sess); - if (!sess->idle_ev || evtimer_add(sess->idle_ev, &tv) != 0) { - MQ_LOGE("mq_udp_srv: idle timer arm failed"); - /* Graceful: FIN'd error RESP so the client learns SOCKET_FAILED, then - * reap WITHOUT a RESET (which would drop the un-acked RESP). - * Callbacks were just re-pointed at the live-session handlers (user = - * sess); detach them before reap so the later close-notify cannot - * fire srv_stream_closed_cb with a reaped sess. sess->graceful makes - * reap skip the RESET and just null the stream. If the RESP could not - * be sent, graceful=0 so reap RESETs instead (client gets a signal). */ - int ok = srv_send_udp_resp(op->stream, MQ_STATUS_ERROR, MQ_UDP_SOCKET_FAILED, - 0, /*fin=*/1); - mq_stream_set_cbs(sess->stream, NULL, NULL, NULL, NULL); - sess->graceful = ok; - srv_reap_session(sess); - srv_open_free(op); - return; - } - } - - srv_send_udp_resp(op->stream, MQ_STATUS_OK, MQ_UDP_OK, eff_idle_ms, /*fin=*/0); - MQ_LOGI("mq_udp_srv: session %u OPEN ok (idle=%llums)", open->session_id, - (unsigned long long)eff_idle_ms); - - srv_open_free(op); - - /* Flush any pre-OPEN buffered datagrams for this sid (FIFO, TTL-filtered). */ - srv_preopen_flush(u, sess); -} - -static void -srv_open_readable_cb(mq_stream_t *s, void *user) -{ - mq_udp_open_t *op = (mq_udp_open_t *)user; - if (op->settled) { - /* Shouldn't fire (callbacks are re-pointed on success / freed on error), - * but drain defensively. */ - uint8_t scratch[256]; - int fin = 0; - while (mq_stream_recv(s, scratch, sizeof(scratch), &fin) > 0) {} - return; - } - - mq_framebuf_fill(s, &op->rx, NULL); - - mq_udp_session_open_t open; - int consumed = mq_decode_udp_session_open(op->rx.buf, op->rx.len, &open); - if (consumed < 0) { - if (op->rx.len >= sizeof(op->rx.buf)) { - MQ_LOGW("mq_udp_srv: UDP_SESSION_OPEN malformed/oversized, resetting"); - op->settled = 1; - /* Malformed OPEN: silent reset (no decodable session to RESP to). */ - srv_open_reject(op, /*send_resp=*/0, MQ_UDP_OK); - } - return; /* need more bytes */ - } - - /* Invariant: the 0x02 control stream carries exactly ONE OPEN message. - * After that single frame, this stream is a pure control handle — any - * trailing or subsequent bytes are intentionally ignored. UDP payload - * rides DATAGRAM frames, not this stream; stream close signals session - * close. `consumed` therefore need not be acted upon. */ - (void)consumed; - - /* OPEN decoded — this stream is now settled regardless of outcome. */ - op->settled = 1; - mq_udp_srv_t *u = op->u; - - /* Duplicate-SID detection (decode-direct, BEFORE socket creation): if the - * session_id is already live, RESET the new stream with NO RESP and leave - * the existing session entirely untouched. */ - if (srv_find_session(u, open.session_id) != NULL) { - MQ_LOGW("mq_udp_srv: duplicate session_id %u, resetting new stream (no RESP)", - open.session_id); - srv_open_reject(op, /*send_resp=*/0, MQ_UDP_OK); - return; - } - - /* Policy gate: --no-udp ⇒ deny. */ - if (!u->enabled) { - srv_open_reject(op, /*send_resp=*/1, MQ_UDP_POLICY_DENIED); - return; - } - - /* Session-table cap. */ - if (u->live_count >= MQ_UDP_SRV_MAX_SESSIONS) { - srv_open_reject(op, /*send_resp=*/1, MQ_UDP_SESSION_LIMIT); - return; - } - - /* Resolve (blocking getaddrinfo for DOMAIN). */ - struct sockaddr_storage ss; - socklen_t sslen = 0; - mq_udp_err_t rerr = MQ_UDP_DNS_FAILED; - if (srv_resolve_udp_target(&open, &ss, &sslen, &rerr) != 0) { - srv_open_reject(op, /*send_resp=*/1, rerr); - return; - } - - /* Effective idle timeout = min(client requested or server default if 0, - * server setting). */ - uint64_t requested = open.idle_timeout_ms ? open.idle_timeout_ms : u->idle_timeout_ms; - uint64_t eff = requested < u->idle_timeout_ms ? requested : u->idle_timeout_ms; - - srv_open_success(op, &open, &ss, sslen, eff); -} - -/* ── public API ─────────────────────────────────────────────────────────── */ - -static void -mq_udp_srv_dump_stats(mq_udp_srv_t *u) -{ - if (!u) { - return; - } - MQ_LOGI("mq_udp_srv: stats frags_sent=%u frags_reassembled=%u " - "drops_send_fail=%u drops_oversize=%u defrag_drops=%u " - "preopen_evictions=%u drops_preauth=%u", - u->frags_sent, u->frags_reassembled, u->drops_send_fail, u->drops_oversize, - u->defrag_drops, u->preopen_evictions, u->drops_preauth); -} - -mq_udp_srv_t * -mq_udp_srv_new(mq_conn_t *c, struct event_base *base, uint64_t idle_timeout_ms, - int enabled) -{ - mq_udp_srv_t *u = calloc(1, sizeof(*u)); - if (!u) { - return NULL; - } - u->conn = c; - u->base = base; - u->idle_timeout_ms = idle_timeout_ms; - u->enabled = enabled; - /* authed starts 0: the auth gate blocks datagrams until auth succeeds. */ - - for (size_t i = 0; i < MQ_UDP_SRV_MAX_SESSIONS; i++) { - u->sessions[i].fd = -1; - } - - /* MSS cache: initialised to 0 (unknown); mss_emit_countdown = 0 so the - * first emit triggers a fetch. */ - u->cached_mss = 0; - u->mss_emit_countdown = 0; - - /* Allocate scratch buffers (once per conn; reused per packet). */ - u->recv_buf = malloc(MQ_UDP_MAX_PAYLOAD); - if (!u->recv_buf) { - free(u); - return NULL; - } - u->emit_buf = malloc(MQ_UDP_MSG_HDR + MQ_UDP_MAX_PAYLOAD); - if (!u->emit_buf) { - free(u->recv_buf); - free(u); - return NULL; - } - - return u; -} - -void -mq_udp_srv_free(mq_udp_srv_t *u) -{ - if (!u) { - return; - } - /* Dump stats before teardown (exactly once per conn). */ - mq_udp_srv_dump_stats(u); - for (size_t i = 0; i < MQ_UDP_SRV_MAX_SESSIONS; i++) { - if (u->sessions[i].used) { - srv_reap_session(&u->sessions[i]); - } - } - srv_preopen_free_all(&u->preopen); - free(u->recv_buf); - free(u->emit_buf); - free(u); -} - -void -mq_udp_srv_set_authed(mq_udp_srv_t *u, int authed) -{ - if (u) { - u->authed = authed; - } -} - -mq_udp_srv_counters_t -mq_udp_srv_counters(const mq_udp_srv_t *u) -{ - mq_udp_srv_counters_t c; - memset(&c, 0, sizeof(c)); - if (!u) { - return c; - } - c.frags_sent = u->frags_sent; - c.frags_reassembled = u->frags_reassembled; - c.drops_send_fail = u->drops_send_fail; - c.drops_oversize = u->drops_oversize; - c.defrag_drops = u->defrag_drops; - c.preopen_evictions = u->preopen_evictions; - c.drops_preauth = u->drops_preauth; - return c; -} - -void -mq_udp_srv_attach_stream(mq_udp_srv_t *u, mq_stream_t *s, const uint8_t *carry, - size_t carry_len) -{ - mq_udp_open_t *op = calloc(1, sizeof(*op)); - if (!op) { - MQ_LOGE("mq_udp_srv: OOM allocating pending-open state"); - mq_stream_close(s); - return; - } - op->u = u; - op->stream = s; - op->settled = 0; - - /* Seed the OPEN framebuf with the carry-over bytes the server's stream-type - * dispatch already pulled off the stream (the OPEN body that trailed the - * 0x02 discriminator — client sends them in one send). Bounded by - * MQ_FRAMEBUF_CAP; clamp defensively. */ - if (carry && carry_len > 0) { - size_t n = carry_len > sizeof(op->rx.buf) ? sizeof(op->rx.buf) : carry_len; - memcpy(op->rx.buf, carry, n); - op->rx.len = n; - } - - mq_stream_set_cbs(s, srv_open_readable_cb, NULL, srv_open_closed_cb, op); - - /* Try decoding immediately (carry-over may already hold the full OPEN); - * else accumulate more from the stream on later readability. */ - srv_open_readable_cb(s, op); -} - -/* ── tunnel→target datagram path ───────────────────────────────────────── */ - -void -mq_udp_srv_on_datagram(mq_udp_srv_t *u, const uint8_t *data, size_t len) -{ - if (!u) { - return; - } - - /* Auth gate: the 0x02 stream's pre-auth guard does NOT apply to DATAGRAM - * frames (they arrive on the connection-level callback). This is the sole - * auth boundary for inbound datagrams. Also gate on !enabled so pre-auth - * datagrams on a --no-udp connection are counted separately (they never - * should arrive, but fail closed). */ - if (!u->authed || !u->enabled) { - u->drops_preauth++; - return; - } - - /* Decode the 9-byte header. */ - mq_udp_msg_hdr_t hdr; - if (mq_udp_msg_decode_hdr(data, len, &hdr) != 0) { - /* Malformed framing: no session to attribute and §9.2 defines no - * counter for structurally unparseable input → silent drop per the - * §9.2 invalid-input pattern. */ - return; - } - - const uint8_t *payload = data + MQ_UDP_MSG_HDR; - size_t plen = len - MQ_UDP_MSG_HDR; - - /* Session lookup. */ - mq_udp_sess_t *sess = srv_find_session(u, hdr.session_id); - if (sess) { - /* Live session hit: defrag → send → re-arm idle. */ - srv_defrag_and_send(u, sess, &hdr, payload, plen); - } else { - /* No live session yet — OPEN may still be in flight. - * Pre-OPEN buffer: sweep stale entries first, then insert (COPY). */ - uint64_t now_ms = srv_mono_ms(); - srv_preopen_sweep_ttl(&u->preopen, now_ms); - srv_preopen_insert(u, hdr.session_id, data, len, now_ms); - } -} - -/* ═══════════════════════════════════════════════════════════════════════════ - * ─── client role ─── (mq_udp_cli_*) - * - * Per-client-connection session table behind the mq_udp_open_fn boundary - * (mq_ingress.h). One mq_udp_cli_t per client mq_conn (mq_client owns it). - * Mirrors the server role's session-table / mss-cache / defrag / reap-once - * patterns, but the session is keyed by a client-assigned session_id and the - * 0x02 stream is locally opened (sending OPEN) rather than accepted. - * - * See mq_udp_session.h "client role" block for the lifecycle contract. - * ═══════════════════════════════════════════════════════════════════════════ */ - -/* Client-side session cap (design §6.1: 1024 per connection). */ -#define MQ_UDP_CLI_MAX_SESSIONS 1024 - -/* Per-session pre-auth send queue bound (design §6.1: 8 datagrams / 8 KiB). - * Holds payloads enqueued before the 0x02 stream is issued; flushed on auth - * success. Overflow drops the OLDEST (UDP semantics) + counts. */ -#define MQ_UDP_CLI_SENDQ_MAX_MSGS 8 -#define MQ_UDP_CLI_SENDQ_MAX_BYTES (8 * 1024) - -/* ── per-session state (client) ────────────────────────────────────────────── - * - * Lives in the parent's `sessions` array; `used` marks a slot live. State is - * implicit: - * - stream == NULL && !issued → pending-auth (OPEN not yet sent) - * - stream != NULL → open-sent (optimistic send allowed; RESP OK - * merely confirms, RESP error → on_err) - * The `closing` flag is the callback-suppression latch (UAF prevention): once - * set, no further user on_rx / on_err fire and the slot is being reaped. */ -typedef struct { - uint8_t *data; - size_t len; -} mq_cli_qmsg_t; - -typedef struct { - int used; - uint32_t session_id; - mq_stream_t *stream; /* 0x02 control stream (NULL until issued / after close) */ - uint16_t next_packet_id; - mq_defrag_t *defrag; /* lazy: allocated on first fragmented inbound packet */ - - int issued; /* the 0x02 stream was opened + OPEN sent */ - int closing; /* callback-suppression latch (reap in progress) */ - int reaped; /* reap-once guard */ - - /* OPEN parameters captured at open() time (replayed at stream issuance). */ - mq_addr_type_t atype; - uint8_t host[MQ_MAX_HOST]; - size_t host_len; - uint16_t port; - - /* RESP accumulation on the 0x02 stream (header phase, until RESP decodes). */ - mq_framebuf_t rx; - int resp_decoded; /* RESP OK seen — further stream bytes ignored */ - - /* Pre-auth send queue (per-session ring; flushed at stream issuance). */ - mq_cli_qmsg_t sendq[MQ_UDP_CLI_SENDQ_MAX_MSGS]; - size_t sq_head; - size_t sq_count; - size_t sq_bytes; - - /* User callbacks (the mq_udp_open_fn boundary). */ - mq_udp_rx_fn on_rx; - mq_udp_err_fn on_err; - void *user; - - struct mq_udp_cli *owner; /* back-pointer */ -} mq_udp_cli_sess_t; - -/* ── mq_udp_cli: per-connection client UDP relay state ───────────────────── */ - -struct mq_udp_cli { - mq_conn_t *conn; - struct event_base *base; - - int authed; /* auth settled and succeeded */ - int available; /* UDP relay capability confirmed (feature bit + mss > 0) */ - int settled; /* on_auth glue ran once (drain/fail done) */ - - uint32_t next_sid; /* client-assigned session_id counter */ - - mq_udp_cli_sess_t sessions[MQ_UDP_CLI_MAX_SESSIONS]; - size_t live_count; - - /* MSS cache (conn-level; see server-role policy — identical rationale: - * mq_conn_datagram_mss() does a per-call calloc/free, never call per send). */ - size_t cached_mss; - unsigned mss_emit_countdown; - - /* Per-conn split scratch (allocated once; reused per frag emit). */ - uint8_t *emit_buf; /* MQ_UDP_MSG_HDR + MQ_UDP_MAX_PAYLOAD bytes */ - - /* Drop / activity counters (design §9.2; client-side observability). */ - uint32_t drops_send_fail; - uint32_t drops_oversize; - uint32_t defrag_drops; - uint32_t sendq_evictions; /* pre-auth send-queue overflow drops */ - uint32_t frags_sent; - uint32_t frags_reassembled; -}; - -/* ── session table helpers (client) ──────────────────────────────────────── */ - -static mq_udp_cli_sess_t * -cli_find_session(mq_udp_cli_t *u, uint32_t sid) -{ - for (size_t i = 0; i < MQ_UDP_CLI_MAX_SESSIONS; i++) { - if (u->sessions[i].used && u->sessions[i].session_id == sid) { - return &u->sessions[i]; - } - } - return NULL; -} - -static mq_udp_cli_sess_t * -cli_alloc_session(mq_udp_cli_t *u) -{ - for (size_t i = 0; i < MQ_UDP_CLI_MAX_SESSIONS; i++) { - if (!u->sessions[i].used) { - return &u->sessions[i]; - } - } - return NULL; -} - -/* Assign the next free session_id (u32 counter, skipping live ids on wrap — - * design §6.1; the live set is ≤ 1024 so a free id always exists). */ -static uint32_t -cli_next_sid(mq_udp_cli_t *u) -{ - for (;;) { - uint32_t sid = u->next_sid++; - if (!cli_find_session(u, sid)) { - return sid; - } - } -} - -/* ── pre-auth send queue ─────────────────────────────────────────────────── */ - -static void -cli_sendq_clear(mq_udp_cli_t *u, mq_udp_cli_sess_t *sess) -{ - for (size_t i = 0; i < MQ_UDP_CLI_SENDQ_MAX_MSGS; i++) { - free(sess->sendq[i].data); - sess->sendq[i].data = NULL; - sess->sendq[i].len = 0; - } - sess->sq_head = 0; - sess->sq_count = 0; - sess->sq_bytes = 0; - (void)u; -} - -/* Drop the oldest queued message (FIFO eviction). */ -static void -cli_sendq_drop_oldest(mq_udp_cli_t *u, mq_udp_cli_sess_t *sess) -{ - if (sess->sq_count == 0) { - return; - } - mq_cli_qmsg_t *m = &sess->sendq[sess->sq_head]; - sess->sq_bytes -= m->len; - free(m->data); - m->data = NULL; - m->len = 0; - sess->sq_head = (sess->sq_head + 1) % MQ_UDP_CLI_SENDQ_MAX_MSGS; - sess->sq_count--; - u->sendq_evictions++; -} - -/* Enqueue a payload copy, evicting oldest until both caps are satisfied. */ -static void -cli_sendq_push(mq_udp_cli_t *u, mq_udp_cli_sess_t *sess, const uint8_t *payload, - size_t len) -{ - /* A single message larger than the byte cap can never fit: drop it. */ - if (len > MQ_UDP_CLI_SENDQ_MAX_BYTES) { - u->sendq_evictions++; - return; - } - while (sess->sq_count > 0 && (sess->sq_count >= MQ_UDP_CLI_SENDQ_MAX_MSGS || - sess->sq_bytes + len > MQ_UDP_CLI_SENDQ_MAX_BYTES)) { - cli_sendq_drop_oldest(u, sess); - } - uint8_t *copy = malloc(len ? len : 1); - if (!copy) { - u->sendq_evictions++; - return; - } - if (len) { - memcpy(copy, payload, len); - } - size_t slot = (sess->sq_head + sess->sq_count) % MQ_UDP_CLI_SENDQ_MAX_MSGS; - sess->sendq[slot].data = copy; - sess->sendq[slot].len = len; - sess->sq_bytes += len; - sess->sq_count++; -} - -/* ── MSS cache (client; mirrors srv_get_mss_payload) ──────────────────────── */ - -static size_t -cli_get_mss_payload(mq_udp_cli_t *u) -{ - if (u->cached_mss == 0 || u->mss_emit_countdown == 0) { - u->cached_mss = mq_conn_datagram_mss(u->conn); - u->mss_emit_countdown = MQ_MSS_REFRESH_INTERVAL; - } - if (u->cached_mss <= MQ_UDP_MSG_HDR) { - return 0; /* not usable */ - } - return u->cached_mss - MQ_UDP_MSG_HDR; -} - -/* ── tunnel send (split → datagram) ────────────────────────────────────────── - * - * The server role's emit_ctx_t carries an mq_udp_srv_t*; to reuse - * mq_udp_msg_split for the client without coupling the two role structs, the - * client uses its own tiny ctx + emit callback. */ -typedef struct { - mq_udp_cli_t *u; - uint32_t frags_ok; -} cli_emit_ctx_t; - -static void -cli_emit_real(const mq_udp_msg_hdr_t *h, const uint8_t *p, size_t len, void *user) -{ - cli_emit_ctx_t *ctx = (cli_emit_ctx_t *)user; - mq_udp_cli_t *u = ctx->u; - - if (mq_udp_msg_encode_hdr(u->emit_buf, h) != 0) { - u->drops_send_fail++; - return; - } - memcpy(u->emit_buf + MQ_UDP_MSG_HDR, p, len); - - int rc = mq_conn_datagram_send(u->conn, u->emit_buf, MQ_UDP_MSG_HDR + len); - if (rc != 0) { - u->drops_send_fail++; - return; /* do NOT abort remaining frags (design §8) */ - } - ctx->frags_ok++; - if (u->mss_emit_countdown > 0) { - u->mss_emit_countdown--; - } -} - -/* Split + send one UDP payload on the tunnel for `sess`. */ -static void -cli_send_now(mq_udp_cli_t *u, mq_udp_cli_sess_t *sess, const uint8_t *payload, size_t len) -{ - size_t mss_payload = cli_get_mss_payload(u); - if (mss_payload == 0) { - u->drops_send_fail++; - return; - } - cli_emit_ctx_t ctx = {.u = u, .frags_ok = 0}; - int rc = mq_udp_msg_split(sess->session_id, sess->next_packet_id, payload, len, - mss_payload, cli_emit_real, &ctx); - if (rc != 0) { - u->drops_oversize++; - return; - } - sess->next_packet_id++; - if (ctx.frags_ok > 1) { - u->frags_sent += ctx.frags_ok; - } -} - -/* ── session reap (client; reap-once) ──────────────────────────────────────── - * - * Frees the session's resources (stream + defrag + send queue) exactly once and - * clears the slot. Callbacks are detached BEFORE any stream close so a deferred - * close-notify cannot fire on the reaped slot. Does NOT itself fire on_err — - * callers decide whether/which on_err to dispatch first. */ -static void -cli_reap_session(mq_udp_cli_sess_t *sess) -{ - if (!sess || !sess->used || sess->reaped) { - return; - } - sess->reaped = 1; - sess->closing = 1; - - mq_udp_cli_t *u = sess->owner; - - if (sess->stream) { - /* Detach our callbacks first (UAF: the eventual close-notify must not - * touch the reaped slot), then RESET the stream. Unlike the server's - * graceful FIN'd-RESP path, the client never owes a RESP on its 0x02 - * stream, so a RESET is always correct here. */ - mq_stream_set_cbs(sess->stream, NULL, NULL, NULL, NULL); - mq_stream_close(sess->stream); - sess->stream = NULL; - } - if (sess->defrag) { - mq_defrag_free(sess->defrag); - sess->defrag = NULL; - } - cli_sendq_clear(u, sess); - - if (u && u->live_count > 0) { - u->live_count--; - } - memset(sess, 0, sizeof(*sess)); -} - -/* Dispatch on_err exactly once (suppressing on closing sessions) then reap. - * Sets `closing` BEFORE the callback so a re-entrant close() during the - * callback is swallowed (the callback-suppression contract). */ -static void -cli_fail_session(mq_udp_cli_sess_t *sess, mq_udp_err_t err) -{ - if (!sess || !sess->used || sess->closing) { - return; - } - sess->closing = 1; - mq_udp_err_fn cb = sess->on_err; - void *user = sess->user; - if (cb) { - cb(sess, err, user); - } - cli_reap_session(sess); -} - -/* ── 0x02 stream callbacks (RESP / close) ────────────────────────────────── */ - -/* Flush the per-session pre-auth send queue (FIFO) now that the stream is up. */ -static void -cli_sendq_flush(mq_udp_cli_t *u, mq_udp_cli_sess_t *sess) -{ - while (sess->sq_count > 0) { - mq_cli_qmsg_t *m = &sess->sendq[sess->sq_head]; - cli_send_now(u, sess, m->data, m->len); - free(m->data); - m->data = NULL; - sess->sq_bytes -= m->len; - m->len = 0; - sess->sq_head = (sess->sq_head + 1) % MQ_UDP_CLI_SENDQ_MAX_MSGS; - sess->sq_count--; - } -} - -static void -cli_stream_readable_cb(mq_stream_t *s, void *user) -{ - mq_udp_cli_sess_t *sess = (mq_udp_cli_sess_t *)user; - if (sess->closing) { - return; - } - if (sess->resp_decoded) { - /* control stream carries only the RESP; no tail expected (design §6.1) - * — leftover rx bytes are intentionally abandoned. */ - uint8_t scratch[256]; - int fin = 0; - while (mq_stream_recv(s, scratch, sizeof(scratch), &fin) > 0) {} - return; - } - - int fin = 0; - if (mq_framebuf_fill(s, &sess->rx, &fin) < 0) { - /* Hard error / RESET before a RESP: normal stream close. */ - cli_fail_session(sess, MQ_UDP_CLOSED); - return; - } - - mq_udp_session_resp_t resp; - memset(&resp, 0, sizeof(resp)); - int consumed = mq_decode_udp_session_resp(sess->rx.buf, sess->rx.len, &resp); - if (consumed < 0) { - if (sess->rx.len >= sizeof(sess->rx.buf) || fin) { - /* Oversized, or FIN without a decodable RESP: treat as a normal - * (non-error-RESP) close → MQ_UDP_CLOSED. */ - cli_fail_session(sess, MQ_UDP_CLOSED); - } - return; /* otherwise need more bytes */ - } - - if (resp.status != MQ_STATUS_OK) { - /* Decoded RESP error (wire code 1-4): report the wire code once, then - * reap. Do NOT RESET the stream from cli_reap here beyond the normal - * detach — the server already FIN'd it; reap's RESET on an already-FIN'd - * stream is harmless (no un-acked RESP to drop on the client side). */ - cli_fail_session(sess, resp.error_code); - return; - } - - /* RESP OK: nothing visible to the user (already optimistic). Mark settled. */ - sess->resp_decoded = 1; -} - -static void -cli_stream_closed_cb(mq_stream_t *s, void *user) -{ - (void)s; - mq_udp_cli_sess_t *sess = (mq_udp_cli_sess_t *)user; - sess->stream = NULL; /* transport frees it after this returns */ - if (sess->closing) { - return; /* late close-notify on an already-reaping session: swallow */ - } - /* Stream closed WITHOUT a decoded RESP error (FIN / RESET from idle timeout, - * server stream close, etc.) → MQ_UDP_CLOSED. If a RESP error was already - * decoded, cli_fail_session already ran and set closing (handled above). */ - cli_fail_session(sess, MQ_UDP_CLOSED); -} - -/* ── stream issuance (issue 0x02 + send OPEN, then flush queued sends) ────── */ - -/* Issue the 0x02 stream for a pending session: open a bidi stream, send - * [varint 0x02][encoded OPEN] in one send (mirrors mq_client's TCP path), then - * flush the pre-auth send queue. On failure fail the session (POLICY_DENIED is - * wrong here — a local stream-open failure is closer to a transport close, so - * MQ_UDP_CLOSED). Returns 0 on success, -1 on failure (session already reaped).*/ -static int -cli_issue_stream(mq_udp_cli_t *u, mq_udp_cli_sess_t *sess) -{ - mq_stream_t *s = mq_conn_open_stream(u->conn); - if (!s) { - MQ_LOGE("mq_udp_cli: open 0x02 stream failed"); - cli_fail_session(sess, MQ_UDP_CLOSED); - return -1; - } - - mq_udp_session_open_t open; - memset(&open, 0, sizeof(open)); - open.session_id = sess->session_id; - open.flags = 0; - open.address_type = sess->atype; - memcpy(open.host, sess->host, sess->host_len); - open.host_len = sess->host_len; - open.port = sess->port; - open.idle_timeout_ms = 0; /* server default */ - - uint8_t buf[512]; - buf[0] = (uint8_t)MQ_STREAM_TYPE_UDP_SESSION; - int n = mq_encode_udp_session_open(buf + 1, sizeof(buf) - 1, &open); - if (n < 0) { - MQ_LOGE("mq_udp_cli: encode UDP_SESSION_OPEN failed"); - mq_stream_set_cbs(s, NULL, NULL, NULL, NULL); - mq_stream_close(s); - cli_fail_session(sess, MQ_UDP_CLOSED); - return -1; - } - - sess->stream = s; - sess->issued = 1; - mq_stream_set_cbs(s, cli_stream_readable_cb, NULL, cli_stream_closed_cb, sess); - - /* No FIN: keep the stream open for the RESP + as the control handle. - * The OPEN frame must be accepted in full: there is no on_writable retry - * path for the control handshake, and sending datagrams while the server - * never received OPEN would just leak them into its pre-OPEN buffer (where - * they expire) and leave a zombie session. If the send is short/blocked - * (EAGAIN -> 0) or fails (-1), fail closed with MQ_UDP_CLOSED. The assoc - * keeps no negative cache for MQ_UDP_CLOSED, so the next datagram naturally - * retries with a fresh session. */ - long sent = mq_stream_send(s, buf, (size_t)(1 + n), /*fin=*/0); - if (sent != (long)(1 + n)) { - MQ_LOGW("mq_udp_cli: OPEN send short/failed (%ld of %d), failing session", sent, - 1 + n); - cli_fail_session(sess, MQ_UDP_CLOSED); - return -1; - } - - /* Flush any payloads buffered while pending-auth (optimistic send). */ - cli_sendq_flush(u, sess); - return 0; -} - -/* ── boundary: open / send / close ───────────────────────────────────────── */ - -void * -mq_udp_cli_open(void *core, const uint8_t *host, size_t host_len, mq_addr_type_t atype, - uint16_t port, mq_udp_rx_fn on_rx, mq_udp_err_fn on_err, void *user) -{ - mq_udp_cli_t *u = (mq_udp_cli_t *)core; - if (!u || !host || host_len == 0 || host_len > MQ_MAX_HOST) { - return NULL; - } - /* Conn already gone (settled with no live conn): fail immediately. */ - if (u->settled && !u->authed) { - return NULL; - } - if (u->live_count >= MQ_UDP_CLI_MAX_SESSIONS) { - return NULL; /* 1024-session cap */ - } - - mq_udp_cli_sess_t *sess = cli_alloc_session(u); - if (!sess) { - return NULL; - } - memset(sess, 0, sizeof(*sess)); - sess->used = 1; - sess->owner = u; - sess->session_id = cli_next_sid(u); - sess->atype = atype; - memcpy(sess->host, host, host_len); - sess->host_len = host_len; - sess->port = port; - sess->on_rx = on_rx; - sess->on_err = on_err; - sess->user = user; - u->live_count++; - - if (u->authed && u->available) { - /* Authed + capable: issue the stream + OPEN immediately (optimistic). */ - if (cli_issue_stream(u, sess) != 0) { - return NULL; /* session already reaped (cli_fail_session) */ - } - return sess; - } - if (u->settled && !u->available) { - /* Authed but capability denied: fail closed right away. */ - cli_fail_session(sess, MQ_UDP_POLICY_DENIED); - return NULL; - } - /* Pre-auth: hold the session; stream issuance is deferred to mq_udp_cli_on_auth. - * Sends in the meantime are buffered in the per-session send queue. */ - return sess; -} - -void -mq_udp_cli_send(void *session, const uint8_t *payload, size_t len) -{ - mq_udp_cli_sess_t *sess = (mq_udp_cli_sess_t *)session; - if (!sess || !sess->used || sess->closing) { - return; - } - mq_udp_cli_t *u = sess->owner; - if (sess->issued && sess->stream) { - cli_send_now(u, sess, payload, len); - } else { - /* Pre-auth: buffer for flush at stream issuance (oldest-drop on overflow). */ - cli_sendq_push(u, sess, payload, len); - } -} - -void -mq_udp_cli_close(void *session) -{ - mq_udp_cli_sess_t *sess = (mq_udp_cli_sess_t *)session; - if (!sess || !sess->used) { - return; - } - /* Caller-initiated: no on_err. Detach + reap synchronously (the - * callback-suppression contract: no on_rx/on_err after close returns). */ - sess->closing = 1; - cli_reap_session(sess); -} - -/* ── server→tunnel datagram dispatch ─────────────────────────────────────── */ - -void -mq_udp_cli_on_datagram(mq_udp_cli_t *u, const uint8_t *data, size_t len) -{ - if (!u) { - return; - } - mq_udp_msg_hdr_t hdr; - if (mq_udp_msg_decode_hdr(data, len, &hdr) != 0) { - return; /* malformed framing: silent drop */ - } - - mq_udp_cli_sess_t *sess = cli_find_session(u, hdr.session_id); - if (!sess || sess->closing) { - /* Unknown sid (or a session being reaped): silent drop. The client has - * no pre-OPEN buffer — server→client datagrams only flow for sessions - * the client itself opened (design §6.2). */ - MQ_LOGD("mq_udp_cli: datagram for unknown/closing sid %u dropped", - hdr.session_id); - return; - } - - const uint8_t *payload = data + MQ_UDP_MSG_HDR; - size_t plen = len - MQ_UDP_MSG_HDR; - - if (!sess->defrag) { - sess->defrag = mq_defrag_new(); - if (!sess->defrag) { - u->defrag_drops++; - return; - } - } - - uint8_t *out = NULL; - size_t out_len = 0; - int rc = mq_defrag_feed(sess->defrag, &hdr, payload, plen, &out, &out_len); - if (rc < 0) { - u->defrag_drops++; - return; - } - if (rc == 0) { - return; /* fragment accepted, packet not yet complete */ - } - /* rc == 1: reassembly complete (out malloc'd, we free). */ - if (hdr.frag_count > 1) { - u->frags_reassembled++; - } - if (sess->on_rx) { - sess->on_rx(out, out_len, sess->user); - } - free(out); -} - -/* ── auth / conn-close glue ──────────────────────────────────────────────── */ - -void -mq_udp_cli_on_auth(mq_udp_cli_t *u, int ok, int available) -{ - if (!u || u->settled) { - return; - } - u->settled = 1; - u->authed = ok ? 1 : 0; - u->available = (ok && available) ? 1 : 0; - - if (ok && available) { - /* Capability confirmed: issue streams + OPEN for every pending session, - * then each session's queued sends flush inside cli_issue_stream. Iterate - * defensively — cli_issue_stream may reap a session on local failure. */ - for (size_t i = 0; i < MQ_UDP_CLI_MAX_SESSIONS; i++) { - mq_udp_cli_sess_t *sess = &u->sessions[i]; - if (sess->used && !sess->issued && !sess->closing) { - cli_issue_stream(u, sess); - } - } - return; - } - - /* Auth failure OR capability denied: fail-close every pending session. - * Auth failure is a remote-caused policy-level denial → MQ_UDP_POLICY_DENIED - * (NOT MQ_UDP_CLOSED, which the assoc reserves for non-OPEN-failure closes). - * Capability denied (authed, no feature bit / mss==0) → MQ_UDP_POLICY_DENIED - * (design §9.1: fail-closed, no negative cache). */ - for (size_t i = 0; i < MQ_UDP_CLI_MAX_SESSIONS; i++) { - mq_udp_cli_sess_t *sess = &u->sessions[i]; - if (sess->used && !sess->closing) { - cli_fail_session(sess, MQ_UDP_POLICY_DENIED); - } - } -} - -void -mq_udp_cli_on_conn_close(mq_udp_cli_t *u) -{ - if (!u) { - return; - } - u->settled = 1; - /* The conn is gone: every live/pending session terminates. Fail each with - * MQ_UDP_CLOSED (a non-OPEN-failure close — the assoc must not negative-cache - * on this). The stream pointers are stale once the transport tears the conn - * down; cli_reap detaches callbacks before any close, and on conn teardown - * the transport frees the streams — so do not touch sess->stream here beyond - * the detach inside cli_reap. */ - u->conn = NULL; - for (size_t i = 0; i < MQ_UDP_CLI_MAX_SESSIONS; i++) { - mq_udp_cli_sess_t *sess = &u->sessions[i]; - if (sess->used && !sess->closing) { - cli_fail_session(sess, MQ_UDP_CLOSED); - } - } -} - -void -mq_udp_cli_reset(mq_udp_cli_t *u) -{ - if (!u) { - return; - } - /* Precondition: session table must be empty (on_conn_close reaped it). */ - if (u->live_count != 0) { - MQ_LOGW("mq_udp_cli_reset: called with live_count=%zu (expected 0)", - u->live_count); - } - u->settled = 0; - u->authed = 0; - u->available = 0; - u->cached_mss = 0; - u->mss_emit_countdown = 0; -} - -void -mq_udp_cli_set_conn(mq_udp_cli_t *u, mq_conn_t *conn) -{ - if (u) { - u->conn = conn; - } -} - -/* ── lifecycle ───────────────────────────────────────────────────────────── */ - -mq_udp_cli_t * -mq_udp_cli_new(mq_conn_t *c, struct event_base *base) -{ - mq_udp_cli_t *u = calloc(1, sizeof(*u)); - if (!u) { - return NULL; - } - u->conn = c; - u->base = base; - u->next_sid = 1; /* 0 is a valid sid but start at 1 for log readability */ - u->cached_mss = 0; - u->mss_emit_countdown = 0; - u->emit_buf = malloc(MQ_UDP_MSG_HDR + MQ_UDP_MAX_PAYLOAD); - if (!u->emit_buf) { - free(u); - return NULL; - } - return u; -} - -void -mq_udp_cli_free(mq_udp_cli_t *u) -{ - if (!u) { - return; - } - /* Teardown: reap every session WITHOUT firing on_err (the owner is going - * away — this is not a remote failure). */ - for (size_t i = 0; i < MQ_UDP_CLI_MAX_SESSIONS; i++) { - mq_udp_cli_sess_t *sess = &u->sessions[i]; - if (sess->used) { - sess->closing = 1; - cli_reap_session(sess); - } - } - free(u->emit_buf); - free(u); -} diff --git a/src/proxy/mq_udp_session.h b/src/proxy/mq_udp_session.h deleted file mode 100644 index e472599f..00000000 --- a/src/proxy/mq_udp_session.h +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_udp_session.h — server-side UDP relay session table + OPEN handling; - * the client role (mq_udp_cli_*, Task 6.2) lives in the same module. - * - * One mq_udp_srv_t is owned per accepted mqproxy connection (created in - * mq_server's on_new_conn, freed in the conn-close teardown). It owns: - * - the session table (session_id → connected UDP socket + idle timer + - * defrag + per-session bookkeeping), capped at MQ_UDP_SRV_MAX_SESSIONS. - * - the per-session lifecycle: an 0x02 stream's OPEN is decoded, the target - * resolved (blocking getaddrinfo, SOCK_DGRAM), a connected UDP socket dialed, - * and a UDP_SESSION_RESP sent. The 0x02 stream stays open as the session's - * control handle — its close (client-initiated), an idle-timer expiry, or - * the conn closing all tear the session down (fd close + event free + defrag - * free), each path reaping exactly once. - * - * The datagram relay paths are fully delivered: mq_udp_srv_on_datagram provides - * conn-level datagram dispatch with an auth gate (drops silently before - * mq_udp_srv_set_authed), tunnel→target defrag+send, and a pre-OPEN buffer - * (16 entries / 32 KiB per conn, 250 ms TTL) that buffers datagrams arriving - * before the OPEN completes and flushes them at session creation. The UDP - * socket's EV_READ drives the target→tunnel path: split+send back to the - * tunnel. §9.2 drop/activity counters are maintained throughout; stats are - * dumped to the log at conn close (internal, called from mq_udp_srv_free). - */ -#ifndef MQ_PROXY_MQ_UDP_SESSION_H -#define MQ_PROXY_MQ_UDP_SESSION_H - -#include -#include - -#include - -#include "ingress/mq_ingress.h" -#include "transport/mq_conn.h" -#include "transport/mq_stream.h" -#include "wire/mq_wire.h" - -/* Session table cap (design §6): small enough that a linear array + free list - * beats a hash table (YAGNI). OPEN beyond the cap is rejected with - * MQ_UDP_SESSION_LIMIT. */ -#define MQ_UDP_SRV_MAX_SESSIONS 1024 - -typedef struct mq_udp_srv mq_udp_srv_t; /* per-connection (mq_srv_conn_t owns) */ - -/* Create the per-connection UDP relay state. `c` is the owning mqproxy conn, - * `base` the libevent base for UDP-socket events + idle timers, idle_timeout_ms - * the server's configured idle timeout (the per-session effective value is - * min(client requested, this)), and `enabled` mirrors !--no-udp (when 0 every - * OPEN is answered MQ_UDP_POLICY_DENIED). Returns NULL on OOM. */ -mq_udp_srv_t *mq_udp_srv_new(mq_conn_t *c, struct event_base *base, - uint64_t idle_timeout_ms, int enabled); - -/* Free all sessions (fd close + event free + defrag free) + pre-OPEN buffers, - * then the struct. Safe on NULL. */ -void mq_udp_srv_free(mq_udp_srv_t *u); - -/* Attach a freshly-accepted 0x02 (UDP_SESSION) stream: buffer + decode OPEN, - * resolve + dial the target, send UDP_SESSION_RESP, and either keep the stream - * as a live session's control handle or reset it on error / duplicate SID. - * - * `carry` / `carry_len` are bytes the server's stream-type dispatch already - * pulled off the stream that follow the 0x02 discriminator (the OPEN frame body - * arrives in the same send as the discriminator — see mq_client — so the - * dispatch's mq_framebuf_fill drains them out of the stream). They are seeded - * into this module's OPEN framebuf so a single-send OPEN is not stalled. Pass - * carry=NULL/carry_len=0 if nothing trailed the discriminator. */ -void mq_udp_srv_attach_stream(mq_udp_srv_t *u, mq_stream_t *s, const uint8_t *carry, - size_t carry_len); - -/* Connection-level datagram dispatch (from mq_server's on_datagram callback). - * Routes tunnel→target: decode header → session lookup → defrag → send to UDP - * socket; or pre-OPEN buffer on session-miss. Drops silently before auth - * (`mq_udp_srv_set_authed` has not been called) or when UDP is disabled. */ -void mq_udp_srv_on_datagram(mq_udp_srv_t *u, const uint8_t *data, size_t len); - -/* Mark this connection as authenticated. Call from the server's auth-success - * path so the datagram entry path opens the auth gate. Thread-safe note: both - * the auth path and the datagram callback run on the same libevent loop — - * no mutex needed. */ -void mq_udp_srv_set_authed(mq_udp_srv_t *u, int authed); - -/* ── Observability: drop / activity counters (design §9.2) ─────────────────── - * - * A by-value snapshot of the per-connection UDP relay counters. These mirror - * the same fields logged at conn close and are exposed for programmatic - * observation (in-process integration tests, future metrics export) without - * scraping the log line. Read on the libevent loop thread; no synchronisation - * needed (single-threaded relay). */ -typedef struct { - uint32_t frags_sent; /* frags emitted from multi-frag splits only */ - uint32_t frags_reassembled; /* completed multi-frag reassemblies */ - uint32_t drops_send_fail; /* datagram_send -1, or split/socket send failed */ - uint32_t drops_oversize; /* recv'd UDP payload too large to split (>255 frags) */ - uint32_t defrag_drops; /* mq_defrag_feed returned -1 */ - uint32_t preopen_evictions; /* pre-OPEN buffer evictions (cap or byte overflow) */ - uint32_t drops_preauth; /* datagrams dropped because !authed or !enabled */ -} mq_udp_srv_counters_t; - -/* Snapshot the current counters by value. Returns a zeroed struct if u is NULL. */ -mq_udp_srv_counters_t mq_udp_srv_counters(const mq_udp_srv_t *u); - -/* ═══════════════════════════════════════════════════════════════════════════ - * client role (mq_udp_cli_*) — Task 6.2 - * ═══════════════════════════════════════════════════════════════════════════ - * - * One mq_udp_cli_t is owned per client mq_conn (created/owned by mq_client). - * It is the proxy-core implementation behind the mq_udp_open_fn boundary - * (mq_ingress.h): the ingress (mq_udp_assoc) opens UDP relay sessions through - * it without touching xquic. - * - * Per-session lifecycle (mirrors the server role's table/mss-cache/defrag/ - * reap-once patterns): - * - open = allocate a session (local, synchronous; NULL on cap / pre-auth - * queue full). When authed it issues a 0x02 stream and sends OPEN - * optimistically (the user may send() immediately, before RESP). - * Pre-auth opens hold the session and defer stream issuance until - * auth succeeds (a small per-session send queue buffers payloads). - * - send = cached mss → split → datagram_send (drop + counter on failure). - * - rx = conn-level datagram dispatch → sid lookup → defrag → on_rx. - * - err = remote-caused failure/termination → on_err (at most once) then - * the session is destroyed by the core. err value: a decoded RESP - * error carries the wire code (1-4); idle timeout / normal stream - * close / conn close carry MQ_UDP_CLOSED (the assoc negative-cache - * decision in Task 6.3 keys off this distinction). Auth failure is - * a policy-level denial → MQ_UDP_POLICY_DENIED. - * - * The negative cache is NOT held here (it is owned by the assoc's DST entry — - * Task 6.3). 1024-session cap (open returns NULL beyond). */ - -typedef struct mq_udp_cli mq_udp_cli_t; /* per-connection (mq_client owns) */ - -/* Create the per-connection client UDP relay state. `c` is the client mqproxy - * conn (must outlive the cli), `base` the libevent base. authed/available start - * unset; mq_client drives them via the setters below. Returns NULL on OOM. */ -mq_udp_cli_t *mq_udp_cli_new(mq_conn_t *c, struct event_base *base); - -/* Free all sessions (on_err is NOT fired — this is core teardown, not a remote - * failure; the owner is going away) and the struct. Safe on NULL. */ -void mq_udp_cli_free(mq_udp_cli_t *u); - -/* Open a UDP relay session (the mq_udp_open_fn boundary). See mq_ingress.h for - * the full contract. Returns an opaque session handle, or NULL on immediate - * failure (1024-session cap / pre-auth queue full / OOM). */ -void *mq_udp_cli_open(void *core, const uint8_t *host, size_t host_len, - mq_addr_type_t atype, uint16_t port, mq_udp_rx_fn on_rx, - mq_udp_err_fn on_err, void *user); - -/* Send one UDP payload on a session (the mq_udp_send_fn boundary). Split into - * datagram frames per the cached mss; drops + counts on failure (UDP - * semantics). Pre-stream-issuance (pre-auth) sends are buffered in a small - * per-session queue (8 datagrams / 8 KiB) and flushed when the stream issues. */ -void mq_udp_cli_send(void *session, const uint8_t *payload, size_t len); - -/* Caller-initiated close of a session (the mq_udp_close_fn boundary). Detaches - * callbacks synchronously (no further on_rx/on_err) and destroys the session. */ -void mq_udp_cli_close(void *session); - -/* Connection-level datagram dispatch (from mq_client's on_datagram callback). - * Routes server→tunnel: decode header → session lookup → defrag → on_rx. - * Unknown sid → silent drop (no client-side pre-OPEN buffer; design §6.2). */ -void mq_udp_cli_on_datagram(mq_udp_cli_t *u, const uint8_t *data, size_t len); - -/* Auth outcome glue (called by mq_client once AUTH_RESPONSE is known): - * ok!=0, available!=0 → drain pending sessions: issue 0x02 stream + OPEN, then - * flush each session's buffered sends. - * ok!=0, available==0 → capability denied: fail all pending sessions with - * on_err(MQ_UDP_POLICY_DENIED) and destroy them. - * ok==0 → auth failure: fail all pending with - * on_err(MQ_UDP_POLICY_DENIED) and destroy them. - * `available` reflects mq_client_udp_available()==1. Idempotent (settles once).*/ -void mq_udp_cli_on_auth(mq_udp_cli_t *u, int ok, int available); - -/* Conn-close glue (called by mq_client on MQ_CONN_CLOSED): fail every live and - * pending session with on_err(MQ_UDP_CLOSED) and destroy them. After this the - * cli holds no sessions; the conn pointer must not be used again. */ -void mq_udp_cli_on_conn_close(mq_udp_cli_t *u); - -/* Reconnect support (Phase 5b). reset clears the one-shot latches (settled/ - * authed/available) and the conn-derived MSS cache so the next auth cycle runs - * fresh; it leaves conn as-is (the caller nulls it via on_conn_close). set_conn - * wires the new conn after a reconnect (mirrors mq_client_start's create-then-wire - * ordering). The session table must already be empty (on_conn_close reaped it). */ -void mq_udp_cli_reset(mq_udp_cli_t *u); -void mq_udp_cli_set_conn(mq_udp_cli_t *u, mq_conn_t *conn); - -#endif /* MQ_PROXY_MQ_UDP_SESSION_H */ diff --git a/src/runtime/mq_runtime_libevent.c b/src/runtime/mq_runtime_libevent.c deleted file mode 100644 index 17768b86..00000000 --- a/src/runtime/mq_runtime_libevent.c +++ /dev/null @@ -1,451 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_runtime_libevent.c — Linux reference App-I/O runtime for mq_transport - * (design §5). - * - * Owns libevent + the per-path UDP sockets and implements the transport's - * callbacks: - * - rt_send_udp: sendto() on the path's fd (primary-path fallback), - * mapping EAGAIN -> MQ_TX_AGAIN (no txqueue / EV_WRITE; - * xquic owns retry). - * - rt_open_path_socket: socket/bind/getsockname (ported from mq_path.c), arms - * an EV_READ|EV_PERSIST recv-drain that feeds datagrams - * into mq_transport_on_udp_recv with the path's cached - * local addr + the recvfrom peer addr. - * - rt_close_path_socket: tear a path's socket/event down. - * A libevent timer drives mq_transport_tick, (re)armed from - * mq_transport_next_timeout_ms after every recv-drain and every tick. - * - * The transport core stays libevent-free; all OS I/O lives in this file. The - * socket open / recvfrom-loop / getsockname logic is a faithful port of - * mq_path.c; the send fallback + timer idiom mirror mq_engine.c. - */ -#include "runtime/mq_runtime_libevent.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "util/mq_log.h" - -/* Max paths the runtime can hold sockets for. Mirrors MQ_ENGINE_MAX_PATHS; - * indexed by xquic path-id (primary == XQC_INITIAL_PATH_ID == 0). */ -#define MQ_RT_MAX_PATHS 8 - -/* Max UDP datagram we read into a stack buffer per recvfrom. */ -#define MQ_RT_RECV_BUF 65536 - -struct mq_runtime_s { - mq_transport_t *t; /* borrowed; not freed by the runtime */ - struct event_base *base; - int base_owned; /* 1 if we created (and must free) base */ - struct event *timer_ev; - - struct { - int fd; - struct event *read_ev; - struct sockaddr_storage local; /* cached getsockname() value */ - socklen_t local_len; - int in_use; - } paths[MQ_RT_MAX_PATHS]; -}; - -/* Per-path recv-drain context. Stored as the read event's arg so the callback - * recovers both the runtime and which path fired. */ -typedef struct { - mq_runtime_t *rt; - uint64_t path; -} rt_path_ctx_t; - -/* (Re)arm the engine timer from the transport's next-timeout. us < 0 -> the - * core wants no wake right now (don't arm). Uses MICROSECOND precision (not the - * ms variant) so xquic pacing timers keep their sub-ms spacing — flooring to ms - * (→0) collapses pacing to immediate fires, bursting the send and tripping CC - * backoff on a shaped path. Mirrors the old mq_engine set_event_timer precision. */ -static void -rt_rearm_timer(mq_runtime_t *rt) -{ - int64_t us = mq_transport_next_timeout_us(rt->t); - if (us < 0) { - return; - } - struct timeval tv; - tv.tv_sec = (time_t)(us / 1000000); - tv.tv_usec = (suseconds_t)(us % 1000000); - event_add(rt->timer_ev, &tv); -} - -/* libevent timer callback: drive the transport's main logic, then re-arm. */ -static void -rt_timer_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - mq_runtime_t *rt = (mq_runtime_t *)arg; - mq_transport_tick(rt->t); - rt_rearm_timer(rt); -} - -/* on_timer callback: the transport's deadline changed (xquic called - * set_event_timer). Re-arm immediately so deadlines set from app-initiated - * paths (connect / stream send) — not just tick/recv — are honoured. */ -static void -rt_on_timer(void *user) -{ - rt_rearm_timer((mq_runtime_t *)user); -} - -/* ── transport callbacks ──────────────────────────────────────────────── */ - -/* send_udp: route an outbound packet to the path's UDP socket. Falls back to - * the primary path's fd if the path's dedicated socket is not yet mapped - * (mirrors mq_engine_write_socket_ex). EINTR retry loop; EAGAIN -> MQ_TX_AGAIN; - * other error -> -1. No txqueue / EV_WRITE: xquic owns retry. */ -static int -rt_send_udp(uint64_t path, const uint8_t *pkt, size_t len, const struct sockaddr *peer, - socklen_t peerlen, void *user) -{ - mq_runtime_t *rt = (mq_runtime_t *)user; - if (!rt || path >= MQ_RT_MAX_PATHS) { - MQ_LOGW("mq_runtime: send on out-of-range path_id %llu", - (unsigned long long)path); - return -1; - } - - int fd = rt->paths[path].fd; - if (fd < 0) { - /* A path's dedicated socket is registered only AFTER xqc_conn_create_path - * returns its path_id, but xquic may queue the first PATH_CHALLENGE on - * the new path before that. Fall back to the primary path's socket so the - * challenge reaches the peer via the primary 4-tuple. */ - fd = rt->paths[0].fd; - if (fd < 0) { - MQ_LOGW("mq_runtime: send on unmapped path_id %llu (no primary fallback)", - (unsigned long long)path); - return -1; - } - } - - ssize_t res; - do { - res = sendto(fd, pkt, len, MSG_DONTWAIT, peer, peerlen); - } while (res < 0 && errno == EINTR); - - if (res < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - return MQ_TX_AGAIN; - } - return -1; - } - return (int)res; -} - -/* libevent EV_READ handler: drain all pending datagrams on this path, feed each - * to the transport core (with the path's cached local addr + the recvfrom peer - * addr), then re-arm the engine timer. Ported from mq_path_read_cb. */ -static void -rt_path_read_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - rt_path_ctx_t *ctx = (rt_path_ctx_t *)arg; - mq_runtime_t *rt = ctx->rt; - uint64_t path = ctx->path; - - unsigned char buf[MQ_RT_RECV_BUF]; - - for (;;) { - struct sockaddr_storage peer; - socklen_t peer_len = sizeof(peer); - memset(&peer, 0, sizeof(peer)); - - ssize_t n = - recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&peer, &peer_len); - if (n < 0) { - if (errno == EINTR) { - continue; - } - if (errno == EAGAIN || errno == EWOULDBLOCK) { - break; - } - MQ_LOGW("mq_runtime: recvfrom(path %llu) failed: %s", - (unsigned long long)path, strerror(errno)); - break; - } - if (n == 0) { - /* Empty datagram; nothing to process. */ - continue; - } - - mq_transport_on_udp_recv( - rt->t, path, buf, (size_t)n, (struct sockaddr *)&rt->paths[path].local, - rt->paths[path].local_len, (struct sockaddr *)&peer, peer_len); - } - - rt_rearm_timer(rt); -} - -/* Parse local_ip into a sockaddr; sets *af. Returns 0 on success. Ported from - * mq_path_build_addr. */ -static int -rt_build_addr(const char *local_ip, uint16_t local_port, struct sockaddr_storage *out, - socklen_t *out_len, int *af) -{ - memset(out, 0, sizeof(*out)); - - struct in6_addr a6; - struct in_addr a4; - - if (inet_pton(AF_INET, local_ip, &a4) == 1) { - struct sockaddr_in *sin = (struct sockaddr_in *)out; - sin->sin_family = AF_INET; - sin->sin_addr = a4; - sin->sin_port = htons(local_port); - *out_len = sizeof(*sin); - *af = AF_INET; - return 0; - } - if (inet_pton(AF_INET6, local_ip, &a6) == 1) { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)out; - sin6->sin6_family = AF_INET6; - sin6->sin6_addr = a6; - sin6->sin6_port = htons(local_port); - *out_len = sizeof(*sin6); - *af = AF_INET6; - return 0; - } - return -1; -} - -/* open_path_socket: create a non-blocking UDP socket bound to ip:port, cache the - * actual bound addr via getsockname, register it under `path`, and arm an - * EV_READ|EV_PERSIST recv-drain. Returns 0/-1. Faithful port of mq_path_open - * (minus the engine path-fd map; here the fd lives in the runtime's paths[]). */ -static int -rt_open_path_socket(uint64_t path, const char *ip, uint16_t port, void *user) -{ - mq_runtime_t *rt = (mq_runtime_t *)user; - if (!rt || !ip || path >= MQ_RT_MAX_PATHS) { - return -1; - } - if (rt->paths[path].in_use) { - MQ_LOGE("mq_runtime: path %llu already open", (unsigned long long)path); - return -1; - } - - struct sockaddr_storage bind_addr; - socklen_t bind_len = 0; - int af = 0; - if (rt_build_addr(ip, port, &bind_addr, &bind_len, &af) != 0) { - MQ_LOGE("mq_runtime: invalid local_ip '%s'", ip); - return -1; - } - - int fd = socket(af, SOCK_DGRAM, 0); - if (fd < 0) { - MQ_LOGE("mq_runtime: socket(af=%d) failed: %s", af, strerror(errno)); - return -1; - } - - /* Non-blocking. */ - int flags = fcntl(fd, F_GETFL, 0); - if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { - MQ_LOGE("mq_runtime: set O_NONBLOCK failed: %s", strerror(errno)); - close(fd); - return -1; - } - - int one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { - MQ_LOGW("mq_runtime: SO_REUSEADDR failed: %s", strerror(errno)); - /* non-fatal */ - } - - if (bind(fd, (struct sockaddr *)&bind_addr, bind_len) < 0) { - MQ_LOGE("mq_runtime: bind(%s:%u) failed: %s", ip, port, strerror(errno)); - close(fd); - return -1; - } - - /* Read back the actual bound address (resolves ephemeral port). */ - struct sockaddr_storage local; - socklen_t local_len = sizeof(local); - if (getsockname(fd, (struct sockaddr *)&local, &local_len) < 0) { - MQ_LOGE("mq_runtime: getsockname failed: %s", strerror(errno)); - close(fd); - return -1; - } - - rt_path_ctx_t *ctx = calloc(1, sizeof(*ctx)); - if (!ctx) { - close(fd); - return -1; - } - ctx->rt = rt; - ctx->path = path; - - struct event *read_ev = - event_new(rt->base, fd, EV_READ | EV_PERSIST, rt_path_read_cb, ctx); - if (!read_ev || event_add(read_ev, NULL) < 0) { - MQ_LOGE("mq_runtime: event_new/event_add failed"); - if (read_ev) { - event_free(read_ev); - } - free(ctx); - close(fd); - return -1; - } - - rt->paths[path].fd = fd; - rt->paths[path].read_ev = read_ev; - rt->paths[path].local = local; - rt->paths[path].local_len = local_len; - rt->paths[path].in_use = 1; - return 0; -} - -/* Tear down a single path's socket + read event. Frees the read event's ctx. */ -static void -rt_teardown_path(mq_runtime_t *rt, uint64_t path) -{ - if (!rt->paths[path].in_use) { - return; - } - if (rt->paths[path].read_ev) { - rt_path_ctx_t *ctx = - (rt_path_ctx_t *)event_get_callback_arg(rt->paths[path].read_ev); - event_del(rt->paths[path].read_ev); - event_free(rt->paths[path].read_ev); - free(ctx); - rt->paths[path].read_ev = NULL; - } - if (rt->paths[path].fd >= 0) { - close(rt->paths[path].fd); - rt->paths[path].fd = -1; - } - rt->paths[path].in_use = 0; - rt->paths[path].local_len = 0; -} - -/* close_path_socket: tear the path's socket down. */ -static void -rt_close_path_socket(uint64_t path, void *user) -{ - mq_runtime_t *rt = (mq_runtime_t *)user; - if (!rt || path >= MQ_RT_MAX_PATHS) { - return; - } - rt_teardown_path(rt, path); -} - -static const mq_transport_callbacks_t g_runtime_cbs = { - .send_udp = rt_send_udp, - .open_path_socket = rt_open_path_socket, - .close_path_socket = rt_close_path_socket, - .on_timer = rt_on_timer, -}; - -/* ── lifecycle ────────────────────────────────────────────────────────── */ - -mq_runtime_t * -mq_runtime_new(mq_transport_t *t, struct event_base *base) -{ - if (!t) { - return NULL; - } - mq_runtime_t *rt = calloc(1, sizeof(*rt)); - if (!rt) { - return NULL; - } - rt->t = t; - for (int i = 0; i < MQ_RT_MAX_PATHS; i++) { - rt->paths[i].fd = -1; - } - - if (base) { - rt->base = base; - rt->base_owned = 0; - } else { - rt->base = event_base_new(); - if (!rt->base) { - MQ_LOGE("mq_runtime: event_base_new failed"); - free(rt); - return NULL; - } - rt->base_owned = 1; - } - - /* Timer event driving mq_transport_tick; (re)armed from next_timeout_ms. - * -1 fd, no flags (one-shot, re-added each arm). */ - rt->timer_ev = event_new(rt->base, -1, 0, rt_timer_cb, rt); - if (!rt->timer_ev) { - MQ_LOGE("mq_runtime: event_new(timer) failed"); - if (rt->base_owned) { - event_base_free(rt->base); - } - free(rt); - return NULL; - } - - /* Install the runtime as the transport's callback user. */ - mq_transport_set_callbacks(t, &g_runtime_cbs, rt); - - return rt; -} - -struct event_base * -mq_runtime_base(mq_runtime_t *r) -{ - return r ? r->base : NULL; -} - -int -mq_runtime_open_udp_path(mq_runtime_t *r, const char *local_ip, uint16_t port) -{ - /* Primary path == XQC_INITIAL_PATH_ID == 0. */ - return rt_open_path_socket(0, local_ip, port, r); -} - -void -mq_runtime_run(mq_runtime_t *r) -{ - if (!r) { - return; - } - event_base_dispatch(r->base); -} - -void -mq_runtime_stop(mq_runtime_t *r) -{ - if (!r) { - return; - } - event_base_loopbreak(r->base); -} - -void -mq_runtime_free(mq_runtime_t *r) -{ - if (!r) { - return; - } - for (uint64_t i = 0; i < MQ_RT_MAX_PATHS; i++) { - rt_teardown_path(r, i); - } - if (r->timer_ev) { - event_free(r->timer_ev); - } - if (r->base_owned && r->base) { - event_base_free(r->base); - } - /* NB: the transport is borrowed — the caller owns and frees it. */ - free(r); -} diff --git a/src/runtime/mq_runtime_libevent.h b/src/runtime/mq_runtime_libevent.h deleted file mode 100644 index 6af4b0f7..00000000 --- a/src/runtime/mq_runtime_libevent.h +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_runtime_libevent.h — Linux reference App-I/O runtime for mq_transport - * (design §5). - * - * The runtime owns libevent + the UDP path sockets and implements the - * mq_transport_callbacks_t hooks (send_udp / open_path_socket / - * close_path_socket). It feeds received datagrams into the sans-io transport - * core via mq_transport_on_udp_recv and drives the engine via a libevent timer - * armed from mq_transport_next_timeout_ms. The transport itself stays - * libevent-free; all OS I/O lives here. - * - * Construction order: caller builds the transport first, then the runtime, - * which installs its callbacks (user == the runtime) onto the transport. - */ -#ifndef MQ_RUNTIME_MQ_RUNTIME_LIBEVENT_H -#define MQ_RUNTIME_MQ_RUNTIME_LIBEVENT_H - -#include - -#include "transport/mq_transport.h" - -struct event_base; - -typedef struct mq_runtime_s mq_runtime_t; - -/* Create a runtime bound to transport t. base==NULL -> the runtime creates and - * owns an event_base; base!=NULL -> the base is borrowed (shared with - * listeners / tests that drive a single loop). Installs the runtime's callbacks - * onto t. Returns NULL on failure (frees everything it created). The transport - * is borrowed, not owned. */ -mq_runtime_t *mq_runtime_new(mq_transport_t *t, struct event_base *base); - -/* The runtime's event_base (owned or borrowed), or NULL if r is NULL. */ -struct event_base *mq_runtime_base(mq_runtime_t *r); - -/* Open the primary path (path id 0): socket/bind/getsockname, arm EV_READ, - * register path 0 -> fd + local addr. Call before connect. Returns 0/-1. */ -int mq_runtime_open_udp_path(mq_runtime_t *r, const char *local_ip, uint16_t port); - -void mq_runtime_run(mq_runtime_t *r); /* event_base_dispatch */ -void mq_runtime_stop(mq_runtime_t *r); /* event_base_loopbreak */ - -/* Free all path sockets/events + the timer, free the base if owned, free the - * runtime. Does NOT free the transport (the caller owns it). Safe on NULL. */ -void mq_runtime_free(mq_runtime_t *r); - -#endif /* MQ_RUNTIME_MQ_RUNTIME_LIBEVENT_H */ diff --git a/src/transport/mq_conn.c b/src/transport/mq_conn.c deleted file mode 100644 index ddcfbbbf..00000000 --- a/src/transport/mq_conn.c +++ /dev/null @@ -1,1005 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_conn.c — raw MPQUIC connection wrapper + ALP callback tables. - * - * See mq_conn.h for the user_data scheme. In short: - * - transport user_data = mq_transport_t* (transport recovers it for sends) - * - app-proto user_data = mq_conn_t* (ALP callbacks recover the conn) - * - * The per-engine ALP registration context (mq_alpn_ctx_t) is stashed via - * xqc_engine_set_priv_ctx so the ALP conn_create_notify can find the owner's - * on_new_conn / on_new_stream hooks (xquic does not pass the registered - * alp_ctx into the per-conn callbacks). - */ -#include "transport/mq_conn.h" - -#include -#include -#include - -#include "transport/mq_stream_internal.h" -#include "util/mq_log.h" - -/* Max extra (non-primary) paths a single conn may add. The runtime's path - * map is 8 slots wide (path_id 0 is the primary), so 7 extra paths max; cap - * lower here — the proxy adds one secondary path in Phase 1. */ -#define MQ_CONN_MAX_EXTRA_PATHS 7 - -/* Per-engine ALP registration context. Owns the new-conn / new-stream hooks - * and is freed when the engine is destroyed (we keep it reachable via the - * engine priv_ctx and free it lazily — see note in mq_conn_register_alpn). */ -typedef struct mq_alpn_ctx_s { - mq_conn_on_new_fn on_new_conn; - mq_stream_on_new_fn on_new_stream; - void *user; - xqc_app_proto_callbacks_t ap_cbs; /* kept alive for the engine's lifetime */ - - /* Set by mq_conn_connect immediately before xqc_connect so the very next - * conn_create_notify (client side) adopts the caller's pre-allocated - * mq_conn instead of creating a server-style one. xquic creates the - * connection synchronously within xqc_connect on this single-threaded - * engine, so this hand-off is race-free. Cleared on adoption. */ - mq_conn_t *pending_client_conn; - - /* The most-recently-serviced server mq_conn, used to attribute a - * peer-initiated stream to its owning connection. - * - * The robust API for stream->conn is xqc_get_conn_alp_user_data_by_stream - * (used by xquic's own hq/h3 demos), but the prebuilt *shared* libxquic - * does not export it (only the static archive does, which we cannot link - * alongside the shared lib without duplicate symbols). As a portable - * substitute we record the conn in every server-side conn-level callback; - * on this single-threaded engine a server conn's conn_create_notify runs - * before any of that conn's stream_create_notify, so the first (control) - * peer stream is attributed correctly. KNOWN LIMITATION (Phase 1): with - * multiple clients whose handshakes interleave, the first stream of one - * conn could in principle be misattributed; revisit once the shared lib - * exports the alp accessor. */ - mq_conn_t *active_server_conn; -} mq_alpn_ctx_t; - -struct mq_conn_s { - mq_transport_t *transport; - xqc_cid_t cid; - int have_cid; - int is_server; - int counted; /* 1 once this conn was counted in transport->n_conns (server, - * admitted). Guards the close_notify decrement so client and - * rejected conns never decrement (no underflow). */ - - mq_conn_on_state_fn on_state; - void *on_state_user; - - /* DATAGRAM receive hook (Phase 3 UDP relay). Set via - * mq_conn_set_on_datagram; dispatched from the dgram_read_notify, which - * recovers `this` from the dgram user_data (bound in conn_create_notify). */ - mq_conn_on_datagram_fn on_datagram; - void *on_datagram_user; - - /* DATAGRAM loss/ack counters. UDP semantics: we never retransmit or reorder - * — lost/acked notifies only bump these (+ DEBUG log). Observability only. */ - uint64_t dgram_lost; - uint64_t dgram_acked; - - void *owner; - - /* Multipath: set once xquic fires ready_to_create_path_notify. */ - int mp_ready; - - /* Extra (non-primary) path-ids added via mq_conn_add_path. Their sockets - * live in the runtime; they are torn down (mq_transport_close_path) on - * conn teardown. */ - uint64_t extra_path_ids[MQ_CONN_MAX_EXTRA_PATHS]; - int n_extra_paths; -}; - -static mq_alpn_ctx_t *mq_conn_actx(void *conn_user_data); -/* Defined under "Multipath" below; forward-declared so mq_conn_close_notify can - * unsubscribe this conn from the transport's mp-ready broadcast before free(c). */ -static void mq_conn_on_mp_ready(const xqc_cid_t *scid, void *user); - -/* ── ALP connection callbacks ───────────────────────────────────────────── */ - -/* conn_create_notify: conn_user_data is the transport (transport user_data); - * conn_proto_data is the app-proto user_data — non-NULL for a client conn we - * already created in mq_conn_connect, NULL for a server-accepted conn. */ -static int -mq_conn_create_notify(xqc_connection_t *conn, const xqc_cid_t *cid, void *conn_user_data, - void *conn_proto_data) -{ - (void)conn_proto_data; - mq_transport_t *t = (mq_transport_t *)conn_user_data; - mq_alpn_ctx_t *actx = (mq_alpn_ctx_t *)xqc_engine_get_priv_ctx(mq_transport_xqc(t)); - - /* Client side: adopt the mq_conn the caller pre-allocated in - * mq_conn_connect, binding it as the connection's alp user_data. */ - if (actx && actx->pending_client_conn) { - mq_conn_t *c = actx->pending_client_conn; - actx->pending_client_conn = NULL; - c->transport = t; - memcpy(&c->cid, (const void *)cid, sizeof(c->cid)); - c->have_cid = 1; - xqc_conn_set_alp_user_data(conn, c); - /* Bind the dgram user_data slot to the mq_conn so the dgram callbacks - * recover it. Distinct from alp/transport user_data — does not clobber - * either (Phase 2 H3 user_data trap). */ - xqc_datagram_set_user_data(conn, c); - return 0; - } - - /* Server side: build an mq_conn for the accepted connection. */ - /* Engine-wide conn cap (authoritative guard): reject before allocating. - * server_accept already refuses the common case pre-handshake; this catches - * a half-open conn that slipped past that check during a flood (design §6). */ - if (mq_transport_conn_at_limit(t)) { - return -1; - } - mq_conn_t *c = calloc(1, sizeof(*c)); - if (!c) { - return -1; - } - c->transport = t; - c->is_server = 1; - /* cid may be misaligned inside xquic internals; copy bytewise. */ - memcpy(&c->cid, (const void *)cid, sizeof(c->cid)); - c->have_cid = 1; - - xqc_conn_set_alp_user_data(conn, c); - /* Bind the dgram user_data slot to the mq_conn (see client path above). */ - xqc_datagram_set_user_data(conn, c); - mq_transport_conn_inc(t); - c->counted = 1; - if (actx) { - actx->active_server_conn = c; - } - - if (actx && actx->on_new_conn) { - actx->on_new_conn(c, actx->user); - } - return 0; -} - -/* Recover the per-engine ALP ctx from a conn-level callback (which receives the - * transport as conn_user_data). */ -static mq_alpn_ctx_t * -mq_conn_actx(void *conn_user_data) -{ - mq_transport_t *t = (mq_transport_t *)conn_user_data; - if (!t) { - return NULL; - } - return (mq_alpn_ctx_t *)xqc_engine_get_priv_ctx(mq_transport_xqc(t)); -} - -static int -mq_conn_close_notify(xqc_connection_t *conn, const xqc_cid_t *cid, void *conn_user_data, - void *conn_proto_data) -{ - (void)conn; - (void)cid; - (void)conn_user_data; - mq_conn_t *c = (mq_conn_t *)conn_proto_data; - if (!c) { - return 0; - } - /* Release the conn-cap slot (design 2026-06-15). Use c->transport, NOT actx: - * the counter lives on the transport, which outlives actx in the teardown - * path described below. counted guards against client/rejected conns. */ - if (c->counted) { - mq_transport_conn_dec(c->transport); - c->counted = 0; - } - /* NB: do NOT touch the ALP ctx (actx) here. During xqc_engine_destroy, - * xquic frees the ALPN registration list (and thus actx) BEFORE destroying - * connections, so xqc_engine_get_priv_ctx would return freed memory in the - * teardown path. active_server_conn need not be cleared: it is only read to - * attribute a *newly arriving* peer stream, and no new streams arrive on a - * connection after it closes; any later stream belongs to another conn - * whose create/handshake already refreshed the slot. */ - if (c->on_state) { - c->on_state(c, MQ_CONN_CLOSED, c->on_state_user); - } - /* Unsubscribe from the transport's mp-ready broadcast BEFORE free(c). - * mq_conn_connect registered mq_conn_on_mp_ready with `c` as the user - * pointer; with multiple conns on one transport (Phase 2: tcp + h3) the - * subscriber table outlives this conn, and a later readiness broadcast would - * deref the freed `c` (use-after-free). A no-op for server conns, which never - * subscribed (matched by fn+user, not found). */ - mq_transport_remove_mp_ready_cb(c->transport, mq_conn_on_mp_ready, c); - /* Release any secondary paths this conn owns (the runtime frees the read - * event + socket via close_path_socket). */ - for (int i = 0; i < c->n_extra_paths; i++) { - mq_transport_close_path(c->transport, c->extra_path_ids[i]); - } - free(c); - return 0; -} - -static void -mq_conn_handshake_finished(xqc_connection_t *conn, void *conn_user_data, - void *conn_proto_data) -{ - (void)conn; - mq_conn_t *c = (mq_conn_t *)conn_proto_data; - /* For a server conn, refresh the active-conn slot so the imminent control - * stream is attributed to this connection (see active_server_conn). */ - if (c && c->is_server) { - mq_alpn_ctx_t *actx = mq_conn_actx(conn_user_data); - if (actx) { - actx->active_server_conn = c; - } - } - if (c && c->on_state) { - c->on_state(c, MQ_CONN_ESTABLISHED, c->on_state_user); - } -} - -/* ── ALP stream create (server / peer-initiated) ────────────────────────── */ - -static int -mq_conn_stream_create_notify(xqc_stream_t *stream, void *strm_user_data) -{ - /* Locally-initiated stream (mq_conn_open_stream): the wrapper was passed - * as user_data to xqc_stream_create and arrives here pre-allocated. Bind - * it to the xquic stream; do NOT fire the peer on_new_stream hook. */ - if (strm_user_data) { - mq_stream_bind((mq_stream_t *)strm_user_data, stream); - return 0; - } - - /* Peer-initiated stream: wrap it and surface to the owner. The - * connection's transport user_data is the mq_transport; from it we reach the - * per-engine ALP registration ctx (and its on_new_stream hook). The owning - * mq_conn is the transport's active server conn (see active_server_conn). */ - mq_transport_t *t = (mq_transport_t *)xqc_get_conn_user_data_by_stream(stream); - mq_stream_t *s = mq_stream_wrap(stream); - if (!s) { - return -1; - } - if (t) { - mq_alpn_ctx_t *actx = - (mq_alpn_ctx_t *)xqc_engine_get_priv_ctx(mq_transport_xqc(t)); - if (actx) { - mq_stream_set_conn(s, actx->active_server_conn); - } - if (actx && actx->on_new_stream) { - actx->on_new_stream(s, actx->user); - } - } - return 0; -} - -/* ── ALP datagram callbacks (Phase 3 UDP relay) ───────────────────────────── - * - * The dgram callbacks receive `user_data` = the dgram_data slot, which is the - * mq_conn_t* bound via xqc_datagram_set_user_data in conn_create_notify. This - * is a DISTINCT slot from the transport user_data (mq_transport_t*) and the - * app-proto user_data (also mq_conn_t* but a different slot) — so binding it - * does NOT clobber either (cf. the Phase 2 H3 user_data trap). */ - -static void -mq_conn_datagram_read_notify(xqc_connection_t *conn, void *user_data, const void *data, - size_t data_len, uint64_t unix_ts) -{ - (void)conn; - (void)unix_ts; - mq_conn_t *c = (mq_conn_t *)user_data; - if (c && c->on_datagram) { - c->on_datagram(c, (const uint8_t *)data, data_len, c->on_datagram_user); - } -} - -/* No-op: the proxy never blocks on the dgram write path (a full send queue is - * reported as EAGAIN-drop by mq_conn_datagram_send, and UDP semantics mean we - * just drop rather than re-arm a write). */ -static void -mq_conn_datagram_write_notify(xqc_connection_t *conn, void *user_data) -{ - (void)conn; - (void)user_data; -} - -/* UDP semantics: never retransmit. Returning 0 tells xquic NOT to retransmit - * (XQC_DGRAM_RETX_ASKED_BY_APP would). Count + DEBUG-log only. */ -static xqc_int_t -mq_conn_datagram_lost_notify(xqc_connection_t *conn, uint64_t dgram_id, void *user_data) -{ - (void)conn; - mq_conn_t *c = (mq_conn_t *)user_data; - if (c) { - c->dgram_lost++; - MQ_LOGD("mq_conn: dgram lost: id=%llu total=%llu", (unsigned long long)dgram_id, - (unsigned long long)c->dgram_lost); - } - return 0; -} - -static void -mq_conn_datagram_acked_notify(xqc_connection_t *conn, uint64_t dgram_id, void *user_data) -{ - (void)conn; - mq_conn_t *c = (mq_conn_t *)user_data; - if (c) { - c->dgram_acked++; - MQ_LOGD("mq_conn: dgram acked: id=%llu total=%llu", (unsigned long long)dgram_id, - (unsigned long long)c->dgram_acked); - } -} - -/* ── ALP registration ───────────────────────────────────────────────────── */ - -int -mq_conn_register_alpn(mq_transport_t *t, const char *alpn, mq_conn_on_new_fn on_new_conn, - mq_stream_on_new_fn on_new_stream, void *user) -{ - if (!t || !alpn) { - return -1; - } - xqc_engine_t *xeng = mq_transport_xqc(t); - if (!xeng) { - return -1; - } - - mq_alpn_ctx_t *actx = calloc(1, sizeof(*actx)); - if (!actx) { - return -1; - } - actx->on_new_conn = on_new_conn; - actx->on_new_stream = on_new_stream; - actx->user = user; - - actx->ap_cbs.conn_cbs.conn_create_notify = mq_conn_create_notify; - actx->ap_cbs.conn_cbs.conn_close_notify = mq_conn_close_notify; - actx->ap_cbs.conn_cbs.conn_handshake_finished = mq_conn_handshake_finished; - - actx->ap_cbs.stream_cbs.stream_read_notify = mq_stream_read_notify; - actx->ap_cbs.stream_cbs.stream_write_notify = mq_stream_write_notify; - actx->ap_cbs.stream_cbs.stream_create_notify = mq_conn_stream_create_notify; - actx->ap_cbs.stream_cbs.stream_close_notify = mq_stream_close_notify; - - /* DATAGRAM callbacks (Phase 3 UDP relay). Registered unconditionally; a conn - * whose peer never negotiates max_datagram_frame_size simply never fires - * them. The dgram user_data (mq_conn_t*) is bound per-conn in - * conn_create_notify, NOT here. */ - actx->ap_cbs.dgram_cbs.datagram_read_notify = mq_conn_datagram_read_notify; - actx->ap_cbs.dgram_cbs.datagram_write_notify = mq_conn_datagram_write_notify; - actx->ap_cbs.dgram_cbs.datagram_lost_notify = mq_conn_datagram_lost_notify; - actx->ap_cbs.dgram_cbs.datagram_acked_notify = mq_conn_datagram_acked_notify; - - if (xqc_engine_register_alpn(xeng, alpn, strlen(alpn), &actx->ap_cbs, actx) != - XQC_OK) { - MQ_LOGE("mq_conn: register_alpn('%s') failed", alpn); - free(actx); - return -1; - } - - /* Stash the registration ctx so conn_create_notify (which only receives - * the engine + conn) can find the owner hooks. One ALPN per engine in the - * proxy, so a single priv_ctx slot suffices. This is a non-owning alias: - * xquic stores `actx` as the alpn registration's alp_ctx and frees it in - * xqc_engine_destroy (xqc_engine_free_alpn_list), so we never free actx - * ourselves. */ - xqc_engine_set_priv_ctx(xeng, actx); - return 0; -} - -/* ── Multipath ──────────────────────────────────────────────────────────── */ - -/* Engine mp-ready hook: xquic signalled that the conn identified by scid can - * create a path. The user pointer is the client mq_conn registered in - * mq_conn_connect. The notification is broadcast to every subscriber on the - * transport, so filter by scid (cid_len + cid_buf) and only flip OUR conn. - * scid points at an aligned copy made by the transport dispatch point (xquic - * may hand it an unaligned cid pointer), so plain member access is safe here. */ -static void -mq_conn_on_mp_ready(const xqc_cid_t *scid, void *user) -{ - mq_conn_t *c = (mq_conn_t *)user; - if (!c || !c->have_cid || !scid) { - return; - } - if (scid->cid_len == c->cid.cid_len && - memcmp(scid->cid_buf, c->cid.cid_buf, c->cid.cid_len) == 0) { - c->mp_ready = 1; - } -} - -int -mq_conn_mp_ready(const mq_conn_t *c) -{ - return c ? c->mp_ready : 0; -} - -int -mq_conn_add_path(mq_conn_t *c, const char *local_ip, uint16_t local_port) -{ - if (!c || !c->have_cid || !local_ip) { - return -1; - } - if (c->n_extra_paths >= MQ_CONN_MAX_EXTRA_PATHS) { - MQ_LOGW("mq_conn: add_path: extra-path slots exhausted"); - return -1; - } - xqc_engine_t *xeng = mq_transport_xqc(c->transport); - if (!xeng) { - return -1; - } - - /* 1. Ask xquic to allocate a new path-id (needs a spare peer cid; that is - * the ready_to_create_path precondition). path_status 0 == AVAILABLE - * (xquic.h: 1==STANDBY, other==AVAILABLE). */ - uint64_t new_path_id = 0; - xqc_int_t rc = xqc_conn_create_path(xeng, &c->cid, &new_path_id, /*AVAILABLE=*/0); - if (rc != XQC_OK) { - MQ_LOGW("mq_conn: xqc_conn_create_path failed: %d (mp_ready=%d)", (int)rc, - c->mp_ready); - return -1; - } - - /* 2. Ask the runtime (via the transport) to open a UDP socket for the new - * path-id and arm its recv-drain so packets for it can be routed. */ - if (mq_transport_open_path(c->transport, new_path_id, local_ip, local_port) != 0) { - MQ_LOGE("mq_conn: add_path: open_path_socket(path %llu) failed", - (unsigned long long)new_path_id); - /* Best-effort: tear the half-created xquic path back down so it does - * not linger waiting on a socket that will never exist. */ - xqc_conn_close_path(xeng, &c->cid, new_path_id); - return -1; - } - - /* 3. Mark path status. Under --scheduler backup, mark the extra path - * STANDBY so the backup scheduler's skip-standby logic keeps all - * traffic on the primary; a PATH_STATUS frame is transmitted to the - * peer so the server-side scheduler also honours the pin (see - * xqc_set_application_path_status → xqc_write_path_status_frame_to_packet). - * All other schedulers leave the path AVAILABLE (existing behaviour). */ - if (mq_conn_scheduler() == MQ_SCHED_BACKUP) { - if (xqc_conn_mark_path_standby(xeng, &c->cid, new_path_id) != XQC_OK) { - MQ_LOGW("mq_conn: mark_path_standby(path %llu) failed; path stays " - "AVAILABLE -- backup pin NOT in effect", - (unsigned long long)new_path_id); - } else { - MQ_LOGI("mq_conn: path id=%llu marked standby (backup scheduler)", - (unsigned long long)new_path_id); - } - } else { - if (xqc_conn_mark_path_available(xeng, &c->cid, new_path_id) != XQC_OK) { - MQ_LOGW("mq_conn: mark_path_available(path %llu) failed", - (unsigned long long)new_path_id); - /* Non-fatal: the path was created + socket bound; validation may - * still proceed. Keep the path so its socket stays alive. */ - } - } - - c->extra_path_ids[c->n_extra_paths++] = new_path_id; - return (int)new_path_id; -} - -/* ── Per-path stats (shared get_stats + free contract) ────────────────────── - * - * Fetch a connection-stats snapshot into *out. Returns 0 on success (and the - * caller MUST free out->paths_info with libc free()), -1 if the conn/engine is - * unknown (out is zeroed; nothing to free). Centralises the xqc_conn_get_stats - * call + ownership contract so callers don't duplicate (or mis-free) it. */ -static int -mq_conn_stats_snapshot(const mq_conn_t *c, xqc_conn_stats_t *out) -{ - memset(out, 0, sizeof(*out)); - if (!c || !c->have_cid) { - return -1; - } - xqc_engine_t *xeng = mq_transport_xqc(c->transport); - if (!xeng) { - return -1; - } - /* cid is const-correct read-only here; xqc_conn_get_stats takes a const - * cid* and returns a snapshot with a heap-allocated paths_info. xquic - * documents (xqc_conn_stats_t) that paths_info must be released with libc - * free() — NOT xqc_free — and may be NULL with count 0 (no free needed, - * free(NULL) is safe). */ - *out = xqc_conn_get_stats(xeng, &c->cid); - return 0; -} - -int -mq_conn_path_state(const mq_conn_t *c, uint64_t path_id) -{ - xqc_conn_stats_t st; - if (mq_conn_stats_snapshot(c, &st) != 0) { - return -1; - } - int state = -1; - for (uint32_t i = 0; st.paths_info && i < st.paths_info_count; i++) { - if (st.paths_info[i].path_id == path_id) { - state = (int)st.paths_info[i].path_state; - break; - } - } - free(st.paths_info); - return state; -} - -int -mq_conn_path_bytes(const mq_conn_t *c, uint64_t path_id, uint64_t *sent, uint64_t *recv) -{ - xqc_conn_stats_t st; - if (mq_conn_stats_snapshot(c, &st) != 0) { - return -1; - } - int rc = -1; - for (uint32_t i = 0; st.paths_info && i < st.paths_info_count; i++) { - if (st.paths_info[i].path_id == path_id) { - if (sent) { - *sent = st.paths_info[i].path_send_bytes; - } - if (recv) { - *recv = st.paths_info[i].path_recv_bytes; - } - rc = 0; - break; - } - } - free(st.paths_info); - return rc; -} - -int -mq_conn_format_path_line(char *buf, size_t cap, const xqc_path_metrics_t *p) -{ - if (!buf || !p) { - return -1; - } - /* state raw int: 2 == XQC_PATH_STATE_ACTIVE (internal header; do not ref). */ - int n = snprintf( - buf, cap, - "mq.path id=%llu state=%u srtt_ms=%llu bw_Bps=%llu " - "sent=%llu recv=%llu lost=%llu" - " min_rtt_ms=%llu cwnd=%llu inflight=%llu", - (unsigned long long)p->path_id, (unsigned)p->path_state, - (unsigned long long)(p->path_srtt / 1000), /* usec -> ms */ - (unsigned long long)p->path_est_bw, /* bytes/sec */ - (unsigned long long)p->path_send_bytes, (unsigned long long)p->path_recv_bytes, - (unsigned long long)p->path_lost_count, - (unsigned long long)(p->path_min_rtt / 1000), /* usec -> ms */ - (unsigned long long)p->path_cwnd, (unsigned long long)p->path_bytes_in_flight); - return (n < 0 || (size_t)n >= cap) ? -1 : n; -} - -int -mq_conn_format_conn_line(char *buf, size_t cap, const xqc_conn_stats_t *st) -{ - if (!buf || !st) { - return -1; - } - int n = snprintf(buf, cap, - "mq.conn mp_state=%d paths=%u app_bytes=%llu standby_bytes=%llu", - st->mp_state, (unsigned)st->paths_info_count, - (unsigned long long)st->total_app_bytes, - (unsigned long long)st->standby_path_app_bytes); - return (n < 0 || (size_t)n >= cap) ? -1 : n; -} - -void -mq_conn_dump_stats_cid(mq_transport_t *t, const xqc_cid_t *cid) -{ - if (!t || !cid) { - MQ_LOGI("mq_conn stats: no connection"); - return; - } - xqc_engine_t *xeng = mq_transport_xqc(t); - if (!xeng) { - MQ_LOGI("mq_conn stats: no connection"); - return; - } - /* xqc_conn_get_stats returns a snapshot whose heap-allocated paths_info MUST - * be released with libc free() — NOT xqc_free — and may be NULL with count 0 - * (no free needed, free(NULL) is safe). Mirrors mq_conn_stats_snapshot's - * ownership contract for the cid-keyed callers (mq_h3). */ - xqc_conn_stats_t st = xqc_conn_get_stats(xeng, cid); - if (!st.paths_info || st.paths_info_count == 0) { - MQ_LOGI("mq_conn stats: no path metrics"); - free(st.paths_info); /* free(NULL) is safe; count 0 may carry a buffer */ - return; - } - char line[MQ_METRICS_LINE_CAP]; - if (mq_conn_format_conn_line(line, sizeof(line), &st) > 0) { - MQ_LOGI("%s", line); - } - for (uint32_t i = 0; i < st.paths_info_count; i++) { - if (mq_conn_format_path_line(line, sizeof(line), &st.paths_info[i]) > 0) { - MQ_LOGI("%s", line); - } - } - free(st.paths_info); -} - -void -mq_conn_dump_stats(mq_conn_t *c) -{ - if (!c || !c->have_cid) { - MQ_LOGI("mq_conn stats: no connection"); - return; - } - mq_conn_dump_stats_cid(c->transport, &c->cid); -} - -mq_cc_t -mq_cc_from_string(const char *name, int *ok) -{ - struct { - const char *name; - mq_cc_t cc; - } table[] = { - {"bbr2", MQ_CC_BBR2}, - {"bbr", MQ_CC_BBR}, - {"cubic", MQ_CC_CUBIC}, - }; - if (name) { - for (size_t i = 0; i < sizeof(table) / sizeof(table[0]); i++) { - if (strcmp(name, table[i].name) == 0) { - if (ok) { - *ok = 1; - } - return table[i].cc; - } - } - } - if (ok) { - *ok = 0; - } - return MQ_CC_DEFAULT; -} - -const char * -mq_cc_name(mq_cc_t cc) -{ - switch (cc) { - case MQ_CC_BBR: return "bbr"; - case MQ_CC_CUBIC: return "cubic"; - case MQ_CC_BBR2: - default: return "bbr2"; - } -} - -static mq_sched_t g_mq_sched = MQ_SCHED_DEFAULT; - -void -mq_conn_set_scheduler(mq_sched_t sched) -{ - g_mq_sched = sched; -} - -mq_sched_t -mq_conn_scheduler(void) -{ - return g_mq_sched; -} - -mq_sched_t -mq_sched_from_string(const char *name, int *ok) -{ - struct { - const char *name; - mq_sched_t sched; - } table[] = { - {"minrtt", MQ_SCHED_MINRTT}, - {"backup", MQ_SCHED_BACKUP}, - {"wlb", MQ_SCHED_WLB}, - }; - if (name) { - for (size_t i = 0; i < sizeof(table) / sizeof(table[0]); i++) { - if (strcmp(name, table[i].name) == 0) { - if (ok) { - *ok = 1; - } - return table[i].sched; - } - } - } - if (ok) { - *ok = 0; - } - return MQ_SCHED_DEFAULT; -} - -const char * -mq_sched_name(mq_sched_t sched) -{ - switch (sched) { - case MQ_SCHED_BACKUP: return "backup"; - case MQ_SCHED_WLB: return "wlb"; - case MQ_SCHED_MINRTT: - default: return "minrtt"; - } -} - -void -mq_conn_apply_mp_settings(xqc_conn_settings_t *s, int is_server, mq_cc_t cc) -{ - if (!s) { - return; - } - /* Multipath (match mqvpn's recipe). enable_multipath + mp_ping_on on both - * sides; init_max_path_id left at the xquic default (8). The server grants - * a larger path-id ceiling on PATHS_BLOCKED since the client is the active - * path creator (draft-21 §3.2.1 ¶7). */ - s->enable_multipath = 1; - s->mp_ping_on = 1; - if (is_server) { - s->max_path_id_grant_max_value = 128; - } - - /* Congestion control: always install a CC callback. Without this, - * cong_ctrl_callback stays NULL and the sender is effectively un-throttled - * — on a real rate-limited/lossy path that overflows the queue, triggers a - * loss/PTO retransmit storm, and collapses throughput to ~KB/s. It is - * INVISIBLE on clean loopback or under ASan (the sanitizer's slowdown keeps - * the send rate below the link rate), which is why it only surfaced in the - * shaped 1-B benchmark. Applies to both sides; the download sender (server) - * needs it most. BBR is the default (MQ_CC_DEFAULT); --cc selects - * bbr2/cubic for benchmarking. */ - switch (cc) { - case MQ_CC_BBR: s->cong_ctrl_callback = xqc_bbr_cb; break; - case MQ_CC_CUBIC: s->cong_ctrl_callback = xqc_cubic_cb; break; - case MQ_CC_BBR2: - default: s->cong_ctrl_callback = xqc_bbr2_cb; break; - } - - /* Scheduler: minRTT. A single proxied flow is ONE QUIC stream, so we want - * its packets spread across paths by RTT (within-stream multipath) to - * aggregate bandwidth. minRTT does that; WLB (xqc_wlb_scheduler_cb) instead - * PINS inner flows to paths — right for mqvpn's per-datagram flows, wrong - * here (it would confine the single stream to one path = no aggregation). - * minRTT is also xquic's current default, but pin it explicitly so a fork - * default change can't silently regress 1-B aggregation. - * --scheduler overrides this for A/B benchmarking (mq_conn_set_scheduler). */ - switch (g_mq_sched) { - case MQ_SCHED_BACKUP: s->scheduler_callback = xqc_backup_scheduler_cb; break; - case MQ_SCHED_WLB: s->scheduler_callback = xqc_wlb_scheduler_cb; break; - case MQ_SCHED_MINRTT: - default: s->scheduler_callback = xqc_minrtt_scheduler_cb; break; - } - - /* Flow-control windows: rely on xquic's default (enable_stream_rate_limit - * == 0) which advertises max_stream_data_bidi_local = XQC_MAX_RECV_WINDOW - * = 16MB — larger than the 8MB aggregate-BDP target and consistent with - * mqvpn. Setting enable_stream_rate_limit=1 would pin the initial window - * to init_recv_window (8MB) for no benefit; recv_rate_bytes_per_sec stays - * 0 so there is no rate throttle either way. The MQ_STREAM_WINDOW / - * MQ_CONN_WINDOW constants are documentary and the static_asserts in - * mq_conn.h guard the fork ceiling. */ -} - -/* ── Client connect ─────────────────────────────────────────────────────── */ - -mq_conn_t * -mq_conn_connect(mq_transport_t *t, const struct sockaddr *peer, socklen_t peerlen, - const char *alpn, const xqc_conn_settings_t *settings, void *owner) -{ - if (!t || !peer || !alpn || !settings) { - return NULL; - } - xqc_engine_t *xeng = mq_transport_xqc(t); - if (!xeng) { - return NULL; - } - - mq_alpn_ctx_t *actx = (mq_alpn_ctx_t *)xqc_engine_get_priv_ctx(xeng); - if (!actx) { - MQ_LOGE("mq_conn: connect before mq_conn_register_alpn"); - return NULL; - } - - mq_conn_t *c = calloc(1, sizeof(*c)); - if (!c) { - return NULL; - } - c->transport = t; - c->is_server = 0; - c->owner = owner; - - /* Subscribe to xquic's ready_to_create_path_notify (delivered to the - * transport) so mq_conn_mp_ready flips once paths can be created. The - * notification is broadcast to every subscriber on the transport (Phase 2: - * a tcp conn and an h3 conn share one engine), so mq_conn_on_mp_ready - * filters by scid to flip only THIS conn. */ - if (mq_transport_add_mp_ready_cb(t, mq_conn_on_mp_ready, c) != 0) { - MQ_LOGE("mq_conn: mp-ready subscriber table full"); - free(c); - return NULL; - } - - xqc_conn_ssl_config_t conn_ssl; - memset(&conn_ssl, 0, sizeof(conn_ssl)); - - /* Hand the mq_conn to the imminent conn_create_notify (fired synchronously - * inside xqc_connect) which binds it as the connection's alp user_data. - * The transport user_data is the mq_transport so the send callback can - * recover it. */ - actx->pending_client_conn = c; - - /* server_host is the TLS SNI; xquic dereferences it unconditionally - * (strlen), so it must be non-NULL even on loopback. */ - const xqc_cid_t *cid = - xqc_connect(xeng, settings, NULL, 0, "mqproxy", /*no_crypto_flag=*/0, &conn_ssl, - peer, peerlen, alpn, /*transport user_data=*/t); - if (!cid) { - MQ_LOGE("mq_conn: xqc_connect failed"); - actx->pending_client_conn = NULL; - /* Unsubscribe from the transport's mp-ready broadcast BEFORE free(c): - * a stale subscriber entry would be dereferenced on the next broadcast - * (use-after-free). Mirrors the close path and the mq_h3 sibling. */ - mq_transport_remove_mp_ready_cb(t, mq_conn_on_mp_ready, c); - free(c); - return NULL; - } - memcpy(&c->cid, cid, sizeof(c->cid)); - c->have_cid = 1; - - if (actx->pending_client_conn == c) { - /* conn_create_notify did not fire synchronously; clear the hand-off - * so a later unrelated conn_create can't adopt this conn. The alp - * user_data binding will then be missing — treat as a hard error. */ - actx->pending_client_conn = NULL; - MQ_LOGE("mq_conn: conn_create_notify did not fire during xqc_connect"); - mq_transport_remove_mp_ready_cb(t, mq_conn_on_mp_ready, c); - free(c); - return NULL; - } - return c; -} - -void -mq_conn_set_on_state(mq_conn_t *c, mq_conn_on_state_fn fn, void *user) -{ - if (!c) { - return; - } - c->on_state = fn; - c->on_state_user = user; -} - -void * -mq_conn_user(const mq_conn_t *c) -{ - return c ? c->owner : NULL; -} - -void -mq_conn_set_user(mq_conn_t *c, void *owner) -{ - if (c) { - c->owner = owner; - } -} - -mq_stream_t * -mq_conn_open_stream(mq_conn_t *c) -{ - if (!c || !c->have_cid) { - return NULL; - } - /* Pre-allocate the wrapper and pass it as the stream user_data so the - * stream_create_notify that xquic fires synchronously inside - * xqc_stream_create binds this exact wrapper (no double-wrap). */ - mq_stream_t *s = mq_stream_alloc(); - if (!s) { - return NULL; - } - xqc_stream_t *xs = - xqc_stream_create(mq_transport_xqc(c->transport), &c->cid, NULL, s); - if (!xs) { - MQ_LOGW("mq_conn: xqc_stream_create failed"); - mq_stream_free(s); - return NULL; - } - /* If stream_create_notify did not run (it should, synchronously), bind - * defensively so the wrapper is usable and freed via close_notify. */ - mq_stream_bind(s, xs); - return s; -} - -/* ── QUIC DATAGRAM (Phase 3 UDP relay) ────────────────────────────────────── - * - * The send/mss wrappers recover the raw xqc_connection_t* afresh on every call - * via xqc_engine_get_conn_by_scid (mq_conn keeps only transport + cid). After - * the conn closes the lookup returns NULL and the wrappers fail naturally - * (send=-1, mss=0) — we lean on that lifetime property rather than tracking - * close state separately. */ - -void -mq_conn_set_on_datagram(mq_conn_t *c, mq_conn_on_datagram_fn fn, void *user) -{ - if (!c) { - return; - } - c->on_datagram = fn; - c->on_datagram_user = user; -} - -/* Recover the raw xqc_connection_t* for this conn's scid, or NULL if unknown - * (e.g. closed). Shared by the send/mss wrappers. */ -static xqc_connection_t * -mq_conn_xqc(const mq_conn_t *c) -{ - if (!c || !c->have_cid) { - return NULL; - } - xqc_engine_t *xeng = mq_transport_xqc(c->transport); - if (!xeng) { - return NULL; - } - return xqc_engine_get_conn_by_scid(xeng, &c->cid); -} - -int -mq_conn_datagram_send(mq_conn_t *c, const uint8_t *data, size_t len) -{ - xqc_connection_t *conn = mq_conn_xqc(c); - if (!conn) { - return -1; - } - /* xqc_datagram_send takes void* (non-const) but does not mutate the buffer; - * cast away const. qos NORMAL, no dgram_id needed (design §8). Any negative - * return (EAGAIN / TOO_LARGE / NOT_SUPPORTED / CLOSING) collapses to -1: the - * caller bumps a drop counter and continues (UDP semantics, design §9.2). */ - xqc_int_t rc = - xqc_datagram_send(conn, (void *)(uintptr_t)data, len, NULL, XQC_DATA_QOS_NORMAL); - return rc == XQC_OK ? 0 : -1; -} - -size_t -mq_conn_datagram_mss(const mq_conn_t *c) -{ - xqc_connection_t *conn = mq_conn_xqc(c); - if (!conn) { - return 0; - } - /* xqc_datagram_get_mss returns the connection-level dgram_mss, computed from - * conn->pkt_out_size (the default/primary path MTU) — it does NOT reflect a - * per-path MTU min across active paths (xqc_datagram.c - * xqc_datagram_record_mss). With multipath, a secondary path with a smaller - * path_max_pkt_out_size would let an oversized datagram through the conn-level - * check yet be too large for that path. So take the min over active paths via - * xqc_datagram_get_mss_on_path; fall back to the conn-level mss if no path is - * reported (single-path / pre-validation). */ - size_t conn_mss = xqc_datagram_get_mss(conn); - if (conn_mss == 0) { - return 0; /* peer does not support datagram */ - } - - /* Enumerate active paths via the stats snapshot (same ownership contract as - * mq_conn_stats_snapshot). XQC_PATH_STATE_ACTIVE == 2 (mirrors mq_conn.h / - * mqvpn; the enum lives in the private xqc_multipath.h). */ - xqc_conn_stats_t st; - if (mq_conn_stats_snapshot(c, &st) != 0) { - return conn_mss; - } - size_t min_mss = conn_mss; - int saw_active = 0; - for (uint32_t i = 0; st.paths_info && i < st.paths_info_count; i++) { - if (st.paths_info[i].path_state != 2 /* XQC_PATH_STATE_ACTIVE */) { - continue; - } - size_t path_mss = xqc_datagram_get_mss_on_path(conn, st.paths_info[i].path_id); - if (path_mss == 0) { - continue; /* path closing/unknown — skip, don't zero the result */ - } - if (!saw_active || path_mss < min_mss) { - min_mss = path_mss; - saw_active = 1; - } - } - free(st.paths_info); - return min_mss; -} - -void -mq_conn_close(mq_conn_t *c) -{ - if (!c || !c->have_cid) { - return; - } - xqc_conn_close(mq_transport_xqc(c->transport), &c->cid); -} diff --git a/src/transport/mq_conn.h b/src/transport/mq_conn.h deleted file mode 100644 index 9e95a056..00000000 --- a/src/transport/mq_conn.h +++ /dev/null @@ -1,274 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_conn.h — wrapper around one raw MPQUIC (xqc_connection_t) connection. - * - * mq_conn ties an xquic connection (identified by its scid) to an owner. It - * is the application-protocol layer for the "mqproxy-tcp/1" ALPN: the - * connection and stream callback tables registered via mq_conn_register_alpn - * recover the mq_conn from xquic's per-connection app-proto user_data and - * surface lifecycle + new-stream events to the owner. - * - * USER_DATA SCHEME (see mq_transport's send callback): - * - transport user_data = the mq_transport_t* (so write_socket[_ex] recovers - * the transport by casting conn_user_data). - * - app-proto user_data = the mq_conn_t* (so the ALP conn/stream callbacks - * recover the mq_conn). - * The transport pointer is also stashed via xqc_engine_set_priv_ctx so the ALP - * conn_create_notify can locate the per-engine registration context. - */ -#ifndef MQ_TRANSPORT_MQ_CONN_H -#define MQ_TRANSPORT_MQ_CONN_H - -#include - -#include - -#include "transport/mq_stream.h" -#include "transport/mq_transport.h" - -/* ── Congestion control selection ─────────────────────────────────────────── - * - * Which xquic congestion-control algorithm the proxy installs on its conn - * settings. BBR is the default (MQ_CC_DEFAULT) — it gave the best single-path - * throughput in the shaped 1-B benchmark; BBR2 / CUBIC are selectable (e.g. CLI - * --cc) for benchmarking and A/B comparison. Maps to xqc_{bbr2,bbr,cubic}_cb in - * mq_conn_apply_mp_settings. (reno/copa are #ifdef-gated out of this xquic - * build, so not exposed.) */ -typedef enum { - MQ_CC_BBR2 = 0, - MQ_CC_BBR = 1, - MQ_CC_CUBIC = 2, -} mq_cc_t; - -/* The policy default CC used when none is explicitly selected (CLI --cc unset). - * The enum numeric values are independent of this. */ -#define MQ_CC_DEFAULT MQ_CC_BBR - -/* Parse a CC name ("bbr2"/"bbr"/"cubic") into mq_cc_t. On an unknown name - * returns MQ_CC_DEFAULT and sets *ok=0 (if ok!=NULL); on a known name *ok=1. */ -mq_cc_t mq_cc_from_string(const char *name, int *ok); - -/* Human-readable name for logging ("bbr2"/"bbr"/"cubic"). */ -const char *mq_cc_name(mq_cc_t cc); - -/* Multipath packet scheduler for the tunnel conn. minRTT (default) sprays a - * stream's packets across paths by RTT — correct for the STREAM lane - * (within-stream aggregation). backup keeps all traffic on the primary path - * while it is healthy — the single-path control for the DATAGRAM-lane A/B - * (see docs/superpowers/specs/2026-06-13-ab-lanes-bench-design.md). wlb is - * the fork's weighted scheduler, exposed for scheduler-improvement A/Bs. */ -typedef enum { - MQ_SCHED_MINRTT = 0, - MQ_SCHED_BACKUP = 1, - MQ_SCHED_WLB = 2, -} mq_sched_t; - -/* The policy default scheduler used when none is explicitly selected (CLI - * --sched unset). minRTT is the correct default for the STREAM lane — it - * aggregates bandwidth across paths by always picking the lowest-RTT one. */ -#define MQ_SCHED_DEFAULT MQ_SCHED_MINRTT - -/* Parse a scheduler name ("minrtt"/"backup"/"wlb") into mq_sched_t. On an - * unknown name returns MQ_SCHED_DEFAULT and sets *ok=0 (if ok!=NULL); on a - * known name *ok=1. */ -mq_sched_t mq_sched_from_string(const char *name, int *ok); - -/* Scheduler name for logs. */ -const char *mq_sched_name(mq_sched_t sched); - -/* Select the scheduler applied by mq_conn_apply_mp_settings to every conn - * created after this call. Process-global, set once by the CLI before any - * conn exists; defaults to MQ_SCHED_DEFAULT when never called. */ -void mq_conn_set_scheduler(mq_sched_t sched); - -/* Return the current process-global scheduler selection. Lets path-creation - * sites apply scheduler-dependent path policy (e.g. STANDBY under backup). */ -mq_sched_t mq_conn_scheduler(void); - -/* ── Flow-control window sizing (aggregate-BDP target) ────────────────────── - * - * The proxy aggregates throughput across multiple paths, so the receive - * windows must cover the sum-of-paths bandwidth-delay product, not a single - * path's. Targets: ≥8MB advertised per-stream, ≤16MB per-connection. - * - * xquic's HARD ceiling is XQC_MAX_RECV_WINDOW = 16MB (internal - * src/transport/xqc_conn.h, not on the public include path). Raising the - * window above it requires a fork patch — the static_assert below guards that. - * - * HOW THESE MAP ONTO xquic (see xqc_conn.c xqc_conn_create_settings / - * local_settings derivation, the `else`/`if (enable_stream_rate_limit)` - * blocks): - * - max_stream_data_bidi_remote / _uni are ALWAYS XQC_MAX_RECV_WINDOW (16MB). - * - max_stream_data_bidi_local (the window WE advertise to the peer for the - * streams the peer opens to us — i.e. what gates inbound stream data) is: - * enable_stream_rate_limit == 0 -> XQC_MAX_RECV_WINDOW (16MB) [DEFAULT] - * enable_stream_rate_limit == 1 -> init_recv_window - * - conn-level max_data (when not interop / no recv_rate cap) is derived as - * max_streams * per-stream-window, i.e. far above 16MB; there is no public - * knob to pin it to exactly 16MB without recv_rate_bytes_per_sec, so - * MQ_CONN_WINDOW documents intent and bounds the static-assert ceiling. - * - * We rely on xquic's DEFAULT (enable_stream_rate_limit == 0) which advertises - * max_stream_data_bidi_local = XQC_MAX_RECV_WINDOW = 16MB. This is ≥ the 8MB - * aggregate-BDP target and matches mqvpn's recipe (no rate-limit knob set). - * MQ_STREAM_WINDOW / MQ_CONN_WINDOW are documentary constants; the - * static_asserts guard that they stay within the xquic fork ceiling. */ -#define MQ_STREAM_WINDOW (8 * 1024 * 1024) -#define MQ_CONN_WINDOW (16 * 1024 * 1024) -/* Mirrors XQC_MAX_RECV_WINDOW (internal xqc_conn.h). Keep in sync. */ -#define MQ_XQUIC_MAX_RECV_WINDOW (16 * 1024 * 1024) -_Static_assert(MQ_CONN_WINDOW <= MQ_XQUIC_MAX_RECV_WINDOW, - "window exceeds xquic ceiling; raising it needs a fork patch"); -_Static_assert(MQ_STREAM_WINDOW <= MQ_XQUIC_MAX_RECV_WINDOW, - "stream window exceeds xquic ceiling; raising it needs a fork patch"); - -/* Apply the proxy's multipath + flow-control window settings to a freshly - * memset() xqc_conn_settings_t. `is_server` selects the server-only - * PATHS_BLOCKED auto-grant (max_path_id_grant_max_value). Call after setting - * proto_version / cc / pacing etc. */ -void mq_conn_apply_mp_settings(xqc_conn_settings_t *s, int is_server, mq_cc_t cc); - -typedef struct mq_conn_s mq_conn_t; - -/* Connection lifecycle state reported to the owner. */ -typedef enum { - MQ_CONN_ESTABLISHED = 1, /* handshake finished */ - MQ_CONN_CLOSED = 2, /* connection closed (mq_conn freed right after) */ -} mq_conn_state_t; - -typedef void (*mq_conn_on_state_fn)(mq_conn_t *c, mq_conn_state_t st, void *user); - -/* Server-side hooks: invoked when xquic accepts a connection / creates a peer - * stream for the registered ALPN. on_new_conn yields a freshly-created - * mq_conn (owner may attach its own context via mq_conn_set_on_state / the - * stream's mq_stream_set_cbs). on_new_stream yields a freshly-created - * mq_stream. Either may be NULL (e.g. on a client engine). */ -typedef void (*mq_conn_on_new_fn)(mq_conn_t *c, void *user); -typedef void (*mq_stream_on_new_fn)(mq_stream_t *s, void *user); - -/* Register the ALP callback tables for `alpn` on `eng`. Both client and - * server engines must register before connecting / accepting. on_new_conn / - * on_new_stream / user describe how accepted connections and peer-initiated - * streams are surfaced (server side); pass NULL on a pure client. - * Returns 0 on success, -1 on failure. */ -int mq_conn_register_alpn(mq_transport_t *t, const char *alpn, - mq_conn_on_new_fn on_new_conn, - mq_stream_on_new_fn on_new_stream, void *user); - -/* Client: initiate a connection to `peer` using `alpn` and `settings`. - * `owner` is opaque caller context (retrievable via mq_conn_user). Returns a - * new mq_conn (state callbacks fire later) or NULL on failure. */ -mq_conn_t *mq_conn_connect(mq_transport_t *t, const struct sockaddr *peer, - socklen_t peerlen, const char *alpn, - const xqc_conn_settings_t *settings, void *owner); - -/* Register the owner's state callback (established / closed). */ -void mq_conn_set_on_state(mq_conn_t *c, mq_conn_on_state_fn fn, void *user); - -/* Opaque owner context. For client conns this is the `owner` passed to - * mq_conn_connect; for server conns it is initially NULL (settable via - * mq_conn_set_user). */ -void *mq_conn_user(const mq_conn_t *c); -void mq_conn_set_user(mq_conn_t *c, void *owner); - -/* Client: open a new locally-initiated bidirectional stream. Returns a new - * mq_stream or NULL on failure. */ -mq_stream_t *mq_conn_open_stream(mq_conn_t *c); - -/* ── Multipath: add a second (or further) path ────────────────────────────── - * - * mq_conn_mp_ready: 1 once xquic has fired ready_to_create_path_notify for - * this connection (cids exchanged) — i.e. mq_conn_add_path can now succeed. - * Returns 0 before that (or on NULL). Client conns only. */ -int mq_conn_mp_ready(const mq_conn_t *c); - -/* Create a NEW MPQUIC path bound to local_ip:local_port (port 0 == ephemeral), - * open its UDP socket via the transport's open_path_socket callback, and mark it - * available so the peer may schedule traffic on it. The new path-id is recorded - * by the conn and torn down (close_path_socket) on conn teardown. - * - * MUST be called after mq_conn_mp_ready(c) returns 1 (otherwise xquic has no - * available path-id and the call fails). Returns the new path_id (> 0) on - * success, or -1 on failure (not ready / out of path slots / bind failure). - * Client conns only. */ -int mq_conn_add_path(mq_conn_t *c, const char *local_ip, uint16_t local_port); - -/* Current xquic path-state for path_id, by polling xqc_conn_get_stats. - * 2 == XQC_PATH_STATE_ACTIVE (validated, usable) — the value of interest. - * Returns -1 if the conn/path is unknown. (XQC_PATH_STATE_ACTIVE lives in the - * private xqc_multipath.h, so the literal 2 mirrors mqvpn's usage.) */ -int mq_conn_path_state(const mq_conn_t *c, uint64_t path_id); - -/* ── Per-path observability (spec §23.1) ──────────────────────────────────── - * - * Per-path observability formatters (Phase 5c). Pure: no I/O, no allocation. - * Render one logfmt line into buf; return bytes written (>0) or -1 on snprintf - * truncation/error. MQ_METRICS_LINE_CAP is a safe caller-buffer size for either - * line (mq.path worst case ≈265 B with 10 numeric fields including min_rtt_ms, - * cwnd, and inflight; 320 cap gives ~55 B headroom for future fields). */ -#define MQ_METRICS_LINE_CAP 320 -int mq_conn_format_path_line(char *buf, size_t cap, const xqc_path_metrics_t *p); -int mq_conn_format_conn_line(char *buf, size_t cap, const xqc_conn_stats_t *st); - -/* mq_conn_dump_stats: snapshot per-path byte counters via xqc_conn_get_stats - * and log each path's id / send-bytes / recv-bytes at INFO. If xquic reports no - * path metrics (paths_info == NULL or count == 0) it logs "no path metrics". - * The heap-allocated paths_info buffer is freed with libc free() (the xquic - * ownership contract — see xqc_conn_stats_t doc). Smoke-safe on any conn. */ -void mq_conn_dump_stats(mq_conn_t *c); - -/* mq_conn_dump_stats_cid: the cid-keyed core of mq_conn_dump_stats, shared with - * mq_h3 (whose conn wrapper is keyed by an h3 cid, not an mq_conn). Snapshots - * per-path byte counters via xqc_conn_get_stats(transport-engine, cid) and logs - * each path's id / send-bytes / recv-bytes at INFO; logs "no path metrics" if - * xquic reports none. The heap-allocated paths_info buffer is freed with libc - * free() (the xquic ownership contract — see xqc_conn_stats_t doc). A no-op if - * t/cid is NULL. Both H3 and raw conns share ONE engine, so the cid alone - * selects the connection. */ -void mq_conn_dump_stats_cid(mq_transport_t *t, const xqc_cid_t *cid); - -/* mq_conn_path_bytes: read path_id's cumulative send/recv byte counters from - * the same stats snapshot. On success writes *sent / *recv and returns 0; if - * the conn is unknown or path_id has no metrics, returns -1 and leaves *sent / - * *recv untouched. sent / recv may be NULL (then only existence is checked). */ -int mq_conn_path_bytes(const mq_conn_t *c, uint64_t path_id, uint64_t *sent, - uint64_t *recv); - -/* ── QUIC DATAGRAM (Phase 3: UDP relay) ───────────────────────────────────── - * - * Thin wrappers over xquic's DATAGRAM API for the UDP-relay carrier. UDP - * semantics: no retransmission, no ordering recovery — a lost/dropped datagram - * is simply counted and the caller moves on (design §8/§9.2). - * - * Enabled by settings.max_datagram_frame_size on the mqproxy-tcp/1 conn (both - * client and server). The gateway h3 conn does NOT set it and so reports mss==0 - * (peer-unsupported), which the wrappers below fail closed on. */ - -/* Receive notification for a DATAGRAM frame. data is owned by xquic and is - * valid only for the duration of the callback — copy if needed. */ -typedef void (*mq_conn_on_datagram_fn)(mq_conn_t *c, const uint8_t *data, size_t len, - void *user); -void mq_conn_set_on_datagram(mq_conn_t *c, mq_conn_on_datagram_fn fn, void *user); - -/* Send a DATAGRAM frame. Returns 0 on acceptance, -1 on a drop-equivalent - * error (EAGAIN / TOO_LARGE / NOT_SUPPORTED / CLOSING are all collapsed into - * -1 — the caller increments its counter and moves on. design §9.2). */ -int mq_conn_datagram_send(mq_conn_t *c, const uint8_t *data, size_t len); - -/* Current datagram payload ceiling at the QUIC layer. 0 = peer does not - * support datagrams or the MSS is unknown. Subtract MQ_UDP_MSG_HDR to get the - * effective UDP payload limit. - * - * Multipath note: xqc_datagram_get_mss returns only the connection-level - * dgram_mss (based on conn->pkt_out_size) and does not reflect per-path MTU - * differences (xqc_datagram.c xqc_datagram_record_mss). This wrapper therefore - * enumerates active paths and takes the minimum of xqc_datagram_get_mss_on_path - * across them, falling back to the connection-level value if no paths are - * listed. */ -size_t mq_conn_datagram_mss(const mq_conn_t *c); - -/* Send CONNECTION_CLOSE. The mq_conn is freed later via conn_close_notify. */ -void mq_conn_close(mq_conn_t *c); - -#endif /* MQ_TRANSPORT_MQ_CONN_H */ diff --git a/src/transport/mq_h3.c b/src/transport/mq_h3.c deleted file mode 100644 index d44b74e2..00000000 --- a/src/transport/mq_h3.c +++ /dev/null @@ -1,916 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_h3.c — H3-over-MPQUIC wrapper over the sans-io transport core. - * - * See mq_h3.h for the user_data scheme and the ABSOLUTE RULES. In short: - * - the transport user_data slot (which xquic's internal H3 ALPN also makes - * the H3 conn's user_data) stays = mq_transport_t* forever, so our h3 conn - * callbacks receive the TRANSPORT, not our wrapper. We recover the - * mq_h3_conn from a small fixed table keyed by xqc_h3_conn_t*. - * - the h3 REQUEST user_data slot is independent and safe to own: it holds - * the mq_h3_req_t* (set at create on the client side, via - * xqc_h3_request_set_user_data on the server side). - * - * RECOVERING mq_h3 IN THE CALLBACKS: the H3 conn/request callbacks only receive - * the transport (as h3c_user_data / via the request's conn user_data), and the - * engine priv_ctx slot is already taken by mq_conn's ALPN — so we cannot stash - * mq_h3 there. Instead a tiny file-scope transport->mq_h3 map (g_h3_by_transport) - * lets every callback recover the owning mq_h3 from the transport. mq_h3_init - * enforces one mq_h3 per transport, so the map is unambiguous. The engine is - * single-threaded, so the map needs no locking. - */ -#include "transport/mq_h3.h" - -#include -#include - -#include "transport/mq_conn.h" /* mq_conn_apply_mp_settings, mq_conn_dump_stats_cid */ -#include "util/mq_log.h" - -/* Max H3 conns tracked per transport. The proxy opens one H3 conn per upstream - * (Phase 2 is a single client conn; servers accept a handful). The table is - * small + fixed to mirror the transport's mp-ready table discipline; the insert - * guard logs + drops on overflow rather than growing unboundedly. */ -#define MQ_H3_MAX_CONNS 16 - -/* Max extra (non-primary) paths per H3 conn — mirrors MQ_CONN_MAX_EXTRA_PATHS - * (the runtime path map is 8 slots wide, path_id 0 is primary). */ -#define MQ_H3_MAX_EXTRA_PATHS 7 - -/* Transport -> mq_h3 map. mq_h3_init enforces one mq_h3 per transport, so this - * tiny global table (engine priv_ctx is taken by mq_conn) lets the H3 conn - * callbacks — which only receive the transport as h3c_user_data — recover the - * owning mq_h3. Single-threaded engine, so no locking. */ -#define MQ_H3_MAX_TRANSPORTS 8 -static struct { - mq_transport_t *t; - mq_h3_t *h; -} g_h3_by_transport[MQ_H3_MAX_TRANSPORTS]; - -struct mq_h3_conn_s { - mq_h3_t *owner; - mq_transport_t *transport; - xqc_h3_conn_t *h3c; /* table key; valid for the conn's lifetime */ - xqc_cid_t cid; - int have_cid; - int is_server; - int counted; /* 1 once counted in transport->n_conns (mirrors mq_conn_s). */ - - mq_h3_conn_state_fn on_state; - void *on_state_user; - - /* Multipath: set once xquic fires ready_to_create_path_notify for our cid. */ - int mp_ready; - - uint64_t extra_path_ids[MQ_H3_MAX_EXTRA_PATHS]; - int n_extra_paths; -}; - -struct mq_h3_req_s { - mq_h3_conn_t *conn; - xqc_h3_request_t *r; /* xquic request handle */ - - mq_h3_req_on_read_fn on_read; - mq_h3_req_on_write_fn on_write; - mq_h3_req_on_close_fn on_close; - void *cb_user; -}; - -struct mq_h3_s { - mq_transport_t *transport; - int initialised; /* xqc_h3_ctx_init succeeded (so mq_h3_free must destroy) */ - - mq_h3_on_new_conn_fn on_conn; - mq_h3_on_new_req_fn on_req; - void *user; - - /* Conn recovery table (xqc_h3_conn_t* -> mq_h3_conn_t*). INSERT in - * h3_conn_create_notify, REMOVE + free in h3_conn_close_notify. */ - mq_h3_conn_t *conns[MQ_H3_MAX_CONNS]; - int n_conns; - - /* Hand-off for the client connect path: set immediately before - * xqc_h3_connect so the synchronous create_notify adopts this wrapper - * instead of building a server-style one. Cleared on adoption. */ - mq_h3_conn_t *pending_client_conn; - - /* Most-recently-serviced server conn, used to attribute a peer-initiated - * request to its owning connection. Same pattern + limitation as - * mq_conn's active_server_conn (no public request->h3_conn accessor in the - * shared lib; on this single-threaded engine a conn's create/handshake runs - * before its first request's create_notify). - * - * xqc_h3_get_conn_user_data_by_request exists but returns the TRANSPORT - * user_data, not the mq_h3_conn wrapper — it is used below to recover the - * mq_h3 from the transport map, not to recover the per-conn wrapper. A true - * per-request h3-conn accessor would need a fork export (analogous to - * mq_conn.c's xqc_get_conn_alp_user_data_by_stream note); absent that, this - * windowed slot is the only safe recovery path. */ - mq_h3_conn_t *active_server_conn; -}; - -/* ── transport <-> mq_h3 map ────────────────────────────────────────────── */ - -static mq_h3_t * -mq_h3_for_transport(mq_transport_t *t) -{ - for (int i = 0; i < MQ_H3_MAX_TRANSPORTS; i++) { - if (g_h3_by_transport[i].t == t) { - return g_h3_by_transport[i].h; - } - } - return NULL; -} - -static int -mq_h3_map_insert(mq_transport_t *t, mq_h3_t *h) -{ - for (int i = 0; i < MQ_H3_MAX_TRANSPORTS; i++) { - if (g_h3_by_transport[i].t == NULL) { - g_h3_by_transport[i].t = t; - g_h3_by_transport[i].h = h; - return 0; - } - } - return -1; -} - -static void -mq_h3_map_remove(mq_transport_t *t) -{ - for (int i = 0; i < MQ_H3_MAX_TRANSPORTS; i++) { - if (g_h3_by_transport[i].t == t) { - g_h3_by_transport[i].t = NULL; - g_h3_by_transport[i].h = NULL; - return; - } - } -} - -/* ── conn table ─────────────────────────────────────────────────────────── */ - -static mq_h3_conn_t * -mq_h3_conn_lookup(mq_h3_t *h, xqc_h3_conn_t *h3c) -{ - for (int i = 0; i < h->n_conns; i++) { - if (h->conns[i]->h3c == h3c) { - return h->conns[i]; - } - } - return NULL; -} - -static int -mq_h3_conn_table_insert(mq_h3_t *h, mq_h3_conn_t *c) -{ - if (h->n_conns >= MQ_H3_MAX_CONNS) { - MQ_LOGW("mq_h3: conn table full (%d slots)", MQ_H3_MAX_CONNS); - return -1; - } - h->conns[h->n_conns++] = c; - return 0; -} - -static void -mq_h3_conn_table_remove(mq_h3_t *h, mq_h3_conn_t *c) -{ - for (int i = 0; i < h->n_conns; i++) { - if (h->conns[i] == c) { - /* Compact the tail down so the lookup loop never indexes a freed - * slot (mirrors the transport's mp-ready table discipline). */ - for (int j = i; j < h->n_conns - 1; j++) { - h->conns[j] = h->conns[j + 1]; - } - h->n_conns--; - h->conns[h->n_conns] = NULL; - return; - } - } -} - -/* ── multipath ──────────────────────────────────────────────────────────── */ - -/* Engine mp-ready hook: xquic signalled that the conn identified by scid can - * create a path. Broadcast to every subscriber on the transport, so filter by - * scid and only flip OUR conn. scid points at an aligned copy made by the - * transport dispatch point, so plain member access is safe here. */ -static void -mq_h3_conn_on_mp_ready(const xqc_cid_t *scid, void *user) -{ - mq_h3_conn_t *c = (mq_h3_conn_t *)user; - if (!c || !c->have_cid || !scid) { - return; - } - if (scid->cid_len == c->cid.cid_len && - memcmp(scid->cid_buf, c->cid.cid_buf, c->cid.cid_len) == 0) { - c->mp_ready = 1; - } -} - -/* ── H3 connection callbacks ────────────────────────────────────────────── */ - -/* h3_conn_create_notify: h3c_user_data is the TRANSPORT (xquic's internal H3 - * ALPN copies the conn's transport user_data into the H3 conn's user_data). - * cid is the conn's scid (owned by xquic). */ -static int -mq_h3_conn_create_notify(xqc_h3_conn_t *h3c, const xqc_cid_t *cid, void *h3c_user_data) -{ - mq_transport_t *t = (mq_transport_t *)h3c_user_data; - mq_h3_t *h = mq_h3_for_transport(t); - if (!h) { - MQ_LOGE("mq_h3: conn_create_notify with no mq_h3 for transport"); - return -1; - } - - /* Client side: adopt the wrapper the caller pre-allocated in mq_h3_connect. - * Its cid was filled from xqc_h3_connect's return; bind the h3c handle. */ - if (h->pending_client_conn) { - mq_h3_conn_t *c = h->pending_client_conn; - h->pending_client_conn = NULL; - c->h3c = h3c; - /* cid may be misaligned inside xquic internals; copy bytewise. */ - memcpy(&c->cid, (const void *)cid, sizeof(c->cid)); - c->have_cid = 1; - if (mq_h3_conn_table_insert(h, c) != 0) { - return -1; - } - return 0; - } - - /* Server side: build a wrapper for the accepted connection. */ - /* Engine-wide conn cap (authoritative guard): reject before allocating - * (mirrors mq_conn_create_notify; catches half-open slips past server_accept, - * design §6). */ - if (mq_transport_conn_at_limit(t)) { - return -1; - } - mq_h3_conn_t *c = calloc(1, sizeof(*c)); - if (!c) { - return -1; - } - c->owner = h; - c->transport = t; - c->h3c = h3c; - c->is_server = 1; - memcpy(&c->cid, (const void *)cid, sizeof(c->cid)); - c->have_cid = 1; - if (mq_h3_conn_table_insert(h, c) != 0) { - free(c); - return -1; - } - mq_transport_conn_inc(t); - c->counted = 1; - h->active_server_conn = c; - - if (h->on_conn) { - h->on_conn(c, h->user); - } - return 0; -} - -static int -mq_h3_conn_close_notify(xqc_h3_conn_t *h3c, const xqc_cid_t *cid, void *h3c_user_data) -{ - (void)cid; - mq_transport_t *t = (mq_transport_t *)h3c_user_data; - mq_h3_t *h = mq_h3_for_transport(t); - if (!h) { - return 0; - } - mq_h3_conn_t *c = mq_h3_conn_lookup(h, h3c); - if (!c) { - return 0; - } - if (c->on_state) { - c->on_state(c, /*established=*/0, c->on_state_user); - } - /* Unsubscribe from the transport's mp-ready broadcast BEFORE free(c): with - * multiple conns on one transport the subscriber table outlives this conn, - * and a later readiness broadcast would deref freed memory. A no-op for - * server conns (never subscribed; matched by fn+user, not found). */ - mq_transport_remove_mp_ready_cb(t, mq_h3_conn_on_mp_ready, c); - /* Release any secondary paths this conn owns. */ - for (int i = 0; i < c->n_extra_paths; i++) { - mq_transport_close_path(t, c->extra_path_ids[i]); - } - if (h->active_server_conn == c) { - h->active_server_conn = NULL; - } - /* Release the conn-cap slot (design 2026-06-15), guarded by counted so client - * and rejected conns never decrement. NOT in mq_h3_conn_handshake_finished — - * that is the establishment path, not teardown. */ - if (c->counted) { - mq_transport_conn_dec(c->transport); - c->counted = 0; - } - mq_h3_conn_table_remove(h, c); - free(c); - return 0; -} - -static void -mq_h3_conn_handshake_finished(xqc_h3_conn_t *h3c, void *h3c_user_data) -{ - mq_transport_t *t = (mq_transport_t *)h3c_user_data; - mq_h3_t *h = mq_h3_for_transport(t); - if (!h) { - return; - } - mq_h3_conn_t *c = mq_h3_conn_lookup(h, h3c); - if (!c) { - return; - } - /* For a server conn, refresh the active-conn slot so the imminent first - * request is attributed to this connection (see active_server_conn). */ - if (c->is_server) { - h->active_server_conn = c; - } - if (c->on_state) { - c->on_state(c, /*established=*/1, c->on_state_user); - } -} - -/* ── H3 request callbacks ───────────────────────────────────────────────── */ - -/* h3_request_create_notify: for a client request, h3s_user_data is the wrapper - * passed to xqc_h3_request_create. For a server (peer-initiated) request, - * h3s_user_data is NULL — wrap it and surface to the owner. */ -static int -mq_h3_request_create_notify(xqc_h3_request_t *r, void *h3s_user_data) -{ - if (h3s_user_data) { - /* Locally-initiated request (mq_h3_req_open): already bound. */ - return 0; - } - - /* Peer-initiated request: recover the owning mq_h3 from the request's conn - * user_data (the transport), then attribute it to the active server conn. */ - mq_transport_t *t = (mq_transport_t *)xqc_h3_get_conn_user_data_by_request(r); - mq_h3_t *h = mq_h3_for_transport(t); - if (!h) { - return -1; - } - mq_h3_req_t *req = calloc(1, sizeof(*req)); - if (!req) { - return -1; - } - req->r = r; - /* active_server_conn can be NULL if the request's create_notify fires outside - * the conn create/handshake window (attribution window edge case, e.g. a very - * late peer-initiated stream after close_notify cleared the slot). Downstream - * owners MUST guard against a NULL req->conn before dereferencing it. */ - req->conn = h->active_server_conn; - /* Bind the wrapper as the request's user_data so subsequent read/write/close - * notifies recover it directly (independent per-stream slot — safe). */ - xqc_h3_request_set_user_data(r, req); - - if (h->on_req) { - h->on_req(req, h->user); - } - return 0; -} - -static int -mq_h3_request_close_notify(xqc_h3_request_t *r, void *h3s_user_data) -{ - (void)r; - mq_h3_req_t *req = (mq_h3_req_t *)h3s_user_data; - if (!req) { - return 0; - } - if (req->on_close) { - req->on_close(req, req->cb_user); - } - free(req); - return 0; -} - -static int -mq_h3_request_read_notify(xqc_h3_request_t *r, xqc_request_notify_flag_t flag, - void *h3s_user_data) -{ - (void)r; - mq_h3_req_t *req = (mq_h3_req_t *)h3s_user_data; - if (req && req->on_read) { - req->on_read(req, (int)flag, req->cb_user); - } - return 0; -} - -static int -mq_h3_request_write_notify(xqc_h3_request_t *r, void *h3s_user_data) -{ - (void)r; - mq_h3_req_t *req = (mq_h3_req_t *)h3s_user_data; - if (req && req->on_write) { - req->on_write(req, req->cb_user); - } - return 0; -} - -/* ── init / free ────────────────────────────────────────────────────────── */ - -mq_h3_t * -mq_h3_init(mq_transport_t *t, mq_h3_on_new_conn_fn on_conn, mq_h3_on_new_req_fn on_req, - void *user) -{ - if (!t) { - return NULL; - } - xqc_engine_t *xeng = mq_transport_xqc(t); - if (!xeng) { - return NULL; - } - /* One mq_h3 per transport: xqc_h3_ctx_init re-registers the H3 ALPN (not - * idempotent), so a second init would corrupt the engine's ALPN list. */ - if (mq_h3_for_transport(t)) { - MQ_LOGE("mq_h3: transport already has an mq_h3 (init is one-per-transport)"); - return NULL; - } - - mq_h3_t *h = calloc(1, sizeof(*h)); - if (!h) { - return NULL; - } - h->transport = t; - h->on_conn = on_conn; - h->on_req = on_req; - h->user = user; - - /* Register the transport->mq_h3 map entry BEFORE xqc_h3_ctx_init so a - * synchronous create_notify (none expected here, but be safe) can find us. */ - if (mq_h3_map_insert(t, h) != 0) { - MQ_LOGE("mq_h3: transport map full"); - free(h); - return NULL; - } - - xqc_h3_callbacks_t h3_cbs; - memset(&h3_cbs, 0, sizeof(h3_cbs)); - h3_cbs.h3c_cbs.h3_conn_create_notify = mq_h3_conn_create_notify; - h3_cbs.h3c_cbs.h3_conn_close_notify = mq_h3_conn_close_notify; - h3_cbs.h3c_cbs.h3_conn_handshake_finished = mq_h3_conn_handshake_finished; - h3_cbs.h3r_cbs.h3_request_create_notify = mq_h3_request_create_notify; - h3_cbs.h3r_cbs.h3_request_close_notify = mq_h3_request_close_notify; - h3_cbs.h3r_cbs.h3_request_read_notify = mq_h3_request_read_notify; - h3_cbs.h3r_cbs.h3_request_write_notify = mq_h3_request_write_notify; - - if (xqc_h3_ctx_init(xeng, &h3_cbs) != XQC_OK) { - MQ_LOGE("mq_h3: xqc_h3_ctx_init failed"); - mq_h3_map_remove(t); - free(h); - return NULL; - } - h->initialised = 1; - return h; -} - -void -mq_h3_free(mq_h3_t *h) -{ - if (!h) { - return; - } - /* Destroy the H3 ctx BEFORE the engine is destroyed (mq_transport_free). - * xqc_h3_ctx_destroy unregisters the H3 ALPNs and frees the H3 ctxs via the - * still-live engine; if the engine were destroyed first it would already - * have freed its ALPN list. Hence the contract: free the mq_h3 before - * mq_transport_free. - * - * CONN-WRAPPER OWNERSHIP: unlike mq_conn (whose ALPN ctx stays registered - * until xqc_engine_destroy, so the engine teardown fires conn_close_notify - * and frees each wrapper), the H3 ALPN callbacks are unregistered HERE by - * xqc_h3_ctx_destroy — strictly before the engine is destroyed in - * mq_transport_free. So any conn still in the table will NEVER get a - * subsequent h3_conn_close_notify (its callback is already gone), and the - * wrapper would leak. We therefore free the remaining wrappers ourselves, - * after the ctx is destroyed. (mq_h3_conn_close on a live conn defers its - * close_notify to engine teardown, which no longer reaches us — so this is - * the real single free point for any wrapper that outlived an explicit - * close, as well as for conns never closed by the owner.) */ - if (h->initialised && h->transport) { - xqc_h3_ctx_destroy(mq_transport_xqc(h->transport)); - } - for (int i = 0; i < h->n_conns; i++) { - /* Best-effort lifecycle signal so an owner watching state sees the conn - * go away; then unsubscribe + free (mirrors h3_conn_close_notify, minus - * the path teardown which the engine destroy below reclaims). */ - mq_h3_conn_t *c = h->conns[i]; - if (c->on_state) { - c->on_state(c, /*established=*/0, c->on_state_user); - } - if (h->transport) { - mq_transport_remove_mp_ready_cb(h->transport, mq_h3_conn_on_mp_ready, c); - /* Release the conn-cap slot here too: this loop is the close_notify - * substitute for conns the owner never closed (the ALPN callbacks are - * already unregistered, so h3_conn_close_notify won't fire for them). - * Keeps n_conns symmetric even on owner-driven teardown. */ - if (c->counted) { - mq_transport_conn_dec(h->transport); - c->counted = 0; - } - } - free(c); - h->conns[i] = NULL; - } - h->n_conns = 0; - mq_h3_map_remove(h->transport); - free(h); -} - -/* ── client connect ─────────────────────────────────────────────────────── */ - -mq_h3_conn_t * -mq_h3_connect(mq_h3_t *h, const struct sockaddr *peer, socklen_t peerlen, mq_cc_t cc, - uint64_t keepalive_idle_ms, mq_h3_conn_state_fn st, void *user) -{ - if (!h || !peer) { - return NULL; - } - mq_transport_t *t = h->transport; - xqc_engine_t *xeng = mq_transport_xqc(t); - if (!xeng) { - return NULL; - } - - mq_h3_conn_t *c = calloc(1, sizeof(*c)); - if (!c) { - return NULL; - } - c->owner = h; - c->transport = t; - c->is_server = 0; - c->on_state = st; - c->on_state_user = user; - - /* Subscribe to ready_to_create_path_notify (broadcast to every subscriber; - * mq_h3_conn_on_mp_ready filters by scid to flip only THIS conn). */ - if (mq_transport_add_mp_ready_cb(t, mq_h3_conn_on_mp_ready, c) != 0) { - MQ_LOGE("mq_h3: mp-ready subscriber table full"); - free(c); - return NULL; - } - - /* Same conn settings as the raw conn (multipath + CC + flow control). - * proto_version MUST be set: xqc_h3_connect selects the H3 ALPN string via - * xqc_h3_alpn[proto_version], and the default (XQC_IDRAFT_INIT_VER == 0) maps - * to "" — an empty ALPN — which fails the client TLS handshake with - * NO_APPLICATION_PROTOCOL. QUIC v1 maps to the "h3" ALPN. */ - xqc_conn_settings_t s; - memset(&s, 0, sizeof(s)); - s.proto_version = XQC_VERSION_V1; - mq_conn_apply_mp_settings(&s, /*is_server=*/0, cc); - /* PING keepalive (mirror mq_client): keep the idle tunnel alive + detect peer - * loss. Only the post-handshake idle_time_out is set here; init_idle_time_out - * is left at the xquic default (pre-handshake idle, intentional). */ - s.ping_on = (keepalive_idle_ms > 0) ? 1 : 0; - if (keepalive_idle_ms > 0) { - s.idle_time_out = keepalive_idle_ms; - } - - xqc_conn_ssl_config_t conn_ssl; - memset(&conn_ssl, 0, sizeof(conn_ssl)); - - /* Hand the wrapper to the imminent (synchronous) create_notify. */ - h->pending_client_conn = c; - - /* server_host is the TLS SNI; xquic dereferences it unconditionally, so it - * must be non-NULL even on loopback. user_data MUST be the transport (RULE - * #1): xquic makes it the H3 conn's user_data, which the send path casts to - * mq_transport_t*. */ - const xqc_cid_t *cid = xqc_h3_connect(xeng, &s, NULL, 0, "mqproxy", - /*no_crypto_flag=*/0, &conn_ssl, peer, peerlen, - /*transport user_data=*/t); - if (!cid) { - MQ_LOGE("mq_h3: xqc_h3_connect failed"); - h->pending_client_conn = NULL; - mq_transport_remove_mp_ready_cb(t, mq_h3_conn_on_mp_ready, c); - free(c); - return NULL; - } - memcpy(&c->cid, cid, sizeof(c->cid)); - c->have_cid = 1; - - if (h->pending_client_conn == c) { - /* create_notify did not fire synchronously; the wrapper was never bound - * to an h3c handle nor inserted into the table — treat as a hard error. */ - h->pending_client_conn = NULL; - MQ_LOGE("mq_h3: h3_conn_create_notify did not fire during xqc_h3_connect"); - mq_transport_remove_mp_ready_cb(t, mq_h3_conn_on_mp_ready, c); - free(c); - return NULL; - } - return c; -} - -int -mq_h3_conn_mp_ready(const mq_h3_conn_t *c) -{ - return c ? c->mp_ready : 0; -} - -void -mq_h3_conn_set_state_cb(mq_h3_conn_t *c, mq_h3_conn_state_fn st, void *user) -{ - if (!c) { - return; - } - c->on_state = st; - c->on_state_user = user; -} - -int -mq_h3_conn_add_path(mq_h3_conn_t *c, const char *local_ip, uint16_t port) -{ - if (!c || !c->have_cid || !local_ip) { - return -1; - } - if (c->n_extra_paths >= MQ_H3_MAX_EXTRA_PATHS) { - MQ_LOGW("mq_h3: add_path: extra-path slots exhausted"); - return -1; - } - xqc_engine_t *xeng = mq_transport_xqc(c->transport); - if (!xeng) { - return -1; - } - - /* 1. Ask xquic to allocate a new path-id (needs a spare peer cid; that is - * the ready_to_create_path precondition). path_status 0 == AVAILABLE. */ - uint64_t new_path_id = 0; - xqc_int_t rc = xqc_conn_create_path(xeng, &c->cid, &new_path_id, /*AVAILABLE=*/0); - if (rc != XQC_OK) { - MQ_LOGW("mq_h3: xqc_conn_create_path failed: %d (mp_ready=%d)", (int)rc, - c->mp_ready); - return -1; - } - - /* 2. Ask the runtime (via the transport) to open a UDP socket for it. */ - if (mq_transport_open_path(c->transport, new_path_id, local_ip, port) != 0) { - MQ_LOGE("mq_h3: add_path: open_path_socket(path %llu) failed", - (unsigned long long)new_path_id); - /* Tear the half-created xquic path back down so it does not linger - * waiting on a socket that will never exist. */ - xqc_conn_close_path(xeng, &c->cid, new_path_id); - return -1; - } - - /* 3. Mark path status. Under --scheduler backup, mark the extra path - * STANDBY so the backup scheduler's skip-standby logic keeps all - * traffic on the primary; a PATH_STATUS frame is transmitted to the - * peer so the server-side scheduler also honours the pin (mirrors the - * logic in mq_conn_add_path). All other schedulers leave AVAILABLE. */ - if (mq_conn_scheduler() == MQ_SCHED_BACKUP) { - if (xqc_conn_mark_path_standby(xeng, &c->cid, new_path_id) != XQC_OK) { - MQ_LOGW("mq_h3: mark_path_standby(path %llu) failed; path stays " - "AVAILABLE -- backup pin NOT in effect", - (unsigned long long)new_path_id); - } else { - MQ_LOGI("mq_h3: path id=%llu marked standby (backup scheduler)", - (unsigned long long)new_path_id); - } - } else { - if (xqc_conn_mark_path_available(xeng, &c->cid, new_path_id) != XQC_OK) { - MQ_LOGW("mq_h3: mark_path_available(path %llu) failed", - (unsigned long long)new_path_id); - /* Non-fatal: the path was created + socket bound; keep it alive. */ - } - } - - c->extra_path_ids[c->n_extra_paths++] = new_path_id; - return (int)new_path_id; -} - -void -mq_h3_conn_dump_stats(mq_h3_conn_t *c) -{ - if (!c || !c->have_cid) { - return; - } - /* Both H3 and raw conns share ONE engine, so the cid alone selects the - * connection; delegate to mq_conn's shared cid-keyed core. */ - mq_conn_dump_stats_cid(c->transport, &c->cid); -} - -void -mq_h3_conn_close(mq_h3_conn_t *c) -{ - if (!c || !c->have_cid) { - return; - } - /* Request close only; the wrapper is freed centrally in close_notify. */ - xqc_h3_conn_close(mq_transport_xqc(c->transport), &c->cid); -} - -/* ── request ────────────────────────────────────────────────────────────── */ - -void -mq_h3_req_set_cbs(mq_h3_req_t *r, mq_h3_req_on_read_fn on_read, - mq_h3_req_on_write_fn on_write, mq_h3_req_on_close_fn on_close, - void *user) -{ - if (!r) { - return; - } - r->on_read = on_read; - r->on_write = on_write; - r->on_close = on_close; - r->cb_user = user; -} - -mq_h3_req_t * -mq_h3_req_open(mq_h3_conn_t *c) -{ - if (!c || !c->have_cid) { - return NULL; - } - mq_h3_req_t *req = calloc(1, sizeof(*req)); - if (!req) { - return NULL; - } - req->conn = c; - /* Pass the wrapper as the request user_data so the synchronous - * create_notify (and later read/write/close notifies) recover it. This is - * the independent per-stream slot — safe to own (unlike the conn slot). - * - * No defensive post-create bind (unlike mq_conn_open_stream) is needed here: - * the request handle is only available as the return value of - * xqc_h3_request_create, and no callback can fire on a brand-new - * locally-initiated request before the caller has stored the return value. */ - xqc_h3_request_t *r = - xqc_h3_request_create(mq_transport_xqc(c->transport), &c->cid, NULL, req); - if (!r) { - MQ_LOGW("mq_h3: xqc_h3_request_create failed"); - free(req); - return NULL; - } - req->r = r; - return req; -} - -/* Normalise an xquic send return like mq_stream_send: -XQC_EAGAIN -> 0 (retry - * after on_write), other negatives -> -1, else the byte/finish count. */ -static long -mq_h3_norm_send(ssize_t rv, const char *what) -{ - if (rv == -XQC_EAGAIN) { - return 0; - } - if (rv < 0) { - MQ_LOGW("mq_h3: %s failed: %zd", what, rv); - return -1; - } - return (long)rv; -} - -long -mq_h3_req_send_headers(mq_h3_req_t *r, const mq_h3_header_t *hs, size_t n, int fin) -{ - if (!r || !r->r || (n && !hs)) { - return -1; - } - if (n == 0 && !fin) { - return 0; - } - - /* Build the xqc_http_headers_t. Header name/value are NUL-terminated C - * strings; frame each as an iovec (strlen). The header array is stack/heap - * scratch consumed entirely by xqc_h3_request_send_headers. */ - xqc_http_header_t *arr = NULL; - if (n) { - arr = calloc(n, sizeof(*arr)); - if (!arr) { - return -1; - } - for (size_t i = 0; i < n; i++) { - const char *name = hs[i].name ? hs[i].name : ""; - const char *value = hs[i].value ? hs[i].value : ""; - arr[i].name.iov_base = (void *)name; - arr[i].name.iov_len = strlen(name); - arr[i].value.iov_base = (void *)value; - arr[i].value.iov_len = strlen(value); - arr[i].flags = XQC_HTTP_HEADER_FLAG_NONE; - } - } - xqc_http_headers_t headers; - memset(&headers, 0, sizeof(headers)); - headers.headers = arr; - headers.count = n; - headers.capacity = n; - - ssize_t rv = xqc_h3_request_send_headers(r->r, &headers, fin ? 1 : 0); - free(arr); - return mq_h3_norm_send(rv, "send_headers"); -} - -long -mq_h3_req_send_body(mq_h3_req_t *r, const uint8_t *p, size_t len, int fin) -{ - if (!r || !r->r) { - return -1; - } - /* xqc_h3_request_send_body takes a non-const pointer but does not mutate the - * buffer; cast away const. */ - union { - const uint8_t *cp; - unsigned char *p; - } u = {.cp = p}; - ssize_t rv = xqc_h3_request_send_body(r->r, u.p, len, fin ? 1 : 0); - return mq_h3_norm_send(rv, "send_body"); -} - -long -mq_h3_req_finish(mq_h3_req_t *r) -{ - if (!r || !r->r) { - return -1; - } - ssize_t rv = xqc_h3_request_finish(r->r); - return mq_h3_norm_send(rv, "finish"); -} - -int -mq_h3_req_recv_headers(mq_h3_req_t *r, - void (*each)(const char *n, size_t nl, const char *v, size_t vl, - void *u), - void *u, int *fin) -{ - if (fin) { - *fin = 0; - } - if (!r || !r->r) { - return -1; - } - uint8_t xfin = 0; - /* xquic OWNS the returned headers; they are valid only here (consumed - * within this call) — copy out via `each` if the caller needs them later. - * NULL is returned both on hard error AND when no header section is pending - * (READ_HEADER/READ_TRAILER not set); callers must gate on the notify flag. */ - xqc_http_headers_t *hdrs = xqc_h3_request_recv_headers(r->r, &xfin); - if (!hdrs) { - return -1; - } - if (each) { - for (size_t i = 0; i < hdrs->count; i++) { - const xqc_http_header_t *hh = &hdrs->headers[i]; - each((const char *)hh->name.iov_base, hh->name.iov_len, - (const char *)hh->value.iov_base, hh->value.iov_len, u); - } - } - if (fin) { - *fin = xfin ? 1 : 0; - } - return (int)hdrs->count; -} - -long -mq_h3_req_recv_body(mq_h3_req_t *r, uint8_t *buf, size_t cap, int *fin) -{ - if (fin) { - *fin = 0; - } - if (!r || !r->r) { - return -1; - } - uint8_t xfin = 0; - ssize_t rv = xqc_h3_request_recv_body(r->r, (unsigned char *)buf, cap, &xfin); - if (rv == -XQC_EAGAIN) { - return 0; - } - if (rv < 0) { - MQ_LOGW("mq_h3: recv_body failed: %zd", rv); - return -1; - } - if (fin) { - *fin = xfin ? 1 : 0; - } - return (long)rv; -} - -void -mq_h3_req_reset(mq_h3_req_t *r) -{ - if (!r || !r->r) { - return; - } - xqc_h3_request_close(r->r); -} - -int -mq_h3_req_get_stats(mq_h3_req_t *r, xqc_request_stats_t *out) -{ - if (!r || !r->r || !out) { - return -1; - } - *out = xqc_h3_request_get_stats(r->r); - return 0; -} - -uint64_t -mq_h3_req_stream_id(mq_h3_req_t *r) -{ - if (!r || !r->r) { - return 0; - } - return (uint64_t)xqc_h3_stream_id(r->r); -} diff --git a/src/transport/mq_h3.h b/src/transport/mq_h3.h deleted file mode 100644 index e0159b4f..00000000 --- a/src/transport/mq_h3.h +++ /dev/null @@ -1,208 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_h3.h — wrapper exposing xquic's built-in HTTP/3 stack at the sans-io - * transport core boundary (design §6.3 / §14 / §24). - * - * mq_h3 is the H3 counterpart of mq_conn + mq_stream: where those wrap raw - * MPQUIC conns/streams for the "mqproxy-tcp/1" ALPN, mq_h3 wraps xquic's H3 - * conns/requests for the "h3"/"h3-29" ALPN. xquic owns the H3 framing, QPACK, - * and control streams; mq_h3 only adds an owner-facing wrapper, a connection - * recovery table, multipath plumbing (shared with mq_conn), and the send/recv - * normalisation the proxy expects. - * - * USER_DATA SCHEME (CRITICAL — see the ABSOLUTE RULES below): - * - transport user_data = the mq_transport_t* (so write_socket_ex recovers - * the transport by casting the conn's user_data). - * - h3 conn user_data = ALSO the mq_transport_t*. xquic's internal H3 - * ALPN sets the H3 conn's user_data = the transport - * user_data; our h3 conn callbacks therefore receive - * the TRANSPORT pointer as h3c_user_data, NOT our - * wrapper. We recover the mq_h3_conn wrapper from a - * small fixed table keyed by xqc_h3_conn_t*. - * - h3 request user_data = the mq_h3_req_t* (an independent, per-stream slot, - * safe to own). Set at create on the client side and - * via xqc_h3_request_set_user_data on the server side. - * - * ABSOLUTE RULES (violating the first two corrupts the send path): - * 1. NEVER call xqc_h3_conn_set_user_data — it overwrites the conn's - * transport user_data slot (xqc_h3_conn.c sets transport_user_data), and - * mq_transport_write_socket_ex casts that slot to mq_transport_t*. The - * transport user_data must stay = mq_transport_t* forever. The client - * passes the transport as xqc_h3_connect's user_data; the server's - * transport user_data is already bound by mq_transport_server_accept. - * 2. NEVER call xqc_engine_set_priv_ctx — that slot belongs to the - * "mqproxy-tcp/1" ALPN registration owned by mq_conn. - * - * LIFETIME / ORDERING: - * - The caller MUST free the mq_h3 (mq_h3_free) BEFORE mq_transport_free, - * because mq_h3_free destroys the H3 ctx (xqc_h3_ctx_destroy), which - * unregisters the H3 ALPNs and frees the H3 ctx via the still-live engine; - * mq_transport_free then destroys the engine. - * - One mq_h3 per transport (xqc_h3_ctx_init re-registers the H3 ALPN, so it - * is not idempotent); mq_h3_init guards against a double init. - */ -#ifndef MQ_TRANSPORT_MQ_H3_H -#define MQ_TRANSPORT_MQ_H3_H - -#include -#include -#include - -#include -#include - -#include "transport/mq_conn.h" /* mq_cc_t */ -#include "transport/mq_transport.h" - -typedef struct mq_h3_s mq_h3_t; -typedef struct mq_h3_conn_s mq_h3_conn_t; -typedef struct mq_h3_req_s mq_h3_req_t; - -/* Server-side hooks. on_conn fires when xquic accepts a peer H3 connection; - * on_req fires when a peer opens an H3 request on an accepted connection. Both - * may be NULL on a pure client. The yielded wrapper is freshly created; the - * owner attaches its own context via mq_h3_req_set_cbs (requests) and may stash - * state through the wrapper. */ -typedef void (*mq_h3_on_new_conn_fn)(mq_h3_conn_t *c, void *user); -typedef void (*mq_h3_on_new_req_fn)(mq_h3_req_t *r, void *user); - -/* Initialise the H3 stack on `t`'s engine and register the H3 ALPN callbacks. - * on_conn / on_req / user are the server-side surfacing hooks (NULL on a pure - * client). Returns a new mq_h3, or NULL on failure or if `t` already has an - * mq_h3 (one per transport — xqc_h3_ctx_init is not idempotent). The returned - * mq_h3 MUST be freed (mq_h3_free) before mq_transport_free. */ -mq_h3_t *mq_h3_init(mq_transport_t *t, mq_h3_on_new_conn_fn on_conn, - mq_h3_on_new_req_fn on_req, void *user); - -/* Destroy the H3 ctx and free the mq_h3. MUST run before mq_transport_free - * (the H3 ctx is destroyed via the still-live engine). Any conn wrappers still - * in the recovery table are freed by their h3_conn_close_notify during the - * engine teardown in mq_transport_free, NOT here. */ -void mq_h3_free(mq_h3_t *h); - -/* ── Connection ─────────────────────────────────────────────────────────── */ - -/* Connection lifecycle reported to the owner. established!=0 once the H3 - * handshake finished; established==0 once the connection closed (the - * mq_h3_conn is freed immediately after this callback returns). */ -typedef void (*mq_h3_conn_state_fn)(mq_h3_conn_t *c, int established, void *user); - -/* Client: initiate an H3 connection to `peer` with congestion control `cc`. - * `st` / `user` receive lifecycle transitions (may be NULL). `keepalive_idle_ms` - * enables xquic PING keepalive when > 0 (and sets the post-handshake idle - * timeout to that value); 0 disables keepalive. Returns a new mq_h3_conn - * (callbacks fire later) or NULL on failure. */ -mq_h3_conn_t *mq_h3_connect(mq_h3_t *h, const struct sockaddr *peer, socklen_t peerlen, - mq_cc_t cc, uint64_t keepalive_idle_ms, - mq_h3_conn_state_fn st, void *user); - -/* 1 once xquic fired ready_to_create_path_notify for this conn (cids exchanged) - * — i.e. mq_h3_conn_add_path can now succeed. 0 before that (or on NULL). - * Client conns only. */ -int mq_h3_conn_mp_ready(const mq_h3_conn_t *c); - -/* Re-point (or DETACH, with st==NULL) this conn's lifecycle callback. The owner - * MUST call this with st==NULL before it frees the `user` it passed to - * mq_h3_connect, if the owner can be freed while the conn is still alive: the - * conn outlives mq_h3_connect's caller and its close transition fires during - * mq_h3_free / engine teardown, so a dangling `user` would be a use-after-free. - * Safe on NULL. */ -void mq_h3_conn_set_state_cb(mq_h3_conn_t *c, mq_h3_conn_state_fn st, void *user); - -/* Create a NEW MPQUIC path bound to local_ip:port (port 0 == ephemeral), open - * its UDP socket via the transport, and mark it available. MUST be called after - * mq_h3_conn_mp_ready(c) == 1. Returns the new path_id (>0) or -1 on failure. - * Client conns only. Mirrors mq_conn_add_path. */ -int mq_h3_conn_add_path(mq_h3_conn_t *c, const char *local_ip, uint16_t port); - -/* Snapshot + log per-path byte counters (spec §23.1) for this conn. Delegates - * to mq_conn_dump_stats_cid (shared core). Smoke-safe on any conn. */ -void mq_h3_conn_dump_stats(mq_h3_conn_t *c); - -/* Send CONNECTION_CLOSE. The mq_h3_conn is freed later via h3_conn_close_notify - * (peer close / idle / error all flow there too — freeing is centralised). - * No production caller today — exercised by the fabric tests and reserved for - * future graceful-shutdown; kept deliberately rather than removed. */ -void mq_h3_conn_close(mq_h3_conn_t *c); - -/* ── Request ────────────────────────────────────────────────────────────── */ - -/* Request callbacks. on_read fires when headers/body/trailer/fin are ready - * (`flag` is the xqc_request_notify_flag_t bitmask: READ_HEADER=1, READ_BODY=2, - * READ_TRAILER=4, READ_EMPTY_FIN=8). on_write fires when a previously-blocked - * send can resume. on_close fires once, just before the mq_h3_req is freed (the - * owner must drop its pointer afterwards). Any may be NULL. */ -typedef void (*mq_h3_req_on_read_fn)(mq_h3_req_t *r, int flag, void *user); -typedef void (*mq_h3_req_on_write_fn)(mq_h3_req_t *r, void *user); -typedef void (*mq_h3_req_on_close_fn)(mq_h3_req_t *r, void *user); - -/* Register the request's owner callbacks (any may be NULL). */ -void mq_h3_req_set_cbs(mq_h3_req_t *r, mq_h3_req_on_read_fn on_read, - mq_h3_req_on_write_fn on_write, mq_h3_req_on_close_fn on_close, - void *user); - -/* Client: open a new H3 request on `c`. Returns a new mq_h3_req or NULL. */ -mq_h3_req_t *mq_h3_req_open(mq_h3_conn_t *c); - -/* A single request/response header. name/value are NUL-terminated C strings - * (the send path measures them with strlen and frames them as iovecs). */ -typedef struct { - const char *name, *value; -} mq_h3_header_t; - -/* Send `n` headers; fin!=0 finishes the request after this write (no body). - * Returns bytes accepted (>0), 0 if flow-control blocked (was -XQC_EAGAIN — - * retry after on_write), or -1 on a hard error. Normalised like mq_stream_send. - * Header values are NUL-terminated C strings. */ -long mq_h3_req_send_headers(mq_h3_req_t *r, const mq_h3_header_t *hs, size_t n, int fin); - -/* Send up to len body bytes; fin!=0 finishes the request after this write. - * Returns bytes accepted (>0), 0 if blocked (retry after on_write), -1 on - * error. Normalised like mq_stream_send. */ -long mq_h3_req_send_body(mq_h3_req_t *r, const uint8_t *p, size_t len, int fin); - -/* Send a bare fin (a STREAM frame carrying only fin) when there is nothing left - * to send. Returns bytes accepted (>0)/0 finished, 0 if blocked, -1 on error. */ -long mq_h3_req_finish(mq_h3_req_t *r); - -/* Receive the request/response headers, invoking `each` once per header with - * borrowed name/value pointers (valid only during the call — copy out if - * needed). *fin (if non-NULL) is set to 1 if the headers carry fin (no body). - * Returns the number of headers delivered (>=0), or -1 on error. xquic owns the - * returned header memory; it is consumed entirely within this call. - * - * CALL GATING: xqc_h3_request_recv_headers returns NULL (mapped to -1 here) - * both on hard error AND when no header section is pending. Callers MUST gate - * calls on the READ_HEADER (flag & 1) or READ_TRAILER (flag & 4) bit from the - * on_read notify to avoid spurious -1 returns. - * - * TRAILERS: one call drains exactly one header section. If the request carries - * trailers, a second call is required after the READ_TRAILER notify fires — - * headers come first, trailers come second. */ -int mq_h3_req_recv_headers(mq_h3_req_t *r, - void (*each)(const char *n, size_t nl, const char *v, - size_t vl, void *u), - void *u, int *fin); - -/* Receive up to cap body bytes into buf. *fin (if non-NULL) is set to 1 if the - * peer finished the request. Returns bytes read (>=0), or -1 on error. 0 with - * *fin==0 means no body available right now (was -XQC_EAGAIN). */ -long mq_h3_req_recv_body(mq_h3_req_t *r, uint8_t *buf, size_t cap, int *fin); - -/* Read the request's transport stats (timings, mp_state, body sizes, stream_err, - * stream_close_msg). Valid only while the request is live — e.g. from inside the - * on_close callback, where the wrapper is freed only AFTER the callback returns. - * Returns 0 and fills *out on success; -1 if r/out is NULL or the inner handle - * is gone. */ -int mq_h3_req_get_stats(mq_h3_req_t *r, xqc_request_stats_t *out); - -/* The request's MPQUIC stream id (for log correlation). 0 if r is NULL/dead. */ -uint64_t mq_h3_req_stream_id(mq_h3_req_t *r); - -/* Close the request. xqc_h3_request_close sends RESET_STREAM only if the - * request has not already completed; on a finished request it is a graceful - * close. The mq_h3_req is freed later via h3_request_close_notify. */ -void mq_h3_req_reset(mq_h3_req_t *r); - -#endif /* MQ_TRANSPORT_MQ_H3_H */ diff --git a/src/transport/mq_stream.c b/src/transport/mq_stream.c deleted file mode 100644 index 410a2638..00000000 --- a/src/transport/mq_stream.c +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_stream.c — raw MPQUIC stream wrapper. - * - * mq_stream stores an xqc_stream_t and registers itself as that stream's - * user_data (xqc_stream_set_user_data / the user_data arg of - * xqc_stream_create). xquic's stream_read/write/close notifications recover - * the mq_stream and forward to owner callbacks. - */ -#include "transport/mq_stream.h" - -#include -#include - -#include "transport/mq_stream_internal.h" -#include "util/mq_log.h" - -mq_stream_t * -mq_stream_wrap(xqc_stream_t *xs) -{ - if (!xs) { - return NULL; - } - mq_stream_t *s = calloc(1, sizeof(*s)); - if (!s) { - return NULL; - } - s->xs = xs; - xqc_stream_set_user_data(xs, s); - return s; -} - -mq_stream_t * -mq_stream_alloc(void) -{ - return calloc(1, sizeof(struct mq_stream_s)); -} - -void -mq_stream_bind(mq_stream_t *s, xqc_stream_t *xs) -{ - if (!s || !xs) { - return; - } - s->xs = xs; - xqc_stream_set_user_data(xs, s); -} - -void -mq_stream_free(mq_stream_t *s) -{ - free(s); -} - -void -mq_stream_set_cbs(mq_stream_t *s, mq_stream_on_readable_fn on_readable, - mq_stream_on_writable_fn on_writable, mq_stream_on_close_fn on_close, - void *user) -{ - if (!s) { - return; - } - s->on_readable = on_readable; - s->on_writable = on_writable; - s->on_close = on_close; - s->cb_user = user; -} - -long -mq_stream_send(mq_stream_t *s, const void *buf, size_t len, int fin) -{ - if (!s || !s->xs) { - return -1; - } - /* xqc_stream_send takes a non-const pointer but does not mutate the - * buffer; cast away const. */ - union { - const void *cp; - unsigned char *p; - } u = {.cp = buf}; - ssize_t r = xqc_stream_send(s->xs, u.p, len, fin ? 1 : 0); - if (r == -XQC_EAGAIN) { - return 0; /* flow-control blocked; caller retries on on_writable */ - } - if (r < 0) { - MQ_LOGW("mq_stream: send failed: %zd", r); - return -1; - } - return (long)r; -} - -long -mq_stream_recv(mq_stream_t *s, void *buf, size_t cap, int *fin) -{ - if (!s || !s->xs) { - return -1; - } - uint8_t xfin = 0; - ssize_t r = xqc_stream_recv(s->xs, (unsigned char *)buf, cap, &xfin); - if (r == -XQC_EAGAIN) { - if (fin) { - *fin = 0; - } - return 0; - } - if (r < 0) { - MQ_LOGW("mq_stream: recv failed: %zd", r); - return -1; - } - if (fin) { - *fin = xfin ? 1 : 0; - } - return (long)r; -} - -void -mq_stream_close(mq_stream_t *s) -{ - if (!s || !s->xs) { - return; - } - xqc_stream_close(s->xs); -} - -uint64_t -mq_stream_id(const mq_stream_t *s) -{ - if (!s || !s->xs) { - return 0; - } - return xqc_stream_id(s->xs); -} - -void * -mq_stream_conn(const mq_stream_t *s) -{ - return s ? s->conn : NULL; -} - -void -mq_stream_set_conn(mq_stream_t *s, void *conn) -{ - if (s) { - s->conn = conn; - } -} - -/* ── xquic stream notify callbacks (used by mq_conn's ALP tables) ───────── */ - -xqc_int_t -mq_stream_read_notify(xqc_stream_t *stream, void *strm_user_data) -{ - (void)stream; - mq_stream_t *s = (mq_stream_t *)strm_user_data; - if (s && s->on_readable) { - s->on_readable(s, s->cb_user); - } - return 0; -} - -xqc_int_t -mq_stream_write_notify(xqc_stream_t *stream, void *strm_user_data) -{ - (void)stream; - mq_stream_t *s = (mq_stream_t *)strm_user_data; - if (s && s->on_writable) { - s->on_writable(s, s->cb_user); - } - return 0; -} - -xqc_int_t -mq_stream_close_notify(xqc_stream_t *stream, void *strm_user_data) -{ - (void)stream; - mq_stream_t *s = (mq_stream_t *)strm_user_data; - if (!s) { - return 0; - } - if (s->on_close) { - s->on_close(s, s->cb_user); - } - mq_stream_free(s); - return 0; -} diff --git a/src/transport/mq_stream.h b/src/transport/mq_stream.h deleted file mode 100644 index 24162999..00000000 --- a/src/transport/mq_stream.h +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_stream.h — wrapper around one raw MPQUIC (xqc_stream_t) stream. - * - * An mq_stream owns the application-side view of a single bidirectional QUIC - * stream: it stores the xqc_stream_t, registers itself as the stream's - * user_data so xquic's read/write/close notifications recover it, and - * forwards those notifications to an owner via mq_stream_set_cbs. - * - * Lifetime: an mq_stream is created either by mq_conn_open_stream (client, - * locally initiated) or by xquic's stream_create_notify (server, peer - * initiated). It is freed from the stream_close_notify path. - */ -#ifndef MQ_TRANSPORT_MQ_STREAM_H -#define MQ_TRANSPORT_MQ_STREAM_H - -#include -#include - -#include - -typedef struct mq_stream_s mq_stream_t; - -/* Owner callbacks. on_readable fires when stream data is ready to read; - * on_writable fires when a previously-blocked send can resume; on_close fires - * once, just before the mq_stream is freed (the owner must drop its pointer - * and must not call mq_stream_* afterwards). user is the value passed to - * mq_stream_set_cbs. */ -typedef void (*mq_stream_on_readable_fn)(mq_stream_t *s, void *user); -typedef void (*mq_stream_on_writable_fn)(mq_stream_t *s, void *user); -typedef void (*mq_stream_on_close_fn)(mq_stream_t *s, void *user); - -/* Register owner callbacks (any may be NULL). */ -void mq_stream_set_cbs(mq_stream_t *s, mq_stream_on_readable_fn on_readable, - mq_stream_on_writable_fn on_writable, - mq_stream_on_close_fn on_close, void *user); - -/* Send up to len bytes; fin!=0 closes the send direction after this write. - * Returns bytes accepted (>=0), or -1 on a hard error. A return of 0 with a - * non-empty buffer means flow control is blocked (was -XQC_EAGAIN) — retry - * after the on_writable callback. */ -long mq_stream_send(mq_stream_t *s, const void *buf, size_t len, int fin); - -/* Recv up to cap bytes. *fin is set to 1 if the peer closed the stream. - * Returns bytes read (>=0), or -1 on error. 0 with *fin==0 means no data - * available right now (was -XQC_EAGAIN). */ -long mq_stream_recv(mq_stream_t *s, void *buf, size_t cap, int *fin); - -/* Send RESET_STREAM. The mq_stream is freed later via stream_close_notify. */ -void mq_stream_close(mq_stream_t *s); - -/* The underlying xquic stream id. */ -uint64_t mq_stream_id(const mq_stream_t *s); - -/* The mq_conn_t* that owns this stream, or NULL. Set by the transport when a - * peer-initiated stream is surfaced, so the owner can recover the connection - * (e.g. to attach/look up per-conn state). Returned as void* to avoid a header - * cycle; callers cast to mq_conn_t*. */ -void *mq_stream_conn(const mq_stream_t *s); - -#endif /* MQ_TRANSPORT_MQ_STREAM_H */ diff --git a/src/transport/mq_stream_internal.h b/src/transport/mq_stream_internal.h deleted file mode 100644 index 55f346d9..00000000 --- a/src/transport/mq_stream_internal.h +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_stream_internal.h — definitions shared between mq_stream.c and mq_conn.c. - * - * Not part of the public transport API. mq_conn needs to construct/wrap - * mq_streams and reference the xquic stream notify callbacks when building - * the ALP callback tables. - */ -#ifndef MQ_TRANSPORT_MQ_STREAM_INTERNAL_H -#define MQ_TRANSPORT_MQ_STREAM_INTERNAL_H - -#include - -#include "transport/mq_stream.h" - -struct mq_stream_s { - xqc_stream_t *xs; - void *conn; /* owning mq_conn_t* (set by mq_conn for peer streams) */ - - mq_stream_on_readable_fn on_readable; - mq_stream_on_writable_fn on_writable; - mq_stream_on_close_fn on_close; - void *cb_user; -}; - -/* Set/get the owning mq_conn_t* (opaque void* to avoid a header cycle). */ -void mq_stream_set_conn(mq_stream_t *s, void *conn); - -/* Allocate an mq_stream around an existing xqc_stream_t and register it as - * the stream's user_data. Returns NULL on alloc failure. */ -mq_stream_t *mq_stream_wrap(xqc_stream_t *xs); - -/* Allocate an unbound mq_stream (no xqc_stream_t yet). Used by the client - * stream-open path so the wrapper can be handed to xqc_stream_create as - * user_data and bound from stream_create_notify, avoiding a double-wrap. */ -mq_stream_t *mq_stream_alloc(void); - -/* Bind a previously-allocated mq_stream to an xqc_stream_t and register it as - * the stream's user_data. */ -void mq_stream_bind(mq_stream_t *s, xqc_stream_t *xs); - -/* Free the mq_stream struct (does not touch xquic state). */ -void mq_stream_free(mq_stream_t *s); - -/* xquic stream notify callbacks, wired into the ALP stream callback table. */ -xqc_int_t mq_stream_read_notify(xqc_stream_t *stream, void *strm_user_data); -xqc_int_t mq_stream_write_notify(xqc_stream_t *stream, void *strm_user_data); -xqc_int_t mq_stream_close_notify(xqc_stream_t *stream, void *strm_user_data); - -#endif /* MQ_TRANSPORT_MQ_STREAM_INTERNAL_H */ diff --git a/src/transport/mq_transport.c b/src/transport/mq_transport.c deleted file mode 100644 index f84ed516..00000000 --- a/src/transport/mq_transport.c +++ /dev/null @@ -1,655 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_transport.c — sans-io transport core (design §4.1/§4.2). - * - * mq_transport owns its own xqc_engine directly: no mq_engine, no libevent, no - * event_base, no sockets. xquic's UDP send is routed through the send_udp - * callback (the runtime owns the path-id->fd map). set_event_timer records a - * deadline instead of arming a libevent timer; the runtime polls it via - * mq_transport_next_timeout_ms (Chunk 4) and drives the engine via tick. - * - * Engine/ssl/config setup is a faithful port of mq_engine's mq_engine_new_impl; - * the engine/transport/ssl callback tables mirror mq_engine.c. - * - * NB: src/transport/mq_transport.c must stay libevent-free. - */ -#include "transport/mq_transport.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "util/mq_log.h" - -/* Dimension of the mp-ready subscriber table. Production subscribers are exactly - * two — the tcp conn and the h3 conn — but the table carries headroom so a test - * can register an extra (negative-path filter) subscriber alongside the real - * conns, and to absorb a future third proto without a struct change. The - * overflow guard in mq_transport_add_mp_ready_cb stays regardless. */ -#define MQ_MP_READY_SUBS_MAX 4 - -struct mq_transport_s { - xqc_engine_t *engine; - mq_transport_callbacks_t cbs; - void *user; - int is_server; - - /* Multipath readiness subscribers (added via mq_transport_add_mp_ready_cb). - * Fixed table: production subscribers are the tcp conn and the h3 conn; - * MQ_MP_READY_SUBS_MAX carries headroom for tests/futures (see its comment). - * ready_to_create_path is broadcast to every populated slot. */ - struct { - mq_transport_mp_ready_fn fn; - void *user; - } mp_ready_subs[MQ_MP_READY_SUBS_MAX]; - int n_mp_ready_subs; - - /* qlog sink. qlog_fd < 0 == disabled (default). The xquic qlog callback - * writes rendered lines here when armed via mq_transport_enable_qlog. */ - int qlog_fd; - char qlog_path[512]; - - /* Deadline recorded by set_event_timer; the runtime polls it (Chunk 4). */ - uint64_t next_deadline_us; - int have_deadline; - - /* Engine-wide cap on established inbound conns (pre-auth DoS, 2026-06-15). - * max_conns == 0 disables. last_refuse_log_us throttles the refusal WARN to - * once/sec. */ - uint32_t max_conns; - uint32_t n_conns; - uint64_t last_refuse_log_us; -}; - -/* Wall-clock microseconds since the epoch — the SAME epoch as xquic's xqc_now / - * mq_path_now_us (gettimeofday-based). Do NOT use CLOCK_MONOTONIC: set_event_timer - * deadlines are compared against xquic's internal clock. */ -static xqc_usec_t -mq_tr_now_us(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (xqc_usec_t)tv.tv_sec * 1000000 + (xqc_usec_t)tv.tv_usec; -} - -/* ── engine callbacks ─────────────────────────────────────────────────── */ - -/* xquic set_event_timer: record the next wake deadline instead of arming a - * libevent timer (the runtime owns the timer). engine_user_data is the - * mq_transport_t* passed to xqc_engine_create. */ -static void -mq_transport_set_event_timer(xqc_usec_t wake_after, void *engine_user_data) -{ - mq_transport_t *t = (mq_transport_t *)engine_user_data; - if (!t) { - return; - } - t->next_deadline_us = mq_tr_now_us() + wake_after; - t->have_deadline = 1; - /* Notify the caller that the deadline changed so it can (re)arm its timer - * immediately — even when set_event_timer is invoked from an app-initiated - * path (connect / stream send) that is not followed by a tick or recv. - * Without this, a connect-time retransmission timer would not arm until the - * next inbound packet, stalling a connect whose first packet is lost. */ - if (t->cbs.on_timer) { - t->cbs.on_timer(t->user); - } -} - -/* xquic log callback (REQUIRED by the engine). Route to mq_log. */ -static void -mq_transport_log_write(xqc_log_level_t lvl, const void *buf, size_t size, - void *engine_user_data) -{ - (void)engine_user_data; - int ml; - switch (lvl) { - case XQC_LOG_REPORT: - case XQC_LOG_FATAL: - case XQC_LOG_ERROR: ml = MQ_LOG_ERROR; break; - case XQC_LOG_WARN: ml = MQ_LOG_WARN; break; - case XQC_LOG_STATS: - case XQC_LOG_INFO: ml = MQ_LOG_INFO; break; - case XQC_LOG_DEBUG: - default: ml = MQ_LOG_DEBUG; break; - } - mq_log(ml, "[xquic] %.*s", (int)size, (const char *)buf); -} - -/* xquic qlog event sink. engine_user_data is the mq_transport_t* passed to - * xqc_engine_create. Writes the rendered qlog line + newline to the armed qlog - * fd; a no-op until mq_transport_enable_qlog opens the file (fd < 0). */ -static void -mq_transport_qlog_write(qlog_event_importance_t imp, const void *buf, size_t size, - void *engine_user_data) -{ - (void)imp; - mq_transport_t *t = (mq_transport_t *)engine_user_data; - if (!t || t->qlog_fd < 0) { - return; - } - ssize_t w = write(t->qlog_fd, buf, size); - if (w < 0) { - return; - } - static const char nl = '\n'; - /* glibc marks write() warn_unused_result; a bare (void) cast does not - * silence it under -Werror. qlog is best-effort, so ignore via a named - * sink the same way as the payload write above. */ - ssize_t w2 = write(t->qlog_fd, &nl, 1); - (void)w2; -} - -/* Multipath send callback. Route the outbound packet through the runtime's - * send_udp hook. conn_user_data is the mq_transport_t* (bound as the - * connection's transport user_data in server_accept / by mq_conn for clients). - * Map send_udp's return: MQ_TX_AGAIN -> XQC_SOCKET_EAGAIN, <0 -> XQC_SOCKET_ERROR, - * else the byte count. */ -static ssize_t -mq_transport_write_socket_ex(uint64_t path_id, const unsigned char *buf, size_t size, - const struct sockaddr *peer_addr, socklen_t peer_addrlen, - void *conn_user_data) -{ - mq_transport_t *t = (mq_transport_t *)conn_user_data; - if (!t || !t->cbs.send_udp) { - return XQC_SOCKET_ERROR; - } - int r = t->cbs.send_udp(path_id, buf, size, peer_addr, peer_addrlen, t->user); - if (r == MQ_TX_AGAIN) { - return XQC_SOCKET_EAGAIN; - } - if (r < 0) { - return XQC_SOCKET_ERROR; - } - return r; -} - -/* Single-path send callback: delegate to the multipath form on the initial - * path. xquic requires write_socket; write_socket_ex covers multipath. */ -static ssize_t -mq_transport_write_socket(const unsigned char *buf, size_t size, - const struct sockaddr *peer_addr, socklen_t peer_addrlen, - void *conn_user_data) -{ - return mq_transport_write_socket_ex(XQC_INITIAL_PATH_ID, buf, size, peer_addr, - peer_addrlen, conn_user_data); -} - -/* At most one refusal WARN per second. server_accept fires per connection - * ATTEMPT (pre-handshake), so under a flood an unthrottled WARN is self-inflicted - * log-DoS; the throttle bounds it. mq_tr_now_us() is the file's wall-clock (the - * engine's clock); an NTP step could momentarily mis-throttle a best-effort log - * line — acceptable, and it keeps one clock source. */ -#define MQ_REFUSE_LOG_INTERVAL_US (1000000ULL) - -static void -mq_transport_note_refusal(mq_transport_t *t) -{ - if (!t) { - return; - } - uint64_t now = mq_tr_now_us(); - if (t->last_refuse_log_us != 0 && - (now - t->last_refuse_log_us) < MQ_REFUSE_LOG_INTERVAL_US) { - return; /* throttle */ - } - MQ_LOGW("mq_transport: connection refused, at max-conns limit (max=%u)", - t->max_conns); - t->last_refuse_log_us = now; -} - -/* Server accept callback (REQUIRED for server). user_data is the engine_user_data - * == the mq_transport. Refuse when the engine-wide connection cap is reached - * (pre-handshake CONNECTION_REFUSED); otherwise bind the transport as the - * connection's transport user_data so the send callback can recover it, then - * accept. The application-protocol mq_conn is created by the ALP - * conn_create_notify (see mq_conn.c). */ -static int -mq_transport_server_accept(xqc_engine_t *engine, xqc_connection_t *conn, - const xqc_cid_t *cid, void *user_data) -{ - (void)engine; - (void)cid; - mq_transport_t *t = (mq_transport_t *)user_data; - if (mq_transport_conn_at_limit(t)) { - mq_transport_note_refusal(t); - return -1; /* -> xquic emits TRA_CONNECTION_REFUSED_ERROR (0x02) */ - } - xqc_conn_set_transport_user_data(conn, t); - return 0; -} - -/* Server refuse callback. No transport-level context is allocated in accept, so - * nothing to free here. */ -static void -mq_transport_server_refuse(xqc_engine_t *engine, xqc_connection_t *conn, - const xqc_cid_t *cid, void *user_data) -{ - (void)engine; - (void)conn; - (void)cid; - (void)user_data; -} - -/* Multipath ready-to-create-path notify. xquic fires this once cids are - * exchanged (precondition for xqc_conn_create_path). conn_user_data is the - * connection's transport user_data == the mq_transport. Broadcast to every - * subscriber: with multiple conns on one transport (Phase 2: tcp + h3) the scid - * identifies WHICH conn became ready, so each subscriber filters by scid. */ -static void -mq_transport_ready_to_create_path(const xqc_cid_t *scid, void *conn_user_data) -{ - mq_transport_t *t = (mq_transport_t *)conn_user_data; - if (!t || !scid) { - return; - } - /* xquic may hand an unaligned scid pointer; copy into an aligned local once - * here so every subscriber can read its members via plain field access. */ - xqc_cid_t aligned; - memcpy(&aligned, scid, sizeof(aligned)); - for (int i = 0; i < t->n_mp_ready_subs; i++) { - t->mp_ready_subs[i].fn(&aligned, t->mp_ready_subs[i].user); - } -} - -/* CID update notify (REQUIRED for both client and server). The mq_conn keys off - * the original scid, which xquic keeps valid for the connection's life, so there - * is nothing to re-key here. */ -static void -mq_transport_conn_update_cid(xqc_connection_t *conn, const xqc_cid_t *retire_cid, - const xqc_cid_t *new_cid, void *conn_user_data) -{ - (void)conn; - (void)retire_cid; - (void)new_cid; - (void)conn_user_data; -} - -/* Client TLS certificate verify (REQUIRED for client). The proxy trusts its own - * peer (the cert is pinned out-of-band by deployment), so accept. */ -static int -mq_transport_cert_verify(const unsigned char *certs[], const size_t cert_len[], - size_t certs_len, void *conn_user_data) -{ - (void)certs; - (void)cert_len; - (void)certs_len; - (void)conn_user_data; - return 0; -} - -/* Client token / session / transport-param save callbacks (REQUIRED for - * client). No 0-RTT / resumption store in the proxy yet, so these are no-ops. */ -static void -mq_transport_save_token(const unsigned char *token, unsigned token_len, - void *conn_user_data) -{ - (void)token; - (void)token_len; - (void)conn_user_data; -} - -static void -mq_transport_save_session(const char *data, size_t data_len, void *conn_user_data) -{ - (void)data; - (void)data_len; - (void)conn_user_data; -} - -static void -mq_transport_save_tp(const char *data, size_t data_len, void *conn_user_data) -{ - (void)data; - (void)data_len; - (void)conn_user_data; -} - -/* ── lifecycle ────────────────────────────────────────────────────────── */ - -static mq_transport_t * -mq_transport_new_impl(int is_server, const char *cert_file, const char *key_file) -{ - mq_transport_t *t = calloc(1, sizeof(*t)); - if (!t) { - return NULL; - } - /* Callbacks are installed later via mq_transport_set_callbacks. */ - memset(&t->cbs, 0, sizeof(t->cbs)); - t->user = NULL; - t->is_server = is_server ? 1 : 0; - t->qlog_fd = -1; /* qlog disabled until mq_transport_enable_qlog */ - - xqc_engine_type_t type = is_server ? XQC_ENGINE_SERVER : XQC_ENGINE_CLIENT; - - xqc_engine_ssl_config_t ssl_config; - memset(&ssl_config, 0, sizeof(ssl_config)); - ssl_config.ciphers = XQC_TLS_CIPHERS; - ssl_config.groups = XQC_TLS_GROUPS; - /* Server mode needs cert_file/private_key_file to complete handshakes. - * xquic stores its own copies during xqc_engine_create, so the const cast - * here is safe (the strings are only read). */ - if (cert_file) { - ssl_config.cert_file = (char *)cert_file; - } - if (key_file) { - ssl_config.private_key_file = (char *)key_file; - } - - xqc_engine_callback_t engine_cbs = { - .set_event_timer = mq_transport_set_event_timer, - /* Inject OUR clock as xquic's time source so the engine's internal now - * (used for RTT/PTO/scheduling and to compute set_event_timer's - * wake_after) is the SAME function we use for the recv timestamp passed - * to packet_process and for the deadline math. Without this, xquic falls - * back to its own xqc_now and the two clocks only agree by coincidence - * (both gettimeofday today) — a future xquic default change or an - * injected monotonic/BOOTTIME clock would silently desync them, corrupting - * RTT samples. One clock, one source of truth (also the single mock-clock - * swap point for deterministic tests). */ - .realtime_ts = mq_tr_now_us, - .monotonic_ts = mq_tr_now_us, - .log_callbacks = - { - .xqc_log_write_err = mq_transport_log_write, - .xqc_log_write_stat = mq_transport_log_write, - /* qlog event sink: a no-op until mq_transport_enable_qlog arms - * the fd. Registered unconditionally so qlog can be enabled after - * engine create without recreating the engine. */ - .xqc_qlog_event_write = mq_transport_qlog_write, - }, - }; - - xqc_transport_callbacks_t tcbs = { - .write_socket = mq_transport_write_socket, - .write_socket_ex = mq_transport_write_socket_ex, - .conn_update_cid_notify = mq_transport_conn_update_cid, - /* Multipath: precondition signal for xqc_conn_create_path. Harmless on a - * server engine (the proxy creates paths from the client side). */ - .ready_to_create_path_notify = mq_transport_ready_to_create_path, - /* Server-only: accept/refuse and the pre-accept send path. Harmless to - * register on a client engine (never invoked). */ - .server_accept = mq_transport_server_accept, - .server_refuse = mq_transport_server_refuse, - .conn_send_packet_before_accept = mq_transport_write_socket, - /* Client-only: TLS verify + resumption-store hooks. Harmless on a server - * engine (never invoked). */ - .cert_verify_cb = mq_transport_cert_verify, - .save_token = mq_transport_save_token, - .save_session_cb = mq_transport_save_session, - .save_tp_cb = mq_transport_save_tp, - }; - - xqc_config_t config; - if (xqc_engine_get_default_config(&config, type) < 0) { - MQ_LOGE("mq_transport: xqc_engine_get_default_config failed"); - free(t); - return NULL; - } - /* qlog: emit event-based qlog at EXTRA importance so the `frames_processed` - * events (incl. DATA_BLOCKED / STREAM_DATA_BLOCKED) reach the qlog sink. - * These are xquic's defaults; set them explicitly so the 1-B blocked-frame - * instrument does not silently break if the fork's defaults drift. The sink - * itself is a no-op until mq_transport_enable_qlog. */ - config.cfg_log_event = 1; - config.cfg_qlog_importance = EVENT_IMPORTANCE_EXTRA; - - t->engine = xqc_engine_create(type, &config, &ssl_config, &engine_cbs, &tcbs, t); - if (!t->engine) { - MQ_LOGE("mq_transport: xqc_engine_create failed"); - free(t); - return NULL; - } - - return t; -} - -mq_transport_t * -mq_transport_new(int is_server) -{ - return mq_transport_new_impl(is_server, NULL, NULL); -} - -mq_transport_t * -mq_transport_new_server(const char *cert_file, const char *key_file) -{ - if (!cert_file || !key_file) { - MQ_LOGE("mq_transport: server transport requires cert_file and key_file"); - return NULL; - } - return mq_transport_new_impl(/*is_server=*/1, cert_file, key_file); -} - -void -mq_transport_free(mq_transport_t *t) -{ - if (!t) { - return; - } - /* NB: the ALP registration context is owned and freed by xquic itself in - * xqc_engine_destroy (xqc_engine_free_alpn_list). Destroy the engine FIRST - * (mirrors mq_engine_free's order); there is no event_base/timer to free. */ - if (t->engine) { - xqc_engine_destroy(t->engine); - } - /* Close the qlog fd AFTER engine destroy so any qlog lines emitted while the - * engine tears down its connections still land in the file. */ - if (t->qlog_fd >= 0) { - close(t->qlog_fd); - t->qlog_fd = -1; - } - free(t); -} - -void -mq_transport_set_callbacks(mq_transport_t *t, const mq_transport_callbacks_t *cbs, - void *user) -{ - if (!t) { - return; - } - if (cbs) { - t->cbs = *cbs; - } else { - memset(&t->cbs, 0, sizeof(t->cbs)); - } - t->user = user; -} - -int -mq_transport_on_udp_recv(mq_transport_t *t, uint64_t path, const uint8_t *pkt, size_t len, - const struct sockaddr *local, socklen_t locallen, - const struct sockaddr *peer, socklen_t peerlen) -{ - if (!t || !t->engine) { - return -1; - } - /* xquic derives the path from the (local,peer) 4-tuple; the path-id arg is - * not needed here (it identifies the receiving socket on the runtime side). */ - (void)path; - xqc_usec_t now = mq_tr_now_us(); - xqc_int_t rc = xqc_engine_packet_process(t->engine, pkt, len, local, locallen, peer, - peerlen, now, t); - xqc_engine_finish_recv(t->engine); - return (int)rc; -} - -void -mq_transport_tick(mq_transport_t *t) -{ - if (!t) { - return; - } - /* Clear any previously-recorded deadline BEFORE running the engine. xquic - * only calls set_event_timer when it wants a (re)wakeup; main_logic below - * re-arms the deadline via set_event_timer iff it still has pending work. - * Without this clear, a stale (already-elapsed) deadline makes - * next_timeout_ms return 0 forever, so the runtime re-arms a 0ms timer in a - * tight loop and spins at 100% CPU (a perpetually self-re-adding 0ms libevent - * timer never lets event_base_loop return). This mirrors the old mq_engine - * one-shot timer that was armed only on demand by set_event_timer. */ - t->have_deadline = 0; - xqc_engine_main_logic(t->engine); -} - -int64_t -mq_transport_next_timeout_us(mq_transport_t *t) -{ - if (!t || !t->have_deadline) { - return -1; - } - int64_t now = (int64_t)mq_tr_now_us(); - int64_t d = (int64_t)t->next_deadline_us - now; - if (d < 0) { - d = 0; - } - return d; -} - -int -mq_transport_next_timeout_ms(mq_transport_t *t) -{ - int64_t us = mq_transport_next_timeout_us(t); - if (us < 0) { - return -1; - } - return (int)(us / 1000); -} - -xqc_engine_t * -mq_transport_xqc(mq_transport_t *t) -{ - return t ? t->engine : NULL; -} - -void -mq_transport_set_max_conns(mq_transport_t *t, uint32_t max_conns) -{ - if (t) { - t->max_conns = max_conns; - } -} - -int -mq_transport_conn_at_limit(const mq_transport_t *t) -{ - return t && t->max_conns != 0 && t->n_conns >= t->max_conns; -} - -void -mq_transport_conn_inc(mq_transport_t *t) -{ - if (t) { - t->n_conns++; - } -} - -void -mq_transport_conn_dec(mq_transport_t *t) -{ - if (t && t->n_conns > 0) { - t->n_conns--; - } -} - -uint32_t -mq_transport_n_conns(const mq_transport_t *t) -{ - return t ? t->n_conns : 0; -} - -int -mq_transport_open_path(mq_transport_t *t, uint64_t path, const char *local_ip, - uint16_t port) -{ - if (!t || !t->cbs.open_path_socket) { - return -1; - } - return t->cbs.open_path_socket(path, local_ip, port, t->user); -} - -void -mq_transport_close_path(mq_transport_t *t, uint64_t path) -{ - if (!t || !t->cbs.close_path_socket) { - return; - } - t->cbs.close_path_socket(path, t->user); -} - -int -mq_transport_add_mp_ready_cb(mq_transport_t *t, mq_transport_mp_ready_fn fn, void *user) -{ - if (!t || !fn) { - return -1; - } - if (t->n_mp_ready_subs >= MQ_MP_READY_SUBS_MAX) { - return -1; /* fixed table full (MQ_MP_READY_SUBS_MAX slots) */ - } - t->mp_ready_subs[t->n_mp_ready_subs].fn = fn; - t->mp_ready_subs[t->n_mp_ready_subs].user = user; - t->n_mp_ready_subs++; - return 0; -} - -void -mq_transport_remove_mp_ready_cb(mq_transport_t *t, mq_transport_mp_ready_fn fn, - void *user) -{ - if (!t || !fn) { - return; - } - /* Match by fn+user pair and compact the table (shift the tail down) so the - * broadcast loop never indexes a removed slot. The pair is the registration - * identity: the same fn may be registered for several conns, each with a - * distinct user (the mq_conn), so user must match too. */ - for (int i = 0; i < t->n_mp_ready_subs; i++) { - if (t->mp_ready_subs[i].fn == fn && t->mp_ready_subs[i].user == user) { - for (int j = i; j < t->n_mp_ready_subs - 1; j++) { - t->mp_ready_subs[j] = t->mp_ready_subs[j + 1]; - } - t->n_mp_ready_subs--; - t->mp_ready_subs[t->n_mp_ready_subs].fn = NULL; - t->mp_ready_subs[t->n_mp_ready_subs].user = NULL; - return; - } - } -} - -int -mq_transport_enable_qlog(mq_transport_t *t, const char *dir, const char **out_path) -{ - if (!t || !dir || dir[0] == '\0') { - return -1; - } - if (t->qlog_fd >= 0) { - /* Already armed; idempotent re-arm just returns the existing path. */ - if (out_path) *out_path = t->qlog_path; - return 0; - } - int n = snprintf(t->qlog_path, sizeof(t->qlog_path), "%s/%s.qlog", dir, - t->is_server ? "server" : "client"); - if (n < 0 || (size_t)n >= sizeof(t->qlog_path)) { - MQ_LOGE("mq_transport: qlog path too long"); - t->qlog_path[0] = '\0'; - return -1; - } - int fd = open(t->qlog_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd < 0) { - MQ_LOGE("mq_transport: failed to open qlog file '%s': %s", t->qlog_path, - strerror(errno)); - t->qlog_path[0] = '\0'; - return -1; - } - t->qlog_fd = fd; - MQ_LOGI("mq_transport: qlog enabled -> %s", t->qlog_path); - if (out_path) *out_path = t->qlog_path; - return 0; -} diff --git a/src/transport/mq_transport.h b/src/transport/mq_transport.h deleted file mode 100644 index b8785357..00000000 --- a/src/transport/mq_transport.h +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* mq_transport.h — sans-io transport core boundary (design §4). - * - * mq_transport is the I/O-free transport core: it owns the xqc_engine but no - * socket and no event loop. The runtime injects received UDP packets via - * on_udp_recv, drives the engine via tick (using next_timeout_ms to arm its - * own timer), and receives outbound packets / path socket lifecycle requests - * through the mq_transport_callbacks_t hooks. - * - * The core owns its xqc_engine directly (no mq_engine, no libevent); the - * runtime installs the callbacks (send_udp / open_path_socket / - * close_path_socket) and drives the engine via tick + next_timeout_ms. - */ -#ifndef MQ_TRANSPORT_MQ_TRANSPORT_H -#define MQ_TRANSPORT_MQ_TRANSPORT_H - -#include -#include -#include - -#include - -typedef struct mq_transport_s mq_transport_t; - -/* send_udp return value: >=0 = bytes written, MQ_TX_AGAIN = would-block, - * -1 = error. The core propagates MQ_TX_AGAIN to xquic as XQC_SOCKET_EAGAIN. */ -#define MQ_TX_AGAIN (-2) - -typedef struct { - /* core -> caller: send one UDP packet. path is the xquic path-id. peer is - * the destination address passed through from xquic. Returns the number of - * bytes written (>=0, normally len for all-or-nothing UDP), MQ_TX_AGAIN on - * would-block, or -1 on error. */ - int (*send_udp)(uint64_t path, const uint8_t *pkt, size_t len, - const struct sockaddr *peer, socklen_t peerlen, void *user); - - /* core -> caller: open a socket for an additional path. path is the - * xquic-assigned path-id (passed in; the runtime does not mint ids). The - * caller does socket()/bind()/getsockname(), arms recv, and registers - * path->fd. Returns 0 on success, -1 on failure. */ - int (*open_path_socket)(uint64_t path, const char *local_ip, uint16_t port, - void *user); - - /* core -> caller: the additional path's socket may be closed. */ - void (*close_path_socket)(uint64_t path, void *user); - - /* core -> caller: the engine's next-wakeup deadline just changed (xquic - * called set_event_timer). The caller should (re)read - * mq_transport_next_timeout_ms() and (re)arm its timer. This fires for - * EVERY deadline change — including those set during app-initiated calls - * (mq_conn_connect / mq_stream_send) that are NOT followed by a tick or - * recv — so e.g. a connect-time retransmission timer is armed immediately - * even with no inbound traffic (restoring the old mq_engine behaviour where - * set_event_timer armed the libevent timer directly). Optional (may be - * NULL); without it the caller only re-arms after tick/recv. */ - void (*on_timer)(void *user); -} mq_transport_callbacks_t; - -/* Create a transport in client (is_server==0) or server (is_server!=0) mode. - * Callbacks are installed separately via mq_transport_set_callbacks. Returns - * NULL on failure. */ -mq_transport_t *mq_transport_new(int is_server); - -/* Create a server-mode transport with a TLS certificate + private key (PEM - * files, both required). Callbacks are installed separately via - * mq_transport_set_callbacks. Returns NULL on failure. */ -mq_transport_t *mq_transport_new_server(const char *cert_file, const char *key_file); - -void mq_transport_free(mq_transport_t *t); - -/* Install (or replace) the transport's callbacks and opaque user pointer after - * construction. cbs is copied (NULL == zeroed). Used by the runtime, which is - * created after the transport and must register itself as the callback user. - * This is the sole way to install transport callbacks. */ -void mq_transport_set_callbacks(mq_transport_t *t, const mq_transport_callbacks_t *cbs, - void *user); - -/* Input: hand the core one UDP packet read from a path socket. path is the - * receiving socket's xquic path-id (primary == 0). local is the receiving - * socket's bind address; xquic identifies the path by the (local,peer) 4-tuple, - * so both are required by xqc_engine_packet_process. */ -int mq_transport_on_udp_recv(mq_transport_t *t, uint64_t path, const uint8_t *pkt, - size_t len, const struct sockaddr *local, socklen_t locallen, - const struct sockaddr *peer, socklen_t peerlen); - -/* Advance the engine (= xqc_engine_main_logic). The runtime calls this after a - * recv or when its timer fires. */ -void mq_transport_tick(mq_transport_t *t); - -/* Microseconds until the core next wants to be ticked; -1 == not needed for - * now. The libevent runtime arms its timer from THIS variant (not the ms one) - * so sub-millisecond deadlines — notably xquic's pacing timers (~tens of µs at - * 100mbit) — keep their precision. Truncating them to ms (→0) collapses pacing - * to immediate fires, bursting the send and tripping congestion-control backoff - * at a shaped path. The old mq_engine armed its libevent timer with µs precision - * (tv_usec = wake_after % 1e6); this preserves that. */ -int64_t mq_transport_next_timeout_us(mq_transport_t *t); - -/* Milliseconds until the core next wants to be ticked; -1 == not needed for - * now. Lossy portable form (floors sub-ms to 0) for ms-granular pollers / - * get_interest; the libevent runtime uses the _us variant for pacing precision. */ -int mq_transport_next_timeout_ms(mq_transport_t *t); - -/* Accessor for the underlying xquic engine (used by mq_conn / cli). */ -xqc_engine_t *mq_transport_xqc(mq_transport_t *t); - -/* ── Connection-count cap (pre-auth DoS resistance, design 2026-06-15) ─────── - * Engine-wide bound on simultaneously-established INBOUND (server-accepted) - * connections, across all ALPNs (raw-QUIC TCP-proxy + H3). The counter lives on - * the transport (one engine == one transport); both ALPNs share it. - * - * conn_at_limit (pure read) and conn_inc are intentionally SEPARATE: server_accept - * must peek WITHOUT counting (it fires pre-handshake; half-open conns stay - * uncounted per design §6), while the create_notify guard checks-then-counts. */ - -/* Configure the cap. max_conns == 0 disables it (unlimited). Default is 0 until - * set (the CLI sets 16); a pure client never configures it. */ -void mq_transport_set_max_conns(mq_transport_t *t, uint32_t max_conns); - -/* 1 if a new inbound connection would exceed the cap (max_conns != 0 && - * n_conns >= max_conns); 0 otherwise. Read-only; used by server_accept (early - * reject) and each ALP create_notify (authoritative guard). */ -int mq_transport_conn_at_limit(const mq_transport_t *t); - -/* Count one admitted inbound connection. Call exactly once, in the ALP - * create_notify server branch, AFTER the at-limit guard passes. */ -void mq_transport_conn_inc(mq_transport_t *t); - -/* Release one admitted inbound connection. Call exactly once, in the ALP - * close_notify, guarded by the per-conn `counted` flag. Saturates at 0. */ -void mq_transport_conn_dec(mq_transport_t *t); - -/* Current established inbound connection count (test / observability accessor). */ -uint32_t mq_transport_n_conns(const mq_transport_t *t); - -/* Request that the caller open a socket for an additional path (the xquic path-id - * is passed in; mq_conn obtained it from xqc_conn_create_path). Invokes the - * runtime's open_path_socket callback. Returns its result, or -1 if no callback - * is installed. */ -int mq_transport_open_path(mq_transport_t *t, uint64_t path, const char *local_ip, - uint16_t port); - -/* Request that the caller close an additional path's socket. Invokes the - * runtime's close_path_socket callback (no-op if none is installed). */ -void mq_transport_close_path(mq_transport_t *t, uint64_t path); - -/* Multipath readiness hook: invoked once cids are exchanged on a connection - * (the precondition for xqc_conn_create_path). scid identifies the connection - * that became ready; it is owned by xquic and valid only for the callback - * duration (copy it out if needed). */ -typedef void (*mq_transport_mp_ready_fn)(const xqc_cid_t *scid, void *user); - -/* Subscribe to ready_to_create_path notifications. Broadcast: every subscriber - * fires for every conn's readiness; subscribers MUST filter by scid. Returns - * 0, or -1 when the (small, fixed) subscriber table is full. */ -int mq_transport_add_mp_ready_cb(mq_transport_t *t, mq_transport_mp_ready_fn fn, - void *user); - -/* Remove a previously-registered mp-ready subscriber (matched by fn+user pair). - * No-op if not found. Compacts the table. */ -void mq_transport_remove_mp_ready_cb(mq_transport_t *t, mq_transport_mp_ready_fn fn, - void *user); - -/* Enable xquic's qlog sink, writing to "/.qlog". Returns 0 on - * success, -1 on bad args / open failure. *out_path (if non-NULL) receives the - * opened path (borrowed, valid until mq_transport_free). */ -int mq_transport_enable_qlog(mq_transport_t *t, const char *dir, const char **out_path); - -#endif /* MQ_TRANSPORT_MQ_TRANSPORT_H */ diff --git a/src/util/mq_backoff.c b/src/util/mq_backoff.c deleted file mode 100644 index e60ccefc..00000000 --- a/src/util/mq_backoff.c +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "mq_backoff.h" - -uint64_t -mq_backoff_ms(uint64_t base_ms, uint64_t cap_ms, unsigned attempt) -{ - unsigned s = attempt < 31 ? attempt : 31; - uint64_t v = base_ms << s; - return v > cap_ms ? cap_ms : v; -} diff --git a/src/util/mq_backoff.h b/src/util/mq_backoff.h deleted file mode 100644 index 60197479..00000000 --- a/src/util/mq_backoff.h +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#ifndef MQ_UTIL_MQ_BACKOFF_H -#define MQ_UTIL_MQ_BACKOFF_H -#include -/* Deterministic exponential backoff (no jitter): - * min(cap_ms, base_ms << min(attempt, 31)). - * Jitter is applied by the caller at arm time; this stays pure for tests. - * Caller must use a sane base_ms (e.g. 250) so base_ms << 31 does not overflow - * u64 (250 << 31 ≈ 5.4e11, well within u64). */ -uint64_t mq_backoff_ms(uint64_t base_ms, uint64_t cap_ms, unsigned attempt); -#endif diff --git a/src/util/mq_buf.c b/src/util/mq_buf.c deleted file mode 100644 index 9c9aa783..00000000 --- a/src/util/mq_buf.c +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "mq_buf.h" - -void -mq_buf_reset(mq_buf_t *b) -{ - b->r = 0; - b->w = 0; -} - -size_t -mq_buf_len(const mq_buf_t *b) -{ - return b->w - b->r; -} - -size_t -mq_buf_space(const mq_buf_t *b) -{ - return MQ_BUF_SIZE - b->w; -} - -uint8_t * -mq_buf_write_ptr(mq_buf_t *b) -{ - return &b->data[b->w]; -} - -void -mq_buf_commit(mq_buf_t *b, size_t n) -{ - b->w += n; -} - -const uint8_t * -mq_buf_read_ptr(const mq_buf_t *b) -{ - return &b->data[b->r]; -} - -void -mq_buf_consume(mq_buf_t *b, size_t n) -{ - b->r += n; - if (b->r == b->w) { - b->r = 0; - b->w = 0; - } -} diff --git a/src/util/mq_buf.h b/src/util/mq_buf.h deleted file mode 100644 index 85dda5a5..00000000 --- a/src/util/mq_buf.h +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_BUF_H -#define MQ_BUF_H - -#include -#include - -#define MQ_BUF_SIZE 65536 - -_Static_assert(MQ_BUF_SIZE == 65536, "MQ_BUF_SIZE must be exactly 65536"); - -typedef struct { - uint8_t data[MQ_BUF_SIZE]; - size_t r; - size_t w; -} mq_buf_t; - -/* Reset cursors to 0 */ -void mq_buf_reset(mq_buf_t *b); - -/* Readable bytes = w - r */ -size_t mq_buf_len(const mq_buf_t *b); - -/* Writable bytes at the tail = MQ_BUF_SIZE - w */ -size_t mq_buf_space(const mq_buf_t *b); - -/* Pointer to write position: &data[w] */ -uint8_t *mq_buf_write_ptr(mq_buf_t *b); - -/* Advance write cursor by n (caller guarantees n <= space) */ -void mq_buf_commit(mq_buf_t *b, size_t n); - -/* Pointer to read position: &data[r] */ -const uint8_t *mq_buf_read_ptr(const mq_buf_t *b); - -/* Advance read cursor by n; if r == w after, compact (reset both to 0) */ -void mq_buf_consume(mq_buf_t *b, size_t n); - -#endif /* MQ_BUF_H */ diff --git a/src/util/mq_ct.h b/src/util/mq_ct.h deleted file mode 100644 index 1de93bcf..00000000 --- a/src/util/mq_ct.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_CT_H -#define MQ_CT_H - -#include - -/* Constant-time comparison of two byte ranges. Returns 1 if equal (same length - * AND same bytes), 0 otherwise. The comparison time depends only on the shorter - * length, not on WHERE the bytes differ: a volatile accumulator folds every - * byte difference (and the length difference) so the compiler cannot - * short-circuit and a timing side channel cannot leak how much of a secret - * (e.g. an auth token) matched. - * - * Header-only so both the proxy server (proxy/mq_server.c) and the gateway - * server (gateway/mq_gw_server.c) share ONE definition across the layering - * boundary instead of each carrying a "keep in sync" static copy. */ -static inline int -mq_ct_equal(const void *a, size_t alen, const void *b, size_t blen) -{ - const unsigned char *pa = (const unsigned char *)a; - const unsigned char *pb = (const unsigned char *)b; - volatile unsigned char acc = 0; - /* Fold the length difference into the accumulator so a mismatching length - * cannot pass even if the shorter is a prefix of the longer. */ - acc |= (unsigned char)((alen ^ blen) | ((alen ^ blen) >> 8) | ((alen ^ blen) >> 16) | - ((alen ^ blen) >> 24)); - size_t n = alen < blen ? alen : blen; - for (size_t i = 0; i < n; i++) - acc |= (unsigned char)(pa[i] ^ pb[i]); - return acc == 0; -} - -#endif /* MQ_CT_H */ diff --git a/src/util/mq_log.c b/src/util/mq_log.c deleted file mode 100644 index a5bf8924..00000000 --- a/src/util/mq_log.c +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "mq_log.h" -#include -#include - -static int g_log_level = MQ_LOG_INFO; - -void -mq_log_set_level(int level) -{ - g_log_level = level; -} - -void -mq_log(int level, const char *fmt, ...) -{ - if (level > g_log_level) return; - - const char *prefix; - switch (level) { - case MQ_LOG_ERROR: prefix = "ERROR"; break; - case MQ_LOG_WARN: prefix = "WARN"; break; - case MQ_LOG_INFO: prefix = "INFO"; break; - case MQ_LOG_DEBUG: prefix = "DEBUG"; break; - default: prefix = "UNKNOWN"; break; - } - - fprintf(stderr, "[%s] ", prefix); - - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - fprintf(stderr, "\n"); -} diff --git a/src/util/mq_log.h b/src/util/mq_log.h deleted file mode 100644 index a144a142..00000000 --- a/src/util/mq_log.h +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_LOG_H -#define MQ_LOG_H - -/* Log level constants — higher value = more verbose */ -#define MQ_LOG_ERROR 0 -#define MQ_LOG_WARN 1 -#define MQ_LOG_INFO 2 -#define MQ_LOG_DEBUG 3 - -/** - * Set the maximum log level that will be emitted. - * Messages with level > configured level are suppressed. - */ -void mq_log_set_level(int level); - -/** - * Emit a log message at the given level. - * Writes to stderr with a level prefix. - * Messages with level > configured level are suppressed. - */ -void mq_log(int level, const char *fmt, ...) __attribute__((format(printf, 2, 3))); - -/* Convenience macros */ -#define MQ_LOGE(...) mq_log(MQ_LOG_ERROR, __VA_ARGS__) -#define MQ_LOGW(...) mq_log(MQ_LOG_WARN, __VA_ARGS__) -#define MQ_LOGI(...) mq_log(MQ_LOG_INFO, __VA_ARGS__) -#define MQ_LOGD(...) mq_log(MQ_LOG_DEBUG, __VA_ARGS__) - -#endif /* MQ_LOG_H */ diff --git a/src/wire/mq_udp_msg.c b/src/wire/mq_udp_msg.c deleted file mode 100644 index 5158d91c..00000000 --- a/src/wire/mq_udp_msg.c +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "wire/mq_udp_msg.h" - -/* ---- encode / decode ---- */ - -int -mq_udp_msg_encode_hdr(uint8_t buf[MQ_UDP_MSG_HDR], const mq_udp_msg_hdr_t *h) -{ - /* session_id: 4 bytes BE */ - buf[0] = (uint8_t)((h->session_id >> 24) & 0xFF); - buf[1] = (uint8_t)((h->session_id >> 16) & 0xFF); - buf[2] = (uint8_t)((h->session_id >> 8) & 0xFF); - buf[3] = (uint8_t)(h->session_id & 0xFF); - /* packet_id: 2 bytes BE */ - buf[4] = (uint8_t)((h->packet_id >> 8) & 0xFF); - buf[5] = (uint8_t)(h->packet_id & 0xFF); - /* flags, frag_id, frag_count: 1 byte each */ - buf[6] = h->flags; - buf[7] = h->frag_id; - buf[8] = h->frag_count; - return 0; -} - -int -mq_udp_msg_decode_hdr(const uint8_t *buf, size_t len, mq_udp_msg_hdr_t *out) -{ - if (len < MQ_UDP_MSG_HDR) return -1; - out->session_id = ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | - ((uint32_t)buf[2] << 8) | (uint32_t)buf[3]; - out->packet_id = (uint16_t)(((uint16_t)buf[4] << 8) | (uint16_t)buf[5]); - out->flags = buf[6]; - out->frag_id = buf[7]; - out->frag_count = buf[8]; - return 0; -} - -/* ---- fragment split ---- */ - -int -mq_udp_msg_split(uint32_t sid, uint16_t packet_id, const uint8_t *payload, size_t len, - size_t mss_payload, mq_udp_frag_emit_fn emit, void *user) -{ - /* mss_payload == 0 is un-splittable */ - if (mss_payload == 0) return -1; - - /* Compute fragment count. len == 0 → 1 frag of 0 bytes. - * Use overflow-safe ceil-div: avoid len + mss_payload - 1 which wraps near - * SIZE_MAX and would silently return 0, bypassing the >255 guard. */ - size_t nfrags = (len == 0) ? 1 : (len / mss_payload) + (len % mss_payload != 0); - - /* frag_count is u8: max 255 */ - if (nfrags > 255) return -1; - - uint8_t frag_count = (uint8_t)nfrags; - size_t offset = 0; - - for (uint8_t frag_id = 0; frag_id < frag_count; frag_id++) { - size_t slice_len = (len - offset < mss_payload) ? (len - offset) : mss_payload; - - mq_udp_msg_hdr_t h; - h.session_id = sid; - h.packet_id = packet_id; - h.flags = 0; - h.frag_id = frag_id; - h.frag_count = frag_count; - - emit(&h, payload + offset, slice_len, user); - offset += slice_len; - } - return 0; -} diff --git a/src/wire/mq_udp_msg.h b/src/wire/mq_udp_msg.h deleted file mode 100644 index a59abc9b..00000000 --- a/src/wire/mq_udp_msg.h +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_UDP_MSG_H -#define MQ_UDP_MSG_H -#include -#include - -/* Fixed 9-byte header that prefixes every UDP payload carried in a QUIC - * DATAGRAM frame. Wire layout (all big-endian): - * session_id u32 BE | packet_id u16 BE | flags u8 | frag_id u8 | frag_count u8 - * = 9 bytes total. §5.4 of the Phase-3 design doc. */ -#define MQ_UDP_MSG_HDR 9 - -typedef struct { - uint32_t session_id; - uint16_t packet_id; - uint8_t flags; /* transmit: set to 0; receive: ignore (forward compat) */ - uint8_t frag_id; - uint8_t frag_count; /* 1 = unfragmented */ -} mq_udp_msg_hdr_t; - -/* Encode hdr into buf[MQ_UDP_MSG_HDR]. Returns 0 on success, -1 on error. */ -int mq_udp_msg_encode_hdr(uint8_t buf[MQ_UDP_MSG_HDR], const mq_udp_msg_hdr_t *h); - -/* Decode hdr from buf[0..len). Returns 0 on success, -1 if len < MQ_UDP_MSG_HDR. */ -int mq_udp_msg_decode_hdr(const uint8_t *buf, size_t len, mq_udp_msg_hdr_t *out); - -/* Fragment callback: called once per fragment with the per-fragment header, - * a pointer into the *original* payload buffer (zero-copy slice), and the - * slice length. */ -typedef void (*mq_udp_frag_emit_fn)(const mq_udp_msg_hdr_t *h, const uint8_t *p, - size_t len, void *user); - -/* Split payload of `len` bytes into fragments of at most `mss_payload` bytes - * each, calling emit() for every fragment. Pure function: no allocation, no - * I/O. Slices point directly into the caller's payload buffer. - * - * Returns 0 on success, -1 (without calling emit) if: - * - mss_payload == 0 - * - the required fragment count exceeds 255 (frag_count is u8) - * - * len == 0: emits exactly 1 fragment of length 0 (frag_count = 1). */ -int mq_udp_msg_split(uint32_t sid, uint16_t packet_id, const uint8_t *payload, size_t len, - size_t mss_payload, mq_udp_frag_emit_fn emit, void *user); - -#endif /* MQ_UDP_MSG_H */ diff --git a/src/wire/mq_varint.c b/src/wire/mq_varint.c deleted file mode 100644 index e7eb6fce..00000000 --- a/src/wire/mq_varint.c +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "mq_varint.h" - -int -mq_varint_len(uint64_t v) -{ - if (v <= 0x3FU) return 1; - if (v <= 0x3FFFU) return 2; - if (v <= 0x3FFFFFFFU) return 4; - return 8; -} - -int -mq_varint_encode(uint8_t *buf, size_t cap, uint64_t v) -{ - if (v > 0x3FFFFFFFFFFFFFFFULL) return -1; - - int n = mq_varint_len(v); - if ((size_t)n > cap) return -1; - - switch (n) { - case 1: - buf[0] = (uint8_t)(v & 0x3F); /* prefix 00 */ - break; - case 2: - buf[0] = (uint8_t)(0x40 | ((v >> 8) & 0x3F)); - buf[1] = (uint8_t)(v & 0xFF); - break; - case 4: - buf[0] = (uint8_t)(0x80 | ((v >> 24) & 0x3F)); - buf[1] = (uint8_t)((v >> 16) & 0xFF); - buf[2] = (uint8_t)((v >> 8) & 0xFF); - buf[3] = (uint8_t)(v & 0xFF); - break; - case 8: - buf[0] = (uint8_t)(0xC0 | ((v >> 56) & 0x3F)); - buf[1] = (uint8_t)((v >> 48) & 0xFF); - buf[2] = (uint8_t)((v >> 40) & 0xFF); - buf[3] = (uint8_t)((v >> 32) & 0xFF); - buf[4] = (uint8_t)((v >> 24) & 0xFF); - buf[5] = (uint8_t)((v >> 16) & 0xFF); - buf[6] = (uint8_t)((v >> 8) & 0xFF); - buf[7] = (uint8_t)(v & 0xFF); - break; - } - return n; -} - -int -mq_varint_decode(const uint8_t *buf, size_t len, uint64_t *out) -{ - if (len == 0) return -1; - - int prefix = (buf[0] >> 6) & 0x03; - int n = 1 << prefix; /* 1, 2, 4, or 8 */ - - if ((size_t)n > len) return -1; - - uint64_t v = (uint64_t)(buf[0] & 0x3F); - for (int i = 1; i < n; i++) { - v = (v << 8) | buf[i]; - } - - *out = v; - return n; -} diff --git a/src/wire/mq_varint.h b/src/wire/mq_varint.h deleted file mode 100644 index 570f67a9..00000000 --- a/src/wire/mq_varint.h +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_VARINT_H -#define MQ_VARINT_H - -#include -#include - -int mq_varint_encode(uint8_t *buf, size_t cap, uint64_t v); -int mq_varint_decode(const uint8_t *buf, size_t len, uint64_t *out); -int mq_varint_len(uint64_t v); - -#endif /* MQ_VARINT_H */ diff --git a/src/wire/mq_wire.c b/src/wire/mq_wire.c deleted file mode 100644 index 4bbefa46..00000000 --- a/src/wire/mq_wire.c +++ /dev/null @@ -1,397 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "wire/mq_wire.h" - -#include - -#include "wire/mq_varint.h" - -/* All multibyte fixed ints are big-endian. String fields are a varint - * length prefix followed by that many raw bytes. Every read is bounds-checked - * against the remaining buffer length because this parses untrusted input. */ - -/* ---- internal helpers ---- */ - -/* Encode a varint at buf[off..cap). Returns new offset, or -1 on overflow. */ -static int -put_varint(uint8_t *buf, size_t cap, size_t off, uint64_t v) -{ - if (off > cap) return -1; - int n = mq_varint_encode(buf + off, cap - off, v); - if (n < 0) return -1; - return (int)(off + (size_t)n); -} - -/* Decode a varint at buf[off..len). Stores value in *v. Returns new offset, - * or -1 on truncation. */ -static int -get_varint(const uint8_t *buf, size_t len, size_t off, uint64_t *v) -{ - if (off > len) return -1; - int n = mq_varint_decode(buf + off, len - off, v); - if (n < 0) return -1; - return (int)(off + (size_t)n); -} - -/* Encode a big-endian uint16 at buf[off..cap). Returns new offset, or -1. */ -static int -put_u16(uint8_t *buf, size_t cap, size_t off, uint16_t v) -{ - if (off + 2 > cap) return -1; - buf[off] = (uint8_t)((v >> 8) & 0xFF); - buf[off + 1] = (uint8_t)(v & 0xFF); - return (int)(off + 2); -} - -/* Decode a big-endian uint16 at buf[off..len). Returns new offset, or -1. */ -static int -get_u16(const uint8_t *buf, size_t len, size_t off, uint16_t *v) -{ - if (off + 2 > len) return -1; - *v = (uint16_t)(((uint16_t)buf[off] << 8) | (uint16_t)buf[off + 1]); - return (int)(off + 2); -} - -/* Encode a string: varint length + bytes. Returns new offset, or -1. */ -static int -put_string(uint8_t *buf, size_t cap, size_t off, const uint8_t *s, size_t slen) -{ - int r = put_varint(buf, cap, off, (uint64_t)slen); - if (r < 0) return -1; - off = (size_t)r; - if (off + slen > cap) return -1; - if (slen > 0) memcpy(buf + off, s, slen); - return (int)(off + slen); -} - -/* Decode a string into dst (capacity dst_cap, NOT counting any NUL the caller - * adds). The declared length must be <= dst_cap and fit in the buffer. - * Stores length in *out_len and (optionally) copies bytes into dst. - * Returns new offset, or -1 on truncation / over-long field. */ -static int -get_string(const uint8_t *buf, size_t len, size_t off, uint8_t *dst, size_t dst_cap, - size_t *out_len) -{ - uint64_t slen64; - int r = get_varint(buf, len, off, &slen64); - if (r < 0) return -1; - off = (size_t)r; - if (slen64 > dst_cap) return -1; - size_t slen = (size_t)slen64; - if (off + slen > len) return -1; - if (slen > 0) memcpy(dst, buf + off, slen); - *out_len = slen; - return (int)(off + slen); -} - -/* Emit a padding_length of 0 (encoders never pad). Returns new offset, or -1. */ -static int -put_zero_padding(uint8_t *buf, size_t cap, size_t off) -{ - return put_varint(buf, cap, off, 0); -} - -/* Read padding_length and skip that many bytes (bounds-checked). - * Returns new offset (end of frame), or -1 if it overflows the buffer. */ -static int -skip_padding(const uint8_t *buf, size_t len, size_t off) -{ - uint64_t pad; - int r = get_varint(buf, len, off, &pad); - if (r < 0) return -1; - off = (size_t)r; - if (pad > len - off) /* off <= len guaranteed by get_varint */ - return -1; - return (int)(off + (size_t)pad); -} - -/* ---- AUTH_REQUEST ---- - * varint version | string client_id | string auth_token | varint features - * | varint padding_length | padding - */ -int -mq_encode_auth_req(uint8_t *buf, size_t cap, const mq_auth_req_t *f) -{ - int r = put_varint(buf, cap, 0, f->version); - if (r < 0) return -1; - r = put_string(buf, cap, (size_t)r, (const uint8_t *)f->client_id, - strlen(f->client_id)); - if (r < 0) return -1; - r = put_string(buf, cap, (size_t)r, (const uint8_t *)f->auth_token, - strlen(f->auth_token)); - if (r < 0) return -1; - r = put_varint(buf, cap, (size_t)r, f->features); - if (r < 0) return -1; - r = put_zero_padding(buf, cap, (size_t)r); - return r; -} - -int -mq_decode_auth_req(const uint8_t *buf, size_t len, mq_auth_req_t *out) -{ - memset(out, 0, sizeof *out); - int r = get_varint(buf, len, 0, &out->version); - if (r < 0) return -1; - - size_t slen; - /* client_id: <=63 bytes + NUL into [64] */ - r = get_string(buf, len, (size_t)r, (uint8_t *)out->client_id, - sizeof out->client_id - 1, &slen); - if (r < 0) return -1; - out->client_id[slen] = '\0'; - - /* auth_token: <=255 bytes + NUL into [256] */ - r = get_string(buf, len, (size_t)r, (uint8_t *)out->auth_token, - sizeof out->auth_token - 1, &slen); - if (r < 0) return -1; - out->auth_token[slen] = '\0'; - - r = get_varint(buf, len, (size_t)r, &out->features); - if (r < 0) return -1; - return skip_padding(buf, len, (size_t)r); -} - -/* ---- AUTH_RESPONSE ---- - * uint8 status | varint error_code | string server_id | varint features - * | varint padding_length | padding - */ -int -mq_encode_auth_resp(uint8_t *buf, size_t cap, const mq_auth_resp_t *f) -{ - if (cap < 1) return -1; - buf[0] = (uint8_t)f->status; - int r = put_varint(buf, cap, 1, (uint64_t)f->error_code); - if (r < 0) return -1; - r = put_string(buf, cap, (size_t)r, (const uint8_t *)f->server_id, - strlen(f->server_id)); - if (r < 0) return -1; - r = put_varint(buf, cap, (size_t)r, f->features); - if (r < 0) return -1; - return put_zero_padding(buf, cap, (size_t)r); -} - -int -mq_decode_auth_resp(const uint8_t *buf, size_t len, mq_auth_resp_t *out) -{ - memset(out, 0, sizeof *out); - if (len < 1) return -1; - out->status = (mq_status_t)buf[0]; - - uint64_t err; - int r = get_varint(buf, len, 1, &err); - if (r < 0) return -1; - out->error_code = (mq_auth_err_t)err; - - size_t slen; - r = get_string(buf, len, (size_t)r, (uint8_t *)out->server_id, - sizeof out->server_id - 1, &slen); - if (r < 0) return -1; - out->server_id[slen] = '\0'; - - r = get_varint(buf, len, (size_t)r, &out->features); - if (r < 0) return -1; - return skip_padding(buf, len, (size_t)r); -} - -/* ---- CONNECT_TCP_REQUEST ---- - * varint flags | uint8 address_type | string target_host | uint16 target_port - * | varint padding_length | padding - */ -static int -addr_type_valid(unsigned t) -{ - return t == MQ_ADDR_IPV4 || t == MQ_ADDR_DOMAIN || t == MQ_ADDR_IPV6; -} - -int -mq_encode_connect_tcp_req(uint8_t *buf, size_t cap, const mq_connect_tcp_req_t *f) -{ - if (f->host_len > MQ_MAX_HOST) return -1; - if (!addr_type_valid((unsigned)f->address_type)) return -1; - - int r = put_varint(buf, cap, 0, f->flags); - if (r < 0) return -1; - size_t off = (size_t)r; - if (off + 1 > cap) return -1; - buf[off++] = (uint8_t)f->address_type; - - r = put_string(buf, cap, off, f->host, f->host_len); - if (r < 0) return -1; - r = put_u16(buf, cap, (size_t)r, f->port); - if (r < 0) return -1; - return put_zero_padding(buf, cap, (size_t)r); -} - -int -mq_decode_connect_tcp_req(const uint8_t *buf, size_t len, mq_connect_tcp_req_t *out) -{ - memset(out, 0, sizeof *out); - int r = get_varint(buf, len, 0, &out->flags); - if (r < 0) return -1; - size_t off = (size_t)r; - if (off + 1 > len) return -1; - uint8_t at = buf[off++]; - if (!addr_type_valid((unsigned)at)) return -1; - out->address_type = (mq_addr_type_t)at; - - r = get_string(buf, len, off, out->host, sizeof out->host, &out->host_len); - if (r < 0) return -1; - r = get_u16(buf, len, (size_t)r, &out->port); - if (r < 0) return -1; - return skip_padding(buf, len, (size_t)r); -} - -/* ---- CONNECT_TCP_RESPONSE ---- - * uint8 status | varint error_code | string message | varint padding_length | padding - */ -int -mq_encode_connect_tcp_resp(uint8_t *buf, size_t cap, const mq_connect_tcp_resp_t *f) -{ - if (cap < 1) return -1; - buf[0] = (uint8_t)f->status; - int r = put_varint(buf, cap, 1, (uint64_t)f->error_code); - if (r < 0) return -1; - r = put_string(buf, cap, (size_t)r, (const uint8_t *)f->message, strlen(f->message)); - if (r < 0) return -1; - return put_zero_padding(buf, cap, (size_t)r); -} - -int -mq_decode_connect_tcp_resp(const uint8_t *buf, size_t len, mq_connect_tcp_resp_t *out) -{ - memset(out, 0, sizeof *out); - if (len < 1) return -1; - out->status = (mq_status_t)buf[0]; - - uint64_t err; - int r = get_varint(buf, len, 1, &err); - if (r < 0) return -1; - out->error_code = (mq_tcp_err_t)err; - - r = get_string(buf, len, (size_t)r, (uint8_t *)out->message, sizeof out->message - 1, - &out->message_len); - if (r < 0) return -1; - out->message[out->message_len] = '\0'; - - return skip_padding(buf, len, (size_t)r); -} - -/* ---- UDP_SESSION_OPEN ---- - * varint session_id | varint flags | uint8 address_type | string host - * | uint16 port | varint idle_timeout_ms | varint padding_length | padding - */ -int -mq_encode_udp_session_open(uint8_t *buf, size_t cap, const mq_udp_session_open_t *f) -{ - if (f->host_len > MQ_MAX_HOST) return -1; - if (!addr_type_valid((unsigned)f->address_type)) return -1; - - int r = put_varint(buf, cap, 0, (uint64_t)f->session_id); - if (r < 0) return -1; - r = put_varint(buf, cap, (size_t)r, f->flags); - if (r < 0) return -1; - size_t off = (size_t)r; - if (off + 1 > cap) return -1; - buf[off++] = (uint8_t)f->address_type; - r = put_string(buf, cap, off, f->host, f->host_len); - if (r < 0) return -1; - r = put_u16(buf, cap, (size_t)r, f->port); - if (r < 0) return -1; - r = put_varint(buf, cap, (size_t)r, f->idle_timeout_ms); - if (r < 0) return -1; - return put_zero_padding(buf, cap, (size_t)r); -} - -int -mq_decode_udp_session_open(const uint8_t *buf, size_t len, mq_udp_session_open_t *out) -{ - memset(out, 0, sizeof *out); - - uint64_t sid; - int r = get_varint(buf, len, 0, &sid); - if (r < 0) return -1; - if (sid > UINT32_MAX) return -1; - out->session_id = (uint32_t)sid; - - r = get_varint(buf, len, (size_t)r, &out->flags); - if (r < 0) return -1; - size_t off = (size_t)r; - if (off + 1 > len) return -1; - uint8_t at = buf[off++]; - if (!addr_type_valid((unsigned)at)) return -1; - out->address_type = (mq_addr_type_t)at; - - r = get_string(buf, len, off, out->host, sizeof out->host, &out->host_len); - if (r < 0) return -1; - r = get_u16(buf, len, (size_t)r, &out->port); - if (r < 0) return -1; - r = get_varint(buf, len, (size_t)r, &out->idle_timeout_ms); - if (r < 0) return -1; - return skip_padding(buf, len, (size_t)r); -} - -/* ---- UDP_SESSION_RESP ---- - * uint8 status | varint error_code | string message | varint idle_timeout_ms - * | varint padding_length | padding - * - * Codec contract: - * encode: MQ_UDP_CLOSED (5) is rejected (-1). - * status×error_code must be consistent: OK⇔UDP_OK, ERROR⇔1-4. - * decode: wire error_code >= 5 is rejected (-1). - * status×error_code inconsistency is rejected (-1). - */ -static int -udp_err_valid_for_wire(mq_udp_err_t e) -{ - return (unsigned)e <= 4; /* 0..4 only; 5 = MQ_UDP_CLOSED is boundary-only */ -} - -static int -udp_status_err_consistent(mq_status_t s, mq_udp_err_t e) -{ - if (s == MQ_STATUS_OK) return e == MQ_UDP_OK; - if (s == MQ_STATUS_ERROR) return e != MQ_UDP_OK; - return 0; -} - -int -mq_encode_udp_session_resp(uint8_t *buf, size_t cap, const mq_udp_session_resp_t *f) -{ - if (!udp_err_valid_for_wire(f->error_code)) return -1; - if (!udp_status_err_consistent(f->status, f->error_code)) return -1; - if (cap < 1) return -1; - buf[0] = (uint8_t)f->status; - int r = put_varint(buf, cap, 1, (uint64_t)f->error_code); - if (r < 0) return -1; - r = put_string(buf, cap, (size_t)r, (const uint8_t *)f->message, f->message_len); - if (r < 0) return -1; - r = put_varint(buf, cap, (size_t)r, f->idle_timeout_ms); - if (r < 0) return -1; - return put_zero_padding(buf, cap, (size_t)r); -} - -int -mq_decode_udp_session_resp(const uint8_t *buf, size_t len, mq_udp_session_resp_t *out) -{ - memset(out, 0, sizeof *out); - if (len < 1) return -1; - out->status = (mq_status_t)buf[0]; - - uint64_t err; - int r = get_varint(buf, len, 1, &err); - if (r < 0) return -1; - if (err > 4) return -1; /* wire error_code >= 5 rejected */ - out->error_code = (mq_udp_err_t)err; - - if (!udp_status_err_consistent(out->status, out->error_code)) return -1; - - r = get_string(buf, len, (size_t)r, (uint8_t *)out->message, sizeof out->message - 1, - &out->message_len); - if (r < 0) return -1; - out->message[out->message_len] = '\0'; - - r = get_varint(buf, len, (size_t)r, &out->idle_timeout_ms); - if (r < 0) return -1; - return skip_padding(buf, len, (size_t)r); -} diff --git a/src/wire/mq_wire.h b/src/wire/mq_wire.h deleted file mode 100644 index 4a234c04..00000000 --- a/src/wire/mq_wire.h +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQ_WIRE_H -#define MQ_WIRE_H -#include -#include - -typedef enum { MQ_STATUS_OK = 0, MQ_STATUS_ERROR = 1 } mq_status_t; -typedef enum { - MQ_AUTH_OK = 0, - MQ_AUTH_FAILED = 1, - MQ_AUTH_TOKEN_EXPIRED = 2, - MQ_AUTH_POLICY_DENIED = 3 -} mq_auth_err_t; -typedef enum { - MQ_TCP_OK = 0, - MQ_TCP_DNS_FAILED = 1, - MQ_TCP_CONN_REFUSED = 2, - MQ_TCP_TIMEOUT = 3, - MQ_TCP_POLICY_DENIED = 4 -} mq_tcp_err_t; -typedef enum { - MQ_ADDR_IPV4 = 0x01, - MQ_ADDR_DOMAIN = 0x03, - MQ_ADDR_IPV6 = 0x04 -} mq_addr_type_t; - -#define MQ_MAX_HOST 255 - -typedef struct { - uint64_t version; - char client_id[64]; - char auth_token[256]; - uint64_t features; -} mq_auth_req_t; -typedef struct { - mq_status_t status; - mq_auth_err_t error_code; - char server_id[64]; - uint64_t features; -} mq_auth_resp_t; -typedef struct { - uint64_t flags; - mq_addr_type_t address_type; - uint8_t host[MQ_MAX_HOST]; - size_t host_len; - uint16_t port; -} mq_connect_tcp_req_t; -typedef struct { - mq_status_t status; - mq_tcp_err_t error_code; - char message[256]; - size_t message_len; -} mq_connect_tcp_resp_t; - -/* Type discriminator (varint) at the head of a data stream. design §5.2 */ -enum { MQ_STREAM_TYPE_CONNECT_TCP = 0x01, MQ_STREAM_TYPE_UDP_SESSION = 0x02 }; - -/* Bit definition for AUTH_RESPONSE.features (first use of the existing u64 - * field). Set by the server when UDP relay is available; cleared by --no-udp. */ -#define MQ_FEAT_UDP_RELAY ((uint64_t)1 << 0) - -typedef enum { - MQ_UDP_OK = 0, - MQ_UDP_DNS_FAILED = 1, - MQ_UDP_SOCKET_FAILED = 2, - MQ_UDP_POLICY_DENIED = 3, - MQ_UDP_SESSION_LIMIT = 4, - /* Boundary-only value (never placed on the wire in a RESP frame): signals - * session termination due to idle timeout, normal stream close, or conn - * close. Not an OPEN failure, so it does not feed the negative cache. - * Codec contract: the encoder rejects CLOSED with -1; the decoder rejects - * any wire error_code >= 5. The status×error_code pairing is fixed — - * MQ_STATUS_OK only with MQ_UDP_OK; MQ_STATUS_ERROR only with 1-4; - * violations cause both encoder and decoder to return -1. */ - MQ_UDP_CLOSED = 5 -} mq_udp_err_t; - -typedef struct { - uint32_t session_id; - uint64_t flags; - mq_addr_type_t address_type; - uint8_t host[MQ_MAX_HOST]; - size_t host_len; - uint16_t port; - uint64_t idle_timeout_ms; /* 0 = use server default */ -} mq_udp_session_open_t; - -typedef struct { - mq_status_t status; - mq_udp_err_t error_code; - char message[256]; - size_t message_len; - uint64_t idle_timeout_ms; /* effective value applied by the server */ -} mq_udp_session_resp_t; - -/* Each returns bytes written (encode) / consumed (decode), or -1 on error. - Decoders are strict: reject truncation and over-long string fields. - Encoders write padding_length = 0 (no padding emitted); decoders MUST read - padding_length and skip that many bytes (bounds-checked) for forward compat. */ -int mq_encode_auth_req(uint8_t *buf, size_t cap, const mq_auth_req_t *f); -int mq_decode_auth_req(const uint8_t *buf, size_t len, mq_auth_req_t *out); -int mq_encode_auth_resp(uint8_t *buf, size_t cap, const mq_auth_resp_t *f); -int mq_decode_auth_resp(const uint8_t *buf, size_t len, mq_auth_resp_t *out); -int mq_encode_connect_tcp_req(uint8_t *buf, size_t cap, const mq_connect_tcp_req_t *f); -int mq_decode_connect_tcp_req(const uint8_t *buf, size_t len, mq_connect_tcp_req_t *out); -int mq_encode_connect_tcp_resp(uint8_t *buf, size_t cap, const mq_connect_tcp_resp_t *f); -int mq_decode_connect_tcp_resp(const uint8_t *buf, size_t len, - mq_connect_tcp_resp_t *out); -int mq_encode_udp_session_open(uint8_t *buf, size_t cap, const mq_udp_session_open_t *f); -int mq_decode_udp_session_open(const uint8_t *buf, size_t len, - mq_udp_session_open_t *out); -int mq_encode_udp_session_resp(uint8_t *buf, size_t cap, const mq_udp_session_resp_t *f); -int mq_decode_udp_session_resp(const uint8_t *buf, size_t len, - mq_udp_session_resp_t *out); -#endif diff --git a/tests/certs/generate-mitm.sh b/tests/certs/generate-mitm.sh new file mode 100644 index 00000000..8e214472 --- /dev/null +++ b/tests/certs/generate-mitm.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Disposable test CA; never install it into a machine's trust store. +set -euo pipefail +cd "$(dirname "$0")" +if [[ -s mitm-ca.crt && -s mitm-ca.key ]]; then exit 0; fi +umask 077 +openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out mitm-ca.key +openssl req -x509 -new -key mitm-ca.key -out mitm-ca.crt -days 3650 \ + -subj /CN=mqproxy-mitm-test-ca \ + -addext basicConstraints=critical,CA:TRUE -addext keyUsage=critical,keyCertSign,cRLSign diff --git a/tests/integration/bench_ab_lanes.sh b/tests/integration/bench_ab_lanes.sh index b74622a9..30742b11 100755 --- a/tests/integration/bench_ab_lanes.sh +++ b/tests/integration/bench_ab_lanes.sh @@ -19,8 +19,8 @@ set -u SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/build/mqproxy}" -UDPSOCKS_BIN="${UDPSOCKS_BIN:-${REPO_ROOT}/build/udpsocks}" +MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/target/release/mqproxy}" +UDPSOCKS_BIN="${UDPSOCKS_BIN:-${REPO_ROOT}/target/release/examples/udpsocks}" MQPROXY_CERT="${MQPROXY_CERT:-${REPO_ROOT}/tests/certs/test.crt}" MQPROXY_KEY="${MQPROXY_KEY:-${REPO_ROOT}/tests/certs/test.key}" PICOQUICDEMO="${PICOQUICDEMO:-${REPO_ROOT}/../picoquic/build/picoquicdemo}" diff --git a/tests/integration/bench_single_tcp_aggregation.sh b/tests/integration/bench_single_tcp_aggregation.sh index b3c53cb7..7c6aae74 100755 --- a/tests/integration/bench_single_tcp_aggregation.sh +++ b/tests/integration/bench_single_tcp_aggregation.sh @@ -33,7 +33,7 @@ set -u SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/build/mqproxy}" +MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/target/release/mqproxy}" MQPROXY_CERT="${MQPROXY_CERT:-${REPO_ROOT}/tests/certs/test.crt}" MQPROXY_KEY="${MQPROXY_KEY:-${REPO_ROOT}/tests/certs/test.key}" MQVPN_DIR="${MQVPN_DIR:-${REPO_ROOT}/../mqvpn}" diff --git a/tests/integration/check_boringssl_isolation.sh b/tests/integration/check_boringssl_isolation.sh deleted file mode 100755 index 65a9c421..00000000 --- a/tests/integration/check_boringssl_isolation.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash -# check_boringssl_isolation.sh — Phase 7 Slice 3 Task 3 build-verification gate. -# -# Asserts the BoringSSL the MITM core links against is *isolated* inside the -# mqproxy executable so it cannot interpose libcurl's system OpenSSL (libssl.so). -# -# SCOPE OF THE CANARIES: the three symbols below (SSL_new, SSL_CTX_new, X509_sign) -# are *representative canaries* for the BoringSSL SSL_*/X509_* surface — NOT an -# exhaustive enumeration. The actual isolation is enforced by --exclude-libs -# covering the WHOLE libssl.a/libcrypto.a archives (plus BoringSSL's own hidden -# visibility); the `nm` checks here only spot-validate non-export on these few. -# -# WHY THE THREE CHECKS LOOK ASYMMETRIC (symbol-presence ordering subtlety): -# A static archive only contributes object files that resolve some *referenced* -# undefined symbol. At THIS task nothing in cli/main.c references the MITM core -# yet (Task 13 wires the caller), so whether BoringSSL SSL_*/X509_* objects are -# pulled into the exe depends on the build flavor: -# * STATIC build (MQPROXY_STATIC_XQUIC=ON): libxquic-static.a references -# BoringSSL SSL_*/EVP_AEAD_* etc., so SSL_new IS present in the exe today. -# The non-export gate (check ii) is therefore fully meaningful here NOW — -# this is the strongest test of the isolation. -# * DYNAMIC build (default): libxquic.so resolves its BoringSSL internally, -# so SSL_new is NOT pulled into the exe until a caller references it (Task -# 13). Check (i) below will find NOTHING in the dynamic build right now. -# -# Hence check (i) is tolerant of ABSENCE: a symbol that is simply not yet -# referenced is acceptable. It fails ONLY on a lingering `U SSL_new` (undefined -# = broken link, the archive was supposed to satisfy a reference but didn't), -# or on a `t`/`T` defined symbol being exported (that is caught by check ii). -# Check (ii) is the real export gate: it must return nothing (vacuously true -# when the symbol is absent, meaningfully enforced when present). Check (iii) -# confirms libcurl still dynamically needs system OpenSSL. -# -# This makes the script: PASS in the dynamic build now (symbols absent → -# vacuous), genuinely VALIDATE non-export in the static build now (symbols -# present from xquic), and AUTOMATICALLY strengthen for the dynamic build once -# Task 13 wires a MITM caller. Do NOT add a fake reference to force SSL_new to -# appear in the dynamic build. -# -# Usage: check_boringssl_isolation.sh -set -euo pipefail - -EXE="${1:?usage: check_boringssl_isolation.sh }" -if [[ ! -x "$EXE" ]]; then - echo "FAIL: executable not found or not executable: $EXE" >&2 - exit 1 -fi - -fail=0 - -# ---- (i) No lingering undefined BoringSSL symbol (broken link guard) -------- -# `nm` lists undefined symbols with type 'U'. A `U SSL_new` would mean the link -# expected the BoringSSL archive to satisfy a reference but it did not resolve. -# Absence of any SSL_new line (not referenced yet) is acceptable. -undef="$(nm "$EXE" 2>/dev/null | grep -E ' U (SSL_new|SSL_CTX_new|X509_sign)$' || true)" -if [[ -n "$undef" ]]; then - echo "FAIL (i): undefined BoringSSL symbols remain (broken link):" >&2 - echo "$undef" >&2 - fail=1 -else - echo "OK (i): no undefined BoringSSL SSL_*/X509_* symbols." -fi - -# Informational: report whether the symbols are present at all (helps debugging -# the static-vs-dynamic difference; not a pass/fail condition by itself). -defined="$(nm "$EXE" 2>/dev/null | grep -E ' [TtWw] (SSL_new|SSL_CTX_new|X509_sign)$' || true)" -if [[ -n "$defined" ]]; then - echo "INFO: BoringSSL symbols are defined in the exe (expected in static build):" - echo "$defined" -else - echo "INFO: BoringSSL symbols not (yet) pulled into the exe (expected in dynamic build pre-Task13)." -fi - -# ---- (ii) THE ISOLATION GATE: BoringSSL symbols must NOT be exported --------- -# `nm -D` lists *dynamic* (exported) symbols. If any BoringSSL SSL_*/X509_* shows -# up as a global 'T' or weak 'W' in the dynamic symbol table, it can interpose -# libcurl's libssl.so at runtime — exactly what --exclude-libs must prevent. -# Vacuously passes when the symbols are absent; meaningfully enforced when present. -exported="$(nm -D "$EXE" 2>/dev/null | grep -E ' (T|W) (SSL_new|SSL_CTX_new|X509_sign)$' || true)" -if [[ -n "$exported" ]]; then - echo "FAIL (ii): BoringSSL symbols EXPORTED in dynamic symbol table (interposition risk):" >&2 - echo "$exported" >&2 - fail=1 -else - echo "OK (ii): no BoringSSL SSL_*/X509_* exported (isolation gate holds)." -fi - -# ---- (iii) libcurl still dynamically needs system OpenSSL -------------------- -# Confirms we did not accidentally sever libcurl's dependency on the system -# libssl.so (i.e. the static BoringSSL is additive/isolated, not a replacement). -if ldd "$EXE" 2>/dev/null | grep -q 'libssl\.so'; then - echo "OK (iii): libcurl still dynamically needs system libssl.so." -else - echo "FAIL (iii): libssl.so not in ldd output — libcurl's system OpenSSL link is gone." >&2 - fail=1 -fi - -if [[ "$fail" -ne 0 ]]; then - echo "BoringSSL isolation check FAILED for $EXE" >&2 - exit 1 -fi -echo "BoringSSL isolation check PASSED for $EXE" -exit 0 diff --git a/tests/integration/e2e_gateway.sh b/tests/integration/e2e_gateway.sh index a48e1ec3..c6dd95bb 100755 --- a/tests/integration/e2e_gateway.sh +++ b/tests/integration/e2e_gateway.sh @@ -35,9 +35,8 @@ # HOW TO RUN: # tests/integration/e2e_gateway.sh # cases 1-7 (+ case-8 skip) # sudo tests/integration/e2e_gateway.sh # also runs case 8 (tc on lo) -# ctest --test-dir build -R e2e_gateway --output-on-failure # -# ENV (passed by CMake; overridable): +# ENV (overridable): # MQPROXY_BIN the `mqproxy` binary. # MQPROXY_CERT/KEY tunnel TLS cert/key (CN=mqproxy-test). # MQPROXY_ORIGIN_CERT/KEY origin TLS leaf cert/key (CA:FALSE, SAN=IP:127.0.0.1, @@ -53,7 +52,7 @@ note() { printf '%s\n' "e2e_gateway: $*" >&2; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/build/mqproxy}" +MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/target/release/mqproxy}" MQPROXY_CERT="${MQPROXY_CERT:-${REPO_ROOT}/tests/certs/test.crt}" MQPROXY_KEY="${MQPROXY_KEY:-${REPO_ROOT}/tests/certs/test.key}" ORIGIN_CERT="${MQPROXY_ORIGIN_CERT:-${REPO_ROOT}/tests/certs/origin.crt}" @@ -67,7 +66,7 @@ PATH_B_IP="127.0.0.3" if [ ! -x "${MQPROXY_BIN}" ]; then note "mqproxy binary not found/executable: ${MQPROXY_BIN}" - note " Build first (cmake --build build) or set MQPROXY_BIN." + note " Build first (cargo build --release -p mqproxy --bins --examples) or set MQPROXY_BIN." exit 1 fi if ! command -v python3 >/dev/null 2>&1; then @@ -80,7 +79,7 @@ if ! python3 -c 'import ssl, http.server' 2>/dev/null; then fi for f in "${ORIGIN_CERT}" "${ORIGIN_KEY}" "${ORIGIN_CA}"; do if [ ! -f "${f}" ]; then - note "origin cert/key missing: ${f} (CMake generates it; set MQPROXY_ORIGIN_CERT/KEY/CA)." + note "origin cert/key missing: ${f} (restore tests/certs or set MQPROXY_ORIGIN_CERT/KEY/CA)." exit 1 fi done diff --git a/tests/integration/e2e_mitm_h2.sh b/tests/integration/e2e_mitm_h2.sh index 70c90133..0a6dc796 100755 --- a/tests/integration/e2e_mitm_h2.sh +++ b/tests/integration/e2e_mitm_h2.sh @@ -98,15 +98,13 @@ # step. It SKIPs (exit 77) cleanly when prerequisites are absent. # # TO VALIDATE AS ROOT (run on a NET_ADMIN-capable machine / container): -# sudo MQPROXY_BIN=/path/to/build/mqproxy \ +# sudo MQPROXY_BIN=/path/to/target/release/mqproxy \ # MQPROXY_CERT=/path/to/tests/certs/test.crt \ # MQPROXY_KEY=/path/to/tests/certs/test.key \ # MQ_MITM_CA_CRT=/path/to/tests/certs/mitm-ca.crt \ # MQ_MITM_CA_KEY=/path/to/tests/certs/mitm-ca.key \ # bash tests/integration/e2e_mitm_h2.sh # -# Or via ctest (registered with SKIP_RETURN_CODE 77): -# sudo ctest --test-dir build -R e2e_mitm_h2 --output-on-failure # # Docker-no-sudo NET_ADMIN pattern (per project memory): # docker run --rm --cap-add=NET_ADMIN -v "$PWD":"$PWD" -w "$PWD" \ @@ -115,7 +113,7 @@ # MQPROXY_BIN=... bash tests/integration/e2e_mitm_h2.sh' # (NOTE the curl-CLI gotcha: libcurl-dev != the curl binary — install `curl`.) # -# ENV (passed by CMake; overridable): +# ENV (overridable): # MQPROXY_BIN the `mqproxy` binary (MUST be MITM-capable; the Rust # binary or, for interop, tests/integration/ # mqproxy-interop-wrapper.sh). @@ -132,7 +130,7 @@ note() { printf '%s\n' "e2e_mitm_h2: $*" >&2; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/build/mqproxy}" +MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/target/release/mqproxy}" MQPROXY_CERT="${MQPROXY_CERT:-${REPO_ROOT}/tests/certs/test.crt}" MQPROXY_KEY="${MQPROXY_KEY:-${REPO_ROOT}/tests/certs/test.key}" MITM_CA_CRT="${MQ_MITM_CA_CRT:-${REPO_ROOT}/tests/certs/mitm-ca.crt}" @@ -153,7 +151,6 @@ ORIGIN_PORT="" if [ "$(id -u)" -ne 0 ]; then note "SKIP: requires NET_ADMIN/root. Transparent MITM capture needs root for nft." note " Run with: sudo $0" - note " Or via ctest: sudo ctest --test-dir build -R e2e_mitm_h2 --output-on-failure" exit "${SKIP}" fi @@ -206,7 +203,7 @@ fi # ── binary + fixture pre-flight (real errors, not skips) ───────────────────── if [ ! -x "${MQPROXY_BIN}" ]; then note "ERROR: mqproxy binary not found/executable: ${MQPROXY_BIN}" - note " Build first (cmake --build build) or set MQPROXY_BIN." + note " Build first (cargo build --release -p mqproxy --bins --examples) or set MQPROXY_BIN." exit 1 fi # MITM capability is gated at runtime, not here: the Rust binary always has it; @@ -214,7 +211,7 @@ fi # ("--mitm unavailable"), which wait_mitm_ready below turns into a SKIP. for f in "${MITM_CA_CRT}" "${MITM_CA_KEY}" "${MQPROXY_CERT}" "${MQPROXY_KEY}"; do if [ ! -f "${f}" ]; then - note "ERROR: cert/key missing: ${f} (CMake generates the MITM CA; set MQ_MITM_CA_*)." + note "ERROR: cert/key missing: ${f} (run bash tests/certs/generate-mitm.sh or set MQ_MITM_CA_*)." exit 1 fi done diff --git a/tests/integration/e2e_mitm_smoke.sh b/tests/integration/e2e_mitm_smoke.sh index f37448cd..695714fb 100755 --- a/tests/integration/e2e_mitm_smoke.sh +++ b/tests/integration/e2e_mitm_smoke.sh @@ -1,33 +1,10 @@ #!/usr/bin/env bash -# -# e2e_mitm_smoke.sh — Phase 7 MITM Slice 2 Task 8: openssl s_client cross-impl smoke. -# -# WHAT THIS PROVES (spec S2-D6): -# The mq_mitm_core TLS termination path works against a NON-BoringSSL client. -# The in-process unit suite (test_mitm_core) drives a BoringSSL client over a -# memory BIO; this complements it with a real, separate TLS implementation (the -# system `openssl s_client`) negotiating over an actual loopback socket. The -# cross-implementation signal is the whole point. -# -# The helper (mitm_smoke_server) loads the MITM CA, accepts ONE connection, and -# terminates TLS through mq_mitm_core, forging a per-SNI leaf signed by the CA -# and negotiating ALPN=h2. We assert s_client sees: -# - "ALPN protocol: h2" (h2 was negotiated) -# - "Verify return code: 0 (ok)" (the forged leaf chains to the CA we trust) -# -# TWO MODES: -# * C helper (default; used by CTest): mitm_smoke_server terminates one connection. -# * Rust binary (MQPROXY_RUST_BIN set): `mqproxy server` (gateway on) + `mqproxy -# client --tproxy ... --mitm` with an nft REDIRECT for one unused destination -# port; s_client connects to that port and is captured. Needs root + NET_ADMIN -# + nft. The client's --tproxy-uid is a uid nobody runs, so root's s_client -# is captured (the default exempts the client's own uid, i.e. root). -# -# ENV (passed by CMake; overridable): -# MITM_SERVER_BIN the mitm_smoke_server helper binary (C mode). -# MQPROXY_RUST_BIN the Rust `mqproxy` binary (selects the Rust mode). +# Verify the Rust MITM TLS endpoint against openssl s_client over loopback. +# Requires root + NET_ADMIN for nft REDIRECT; asserts h2 ALPN and a valid forged chain. +# ENV (overridable): +# MQPROXY_BIN the Rust mqproxy binary (MQPROXY_RUST_BIN remains an alias). # MQPROXY_CERT/KEY tunnel TLS cert/key for the Rust server (default tests/certs/test.*). -# MITM_CA_CRT/KEY the MITM CA cert/key (configure-time fixtures). +# MITM_CA_CRT/KEY the MITM CA cert/key (run tests/certs/generate-mitm.sh). # # SKIP semantics: exit 77 when `openssl` is absent, or (Rust mode) when root/nft/ # NET_ADMIN/python3 is missing. Any assertion failure is a hard failure (non-zero, @@ -46,33 +23,27 @@ fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -MITM_SERVER_BIN="${MITM_SERVER_BIN:-${REPO_ROOT}/build/mitm_smoke_server}" -RUST_BIN="${MQPROXY_RUST_BIN:-}" +RUST_BIN="${MQPROXY_BIN:-${MQPROXY_RUST_BIN:-${REPO_ROOT}/target/release/mqproxy}}" MQPROXY_CERT="${MQPROXY_CERT:-${REPO_ROOT}/tests/certs/test.crt}" MQPROXY_KEY="${MQPROXY_KEY:-${REPO_ROOT}/tests/certs/test.key}" MITM_CA_CRT="${MITM_CA_CRT:-${REPO_ROOT}/tests/certs/mitm-ca.crt}" MITM_CA_KEY="${MITM_CA_KEY:-${REPO_ROOT}/tests/certs/mitm-ca.key}" # ── pre-flight (real errors, not skips) ────────────────────────────────────── -if [ -n "${RUST_BIN}" ]; then - if [ ! -x "${RUST_BIN}" ]; then - note "ERROR: Rust binary not found/executable: ${RUST_BIN}" - exit 1 - fi - if [ "$(id -u)" -ne 0 ] || ! command -v nft >/dev/null 2>&1 \ - || ! command -v python3 >/dev/null 2>&1 \ - || ! nft add table ip mqproxy_probe 2>/dev/null; then - note "SKIP: Rust mode needs root + CAP_NET_ADMIN + nft + python3." - exit "${SKIP}" - fi - nft delete table ip mqproxy_probe 2>/dev/null || true -elif [ ! -x "${MITM_SERVER_BIN}" ]; then - note "ERROR: helper not found/executable: ${MITM_SERVER_BIN} (build mitm_smoke_server)." +if [ ! -x "${RUST_BIN}" ]; then + note "ERROR: Rust binary not found/executable: ${RUST_BIN}" exit 1 fi +if [ "$(id -u)" -ne 0 ] || ! command -v nft >/dev/null 2>&1 \ + || ! command -v python3 >/dev/null 2>&1 \ + || ! nft add table ip mqproxy_probe 2>/dev/null; then + note "SKIP: Rust mode needs root + CAP_NET_ADMIN + nft + python3." + exit "${SKIP}" +fi +nft delete table ip mqproxy_probe 2>/dev/null || true for f in "${MITM_CA_CRT}" "${MITM_CA_KEY}"; do if [ ! -f "${f}" ]; then - note "ERROR: CA fixture missing: ${f} (CMake generates it)." + note "ERROR: CA fixture missing: ${f} (run bash tests/certs/generate-mitm.sh)." exit 1 fi done @@ -143,73 +114,45 @@ cleanup() { } trap cleanup EXIT INT TERM -if [ -n "${RUST_BIN}" ]; then - # ── Rust mode: server + MITM client + nft REDIRECT for one unused port ──── - QUIC_PORT="$(free_port udp)" - PORT="$(free_port tcp)" # the redirected destination: nothing listens here - TPROXY_PORT="$(free_port tcp)" - while [ "${TPROXY_PORT}" = "${PORT}" ]; do TPROXY_PORT="$(free_port tcp)"; done - # The key must be owned by the running uid with mode 0600 (Rust loader gate). - cp "${MITM_CA_CRT}" "${WORK}/ca.crt" && cp "${MITM_CA_KEY}" "${WORK}/ca.key" \ - && chmod 600 "${WORK}/ca.crt" "${WORK}/ca.key" || { note "ERROR: staging CA failed."; exit 1; } - note "launching Rust server (udp ${QUIC_PORT}) + MITM client (tproxy ${TPROXY_PORT}, dport ${PORT}) ..." - "${RUST_BIN}" server --listen "127.0.0.1:${QUIC_PORT}" --token smoke-token \ - --cert "${MQPROXY_CERT}" --key "${MQPROXY_KEY}" >"${WORK}/server.log" 2>&1 & - SERVER_PID=$! - "${RUST_BIN}" client --server "127.0.0.1:${QUIC_PORT}" --token smoke-token \ - --tproxy "127.0.0.1:${TPROXY_PORT}" --tproxy-mode redirect \ - --tproxy-dport "${PORT}" --setup-redirect --tproxy-uid 65433 \ - --mitm --ca-cert "${WORK}/ca.crt" --ca-key "${WORK}/ca.key" \ - >"${WORK}/client.log" 2>&1 & - CLIENT_PID=$! - ready=0 - for _ in $(seq 1 120); do - if ! kill -0 "${SERVER_PID}" 2>/dev/null || ! kill -0 "${CLIENT_PID}" 2>/dev/null; then - note "ERROR: server or client exited during startup." - exit 1 - fi - if grep -q "tunnel conn established" "${WORK}/client.log" 2>/dev/null \ - && grep -q "REDIRECT rules installed" "${WORK}/client.log" 2>/dev/null; then - ready=1 - break - fi - sleep 0.1 - done - if [ "${ready}" -ne 1 ]; then - note "ERROR: Rust client did not become ready within timeout." +# ── Rust mode: server + MITM client + nft REDIRECT for one unused port ──── +QUIC_PORT="$(free_port udp)" +PORT="$(free_port tcp)" # the redirected destination: nothing listens here +TPROXY_PORT="$(free_port tcp)" +while [ "${TPROXY_PORT}" = "${PORT}" ]; do TPROXY_PORT="$(free_port tcp)"; done +# The key must be owned by the running uid with mode 0600 (Rust loader gate). +cp "${MITM_CA_CRT}" "${WORK}/ca.crt" && cp "${MITM_CA_KEY}" "${WORK}/ca.key" \ + && chmod 600 "${WORK}/ca.crt" "${WORK}/ca.key" || { note "ERROR: staging CA failed."; exit 1; } +note "launching Rust server (udp ${QUIC_PORT}) + MITM client (tproxy ${TPROXY_PORT}, dport ${PORT}) ..." +"${RUST_BIN}" server --listen "127.0.0.1:${QUIC_PORT}" --token smoke-token \ + --cert "${MQPROXY_CERT}" --key "${MQPROXY_KEY}" >"${WORK}/server.log" 2>&1 & +SERVER_PID=$! +"${RUST_BIN}" client --server "127.0.0.1:${QUIC_PORT}" --token smoke-token \ + --tproxy "127.0.0.1:${TPROXY_PORT}" --tproxy-mode redirect \ + --tproxy-dport "${PORT}" --setup-redirect --tproxy-uid 65433 \ + --mitm --ca-cert "${WORK}/ca.crt" --ca-key "${WORK}/ca.key" \ + >"${WORK}/client.log" 2>&1 & +CLIENT_PID=$! +ready=0 +for _ in $(seq 1 120); do + if ! kill -0 "${SERVER_PID}" 2>/dev/null || ! kill -0 "${CLIENT_PID}" 2>/dev/null; then + note "ERROR: server or client exited during startup." exit 1 fi -else - # ── launch the single-shot helper ──────────────────────────────────────────── - note "launching helper on 127.0.0.1:${PORT} ..." - "${MITM_SERVER_BIN}" "${MITM_CA_CRT}" "${MITM_CA_KEY}" "${PORT}" \ - >"${WORK}/server.log" 2>&1 & - SERVER_PID=$! - - # ── wait for the listener to be ready (retry-connect, not a fixed sleep) ────── - ready=0 - for _ in $(seq 1 100); do - if ! kill -0 "${SERVER_PID}" 2>/dev/null; then - note "ERROR: helper exited before becoming ready; see ${WORK}/server.log:" - sed 's/^/ server| /' "${WORK}/server.log" >&2 2>/dev/null - exit 1 - fi - # The helper prints "LISTENING" once it has bound+listen()ed. - if grep -q "LISTENING" "${WORK}/server.log" 2>/dev/null; then - ready=1 - break - fi - sleep 0.1 - done - if [ "${ready}" -ne 1 ]; then - note "ERROR: helper did not become ready within timeout." - exit 1 + if grep -q "tunnel conn established" "${WORK}/client.log" 2>/dev/null \ + && grep -q "REDIRECT rules installed" "${WORK}/client.log" 2>/dev/null; then + ready=1 + break fi + sleep 0.1 +done +if [ "${ready}" -ne 1 ]; then + note "ERROR: Rust client did not become ready within timeout." + exit 1 fi # ── drive openssl s_client (cross-impl TLS client) ─────────────────────────── -# -alpn h2 offer only h2 (the helper's selector must pick it) -# -servername SNI → the helper forges a leaf with this in the SAN +# -alpn h2 offer only h2 (the TLS selector must pick it) +# -servername SNI → the client forges a leaf with this in the SAN # -CAfile trust the MITM CA → "Verify return code: 0 (ok)" iff chain OK # Bounded by `timeout` so a hung handshake fails fast instead of hanging CI. SCLIENT_OUT="${WORK}/sclient.out" @@ -221,14 +164,6 @@ timeout 20 openssl s_client \ -CAfile "${MITM_CA_CRT}" \ "${SCLIENT_OUT}" 2>&1 || true -# Reap the single-shot helper now that the connection is done (C mode only). -helper_rc=0 -if [ -z "${RUST_BIN}" ]; then - wait "${SERVER_PID}" 2>/dev/null - helper_rc=$? - SERVER_PID="" -fi - # ── assertions ─────────────────────────────────────────────────────────────── fail=0 @@ -246,11 +181,6 @@ else fail=1 fi -# Defensive: confirm the helper itself reported a clean ALPN=h2 termination. -if [ -z "${RUST_BIN}" ] && ! grep -q "ALPN=h2" "${WORK}/server.log" 2>/dev/null; then - note "WARN: helper did not log ALPN=h2 (rc=${helper_rc}); see server.log." -fi - if [ "${fail}" -ne 0 ]; then note "──── s_client output (tail) ────" tail -n 40 "${SCLIENT_OUT}" | sed 's/^/ client| /' >&2 diff --git a/tests/integration/e2e_multipath.sh b/tests/integration/e2e_multipath.sh index baaf1b38..7da8bbd5 100755 --- a/tests/integration/e2e_multipath.sh +++ b/tests/integration/e2e_multipath.sh @@ -22,12 +22,9 @@ # # HOW TO RUN: # sudo tests/integration/e2e_multipath.sh -# or via ctest (registered as `e2e_multipath`, skips cleanly without root): -# sudo ctest --test-dir build -R e2e_multipath --output-on-failure # # Requires NET_ADMIN (tc/netem on `lo`). WITHOUT it the script prints a notice -# and exits 77 (the autotools "skip" code) — ctest is configured with -# SKIP_RETURN_CODE 77 so it reports SKIPPED, not FAILED. +# and exits 77 (skip). CI requires NET_ADMIN and treats this as a failure. # # TUNABLE ENV VARS (defaults in parens): # RATE (100mbit) per-path rate cap, applied with an HTB class (+ a small @@ -35,7 +32,7 @@ # DELAY (25ms) per-path netem delay applied to EACH direction # (upstream + download), so the path RTT ~= 2*DELAY. # SIZE (128) bulk file size in MB. -# MQPROXY_BIN path to the `mqproxy` binary (default: ./build/mqproxy). +# MQPROXY_BIN path to the `mqproxy` binary (default: ./target/release/mqproxy). # MQPROXY_CERT/KEY TLS cert/key for the server (default: tests/certs/test.*). # # tc SHAPING APPROACH (documented): @@ -80,7 +77,7 @@ SIZE_MB="${SIZE:-128}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/build/mqproxy}" +MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/target/release/mqproxy}" MQPROXY_CERT="${MQPROXY_CERT:-${REPO_ROOT}/tests/certs/test.crt}" MQPROXY_KEY="${MQPROXY_KEY:-${REPO_ROOT}/tests/certs/test.key}" @@ -114,7 +111,7 @@ tc qdisc del dev lo root 2>/dev/null || true if [ ! -x "${MQPROXY_BIN}" ]; then note "e2e_multipath: mqproxy binary not found/executable: ${MQPROXY_BIN}" - note " Build first (cmake --build build) or set MQPROXY_BIN." + note " Build first (cargo build --release -p mqproxy --bins --examples) or set MQPROXY_BIN." exit 1 fi @@ -126,10 +123,8 @@ if ldd "${MQPROXY_BIN}" 2>/dev/null | grep -qi 'libasan'; then note "e2e_multipath: WARNING: ${MQPROXY_BIN} is AddressSanitizer-instrumented." note " Throughput will be ASan-crippled and the >=1.5x assertion is NOT meaningful." note " Build a release binary + release xquic and re-run, e.g.:" - note " bash scripts/build-xquic.sh # release xquic (XQC_ENABLE_EVENT_LOG=ON)" - note " cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release \\" - note " -DXQUIC_BUILD_DIR=\$PWD/third_party/xquic/build && cmake --build build-release" - note " sudo env MQPROXY_BIN=\$PWD/build-release/mqproxy $0" + note " cargo build --release --locked -p mqproxy" + note " sudo env MQPROXY_BIN=\$PWD/target/release/mqproxy $0" note " Continuing anyway (numbers are diagnostic-only)..." fi diff --git a/tests/integration/e2e_tproxy.sh b/tests/integration/e2e_tproxy.sh index 79b679d2..0ed149e2 100755 --- a/tests/integration/e2e_tproxy.sh +++ b/tests/integration/e2e_tproxy.sh @@ -48,7 +48,7 @@ # closely; see the NOTE comments for each uncertain step. # # TO VALIDATE AS ROOT (run this on a root-capable machine): -# sudo MQPROXY_BIN=/path/to/build/mqproxy \ +# sudo MQPROXY_BIN=/path/to/target/release/mqproxy \ # MQPROXY_CERT=/path/to/tests/certs/test.crt \ # MQPROXY_KEY=/path/to/tests/certs/test.key \ # MQPROXY_ORIGIN_CERT=/path/to/tests/certs/origin.crt \ @@ -56,10 +56,8 @@ # MQPROXY_ORIGIN_CA=/path/to/tests/certs/origin-ca.crt \ # bash tests/integration/e2e_tproxy.sh # -# Or via ctest (registered with SKIP_RETURN_CODE 77): -# sudo ctest --test-dir build -R e2e_tproxy --output-on-failure # -# ENV (passed by CMake; overridable): +# ENV (overridable): # MQPROXY_BIN the `mqproxy` binary. # MQPROXY_CERT/KEY tunnel TLS cert/key (CN=mqproxy-test). # MQPROXY_ORIGIN_CERT/KEY origin TLS leaf cert/key (SAN=IP:127.0.0.1), served @@ -73,7 +71,7 @@ note() { printf '%s\n' "e2e_tproxy: $*" >&2; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/build/mqproxy}" +MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/target/release/mqproxy}" MQPROXY_CERT="${MQPROXY_CERT:-${REPO_ROOT}/tests/certs/test.crt}" MQPROXY_KEY="${MQPROXY_KEY:-${REPO_ROOT}/tests/certs/test.key}" ORIGIN_CERT="${MQPROXY_ORIGIN_CERT:-${REPO_ROOT}/tests/certs/origin.crt}" @@ -95,7 +93,6 @@ PATH_B_IP="127.0.0.3" if [ "$(id -u)" -ne 0 ]; then note "SKIP: not root. Transparent capture needs root for nft + IP_TRANSPARENT." note " Run with: sudo $0" - note " Or via ctest: sudo ctest --test-dir build -R e2e_tproxy --output-on-failure" exit "${SKIP}" fi @@ -128,7 +125,7 @@ fi # ── binary + cert pre-flight (these are real errors, not skips) ────────────── if [ ! -x "${MQPROXY_BIN}" ]; then note "ERROR: mqproxy binary not found/executable: ${MQPROXY_BIN}" - note " Build first (cmake --build build) or set MQPROXY_BIN." + note " Build first (cargo build --release -p mqproxy --bins --examples) or set MQPROXY_BIN." exit 1 fi if ! command -v python3 >/dev/null 2>&1; then @@ -141,7 +138,7 @@ if ! python3 -c 'import ssl, http.server' 2>/dev/null; then fi for f in "${ORIGIN_CERT}" "${ORIGIN_KEY}" "${ORIGIN_CA}" "${MQPROXY_CERT}" "${MQPROXY_KEY}"; do if [ ! -f "${f}" ]; then - note "ERROR: cert/key missing: ${f} (CMake generates it; set MQPROXY_ORIGIN_CERT/KEY/CA)." + note "ERROR: cert/key missing: ${f} (restore tests/certs or set MQPROXY_ORIGIN_CERT/KEY/CA)." exit 1 fi done diff --git a/tests/integration/e2e_udp.sh b/tests/integration/e2e_udp.sh index d6155c6f..7e6ebeb7 100755 --- a/tests/integration/e2e_udp.sh +++ b/tests/integration/e2e_udp.sh @@ -58,9 +58,8 @@ # HOW TO RUN: # tests/integration/e2e_udp.sh # cases 1-5, 7 (+ cases 6,8 skip) # sudo tests/integration/e2e_udp.sh # also runs cases 6 and 8 -# ctest --test-dir build -R e2e_udp --output-on-failure # -# ENV (passed by CMake; overridable): +# ENV (overridable): # MQPROXY_BIN the `mqproxy` binary. # UDPSOCKS_BIN the `udpsocks` binary. # UDPECHO_BIN the `udp_echo` binary. @@ -100,9 +99,9 @@ for c in 1 2 3 5 7; do want "$c" && NEED_A=1; done SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/build/mqproxy}" -UDPSOCKS_BIN="${UDPSOCKS_BIN:-${REPO_ROOT}/build/udpsocks}" -UDPECHO_BIN="${UDPECHO_BIN:-${REPO_ROOT}/build/udp_echo}" +MQPROXY_BIN="${MQPROXY_BIN:-${REPO_ROOT}/target/release/mqproxy}" +UDPSOCKS_BIN="${UDPSOCKS_BIN:-${REPO_ROOT}/target/release/examples/udpsocks}" +UDPECHO_BIN="${UDPECHO_BIN:-${REPO_ROOT}/target/release/examples/udp_echo}" MQPROXY_CERT="${MQPROXY_CERT:-${REPO_ROOT}/tests/certs/test.crt}" MQPROXY_KEY="${MQPROXY_KEY:-${REPO_ROOT}/tests/certs/test.key}" @@ -114,7 +113,7 @@ PATH_B_IP="127.0.0.3" for bin in "${MQPROXY_BIN}" "${UDPSOCKS_BIN}" "${UDPECHO_BIN}"; do if [ ! -x "${bin}" ]; then note "binary not found/executable: ${bin}" - note " Build first (cmake --build build) or set MQPROXY_BIN/UDPSOCKS_BIN/UDPECHO_BIN." + note " Build first (cargo build --release -p mqproxy --bins --examples) or set MQPROXY_BIN/UDPSOCKS_BIN/UDPECHO_BIN." exit "${SKIP}" fi done diff --git a/tests/integration/mitm_smoke_server.c b/tests/integration/mitm_smoke_server.c deleted file mode 100644 index 7cdceea9..00000000 --- a/tests/integration/mitm_smoke_server.c +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors - -/* - * mitm_smoke_server.c — Phase 7 Slice 2 Task 8 cross-implementation smoke helper. - * - * Usage: - * mitm_smoke_server - * - * Loads the MITM CA via mq_mitm_core, binds 127.0.0.1:, accepts exactly - * ONE TCP connection, and terminates the browser-facing TLS handshake through - * mq_mitm_core (forging a per-SNI leaf signed by the CA, negotiating ALPN). On a - * successful SSL_accept it prints "ALPN=" to stdout (flushed) so the - * driving shell can assert what was negotiated, cleanly shuts the TLS session - * down, frees everything, and exits 0. Any failure exits non-zero. - * - * This is the ONLY place the MITM module is driven against a real socket — and it - * lives in a test helper, not in mq_mitm_core itself (the module stays I/O-free). - * It is deliberately minimal, single-shot, and leak-conscious. - */ -#include "mitm/mq_mitm_core.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int -main(int argc, char **argv) -{ - if (argc != 4) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 2; - } - const char *ca_crt = argv[1]; - const char *ca_key = argv[2]; - int port = atoi(argv[3]); - if (port <= 0 || port > 65535) { - fprintf(stderr, "mitm_smoke_server: invalid port: %s\n", argv[3]); - return 2; - } - - mq_mitm_core_t *core = mq_mitm_core_create(ca_crt, ca_key, NULL); - if (!core) { - fprintf(stderr, "mitm_smoke_server: mq_mitm_core_create failed\n"); - return 1; - } - - int rc = 1; /* default: failure */ - int lfd = -1, cfd = -1; - SSL *ssl = NULL; - - lfd = socket(AF_INET, SOCK_STREAM, 0); - if (lfd < 0) { - perror("mitm_smoke_server: socket"); - goto out; - } - int one = 1; - setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr); - sa.sin_port = htons((uint16_t)port); - - if (bind(lfd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - perror("mitm_smoke_server: bind"); - goto out; - } - if (listen(lfd, 1) != 0) { - perror("mitm_smoke_server: listen"); - goto out; - } - /* Signal readiness so the driver can stop its retry-connect loop. */ - fprintf(stdout, "LISTENING\n"); - fflush(stdout); - - cfd = accept(lfd, NULL, NULL); - if (cfd < 0) { - perror("mitm_smoke_server: accept"); - goto out; - } - - ssl = mq_mitm_core_new_ssl(core); - if (!ssl) { - fprintf(stderr, "mitm_smoke_server: mq_mitm_core_new_ssl failed\n"); - goto out; - } - if (SSL_set_fd(ssl, cfd) != 1) { - fprintf(stderr, "mitm_smoke_server: SSL_set_fd failed\n"); - goto out; - } - /* BoringSSL SSL_set_fd uses BIO_NOCLOSE: caller retains fd ownership. */ - - if (SSL_accept(ssl) != 1) { - fprintf(stderr, "mitm_smoke_server: SSL_accept failed\n"); - ERR_print_errors_fp(stderr); - goto out; - } - - const unsigned char *alpn = NULL; - unsigned int alpn_len = 0; - SSL_get0_alpn_selected(ssl, &alpn, &alpn_len); - fprintf(stdout, "ALPN=%.*s\n", (int)alpn_len, alpn ? (const char *)alpn : ""); - fflush(stdout); - - SSL_shutdown(ssl); - rc = 0; /* success */ - -out: - if (ssl) SSL_free(ssl); /* BIO_NOCLOSE: does NOT close the accepted fd */ - if (cfd >= 0) close(cfd); /* sole owner of the accepted fd: close exactly once */ - if (lfd >= 0) close(lfd); - mq_mitm_core_destroy(core); - return rc; -} diff --git a/tests/integration/test_client_reconnect.c b/tests/integration/test_client_reconnect.c deleted file mode 100644 index 3ebad714..00000000 --- a/tests/integration/test_client_reconnect.c +++ /dev/null @@ -1,1126 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_client_reconnect.c — Task 4.3 (Phase 5b): positive coverage that the - * in-process client reconnect + keepalive paths work END-TO-END. - * - * Same in-process harness shape as test_client_server.c / test_udp_relay.c: a - * REAL mq_client + REAL mq_server share one libevent base over loopback, plus a - * local TCP echo origin (TCP cases) and a local UDP echo origin (UDP cases). - * - * The DIFFERENCE from those fixtures: here reconnect is ENABLED (the Phase 5b - * default) with a SHORT max-backoff so a deterministic conn drop recovers fast. - * - * The conn-drop mechanism is mq_conn_close(mq_client_conn(client)): it closes - * the client's tunnel conn. The client sees MQ_CONN_CLOSED → (reconnect on) - * arms the backoff timer → the timer fires → client_issue_connect re-dials the - * SAME still-up server fixture → re-accept → re-auth → SERVING. - * - * Detecting "reconnected + re-authed": mq_client's on_auth fires once PER conn - * and its auth_reported latch is reset on the reconnect path (mq_client.c - * client_on_state CLOSED branch), so on_auth fires AGAIN after each reconnect. - * We count on_auth invocations: count>=2 ⇒ reconnected AND re-authed. Every case - * then asserts an end-to-end FLOW completes on the reconnected conn (not merely - * "reconnect happened"). - * - * Cases (see the per-function headers): - * 1. TCP reconnect (+ kill-during-auth variant) → new TCP flow echoes. - * 2. UDP reconnect → new datagram echoes byte-exact (mq_udp_cli rebind). - * 3. open DURING the backoff window → enqueued, completes after re-auth. - * 4. queue carry-over: open right after close → completes after re-auth. - * 5. mq_client_free while RECONNECTING → no crash / no late reconnect (ASan). - * 6. keepalive: short idle timeout set, idle under it, conn stays up + flow OK. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "proxy/mq_client.h" -#include "proxy/mq_server.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_stream.h" -#include "transport/mq_transport.h" -#include "wire/mq_wire.h" - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -/* Short reconnect max-backoff. mq_client floors max-backoff to 1000ms (anti - * busy-spin), so 1000 is the smallest deterministic value; with full-jitter the - * actual first delay is in [base, min(cap, base*2^attempt)] anyway. */ -#define RECONNECT_MAX_BACKOFF_MS 1000 -/* Generous per-cycle budget: backoff (<=1s) + handshake + re-auth on loopback. */ -#define RECONNECT_BUDGET_MS 8000 - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -static void -set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - if (fl >= 0) (void)fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -/* ── auth bookkeeping: COUNT invocations (re-auth fires on_auth again) ────── */ -static int g_auth_count; /* total on_auth fires across reconnect cycles */ -static int g_auth_ok; /* most-recent ok */ - -static void -on_auth(int ok, mq_auth_err_t err, void *user) -{ - (void)err; - (void)user; - g_auth_count++; - g_auth_ok = ok; -} - -/* ── fixture: real client + real server on a shared base, RECONNECT ENABLED ── */ -typedef struct { - struct event_base *base; - mq_transport_t *srv_t; - mq_transport_t *cli_t; - mq_runtime_t *srv_rt; - mq_runtime_t *cli_rt; - mq_server_t *server; - mq_client_t *client; - uint16_t port; /* the loopback port the client dials; stable across server - teardown/rebuild (used by the persistent-outage case). */ -} fixture_t; - -/* Stand up ONLY the server side (transport + runtime + server) bound to f->port. - * Split out of fixture_up so the persistent-outage case can tear the server down - * and rebuild it on the SAME port while the client keeps dialing. Returns 0/-1. - * On failure leaves the partially-built server fields for server_down to clean. */ -static int -server_up(fixture_t *f) -{ - f->srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(f->srv_t != NULL); - if (!f->srv_t) return -1; - f->srv_rt = mq_runtime_new(f->srv_t, f->base); - MQ_CHECK(f->srv_rt != NULL); - if (!f->srv_rt) return -1; - f->server = mq_server_new(f->srv_t, f->srv_rt, "secret", MQ_CC_BBR2, 60000, 1); - MQ_CHECK(f->server != NULL); - if (!f->server) return -1; - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->srv_rt, "127.0.0.1", f->port), 0); - return 0; -} - -/* Tear the server side down (transport, runtime, server) and NULL the fields so - * the client's re-dials cannot complete: "server gone". The client + its event - * base + the runtime/transport stay alive and keep retrying. SO_REUSEADDR (set - * by the runtime) lets server_up re-bind the same UDP port afterward. - * - * ORDER MIRRORS fixture_down: transport FIRST, then runtime, then server. The - * transport free runs xqc_engine_destroy → xqc_conn_destroy → mq_conn_close_notify - * → srv_conn_state, which dereferences the server struct — so the server MUST - * still be alive when the transport is freed (freeing the server first is a UAF). */ -static void -server_down(fixture_t *f) -{ - if (f->srv_t) { - mq_transport_free(f->srv_t); - f->srv_t = NULL; - } - if (f->srv_rt) { - mq_runtime_free(f->srv_rt); - f->srv_rt = NULL; - } - if (f->server) { - mq_server_free(f->server); - f->server = NULL; - } -} - -/* Stand the fixture up. keepalive_ms == 0 leaves the client default (30s); a - * positive value is applied via mq_client_set_keepalive before start. Reconnect - * is ENABLED with the short max-backoff (the whole point of this test). */ -static int -fixture_up(fixture_t *f, uint64_t keepalive_ms) -{ - memset(f, 0, sizeof(*f)); - g_auth_count = 0; - g_auth_ok = 0; - - f->base = event_base_new(); - MQ_CHECK(f->base != NULL); - if (!f->base) return -1; - - uint16_t port = reserve_udp_port(); - MQ_CHECK(port != 0); - if (!port) return -1; - f->port = port; - - if (server_up(f) != 0) return -1; - - f->cli_t = mq_transport_new(0); - MQ_CHECK(f->cli_t != NULL); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - MQ_CHECK(f->cli_rt != NULL); - if (!f->cli_rt) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0), 0); - - f->client = mq_client_new(f->cli_t, f->cli_rt, "127.0.0.1", port, "client-1", - "secret", MQ_CC_BBR2); - MQ_CHECK(f->client != NULL); - if (!f->client) return -1; - mq_client_set_on_auth(f->client, on_auth, NULL); - /* RECONNECT ENABLED (default) with a short max-backoff for fast recovery. */ - mq_client_set_reconnect(f->client, 1, RECONNECT_MAX_BACKOFF_MS); - if (keepalive_ms) mq_client_set_keepalive(f->client, keepalive_ms); - - MQ_CHECK_EQ_INT(mq_client_start(f->client), 0); - return 0; -} - -static void -fixture_down(fixture_t *f) -{ - if (f->cli_t) mq_transport_free(f->cli_t); - if (f->srv_t) mq_transport_free(f->srv_t); - if (f->cli_rt) mq_runtime_free(f->cli_rt); - if (f->srv_rt) mq_runtime_free(f->srv_rt); - if (f->client) mq_client_free(f->client); - if (f->server) mq_server_free(f->server); - if (f->base) event_base_free(f->base); -} - -static void -pump_until(struct event_base *base, int *flag, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while ((!flag || !*flag) && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -static void -pump_for(struct event_base *base, uint64_t ms) -{ - uint64_t deadline = now_ms() + ms; - while (now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -/* Pump until on_auth has fired at least `target` times (poll a local flag). */ -static void -pump_until_auth_count(struct event_base *base, int target, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (g_auth_count < target && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -/* mq_conn_close() is ASYNC: it initiates close (CLOSING) and MQ_CONN_CLOSED - * fires later when xquic destroys the conn, at which point mq_client nulls - * c->conn and arms the backoff timer. Pump until that has happened — i.e. the - * client is in the backoff window (conn == NULL) — bounded by budget_ms. The - * backoff floor is 1000ms, so a few hundred ms of pumping lands here reliably - * WITHOUT overshooting into a completed reconnect. */ -static void -pump_until_conn_null(fixture_t *f, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (mq_client_conn(f->client) != NULL && now_ms() < deadline) { - event_base_loop(f->base, EVLOOP_NONBLOCK); - } -} - -/* ── local TCP echo origin (mirrors test_client_server.c) ────────────────── */ -typedef struct { - struct event_base *base; - int listen_fd; - int conn_fd; - struct event *listen_ev; - struct event *conn_ev; -} echo_origin_t; - -static void -echo_conn_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - echo_origin_t *o = (echo_origin_t *)arg; - unsigned char buf[4096]; - ssize_t n = recv(fd, buf, sizeof(buf), 0); - if (n <= 0) { - if (n == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) { - if (o->conn_ev) { - event_free(o->conn_ev); - o->conn_ev = NULL; - } - close(fd); - o->conn_fd = -1; - } - return; - } - ssize_t off = 0; - while (off < n) { - ssize_t w = send(fd, buf + off, (size_t)(n - off), 0); - if (w <= 0) break; - off += w; - } -} - -static void -echo_accept_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - echo_origin_t *o = (echo_origin_t *)arg; - int c = accept(fd, NULL, NULL); - if (c < 0) return; - set_nonblock(c); - /* Replace any prior accepted conn's read event (each reconnected flow dials - * a fresh origin connection; keep only the newest). */ - if (o->conn_ev) { - event_free(o->conn_ev); - o->conn_ev = NULL; - } - if (o->conn_fd >= 0) close(o->conn_fd); - o->conn_fd = c; - o->conn_ev = event_new(o->base, c, EV_READ | EV_PERSIST, echo_conn_cb, o); - event_add(o->conn_ev, NULL); -} - -static int -echo_origin_up(echo_origin_t *o, struct event_base *base, uint16_t *out_port) -{ - memset(o, 0, sizeof(*o)); - o->base = base; - o->conn_fd = -1; - o->listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (o->listen_fd < 0) return -1; - int one = 1; - setsockopt(o->listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(o->listen_fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) return -1; - if (listen(o->listen_fd, 4) != 0) return -1; - socklen_t sl = sizeof(sa); - if (getsockname(o->listen_fd, (struct sockaddr *)&sa, &sl) != 0) return -1; - *out_port = ntohs(sa.sin_port); - set_nonblock(o->listen_fd); - o->listen_ev = event_new(base, o->listen_fd, EV_READ | EV_PERSIST, echo_accept_cb, o); - event_add(o->listen_ev, NULL); - return 0; -} - -static void -echo_origin_down(echo_origin_t *o) -{ - if (o->conn_ev) event_free(o->conn_ev); - if (o->listen_ev) event_free(o->listen_ev); - if (o->conn_fd >= 0) close(o->conn_fd); - if (o->listen_fd >= 0) close(o->listen_fd); -} - -/* ── local UDP echo origin (mirrors test_udp_relay.c) ────────────────────── */ -typedef struct { - struct event_base *base; - int fd; - struct event *ev; - int rx_count; -} udp_echo_t; - -static void -udp_echo_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - udp_echo_t *e = (udp_echo_t *)arg; - for (;;) { - unsigned char buf[65535]; - struct sockaddr_storage from; - socklen_t fl = sizeof(from); - ssize_t n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fl); - if (n < 0) break; - e->rx_count++; - (void)sendto(fd, buf, (size_t)n, 0, (struct sockaddr *)&from, fl); - } -} - -static int -udp_echo_up(udp_echo_t *e, struct event_base *base, uint16_t *out_port) -{ - memset(e, 0, sizeof(*e)); - e->base = base; - e->fd = socket(AF_INET, SOCK_DGRAM, 0); - if (e->fd < 0) return -1; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(e->fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) return -1; - socklen_t sl = sizeof(sa); - if (getsockname(e->fd, (struct sockaddr *)&sa, &sl) != 0) return -1; - *out_port = ntohs(sa.sin_port); - set_nonblock(e->fd); - e->ev = event_new(base, e->fd, EV_READ | EV_PERSIST, udp_echo_cb, e); - event_add(e->ev, NULL); - return 0; -} - -static void -udp_echo_down(udp_echo_t *e) -{ - if (e->ev) event_free(e->ev); - if (e->fd >= 0) close(e->fd); -} - -/* ── TCP flow helper: mq_client_tcp_open over a socketpair, drive "ping" echo ── - * - * Returns 0 if the round-trip "ping"→echo completed, -1 otherwise. The client - * takes ownership of local_fd; the test owns/closes app_fd. */ -typedef struct { - int fired; - int ok; - mq_tcp_err_t err; -} open_result_t; - -static void -on_tcp_open(int ok, mq_tcp_err_t err, void *user) -{ - open_result_t *r = (open_result_t *)user; - r->fired = 1; - r->ok = ok; - r->err = err; -} - -static void -v4_loopback(uint8_t host[4]) -{ - uint32_t v4 = htonl(INADDR_LOOPBACK); - memcpy(host, &v4, 4); -} - -/* Run ONE full TCP echo flow on the client's CURRENT conn: open → ping → echo. - * Asserts each step. Returns 0 on byte-exact echo, -1 on any failure. */ -static int -run_tcp_echo_flow(fixture_t *f, uint16_t origin_port) -{ - int sp[2]; - if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) != 0) return -1; - set_nonblock(sp[0]); - set_nonblock(sp[1]); - int app_fd = sp[0]; - int local_fd = sp[1]; /* handed to the client */ - - open_result_t res; - memset(&res, 0, sizeof(res)); - uint8_t host[4]; - v4_loopback(host); - mq_client_tcp_open(mq_client_tcp_open_core(f->client), host, 4, MQ_ADDR_IPV4, - origin_port, local_fd, NULL, 0, &res, on_tcp_open); - - pump_until(f->base, &res.fired, 4000); - MQ_CHECK(res.fired); - MQ_CHECK_EQ_INT(res.ok, 1); - if (!res.fired || !res.ok) { - close(app_fd); - return -1; - } - - if (write(app_fd, "ping", 4) != 4) { - close(app_fd); - return -1; - } - char rx[16]; - size_t got = 0; - uint64_t deadline = now_ms() + 4000; - while (got < 4 && now_ms() < deadline) { - event_base_loop(f->base, EVLOOP_NONBLOCK); - ssize_t n = recv(app_fd, rx + got, sizeof(rx) - got, 0); - if (n > 0) got += (size_t)n; - } - close(app_fd); - MQ_CHECK_EQ_INT((int)got, 4); - if (got != 4) return -1; - MQ_CHECK(memcmp(rx, "ping", 4) == 0); - return (got == 4 && memcmp(rx, "ping", 4) == 0) ? 0 : -1; -} - -/* ── UDP flow helpers (client-role boundary, mirrors test_udp_relay.c) ────── */ -typedef struct { - uint8_t rx[4096]; - size_t rxlen; - int rx_done; - int err_fired; - mq_udp_err_t err; -} cli_rx_t; - -static void -cli_on_rx(const uint8_t *payload, size_t len, void *user) -{ - cli_rx_t *c = (cli_rx_t *)user; - if (c->rx_done) return; - size_t n = len > sizeof(c->rx) ? sizeof(c->rx) : len; - memcpy(c->rx, payload, n); - c->rxlen = n; - c->rx_done = 1; -} - -static void -cli_on_err(void *session, mq_udp_err_t err, void *user) -{ - (void)session; - cli_rx_t *c = (cli_rx_t *)user; - c->err_fired = 1; - c->err = err; -} - -static void * -cli_open_v4(mq_client_t *client, uint16_t port, cli_rx_t *cap) -{ - uint8_t host[4]; - v4_loopback(host); - mq_udp_open_fn open_fn = mq_client_udp_open_fn(); - void *core = mq_client_udp_open_core(client); - return open_fn(core, host, 4, MQ_ADDR_IPV4, port, cli_on_rx, cli_on_err, cap); -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 1: TCP reconnect (+ kill-during-auth variant + in-flight reap note). - * ════════════════════════════════════════════════════════════════════════ */ -/* (1) Establish + run a baseline TCP echo. (a) Open an in-flight flow then drop - * the conn mid-flight: the open's cb must fire with an error (failed cleanly, no - * hang). Pump until reconnected+re-authed (auth_count>=2). Then run a NEW TCP - * echo on the reconnected conn → byte-exact. */ -static void -test_case1_tcp_reconnect(void) -{ - fixture_t f; - if (fixture_up(&f, 0) != 0) { - fixture_down(&f); - return; - } - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - /* Establish + baseline flow. */ - pump_until_auth_count(f.base, 1, 4000); - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(run_tcp_echo_flow(&f, origin_port), 0); - - /* (a) Start an in-flight flow, then drop the conn before completing it. The - * open cb must fire with an error (in-flight flows do NOT survive a tunnel - * loss — design §Non-goal — but must fail CLEANLY, never hang). */ - int sp[2]; - MQ_CHECK_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sp), 0); - set_nonblock(sp[0]); - set_nonblock(sp[1]); - open_result_t inflight; - memset(&inflight, 0, sizeof(inflight)); - uint8_t host[4]; - v4_loopback(host); - mq_client_tcp_open(mq_client_tcp_open_core(f.client), host, 4, MQ_ADDR_IPV4, - origin_port, sp[1], NULL, 0, &inflight, on_tcp_open); - /* Drop the conn immediately (likely before the CONNECT_TCP_RESPONSE). */ - mq_conn_t *conn = mq_client_conn(f.client); - MQ_CHECK(conn != NULL); - mq_conn_close(conn); - /* The in-flight open must reach a terminal cb (ok=0) — clean failure. */ - pump_until(f.base, &inflight.fired, RECONNECT_BUDGET_MS); - MQ_CHECK(inflight.fired); - MQ_CHECK_EQ_INT(inflight.ok, 0); /* failed, not hung */ - close(sp[0]); /* client owns sp[1], closes it on reap */ - - /* Pump until reconnected + RE-AUTHED (on_auth fires a 2nd time). */ - pump_until_auth_count(f.base, 2, RECONNECT_BUDGET_MS); - MQ_CHECK_EQ_INT(g_auth_count, 2); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 1); - MQ_CHECK(mq_client_conn(f.client) != NULL); - - /* NEW flow on the reconnected conn → byte-exact echo (the real assertion). */ - MQ_CHECK_EQ_INT(run_tcp_echo_flow(&f, origin_port), 0); - - /* (b) Kill-during-auth variant: drop the conn again, then drop AGAIN as soon - * as the next connect re-establishes (kill near the AUTH cycle). The client - * must keep recovering (reconnect, not terminal) and re-auth once more. */ - int auth_before = g_auth_count; /* == 2 */ - mq_conn_close(mq_client_conn(f.client)); - /* Pump a touch, then attempt a SECOND drop of the fresh conn. NOTE: this is a - * best-effort second drop that MAY OR MAY NOT land during the auth cycle — if - * c2 is NULL we're mid-backoff and skip it. So this exercises double-loss - * recovery deterministically; it does NOT deterministically hit a - * kill-DURING-auth point (that would require closing from inside on_auth). - * The recovery assertion below is sound regardless. */ - pump_for(f.base, 50); - mq_conn_t *c2 = mq_client_conn(f.client); - if (c2) mq_conn_close(c2); /* may be NULL mid-backoff — that's fine */ - /* Despite the double drop, the client must recover + re-auth. */ - pump_until_auth_count(f.base, auth_before + 1, RECONNECT_BUDGET_MS * 2); - MQ_CHECK(g_auth_count >= auth_before + 1); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 1); - /* And a flow still completes after that recovery. */ - MQ_CHECK_EQ_INT(run_tcp_echo_flow(&f, origin_port), 0); - - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 2: UDP reconnect → new datagram echoes byte-exact (mq_udp_cli rebind). - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case2_udp_reconnect(void) -{ - fixture_t f; - if (fixture_up(&f, 0) != 0) { - fixture_down(&f); - return; - } - udp_echo_t echo; - uint16_t echo_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&echo, f.base, &echo_port), 0); - - pump_until_auth_count(f.base, 1, 4000); - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(mq_client_udp_available(f.client), 1); - - /* Baseline UDP round-trip. */ - cli_rx_t cap1; - memset(&cap1, 0, sizeof(cap1)); - void *sess1 = cli_open_v4(f.client, echo_port, &cap1); - MQ_CHECK(sess1 != NULL); - uint8_t p1[256]; - for (int i = 0; i < 256; i++) - p1[i] = (uint8_t)((i * 37 + 5) & 0xff); - mq_client_udp_send_fn()(sess1, p1, sizeof(p1)); - pump_until(f.base, &cap1.rx_done, 4000); - MQ_CHECK(cap1.rx_done); - MQ_CHECK_EQ_INT((int)cap1.rxlen, 256); - if (cap1.rx_done) MQ_CHECK_MEM(cap1.rx, p1, 256); - /* The pre-loss session handle dies with the conn; close it now. */ - mq_client_udp_close_fn()(sess1); - - /* Drop the conn; pump until reconnected + re-authed. */ - mq_conn_close(mq_client_conn(f.client)); - pump_until_auth_count(f.base, 2, RECONNECT_BUDGET_MS); - MQ_CHECK_EQ_INT(g_auth_count, 2); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(mq_client_udp_available(f.client), 1); - - /* NEW session on the reconnected conn → byte-exact echo proves mq_udp_cli - * rebind (mq_udp_cli_set_conn) worked end-to-end through the real reconnect. */ - cli_rx_t cap2; - memset(&cap2, 0, sizeof(cap2)); - void *sess2 = cli_open_v4(f.client, echo_port, &cap2); - MQ_CHECK(sess2 != NULL); - uint8_t p2[300]; - for (int i = 0; i < 300; i++) - p2[i] = (uint8_t)((i * 91 + 13) & 0xff); - mq_client_udp_send_fn()(sess2, p2, sizeof(p2)); - pump_until(f.base, &cap2.rx_done, RECONNECT_BUDGET_MS); - MQ_CHECK(cap2.rx_done); - MQ_CHECK_EQ_INT((int)cap2.rxlen, 300); - if (cap2.rx_done) MQ_CHECK_MEM(cap2.rx, p2, 300); - MQ_CHECK_EQ_INT(cap2.err_fired, 0); - - mq_client_udp_close_fn()(sess2); - udp_echo_down(&echo); - fixture_down(&f); -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 3: open DURING the reconnect window → enqueued (no NULL-conn crash), - * completes byte-exact AFTER re-auth. Both TCP and UDP variants. - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case3_open_during_window(void) -{ - fixture_t f; - if (fixture_up(&f, 0) != 0) { - fixture_down(&f); - return; - } - echo_origin_t tcp_origin; - uint16_t tcp_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&tcp_origin, f.base, &tcp_port), 0); - udp_echo_t udp_origin; - uint16_t udp_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&udp_origin, f.base, &udp_port), 0); - - pump_until_auth_count(f.base, 1, 4000); - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK(g_auth_ok); - - /* Drop the conn and enter the backoff window: pump until CLOSED is processed - * (conn nulled, backoff armed). The backoff floor is 1000ms, so this lands in - * the window without overshooting into a completed reconnect. */ - mq_conn_close(mq_client_conn(f.client)); - pump_until_conn_null(&f, 4000); - MQ_CHECK(mq_client_conn(f.client) == NULL); /* in the backoff window */ - - /* TCP open DURING the window: must enqueue (not crash on NULL conn, not fail - * immediately). The cb must NOT have fired yet. */ - int tsp[2]; - MQ_CHECK_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, tsp), 0); - set_nonblock(tsp[0]); - set_nonblock(tsp[1]); - open_result_t tres; - memset(&tres, 0, sizeof(tres)); - uint8_t host[4]; - v4_loopback(host); - mq_client_tcp_open(mq_client_tcp_open_core(f.client), host, 4, MQ_ADDR_IPV4, tcp_port, - tsp[1], NULL, 0, &tres, on_tcp_open); - MQ_CHECK_EQ_INT(tres.fired, 0); /* queued across the loss, not failed */ - - /* UDP open DURING the window: handle returned (queued), no crash. */ - cli_rx_t ucap; - memset(&ucap, 0, sizeof(ucap)); - void *usess = cli_open_v4(f.client, udp_port, &ucap); - MQ_CHECK(usess != NULL); - uint8_t up[200]; - for (int i = 0; i < 200; i++) - up[i] = (uint8_t)((i * 53 + 17) & 0xff); - mq_client_udp_send_fn()(usess, up, sizeof(up)); /* queued */ - - /* Let the reconnect + re-auth complete; the queued opens then drain. */ - pump_until_auth_count(f.base, 2, RECONNECT_BUDGET_MS); - MQ_CHECK_EQ_INT(g_auth_count, 2); - MQ_CHECK(g_auth_ok); - - /* TCP queued open completes byte-exact AFTER re-auth. */ - pump_until(f.base, &tres.fired, RECONNECT_BUDGET_MS); - MQ_CHECK(tres.fired); - MQ_CHECK_EQ_INT(tres.ok, 1); - MQ_CHECK_EQ_INT((int)write(tsp[0], "ping", 4), 4); - char rx[16]; - size_t got = 0; - uint64_t deadline = now_ms() + 4000; - while (got < 4 && now_ms() < deadline) { - event_base_loop(f.base, EVLOOP_NONBLOCK); - ssize_t n = recv(tsp[0], rx + got, sizeof(rx) - got, 0); - if (n > 0) got += (size_t)n; - } - MQ_CHECK_EQ_INT((int)got, 4); - if (got == 4) MQ_CHECK(memcmp(rx, "ping", 4) == 0); - close(tsp[0]); - - /* UDP queued open echoes byte-exact AFTER re-auth. */ - pump_until(f.base, &ucap.rx_done, RECONNECT_BUDGET_MS); - MQ_CHECK(ucap.rx_done); - MQ_CHECK_EQ_INT((int)ucap.rxlen, 200); - if (ucap.rx_done) MQ_CHECK_MEM(ucap.rx, up, 200); - - mq_client_udp_close_fn()(usess); - udp_echo_down(&udp_origin); - echo_origin_down(&tcp_origin); - fixture_down(&f); -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 4: queue carry-over — once the conn loss is registered, an open is - * carried in the queue across the loss (no manual re-open) and its echo - * completes after re-auth. (Distinct from case 3: single TCP flow, the - * pure queue-survives-the-loss path with no mid-window cb-not-fired check - * and no UDP variant.) - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case4_queue_carryover(void) -{ - fixture_t f; - if (fixture_up(&f, 0) != 0) { - fixture_down(&f); - return; - } - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - pump_until_auth_count(f.base, 1, 4000); - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK(g_auth_ok); - - /* Close and let the loss register (CLOSED processed: authed→0, conn→NULL). - * NOTE: mq_conn_close is async, so opening in the SAME turn would still see - * authed==1 + a (closing) conn and issue on the dying conn (fails). The - * carry-over contract is: once the loss is registered (authed==0), an open is - * ENQUEUED and drains on the next re-auth with NO manual re-open. */ - mq_conn_close(mq_client_conn(f.client)); - pump_until_conn_null(&f, 4000); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 0); /* loss registered */ - int sp[2]; - MQ_CHECK_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sp), 0); - set_nonblock(sp[0]); - set_nonblock(sp[1]); - open_result_t res; - memset(&res, 0, sizeof(res)); - uint8_t host[4]; - v4_loopback(host); - mq_client_tcp_open(mq_client_tcp_open_core(f.client), host, 4, MQ_ADDR_IPV4, - origin_port, sp[1], NULL, 0, &res, on_tcp_open); - - /* Pump through reconnect + re-auth + queue drain → echo completes. */ - pump_until_auth_count(f.base, 2, RECONNECT_BUDGET_MS); - MQ_CHECK_EQ_INT(g_auth_count, 2); - pump_until(f.base, &res.fired, RECONNECT_BUDGET_MS); - MQ_CHECK(res.fired); - MQ_CHECK_EQ_INT(res.ok, 1); - - MQ_CHECK_EQ_INT((int)write(sp[0], "ping", 4), 4); - char rx[16]; - size_t got = 0; - uint64_t deadline = now_ms() + 4000; - while (got < 4 && now_ms() < deadline) { - event_base_loop(f.base, EVLOOP_NONBLOCK); - ssize_t n = recv(sp[0], rx + got, sizeof(rx) - got, 0); - if (n > 0) got += (size_t)n; - } - MQ_CHECK_EQ_INT((int)got, 4); - if (got == 4) MQ_CHECK(memcmp(rx, "ping", 4) == 0); - close(sp[0]); - - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 5: shutdown vs loss — mq_client_free while RECONNECTING. No crash, no - * late reconnect fires, leak-free (must pass under ASan). - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case5_free_while_reconnecting(void) -{ - fixture_t f; - if (fixture_up(&f, 0) != 0) { - fixture_down(&f); - return; - } - pump_until_auth_count(f.base, 1, 4000); - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK(g_auth_ok); - - /* Drop the conn and enter the backoff window (conn nulled, backoff armed), - * but do NOT pump long enough to reconnect (backoff floor 1000ms). */ - mq_conn_close(mq_client_conn(f.client)); - pump_until_conn_null(&f, 4000); - MQ_CHECK(mq_client_conn(f.client) == NULL); /* reconnecting (backoff armed) */ - - int auth_at_free = g_auth_count; /* == 1 */ - - /* Free the client mid-reconnect: mq_client_free sets shutting_down first + - * disarms/frees the backoff timer, so no reconnect fires after free. */ - mq_client_free(f.client); - f.client = NULL; /* prevent double-free in fixture_down */ - - /* Pump well past the backoff window: NO reconnect must occur (on_auth must - * NOT fire again) and no crash / UAF (caught by ASan). */ - pump_for(f.base, RECONNECT_BUDGET_MS); - MQ_CHECK_EQ_INT(g_auth_count, auth_at_free); /* no late reconnect */ - - fixture_down(&f); /* client already freed; frees the rest cleanly */ -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 6: keepalive — a short idle-timeout is set; the conn stays up across an - * idle window and a flow opened afterward still works. - * - * NOTE on semantics: xquic's keepalive PING cadence is a FIXED 15000ms - * (XQC_PING_TIMEOUT), independent of idle_time_out. Proving the PING actually - * REFRESHES the idle timer would require an idle wait > 15s (too slow for CI). - * So this case sets a keepalive idle of 4000ms and idles ~1500ms (< idle) while - * pumping, then asserts the conn is STILL up (not idle-closed) and a fresh flow - * completes — i.e. the keepalive idle-timeout setter is wired and traffic is - * held open across realistic idle. (The literal "<500ms idle" form is infeasible - * here: a sub-15s idle_time_out cannot be PING-refreshed, so it would close.) - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case6_keepalive_holds(void) -{ - fixture_t f; - if (fixture_up(&f, 4000) != 0) { /* keepalive idle = 4000ms */ - fixture_down(&f); - return; - } - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - pump_until_auth_count(f.base, 1, 4000); - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK(g_auth_ok); - MQ_CHECK(mq_client_conn(f.client) != NULL); - - /* Idle ~1500ms (< 4000ms idle timeout) with NO traffic, pumping the loop. */ - pump_for(f.base, 1500); - - /* The conn must still be up (NOT idle-closed) and NOT have reconnected: - * on_auth must NOT have fired a 2nd time (a drop+reconnect would bump it). */ - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 1); - MQ_CHECK(mq_client_conn(f.client) != NULL); - - /* A flow opened after the idle period still works end-to-end. */ - MQ_CHECK_EQ_INT(run_tcp_echo_flow(&f, origin_port), 0); - - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 7: persistent outage — server GONE across MULTIPLE backoff cycles, then - * brought BACK on the same port. Proves the always-on self-heal: the - * client stays unauthed + keeps retrying (no spurious "recovery", no - * crash/wedge) across ≥2 backoff intervals, then re-auths + a fresh TCP - * echo flow completes byte-exact once the server returns. - * - * Mechanism: tear the server side DOWN entirely (server_down) so re-dials cannot - * complete, while the client + its event base stay alive and keep arming backoff. - * The reserved port is stable (f.port), so server_up rebinds the SAME UDP port - * (SO_REUSEADDR) and the client's ongoing re-dials then land on a live server. - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case7_persistent_outage(void) -{ - fixture_t f; - if (fixture_up(&f, 0) != 0) { - fixture_down(&f); - return; - } - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - /* Establish + first auth. */ - pump_until_auth_count(f.base, 1, 4000); - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 1); - MQ_CHECK_EQ_INT(run_tcp_echo_flow(&f, origin_port), 0); - - /* Bring the SERVER fully down, then drop the client's conn. Re-dials now - * cannot complete: server gone. The client keeps arming backoff + retrying. */ - server_down(&f); - mq_conn_close(mq_client_conn(f.client)); - - /* mq_conn_close is async: pump until the CLOSED is processed (conn nulled, - * authed reset to 0, backoff armed) before asserting the outage invariant. */ - pump_until_conn_null(&f, 4000); - MQ_CHECK(mq_client_conn(f.client) == NULL); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 0); - - /* Pump across ≥2 backoff cycles (max-backoff 1000ms + full-jitter ⇒ a cap of - * ~1s per cycle; 3500ms spans at least two re-dial attempts). Throughout the - * outage the client must stay UNAUTHED, must NOT spuriously report recovery - * (auth_count stays 1), and must not crash/wedge. */ - uint64_t outage_deadline = now_ms() + 3500; - while (now_ms() < outage_deadline) { - event_base_loop(f.base, EVLOOP_NONBLOCK); - /* Invariant checked continuously: no false "recovery" while server gone. */ - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 0); - } - /* Outage assertions: still exactly one auth, still NOT authed. (conn may be - * NULL in the backoff window OR a non-NULL pending wrapper from an in-flight - * re-dial that is mid-handshake against the gone server — either is the - * "still trying, not recovered" state. is_authed==0 is the load-bearing - * proof of no spurious recovery.) */ - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 0); - - /* Bring the server BACK on the SAME port the client keeps dialing. */ - MQ_CHECK_EQ_INT(server_up(&f), 0); - - /* Pump until the client's next re-dial lands on the live server and re-auths - * (auth_count reaches 2). Budget generously: a backoff cycle + handshake + - * re-auth, and the re-dial may have to wait out the current backoff interval. */ - pump_until_auth_count(f.base, 2, RECONNECT_BUDGET_MS * 2); - MQ_CHECK_EQ_INT(g_auth_count, 2); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 1); - MQ_CHECK(mq_client_conn(f.client) != NULL); - - /* A NEW TCP echo flow completes byte-exact on the recovered conn. */ - MQ_CHECK_EQ_INT(run_tcp_echo_flow(&f, origin_port), 0); - - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 8: terminal close (reconnect DISABLED) → tcp_open fast-fails. Establish, - * drop the conn so the client goes terminal (c->closed=1, no reconnect), - * then a tcp_open must fail fast via the c->closed arm (no enqueue, no - * hang). Exercises mq_client.c tcp_open's terminal fast-fail. - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case8_open_after_terminal(void) -{ - fixture_t f; - if (fixture_up(&f, 0) != 0) { - fixture_down(&f); - return; - } - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - pump_until_auth_count(f.base, 1, 4000); - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK(g_auth_ok); - - /* DISABLE reconnect: the next conn loss is terminal (c->closed=1). */ - mq_client_set_reconnect(f.client, 0, 0); - - /* Drop the conn; pump until the loss is registered and the client has gone - * terminal (conn NULL, no reconnect armed). NOTE: the terminal branch sets - * c->closed=1 and conn=NULL but does NOT reset c->authed (only the reconnect - * branch does), so is_authed may remain 1 — the c->closed latch is what gates - * tcp_open here, checked before the authed arm. */ - mq_conn_close(mq_client_conn(f.client)); - pump_until_conn_null(&f, 4000); - MQ_CHECK(mq_client_conn(f.client) == NULL); - /* No reconnect can recover it now: pump well past any backoff window; auth - * count must stay 1 (terminal, not reconnecting). */ - pump_for(f.base, 1500); - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK(mq_client_conn(f.client) == NULL); - - /* tcp_open after terminal close must fail FAST (c->closed arm): the cb fires - * with ok=0 synchronously and the fd is closed — not enqueued, not hung. */ - int sp[2]; - MQ_CHECK_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sp), 0); - set_nonblock(sp[0]); - set_nonblock(sp[1]); - open_result_t res; - memset(&res, 0, sizeof(res)); - uint8_t host[4]; - v4_loopback(host); - mq_client_tcp_open(mq_client_tcp_open_core(f.client), host, 4, MQ_ADDR_IPV4, - origin_port, sp[1], NULL, 0, &res, on_tcp_open); - /* Fast-fail is synchronous: the cb has already fired (no pump needed). */ - MQ_CHECK_EQ_INT(res.fired, 1); - MQ_CHECK_EQ_INT(res.ok, 0); - close(sp[0]); /* the client closed sp[1] on the fast-fail path */ - - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 9: late-callback UAF guard — free the client while its conn is STILL - * ALIVE (SERVING, never dropped), THEN tear the engine down. The engine - * teardown (mq_transport_free → xqc_engine_destroy → mq_conn_close_notify) - * delivers a LATE MQ_CONN_CLOSED for the now-orphaned conn whose on_state - * still points at the just-freed client. mq_client_free's state-cb detach - * (mq_conn_set_on_state(c->conn, NULL, NULL)) is what makes that late - * CLOSED a no-op. - * - * This is the ONLY case that exercises that detach: every other case (and the - * shared fixture_down) frees the transport BEFORE the client, so their late - * CLOSED fires while the client is still alive (no UAF). Here we INVERT the - * order — free the client first — so the detach is load-bearing. - * - * REVERT-PROOF: deleting the detach in mq_client_free makes this case fail under - * ASan (heap-use-after-free reading c->data_head in client_on_state's CLOSED - * branch). Verified by disabling the detach: this case then ASan-aborts. - * - * The conn is NEVER dropped before the free, so this is distinct from case 5 - * (free DURING the backoff window, conn already NULL): there the late callback - * cannot fire (conn already freed); here the conn is live at free time. - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case9_free_before_engine_destroy(void) -{ - fixture_t f; - if (fixture_up(&f, 0) != 0) { - fixture_down(&f); - return; - } - /* Reach SERVING; the conn is alive and was NEVER dropped. */ - pump_until_auth_count(f.base, 1, 4000); - MQ_CHECK_EQ_INT(g_auth_count, 1); - MQ_CHECK(g_auth_ok); - MQ_CHECK(mq_client_conn(f.client) != NULL); /* live conn, not dropped */ - - /* Free the client FIRST, while its conn is still live in the engine. The conn - * is now orphaned (its on_state still references the freed client struct); - * mq_client_free's mq_conn_set_on_state(NULL) detach must have severed it so - * the imminent engine-teardown CLOSED cannot re-enter client_on_state. */ - mq_client_free(f.client); - f.client = NULL; /* prevent double-free in fixture_down */ - - /* Tear the engine down. fixture_down frees cli_t FIRST: mq_transport_free → - * xqc_engine_destroy → mq_conn_close_notify for the orphaned conn → on_state - * is NULL (detached), so NO re-entry into client_on_state. Without the detach - * this is a heap-use-after-free (caught by ASan). No positive assertion: the - * proof is "ASan stays silent here, and fails when the detach is reverted". */ - fixture_down(&f); -} - -static void -test_client_reconnect(void) -{ - /* Writes to a socketpair whose peer was closed (e.g. a flow torn down by a - * conn drop) can raise SIGPIPE; ignore it so such a write returns EPIPE. */ - signal(SIGPIPE, SIG_IGN); - test_case1_tcp_reconnect(); - test_case2_udp_reconnect(); - test_case3_open_during_window(); - test_case4_queue_carryover(); - test_case5_free_while_reconnecting(); - test_case6_keepalive_holds(); - test_case7_persistent_outage(); - test_case8_open_after_terminal(); - test_case9_free_before_engine_destroy(); -} - -MQ_TEST_MAIN(test_client_reconnect()) diff --git a/tests/integration/test_client_server.c b/tests/integration/test_client_server.c deleted file mode 100644 index d7c2cfb8..00000000 --- a/tests/integration/test_client_server.c +++ /dev/null @@ -1,1278 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_client_server.c — Task 11 integration test: control-stream AUTH. - * - * Two engines (server with cert/key + client) share one libevent base on - * loopback. The client opens the control stream (first client-initiated bidi - * stream) and sends AUTH_REQUEST; the server replies AUTH_RESPONSE on the same - * stream. - * - * This task tests ONLY the auth FAILURE path + control-stream identification: - * Case A — wrong token: on_auth fires ok==0 / MQ_AUTH_FAILED and the client - * connection subsequently closes. - * Case B — matching token: on_auth fires ok==1, and the server ran auth on - * exactly the first stream (auth-attempt counter == 1) even though - * the client opens a second (data) stream afterward. - * - * Happy-path data exchange is covered by Tasks 12/13. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "proxy/mq_client.h" -#include "proxy/mq_server.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_stream.h" -#include "transport/mq_transport.h" -#include "wire/mq_wire.h" - -#define TEST_ALPN "mqproxy-tcp/1" - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -/* Reserve an ephemeral loopback UDP port: bind a temp socket to :0, read the - * assigned port, then close it so a runtime can bind that fixed port. The - * runtime no longer exposes the bound port of an ephemeral (:0) bind, so the - * test pins a concrete port for the server up front. */ -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -/* ── auth-callback bookkeeping ──────────────────────────────────────────── */ -static int g_auth_fired; -static int g_auth_ok; -static mq_auth_err_t g_auth_err; - -static void -on_auth(int ok, mq_auth_err_t err, void *user) -{ - (void)user; - g_auth_fired = 1; - g_auth_ok = ok; - g_auth_err = err; -} - -static int g_client_closed; -static void -on_client_state(mq_conn_t *c, mq_conn_state_t st, void *user) -{ - (void)c; - (void)user; - if (st == MQ_CONN_CLOSED) { - g_client_closed = 1; - } -} - -/* Stand up a server+client on a shared base, run the handshake + auth, and - * report the auth outcome. Returns the mq_server (so the caller can read the - * auth-attempt counter) via out_server; client conn via out_conn. */ -typedef struct { - struct event_base *base; - mq_transport_t *srv_t; - mq_transport_t *cli_t; - mq_runtime_t *srv_rt; - mq_runtime_t *cli_rt; - mq_server_t *server; - mq_client_t *client; -} fixture_t; - -static int -fixture_up(fixture_t *f, const char *client_token) -{ - memset(f, 0, sizeof(*f)); - f->base = event_base_new(); - MQ_CHECK(f->base != NULL); - if (!f->base) return -1; - - uint16_t port = reserve_udp_port(); - MQ_CHECK(port != 0); - if (!port) return -1; - - /* Server: transport (TLS) + runtime borrowing the shared base. */ - f->srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(f->srv_t != NULL); - if (!f->srv_t) return -1; - f->srv_rt = mq_runtime_new(f->srv_t, f->base); - MQ_CHECK(f->srv_rt != NULL); - if (!f->srv_rt) return -1; - f->server = mq_server_new(f->srv_t, f->srv_rt, "secret", MQ_CC_BBR2, 60000, 1); - MQ_CHECK(f->server != NULL); - if (!f->server) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->srv_rt, "127.0.0.1", port), 0); - - /* Client: transport + runtime borrowing the SAME shared base. */ - f->cli_t = mq_transport_new(0); - MQ_CHECK(f->cli_t != NULL); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - MQ_CHECK(f->cli_rt != NULL); - if (!f->cli_rt) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0), 0); - - f->client = mq_client_new(f->cli_t, f->cli_rt, "127.0.0.1", port, "client-1", - client_token, MQ_CC_BBR2); - MQ_CHECK(f->client != NULL); - if (!f->client) return -1; - mq_client_set_on_auth(f->client, on_auth, NULL); - /* These existing cases assert terminal behavior on conn close (esp. Case F, - * which closes the conn mid-pump); reconnect (default-ON in Phase 5b) would - * otherwise re-establish during the pump. Disable it here so the suite stays - * deterministic. Reconnect is exercised by the dedicated Task 4.3 test. */ - mq_client_set_reconnect(f->client, 0, 30000); - - MQ_CHECK_EQ_INT(mq_client_start(f->client), 0); - mq_client_set_on_state(f->client, on_client_state, NULL); - return 0; -} - -static void -fixture_down(fixture_t *f) -{ - /* Per side: free the transport FIRST (engine destroy fires conn_close_notify - * -> the client/server state callbacks, whose `user` is the mq_client / - * mq_server, and may also fire the runtime send_udp on a final close), so the - * runtime + client + server must outlive the transport. Then the runtimes - * (which BORROWED the shared base, so they do NOT free it), then the proxy - * objects. The test owns the shared base and frees it last. */ - if (f->cli_t) mq_transport_free(f->cli_t); - if (f->srv_t) mq_transport_free(f->srv_t); - if (f->cli_rt) mq_runtime_free(f->cli_rt); - if (f->srv_rt) mq_runtime_free(f->srv_rt); - if (f->client) mq_client_free(f->client); - if (f->server) mq_server_free(f->server); - if (f->base) event_base_free(f->base); -} - -static void -pump_until(struct event_base *base, int *flag, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while ((!flag || !*flag) && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -/* ── Case A: wrong token → auth FAILED + conn closes ────────────────────── */ -static void -test_auth_wrong_token(void) -{ - g_auth_fired = g_auth_ok = 0; - g_auth_err = MQ_AUTH_OK; - g_client_closed = 0; - - fixture_t f; - if (fixture_up(&f, "wrong") != 0) { - fixture_down(&f); - return; - } - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_fired); - MQ_CHECK_EQ_INT(g_auth_ok, 0); - MQ_CHECK_EQ_INT((int)g_auth_err, (int)MQ_AUTH_FAILED); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 0); - - /* Server closes the connection after rejecting; client should observe it. */ - pump_until(f.base, &g_client_closed, 2000); - MQ_CHECK(g_client_closed); - - fixture_down(&f); -} - -/* ── Case B: matching token → auth OK + auth ran on exactly one stream ──── */ -static void -test_auth_matching_token(void) -{ - g_auth_fired = g_auth_ok = 0; - g_auth_err = MQ_AUTH_FAILED; - g_client_closed = 0; - - fixture_t f; - if (fixture_up(&f, "secret") != 0) { - fixture_down(&f); - return; - } - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_fired); - MQ_CHECK_EQ_INT(g_auth_ok, 1); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 1); - - /* Open a SECOND (non-control) stream afterward; the server must NOT run - * auth on it. Give the loop time to deliver it. */ - mq_conn_t *conn = mq_client_conn(f.client); - MQ_CHECK(conn != NULL); - if (conn) { - mq_stream_t *s2 = mq_conn_open_stream(conn); - MQ_CHECK(s2 != NULL); - if (s2) { - static const unsigned char data[] = "data"; - (void)mq_stream_send(s2, data, sizeof(data), 0); - } - } - pump_until(f.base, NULL, 800); - - MQ_CHECK_EQ_INT(mq_server_auth_attempts(f.server), 1); - - fixture_down(&f); -} - -/* ── in-process echo TCP origin ─────────────────────────────────────────── */ -/* A libevent-driven listening socket on 127.0.0.1:ephemeral that accepts one - * connection and echoes back every byte it receives. Returns the listening fd - * and reports the bound port. */ -typedef struct { - struct event_base *base; - int listen_fd; - int conn_fd; - struct event *listen_ev; - struct event *conn_ev; -} echo_origin_t; - -static void -echo_set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - if (fl >= 0) (void)fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -static void -echo_conn_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - echo_origin_t *o = (echo_origin_t *)arg; - unsigned char buf[4096]; - ssize_t n = recv(fd, buf, sizeof(buf), 0); - if (n <= 0) { - if (n == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) { - if (o->conn_ev) { - event_free(o->conn_ev); - o->conn_ev = NULL; - } - close(fd); - o->conn_fd = -1; - } - return; - } - /* Echo back (best effort; test payloads are tiny). */ - ssize_t off = 0; - while (off < n) { - ssize_t w = send(fd, buf + off, (size_t)(n - off), 0); - if (w <= 0) break; - off += w; - } -} - -static void -echo_accept_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - echo_origin_t *o = (echo_origin_t *)arg; - int c = accept(fd, NULL, NULL); - if (c < 0) return; - echo_set_nonblock(c); - o->conn_fd = c; - o->conn_ev = event_new(o->base, c, EV_READ | EV_PERSIST, echo_conn_cb, o); - event_add(o->conn_ev, NULL); -} - -static int -echo_origin_up(echo_origin_t *o, struct event_base *base, uint16_t *out_port) -{ - memset(o, 0, sizeof(*o)); - o->base = base; - o->conn_fd = -1; - o->listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (o->listen_fd < 0) return -1; - int one = 1; - setsockopt(o->listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(o->listen_fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) return -1; - if (listen(o->listen_fd, 4) != 0) return -1; - socklen_t sl = sizeof(sa); - if (getsockname(o->listen_fd, (struct sockaddr *)&sa, &sl) != 0) return -1; - *out_port = ntohs(sa.sin_port); - echo_set_nonblock(o->listen_fd); - o->listen_ev = event_new(base, o->listen_fd, EV_READ | EV_PERSIST, echo_accept_cb, o); - event_add(o->listen_ev, NULL); - return 0; -} - -static void -echo_origin_down(echo_origin_t *o) -{ - if (o->conn_ev) event_free(o->conn_ev); - if (o->listen_ev) event_free(o->listen_ev); - if (o->conn_fd >= 0) close(o->conn_fd); - if (o->listen_fd >= 0) close(o->listen_fd); -} - -/* ── in-process bulk origin (sends N bytes, then closes; never reads) ───── */ -/* On accept, this origin streams a deterministic N-byte payload to the client - * (writing as flow control allows via an on-demand EV_WRITE) and then closes - * its socket. It is the "download to completion" peer for Case G. Byte i has - * value (i & 0xff); the client recomputes the same rolling checksum. */ -typedef struct { - struct event_base *base; - int listen_fd; - int conn_fd; - struct event *listen_ev; - struct event *write_ev; - size_t total; /* N bytes to send */ - size_t sent; /* bytes sent so far */ - uint32_t sum; /* rolling checksum of bytes sent (for cross-check) */ - int finished; /* all N bytes sent + socket closed */ -} bulk_origin_t; - -static void -bulk_set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - if (fl >= 0) (void)fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -static void -bulk_write_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - bulk_origin_t *o = (bulk_origin_t *)arg; - unsigned char chunk[8192]; - while (o->sent < o->total) { - size_t want = o->total - o->sent; - if (want > sizeof(chunk)) want = sizeof(chunk); - for (size_t i = 0; i < want; i++) { - chunk[i] = (unsigned char)((o->sent + i) & 0xff); - } - ssize_t w = send(fd, chunk, want, MSG_NOSIGNAL); - if (w < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - event_add(o->write_ev, NULL); /* resume on writable edge */ - return; - } - break; /* hard error: stop */ - } - if (w == 0) break; - for (ssize_t i = 0; i < w; i++) - o->sum += chunk[i]; - o->sent += (size_t)w; - } - /* All bytes flushed (or errored): half-close the write side so the proxy - * sees EOF and propagates a clean FIN, then drop the fd. */ - shutdown(fd, SHUT_WR); - if (o->write_ev) { - event_free(o->write_ev); - o->write_ev = NULL; - } - close(fd); - o->conn_fd = -1; - o->finished = 1; -} - -static void -bulk_accept_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - bulk_origin_t *o = (bulk_origin_t *)arg; - int c = accept(fd, NULL, NULL); - if (c < 0) return; - bulk_set_nonblock(c); - o->conn_fd = c; - o->write_ev = event_new(o->base, c, EV_WRITE, bulk_write_cb, o); - bulk_write_cb(c, EV_WRITE, o); /* prime the pump immediately */ -} - -static int -bulk_origin_up(bulk_origin_t *o, struct event_base *base, size_t total, - uint16_t *out_port) -{ - memset(o, 0, sizeof(*o)); - o->base = base; - o->conn_fd = -1; - o->total = total; - o->listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (o->listen_fd < 0) return -1; - int one = 1; - setsockopt(o->listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(o->listen_fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) return -1; - if (listen(o->listen_fd, 4) != 0) return -1; - socklen_t sl = sizeof(sa); - if (getsockname(o->listen_fd, (struct sockaddr *)&sa, &sl) != 0) return -1; - *out_port = ntohs(sa.sin_port); - bulk_set_nonblock(o->listen_fd); - o->listen_ev = event_new(base, o->listen_fd, EV_READ | EV_PERSIST, bulk_accept_cb, o); - event_add(o->listen_ev, NULL); - return 0; -} - -static void -bulk_origin_down(bulk_origin_t *o) -{ - if (o->write_ev) event_free(o->write_ev); - if (o->listen_ev) event_free(o->listen_ev); - if (o->conn_fd >= 0) close(o->conn_fd); - if (o->listen_fd >= 0) close(o->listen_fd); -} - -/* ── data-stream client helper (test-only; mq_client tcp_open is Task 13) ── */ -/* Build a CONNECT_TCP_REQUEST to 127.0.0.1:port and capture stream rx. */ -typedef struct { - unsigned char rx[8192]; - size_t rxlen; - int resp_seen; - mq_connect_tcp_resp_t resp; - size_t resp_consumed; /* bytes of rx the CONNECT_TCP_RESPONSE occupied */ - int payload_seen; /* the echoed payload arrived after the response */ - - /* Bulk-download bookkeeping (Case G). The post-response payload may exceed - * the small rx[] above, so we count it out-of-band with a rolling checksum - * rather than buffering it all. */ - int track_bulk; /* enable bulk counting on the post-response stream */ - size_t payload_bytes; /* count of post-CONNECT_TCP_RESPONSE payload bytes */ - uint32_t payload_sum; /* rolling additive checksum of payload bytes */ - int fin_seen; /* the QUIC stream delivered a clean FIN */ - int err_seen; /* mq_stream_recv returned a hard error (RESET) */ -} datastream_t; - -/* Fold post-response bytes that are currently sitting in rx[] into the bulk - * counters, then drop them so rx[] never overflows on a long download. Only the - * response prefix is preserved in rx[]. */ -static void -ds_drain_bulk(datastream_t *d) -{ - if (!d->track_bulk || !d->resp_seen) return; - if (d->rxlen <= d->resp_consumed) return; - size_t avail = d->rxlen - d->resp_consumed; - for (size_t i = 0; i < avail; i++) { - d->payload_sum += d->rx[d->resp_consumed + i]; - } - d->payload_bytes += avail; - d->rxlen = d->resp_consumed; /* keep only the response prefix */ -} - -static void -ds_readable(mq_stream_t *s, void *user) -{ - datastream_t *d = (datastream_t *)user; - for (;;) { - if (d->rxlen >= sizeof(d->rx)) { - /* rx[] full: in bulk mode, fold+drop to make room and keep reading. */ - if (d->track_bulk && d->resp_seen) { - ds_drain_bulk(d); - if (d->rxlen >= sizeof(d->rx)) break; /* still full: give up */ - } else { - break; - } - } - int fin = 0; - long n = mq_stream_recv(s, d->rx + d->rxlen, sizeof(d->rx) - d->rxlen, &fin); - if (n < 0) { - d->err_seen = 1; - break; - } - if (n > 0) d->rxlen += (size_t)n; - if (fin) d->fin_seen = 1; - if (n == 0 && !fin) break; /* EAGAIN: nothing more right now */ - if (!d->resp_seen) { - int c = mq_decode_connect_tcp_resp(d->rx, d->rxlen, &d->resp); - if (c > 0) { - d->resp_seen = 1; - d->resp_consumed = (size_t)c; - } - } - ds_drain_bulk(d); - if (fin) break; - } - if (d->resp_seen && d->rxlen >= d->resp_consumed + 4) { - if (memcmp(d->rx + d->resp_consumed, "ping", 4) == 0) d->payload_seen = 1; - } -} - -/* Open a data stream, send CONNECT_TCP_REQUEST(IPv4 127.0.0.1:port). */ -static mq_stream_t * -open_tcp_data_stream(mq_conn_t *conn, uint16_t port, datastream_t *d) -{ - mq_stream_t *s = mq_conn_open_stream(conn); - if (!s) return NULL; - mq_stream_set_cbs(s, ds_readable, NULL, NULL, d); - - mq_connect_tcp_req_t req; - memset(&req, 0, sizeof(req)); - req.flags = 0; - req.address_type = MQ_ADDR_IPV4; - uint32_t v4 = htonl(INADDR_LOOPBACK); - memcpy(req.host, &v4, 4); - req.host_len = 4; - req.port = port; - - uint8_t buf[512]; - /* Prefix the stream-type discriminator (design §5.2). */ - buf[0] = (uint8_t)MQ_STREAM_TYPE_CONNECT_TCP; - int n = mq_encode_connect_tcp_req(buf + 1, sizeof(buf) - 1, &req); - if (n < 0) return NULL; - (void)mq_stream_send(s, buf, (size_t)(1 + n), 0); - return s; -} - -/* ── Case C: authed data stream → dial echo origin → OK + payload echoes ── */ -static void -test_data_stream_echo(void) -{ - g_auth_fired = g_auth_ok = 0; - g_auth_err = MQ_AUTH_FAILED; - - fixture_t f; - if (fixture_up(&f, "secret") != 0) { - fixture_down(&f); - return; - } - - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - - mq_conn_t *conn = mq_client_conn(f.client); - MQ_CHECK(conn != NULL); - - datastream_t d; - memset(&d, 0, sizeof(d)); - mq_stream_t *s = open_tcp_data_stream(conn, origin_port, &d); - MQ_CHECK(s != NULL); - - /* Wait for CONNECT_TCP_RESPONSE(OK). */ - pump_until(f.base, &d.resp_seen, 4000); - MQ_CHECK(d.resp_seen); - MQ_CHECK_EQ_INT((int)d.resp.status, (int)MQ_STATUS_OK); - MQ_CHECK_EQ_INT((int)d.resp.error_code, (int)MQ_TCP_OK); - - /* Now send "ping" on the data stream and expect it echoed back. */ - if (s) (void)mq_stream_send(s, (const unsigned char *)"ping", 4, 0); - pump_until(f.base, &d.payload_seen, 4000); - MQ_CHECK(d.payload_seen); - - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ── Case D: CONNECT_TCP to a closed port → ERROR(CONN_REFUSED) ─────────── */ -static void -test_data_stream_refused(void) -{ - g_auth_fired = g_auth_ok = 0; - - fixture_t f; - if (fixture_up(&f, "secret") != 0) { - fixture_down(&f); - return; - } - - /* Bind+listen then close to obtain a port that is reliably not listening. */ - int tmp = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - bind(tmp, (struct sockaddr *)&sa, sizeof(sa)); - socklen_t sl = sizeof(sa); - getsockname(tmp, (struct sockaddr *)&sa, &sl); - uint16_t dead_port = ntohs(sa.sin_port); - close(tmp); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - - mq_conn_t *conn = mq_client_conn(f.client); - MQ_CHECK(conn != NULL); - - datastream_t d; - memset(&d, 0, sizeof(d)); - mq_stream_t *s = open_tcp_data_stream(conn, dead_port, &d); - MQ_CHECK(s != NULL); - - pump_until(f.base, &d.resp_seen, 4000); - MQ_CHECK(d.resp_seen); - MQ_CHECK_EQ_INT((int)d.resp.status, (int)MQ_STATUS_ERROR); - MQ_CHECK_EQ_INT((int)d.resp.error_code, (int)MQ_TCP_CONN_REFUSED); - - fixture_down(&f); -} - -/* ── Case E: data stream BEFORE auth completes → reset, no dial ─────────── */ -/* Driven by a RAW client conn (no mq_client / no AUTH_REQUEST), so the server's - * `authed` flag is deterministically 0 when the data stream arrives. - * - * The server claims the FIRST client-initiated bidi stream as the control - * stream (Task 11) and waits for AUTH_REQUEST bytes there. We open that control - * stream but never send a valid AUTH_REQUEST, so auth never succeeds. Then we - * open a SECOND (data) stream carrying a CONNECT_TCP_REQUEST. xquic surfaces the - * streams to the server in stream-id order, so by the time the data stream is - * seen, `authed` is still 0 → the server MUST reset it without dialing. */ -typedef struct { - uint16_t origin_port; - datastream_t *d; - int opened; -} preauth_ctx_t; - -static void -preauth_on_state(mq_conn_t *conn, mq_conn_state_t st, void *user) -{ - preauth_ctx_t *p = (preauth_ctx_t *)user; - if (st != MQ_CONN_ESTABLISHED || p->opened) return; - p->opened = 1; - - /* Stream #1: claimed as the control stream by the server. Send nothing - * (and definitely no valid AUTH_REQUEST) so the server stays unauthed. */ - mq_stream_t *ctrl = mq_conn_open_stream(conn); - MQ_CHECK(ctrl != NULL); - if (ctrl) { - static const unsigned char nudge[] = {0x00}; - (void)mq_stream_send(ctrl, nudge, sizeof(nudge), 0); - } - - /* Stream #2: a data stream with a CONNECT_TCP_REQUEST while unauthed. */ - (void)open_tcp_data_stream(conn, p->origin_port, p->d); -} - -static void -test_data_stream_preauth_reset(void) -{ - struct event_base *base = event_base_new(); - MQ_CHECK(base != NULL); - if (!base) return; - - uint16_t srv_port = reserve_udp_port(); - MQ_CHECK(srv_port != 0); - - /* Server: transport (TLS) + runtime borrowing the shared base. */ - mq_transport_t *srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(srv_t != NULL); - mq_runtime_t *srv_rt = srv_t ? mq_runtime_new(srv_t, base) : NULL; - MQ_CHECK(srv_rt != NULL); - mq_server_t *server = - (srv_t && srv_rt) ? mq_server_new(srv_t, srv_rt, "secret", MQ_CC_BBR2, 60000, 1) - : NULL; - MQ_CHECK(server != NULL); - - int srv_bound = srv_rt ? mq_runtime_open_udp_path(srv_rt, "127.0.0.1", srv_port) : -1; - MQ_CHECK_EQ_INT(srv_bound, 0); - - echo_origin_t origin; - uint16_t origin_port = 0; - if (base) MQ_CHECK_EQ_INT(echo_origin_up(&origin, base, &origin_port), 0); - - /* Client: RAW transport conn (no mq_client / no AUTH_REQUEST). */ - mq_transport_t *cli_t = mq_transport_new(0); - MQ_CHECK(cli_t != NULL); - mq_runtime_t *cli_rt = cli_t ? mq_runtime_new(cli_t, base) : NULL; - MQ_CHECK(cli_rt != NULL); - if (cli_t) - MQ_CHECK_EQ_INT(mq_conn_register_alpn(cli_t, TEST_ALPN, NULL, NULL, NULL), 0); - int cli_bound = cli_rt ? mq_runtime_open_udp_path(cli_rt, "127.0.0.1", 0) : -1; - MQ_CHECK_EQ_INT(cli_bound, 0); - - datastream_t d; - memset(&d, 0, sizeof(d)); - preauth_ctx_t pctx = {.origin_port = origin_port, .d = &d, .opened = 0}; - - mq_conn_t *conn = NULL; - if (cli_t && cli_bound == 0) { - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = htons(srv_port); - xqc_conn_settings_t settings; - memset(&settings, 0, sizeof(settings)); - settings.proto_version = XQC_VERSION_V1; - settings.pacing_on = 1; - settings.max_pkt_out_size = 1200; - conn = mq_conn_connect(cli_t, (struct sockaddr *)&sa, sizeof(sa), TEST_ALPN, - &settings, NULL); - MQ_CHECK(conn != NULL); - if (conn) mq_conn_set_on_state(conn, preauth_on_state, &pctx); - } - - /* Let the handshake + both streams settle. */ - pump_until(base, NULL, 2500); - - /* The server must NOT have dialed the origin (no accepted connection) and - * must NOT have returned a CONNECT_TCP_RESPONSE(OK) for the premature - * stream. (It is reset; the client may or may not observe a response.) */ - MQ_CHECK_EQ_INT(origin.conn_fd, -1); - MQ_CHECK(!(d.resp_seen && d.resp.status == MQ_STATUS_OK)); - - echo_origin_down(&origin); - /* Per side: transport first, then runtime (borrowed base, not freed here), - * then the server. The test frees the shared base last. */ - if (cli_t) mq_transport_free(cli_t); - if (srv_t) mq_transport_free(srv_t); - if (cli_rt) mq_runtime_free(cli_rt); - if (srv_rt) mq_runtime_free(srv_rt); - if (server) mq_server_free(server); - if (base) event_base_free(base); -} - -/* ── Case F: connection drop mid-transfer reaps relays (ASan/LSan) ──────── */ -static void -test_data_stream_teardown(void) -{ - g_auth_fired = g_auth_ok = 0; - - fixture_t f; - if (fixture_up(&f, "secret") != 0) { - fixture_down(&f); - return; - } - - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - - mq_conn_t *conn = mq_client_conn(f.client); - MQ_CHECK(conn != NULL); - - datastream_t d; - memset(&d, 0, sizeof(d)); - mq_stream_t *s = open_tcp_data_stream(conn, origin_port, &d); - MQ_CHECK(s != NULL); - - pump_until(f.base, &d.resp_seen, 4000); - MQ_CHECK(d.resp_seen); - MQ_CHECK_EQ_INT((int)d.resp.status, (int)MQ_STATUS_OK); - - /* Force the MPQUIC connection closed mid-transfer. The server must reap the - * active relay (close stream + fd once, free relay) with no leak/UAF. */ - mq_conn_close(conn); - pump_until(f.base, NULL, 1500); - - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ── Case G: download to completion → ALL N bytes + clean FIN, no RESET ─── */ -/* The origin streams N bytes (N spans several packets / flow-control quanta) - * then closes. The client reads the data stream until the QUIC stream FIN and - * must observe EXACTLY N payload bytes with a matching checksum AND a clean FIN - * (never a reset/error). Against the pre-fix server this FAILS: completion reap - * runs with graceful==0 → RESET_STREAM, truncating un-acked STREAM data, so the - * client sees far fewer than N bytes and never a FIN. */ -static void -test_data_stream_download_completion(void) -{ - g_auth_fired = g_auth_ok = 0; - - fixture_t f; - if (fixture_up(&f, "secret") != 0) { - fixture_down(&f); - return; - } - - const size_t N = 200000; /* > one MTU / flow-control quantum: multi-packet */ - bulk_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(bulk_origin_up(&origin, f.base, N, &origin_port), 0); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - - mq_conn_t *conn = mq_client_conn(f.client); - MQ_CHECK(conn != NULL); - - datastream_t d; - memset(&d, 0, sizeof(d)); - d.track_bulk = 1; - mq_stream_t *s = open_tcp_data_stream(conn, origin_port, &d); - MQ_CHECK(s != NULL); - - /* Drive until the client sees the QUIC stream FIN (or the budget expires). - * A RESET would surface as err_seen / a missing FIN, not a clean fin_seen. */ - pump_until(f.base, &d.fin_seen, 8000); - - /* Compute the expected checksum the origin would have produced for N bytes. */ - uint32_t expect_sum = 0; - for (size_t i = 0; i < N; i++) - expect_sum += (unsigned char)(i & 0xff); - - MQ_CHECK(d.resp_seen); - MQ_CHECK_EQ_INT((int)d.resp.status, (int)MQ_STATUS_OK); - MQ_CHECK(d.fin_seen); /* clean FIN, not a reset */ - MQ_CHECK_EQ_INT(d.err_seen, 0); /* no hard error / RESET */ - MQ_CHECK_EQ_INT((long long)d.payload_bytes, (long long)N); /* ALL bytes */ - MQ_CHECK_EQ_INT((long long)d.payload_sum, (long long)expect_sum); - - bulk_origin_down(&origin); - fixture_down(&f); -} - -/* ── Case H: origin half-closes while client lingers → no busy-spin ─────── */ -/* The origin sends a tiny reply then closes (recv→0 at the proxy). The client - * keeps its side open briefly. Against the pre-fix server, the EOF'd origin fd's - * level-triggered EV_READ re-fires forever (one event_base_loop iteration never - * returns), so the bounded pump budget below is blown / the test hangs. After - * the fix, the dead fd's read event is event_del'd on EOF and a FIN is - * propagated, so the client gets the reply + a clean FIN well within budget. */ -static void -test_data_stream_halfclose_nospin(void) -{ - g_auth_fired = g_auth_ok = 0; - - fixture_t f; - if (fixture_up(&f, "secret") != 0) { - fixture_down(&f); - return; - } - - const size_t N = 64; /* tiny reply */ - bulk_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(bulk_origin_up(&origin, f.base, N, &origin_port), 0); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - - mq_conn_t *conn = mq_client_conn(f.client); - MQ_CHECK(conn != NULL); - - datastream_t d; - memset(&d, 0, sizeof(d)); - d.track_bulk = 1; - mq_stream_t *s = open_tcp_data_stream(conn, origin_port, &d); - MQ_CHECK(s != NULL); - - uint64_t start = now_ms(); - /* Bounded budget: a busy-spin would make a single loop iteration never - * return, so wall-clock would blow far past this. */ - pump_until(f.base, &d.fin_seen, 5000); - uint64_t elapsed = now_ms() - start; - - MQ_CHECK(d.resp_seen); - MQ_CHECK(d.fin_seen); - MQ_CHECK_EQ_INT(d.err_seen, 0); - MQ_CHECK_EQ_INT((long long)d.payload_bytes, (long long)N); - /* Completed well within the budget (no spin): generous ceiling. */ - MQ_CHECK(elapsed < 5000); - - bulk_origin_down(&origin); - fixture_down(&f); -} - -/* ── Task 13: real mq_client_tcp_open end-to-end ──────────────────────────── - * - * These exercise the CLIENT data path (mq_client_tcp_open), not the test-only - * raw-stream helper above. A socketpair() stands in for the local app socket: - * one end (local_fd) is handed to the client as the relay's B side; the test - * drives/reads the OTHER end (app_fd) as the application would. */ - -/* tcp_open callback bookkeeping. */ -typedef struct { - int fired; - int ok; - mq_tcp_err_t err; -} open_result_t; - -static void -on_tcp_open(int ok, mq_tcp_err_t err, void *user) -{ - open_result_t *r = (open_result_t *)user; - r->fired = 1; - r->ok = ok; - r->err = err; -} - -/* Build an IPv4 host buffer for 127.0.0.1 into host[4]. */ -static void -v4_loopback(uint8_t host[4]) -{ - uint32_t v4 = htonl(INADDR_LOOPBACK); - memcpy(host, &v4, 4); -} - -/* Case I — client tcp_open echo: app→local_fd→client→stream→server→origin→back. */ -static void -test_client_open_echo(void) -{ - g_auth_fired = g_auth_ok = 0; - g_auth_err = MQ_AUTH_FAILED; - - fixture_t f; - if (fixture_up(&f, "secret") != 0) { - fixture_down(&f); - return; - } - - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - - int sp[2]; - MQ_CHECK_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sp), 0); - echo_set_nonblock(sp[0]); - echo_set_nonblock(sp[1]); - int app_fd = sp[0]; - int local_fd = sp[1]; /* handed to the client */ - - open_result_t res; - memset(&res, 0, sizeof(res)); - uint8_t host[4]; - v4_loopback(host); - mq_client_tcp_open(mq_client_tcp_open_core(f.client), host, 4, MQ_ADDR_IPV4, - origin_port, local_fd, NULL, 0, &res, on_tcp_open); - - pump_until(f.base, &res.fired, 4000); - MQ_CHECK(res.fired); - MQ_CHECK_EQ_INT(res.ok, 1); - MQ_CHECK_EQ_INT((int)res.err, (int)MQ_TCP_OK); - - /* Write "ping" into the app end; expect it echoed back through the path. */ - MQ_CHECK_EQ_INT((int)write(app_fd, "ping", 4), 4); - - char rx[16]; - size_t got = 0; - uint64_t deadline = now_ms() + 4000; - while (got < 4 && now_ms() < deadline) { - event_base_loop(f.base, EVLOOP_NONBLOCK); - ssize_t n = recv(app_fd, rx + got, sizeof(rx) - got, 0); - if (n > 0) got += (size_t)n; - } - MQ_CHECK_EQ_INT((int)got, 4); - MQ_CHECK(memcmp(rx, "ping", 4) == 0); - - close(app_fd); - echo_origin_down(&origin); - fixture_down(&f); -} - -/* Case J — client-side download completion: ALL N bytes + clean EOF on local_fd, - * no truncation. Proves the client relay propagates the server's stream FIN to a - * shutdown/close of the local fd. */ -static void -test_client_open_download(void) -{ - g_auth_fired = g_auth_ok = 0; - - fixture_t f; - if (fixture_up(&f, "secret") != 0) { - fixture_down(&f); - return; - } - - const size_t N = 200000; - bulk_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(bulk_origin_up(&origin, f.base, N, &origin_port), 0); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - - int sp[2]; - MQ_CHECK_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sp), 0); - echo_set_nonblock(sp[0]); - echo_set_nonblock(sp[1]); - int app_fd = sp[0]; - int local_fd = sp[1]; - - open_result_t res; - memset(&res, 0, sizeof(res)); - uint8_t host[4]; - v4_loopback(host); - mq_client_tcp_open(mq_client_tcp_open_core(f.client), host, 4, MQ_ADDR_IPV4, - origin_port, local_fd, NULL, 0, &res, on_tcp_open); - - pump_until(f.base, &res.fired, 4000); - MQ_CHECK(res.fired); - MQ_CHECK_EQ_INT(res.ok, 1); - - /* Drain the app end until clean EOF (recv == 0), counting bytes + checksum. */ - size_t got = 0; - uint32_t sum = 0; - int eof = 0; - uint64_t deadline = now_ms() + 8000; - while (!eof && now_ms() < deadline) { - event_base_loop(f.base, EVLOOP_NONBLOCK); - unsigned char buf[8192]; - for (;;) { - ssize_t n = recv(app_fd, buf, sizeof(buf), 0); - if (n > 0) { - for (ssize_t i = 0; i < n; i++) - sum += buf[i]; - got += (size_t)n; - } else if (n == 0) { - eof = 1; /* clean EOF: the FIN propagated to a local-fd close */ - break; - } else { - break; /* EAGAIN */ - } - } - } - - uint32_t expect = 0; - for (size_t i = 0; i < N; i++) - expect += (unsigned char)(i & 0xff); - - MQ_CHECK(eof); /* clean EOF, no hang */ - MQ_CHECK_EQ_INT((long long)got, (long long)N); /* ALL bytes, no trunc */ - MQ_CHECK_EQ_INT((long long)sum, (long long)expect); /* checksum matches */ - - close(app_fd); - bulk_origin_down(&origin); - fixture_down(&f); -} - -/* Case K — pre-auth queue: call tcp_open BEFORE auth, drains after auth + echoes. */ -static void -test_client_open_preauth_queue(void) -{ - g_auth_fired = g_auth_ok = 0; - g_auth_err = MQ_AUTH_FAILED; - - fixture_t f; - if (fixture_up(&f, "secret") != 0) { - fixture_down(&f); - return; - } - - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - /* Do NOT pump for auth yet — issue tcp_open while unauthed. */ - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 0); - - int sp[2]; - MQ_CHECK_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sp), 0); - echo_set_nonblock(sp[0]); - echo_set_nonblock(sp[1]); - int app_fd = sp[0]; - int local_fd = sp[1]; - - open_result_t res; - memset(&res, 0, sizeof(res)); - uint8_t host[4]; - v4_loopback(host); - mq_client_tcp_open(mq_client_tcp_open_core(f.client), host, 4, MQ_ADDR_IPV4, - origin_port, local_fd, NULL, 0, &res, on_tcp_open); - - /* It must NOT have completed yet (queued, awaiting auth). */ - MQ_CHECK_EQ_INT(res.fired, 0); - - /* Now let auth + the drained open complete. */ - pump_until(f.base, &res.fired, 6000); - MQ_CHECK(g_auth_ok); - MQ_CHECK(res.fired); - MQ_CHECK_EQ_INT(res.ok, 1); - - MQ_CHECK_EQ_INT((int)write(app_fd, "ping", 4), 4); - char rx[16]; - size_t got = 0; - uint64_t deadline = now_ms() + 4000; - while (got < 4 && now_ms() < deadline) { - event_base_loop(f.base, EVLOOP_NONBLOCK); - ssize_t n = recv(app_fd, rx + got, sizeof(rx) - got, 0); - if (n > 0) got += (size_t)n; - } - MQ_CHECK_EQ_INT((int)got, 4); - MQ_CHECK(memcmp(rx, "ping", 4) == 0); - - close(app_fd); - echo_origin_down(&origin); - fixture_down(&f); -} - -/* Case L — refused propagation: tcp_open to a closed port → cb ok=0 / REFUSED. */ -static void -test_client_open_refused(void) -{ - g_auth_fired = g_auth_ok = 0; - - fixture_t f; - if (fixture_up(&f, "secret") != 0) { - fixture_down(&f); - return; - } - - int tmp = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - bind(tmp, (struct sockaddr *)&sa, sizeof(sa)); - socklen_t sl = sizeof(sa); - getsockname(tmp, (struct sockaddr *)&sa, &sl); - uint16_t dead_port = ntohs(sa.sin_port); - close(tmp); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - - int sp[2]; - MQ_CHECK_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sp), 0); - echo_set_nonblock(sp[0]); - echo_set_nonblock(sp[1]); - int app_fd = sp[0]; - int local_fd = sp[1]; - - open_result_t res; - memset(&res, 0, sizeof(res)); - uint8_t host[4]; - v4_loopback(host); - mq_client_tcp_open(mq_client_tcp_open_core(f.client), host, 4, MQ_ADDR_IPV4, - dead_port, local_fd, NULL, 0, &res, on_tcp_open); - - pump_until(f.base, &res.fired, 4000); - MQ_CHECK(res.fired); - MQ_CHECK_EQ_INT(res.ok, 0); - MQ_CHECK_EQ_INT((int)res.err, (int)MQ_TCP_CONN_REFUSED); - - close(app_fd); - fixture_down(&f); -} - -/* Case M — server-side coalesced request+payload: a client sends the - * CONNECT_TCP_REQUEST and its first upload bytes in ONE stream write, so the - * server pulls both into its header buffer in a single read. The trailing - * payload must reach the origin (the server's flow prebuffer), not be dropped. - * Asserts the echoed payload returns. */ -static void -test_data_stream_coalesced_payload(void) -{ - g_auth_fired = g_auth_ok = 0; - - fixture_t f; - if (fixture_up(&f, "secret") != 0) { - fixture_down(&f); - return; - } - - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - - mq_conn_t *conn = mq_client_conn(f.client); - MQ_CHECK(conn != NULL); - - datastream_t d; - memset(&d, 0, sizeof(d)); - mq_stream_t *s = mq_conn_open_stream(conn); - MQ_CHECK(s != NULL); - if (s) { - mq_stream_set_cbs(s, ds_readable, NULL, NULL, &d); - - mq_connect_tcp_req_t req; - memset(&req, 0, sizeof(req)); - req.flags = 0; - req.address_type = MQ_ADDR_IPV4; - uint32_t v4 = htonl(INADDR_LOOPBACK); - memcpy(req.host, &v4, 4); - req.host_len = 4; - req.port = origin_port; - - /* Encode the request (prefixed with stream-type discriminator) and - * append "ping" so they go out in one write — the server reads - * discriminator+request+payload in a single buffer fill. */ - uint8_t buf[512]; - buf[0] = (uint8_t)MQ_STREAM_TYPE_CONNECT_TCP; - int n = mq_encode_connect_tcp_req(buf + 1, sizeof(buf) - 1, &req); - MQ_CHECK(n > 0); - if (n > 0 && (size_t)(1 + n) + 4 <= sizeof(buf)) { - memcpy(buf + 1 + n, "ping", 4); - (void)mq_stream_send(s, buf, (size_t)(1 + n) + 4, 0); - } - } - - pump_until(f.base, &d.payload_seen, 6000); - MQ_CHECK(d.resp_seen); - MQ_CHECK_EQ_INT((int)d.resp.status, (int)MQ_STATUS_OK); - MQ_CHECK(d.payload_seen); /* the coalesced "ping" reached the origin + echoed */ - - echo_origin_down(&origin); - fixture_down(&f); -} - -static void -test_client_server(void) -{ - test_auth_wrong_token(); - test_auth_matching_token(); - test_data_stream_echo(); - test_data_stream_refused(); - test_data_stream_preauth_reset(); - test_data_stream_teardown(); - test_data_stream_download_completion(); - test_data_stream_halfclose_nospin(); - test_data_stream_coalesced_payload(); - test_client_open_echo(); - test_client_open_download(); - test_client_open_preauth_queue(); - test_client_open_refused(); -} - -MQ_TEST_MAIN(test_client_server()) diff --git a/tests/integration/test_conn_handshake.c b/tests/integration/test_conn_handshake.c deleted file mode 100644 index cad8fda2..00000000 --- a/tests/integration/test_conn_handshake.c +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_conn_handshake.c — loopback MPQUIC handshake over mq_transport+mq_runtime. - * - * Drives a real loopback MPQUIC handshake between two transport cores sharing one - * libevent base: - * - a server-mode mq_transport with a self-signed cert/key, ALPN - * "mqproxy-tcp/1" registered, whose runtime binds the primary path to - * 127.0.0.1:; - * - a client-mode mq_transport whose runtime binds 127.0.0.1:0 and whose - * mq_conn_connect()s to the server's bound address. - * - * Both runtimes BORROW the shared base; the test pumps it with - * event_base_loop(EVLOOP_NONBLOCK) under a bounded wall-clock budget until the - * client's on_state callback reports ESTABLISHED. Then everything is torn down - * and the test must be ASan-clean. - * - * No application stream data is exchanged beyond a tiny stream to validate the - * stream callback wiring — this validates handshake only. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include - -#include - -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_stream.h" -#include "transport/mq_transport.h" - -#define TEST_ALPN "mqproxy-tcp/1" - -/* Cert/key paths injected by CMake (generated at build time if missing). */ -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -static int g_client_established; -static int g_client_closed; - -static void -on_client_state(mq_conn_t *c, mq_conn_state_t st, void *user) -{ - (void)c; - (void)user; - if (st == MQ_CONN_ESTABLISHED) { - g_client_established = 1; - } else if (st == MQ_CONN_CLOSED) { - g_client_closed = 1; - } -} - -/* Server-side: accepted connections surface here. */ -static int g_server_conn_seen; -static void -on_server_conn(mq_conn_t *c, void *user) -{ - (void)c; - (void)user; - g_server_conn_seen = 1; -} - -static int g_server_stream_seen; -static void -on_server_stream(mq_stream_t *s, void *user) -{ - (void)s; - (void)user; - g_server_stream_seen = 1; -} - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -/* Reserve an ephemeral loopback UDP port: bind a temp socket to :0, read the - * assigned port, then close it so the runtime can bind that fixed port. The - * runtime no longer exposes the bound port of an ephemeral (:0) bind, so the - * test pins a concrete port for the server up front. */ -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -static void -test_conn_handshake(void) -{ - struct event_base *base = event_base_new(); - MQ_CHECK(base != NULL); - if (!base) return; - - uint16_t srv_port = reserve_udp_port(); - MQ_CHECK(srv_port != 0); - - /* ── Server: transport (with TLS) + runtime borrowing the shared base ── */ - mq_transport_t *srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(srv_t != NULL); - mq_runtime_t *srv_rt = srv_t ? mq_runtime_new(srv_t, base) : NULL; - MQ_CHECK(srv_rt != NULL); - - int rc = srv_t ? mq_conn_register_alpn(srv_t, TEST_ALPN, on_server_conn, - on_server_stream, NULL) - : -1; - MQ_CHECK_EQ_INT(rc, 0); - - int srv_bound = srv_rt ? mq_runtime_open_udp_path(srv_rt, "127.0.0.1", srv_port) : -1; - MQ_CHECK_EQ_INT(srv_bound, 0); - - /* ── Client: transport + runtime borrowing the SAME shared base ── */ - mq_transport_t *cli_t = mq_transport_new(/*is_server=*/0); - MQ_CHECK(cli_t != NULL); - mq_runtime_t *cli_rt = cli_t ? mq_runtime_new(cli_t, base) : NULL; - MQ_CHECK(cli_rt != NULL); - - rc = cli_t ? mq_conn_register_alpn(cli_t, TEST_ALPN, NULL, NULL, NULL) : -1; - MQ_CHECK_EQ_INT(rc, 0); - - int cli_bound = cli_rt ? mq_runtime_open_udp_path(cli_rt, "127.0.0.1", 0) : -1; - MQ_CHECK_EQ_INT(cli_bound, 0); - - if (!srv_t || !srv_rt || !cli_t || !cli_rt || srv_bound != 0 || cli_bound != 0) { - goto cleanup; - } - - struct sockaddr_in srv_addr; - memset(&srv_addr, 0, sizeof(srv_addr)); - srv_addr.sin_family = AF_INET; - srv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - srv_addr.sin_port = htons(srv_port); - - xqc_conn_settings_t settings; - memset(&settings, 0, sizeof(settings)); - settings.proto_version = XQC_VERSION_V1; - settings.pacing_on = 1; - settings.max_pkt_out_size = 1200; - - mq_conn_t *conn = mq_conn_connect(cli_t, (struct sockaddr *)&srv_addr, - sizeof(srv_addr), TEST_ALPN, &settings, NULL); - MQ_CHECK(conn != NULL); - if (!conn) goto cleanup; - - mq_conn_set_on_state(conn, on_client_state, NULL); - - /* Pump both sides (shared base) until established or 3s budget. */ - uint64_t deadline = now_ms() + 3000; - while (!g_client_established && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - - MQ_CHECK(g_client_established); - MQ_CHECK(g_server_conn_seen); - - /* Bonus: open one client stream and confirm the server's - * stream_create_notify fires (validates the mq_stream wrappers + the ALP - * stream callback wiring). A short send forces the stream open on the wire. */ - mq_stream_t *cs = mq_conn_open_stream(conn); - MQ_CHECK(cs != NULL); - if (cs) { - static const unsigned char hello[] = "hi"; - long sent = mq_stream_send(cs, hello, sizeof(hello), /*fin=*/1); - MQ_CHECK(sent >= 0); - } - - uint64_t stream_deadline = now_ms() + 1000; - while (!g_server_stream_seen && now_ms() < stream_deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - MQ_CHECK(g_server_stream_seen); - - mq_conn_close(conn); - - /* Pump briefly so close frames flush and callbacks fire. */ - uint64_t close_deadline = now_ms() + 500; - while (now_ms() < close_deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - -cleanup: - /* Per side: free the transport first (engine destroy; callbacks land on the - * still-live runtime), then the runtime (which BORROWED the base, so it does - * NOT free it). The test owns the shared base and frees it last. */ - if (cli_t) mq_transport_free(cli_t); - if (srv_t) mq_transport_free(srv_t); - if (cli_rt) mq_runtime_free(cli_rt); - if (srv_rt) mq_runtime_free(srv_rt); - event_base_free(base); -} - -MQ_TEST_MAIN(test_conn_handshake()) diff --git a/tests/integration/test_datagram_fabric.c b/tests/integration/test_datagram_fabric.c deleted file mode 100644 index b5820955..00000000 --- a/tests/integration/test_datagram_fabric.c +++ /dev/null @@ -1,414 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_datagram_fabric.c — Task 3.2: QUIC DATAGRAM API round-trip validation. - * - * Uses the same in-process loopback harness as test_conn_handshake.c - * (shared libevent base, two mq_transport+mq_runtime pairs, real UDP sockets - * on 127.0.0.1). Both sides negotiate max_datagram_frame_size=65535 so - * datagrams are available after handshake. - * - * Cases verified: - * 1. mq_conn_datagram_mss > 0 on both client and server after handshake. - * 2. Byte-exact round-trip: client→server and server→client, each a - * 1024-byte patterned payload. Data is copied in on_datagram (xquic owns - * the pointer only during the callback). - * 3. mss+1 byte send returns -1 immediately (TOO_LARGE → drop). - * 4. EAGAIN recovery: flood the client send queue (20 × 1024 B) without - * pumping, then pump to drain, then verify the connection is still usable - * via a server→client follow-on datagram. If -1 is never hit (xquic's - * dgram queue appears unbounded with retx disabled), the test logs the - * finding and asserts the follow-on send+arrival anyway. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_transport.h" - -#define TEST_ALPN "mqproxy-tcp/1" - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -/* Patterned payload size for cases 2 and 4. */ -#define DGM_PAYLOAD_LEN 1024 - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -/* Reserve an ephemeral loopback UDP port (same trick as test_conn_handshake). */ -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -/* ── shared test state ────────────────────────────────────────────────────── */ - -static int g_client_established; - -/* Server-side conn handle (wired in on_server_conn so we can call send on it). */ -static mq_conn_t *g_server_conn; - -/* Case 2: bidirectional datagram receive buffers. */ -static uint8_t g_srv_rx_buf[DGM_PAYLOAD_LEN]; -static size_t g_srv_rx_len; -static int g_srv_rx_done; /* 1 once DGM_PAYLOAD_LEN bytes accumulated */ - -static uint8_t g_cli_rx_buf[DGM_PAYLOAD_LEN]; -static size_t g_cli_rx_len; -static int g_cli_rx_done; - -/* Case 4 follow-on receive. g_cli_rx4_armed is set to 1 by the test body just - * before the follow-on send; the callback only captures into the case4 buffer - * when it is armed, avoiding false triggers from the earlier datagram flood. */ -static uint8_t g_cli_rx4_buf[DGM_PAYLOAD_LEN]; -static size_t g_cli_rx4_len; -static int g_cli_rx4_done; -static int g_cli_rx4_armed; /* set by test body before follow-on send */ - -/* ── datagram callbacks ───────────────────────────────────────────────────── */ - -/* Server receives a datagram from the client (case 2). */ -static void -on_server_datagram(mq_conn_t *c, const uint8_t *data, size_t len, void *user) -{ - (void)c; - (void)user; - if (!g_srv_rx_done && len <= DGM_PAYLOAD_LEN) { - /* Accumulate up to DGM_PAYLOAD_LEN bytes (a single datagram is always - * <= mss, so this fires exactly once for the one send in case 2). */ - size_t copy = len; - if (copy > DGM_PAYLOAD_LEN - g_srv_rx_len) copy = DGM_PAYLOAD_LEN - g_srv_rx_len; - memcpy(g_srv_rx_buf + g_srv_rx_len, data, copy); - g_srv_rx_len += copy; - if (g_srv_rx_len >= DGM_PAYLOAD_LEN) g_srv_rx_done = 1; - } -} - -/* Client receives a datagram from the server (case 2 and case 4). */ -static void -on_client_datagram(mq_conn_t *c, const uint8_t *data, size_t len, void *user) -{ - (void)c; - (void)user; - if (!g_cli_rx_done && len <= DGM_PAYLOAD_LEN) { - size_t copy = len; - if (copy > DGM_PAYLOAD_LEN - g_cli_rx_len) copy = DGM_PAYLOAD_LEN - g_cli_rx_len; - memcpy(g_cli_rx_buf + g_cli_rx_len, data, copy); - g_cli_rx_len += copy; - if (g_cli_rx_len >= DGM_PAYLOAD_LEN) g_cli_rx_done = 1; - } else if (g_cli_rx4_armed && !g_cli_rx4_done && len <= DGM_PAYLOAD_LEN) { - /* Case 4 follow-on datagram: only captured when the test body has armed - * us (g_cli_rx4_armed == 1), i.e. after the flood has been flushed. */ - size_t copy = len; - if (copy > DGM_PAYLOAD_LEN - g_cli_rx4_len) - copy = DGM_PAYLOAD_LEN - g_cli_rx4_len; - memcpy(g_cli_rx4_buf + g_cli_rx4_len, data, copy); - g_cli_rx4_len += copy; - if (g_cli_rx4_len >= DGM_PAYLOAD_LEN) g_cli_rx4_done = 1; - } -} - -static void -on_client_state(mq_conn_t *c, mq_conn_state_t st, void *user) -{ - (void)c; - (void)user; - if (st == MQ_CONN_ESTABLISHED) g_client_established = 1; -} - -/* Server accepted connection: save it and wire the datagram callback. */ -static void -on_server_conn(mq_conn_t *c, void *user) -{ - (void)user; - g_server_conn = c; - mq_conn_set_on_datagram(c, on_server_datagram, NULL); -} - -/* ── test body ────────────────────────────────────────────────────────────── */ - -static void -test_datagram_fabric(void) -{ - struct event_base *base = event_base_new(); - MQ_CHECK(base != NULL); - if (!base) return; - - uint16_t srv_port = reserve_udp_port(); - MQ_CHECK(srv_port != 0); - - /* ── Server transport + runtime ── */ - mq_transport_t *srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(srv_t != NULL); - mq_runtime_t *srv_rt = srv_t ? mq_runtime_new(srv_t, base) : NULL; - MQ_CHECK(srv_rt != NULL); - - /* Register ALPN on the server; on_server_conn is called when a new conn is - * accepted — we wire the datagram callback there. */ - int rc = - srv_t ? mq_conn_register_alpn(srv_t, TEST_ALPN, on_server_conn, NULL, NULL) : -1; - MQ_CHECK_EQ_INT(rc, 0); - - /* Server must advertise max_datagram_frame_size so xquic will accept and send - * DATAGRAM frames on accepted conns. */ - { - xqc_conn_settings_t srv_settings; - memset(&srv_settings, 0, sizeof(srv_settings)); - srv_settings.proto_version = XQC_VERSION_V1; - srv_settings.pacing_on = 1; - srv_settings.max_pkt_out_size = 1200; - srv_settings.max_datagram_frame_size = 65535; - xqc_server_set_conn_settings(mq_transport_xqc(srv_t), &srv_settings); - } - - int srv_bound = srv_rt ? mq_runtime_open_udp_path(srv_rt, "127.0.0.1", srv_port) : -1; - MQ_CHECK_EQ_INT(srv_bound, 0); - - /* ── Client transport + runtime (SAME shared base) ── */ - mq_transport_t *cli_t = mq_transport_new(/*is_server=*/0); - MQ_CHECK(cli_t != NULL); - mq_runtime_t *cli_rt = cli_t ? mq_runtime_new(cli_t, base) : NULL; - MQ_CHECK(cli_rt != NULL); - - rc = cli_t ? mq_conn_register_alpn(cli_t, TEST_ALPN, NULL, NULL, NULL) : -1; - MQ_CHECK_EQ_INT(rc, 0); - - int cli_bound = cli_rt ? mq_runtime_open_udp_path(cli_rt, "127.0.0.1", 0) : -1; - MQ_CHECK_EQ_INT(cli_bound, 0); - - if (!srv_t || !srv_rt || !cli_t || !cli_rt || srv_bound != 0 || cli_bound != 0) { - goto cleanup; - } - - struct sockaddr_in srv_addr; - memset(&srv_addr, 0, sizeof(srv_addr)); - srv_addr.sin_family = AF_INET; - srv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - srv_addr.sin_port = htons(srv_port); - - xqc_conn_settings_t settings; - memset(&settings, 0, sizeof(settings)); - settings.proto_version = XQC_VERSION_V1; - settings.pacing_on = 1; - settings.max_pkt_out_size = 1200; - /* Enable QUIC DATAGRAM: both sides must set max_datagram_frame_size. */ - settings.max_datagram_frame_size = 65535; - - mq_conn_t *conn = mq_conn_connect(cli_t, (struct sockaddr *)&srv_addr, - sizeof(srv_addr), TEST_ALPN, &settings, NULL); - MQ_CHECK(conn != NULL); - if (!conn) goto cleanup; - - mq_conn_set_on_state(conn, on_client_state, NULL); - /* Wire the client's datagram receive callback before the handshake completes - * (datagrams cannot arrive before ESTABLISHED, but pre-wiring is safe). */ - mq_conn_set_on_datagram(conn, on_client_datagram, NULL); - - /* ── Handshake ── */ - uint64_t deadline = now_ms() + 3000; - while (!g_client_established && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - MQ_CHECK(g_client_established); - MQ_CHECK(g_server_conn != NULL); - if (!g_client_established || !g_server_conn) goto cleanup; - - /* Give the handshake one more pump round so xquic finishes internalising the - * peer's transport parameters (max_datagram_frame_size) before we query mss. */ - uint64_t settle = now_ms() + 200; - while (now_ms() < settle) - event_base_loop(base, EVLOOP_NONBLOCK); - - /* ── Case 1: mss > 0 on both sides ── */ - size_t cli_mss = mq_conn_datagram_mss(conn); - size_t srv_mss = mq_conn_datagram_mss(g_server_conn); - if (cli_mss == 0 || srv_mss == 0) { - fprintf(stderr, - "case1: mss==0 (cli_mss=%zu srv_mss=%zu) — " - "peer transport params may not have propagated yet; " - "pumping an extra 500 ms before asserting.\n", - cli_mss, srv_mss); - /* Re-sample after a short extra pump. */ - settle = now_ms() + 500; - while (now_ms() < settle) - event_base_loop(base, EVLOOP_NONBLOCK); - cli_mss = mq_conn_datagram_mss(conn); - srv_mss = mq_conn_datagram_mss(g_server_conn); - } - MQ_CHECK(cli_mss > 0); - MQ_CHECK(srv_mss > 0); - fprintf(stderr, "case1: cli_mss=%zu srv_mss=%zu\n", cli_mss, srv_mss); - if (cli_mss == 0 || srv_mss == 0) goto cleanup; - - /* ── Case 2: byte-exact bidirectional datagram round-trip ── */ - - /* Build patterned payloads. */ - uint8_t c2s_payload[DGM_PAYLOAD_LEN]; /* client → server */ - uint8_t s2c_payload[DGM_PAYLOAD_LEN]; /* server → client */ - for (int i = 0; i < DGM_PAYLOAD_LEN; i++) { - c2s_payload[i] = (uint8_t)((i * 53 + 17) & 0xff); - s2c_payload[i] = (uint8_t)((i * 97 + 31) & 0xff); - } - - /* Send client→server and server→client (both before pumping, to keep the - * test simple — datagrams are unordered, order of sends doesn't matter). */ - rc = mq_conn_datagram_send(conn, c2s_payload, DGM_PAYLOAD_LEN); - MQ_CHECK_EQ_INT(rc, 0); - rc = mq_conn_datagram_send(g_server_conn, s2c_payload, DGM_PAYLOAD_LEN); - MQ_CHECK_EQ_INT(rc, 0); - - /* Pump until both sides have received, or 2s budget expires. */ - deadline = now_ms() + 2000; - while ((!g_srv_rx_done || !g_cli_rx_done) && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - - MQ_CHECK(g_srv_rx_done); - MQ_CHECK(g_cli_rx_done); - - if (g_srv_rx_done) { - MQ_CHECK_EQ_INT((int)g_srv_rx_len, DGM_PAYLOAD_LEN); - MQ_CHECK_MEM(g_srv_rx_buf, c2s_payload, DGM_PAYLOAD_LEN); - } - if (g_cli_rx_done) { - MQ_CHECK_EQ_INT((int)g_cli_rx_len, DGM_PAYLOAD_LEN); - MQ_CHECK_MEM(g_cli_rx_buf, s2c_payload, DGM_PAYLOAD_LEN); - } - fprintf(stderr, "case2: srv_rx=%zu cli_rx=%zu\n", g_srv_rx_len, g_cli_rx_len); - - /* ── Case 3: mss+1 send returns -1 immediately ── */ - { - size_t oversized = cli_mss + 1; - uint8_t *big = (uint8_t *)malloc(oversized); - MQ_CHECK(big != NULL); - if (big) { - memset(big, 0xCC, oversized); - int r3 = mq_conn_datagram_send(conn, big, oversized); - MQ_CHECK_EQ_INT(r3, -1); - free(big); - fprintf(stderr, "case3: mss+1=%zu send returned %d (expect -1)\n", oversized, - r3); - } - } - - /* ── Case 4: EAGAIN recovery ────────────────────────────────────────────── - * - * Flood the CLIENT send queue without pumping the event loop, then pump to - * drain, then verify the connection is still healthy via a server→client - * send (received by on_client_datagram). - * - * Empirical finding: xquic's dgram send-queue appears effectively unbounded - * with retx disabled on loopback; all sends consistently return 0 regardless - * of flood size (no EAGAIN observed). The flood size is capped at 20 × 1024 B - * so the queue drains within the 2 s flush window. - */ - { - const int MAX_TIGHT = 20; - uint8_t c4_payload[DGM_PAYLOAD_LEN]; - for (int i = 0; i < DGM_PAYLOAD_LEN; i++) - c4_payload[i] = (uint8_t)((i * 73 + 41) & 0xff); - - int first_neg = -1; /* index where -1 first occurred, -1 = never */ - for (int i = 0; i < MAX_TIGHT; i++) { - int r = mq_conn_datagram_send(conn, c4_payload, DGM_PAYLOAD_LEN); - if (r == -1) { - first_neg = i; - break; - } - } - fprintf(stderr, - "case4: tight-loop send result: %s (first_neg=%d out of max %d)\n", - first_neg == -1 ? "never hit -1 (queue absorbed all)" : "hit -1", - first_neg, MAX_TIGHT); - - /* Pump briefly to let the flood flush through (≤200 ms settle). - * We break early once the follow-on send path is clear; the real - * health signal is the condition-based arrival wait below. */ - uint64_t flush_deadline = now_ms() + 200; - while (now_ms() < flush_deadline) - event_base_loop(base, EVLOOP_NONBLOCK); - - /* Follow-on: SERVER sends one datagram to the CLIENT. This exercises - * the server→client path and provides a clean signal at the client via - * on_client_datagram. Arm the case4 capture window BEFORE the send so - * we don't capture any stray case-2 retries (case2 g_cli_rx_done is - * already 1, so new server→client datagrams land in the armed branch). */ - uint8_t c4b_payload[DGM_PAYLOAD_LEN]; - for (int i = 0; i < DGM_PAYLOAD_LEN; i++) - c4b_payload[i] = (uint8_t)((i * 113 + 7) & 0xff); - - g_cli_rx4_armed = 1; - int r4 = mq_conn_datagram_send(g_server_conn, c4b_payload, DGM_PAYLOAD_LEN); - MQ_CHECK_EQ_INT(r4, 0); - - /* Wait for arrival at the client (condition-based; exits early on success). */ - deadline = now_ms() + 5000; - while (!g_cli_rx4_done && now_ms() < deadline) - event_base_loop(base, EVLOOP_NONBLOCK); - - MQ_CHECK(g_cli_rx4_done); - if (g_cli_rx4_done) { - MQ_CHECK_EQ_INT((int)g_cli_rx4_len, DGM_PAYLOAD_LEN); - MQ_CHECK_MEM(g_cli_rx4_buf, c4b_payload, DGM_PAYLOAD_LEN); - } - fprintf(stderr, "case4: follow-on server→client rc=%d arrived=%d\n", r4, - g_cli_rx4_done); - } - - /* Tear down the client conn, pump briefly for close frames to flush. */ - mq_conn_close(conn); - uint64_t close_deadline = now_ms() + 500; - while (now_ms() < close_deadline) - event_base_loop(base, EVLOOP_NONBLOCK); - -cleanup: - if (cli_t) mq_transport_free(cli_t); - if (srv_t) mq_transport_free(srv_t); - if (cli_rt) mq_runtime_free(cli_rt); - if (srv_rt) mq_runtime_free(srv_rt); - event_base_free(base); -} - -MQ_TEST_MAIN(test_datagram_fabric()) diff --git a/tests/integration/test_e2e_single_path.c b/tests/integration/test_e2e_single_path.c deleted file mode 100644 index bade3f75..00000000 --- a/tests/integration/test_e2e_single_path.c +++ /dev/null @@ -1,1029 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_e2e_single_path.c — Task 16: the 1-A end-to-end milestone. - * - * Drives the FULL ingress→origin path with REAL client sockets over a SINGLE - * path and asserts byte-exact transfer + error mapping. Unlike - * test_client_server.c (which calls mq_client_tcp_open directly), this test - * exercises the real LISTENERS: a real SOCKS5 handshake and a real HTTP CONNECT - * request come in on a TCP socket, traverse - * - * listener (SOCKS5 / HTTP CONNECT) - * → mq_client (tcp_open → CONNECT_TCP_REQUEST over MPQUIC) - * → mq_server (auth + dial origin + relay) - * → in-process origin - * - * and the reply + relayed bytes come all the way back to the client socket. - * - * Everything runs in ONE process on ONE libevent base (server engine + client - * engine + in-process origins + the two listeners). The client sockets are made - * non-blocking and polled by the same bounded pump loop that drives the event - * base — single-threaded, deterministic, ASan-clean (no threads to join). - * - * Cases (design §8 / §9.1): - * 1. SOCKS5 download e2e: origin streams N=200000 deterministic bytes then - * closes; a real SOCKS5 client reads ALL N bytes (count + checksum) and a - * clean EOF — byte-exact through the whole stack incl. the SOCKS5 reply. - * 2. SOCKS5 echo e2e: SOCKS5 CONNECT to an echo origin; a few KB echo back - * byte-exact. - * 3. HTTP CONNECT echo e2e: same as (2) through the HTTP CONNECT listener. - * 4. Error mapping: SOCKS5 CONNECT to a closed port → REP 0x05 (refused); - * HTTP CONNECT to a closed port → HTTP/1.1 502. - * 5/6. Pipelined early payload coalesced with the SOCKS5 / HTTP CONNECT head. - * 7. Concurrent ingress: a SOCKS5 flow and an HTTP CONNECT flow run at the - * SAME time over the ONE client / MPQUIC connection (both listeners share - * the same tcp_open core, exactly as cli/main.c wires them when --socks5 - * and --http-connect are passed together). Each carries a distinct payload - * and must echo back byte-exact — a regression guard against the two - * simultaneous streams being mis-attributed across the shared connection. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ingress/mq_listener.h" -#include "proxy/mq_client.h" -#include "proxy/mq_server.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_transport.h" - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -static void -set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - if (fl >= 0) (void)fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -/* ── full-stack fixture: server + client engines + the two listeners ───────── - * One libevent base. The client engine + mq_client wrap a single MPQUIC conn to - * the server; both listeners hand accepted fds to mq_client_tcp_open via the - * mq_tcp_open_fn boundary. */ -typedef struct { - struct event_base *base; - mq_transport_t *srv_t; - mq_transport_t *cli_t; - mq_runtime_t *srv_rt; - mq_runtime_t *cli_rt; - mq_server_t *server; - mq_client_t *client; - mq_listener_t *socks5; - mq_listener_t *http; -} fixture_t; - -/* Reserve an ephemeral loopback UDP port: bind a temp socket to :0, read the - * assigned port, close it so the server runtime can bind that fixed port (the - * runtime no longer exposes the bound port of a :0 bind). */ -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -static int -fixture_up(fixture_t *f) -{ - memset(f, 0, sizeof(*f)); - f->base = event_base_new(); - MQ_CHECK(f->base != NULL); - if (!f->base) return -1; - - uint16_t port = reserve_udp_port(); - MQ_CHECK(port != 0); - if (!port) return -1; - - /* Server: transport (TLS) + runtime borrowing the shared base. */ - f->srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(f->srv_t != NULL); - if (!f->srv_t) return -1; - f->srv_rt = mq_runtime_new(f->srv_t, f->base); - MQ_CHECK(f->srv_rt != NULL); - if (!f->srv_rt) return -1; - f->server = mq_server_new(f->srv_t, f->srv_rt, "secret", MQ_CC_BBR2, 60000, 1); - MQ_CHECK(f->server != NULL); - if (!f->server) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->srv_rt, "127.0.0.1", port), 0); - - /* Client: transport + runtime borrowing the SAME shared base. */ - f->cli_t = mq_transport_new(0); - MQ_CHECK(f->cli_t != NULL); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - MQ_CHECK(f->cli_rt != NULL); - if (!f->cli_rt) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0), 0); - - f->client = mq_client_new(f->cli_t, f->cli_rt, "127.0.0.1", port, "client-1", - "secret", MQ_CC_BBR2); - MQ_CHECK(f->client != NULL); - if (!f->client) return -1; - - MQ_CHECK_EQ_INT(mq_client_start(f->client), 0); - - /* Listeners wired to the real client core via the tcp_open boundary. */ - void *core = mq_client_tcp_open_core(f->client); - f->socks5 = mq_socks5_listener_new(f->base, "127.0.0.1", 0, mq_client_tcp_open_fn(), - core, NULL, NULL, NULL, NULL); - MQ_CHECK(f->socks5 != NULL); - if (!f->socks5) return -1; - f->http = mq_http_connect_listener_new( - f->base, "127.0.0.1", 0, mq_client_tcp_open_fn(), core, NULL, NULL, NULL, NULL); - MQ_CHECK(f->http != NULL); - if (!f->http) return -1; - return 0; -} - -static void -fixture_down(fixture_t *f) -{ - /* Teardown order mirrors cli/main.c's verified callback graph: free the - * transports FIRST (engine destroy fires conn-close + in-flight tcp_open - * callbacks, which write reject replies onto the live client + listeners and - * may fire the runtime send_udp on a final close), so the runtimes, client, - * server AND listeners must all outlive the transport. Then the runtimes - * (which BORROWED the shared base, so they do NOT free it), then the proxy - * objects, then the listeners. The test owns the shared base and frees it - * last. */ - if (f->cli_t) mq_transport_free(f->cli_t); - if (f->srv_t) mq_transport_free(f->srv_t); - if (f->cli_rt) mq_runtime_free(f->cli_rt); - if (f->srv_rt) mq_runtime_free(f->srv_rt); - if (f->client) mq_client_free(f->client); - if (f->server) mq_server_free(f->server); - if (f->socks5) mq_listener_free(f->socks5); - if (f->http) mq_listener_free(f->http); - if (f->base) event_base_free(f->base); -} - -/* ── client-socket helpers (non-blocking, pumped by the same base) ──────────── */ - -/* Dial 127.0.0.1:port, return a non-blocking fd or -1. The connect is - * non-blocking; on loopback it usually completes immediately, but we treat - * EINPROGRESS as success (the pump drives it). */ -static int -dial_nb(uint16_t port) -{ - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) return -1; - set_nonblock(fd); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr); - int r = connect(fd, (struct sockaddr *)&sa, sizeof(sa)); - if (r != 0 && errno != EINPROGRESS) { - close(fd); - return -1; - } - return fd; -} - -/* Send all of buf on a non-blocking fd while pumping the base for writability. */ -static int -send_all_nb(struct event_base *base, int fd, const uint8_t *buf, size_t len, - uint64_t budget_ms) -{ - size_t off = 0; - uint64_t deadline = now_ms() + budget_ms; - while (off < len && now_ms() < deadline) { - ssize_t n = send(fd, buf + off, len - off, MSG_NOSIGNAL); - if (n > 0) { - off += (size_t)n; - } else if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - event_base_loop(base, EVLOOP_NONBLOCK); - } else if (n < 0 && errno == EINTR) { - continue; - } else { - break; - } - } - return off == len ? 0 : -1; -} - -/* Read exactly `want` bytes into out, pumping the base. Returns bytes read - * (< want on timeout / EOF). */ -static size_t -recv_exact(struct event_base *base, int fd, uint8_t *out, size_t want, uint64_t budget_ms) -{ - size_t got = 0; - uint64_t deadline = now_ms() + budget_ms; - while (got < want && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - ssize_t n = recv(fd, out + got, want - got, 0); - if (n > 0) { - got += (size_t)n; - } else if (n == 0) { - break; /* EOF */ - } - } - return got; -} - -/* SOCKS5 greeting + method reply. Returns 0 on success. */ -static int -socks5_greet(struct event_base *base, int fd) -{ - uint8_t greeting[] = {0x05, 0x01, 0x00}; - if (send_all_nb(base, fd, greeting, sizeof(greeting), 1000) != 0) return -1; - uint8_t mreply[2] = {0}; - if (recv_exact(base, fd, mreply, 2, 2000) != 2) return -1; - if (mreply[0] != 0x05 || mreply[1] != 0x00) return -1; - return 0; -} - -/* Send a SOCKS5 CONNECT request to IPv4 127.0.0.1:port. */ -static int -socks5_connect_v4(struct event_base *base, int fd, uint16_t port) -{ - uint8_t req[10]; - size_t i = 0; - req[i++] = 0x05; /* VER */ - req[i++] = 0x01; /* CMD CONNECT */ - req[i++] = 0x00; /* RSV */ - req[i++] = 0x01; /* ATYP IPv4 */ - uint32_t v4 = htonl(INADDR_LOOPBACK); - memcpy(req + i, &v4, 4); - i += 4; - req[i++] = (uint8_t)(port >> 8); - req[i++] = (uint8_t)(port & 0xff); - return send_all_nb(base, fd, req, i, 1000); -} - -/* Obtain a port with nothing listening: bind+listen an ephemeral port, capture - * it, then close it. */ -static uint16_t -dead_port(void) -{ - int tmp = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - bind(tmp, (struct sockaddr *)&sa, sizeof(sa)); - socklen_t sl = sizeof(sa); - getsockname(tmp, (struct sockaddr *)&sa, &sl); - uint16_t p = ntohs(sa.sin_port); - close(tmp); - return p; -} - -/* ── in-process echo origin ───────────────────────────────────────────────── - * Accepts one connection and echoes every byte. */ -typedef struct { - struct event_base *base; - int listen_fd; - int conn_fd; - struct event *listen_ev; - struct event *conn_ev; -} echo_origin_t; - -static void -echo_conn_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - echo_origin_t *o = (echo_origin_t *)arg; - unsigned char buf[8192]; - ssize_t n = recv(fd, buf, sizeof(buf), 0); - if (n <= 0) { - if (n == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) { - if (o->conn_ev) { - event_free(o->conn_ev); - o->conn_ev = NULL; - } - close(fd); - o->conn_fd = -1; - } - return; - } - ssize_t off = 0; - while (off < n) { - ssize_t w = send(fd, buf + off, (size_t)(n - off), MSG_NOSIGNAL); - if (w <= 0) break; - off += w; - } -} - -static void -echo_accept_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - echo_origin_t *o = (echo_origin_t *)arg; - int c = accept(fd, NULL, NULL); - if (c < 0) return; - set_nonblock(c); - o->conn_fd = c; - o->conn_ev = event_new(o->base, c, EV_READ | EV_PERSIST, echo_conn_cb, o); - event_add(o->conn_ev, NULL); -} - -static int -echo_origin_up(echo_origin_t *o, struct event_base *base, uint16_t *out_port) -{ - memset(o, 0, sizeof(*o)); - o->base = base; - o->conn_fd = -1; - o->listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (o->listen_fd < 0) return -1; - int one = 1; - setsockopt(o->listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(o->listen_fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) return -1; - if (listen(o->listen_fd, 4) != 0) return -1; - socklen_t sl = sizeof(sa); - if (getsockname(o->listen_fd, (struct sockaddr *)&sa, &sl) != 0) return -1; - *out_port = ntohs(sa.sin_port); - set_nonblock(o->listen_fd); - o->listen_ev = event_new(base, o->listen_fd, EV_READ | EV_PERSIST, echo_accept_cb, o); - event_add(o->listen_ev, NULL); - return 0; -} - -static void -echo_origin_down(echo_origin_t *o) -{ - if (o->conn_ev) event_free(o->conn_ev); - if (o->listen_ev) event_free(o->listen_ev); - if (o->conn_fd >= 0) close(o->conn_fd); - if (o->listen_fd >= 0) close(o->listen_fd); -} - -/* ── in-process bulk origin: streams N deterministic bytes, then closes ─────── */ -typedef struct { - struct event_base *base; - int listen_fd; - int conn_fd; - struct event *listen_ev; - struct event *write_ev; - size_t total; - size_t sent; - int finished; -} bulk_origin_t; - -static void -bulk_write_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - bulk_origin_t *o = (bulk_origin_t *)arg; - unsigned char chunk[8192]; - while (o->sent < o->total) { - size_t want = o->total - o->sent; - if (want > sizeof(chunk)) want = sizeof(chunk); - for (size_t i = 0; i < want; i++) - chunk[i] = (unsigned char)((o->sent + i) & 0xff); - ssize_t w = send(fd, chunk, want, MSG_NOSIGNAL); - if (w < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - event_add(o->write_ev, NULL); - return; - } - break; - } - if (w == 0) break; - o->sent += (size_t)w; - } - shutdown(fd, SHUT_WR); - if (o->write_ev) { - event_free(o->write_ev); - o->write_ev = NULL; - } - close(fd); - o->conn_fd = -1; - o->finished = 1; -} - -static void -bulk_accept_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - bulk_origin_t *o = (bulk_origin_t *)arg; - int c = accept(fd, NULL, NULL); - if (c < 0) return; - set_nonblock(c); - o->conn_fd = c; - o->write_ev = event_new(o->base, c, EV_WRITE, bulk_write_cb, o); - bulk_write_cb(c, EV_WRITE, o); -} - -static int -bulk_origin_up(bulk_origin_t *o, struct event_base *base, size_t total, - uint16_t *out_port) -{ - memset(o, 0, sizeof(*o)); - o->base = base; - o->conn_fd = -1; - o->total = total; - o->listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (o->listen_fd < 0) return -1; - int one = 1; - setsockopt(o->listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(o->listen_fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) return -1; - if (listen(o->listen_fd, 4) != 0) return -1; - socklen_t sl = sizeof(sa); - if (getsockname(o->listen_fd, (struct sockaddr *)&sa, &sl) != 0) return -1; - *out_port = ntohs(sa.sin_port); - set_nonblock(o->listen_fd); - o->listen_ev = event_new(base, o->listen_fd, EV_READ | EV_PERSIST, bulk_accept_cb, o); - event_add(o->listen_ev, NULL); - return 0; -} - -static void -bulk_origin_down(bulk_origin_t *o) -{ - if (o->write_ev) event_free(o->write_ev); - if (o->listen_ev) event_free(o->listen_ev); - if (o->conn_fd >= 0) close(o->conn_fd); - if (o->listen_fd >= 0) close(o->listen_fd); -} - -/* ── Case 1: SOCKS5 download e2e (byte-exact, clean EOF) ─────────────────── */ -static void -test_socks5_download(void) -{ - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - const size_t N = 200000; - bulk_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(bulk_origin_up(&origin, f.base, N, &origin_port), 0); - - int c = dial_nb(mq_listener_local_port(f.socks5)); - MQ_CHECK(c >= 0); - if (c < 0) { - bulk_origin_down(&origin); - fixture_down(&f); - return; - } - - MQ_CHECK_EQ_INT(socks5_greet(f.base, c), 0); - MQ_CHECK_EQ_INT(socks5_connect_v4(f.base, c, origin_port), 0); - - /* SOCKS5 CONNECT reply: 05 00 00 01 <4-byte bind addr> <2-byte port>. */ - uint8_t reply[10] = {0}; - MQ_CHECK_EQ_INT((int)recv_exact(f.base, c, reply, 10, 8000), 10); - MQ_CHECK_EQ_INT(reply[0], 0x05); - MQ_CHECK_EQ_INT(reply[1], 0x00); /* success */ - MQ_CHECK_EQ_INT(reply[2], 0x00); - MQ_CHECK_EQ_INT(reply[3], 0x01); - - /* Drain the download until clean EOF, counting bytes + checksum. */ - size_t got = 0; - uint32_t sum = 0; - int eof = 0; - uint64_t deadline = now_ms() + 12000; - while (!eof && now_ms() < deadline) { - event_base_loop(f.base, EVLOOP_NONBLOCK); - unsigned char buf[16384]; - for (;;) { - ssize_t n = recv(c, buf, sizeof(buf), 0); - if (n > 0) { - for (ssize_t i = 0; i < n; i++) - sum += buf[i]; - got += (size_t)n; - } else if (n == 0) { - eof = 1; - break; - } else { - break; /* EAGAIN */ - } - } - } - - uint32_t expect = 0; - for (size_t i = 0; i < N; i++) - expect += (unsigned char)(i & 0xff); - - MQ_CHECK(eof); /* clean EOF, no hang */ - MQ_CHECK_EQ_INT((long long)got, (long long)N); /* ALL bytes, no trunc */ - MQ_CHECK_EQ_INT((long long)sum, (long long)expect); /* checksum matches */ - - close(c); - bulk_origin_down(&origin); - fixture_down(&f); -} - -/* ── Case 2: SOCKS5 echo e2e (byte-exact round-trip) ────────────────────── */ -static void -test_socks5_echo(void) -{ - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - int c = dial_nb(mq_listener_local_port(f.socks5)); - MQ_CHECK(c >= 0); - if (c < 0) { - echo_origin_down(&origin); - fixture_down(&f); - return; - } - - MQ_CHECK_EQ_INT(socks5_greet(f.base, c), 0); - MQ_CHECK_EQ_INT(socks5_connect_v4(f.base, c, origin_port), 0); - - uint8_t reply[10] = {0}; - MQ_CHECK_EQ_INT((int)recv_exact(f.base, c, reply, 10, 8000), 10); - MQ_CHECK_EQ_INT(reply[0], 0x05); - MQ_CHECK_EQ_INT(reply[1], 0x00); - - /* Send a few KB and assert byte-exact echo. */ - const size_t M = 4096; - uint8_t *payload = malloc(M); - MQ_CHECK(payload != NULL); - for (size_t i = 0; i < M; i++) - payload[i] = (uint8_t)((i * 31 + 7) & 0xff); - MQ_CHECK_EQ_INT(send_all_nb(f.base, c, payload, M, 4000), 0); - - uint8_t *back = malloc(M); - MQ_CHECK(back != NULL); - size_t got = recv_exact(f.base, c, back, M, 8000); - MQ_CHECK_EQ_INT((long long)got, (long long)M); - MQ_CHECK(memcmp(payload, back, M) == 0); - - free(payload); - free(back); - close(c); - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ── Case 3: HTTP CONNECT echo e2e (byte-exact round-trip) ──────────────── */ -static void -test_http_echo(void) -{ - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - int c = dial_nb(mq_listener_local_port(f.http)); - MQ_CHECK(c >= 0); - if (c < 0) { - echo_origin_down(&origin); - fixture_down(&f); - return; - } - - char line[128]; - int ln = snprintf(line, sizeof(line), - "CONNECT 127.0.0.1:%u HTTP/1.1\r\nHost: 127.0.0.1:%u\r\n\r\n", - (unsigned)origin_port, (unsigned)origin_port); - MQ_CHECK_EQ_INT(send_all_nb(f.base, c, (const uint8_t *)line, (size_t)ln, 2000), 0); - - /* Expect a "HTTP/1.1 200" status line. */ - uint8_t status[12] = {0}; - MQ_CHECK_EQ_INT((int)recv_exact(f.base, c, status, 12, 8000), 12); - MQ_CHECK_MEM(status, "HTTP/1.1 200", 12); - - /* Consume the rest of the response headers up to the blank line so the - * subsequent reads see only relayed application bytes. The HTTP CONNECT - * reply is a fixed short status line ending in \r\n\r\n; read until we have - * consumed it. */ - uint8_t hdr[256]; - size_t hlen = 0; - uint64_t deadline = now_ms() + 4000; - int done = 0; - while (!done && now_ms() < deadline) { - event_base_loop(f.base, EVLOOP_NONBLOCK); - while (hlen < sizeof(hdr)) { - ssize_t n = recv(c, hdr + hlen, 1, 0); - if (n == 1) { - hlen++; - if (hlen >= 4 && memcmp(hdr + hlen - 4, "\r\n\r\n", 4) == 0) { - done = 1; - break; - } - } else { - break; - } - } - } - MQ_CHECK(done); - - /* Echo a few KB byte-exact. */ - const size_t M = 4096; - uint8_t *payload = malloc(M); - MQ_CHECK(payload != NULL); - for (size_t i = 0; i < M; i++) - payload[i] = (uint8_t)((i * 17 + 3) & 0xff); - MQ_CHECK_EQ_INT(send_all_nb(f.base, c, payload, M, 4000), 0); - - uint8_t *recvd = malloc(M); - MQ_CHECK(recvd != NULL); - size_t got = recv_exact(f.base, c, recvd, M, 8000); - MQ_CHECK_EQ_INT((long long)got, (long long)M); - MQ_CHECK(memcmp(payload, recvd, M) == 0); - - free(payload); - free(recvd); - close(c); - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ── Case 5: SOCKS5 pipelined early payload (coalesced with the request) ───── - * The app sends its first upload bytes in the SAME write as the SOCKS5 CONNECT - * request, so the listener pulls request+payload into a single recv. Those - * trailing bytes must survive the listener→client handoff and reach the origin, - * which echoes them back. Regression test for the ingress dropping bytes that - * arrive in the same read as the request head. */ -static void -test_socks5_pipelined_payload(void) -{ - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - int c = dial_nb(mq_listener_local_port(f.socks5)); - MQ_CHECK(c >= 0); - if (c < 0) { - echo_origin_down(&origin); - fixture_down(&f); - return; - } - - MQ_CHECK_EQ_INT(socks5_greet(f.base, c), 0); - - /* CONNECT request (IPv4 origin) immediately followed by early payload, in - * ONE send so the listener reads both in a single recv. */ - const char early[] = "EARLY-PIPELINED-BYTES"; - const size_t elen = sizeof(early) - 1; - uint8_t buf[10 + 64]; - size_t i = 0; - buf[i++] = 0x05; /* VER */ - buf[i++] = 0x01; /* CMD CONNECT */ - buf[i++] = 0x00; /* RSV */ - buf[i++] = 0x01; /* ATYP IPv4 */ - uint32_t v4 = htonl(INADDR_LOOPBACK); - memcpy(buf + i, &v4, 4); - i += 4; - buf[i++] = (uint8_t)(origin_port >> 8); - buf[i++] = (uint8_t)(origin_port & 0xff); - memcpy(buf + i, early, elen); - i += elen; - MQ_CHECK_EQ_INT(send_all_nb(f.base, c, buf, i, 2000), 0); - - /* SOCKS5 CONNECT reply first. */ - uint8_t reply[10] = {0}; - MQ_CHECK_EQ_INT((int)recv_exact(f.base, c, reply, 10, 8000), 10); - MQ_CHECK_EQ_INT(reply[0], 0x05); - MQ_CHECK_EQ_INT(reply[1], 0x00); - - /* The early bytes must echo back byte-exact (proves they were not dropped at - * the ingress handoff). */ - uint8_t back[64] = {0}; - size_t got = recv_exact(f.base, c, back, elen, 8000); - MQ_CHECK_EQ_INT((long long)got, (long long)elen); - MQ_CHECK(memcmp(early, back, elen) == 0); - - close(c); - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ── Case 6: HTTP CONNECT pipelined early payload (coalesced with the head) ─── - * Same as Case 5 but through the HTTP CONNECT listener: the CONNECT head and the - * first tunnel bytes arrive in one read; the trailing bytes must reach the - * origin and echo back. */ -static void -test_http_pipelined_payload(void) -{ - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - int c = dial_nb(mq_listener_local_port(f.http)); - MQ_CHECK(c >= 0); - if (c < 0) { - echo_origin_down(&origin); - fixture_down(&f); - return; - } - - /* CONNECT head + early payload in ONE send. */ - const char early[] = "EARLY-HTTP-BYTES"; - const size_t elen = sizeof(early) - 1; - char buf[256]; - int hn = snprintf(buf, sizeof(buf), - "CONNECT 127.0.0.1:%u HTTP/1.1\r\nHost: 127.0.0.1:%u\r\n\r\n", - (unsigned)origin_port, (unsigned)origin_port); - MQ_CHECK(hn > 0 && (size_t)hn + elen <= sizeof(buf)); - memcpy(buf + hn, early, elen); - MQ_CHECK_EQ_INT(send_all_nb(f.base, c, (const uint8_t *)buf, (size_t)hn + elen, 2000), - 0); - - /* Consume the 200 status line + headers up to the blank line. */ - uint8_t status[12] = {0}; - MQ_CHECK_EQ_INT((int)recv_exact(f.base, c, status, 12, 8000), 12); - MQ_CHECK_MEM(status, "HTTP/1.1 200", 12); - uint8_t hdr[256]; - size_t hlen = 0; - uint64_t deadline = now_ms() + 4000; - int done = 0; - while (!done && now_ms() < deadline) { - event_base_loop(f.base, EVLOOP_NONBLOCK); - while (hlen < sizeof(hdr)) { - ssize_t n = recv(c, hdr + hlen, 1, 0); - if (n == 1) { - hlen++; - if (hlen >= 4 && memcmp(hdr + hlen - 4, "\r\n\r\n", 4) == 0) { - done = 1; - break; - } - } else { - break; - } - } - } - MQ_CHECK(done); - - /* The early tunnel bytes must echo back byte-exact. */ - uint8_t back[64] = {0}; - size_t got = recv_exact(f.base, c, back, elen, 8000); - MQ_CHECK_EQ_INT((long long)got, (long long)elen); - MQ_CHECK(memcmp(early, back, elen) == 0); - - close(c); - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ── shared open helpers (used by the concurrent-ingress case) ──────────────── */ - -/* Full SOCKS5 open: greet + CONNECT v4 + success reply. Returns 0 on success. */ -static int -socks5_open(struct event_base *base, int fd, uint16_t port) -{ - if (socks5_greet(base, fd) != 0) return -1; - if (socks5_connect_v4(base, fd, port) != 0) return -1; - uint8_t reply[10] = {0}; - if (recv_exact(base, fd, reply, 10, 8000) != 10) return -1; - if (reply[0] != 0x05 || reply[1] != 0x00) return -1; - return 0; -} - -/* Full HTTP CONNECT open: send the CONNECT head, read the 200 status line, then - * drain headers up to the blank line. Returns 0 on success. */ -static int -http_connect_open(struct event_base *base, int fd, uint16_t port) -{ - char line[128]; - int ln = snprintf(line, sizeof(line), - "CONNECT 127.0.0.1:%u HTTP/1.1\r\nHost: 127.0.0.1:%u\r\n\r\n", - (unsigned)port, (unsigned)port); - if (send_all_nb(base, fd, (const uint8_t *)line, (size_t)ln, 2000) != 0) return -1; - uint8_t status[12] = {0}; - if (recv_exact(base, fd, status, 12, 8000) != 12) return -1; - if (memcmp(status, "HTTP/1.1 200", 12) != 0) return -1; - uint8_t hdr[256]; - size_t hlen = 0; - uint64_t deadline = now_ms() + 4000; - int done = 0; - while (!done && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - while (hlen < sizeof(hdr)) { - ssize_t n = recv(fd, hdr + hlen, 1, 0); - if (n == 1) { - hlen++; - if (hlen >= 4 && memcmp(hdr + hlen - 4, "\r\n\r\n", 4) == 0) { - done = 1; - break; - } - } else { - break; - } - } - } - return done ? 0 : -1; -} - -/* ── Case 7: concurrent SOCKS5 + HTTP CONNECT over the one client conn ──────── */ -static void -test_concurrent_ingress(void) -{ - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - /* Two independent echo origins — one per ingress — so a mis-attributed flow - * would echo the other ingress's payload back. */ - echo_origin_t oa, ob; - uint16_t pa = 0, pb = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&oa, f.base, &pa), 0); - MQ_CHECK_EQ_INT(echo_origin_up(&ob, f.base, &pb), 0); - - /* One flow per ingress, both live on the SAME client / MPQUIC connection. */ - int cs = dial_nb(mq_listener_local_port(f.socks5)); - int ch = dial_nb(mq_listener_local_port(f.http)); - MQ_CHECK(cs >= 0 && ch >= 0); - if (cs < 0 || ch < 0) { - if (cs >= 0) close(cs); - if (ch >= 0) close(ch); - echo_origin_down(&oa); - echo_origin_down(&ob); - fixture_down(&f); - return; - } - - MQ_CHECK_EQ_INT(socks5_open(f.base, cs, pa), 0); - MQ_CHECK_EQ_INT(http_connect_open(f.base, ch, pb), 0); - - /* Distinct payloads: if the two concurrent streams were swapped on the - * shared connection the byte-exact compares below would fail. */ - const size_t M = 4096; - uint8_t *sa = malloc(M), *sh = malloc(M); - MQ_CHECK(sa != NULL && sh != NULL); - for (size_t i = 0; i < M; i++) { - sa[i] = (uint8_t)((i * 31 + 7) & 0xff); /* SOCKS5 pattern */ - sh[i] = (uint8_t)(((i * 17 + 3) & 0xff) ^ 0xa5); /* distinct HTTP pattern */ - } - - /* Both streams in flight at once on the one connection. */ - MQ_CHECK_EQ_INT(send_all_nb(f.base, cs, sa, M, 4000), 0); - MQ_CHECK_EQ_INT(send_all_nb(f.base, ch, sh, M, 4000), 0); - - uint8_t *ba = malloc(M), *bh = malloc(M); - MQ_CHECK(ba != NULL && bh != NULL); - MQ_CHECK_EQ_INT((long long)recv_exact(f.base, cs, ba, M, 8000), (long long)M); - MQ_CHECK_EQ_INT((long long)recv_exact(f.base, ch, bh, M, 8000), (long long)M); - - /* Each ingress gets exactly its own bytes back — no cross-talk. */ - MQ_CHECK(memcmp(sa, ba, M) == 0); - MQ_CHECK(memcmp(sh, bh, M) == 0); - - free(sa); - free(sh); - free(ba); - free(bh); - close(cs); - close(ch); - echo_origin_down(&oa); - echo_origin_down(&ob); - fixture_down(&f); -} - -/* ── Case 4a: SOCKS5 error mapping → REP 0x05 (connection refused) ───────── */ -static void -test_socks5_refused(void) -{ - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - uint16_t dp = dead_port(); - - int c = dial_nb(mq_listener_local_port(f.socks5)); - MQ_CHECK(c >= 0); - if (c < 0) { - fixture_down(&f); - return; - } - - MQ_CHECK_EQ_INT(socks5_greet(f.base, c), 0); - MQ_CHECK_EQ_INT(socks5_connect_v4(f.base, c, dp), 0); - - uint8_t reply[10] = {0}; - MQ_CHECK_EQ_INT((int)recv_exact(f.base, c, reply, 10, 8000), 10); - MQ_CHECK_EQ_INT(reply[0], 0x05); - MQ_CHECK_EQ_INT(reply[1], 0x05); /* connection refused */ - - close(c); - fixture_down(&f); -} - -/* ── Case 4b: HTTP CONNECT error mapping → HTTP/1.1 502 ──────────────────── */ -static void -test_http_refused(void) -{ - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - uint16_t dp = dead_port(); - - int c = dial_nb(mq_listener_local_port(f.http)); - MQ_CHECK(c >= 0); - if (c < 0) { - fixture_down(&f); - return; - } - - char line[128]; - int ln = snprintf(line, sizeof(line), - "CONNECT 127.0.0.1:%u HTTP/1.1\r\nHost: 127.0.0.1:%u\r\n\r\n", - (unsigned)dp, (unsigned)dp); - MQ_CHECK_EQ_INT(send_all_nb(f.base, c, (const uint8_t *)line, (size_t)ln, 2000), 0); - - uint8_t status[12] = {0}; - MQ_CHECK_EQ_INT((int)recv_exact(f.base, c, status, 12, 8000), 12); - MQ_CHECK_MEM(status, "HTTP/1.1 502", 12); - - close(c); - fixture_down(&f); -} - -static void -test_e2e_single_path(void) -{ - test_socks5_download(); - test_socks5_echo(); - test_http_echo(); - test_socks5_pipelined_payload(); - test_http_pipelined_payload(); - test_concurrent_ingress(); - test_socks5_refused(); - test_http_refused(); -} - -MQ_TEST_MAIN(test_e2e_single_path()) diff --git a/tests/integration/test_engine_boot.c b/tests/integration/test_engine_boot.c deleted file mode 100644 index db4a8823..00000000 --- a/tests/integration/test_engine_boot.c +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_engine_boot.c — transport+runtime boot smoke test (Chunk 7). - * - * Proves that a client-mode mq_transport plus its libevent mq_runtime construct - * cleanly, expose the underlying xquic engine + the runtime's event_base, open a - * primary UDP path, run the loop, are stopped cleanly from a libevent timer, and - * tear down without leaks (ASan-clean). - * - * The pre-cutover engine's register_path_fd / unregister_path_fd scaffold has no - * runtime successor (the path->fd map is internal to the runtime), so that - * assertion is replaced by a mq_runtime_open_udp_path success check. - */ -#include "mqtest.h" - -#include - -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_transport.h" - -/* Fired ~50ms into the loop; breaks out of event_base_dispatch. */ -static void -stop_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - mq_runtime_t *rt = (mq_runtime_t *)arg; - mq_runtime_stop(rt); -} - -static void -test_engine_boot(void) -{ - /* Construction order: transport first, then the runtime (which installs its - * callbacks onto the transport as cbs.user). The runtime owns its own base - * here (base == NULL). */ - mq_transport_t *t = mq_transport_new(/*is_server=*/0); - MQ_CHECK(t != NULL); - if (!t) return; - - MQ_CHECK(mq_transport_xqc(t) != NULL); - - mq_runtime_t *rt = mq_runtime_new(t, /*base=*/NULL); - MQ_CHECK(rt != NULL); - if (!rt) { - mq_transport_free(t); - return; - } - - struct event_base *base = mq_runtime_base(rt); - MQ_CHECK(base != NULL); - - /* Open the primary path (replaces the old register_path_fd scaffold, which - * has no runtime successor — the path->fd map is internal to the runtime). */ - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(rt, "127.0.0.1", /*port=*/0), 0); - - /* Schedule a one-shot timer to stop the loop, then run it. */ - struct timeval tv = {0, 50000}; /* 50ms */ - struct event *ev = evtimer_new(base, stop_cb, rt); - MQ_CHECK(ev != NULL); - evtimer_add(ev, &tv); - - mq_runtime_run(rt); /* returns after stop_cb -> loopbreak */ - - event_free(ev); - /* Teardown per side: transport first (engine destroy; callbacks land on the - * still-live runtime), then the runtime (closes the path socket + timer; it - * owns and frees its base here). */ - mq_transport_free(t); - mq_runtime_free(rt); -} - -MQ_TEST_MAIN(test_engine_boot()) diff --git a/tests/integration/test_gw_client.c b/tests/integration/test_gw_client.c deleted file mode 100644 index cf9bf612..00000000 --- a/tests/integration/test_gw_client.c +++ /dev/null @@ -1,1664 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_gw_client.c — integration tests for the local fetch-API listener - * (mq_fetch_listener, Task 2.2). Drives the loopback HTTP/1.1 accept loop with - * FAKE gateway callbacks over real loopback sockets + a manually pumped - * event_base. No QUIC / transports are involved. - * - * This file is shared with the gateway client (Task 3.2), which will append - * its own scenarios. The cases here exercise the listener in isolation: - * - * 1. echo roundtrip: POST /_mqproxy/fetch with a CL body; fake on_request - * accepts, on_body accumulates, on_body_done writes a 200 + body back via - * the handle ops, then finish. Assert body byte-exact, on_body_done fired, - * reply parsed 200. - * 2. 404: GET /other -> listener's own 404, no cbs fired. - * 3. 411: chunked Transfer-Encoding -> 411, no cbs fired. - * 4. 400: malformed head -> 400. - * 5. mid-body abort: CL=1000 but only 10 bytes then close -> on_aborted (not - * on_body_done). - * 6. backpressure smoke: on_body returns -1 once -> listener pauses; resume - * via the handle; full body still delivered (pause asserted via a flag). - * 7. oversize body: CL bytes + extra trailing garbage -> conn aborted; cbs - * saw exactly CL body bytes; no crash. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "gateway/mq_fetch_listener.h" -#include "gateway/mq_gw_client.h" -#include "gateway/mq_gw_fetch_adapter.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_h3.h" -#include "transport/mq_transport.h" - -#include -#include - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -/* ── fake gateway state ─────────────────────────────────────────────────── */ -struct fake_gw { - int on_request_called; - int on_body_done_called; - int on_aborted_called; - void *handle; /* captured per-conn handle */ - - uint8_t body[4096]; - size_t body_len; - - /* backpressure control */ - int pause_once; /* return -1 from the next on_body, then clear */ - int paused_flag; /* set to 1 when we asked for a pause */ - - /* what on_body_done should do */ - int write_echo_reply; /* on done, write 200 + accumulated body, finish */ - int write_small_200_on_done; /* on done, write a small empty-body 200, finish */ - int write_big_on_done; /* on done, stream a large body to exercise hw */ - int reject; /* on_request returns -1 (handler-owned reply) */ - - /* high-watermark / drain bookkeeping (write_big_on_done path) */ - int saw_zero_return; /* mq_fetch_conn_write returned 0 at least once */ - int drain_fired; /* drain_cb invoked */ - size_t big_total; /* bytes to stream */ - size_t big_sent; /* bytes accepted so far */ -}; - -static int -fake_on_request(const mq_http1_req_t *req, void *handle, void *user, void **req_ctx) -{ - (void)req; - struct fake_gw *g = (struct fake_gw *)user; - g->on_request_called++; - g->handle = handle; - *req_ctx = g; - - if (g->reject) { - const char *r = "HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n" - "Content-Length: 0\r\n\r\n"; - mq_fetch_conn_write(handle, r, strlen(r)); - return -1; - } - return 0; -} - -static int -fake_on_body(void *req_ctx, const uint8_t *p, size_t len) -{ - struct fake_gw *g = (struct fake_gw *)req_ctx; - if (g->body_len + len <= sizeof(g->body)) { - memcpy(g->body + g->body_len, p, len); - } - g->body_len += len; - if (g->pause_once) { - g->pause_once = 0; - g->paused_flag = 1; - return -1; /* request a pause (chunk still consumed) */ - } - return 0; -} - -/* Write a small fixed 200 OK (empty body) via the handle, then finish. Used by - * the oversize-after-done case to prove a gateway response written after the - * body is complete still flushes to the client even though trailing garbage - * arrived. */ -static void -write_small_200_and_finish(struct fake_gw *g) -{ - char head[128]; - int o = 0, n; - n = mq_http1_write_status(head + o, sizeof(head) - o, 200, "OK"); - o += n; - n = mq_http1_write_header(head + o, sizeof(head) - o, "Connection", "close"); - o += n; - n = mq_http1_write_header(head + o, sizeof(head) - o, "Content-Length", "0"); - o += n; - head[o++] = '\r'; - head[o++] = '\n'; - mq_fetch_conn_write(g->handle, head, (size_t)o); - mq_fetch_conn_finish(g->handle); -} - -/* Feed as much of a large response body as the output queue accepts, stopping - * when mq_fetch_conn_write returns 0 (high watermark). When all bytes are - * accepted, finish. Drives the high-watermark/drain test. */ -static void -big_pump(struct fake_gw *g) -{ - static const uint8_t chunk[8192] = {'A'}; - while (g->big_sent < g->big_total) { - size_t want = g->big_total - g->big_sent; - if (want > sizeof(chunk)) want = sizeof(chunk); - int rc = mq_fetch_conn_write(g->handle, chunk, want); - if (rc < 0) return; /* handle dead */ - g->big_sent += want; - if (rc == 0) { - g->saw_zero_return = 1; - return; /* high watermark: wait for drain_cb */ - } - } - mq_fetch_conn_finish(g->handle); -} - -static void -big_drain_cb(void *user) -{ - struct fake_gw *g = (struct fake_gw *)user; - g->drain_fired = 1; - big_pump(g); -} - -static void -fake_on_body_done(void *req_ctx) -{ - struct fake_gw *g = (struct fake_gw *)req_ctx; - g->on_body_done_called++; - if (g->write_small_200_on_done) { - write_small_200_and_finish(g); - return; - } - if (g->write_big_on_done) { - /* Write a head with a large Content-Length, then stream the body, - * exercising the high watermark and drain callback. */ - char head[128]; - int o = 0, n; - n = mq_http1_write_status(head + o, sizeof(head) - o, 200, "OK"); - o += n; - n = mq_http1_write_header(head + o, sizeof(head) - o, "Connection", "close"); - o += n; - char cl[32]; - snprintf(cl, sizeof(cl), "%zu", g->big_total); - n = mq_http1_write_header(head + o, sizeof(head) - o, "Content-Length", cl); - o += n; - head[o++] = '\r'; - head[o++] = '\n'; - mq_fetch_conn_write(g->handle, head, (size_t)o); - mq_fetch_conn_set_drain_cb(g->handle, big_drain_cb, g); - big_pump(g); - return; - } - if (g->write_echo_reply) { - char head[128]; - int o = 0, n; - n = mq_http1_write_status(head + o, sizeof(head) - o, 200, "OK"); - o += n; - n = mq_http1_write_header(head + o, sizeof(head) - o, "Connection", "close"); - o += n; - char cl[32]; - snprintf(cl, sizeof(cl), "%zu", g->body_len); - n = mq_http1_write_header(head + o, sizeof(head) - o, "Content-Length", cl); - o += n; - head[o++] = '\r'; - head[o++] = '\n'; - mq_fetch_conn_write(g->handle, head, (size_t)o); - if (g->body_len > 0) mq_fetch_conn_write(g->handle, g->body, g->body_len); - mq_fetch_conn_finish(g->handle); - } -} - -static void -fake_on_aborted(void *req_ctx) -{ - struct fake_gw *g = (struct fake_gw *)req_ctx; - g->on_aborted_called++; -} - -static mq_fetch_cbs_t -fake_cbs(void) -{ - mq_fetch_cbs_t cbs; - memset(&cbs, 0, sizeof(cbs)); - cbs.on_request = fake_on_request; - cbs.on_body = fake_on_body; - cbs.on_body_done = fake_on_body_done; - cbs.on_aborted = fake_on_aborted; - return cbs; -} - -/* Substring search over a byte range (avoids the GNU-only memmem). */ -static int -contains(const uint8_t *hay, size_t hlen, const char *needle) -{ - size_t nl = strlen(needle); - if (nl == 0 || hlen < nl) return 0; - for (size_t i = 0; i + nl <= hlen; i++) { - if (memcmp(hay + i, needle, nl) == 0) return 1; - } - return 0; -} - -/* ── loopback test plumbing (mirrors test_socks5_listener.c) ────────────── */ -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -static int -dial(uint16_t port) -{ - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) return -1; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr); - if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return -1; - } - return fd; -} - -static void -send_all(int fd, const void *buf, size_t len) -{ - const uint8_t *p = buf; - size_t off = 0; - while (off < len) { - ssize_t n = send(fd, p + off, len - off, 0); - if (n <= 0) break; - off += (size_t)n; - } -} - -/* Pump the base while reading up to `want` bytes from fd, up to budget_ms. */ -static size_t -pump_read(struct event_base *base, int fd, uint8_t *out, size_t want, uint64_t budget_ms) -{ - int fl = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, fl | O_NONBLOCK); - size_t got = 0; - uint64_t deadline = now_ms() + budget_ms; - while (got < want && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - ssize_t n = recv(fd, out + got, want - got, 0); - if (n > 0) { - got += (size_t)n; - } else if (n == 0) { - break; - } - } - fcntl(fd, F_SETFL, fl); - return got; -} - -/* Pump until fd is at EOF (peer closed), or budget elapses. Returns 1 if EOF. */ -static int -pump_until_eof(struct event_base *base, int fd, uint64_t budget_ms) -{ - int fl = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, fl | O_NONBLOCK); - int eof = 0; - uint64_t deadline = now_ms() + budget_ms; - while (!eof && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - uint8_t b[64]; - ssize_t n = recv(fd, b, sizeof(b), 0); - if (n == 0) { - eof = 1; - } else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { - eof = 1; - } - } - fcntl(fd, F_SETFL, fl); - return eof; -} - -/* Pump the base a few times to let pending I/O / cbs settle. */ -static void -pump_a_bit(struct event_base *base, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -/* Read the full reply (head + body) until EOF, into out. Returns total bytes. */ -static size_t -pump_read_all(struct event_base *base, int fd, uint8_t *out, size_t cap, - uint64_t budget_ms) -{ - int fl = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, fl | O_NONBLOCK); - size_t got = 0; - int eof = 0; - uint64_t deadline = now_ms() + budget_ms; - while (!eof && got < cap && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - ssize_t n = recv(fd, out + got, cap - got, 0); - if (n > 0) { - got += (size_t)n; - } else if (n == 0) { - eof = 1; - } - } - fcntl(fd, F_SETFL, fl); - return got; -} - -/* ── Scenario 1: echo roundtrip ─────────────────────────────────────────── */ -static void -test_echo_roundtrip(struct event_base *base) -{ - struct fake_gw g; - memset(&g, 0, sizeof(g)); - g.write_echo_reply = 1; - mq_fetch_cbs_t cbs = fake_cbs(); - - mq_fetch_listener_t *l = mq_fetch_listener_new(base, "127.0.0.1", 0, &cbs, &g); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t port = mq_fetch_listener_port(l); - MQ_CHECK(port != 0); - - int c = dial(port); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_fetch_listener_free(l); - return; - } - - const char *body = "hello world"; - char reqbuf[256]; - int rn = snprintf(reqbuf, sizeof(reqbuf), - "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "Content-Length: %zu\r\n\r\n%s", - strlen(body), body); - send_all(c, reqbuf, (size_t)rn); - - uint8_t reply[512] = {0}; - size_t got = pump_read_all(base, c, reply, sizeof(reply), 2000); - MQ_CHECK(got > 0); - - /* Reply parses as 200 with the echoed body. */ - MQ_CHECK_MEM(reply, "HTTP/1.1 200", 12); - MQ_CHECK_EQ_INT(g.on_request_called, 1); - MQ_CHECK_EQ_INT(g.on_body_done_called, 1); - MQ_CHECK_EQ_INT(g.on_aborted_called, 0); - MQ_CHECK_EQ_INT(g.body_len, strlen(body)); - MQ_CHECK_MEM(g.body, body, strlen(body)); - - /* The reply tail must contain the echoed body. */ - MQ_CHECK(contains(reply, got, body)); - - close(c); - pump_a_bit(base, 100); - mq_fetch_listener_free(l); -} - -/* ── Scenario 2: 404 (wrong method/path), no cbs ────────────────────────── */ -static void -test_404(struct event_base *base) -{ - struct fake_gw g; - memset(&g, 0, sizeof(g)); - mq_fetch_cbs_t cbs = fake_cbs(); - mq_fetch_listener_t *l = mq_fetch_listener_new(base, "127.0.0.1", 0, &cbs, &g); - MQ_CHECK(l != NULL); - uint16_t port = mq_fetch_listener_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - - const char *req = "GET /other HTTP/1.1\r\nHost: x\r\n\r\n"; - send_all(c, req, strlen(req)); - - uint8_t reply[128] = {0}; - size_t got = pump_read(base, c, reply, 12, 1000); - MQ_CHECK(got >= 12); - MQ_CHECK_MEM(reply, "HTTP/1.1 404", 12); - MQ_CHECK_EQ_INT(g.on_request_called, 0); - MQ_CHECK(pump_until_eof(base, c, 1000)); - - close(c); - pump_a_bit(base, 50); - mq_fetch_listener_free(l); -} - -/* ── Scenario 3: 411 (chunked TE), no cbs ───────────────────────────────── */ -static void -test_411(struct event_base *base) -{ - struct fake_gw g; - memset(&g, 0, sizeof(g)); - mq_fetch_cbs_t cbs = fake_cbs(); - mq_fetch_listener_t *l = mq_fetch_listener_new(base, "127.0.0.1", 0, &cbs, &g); - MQ_CHECK(l != NULL); - uint16_t port = mq_fetch_listener_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - - const char *req = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "Transfer-Encoding: chunked\r\n\r\n"; - send_all(c, req, strlen(req)); - - uint8_t reply[128] = {0}; - size_t got = pump_read(base, c, reply, 12, 1000); - MQ_CHECK(got >= 12); - MQ_CHECK_MEM(reply, "HTTP/1.1 411", 12); - MQ_CHECK_EQ_INT(g.on_request_called, 0); - MQ_CHECK(pump_until_eof(base, c, 1000)); - - close(c); - pump_a_bit(base, 50); - mq_fetch_listener_free(l); -} - -/* ── Scenario 4: 400 (malformed head) ───────────────────────────────────── */ -static void -test_400(struct event_base *base) -{ - struct fake_gw g; - memset(&g, 0, sizeof(g)); - mq_fetch_cbs_t cbs = fake_cbs(); - mq_fetch_listener_t *l = mq_fetch_listener_new(base, "127.0.0.1", 0, &cbs, &g); - MQ_CHECK(l != NULL); - uint16_t port = mq_fetch_listener_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - - /* obs-fold continuation line is rejected as BAD by the parser. */ - const char *req = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n \tfolded\r\n\r\n"; - send_all(c, req, strlen(req)); - - uint8_t reply[128] = {0}; - size_t got = pump_read(base, c, reply, 12, 1000); - MQ_CHECK(got >= 12); - MQ_CHECK_MEM(reply, "HTTP/1.1 400", 12); - MQ_CHECK_EQ_INT(g.on_request_called, 0); - MQ_CHECK(pump_until_eof(base, c, 1000)); - - close(c); - pump_a_bit(base, 50); - mq_fetch_listener_free(l); -} - -/* ── Scenario 5: mid-body abort ─────────────────────────────────────────── */ -static void -test_mid_body_abort(struct event_base *base) -{ - struct fake_gw g; - memset(&g, 0, sizeof(g)); - mq_fetch_cbs_t cbs = fake_cbs(); - mq_fetch_listener_t *l = mq_fetch_listener_new(base, "127.0.0.1", 0, &cbs, &g); - MQ_CHECK(l != NULL); - uint16_t port = mq_fetch_listener_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - - const char *head = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "Content-Length: 1000\r\n\r\n"; - send_all(c, head, strlen(head)); - /* Only 10 of the promised 1000 body bytes, then close. */ - send_all(c, "0123456789", 10); - pump_a_bit(base, 100); - close(c); - - /* Pump until the listener sees EOF and fires on_aborted. */ - uint64_t deadline = now_ms() + 1000; - while (g.on_aborted_called == 0 && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - MQ_CHECK_EQ_INT(g.on_request_called, 1); - MQ_CHECK_EQ_INT(g.on_aborted_called, 1); - MQ_CHECK_EQ_INT(g.on_body_done_called, 0); - MQ_CHECK_EQ_INT(g.body_len, 10); - - pump_a_bit(base, 50); - mq_fetch_listener_free(l); -} - -/* ── Scenario 6: backpressure smoke ─────────────────────────────────────── */ -static void -test_backpressure(struct event_base *base) -{ - struct fake_gw g; - memset(&g, 0, sizeof(g)); - g.pause_once = 1; /* first on_body returns -1 -> pause */ - g.write_echo_reply = 1; /* on done, echo back + finish */ - mq_fetch_cbs_t cbs = fake_cbs(); - mq_fetch_listener_t *l = mq_fetch_listener_new(base, "127.0.0.1", 0, &cbs, &g); - MQ_CHECK(l != NULL); - uint16_t port = mq_fetch_listener_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - - /* Send head, then body in two pieces so the first on_body triggers a pause - * before all body is delivered. */ - const char *head = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "Content-Length: 20\r\n\r\n"; - send_all(c, head, strlen(head)); - send_all(c, "0123456789", 10); - pump_a_bit(base, 100); - - /* The pause must have happened, and only the first chunk delivered so far. */ - MQ_CHECK_EQ_INT(g.paused_flag, 1); - MQ_CHECK_EQ_INT(g.on_body_done_called, 0); - - /* Send the rest; it stays buffered in the socket while paused. */ - send_all(c, "abcdefghij", 10); - pump_a_bit(base, 100); - /* Still paused: the remaining bytes must NOT have been delivered yet. */ - MQ_CHECK_EQ_INT(g.on_body_done_called, 0); - - /* Resume: full body must now flow and on_body_done fire. */ - mq_fetch_conn_resume_read(g.handle); - - uint8_t reply[512] = {0}; - size_t got = pump_read_all(base, c, reply, sizeof(reply), 2000); - MQ_CHECK(got > 0); - MQ_CHECK_MEM(reply, "HTTP/1.1 200", 12); - MQ_CHECK_EQ_INT(g.on_body_done_called, 1); - MQ_CHECK_EQ_INT(g.body_len, 20); - MQ_CHECK_MEM(g.body, "0123456789abcdefghij", 20); - - close(c); - pump_a_bit(base, 50); - mq_fetch_listener_free(l); -} - -/* ── Scenario 7: oversize body (trailing garbage after a complete body) ────── - * - * Trailing bytes past Content-Length are a peer protocol violation, but the - * request body is FULLY received: on_body_done fires, the gateway writes its - * response, and that response must still flush to the client. The listener must - * NOT fire on_aborted (that contract is "peer died BEFORE CL") and must NOT yank - * the conn from the read path — teardown is the gateway's finish()/abort(). */ -static void -test_oversize_body(struct event_base *base) -{ - struct fake_gw g; - memset(&g, 0, sizeof(g)); - g.write_small_200_on_done = 1; /* gateway responds after the body completes */ - mq_fetch_cbs_t cbs = fake_cbs(); - mq_fetch_listener_t *l = mq_fetch_listener_new(base, "127.0.0.1", 0, &cbs, &g); - MQ_CHECK(l != NULL); - uint16_t port = mq_fetch_listener_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - - /* CL=5 but send 5 + 7 trailing garbage bytes in one shot. */ - const char *head = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "Content-Length: 5\r\n\r\n"; - send_all(c, head, strlen(head)); - send_all(c, "HELLOgarbage", 12); - - /* The gateway's 200 (written from on_body_done) must reach the client. */ - uint8_t reply[256] = {0}; - size_t got = pump_read_all(base, c, reply, sizeof(reply), 2000); - - /* on_body saw exactly CL=5 bytes; on_body_done fired exactly once. The - * trailing garbage must NOT fire on_aborted and must NOT discard the - * gateway's response. */ - MQ_CHECK_EQ_INT(g.on_request_called, 1); - MQ_CHECK_EQ_INT(g.body_len, 5); - MQ_CHECK_MEM(g.body, "HELLO", 5); - MQ_CHECK_EQ_INT(g.on_body_done_called, 1); /* exactly CL delivered */ - MQ_CHECK_EQ_INT(g.on_aborted_called, 0); /* NOT aborted: body was complete */ - MQ_CHECK(got > 0); - MQ_CHECK_MEM(reply, "HTTP/1.1 200", 12); /* gateway response still flushed */ - - close(c); - pump_a_bit(base, 50); - mq_fetch_listener_free(l); -} - -/* ── Scenario 8: free listener with a live mid-body conn ───────────────────── - * - * mq_fetch_listener_free must tear down a connection that is still mid-body - * (head parsed, on_request fired, body incomplete) without crashing or leaking - * (verified under ASan). No on_aborted is required here — free is a hard - * shutdown, not a peer EOF. */ -static void -test_free_live_conn(struct event_base *base) -{ - struct fake_gw g; - memset(&g, 0, sizeof(g)); - mq_fetch_cbs_t cbs = fake_cbs(); - mq_fetch_listener_t *l = mq_fetch_listener_new(base, "127.0.0.1", 0, &cbs, &g); - MQ_CHECK(l != NULL); - uint16_t port = mq_fetch_listener_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - - const char *head = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "Content-Length: 1000\r\n\r\n"; - send_all(c, head, strlen(head)); - send_all(c, "0123456789", 10); /* partial body: conn stays mid-body */ - pump_a_bit(base, 100); - - /* The conn is alive and mid-body. */ - MQ_CHECK_EQ_INT(g.on_request_called, 1); - MQ_CHECK_EQ_INT(g.on_body_done_called, 0); - - /* Free with the conn still live: must not crash / leak. */ - mq_fetch_listener_free(l); - - close(c); - pump_a_bit(base, 50); -} - -/* ── Scenario 9: high watermark + drain ────────────────────────────────────── - * - * The gateway writes more than the high watermark, so mq_fetch_conn_write - * returns 0 at least once; the drain callback then fires and the write - * completes. */ -static void -test_highwater_drain(struct event_base *base) -{ - struct fake_gw g; - memset(&g, 0, sizeof(g)); - g.write_big_on_done = 1; - /* Far larger than any plausible loopback socket buffer, so the output queue - * is forced past the 256 KiB high watermark while the client is not being - * read. The gateway stops feeding at the watermark (queue stays ~256 KiB, - * well under the 4 MiB ceiling regardless of this total). */ - g.big_total = 8 * 1024 * 1024; - mq_fetch_cbs_t cbs = fake_cbs(); - mq_fetch_listener_t *l = mq_fetch_listener_new(base, "127.0.0.1", 0, &cbs, &g); - MQ_CHECK(l != NULL); - uint16_t port = mq_fetch_listener_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - - const char *head = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "Content-Length: 0\r\n\r\n"; - send_all(c, head, strlen(head)); - - /* Drain the client slowly enough that the queue actually crosses the high - * watermark first (pump a few times before reading everything). */ - pump_a_bit(base, 50); - - uint8_t *reply = malloc(g.big_total + 1024); - MQ_CHECK(reply != NULL); - size_t got = pump_read_all(base, c, reply, g.big_total + 1024, 4000); - - MQ_CHECK_EQ_INT(g.on_body_done_called, 1); - MQ_CHECK_EQ_INT(g.saw_zero_return, 1); /* high watermark was hit */ - MQ_CHECK_EQ_INT(g.drain_fired, 1); /* drain cb fired */ - MQ_CHECK_EQ_INT(g.big_sent, g.big_total); - MQ_CHECK(got >= g.big_total); /* full body reached the client */ - MQ_CHECK_MEM(reply, "HTTP/1.1 200", 12); - - free(reply); - close(c); - pump_a_bit(base, 50); - mq_fetch_listener_free(l); -} - -/* ════════════════════════════════════════════════════════════════════════════ - * Gateway client (Task 3.2): fetch→H3 bridge cases. - * - * These stand up a REAL in-process H3 tunnel over loopback UDP: - * - a fake gateway SERVER: server transport (TLS) + runtime + mq_h3 with - * server hooks that recv the forwarded request and reply per scenario; - * - the CLIENT under test: client transport + runtime + mq_h3 (NULL hooks) + - * mq_gw_client + a real mq_fetch_listener wired to the gw_client's cbs. - * A local "curl" socket POSTs /_mqproxy/fetch; we assert the response. - * ════════════════════════════════════════════════════════════════════════════ */ - -#include -#include - -/* Reserve an ephemeral loopback UDP port (bind :0, read it, close). */ -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -/* ── fake gateway server (H3) ───────────────────────────────────────────────*/ - -#define SRV_MAX_HDRS 32 -#define SRV_HDR_LEN 512 - -typedef struct { - char name[SRV_HDR_LEN]; - char value[SRV_HDR_LEN]; -} srv_hdr_t; - -/* Per-request server state + scenario controls. */ -typedef struct { - mq_h3_req_t *req; - int got_headers; - int saw_fin; - int closed; - - srv_hdr_t hdrs[SRV_MAX_HDRS]; - int n_hdrs; - - uint8_t body[256 * 1024]; - size_t body_len; - - int responded; - - /* response send state (driven by srv_respond + srv_on_req_write). */ - const uint8_t *snd; /* response body to send (points at s->body or g_dl_body) */ - size_t snd_total; /* total response body length */ - size_t snd_off; /* bytes accepted so far */ - int snd_fin_done; /* the fin has been sent */ - - /* scenario controls (set on the shared server before the request). */ - int scenario; /* see SC_* below */ - int request_count; /* incremented on each on_new_req (for case 4 asserts) */ -} gw_srv_t; - -enum { - SC_200_CL = 0, /* 200 + content-length + body */ - SC_200_NOCL = 1, /* 200 WITHOUT content-length (forces chunked downstream) */ - SC_ECHO_UPLOAD = 2, /* 200 + echo the received upload body back (CL) */ - SC_RESET_MID = 3, /* headers + partial body then reset (mid-download trunc) */ - SC_HANG = 4, /* receive everything but NEVER respond (request stays live) */ - SC_BAD_STATUS = 5, /* reply with a hostile out-of-range :status ("99999999999") */ -}; - -static gw_srv_t g_srv; /* one in-flight request at a time in these tests */ - -static const uint8_t *g_dl_body; /* download body for SC_200_CL / SC_200_NOCL */ -static size_t g_dl_body_len; - -static void -srv_capture_hdr(const char *n, size_t nl, const char *v, size_t vl, void *u) -{ - gw_srv_t *s = (gw_srv_t *)u; - if (s->n_hdrs >= SRV_MAX_HDRS) return; - srv_hdr_t *h = &s->hdrs[s->n_hdrs++]; - size_t cn = nl < SRV_HDR_LEN - 1 ? nl : SRV_HDR_LEN - 1; - size_t cv = vl < SRV_HDR_LEN - 1 ? vl : SRV_HDR_LEN - 1; - memcpy(h->name, n, cn); - h->name[cn] = '\0'; - memcpy(h->value, v, cv); - h->value[cv] = '\0'; -} - -static const char * -srv_find_hdr(const gw_srv_t *s, const char *name) -{ - for (int i = 0; i < s->n_hdrs; i++) - if (strcmp(s->hdrs[i].name, name) == 0) return s->hdrs[i].value; - return NULL; -} - -/* Push as much of the pending response body as xquic accepts, riding fin on the - * last byte; any EAGAIN remainder is retried from srv_on_req_write. Safe to call - * repeatedly. */ -static void -srv_body_pump(gw_srv_t *s) -{ - if (!s->req || s->snd_fin_done) return; - while (s->snd_off < s->snd_total) { - int last = 1; /* whole remainder is the tail */ - long acc = mq_h3_req_send_body(s->req, s->snd + s->snd_off, - s->snd_total - s->snd_off, last); - if (acc <= 0) return; /* EAGAIN: resume on the next on_write */ - s->snd_off += (size_t)acc; - if (s->snd_off >= s->snd_total) { - s->snd_fin_done = 1; /* fin rode the last byte */ - return; - } - } - /* Zero-length body: send a bare fin. */ - if (s->snd_total == 0) { - mq_h3_req_finish(s->req); - s->snd_fin_done = 1; - } -} - -static void -srv_respond(gw_srv_t *s) -{ - if (s->responded || !s->req) return; - - if (s->scenario == SC_HANG) { - /* Never respond: leave the request open so the bridge has a live in-flight - * request at teardown. Do NOT mark responded (nothing was sent). */ - return; - } - - s->responded = 1; - - if (s->scenario == SC_BAD_STATUS) { - /* Hostile peer: a :status far outside the 3-digit/[100,599] range. The - * client-side :status parser must NOT accumulate this into a signed int - * (overflow UB) — it must reject the response as an upstream-protocol - * failure. fin on headers (no body). */ - mq_h3_header_t h[] = {{":status", "99999999999"}}; - mq_h3_req_send_headers(s->req, h, 1, /*fin=*/1); - s->snd_fin_done = 1; - return; - } - - if (s->scenario == SC_RESET_MID) { - /* Headers (no CL) + a partial body chunk, then reset → the downstream - * local socket must see a truncated (no clean terminator) close. */ - mq_h3_header_t h[] = {{":status", "200"}}; - mq_h3_req_send_headers(s->req, h, 1, 0); - static const uint8_t part[64] = {'P'}; - mq_h3_req_send_body(s->req, part, sizeof(part), 0); - mq_h3_req_reset(s->req); - s->req = NULL; - return; - } - - if (s->scenario == SC_ECHO_UPLOAD) { - char cl[32]; - snprintf(cl, sizeof(cl), "%zu", s->body_len); - mq_h3_header_t h[] = {{":status", "200"}, {"content-length", cl}}; - mq_h3_req_send_headers(s->req, h, 2, 0); - s->snd = s->body; - s->snd_total = s->body_len; - s->snd_off = 0; - srv_body_pump(s); - return; - } - - /* SC_200_CL / SC_200_NOCL: download a fixed body. */ - if (s->scenario == SC_200_NOCL) { - mq_h3_header_t h[] = {{":status", "200"}, {"x-mq-origin-protocol", "h2"}}; - mq_h3_req_send_headers(s->req, h, 2, 0); - } else { - char cl[32]; - snprintf(cl, sizeof(cl), "%zu", g_dl_body_len); - mq_h3_header_t h[] = {{":status", "200"}, {"content-length", cl}}; - mq_h3_req_send_headers(s->req, h, 2, 0); - } - s->snd = g_dl_body; - s->snd_total = g_dl_body_len; - s->snd_off = 0; - srv_body_pump(s); -} - -static void -srv_on_req_read(mq_h3_req_t *r, int flag, void *user) -{ - gw_srv_t *s = (gw_srv_t *)user; - if (flag & (XQC_REQ_NOTIFY_READ_HEADER | XQC_REQ_NOTIFY_READ_TRAILER)) { - int fin = 0; - int n = mq_h3_req_recv_headers(r, srv_capture_hdr, s, &fin); - if (n >= 0) s->got_headers = 1; - if (fin) s->saw_fin = 1; - } - for (;;) { - int fin = 0; - long n = mq_h3_req_recv_body(r, s->body + s->body_len, - sizeof(s->body) - s->body_len, &fin); - if (n > 0) s->body_len += (size_t)n; - if (fin) s->saw_fin = 1; - if (n <= 0) break; - } - /* Respond once the full request body (fin) has arrived. */ - if (s->saw_fin && !s->responded) srv_respond(s); -} - -static void -srv_on_req_write(mq_h3_req_t *r, void *user) -{ - (void)r; - gw_srv_t *s = (gw_srv_t *)user; - if (!s->responded) return; - srv_body_pump(s); /* resume any flow-control-blocked response body */ -} - -static void -srv_on_req_close(mq_h3_req_t *r, void *user) -{ - (void)r; - gw_srv_t *s = (gw_srv_t *)user; - s->closed = 1; - s->req = NULL; -} - -static void -srv_on_new_req(mq_h3_req_t *r, void *user) -{ - (void)user; - g_srv.request_count++; - g_srv.req = r; - mq_h3_req_set_cbs(r, srv_on_req_read, srv_on_req_write, srv_on_req_close, &g_srv); -} - -static void -srv_on_new_conn(mq_h3_conn_t *c, void *user) -{ - (void)c; - (void)user; -} - -/* ── gateway-client fixture ─────────────────────────────────────────────────*/ - -typedef struct { - struct event_base *base; - mq_transport_t *srv_t; - mq_transport_t *cli_t; - mq_runtime_t *srv_rt; - mq_runtime_t *cli_rt; - mq_h3_t *srv_h3; - mq_h3_t *cli_h3; - mq_gw_client_t *gw; - mq_gw_fetch_adapter_t *fetch_adp; - mq_fetch_listener_t *listener; - uint16_t lport; -} gw_fixture_t; - -static int -gw_fixture_up(gw_fixture_t *f, const char *token) -{ - memset(f, 0, sizeof(*f)); - memset(&g_srv, 0, sizeof(g_srv)); - - f->base = event_base_new(); - if (!f->base) return -1; - - uint16_t srv_port = reserve_udp_port(); - if (!srv_port) return -1; - - /* Server transport + runtime + h3 (server hooks). */ - f->srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - if (!f->srv_t) return -1; - f->srv_rt = mq_runtime_new(f->srv_t, f->base); - if (!f->srv_rt) return -1; - - /* Match the H3 fabric's mp settings so the negotiated conn settings line up. */ - xqc_conn_settings_t srv_settings; - memset(&srv_settings, 0, sizeof(srv_settings)); - srv_settings.proto_version = XQC_VERSION_V1; - srv_settings.pacing_on = 1; - mq_conn_apply_mp_settings(&srv_settings, /*is_server=*/1, MQ_CC_BBR2); - xqc_server_set_conn_settings(mq_transport_xqc(f->srv_t), &srv_settings); - - f->srv_h3 = mq_h3_init(f->srv_t, srv_on_new_conn, srv_on_new_req, NULL); - if (!f->srv_h3) return -1; - if (mq_runtime_open_udp_path(f->srv_rt, "127.0.0.1", srv_port) != 0) return -1; - - /* Client transport + runtime + h3 (pure client). */ - f->cli_t = mq_transport_new(0); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - if (!f->cli_rt) return -1; - if (mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0) != 0) return -1; - f->cli_h3 = mq_h3_init(f->cli_t, NULL, NULL, NULL); - if (!f->cli_h3) return -1; - - f->gw = mq_gw_client_new(f->cli_t, f->cli_rt, f->cli_h3, "127.0.0.1", srv_port, token, - MQ_CC_BBR2, /*keepalive_idle_ms=*/0, - /*reconnect_enabled=*/0, /*reconnect_max_backoff_ms=*/30000); - if (!f->gw) return -1; - - /* Real fetch listener wired to the gw client via the fetch adapter. */ - f->fetch_adp = mq_gw_fetch_adapter_new(f->gw); - if (!f->fetch_adp) return -1; - f->listener = mq_fetch_listener_new(f->base, "127.0.0.1", 0, - mq_gw_fetch_adapter_cbs(f->fetch_adp), - mq_gw_fetch_adapter_user(f->fetch_adp)); - if (!f->listener) return -1; - f->lport = mq_fetch_listener_port(f->listener); - if (!f->lport) return -1; - - /* Pump until the tunnel conn is established (gw connected eagerly). */ - uint64_t deadline = now_ms() + 5000; - while (now_ms() < deadline) { - event_base_loop(f->base, EVLOOP_NONBLOCK); - /* No direct accessor; a short settle is enough on loopback. The first - * fetch request itself drives further pumping. */ - if (now_ms() > deadline - 4700) break; /* ~300ms settle */ - } - return 0; -} - -/* tunnel-unavailable fixture: gw points at a CLOSED UDP port (no server). */ -static int -gw_fixture_up_dead(gw_fixture_t *f) -{ - memset(f, 0, sizeof(*f)); - memset(&g_srv, 0, sizeof(g_srv)); - f->base = event_base_new(); - if (!f->base) return -1; - uint16_t dead = reserve_udp_port(); /* reserved then released → nothing listens */ - if (!dead) return -1; - - f->cli_t = mq_transport_new(0); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - if (!f->cli_rt) return -1; - if (mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0) != 0) return -1; - f->cli_h3 = mq_h3_init(f->cli_t, NULL, NULL, NULL); - if (!f->cli_h3) return -1; - f->gw = mq_gw_client_new(f->cli_t, f->cli_rt, f->cli_h3, "127.0.0.1", dead, "tok", - MQ_CC_BBR2, /*keepalive_idle_ms=*/0, - /*reconnect_enabled=*/0, /*reconnect_max_backoff_ms=*/30000); - if (!f->gw) return -1; - f->fetch_adp = mq_gw_fetch_adapter_new(f->gw); - if (!f->fetch_adp) return -1; - f->listener = mq_fetch_listener_new(f->base, "127.0.0.1", 0, - mq_gw_fetch_adapter_cbs(f->fetch_adp), - mq_gw_fetch_adapter_user(f->fetch_adp)); - if (!f->listener) return -1; - f->lport = mq_fetch_listener_port(f->listener); - if (!f->lport) return -1; - return 0; -} - -static void -gw_fixture_down(gw_fixture_t *f) -{ - /* SANCTIONED TEARDOWN ORDER (mq_gw_client.h): gw_client_free FIRST, while the - * client H3 engine is STILL LIVE, so it can detach (set_cbs NULL) + reset any - * in-flight request against a live engine and abort its local handle — no - * UAF. THEN mq_h3_free (mq_h3.h contract: before transport_free), THEN - * transport_free. The listener is freed after gw_client (gw_client aborts the - * local handles; the listener then tears down its own conn state). The server - * side has no gw_client, so its h3/transport free in the usual order. */ - if (f->gw) mq_gw_client_free(f->gw); - if (f->cli_h3) mq_h3_free(f->cli_h3); - if (f->srv_h3) mq_h3_free(f->srv_h3); - if (f->cli_t) mq_transport_free(f->cli_t); - if (f->srv_t) mq_transport_free(f->srv_t); - if (f->cli_rt) mq_runtime_free(f->cli_rt); - if (f->srv_rt) mq_runtime_free(f->srv_rt); - if (f->listener) mq_fetch_listener_free(f->listener); - if (f->fetch_adp) mq_gw_fetch_adapter_free(f->fetch_adp); - if (f->base) event_base_free(f->base); -} - -/* Parse a chunked HTTP/1.1 body from the response buffer into out. Returns the - * de-chunked length, or (size_t)-1 on a framing error. `body_start` is the - * offset of the first chunk-size line (after the head's \r\n\r\n). */ -static size_t -dechunk(const uint8_t *p, size_t len, uint8_t *out, size_t out_cap) -{ - size_t i = 0, o = 0; - for (;;) { - /* parse hex chunk size up to \r\n */ - size_t sz = 0; - int any = 0; - while (i < len && p[i] != '\r') { - char ch = (char)p[i]; - int d; - if (ch >= '0' && ch <= '9') - d = ch - '0'; - else if (ch >= 'a' && ch <= 'f') - d = ch - 'a' + 10; - else if (ch >= 'A' && ch <= 'F') - d = ch - 'A' + 10; - else - return (size_t)-1; - sz = sz * 16 + (size_t)d; - any = 1; - i++; - } - if (!any || i + 1 >= len || p[i] != '\r' || p[i + 1] != '\n') return (size_t)-1; - i += 2; - if (sz == 0) return o; /* terminator */ - if (i + sz + 2 > len || o + sz > out_cap) return (size_t)-1; - memcpy(out + o, p + i, sz); - o += sz; - i += sz; - if (p[i] != '\r' || p[i + 1] != '\n') return (size_t)-1; - i += 2; - } -} - -/* Locate the response body start (offset after the head terminator). */ -static size_t -head_end(const uint8_t *p, size_t len) -{ - for (size_t i = 0; i + 3 < len; i++) - if (p[i] == '\r' && p[i + 1] == '\n' && p[i + 2] == '\r' && p[i + 3] == '\n') - return i + 4; - return (size_t)-1; -} - -/* Pump while sending the POST request and reading the full response to EOF. */ -static size_t -fetch_roundtrip(struct event_base *base, uint16_t lport, const char *reqbytes, - size_t reqlen, uint8_t *out, size_t cap, uint64_t budget_ms) -{ - int c = dial(lport); - MQ_CHECK(c >= 0); - if (c < 0) return 0; - send_all(c, reqbytes, reqlen); - size_t got = pump_read_all(base, c, out, cap, budget_ms); - close(c); - pump_a_bit(base, 100); - return got; -} - -/* ── Case 1: GET via fetch API → 200 + CL passthrough + byte-exact body ──────*/ -static void -test_gw_get_cl(void) -{ - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - static const uint8_t DL[] = "the-downloaded-file-contents-0123456789"; - g_dl_body = DL; - g_dl_body_len = sizeof(DL) - 1; - g_srv.scenario = SC_200_CL; - - const char *req = "POST /_mqproxy/fetch HTTP/1.1\r\n" - "Host: x\r\n" - "X-Mq-Auth: Bearer sekrit\r\n" - "X-Mq-Target: https://example.test/file\r\n" - "Cookie: should-be-stripped\r\n" - "X-Custom-Hdr: keepme\r\n" - "Content-Length: 0\r\n\r\n"; - uint8_t reply[4096] = {0}; - size_t got = - fetch_roundtrip(f.base, f.lport, req, strlen(req), reply, sizeof(reply), 6000); - - MQ_CHECK(got > 0); - MQ_CHECK_MEM(reply, "HTTP/1.1 200", 12); - /* Server saw the forwarded request. */ - MQ_CHECK_EQ_INT(g_srv.request_count, 1); - MQ_CHECK(g_srv.got_headers); - const char *m = srv_find_hdr(&g_srv, ":method"); - const char *sc = srv_find_hdr(&g_srv, ":scheme"); - const char *au = srv_find_hdr(&g_srv, ":authority"); - const char *pa = srv_find_hdr(&g_srv, ":path"); - const char *xa = srv_find_hdr(&g_srv, "x-mq-auth"); - const char *ck = srv_find_hdr(&g_srv, "cookie"); - const char *cu = srv_find_hdr(&g_srv, "x-custom-hdr"); - MQ_CHECK(m && strcmp(m, "GET") == 0); - MQ_CHECK(sc && strcmp(sc, "https") == 0); - MQ_CHECK(au && strcmp(au, "example.test") == 0); - MQ_CHECK(pa && strcmp(pa, "/file") == 0); - MQ_CHECK(xa && strcmp(xa, "Bearer sekrit") == 0); /* forwarded verbatim */ - MQ_CHECK(ck == NULL); /* Cookie stripped */ - MQ_CHECK(cu && strcmp(cu, "keepme") == 0); /* custom header kept */ - - /* Local reply: CL passthrough + byte-exact body. The response header name is - * carried verbatim from H3 (lowercase field names). */ - MQ_CHECK(contains(reply, got, "content-length: 39")); - MQ_CHECK(contains(reply, got, (const char *)DL)); - - gw_fixture_down(&f); -} - -/* ── Case 2: response WITHOUT content-length → chunked downstream ────────────*/ -static void -test_gw_chunked(void) -{ - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - static const uint8_t DL[] = - "no-content-length-here-so-this-must-be-chunked-downstream!!"; - g_dl_body = DL; - g_dl_body_len = sizeof(DL) - 1; - g_srv.scenario = SC_200_NOCL; - - const char *req = "POST /_mqproxy/fetch HTTP/1.1\r\n" - "Host: x\r\n" - "X-Mq-Auth: Bearer sekrit\r\n" - "X-Mq-Target: https://example.test/stream\r\n" - "Content-Length: 0\r\n\r\n"; - uint8_t reply[4096] = {0}; - size_t got = - fetch_roundtrip(f.base, f.lport, req, strlen(req), reply, sizeof(reply), 6000); - - MQ_CHECK(got > 0); - MQ_CHECK_MEM(reply, "HTTP/1.1 200", 12); - MQ_CHECK(contains(reply, got, "Transfer-Encoding: chunked")); - /* x-mq-* diagnostic header forwarded downstream. */ - MQ_CHECK(contains(reply, got, "x-mq-origin-protocol")); - - size_t hs = head_end(reply, got); - MQ_CHECK(hs != (size_t)-1); - uint8_t debody[4096]; - size_t dl = dechunk(reply + hs, got - hs, debody, sizeof(debody)); - MQ_CHECK_EQ_INT((int)dl, (int)(sizeof(DL) - 1)); - MQ_CHECK_MEM(debody, DL, sizeof(DL) - 1); - - gw_fixture_down(&f); -} - -/* ── Case 3: upload (POST with CL body ≥ 64 KiB) echoed back ─────────────────*/ -static void -test_gw_upload(void) -{ - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - g_srv.scenario = SC_ECHO_UPLOAD; - - const size_t N = 96 * 1024; - uint8_t *body = malloc(N); - MQ_CHECK(body != NULL); - for (size_t i = 0; i < N; i++) - body[i] = (uint8_t)((i * 131 + 7) & 0xff); - - char head[256]; - int hn = snprintf(head, sizeof(head), - "POST /_mqproxy/fetch HTTP/1.1\r\n" - "Host: x\r\n" - "X-Mq-Auth: Bearer sekrit\r\n" - "X-Mq-Target: https://example.test/upload\r\n" - "X-Mq-Method: POST\r\n" - "Content-Length: %zu\r\n\r\n", - N); - uint8_t *reqbuf = malloc((size_t)hn + N); - MQ_CHECK(reqbuf != NULL); - memcpy(reqbuf, head, (size_t)hn); - memcpy(reqbuf + hn, body, N); - - int c = dial(f.lport); - MQ_CHECK(c >= 0); - /* Send the whole request (head + body); the local listener streams the body - * and our gw client applies backpressure as needed. */ - send_all(c, reqbuf, (size_t)hn + N); - - uint8_t *reply = malloc(N + 4096); - MQ_CHECK(reply != NULL); - size_t got = pump_read_all(f.base, c, reply, N + 4096, 15000); - - MQ_CHECK(got > 0); - MQ_CHECK_MEM(reply, "HTTP/1.1 200", 12); - MQ_CHECK_EQ_INT(g_srv.request_count, 1); - const char *m = srv_find_hdr(&g_srv, ":method"); - MQ_CHECK(m && strcmp(m, "POST") == 0); - /* The known request Content-Length must be re-emitted over the tunnel - * (design §7.1 "recompute"): the gw client strips the client's original CL and - * sends its own validated value, so the server frames the upload by CL. */ - { - const char *cl = srv_find_hdr(&g_srv, "content-length"); - char want[32]; - snprintf(want, sizeof(want), "%zu", N); - MQ_CHECK(cl && strcmp(cl, want) == 0); - } - MQ_CHECK(g_srv.saw_fin); /* upload finished cleanly */ - - /* The echoed body must reach the local socket byte-exact. */ - size_t hs = head_end(reply, got); - MQ_CHECK(hs != (size_t)-1); - MQ_CHECK_EQ_INT((int)(got - hs), (int)N); - if (got - hs == N) MQ_CHECK(memcmp(reply + hs, body, N) == 0); - - close(c); - pump_a_bit(f.base, 100); - free(body); - free(reqbuf); - free(reply); - gw_fixture_down(&f); -} - -/* ── Case 3b: hostile origin :status → 502-class, NO overflow UB ───────────── - * - * A malicious gateway server replies with ":status: 99999999999". The client's - * :status parser must not accumulate that into a signed int (overflow UB → - * UBSan abort); it must treat the response as an upstream-protocol failure and - * synthesize a 502 to the local client. Under the sanitized build this test is - * the RED evidence: pre-fix the parse overflows and aborts. */ -static void -test_gw_bad_status(void) -{ - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - g_srv.scenario = SC_BAD_STATUS; - - const char *req = "POST /_mqproxy/fetch HTTP/1.1\r\n" - "Host: x\r\n" - "X-Mq-Auth: Bearer sekrit\r\n" - "X-Mq-Target: https://example.test/badstatus\r\n" - "Content-Length: 0\r\n\r\n"; - uint8_t reply[1024] = {0}; - size_t got = - fetch_roundtrip(f.base, f.lport, req, strlen(req), reply, sizeof(reply), 6000); - - /* No crash under UBSan; the bridge fails the response closed as a 502. */ - MQ_CHECK(got > 0); - MQ_CHECK_MEM(reply, "HTTP/1.1 502", 12); - MQ_CHECK(contains(reply, got, "X-Mq-Error: upstream-protocol")); - /* The request WAS forwarded (the server replied). */ - MQ_CHECK_EQ_INT(g_srv.request_count, 1); - - gw_fixture_down(&f); -} - -/* ── Case 4: 400 family (client-owned) — server never sees a request ─────────*/ -static void -test_gw_400_family(void) -{ - /* 4a: missing X-Mq-Auth. */ - { - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - const char *req = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "X-Mq-Target: https://example.test/x\r\n" - "Content-Length: 0\r\n\r\n"; - uint8_t reply[1024] = {0}; - size_t got = fetch_roundtrip(f.base, f.lport, req, strlen(req), reply, - sizeof(reply), 4000); - MQ_CHECK(got > 0); - MQ_CHECK_MEM(reply, "HTTP/1.1 400", 12); - MQ_CHECK(contains(reply, got, "X-Mq-Error: missing-auth")); - MQ_CHECK_EQ_INT(g_srv.request_count, 0); /* server never saw it */ - gw_fixture_down(&f); - } - /* 4b: bad target (no scheme). */ - { - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - const char *req = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "X-Mq-Auth: Bearer sekrit\r\n" - "X-Mq-Target: not-a-valid-url\r\n" - "Content-Length: 0\r\n\r\n"; - uint8_t reply[1024] = {0}; - size_t got = fetch_roundtrip(f.base, f.lport, req, strlen(req), reply, - sizeof(reply), 4000); - MQ_CHECK(got > 0); - MQ_CHECK_MEM(reply, "HTTP/1.1 400", 12); - MQ_CHECK(contains(reply, got, "X-Mq-Error: bad-target")); - MQ_CHECK_EQ_INT(g_srv.request_count, 0); - gw_fixture_down(&f); - } - /* 4c: duplicate X-Mq-Target → 400. */ - { - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - const char *req = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "X-Mq-Auth: Bearer sekrit\r\n" - "X-Mq-Target: https://example.test/a\r\n" - "X-Mq-Target: https://example.test/b\r\n" - "Content-Length: 0\r\n\r\n"; - uint8_t reply[1024] = {0}; - size_t got = fetch_roundtrip(f.base, f.lport, req, strlen(req), reply, - sizeof(reply), 4000); - MQ_CHECK(got > 0); - MQ_CHECK_MEM(reply, "HTTP/1.1 400", 12); - MQ_CHECK(contains(reply, got, "X-Mq-Error: duplicate-control-header")); - MQ_CHECK_EQ_INT(g_srv.request_count, 0); - gw_fixture_down(&f); - } -} - -/* ── Case 4d: oversized forwarded header rejected locally (not truncated) ──── - * - * A forwarded request header VALUE >= 1024 (the H3 forwarding arena slot) would - * be silently clamped before reaching the tunnel. The gw_client must reject the - * fetch with 400 header-too-long BEFORE opening the H3 request — the server never - * sees it. */ -static void -test_gw_oversized_header(void) -{ - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - - /* Build a request with a 2000-byte X-Custom-Hdr value. */ - char big[2000]; - memset(big, 'v', sizeof(big)); - char req[4096]; - int rn = snprintf(req, sizeof(req), - "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "X-Mq-Auth: Bearer sekrit\r\n" - "X-Mq-Target: https://example.test/x\r\n" - "X-Custom-Hdr: %.*s\r\n" - "Content-Length: 0\r\n\r\n", - (int)sizeof(big), big); - MQ_CHECK(rn > 0 && (size_t)rn < sizeof(req)); - - uint8_t reply[1024] = {0}; - size_t got = - fetch_roundtrip(f.base, f.lport, req, (size_t)rn, reply, sizeof(reply), 4000); - MQ_CHECK(got > 0); - MQ_CHECK_MEM(reply, "HTTP/1.1 400", 12); - MQ_CHECK(contains(reply, got, "X-Mq-Error: header-too-long")); - MQ_CHECK_EQ_INT(g_srv.request_count, 0); /* server never saw it */ - - gw_fixture_down(&f); -} - -/* ── Case 5: tunnel-unavailable → 502 + X-Mq-Error: tunnel-unavailable ───────*/ -static void -test_gw_tunnel_unavailable(void) -{ - gw_fixture_t f; - if (gw_fixture_up_dead(&f) != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - const char *req = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "X-Mq-Auth: Bearer tok\r\n" - "X-Mq-Target: https://example.test/x\r\n" - "Content-Length: 0\r\n\r\n"; - uint8_t reply[1024] = {0}; - size_t got = - fetch_roundtrip(f.base, f.lport, req, strlen(req), reply, sizeof(reply), 3000); - MQ_CHECK(got > 0); - MQ_CHECK_MEM(reply, "HTTP/1.1 502", 12); - MQ_CHECK(contains(reply, got, "X-Mq-Error: tunnel-unavailable")); - gw_fixture_down(&f); -} - -/* ── Case 6: mid-download reset → local socket sees a truncated close ────────*/ -static void -test_gw_mid_download_reset(void) -{ - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - g_srv.scenario = SC_RESET_MID; - - const char *req = "POST /_mqproxy/fetch HTTP/1.1\r\nHost: x\r\n" - "X-Mq-Auth: Bearer sekrit\r\n" - "X-Mq-Target: https://example.test/trunc\r\n" - "Content-Length: 0\r\n\r\n"; - uint8_t reply[4096] = {0}; - size_t got = - fetch_roundtrip(f.base, f.lport, req, strlen(req), reply, sizeof(reply), 6000); - - /* The upstream was reset mid-response. Two valid outcomes depending on - * whether the 200 headers raced ahead of the RESET_STREAM: - * (a) headers arrived first → local sees "HTTP/1.1 200" but the chunked - * body is TRUNCATED: no clean "0\r\n\r\n" terminator (abort, not a - * fake clean finish); - * (b) the reset won the race (no headers delivered) → the bridge - * synthesizes "HTTP/1.1 502" + X-Mq-Error: upstream-reset. - * In NEITHER case may the local client see a clean, complete download. */ - MQ_CHECK(got > 0); - int is_200 = (got >= 12 && memcmp(reply, "HTTP/1.1 200", 12) == 0); - int is_502 = (got >= 12 && memcmp(reply, "HTTP/1.1 502", 12) == 0); - MQ_CHECK(is_200 || is_502); - if (is_200) { - /* Truncated: no clean chunked terminator. */ - MQ_CHECK(!contains(reply, got, "\r\n0\r\n\r\n")); - } else if (is_502) { - MQ_CHECK(contains(reply, got, "X-Mq-Error: upstream-reset")); - } - /* The request was forwarded (server saw it). */ - MQ_CHECK_EQ_INT(g_srv.request_count, 1); - - gw_fixture_down(&f); -} - -/* ── Case 7: teardown with a request mid-flight (sanctioned order) ─────────── - * - * A POST upload is STARTED but never completed (only part of the promised CL - * body is sent) and the server NEVER responds (SC_HANG). At teardown the bridge - * therefore holds a live in-flight mq_gw_req_t: the H3 request is open (tunnel - * side live), the local handle is live, no response has begun. We then tear down - * in the sanctioned order (gw_client_free FIRST, while the H3 engine is still - * live). gw_client_free must detach the H3 callbacks + reset the request against - * the LIVE engine and abort the local handle, with NO use-after-free and NO leak - * (verified under ASan / LSan). */ -static void -test_gw_teardown_midflight(void) -{ - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - g_srv.scenario = SC_HANG; /* server receives but never responds */ - - /* Promise a large body but send only a fraction of it: the upload is started - * (request open, headers + partial body forwarded) but never finished, so the - * bridge keeps a live in-flight request. */ - const size_t CL = 256 * 1024; - char head[256]; - int hn = snprintf(head, sizeof(head), - "POST /_mqproxy/fetch HTTP/1.1\r\n" - "Host: x\r\n" - "X-Mq-Auth: Bearer sekrit\r\n" - "X-Mq-Target: https://example.test/hang\r\n" - "X-Mq-Method: POST\r\n" - "Content-Length: %zu\r\n\r\n", - CL); - - int c = dial(f.lport); - MQ_CHECK(c >= 0); - send_all(c, head, (size_t)hn); - /* Only a small slice of the promised body — upload stays mid-flight. */ - uint8_t chunk[4096]; - memset(chunk, 'U', sizeof(chunk)); - send_all(c, chunk, sizeof(chunk)); - - /* Pump enough to drive the H3 request open + forward headers/partial body to - * the (hanging) server. The request is now live on the bridge. */ - pump_a_bit(f.base, 400); - - /* The server saw exactly one request and never responded. */ - MQ_CHECK_EQ_INT(g_srv.request_count, 1); - MQ_CHECK_EQ_INT(g_srv.responded, 0); - - /* Tear down with the request still mid-flight, in the sanctioned order - * (gw_fixture_down frees gw_client FIRST while the H3 engine is live). Must - * be ASan-clean and leak-free. */ - close(c); - pump_a_bit(f.base, 50); - gw_fixture_down(&f); -} - -/* Step 0 (Slice 3 Task 6): mq_gw_client_token() returns the RAW stored token - * (no "Bearer " prefix, no new storage). The dead fixture constructs a gwc with - * token "tok"; the production H2 submit vtable's auth_token returns this. */ -static void -test_gw_client_token_accessor(void) -{ - gw_fixture_t f; - if (gw_fixture_up_dead(&f) != 0) { - MQ_CHECK(0 && "token-accessor fixture up failed"); - gw_fixture_down(&f); - return; - } - const char *tok = mq_gw_client_token(f.gw); - MQ_CHECK(tok != NULL); - if (tok) MQ_CHECK(strcmp(tok, "tok") == 0); - MQ_CHECK(mq_gw_client_token(NULL) == NULL); - gw_fixture_down(&f); -} - -static void -run_all(void) -{ - struct event_base *base = event_base_new(); - MQ_CHECK(base != NULL); - if (!base) return; - - test_echo_roundtrip(base); - test_404(base); - test_411(base); - test_400(base); - test_mid_body_abort(base); - test_backpressure(base); - test_oversize_body(base); - test_free_live_conn(base); - test_highwater_drain(base); - - event_base_free(base); - - /* Gateway-client (Task 3.2) cases — each owns its own base + transports. */ - test_gw_get_cl(); - test_gw_chunked(); - test_gw_bad_status(); - test_gw_upload(); - test_gw_400_family(); - test_gw_oversized_header(); - test_gw_tunnel_unavailable(); - test_gw_mid_download_reset(); - test_gw_teardown_midflight(); - test_gw_client_token_accessor(); -} - -MQ_TEST_MAIN(run_all()) diff --git a/tests/integration/test_gw_reconnect.c b/tests/integration/test_gw_reconnect.c deleted file mode 100644 index 12cb1525..00000000 --- a/tests/integration/test_gw_reconnect.c +++ /dev/null @@ -1,760 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_gw_reconnect.c — Task 5.2 (Phase 5b): in-process proof that the gateway - * client reconnect path (Task 5.1) works END-TO-END. - * - * Same in-process H3-tunnel harness shape as the gateway-client cases in - * test_gw_client.c: a fake gateway SERVER (server transport (TLS) + runtime + - * mq_h3 with server hooks) and the CLIENT under test (client transport + - * runtime + mq_h3 + mq_gw_client + a real mq_fetch_listener). A local "curl" - * socket POSTs /_mqproxy/fetch; the gw_client bridges it onto an H3 request to - * the server, which replies with a fixed download body. - * - * The DIFFERENCE from test_gw_client's fixture: here reconnect is ENABLED with a - * SHORT max-backoff (1000ms — mq_gw_client floors it to 1000) so a deterministic - * conn drop recovers fast. - * - * CONN-DROP MECHANISM: mq_gw_client exposes NO conn accessor and NO conn-close - * path (auth is per-request; the conn is internal). So we drop the tunnel from - * the SERVER side: srv_on_new_conn captures each accepted mq_h3_conn, and we call - * mq_h3_conn_close() on it. That sends CONNECTION_CLOSE to the client, whose - * gw_conn_state(established=0) fires → (reconnect on) arms the backoff timer → - * the timer fires → gw_issue_connect re-dials the SAME still-up server fixture → - * re-accept (a 2nd srv_on_new_conn). The same server port is re-dialed - * (c->peer), and the server listener stays up across the drop, so recovery is - * deterministic on loopback. - * - * RECONNECT-SUCCESS DETECTION: the gateway has NO connection-level auth event - * (per-request auth), and conn_up is not observable from outside the gw_client. - * We detect re-establishment two ways, both observable from the test's SERVER - * side / the local socket: - * (a) the server's on_new_conn count goes to >= 2 (client re-dialed, server - * re-accepted) — proves the conn was re-established, not merely that the - * drop happened; - * (b) a NEW fetch issued after the drop COMPLETES end-to-end (status + body - * byte-exact). A fetch can only succeed on a live tunnel, so the end-to-end - * success IS the proof of backoff→re-establish→fresh-request-on-new-conn. - * - * Cases: - * 1. Gateway reconnect end-to-end: baseline fetch → drop the tunnel from the - * server side → pump until re-established (server conn count >= 2) → a NEW - * fetch completes 200 + byte-exact body on the new conn. (Also validates the - * 5.1 review concern: per-conn-close in-flight reaping didn't corrupt state.) - * 2. Shutdown while reconnecting (ASan): drop the conn, and DURING the backoff - * window (before reconnect completes) call mq_gw_client_free → no crash, no - * late reconnect, leak/UAF-free under ASan. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "gateway/mq_fetch_listener.h" -#include "gateway/mq_gw_client.h" -#include "gateway/mq_gw_fetch_adapter.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_h3.h" -#include "transport/mq_transport.h" - -#include -#include - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -/* Short reconnect max-backoff. mq_gw_client floors it to 1000ms (anti - * busy-spin), so 1000 is the smallest deterministic value. */ -#define RECONNECT_MAX_BACKOFF_MS 1000 -/* Generous per-cycle budget: backoff (<=1s) + handshake on loopback. */ -#define RECONNECT_BUDGET_MS 8000 - -/* ── time / port helpers (mirror test_gw_client.c) ──────────────────────────*/ -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -static int -dial(uint16_t port) -{ - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) return -1; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr); - if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return -1; - } - return fd; -} - -static void -send_all(int fd, const void *buf, size_t len) -{ - const uint8_t *p = buf; - size_t off = 0; - while (off < len) { - ssize_t n = send(fd, p + off, len - off, 0); - if (n <= 0) break; - off += (size_t)n; - } -} - -static size_t -pump_read_all(struct event_base *base, int fd, uint8_t *out, size_t cap, - uint64_t budget_ms) -{ - int fl = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, fl | O_NONBLOCK); - size_t got = 0; - int eof = 0; - uint64_t deadline = now_ms() + budget_ms; - while (!eof && got < cap && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - ssize_t n = recv(fd, out + got, cap - got, 0); - if (n > 0) { - got += (size_t)n; - } else if (n == 0) { - eof = 1; - } - } - fcntl(fd, F_SETFL, fl); - return got; -} - -static void -pump_a_bit(struct event_base *base, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -/* Substring search over a byte range (avoids the GNU-only memmem). */ -static int -contains(const uint8_t *hay, size_t hlen, const char *needle) -{ - size_t nl = strlen(needle); - if (nl == 0 || hlen < nl) return 0; - for (size_t i = 0; i + nl <= hlen; i++) { - if (memcmp(hay + i, needle, nl) == 0) return 1; - } - return 0; -} - -/* ── fake gateway server (H3) — minimal: 200 + content-length + body ────────*/ -#define SRV_MAX_HDRS 32 -#define SRV_HDR_LEN 512 - -typedef struct { - char name[SRV_HDR_LEN]; - char value[SRV_HDR_LEN]; -} srv_hdr_t; - -typedef struct { - mq_h3_req_t *req; - int got_headers; - int saw_fin; - - srv_hdr_t hdrs[SRV_MAX_HDRS]; - int n_hdrs; - - uint8_t body[64 * 1024]; - size_t body_len; - - int responded; - - /* response send state. */ - const uint8_t *snd; - size_t snd_total; - size_t snd_off; - int snd_fin_done; - - int request_count; /* on_new_req count */ -} gw_srv_t; - -static gw_srv_t g_srv; - -/* Reconnect-success signals observable from the server side. */ -static mq_h3_conn_t *g_srv_conn; /* most-recent accepted tunnel conn */ -static int g_srv_conn_count; /* on_new_conn count (>=2 ⇒ client re-dialed) */ - -static const uint8_t *g_dl_body; -static size_t g_dl_body_len; - -static void -srv_capture_hdr(const char *n, size_t nl, const char *v, size_t vl, void *u) -{ - gw_srv_t *s = (gw_srv_t *)u; - if (s->n_hdrs >= SRV_MAX_HDRS) return; - srv_hdr_t *h = &s->hdrs[s->n_hdrs++]; - size_t cn = nl < SRV_HDR_LEN - 1 ? nl : SRV_HDR_LEN - 1; - size_t cv = vl < SRV_HDR_LEN - 1 ? vl : SRV_HDR_LEN - 1; - memcpy(h->name, n, cn); - h->name[cn] = '\0'; - memcpy(h->value, v, cv); - h->value[cv] = '\0'; -} - -static void -srv_body_pump(gw_srv_t *s) -{ - if (!s->req || s->snd_fin_done) return; - while (s->snd_off < s->snd_total) { - long acc = mq_h3_req_send_body(s->req, s->snd + s->snd_off, - s->snd_total - s->snd_off, 1); - if (acc <= 0) return; - s->snd_off += (size_t)acc; - if (s->snd_off >= s->snd_total) { - s->snd_fin_done = 1; - return; - } - } - if (s->snd_total == 0) { - mq_h3_req_finish(s->req); - s->snd_fin_done = 1; - } -} - -static void -srv_respond(gw_srv_t *s) -{ - if (s->responded || !s->req) return; - s->responded = 1; - char cl[32]; - snprintf(cl, sizeof(cl), "%zu", g_dl_body_len); - mq_h3_header_t h[] = {{":status", "200"}, {"content-length", cl}}; - mq_h3_req_send_headers(s->req, h, 2, 0); - s->snd = g_dl_body; - s->snd_total = g_dl_body_len; - s->snd_off = 0; - srv_body_pump(s); -} - -static void -srv_on_req_read(mq_h3_req_t *r, int flag, void *user) -{ - gw_srv_t *s = (gw_srv_t *)user; - if (flag & (XQC_REQ_NOTIFY_READ_HEADER | XQC_REQ_NOTIFY_READ_TRAILER)) { - int fin = 0; - int n = mq_h3_req_recv_headers(r, srv_capture_hdr, s, &fin); - if (n >= 0) s->got_headers = 1; - if (fin) s->saw_fin = 1; - } - for (;;) { - int fin = 0; - long n = mq_h3_req_recv_body(r, s->body + s->body_len, - sizeof(s->body) - s->body_len, &fin); - if (n > 0) s->body_len += (size_t)n; - if (fin) s->saw_fin = 1; - if (n <= 0) break; - } - if (s->saw_fin && !s->responded) srv_respond(s); -} - -static void -srv_on_req_write(mq_h3_req_t *r, void *user) -{ - (void)r; - gw_srv_t *s = (gw_srv_t *)user; - if (!s->responded) return; - srv_body_pump(s); -} - -static void -srv_on_req_close(mq_h3_req_t *r, void *user) -{ - (void)r; - gw_srv_t *s = (gw_srv_t *)user; - s->req = NULL; -} - -/* Reset the per-request state for the NEXT request (a reconnect re-dials a fresh - * conn; the next fetch is a fresh H3 request whose headers/body we re-capture). */ -static void -srv_reset_req_state(gw_srv_t *s) -{ - s->req = NULL; - s->got_headers = 0; - s->saw_fin = 0; - s->n_hdrs = 0; - s->body_len = 0; - s->responded = 0; - s->snd = NULL; - s->snd_total = 0; - s->snd_off = 0; - s->snd_fin_done = 0; -} - -static void -srv_on_new_req(mq_h3_req_t *r, void *user) -{ - (void)user; - srv_reset_req_state(&g_srv); /* fresh request: clear per-request capture */ - g_srv.request_count++; - g_srv.req = r; - mq_h3_req_set_cbs(r, srv_on_req_read, srv_on_req_write, srv_on_req_close, &g_srv); -} - -static void -srv_on_new_conn(mq_h3_conn_t *c, void *user) -{ - (void)user; - g_srv_conn = c; /* capture so the test can drop the tunnel from the server side */ - g_srv_conn_count++; -} - -/* ── gateway-client fixture (RECONNECT ENABLED) ─────────────────────────────*/ -typedef struct { - struct event_base *base; - mq_transport_t *srv_t; - mq_transport_t *cli_t; - mq_runtime_t *srv_rt; - mq_runtime_t *cli_rt; - mq_h3_t *srv_h3; - mq_h3_t *cli_h3; - mq_gw_client_t *gw; - mq_gw_fetch_adapter_t *fetch_adp; - mq_fetch_listener_t *listener; - uint16_t lport; -} gw_fixture_t; - -/* Full fixture constructor: parameterizes reconnect_enabled + keepalive_idle_ms - * so the terminal-path (reconnect off) and keepalive-setter (idle > 0) coverage - * cases can stand up the matching client. gw_fixture_up wraps this with the - * default (reconnect on, keepalive 0). */ -static int -gw_fixture_up_ex(gw_fixture_t *f, const char *token, int reconnect_enabled, - uint64_t keepalive_idle_ms) -{ - memset(f, 0, sizeof(*f)); - memset(&g_srv, 0, sizeof(g_srv)); - g_srv_conn = NULL; - g_srv_conn_count = 0; - - f->base = event_base_new(); - if (!f->base) return -1; - - uint16_t srv_port = reserve_udp_port(); - if (!srv_port) return -1; - - f->srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - if (!f->srv_t) return -1; - f->srv_rt = mq_runtime_new(f->srv_t, f->base); - if (!f->srv_rt) return -1; - - /* Match the H3 fabric's mp settings so the negotiated conn settings line up. */ - xqc_conn_settings_t srv_settings; - memset(&srv_settings, 0, sizeof(srv_settings)); - srv_settings.proto_version = XQC_VERSION_V1; - srv_settings.pacing_on = 1; - mq_conn_apply_mp_settings(&srv_settings, /*is_server=*/1, MQ_CC_BBR2); - xqc_server_set_conn_settings(mq_transport_xqc(f->srv_t), &srv_settings); - - f->srv_h3 = mq_h3_init(f->srv_t, srv_on_new_conn, srv_on_new_req, NULL); - if (!f->srv_h3) return -1; - if (mq_runtime_open_udp_path(f->srv_rt, "127.0.0.1", srv_port) != 0) return -1; - - f->cli_t = mq_transport_new(0); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - if (!f->cli_rt) return -1; - if (mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0) != 0) return -1; - f->cli_h3 = mq_h3_init(f->cli_t, NULL, NULL, NULL); - if (!f->cli_h3) return -1; - - /* RECONNECT ENABLED with a short max-backoff for fast recovery (the whole - * point of this test; test_gw_client builds the same fixture with reconnect - * DISABLED). */ - f->gw = mq_gw_client_new(f->cli_t, f->cli_rt, f->cli_h3, "127.0.0.1", srv_port, token, - MQ_CC_BBR2, keepalive_idle_ms, reconnect_enabled, - /*reconnect_max_backoff_ms=*/RECONNECT_MAX_BACKOFF_MS); - if (!f->gw) return -1; - - f->fetch_adp = mq_gw_fetch_adapter_new(f->gw); - if (!f->fetch_adp) return -1; - f->listener = mq_fetch_listener_new(f->base, "127.0.0.1", 0, - mq_gw_fetch_adapter_cbs(f->fetch_adp), - mq_gw_fetch_adapter_user(f->fetch_adp)); - if (!f->listener) return -1; - f->lport = mq_fetch_listener_port(f->listener); - if (!f->lport) return -1; - - /* Settle: let the eager tunnel conn establish on loopback. */ - pump_a_bit(f->base, 300); - return 0; -} - -/* Default fixture: reconnect ENABLED, keepalive OFF (the original behavior). */ -static int -gw_fixture_up(gw_fixture_t *f, const char *token) -{ - return gw_fixture_up_ex(f, token, /*reconnect_enabled=*/1, /*keepalive_idle_ms=*/0); -} - -static void -gw_fixture_down(gw_fixture_t *f) -{ - /* SANCTIONED TEARDOWN ORDER (mq_gw_client.h): gw_client_free FIRST while the - * client H3 engine is still live, THEN mq_h3_free, THEN transport_free. - * - * NB: this order is exactly what revert-proves the late-callback UAF guard in - * mq_gw_client_free (the mq_h3_conn_set_state_cb(NULL) detach). Whenever a case - * ends with a LIVE tunnel conn (e.g. case 1), freeing the gw_client here while - * the conn is still alive orphans it; the subsequent mq_h3_free engine teardown - * delivers a late conn-close into gw_conn_state. The detach makes that a no-op; - * removing it makes this teardown ASan-abort (heap-use-after-free at the - * gw_conn_state CLOSED branch reading the freed cli). So the gateway detach is - * load-bearing and exercised by the existing cases — no dedicated case needed - * (cf. test_client_reconnect case 9, which must INVERT its fixture order because - * mq_client's fixture_down frees the transport before the client). */ - if (f->gw) mq_gw_client_free(f->gw); - if (f->cli_h3) mq_h3_free(f->cli_h3); - if (f->srv_h3) mq_h3_free(f->srv_h3); - if (f->cli_t) mq_transport_free(f->cli_t); - if (f->srv_t) mq_transport_free(f->srv_t); - if (f->cli_rt) mq_runtime_free(f->cli_rt); - if (f->srv_rt) mq_runtime_free(f->srv_rt); - if (f->listener) mq_fetch_listener_free(f->listener); - if (f->fetch_adp) mq_gw_fetch_adapter_free(f->fetch_adp); - if (f->base) event_base_free(f->base); -} - -/* Locate the response body start (offset after the head terminator). */ -static size_t -head_end(const uint8_t *p, size_t len) -{ - for (size_t i = 0; i + 3 < len; i++) - if (p[i] == '\r' && p[i + 1] == '\n' && p[i + 2] == '\r' && p[i + 3] == '\n') - return i + 4; - return (size_t)-1; -} - -/* Issue one fetch through the gateway, reading the full reply to EOF. */ -static size_t -fetch_roundtrip(struct event_base *base, uint16_t lport, const char *reqbytes, - size_t reqlen, uint8_t *out, size_t cap, uint64_t budget_ms) -{ - int c = dial(lport); - MQ_CHECK(c >= 0); - if (c < 0) return 0; - send_all(c, reqbytes, reqlen); - size_t got = pump_read_all(base, c, out, cap, budget_ms); - close(c); - pump_a_bit(base, 100); - return got; -} - -/* Run one GET fetch and assert 200 + byte-exact download body. Returns 0 on a - * fully successful end-to-end fetch, -1 otherwise. */ -static int -run_fetch_ok(gw_fixture_t *f, const char *path, const uint8_t *dl, size_t dl_len) -{ - g_dl_body = dl; - g_dl_body_len = dl_len; - - char req[256]; - int rn = snprintf(req, sizeof(req), - "POST /_mqproxy/fetch HTTP/1.1\r\n" - "Host: x\r\n" - "X-Mq-Auth: Bearer sekrit\r\n" - "X-Mq-Target: https://example.test%s\r\n" - "Content-Length: 0\r\n\r\n", - path); - MQ_CHECK(rn > 0 && (size_t)rn < sizeof(req)); - - uint8_t reply[4096] = {0}; - size_t got = - fetch_roundtrip(f->base, f->lport, req, (size_t)rn, reply, sizeof(reply), 6000); - - MQ_CHECK(got > 0); - if (got < 12 || memcmp(reply, "HTTP/1.1 200", 12) != 0) return -1; - - size_t hs = head_end(reply, got); - MQ_CHECK(hs != (size_t)-1); - if (hs == (size_t)-1) return -1; - if (got - hs != dl_len) { - MQ_CHECK_EQ_INT((int)(got - hs), (int)dl_len); - return -1; - } - if (memcmp(reply + hs, dl, dl_len) != 0) { - MQ_CHECK(0); - return -1; - } - MQ_CHECK(contains(reply, got, (const char *)dl)); - return 0; -} - -/* Drop the tunnel conn from the SERVER side and pump until the client has - * re-established (server accepted a NEW conn — on_new_conn count grew). Returns 1 - * on re-establishment, 0 on timeout. */ -static int -drop_and_await_reconnect(gw_fixture_t *f, uint64_t budget_ms) -{ - int before = g_srv_conn_count; - MQ_CHECK(g_srv_conn != NULL); - if (!g_srv_conn) return 0; - mq_h3_conn_close(g_srv_conn); - g_srv_conn = NULL; /* freed after its close-notify; re-set on the next accept */ - - uint64_t deadline = now_ms() + budget_ms; - while (g_srv_conn_count <= before && now_ms() < deadline) { - event_base_loop(f->base, EVLOOP_NONBLOCK); - } - return g_srv_conn_count > before; -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 1: gateway reconnect end-to-end. - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case1_gw_reconnect(void) -{ - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - - /* Baseline fetch over the eagerly-established tunnel. */ - static const uint8_t DL1[] = "baseline-download-body-0123456789"; - MQ_CHECK_EQ_INT(run_fetch_ok(&f, "/file1", DL1, sizeof(DL1) - 1), 0); - MQ_CHECK_EQ_INT(g_srv.request_count, 1); - MQ_CHECK_EQ_INT(g_srv_conn_count, 1); /* one tunnel conn so far */ - - /* Drop the tunnel from the server side; pump until the client re-establishes - * (server accepts a 2nd conn). This exercises conn-loss → backoff → - * re-establish, and the per-conn-close in-flight reaping (no live request at - * the drop here, but the reconnect path must not corrupt conn state). */ - MQ_CHECK_EQ_INT(drop_and_await_reconnect(&f, RECONNECT_BUDGET_MS), 1); - MQ_CHECK(g_srv_conn_count >= 2); - - /* A NEW fetch must complete end-to-end on the reconnected conn (status + body - * byte-exact). This is the real assertion: a fetch can ONLY succeed on a live - * re-established tunnel, so success proves backoff→re-establish→fresh request - * on the new conn AND that the reaping/teardown left conn state intact. */ - static const uint8_t DL2[] = "post-reconnect-body-abcdefghijklmnop"; - MQ_CHECK_EQ_INT(run_fetch_ok(&f, "/file2", DL2, sizeof(DL2) - 1), 0); - MQ_CHECK_EQ_INT(g_srv.request_count, 2); /* server saw the 2nd fetch */ - - /* Drop + recover once more, then a third fetch — proves repeated cycles work - * (backoff counter reset on establish; not a one-shot). */ - MQ_CHECK_EQ_INT(drop_and_await_reconnect(&f, RECONNECT_BUDGET_MS), 1); - MQ_CHECK(g_srv_conn_count >= 3); - static const uint8_t DL3[] = "third-cycle-body-QRSTUVWXYZ"; - MQ_CHECK_EQ_INT(run_fetch_ok(&f, "/file3", DL3, sizeof(DL3) - 1), 0); - MQ_CHECK_EQ_INT(g_srv.request_count, 3); - - gw_fixture_down(&f); -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 2: shutdown while reconnecting — mq_gw_client_free DURING the backoff - * window. No crash, no late reconnect, leak/UAF-free (ASan). - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case2_free_while_reconnecting(void) -{ - gw_fixture_t f; - if (gw_fixture_up(&f, "sekrit") != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - - /* Establish a baseline so there is a live tunnel conn to drop. */ - static const uint8_t DL[] = "before-shutdown-body"; - MQ_CHECK_EQ_INT(run_fetch_ok(&f, "/x", DL, sizeof(DL) - 1), 0); - MQ_CHECK_EQ_INT(g_srv_conn_count, 1); - - /* Drop the tunnel and pump JUST enough to register the close + arm the backoff - * timer, WITHOUT pumping long enough to reconnect. The "1000ms" knob is the - * MAX-backoff cap, not the first delay: the actual first-attempt delay is - * mq_backoff_ms(250, cap, attempt=1)=500ms jittered to [250,500]ms, so the - * real minimum before the timer can fire is ~250ms. We pump ~150ms — under - * that ~250ms minimum — so gw_conn_state(closed) has fired and armed backoff - * but the re-dial has NOT yet happened (margin ~100ms, robust on loopback; - * ASan leak/UAF-freeness, not this timing, is the case's primary assertion). */ - MQ_CHECK(g_srv_conn != NULL); - mq_h3_conn_close(g_srv_conn); - g_srv_conn = NULL; - int conn_count_at_drop = g_srv_conn_count; - pump_a_bit(f.base, 150); - /* Still in the backoff window: the client must NOT have re-established yet. */ - MQ_CHECK_EQ_INT(g_srv_conn_count, conn_count_at_drop); /* no reconnect yet */ - - /* Free the gw_client mid-reconnect. mq_gw_client_free sets shutting_down first - * and disarms/frees the backoff timer, so no reconnect fires after free. The - * SANCTIONED TEARDOWN ORDER is honored (gw_client_free FIRST while h3 is live). - * Free it here, then NULL it so gw_fixture_down does not double-free. */ - mq_gw_client_free(f.gw); - f.gw = NULL; - - /* Pump well past the backoff window: NO reconnect must occur (server must NOT - * accept a new conn) and no crash / UAF (caught by ASan). */ - pump_a_bit(f.base, RECONNECT_BUDGET_MS); - MQ_CHECK_EQ_INT(g_srv_conn_count, conn_count_at_drop); /* no late reconnect */ - - gw_fixture_down(&f); /* gw already freed; frees the rest cleanly */ -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 3: terminal path — reconnect DISABLED. Drop the tunnel from the server - * side; the client must NOT reconnect (server on_new_conn stays at 1) and - * a subsequent fetch must FAIL (tunnel is terminal-dead). Exercises - * gw_conn_state's terminal else branch (reconnect off). - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case3_terminal_no_reconnect(void) -{ - gw_fixture_t f; - if (gw_fixture_up_ex(&f, "sekrit", /*reconnect_enabled=*/0, - /*keepalive_idle_ms=*/0) != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - - /* Baseline fetch over the eager tunnel. */ - static const uint8_t DL[] = "terminal-baseline-body"; - MQ_CHECK_EQ_INT(run_fetch_ok(&f, "/file1", DL, sizeof(DL) - 1), 0); - MQ_CHECK_EQ_INT(g_srv_conn_count, 1); - - /* Drop the tunnel from the server side. With reconnect DISABLED the client - * takes gw_conn_state's terminal else branch: conn NULLed, NO backoff armed. */ - MQ_CHECK(g_srv_conn != NULL); - mq_h3_conn_close(g_srv_conn); - g_srv_conn = NULL; - - /* Pump well past any backoff window: the client must NOT re-establish. The - * server must NOT accept a 2nd conn (terminal, not reconnecting). */ - pump_a_bit(f.base, RECONNECT_BUDGET_MS); - MQ_CHECK_EQ_INT(g_srv_conn_count, 1); /* no reconnect — terminal */ - - /* A subsequent fetch must FAIL: the tunnel is terminal-dead, so it cannot - * complete with a 200. (Use a quiet round-trip, not run_fetch_ok, whose - * MQ_CHECKs would count a failed fetch as a test failure.) The gateway maps a - * dead tunnel to a non-2xx reply (e.g. 502/504) or a closed connection — in - * all cases NOT "HTTP/1.1 200". */ - static const uint8_t DL2[] = "should-not-arrive"; - g_dl_body = DL2; - g_dl_body_len = sizeof(DL2) - 1; - char req[256]; - int rn = snprintf(req, sizeof(req), - "POST /_mqproxy/fetch HTTP/1.1\r\n" - "Host: x\r\n" - "X-Mq-Auth: Bearer sekrit\r\n" - "X-Mq-Target: https://example.test/file2\r\n" - "Content-Length: 0\r\n\r\n"); - MQ_CHECK(rn > 0 && (size_t)rn < sizeof(req)); - uint8_t reply[4096] = {0}; - size_t got = - fetch_roundtrip(f.base, f.lport, req, (size_t)rn, reply, sizeof(reply), 6000); - /* Must NOT be a 200 + download body. Either no reply, or a non-2xx status. */ - int is_200 = (got >= 12 && memcmp(reply, "HTTP/1.1 200", 12) == 0); - MQ_CHECK_EQ_INT(is_200, 0); - MQ_CHECK_EQ_INT(g_srv.request_count, 1); /* server never saw a 2nd request */ - /* Still no reconnect: the failed fetch must not have re-dialed the server. */ - MQ_CHECK_EQ_INT(g_srv_conn_count, 1); - - gw_fixture_down(&f); -} - -/* ══════════════════════════════════════════════════════════════════════════ - * Case 4: keepalive non-zero wire-up. A gw client built with keepalive_idle_ms - * > 0 makes mq_h3_connect take the idle_time_out setter branch. Establish - * + fetch, idle ~1500ms (< the 4000ms idle) while pumping, then a second - * fetch still succeeds (conn held open across a sub-timeout idle). The - * primary goal is exercising the L545 idle_time_out setter in - * mq_h3_connect; the conn-survives assertion is the observable proof. - * ════════════════════════════════════════════════════════════════════════ */ -static void -test_case4_keepalive_wireup(void) -{ - gw_fixture_t f; - if (gw_fixture_up_ex(&f, "sekrit", /*reconnect_enabled=*/1, - /*keepalive_idle_ms=*/4000) != 0) { - gw_fixture_down(&f); - MQ_CHECK(0); - return; - } - - /* Baseline fetch over the eager tunnel (keepalive idle = 4000ms). */ - static const uint8_t DL1[] = "keepalive-first-body-0123456789"; - MQ_CHECK_EQ_INT(run_fetch_ok(&f, "/k1", DL1, sizeof(DL1) - 1), 0); - MQ_CHECK_EQ_INT(g_srv_conn_count, 1); - - /* Idle ~1500ms (< 4000ms idle) with no fetch traffic, pumping the loop. The - * conn must stay up (not idle-closed) and not reconnect. */ - pump_a_bit(f.base, 1500); - MQ_CHECK_EQ_INT(g_srv_conn_count, 1); /* no drop+reconnect during the idle */ - - /* A second fetch still succeeds on the same held-open conn. */ - static const uint8_t DL2[] = "keepalive-second-body-abcdefghij"; - MQ_CHECK_EQ_INT(run_fetch_ok(&f, "/k2", DL2, sizeof(DL2) - 1), 0); - MQ_CHECK_EQ_INT(g_srv_conn_count, 1); /* same conn, no reconnect */ - MQ_CHECK_EQ_INT(g_srv.request_count, 2); - - gw_fixture_down(&f); -} - -static void -run_all(void) -{ - /* A torn-down local socket write can raise SIGPIPE; ignore it. */ - signal(SIGPIPE, SIG_IGN); - test_case1_gw_reconnect(); - test_case2_free_while_reconnecting(); - test_case3_terminal_no_reconnect(); - test_case4_keepalive_wireup(); -} - -MQ_TEST_MAIN(run_all()) diff --git a/tests/integration/test_gw_relay.c b/tests/integration/test_gw_relay.c deleted file mode 100644 index 573070e6..00000000 --- a/tests/integration/test_gw_relay.c +++ /dev/null @@ -1,1342 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_gw_relay.c — Task 5 (Phase 7 MITM Slice 0+1): lock the NEUTRAL gateway - * boundary (mq_gw_client_prevalidate / mq_gw_client_req_begin / per-request ops) - * by driving it directly with a FAKE sink adapter (mq_gw_sink_ops_t). A future - * H2/H3 MITM adapter will rely on these same invariants. - * - * Layer (a): synchronous reject matrix — prevalidate and req_begin checks that - * fire BEFORE or WITHOUT a live tunnel. - * - * Layer (b): body-flow over the REAL loopback H3 fixture (same fixture as - * test_gw_reconnect.c) — fake sink records resp_head/resp_body/resp_finish/ - * resp_abort/resume_read; assertions are byte-exact. - * - * Cases covered: - * a1 dup X-Mq-* (two X-Mq-Auth) → MQ_GW_REJ_DUP_CONTROL, 400 - * a2 dup X-Mq-* (mixed case X-Mq-Auth + x-mq-auth) → same - * a3 missing X-Mq-Auth → MQ_GW_REJ_MISSING_AUTH, 400 - * a4 bad auth format ("Token" not "Bearer ") → MQ_GW_REJ_BAD_AUTH, 400 - * a5 malformed auth (missing trailing token) → MQ_GW_REJ_BAD_AUTH, 400 - * a6 valid headers → MQ_GW_OK - * a7 tunnel-unavailable: req_begin on a dead-tunnel gw_client - * → NULL + MQ_GW_REJ_TUNNEL_UNAVAIL, 502 - * - * b1 echo roundtrip: req_begin + body + body_done via fake sink; pump until - * resp_head/resp_body/resp_finish fire; body byte-exact - * b2 chunked (no content-length) response: resp_head reports MQ_GW_BODY_STREAM - * b3 upload backpressure: mq_gw_client_req_body returns -1 (pause) for a large - * upload; resume_read fires later; upload completes - * b4 download highwater: resp_body returns 0 (highwater) on the 1st 16-KiB - * chunk of a 50-KiB body (fin==0 guaranteed); req_drained resumes; full - * body delivered byte-exact. Guard: resp_finish must NOT have fired before - * req_drained is called — test FAILs if req_drained is a no-op. - * b5 abort (intake-first): req_aborted mid-flight; clean teardown, no leak/UAF - * b6a abort (tunnel-first, truncation path): server sends 200+64B+RESET; since - * resp_started==true h3_on_close calls resp_abort (not resp_finish); - * 64 partial bytes are delivered before the reset. - * b6b abort (tunnel-first, before-head path): server sends RESET before any - * response headers; h3_on_close synthesizes a clean 502 via resp_finish - * (not resp_abort); status==502, body_len==0. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "gateway/mq_gw_client.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_h3.h" -#include "transport/mq_transport.h" - -#include -#include - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -/* ── time / port helpers ─────────────────────────────────────────────────────*/ -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -static void -pump_a_bit(struct event_base *base, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (now_ms() < deadline) - event_base_loop(base, EVLOOP_NONBLOCK); -} - -/* ── fake sink (mq_gw_sink_ops_t recording adapter) ──────────────────────────*/ - -typedef struct { - /* resp_head state */ - int resp_head_called; - int resp_status; - mq_gw_body_mode_t body_mode; - - /* resp_body state */ - int resp_body_called; - uint8_t *body_buf; /* heap-allocated accumulation (cleared to cap on init) */ - size_t body_cap; - size_t body_len; /* total bytes delivered via resp_body so far */ - int highwater_at; /* if > 0, return 0 (highwater) on the n-th resp_body call */ - int highwater_fired; /* 1 once highwater path was taken */ - mq_gw_xreq_t *xreq; /* for calling mq_gw_client_req_drained from resp_body */ - - /* resp_finish / resp_abort */ - int resp_finish_called; - int resp_abort_called; - - /* resume_read count (upload backpressure released) */ - int resume_read_count; -} fake_sink_t; - -static int -fake_resp_head(void *u, int status, const mq_h3_header_t *hs, size_t n, - mq_gw_body_mode_t body_mode) -{ - (void)hs; - (void)n; - fake_sink_t *s = (fake_sink_t *)u; - s->resp_head_called++; - s->resp_status = status; - s->body_mode = body_mode; - return 0; -} - -static int -fake_resp_body(void *u, const uint8_t *p, size_t len) -{ - fake_sink_t *s = (fake_sink_t *)u; - s->resp_body_called++; - - /* Check highwater trigger: if we're at the Nth call, signal highwater. */ - if (s->highwater_at > 0 && s->resp_body_called == s->highwater_at) { - s->highwater_fired = 1; - /* Still accumulate the bytes (the production code has already delivered - * them to us; returning 0 means "pause" not "discard"). */ - if (s->body_buf && s->body_len + len <= s->body_cap) { - memcpy(s->body_buf + s->body_len, p, len); - s->body_len += len; - } - /* Signal highwater to the core: it will stop calling resp_body and wait - * for mq_gw_client_req_drained. We schedule drained via the event loop - * indirectly — the test pumps the loop and calls drained after this. */ - return 0; /* 0 = highwater */ - } - - if (s->body_buf && s->body_len + len <= s->body_cap) { - memcpy(s->body_buf + s->body_len, p, len); - } - s->body_len += len; - return 1; /* >0 = accept */ -} - -static void -fake_resp_finish(void *u) -{ - fake_sink_t *s = (fake_sink_t *)u; - s->resp_finish_called++; -} - -static void -fake_resp_abort(void *u) -{ - fake_sink_t *s = (fake_sink_t *)u; - s->resp_abort_called++; -} - -static void -fake_resume_read(void *u) -{ - fake_sink_t *s = (fake_sink_t *)u; - s->resume_read_count++; -} - -static mq_gw_sink_ops_t -fake_sink_ops(void) -{ - mq_gw_sink_ops_t ops; - memset(&ops, 0, sizeof(ops)); - ops.resp_head = fake_resp_head; - ops.resp_body = fake_resp_body; - ops.resp_finish = fake_resp_finish; - ops.resp_abort = fake_resp_abort; - ops.resume_read = fake_resume_read; - return ops; -} - -/* ── fake H3 server (minimal — echoes the upload body or sends a fixed body) ─*/ - -#define SRV_MAX_HDRS 32 -#define SRV_HDR_LEN 512 - -typedef struct { - char name[SRV_HDR_LEN]; - char value[SRV_HDR_LEN]; -} srv_hdr_t; - -/* Scenario controls (set before each test case) */ -enum { - SC_200_CL = 0, /* 200 + content-length + g_dl_body */ - SC_200_NOCL = 1, /* 200 without content-length (MQ_GW_BODY_STREAM) */ - SC_ECHO_UPLOAD = 2, /* 200 + echo the received upload body back */ - SC_RESET_MID = 3, /* partial body then reset (triggers resp_abort) */ - SC_HANG = 4, /* receive everything but NEVER respond */ - SC_RESET_BEFORE_HEAD = 5, /* reset WITHOUT sending any response headers */ -}; - -typedef struct { - mq_h3_req_t *req; - int got_headers; - int saw_fin; - - srv_hdr_t hdrs[SRV_MAX_HDRS]; - int n_hdrs; - - uint8_t body[256 * 1024]; - size_t body_len; - - int responded; - - const uint8_t *snd; - size_t snd_total; - size_t snd_off; - int snd_fin_done; - - int scenario; - int request_count; -} gw_srv_t; - -static gw_srv_t g_srv; -static mq_h3_conn_t *g_srv_conn; - -static const uint8_t *g_dl_body; -static size_t g_dl_body_len; - -static void -srv_capture_hdr(const char *n, size_t nl, const char *v, size_t vl, void *u) -{ - gw_srv_t *s = (gw_srv_t *)u; - if (s->n_hdrs >= SRV_MAX_HDRS) return; - srv_hdr_t *h = &s->hdrs[s->n_hdrs++]; - size_t cn = nl < SRV_HDR_LEN - 1 ? nl : SRV_HDR_LEN - 1; - size_t cv = vl < SRV_HDR_LEN - 1 ? vl : SRV_HDR_LEN - 1; - memcpy(h->name, n, cn); - h->name[cn] = '\0'; - memcpy(h->value, v, cv); - h->value[cv] = '\0'; -} - -static void -srv_body_pump(gw_srv_t *s) -{ - if (!s->req || s->snd_fin_done) return; - while (s->snd_off < s->snd_total) { - long acc = mq_h3_req_send_body(s->req, s->snd + s->snd_off, - s->snd_total - s->snd_off, 1); - if (acc <= 0) return; - s->snd_off += (size_t)acc; - if (s->snd_off >= s->snd_total) { - s->snd_fin_done = 1; - return; - } - } - if (s->snd_total == 0) { - mq_h3_req_finish(s->req); - s->snd_fin_done = 1; - } -} - -static void -srv_respond(gw_srv_t *s) -{ - if (s->responded || !s->req) return; - - if (s->scenario == SC_HANG) return; /* never respond */ - - s->responded = 1; - - if (s->scenario == SC_RESET_BEFORE_HEAD) { - /* Reset immediately — never send response headers. h3_on_close on the - * client will fire with resp_started==false, so the core synthesizes a - * clean 502 via resp_head+resp_finish (not resp_abort). */ - mq_h3_req_reset(s->req); - s->req = NULL; - return; - } - - if (s->scenario == SC_RESET_MID) { - /* Send 200 headers + partial body, but DO NOT reset here. The test - * pumps until the client processes the headers (resp_head_called==1), - * THEN calls mq_h3_req_reset(g_srv.req) directly. This guarantees that - * resp_started==true when h3_on_close fires, so the truncation branch - * (resp_abort) is taken — not the synthesized-502 branch. - * - * Set snd_fin_done=1 to prevent srv_body_pump (called from - * srv_on_req_write) from accidentally sending a fin on the stream - * while the test is waiting to issue the reset. */ - mq_h3_header_t h[] = {{":status", "200"}}; - mq_h3_req_send_headers(s->req, h, 1, 0); - static const uint8_t part[64] = {'P'}; - mq_h3_req_send_body(s->req, part, sizeof(part), 0); - s->snd_fin_done = 1; /* suppress any further writes from write-ready cb */ - /* NOTE: no reset here — the test drives the reset after confirming - * the client has seen the headers. */ - return; - } - - if (s->scenario == SC_ECHO_UPLOAD) { - char cl[32]; - snprintf(cl, sizeof(cl), "%zu", s->body_len); - mq_h3_header_t h[] = {{":status", "200"}, {"content-length", cl}}; - mq_h3_req_send_headers(s->req, h, 2, 0); - s->snd = s->body; - s->snd_total = s->body_len; - s->snd_off = 0; - srv_body_pump(s); - return; - } - - if (s->scenario == SC_200_NOCL) { - mq_h3_header_t h[] = {{":status", "200"}}; - mq_h3_req_send_headers(s->req, h, 1, 0); - s->snd = g_dl_body; - s->snd_total = g_dl_body_len; - s->snd_off = 0; - srv_body_pump(s); - return; - } - - /* SC_200_CL */ - char cl[32]; - snprintf(cl, sizeof(cl), "%zu", g_dl_body_len); - mq_h3_header_t h[] = {{":status", "200"}, {"content-length", cl}}; - mq_h3_req_send_headers(s->req, h, 2, 0); - s->snd = g_dl_body; - s->snd_total = g_dl_body_len; - s->snd_off = 0; - srv_body_pump(s); -} - -static void -srv_on_req_read(mq_h3_req_t *r, int flag, void *user) -{ - gw_srv_t *s = (gw_srv_t *)user; - if (flag & (XQC_REQ_NOTIFY_READ_HEADER | XQC_REQ_NOTIFY_READ_TRAILER)) { - int fin = 0; - int n = mq_h3_req_recv_headers(r, srv_capture_hdr, s, &fin); - if (n >= 0) s->got_headers = 1; - if (fin) s->saw_fin = 1; - } - /* Drain body, discarding bytes once the fixed buffer is full (handles large - * uploads like the 20 MiB backpressure test where we don't need to echo). */ - static uint8_t discard[64 * 1024]; - for (;;) { - int fin = 0; - size_t avail = sizeof(s->body) - s->body_len; - uint8_t *dst = avail > 0 ? s->body + s->body_len : discard; - size_t cap = avail > 0 ? avail : sizeof(discard); - long n = mq_h3_req_recv_body(r, dst, cap, &fin); - if (n > 0 && avail > 0) s->body_len += (size_t)n; - if (fin) s->saw_fin = 1; - if (n <= 0) break; - } - if (s->saw_fin && !s->responded) srv_respond(s); -} - -static void -srv_on_req_write(mq_h3_req_t *r, void *user) -{ - (void)r; - gw_srv_t *s = (gw_srv_t *)user; - if (!s->responded) return; - srv_body_pump(s); -} - -static void -srv_on_req_close(mq_h3_req_t *r, void *user) -{ - (void)r; - gw_srv_t *s = (gw_srv_t *)user; - s->req = NULL; -} - -static void -srv_on_new_req(mq_h3_req_t *r, void *user) -{ - (void)user; - /* Reset per-request state for each new request (supports b5/b6 sub-cases). */ - g_srv.req = r; - g_srv.got_headers = 0; - g_srv.saw_fin = 0; - g_srv.n_hdrs = 0; - g_srv.body_len = 0; - g_srv.responded = 0; - g_srv.snd = NULL; - g_srv.snd_total = 0; - g_srv.snd_off = 0; - g_srv.snd_fin_done = 0; - g_srv.request_count++; - mq_h3_req_set_cbs(r, srv_on_req_read, srv_on_req_write, srv_on_req_close, &g_srv); -} - -static void -srv_on_new_conn(mq_h3_conn_t *c, void *user) -{ - (void)user; - g_srv_conn = c; -} - -/* ── relay fixture: gw_client WITHOUT a fetch adapter/listener ─────────────── - * - * This is the key difference from test_gw_client.c: we do NOT wire up - * mq_gw_fetch_adapter or mq_fetch_listener. The test drives the neutral - * intake boundary (prevalidate / req_begin / req_body / req_body_done / - * req_aborted / req_drained) directly, with a fake_sink_t as the sink. - */ -typedef struct { - struct event_base *base; - mq_transport_t *srv_t; - mq_transport_t *cli_t; - mq_runtime_t *srv_rt; - mq_runtime_t *cli_rt; - mq_h3_t *srv_h3; - mq_h3_t *cli_h3; - mq_gw_client_t *gw; -} relay_fixture_t; - -static int -relay_fixture_up(relay_fixture_t *f, const char *token) -{ - memset(f, 0, sizeof(*f)); - memset(&g_srv, 0, sizeof(g_srv)); - g_srv_conn = NULL; - - f->base = event_base_new(); - if (!f->base) return -1; - - uint16_t srv_port = reserve_udp_port(); - if (!srv_port) return -1; - - f->srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - if (!f->srv_t) return -1; - f->srv_rt = mq_runtime_new(f->srv_t, f->base); - if (!f->srv_rt) return -1; - - xqc_conn_settings_t srv_settings; - memset(&srv_settings, 0, sizeof(srv_settings)); - srv_settings.proto_version = XQC_VERSION_V1; - srv_settings.pacing_on = 1; - mq_conn_apply_mp_settings(&srv_settings, /*is_server=*/1, MQ_CC_BBR2); - xqc_server_set_conn_settings(mq_transport_xqc(f->srv_t), &srv_settings); - - f->srv_h3 = mq_h3_init(f->srv_t, srv_on_new_conn, srv_on_new_req, NULL); - if (!f->srv_h3) return -1; - if (mq_runtime_open_udp_path(f->srv_rt, "127.0.0.1", srv_port) != 0) return -1; - - f->cli_t = mq_transport_new(0); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - if (!f->cli_rt) return -1; - if (mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0) != 0) return -1; - f->cli_h3 = mq_h3_init(f->cli_t, NULL, NULL, NULL); - if (!f->cli_h3) return -1; - - f->gw = mq_gw_client_new(f->cli_t, f->cli_rt, f->cli_h3, "127.0.0.1", srv_port, token, - MQ_CC_BBR2, /*keepalive_idle_ms=*/0, /*reconnect_enabled=*/0, - /*reconnect_max_backoff_ms=*/30000); - if (!f->gw) return -1; - - /* Settle the tunnel connection (mirrors test_gw_client's fixture). */ - pump_a_bit(f->base, 300); - return 0; -} - -/* Dead-tunnel fixture: gw_client pointed at a closed UDP port. */ -static int -relay_fixture_up_dead(relay_fixture_t *f) -{ - memset(f, 0, sizeof(*f)); - memset(&g_srv, 0, sizeof(g_srv)); - - f->base = event_base_new(); - if (!f->base) return -1; - - uint16_t dead = reserve_udp_port(); /* released immediately — nothing listens */ - if (!dead) return -1; - - f->cli_t = mq_transport_new(0); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - if (!f->cli_rt) return -1; - if (mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0) != 0) return -1; - f->cli_h3 = mq_h3_init(f->cli_t, NULL, NULL, NULL); - if (!f->cli_h3) return -1; - f->gw = mq_gw_client_new(f->cli_t, f->cli_rt, f->cli_h3, "127.0.0.1", dead, "tok", - MQ_CC_BBR2, /*keepalive_idle_ms=*/0, /*reconnect_enabled=*/0, - /*reconnect_max_backoff_ms=*/30000); - if (!f->gw) return -1; - return 0; -} - -static void -relay_fixture_down(relay_fixture_t *f) -{ - /* SANCTIONED TEARDOWN ORDER: gw_client FIRST, then mq_h3_free, then - * mq_transport_free (both client and server sides). */ - if (f->gw) mq_gw_client_free(f->gw); - if (f->cli_h3) mq_h3_free(f->cli_h3); - if (f->srv_h3) mq_h3_free(f->srv_h3); - if (f->cli_t) mq_transport_free(f->cli_t); - if (f->srv_t) mq_transport_free(f->srv_t); - if (f->cli_rt) mq_runtime_free(f->cli_rt); - if (f->srv_rt) mq_runtime_free(f->srv_rt); - if (f->base) event_base_free(f->base); -} - -/* Pump until the flag becomes non-zero or the budget elapses. */ -static void -pump_until(struct event_base *base, volatile int *flag, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (!*flag && now_ms() < deadline) - event_base_loop(base, EVLOOP_NONBLOCK); -} - -/* ── Layer (a): synchronous reject matrix ───────────────────────────────────── - * - * These tests call mq_gw_client_prevalidate directly with minimal header - * arrays. No loopback H3 fixture is needed for a1–a6; a7 uses a dead-tunnel - * fixture to exercise the tunnel-liveness check in req_begin. - */ - -/* a1: two X-Mq-Auth headers (identical name) → MQ_GW_REJ_DUP_CONTROL, 400 */ -static void -test_a1_dup_xmq_same_name(void) -{ - relay_fixture_t f; - if (relay_fixture_up_dead(&f) != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Bearer tok"}, - {"x-mq-auth", "Bearer tok2"}, - }; - int status = 0; - mq_gw_reject_reason_t r = mq_gw_client_prevalidate(f.gw, hdrs, 2, &status); - MQ_CHECK_EQ_INT(r, MQ_GW_REJ_DUP_CONTROL); - MQ_CHECK_EQ_INT(status, 400); - relay_fixture_down(&f); -} - -/* a2: X-Mq-Auth + x-mq-auth (mixed case) → MQ_GW_REJ_DUP_CONTROL, 400 */ -static void -test_a2_dup_xmq_mixed_case(void) -{ - relay_fixture_t f; - if (relay_fixture_up_dead(&f) != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - mq_h3_header_t hdrs[] = { - {"X-Mq-Auth", "Bearer tok"}, - {"x-mq-auth", "Bearer tok2"}, - }; - int status = 0; - mq_gw_reject_reason_t r = mq_gw_client_prevalidate(f.gw, hdrs, 2, &status); - MQ_CHECK_EQ_INT(r, MQ_GW_REJ_DUP_CONTROL); - MQ_CHECK_EQ_INT(status, 400); - relay_fixture_down(&f); -} - -/* a3: missing X-Mq-Auth → MQ_GW_REJ_MISSING_AUTH, 400 */ -static void -test_a3_missing_auth(void) -{ - relay_fixture_t f; - if (relay_fixture_up_dead(&f) != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - mq_h3_header_t hdrs[] = { - {"x-mq-target", "https://example.test/x"}, - }; - int status = 0; - mq_gw_reject_reason_t r = mq_gw_client_prevalidate(f.gw, hdrs, 1, &status); - MQ_CHECK_EQ_INT(r, MQ_GW_REJ_MISSING_AUTH); - MQ_CHECK_EQ_INT(status, 400); - relay_fixture_down(&f); -} - -/* a4: bad auth format ("Token x" instead of "Bearer x") → MQ_GW_REJ_BAD_AUTH, 400 */ -static void -test_a4_bad_auth_wrong_scheme(void) -{ - relay_fixture_t f; - if (relay_fixture_up_dead(&f) != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Token notabearer"}, - }; - int status = 0; - mq_gw_reject_reason_t r = mq_gw_client_prevalidate(f.gw, hdrs, 1, &status); - MQ_CHECK_EQ_INT(r, MQ_GW_REJ_BAD_AUTH); - MQ_CHECK_EQ_INT(status, 400); - relay_fixture_down(&f); -} - -/* a5: malformed auth ("Bearer " with no trailing token) → MQ_GW_REJ_BAD_AUTH, 400 */ -static void -test_a5_bad_auth_empty_token(void) -{ - relay_fixture_t f; - if (relay_fixture_up_dead(&f) != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Bearer "}, - }; - int status = 0; - mq_gw_reject_reason_t r = mq_gw_client_prevalidate(f.gw, hdrs, 1, &status); - MQ_CHECK_EQ_INT(r, MQ_GW_REJ_BAD_AUTH); - MQ_CHECK_EQ_INT(status, 400); - relay_fixture_down(&f); -} - -/* a6: valid headers → MQ_GW_OK */ -static void -test_a6_valid_headers(void) -{ - relay_fixture_t f; - if (relay_fixture_up_dead(&f) != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Bearer validtoken"}, - {"x-mq-target", "https://example.test/x"}, - }; - int status = 99; - mq_gw_reject_reason_t r = mq_gw_client_prevalidate(f.gw, hdrs, 2, &status); - MQ_CHECK_EQ_INT(r, MQ_GW_OK); - /* status is unmodified on OK */ - MQ_CHECK_EQ_INT(status, 99); - relay_fixture_down(&f); -} - -/* a7: req_begin on a dead-tunnel gw_client → NULL + MQ_GW_REJ_TUNNEL_UNAVAIL, 502 */ -static void -test_a7_tunnel_unavail_req_begin(void) -{ - relay_fixture_t f; - if (relay_fixture_up_dead(&f) != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - - /* Pump briefly so the connect fails (no server at that port). */ - pump_a_bit(f.base, 200); - - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Bearer tok"}, - }; - mq_gw_req_head_t head; - memset(&head, 0, sizeof(head)); - head.method = "GET"; - head.scheme = "https"; - head.authority = "example.test"; - head.path = "/x"; - head.headers = hdrs; - head.n_headers = 1; - head.content_length = 0; - - fake_sink_t sink_state; - memset(&sink_state, 0, sizeof(sink_state)); - mq_gw_sink_ops_t ops = fake_sink_ops(); - - int err_status = 0; - mq_gw_reject_reason_t reason = MQ_GW_OK; - mq_gw_xreq_t *xr = - mq_gw_client_req_begin(f.gw, &head, &ops, &sink_state, &err_status, &reason); - MQ_CHECK(xr == NULL); - MQ_CHECK_EQ_INT(reason, MQ_GW_REJ_TUNNEL_UNAVAIL); - MQ_CHECK_EQ_INT(err_status, 502); - - relay_fixture_down(&f); -} - -/* ── Layer (b): body-flow with the real loopback H3 fixture ────────────────── - * - * Helpers that build a well-formed mq_gw_req_head_t and dispatch req_begin via - * the fake sink. - */ - -/* Build a minimal well-formed request head. Caller owns the headers array. */ -static mq_gw_req_head_t -make_head(const mq_h3_header_t *hdrs, size_t n, int64_t content_length) -{ - mq_gw_req_head_t h; - memset(&h, 0, sizeof(h)); - h.method = "GET"; - h.scheme = "https"; - h.authority = "example.test"; - h.path = "/x"; - h.headers = hdrs; - h.n_headers = n; - h.content_length = content_length; - return h; -} - -/* b1: echo roundtrip — req_begin + body + body_done; fake sink records - * resp_head(200, MQ_GW_BODY_CONTENT_LENGTH) + resp_body + resp_finish. */ -static void -test_b1_echo_roundtrip(void) -{ - relay_fixture_t f; - if (relay_fixture_up(&f, "sekrit") != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - - static const uint8_t UPLOAD[] = "hello-from-b1-upload"; - static const size_t UL = sizeof(UPLOAD) - 1; - - g_srv.scenario = SC_ECHO_UPLOAD; - - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Bearer sekrit"}, - }; - mq_gw_req_head_t head = make_head(hdrs, 1, (int64_t)UL); - head.method = "POST"; - - fake_sink_t sink_state; - memset(&sink_state, 0, sizeof(sink_state)); - uint8_t body_out[256] = {0}; - sink_state.body_buf = body_out; - sink_state.body_cap = sizeof(body_out); - mq_gw_sink_ops_t ops = fake_sink_ops(); - - int err_status = 0; - mq_gw_reject_reason_t reason = MQ_GW_OK; - mq_gw_xreq_t *xr = - mq_gw_client_req_begin(f.gw, &head, &ops, &sink_state, &err_status, &reason); - MQ_CHECK(xr != NULL); - if (!xr) { - relay_fixture_down(&f); - return; - } - - /* Send the upload body. */ - int rc = mq_gw_client_req_body(xr, UPLOAD, UL); - MQ_CHECK(rc == 0); /* accepted (no spill yet for this small body) */ - mq_gw_client_req_body_done(xr); - - /* Pump until resp_finish fires. */ - pump_until(f.base, &sink_state.resp_finish_called, 8000); - - MQ_CHECK_EQ_INT(sink_state.resp_head_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_status, 200); - MQ_CHECK_EQ_INT(sink_state.body_mode, MQ_GW_BODY_CONTENT_LENGTH); - MQ_CHECK_EQ_INT(sink_state.resp_finish_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_abort_called, 0); - MQ_CHECK_EQ_INT((int)sink_state.body_len, (int)UL); - MQ_CHECK_MEM(body_out, UPLOAD, UL); - MQ_CHECK_EQ_INT(g_srv.request_count, 1); - - relay_fixture_down(&f); -} - -/* b2: response without content-length → body_mode == MQ_GW_BODY_STREAM */ -static void -test_b2_stream_body_mode(void) -{ - relay_fixture_t f; - if (relay_fixture_up(&f, "sekrit") != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - - static const uint8_t DL[] = "streaming-body-no-cl-0123456789"; - g_dl_body = DL; - g_dl_body_len = sizeof(DL) - 1; - g_srv.scenario = SC_200_NOCL; - - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Bearer sekrit"}, - }; - mq_gw_req_head_t head = make_head(hdrs, 1, 0); - - fake_sink_t sink_state; - memset(&sink_state, 0, sizeof(sink_state)); - uint8_t body_out[512] = {0}; - sink_state.body_buf = body_out; - sink_state.body_cap = sizeof(body_out); - mq_gw_sink_ops_t ops = fake_sink_ops(); - - int err_status = 0; - mq_gw_reject_reason_t reason = MQ_GW_OK; - mq_gw_xreq_t *xr = - mq_gw_client_req_begin(f.gw, &head, &ops, &sink_state, &err_status, &reason); - MQ_CHECK(xr != NULL); - if (!xr) { - relay_fixture_down(&f); - return; - } - - mq_gw_client_req_body_done(xr); - - pump_until(f.base, &sink_state.resp_finish_called, 8000); - - MQ_CHECK_EQ_INT(sink_state.resp_head_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_status, 200); - MQ_CHECK_EQ_INT(sink_state.body_mode, MQ_GW_BODY_STREAM); - MQ_CHECK_EQ_INT(sink_state.resp_finish_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_abort_called, 0); - MQ_CHECK_EQ_INT((int)sink_state.body_len, (int)(sizeof(DL) - 1)); - MQ_CHECK_MEM(body_out, DL, sizeof(DL) - 1); - - relay_fixture_down(&f); -} - -/* b3: upload backpressure — exercise the req_body → -1 (pause) → resume_read - * path via a body that fills xquic's stream flow-control window. - * - * The gw_client spill buffer is 256 KiB; `mq_gw_client_req_body` returns -1 - * when xquic's `mq_h3_req_send_body` returns 0 (EAGAIN) for ANY part of the - * chunk, which triggers spill accumulation and a pause signal. The xquic - * initial stream send window is the remote peer's max_stream_data (16 MB per - * XQC_MAX_RECV_WINDOW). Exhausting it on loopback — without ACKs/window - * updates from the event loop — requires feeding more than 16 MB in a tight - * loop before any pump iteration. - * - * We do this: allocate 20 MB, call req_body in 64-KiB chunks WITHOUT pumping - * the loop, until we get a -1. The initial window (no prior pumping) is just - * the data the kernel TCP-lookalike can buffer in xquic's send queue before - * the peer's credit is used up. On loopback this hits the 16-MB ceiling well - * before 20 MB is sent, giving a deterministic -1. - * - * After the -1: pump until resume_read fires (core drained its spill); send - * the remainder; body_done; pump until resp_finish — asserts the upload - * completes byte-exact. */ -static void -test_b3_upload_backpressure(void) -{ - relay_fixture_t f; - if (relay_fixture_up(&f, "sekrit") != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - - /* 20 MB upload — exceeds the 16 MB xquic initial stream window so we are - * guaranteed at least one -1 from req_body before pumping the loop. */ - const size_t UPLOAD_SIZE = 20 * 1024 * 1024; - uint8_t *upload = malloc(UPLOAD_SIZE); - MQ_CHECK(upload != NULL); - if (!upload) { - relay_fixture_down(&f); - return; - } - for (size_t i = 0; i < UPLOAD_SIZE; i++) - upload[i] = (uint8_t)((i * 131 + 7) & 0xff); - - /* Server responds with a small fixed body (not an echo), so we don't need - * the server to buffer the entire 20 MB upload. The server's on_req_read - * accumulates whatever fits in its 256-KiB body buffer and then responds - * once saw_fin is set (the tunnel delivers a fin when body_done is called). */ - static const uint8_t DL_B3[] = "b3-response-ok"; - g_dl_body = DL_B3; - g_dl_body_len = sizeof(DL_B3) - 1; - g_srv.scenario = SC_200_CL; - - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Bearer sekrit"}, - }; - mq_gw_req_head_t head = make_head(hdrs, 1, (int64_t)UPLOAD_SIZE); - head.method = "POST"; - - fake_sink_t sink_state; - memset(&sink_state, 0, sizeof(sink_state)); - uint8_t body_out[64] = {0}; - sink_state.body_buf = body_out; - sink_state.body_cap = sizeof(body_out); - mq_gw_sink_ops_t ops = fake_sink_ops(); - - int err_status = 0; - mq_gw_reject_reason_t reason = MQ_GW_OK; - mq_gw_xreq_t *xr = - mq_gw_client_req_begin(f.gw, &head, &ops, &sink_state, &err_status, &reason); - MQ_CHECK(xr != NULL); - if (!xr) { - free(upload); - relay_fixture_down(&f); - return; - } - - /* Feed body in 64-KiB chunks WITHOUT pumping the event loop between calls - * so the stream window is exhausted and at least one req_body returns -1. */ - size_t sent = 0; - int got_pause = 0; - const size_t CHUNK = 64 * 1024; - while (sent < UPLOAD_SIZE) { - size_t want = UPLOAD_SIZE - sent; - if (want > CHUNK) want = CHUNK; - int ret = mq_gw_client_req_body(xr, upload + sent, want); - sent += want; - if (ret < 0) { - got_pause = 1; - break; - } - } - - MQ_CHECK_EQ_INT(got_pause, 1); - MQ_CHECK_EQ_INT(sink_state.resume_read_count, 0); /* not yet */ - - /* Pump to let H3 drain the spill buffer; resume_read fires once drained. */ - uint64_t deadline = now_ms() + 30000; - while (sink_state.resume_read_count == 0 && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK(sink_state.resume_read_count >= 1); - - /* Send the remainder (post-resume), finish the upload, wait for echo. */ - while (sent < UPLOAD_SIZE) { - size_t want = UPLOAD_SIZE - sent; - if (want > CHUNK) want = CHUNK; - int ret = mq_gw_client_req_body(xr, upload + sent, want); - sent += want; - /* After resume we may pause again for more data; pump loop between calls - * if we get a -1 to let the spill drain before continuing. */ - if (ret < 0) { - pump_a_bit(f.base, 100); - } - } - mq_gw_client_req_body_done(xr); - - pump_until(f.base, &sink_state.resp_finish_called, 60000); - - MQ_CHECK_EQ_INT(sink_state.resp_head_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_status, 200); - MQ_CHECK_EQ_INT(sink_state.resp_finish_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_abort_called, 0); - /* Response body is the small fixed download, not the echo. */ - MQ_CHECK_EQ_INT((int)sink_state.body_len, (int)(sizeof(DL_B3) - 1)); - MQ_CHECK_MEM(body_out, DL_B3, sizeof(DL_B3) - 1); - - free(upload); - relay_fixture_down(&f); -} - -/* b4: download highwater — resp_body returns 0 (highwater signal) on the 1st - * call; we call mq_gw_client_req_drained; the download resumes and - * resp_finish fires with all bytes delivered. - * - * ROBUSTNESS: the download body is 50 KiB — larger than 3× MQ_GW_DOWNLOAD_CHUNK - * (16 KiB). This guarantees that when the first 16-KiB chunk is delivered - * and the fake sink returns 0 (highwater), there is STILL pending body with - * fin==0; the download_pump early-return path (read_deferred, !fin) is taken - * and the response can ONLY complete after mq_gw_client_req_drained re-kicks - * download_pump. With a tiny body (< 16 KiB) the entire body + fin arrive in - * the first recv, so the fin fall-through calls gw_local_finish immediately - * even when read_deferred is set — neutering req_drained would still pass. - * - * REGRESSION GUARD: after highwater fires and BEFORE req_drained is called, - * we assert resp_finish_called==0. If req_drained is a no-op the download - * never resumes, resp_finish never fires, and pump_until times out → b4 FAILs. - */ -static void -test_b4_download_highwater(void) -{ - relay_fixture_t f; - if (relay_fixture_up(&f, "sekrit") != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - - /* 50 KiB download body — spans >3 MQ_GW_DOWNLOAD_CHUNK (16 KiB) reads. - * Use a deterministic pattern so byte-exact comparison catches corruption. */ - static const size_t DL_SIZE = 50 * 1024; - static uint8_t dl_body[50 * 1024]; - for (size_t i = 0; i < DL_SIZE; i++) - dl_body[i] = (uint8_t)((i * 97 + 13) & 0xff); - - g_dl_body = dl_body; - g_dl_body_len = DL_SIZE; - g_srv.scenario = SC_200_CL; - - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Bearer sekrit"}, - }; - mq_gw_req_head_t head = make_head(hdrs, 1, 0); - - fake_sink_t sink_state; - memset(&sink_state, 0, sizeof(sink_state)); - uint8_t *body_out = malloc(DL_SIZE); - MQ_CHECK(body_out != NULL); - if (!body_out) { - relay_fixture_down(&f); - return; - } - memset(body_out, 0, DL_SIZE); - sink_state.body_buf = body_out; - sink_state.body_cap = DL_SIZE; - /* Signal highwater on the 1st resp_body call. Because the body is 50 KiB, - * the first chunk (16 KiB) arrives with fin==0; download_pump sets - * read_deferred and returns WITHOUT calling gw_local_finish. The response - * can ONLY complete after mq_gw_client_req_drained re-kicks download_pump. */ - sink_state.highwater_at = 1; - mq_gw_sink_ops_t ops = fake_sink_ops(); - - int err_status = 0; - mq_gw_reject_reason_t reason = MQ_GW_OK; - mq_gw_xreq_t *xr = - mq_gw_client_req_begin(f.gw, &head, &ops, &sink_state, &err_status, &reason); - MQ_CHECK(xr != NULL); - if (!xr) { - free(body_out); - relay_fixture_down(&f); - return; - } - - sink_state.xreq = xr; - mq_gw_client_req_body_done(xr); - - /* Pump until the highwater fires (1st resp_body call returns 0). */ - uint64_t deadline = now_ms() + 8000; - while (!sink_state.highwater_fired && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(sink_state.highwater_fired, 1); - - /* REGRESSION GUARD: resp_finish must NOT have fired yet — download_pump is - * stalled at read_deferred. If this assertion fails it means the body was - * small enough to arrive with fin==1 in the first chunk and req_drained is - * not actually needed (false-confidence failure). */ - MQ_CHECK_EQ_INT(sink_state.resp_finish_called, 0); - - /* Call drained to un-stall download_pump. If this is a no-op the download - * never resumes and the pump_until below will time out → test FAILs. */ - mq_gw_client_req_drained(xr); - - /* Pump until resp_finish fires. */ - pump_until(f.base, &sink_state.resp_finish_called, 8000); - - MQ_CHECK_EQ_INT(sink_state.resp_head_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_status, 200); - MQ_CHECK_EQ_INT(sink_state.resp_finish_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_abort_called, 0); - MQ_CHECK_EQ_INT((int)sink_state.body_len, (int)DL_SIZE); - MQ_CHECK_MEM(body_out, dl_body, DL_SIZE); - - free(body_out); - relay_fixture_down(&f); -} - -/* b5: intake-first abort — call mq_gw_client_req_aborted mid-flight; clean - * teardown with no leak/UAF (ASan will catch). */ -static void -test_b5_intake_abort(void) -{ - relay_fixture_t f; - if (relay_fixture_up(&f, "sekrit") != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - - g_srv.scenario = SC_HANG; /* server receives but never responds */ - - /* Promise a large body but never finish the upload. */ - const size_t CL = 256 * 1024; - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Bearer sekrit"}, - }; - mq_gw_req_head_t head = make_head(hdrs, 1, (int64_t)CL); - head.method = "POST"; - - fake_sink_t sink_state; - memset(&sink_state, 0, sizeof(sink_state)); - mq_gw_sink_ops_t ops = fake_sink_ops(); - - int err_status = 0; - mq_gw_reject_reason_t reason = MQ_GW_OK; - mq_gw_xreq_t *xr = - mq_gw_client_req_begin(f.gw, &head, &ops, &sink_state, &err_status, &reason); - MQ_CHECK(xr != NULL); - if (!xr) { - relay_fixture_down(&f); - return; - } - - /* Send a small slice of the promised body. */ - uint8_t chunk[4096]; - memset(chunk, 'U', sizeof(chunk)); - mq_gw_client_req_body(xr, chunk, sizeof(chunk)); - - /* Pump to drive the H3 request live. */ - pump_a_bit(f.base, 300); - MQ_CHECK_EQ_INT(g_srv.request_count, 1); - - /* Abort from the intake side: the adapter (our test) owns the local-side - * lifetime and signals that the local peer died. */ - mq_gw_client_req_aborted(xr); - - /* Pump to let the core reset the H3 request and free the per-request state. - * ASan will catch any UAF or leak. */ - pump_a_bit(f.base, 300); - - /* Neither resp_finish nor resp_abort should have fired (the adapter signalled - * the local side died — no sink callbacks after that). */ - MQ_CHECK_EQ_INT(sink_state.resp_finish_called, 0); - MQ_CHECK_EQ_INT(sink_state.resp_abort_called, 0); - - relay_fixture_down(&f); -} - -/* b6a: tunnel-first abort — truncation path (resp_started == true). - * - * SC_RESET_MID sends 200 headers + 64 bytes of body (no reset yet). The - * test pumps until the CLIENT has processed the headers (resp_head_called==1), - * establishing resp_started==true, THEN calls mq_h3_req_reset(g_srv.req) - * directly. This guarantees h3_on_close fires with resp_started==true, so - * the truncation branch is taken: resp_abort (NOT resp_finish). - * - * Two-phase reset (pump-then-reset) eliminates the race where the RESET_STREAM - * frame arrives before the client event loop processes the HEADERS frame. - * - * Strict assertions: - * - resp_abort_called == 1 - * - resp_finish_called == 0 (no fake clean finish on a truncated response) - * - 64 partial-body bytes were received before the reset - * - * This test FAILS if h3_on_close takes the synthesized-502 branch instead. - */ -static void -test_b6a_tunnel_abort_truncation(void) -{ - relay_fixture_t f; - if (relay_fixture_up(&f, "sekrit") != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - - /* SC_RESET_MID: server sends 200 + 64-byte partial body, no fin, no reset. - * The test calls the reset after the client has seen the headers. */ - g_srv.scenario = SC_RESET_MID; - static const size_t PARTIAL_LEN = 64; - - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Bearer sekrit"}, - }; - mq_gw_req_head_t head = make_head(hdrs, 1, 0); - - fake_sink_t sink_state; - memset(&sink_state, 0, sizeof(sink_state)); - uint8_t body_out[512] = {0}; - sink_state.body_buf = body_out; - sink_state.body_cap = sizeof(body_out); - mq_gw_sink_ops_t ops = fake_sink_ops(); - - int err_status = 0; - mq_gw_reject_reason_t reason = MQ_GW_OK; - mq_gw_xreq_t *xr = - mq_gw_client_req_begin(f.gw, &head, &ops, &sink_state, &err_status, &reason); - MQ_CHECK(xr != NULL); - if (!xr) { - relay_fixture_down(&f); - return; - } - - mq_gw_client_req_body_done(xr); - - /* Phase 1: pump until the client has processed the 200 headers. - * At this point resp_started==true on the gw_req, so the next h3_on_close - * will take the truncation path (resp_abort). */ - pump_until(f.base, &sink_state.resp_head_called, 8000); - MQ_CHECK_EQ_INT(sink_state.resp_head_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_status, 200); - - /* Phase 2: reset the server stream now that the client has resp_started==true. - * g_srv.req is still live (SC_RESET_MID does not reset in srv_respond). */ - MQ_CHECK(g_srv.req != NULL); - if (g_srv.req) { - mq_h3_req_reset(g_srv.req); - g_srv.req = NULL; - } - - /* Pump until resp_abort or resp_finish fires. */ - uint64_t deadline = now_ms() + 8000; - while (!sink_state.resp_abort_called && !sink_state.resp_finish_called && - now_ms() < deadline) { - event_base_loop(f.base, EVLOOP_NONBLOCK); - } - - /* Strict truncation-path invariants: - * h3_on_close with resp_started==true → resp_abort, never resp_finish. */ - MQ_CHECK_EQ_INT(sink_state.resp_abort_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_finish_called, 0); - /* Partial body (64 bytes, first byte 'P', rest 0x00) was delivered before - * the reset arrived. */ - MQ_CHECK_EQ_INT((int)sink_state.body_len, (int)PARTIAL_LEN); - MQ_CHECK_EQ_INT(body_out[0], 'P'); - MQ_CHECK_EQ_INT(g_srv.request_count, 1); - - relay_fixture_down(&f); -} - -/* b6b: tunnel-first abort — synthesized-502 path (resp_started == false). - * - * SC_RESET_BEFORE_HEAD sends RESET_STREAM without any response headers. - * The client never sets resp_started; h3_on_close synthesizes a clean 502 - * via resp_head(502) + resp_finish (NOT resp_abort). - * - * Strict assertions: - * - resp_finish_called == 1 - * - resp_abort_called == 0 - * - status == 502 - * - body_len == 0 (synthesized response has Content-Length: 0) - */ -static void -test_b6b_tunnel_abort_before_head(void) -{ - relay_fixture_t f; - if (relay_fixture_up(&f, "sekrit") != 0) { - relay_fixture_down(&f); - MQ_CHECK(0); - return; - } - - g_srv.scenario = SC_RESET_BEFORE_HEAD; - - mq_h3_header_t hdrs[] = { - {"x-mq-auth", "Bearer sekrit"}, - }; - mq_gw_req_head_t head = make_head(hdrs, 1, 0); - - fake_sink_t sink_state; - memset(&sink_state, 0, sizeof(sink_state)); - uint8_t body_out[64] = {0}; - sink_state.body_buf = body_out; - sink_state.body_cap = sizeof(body_out); - mq_gw_sink_ops_t ops = fake_sink_ops(); - - int err_status = 0; - mq_gw_reject_reason_t reason = MQ_GW_OK; - mq_gw_xreq_t *xr = - mq_gw_client_req_begin(f.gw, &head, &ops, &sink_state, &err_status, &reason); - MQ_CHECK(xr != NULL); - if (!xr) { - relay_fixture_down(&f); - return; - } - - mq_gw_client_req_body_done(xr); - - /* Pump until either resp_abort or resp_finish fires. */ - uint64_t deadline = now_ms() + 8000; - while (!sink_state.resp_abort_called && !sink_state.resp_finish_called && - now_ms() < deadline) { - event_base_loop(f.base, EVLOOP_NONBLOCK); - } - - /* Strict synthesized-502-path invariants: - * h3_on_close with resp_started==false → resp_head(502)+resp_finish, never - * resp_abort. */ - MQ_CHECK_EQ_INT(sink_state.resp_finish_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_abort_called, 0); - MQ_CHECK_EQ_INT(sink_state.resp_head_called, 1); - MQ_CHECK_EQ_INT(sink_state.resp_status, 502); - MQ_CHECK_EQ_INT((int)sink_state.body_len, 0); - MQ_CHECK_EQ_INT(g_srv.request_count, 1); - - relay_fixture_down(&f); -} - -/* ── main ────────────────────────────────────────────────────────────────────*/ - -static void -run_all(void) -{ - signal(SIGPIPE, SIG_IGN); - - /* Layer (a): synchronous reject matrix */ - test_a1_dup_xmq_same_name(); - test_a2_dup_xmq_mixed_case(); - test_a3_missing_auth(); - test_a4_bad_auth_wrong_scheme(); - test_a5_bad_auth_empty_token(); - test_a6_valid_headers(); - test_a7_tunnel_unavail_req_begin(); - - /* Layer (b): body-flow over the real loopback H3 fixture */ - test_b1_echo_roundtrip(); - test_b2_stream_body_mode(); - test_b3_upload_backpressure(); - test_b4_download_highwater(); - test_b5_intake_abort(); - test_b6a_tunnel_abort_truncation(); - test_b6b_tunnel_abort_before_head(); -} - -MQ_TEST_MAIN(run_all()) diff --git a/tests/integration/test_gw_server.c b/tests/integration/test_gw_server.c deleted file mode 100644 index ae1f37f2..00000000 --- a/tests/integration/test_gw_server.c +++ /dev/null @@ -1,2349 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_gw_server.c — integration tests for mq_origin_curl (Task 4.2), the - * libcurl-multi origin client wired into a libevent loop. An in-process libevent - * evhttp server stands in for the upstream origin over plain loopback HTTP/1.1; - * the mq_origin_* API drives requests against it on the SAME event_base. - * - * Origin routes (127.0.0.1:0, ephemeral): - * GET /blob → 200, N bytes of a deterministic pattern + x-origin-tag header - * GET /slow → 200 (empty) after a 300ms evtimer (lets a request be aborted - * in-flight before the reply arrives) - * PUT /up → drain the request body, reply 200 with body "len=" - * (default) → 404 - * - * Cases: - * 1. GET 1 MiB: on_status 200 once; x-origin-tag via on_header; on_body total - * == 1 MiB byte-exact; on_done CURLE_OK, http_ver = HTTP/1.1. - * 2. download PAUSE/resume: on_body accepts 0 after the first chunk → no - * further on_body until mq_origin_resume_body; then byte-exact + OK. - * 3. upload PUT 256 KiB via pull_body (16 KiB chunks, PAUSE once mid-way then - * resume via mq_origin_resume_pull): origin replies "len=262144"; on_done OK. - * 4. NXDOMAIN → on_done CURLE_COULDNT_RESOLVE_HOST (== MQ_GW_CURL_RESOLVE). - * 5. connection refused → on_done CURLE_COULDNT_CONNECT (== MQ_GW_CURL_CONNECT). - * 6. abort mid-flight (GET /slow, abort before reply): no on_done, no crash. - * 7. 1xx skip: evhttp can't easily emit a 100-continue, so we unit-test the - * factored status-line parser (mq_origin_parse_status_line) directly with - * synthetic 1xx/final lines. Expect: suppression keeps 100s off the wire in - * practice; see the design note in mq_origin_curl.c header_cb. - * - * The event loop is pumped non-blocking with millisecond deadlines, like the - * other integration tests. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include /* CURL_HTTP_VERSION_1_1 for the on_done http_ver assert */ -#include -#include -#include -#include - -#include "gateway/mq_gw_headers.h" /* MQ_GW_CURL_* mirrors */ -#include "gateway/mq_gw_server.h" -#include "gateway/mq_origin_curl.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_h3.h" -#include "transport/mq_transport.h" - -#include -#include - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -/* ── timing helpers (mirror the other integration tests) ────────────────────*/ -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -/* Capture stderr (where MQ_LOGI writes) into a buffer across a code region. - * The transport emits verbose xquic [report] lines to stderr too, so callers - * use a generous buffer and strstr() for the field they care about. */ -typedef struct { - int saved_fd; - FILE *tmp; -} stderr_cap_t; -static void -stderr_cap_begin(stderr_cap_t *c) -{ - fflush(stderr); - c->saved_fd = dup(STDERR_FILENO); - c->tmp = tmpfile(); - dup2(fileno(c->tmp), STDERR_FILENO); -} -static void -stderr_cap_end(stderr_cap_t *c, char *buf, size_t cap) -{ - fflush(stderr); - dup2(c->saved_fd, STDERR_FILENO); - close(c->saved_fd); - rewind(c->tmp); - size_t n = fread(buf, 1, cap - 1, c->tmp); - buf[n] = '\0'; - fclose(c->tmp); -} - -/* Pump the base non-blocking until *flag != 0 or the budget elapses. Returns the - * final flag value. */ -static int -pump_until(struct event_base *base, const volatile int *flag, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (!*flag && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - return *flag; -} - -static void -pump_a_bit(struct event_base *base, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (now_ms() < deadline) - event_base_loop(base, EVLOOP_NONBLOCK); -} - -/* ── deterministic blob pattern ─────────────────────────────────────────────*/ -static uint8_t -pat_byte(size_t i) -{ - return (uint8_t)((i * 131u + 7u) & 0xff); -} - -/* ── in-process evhttp origin ───────────────────────────────────────────────*/ -typedef struct { - struct evhttp *http; - uint16_t port; - size_t blob_len; /* size served by GET /blob */ - - /* Captured request headers from the most recent /blob request (for the - * gw_server forwarding asserts: NO x-mq-* reach the origin; custom header - * forwarded). */ - int saw_xmq; /* any x-mq-* request header seen */ - int saw_custom; /* x-custom-hdr: keepme seen */ - char custom_val[64]; /* its value */ - int saw_content_length; /* content-length request header seen (upload tests) */ -} origin_t; - -/* Scan the request headers: flag any x-mq-*, capture x-custom-hdr, and note - * whether a content-length header reached the origin (used by chunked-upload - * assert to confirm the gw_server stripped it before forwarding). */ -static void -origin_capture_req_hdrs(struct evhttp_request *req, origin_t *o) -{ - struct evkeyvalq *h = evhttp_request_get_input_headers(req); - if (!h) return; - struct evkeyval *kv; - TAILQ_FOREACH(kv, h, next) - { - if (strncasecmp(kv->key, "x-mq-", 5) == 0) o->saw_xmq = 1; - if (strcasecmp(kv->key, "x-custom-hdr") == 0) { - o->saw_custom = 1; - snprintf(o->custom_val, sizeof(o->custom_val), "%s", kv->value); - } - if (strcasecmp(kv->key, "content-length") == 0) o->saw_content_length = 1; - } -} - -static void -origin_cb_blob(struct evhttp_request *req, void *arg) -{ - origin_t *o = (origin_t *)arg; - origin_capture_req_hdrs(req, o); - struct evbuffer *out = evbuffer_new(); - for (size_t i = 0; i < o->blob_len; i++) { - uint8_t b = pat_byte(i); - evbuffer_add(out, &b, 1); - } - evhttp_add_header(evhttp_request_get_output_headers(req), "x-origin-tag", "blobby"); - evhttp_send_reply(req, 200, "OK", out); - evbuffer_free(out); -} - -/* GET /slow: reply after a 300ms timer (so a request can be aborted first). */ -typedef struct { - struct evhttp_request *req; - struct event *timer; -} slow_ctx_t; - -/* Outstanding /slow context so origin_down can free a still-pending timer (the - * gw_server teardown case frees the fixture BEFORE the 300ms timer fires; without - * this the slow_ctx + its timer would leak under LSan). Single-slot is enough — - * these tests have at most one /slow request in flight. */ -static slow_ctx_t *g_pending_slow; - -static void -slow_fire(evutil_socket_t fd, short what, void *arg) -{ - (void)fd; - (void)what; - slow_ctx_t *sc = (slow_ctx_t *)arg; - struct evbuffer *out = evbuffer_new(); - evhttp_send_reply(sc->req, 200, "OK", out); - evbuffer_free(out); - event_free(sc->timer); - if (g_pending_slow == sc) g_pending_slow = NULL; - free(sc); -} - -static struct event_base *g_origin_base; /* for arming the slow timer */ - -static void -origin_cb_slow(struct evhttp_request *req, void *arg) -{ - (void)arg; - slow_ctx_t *sc = (slow_ctx_t *)calloc(1, sizeof(*sc)); - sc->req = req; - struct timeval tv = {0, 300 * 1000}; - sc->timer = evtimer_new(g_origin_base, slow_fire, sc); - evtimer_add(sc->timer, &tv); - g_pending_slow = sc; -} - -/* PUT /up: drain the request body, reply 200 with "len=". */ -static void -origin_cb_up(struct evhttp_request *req, void *arg) -{ - origin_t *o = (origin_t *)arg; - origin_capture_req_hdrs(req, o); - struct evbuffer *in = evhttp_request_get_input_buffer(req); - size_t n = in ? evbuffer_get_length(in) : 0; - if (in) evbuffer_drain(in, n); - char body[64]; - int bn = snprintf(body, sizeof(body), "len=%zu", n); - struct evbuffer *out = evbuffer_new(); - evbuffer_add(out, body, (size_t)bn); - evhttp_send_reply(req, 200, "OK", out); - evbuffer_free(out); -} - -/* GET /bighdr: reply 200 with a pathologically large response header VALUE - * (1500 bytes). Used to drive the gw_server's fail-closed path for an oversized - * ORIGIN response header (>1023 = the per-request arena value slot). */ -static void -origin_cb_bighdr(struct evhttp_request *req, void *arg) -{ - (void)arg; - char big[1501]; - memset(big, 'B', sizeof(big) - 1); - big[sizeof(big) - 1] = '\0'; - evhttp_add_header(evhttp_request_get_output_headers(req), "x-origin-big", big); - evhttp_send_reply(req, 200, "OK", NULL); -} - -/* GET /manyhdr: reply 200 with 70 distinct small response headers (x-h-00 .. - * x-h-69). Used to drive the gw_server's fail-closed path for a response - * header-COUNT overflow (> MQ_GWS_MAX_HDRS = 64). */ -static void -origin_cb_manyhdr(struct evhttp_request *req, void *arg) -{ - (void)arg; - struct evkeyvalq *outh = evhttp_request_get_output_headers(req); - for (int i = 0; i < 70; i++) { - char name[16]; - snprintf(name, sizeof(name), "x-h-%02d", i); - evhttp_add_header(outh, name, "v"); - } - evhttp_send_reply(req, 200, "OK", NULL); -} - -/* GET /boundary64: reply 200 with exactly MQ_GWS_MAX_HDRS(64) forwardable - * headers followed by an x-mq-origin-protocol header. - * - * The 64 forwardable headers are composed of: - * 3 explicitly pre-set standard headers that would otherwise be auto-added by - * evhttp (Date, Content-Length, Content-Type) — pre-setting them ensures they - * appear FIRST in the wire order, before the x-h-NN set, and prevents evhttp - * from appending duplicates. - * 61 custom x-h-NN headers. - * Together that is exactly 64 forwardable (non-hop-by-hop, non-x-mq-origin- - * protocol) headers. Then x-mq-origin-protocol: fake is added AFTER the 64. - * - * evhttp still appends "Connection: close" automatically; that header is in the - * hop-by-hop strip list and is therefore not counted. - * - * Before the fix: origin_on_header checks the count (n_hdrs >= 64) BEFORE the - * x-mq-origin-protocol drop, so the 65th call with x-mq-origin-protocol trips - * hdrs_overflow = 1 → gateway returns 502 (false positive). - * After the fix: x-mq-origin-protocol is dropped first; the 64 forwardable - * headers fit exactly; gateway returns 200 with x-mq-origin-protocol set to - * "http/1.1" (the gateway's own synthesis) and all 64 headers forwarded. */ -static void -origin_cb_boundary64(struct evhttp_request *req, void *arg) -{ - (void)arg; - struct evkeyvalq *outh = evhttp_request_get_output_headers(req); - /* Pre-set the 3 headers evhttp would auto-add so they appear first and - * are not duplicated; they count toward the 64 forwardable total. */ - evhttp_add_header(outh, "Date", "Thu, 01 Jan 2026 00:00:00 GMT"); - evhttp_add_header(outh, "Content-Length", "0"); - evhttp_add_header(outh, "Content-Type", "text/plain"); - /* 61 custom x-h-NN headers (3 + 61 = 64 forwardable total). */ - for (int i = 0; i < 61; i++) { - char name[16]; - snprintf(name, sizeof(name), "x-h-%02d", i); - evhttp_add_header(outh, name, "v"); - } - /* This is the (would-be) 65th header in the current buggy order; it must be - * dropped without being counted against the capacity. */ - evhttp_add_header(outh, "x-mq-origin-protocol", "fake"); - evhttp_send_reply(req, 200, "OK", NULL); -} - -static void -origin_cb_404(struct evhttp_request *req, void *arg) -{ - (void)arg; - evhttp_send_reply(req, 404, "Not Found", NULL); -} - -static int -origin_up(struct event_base *base, origin_t *o, size_t blob_len) -{ - memset(o, 0, sizeof(*o)); - o->blob_len = blob_len; - o->http = evhttp_new(base); - if (!o->http) return -1; - g_origin_base = base; - evhttp_set_cb(o->http, "/blob", origin_cb_blob, o); - evhttp_set_cb(o->http, "/slow", origin_cb_slow, o); - evhttp_set_cb(o->http, "/up", origin_cb_up, o); - evhttp_set_cb(o->http, "/bighdr", origin_cb_bighdr, o); - evhttp_set_cb(o->http, "/manyhdr", origin_cb_manyhdr, o); - evhttp_set_cb(o->http, "/boundary64", origin_cb_boundary64, o); - evhttp_set_gencb(o->http, origin_cb_404, o); - - struct evhttp_bound_socket *bs = - evhttp_bind_socket_with_handle(o->http, "127.0.0.1", 0); - if (!bs) return -1; - evutil_socket_t fd = evhttp_bound_socket_get_fd(bs); - struct sockaddr_storage ss; - ev_socklen_t sl = sizeof(ss); - if (getsockname(fd, (struct sockaddr *)&ss, &sl) != 0) return -1; - o->port = ntohs(((struct sockaddr_in *)&ss)->sin_port); - return o->port ? 0 : -1; -} - -static void -origin_down(origin_t *o) -{ - if (o->http) evhttp_free(o->http); - o->http = NULL; - /* Free a /slow timer that never fired (fixture torn down before the 300ms - * reply). evhttp_free above already released the bound request, so only our - * own ctx + timer remain. */ - if (g_pending_slow) { - if (g_pending_slow->timer) event_free(g_pending_slow->timer); - free(g_pending_slow); - g_pending_slow = NULL; - } -} - -/* ── shared per-request capture ─────────────────────────────────────────────*/ -typedef struct { - int status; - int status_calls; - int saw_tag; /* x-origin-tag observed via on_header */ - - uint8_t *body; - size_t body_cap; - size_t body_len; - int body_calls; - - /* download pause control */ - int pause_dl_once; /* return 0 from the next on_body, then clear */ - int dl_paused; /* set when we requested a pause */ - - /* upload source */ - const uint8_t *up_src; - size_t up_total; - size_t up_sent; - int pause_ul_at; /* byte offset at which to pause once (0 = never) */ - int ul_paused; /* set when we requested an upload pause */ - mq_origin_req_t *req; /* so callbacks can request a resume */ - - int done; - int done_result; - long done_http_ver; - int done_origin_reuse; - int done_origin_connect_ms; -} cap_t; - -static void -cap_on_status(int status, void *u) -{ - cap_t *c = (cap_t *)u; - c->status = status; - c->status_calls++; -} - -static void -cap_on_header(const char *n, size_t nl, const char *v, size_t vl, void *u) -{ - cap_t *c = (cap_t *)u; - if (nl == strlen("x-origin-tag") && strncasecmp(n, "x-origin-tag", nl) == 0 && - vl == strlen("blobby") && memcmp(v, "blobby", vl) == 0) { - c->saw_tag = 1; - } -} - -static long -cap_on_body(const uint8_t *p, size_t len, void *u) -{ - cap_t *c = (cap_t *)u; - if (c->pause_dl_once) { - c->pause_dl_once = 0; - c->dl_paused = 1; - return 0; /* accept nothing → PAUSE */ - } - if (c->body_len + len <= c->body_cap) { - memcpy(c->body + c->body_len, p, len); - } - c->body_len += len; - c->body_calls++; - return (long)len; /* accept all */ -} - -static long -cap_pull_body(uint8_t *buf, size_t cap, void *u) -{ - cap_t *c = (cap_t *)u; - if (c->up_sent >= c->up_total) return -1; /* EOF */ - /* Pause once when we cross the configured offset. */ - if (c->pause_ul_at > 0 && (int)c->up_sent >= c->pause_ul_at) { - c->pause_ul_at = 0; /* one-shot */ - c->ul_paused = 1; - return 0; /* PAUSE */ - } - size_t want = c->up_total - c->up_sent; - size_t chunk = 16 * 1024; - if (want > chunk) want = chunk; - if (want > cap) want = cap; - memcpy(buf, c->up_src + c->up_sent, want); - c->up_sent += want; - return (long)want; -} - -static void -cap_on_done(int result, long http_ver, long ssl_verify, int origin_reuse, - int origin_connect_ms, void *u) -{ - (void)ssl_verify; - cap_t *c = (cap_t *)u; - c->done = 1; - c->done_result = result; - c->done_http_ver = http_ver; - c->done_origin_reuse = origin_reuse; - c->done_origin_connect_ms = origin_connect_ms; -} - -static mq_origin_cbs_t -cap_cbs(void) -{ - mq_origin_cbs_t cbs; - memset(&cbs, 0, sizeof(cbs)); - cbs.on_status = cap_on_status; - cbs.on_header = cap_on_header; - cbs.on_body = cap_on_body; - cbs.pull_body = cap_pull_body; - cbs.on_done = cap_on_done; - return cbs; -} - -static void -cap_init(cap_t *c, size_t body_cap) -{ - memset(c, 0, sizeof(*c)); - if (body_cap) { - c->body = malloc(body_cap); - c->body_cap = body_cap; - } -} - -static void -cap_free(cap_t *c) -{ - free(c->body); - c->body = NULL; -} - -static char * -make_url(uint16_t port, const char *path) -{ - static char buf[128]; - snprintf(buf, sizeof(buf), "http://127.0.0.1:%u%s", (unsigned)port, path); - return buf; -} - -/* ── Case 1: GET 1 MiB byte-exact ───────────────────────────────────────────*/ -static void -test_get_blob(struct event_base *base) -{ - const size_t N = 1024 * 1024; - origin_t o; - MQ_CHECK(origin_up(base, &o, N) == 0); - - mq_origin_t *org = mq_origin_new(base, NULL, 10); - MQ_CHECK(org != NULL); - - cap_t c; - cap_init(&c, N); - mq_origin_cbs_t cbs = cap_cbs(); - mq_origin_req_t *r = mq_origin_start(org, make_url(o.port, "/blob"), "GET", NULL, 0, - -1, MQ_HTTP_VER_DEFAULT, &cbs, &c); - MQ_CHECK(r != NULL); - - pump_until(base, &c.done, 10000); - MQ_CHECK_EQ_INT(c.done, 1); - MQ_CHECK_EQ_INT(c.done_result, 0); /* CURLE_OK */ - MQ_CHECK_EQ_INT(c.status, 200); - MQ_CHECK_EQ_INT(c.status_calls, 1); - MQ_CHECK_EQ_INT(c.saw_tag, 1); - MQ_CHECK_EQ_INT((long long)c.body_len, (long long)N); - MQ_CHECK_EQ_INT(c.done_http_ver, CURL_HTTP_VERSION_1_1); - MQ_CHECK(c.done_origin_reuse == 0 || c.done_origin_reuse == 1); - MQ_CHECK(c.done_origin_connect_ms >= -1); - - /* Byte-exact pattern check. */ - int ok = 1; - for (size_t i = 0; i < N && ok; i++) - if (c.body[i] != pat_byte(i)) ok = 0; - MQ_CHECK(ok); - - mq_origin_free(org); - origin_down(&o); - cap_free(&c); -} - -/* ── Case 2: download PAUSE / resume ────────────────────────────────────────*/ -static void -test_download_pause(struct event_base *base) -{ - const size_t N = 512 * 1024; - origin_t o; - MQ_CHECK(origin_up(base, &o, N) == 0); - - mq_origin_t *org = mq_origin_new(base, NULL, 10); - MQ_CHECK(org != NULL); - - cap_t c; - cap_init(&c, N); - c.pause_dl_once = 1; - mq_origin_cbs_t cbs = cap_cbs(); - mq_origin_req_t *r = mq_origin_start(org, make_url(o.port, "/blob"), "GET", NULL, 0, - -1, MQ_HTTP_VER_DEFAULT, &cbs, &c); - MQ_CHECK(r != NULL); - - /* Pump until the pause fires (first on_body returns 0). */ - pump_until(base, &c.dl_paused, 5000); - MQ_CHECK_EQ_INT(c.dl_paused, 1); - MQ_CHECK_EQ_INT(c.done, 0); /* not finished while paused */ - - /* While paused, no further on_body / no completion even after pumping. */ - int calls_at_pause = c.body_calls; - size_t len_at_pause = c.body_len; - pump_a_bit(base, 200); - MQ_CHECK_EQ_INT(c.body_calls, calls_at_pause); - MQ_CHECK_EQ_INT((long long)c.body_len, (long long)len_at_pause); - MQ_CHECK_EQ_INT(c.done, 0); - - /* Resume → completes byte-exact. */ - mq_origin_resume_body(r); - pump_until(base, &c.done, 10000); - MQ_CHECK_EQ_INT(c.done, 1); - MQ_CHECK_EQ_INT(c.done_result, 0); - MQ_CHECK_EQ_INT((long long)c.body_len, (long long)N); - int ok = 1; - for (size_t i = 0; i < N && ok; i++) - if (c.body[i] != pat_byte(i)) ok = 0; - MQ_CHECK(ok); - - mq_origin_free(org); - origin_down(&o); - cap_free(&c); -} - -/* ── Case 3: upload PUT 256 KiB with pull-side PAUSE / resume ────────────────*/ -static void -test_upload_pause(struct event_base *base) -{ - const size_t N = 256 * 1024; - origin_t o; - MQ_CHECK(origin_up(base, &o, 0) == 0); - - mq_origin_t *org = mq_origin_new(base, NULL, 10); - MQ_CHECK(org != NULL); - - uint8_t *src = malloc(N); - for (size_t i = 0; i < N; i++) - src[i] = pat_byte(i); - - cap_t c; - cap_init(&c, 256); /* small body buffer: reply is just "len=262144" */ - c.up_src = src; - c.up_total = N; - c.pause_ul_at = 128 * 1024; /* pause once around the midpoint */ - mq_origin_cbs_t cbs = cap_cbs(); - mq_origin_req_t *r = mq_origin_start(org, make_url(o.port, "/up"), "PUT", NULL, 0, - (int64_t)N, MQ_HTTP_VER_DEFAULT, &cbs, &c); - MQ_CHECK(r != NULL); - c.req = r; - - /* Pump until the upload pause fires. */ - pump_until(base, &c.ul_paused, 5000); - MQ_CHECK_EQ_INT(c.ul_paused, 1); - MQ_CHECK_EQ_INT(c.done, 0); - size_t sent_at_pause = c.up_sent; - MQ_CHECK(sent_at_pause >= (size_t)(128 * 1024)); - MQ_CHECK(sent_at_pause < N); /* not all sent yet */ - - /* Resume the upload → completes. */ - mq_origin_resume_pull(r); - pump_until(base, &c.done, 10000); - MQ_CHECK_EQ_INT(c.done, 1); - MQ_CHECK_EQ_INT(c.done_result, 0); - MQ_CHECK_EQ_INT(c.status, 200); - MQ_CHECK_EQ_INT((long long)c.up_sent, (long long)N); - /* Origin echoes the received length. */ - c.body[c.body_len < c.body_cap ? c.body_len : c.body_cap - 1] = '\0'; - MQ_CHECK(strstr((char *)c.body, "len=262144") != NULL); - - mq_origin_free(org); - origin_down(&o); - cap_free(&c); - free(src); -} - -/* ── Case 4: NXDOMAIN → CURLE_COULDNT_RESOLVE_HOST ──────────────────────────*/ -static void -test_nxdomain(struct event_base *base) -{ - mq_origin_t *org = mq_origin_new(base, NULL, 5); - MQ_CHECK(org != NULL); - - cap_t c; - cap_init(&c, 0); - mq_origin_cbs_t cbs = cap_cbs(); - mq_origin_req_t *r = - mq_origin_start(org, "http://nxdomain-mqproxy-test.invalid/", "GET", NULL, 0, -1, - MQ_HTTP_VER_DEFAULT, &cbs, &c); - MQ_CHECK(r != NULL); - - pump_until(base, &c.done, 10000); - MQ_CHECK_EQ_INT(c.done, 1); - MQ_CHECK_EQ_INT(c.done_result, MQ_GW_CURL_RESOLVE); /* CURLE_COULDNT_RESOLVE_HOST */ - - mq_origin_free(org); - cap_free(&c); -} - -/* ── Case 5: connection refused → CURLE_COULDNT_CONNECT ─────────────────────*/ -static void -test_conn_refused(struct event_base *base) -{ - mq_origin_t *org = mq_origin_new(base, NULL, 5); - MQ_CHECK(org != NULL); - - cap_t c; - cap_init(&c, 0); - mq_origin_cbs_t cbs = cap_cbs(); - /* Port 1 on loopback: nothing listens → ECONNREFUSED. */ - mq_origin_req_t *r = mq_origin_start(org, "http://127.0.0.1:1/", "GET", NULL, 0, -1, - MQ_HTTP_VER_DEFAULT, &cbs, &c); - MQ_CHECK(r != NULL); - - pump_until(base, &c.done, 10000); - MQ_CHECK_EQ_INT(c.done, 1); - /* Mirror constant: refused surfaces as CURLE_COULDNT_CONNECT (7). */ - MQ_CHECK_EQ_INT(c.done_result, MQ_GW_CURL_CONNECT); - - mq_origin_free(org); - cap_free(&c); -} - -/* ── Case 6: abort mid-flight (no on_done, no crash) ────────────────────────*/ -static void -test_abort_midflight(struct event_base *base) -{ - origin_t o; - MQ_CHECK(origin_up(base, &o, 0) == 0); - - mq_origin_t *org = mq_origin_new(base, NULL, 10); - MQ_CHECK(org != NULL); - - cap_t c; - cap_init(&c, 0); - mq_origin_cbs_t cbs = cap_cbs(); - mq_origin_req_t *r = mq_origin_start(org, make_url(o.port, "/slow"), "GET", NULL, 0, - -1, MQ_HTTP_VER_DEFAULT, &cbs, &c); - MQ_CHECK(r != NULL); - - /* Let the request connect + send, but abort before the 300ms reply timer. */ - pump_a_bit(base, 80); - MQ_CHECK_EQ_INT(c.done, 0); /* reply not yet sent */ - mq_origin_abort(r); - - /* Pump well past the origin's reply timer: on_done must NOT fire and the - * loop must drain cleanly (no crash / UAF — verified under ASan). */ - pump_a_bit(base, 500); - MQ_CHECK_EQ_INT(c.done, 0); - - mq_origin_free(org); - origin_down(&o); - cap_free(&c); -} - -/* ── Case 7: 1xx-skip parser (status-line parsing, unit) ────────────────────*/ -static void -test_status_line_parser(void) -{ - /* Final-status lines. */ - MQ_CHECK_EQ_INT(mq_origin_parse_status_line("HTTP/1.1 200 OK", 15), 200); - MQ_CHECK_EQ_INT(mq_origin_parse_status_line("HTTP/1.0 404 Not Found", 22), 404); - MQ_CHECK_EQ_INT(mq_origin_parse_status_line("HTTP/2 503", 10), 503); - /* 1xx lines parse to a 1xx code (header_cb routes these into the skip path). */ - MQ_CHECK_EQ_INT(mq_origin_parse_status_line("HTTP/1.1 100 Continue", 21), 100); - MQ_CHECK_EQ_INT(mq_origin_parse_status_line("HTTP/1.1 103 Early Hints", 24), 103); - /* Non-status (field) lines → -1. */ - MQ_CHECK_EQ_INT(mq_origin_parse_status_line("Content-Length: 5", 17), -1); - MQ_CHECK_EQ_INT(mq_origin_parse_status_line("x-origin-tag: blobby", 20), -1); - MQ_CHECK_EQ_INT(mq_origin_parse_status_line("HTTP/1.1 ", 9), -1); /* no code */ - MQ_CHECK_EQ_INT(mq_origin_parse_status_line("HTTP/1.1 99 x", 13), -1); /* <100 */ -} - -/* ════════════════════════════════════════════════════════════════════════════ - * Gateway SERVER (Task 4.3): H3 request → origin execution → H3 response. - * - * These stand up a REAL in-process H3 tunnel over loopback UDP plus the evhttp - * origin (reused from above): - * - the gw_server under test: server transport (TLS) + runtime + mq_gw_server - * (which mq_h3_init's its own hooks). The evhttp origin is on the SAME base. - * - a fake H3 CLIENT: client transport + runtime + mq_h3 (NULL hooks) that - * opens an H3 request, forwards the pseudo-headers + x-mq-auth + arbitrary - * headers, optionally uploads a body, and captures the H3 response. - * ════════════════════════════════════════════════════════════════════════════ */ - -/* Reserve an ephemeral loopback UDP port (bind :0, read it, close). */ -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -/* ── client-side H3 response capture ────────────────────────────────────────*/ - -#define CLI_MAX_HDRS 32 -#define CLI_HDR_LEN 512 - -typedef struct { - char name[CLI_HDR_LEN]; - char value[CLI_HDR_LEN]; -} cli_hdr_t; - -typedef struct { - mq_h3_req_t *req; - - /* response captured from the gw_server */ - int got_headers; - int status; /* parsed :status */ - cli_hdr_t hdrs[CLI_MAX_HDRS]; - int n_hdrs; - - uint8_t *body; - size_t body_cap; - size_t body_len; - int saw_fin; - int closed; - - /* upload source (set before sending the request) */ - const uint8_t *up_src; - size_t up_total; - size_t up_sent; - int up_fin_sent; - - /* control: reset the request once we have received `reset_after` body bytes */ - int reset_after; /* >0 = reset mid-download after this many bytes */ - int did_reset; -} cli_req_t; - -static void -cli_capture_hdr(const char *n, size_t nl, const char *v, size_t vl, void *u) -{ - cli_req_t *c = (cli_req_t *)u; - if (nl == 7 && memcmp(n, ":status", 7) == 0) { - int code = 0; - for (size_t i = 0; i < vl && v[i] >= '0' && v[i] <= '9'; i++) - code = code * 10 + (v[i] - '0'); - c->status = code; - return; - } - if (c->n_hdrs >= CLI_MAX_HDRS) return; - cli_hdr_t *h = &c->hdrs[c->n_hdrs++]; - size_t cn = nl < CLI_HDR_LEN - 1 ? nl : CLI_HDR_LEN - 1; - size_t cv = vl < CLI_HDR_LEN - 1 ? vl : CLI_HDR_LEN - 1; - memcpy(h->name, n, cn); - h->name[cn] = '\0'; - memcpy(h->value, v, cv); - h->value[cv] = '\0'; -} - -static const char * -cli_find_hdr(const cli_req_t *c, const char *name) -{ - for (int i = 0; i < c->n_hdrs; i++) - if (strcasecmp(c->hdrs[i].name, name) == 0) return c->hdrs[i].value; - return NULL; -} - -/* Push as much of the pending upload as xquic accepts, riding fin on the last - * byte; EAGAIN remainder retried from cli_on_write. */ -static void -cli_upload_pump(cli_req_t *c) -{ - if (!c->req || c->up_fin_sent) return; - while (c->up_sent < c->up_total) { - long acc = mq_h3_req_send_body(c->req, c->up_src + c->up_sent, - c->up_total - c->up_sent, /*fin=*/1); - if (acc <= 0) return; /* EAGAIN: resume on next on_write */ - c->up_sent += (size_t)acc; - if (c->up_sent >= c->up_total) { - c->up_fin_sent = 1; - return; - } - } - if (c->up_total == 0) { - mq_h3_req_finish(c->req); - c->up_fin_sent = 1; - } -} - -static void -cli_on_read(mq_h3_req_t *r, int flag, void *user) -{ - cli_req_t *c = (cli_req_t *)user; - if (flag & (XQC_REQ_NOTIFY_READ_HEADER | XQC_REQ_NOTIFY_READ_TRAILER)) { - int fin = 0; - int n = mq_h3_req_recv_headers(r, cli_capture_hdr, c, &fin); - if (n >= 0) c->got_headers = 1; - if (fin) c->saw_fin = 1; - } - for (;;) { - int fin = 0; - size_t room = c->body_cap > c->body_len ? c->body_cap - c->body_len : 0; - uint8_t scratch[4096]; - uint8_t *dst = c->body ? c->body + c->body_len : scratch; - size_t cap = c->body ? room : sizeof(scratch); - if (cap == 0) { - dst = scratch; - cap = sizeof(scratch); - } - long n = mq_h3_req_recv_body(r, dst, cap, &fin); - if (n > 0 && c->body && room > 0) - c->body_len += ((size_t)n < room ? (size_t)n : room); - if (fin) c->saw_fin = 1; - /* Mid-download reset trigger (case 7). */ - if (c->reset_after > 0 && !c->did_reset && - c->body_len >= (size_t)c->reset_after) { - c->did_reset = 1; - mq_h3_req_reset(c->req); - c->req = NULL; - return; - } - if (n <= 0) break; - } -} - -static void -cli_on_write(mq_h3_req_t *r, void *user) -{ - (void)r; - cli_req_t *c = (cli_req_t *)user; - cli_upload_pump(c); -} - -static void -cli_on_close(mq_h3_req_t *r, void *user) -{ - (void)r; - cli_req_t *c = (cli_req_t *)user; - c->closed = 1; - c->req = NULL; -} - -/* ── gw_server fixture ──────────────────────────────────────────────────────*/ - -typedef struct { - struct event_base *base; - origin_t origin; - mq_transport_t *srv_t; - mq_runtime_t *srv_rt; - mq_gw_server_t *gw; - uint16_t srv_port; /* the gw_server's loopback UDP port (for a 2nd client) */ - - mq_transport_t *cli_t; - mq_runtime_t *cli_rt; - mq_h3_t *cli_h3; - mq_h3_conn_t *cli_conn; - int cli_conn_up; -} gws_fixture_t; - -static void -gws_cli_conn_state(mq_h3_conn_t *cn, int established, void *user) -{ - (void)cn; - gws_fixture_t *f = (gws_fixture_t *)user; - if (established) - f->cli_conn_up = 1; - else - f->cli_conn_up = 0; -} - -static int -gws_fixture_up(gws_fixture_t *f, const char *token, size_t blob_len) -{ - memset(f, 0, sizeof(*f)); - - f->base = event_base_new(); - if (!f->base) return -1; - - /* evhttp origin on the shared base. */ - if (origin_up(f->base, &f->origin, blob_len) != 0) return -1; - - uint16_t srv_port = reserve_udp_port(); - if (!srv_port) return -1; - f->srv_port = srv_port; - - /* Server transport + runtime + gw_server (mq_h3_init done inside gw_server). */ - f->srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - if (!f->srv_t) return -1; - f->srv_rt = mq_runtime_new(f->srv_t, f->base); - if (!f->srv_rt) return -1; - - xqc_conn_settings_t srv_settings; - memset(&srv_settings, 0, sizeof(srv_settings)); - srv_settings.proto_version = XQC_VERSION_V1; - srv_settings.pacing_on = 1; - mq_conn_apply_mp_settings(&srv_settings, /*is_server=*/1, MQ_CC_BBR2); - xqc_server_set_conn_settings(mq_transport_xqc(f->srv_t), &srv_settings); - - f->gw = mq_gw_server_new(f->srv_t, f->srv_rt, token, NULL, 10); - if (!f->gw) return -1; - if (mq_runtime_open_udp_path(f->srv_rt, "127.0.0.1", srv_port) != 0) return -1; - - /* Client transport + runtime + h3 (pure client). */ - f->cli_t = mq_transport_new(0); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - if (!f->cli_rt) return -1; - if (mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0) != 0) return -1; - f->cli_h3 = mq_h3_init(f->cli_t, NULL, NULL, NULL); - if (!f->cli_h3) return -1; - - struct sockaddr_in peer; - memset(&peer, 0, sizeof(peer)); - peer.sin_family = AF_INET; - peer.sin_port = htons(srv_port); - inet_pton(AF_INET, "127.0.0.1", &peer.sin_addr); - f->cli_conn = - mq_h3_connect(f->cli_h3, (struct sockaddr *)&peer, sizeof(peer), MQ_CC_BBR2, - /*keepalive_idle_ms=*/0, gws_cli_conn_state, f); - if (!f->cli_conn) return -1; - - /* Pump until the client conn is established. */ - uint64_t deadline = now_ms() + 5000; - while (!f->cli_conn_up && now_ms() < deadline) - event_base_loop(f->base, EVLOOP_NONBLOCK); - return f->cli_conn_up ? 0 : -1; -} - -static void -gws_fixture_down(gws_fixture_t *f) -{ - /* SANCTIONED TEARDOWN ORDER (mq_gw_server.h): capture the server h3 BEFORE - * gw_server_free (the accessor reads the freed struct otherwise), then - * gw_server_free FIRST (aborts in-flight origin + detaches H3 cbs against the - * LIVE engine), THEN mq_h3_free (both sides), THEN transport_free. */ - mq_h3_t *srv_h3 = mq_gw_server_h3(f->gw); - if (f->gw) mq_gw_server_free(f->gw); - if (f->cli_h3) mq_h3_free(f->cli_h3); - if (srv_h3) mq_h3_free(srv_h3); - if (f->cli_t) mq_transport_free(f->cli_t); - if (f->srv_t) mq_transport_free(f->srv_t); - if (f->cli_rt) mq_runtime_free(f->cli_rt); - if (f->srv_rt) mq_runtime_free(f->srv_rt); - origin_down(&f->origin); - if (f->base) event_base_free(f->base); -} - -/* Open + send an H3 request at the gw_server. `extra` headers are appended after - * the standard pseudo+auth set. Returns 0 on success (request open + headers - * sent). The caller wires cli cbs first via the returned cli_req. */ -static int -gws_send_request(gws_fixture_t *f, cli_req_t *c, const char *method, const char *scheme, - const char *authority, const char *path, const char *auth, - const mq_h3_header_t *extra, size_t n_extra, int has_body, - const char *content_length) -{ - mq_h3_req_t *hr = mq_h3_req_open(f->cli_conn); - if (!hr) return -1; - c->req = hr; - mq_h3_req_set_cbs(hr, cli_on_read, cli_on_write, cli_on_close, c); - - mq_h3_header_t hs[CLI_MAX_HDRS]; - size_t nh = 0; - if (method) { - hs[nh].name = ":method"; - hs[nh].value = method; - nh++; - } - if (scheme) { - hs[nh].name = ":scheme"; - hs[nh].value = scheme; - nh++; - } - if (authority) { - hs[nh].name = ":authority"; - hs[nh].value = authority; - nh++; - } - if (path) { - hs[nh].name = ":path"; - hs[nh].value = path; - nh++; - } - if (auth) { - hs[nh].name = "x-mq-auth"; - hs[nh].value = auth; - nh++; - } - if (has_body && content_length) { - hs[nh].name = "content-length"; - hs[nh].value = content_length; - nh++; - } - for (size_t i = 0; i < n_extra && nh < CLI_MAX_HDRS; i++) - hs[nh++] = extra[i]; - - long sh = mq_h3_req_send_headers(hr, hs, nh, has_body ? 0 : 1); - if (sh <= 0) return -1; - if (has_body) cli_upload_pump(c); - return 0; -} - -/* Build an authority string "127.0.0.1:". */ -static const char * -origin_authority(const origin_t *o) -{ - static char buf[64]; - snprintf(buf, sizeof(buf), "127.0.0.1:%u", (unsigned)o->port); - return buf; -} - -/* ── gw_server Case 1: GET happy path ───────────────────────────────────────*/ -static void -test_gws_get(void) -{ - const size_t N = 64 * 1024; - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", N) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(N); - c.body_cap = N; - - mq_h3_header_t extra[] = {{"x-custom-hdr", "keepme"}}; - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "/blob", - "Bearer sekrit", extra, 1, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - - uint64_t deadline = now_ms() + 10000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(c.status, 200); - MQ_CHECK(c.got_headers); - MQ_CHECK_EQ_INT((long long)c.body_len, (long long)N); - /* Byte-exact vs the origin pattern. */ - int ok = 1; - for (size_t i = 0; i < N && i < c.body_len && ok; i++) - if (c.body[i] != pat_byte(i)) ok = 0; - MQ_CHECK(ok); - /* x-mq-origin-protocol present (origin is HTTP/1.1). */ - const char *prot = cli_find_hdr(&c, "x-mq-origin-protocol"); - MQ_CHECK(prot && strcmp(prot, "http/1.1") == 0); - /* The origin saw NO x-mq-* headers, but DID see the forwarded custom header. */ - MQ_CHECK_EQ_INT(f.origin.saw_xmq, 0); - MQ_CHECK_EQ_INT(f.origin.saw_custom, 1); - MQ_CHECK(strcmp(f.origin.custom_val, "keepme") == 0); - MQ_CHECK_EQ_INT(mq_gw_server_requests(f.gw), 1); - - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 1b: mq.req emitted when request-metrics is ON ────────────*/ -static void -test_gws_req_metrics_on(void) -{ - const size_t N = 64 * 1024; - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", N) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - mq_gw_server_set_request_metrics(f.gw, 1); - - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(N); - c.body_cap = N; - - /* Capture stderr (MQ_LOGI sink) across a GET driven to clean close. The buffer - * is generous because the transport also logs verbose [report] lines here. */ - static char log[1 << 18]; /* 256 KiB; static to keep it off the stack */ - stderr_cap_t cap; - stderr_cap_begin(&cap); - - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "/blob", - "Bearer sekrit", NULL, 0, 0, NULL); - if (rc == 0) { - uint64_t deadline = now_ms() + 10000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - } - - stderr_cap_end(&cap, log, sizeof(log)); - - MQ_CHECK_EQ_INT(rc, 0); - MQ_CHECK_EQ_INT(c.status, 200); - MQ_CHECK_EQ_INT((long long)c.body_len, (long long)N); - - /* The mq.req line carries the expected app fields. */ - MQ_CHECK(strstr(log, "mq.req cid=-") != NULL); - MQ_CHECK(strstr(log, "method=GET status=200") != NULL); - MQ_CHECK(strstr(log, "origin_protocol=") != NULL); - MQ_CHECK(strstr(log, "cache=bypass origin_reuse=0") != NULL); - - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 1c: NO mq.req when request-metrics is OFF (opt-in gate) ──*/ -static void -test_gws_req_metrics_off(void) -{ - const size_t N = 64 * 1024; - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", N) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - /* Default is OFF; do not enable request-metrics. */ - - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(N); - c.body_cap = N; - - static char log[1 << 18]; - stderr_cap_t cap; - stderr_cap_begin(&cap); - - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "/blob", - "Bearer sekrit", NULL, 0, 0, NULL); - if (rc == 0) { - uint64_t deadline = now_ms() + 10000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - } - - stderr_cap_end(&cap, log, sizeof(log)); - - MQ_CHECK_EQ_INT(rc, 0); - MQ_CHECK_EQ_INT(c.status, 200); - MQ_CHECK((long long)c.body_len == (long long)N); - /* Opt-in gate: no mq.req line emitted with the flag off. */ - MQ_CHECK(strstr(log, "mq.req") == NULL); - - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 2: x-mq-class forwarded + logged (request completes) ─────*/ -static void -test_gws_class(void) -{ - const size_t N = 4096; - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", N) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(N); - c.body_cap = N; - - /* x-mq-class is stripped server-side before the origin; the gw logs it at - * INFO (manual evidence in test output). We just assert the request - * completes 200 and the origin saw NO x-mq-* (class included). */ - mq_h3_header_t extra[] = {{"x-mq-class", "bulk"}}; - printf("[case2] sending x-mq-class: bulk (expect an INFO log line from gw_server)\n"); - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "/blob", - "Bearer sekrit", extra, 1, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - - uint64_t deadline = now_ms() + 10000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(c.status, 200); - MQ_CHECK_EQ_INT((long long)c.body_len, (long long)N); - MQ_CHECK_EQ_INT(f.origin.saw_xmq, 0); /* x-mq-class did NOT reach the origin */ - MQ_CHECK_EQ_INT(mq_gw_server_requests(f.gw), 1); - - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 3: 403 on a wrong token (no origin contact) ─────────────*/ -static void -test_gws_403(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 4096) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "/blob", - "Bearer WRONG", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(c.status, 403); - const char *err = cli_find_hdr(&c, "x-mq-error"); - MQ_CHECK(err && strcmp(err, "auth-failed") == 0); - /* No origin contact: the origin never saw a request. */ - MQ_CHECK_EQ_INT(f.origin.saw_xmq, 0); - MQ_CHECK_EQ_INT(f.origin.saw_custom, 0); - - gws_fixture_down(&f); -} - -/* ── gw_server masquerade: unauth GET → bare 404, no x-mq-* ──────────────────*/ -static void -test_gws_masq_unauth_404(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 64 * 1024) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - mq_gw_server_set_masquerade(f.gw, 1); - - cli_req_t c; - memset(&c, 0, sizeof(c)); - /* auth=NULL → no x-mq-auth header (unauthenticated probe). */ - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "/x", - NULL, NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(c.status, 404); - MQ_CHECK(cli_find_hdr(&c, "x-mq-error") == NULL); /* no mqproxy tell */ - gws_fixture_down(&f); -} - -/* ── gw_server masquerade: authed + bad :path → 400 WITH x-mq-error ──────────*/ -static void -test_gws_masq_authed_keeps_diag(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 64 * 1024) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - mq_gw_server_set_masquerade(f.gw, 1); - - cli_req_t c; - memset(&c, 0, sizeof(c)); - /* valid auth, but :path does not start with '/' → post-auth 400 bad-request. */ - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "bad", - "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(c.status, 400); - MQ_CHECK(cli_find_hdr(&c, "x-mq-error") != NULL); /* authed → diagnostics retained */ - gws_fixture_down(&f); -} - -/* ── gw_server Case 4: 400 on a missing :authority ──────────────────────────*/ -static void -test_gws_400(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 4096) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - - /* Omit :authority (pass NULL). */ - int rc = gws_send_request(&f, &c, "GET", "http", NULL, "/blob", "Bearer sekrit", NULL, - 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(c.status, 400); - const char *err = cli_find_hdr(&c, "x-mq-error"); - MQ_CHECK(err && strcmp(err, "bad-request") == 0); - MQ_CHECK_EQ_INT(f.origin.saw_xmq, 0); - - gws_fixture_down(&f); -} - -/* ── gw_server Case 4b: 400 on control bytes in a forwarded header / :path ───*/ -static void -test_gws_bad_header(void) -{ - /* 4b-i: a forwarded header VALUE with a control byte (CR) → 400 bad-header. */ - { - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 4096) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - mq_h3_header_t extra[] = {{"x-evil", "a\r\nInjected: 1"}}; - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), - "/blob", "Bearer sekrit", extra, 1, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - MQ_CHECK_EQ_INT(c.status, 400); - const char *err = cli_find_hdr(&c, "x-mq-error"); - MQ_CHECK(err && strcmp(err, "bad-header") == 0); - /* The request never reached the origin. */ - MQ_CHECK_EQ_INT(f.origin.saw_xmq, 0); - gws_fixture_down(&f); - } - /* 4b-ii: a :path with a control byte → 400 bad-target. */ - { - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 4096) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), - "/bl\tob", "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - MQ_CHECK_EQ_INT(c.status, 400); - const char *err = cli_find_hdr(&c, "x-mq-error"); - MQ_CHECK(err && strcmp(err, "bad-target") == 0); - gws_fixture_down(&f); - } -} - -/* ── gw_server Case 5: 502 on NXDOMAIN authority + on refused ───────────────*/ -static void -test_gws_502(void) -{ - /* 5a: NXDOMAIN authority → curl:6 (CURLE_COULDNT_RESOLVE_HOST) → 502. */ - { - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 4096) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - int rc = gws_send_request(&f, &c, "GET", "http", "nxdomain-mqproxy-test.invalid", - "/x", "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 10000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - MQ_CHECK_EQ_INT(c.status, 502); - const char *err = cli_find_hdr(&c, "x-mq-error"); - char want[16]; - snprintf(want, sizeof(want), "curl:%d", MQ_GW_CURL_RESOLVE); - MQ_CHECK(err && strcmp(err, want) == 0); - gws_fixture_down(&f); - } - /* 5b: connection refused (127.0.0.1:1) → curl:7 → 502. */ - { - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 4096) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - int rc = gws_send_request(&f, &c, "GET", "http", "127.0.0.1:1", "/x", - "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 10000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - MQ_CHECK_EQ_INT(c.status, 502); - const char *err = cli_find_hdr(&c, "x-mq-error"); - char want[16]; - snprintf(want, sizeof(want), "curl:%d", MQ_GW_CURL_CONNECT); - MQ_CHECK(err && strcmp(err, want) == 0); - gws_fixture_down(&f); - } -} - -/* ── gw_server Case 6: upload PUT 64 KiB+ → origin echoes len= ────────────*/ -static void -test_gws_upload(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 0) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - const size_t N = 96 * 1024; - uint8_t *src = malloc(N); - for (size_t i = 0; i < N; i++) - src[i] = pat_byte(i); - - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(256); - c.body_cap = 256; - c.up_src = src; - c.up_total = N; - - char cl[32]; - snprintf(cl, sizeof(cl), "%zu", N); - int rc = gws_send_request(&f, &c, "PUT", "http", origin_authority(&f.origin), "/up", - "Bearer sekrit", NULL, 0, 1, cl); - MQ_CHECK_EQ_INT(rc, 0); - - uint64_t deadline = now_ms() + 15000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(c.status, 200); - MQ_CHECK_EQ_INT((long long)c.up_sent, (long long)N); - /* Origin replied "len=". */ - c.body[c.body_len < c.body_cap ? c.body_len : c.body_cap - 1] = '\0'; - char want[32]; - snprintf(want, sizeof(want), "len=%zu", N); - MQ_CHECK(strstr((char *)c.body, want) != NULL); - - free(src); - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 6b: chunked upload PUT 32 KiB (NO content-length header) ─ - * - * The client sends a PUT with has_body=1 but omits content-length from the H3 - * request. gw_server detects has_body && CL absent → upload_len = CHUNKED → - * libcurl uses chunked Transfer-Encoding toward the origin. - * - * Assert: - * - H3 response status == 200. - * - Origin reply body contains "len=32768" (origin drained the full 32 KiB). - * - origin.saw_content_length == 0: the gw_server never forwarded a - * content-length request header (it is stripped before the origin, even when - * the client had sent one; in this test the client never sends one either). - * NOTE: evhttp on the origin side reassembles the chunked body and does NOT - * synthesize a content-length in the parsed request headers, so the assert - * is reliable without special evhttp configuration. - */ -static void -test_gws_chunked_upload(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 0) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - - const size_t N = 32 * 1024; - uint8_t *src = malloc(N); - for (size_t i = 0; i < N; i++) - src[i] = pat_byte(i); - - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(256); - c.body_cap = 256; - c.up_src = src; - c.up_total = N; - - /* has_body=1, content_length=NULL → no CL header on the H3 request; the - * gw_server must classify this as CHUNKED and use chunked TE toward origin. */ - int rc = gws_send_request(&f, &c, "PUT", "http", origin_authority(&f.origin), "/up", - "Bearer sekrit", NULL, 0, /*has_body=*/1, /*cl=*/NULL); - MQ_CHECK_EQ_INT(rc, 0); - - uint64_t deadline = now_ms() + 15000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(c.status, 200); - MQ_CHECK_EQ_INT((long long)c.up_sent, (long long)N); - - /* Origin echoes the received byte count. */ - c.body[c.body_len < c.body_cap ? c.body_len : c.body_cap - 1] = '\0'; - char want[32]; - snprintf(want, sizeof(want), "len=%zu", N); - MQ_CHECK(strstr((char *)c.body, want) != NULL); - - /* gw_server must NOT have forwarded a content-length request header. - * evhttp natively handles chunked requests so origin sees no synthesized CL. */ - MQ_CHECK_EQ_INT(f.origin.saw_content_length, 0); - - free(src); - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 7: mid-download client reset → origin reaped, gw survives ─*/ -static void -test_gws_mid_reset(void) -{ - const size_t N = 1024 * 1024; /* big enough that a reset lands mid-body */ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", N) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(N); - c.body_cap = N; - c.reset_after = 16 * 1024; /* reset once 16 KiB has arrived */ - - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "/blob", - "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - - /* Pump until our reset fires + a settle so the gw reaps the origin handle. */ - uint64_t deadline = now_ms() + 10000; - while (!c.did_reset && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - MQ_CHECK_EQ_INT(c.did_reset, 1); - pump_a_bit(f.base, 500); /* let the gw's on_close abort the origin */ - - MQ_CHECK_EQ_INT(mq_gw_server_requests(f.gw), 1); - - /* A subsequent request on the same conn still works (gw did not crash / wedge). - * Reuse the origin's /up route as a quick 0-byte round-trip. */ - f.origin.saw_xmq = 0; - cli_req_t c2; - memset(&c2, 0, sizeof(c2)); - c2.body = malloc(256); - c2.body_cap = 256; - int rc2 = gws_send_request(&f, &c2, "GET", "http", origin_authority(&f.origin), - "/blob", "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc2, 0); - deadline = now_ms() + 10000; - while (!c2.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - MQ_CHECK_EQ_INT(c2.status, 200); - MQ_CHECK_EQ_INT(mq_gw_server_requests(f.gw), 2); - - free(c.body); - free(c2.body); - gws_fixture_down(&f); -} - -/* Helper: send ONE request with the given pseudo-headers, pump to close, and - * return the captured H3 :status (0 if the request never produced a response). - * `*err_out` (if non-NULL) receives a copy of the x-mq-error value. */ -static int -gws_one_status(gws_fixture_t *f, const char *method, const char *scheme, - const char *authority, const char *path, char *err_out, size_t err_cap) -{ - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(4096); - c.body_cap = 4096; - int rc = gws_send_request(f, &c, method, scheme, authority, path, "Bearer sekrit", - NULL, 0, 0, NULL); - if (rc != 0) { - free(c.body); - return -1; - } - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f->base, EVLOOP_NONBLOCK); - if (err_out) { - const char *e = cli_find_hdr(&c, "x-mq-error"); - snprintf(err_out, err_cap, "%s", e ? e : ""); - } - int st = c.status; - free(c.body); - return st; -} - -/* ── gw_server Case 10: :method validated server-side (smuggling defense) ──── - * - * curl does NOT sanitize CURLOPT_CUSTOMREQUEST, so an unvalidated :method from a - * direct H3 peer is a request-line-injection surface. The gw_server must validate - * with mq_gw_parse_method BEFORE use: token chars only, <=15, uppercased. */ -static void -test_gws_method_validation(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 4096) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - const char *auth = origin_authority(&f.origin); - char err[64]; - - /* :method with an embedded space → 400 bad-request. */ - MQ_CHECK_EQ_INT(gws_one_status(&f, "GE T", "http", auth, "/blob", err, sizeof(err)), - 400); - MQ_CHECK(strcmp(err, "bad-request") == 0); - - /* :method with a CR → 400 bad-request. */ - MQ_CHECK_EQ_INT(gws_one_status(&f, "GET\r", "http", auth, "/blob", err, sizeof(err)), - 400); - MQ_CHECK(strcmp(err, "bad-request") == 0); - - /* :method 16+ chars (overlong, would be silently clipped) → 400. */ - MQ_CHECK_EQ_INT(gws_one_status(&f, "AAAAAAAAAAAAAAAAAAAA", "http", auth, "/blob", err, - sizeof(err)), - 400); - MQ_CHECK(strcmp(err, "bad-request") == 0); - - /* lowercase "get" → accepted (uppercased): request reaches the origin → 200, - * and the origin sees GET. */ - f.origin.saw_xmq = 0; - MQ_CHECK_EQ_INT(gws_one_status(&f, "get", "http", auth, "/blob", err, sizeof(err)), - 200); - - /* :authority 256+ bytes (overlong — silent truncation changes the target!) - * → 400 bad-target. */ - { - char big[300]; - memset(big, 'a', sizeof(big) - 1); - big[sizeof(big) - 1] = '\0'; - MQ_CHECK_EQ_INT(gws_one_status(&f, "GET", "http", big, "/blob", err, sizeof(err)), - 400); - MQ_CHECK(strcmp(err, "bad-target") == 0); - } - - gws_fixture_down(&f); -} - -/* ── gw_server Case 11b: forwarded-header COUNT overflow rejected ──────────── - * - * More forwardable headers than the per-request arena holds (MQ_GWS_MAX_HDRS=64) - * sets ctx.bad in req_each_header — but pre-fix that flag was never read, so the - * request was forwarded to the origin with a SILENTLY TRUNCATED header set. The - * gw_server must instead reject with 400 bad-request and never contact the - * origin. We send 70 distinct forwardable headers, one of them an identifiable - * x-custom-hdr: the origin must NOT see it (request uncontacted). */ -static void -test_gws_header_count_overflow(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 4096) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(4096); - c.body_cap = 4096; - - /* Build pseudo + auth + 70 forwardable headers directly (gws_send_request - * caps at CLI_MAX_HDRS=32, too small here). */ - const int NEXTRA = 70; - enum { NAMES_CAP = 4 + 5 + 70 }; - mq_h3_header_t hs[NAMES_CAP]; - static char names[70][32]; - size_t nh = 0; - hs[nh].name = ":method"; - hs[nh++].value = "GET"; - hs[nh].name = ":scheme"; - hs[nh++].value = "http"; - hs[nh].name = ":authority"; - hs[nh++].value = origin_authority(&f.origin); - hs[nh].name = ":path"; - hs[nh++].value = "/blob"; - hs[nh].name = "x-mq-auth"; - hs[nh++].value = "Bearer sekrit"; - for (int i = 0; i < NEXTRA; i++) { - if (i == 0) { - hs[nh].name = "x-custom-hdr"; - hs[nh++].value = "keepme"; - } else { - snprintf(names[i], sizeof(names[i]), "x-fill-hdr-%02d", i); - hs[nh].name = names[i]; - hs[nh++].value = "v"; - } - } - - mq_h3_req_t *hr = mq_h3_req_open(f.cli_conn); - MQ_CHECK(hr != NULL); - c.req = hr; - mq_h3_req_set_cbs(hr, cli_on_read, cli_on_write, cli_on_close, &c); - long sh = mq_h3_req_send_headers(hr, hs, nh, /*fin=*/1); - MQ_CHECK(sh > 0); - - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(c.status, 400); - const char *err = cli_find_hdr(&c, "x-mq-error"); - MQ_CHECK(err && strcmp(err, "bad-request") == 0); - /* Origin uncontacted: it never saw the forwardable custom header. */ - MQ_CHECK_EQ_INT(f.origin.saw_custom, 0); - - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 12: oversized forwarded header rejected (not truncated) ── - * - * A forwarded header VALUE >= 1024 bytes (the arena slot) would be silently - * clamped by copy_z, changing what reaches the origin. The gw_server must reject - * with 400 bad-header instead. */ -static void -test_gws_oversized_header(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 4096) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(4096); - c.body_cap = 4096; - - char big[1501]; - memset(big, 'x', sizeof(big) - 1); - big[sizeof(big) - 1] = '\0'; - mq_h3_header_t extra[] = {{"x-big-hdr", big}}; - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "/blob", - "Bearer sekrit", extra, 1, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(c.status, 400); - const char *err = cli_find_hdr(&c, "x-mq-error"); - MQ_CHECK(err && strcmp(err, "bad-header") == 0); - /* The request never reached the origin. */ - MQ_CHECK_EQ_INT(f.origin.saw_xmq, 0); - - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 11: strict content-length parse on the tunnel side ────── - * - * Mirror mq_http1's strictness: an overflowing / non-digit / duplicated - * content-length on a bodied H3 request must be rejected (400 bad-request), not - * silently unset (which would fall back to chunked) or last-win. A request body - * is required so the CL is actually consulted. */ -static void -test_gws_content_length_strict(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 0) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - const char *auth = origin_authority(&f.origin); - - struct { - const char *cl; /* content-length value */ - const char *cl_dup; /* second content-length value, or NULL */ - int want_status; - const char *desc; - } cases[] = { - {"9999999999999999999999", NULL, 400, "overflow"}, - {"12a", NULL, 400, "non-digit"}, - {"", NULL, 400, "empty"}, - {"5", "5", 400, "duplicate (identical)"}, - {"5", "6", 400, "duplicate (differing)"}, - }; - - for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(4096); - c.body_cap = 4096; - - mq_h3_header_t extra[2]; - size_t ne = 0; - extra[ne].name = "content-length"; - extra[ne].value = cases[i].cl; - ne++; - if (cases[i].cl_dup) { - extra[ne].name = "content-length"; - extra[ne].value = cases[i].cl_dup; - ne++; - } - /* has_body=1, content_length param NULL (we supply CL via `extra`). We do - * not stream a body — the reject lands on the header section. */ - int rc = gws_send_request(&f, &c, "PUT", "http", auth, "/up", "Bearer sekrit", - extra, ne, /*has_body=*/1, /*cl=*/NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - printf("[case11] CL=%s -> %d (want %d)\n", cases[i].desc, c.status, - cases[i].want_status); - MQ_CHECK_EQ_INT(c.status, cases[i].want_status); - const char *err = cli_find_hdr(&c, "x-mq-error"); - MQ_CHECK(err && strcmp(err, "bad-request") == 0); - free(c.body); - } - - gws_fixture_down(&f); -} - -/* ── gw_server Case 12b: oversized ORIGIN response header → 502 fail-closed ── - * - * Symmetric to the client-side dl_each_header fail-closed: an origin RESPONSE - * header whose name (>127) or value (>1023) overflows the per-request arena slot - * must NOT be silently clamped (a clipped header corrupts the downstream - * response). The gw_server must fail the response closed — synthesize 502 + - * x-mq-error: upstream-protocol — since no response header has reached the H3 - * client yet. The origin /bighdr route emits a 1500-byte response header value. */ -static void -test_gws_oversized_origin_header(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 0) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(4096); - c.body_cap = 4096; - - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), - "/bighdr", "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK_EQ_INT(c.status, 502); - const char *err = cli_find_hdr(&c, "x-mq-error"); - MQ_CHECK(err && strcmp(err, "upstream-protocol") == 0); - /* The oversized origin header was NOT forwarded (truncated) to the client. */ - MQ_CHECK(cli_find_hdr(&c, "x-origin-big") == NULL); - - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 12c: origin response header-COUNT overflow → 502 fail-closed - * - * When an origin replies with more than MQ_GWS_MAX_HDRS (64) non-hop-by-hop - * response headers, origin_on_header sets r->hdrs_overflow after the 64th. - * download_send_headers MUST check hdrs_overflow (symmetric to hdr_oversized) - * and fail closed with 502 + x-mq-error: upstream-protocol before any header - * reaches the H3 client. - * - * The origin /manyhdr route returns 200 with 70 distinct x-h-NN headers. - * Expected: gateway returns 502 + x-mq-error: upstream-protocol; no x-h-* - * header forwarded to the client. */ -static void -test_gws_resp_header_count_overflow(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 0) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(4096); - c.body_cap = 4096; - - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), - "/manyhdr", "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - /* Fail closed: gateway must synthesize 502 + x-mq-error: upstream-protocol. - * No x-h-* origin headers must reach the H3 client. */ - MQ_CHECK_EQ_INT(c.status, 502); - const char *err = cli_find_hdr(&c, "x-mq-error"); - MQ_CHECK(err && strcmp(err, "upstream-protocol") == 0); - /* Confirm that none of the 70 origin x-h-NN headers were forwarded. */ - MQ_CHECK(cli_find_hdr(&c, "x-h-00") == NULL); - MQ_CHECK(cli_find_hdr(&c, "x-h-63") == NULL); - MQ_CHECK(cli_find_hdr(&c, "x-h-64") == NULL); - MQ_CHECK(cli_find_hdr(&c, "x-h-69") == NULL); - - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 12d: x-mq-origin-protocol from origin dropped before count - * - * Bug: origin_on_header checked the header-count overflow BEFORE dropping - * x-mq-origin-protocol. When the origin replied with exactly 64 forwardable - * headers (fitting within MQ_GWS_MAX_HDRS) followed by x-mq-origin-protocol, - * the count was already 64 when x-mq-origin-protocol arrived, so the overflow - * check tripped even though that header would merely be dropped — producing a - * false-positive 502. - * - * Fix: move the x-mq-origin-protocol drop ABOVE the count check so that drop- - * class checks run before capacity checks; dropped headers must not count - * toward capacity. - * - * The /boundary64 origin route emits exactly 64 forwardable headers (3 pre-set - * standard headers + 61 x-h-NN) followed by x-mq-origin-protocol: fake. - * Expected post-fix: 200, all 64 headers forwarded, x-mq-origin-protocol - * overwritten to "http/1.1" (the gateway's synthesis). */ -static void -test_gws_xmq_origin_protocol_drop_before_count(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 0) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(4096); - c.body_cap = 4096; - - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), - "/boundary64", "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - /* Post-fix: the 64 forwardable headers fit; x-mq-origin-protocol is dropped - * (not counted) → no overflow → 200. Pre-fix: 502 + upstream-protocol. */ - MQ_CHECK_EQ_INT(c.status, 200); - - /* x-mq-origin-protocol must be the gateway's own synthesis ("http/1.1"), - * NOT the origin's "fake" value, confirming the drop + re-synthesis. - * Guard the strcmp to avoid a crash on the NULL that the bug produces. */ - const char *prot = cli_find_hdr(&c, "x-mq-origin-protocol"); - MQ_CHECK(prot != NULL); - if (prot) MQ_CHECK(strcmp(prot, "http/1.1") == 0); - - /* Spot-check that x-h-NN headers are forwarded. CLI_MAX_HDRS=32 limits the - * client-side capture to 32 non-status headers (x-mq-origin-protocol, Date, - * Content-Length, Content-Type occupy slots 0-3; x-h-00..x-h-27 fill 4-31). - * We check x-h-00 (first custom) and x-h-27 (last in the capture window); - * headers beyond x-h-27 are silently capped by the test fixture, but the - * 200 status already confirms no overflow was triggered. */ - MQ_CHECK(cli_find_hdr(&c, "x-h-00") != NULL); - MQ_CHECK(cli_find_hdr(&c, "x-h-27") != NULL); - - /* No x-mq-error (was "upstream-protocol" under the bug). */ - MQ_CHECK(cli_find_hdr(&c, "x-mq-error") == NULL); - - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 9: rejected requests reclaim per-request state ────────── - * - * Regression for the no-origin reject state leak: a request rejected BEFORE the - * origin side ever exists (wrong token → 403, bad header/target → 400) had - * oreq==NULL && origin_dead==0, so h3_on_close never flipped origin_dead and - * gw_req_maybe_free never freed the state — it stayed linked in s->reqs forever. - * Send N wrong-token requests sequentially on one conn, then assert the live - * per-request count is 0 once all close + the loop drains. */ -static void -test_gws_reject_no_leak(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 4096) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - - const int N = 8; - for (int i = 0; i < N; i++) { - cli_req_t c; - memset(&c, 0, sizeof(c)); - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), - "/blob", "Bearer WRONG", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 5000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - MQ_CHECK_EQ_INT(c.status, 403); - } - - /* All N rejected requests have closed; let any deferred on_close drain. */ - pump_a_bit(f.base, 300); - - MQ_CHECK_EQ_INT(mq_gw_server_requests(f.gw), (unsigned)N); - /* The leak shows here: pre-fix this returns N (8); post-fix it must be 0. */ - MQ_CHECK_EQ_INT(mq_gw_server_live_reqs(f.gw), 0); - - gws_fixture_down(&f); -} - -/* ── gw_server Case 8: teardown with a request in flight (origin /slow) ──────*/ -static void -test_gws_teardown_inflight(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 0) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - cli_req_t c; - memset(&c, 0, sizeof(c)); - - /* /slow replies only after a 300ms origin timer — so at teardown the gw holds - * a live in-flight origin request. */ - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "/slow", - "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - - /* Pump enough to forward the request + start the origin (but NOT the 300ms - * reply). The gw now has a live origin request. */ - pump_a_bit(f.base, 80); - MQ_CHECK_EQ_INT(c.closed, 0); /* not done yet */ - MQ_CHECK_EQ_INT(mq_gw_server_requests(f.gw), 1); - - /* Tear down mid-request, sanctioned order. Must be ASan / LSan clean. */ - gws_fixture_down(&f); -} - -/* ── gw_server Case 9: mq_gw_server_active_conn lifecycle + clear-on-close ──── - * - * Phase 5c review fix (P2): the gw_server tracks its most-recently-accepted H3 - * tunnel conn for periodic metrics. This mirrors test_metrics_active_conn.c - * (which covers the TCP server's mq_server_active_conn) for the gateway path: - * 1. After one full gateway fetch the gw has accepted an H3 conn, so - * mq_gw_server_active_conn(f.gw) is non-NULL (set in gw_on_new_conn). - * 2. mq_h3_conn_dump_stats() on that pointer must not crash/leak (ASan smoke). - * 3. Closing that conn fires gw_srv_conn_state's clear-if-self guard, so the - * accessor returns NULL — the observability pointer is never left dangling - * (UAF guard, load-bearing: see the revert-proof in the commit message). */ -static void -test_gws_active_conn(void) -{ - const size_t N = 4096; - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", N) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(N); - c.body_cap = N; - - /* (Setup) Drive ONE full fetch through the real gw client→server path, so the - * gw_server accepts an H3 conn and sets last_conn. */ - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "/blob", - "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - - uint64_t deadline = now_ms() + 10000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - MQ_CHECK_EQ_INT(c.status, 200); - MQ_CHECK_EQ_INT((long long)c.body_len, (long long)N); - - /* (1) A conn was accepted + tracked. */ - mq_h3_conn_t *active = mq_gw_server_active_conn(f.gw); - MQ_CHECK(active != NULL); - - /* (2) ASan smoke: dumping stats on the live conn must not crash/leak — this is - * exactly what the periodic metrics path will call on the server side. */ - mq_h3_conn_dump_stats(mq_gw_server_active_conn(f.gw)); - - /* (3) Close that conn; pump (bounded) until gw_srv_conn_state's clear-if-self - * guard NULLs last_conn. There is no client reconnect, so nothing re-sets it. */ - mq_h3_conn_close(mq_gw_server_active_conn(f.gw)); - uint64_t clear_deadline = now_ms() + 3000; - while (mq_gw_server_active_conn(f.gw) != NULL && now_ms() < clear_deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - - MQ_CHECK(mq_gw_server_active_conn(f.gw) == NULL); - - free(c.body); - gws_fixture_down(&f); -} - -/* ── gw_server Case 13: multi-conn teardown — older live conn must not UAF ──── - * - * Regression for the gw_srv_conn_state detach-on-overwrite (gw_on_new_conn in - * src/gateway/mq_gw_server.c). The gw_server installs gw_srv_conn_state (user = - * the mq_gw_server_t *) on every accepted H3 tunnel conn and tracks the most- - * recent one in s->last_conn. When a SECOND conn is accepted while the FIRST is - * still live, gw_on_new_conn must DETACH the cb from the first before overwriting - * last_conn, so AT MOST ONE live conn ever carries the cb. - * - * Without that detach, the older still-live conn keeps gw_srv_conn_state(s). - * At teardown gw_server_free(s) frees `s` BEFORE mq_h3_free() (engine teardown, - * which fires c->on_state(c, 0, user) for every remaining conn) — so the older - * conn would fire gw_srv_conn_state into the freed `s`: a heap-use-after-free. - * - * This test accepts TWO server-side conns to the SAME gw_server (via two SEPARATE - * client H3 stacks → two mq_h3_connect), leaves the OLDER one LIVE (never closed), - * then runs the sanctioned fixture teardown (mq_gw_server_free → mq_h3_free → - * transport_free). Under ASan it must be clean WITH the fix and a heap-use-after- - * free WITHOUT it. */ - -/* A minimal second client-side H3 stack (its own transport + runtime + h3 + conn) - * connecting to the gw_server, used only to force a 2nd server-side accept. */ -typedef struct { - mq_transport_t *t; - mq_runtime_t *rt; - mq_h3_t *h3; - mq_h3_conn_t *conn; - int up; -} gws_cli2_t; - -static void -gws_cli2_conn_state(mq_h3_conn_t *cn, int established, void *user) -{ - (void)cn; - gws_cli2_t *c2 = (gws_cli2_t *)user; - c2->up = established ? 1 : 0; -} - -/* Stand up an independent client H3 conn to f->srv_port; pump until established. - * Returns 0 on success. Caller frees with gws_cli2_down. */ -static int -gws_cli2_up(gws_fixture_t *f, gws_cli2_t *c2) -{ - memset(c2, 0, sizeof(*c2)); - c2->t = mq_transport_new(0); - if (!c2->t) return -1; - c2->rt = mq_runtime_new(c2->t, f->base); - if (!c2->rt) return -1; - if (mq_runtime_open_udp_path(c2->rt, "127.0.0.1", 0) != 0) return -1; - c2->h3 = mq_h3_init(c2->t, NULL, NULL, NULL); - if (!c2->h3) return -1; - - struct sockaddr_in peer; - memset(&peer, 0, sizeof(peer)); - peer.sin_family = AF_INET; - peer.sin_port = htons(f->srv_port); - inet_pton(AF_INET, "127.0.0.1", &peer.sin_addr); - c2->conn = mq_h3_connect(c2->h3, (struct sockaddr *)&peer, sizeof(peer), MQ_CC_BBR2, - /*keepalive_idle_ms=*/0, gws_cli2_conn_state, c2); - if (!c2->conn) return -1; - - uint64_t deadline = now_ms() + 5000; - while (!c2->up && now_ms() < deadline) - event_base_loop(f->base, EVLOOP_NONBLOCK); - return c2->up ? 0 : -1; -} - -static void -gws_cli2_down(gws_cli2_t *c2) -{ - if (c2->h3) mq_h3_free(c2->h3); - if (c2->t) mq_transport_free(c2->t); - if (c2->rt) mq_runtime_free(c2->rt); -} - -static void -test_gws_multi_conn_teardown(void) -{ - gws_fixture_t f; - if (gws_fixture_up(&f, "sekrit", 4096) != 0) { - gws_fixture_down(&f); - MQ_CHECK(0); - return; - } - - /* (1) First server-side accept: the fixture's own client conn. Drive ONE full - * fetch so the gw_server has surely accepted + tracked it (gw_on_new_conn). */ - cli_req_t c; - memset(&c, 0, sizeof(c)); - c.body = malloc(4096); - c.body_cap = 4096; - int rc = gws_send_request(&f, &c, "GET", "http", origin_authority(&f.origin), "/blob", - "Bearer sekrit", NULL, 0, 0, NULL); - MQ_CHECK_EQ_INT(rc, 0); - uint64_t deadline = now_ms() + 10000; - while (!c.closed && now_ms() < deadline) - event_base_loop(f.base, EVLOOP_NONBLOCK); - MQ_CHECK_EQ_INT(c.status, 200); - - mq_h3_conn_t *first = mq_gw_server_active_conn(f.gw); - MQ_CHECK(first != NULL); /* positive assertion: first conn accepted + tracked */ - free(c.body); - - /* (2) Second server-side accept: a SEPARATE client H3 stack → a 2nd - * mq_h3_connect to the SAME gw_server. After it establishes, gw_on_new_conn - * fires again and active_conn must move to the new (second) conn — proving two - * DISTINCT conns were accepted. The FIRST conn is never closed → still live. */ - gws_cli2_t c2; - int rc2 = gws_cli2_up(&f, &c2); - MQ_CHECK_EQ_INT(rc2, 0); - - mq_h3_conn_t *second = mq_gw_server_active_conn(f.gw); - MQ_CHECK(second != NULL); /* positive assertion: a conn is still tracked */ - MQ_CHECK(second != first); /* positive assertion: it MOVED to a 2nd conn */ - - /* (3) Tear down WITHOUT closing either conn. The first is still live and, pre- - * fix, still carries gw_srv_conn_state(s). gws_fixture_down frees the gw_server - * (frees `s`) BEFORE mq_h3_free fires the close transition on every remaining - * server conn — pre-fix that fires gw_srv_conn_state(freed s) → ASan UAF. - * Free the 2nd client stack first (so its conn is gone before the server-side - * teardown), then the sanctioned fixture teardown for the rest. */ - gws_cli2_down(&c2); - gws_fixture_down(&f); -} - -int -main(void) -{ - struct event_base *base = event_base_new(); - MQ_CHECK(base != NULL); - if (!base) return 1; - - test_get_blob(base); - test_download_pause(base); - test_upload_pause(base); - test_nxdomain(base); - test_conn_refused(base); - test_abort_midflight(base); - test_status_line_parser(); - - event_base_free(base); - - /* Gateway-server (Task 4.3) cases — each owns its own base + transports. */ - test_gws_get(); - test_gws_req_metrics_on(); - test_gws_req_metrics_off(); - test_gws_class(); - test_gws_403(); - test_gws_masq_unauth_404(); - test_gws_masq_authed_keeps_diag(); - test_gws_400(); - test_gws_bad_header(); - test_gws_502(); - test_gws_upload(); - test_gws_chunked_upload(); - test_gws_mid_reset(); - test_gws_reject_no_leak(); - test_gws_method_validation(); - test_gws_content_length_strict(); - test_gws_header_count_overflow(); - test_gws_oversized_header(); - test_gws_oversized_origin_header(); - test_gws_resp_header_count_overflow(); - test_gws_xmq_origin_protocol_drop_before_count(); - test_gws_teardown_inflight(); - test_gws_active_conn(); - test_gws_multi_conn_teardown(); - - if (mq_test_failures) { - fprintf(stderr, "%d failure(s)\n", mq_test_failures); - return 1; - } - printf("OK\n"); - return 0; -} diff --git a/tests/integration/test_h3_fabric.c b/tests/integration/test_h3_fabric.c deleted file mode 100644 index 45a1cf56..00000000 --- a/tests/integration/test_h3_fabric.c +++ /dev/null @@ -1,792 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_h3_fabric.c — socket-free + libevent-free HTTP/3 request round-trip over - * the mq_h3 wrapper (Phase 2, Task 1.3). - * - * This is the H3 counterpart of test_transport_fabric.c: two mq_transport - * instances (client + server) wired memory-to-memory through an in-memory - * "fabric" (two directional packet queues). Each transport's send_udp callback - * enqueues into the PEER's inbound queue; a single-threaded bounded driver loop - * ticks both transports and drains each queue into the peer via - * mq_transport_on_udp_recv (design §7). No real UDP sockets, no libevent. - * - * client mq_transport.send_udp ──┐ - * ├─ in-memory fabric (per-path queues) - * server mq_transport.send_udp ──┘ peer's on_udp_recv ← driver drains - * - * On top of that transport fabric this drives xquic's built-in HTTP/3 stack via - * the mq_h3 wrapper (src/transport/mq_h3.{c,h}). mq_h3 is the module under test: - * - * 1. client→server request: headers (:method/:scheme/:authority/:path + a - * custom header) + body in 3 chunks + fin. The server's on_new_req fires; - * recv_headers/recv_body are gated on the READ_HEADER/READ_BODY notify - * flags; the server reconstructs headers (names+values) and body byte-exact. - * 2. server→client response on the same request: headers (:status: 200 + a - * header) + body + fin; the client receives them byte-exact. - * 3. empty-fin: a request with headers + fin and no body; the server observes - * fin via the recv path (asserted on the fin flag from recv_headers / - * recv_body, NOT on a specific notify flag — framing-dependent). - * 4. reset + slot reclaim: mq_h3_req_reset on the client fires the server's - * on_close for that request; then closing the whole h3 conn and opening a - * NEW h3 conn succeeds (the conn-table slot freed in h3_conn_close_notify - * is reused — proves the close_notify removal path). - * - * Plus the basic conn lifecycle: mq_h3_connect → the state callback reports - * established=1 after pumping the handshake. - * - * KNOWN LIMITATION (Phase T): an in-memory fabric cannot catch timer bugs (the - * runtime's armed timer is replaced by manual ticking). Timer correctness is the - * job of the real-socket tests; this test isolates the H3 data plane and proves - * it correct deterministically. - * - * LIFETIME: mq_h3_free MUST run before mq_transport_free (mq_h3.h contract — the - * H3 ctx is destroyed via the still-live engine). - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "transport/mq_conn.h" -#include "transport/mq_h3.h" -#include "transport/mq_transport.h" - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -/* Cap the driver loop so the test can NEVER hang. A working transport settles - * the handshake + an H3 request/response in a handful of round trips; this is - * comfortably above that. A regression must FAIL (cap reached), not hang. */ -#define MAX_ITERS 4000 -#define FABRIC_MAX_PKTS 256 -#define FABRIC_MTU 2048 -#define WALL_BUDGET_MS 5000 - -#define CLI_PORT 50001 -#define SRV_PORT 50002 - -/* ── request payloads (round-trip byte-integrity assertions) ──────────────── */ - -/* Client request body, sent in 3 chunks. */ -static const unsigned char REQ_BODY[] = - "alpha-bravo-charlie-delta-echo-foxtrot-golf-hotel-india-juliet"; -#define REQ_BODY_LEN (sizeof(REQ_BODY) - 1) - -/* Server response body. */ -static const unsigned char RESP_BODY[] = "HTTP/3 fabric response payload bytes"; -#define RESP_BODY_LEN (sizeof(RESP_BODY) - 1) - -/* ── in-memory fabric (identical pattern to test_transport_fabric.c) ──────── */ - -typedef struct { - uint8_t buf[FABRIC_MTU]; - size_t len; - uint64_t path; -} fabric_pkt_t; - -typedef struct { - fabric_pkt_t pkts[FABRIC_MAX_PKTS]; - size_t count; - int overflow; -} fabric_queue_t; - -typedef struct { - fabric_queue_t to_server; - fabric_queue_t to_client; -} fabric_t; - -typedef struct { - fabric_queue_t *outbound; /* the PEER's inbound queue */ - const char *role; -} endpoint_t; - -static void -fabric_enqueue(fabric_queue_t *q, uint64_t path, const uint8_t *pkt, size_t len) -{ - if (q->count >= FABRIC_MAX_PKTS || len > FABRIC_MTU) { - q->overflow = 1; - return; - } - fabric_pkt_t *p = &q->pkts[q->count++]; - memcpy(p->buf, pkt, len); - p->len = len; - p->path = path; -} - -static void -make_addr(struct sockaddr_in *sa, uint16_t port) -{ - memset(sa, 0, sizeof(*sa)); - sa->sin_family = AF_INET; - sa->sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa->sin_port = htons(port); -} - -static int -fab_send_udp(uint64_t path, const uint8_t *pkt, size_t len, const struct sockaddr *peer, - socklen_t peerlen, void *user) -{ - (void)peer; - (void)peerlen; - endpoint_t *ep = (endpoint_t *)user; - fabric_enqueue(ep->outbound, path, pkt, len); - return (int)len; -} - -static int -fab_open_path_socket(uint64_t path, const char *local_ip, uint16_t port, void *user) -{ - (void)path; - (void)local_ip; - (void)port; - (void)user; - return 0; -} - -static void -fab_close_path_socket(uint64_t path, void *user) -{ - (void)path; - (void)user; -} - -static int -fabric_drain(fabric_queue_t *q, mq_transport_t *dst, uint16_t dst_port, uint16_t src_port) -{ - struct sockaddr_in local, peer; - make_addr(&local, dst_port); - make_addr(&peer, src_port); - - int consumed = 0; - for (size_t i = 0; i < q->count; i++) { - fabric_pkt_t *p = &q->pkts[i]; - int r = mq_transport_on_udp_recv(dst, p->path, p->buf, p->len, - (struct sockaddr *)&local, sizeof(local), - (struct sockaddr *)&peer, sizeof(peer)); - if (r >= 0) consumed++; - } - q->count = 0; - return consumed; -} - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -/* ── shared test state ────────────────────────────────────────────────────── */ - -typedef struct { - fabric_t *fabric; - mq_transport_t *client; - mq_transport_t *server; -} pump_ctx_t; - -/* Drive both transports one step: tick both, drain both directions. */ -static void -pump_once(pump_ctx_t *p) -{ - mq_transport_tick(p->client); - mq_transport_tick(p->server); - fabric_drain(&p->fabric->to_server, p->server, SRV_PORT, CLI_PORT); - fabric_drain(&p->fabric->to_client, p->client, CLI_PORT, SRV_PORT); - (void)mq_transport_next_timeout_ms(p->client); - (void)mq_transport_next_timeout_ms(p->server); -} - -/* Pump until `*done` is set, or the bounded caps fire. Returns iteration count. */ -static int -pump_until(pump_ctx_t *p, const volatile int *done) -{ - uint64_t deadline = now_ms() + WALL_BUDGET_MS; - int iters = 0; - while (!*done && iters < MAX_ITERS && now_ms() < deadline) { - pump_once(p); - iters++; - } - return iters; -} - -/* Bounded pump regardless of state (let in-flight frames flush). */ -static void -pump_n(pump_ctx_t *p, int n) -{ - uint64_t deadline = now_ms() + WALL_BUDGET_MS; - for (int i = 0; i < n && now_ms() < deadline; i++) { - pump_once(p); - } -} - -/* Client-side conn lifecycle witness. */ -static int g_client_conn_established; - -static void -on_client_conn_state(mq_h3_conn_t *c, int established, void *user) -{ - (void)c; - (void)user; - if (established) { - g_client_conn_established = 1; - } -} - -/* ── server-side request accumulation ─────────────────────────────────────── */ - -#define MAX_HDRS 16 -#define MAX_HDR_LEN 256 - -typedef struct { - char name[MAX_HDR_LEN]; - char value[MAX_HDR_LEN]; -} captured_hdr_t; - -typedef struct { - mq_h3_req_t *req; - - /* Captured request headers (client → server). */ - captured_hdr_t hdrs[MAX_HDRS]; - int n_hdrs; - int got_headers; - - /* Accumulated request body. */ - unsigned char body[FABRIC_MTU]; - size_t body_len; - - int saw_fin; /* fin observed via recv path */ - int closed; /* on_close fired */ - int recv_err; /* recv reported a hard error (e.g. peer reset) */ -} srv_req_t; - -static srv_req_t g_srv_req; - -static void -capture_header(const char *n, size_t nl, const char *v, size_t vl, void *u) -{ - srv_req_t *sr = (srv_req_t *)u; - if (sr->n_hdrs >= MAX_HDRS) { - return; - } - captured_hdr_t *h = &sr->hdrs[sr->n_hdrs++]; - size_t cn = nl < MAX_HDR_LEN - 1 ? nl : MAX_HDR_LEN - 1; - size_t cv = vl < MAX_HDR_LEN - 1 ? vl : MAX_HDR_LEN - 1; - memcpy(h->name, n, cn); - h->name[cn] = '\0'; - memcpy(h->value, v, cv); - h->value[cv] = '\0'; -} - -/* Look up a captured header by name; returns its value or NULL. */ -static const char * -find_header(const srv_req_t *sr, const char *name) -{ - for (int i = 0; i < sr->n_hdrs; i++) { - if (strcmp(sr->hdrs[i].name, name) == 0) { - return sr->hdrs[i].value; - } - } - return NULL; -} - -/* Server request read notify: gate recv_headers on READ_HEADER/READ_TRAILER and - * recv_body on READ_BODY, accumulating both. */ -static void -on_srv_req_read(mq_h3_req_t *r, int flag, void *user) -{ - srv_req_t *sr = (srv_req_t *)user; - - if (flag & (XQC_REQ_NOTIFY_READ_HEADER | XQC_REQ_NOTIFY_READ_TRAILER)) { - int fin = 0; - int n = mq_h3_req_recv_headers(r, capture_header, sr, &fin); - if (n >= 0) { - sr->got_headers = 1; - } - if (fin) { - sr->saw_fin = 1; - } - } - - /* Drain body whenever any read notify fires (READ_BODY, or an EMPTY_FIN that - * carries no body — recv_body still reports the fin). */ - for (;;) { - int fin = 0; - long n = mq_h3_req_recv_body(r, sr->body + sr->body_len, - sizeof(sr->body) - sr->body_len, &fin); - if (n > 0) { - sr->body_len += (size_t)n; - } - if (fin) { - sr->saw_fin = 1; - } - if (n < 0) { - sr->recv_err = 1; /* hard error (e.g. peer reset) surfaced on recv */ - } - if (n <= 0) { - break; - } - } -} - -static void -on_srv_req_close(mq_h3_req_t *r, void *user) -{ - (void)r; - srv_req_t *sr = (srv_req_t *)user; - sr->closed = 1; - sr->req = NULL; /* freed immediately after this callback returns */ -} - -static void -on_srv_new_req(mq_h3_req_t *r, void *user) -{ - (void)user; - g_srv_req.req = r; - mq_h3_req_set_cbs(r, on_srv_req_read, /*on_write=*/NULL, on_srv_req_close, - &g_srv_req); -} - -static void -on_srv_new_conn(mq_h3_conn_t *c, void *user) -{ - (void)c; - (void)user; -} - -/* ── client-side response accumulation ────────────────────────────────────── */ - -typedef struct { - captured_hdr_t hdrs[MAX_HDRS]; - int n_hdrs; - int got_headers; - unsigned char body[FABRIC_MTU]; - size_t body_len; - int saw_fin; -} cli_resp_t; - -static cli_resp_t g_cli_resp; - -/* Capture response headers into cli_resp_t (client side). */ -static void -capture_cli_header(const char *n, size_t nl, const char *v, size_t vl, void *u) -{ - cli_resp_t *cr = (cli_resp_t *)u; - if (cr->n_hdrs >= MAX_HDRS) { - return; - } - captured_hdr_t *h = &cr->hdrs[cr->n_hdrs++]; - size_t cn = nl < MAX_HDR_LEN - 1 ? nl : MAX_HDR_LEN - 1; - size_t cv = vl < MAX_HDR_LEN - 1 ? vl : MAX_HDR_LEN - 1; - memcpy(h->name, n, cn); - h->name[cn] = '\0'; - memcpy(h->value, v, cv); - h->value[cv] = '\0'; -} - -static const char * -find_cli_header(const cli_resp_t *cr, const char *name) -{ - for (int i = 0; i < cr->n_hdrs; i++) { - if (strcmp(cr->hdrs[i].name, name) == 0) { - return cr->hdrs[i].value; - } - } - return NULL; -} - -/* Client read handler: capture response headers + body into cli_resp_t. */ -static void -on_cli_resp_read(mq_h3_req_t *r, int flag, void *user) -{ - cli_resp_t *cr = (cli_resp_t *)user; - - if (flag & (XQC_REQ_NOTIFY_READ_HEADER | XQC_REQ_NOTIFY_READ_TRAILER)) { - int fin = 0; - int n = mq_h3_req_recv_headers(r, capture_cli_header, cr, &fin); - if (n >= 0) { - cr->got_headers = 1; - } - if (fin) { - cr->saw_fin = 1; - } - } - - for (;;) { - int fin = 0; - long n = mq_h3_req_recv_body(r, cr->body + cr->body_len, - sizeof(cr->body) - cr->body_len, &fin); - if (n > 0) { - cr->body_len += (size_t)n; - } - if (fin) { - cr->saw_fin = 1; - } - if (n <= 0) { - break; - } - } -} - -/* ── fabric / transport bring-up shared by the cases ──────────────────────── */ - -typedef struct { - fabric_t fabric; - endpoint_t client_ctx; - endpoint_t server_ctx; - mq_transport_t *client; - mq_transport_t *server; - mq_h3_t *ch3; - mq_h3_t *sh3; - pump_ctx_t pump; -} h3_fabric_t; - -/* Stand up two transports + their mq_h3 stacks over a fresh fabric. The server - * surfaces conns/requests via on_srv_new_conn / on_srv_new_req. Returns 0 on - * success. */ -static int -h3_fabric_setup(h3_fabric_t *f) -{ - memset(f, 0, sizeof(*f)); - f->client_ctx.outbound = &f->fabric.to_server; - f->client_ctx.role = "client"; - f->server_ctx.outbound = &f->fabric.to_client; - f->server_ctx.role = "server"; - - mq_transport_callbacks_t cbs = { - .send_udp = fab_send_udp, - .open_path_socket = fab_open_path_socket, - .close_path_socket = fab_close_path_socket, - }; - - f->client = mq_transport_new(/*is_server=*/0); - f->server = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - if (!f->client || !f->server) { - return -1; - } - mq_transport_set_callbacks(f->client, &cbs, &f->client_ctx); - mq_transport_set_callbacks(f->server, &cbs, &f->server_ctx); - - /* Server must advertise enable_multipath + the path-id grant so the H3 - * conn settings line up with the client (negotiated on both sides). */ - xqc_conn_settings_t srv_settings; - memset(&srv_settings, 0, sizeof(srv_settings)); - srv_settings.proto_version = XQC_VERSION_V1; - srv_settings.pacing_on = 1; - mq_conn_apply_mp_settings(&srv_settings, /*is_server=*/1, MQ_CC_DEFAULT); - xqc_server_set_conn_settings(mq_transport_xqc(f->server), &srv_settings); - - /* H3 stacks: the server surfaces peer conns/requests; the client is a pure - * initiator (NULL server hooks). */ - f->sh3 = mq_h3_init(f->server, on_srv_new_conn, on_srv_new_req, NULL); - f->ch3 = mq_h3_init(f->client, NULL, NULL, NULL); - if (!f->sh3 || !f->ch3) { - return -1; - } - - f->pump.fabric = &f->fabric; - f->pump.client = f->client; - f->pump.server = f->server; - return 0; -} - -/* LIFETIME: mq_h3_free before mq_transport_free (mq_h3.h contract). */ -static void -h3_fabric_teardown(h3_fabric_t *f) -{ - if (f->ch3) mq_h3_free(f->ch3); - if (f->sh3) mq_h3_free(f->sh3); - if (f->client) mq_transport_free(f->client); - if (f->server) mq_transport_free(f->server); -} - -/* Open a client H3 conn and pump until established. Returns the conn or NULL. */ -static mq_h3_conn_t * -h3_connect_established(h3_fabric_t *f) -{ - struct sockaddr_in srv_addr; - make_addr(&srv_addr, SRV_PORT); - - g_client_conn_established = 0; - mq_h3_conn_t *c = - mq_h3_connect(f->ch3, (struct sockaddr *)&srv_addr, sizeof(srv_addr), - MQ_CC_DEFAULT, /*keepalive_idle_ms=*/0, on_client_conn_state, NULL); - if (!c) { - return NULL; - } - pump_until(&f->pump, &g_client_conn_established); - return c; -} - -/* ── Case 1+2: full request + response round trip ─────────────────────────── */ - -static void -test_h3_request_response(void) -{ - h3_fabric_t f; - if (h3_fabric_setup(&f) != 0) { - MQ_CHECK(0); - return; - } - - memset(&g_srv_req, 0, sizeof(g_srv_req)); - memset(&g_cli_resp, 0, sizeof(g_cli_resp)); - - mq_h3_conn_t *conn = h3_connect_established(&f); - MQ_CHECK(conn != NULL); - MQ_CHECK(g_client_conn_established); - if (!conn) { - h3_fabric_teardown(&f); - return; - } - - /* Client opens a request and sends headers (no fin — body follows). */ - mq_h3_req_t *creq = mq_h3_req_open(conn); - MQ_CHECK(creq != NULL); - if (!creq) { - h3_fabric_teardown(&f); - return; - } - mq_h3_req_set_cbs(creq, on_cli_resp_read, NULL, NULL, &g_cli_resp); - - mq_h3_header_t req_hdrs[] = { - {":method", "GET"}, {":scheme", "https"}, {":authority", "example.test"}, - {":path", "/x"}, {"x-mq-custom", "fabric-1"}, - }; - long sh = mq_h3_req_send_headers(creq, req_hdrs, - sizeof(req_hdrs) / sizeof(req_hdrs[0]), /*fin=*/0); - MQ_CHECK(sh > 0); - - /* Body in 3 chunks; fin on the last. */ - size_t third = REQ_BODY_LEN / 3; - size_t offs[4] = {0, third, 2 * third, REQ_BODY_LEN}; - for (int i = 0; i < 3; i++) { - size_t len = offs[i + 1] - offs[i]; - int last = (i == 2); - long sb = mq_h3_req_send_body(creq, REQ_BODY + offs[i], len, /*fin=*/last); - /* A chunk may flow-control block (0); pump and retry. */ - int tries = 0; - while (sb == 0 && tries < 100) { - pump_once(&f.pump); - sb = mq_h3_req_send_body(creq, REQ_BODY + offs[i], len, last); - tries++; - } - MQ_CHECK(sb == (long)len); - } - - /* Server side: pump until the full request (headers + body + fin) landed. */ - pump_until(&f.pump, &g_srv_req.saw_fin); - - /* Case 1 assertions: server saw the request, headers byte-exact, body - * byte-exact, fin observed. */ - MQ_CHECK(g_srv_req.req != NULL || g_srv_req.saw_fin); /* req surfaced */ - MQ_CHECK(g_srv_req.got_headers); - MQ_CHECK(g_srv_req.saw_fin); - MQ_CHECK_EQ_INT((int)g_srv_req.body_len, (int)REQ_BODY_LEN); - MQ_CHECK_MEM(g_srv_req.body, REQ_BODY, REQ_BODY_LEN); - - const char *m = find_header(&g_srv_req, ":method"); - const char *sc = find_header(&g_srv_req, ":scheme"); - const char *au = find_header(&g_srv_req, ":authority"); - const char *pa = find_header(&g_srv_req, ":path"); - const char *cu = find_header(&g_srv_req, "x-mq-custom"); - MQ_CHECK(m && strcmp(m, "GET") == 0); - MQ_CHECK(sc && strcmp(sc, "https") == 0); - MQ_CHECK(au && strcmp(au, "example.test") == 0); - MQ_CHECK(pa && strcmp(pa, "/x") == 0); - MQ_CHECK(cu && strcmp(cu, "fabric-1") == 0); - - /* ── Case 2: server sends a response on the SAME request ──────────────── */ - MQ_CHECK(g_srv_req.req != NULL); - if (g_srv_req.req) { - mq_h3_header_t resp_hdrs[] = { - {":status", "200"}, - {"x-mq-resp", "ok"}, - }; - long rh = mq_h3_req_send_headers(g_srv_req.req, resp_hdrs, - sizeof(resp_hdrs) / sizeof(resp_hdrs[0]), - /*fin=*/0); - MQ_CHECK(rh > 0); - long rb = mq_h3_req_send_body(g_srv_req.req, RESP_BODY, RESP_BODY_LEN, /*fin=*/1); - int tries = 0; - while (rb == 0 && tries < 100) { - pump_once(&f.pump); - rb = mq_h3_req_send_body(g_srv_req.req, RESP_BODY, RESP_BODY_LEN, 1); - tries++; - } - MQ_CHECK(rb == (long)RESP_BODY_LEN); - } - - pump_until(&f.pump, &g_cli_resp.saw_fin); - - MQ_CHECK(g_cli_resp.got_headers); - MQ_CHECK(g_cli_resp.saw_fin); - MQ_CHECK_EQ_INT((int)g_cli_resp.body_len, (int)RESP_BODY_LEN); - MQ_CHECK_MEM(g_cli_resp.body, RESP_BODY, RESP_BODY_LEN); - - const char *st = find_cli_header(&g_cli_resp, ":status"); - const char *xr = find_cli_header(&g_cli_resp, "x-mq-resp"); - MQ_CHECK(st && strcmp(st, "200") == 0); - MQ_CHECK(xr && strcmp(xr, "ok") == 0); - - MQ_CHECK_EQ_INT(f.fabric.to_server.overflow, 0); - MQ_CHECK_EQ_INT(f.fabric.to_client.overflow, 0); - - mq_h3_conn_close(conn); - pump_n(&f.pump, 50); - h3_fabric_teardown(&f); -} - -/* ── Case 3: empty-fin request (headers + fin, no body) ───────────────────── */ - -static void -test_h3_empty_fin(void) -{ - h3_fabric_t f; - if (h3_fabric_setup(&f) != 0) { - MQ_CHECK(0); - return; - } - memset(&g_srv_req, 0, sizeof(g_srv_req)); - - mq_h3_conn_t *conn = h3_connect_established(&f); - MQ_CHECK(conn != NULL); - if (!conn) { - h3_fabric_teardown(&f); - return; - } - - mq_h3_req_t *creq = mq_h3_req_open(conn); - MQ_CHECK(creq != NULL); - if (!creq) { - h3_fabric_teardown(&f); - return; - } - - mq_h3_header_t req_hdrs[] = { - {":method", "GET"}, - {":scheme", "https"}, - {":authority", "example.test"}, - {":path", "/empty"}, - }; - /* Headers WITH fin: no body follows. */ - long sh = mq_h3_req_send_headers(creq, req_hdrs, - sizeof(req_hdrs) / sizeof(req_hdrs[0]), /*fin=*/1); - MQ_CHECK(sh > 0); - - pump_until(&f.pump, &g_srv_req.saw_fin); - - /* The server observed fin via the recv path (do NOT assert on a specific - * notify flag — EMPTY_FIN framing is implementation-dependent). */ - MQ_CHECK(g_srv_req.got_headers); - MQ_CHECK(g_srv_req.saw_fin); - MQ_CHECK_EQ_INT((int)g_srv_req.body_len, 0); - - const char *pa = find_header(&g_srv_req, ":path"); - MQ_CHECK(pa && strcmp(pa, "/empty") == 0); - - mq_h3_conn_close(conn); - pump_n(&f.pump, 50); - h3_fabric_teardown(&f); -} - -/* ── Case 4: reset fires server on_close + conn slot reclaim ───────────────── */ - -static void -test_h3_reset_and_slot_reclaim(void) -{ - h3_fabric_t f; - if (h3_fabric_setup(&f) != 0) { - MQ_CHECK(0); - return; - } - memset(&g_srv_req, 0, sizeof(g_srv_req)); - - mq_h3_conn_t *conn = h3_connect_established(&f); - MQ_CHECK(conn != NULL); - if (!conn) { - h3_fabric_teardown(&f); - return; - } - - /* Client opens a request, sends headers (no fin), then RESETs it. */ - mq_h3_req_t *creq = mq_h3_req_open(conn); - MQ_CHECK(creq != NULL); - if (!creq) { - h3_fabric_teardown(&f); - return; - } - mq_h3_header_t req_hdrs[] = { - {":method", "GET"}, - {":scheme", "https"}, - {":authority", "example.test"}, - {":path", "/reset"}, - }; - long sh = mq_h3_req_send_headers(creq, req_hdrs, - sizeof(req_hdrs) / sizeof(req_hdrs[0]), /*fin=*/0); - MQ_CHECK(sh > 0); - - /* Make sure the request reaches the server (so it has a wrapper to close). */ - pump_until(&f.pump, &g_srv_req.got_headers); - MQ_CHECK(g_srv_req.got_headers); - - /* Reset the request from the client and pump. - * - * FABRIC LIMITATION: in this manual-tick in-memory fabric, a client - * RESET_STREAM half-closes only the client→server direction; the server's - * send side stays open, so xquic does not destroy the bidi h3 stream - * mid-pump and defers the stream teardown — and thus h3_request_close_notify - * — to engine teardown. This is the same deferral the raw transport fabric - * documents for conn_close_notify (the runtime's armed-timer-driven cleanup - * is replaced by manual ticking). So the server's on_close is NOT observable - * mid-pump; it is asserted AFTER teardown below, where engine-destroy fires - * it. */ - mq_h3_req_reset(creq); - pump_n(&f.pump, 50); - - /* ── conn-table slot reclaim ──────────────────────────────────────────── */ - /* Close the first H3 conn; then open a NEW H3 conn — it must connect and - * reach established, exercising a fresh conn-table insert on the SAME h3 - * stack. With MQ_H3_MAX_CONNS finite, repeated open-without-reclaim would - * eventually exhaust the table; that a second conn succeeds after the first - * is closed exercises the table insert path (the matching remove runs in - * h3_conn_close_notify / mq_h3_free at teardown). */ - mq_h3_conn_close(conn); - pump_n(&f.pump, 50); - - g_client_conn_established = 0; - mq_h3_conn_t *conn2 = h3_connect_established(&f); - MQ_CHECK(conn2 != NULL); - MQ_CHECK(g_client_conn_established); - - if (conn2) { - mq_h3_conn_close(conn2); - pump_n(&f.pump, 50); - } - - h3_fabric_teardown(&f); - - /* on_close fires during engine teardown (see FABRIC LIMITATION above): the - * server's request wrapper is closed + freed, setting g_srv_req.closed. This - * proves the reset ultimately drove the request's close_notify through to the - * owner. */ - MQ_CHECK(g_srv_req.closed); -} - -MQ_TEST_MAIN({ - test_h3_request_response(); - test_h3_empty_fin(); - test_h3_reset_and_slot_reclaim(); -}) diff --git a/tests/integration/test_max_conns_e2e.c b/tests/integration/test_max_conns_e2e.c deleted file mode 100644 index 08d9469e..00000000 --- a/tests/integration/test_max_conns_e2e.c +++ /dev/null @@ -1,231 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_max_conns_e2e.c — falsifiable proof of the engine-wide connection cap. - * - * Stands up one real mq_server (--max-conns 1 equivalent) and several real - * mq_clients on a single shared libevent base (loopback), and asserts, against - * the live mq_transport_n_conns(server_transport) accessor: - * A. Client A connects + auths -> n_conns == 1. - * B. Client B (concurrent) is REFUSED at server_accept (CONNECTION_REFUSED, - * pre-handshake) -> B never auths and n_conns stays 1. - * C. Close A -> n_conns == 0 (the counted-flag decrement). - * D. Client C connects + auths -> n_conns == 1 (the freed slot is reusable). - * - * FALSIFIABILITY: with mq_transport_set_max_conns(srv_t, 0) (unlimited) instead - * of 1, B establishes and n_conns reaches 2 -> assertion B fails. This proves - * the test rides the cap path, not an unrelated reject. - * - * Reconnect is disabled on every client: default-on reconnect would re-dial a - * refused/closed conn and race the counts. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include "proxy/mq_client.h" -#include "proxy/mq_server.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_transport.h" - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -/* Reserve an ephemeral loopback UDP port, then close it so the server runtime - * can bind that fixed port (mirrors test_metrics_active_conn.c). */ -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -/* Per-client auth state, passed as the on_auth user pointer so each client's - * result is tracked independently. */ -typedef struct { - int fired; - int ok; -} auth_state_t; - -static void -on_auth_cb(int ok, mq_auth_err_t err, void *user) -{ - (void)err; - auth_state_t *s = (auth_state_t *)user; - s->fired = 1; - s->ok = ok; -} - -/* One client on the shared base: its own transport + runtime + UDP path. */ -typedef struct { - mq_transport_t *t; - mq_runtime_t *rt; - mq_client_t *cli; - auth_state_t auth; -} client_t; - -static int -client_up(client_t *c, struct event_base *base, uint16_t srv_port, const char *name) -{ - memset(c, 0, sizeof(*c)); - c->t = mq_transport_new(0); - MQ_CHECK(c->t != NULL); - if (!c->t) return -1; - c->rt = mq_runtime_new(c->t, base); - MQ_CHECK(c->rt != NULL); - if (!c->rt) return -1; - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(c->rt, "127.0.0.1", 0), 0); - c->cli = - mq_client_new(c->t, c->rt, "127.0.0.1", srv_port, name, "secret", MQ_CC_BBR2); - MQ_CHECK(c->cli != NULL); - if (!c->cli) return -1; - mq_client_set_on_auth(c->cli, on_auth_cb, &c->auth); - mq_client_set_reconnect(c->cli, 0, 30000); /* no re-dial: would race the counts */ - MQ_CHECK_EQ_INT(mq_client_start(c->cli), 0); - return 0; -} - -static void -client_down(client_t *c) -{ - /* Transport first (engine destroy fires close_notify), then runtime, then the - * client object (mirrors test_metrics_active_conn fixture_down ordering). */ - if (c->t) mq_transport_free(c->t); - if (c->rt) mq_runtime_free(c->rt); - if (c->cli) mq_client_free(c->cli); -} - -static void -pump_until_flag(struct event_base *base, int *flag, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (!*flag && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -static void -pump_until_nconns(struct event_base *base, mq_transport_t *srv_t, uint32_t target, - uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (mq_transport_n_conns(srv_t) != target && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -static void -pump_fixed(struct event_base *base, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -static void -test_max_conns_refuses_and_frees(void) -{ - struct event_base *base = event_base_new(); - MQ_CHECK(base != NULL); - if (!base) return; - - uint16_t port = reserve_udp_port(); - MQ_CHECK(port != 0); - if (!port) { - event_base_free(base); - return; - } - - /* Declared (zero-init) before any goto so no jump bypasses their init. */ - client_t a = {0}, b = {0}, cc = {0}; - - /* Server with an engine-wide cap of 1 (flip to 0 to falsify — see header). */ - mq_transport_t *srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(srv_t != NULL); - mq_runtime_t *srv_rt = srv_t ? mq_runtime_new(srv_t, base) : NULL; - MQ_CHECK(srv_rt != NULL); - mq_server_t *server = - srv_rt ? mq_server_new(srv_t, srv_rt, "secret", MQ_CC_BBR2, 60000, 1) : NULL; - MQ_CHECK(server != NULL); - if (!server) goto out_srv; - mq_transport_set_max_conns(srv_t, 1); - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(srv_rt, "127.0.0.1", port), 0); - - /* (A) First client connects + auths -> exactly one established conn. */ - if (client_up(&a, base, port, "client-a") != 0) goto out_a; - pump_until_flag(base, &a.auth.fired, 4000); - MQ_CHECK(a.auth.fired); - MQ_CHECK(a.auth.ok); - MQ_CHECK_EQ_INT(mq_transport_n_conns(srv_t), 1); - - /* (B) Second concurrent client is refused at the cap; it never auths and the - * established count stays at 1. */ - if (client_up(&b, base, port, "client-b") != 0) goto out_b; - pump_fixed(base, 2000); - MQ_CHECK(!b.auth.ok); - MQ_CHECK_EQ_INT(mq_transport_n_conns(srv_t), 1); - - /* (C) Close A -> the counted-flag decrement frees the slot. */ - mq_conn_close(mq_client_conn(a.cli)); - pump_until_nconns(base, srv_t, 0, 3000); - MQ_CHECK_EQ_INT(mq_transport_n_conns(srv_t), 0); - - /* (D) A new client now fits in the freed slot (decrement is real + reusable). */ - if (client_up(&cc, base, port, "client-c") != 0) goto out_c; - pump_until_flag(base, &cc.auth.fired, 4000); - MQ_CHECK(cc.auth.fired); - MQ_CHECK(cc.auth.ok); - MQ_CHECK_EQ_INT(mq_transport_n_conns(srv_t), 1); - -out_c: - client_down(&cc); -out_b: - client_down(&b); -out_a: - client_down(&a); -out_srv: - if (srv_t) mq_transport_free(srv_t); - if (srv_rt) mq_runtime_free(srv_rt); - if (server) mq_server_free(server); - event_base_free(base); -} - -MQ_TEST_MAIN(test_max_conns_refuses_and_frees()) diff --git a/tests/integration/test_metrics_active_conn.c b/tests/integration/test_metrics_active_conn.c deleted file mode 100644 index b6ea3618..00000000 --- a/tests/integration/test_metrics_active_conn.c +++ /dev/null @@ -1,223 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_metrics_active_conn.c — Phase 5c Task 3: mq_server_active_conn lifecycle. - * - * Stands up one real mq_client + one real mq_server on a single shared libevent - * base (loopback), runs the handshake + AUTH, and asserts: - * 1. After auth, mq_server_active_conn(server) is non-NULL (set in - * srv_on_new_conn). - * 2. mq_conn_dump_stats() on that pointer does not crash/leak (ASan smoke; - * the CLI in Task 4 will call this on the server side). - * 3. After the client conn closes, the server's clear-if-self guard fires and - * mq_server_active_conn(server) returns NULL — i.e. the observability - * pointer is never left dangling (UAF guard). - * - * Client reconnect is disabled before start: default-on reconnect would re-dial - * after the close, re-setting last_conn non-NULL and racing the == NULL assert. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include "proxy/mq_client.h" -#include "proxy/mq_server.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_transport.h" - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -/* Reserve an ephemeral loopback UDP port: bind a temp socket to :0, read the - * assigned port, then close it so the server runtime can bind that fixed port. */ -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -static int g_auth_fired; -static int g_auth_ok; - -static void -on_auth(int ok, mq_auth_err_t err, void *user) -{ - (void)err; - (void)user; - g_auth_fired = 1; - g_auth_ok = ok; -} - -/* Shared-base client+server fixture (mirrors test_client_server.c). */ -typedef struct { - struct event_base *base; - mq_transport_t *srv_t; - mq_transport_t *cli_t; - mq_runtime_t *srv_rt; - mq_runtime_t *cli_rt; - mq_server_t *server; - mq_client_t *client; -} fixture_t; - -static int -fixture_up(fixture_t *f) -{ - memset(f, 0, sizeof(*f)); - f->base = event_base_new(); - MQ_CHECK(f->base != NULL); - if (!f->base) return -1; - - uint16_t port = reserve_udp_port(); - MQ_CHECK(port != 0); - if (!port) return -1; - - /* Server: transport (TLS) + runtime borrowing the shared base. */ - f->srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(f->srv_t != NULL); - if (!f->srv_t) return -1; - f->srv_rt = mq_runtime_new(f->srv_t, f->base); - MQ_CHECK(f->srv_rt != NULL); - if (!f->srv_rt) return -1; - f->server = mq_server_new(f->srv_t, f->srv_rt, "secret", MQ_CC_BBR2, 60000, 1); - MQ_CHECK(f->server != NULL); - if (!f->server) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->srv_rt, "127.0.0.1", port), 0); - - /* Client: transport + runtime borrowing the SAME shared base. */ - f->cli_t = mq_transport_new(0); - MQ_CHECK(f->cli_t != NULL); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - MQ_CHECK(f->cli_rt != NULL); - if (!f->cli_rt) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0), 0); - - f->client = mq_client_new(f->cli_t, f->cli_rt, "127.0.0.1", port, "client-1", - "secret", MQ_CC_BBR2); - MQ_CHECK(f->client != NULL); - if (!f->client) return -1; - mq_client_set_on_auth(f->client, on_auth, NULL); - /* CRITICAL: disable reconnect before start. Default-on reconnect would - * re-dial after the close below, re-setting last_conn non-NULL and racing - * the == NULL assertion. */ - mq_client_set_reconnect(f->client, 0, 30000); - - MQ_CHECK_EQ_INT(mq_client_start(f->client), 0); - return 0; -} - -static void -fixture_down(fixture_t *f) -{ - /* Per side: free the transport FIRST (engine destroy fires conn_close_notify - * -> the client/server state callbacks), so the runtime + client + server - * must outlive the transport. Then the runtimes (which BORROWED the shared - * base, so they do NOT free it), then the proxy objects. The test owns the - * shared base and frees it last. */ - if (f->cli_t) mq_transport_free(f->cli_t); - if (f->srv_t) mq_transport_free(f->srv_t); - if (f->cli_rt) mq_runtime_free(f->cli_rt); - if (f->srv_rt) mq_runtime_free(f->srv_rt); - if (f->client) mq_client_free(f->client); - if (f->server) mq_server_free(f->server); - if (f->base) event_base_free(f->base); -} - -static void -pump_until(struct event_base *base, int *flag, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while ((!flag || !*flag) && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -/* Pump until the server's active-conn accessor goes NULL (or the budget runs - * out). Cannot use pump_until's int* flag form since the predicate is an - * accessor call, so spin a bounded loop directly. */ -static void -pump_until_active_null(fixture_t *f, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (mq_server_active_conn(f->server) != NULL && now_ms() < deadline) { - event_base_loop(f->base, EVLOOP_NONBLOCK); - } -} - -static void -test_active_conn_lifecycle(void) -{ - g_auth_fired = g_auth_ok = 0; - - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - /* Pump until the client has authed → the server has accepted the conn. */ - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_fired); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 1); - - /* (1) The server tracks the accepted conn. */ - mq_conn_t *active = mq_server_active_conn(f.server); - MQ_CHECK(active != NULL); - - /* (2) ASan smoke: dumping stats on the live conn must not crash/leak. This - * is exactly what the Task 4 CLI will call on the server side. */ - mq_conn_dump_stats(mq_server_active_conn(f.server)); - - /* (3) Close the client conn; pump until the server observes MQ_CONN_CLOSED - * and the clear-if-self guard NULLs last_conn. Reconnect is off, so there is - * no re-dial to re-set it. */ - mq_conn_close(mq_client_conn(f.client)); - pump_until_active_null(&f, 3000); - - MQ_CHECK(mq_server_active_conn(f.server) == NULL); - - fixture_down(&f); -} - -MQ_TEST_MAIN(test_active_conn_lifecycle()) diff --git a/tests/integration/test_path_bind.c b/tests/integration/test_path_bind.c deleted file mode 100644 index db2268af..00000000 --- a/tests/integration/test_path_bind.c +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_path_bind.c — runtime primary-path bind smoke test (Chunk 7). - * - * Opens the primary UDP path via mq_runtime_open_udp_path on 127.0.0.1:0 against - * a client-mode transport+runtime, checks the bind succeeds (ephemeral port-0 - * bind yields a usable path), that a bad local IP is rejected without leaking, - * and that a second open of the (already-bound) primary path is refused. Then it - * tears everything down cleanly (ASan-clean). - * - * The fine-grained mq_path accessors (fd / id / local_addr) are gone; the - * runtime only exposes open success, so that is what we assert. - */ -#include "mqtest.h" - -#include -#include - -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_transport.h" - -static void -test_path_bind(void) -{ - mq_transport_t *t = mq_transport_new(/*is_server=*/0); - MQ_CHECK(t != NULL); - if (!t) return; - - mq_runtime_t *rt = mq_runtime_new(t, /*base=*/NULL); - MQ_CHECK(rt != NULL); - if (!rt) { - mq_transport_free(t); - return; - } - - /* Ephemeral port-0 bind on loopback succeeds and yields a usable path. */ - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(rt, "127.0.0.1", /*port=*/0), 0); - - /* A bad local address is rejected without leaking. */ - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(rt, "not-an-ip", 0), -1); - - /* The primary path is already open; a second open of it is refused. */ - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(rt, "127.0.0.1", 0), -1); - - /* Teardown per side: transport first, then runtime. */ - mq_transport_free(t); - mq_runtime_free(rt); -} - -MQ_TEST_MAIN(test_path_bind()) diff --git a/tests/integration/test_qlog_blocked.c b/tests/integration/test_qlog_blocked.c deleted file mode 100644 index 877eb309..00000000 --- a/tests/integration/test_qlog_blocked.c +++ /dev/null @@ -1,554 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_qlog_blocked.c — Task 19: validate the qlog "not window-limited" instrument. - * - * The 1-B multipath aggregation benchmark (e2e_multipath.sh) proves aggregation - * is not flow-control limited by counting DATA_BLOCKED / STREAM_DATA_BLOCKED - * frames in the client's qlog: zero blocked frames == "not window-limited". That - * benchmark needs traffic shaping (tc/netem) and CANNOT run in this sandbox - * (no NET_ADMIN). This in-process test validates the MEASUREMENT INSTRUMENT the - * script relies on, which CAN run here: - * - * 1. Enable xquic qlog at EVENT_IMPORTANCE_EXTRA on the client engine, writing - * to a tmp file (mq_transport_enable_qlog). - * 2. Bring up an authed client+server over a single loopback path and run a - * clean multi-hundred-KB download through a real SOCKS5 tcp_open. The 16MB - * flow-control window dwarfs the loopback BDP, so the transfer is NEVER - * window-limited → ZERO blocked frames are emitted. - * 3. Assert on the rendered qlog file: - * - grep -c 'frames_processed' > 0 (EXTRA importance is - * actually on — proves - * the sink works) - * - grep -c 'xqc_parse_data_blocked_frame' == 0 (conn-level not blocked) - * - grep -c 'xqc_parse_stream_data_blocked_frame'==0 (stream-level not blocked) - * - * This is exactly the read the .sh performs over a real shaped 2-path run; here - * we prove the tokens are absent on an unblocked transfer and that the qlog is - * actually populated. ASan/LSan-clean; the tmp qlog is removed on exit. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ingress/mq_listener.h" -#include "proxy/mq_client.h" -#include "proxy/mq_server.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_transport.h" -#include "util/mq_log.h" - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -static void -set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - if (fl >= 0) (void)fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -/* ── full-stack fixture (mirrors test_two_paths, qlog armed on the client) ── */ -typedef struct { - struct event_base *base; - mq_transport_t *srv_t; - mq_transport_t *cli_t; - mq_runtime_t *srv_rt; - mq_runtime_t *cli_rt; - mq_server_t *server; - mq_client_t *client; - mq_listener_t *socks5; - char qlog_dir[256]; - char qlog_path[320]; /* fixture-owned copy (transport's borrow is freed early) */ -} fixture_t; - -/* Reserve an ephemeral loopback UDP port (the runtime no longer exposes the - * bound port of a :0 bind, so the server pins a concrete port up front). */ -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -static int -fixture_up(fixture_t *f) -{ - memset(f, 0, sizeof(*f)); - f->base = event_base_new(); - MQ_CHECK(f->base != NULL); - if (!f->base) return -1; - - uint16_t port = reserve_udp_port(); - MQ_CHECK(port != 0); - if (!port) return -1; - - f->srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(f->srv_t != NULL); - if (!f->srv_t) return -1; - f->srv_rt = mq_runtime_new(f->srv_t, f->base); - MQ_CHECK(f->srv_rt != NULL); - if (!f->srv_rt) return -1; - f->server = mq_server_new(f->srv_t, f->srv_rt, "secret", MQ_CC_BBR2, 60000, 1); - MQ_CHECK(f->server != NULL); - if (!f->server) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->srv_rt, "127.0.0.1", port), 0); - - f->cli_t = mq_transport_new(0); - MQ_CHECK(f->cli_t != NULL); - if (!f->cli_t) return -1; - - /* Arm qlog on the CLIENT transport to a per-pid tmp dir BEFORE any traffic. */ - snprintf(f->qlog_dir, sizeof(f->qlog_dir), "/tmp/mqproxy_qlog_test_%d", - (int)getpid()); - if (mkdir(f->qlog_dir, 0700) != 0 && errno != EEXIST) { - MQ_LOGE("test_qlog_blocked: mkdir(%s) failed: %s", f->qlog_dir, strerror(errno)); - return -1; - } - const char *qpath = NULL; - MQ_CHECK_EQ_INT(mq_transport_enable_qlog(f->cli_t, f->qlog_dir, &qpath), 0); - MQ_CHECK(qpath != NULL); - if (qpath) snprintf(f->qlog_path, sizeof(f->qlog_path), "%s", qpath); - - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - MQ_CHECK(f->cli_rt != NULL); - if (!f->cli_rt) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0), 0); - - f->client = mq_client_new(f->cli_t, f->cli_rt, "127.0.0.1", port, "client-1", - "secret", MQ_CC_BBR2); - MQ_CHECK(f->client != NULL); - if (!f->client) return -1; - - MQ_CHECK_EQ_INT(mq_client_start(f->client), 0); - - void *core = mq_client_tcp_open_core(f->client); - f->socks5 = mq_socks5_listener_new(f->base, "127.0.0.1", 0, mq_client_tcp_open_fn(), - core, NULL, NULL, NULL, NULL); - MQ_CHECK(f->socks5 != NULL); - if (!f->socks5) return -1; - return 0; -} - -static void -fixture_down(fixture_t *f) -{ - /* Per side: transport first (flushes + closes the qlog fd; fires conn-close + - * in-flight callbacks into the live runtime/client/server/listener), then the - * runtimes (which BORROWED the shared base, so they do NOT free it), then the - * proxy objects + listener. The test owns the shared base and frees it last. - * Guarded so it is safe after the early client-side teardown below NULLs the - * already-freed members. */ - if (f->cli_t) mq_transport_free(f->cli_t); /* closes + flushes the qlog fd */ - if (f->srv_t) mq_transport_free(f->srv_t); - if (f->cli_rt) mq_runtime_free(f->cli_rt); - if (f->srv_rt) mq_runtime_free(f->srv_rt); - if (f->client) mq_client_free(f->client); - if (f->server) mq_server_free(f->server); - if (f->socks5) mq_listener_free(f->socks5); - if (f->base) event_base_free(f->base); - /* Remove the tmp qlog file + dir (best effort). */ - if (f->qlog_path[0]) unlink(f->qlog_path); - if (f->qlog_dir[0]) rmdir(f->qlog_dir); -} - -static int -pump_until(struct event_base *base, int (*pred)(void *), void *arg, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (now_ms() < deadline) { - if (pred(arg)) return 1; - event_base_loop(base, EVLOOP_NONBLOCK); - } - return pred(arg); -} - -static int -pred_authed(void *arg) -{ - return mq_client_is_authed((const mq_client_t *)arg); -} - -/* ── client-socket helpers (subset of test_two_paths) ────────────────────── */ -static int -dial_nb(uint16_t port) -{ - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) return -1; - set_nonblock(fd); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr); - int r = connect(fd, (struct sockaddr *)&sa, sizeof(sa)); - if (r != 0 && errno != EINPROGRESS) { - close(fd); - return -1; - } - return fd; -} - -static int -send_all_nb(struct event_base *base, int fd, const uint8_t *buf, size_t len, - uint64_t budget_ms) -{ - size_t off = 0; - uint64_t deadline = now_ms() + budget_ms; - while (off < len && now_ms() < deadline) { - ssize_t n = send(fd, buf + off, len - off, MSG_NOSIGNAL); - if (n > 0) { - off += (size_t)n; - } else if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - event_base_loop(base, EVLOOP_NONBLOCK); - } else if (n < 0 && errno == EINTR) { - continue; - } else { - break; - } - } - return off == len ? 0 : -1; -} - -static size_t -recv_exact(struct event_base *base, int fd, uint8_t *out, size_t want, uint64_t budget_ms) -{ - size_t got = 0; - uint64_t deadline = now_ms() + budget_ms; - while (got < want && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - ssize_t n = recv(fd, out + got, want - got, 0); - if (n > 0) { - got += (size_t)n; - } else if (n == 0) { - break; - } - } - return got; -} - -static size_t -drain_some(struct event_base *base, int fd, uint8_t *out, size_t cap, uint64_t budget_ms) -{ - size_t got = 0; - uint64_t deadline = now_ms() + budget_ms; - while (got < cap && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - ssize_t n = recv(fd, out + got, cap - got, 0); - if (n > 0) { - got += (size_t)n; - } else if (n == 0) { - break; - } - } - return got; -} - -static int -socks5_greet(struct event_base *base, int fd) -{ - uint8_t greeting[] = {0x05, 0x01, 0x00}; - if (send_all_nb(base, fd, greeting, sizeof(greeting), 1000) != 0) return -1; - uint8_t mreply[2] = {0}; - if (recv_exact(base, fd, mreply, 2, 2000) != 2) return -1; - if (mreply[0] != 0x05 || mreply[1] != 0x00) return -1; - return 0; -} - -static int -socks5_connect_v4(struct event_base *base, int fd, uint16_t port) -{ - uint8_t req[10]; - size_t i = 0; - req[i++] = 0x05; - req[i++] = 0x01; - req[i++] = 0x00; - req[i++] = 0x01; - uint32_t v4 = htonl(INADDR_LOOPBACK); - memcpy(req + i, &v4, 4); - i += 4; - req[i++] = (uint8_t)(port >> 8); - req[i++] = (uint8_t)(port & 0xff); - return send_all_nb(base, fd, req, i, 1000); -} - -/* ── in-process echo origin (identical to test_two_paths) ─────────────────── */ -typedef struct { - struct event_base *base; - int listen_fd; - int conn_fd; - struct event *listen_ev; - struct event *conn_ev; -} echo_origin_t; - -static void -echo_conn_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - echo_origin_t *o = (echo_origin_t *)arg; - unsigned char buf[8192]; - ssize_t n = recv(fd, buf, sizeof(buf), 0); - if (n <= 0) { - if (n == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) { - if (o->conn_ev) { - event_free(o->conn_ev); - o->conn_ev = NULL; - } - close(fd); - o->conn_fd = -1; - } - return; - } - ssize_t off = 0; - while (off < n) { - ssize_t w = send(fd, buf + off, (size_t)(n - off), MSG_NOSIGNAL); - if (w <= 0) break; - off += w; - } -} - -static void -echo_accept_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - echo_origin_t *o = (echo_origin_t *)arg; - int c = accept(fd, NULL, NULL); - if (c < 0) return; - set_nonblock(c); - o->conn_fd = c; - o->conn_ev = event_new(o->base, c, EV_READ | EV_PERSIST, echo_conn_cb, o); - event_add(o->conn_ev, NULL); -} - -static int -echo_origin_up(echo_origin_t *o, struct event_base *base, uint16_t *out_port) -{ - memset(o, 0, sizeof(*o)); - o->base = base; - o->conn_fd = -1; - o->listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (o->listen_fd < 0) return -1; - int one = 1; - setsockopt(o->listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(o->listen_fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) return -1; - if (listen(o->listen_fd, 4) != 0) return -1; - socklen_t sl = sizeof(sa); - if (getsockname(o->listen_fd, (struct sockaddr *)&sa, &sl) != 0) return -1; - *out_port = ntohs(sa.sin_port); - set_nonblock(o->listen_fd); - o->listen_ev = event_new(base, o->listen_fd, EV_READ | EV_PERSIST, echo_accept_cb, o); - event_add(o->listen_ev, NULL); - return 0; -} - -static void -echo_origin_down(echo_origin_t *o) -{ - if (o->conn_ev) event_free(o->conn_ev); - if (o->listen_ev) event_free(o->listen_ev); - if (o->conn_fd >= 0) close(o->conn_fd); - if (o->listen_fd >= 0) close(o->listen_fd); -} - -/* ── qlog token counter (the exact greps the .sh runs) ────────────────────── */ -/* Count NON-OVERLAPPING occurrences of needle in the file. The two blocked-frame - * tokens are distinct full strings (xqc_parse_data_blocked_frame is NOT a - * substring of xqc_parse_stream_data_blocked_frame at a matching position once - * counted independently), matching the .sh's two separate grep -c calls. */ -static long -count_token_in_file(const char *path, const char *needle) -{ - FILE *fp = fopen(path, "rb"); - if (!fp) return -1; - fseek(fp, 0, SEEK_END); - long sz = ftell(fp); - if (sz < 0) { - fclose(fp); - return -1; - } - rewind(fp); - char *buf = malloc((size_t)sz + 1); - if (!buf) { - fclose(fp); - return -1; - } - size_t rd = fread(buf, 1, (size_t)sz, fp); - fclose(fp); - buf[rd] = '\0'; - - long count = 0; - size_t nlen = strlen(needle); - if (nlen == 0) { - free(buf); - return 0; - } - const char *p = buf; - while ((p = strstr(p, needle)) != NULL) { - count++; - p += nlen; /* non-overlapping */ - } - free(buf); - return count; -} - -/* ── the test ─────────────────────────────────────────────────────────────── */ -static void -test_qlog_unblocked_transfer(void) -{ - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - MQ_CHECK(pump_until(f.base, pred_authed, f.client, 8000)); - MQ_CHECK(mq_client_is_authed(f.client)); - - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - int c = dial_nb(mq_listener_local_port(f.socks5)); - MQ_CHECK(c >= 0); - if (c < 0) { - echo_origin_down(&origin); - fixture_down(&f); - return; - } - MQ_CHECK_EQ_INT(socks5_greet(f.base, c), 0); - MQ_CHECK_EQ_INT(socks5_connect_v4(f.base, c, origin_port), 0); - uint8_t reply[10] = {0}; - MQ_CHECK_EQ_INT((int)recv_exact(f.base, c, reply, 10, 8000), 10); - MQ_CHECK_EQ_INT(reply[0], 0x05); - MQ_CHECK_EQ_INT(reply[1], 0x00); - - /* Push ~768KB up and drain the echoed copy back down. Well within the 16MB - * window over loopback (sub-ms RTT), so NO flow-control blocking occurs. */ - const size_t TOTAL = 768 * 1024; - const size_t CHUNK = 16 * 1024; - uint8_t *chunk = malloc(CHUNK); - uint8_t *sink = malloc(TOTAL); - MQ_CHECK(chunk != NULL && sink != NULL); - for (size_t i = 0; i < CHUNK; i++) - chunk[i] = (uint8_t)((i * 31 + 7) & 0xff); - - size_t sent_total = 0, recv_total = 0; - while (sent_total < TOTAL) { - size_t want = TOTAL - sent_total; - if (want > CHUNK) want = CHUNK; - if (send_all_nb(f.base, c, chunk, want, 4000) != 0) break; - sent_total += want; - recv_total += drain_some(f.base, c, sink, TOTAL - recv_total, 1000); - } - recv_total += drain_some(f.base, c, sink, TOTAL - recv_total, 8000); - - MQ_CHECK_EQ_INT((long long)sent_total, (long long)TOTAL); - MQ_CHECK((long long)recv_total > 0); - - free(chunk); - free(sink); - close(c); - echo_origin_down(&origin); - - /* Tear the CLIENT side down FIRST so all qlog lines are flushed and the qlog - * fd closed, while keeping the file on disk for the grep below. The qlog file - * is fully written once the client transport (which owns the xqc engine + - * qlog sink) is destroyed. Free in the verified order — transport (fires the - * conn-close + in-flight callbacks into the live runtime/client/listener), - * then runtime (borrowed base, not freed), then client, then listener — and - * NULL each so fixture_down does not double-free. The server side stays alive - * until fixture_down. */ - if (f.cli_t) { - mq_transport_free(f.cli_t); /* flushes + closes the qlog fd */ - f.cli_t = NULL; - } - if (f.cli_rt) { - mq_runtime_free(f.cli_rt); - f.cli_rt = NULL; - } - if (f.client) { - mq_client_free(f.client); - f.client = NULL; - } - if (f.socks5) { - mq_listener_free(f.socks5); - f.socks5 = NULL; - } - - /* Now grep the rendered qlog — the exact reads the .sh performs. */ - long frames = count_token_in_file(f.qlog_path, "frames_processed"); - long blocked_conn = count_token_in_file(f.qlog_path, "xqc_parse_data_blocked_frame"); - long blocked_stream = - count_token_in_file(f.qlog_path, "xqc_parse_stream_data_blocked_frame"); - - MQ_LOGI("test_qlog_blocked: frames_processed=%ld data_blocked=%ld " - "stream_data_blocked=%ld (qlog=%s)", - frames, blocked_conn, blocked_stream, f.qlog_path); - - /* EXTRA importance is actually on → the qlog is populated. */ - MQ_CHECK(frames > 0); - /* An unblocked loopback transfer emits ZERO blocked frames in either form. */ - MQ_CHECK_EQ_INT((int)blocked_conn, 0); - MQ_CHECK_EQ_INT((int)blocked_stream, 0); - - fixture_down(&f); -} - -static void -test_qlog_blocked(void) -{ - test_qlog_unblocked_transfer(); -} - -MQ_TEST_MAIN(test_qlog_blocked()) diff --git a/tests/integration/test_socks5_listener.c b/tests/integration/test_socks5_listener.c deleted file mode 100644 index b61fd7d0..00000000 --- a/tests/integration/test_socks5_listener.c +++ /dev/null @@ -1,1190 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_socks5_listener.c — Task 14 integration test for the ingress listeners. - * - * Drives the SOCKS5 (and a smoke-test of the HTTP CONNECT) accept loop with a - * MOCK tcp_open to isolate the listener from the real proxy client. The mock - * records the call (host/port/atype), fires cb(ok=1), and sets up an in-process - * echo on local_fd so the test can assert bytes relay end-to-end. Because the - * mock owns local_fd from the call onward (matching mq_client ownership), it - * closes local_fd when the client side disconnects. - * - * Scenarios: - * 1. Happy path: greeting (05 01 00) -> method reply (05 00); CONNECT to a - * domain host:port -> assert the mock saw the right target, the success - * reply (05 00 00 01 ...) arrives, and an echoed byte round-trips. - * 2. UNSUPPORTED command (BIND): assert a reject reply (REP != 0) and the fd - * is closed by the listener (read returns EOF). - * 3. Malformed greeting (bad version): assert the fd is closed (EOF), no - * tcp_open call. - * 4. HTTP CONNECT smoke: a CONNECT line -> assert the mock saw the target and - * a 200 arrives. - * - * UDP ASSOCIATE scenarios (stub UDP relay core) cover establish/free, TCP-close - * teardown, mixed shutdown, no-close-after-err, availability sweep, REP 0x07 - * refusals, and the dst_alloc reclaim sweep after destination churn (the last - * pins the codex-found mq_udp_assoc.c dst_alloc dead-entry reclaim fix). - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ingress/mq_listener.h" -#include "ingress/mq_socks5.h" -#include "ingress/mq_udp_assoc.h" - -/* ── Mock tcp_open ────────────────────────────────────────────────────────── - * Records the most recent call, fires cb(ok=1), and keeps local_fd to act as - * an echo origin: anything the app writes after the success reply is echoed - * back. The mock owns local_fd (like the real client) and closes it on EOF. - */ -struct mock_open { - struct event_base *base; - int called; - uint8_t host[256]; - size_t host_len; - mq_addr_type_t atype; - uint16_t port; - uint8_t prebuf[512]; /* bytes coalesced with the request head */ - size_t prebuf_len; - /* echo plumbing */ - int local_fd; - struct event *echo_ev; - int forced_ok; /* 1 => ok, 0 => fail with forced_err */ - mq_tcp_err_t forced_err; -}; - -static void -mock_echo_cb(evutil_socket_t fd, short what, void *user) -{ - (void)what; - struct mock_open *m = (struct mock_open *)user; - uint8_t buf[256]; - ssize_t n = recv(fd, buf, sizeof(buf), 0); - if (n > 0) { - ssize_t off = 0; - while (off < n) { - ssize_t w = send(fd, buf + off, (size_t)(n - off), 0); - if (w <= 0) break; - off += w; - } - return; - } - /* EOF or error: mock (owner of local_fd) closes it. */ - if (m->echo_ev) { - event_free(m->echo_ev); - m->echo_ev = NULL; - } - close(fd); - m->local_fd = -1; -} - -static void -mock_tcp_open(void *core, const uint8_t *host, size_t host_len, mq_addr_type_t atype, - uint16_t port, int local_fd, const uint8_t *prebuf, size_t prebuf_len, - void *user, mq_tcp_open_cb cb) -{ - struct mock_open *m = (struct mock_open *)core; - m->called++; - m->host_len = host_len < sizeof(m->host) ? host_len : sizeof(m->host); - memcpy(m->host, host, m->host_len); - m->atype = atype; - m->port = port; - m->local_fd = local_fd; - m->prebuf_len = prebuf_len < sizeof(m->prebuf) ? prebuf_len : sizeof(m->prebuf); - if (m->prebuf_len > 0 && prebuf) memcpy(m->prebuf, prebuf, m->prebuf_len); - - if (!m->forced_ok) { - /* Fail path: fire cb(0) so the listener writes its error reply on - * local_fd; the mock (owner) then closes local_fd after the cb. */ - if (cb) cb(0, m->forced_err, user); - close(local_fd); - m->local_fd = -1; - return; - } - - /* Success: fire cb(1) so the listener writes the success reply, then set up - * an echo on local_fd. */ - if (cb) cb(1, MQ_TCP_OK, user); - if (m->local_fd >= 0) { - m->echo_ev = - event_new(m->base, m->local_fd, EV_READ | EV_PERSIST, mock_echo_cb, m); - event_add(m->echo_ev, NULL); - } -} - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -/* Connect a blocking-ish client socket to 127.0.0.1:port; returns fd or -1. */ -static int -dial(uint16_t port) -{ - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) return -1; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr); - if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return -1; - } - return fd; -} - -/* Pump the event base while trying to read `want` bytes from fd, up to budget. - * Returns total bytes read (may be < want on timeout). The client fd is - * blocking, so we make it non-blocking for the pump and poll it. */ -static size_t -pump_read(struct event_base *base, int fd, uint8_t *out, size_t want, uint64_t budget_ms) -{ - int fl = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, fl | O_NONBLOCK); - size_t got = 0; - uint64_t deadline = now_ms() + budget_ms; - while (got < want && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - ssize_t n = recv(fd, out + got, want - got, 0); - if (n > 0) { - got += (size_t)n; - } else if (n == 0) { - break; /* EOF */ - } - } - fcntl(fd, F_SETFL, fl); - return got; -} - -/* Returns 1 if fd is at EOF (peer closed) within budget, else 0. */ -static int -pump_until_eof(struct event_base *base, int fd, uint64_t budget_ms) -{ - int fl = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, fl | O_NONBLOCK); - int eof = 0; - uint64_t deadline = now_ms() + budget_ms; - while (!eof && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - uint8_t b[64]; - ssize_t n = recv(fd, b, sizeof(b), 0); - if (n == 0) { - eof = 1; - } else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { - eof = 1; - } - } - fcntl(fd, F_SETFL, fl); - return eof; -} - -static void -send_all(int fd, const uint8_t *buf, size_t len) -{ - size_t off = 0; - while (off < len) { - ssize_t n = send(fd, buf + off, len - off, 0); - if (n <= 0) break; - off += (size_t)n; - } -} - -/* ── Scenario 1: SOCKS5 happy path ──────────────────────────────────────── */ -static void -test_socks5_happy(struct event_base *base) -{ - struct mock_open m; - memset(&m, 0, sizeof(m)); - m.base = base; - m.forced_ok = 1; - m.local_fd = -1; - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, &m, - NULL, NULL, NULL, NULL); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t port = mq_listener_local_port(l); - MQ_CHECK(port != 0); - - int c = dial(port); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - - /* Greeting: VER=5, NMETHODS=1, METHOD=0 (no-auth). */ - uint8_t greeting[] = {0x05, 0x01, 0x00}; - send_all(c, greeting, sizeof(greeting)); - - uint8_t mreply[2] = {0}; - size_t got = pump_read(base, c, mreply, 2, 1000); - MQ_CHECK_EQ_INT(got, 2); - MQ_CHECK_EQ_INT(mreply[0], 0x05); - MQ_CHECK_EQ_INT(mreply[1], 0x00); - - /* CONNECT to "example.com":443 (domain). */ - const char host[] = "example.com"; - uint8_t req[4 + 1 + 11 + 2]; - size_t i = 0; - req[i++] = 0x05; /* VER */ - req[i++] = 0x01; /* CMD CONNECT */ - req[i++] = 0x00; /* RSV */ - req[i++] = 0x03; /* ATYP DOMAIN */ - req[i++] = (uint8_t)(sizeof(host) - 1); - memcpy(req + i, host, sizeof(host) - 1); - i += sizeof(host) - 1; - req[i++] = (uint8_t)(443 >> 8); - req[i++] = (uint8_t)(443 & 0xff); - send_all(c, req, i); - - uint8_t creply[10] = {0}; - got = pump_read(base, c, creply, 10, 1000); - MQ_CHECK_EQ_INT(got, 10); - MQ_CHECK_EQ_INT(creply[0], 0x05); - MQ_CHECK_EQ_INT(creply[1], 0x00); /* REP success */ - MQ_CHECK_EQ_INT(creply[3], 0x01); /* ATYP IPv4 bind */ - - /* Mock saw the right target. */ - MQ_CHECK_EQ_INT(m.called, 1); - MQ_CHECK_EQ_INT(m.atype, MQ_ADDR_DOMAIN); - MQ_CHECK_EQ_INT(m.port, 443); - MQ_CHECK_EQ_INT(m.host_len, sizeof(host) - 1); - MQ_CHECK_MEM(m.host, host, sizeof(host) - 1); - - /* End-to-end echo through local_fd. */ - uint8_t payload[] = "ping"; - send_all(c, payload, sizeof(payload) - 1); - uint8_t echo[4] = {0}; - got = pump_read(base, c, echo, 4, 1000); - MQ_CHECK_EQ_INT(got, 4); - MQ_CHECK_MEM(echo, payload, 4); - - close(c); - /* Pump so the mock's echo sees EOF and closes local_fd (no leak). */ - uint64_t deadline = now_ms() + 500; - while (m.local_fd >= 0 && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - MQ_CHECK_EQ_INT(m.local_fd, -1); - - mq_listener_free(l); -} - -/* ── Scenario 2: SOCKS5 UNSUPPORTED command (BIND) ──────────────────────── */ -static void -test_socks5_unsupported(struct event_base *base) -{ - struct mock_open m; - memset(&m, 0, sizeof(m)); - m.base = base; - m.forced_ok = 1; - m.local_fd = -1; - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, &m, - NULL, NULL, NULL, NULL); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t port = mq_listener_local_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - - uint8_t greeting[] = {0x05, 0x01, 0x00}; - send_all(c, greeting, sizeof(greeting)); - uint8_t mreply[2] = {0}; - MQ_CHECK_EQ_INT(pump_read(base, c, mreply, 2, 1000), 2); - - /* CONNECT request but CMD = BIND (0x02) => UNSUPPORTED. */ - uint8_t req[] = {0x05, 0x02, 0x00, 0x01, 127, 0, 0, 1, 0x00, 0x50}; - send_all(c, req, sizeof(req)); - - uint8_t creply[10] = {0}; - size_t got = pump_read(base, c, creply, 10, 1000); - MQ_CHECK_EQ_INT(got, 10); - MQ_CHECK_EQ_INT(creply[0], 0x05); - MQ_CHECK_EQ_INT(creply[1], 0x07); /* REP 0x07: command not supported (BIND) */ - - /* No tcp_open call, and the listener closes the accepted fd. */ - MQ_CHECK_EQ_INT(m.called, 0); - MQ_CHECK(pump_until_eof(base, c, 1000)); - - close(c); - mq_listener_free(l); -} - -/* ── Scenario 3: malformed greeting (bad version) ───────────────────────── */ -static void -test_socks5_malformed(struct event_base *base) -{ - struct mock_open m; - memset(&m, 0, sizeof(m)); - m.base = base; - m.forced_ok = 1; - m.local_fd = -1; - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, &m, - NULL, NULL, NULL, NULL); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t port = mq_listener_local_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - - /* Bad version 0x04 => ERROR, fd closed, no reply guaranteed. */ - uint8_t bad[] = {0x04, 0x01, 0x00}; - send_all(c, bad, sizeof(bad)); - - MQ_CHECK(pump_until_eof(base, c, 1000)); - MQ_CHECK_EQ_INT(m.called, 0); - - close(c); - mq_listener_free(l); -} - -/* ── Scenario 4: HTTP CONNECT smoke ─────────────────────────────────────── */ -static void -test_http_connect_smoke(struct event_base *base) -{ - struct mock_open m; - memset(&m, 0, sizeof(m)); - m.base = base; - m.forced_ok = 1; - m.local_fd = -1; - - mq_listener_t *l = mq_http_connect_listener_new(base, "127.0.0.1", 0, mock_tcp_open, - &m, NULL, NULL, NULL, NULL); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t port = mq_listener_local_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - - const char *line = - "CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n"; - send_all(c, (const uint8_t *)line, strlen(line)); - - /* Read the 200 status line (just the start "HTTP/1.1 200"). */ - uint8_t buf[64] = {0}; - size_t got = pump_read(base, c, buf, 12, 1000); - MQ_CHECK(got >= 12); - MQ_CHECK_MEM(buf, "HTTP/1.1 200", 12); - - MQ_CHECK_EQ_INT(m.called, 1); - MQ_CHECK_EQ_INT(m.atype, MQ_ADDR_DOMAIN); - MQ_CHECK_EQ_INT(m.port, 443); - MQ_CHECK_EQ_INT(m.host_len, strlen("example.com")); - MQ_CHECK_MEM(m.host, "example.com", strlen("example.com")); - - close(c); - uint64_t deadline = now_ms() + 500; - while (m.local_fd >= 0 && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - MQ_CHECK_EQ_INT(m.local_fd, -1); - - mq_listener_free(l); -} - -/* ── Scenario 5: SOCKS5 request coalesced with early payload ───────────────── - * The CONNECT request and the app's first upload bytes arrive in ONE send, so - * the listener reads them in a single recv. The trailing bytes must be handed to - * open_fn as the prebuffer (not dropped). */ -static void -test_socks5_pipelined_prebuf(struct event_base *base) -{ - struct mock_open m; - memset(&m, 0, sizeof(m)); - m.base = base; - m.forced_ok = 1; - m.local_fd = -1; - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, &m, - NULL, NULL, NULL, NULL); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t port = mq_listener_local_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - - uint8_t greeting[] = {0x05, 0x01, 0x00}; - send_all(c, greeting, sizeof(greeting)); - uint8_t mreply[2] = {0}; - MQ_CHECK_EQ_INT(pump_read(base, c, mreply, 2, 1000), 2); - - /* CONNECT to example.com:443 (domain) immediately followed by "ping", in one - * send so the listener reads request+payload together. */ - const char host[] = "example.com"; - const uint8_t early[] = "ping"; - uint8_t req[4 + 1 + 11 + 2 + 4]; - size_t i = 0; - req[i++] = 0x05; - req[i++] = 0x01; - req[i++] = 0x00; - req[i++] = 0x03; - req[i++] = (uint8_t)(sizeof(host) - 1); - memcpy(req + i, host, sizeof(host) - 1); - i += sizeof(host) - 1; - req[i++] = (uint8_t)(443 >> 8); - req[i++] = (uint8_t)(443 & 0xff); - memcpy(req + i, early, sizeof(early) - 1); - i += sizeof(early) - 1; - send_all(c, req, i); - - /* Drain the success reply so the pump runs the drive. */ - uint8_t creply[10] = {0}; - MQ_CHECK_EQ_INT(pump_read(base, c, creply, 10, 1000), 10); - - MQ_CHECK_EQ_INT(m.called, 1); - MQ_CHECK_EQ_INT(m.prebuf_len, sizeof(early) - 1); - MQ_CHECK_MEM(m.prebuf, early, sizeof(early) - 1); - - close(c); - uint64_t deadline = now_ms() + 500; - while (m.local_fd >= 0 && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - mq_listener_free(l); -} - -/* ── Scenario 6: HTTP CONNECT head coalesced with early payload ──────────── */ -static void -test_http_pipelined_prebuf(struct event_base *base) -{ - struct mock_open m; - memset(&m, 0, sizeof(m)); - m.base = base; - m.forced_ok = 1; - m.local_fd = -1; - - mq_listener_t *l = mq_http_connect_listener_new(base, "127.0.0.1", 0, mock_tcp_open, - &m, NULL, NULL, NULL, NULL); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t port = mq_listener_local_port(l); - - int c = dial(port); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - - const char head[] = - "CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n"; - const uint8_t early[] = "ping"; - uint8_t buf[256]; - size_t hn = strlen(head); - memcpy(buf, head, hn); - memcpy(buf + hn, early, sizeof(early) - 1); - send_all(c, buf, hn + sizeof(early) - 1); - - uint8_t status[12] = {0}; - MQ_CHECK(pump_read(base, c, status, 12, 1000) >= 12); - MQ_CHECK_MEM(status, "HTTP/1.1 200", 12); - - MQ_CHECK_EQ_INT(m.called, 1); - MQ_CHECK_EQ_INT(m.prebuf_len, sizeof(early) - 1); - MQ_CHECK_MEM(m.prebuf, early, sizeof(early) - 1); - - close(c); - uint64_t deadline = now_ms() + 500; - while (m.local_fd >= 0 && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - mq_listener_free(l); -} - -/* ── Stub UDP relay core (the mq_udp_open_fn boundary) ─────────────────────── - * - * Models the client-role relay table for ASSOCIATE tests. open() hands back a - * heap session and records the DST; send() optionally round-trips the payload - * back via on_rx (so the test can read it on the UDP socket); close() and on_err - * counters let us assert lifecycle. Forcing knobs: - * - force_open_null: open() returns NULL (transient failure / unavailable) - * - force_err_on_send: the FIRST send triggers an on_err(err) instead of rx - * - * The stub honors the boundary contract: after on_err for a session it never - * touches that handle again, and after close() it fires no callbacks. Test (d) - * is realized here: the stub records close_fn calls and, post-close, does not - * deliver on_rx/on_err — exactly the suppression the real core guarantees. - */ -struct stub_sess { - struct stub_udp *core; - mq_udp_rx_fn on_rx; - mq_udp_err_fn on_err; - void *user; - mq_addr_type_t atype; - uint8_t host[MQ_MAX_HOST]; - size_t host_len; - uint16_t port; - int closed; /* close_fn called */ - int errored; /* on_err fired (handle invalid after) */ - int alive; /* slot in use */ -}; - -struct stub_udp { - int open_calls; - int close_calls; - int send_calls; - int post_death_sends; /* sends on a closed/errored handle — contract violation */ - int force_open_null; - int force_err_on_send; - mq_udp_err_t forced_err; - int echo; /* 1 => send() round-trips payload via on_rx */ - struct stub_sess sessions[16]; -}; - -static void * -stub_open(void *core, const uint8_t *host, size_t host_len, mq_addr_type_t atype, - uint16_t port, mq_udp_rx_fn on_rx, mq_udp_err_fn on_err, void *user) -{ - struct stub_udp *s = (struct stub_udp *)core; - s->open_calls++; - if (s->force_open_null) return NULL; - for (size_t i = 0; i < 16; i++) { - if (!s->sessions[i].alive) { - struct stub_sess *ss = &s->sessions[i]; - memset(ss, 0, sizeof(*ss)); - ss->core = s; - ss->on_rx = on_rx; - ss->on_err = on_err; - ss->user = user; - ss->atype = atype; - ss->host_len = host_len < sizeof(ss->host) ? host_len : sizeof(ss->host); - if (ss->host_len) memcpy(ss->host, host, ss->host_len); - ss->port = port; - ss->alive = 1; - return ss; - } - } - return NULL; -} - -static void -stub_send(void *session, const uint8_t *payload, size_t len) -{ - struct stub_sess *ss = (struct stub_sess *)session; - struct stub_udp *s = ss->core; - s->send_calls++; - /* Contract: assoc must never send on a dead/closed handle. Record any - * violation so tests can assert post_death_sends == 0 at teardown. */ - if (ss->closed || ss->errored) { - s->post_death_sends++; - return; - } - if (s->force_err_on_send) { - ss->errored = 1; - /* Per the boundary contract the handle is invalid after on_err: free the - * slot so the stub can admit a fresh open() for the next destination. - * (errored stays set so post_death_sends still catches any stray send.) */ - ss->alive = 0; - if (ss->on_err) ss->on_err(ss, s->forced_err, ss->user); - return; - } - if (s->echo && ss->on_rx) { - ss->on_rx(payload, len, ss->user); /* round-trip back to the client */ - } -} - -static void -stub_close(void *session) -{ - struct stub_sess *ss = (struct stub_sess *)session; - struct stub_udp *s = ss->core; - s->close_calls++; - ss->closed = 1; - ss->alive = 0; -} - -/* Dial a UDP socket bound to an ephemeral 127.0.0.1 port. */ -static int -udp_dial(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return -1; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return -1; - } - return fd; -} - -/* Complete the SOCKS5 greeting + ASSOCIATE request on a TCP control socket and - * read the 10-byte reply. Returns the reply REP code and fills *udp_port (host - * order) from BND.PORT on success. If reply_out is non-NULL, the full 10-byte - * reply is copied there. */ -static int -socks5_associate(struct event_base *base, int c, uint16_t *udp_port_out, uint8_t *rep_out, - uint8_t reply_out[10]) -{ - uint8_t greeting[] = {0x05, 0x01, 0x00}; - send_all(c, greeting, sizeof(greeting)); - uint8_t mreply[2] = {0}; - if (pump_read(base, c, mreply, 2, 1000) != 2) return -1; - - /* ASSOCIATE with DST 0.0.0.0:0 (client does not know its src yet). */ - uint8_t req[] = {0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0, 0, 0}; - send_all(c, req, sizeof(req)); - - uint8_t reply[10] = {0}; - if (pump_read(base, c, reply, 10, 1000) != 10) return -1; - if (rep_out) *rep_out = reply[1]; - if (udp_port_out) *udp_port_out = (uint16_t)((reply[8] << 8) | reply[9]); - if (reply_out) memcpy(reply_out, reply, 10); - return 0; -} - -/* Build a SOCKS5 UDP encapsulation header + payload into out; returns total len. */ -static size_t -build_udp_dgram(uint8_t *out, size_t cap, uint32_t dst_ip_be, uint16_t dst_port, - const uint8_t *payload, size_t plen) -{ - mq_socks5_req_t dst; - memset(&dst, 0, sizeof(dst)); - dst.atype = MQ_ADDR_IPV4; - dst.host_len = 4; - memcpy(dst.host, &dst_ip_be, 4); - dst.port = dst_port; - int hlen = mq_socks5_build_udp_hdr(out, cap, &dst); - if (hlen < 0) return 0; - if ((size_t)hlen + plen > cap) return 0; - memcpy(out + hlen, payload, plen); - return (size_t)hlen + plen; -} - -/* ── ASSOCIATE Scenario A: establish, free listener while TCP open ─────────── - * (a) ASSOCIATE established → TCP kept open → listener free → no fd/session/ - * event leak (ASan). */ -static void -test_assoc_establish_free(struct event_base *base) -{ - struct stub_udp s; - memset(&s, 0, sizeof(s)); - s.echo = 1; - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, NULL, - stub_open, stub_send, stub_close, &s); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t tport = mq_listener_local_port(l); - - int c = dial(tport); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - - uint16_t uport = 0; - uint8_t rep = 0xff; - uint8_t areply[10] = {0}; - MQ_CHECK_EQ_INT(socks5_associate(base, c, &uport, &rep, areply), 0); - MQ_CHECK_EQ_INT(rep, 0x00); /* REP success */ - MQ_CHECK(uport != 0); - /* BND.ADDR must be IPv4 127.0.0.1 (listener bound to 127.0.0.1). */ - MQ_CHECK_EQ_INT(areply[3], 0x01); /* ATYP = IPv4 */ - MQ_CHECK_EQ_INT(areply[4], 127); /* BND.ADDR[0] */ - MQ_CHECK_EQ_INT(areply[5], 0); /* BND.ADDR[1] */ - MQ_CHECK_EQ_INT(areply[6], 0); /* BND.ADDR[2] */ - MQ_CHECK_EQ_INT(areply[7], 1); /* BND.ADDR[3] */ - - /* Round-trip a UDP datagram: client → UDP socket → stub echo → back. */ - int u = udp_dial(); - MQ_CHECK(u >= 0); - struct sockaddr_in dst; - memset(&dst, 0, sizeof(dst)); - dst.sin_family = AF_INET; - dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - dst.sin_port = htons(uport); - - uint8_t dgram[64]; - uint8_t payload[] = "udp-ping"; - uint32_t dst_ip = htonl(0x08080808); /* 8.8.8.8 */ - size_t dlen = - build_udp_dgram(dgram, sizeof(dgram), dst_ip, 53, payload, sizeof(payload) - 1); - MQ_CHECK(dlen > 0); - sendto(u, dgram, dlen, 0, (struct sockaddr *)&dst, sizeof(dst)); - - /* Pump and read the echoed datagram back on the same UDP socket. */ - uint8_t rxb[64] = {0}; - int fl = fcntl(u, F_GETFL, 0); - fcntl(u, F_SETFL, fl | O_NONBLOCK); - ssize_t rn = -1; - uint64_t deadline = now_ms() + 1000; - while (now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - rn = recv(u, rxb, sizeof(rxb), 0); - if (rn > 0) break; - } - MQ_CHECK(rn > 0); - MQ_CHECK_EQ_INT(s.open_calls, 1); - MQ_CHECK_EQ_INT(s.send_calls, 1); - /* The echoed datagram has the encap header (DST as src) + payload. */ - mq_socks5_udp_hdr_t hdr; - memset(&hdr, 0, sizeof(hdr)); - int hr = mq_socks5_parse_udp_hdr(rxb, (size_t)rn, &hdr); - MQ_CHECK(hr > 0); - if (hr > 0) { - MQ_CHECK_EQ_INT(hdr.dst.port, 53); - MQ_CHECK_EQ_INT((size_t)rn - hdr.hdr_len, sizeof(payload) - 1); - MQ_CHECK_MEM(rxb + hdr.hdr_len, payload, sizeof(payload) - 1); - } - - close(u); - /* Free the listener with the TCP control connection STILL open: the assoc - * shell (UDP fd / TCP fd / events) and the live stub session must all be - * reaped (close_fn called for the live session). */ - mq_listener_free(l); - MQ_CHECK_EQ_INT(s.close_calls, 1); /* the live session was closed at reap */ - MQ_CHECK_EQ_INT(s.post_death_sends, 0); - close(c); -} - -/* ── ASSOCIATE Scenario B: TCP close tears the assoc down ────────────────── */ -static void -test_assoc_tcp_close_teardown(struct event_base *base) -{ - struct stub_udp s; - memset(&s, 0, sizeof(s)); - s.echo = 1; - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, NULL, - stub_open, stub_send, stub_close, &s); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t tport = mq_listener_local_port(l); - - int c = dial(tport); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - uint16_t uport = 0; - uint8_t rep = 0xff; - MQ_CHECK_EQ_INT(socks5_associate(base, c, &uport, &rep, NULL), 0); - MQ_CHECK_EQ_INT(rep, 0x00); - - /* Establish a live session via one datagram. */ - int u = udp_dial(); - struct sockaddr_in dst; - memset(&dst, 0, sizeof(dst)); - dst.sin_family = AF_INET; - dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - dst.sin_port = htons(uport); - uint8_t dgram[64]; - uint8_t payload[] = "x"; - size_t dlen = build_udp_dgram(dgram, sizeof(dgram), htonl(0x01020304), 99, payload, - sizeof(payload) - 1); - sendto(u, dgram, dlen, 0, (struct sockaddr *)&dst, sizeof(dst)); - uint64_t deadline = now_ms() + 500; - while (s.open_calls == 0 && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - MQ_CHECK_EQ_INT(s.open_calls, 1); - close(u); - - /* Close the TCP control connection → assoc tears down → close_fn called. */ - close(c); - deadline = now_ms() + 1000; - while (s.close_calls == 0 && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - MQ_CHECK_EQ_INT(s.close_calls, 1); - - mq_listener_free(l); - /* No double-close: the assoc already self-removed at TCP-EOF teardown. */ - MQ_CHECK_EQ_INT(s.close_calls, 1); - MQ_CHECK_EQ_INT(s.post_death_sends, 0); -} - -/* ── ASSOCIATE Scenario C: mixed shutdown (in-flight CONNECT + live ASSOCIATE) - * The graph-A ordering (transport→runtime→client→listener) is exercised by the - * real-core e2e tests; here, with stub cores, we assert the listener-side - * structural half: an accepted-but-not-yet-handed-off CONNECT parse state AND a - * live UDP assoc co-exist, then mq_listener_free reaps BOTH without UAF/leak - * (ASan). The CONNECT conn is left mid-greeting so it is still a listener-owned - * conn_state at free time. */ -static void -test_assoc_mixed_shutdown(struct event_base *base) -{ - struct stub_udp s; - memset(&s, 0, sizeof(s)); - s.echo = 1; - struct mock_open m; - memset(&m, 0, sizeof(m)); - m.base = base; - m.forced_ok = 1; - m.local_fd = -1; - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, &m, - stub_open, stub_send, stub_close, &s); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t tport = mq_listener_local_port(l); - - /* (1) A live ASSOCIATE with one open session. */ - int ca = dial(tport); - MQ_CHECK(ca >= 0); - uint16_t uport = 0; - uint8_t rep = 0xff; - MQ_CHECK_EQ_INT(socks5_associate(base, ca, &uport, &rep, NULL), 0); - MQ_CHECK_EQ_INT(rep, 0x00); - int u = udp_dial(); - struct sockaddr_in dst; - memset(&dst, 0, sizeof(dst)); - dst.sin_family = AF_INET; - dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - dst.sin_port = htons(uport); - uint8_t dgram[64]; - uint8_t payload[] = "m"; - size_t dlen = build_udp_dgram(dgram, sizeof(dgram), htonl(0x09090909), 1, payload, - sizeof(payload) - 1); - sendto(u, dgram, dlen, 0, (struct sockaddr *)&dst, sizeof(dst)); - uint64_t deadline = now_ms() + 500; - while (s.open_calls == 0 && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - MQ_CHECK_EQ_INT(s.open_calls, 1); - close(u); - - /* (2) A second connection mid-greeting (accepted, parse state owned by the - * listener, NOT yet handed off). */ - int cp = dial(tport); - MQ_CHECK(cp >= 0); - uint8_t partial[] = {0x05}; /* incomplete greeting */ - send_all(cp, partial, sizeof(partial)); - deadline = now_ms() + 200; - while (now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - - /* Free with both alive: assoc reaped (close_fn on the live session) + the - * pending CONNECT conn_state reaped (its fd closed by the listener). */ - mq_listener_free(l); - MQ_CHECK_EQ_INT(s.close_calls, 1); - MQ_CHECK_EQ_INT(s.post_death_sends, 0); - close(ca); - close(cp); -} - -/* ── ASSOCIATE Scenario D: post-teardown callback suppression ──────────────── - * Interpretation (see context): the stub IS the core, so this asserts the - * assoc-side half of the boundary contract — at teardown the assoc calls - * close_fn exactly once per live session, and after a session's on_err it never - * calls close_fn on that (now invalid) handle. We drive force_err_on_send so the - * single session dies via on_err, then tear down: close_fn must NOT be called for - * the dead session (else it would be a UAF in the real core). */ -static void -test_assoc_no_close_after_err(struct event_base *base) -{ - struct stub_udp s; - memset(&s, 0, sizeof(s)); - s.force_err_on_send = 1; - s.forced_err = MQ_UDP_DNS_FAILED; - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, NULL, - stub_open, stub_send, stub_close, &s); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t tport = mq_listener_local_port(l); - int c = dial(tport); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - uint16_t uport = 0; - uint8_t rep = 0xff; - MQ_CHECK_EQ_INT(socks5_associate(base, c, &uport, &rep, NULL), 0); - MQ_CHECK_EQ_INT(rep, 0x00); - - int u = udp_dial(); - struct sockaddr_in dst; - memset(&dst, 0, sizeof(dst)); - dst.sin_family = AF_INET; - dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - dst.sin_port = htons(uport); - uint8_t dgram[64]; - uint8_t payload[] = "q"; - size_t dlen = build_udp_dgram(dgram, sizeof(dgram), htonl(0x05060708), 7, payload, - sizeof(payload) - 1); - sendto(u, dgram, dlen, 0, (struct sockaddr *)&dst, sizeof(dst)); - uint64_t deadline = now_ms() + 500; - while (s.open_calls == 0 && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - MQ_CHECK_EQ_INT(s.open_calls, 1); - MQ_CHECK_EQ_INT(s.send_calls, 1); /* the send triggered on_err */ - close(u); - - /* Tear down via listener free: the (dead) session must NOT be closed. */ - mq_listener_free(l); - MQ_CHECK_EQ_INT(s.close_calls, 0); /* dead handle never closed (no UAF) */ - MQ_CHECK_EQ_INT(s.post_death_sends, 0); - close(c); -} - -/* ── ASSOCIATE Scenario E: availability sweep ──────────────────────────────── - * tri-state -1 optimistically admits ASSOCIATE (no UDP sent); a stub then sets - * availability 0 → the assoc's TCP control connection is closed (UDP-unused - * assoc must not survive). */ -static void -test_assoc_availability_sweep(struct event_base *base) -{ - struct stub_udp s; - memset(&s, 0, sizeof(s)); - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, NULL, - stub_open, stub_send, stub_close, &s); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t tport = mq_listener_local_port(l); - int c = dial(tport); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - uint16_t uport = 0; - uint8_t rep = 0xff; - /* -1 default → optimistic admit. */ - MQ_CHECK_EQ_INT(socks5_associate(base, c, &uport, &rep, NULL), 0); - MQ_CHECK_EQ_INT(rep, 0x00); - MQ_CHECK_EQ_INT(s.open_calls, 0); /* no UDP sent yet */ - - /* Server turns out not to support UDP → set availability 0 → sweep closes - * the control connection of the (UDP-unused) assoc. */ - mq_listener_set_udp_availability(l, 0); - - /* The client side sees EOF on the TCP control connection. */ - MQ_CHECK(pump_until_eof(base, c, 1000)); - MQ_CHECK_EQ_INT(s.close_calls, 0); /* no session was ever opened */ - MQ_CHECK_EQ_INT(s.post_death_sends, 0); - - mq_listener_free(l); - close(c); -} - -/* ── ASSOCIATE Scenario: REP 0x07 when no UDP boundary ─────────────────────── - * A SOCKS5 listener with a NULL udp_open quad refuses ASSOCIATE unconditionally. */ -static void -test_assoc_refused_no_udp(struct event_base *base) -{ - struct mock_open m; - memset(&m, 0, sizeof(m)); - m.base = base; - m.forced_ok = 1; - m.local_fd = -1; - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, &m, - NULL, NULL, NULL, NULL); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t tport = mq_listener_local_port(l); - int c = dial(tport); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - uint16_t uport = 0; - uint8_t rep = 0xff; - MQ_CHECK_EQ_INT(socks5_associate(base, c, &uport, &rep, NULL), 0); - MQ_CHECK_EQ_INT(rep, 0x07); /* command not supported */ - MQ_CHECK(pump_until_eof(base, c, 1000)); - mq_listener_free(l); - close(c); -} - -/* ── ASSOCIATE Scenario: availability 0 refuses up front (REP 0x07) ──────── */ -static void -test_assoc_refused_unavail(struct event_base *base) -{ - struct stub_udp s; - memset(&s, 0, sizeof(s)); - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, NULL, - stub_open, stub_send, stub_close, &s); - MQ_CHECK(l != NULL); - if (!l) return; - mq_listener_set_udp_availability(l, 0); /* server has no UDP */ - uint16_t tport = mq_listener_local_port(l); - int c = dial(tport); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - uint16_t uport = 0; - uint8_t rep = 0xff; - MQ_CHECK_EQ_INT(socks5_associate(base, c, &uport, &rep, NULL), 0); - MQ_CHECK_EQ_INT(rep, 0x07); - MQ_CHECK_EQ_INT(s.open_calls, 0); - MQ_CHECK_EQ_INT(s.post_death_sends, 0); - mq_listener_free(l); - close(c); -} - -/* ── ASSOCIATE Scenario: dst-entry reclaim after destination churn ─────────── - * Pins the dst_alloc reclaim sweep (mq_udp_assoc.c dst_alloc): when no free DST - * slot exists, dst_alloc must reclaim a dead entry that is immediately - * reclaimable (dead && failed_at == 0, i.e. closed via MQ_UDP_CLOSED which is - * never negative-cached) instead of returning NULL and dropping the datagram. - * - * Drive MQ_UDP_ASSOC_MAX_DST (64) DISTINCT destinations through one established - * assoc; for each, the stub fires on_err(MQ_UDP_CLOSED) on the first send so the - * entry becomes dead with failed_at == 0 (immediately reclaimable). Then drive a - * 65th distinct destination and assert open_fn was invoked for it — proving the - * sweep reclaimed a dead slot rather than returning NULL. Without the sweep, the - * 65th open never happens (open_calls stays at 64). */ -static void -test_assoc_dst_reclaim_churn(struct event_base *base) -{ - struct stub_udp s; - memset(&s, 0, sizeof(s)); - s.force_err_on_send = 1; - s.forced_err = MQ_UDP_CLOSED; /* failed_at stays 0 → immediately reclaimable */ - - mq_listener_t *l = mq_socks5_listener_new(base, "127.0.0.1", 0, mock_tcp_open, NULL, - stub_open, stub_send, stub_close, &s); - MQ_CHECK(l != NULL); - if (!l) return; - uint16_t tport = mq_listener_local_port(l); - - int c = dial(tport); - MQ_CHECK(c >= 0); - if (c < 0) { - mq_listener_free(l); - return; - } - uint16_t uport = 0; - uint8_t rep = 0xff; - MQ_CHECK_EQ_INT(socks5_associate(base, c, &uport, &rep, NULL), 0); - MQ_CHECK_EQ_INT(rep, 0x00); - MQ_CHECK(uport != 0); - - int u = udp_dial(); - MQ_CHECK(u >= 0); - struct sockaddr_in dst; - memset(&dst, 0, sizeof(dst)); - dst.sin_family = AF_INET; - dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - dst.sin_port = htons(uport); - - /* Fill all 64 DST slots: each distinct destination opens a session, the first - * send fires on_err(MQ_UDP_CLOSED) → the entry is marked dead (failed_at==0). - * Vary BOTH the DST.ADDR and DST.PORT so every encap header is distinct. */ - const int MAXDST = 64; /* == MQ_UDP_ASSOC_MAX_DST */ - uint8_t payload[] = "x"; - for (int i = 0; i < MAXDST; i++) { - uint8_t dgram[64]; - uint32_t dip = htonl(0x0A000000u + (uint32_t)i); /* 10.0.0.i (distinct addr) */ - uint16_t dport = (uint16_t)(1000 + i); /* distinct port too */ - size_t dlen = build_udp_dgram(dgram, sizeof(dgram), dip, dport, payload, - sizeof(payload) - 1); - MQ_CHECK(dlen > 0); - sendto(u, dgram, dlen, 0, (struct sockaddr *)&dst, sizeof(dst)); - /* Pump until this destination's open + on_err have been processed. */ - int want = i + 1; - uint64_t deadline = now_ms() + 500; - while (s.open_calls < want && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - MQ_CHECK_EQ_INT(s.open_calls, want); - } - /* All 64 DST entries are now dead-and-immediately-reclaimable. */ - MQ_CHECK_EQ_INT(s.open_calls, MAXDST); - - /* 65th DISTINCT destination: with no free slot, dst_alloc must reclaim a dead - * entry → open_fn is invoked again (open_calls advances to 65). */ - uint8_t dgram65[64]; - uint32_t dip65 = htonl(0x0A000000u + (uint32_t)MAXDST); /* 10.0.0.64 */ - uint16_t dport65 = (uint16_t)(1000 + MAXDST); - size_t dlen65 = build_udp_dgram(dgram65, sizeof(dgram65), dip65, dport65, payload, - sizeof(payload) - 1); - MQ_CHECK(dlen65 > 0); - sendto(u, dgram65, dlen65, 0, (struct sockaddr *)&dst, sizeof(dst)); - uint64_t deadline = now_ms() + 1000; - while (s.open_calls < MAXDST + 1 && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } - /* The load-bearing assertion: the 65th destination opened a session, proving - * dst_alloc reclaimed a dead slot instead of returning NULL/dropping. */ - MQ_CHECK_EQ_INT(s.open_calls, MAXDST + 1); - - close(u); - mq_listener_free(l); - MQ_CHECK_EQ_INT(s.post_death_sends, 0); - close(c); -} - -static void -run_all(void) -{ - struct event_base *base = event_base_new(); - MQ_CHECK(base != NULL); - if (!base) return; - - test_socks5_happy(base); - test_socks5_unsupported(base); - test_socks5_malformed(base); - test_http_connect_smoke(base); - test_socks5_pipelined_prebuf(base); - test_http_pipelined_prebuf(base); - - /* UDP ASSOCIATE edge (Task 6.3). */ - test_assoc_establish_free(base); /* (a) */ - test_assoc_tcp_close_teardown(base); /* (b) */ - test_assoc_mixed_shutdown(base); /* (c) */ - test_assoc_no_close_after_err(base); /* (d) */ - test_assoc_availability_sweep(base); /* (e) */ - test_assoc_refused_no_udp(base); /* REP 0x07 when no UDP boundary */ - test_assoc_refused_unavail(base); /* REP 0x07 when availability == 0 */ - test_assoc_dst_reclaim_churn(base); /* dst_alloc reclaim sweep (codex gap) */ - - event_base_free(base); -} - -MQ_TEST_MAIN(run_all()) diff --git a/tests/integration/test_transport_fabric.c b/tests/integration/test_transport_fabric.c deleted file mode 100644 index b5f915fe..00000000 --- a/tests/integration/test_transport_fabric.c +++ /dev/null @@ -1,760 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_transport_fabric.c — socket-free + libevent-free transport+stream - * data-plane E2E (design §7, plan Chunk 8). - * - * Two mq_transport instances (client + server) are driven ENTIRELY in memory: - * no real UDP sockets, no libevent dispatch, no mq_client / mq_server / mq_flow. - * An in-memory "fabric" with two directional packet queues stands in for the - * network — each transport's send_udp callback enqueues into the PEER's inbound - * queue, and a single-threaded bounded driver loop ticks both transports and - * drains each queue into the peer via mq_transport_on_udp_recv (design §7): - * - * client mq_transport.send_udp ──┐ - * ├─ in-memory fabric (per-path queues) - * server mq_transport.send_udp ──┘ peer's on_udp_recv ← driver drains - * - * driver: while (i -#include -#include -#include -#include -#include - -#include - -#include "transport/mq_conn.h" -#include "transport/mq_stream.h" -#include "transport/mq_transport.h" - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -#define TEST_ALPN "mqproxy-tcp/1" - -/* Cap the driver loop so the test can NEVER hang. A working transport settles - * the handshake + a one-shot stream echo in a handful of round trips; this is - * comfortably above that. A regression must FAIL (cap reached), not hang. */ -#define MAX_ITERS 4000 -#define FABRIC_MAX_PKTS 256 -#define FABRIC_MTU 2048 - -/* Wall-clock backstop (ms): even if the cap were mis-sized, the loop cannot run - * longer than this. */ -#define WALL_BUDGET_MS 5000 - -/* Synthetic loopback ports for the two logical endpoints. Real binds never - * happen; these only have to give xquic two distinct, stable (local,peer) - * 4-tuples so it keys the path consistently across packets. */ -#define CLI_PORT 50001 -#define SRV_PORT 50002 - -/* The payload whose round-trip byte integrity is the core assertion. */ -static const unsigned char PAYLOAD[] = "the quick brown fox jumps over the lazy dog"; -#define PAYLOAD_LEN (sizeof(PAYLOAD) - 1) - -/* ── in-memory fabric ────────────────────────────────────────────────────── */ - -typedef struct { - uint8_t buf[FABRIC_MTU]; - size_t len; - uint64_t path; -} fabric_pkt_t; - -/* One directional queue (a bounded FIFO drained to empty each tick). */ -typedef struct { - fabric_pkt_t pkts[FABRIC_MAX_PKTS]; - size_t count; - int overflow; /* set if a packet was dropped (queue full) */ -} fabric_queue_t; - -typedef struct { - fabric_queue_t to_server; /* packets the client sent, awaiting the server */ - fabric_queue_t to_client; /* packets the server sent, awaiting the client */ -} fabric_t; - -/* Per-transport context: which queue this endpoint WRITES into when it sends, - * plus the synthetic local addr xquic must see on the receiving side. */ -typedef struct { - fabric_queue_t *outbound; /* the PEER's inbound queue */ - const char *role; -} endpoint_t; - -static void -fabric_enqueue(fabric_queue_t *q, uint64_t path, const uint8_t *pkt, size_t len) -{ - if (q->count >= FABRIC_MAX_PKTS || len > FABRIC_MTU) { - q->overflow = 1; - return; - } - fabric_pkt_t *p = &q->pkts[q->count++]; - memcpy(p->buf, pkt, len); - p->len = len; - p->path = path; -} - -static void -make_addr(struct sockaddr_in *sa, uint16_t port) -{ - memset(sa, 0, sizeof(*sa)); - sa->sin_family = AF_INET; - sa->sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa->sin_port = htons(port); -} - -/* ── mq_transport callbacks (the fabric backend) ─────────────────────────── */ - -/* send_udp: copy the packet into the PEER's inbound queue and report the byte - * count written (xquic's xqc_socket_write_pt contract; UDP is all-or-nothing). - * The destination addr is implicit (each endpoint writes only into its peer's - * queue), so it is not carried — the drain synthesises the (local,peer) tuple - * from the receiving endpoint's identity. */ -static int -fab_send_udp(uint64_t path, const uint8_t *pkt, size_t len, const struct sockaddr *peer, - socklen_t peerlen, void *user) -{ - (void)peer; - (void)peerlen; - endpoint_t *ep = (endpoint_t *)user; - fabric_enqueue(ep->outbound, path, pkt, len); - return (int)len; -} - -/* open_path_socket: no real socket. The primary path (path 0) is never routed - * through here — xquic sends primary traffic via write_socket with no socket - * lifecycle — so this only fires if a secondary path were added (not exercised - * by this test). Installed for completeness; a no-op success. */ -static int -fab_open_path_socket(uint64_t path, const char *local_ip, uint16_t port, void *user) -{ - (void)path; - (void)local_ip; - (void)port; - (void)user; - return 0; -} - -static void -fab_close_path_socket(uint64_t path, void *user) -{ - (void)path; - (void)user; -} - -/* Drain every queued packet into `dst` via the real on_udp_recv input edge. - * `dst` is the receiving endpoint, bound (logically) to dst_port; the source is - * bound to src_port. xquic keys the path on the (local,peer) 4-tuple, so the - * receiving side must always see local=dst_port, peer=src_port. - * - * Returns the number of packets on_udp_recv reported as accepted. The success - * code from xqc_engine_packet_process is XQC_OK == 0, so a successfully consumed - * packet returns 0 (>= 0); only a hard error is < 0. We count r >= 0. */ -static int -fabric_drain(fabric_queue_t *q, mq_transport_t *dst, uint16_t dst_port, uint16_t src_port) -{ - struct sockaddr_in local, peer; - make_addr(&local, dst_port); - make_addr(&peer, src_port); - - int consumed = 0; - for (size_t i = 0; i < q->count; i++) { - fabric_pkt_t *p = &q->pkts[i]; - int r = mq_transport_on_udp_recv(dst, p->path, p->buf, p->len, - (struct sockaddr *)&local, sizeof(local), - (struct sockaddr *)&peer, sizeof(peer)); - if (r >= 0) consumed++; - } - q->count = 0; - return consumed; -} - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -/* ── application state (the data-plane assertions) ───────────────────────── */ - -static int g_client_established; - -/* Server side: the echo stream + the bytes seen so far. */ -static mq_stream_t *g_server_stream; -static unsigned char g_server_rx[FABRIC_MTU]; -static size_t g_server_rx_len; -static int g_server_saw_fin; -static int g_server_echoed; - -/* Client side: the echo received back from the server. */ -static unsigned char g_client_rx[FABRIC_MTU]; -static size_t g_client_rx_len; -static int g_client_saw_fin; - -static void -on_client_state(mq_conn_t *c, mq_conn_state_t st, void *user) -{ - (void)c; - (void)user; - if (st == MQ_CONN_ESTABLISHED) { - g_client_established = 1; - } -} - -/* Client stream readable: drain the server's echo and note the FIN. */ -static void -on_client_stream_readable(mq_stream_t *s, void *user) -{ - (void)user; - for (;;) { - int fin = 0; - long n = mq_stream_recv(s, g_client_rx + g_client_rx_len, - sizeof(g_client_rx) - g_client_rx_len, &fin); - if (n > 0) { - g_client_rx_len += (size_t)n; - } - if (fin) { - g_client_saw_fin = 1; - } - if (n <= 0) break; - } -} - -/* Server stream readable: drain the client's payload; once FIN is seen, echo - * the accumulated bytes back with FIN. */ -static void -on_server_stream_readable(mq_stream_t *s, void *user) -{ - (void)user; - for (;;) { - int fin = 0; - long n = mq_stream_recv(s, g_server_rx + g_server_rx_len, - sizeof(g_server_rx) - g_server_rx_len, &fin); - if (n > 0) { - g_server_rx_len += (size_t)n; - } - if (fin) { - g_server_saw_fin = 1; - } - if (n <= 0) break; - } - - if (g_server_saw_fin && !g_server_echoed) { - long sent = mq_stream_send(s, g_server_rx, g_server_rx_len, /*fin=*/1); - if (sent >= 0) { - g_server_echoed = 1; - } - } -} - -/* Server side: accepted connections surface here. */ -static void -on_server_conn(mq_conn_t *c, void *user) -{ - (void)c; - (void)user; -} - -/* Server side: peer-initiated stream surfaces here — wire its read callback so - * we receive + echo the client's payload. */ -static void -on_server_stream(mq_stream_t *s, void *user) -{ - (void)user; - g_server_stream = s; - mq_stream_set_cbs(s, on_server_stream_readable, /*on_writable=*/NULL, - /*on_close=*/NULL, NULL); -} - -/* ── broadcast mp-ready test state ───────────────────────────────────────── */ - -/* A standalone subscriber registered via mq_transport_add_mp_ready_cb alongside - * the conn's own subscriber: it records that it fired and copies out the scid so - * the test can confirm the broadcast delivered the readiness signal (and the - * exact scid xquic supplied). */ -typedef struct { - int fired; - xqc_cid_t scid; -} mp_sub_t; - -static void -on_extra_mp_ready(const xqc_cid_t *scid, void *user) -{ - mp_sub_t *sub = (mp_sub_t *)user; - sub->fired = 1; - if (scid) { - memcpy(&sub->scid, scid, sizeof(sub->scid)); - } -} - -/* Negative-path sentinel: a conn-like object whose stored cid is deliberately - * DIFFERENT from any real conn's scid. mq_conn_on_mp_ready is static in - * mq_conn.c, so this replicates its scid-filter contract (have_cid + cid_len + - * cid_buf compare) verbatim. Bound as a subscriber, it must NEVER flip mp_ready, - * because no broadcast carries its (bogus) cid — proving the filter REJECTS a - * mismatched scid rather than flipping every subscriber unconditionally. */ -typedef struct { - int have_cid; - xqc_cid_t cid; - int mp_ready; /* must stay 0 for the whole run */ -} mp_filter_obj_t; - -static void -on_filter_mp_ready(const xqc_cid_t *scid, void *user) -{ - mp_filter_obj_t *o = (mp_filter_obj_t *)user; - if (!o || !o->have_cid || !scid) { - return; - } - if (scid->cid_len == o->cid.cid_len && - memcmp(scid->cid_buf, o->cid.cid_buf, o->cid.cid_len) == 0) { - o->mp_ready = 1; - } -} - -/* Helper: drive both transports one step (tick both, drain both directions). - * Used by the close-one-keep-one phase below where the survivor must keep making - * progress after a peer conn was closed. */ -static void -mp_pump_once(fabric_t *fabric, mq_transport_t *client, mq_transport_t *server) -{ - mq_transport_tick(client); - mq_transport_tick(server); - fabric_drain(&fabric->to_server, server, SRV_PORT, CLI_PORT); - fabric_drain(&fabric->to_client, client, CLI_PORT, SRV_PORT); - (void)mq_transport_next_timeout_ms(client); - (void)mq_transport_next_timeout_ms(server); -} - -/* Broadcast test: with multipath enabled, drive a real handshake to the point - * where xquic fires ready_to_create_path_notify, with TWO subscribers on the - * client transport — the conn's own (registered inside mq_conn_connect) plus an - * extra one registered via the new mq_transport_add_mp_ready_cb. Assert BOTH - * fire (broadcast), the extra one received the conn's scid, and the conn's own - * subscriber flipped mq_conn_mp_ready (its scid filter matched). This is the - * Phase 2 invariant: subscribers no longer clobber each other and every - * subscriber sees every conn's readiness. - * - * It then opens a SECOND real client conn on the same transport (two conns now - * share the engine + the mp-ready subscriber table), drives BOTH to readiness, - * and verifies the per-scid filter: each conn's own subscriber flips only its - * OWN mq_conn_mp_ready (a broadcast that matched the other conn's scid must not - * flip this one). Finally it closes the FIRST conn and keeps driving the SECOND - * to readiness: mq_conn_close_notify unsubscribes the closed conn, so a later - * broadcast can no longer deref its freed mq_conn (the use-after-free this fix - * removes — proven clean under ASan). */ -static void -test_mp_ready_broadcast(void) -{ - fabric_t fabric; - memset(&fabric, 0, sizeof(fabric)); - - endpoint_t client_ctx = {.outbound = &fabric.to_server, .role = "client"}; - endpoint_t server_ctx = {.outbound = &fabric.to_client, .role = "server"}; - - mq_transport_callbacks_t cbs = { - .send_udp = fab_send_udp, - .open_path_socket = fab_open_path_socket, - .close_path_socket = fab_close_path_socket, - }; - - mq_transport_t *client = mq_transport_new(/*is_server=*/0); - MQ_CHECK(client != NULL); - mq_transport_t *server = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(server != NULL); - if (!client || !server) { - if (client) mq_transport_free(client); - if (server) mq_transport_free(server); - return; - } - mq_transport_set_callbacks(client, &cbs, &client_ctx); - mq_transport_set_callbacks(server, &cbs, &server_ctx); - - int rc = mq_conn_register_alpn(server, TEST_ALPN, on_server_conn, NULL, NULL); - MQ_CHECK_EQ_INT(rc, 0); - rc = mq_conn_register_alpn(client, TEST_ALPN, NULL, NULL, NULL); - MQ_CHECK_EQ_INT(rc, 0); - - /* The server is a pure responder, but it must advertise enable_multipath + - * the larger path-id grant so the client's ready_to_create_path_notify can - * fire (multipath is negotiated; both sides must enable it). */ - xqc_conn_settings_t srv_settings; - memset(&srv_settings, 0, sizeof(srv_settings)); - srv_settings.proto_version = XQC_VERSION_V1; - srv_settings.pacing_on = 1; - mq_conn_apply_mp_settings(&srv_settings, /*is_server=*/1, MQ_CC_DEFAULT); - xqc_server_set_conn_settings(mq_transport_xqc(server), &srv_settings); - - /* Register the extra (broadcast-witness) subscriber BEFORE connect; the - * conn's own subscriber is added inside mq_conn_connect. */ - mp_sub_t extra = {0}; - rc = mq_transport_add_mp_ready_cb(client, on_extra_mp_ready, &extra); - MQ_CHECK_EQ_INT(rc, 0); - - /* Negative-path subscriber: a conn-like object with a deliberately BOGUS cid - * (have_cid=1, distinct cid_len + bytes). It replicates mq_conn_on_mp_ready's - * scid filter, so it must NEVER flip — no broadcast carries this cid. This - * proves the filter rejects a mismatched scid (vs. flipping unconditionally), - * and consumes the table headroom MQ_MP_READY_SUBS_MAX was widened for. */ - mp_filter_obj_t bogus = {0}; - bogus.have_cid = 1; - bogus.cid.cid_len = 8; - memset(bogus.cid.cid_buf, 0xAB, bogus.cid.cid_len); - rc = mq_transport_add_mp_ready_cb(client, on_filter_mp_ready, &bogus); - MQ_CHECK_EQ_INT(rc, 0); - - struct sockaddr_in srv_addr; - make_addr(&srv_addr, SRV_PORT); - - /* Multipath ON on both sides so xquic fires ready_to_create_path_notify. */ - xqc_conn_settings_t settings; - memset(&settings, 0, sizeof(settings)); - settings.proto_version = XQC_VERSION_V1; - settings.pacing_on = 1; - settings.max_pkt_out_size = 1200; - mq_conn_apply_mp_settings(&settings, /*is_server=*/0, MQ_CC_DEFAULT); - - mq_conn_t *conn = mq_conn_connect(client, (struct sockaddr *)&srv_addr, - sizeof(srv_addr), TEST_ALPN, &settings, NULL); - MQ_CHECK(conn != NULL); - if (!conn) { - mq_transport_free(client); - mq_transport_free(server); - return; - } - - /* Drive until BOTH subscribers have fired (mp-ready broadcast) or the bounded - * caps fire. mq_conn_mp_ready reflects the conn's own (scid-filtered) - * subscriber; extra.fired reflects the standalone one. */ - uint64_t deadline = now_ms() + WALL_BUDGET_MS; - int iters = 0; - while ((!extra.fired || !mq_conn_mp_ready(conn)) && iters < MAX_ITERS && - now_ms() < deadline) { - mp_pump_once(&fabric, client, server); - iters++; - } - - /* Both subscribers fired: the broadcast reached every slot. */ - MQ_CHECK(extra.fired); - MQ_CHECK(mq_conn_mp_ready(conn)); - - /* The extra subscriber received a non-empty scid (xquic supplied the conn's - * scid to every subscriber). */ - MQ_CHECK(extra.scid.cid_len > 0); - - /* The negative-path subscriber was broadcast to (same as every slot) but its - * bogus cid matched no conn's scid, so its filter rejected every fire. */ - MQ_CHECK_EQ_INT(bogus.mp_ready, 0); - - /* ── Second real conn on the same transport ────────────────────────────── - * Open a SECOND client conn on the SAME client transport. Both conns now - * share the engine and the mp-ready subscriber table; each conn's own - * subscriber (mq_conn_on_mp_ready) is registered with its own mq_conn as the - * user, and filters by scid. The first conn's mq_conn_mp_ready is ALREADY 1; - * driving the second to readiness exercises the per-scid filter — the second - * conn's readiness broadcast must NOT touch the first (different scid), and - * the first conn's readiness broadcast (already past) never flipped the - * second. We assert the second conn flips independently. */ - mq_conn_t *conn2 = mq_conn_connect(client, (struct sockaddr *)&srv_addr, - sizeof(srv_addr), TEST_ALPN, &settings, NULL); - MQ_CHECK(conn2 != NULL); - if (!conn2) { - mq_conn_close(conn); - mq_transport_free(client); - mq_transport_free(server); - return; - } - - deadline = now_ms() + WALL_BUDGET_MS; - iters = 0; - while (!mq_conn_mp_ready(conn2) && iters < MAX_ITERS && now_ms() < deadline) { - mp_pump_once(&fabric, client, server); - iters++; - } - /* The second conn's per-scid filter matched its OWN scid and flipped. */ - MQ_CHECK(mq_conn_mp_ready(conn2)); - /* The first conn stayed ready (its flag is sticky; not clobbered). */ - MQ_CHECK(mq_conn_mp_ready(conn)); - - /* ── Close one, keep one ────────────────────────────────────────────────── - * Close the FIRST conn while the SECOND stays open + ready — the two-conn - * shape the broadcast work enables. When xquic surfaces conn_close_notify, - * mq_conn_close_notify unsubscribes the closing conn from the transport's - * mp-ready broadcast BEFORE free(c); without it the freed mq_conn's `user` - * pointer would linger in the subscriber table and a later broadcast would - * deref it (use-after-free). Run under ASan, a clean teardown (no sanitizer - * report) proves the unsubscribe + free do not corrupt the table or leave a - * dangling slot the survivor's teardown then touches. - * - * NB: this in-memory fabric defers xquic's conn_close_notify to engine - * teardown, so it cannot stage a broadcast AFTER a mid-life free; the remove - * API's table contract (a removed slot is reclaimed, not left dangling) is - * pinned directly by test_mp_ready_unsubscribe below. */ - mq_conn_close(conn); - - /* Pump past the close; the survivor is already ready, so there is no flag - * left to wait on — keep the engine busy so the closing conn's teardown - * exercises the unsubscribe under ASan. */ - uint64_t close_deadline = now_ms() + 200; - int close_iters = 0; - while (close_iters < MAX_ITERS && now_ms() < close_deadline) { - mp_pump_once(&fabric, client, server); - close_iters++; - } - /* The survivor is intact and still ready after the peer conn was closed (no - * crash/UAF reaching here under ASan is the real assertion). */ - MQ_CHECK(mq_conn_mp_ready(conn2)); - - mq_conn_close(conn2); - close_deadline = now_ms() + 200; - close_iters = 0; - while (close_iters < MAX_ITERS && now_ms() < close_deadline) { - mp_pump_once(&fabric, client, server); - close_iters++; - } - - mq_transport_free(client); - mq_transport_free(server); -} - -static void -test_transport_fabric(void) -{ - fabric_t fabric; - memset(&fabric, 0, sizeof(fabric)); - - /* Per-endpoint contexts: each transport sends into the PEER's queue. */ - endpoint_t client_ctx = {.outbound = &fabric.to_server, .role = "client"}; - endpoint_t server_ctx = {.outbound = &fabric.to_client, .role = "server"}; - - mq_transport_callbacks_t cbs = { - .send_udp = fab_send_udp, - .open_path_socket = fab_open_path_socket, - .close_path_socket = fab_close_path_socket, - }; - - /* Two transports over the shared fabric: client + server (with TLS material - * so the server transport is handshake-capable). */ - mq_transport_t *client = mq_transport_new(/*is_server=*/0); - MQ_CHECK(client != NULL); - mq_transport_t *server = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(server != NULL); - if (!client || !server) { - if (client) mq_transport_free(client); - if (server) mq_transport_free(server); - return; - } - mq_transport_set_callbacks(client, &cbs, &client_ctx); - mq_transport_set_callbacks(server, &cbs, &server_ctx); - - MQ_CHECK(mq_transport_xqc(client) != NULL); - MQ_CHECK(mq_transport_xqc(server) != NULL); - - /* Register the ALPN on both engines: server supplies new-conn / new-stream - * hooks; the client is a pure initiator (NULL server hooks). */ - int rc = - mq_conn_register_alpn(server, TEST_ALPN, on_server_conn, on_server_stream, NULL); - MQ_CHECK_EQ_INT(rc, 0); - rc = mq_conn_register_alpn(client, TEST_ALPN, NULL, NULL, NULL); - MQ_CHECK_EQ_INT(rc, 0); - - /* The client connects to the server's synthetic loopback address. The - * primary path (path 0) needs no open_path_socket — xquic emits its packets - * via write_socket, which the fabric routes by endpoint identity. */ - struct sockaddr_in srv_addr; - make_addr(&srv_addr, SRV_PORT); - - xqc_conn_settings_t settings; - memset(&settings, 0, sizeof(settings)); - settings.proto_version = XQC_VERSION_V1; - settings.pacing_on = 1; - settings.max_pkt_out_size = 1200; - - mq_conn_t *conn = mq_conn_connect(client, (struct sockaddr *)&srv_addr, - sizeof(srv_addr), TEST_ALPN, &settings, NULL); - MQ_CHECK(conn != NULL); - if (!conn) { - mq_transport_free(client); - mq_transport_free(server); - return; - } - mq_conn_set_on_state(conn, on_client_state, NULL); - - /* ── Phase 1: drive the handshake to ESTABLISHED ─────────────────────── */ - uint64_t deadline = now_ms() + WALL_BUDGET_MS; - int iters = 0; - while (!g_client_established && iters < MAX_ITERS && now_ms() < deadline) { - mq_transport_tick(client); - mq_transport_tick(server); - fabric_drain(&fabric.to_server, server, SRV_PORT, CLI_PORT); - fabric_drain(&fabric.to_client, client, CLI_PORT, SRV_PORT); - (void)mq_transport_next_timeout_ms(client); - (void)mq_transport_next_timeout_ms(server); - iters++; - } - MQ_CHECK(g_client_established); - - /* ── Phase 2: client opens a bidi stream, sends the payload with FIN ──── */ - mq_stream_t *cs = mq_conn_open_stream(conn); - MQ_CHECK(cs != NULL); - if (cs) { - mq_stream_set_cbs(cs, on_client_stream_readable, /*on_writable=*/NULL, - /*on_close=*/NULL, NULL); - long sent = mq_stream_send(cs, PAYLOAD, PAYLOAD_LEN, /*fin=*/1); - MQ_CHECK_EQ_INT((int)sent, (int)PAYLOAD_LEN); - } - - /* Drive until the round-trip echo lands (or the bounded caps fire). */ - int done = 0; - while (!done && iters < MAX_ITERS && now_ms() < deadline) { - mq_transport_tick(client); - mq_transport_tick(server); - fabric_drain(&fabric.to_server, server, SRV_PORT, CLI_PORT); - fabric_drain(&fabric.to_client, client, CLI_PORT, SRV_PORT); - (void)mq_transport_next_timeout_ms(client); - (void)mq_transport_next_timeout_ms(server); - iters++; - done = (g_client_saw_fin && g_client_rx_len == PAYLOAD_LEN); - } - - /* The fabric must not have silently dropped packets (would mask a failure - * as a pass). */ - MQ_CHECK_EQ_INT(fabric.to_server.overflow, 0); - MQ_CHECK_EQ_INT(fabric.to_client.overflow, 0); - - int established = g_client_established; - int server_saw_stream = (g_server_stream != NULL) && g_server_saw_fin; - int echo_matches = (g_client_rx_len == PAYLOAD_LEN) && - (memcmp(g_client_rx, PAYLOAD, PAYLOAD_LEN) == 0); - int clean_close = g_client_saw_fin && g_server_echoed; - - int success = established && server_saw_stream && echo_matches && clean_close; - if (!success) { - fprintf(stderr, - "fabric E2E FAILED [%s/%s]: established=%d server_saw_stream=%d " - "(stream=%p server_fin=%d) echo_matches=%d (rx_len=%zu want=%zu) " - "clean_close=%d (client_fin=%d echoed=%d) iters=%d\n", - client_ctx.role, server_ctx.role, established, server_saw_stream, - (void *)g_server_stream, g_server_saw_fin, echo_matches, g_client_rx_len, - (size_t)PAYLOAD_LEN, clean_close, g_client_saw_fin, g_server_echoed, - iters); - } - MQ_CHECK(success); - - mq_conn_close(conn); - - /* Pump briefly so the local CONNECTION_CLOSE frame flushes to the peer - * (bounded). xquic surfaces conn_close_notify only at engine teardown (after - * its closing/drain timer), not within this in-memory pump, so the - * MQ_CONN_CLOSED transition is not asserted here — the clean-close byte-level - * criterion (client FIN seen + server echoed with FIN) is asserted above. */ - uint64_t close_deadline = now_ms() + 200; - int close_iters = 0; - while (close_iters < MAX_ITERS && now_ms() < close_deadline) { - mq_transport_tick(client); - mq_transport_tick(server); - fabric_drain(&fabric.to_server, server, SRV_PORT, CLI_PORT); - fabric_drain(&fabric.to_client, client, CLI_PORT, SRV_PORT); - close_iters++; - } - - mq_transport_free(client); - mq_transport_free(server); -} - -/* Focused unit test for mq_transport_remove_mp_ready_cb (the unsubscribe path - * mq_conn_close_notify relies on). The in-memory fabric defers xquic's - * conn_close_notify to engine teardown, so it cannot manufacture a broadcast - * AFTER a mid-life conn free; this test instead pins the remove API's contract - * directly via the one observable the public API exposes — the bounded table: - * 1. fill the table to MQ_MP_READY_SUBS_MAX (adds succeed, then overflow -1); - * 2. remove one entry (no-op-safe, matched by fn+user); - * 3. a fresh add now SUCCEEDS — proving the slot was reclaimed (compacted), - * i.e. the removed subscriber is truly gone from the table a later - * broadcast would iterate (no stale pointer left behind); - * 4. removing a never-registered pair is a harmless no-op. - * MQ_MP_READY_SUBS_MAX is 4, matching the table's compile-time dimension. */ -static void -noop_mp_ready(const xqc_cid_t *scid, void *user) -{ - (void)scid; - (void)user; -} - -static void -test_mp_ready_unsubscribe(void) -{ - mq_transport_t *t = mq_transport_new(/*is_server=*/0); - MQ_CHECK(t != NULL); - if (!t) { - return; - } - - /* Distinct user pointers so each (fn,user) pair is a unique registration. */ - int users[8]; - const int cap = 4; /* == MQ_MP_READY_SUBS_MAX */ - - /* 1. Fill the table; the (cap+1)-th add overflows. */ - for (int i = 0; i < cap; i++) { - MQ_CHECK_EQ_INT(mq_transport_add_mp_ready_cb(t, noop_mp_ready, &users[i]), 0); - } - MQ_CHECK_EQ_INT(mq_transport_add_mp_ready_cb(t, noop_mp_ready, &users[cap]), -1); - - /* 2. Remove a middle entry (exercises the tail-shift compaction). */ - mq_transport_remove_mp_ready_cb(t, noop_mp_ready, &users[1]); - - /* 3. A fresh add now succeeds: the removed slot was reclaimed. */ - MQ_CHECK_EQ_INT(mq_transport_add_mp_ready_cb(t, noop_mp_ready, &users[5]), 0); - /* And the table is full again. */ - MQ_CHECK_EQ_INT(mq_transport_add_mp_ready_cb(t, noop_mp_ready, &users[6]), -1); - - /* 4. Removing a pair that was never registered is a harmless no-op (count - * unchanged, so the table stays full). */ - mq_transport_remove_mp_ready_cb(t, noop_mp_ready, &users[7]); - MQ_CHECK_EQ_INT(mq_transport_add_mp_ready_cb(t, noop_mp_ready, &users[7]), -1); - - /* NULL-arg guards: no-op, no crash. */ - mq_transport_remove_mp_ready_cb(NULL, noop_mp_ready, &users[0]); - mq_transport_remove_mp_ready_cb(t, NULL, &users[0]); - - mq_transport_free(t); -} - -MQ_TEST_MAIN({ - test_transport_fabric(); - test_mp_ready_broadcast(); - test_mp_ready_unsubscribe(); -}) diff --git a/tests/integration/test_two_paths.c b/tests/integration/test_two_paths.c deleted file mode 100644 index 8c35d2a1..00000000 --- a/tests/integration/test_two_paths.c +++ /dev/null @@ -1,632 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_two_paths.c — Task 17: a SECOND MPQUIC path comes up. - * - * Brings up an AUTHED client+server over loopback (single libevent base, one - * process). After auth, the client adds a SECOND loopback path (a distinct - * ephemeral local UDP port) via mq_conn_add_path() and asserts: - * 1. mq_conn_add_path returns a NEW, distinct path_id (!= 0, the primary). - * 2. Driving the loop a bounded time, the second path reaches xquic's - * ACTIVE path-state (path validated) — proving it COMES UP. - * 3. With two paths registered, a small end-to-end transfer still succeeds - * (the connection still works). - * - * Throughput / aggregation is Task 19; this test only proves the second path - * validates and the conn is still usable. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ingress/mq_listener.h" -#include "proxy/mq_client.h" -#include "proxy/mq_server.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_transport.h" -#include "util/mq_log.h" - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -static void -set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - if (fl >= 0) (void)fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -/* Window constants must mirror xquic's ceiling and the 8MB/16MB BDP target. */ -static void -test_window_constants(void) -{ - /* These are compile-time asserts in mq_conn.h; re-check at runtime so the - * test fails loudly if the header drifts. */ - MQ_CHECK_EQ_INT(MQ_STREAM_WINDOW, 8 * 1024 * 1024); - MQ_CHECK_EQ_INT(MQ_CONN_WINDOW, 16 * 1024 * 1024); - MQ_CHECK(MQ_CONN_WINDOW <= MQ_XQUIC_MAX_RECV_WINDOW); -} - -/* ── full-stack fixture (mirrors test_e2e_single_path) ───────────────────── */ -/* The SECOND MPQUIC path (Task 17) is now opened internally by mq_conn_add_path - * routing through the transport's open_path_socket callback into the client - * runtime — there is no test-owned second-path handle to track or free. */ -typedef struct { - struct event_base *base; - mq_transport_t *srv_t; - mq_transport_t *cli_t; - mq_runtime_t *srv_rt; - mq_runtime_t *cli_rt; - mq_server_t *server; - mq_client_t *client; - mq_listener_t *socks5; -} fixture_t; - -/* Reserve an ephemeral loopback UDP port (the runtime no longer exposes the - * bound port of a :0 bind, so the server pins a concrete port up front). */ -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -static int -fixture_up(fixture_t *f) -{ - memset(f, 0, sizeof(*f)); - f->base = event_base_new(); - MQ_CHECK(f->base != NULL); - if (!f->base) return -1; - - uint16_t port = reserve_udp_port(); - MQ_CHECK(port != 0); - if (!port) return -1; - - f->srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(f->srv_t != NULL); - if (!f->srv_t) return -1; - f->srv_rt = mq_runtime_new(f->srv_t, f->base); - MQ_CHECK(f->srv_rt != NULL); - if (!f->srv_rt) return -1; - f->server = mq_server_new(f->srv_t, f->srv_rt, "secret", MQ_CC_BBR2, 60000, 1); - MQ_CHECK(f->server != NULL); - if (!f->server) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->srv_rt, "127.0.0.1", port), 0); - - f->cli_t = mq_transport_new(0); - MQ_CHECK(f->cli_t != NULL); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - MQ_CHECK(f->cli_rt != NULL); - if (!f->cli_rt) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0), 0); - - f->client = mq_client_new(f->cli_t, f->cli_rt, "127.0.0.1", port, "client-1", - "secret", MQ_CC_BBR2); - MQ_CHECK(f->client != NULL); - if (!f->client) return -1; - - MQ_CHECK_EQ_INT(mq_client_start(f->client), 0); - - void *core = mq_client_tcp_open_core(f->client); - f->socks5 = mq_socks5_listener_new(f->base, "127.0.0.1", 0, mq_client_tcp_open_fn(), - core, NULL, NULL, NULL, NULL); - MQ_CHECK(f->socks5 != NULL); - if (!f->socks5) return -1; - return 0; -} - -static void -fixture_down(fixture_t *f) -{ - /* Per side: transport first (fires conn-close + in-flight callbacks into the - * live runtime/client/server/listener, and tears down the second path via - * close_path_socket on the client runtime), then the runtimes (which BORROWED - * the shared base, so they do NOT free it), then the proxy objects + the - * listener. The test owns the shared base and frees it last. */ - if (f->cli_t) mq_transport_free(f->cli_t); - if (f->srv_t) mq_transport_free(f->srv_t); - if (f->cli_rt) mq_runtime_free(f->cli_rt); - if (f->srv_rt) mq_runtime_free(f->srv_rt); - if (f->client) mq_client_free(f->client); - if (f->server) mq_server_free(f->server); - if (f->socks5) mq_listener_free(f->socks5); - if (f->base) event_base_free(f->base); -} - -/* Pump the base until pred() returns non-zero or the budget expires. */ -static int -pump_until(struct event_base *base, int (*pred)(void *), void *arg, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while (now_ms() < deadline) { - if (pred(arg)) return 1; - event_base_loop(base, EVLOOP_NONBLOCK); - } - return pred(arg); -} - -static int -pred_authed(void *arg) -{ - return mq_client_is_authed((const mq_client_t *)arg); -} - -static int -pred_mp_ready(void *arg) -{ - return mq_conn_mp_ready((const mq_conn_t *)arg); -} - -struct path_up_ctx { - mq_conn_t *conn; - uint64_t path_id; -}; - -static int -pred_path_active(void *arg) -{ - struct path_up_ctx *c = (struct path_up_ctx *)arg; - /* xquic XQC_PATH_STATE_ACTIVE == 2 (private xqc_multipath.h). */ - return mq_conn_path_state(c->conn, c->path_id) == 2; -} - -/* ── client-socket helpers (subset of test_e2e_single_path) ──────────────── */ -static int -dial_nb(uint16_t port) -{ - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) return -1; - set_nonblock(fd); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr); - int r = connect(fd, (struct sockaddr *)&sa, sizeof(sa)); - if (r != 0 && errno != EINPROGRESS) { - close(fd); - return -1; - } - return fd; -} - -static int -send_all_nb(struct event_base *base, int fd, const uint8_t *buf, size_t len, - uint64_t budget_ms) -{ - size_t off = 0; - uint64_t deadline = now_ms() + budget_ms; - while (off < len && now_ms() < deadline) { - ssize_t n = send(fd, buf + off, len - off, MSG_NOSIGNAL); - if (n > 0) { - off += (size_t)n; - } else if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - event_base_loop(base, EVLOOP_NONBLOCK); - } else if (n < 0 && errno == EINTR) { - continue; - } else { - break; - } - } - return off == len ? 0 : -1; -} - -static size_t -recv_exact(struct event_base *base, int fd, uint8_t *out, size_t want, uint64_t budget_ms) -{ - size_t got = 0; - uint64_t deadline = now_ms() + budget_ms; - while (got < want && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - ssize_t n = recv(fd, out + got, want - got, 0); - if (n > 0) { - got += (size_t)n; - } else if (n == 0) { - break; - } - } - return got; -} - -static int -socks5_greet(struct event_base *base, int fd) -{ - uint8_t greeting[] = {0x05, 0x01, 0x00}; - if (send_all_nb(base, fd, greeting, sizeof(greeting), 1000) != 0) return -1; - uint8_t mreply[2] = {0}; - if (recv_exact(base, fd, mreply, 2, 2000) != 2) return -1; - if (mreply[0] != 0x05 || mreply[1] != 0x00) return -1; - return 0; -} - -static int -socks5_connect_v4(struct event_base *base, int fd, uint16_t port) -{ - uint8_t req[10]; - size_t i = 0; - req[i++] = 0x05; - req[i++] = 0x01; - req[i++] = 0x00; - req[i++] = 0x01; - uint32_t v4 = htonl(INADDR_LOOPBACK); - memcpy(req + i, &v4, 4); - i += 4; - req[i++] = (uint8_t)(port >> 8); - req[i++] = (uint8_t)(port & 0xff); - return send_all_nb(base, fd, req, i, 1000); -} - -/* ── in-process echo origin ──────────────────────────────────────────────── */ -typedef struct { - struct event_base *base; - int listen_fd; - int conn_fd; - struct event *listen_ev; - struct event *conn_ev; -} echo_origin_t; - -static void -echo_conn_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - echo_origin_t *o = (echo_origin_t *)arg; - unsigned char buf[8192]; - ssize_t n = recv(fd, buf, sizeof(buf), 0); - if (n <= 0) { - if (n == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) { - if (o->conn_ev) { - event_free(o->conn_ev); - o->conn_ev = NULL; - } - close(fd); - o->conn_fd = -1; - } - return; - } - ssize_t off = 0; - while (off < n) { - ssize_t w = send(fd, buf + off, (size_t)(n - off), MSG_NOSIGNAL); - if (w <= 0) break; - off += w; - } -} - -static void -echo_accept_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - echo_origin_t *o = (echo_origin_t *)arg; - int c = accept(fd, NULL, NULL); - if (c < 0) return; - set_nonblock(c); - o->conn_fd = c; - o->conn_ev = event_new(o->base, c, EV_READ | EV_PERSIST, echo_conn_cb, o); - event_add(o->conn_ev, NULL); -} - -static int -echo_origin_up(echo_origin_t *o, struct event_base *base, uint16_t *out_port) -{ - memset(o, 0, sizeof(*o)); - o->base = base; - o->conn_fd = -1; - o->listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (o->listen_fd < 0) return -1; - int one = 1; - setsockopt(o->listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(o->listen_fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) return -1; - if (listen(o->listen_fd, 4) != 0) return -1; - socklen_t sl = sizeof(sa); - if (getsockname(o->listen_fd, (struct sockaddr *)&sa, &sl) != 0) return -1; - *out_port = ntohs(sa.sin_port); - set_nonblock(o->listen_fd); - o->listen_ev = event_new(base, o->listen_fd, EV_READ | EV_PERSIST, echo_accept_cb, o); - event_add(o->listen_ev, NULL); - return 0; -} - -static void -echo_origin_down(echo_origin_t *o) -{ - if (o->conn_ev) event_free(o->conn_ev); - if (o->listen_ev) event_free(o->listen_ev); - if (o->conn_fd >= 0) close(o->conn_fd); - if (o->listen_fd >= 0) close(o->listen_fd); -} - -/* ── the test: second path comes up, conn still works ────────────────────── */ -static void -test_second_path_comes_up(void) -{ - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - /* 1. Wait for auth to complete. */ - MQ_CHECK(pump_until(f.base, pred_authed, f.client, 8000)); - MQ_CHECK(mq_client_is_authed(f.client)); - - mq_conn_t *conn = mq_client_conn(f.client); - MQ_CHECK(conn != NULL); - if (!conn) { - fixture_down(&f); - return; - } - - /* 2. Wait until xquic signals it can create a path (cids exchanged). */ - MQ_CHECK(pump_until(f.base, pred_mp_ready, conn, 8000)); - MQ_CHECK(mq_conn_mp_ready(conn)); - - /* 3. Add a SECOND loopback path on a distinct ephemeral local port. */ - int pid = mq_conn_add_path(conn, "127.0.0.1", 0); - MQ_CHECK(pid > 0); /* distinct from primary path_id 0 */ - if (pid <= 0) { - fixture_down(&f); - return; - } - - /* 4. Drive the loop until the second path reaches ACTIVE (validated). */ - struct path_up_ctx pctx = {.conn = conn, .path_id = (uint64_t)pid}; - MQ_CHECK(pump_until(f.base, pred_path_active, &pctx, 8000)); - MQ_CHECK_EQ_INT(mq_conn_path_state(conn, (uint64_t)pid), 2); - - /* 5. With two paths registered, a small e2e transfer still succeeds. */ - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - int c = dial_nb(mq_listener_local_port(f.socks5)); - MQ_CHECK(c >= 0); - if (c < 0) { - echo_origin_down(&origin); - fixture_down(&f); - return; - } - - MQ_CHECK_EQ_INT(socks5_greet(f.base, c), 0); - MQ_CHECK_EQ_INT(socks5_connect_v4(f.base, c, origin_port), 0); - - uint8_t reply[10] = {0}; - MQ_CHECK_EQ_INT((int)recv_exact(f.base, c, reply, 10, 8000), 10); - MQ_CHECK_EQ_INT(reply[0], 0x05); - MQ_CHECK_EQ_INT(reply[1], 0x00); - - const size_t M = 4096; - uint8_t *payload = malloc(M); - uint8_t *back = malloc(M); - MQ_CHECK(payload != NULL && back != NULL); - for (size_t i = 0; i < M; i++) - payload[i] = (uint8_t)((i * 31 + 7) & 0xff); - MQ_CHECK_EQ_INT(send_all_nb(f.base, c, payload, M, 4000), 0); - size_t got = recv_exact(f.base, c, back, M, 8000); - MQ_CHECK_EQ_INT((long long)got, (long long)M); - MQ_CHECK(memcmp(payload, back, M) == 0); - - free(payload); - free(back); - close(c); - echo_origin_down(&origin); - fixture_down(&f); -} - -/* ── Task 18: per-path byte stats (mq_conn_dump_stats / mq_conn_path_bytes) ── - * - * After the second path is ACTIVE, push a multi-MB transfer through a SOCKS5 - * tcp_open so xquic accumulates real per-path byte counters, then: - * - smoke-call mq_conn_dump_stats (must not crash / leak paths_info), - * - assert via mq_conn_path_bytes that BOTH the primary (path_id 0) and the - * second path report real, non-decreasing byte counters, and that the - * transfer as a whole moved at least the bytes we pushed. - * - * HONEST SCOPE: the default xquic scheduler is minRTT (mqproxy sets no custom - * scheduler — see design §7). On equal-RTT loopback minRTT is free to put the - * whole bulk stream on EITHER path, so we do NOT hard-require a particular - * split ratio (controlled traffic shaping to force a deterministic split is - * Task 19's job, alongside qlog blocked-frame counting). What we prove here: - * the accessor returns truthful per-path counters for BOTH path-ids, every - * path carried >= 0 bytes, and the aggregate (path0+path1) actually moved data - * in both directions — i.e. real traffic flowed and the per-path numbers are - * not fabricated. Empirically (equal-RTT loopback) minRTT often parks the bulk - * on the SECOND path with only handshake-sized bytes on path 0; we log which - * paths carried data rather than asserting a fixed distribution. */ - -/* Drain echo replies on `fd` for up to budget_ms, returning total bytes read. - * Used to keep the QUIC stream flowing during a bulk echo so both directions - * actually move multi-MB of data (and per-path counters accumulate). */ -static size_t -drain_some(struct event_base *base, int fd, uint8_t *out, size_t cap, uint64_t budget_ms) -{ - size_t got = 0; - uint64_t deadline = now_ms() + budget_ms; - while (got < cap && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - ssize_t n = recv(fd, out + got, cap - got, 0); - if (n > 0) { - got += (size_t)n; - } else if (n == 0) { - break; - } - } - return got; -} - -static void -test_per_path_stats(void) -{ - fixture_t f; - if (fixture_up(&f) != 0) { - fixture_down(&f); - return; - } - - MQ_CHECK(pump_until(f.base, pred_authed, f.client, 8000)); - MQ_CHECK(mq_client_is_authed(f.client)); - - mq_conn_t *conn = mq_client_conn(f.client); - MQ_CHECK(conn != NULL); - if (!conn) { - fixture_down(&f); - return; - } - - MQ_CHECK(pump_until(f.base, pred_mp_ready, conn, 8000)); - int pid = mq_conn_add_path(conn, "127.0.0.1", 0); - MQ_CHECK(pid > 0); - if (pid <= 0) { - fixture_down(&f); - return; - } - - struct path_up_ctx pctx = {.conn = conn, .path_id = (uint64_t)pid}; - MQ_CHECK(pump_until(f.base, pred_path_active, &pctx, 8000)); - MQ_CHECK_EQ_INT(mq_conn_path_state(conn, (uint64_t)pid), 2); - - /* Bulk transfer through the echo origin so per-path counters accumulate. */ - echo_origin_t origin; - uint16_t origin_port = 0; - MQ_CHECK_EQ_INT(echo_origin_up(&origin, f.base, &origin_port), 0); - - int c = dial_nb(mq_listener_local_port(f.socks5)); - MQ_CHECK(c >= 0); - if (c < 0) { - echo_origin_down(&origin); - fixture_down(&f); - return; - } - MQ_CHECK_EQ_INT(socks5_greet(f.base, c), 0); - MQ_CHECK_EQ_INT(socks5_connect_v4(f.base, c, origin_port), 0); - uint8_t reply[10] = {0}; - MQ_CHECK_EQ_INT((int)recv_exact(f.base, c, reply, 10, 8000), 10); - MQ_CHECK_EQ_INT(reply[0], 0x05); - MQ_CHECK_EQ_INT(reply[1], 0x00); - - /* Push ~512KB up and drain the echoed copy back down. Interleave send + - * drain so the TCP socket buffers never deadlock on a single large write. */ - const size_t TOTAL = 512 * 1024; - const size_t CHUNK = 16 * 1024; - uint8_t *chunk = malloc(CHUNK); - uint8_t *sink = malloc(TOTAL); - MQ_CHECK(chunk != NULL && sink != NULL); - for (size_t i = 0; i < CHUNK; i++) - chunk[i] = (uint8_t)((i * 31 + 7) & 0xff); - - size_t sent_total = 0, recv_total = 0; - while (sent_total < TOTAL) { - size_t want = TOTAL - sent_total; - if (want > CHUNK) want = CHUNK; - if (send_all_nb(f.base, c, chunk, want, 4000) != 0) break; - sent_total += want; - recv_total += drain_some(f.base, c, sink, TOTAL - recv_total, 1000); - } - /* Drain whatever echo is still in flight. */ - recv_total += drain_some(f.base, c, sink, TOTAL - recv_total, 8000); - - MQ_CHECK_EQ_INT((long long)sent_total, (long long)TOTAL); - MQ_CHECK((long long)recv_total > 0); - - /* Smoke: dump must not crash and must free paths_info (LSan watches). */ - mq_conn_dump_stats(conn); - - /* Structured accessor: both path-ids report truthful counters. */ - uint64_t s0 = UINT64_MAX, r0 = UINT64_MAX, s1 = UINT64_MAX, r1 = UINT64_MAX; - MQ_CHECK_EQ_INT(mq_conn_path_bytes(conn, 0, &s0, &r0), 0); - MQ_CHECK_EQ_INT(mq_conn_path_bytes(conn, (uint64_t)pid, &s1, &r1), 0); - /* They were written (no longer the sentinel). */ - MQ_CHECK(s0 != UINT64_MAX && r0 != UINT64_MAX); - MQ_CHECK(s1 != UINT64_MAX && r1 != UINT64_MAX); - /* Unknown path -> -1, outputs untouched. */ - uint64_t sx = 99, rx = 99; - MQ_CHECK_EQ_INT(mq_conn_path_bytes(conn, 4242, &sx, &rx), -1); - MQ_CHECK(sx == 99 && rx == 99); - - /* Aggregate across both paths must show real movement in BOTH directions - * (we pushed ~512KB up and drained the echo back down). We do NOT pin the - * split ratio — minRTT may park the bulk on either path on equal-RTT - * loopback (see HONEST SCOPE above). Task 19 shapes the paths to force a - * deterministic split. */ - MQ_CHECK((s0 + s1) > 0); - MQ_CHECK((r0 + r1) > 0); - /* The bulk stream's payload dwarfs framing, so at least one path moved a - * meaningful chunk of the transfer in each direction. */ - MQ_CHECK((s0 + s1) >= (uint64_t)(TOTAL / 2)); - MQ_CHECK((r0 + r1) >= (uint64_t)(recv_total / 2)); - - MQ_LOGI("test_per_path_stats: path0 sent=%llu recv=%llu | path%d sent=%llu recv=%llu", - (unsigned long long)s0, (unsigned long long)r0, pid, (unsigned long long)s1, - (unsigned long long)r1); - if (s1 > 0 || r1 > 0) { - MQ_LOGI("test_per_path_stats: traffic SPLIT across both paths"); - } else { - MQ_LOGI("test_per_path_stats: second path idle (minRTT kept traffic on " - "primary on equal-RTT loopback; Task 19 shapes paths to split)"); - } - - free(chunk); - free(sink); - close(c); - echo_origin_down(&origin); - fixture_down(&f); -} - -static void -test_two_paths(void) -{ - test_window_constants(); - test_second_path_comes_up(); - test_per_path_stats(); -} - -MQ_TEST_MAIN(test_two_paths()) diff --git a/tests/integration/test_udp_relay.c b/tests/integration/test_udp_relay.c deleted file mode 100644 index 96165bec..00000000 --- a/tests/integration/test_udp_relay.c +++ /dev/null @@ -1,1392 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* test_udp_relay.c — UDP relay full-path integration (Task 5.3 + 6.2). - * - * Stands up a REAL mq_client + REAL mq_server over loopback in-process on a - * shared libevent base (same harness shape as test_client_server.c), plus a - * local UDP echo socket bound inside the test. - * - * Cases 1/2/a/b drive the CLIENT-ROLE boundary (mq_client_udp_open_fn / - * send / close — Task 6.2): the client owns the conn's datagram callback and - * reassembles server→client datagrams into on_rx. Cases 3/6/7/8/9 assemble the - * 0x02 (UDP_SESSION) stream + DATAGRAM frames DIRECTLY on the conn using the - * Task 1/4 codecs (raw-conn server-stimulus scaffolding), keeping precise - * control over send ordering / malformed inputs for server-behaviour coverage. - * - * Cases (design §2/§6/§9.2): - * 1. client-role open → send → byte-exact echo (optimistic send, post-auth). - * 2. client-role open + send SAME turn → server pre-OPEN buffer delivers echo - * (xquic datagram-before-stream ordering regression — design §2). - * a. client-role open + send BEFORE auth → queued; flushed post-auth (startup - * race regression). - * b. client-role 3000B frag relay → byte-exact echo + frags_reassembled > 0. - * c. client-role on_err path — open to nonexistent.invalid (DOMAIN atype) → - * on_err fired once with MQ_UDP_DNS_FAILED; handle dead (no send/close). - * 3. OPEN to a non-existent host (.invalid) → RESP MQ_UDP_DNS_FAILED + close. - * 4. enabled=0 (--no-udp) → RESP MQ_UDP_POLICY_DENIED. - * 5. idle_timeout_ms=100, left idle → server closes the 0x02 stream. - * 6. 17 unknown-sid datagrams → pre-OPEN ring evicts the oldest (counter ≥ 1). - * 7. datagram BEFORE auth → dropped (drops_preauth), and NOT later flushed - * when the same sid OPENs after auth (pre-auth bytes never leak through). - * 8. duplicate-SID OPEN: a second OPEN for a live sid resets the new stream; - * the original session keeps echoing. - * 9. enabled=0 + datagram → dropped (drops_preauth), pre-OPEN buffer stays - * empty (no eviction churn while every OPEN is denied). - * z. (codex gap) zero-length UDP datagram relay: a target→tunnel recv()==0 is - * a valid EMPTY datagram, not EOF — it must be relayed (on_rx len==0), not - * dropped. Pins the mq_udp_session.c recv n==0 fix. - * y. (codex gap) short client-requested idle timeout honored ACROSS activity: - * client requests 150ms idle while server default is 60s; after two activity - * bursts the session must still expire ~150ms after the last one (not at the - * 60s default). Pins the srv_idle_rearm per-session-value fix. - * 10. (d2) pre-OPEN flush double-free: buffer datagrams for TWO distinct unknown - * sids, OPEN only one → srv_preopen_flush must MOVE (not shallow-copy) the - * surviving sid's entry; teardown runs srv_preopen_free_all over the - * compacted ring. ASan-clean = no double-free (regression pin). - * - * Counter access (cases 6/7/9): the server exposes the most-recent conn's UDP - * relay state via mq_server_last_udp_srv() (observability accessor) and the - * relay exposes a by-value counter snapshot via mq_udp_srv_counters(). Both are - * public, documented observability APIs (not test-only hacks) added alongside - * this test; with one client per server instance the "most recent" conn is the - * one under test. - */ -#include "mqtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "proxy/mq_client.h" -#include "proxy/mq_server.h" -#include "proxy/mq_udp_session.h" -#include "runtime/mq_runtime_libevent.h" -#include "transport/mq_conn.h" -#include "transport/mq_stream.h" -#include "transport/mq_transport.h" -#include "wire/mq_udp_msg.h" -#include "wire/mq_wire.h" - -#ifndef TEST_CERT_FILE -# define TEST_CERT_FILE "tests/certs/test.crt" -#endif -#ifndef TEST_KEY_FILE -# define TEST_KEY_FILE "tests/certs/test.key" -#endif - -static uint64_t -now_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -static uint16_t -reserve_udp_port(void) -{ - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) return 0; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - close(fd); - return 0; - } - socklen_t sl = sizeof(sa); - if (getsockname(fd, (struct sockaddr *)&sa, &sl) != 0) { - close(fd); - return 0; - } - uint16_t port = ntohs(sa.sin_port); - close(fd); - return port; -} - -static void -set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - if (fl >= 0) (void)fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -/* ── local UDP echo origin ────────────────────────────────────────────────── - * - * A connected-less UDP socket bound on 127.0.0.1:ephemeral. On EV_READ it - * recvfrom()s each datagram and sendto()s the identical bytes back to the - * sender. This is the target the server's relay dials. */ -typedef struct { - struct event_base *base; - int fd; - struct event *ev; - int rx_count; /* datagrams echoed (for sanity) */ -} udp_echo_t; - -static void -udp_echo_cb(evutil_socket_t fd, short what, void *arg) -{ - (void)what; - udp_echo_t *e = (udp_echo_t *)arg; - for (;;) { - unsigned char buf[65535]; - struct sockaddr_storage from; - socklen_t fl = sizeof(from); - ssize_t n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fl); - if (n < 0) break; /* EAGAIN */ - e->rx_count++; - (void)sendto(fd, buf, (size_t)n, 0, (struct sockaddr *)&from, fl); - } -} - -static int -udp_echo_up(udp_echo_t *e, struct event_base *base, uint16_t *out_port) -{ - memset(e, 0, sizeof(*e)); - e->base = base; - e->fd = socket(AF_INET, SOCK_DGRAM, 0); - if (e->fd < 0) return -1; - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = 0; - if (bind(e->fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) return -1; - socklen_t sl = sizeof(sa); - if (getsockname(e->fd, (struct sockaddr *)&sa, &sl) != 0) return -1; - *out_port = ntohs(sa.sin_port); - set_nonblock(e->fd); - e->ev = event_new(base, e->fd, EV_READ | EV_PERSIST, udp_echo_cb, e); - event_add(e->ev, NULL); - return 0; -} - -static void -udp_echo_down(udp_echo_t *e) -{ - if (e->ev) event_free(e->ev); - if (e->fd >= 0) close(e->fd); -} - -/* ── fixture: real client + real server on a shared base ─────────────────── */ -typedef struct { - struct event_base *base; - mq_transport_t *srv_t; - mq_transport_t *cli_t; - mq_runtime_t *srv_rt; - mq_runtime_t *cli_rt; - mq_server_t *server; - mq_client_t *client; -} fixture_t; - -static int g_auth_fired; -static int g_auth_ok; - -static void -on_auth(int ok, mq_auth_err_t err, void *user) -{ - (void)err; - (void)user; - g_auth_fired = 1; - g_auth_ok = ok; -} - -static int -fixture_up(fixture_t *f, uint64_t udp_idle_ms, int udp_enabled) -{ - memset(f, 0, sizeof(*f)); - g_auth_fired = 0; - g_auth_ok = 0; - - f->base = event_base_new(); - MQ_CHECK(f->base != NULL); - if (!f->base) return -1; - - uint16_t port = reserve_udp_port(); - MQ_CHECK(port != 0); - if (!port) return -1; - - f->srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(f->srv_t != NULL); - if (!f->srv_t) return -1; - f->srv_rt = mq_runtime_new(f->srv_t, f->base); - MQ_CHECK(f->srv_rt != NULL); - if (!f->srv_rt) return -1; - f->server = mq_server_new(f->srv_t, f->srv_rt, "secret", MQ_CC_BBR2, udp_idle_ms, - udp_enabled); - MQ_CHECK(f->server != NULL); - if (!f->server) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->srv_rt, "127.0.0.1", port), 0); - - f->cli_t = mq_transport_new(0); - MQ_CHECK(f->cli_t != NULL); - if (!f->cli_t) return -1; - f->cli_rt = mq_runtime_new(f->cli_t, f->base); - MQ_CHECK(f->cli_rt != NULL); - if (!f->cli_rt) return -1; - - MQ_CHECK_EQ_INT(mq_runtime_open_udp_path(f->cli_rt, "127.0.0.1", 0), 0); - - f->client = mq_client_new(f->cli_t, f->cli_rt, "127.0.0.1", port, "client-1", - "secret", MQ_CC_BBR2); - MQ_CHECK(f->client != NULL); - if (!f->client) return -1; - mq_client_set_on_auth(f->client, on_auth, NULL); - - MQ_CHECK_EQ_INT(mq_client_start(f->client), 0); - return 0; -} - -static void -fixture_down(fixture_t *f) -{ - if (f->cli_t) mq_transport_free(f->cli_t); - if (f->srv_t) mq_transport_free(f->srv_t); - if (f->cli_rt) mq_runtime_free(f->cli_rt); - if (f->srv_rt) mq_runtime_free(f->srv_rt); - if (f->client) mq_client_free(f->client); - if (f->server) mq_server_free(f->server); - if (f->base) event_base_free(f->base); -} - -static void -pump_until(struct event_base *base, int *flag, uint64_t budget_ms) -{ - uint64_t deadline = now_ms() + budget_ms; - while ((!flag || !*flag) && now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -static void -pump_for(struct event_base *base, uint64_t ms) -{ - uint64_t deadline = now_ms() + ms; - while (now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - } -} - -/* ── 0x02 UDP_SESSION stream + RESP capture ──────────────────────────────── */ -typedef struct { - unsigned char rx[1024]; - size_t rxlen; - int resp_seen; - mq_udp_session_resp_t resp; - int closed; /* close-notify fired (full close: RESET, or both-side FIN) */ - int fin_seen; /* the server FIN'd its send side (read direction closed) */ -} udpstream_t; - -static void -us_readable(mq_stream_t *s, void *user) -{ - udpstream_t *d = (udpstream_t *)user; - for (;;) { - if (d->rxlen >= sizeof(d->rx)) break; - int fin = 0; - long n = mq_stream_recv(s, d->rx + d->rxlen, sizeof(d->rx) - d->rxlen, &fin); - if (n < 0) break; - if (n > 0) d->rxlen += (size_t)n; - if (fin) d->fin_seen = 1; - if (!d->resp_seen) { - int c = mq_decode_udp_session_resp(d->rx, d->rxlen, &d->resp); - if (c > 0) d->resp_seen = 1; - } - if (n == 0) break; - } -} - -static void -us_closed(mq_stream_t *s, void *user) -{ - (void)s; - udpstream_t *d = (udpstream_t *)user; - d->closed = 1; -} - -/* Open a 0x02 stream and send [discriminator varint][UDP_SESSION_OPEN] in ONE - * mq_stream_send — mirrors how mq_client coalesces the discriminator + body. - * IPv4 127.0.0.1:port unless host/host_len override it (for the .invalid case). */ -static mq_stream_t * -open_udp_session(mq_conn_t *conn, uint32_t sid, mq_addr_type_t atype, const uint8_t *host, - size_t host_len, uint16_t port, uint64_t idle_ms, udpstream_t *d) -{ - mq_stream_t *s = mq_conn_open_stream(conn); - if (!s) return NULL; - mq_stream_set_cbs(s, us_readable, NULL, us_closed, d); - - mq_udp_session_open_t open; - memset(&open, 0, sizeof(open)); - open.session_id = sid; - open.flags = 0; - open.address_type = atype; - memcpy(open.host, host, host_len); - open.host_len = host_len; - open.port = port; - open.idle_timeout_ms = idle_ms; - - uint8_t buf[512]; - buf[0] = (uint8_t)MQ_STREAM_TYPE_UDP_SESSION; - int n = mq_encode_udp_session_open(buf + 1, sizeof(buf) - 1, &open); - if (n < 0) return NULL; - (void)mq_stream_send(s, buf, (size_t)(1 + n), 0); - return s; -} - -static mq_stream_t * -open_udp_v4(mq_conn_t *conn, uint32_t sid, uint16_t port, uint64_t idle_ms, - udpstream_t *d) -{ - uint8_t host[4]; - uint32_t v4 = htonl(INADDR_LOOPBACK); - memcpy(host, &v4, 4); - return open_udp_session(conn, sid, MQ_ADDR_IPV4, host, 4, port, idle_ms, d); -} - -/* ── tunnel datagram assembly (single-fragment) ──────────────────────────── */ -/* Build one unfragmented UDP_MSG datagram (9-byte hdr + payload) into out and - * send it on the conn's DATAGRAM channel. */ -static int -send_tunnel_datagram(mq_conn_t *conn, uint32_t sid, uint16_t pkt_id, - const uint8_t *payload, size_t plen) -{ - uint8_t out[9 + 2048]; - if (plen > sizeof(out) - MQ_UDP_MSG_HDR) return -1; - mq_udp_msg_hdr_t h; - h.session_id = sid; - h.packet_id = pkt_id; - h.flags = 0; - h.frag_id = 0; - h.frag_count = 1; - mq_udp_msg_encode_hdr(out, &h); - memcpy(out + MQ_UDP_MSG_HDR, payload, plen); - return mq_conn_datagram_send(conn, out, MQ_UDP_MSG_HDR + plen); -} - -/* ── datagram receive capture (server→tunnel echo lands here) ────────────── - * - * Sized to 4096 so the 3000B frag-relay case (b) captures byte-exact. Used by - * the RAW-conn cases (6/7/8/9) that decode datagrams directly; the client-role - * cases use the cli_rx_t capture below (on_rx delivers reassembled payload). */ -static uint8_t g_dgm_rx[4096]; -static size_t g_dgm_rxlen; -static int g_dgm_done; -static uint32_t g_dgm_want_sid; - -static void -on_client_datagram(mq_conn_t *c, const uint8_t *data, size_t len, void *user) -{ - (void)c; - (void)user; - if (g_dgm_done || len < MQ_UDP_MSG_HDR) return; - mq_udp_msg_hdr_t h; - if (mq_udp_msg_decode_hdr(data, len, &h) != 0) return; - if (h.session_id != g_dgm_want_sid) return; - size_t plen = len - MQ_UDP_MSG_HDR; - if (plen > sizeof(g_dgm_rx)) plen = sizeof(g_dgm_rx); - memcpy(g_dgm_rx, data + MQ_UDP_MSG_HDR, plen); - g_dgm_rxlen = plen; - g_dgm_done = 1; -} - -static void -reset_dgm_capture(uint32_t sid) -{ - g_dgm_done = 0; - g_dgm_rxlen = 0; - g_dgm_want_sid = sid; - memset(g_dgm_rx, 0, sizeof(g_dgm_rx)); -} - -/* Authenticate the fixture's client + wire the client's datagram callback. - * - * NOTE: the RAW-conn cases (6/7/8/9) install on_client_datagram on the conn to - * decode datagrams directly. The client-role cases (1/2/a/b) must NOT do this: - * the mq_client already owns the conn's datagram callback (routing to its - * mq_udp_cli dispatch), so they leave it in place and capture via cli_rx_t. */ -static mq_conn_t * -fixture_authed_conn(fixture_t *f) -{ - pump_until(f->base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - mq_conn_t *conn = mq_client_conn(f->client); - MQ_CHECK(conn != NULL); - if (conn) mq_conn_set_on_datagram(conn, on_client_datagram, NULL); - return conn; -} - -/* ── client-role session capture (on_rx / on_err via the mq_udp_open_fn) ──── - * - * The boundary delivers reassembled UDP payloads through on_rx and terminal - * failures through on_err. cli_rx_t captures the first payload (byte-exact, up - * to 4096B for the frag case) and any on_err code for assertion. */ -typedef struct { - uint8_t rx[4096]; - size_t rxlen; - int rx_done; - int err_fired; - mq_udp_err_t err; -} cli_rx_t; - -static void -cli_on_rx(const uint8_t *payload, size_t len, void *user) -{ - cli_rx_t *c = (cli_rx_t *)user; - if (c->rx_done) return; - size_t n = len > sizeof(c->rx) ? sizeof(c->rx) : len; - memcpy(c->rx, payload, n); - c->rxlen = n; - c->rx_done = 1; -} - -static void -cli_on_err(void *session, mq_udp_err_t err, void *user) -{ - (void)session; - cli_rx_t *c = (cli_rx_t *)user; - c->err_fired = 1; - c->err = err; -} - -/* Open a UDP relay session through the boundary (mq_client_udp_open_fn) using - * a domain atype. Returns the opaque session handle (or NULL). */ -static void * -cli_open_domain(mq_client_t *client, const char *hostname, uint16_t port, cli_rx_t *cap) -{ - mq_udp_open_fn open_fn = mq_client_udp_open_fn(); - void *core = mq_client_udp_open_core(client); - return open_fn(core, (const uint8_t *)hostname, strlen(hostname), MQ_ADDR_DOMAIN, - port, cli_on_rx, cli_on_err, cap); -} - -/* Open a UDP relay session through the boundary (mq_client_udp_open_fn) to the - * loopback echo on `port`. Returns the opaque session handle (or NULL). */ -static void * -cli_open_v4(mq_client_t *client, uint16_t port, cli_rx_t *cap) -{ - uint8_t host[4]; - uint32_t v4 = htonl(INADDR_LOOPBACK); - memcpy(host, &v4, 4); - mq_udp_open_fn open_fn = mq_client_udp_open_fn(); - void *core = mq_client_udp_open_core(client); - return open_fn(core, host, 4, MQ_ADDR_IPV4, port, cli_on_rx, cli_on_err, cap); -} - -/* ── default-server scenarios (enabled=1, idle=60s): cases 1,2,3,6,7,8 ───── */ - -/* Case 1: client-role open → send → byte-exact echo (via the mq_udp_open_fn - * boundary). Exercises optimistic send (open then send, post-auth) end-to-end. */ -static void -test_case1_open_resp_echo(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { - fixture_down(&f); - return; - } - udp_echo_t echo; - uint16_t echo_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&echo, f.base, &echo_port), 0); - - /* Wait for auth (so the open issues the stream immediately); the mq_client - * owns the conn datagram callback (routing to its cli dispatch). */ - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(mq_client_udp_available(f.client), 1); - - cli_rx_t cap; - memset(&cap, 0, sizeof(cap)); - void *sess = cli_open_v4(f.client, echo_port, &cap); - MQ_CHECK(sess != NULL); - - /* Optimistic send: payload goes out without waiting for RESP. */ - uint8_t payload[512]; - for (int i = 0; i < 512; i++) - payload[i] = (uint8_t)((i * 37 + 5) & 0xff); - mq_client_udp_send_fn()(sess, payload, sizeof(payload)); - - pump_until(f.base, &cap.rx_done, 4000); - MQ_CHECK(cap.rx_done); - MQ_CHECK_EQ_INT((int)cap.rxlen, 512); - if (cap.rx_done) MQ_CHECK_MEM(cap.rx, payload, 512); - MQ_CHECK_EQ_INT(cap.err_fired, 0); - - mq_client_udp_close_fn()(sess); - udp_echo_down(&echo); - fixture_down(&f); -} - -/* Case 2: client-role open + send in the SAME turn (post-auth) → optimistic - * send + server pre-OPEN buffer deliver the echo byte-exact. */ -static void -test_case2_preopen_sameflight(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { - fixture_down(&f); - return; - } - udp_echo_t echo; - uint16_t echo_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&echo, f.base, &echo_port), 0); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - - cli_rx_t cap; - memset(&cap, 0, sizeof(cap)); - - uint8_t payload[300]; - for (int i = 0; i < 300; i++) - payload[i] = (uint8_t)((i * 91 + 13) & 0xff); - - /* SAME turn: open issues the 0x02 stream + OPEN AND we send the datagram, - * before any pump. The datagram beats the stream at the server (xquic - * ordering, design §2), so the server's pre-OPEN buffer must flush it. */ - void *sess = cli_open_v4(f.client, echo_port, &cap); - MQ_CHECK(sess != NULL); - mq_client_udp_send_fn()(sess, payload, sizeof(payload)); - - pump_until(f.base, &cap.rx_done, 5000); - MQ_CHECK(cap.rx_done); - MQ_CHECK_EQ_INT((int)cap.rxlen, 300); - if (cap.rx_done) MQ_CHECK_MEM(cap.rx, payload, 300); - - mq_client_udp_close_fn()(sess); - udp_echo_down(&echo); - fixture_down(&f); -} - -/* Case (a): client-role open + send BEFORE auth completes (startup race - * regression). The open is issued immediately after mq_client_start (auth not - * yet settled); the send is queued in the per-session pre-auth queue; after - * auth the stream issues, the queue flushes, and the echo returns. */ -static void -test_caseA_preauth_open_send(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { - fixture_down(&f); - return; - } - udp_echo_t echo; - uint16_t echo_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&echo, f.base, &echo_port), 0); - - /* No auth pump: open + send right after fixture_up (auth pending). */ - MQ_CHECK_EQ_INT(mq_client_is_authed(f.client), 0); - MQ_CHECK_EQ_INT(mq_client_udp_available(f.client), -1); /* undetermined */ - - cli_rx_t cap; - memset(&cap, 0, sizeof(cap)); - void *sess = cli_open_v4(f.client, echo_port, &cap); - MQ_CHECK(sess != NULL); /* held pending-auth, stream deferred */ - - uint8_t payload[200]; - for (int i = 0; i < 200; i++) - payload[i] = (uint8_t)((i * 53 + 17) & 0xff); - mq_client_udp_send_fn()(sess, payload, sizeof(payload)); /* queued pre-auth */ - - /* Now pump: auth settles → stream issues → queued send flushes → echo. */ - pump_until(f.base, &cap.rx_done, 5000); - MQ_CHECK(cap.rx_done); - MQ_CHECK_EQ_INT((int)cap.rxlen, 200); - if (cap.rx_done) MQ_CHECK_MEM(cap.rx, payload, 200); - MQ_CHECK_EQ_INT(cap.err_fired, 0); - - mq_client_udp_close_fn()(sess); - udp_echo_down(&echo); - fixture_down(&f); -} - -/* Case (b): in-process frag relay — a 3000B payload (> datagram mss) sent via - * the client role is split into frags, echoed by the origin, reassembled on the - * way back, and delivered byte-exact through on_rx. Asserts the server's - * frags_reassembled counter advanced (deterministic frag-path proof). */ -static void -test_caseB_frag_relay(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { - fixture_down(&f); - return; - } - udp_echo_t echo; - uint16_t echo_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&echo, f.base, &echo_port), 0); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - - cli_rx_t cap; - memset(&cap, 0, sizeof(cap)); - void *sess = cli_open_v4(f.client, echo_port, &cap); - MQ_CHECK(sess != NULL); - - /* 3000B forces a multi-frag split (datagram mss ~1200). */ - uint8_t payload[3000]; - for (int i = 0; i < 3000; i++) - payload[i] = (uint8_t)((i * 131 + 7) & 0xff); - mq_client_udp_send_fn()(sess, payload, sizeof(payload)); - - pump_until(f.base, &cap.rx_done, 6000); - MQ_CHECK(cap.rx_done); - MQ_CHECK_EQ_INT((int)cap.rxlen, 3000); - if (cap.rx_done) MQ_CHECK_MEM(cap.rx, payload, 3000); - MQ_CHECK_EQ_INT(cap.err_fired, 0); - - /* The server reassembled the multi-frag tunnel→target packet. */ - mq_udp_srv_t *u = mq_server_last_udp_srv(f.server); - MQ_CHECK(u != NULL); - if (u) { - mq_udp_srv_counters_t c = mq_udp_srv_counters(u); - MQ_CHECK(c.frags_reassembled > 0); - } - - mq_client_udp_close_fn()(sess); - udp_echo_down(&echo); - fixture_down(&f); -} - -/* Case 3: OPEN to a non-existent host (.invalid) → RESP DNS_FAILED + close. */ -static void -test_case3_dns_failed(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { - fixture_down(&f); - return; - } - mq_conn_t *conn = fixture_authed_conn(&f); - - const uint32_t SID = 0x33333333u; - udpstream_t d; - memset(&d, 0, sizeof(d)); - static const char host[] = "nonexistent.invalid"; - mq_stream_t *s = open_udp_session(conn, SID, MQ_ADDR_DOMAIN, (const uint8_t *)host, - strlen(host), 9, 0, &d); - MQ_CHECK(s != NULL); - - pump_until(f.base, &d.resp_seen, 5000); - MQ_CHECK(d.resp_seen); - MQ_CHECK_EQ_INT((int)d.resp.status, (int)MQ_STATUS_ERROR); - MQ_CHECK_EQ_INT((int)d.resp.error_code, (int)MQ_UDP_DNS_FAILED); - - /* The server FIN's the RESP (graceful, no RESET): the client observes the - * FIN on its read side. (A half-close: the client never closes its own send - * side, so xquic's full close-notify need not fire — the FIN is the close - * signal the protocol delivers, mirroring the TCP refused path.) */ - pump_until(f.base, &d.fin_seen, 2000); - MQ_CHECK(d.fin_seen); - - fixture_down(&f); -} - -/* Case 6: 17 unknown-sid datagrams → pre-OPEN ring evicts oldest (≥ 1). */ -static void -test_case6_preopen_evict(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { - fixture_down(&f); - return; - } - mq_conn_t *conn = fixture_authed_conn(&f); - - /* 17 distinct unknown sids, each a small (≤1KiB) payload so the 32KiB byte - * cap is not the trigger — the 16-entry count cap evicts the oldest. */ - uint8_t payload[256]; - memset(payload, 0xAB, sizeof(payload)); - for (uint32_t i = 0; i < 17; i++) { - MQ_CHECK_EQ_INT( - send_tunnel_datagram(conn, 0x9000u + i, 0, payload, sizeof(payload)), 0); - } - /* Pump so the datagrams land at the server (no OPEN ⇒ all buffered). */ - pump_for(f.base, 500); - - mq_udp_srv_t *u = mq_server_last_udp_srv(f.server); - MQ_CHECK(u != NULL); - mq_udp_srv_counters_t c = mq_udp_srv_counters(u); - MQ_CHECK(c.preopen_evictions >= 1); - /* All 17 were authed+enabled ⇒ none counted as pre-auth drops. */ - MQ_CHECK_EQ_INT((int)c.drops_preauth, 0); - - fixture_down(&f); -} - -/* Case 7 uses a RAW client conn (not mq_client) so the test controls send - * ordering precisely: on ESTABLISHED it emits the pre-auth datagram FIRST, THEN - * opens the control stream + sends AUTH_REQUEST. The datagram is therefore in an - * earlier flight than the AUTH_REQUEST and lands at the server while `authed`==0 - * → counted as drops_preauth and (critically) NEVER buffered. mq_client cannot - * be used here because it sends AUTH_REQUEST on the ESTABLISHED edge before - * forwarding to the user callback, so a datagram queued there always loses the - * race to auth on fast loopback. */ -#define CASE7_ALPN "mqproxy-tcp/1" - -typedef struct { - uint32_t sid; - const uint8_t *marker; - size_t marker_len; - int established; - int auth_sent; -} case7_ctx_t; - -static void -case7_send_auth(mq_conn_t *conn, case7_ctx_t *p) -{ - mq_stream_t *ctrl = mq_conn_open_stream(conn); - if (!ctrl) return; - mq_auth_req_t req; - memset(&req, 0, sizeof(req)); - req.version = 1; - snprintf(req.client_id, sizeof(req.client_id), "%s", "case7"); - snprintf(req.auth_token, sizeof(req.auth_token), "%s", "secret"); - req.features = 0; - uint8_t buf[512]; - int n = mq_encode_auth_req(buf, sizeof(buf), &req); - if (n < 0) return; - (void)mq_stream_send(ctrl, buf, (size_t)n, 0); - p->auth_sent = 1; -} - -static void -case7_on_state(mq_conn_t *conn, mq_conn_state_t st, void *user) -{ - case7_ctx_t *p = (case7_ctx_t *)user; - if (st != MQ_CONN_ESTABLISHED || p->established) return; - p->established = 1; - - /* 1) Pre-auth datagram FIRST — queued/flushed before any auth bytes. */ - (void)send_tunnel_datagram(conn, p->sid, 0, p->marker, p->marker_len); - - /* 2) THEN open the control stream + send AUTH_REQUEST. */ - case7_send_auth(conn, p); -} - -/* Case 7: datagram BEFORE auth → dropped (drops_preauth) and NOT flushed when - * the same sid OPENs after auth (pre-auth bytes must not leak through). */ -static void -test_case7_preauth_drop_noflush(void) -{ - struct event_base *base = event_base_new(); - MQ_CHECK(base != NULL); - if (!base) return; - - uint16_t srv_port = reserve_udp_port(); - MQ_CHECK(srv_port != 0); - - mq_transport_t *srv_t = mq_transport_new_server(TEST_CERT_FILE, TEST_KEY_FILE); - MQ_CHECK(srv_t != NULL); - mq_runtime_t *srv_rt = srv_t ? mq_runtime_new(srv_t, base) : NULL; - MQ_CHECK(srv_rt != NULL); - mq_server_t *server = - (srv_t && srv_rt) ? mq_server_new(srv_t, srv_rt, "secret", MQ_CC_BBR2, 60000, 1) - : NULL; - MQ_CHECK(server != NULL); - int srv_bound = srv_rt ? mq_runtime_open_udp_path(srv_rt, "127.0.0.1", srv_port) : -1; - MQ_CHECK_EQ_INT(srv_bound, 0); - - udp_echo_t echo; - uint16_t echo_port = 0; - if (base) MQ_CHECK_EQ_INT(udp_echo_up(&echo, base, &echo_port), 0); - - mq_transport_t *cli_t = mq_transport_new(0); - MQ_CHECK(cli_t != NULL); - mq_runtime_t *cli_rt = cli_t ? mq_runtime_new(cli_t, base) : NULL; - MQ_CHECK(cli_rt != NULL); - if (cli_t) - MQ_CHECK_EQ_INT(mq_conn_register_alpn(cli_t, CASE7_ALPN, NULL, NULL, NULL), 0); - int cli_bound = cli_rt ? mq_runtime_open_udp_path(cli_rt, "127.0.0.1", 0) : -1; - MQ_CHECK_EQ_INT(cli_bound, 0); - - const uint32_t SID = 0x77777777u; - const uint8_t preauth_marker[16] = {0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, - 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF}; - case7_ctx_t pctx; - memset(&pctx, 0, sizeof(pctx)); - pctx.sid = SID; - pctx.marker = preauth_marker; - pctx.marker_len = sizeof(preauth_marker); - - mq_conn_t *conn = NULL; - if (cli_t && cli_bound == 0) { - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sa.sin_port = htons(srv_port); - xqc_conn_settings_t settings; - memset(&settings, 0, sizeof(settings)); - settings.proto_version = XQC_VERSION_V1; - settings.pacing_on = 1; - settings.max_pkt_out_size = 1200; - settings.max_datagram_frame_size = 65535; - mq_conn_apply_mp_settings(&settings, /*is_server=*/0, MQ_CC_BBR2); - conn = mq_conn_connect(cli_t, (struct sockaddr *)&sa, sizeof(sa), CASE7_ALPN, - &settings, NULL); - MQ_CHECK(conn != NULL); - if (conn) { - mq_conn_set_on_datagram(conn, on_client_datagram, NULL); - mq_conn_set_on_state(conn, case7_on_state, &pctx); - } - } - - /* Pump until the pre-auth datagram has been dropped at the server. */ - mq_udp_srv_t *u = NULL; - { - uint64_t deadline = now_ms() + 4000; - while (now_ms() < deadline) { - event_base_loop(base, EVLOOP_NONBLOCK); - u = mq_server_last_udp_srv(server); - if (u && mq_udp_srv_counters(u).drops_preauth >= 1) break; - } - } - MQ_CHECK(u != NULL); - mq_udp_srv_counters_t c0 = mq_udp_srv_counters(u); - MQ_CHECK(c0.drops_preauth >= 1); /* pre-auth datagram dropped */ - MQ_CHECK_EQ_INT((int)c0.preopen_evictions, 0); /* never entered the buffer */ - - /* Let auth settle (AUTH_REQUEST already sent in case7_on_state). */ - pump_for(base, 300); - - /* OPEN the SAME sid now (post-auth), send a fresh distinct payload, and - * verify the echo is the fresh payload — the pre-auth marker must NOT have - * been buffered/flushed (it was dropped at the gate). */ - udpstream_t d; - memset(&d, 0, sizeof(d)); - if (conn) { - mq_stream_t *s = open_udp_v4(conn, SID, echo_port, 0, &d); - MQ_CHECK(s != NULL); - } - pump_until(base, &d.resp_seen, 4000); - MQ_CHECK(d.resp_seen); - MQ_CHECK_EQ_INT((int)d.resp.error_code, (int)MQ_UDP_OK); - - uint8_t fresh[64]; - for (int i = 0; i < 64; i++) - fresh[i] = (uint8_t)(0x40 + (i & 0x3f)); - reset_dgm_capture(SID); - if (conn) - MQ_CHECK_EQ_INT(send_tunnel_datagram(conn, SID, 1, fresh, sizeof(fresh)), 0); - pump_until(base, &g_dgm_done, 4000); - MQ_CHECK(g_dgm_done); - MQ_CHECK_EQ_INT((int)g_dgm_rxlen, 64); - if (g_dgm_done) { - MQ_CHECK_MEM(g_dgm_rx, fresh, 64); - /* The echoed bytes are the fresh payload, never the pre-auth marker. */ - MQ_CHECK(memcmp(g_dgm_rx, preauth_marker, sizeof(preauth_marker)) != 0); - } - - udp_echo_down(&echo); - if (cli_t) mq_transport_free(cli_t); - if (srv_t) mq_transport_free(srv_t); - if (cli_rt) mq_runtime_free(cli_rt); - if (srv_rt) mq_runtime_free(srv_rt); - if (server) mq_server_free(server); - if (base) event_base_free(base); -} - -/* Case 8: duplicate-SID OPEN resets the new stream; original keeps echoing. */ -static void -test_case8_dup_sid(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { - fixture_down(&f); - return; - } - udp_echo_t echo; - uint16_t echo_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&echo, f.base, &echo_port), 0); - - mq_conn_t *conn = fixture_authed_conn(&f); - - const uint32_t SID = 0x88888888u; - udpstream_t d1; - memset(&d1, 0, sizeof(d1)); - mq_stream_t *s1 = open_udp_v4(conn, SID, echo_port, 0, &d1); - MQ_CHECK(s1 != NULL); - pump_until(f.base, &d1.resp_seen, 4000); - MQ_CHECK(d1.resp_seen); - MQ_CHECK_EQ_INT((int)d1.resp.error_code, (int)MQ_UDP_OK); - - /* Second OPEN for the SAME live sid: the server resets the NEW stream with - * no RESP and leaves the original session intact. */ - udpstream_t d2; - memset(&d2, 0, sizeof(d2)); - mq_stream_t *s2 = open_udp_v4(conn, SID, echo_port, 0, &d2); - MQ_CHECK(s2 != NULL); - pump_until(f.base, &d2.closed, 4000); - MQ_CHECK(d2.closed); /* new stream was reset */ - MQ_CHECK_EQ_INT(d2.resp_seen, 0); /* no RESP decoded on the duplicate */ - - /* The original session still relays: round-trip a datagram on SID. */ - uint8_t payload[128]; - for (int i = 0; i < 128; i++) - payload[i] = (uint8_t)((i * 7 + 3) & 0xff); - reset_dgm_capture(SID); - MQ_CHECK_EQ_INT(send_tunnel_datagram(conn, SID, 0, payload, sizeof(payload)), 0); - pump_until(f.base, &g_dgm_done, 4000); - MQ_CHECK(g_dgm_done); - MQ_CHECK_EQ_INT((int)g_dgm_rxlen, 128); - if (g_dgm_done) MQ_CHECK_MEM(g_dgm_rx, payload, 128); - /* The original control stream stayed open (not closed by the duplicate). */ - MQ_CHECK_EQ_INT(d1.closed, 0); - - udp_echo_down(&echo); - fixture_down(&f); -} - -/* Case 10 (d2): pre-OPEN flush double-free regression. Buffer datagrams for TWO - * distinct unknown sids (A=0xAAA1, B=0xBBB2) BEFORE opening either, then OPEN - * ONLY A. srv_preopen_flush() drains A's entries and MOVES B's surviving entry - * into the compacted ring — it MUST clear the source slot's data pointer so B is - * not left double-owned. At teardown srv_preopen_free_all() walks all 16 physical - * slots; with the OLD bug (no source-slot clear) B's buffer is freed twice. The - * assertion is implicit: under ASan there must be NO double-free / NO leak over - * the compaction + free_all path. We also confirm A's buffered datagram was - * delivered by the flush (its echo round-trips). B (0xBBB2) is NEVER opened — its - * kept entry is freed only at teardown, which is the path under test. All - * payloads are < MQ_PREOPEN_MAX_BYTES and the open happens within the 250ms TTL - * (pumped non-blocking, no wall sleeps) so the entries survive to the flush. */ -static void -test_case10_preopen_flush_double_free(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { - fixture_down(&f); - return; - } - udp_echo_t echo; - uint16_t echo_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&echo, f.base, &echo_port), 0); - - mq_conn_t *conn = fixture_authed_conn(&f); - - const uint32_t SID_A = 0x0000AAA1u; /* opened → flushed */ - const uint32_t SID_B = 0x0000BBB2u; /* NEVER opened → kept, freed at teardown */ - - /* 1) Buffer a datagram for sid A WITHOUT opening it (lands in pre-OPEN buf). */ - uint8_t payload_a[96]; - for (int i = 0; i < (int)sizeof(payload_a); i++) - payload_a[i] = (uint8_t)((i * 11 + 3) & 0xff); - MQ_CHECK_EQ_INT(send_tunnel_datagram(conn, SID_A, 0, payload_a, sizeof(payload_a)), - 0); - /* Pump briefly (non-blocking, well under the 250ms TTL) so it's buffered. */ - pump_for(f.base, 30); - - /* 2) Buffer a datagram for the SECOND distinct sid B (also unopened). */ - uint8_t payload_b[112]; - for (int i = 0; i < (int)sizeof(payload_b); i++) - payload_b[i] = (uint8_t)((i * 29 + 7) & 0xff); - MQ_CHECK_EQ_INT(send_tunnel_datagram(conn, SID_B, 0, payload_b, sizeof(payload_b)), - 0); - pump_for(f.base, 30); - - /* 3) OPEN ONLY sid A. The RESP-OK path runs srv_preopen_flush(A), which drains - * A's buffered datagram and MUST move B's entry while clearing the source - * slot. We capture A's flushed echo on the datagram channel. */ - udpstream_t d; - memset(&d, 0, sizeof(d)); - reset_dgm_capture(SID_A); - mq_stream_t *s = open_udp_v4(conn, SID_A, echo_port, 0, &d); - MQ_CHECK(s != NULL); - pump_until(f.base, &d.resp_seen, 4000); - MQ_CHECK(d.resp_seen); - MQ_CHECK_EQ_INT((int)d.resp.error_code, (int)MQ_UDP_OK); - - /* The flush delivered A's buffered datagram → origin echo round-trips. */ - pump_until(f.base, &g_dgm_done, 4000); - MQ_CHECK(g_dgm_done); - MQ_CHECK_EQ_INT((int)g_dgm_rxlen, (int)sizeof(payload_a)); - if (g_dgm_done) MQ_CHECK_MEM(g_dgm_rx, payload_a, sizeof(payload_a)); - - /* 4) Tear down WITHOUT ever opening sid B. fixture_down → mq_server_free → - * mq_udp_srv_free → srv_preopen_free_all walks the compacted ring containing - * B's kept entry. With the fix this frees B exactly once; the OLD bug - * double-frees → ASan abort. */ - udp_echo_down(&echo); - fixture_down(&f); -} - -/* Case (z): zero-length UDP datagram relay (codex gap). Pins the fix that a - * target→tunnel recv() returning 0 is a legitimate EMPTY datagram, not EOF: it - * must be relayed, not dropped. We open a client-role session to the echo origin - * and send a ZERO-LENGTH payload through the relay; the echo reflects 0 bytes, - * the server's recv()==0 path relays it, and on_rx must fire with len==0 - * (byte-exact empty round-trip). */ -static void -test_casez_zero_len_datagram(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { - fixture_down(&f); - return; - } - udp_echo_t echo; - uint16_t echo_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&echo, f.base, &echo_port), 0); - - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(mq_client_udp_available(f.client), 1); - - cli_rx_t cap; - memset(&cap, 0, sizeof(cap)); - void *sess = cli_open_v4(f.client, echo_port, &cap); - MQ_CHECK(sess != NULL); - - /* Zero-length datagram: tunnel→target sends 0 bytes to the echo, which - * reflects 0 bytes back; the server's target→tunnel recv() returns 0 and the - * fixed code relays it as an empty datagram. */ - uint8_t dummy = 0; - mq_client_udp_send_fn()(sess, &dummy, 0); - - pump_until(f.base, &cap.rx_done, 5000); - MQ_CHECK(cap.rx_done); /* an empty datagram was delivered */ - MQ_CHECK_EQ_INT((int)cap.rxlen, 0); /* byte-exact: 0 bytes round-tripped */ - MQ_CHECK_EQ_INT(cap.err_fired, 0); - /* The echo origin actually saw + reflected the empty datagram. */ - MQ_CHECK(echo.rx_count >= 1); - - mq_client_udp_close_fn()(sess); - udp_echo_down(&echo); - fixture_down(&f); -} - -/* Case (y): short CLIENT-requested idle timeout honored ACROSS activity (codex - * gap). Pins the srv_idle_rearm fix: the per-session effective idle timeout - * (= min(client requested, server default)) must be used on EVERY re-arm, not - * just the first. The client requests 150ms while the server default is 60000ms; - * after two bursts of activity (each re-arming) and then 250ms of silence the - * server must close the session — proving the re-arm used 150ms, not 60s. */ -static void -test_casey_short_idle_across_activity(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { /* server default idle = 60s */ - fixture_down(&f); - return; - } - udp_echo_t echo; - uint16_t echo_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&echo, f.base, &echo_port), 0); - - mq_conn_t *conn = fixture_authed_conn(&f); - - const uint32_t SID = 0x59595959u; - udpstream_t d; - memset(&d, 0, sizeof(d)); - /* Client requests a SHORT 150ms idle timeout (hand-assembled OPEN). */ - mq_stream_t *s = open_udp_v4(conn, SID, echo_port, 150, &d); - MQ_CHECK(s != NULL); - pump_until(f.base, &d.resp_seen, 4000); - MQ_CHECK(d.resp_seen); - MQ_CHECK_EQ_INT((int)d.resp.error_code, (int)MQ_UDP_OK); - /* RESP echoes the effective idle = min(150, 60000) = 150. */ - MQ_CHECK_EQ_INT((int)d.resp.idle_timeout_ms, 150); - - /* Activity burst #1: one datagram → defrag-complete send re-arms the timer. */ - uint8_t p1[32]; - for (int i = 0; i < 32; i++) - p1[i] = (uint8_t)(i + 1); - reset_dgm_capture(SID); - MQ_CHECK_EQ_INT(send_tunnel_datagram(conn, SID, 0, p1, sizeof(p1)), 0); - pump_until(f.base, &g_dgm_done, 2000); - MQ_CHECK(g_dgm_done); - - /* Stay alive across ~80ms (< 150ms) — the session must NOT have expired. */ - pump_for(f.base, 80); - MQ_CHECK_EQ_INT(d.closed, 0); - - /* Activity burst #2: another datagram re-arms the (per-session 150ms) timer. - * With the OLD bug the re-arm would use the 60s server default here. */ - uint8_t p2[32]; - for (int i = 0; i < 32; i++) - p2[i] = (uint8_t)(100 + i); - reset_dgm_capture(SID); - MQ_CHECK_EQ_INT(send_tunnel_datagram(conn, SID, 1, p2, sizeof(p2)), 0); - pump_until(f.base, &g_dgm_done, 2000); - MQ_CHECK(g_dgm_done); - MQ_CHECK_EQ_INT(d.closed, 0); /* still alive immediately after activity */ - - /* Now go silent: the per-session idle timer must fire ~150ms after the last - * activity and the server closes the 0x02 stream; the close-notify then - * propagates to the client. Measured close latency is ~260ms (150ms idle + - * RESET round-trip + loop granularity), so a 400ms window catches it with - * margin while staying ORDERS of magnitude below the 60s server default — if - * the re-arm had used 60s, no close would arrive in this window. */ - pump_until(f.base, &d.closed, 400); - MQ_CHECK(d.closed); - - udp_echo_down(&echo); - fixture_down(&f); -} - -/* Case 5: idle_timeout_ms=100 server, left idle → server closes the stream. */ -static void -test_case5_idle_timeout(void) -{ - fixture_t f; - if (fixture_up(&f, 100, 1) != 0) { - fixture_down(&f); - return; - } - udp_echo_t echo; - uint16_t echo_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&echo, f.base, &echo_port), 0); - - mq_conn_t *conn = fixture_authed_conn(&f); - - const uint32_t SID = 0x55555555u; - udpstream_t d; - memset(&d, 0, sizeof(d)); - /* Request idle 0 ⇒ server applies its own 100ms. */ - mq_stream_t *s = open_udp_v4(conn, SID, echo_port, 0, &d); - MQ_CHECK(s != NULL); - pump_until(f.base, &d.resp_seen, 4000); - MQ_CHECK(d.resp_seen); - MQ_CHECK_EQ_INT((int)d.resp.error_code, (int)MQ_UDP_OK); - - /* Sit idle: no datagrams. The server's 100ms idle timer must fire and tear - * the session down, closing the 0x02 stream. Pump ~600ms. */ - pump_until(f.base, &d.closed, 600); - MQ_CHECK(d.closed); - - udp_echo_down(&echo); - fixture_down(&f); -} - -/* ── disabled-server scenarios (enabled=0): cases 4, 9 ───────────────────── */ - -/* Case 4: enabled=0 → OPEN answered MQ_UDP_POLICY_DENIED. */ -static void -test_case4_policy_denied(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 0) != 0) { - fixture_down(&f); - return; - } - /* echo port is irrelevant (OPEN is denied before any dial). */ - mq_conn_t *conn = fixture_authed_conn(&f); - - const uint32_t SID = 0x44444444u; - udpstream_t d; - memset(&d, 0, sizeof(d)); - mq_stream_t *s = open_udp_v4(conn, SID, 9, 0, &d); - MQ_CHECK(s != NULL); - pump_until(f.base, &d.resp_seen, 4000); - MQ_CHECK(d.resp_seen); - MQ_CHECK_EQ_INT((int)d.resp.status, (int)MQ_STATUS_ERROR); - MQ_CHECK_EQ_INT((int)d.resp.error_code, (int)MQ_UDP_POLICY_DENIED); - - fixture_down(&f); -} - -/* Case 9: enabled=0 + datagram → dropped (drops_preauth), no pre-OPEN buffering - * (the buffer must not grow while every OPEN is denied). */ -static void -test_case9_disabled_datagram_drop(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 0) != 0) { - fixture_down(&f); - return; - } - mq_conn_t *conn = fixture_authed_conn(&f); - - uint8_t payload[256]; - memset(payload, 0xCD, sizeof(payload)); - for (uint32_t i = 0; i < 5; i++) { - MQ_CHECK_EQ_INT( - send_tunnel_datagram(conn, 0xA000u + i, 0, payload, sizeof(payload)), 0); - } - pump_for(f.base, 400); - - mq_udp_srv_t *u = mq_server_last_udp_srv(f.server); - MQ_CHECK(u != NULL); - mq_udp_srv_counters_t c = mq_udp_srv_counters(u); - /* On a disabled conn every datagram is dropped at the auth/enabled gate and - * never reaches the pre-OPEN buffer. */ - MQ_CHECK(c.drops_preauth >= 5); - MQ_CHECK_EQ_INT((int)c.preopen_evictions, 0); - - fixture_down(&f); -} - -/* Case (c): client-role on_err path — open to a non-resolvable domain via the - * mq_udp_open_fn boundary, pump, assert on_err fired exactly once with - * err == MQ_UDP_DNS_FAILED, and that the handle is dead afterwards (do NOT call - * send/close — per contract the core already freed it when on_err fired). */ -static void -test_casec_client_on_err_dns_failed(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { - fixture_down(&f); - return; - } - - /* Wait for auth: the client must be authed so the 0x02 stream is issued - * immediately and the server sees the OPEN → DNS failure → RESP(ERROR). */ - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(mq_client_udp_available(f.client), 1); - - cli_rx_t cap; - memset(&cap, 0, sizeof(cap)); - - /* Open a session to an unresolvable domain (atype = DOMAIN). Port 9 is the - * discard port — any port is fine since DNS fails before any socket dial. */ - void *sess = cli_open_domain(f.client, "nonexistent.invalid", 9, &cap); - MQ_CHECK(sess != NULL); /* open itself succeeds locally; failure is async */ - - /* Pump until on_err fires or the budget expires. */ - pump_until(f.base, &cap.err_fired, 5000); - - /* on_err must fire exactly once with MQ_UDP_DNS_FAILED. */ - MQ_CHECK(cap.err_fired); - MQ_CHECK_EQ_INT((int)cap.err, (int)MQ_UDP_DNS_FAILED); - - /* on_rx must NOT have fired (no payload ever delivered on an error path). */ - MQ_CHECK_EQ_INT(cap.rx_done, 0); - - /* Per contract: the core freed the session inside on_err. Do NOT call - * send/close here — sess is a dangling pointer at this point. */ - - fixture_down(&f); -} - -/* Case (r): reconnect rebind regression (Phase 5b). - * - * Verifies that mq_udp_cli_reset + mq_udp_cli_set_conn correctly clear the - * one-shot latches and re-wire the conn, allowing a second auth/open cycle to - * run after an in-process simulated reconnect. - * - * Steps: - * 1. Establish a client-role UDP session + byte-exact echo (same as case 1). - * 2. Close the session caller-side so the table is empty. - * 3. Simulate a reconnect at the mq_udp_cli level: - * mq_udp_cli_on_conn_close(u) -- sets settled=1, conn=NULL - * mq_udp_cli_reset(u) -- clears settled/authed/available/mss - * mq_udp_cli_set_conn(u, conn) -- re-wires the (still-live) conn - * mq_udp_cli_on_auth(u, 1, 1) -- fresh auth cycle runs (settled was 0) - * 4. Open a NEW session on the same `u` and assert a datagram round-trip is - * byte-exact — proving the latches were cleared (on_auth fired again) and - * the new conn is correctly wired. */ -static void -test_caser_rebind_reconnect(void) -{ - fixture_t f; - if (fixture_up(&f, 60000, 1) != 0) { - fixture_down(&f); - return; - } - udp_echo_t echo; - uint16_t echo_port = 0; - MQ_CHECK_EQ_INT(udp_echo_up(&echo, f.base, &echo_port), 0); - - /* Wait for auth (establishes the authed+available state). */ - pump_until(f.base, &g_auth_fired, 4000); - MQ_CHECK(g_auth_ok); - MQ_CHECK_EQ_INT(mq_client_udp_available(f.client), 1); - - /* Step 1: open a session + do a byte-exact echo round-trip. */ - cli_rx_t cap1; - memset(&cap1, 0, sizeof(cap1)); - void *sess1 = cli_open_v4(f.client, echo_port, &cap1); - MQ_CHECK(sess1 != NULL); - - uint8_t payload1[128]; - for (int i = 0; i < 128; i++) - payload1[i] = (uint8_t)((i * 43 + 11) & 0xff); - mq_client_udp_send_fn()(sess1, payload1, sizeof(payload1)); - - pump_until(f.base, &cap1.rx_done, 4000); - MQ_CHECK(cap1.rx_done); - MQ_CHECK_EQ_INT((int)cap1.rxlen, 128); - if (cap1.rx_done) MQ_CHECK_MEM(cap1.rx, payload1, 128); - MQ_CHECK_EQ_INT(cap1.err_fired, 0); - - /* Step 2: caller-close the session so the table is empty before rebind. */ - mq_client_udp_close_fn()(sess1); - - /* Step 3: simulate reconnect at the mq_udp_cli level. - * Grab the cli + conn pointers before on_conn_close nulls the conn. */ - mq_udp_cli_t *u = (mq_udp_cli_t *)mq_client_udp_open_core(f.client); - MQ_CHECK(u != NULL); - mq_conn_t *conn = mq_client_conn(f.client); - MQ_CHECK(conn != NULL); - - mq_udp_cli_on_conn_close(u); /* settled=1, conn=NULL, table already empty */ - mq_udp_cli_reset(u); /* settled=0, authed=0, available=0, mss=0 */ - mq_udp_cli_set_conn(u, conn); /* re-wire the still-live conn */ - mq_udp_cli_on_auth(u, 1, 1); /* fresh auth cycle: settled→1, authed→1, available→1 */ - - /* Step 4: open a NEW session on the rebind'd cli and assert byte-exact echo. */ - cli_rx_t cap2; - memset(&cap2, 0, sizeof(cap2)); - /* Use the boundary function pointer directly (same as cli_open_v4 does). */ - { - uint8_t host[4]; - uint32_t v4 = htonl(INADDR_LOOPBACK); - memcpy(host, &v4, 4); - mq_udp_open_fn open_fn = mq_client_udp_open_fn(); - void *sess2 = - open_fn(u, host, 4, MQ_ADDR_IPV4, echo_port, cli_on_rx, cli_on_err, &cap2); - MQ_CHECK(sess2 != NULL); - - uint8_t payload2[256]; - for (int i = 0; i < 256; i++) - payload2[i] = (uint8_t)((i * 71 + 23) & 0xff); - mq_client_udp_send_fn()(sess2, payload2, sizeof(payload2)); - - pump_until(f.base, &cap2.rx_done, 4000); - MQ_CHECK(cap2.rx_done); - MQ_CHECK_EQ_INT((int)cap2.rxlen, 256); - if (cap2.rx_done) MQ_CHECK_MEM(cap2.rx, payload2, 256); - MQ_CHECK_EQ_INT(cap2.err_fired, 0); - - mq_client_udp_close_fn()(sess2); - } - - udp_echo_down(&echo); - fixture_down(&f); -} - -static void -test_udp_relay(void) -{ - test_case1_open_resp_echo(); - test_case2_preopen_sameflight(); - test_caseA_preauth_open_send(); - test_caseB_frag_relay(); - test_case3_dns_failed(); - test_casec_client_on_err_dns_failed(); - test_case6_preopen_evict(); - test_case7_preauth_drop_noflush(); - test_case8_dup_sid(); - test_case10_preopen_flush_double_free(); - test_case5_idle_timeout(); - test_case4_policy_denied(); - test_case9_disabled_datagram_drop(); - test_casez_zero_len_datagram(); - test_casey_short_idle_across_activity(); - test_caser_rebind_reconnect(); -} - -MQ_TEST_MAIN(test_udp_relay()) diff --git a/tests/integration/udp_echo.c b/tests/integration/udp_echo.c deleted file mode 100644 index 970e9282..00000000 --- a/tests/integration/udp_echo.c +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* - * udp_echo.c — simple UDP echo server for e2e UDP relay tests. - * - * Usage: - * udp_echo --port

[--max-size ] - * - * Binds to 127.0.0.1:

, receives datagrams up to --max-size bytes (default - * 65535 — fragmentation test range) and sends each one back to the sender. - * Runs until SIGTERM / SIGINT; exits 0 on signal, 1 on startup failure. - * - * Note: blocking recvfrom loop is intentional — this utility is single-threaded - * and driven by a shell e2e harness which kills it when done. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static volatile int g_stop = 0; - -static void -sig_handler(int sig) -{ - (void)sig; - g_stop = 1; -} - -static void -usage(void) -{ - fprintf(stderr, "Usage: udp_echo --port

[--max-size ]\n" - "\n" - " --port

UDP port to bind on 127.0.0.1 (required).\n" - " --max-size Receive buffer size in bytes (default 65535).\n"); -} - -int -main(int argc, char **argv) -{ - int port = 0; - int max_size = 65535; - - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--port") == 0 && i + 1 < argc) { - port = atoi(argv[++i]); - } else if (strcmp(argv[i], "--max-size") == 0 && i + 1 < argc) { - max_size = atoi(argv[++i]); - } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { - usage(); - return 0; - } else { - fprintf(stderr, "udp_echo: unknown argument: %s\n", argv[i]); - usage(); - return 1; - } - } - - if (port <= 0 || port > 65535) { - fprintf(stderr, "udp_echo: --port is required and must be 1-65535\n"); - usage(); - return 1; - } - if (max_size <= 0 || max_size > 65535) { - fprintf(stderr, "udp_echo: --max-size must be 1-65535\n"); - return 1; - } - - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) { - perror("udp_echo: socket"); - return 1; - } - - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr); - sa.sin_port = htons((uint16_t)port); - - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { - perror("udp_echo: bind"); - close(fd); - return 1; - } - - /* Print the actual bound port (useful if --port 0 were used, and for - * diagnosing bind on fixed ports). */ - struct sockaddr_in bound; - socklen_t blen = sizeof(bound); - if (getsockname(fd, (struct sockaddr *)&bound, &blen) == 0) { - fprintf(stdout, "udp_echo: bound 127.0.0.1:%d\n", ntohs(bound.sin_port)); - fflush(stdout); - } - - /* Use sigaction() WITHOUT SA_RESTART so that a signal during blocked - * recvfrom() returns EINTR instead of restarting the syscall. The - * signal() wrapper in glibc sets SA_RESTART, which would cause SIGTERM to - * restart recvfrom and leave the process stuck. */ - struct sigaction act; - memset(&act, 0, sizeof(act)); - act.sa_handler = sig_handler; - sigemptyset(&act.sa_mask); - act.sa_flags = 0; /* no SA_RESTART */ - sigaction(SIGTERM, &act, NULL); - sigaction(SIGINT, &act, NULL); - - uint8_t *buf = (uint8_t *)malloc((size_t)max_size); - if (!buf) { - fprintf(stderr, "udp_echo: malloc(%d) failed\n", max_size); - close(fd); - return 1; - } - - while (!g_stop) { - struct sockaddr_in peer; - socklen_t plen = sizeof(peer); - ssize_t n = - recvfrom(fd, buf, (size_t)max_size, 0, (struct sockaddr *)&peer, &plen); - if (n < 0) { - if (errno == EINTR) continue; - if (!g_stop) perror("udp_echo: recvfrom"); - break; - } - /* Echo back to sender. */ - ssize_t sent = sendto(fd, buf, (size_t)n, 0, (struct sockaddr *)&peer, plen); - if (sent < 0 && !g_stop) { - perror("udp_echo: sendto"); - /* non-fatal — keep running for next datagram */ - } - } - - free(buf); - close(fd); - return 0; -} diff --git a/tests/integration/udpsocks.c b/tests/integration/udpsocks.c deleted file mode 100644 index 77d3fbe2..00000000 --- a/tests/integration/udpsocks.c +++ /dev/null @@ -1,931 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* - * udpsocks.c — SOCKS5 UDP ASSOCIATE client / forwarder shim for e2e UDP relay - * tests. - * - * Usage (echo-client mode): - * udpsocks --proxy --target - * [--send ] [--count ] [--timeout-ms ] - * [--verbose] - * - * Usage (forwarder / bench-shim mode): - * udpsocks --proxy --target --listen - * [--verbose] - * - * ── Echo-client flow ──────────────────────────────────────────────────────── - * 1. TCP connect to --proxy. - * 2. SOCKS5 greeting (VER=5, 1 method, NO-AUTH). - * 3. UDP ASSOCIATE request (CMD=0x03, DST=0.0.0.0:0). - * 4. Parse ASSOCIATE reply — extract BND.ADDR:BND.PORT. - * 5. Create UDP socket. - * 6. For each of --count iterations: - * a. Build [SOCKS5 UDP header (target as DST) | payload] via - * mq_socks5_build_udp_hdr and send to BND. - * b. recvfrom with --timeout-ms deadline. - * c. Strip SOCKS5 UDP header with mq_socks5_parse_udp_hdr. - * d. Hex-dump the inner payload to stdout (one line per response). - * e. If payload was specified: verify echo == sent; print - * "OK bytes" or "MISMATCH" to stderr. - * 7. Exit 0 if all --count responses received and all verified OK; - * exit 1 otherwise (missing responses / mismatches). - * - * --send argument: - * - If the arg is a readable file path: treat contents as hex-encoded - * payload (whitespace/newlines ignored, must be even hex digits). - * - Else: parse as decimal integer N and generate N bytes of - * deterministic patterned data (byte i = (uint8_t)(i & 0xff)). - * - * --target host:port: - * - If host is an IPv4 literal (a.b.c.d): use ATYP=0x01. - * - Otherwise: use ATYP=0x03 (domain). - * - * ── Forwarder / bench-shim mode (--listen) ────────────────────────────────── - * Performs SOCKS5 ASSOCIATE setup as above, then binds 127.0.0.1: - * and relays plain UDP traffic between the local listener and the SOCKS5 relay. - * - * Forwarder contract: - * - Sticky single peer: the source address of the first inbound datagram is - * remembered for the process lifetime and used as the return address for - * all unwrapped datagrams. Inner-address / port migration is out of scope. - * - Wrap path (local peer → relay): prepend the prebuilt SOCKS5 UDP header - * (built from --target) and forward to BND. - * - Unwrap path (relay → local peer): parse and strip the SOCKS5 UDP header, - * forward inner payload to the learned peer address. - * - Oversize datagrams that would exceed the tx buffer are silently dropped - * on the wrap path and counted. - * - The process exits (cleanly, status 0) when the ASSOCIATE control TCP - * connection reaches EOF — the proxy session has been torn down. - * - SIGTERM / SIGINT also cause a clean (status 0) exit. - * - In all exit paths a one-line summary is printed to stderr: - * udpsocks: forwarder stats wrap_fail=N unwrap_fail=N no_peer_drop=N - * - * Links mqproxy static library for mq_socks5_build_udp_hdr / - * mq_socks5_parse_udp_hdr (pure functions — no event loop). - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ingress/mq_socks5.h" -#include "wire/mq_wire.h" - -/* ── helpers ─────────────────────────────────────────────────────────────────*/ - -static void -usage(void) -{ - fprintf( - stderr, - "Usage: udpsocks --proxy --target \n" - " [--send ] [--count ]\n" - " [--timeout-ms ] [--verbose]\n" - " [--listen ]\n" - "\n" - " --proxy SOCKS5 TCP proxy address (required).\n" - " --target UDP echo target (required); host is IP literal\n" - " (ATYP=IPv4) or domain name (ATYP=DOMAIN).\n" - " --send Payload: path to hex file OR byte count N\n" - " (deterministic pattern, default 8).\n" - " --count Send/recv cycles (default 1).\n" - " --timeout-ms Per-response receive deadline in ms (default 3000).\n" - " --verbose Hex-dump even on successful match.\n" - " --listen Forwarder mode: bind 127.0.0.1:, relay plain\n" - " UDP <-> SOCKS5 UDP encapsulation (bench shim).\n" - " Mutually exclusive with --send/--count.\n"); -} - -/* Write all bytes; returns 0 on success, -1 on error. */ -static int -write_all(int fd, const uint8_t *buf, size_t len) -{ - size_t off = 0; - while (off < len) { - ssize_t n = send(fd, buf + off, len - off, 0); - if (n <= 0) return -1; - off += (size_t)n; - } - return 0; -} - -/* Read exactly want bytes from fd; returns 0 on success, -1 on error/EOF. */ -static int -read_exact(int fd, uint8_t *out, size_t want) -{ - size_t got = 0; - while (got < want) { - ssize_t n = recv(fd, out + got, want - got, 0); - if (n <= 0) return -1; - got += (size_t)n; - } - return 0; -} - -/* Hex dump buf to stdout (no newline at end — caller adds it). */ -static void -hex_dump(const uint8_t *buf, size_t len) -{ - for (size_t i = 0; i < len; i++) { - printf("%02x", buf[i]); - } -} - -/* Convert nybble char to value 0-15; returns -1 on bad char. */ -static int -hex_nybble(char c) -{ - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - return -1; -} - -/* Load hex-encoded file into *out (caller frees). Returns byte count or -1. */ -static ssize_t -load_hex_file(const char *path, uint8_t **out) -{ - FILE *f = fopen(path, "r"); - if (!f) return -1; - - /* Read whole file into a temp string buffer. */ - fseek(f, 0, SEEK_END); - long fsz = ftell(f); - rewind(f); - if (fsz <= 0) { - fclose(f); - *out = (uint8_t *)malloc(1); /* empty payload */ - if (!*out) return -1; - return 0; - } - - char *raw = (char *)malloc((size_t)fsz + 1); - if (!raw) { - fclose(f); - return -1; - } - size_t rn = fread(raw, 1, (size_t)fsz, f); - fclose(f); - raw[rn] = '\0'; - - /* Count non-whitespace chars (must be even). */ - size_t nhex = 0; - for (size_t i = 0; i < rn; i++) { - char c = raw[i]; - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; - nhex++; - } - if (nhex % 2 != 0) { - fprintf(stderr, "udpsocks: hex file has odd number of hex digits\n"); - free(raw); - return -1; - } - - size_t blen = nhex / 2; - uint8_t *bytes = (uint8_t *)malloc(blen + 1); - if (!bytes) { - free(raw); - return -1; - } - - size_t bi = 0; - int hi = -1; - for (size_t i = 0; i < rn; i++) { - char c = raw[i]; - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; - int v = hex_nybble(c); - if (v < 0) { - fprintf(stderr, "udpsocks: invalid hex char '%c' in file\n", c); - free(raw); - free(bytes); - return -1; - } - if (hi < 0) { - hi = v; - } else { - bytes[bi++] = (uint8_t)((hi << 4) | v); - hi = -1; - } - } - - free(raw); - *out = bytes; - return (ssize_t)blen; -} - -/* Generate N bytes of deterministic pattern (byte i = i & 0xff). */ -static uint8_t * -gen_pattern(size_t n) -{ - uint8_t *buf = (uint8_t *)malloc(n + 1); - if (!buf) return NULL; - for (size_t i = 0; i < n; i++) { - buf[i] = (uint8_t)(i & 0xff); - } - return buf; -} - -/* Wait for readable on fd up to timeout_ms; returns 1 if readable, 0 if - * timeout, -1 on error. */ -static int -wait_readable(int fd, int timeout_ms) -{ - fd_set rfds; - FD_ZERO(&rfds); - FD_SET(fd, &rfds); - struct timeval tv; - tv.tv_sec = timeout_ms / 1000; - tv.tv_usec = (timeout_ms % 1000) * 1000; - int r = select(fd + 1, &rfds, NULL, NULL, &tv); - if (r > 0) return 1; - if (r == 0) return 0; - return -1; -} - -/* ── arg parsing helpers ──────────────────────────────────────────────────────*/ - -/* Parse "host:port" — host may contain colons (IPv6 literals in brackets are - * not supported here; IPv4 and domain names only). The LAST colon separates - * host from port. Returns 0 on success, -1 on parse error. */ -static int -parse_hostport(const char *arg, char *host_out, size_t host_cap, uint16_t *port_out) -{ - const char *colon = strrchr(arg, ':'); - if (!colon) return -1; - size_t hlen = (size_t)(colon - arg); - if (hlen == 0 || hlen >= host_cap) return -1; - memcpy(host_out, arg, hlen); - host_out[hlen] = '\0'; - int p = atoi(colon + 1); - if (p <= 0 || p > 65535) return -1; - *port_out = (uint16_t)p; - return 0; -} - -/* ── forwarder mode (--listen) ─────────────────────────────────────────────── - * Plain-UDP <-> SOCKS5-UDP shim for the A/B lanes bench: bind - * 127.0.0.1:listen_port, learn the single peer from the first datagram - * (sticky for the process lifetime — inner port migration is out of scope), - * prepend the prebuilt SOCKS5 UDP header toward BND, strip it on the way - * back. Runs until killed or the ASSOCIATE control connection drops. - */ - -static volatile sig_atomic_t g_stop = 0; - -static void -sig_handler(int sig) -{ - (void)sig; - g_stop = 1; -} - -static int -run_forwarder(uint16_t listen_port, int tcp_fd, int udp_fd, - const struct sockaddr_in *bnd_sa, const uint8_t *hdr_buf, int hdr_len, - int verbose) -{ - /* Install signal handlers so SIGTERM/SIGINT cause a clean exit. */ - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = sig_handler; - sigaction(SIGTERM, &sa, NULL); - sigaction(SIGINT, &sa, NULL); - - int lfd = socket(AF_INET, SOCK_DGRAM, 0); - if (lfd < 0) { - perror("udpsocks: socket(listen)"); - return 1; - } - struct sockaddr_in la; - memset(&la, 0, sizeof(la)); - la.sin_family = AF_INET; - inet_pton(AF_INET, "127.0.0.1", &la.sin_addr); - la.sin_port = htons(listen_port); - if (bind(lfd, (struct sockaddr *)&la, sizeof(la)) != 0) { - perror("udpsocks: bind(listen)"); - close(lfd); - return 1; - } - - struct sockaddr_in peer; - memset(&peer, 0, sizeof(peer)); /* read only after learn, but keep - * -Wmaybe-uninitialized quiet */ - socklen_t peer_len = 0; /* 0 = not learned yet */ - static uint8_t rx[65535 + 300]; - static uint8_t tx[65535 + 300]; - - uint64_t wrap_fail = 0; /* sendto < 0 or oversize-skipped on wrap path */ - uint64_t unwrap_fail = 0; /* parse failed or sendto < 0 on unwrap path */ - uint64_t no_peer_drop = 0; /* inbound datagram dropped before peer learned */ - - fprintf(stderr, "udpsocks: forwarding 127.0.0.1:%u <-> SOCKS5 UDP relay\n", - (unsigned)listen_port); - - for (;;) { - if (g_stop) break; - - fd_set rfds; - FD_ZERO(&rfds); - FD_SET(lfd, &rfds); - FD_SET(udp_fd, &rfds); - FD_SET(tcp_fd, &rfds); - int maxfd = lfd; - if (udp_fd > maxfd) maxfd = udp_fd; - if (tcp_fd > maxfd) maxfd = tcp_fd; - if (select(maxfd + 1, &rfds, NULL, NULL, NULL) < 0) { - if (errno == EINTR) { - /* Interrupted by signal — recheck g_stop at top of loop. */ - continue; - } - perror("udpsocks: select"); - close(lfd); - fprintf(stderr, - "udpsocks: forwarder stats wrap_fail=%llu unwrap_fail=%llu" - " no_peer_drop=%llu\n", - (unsigned long long)wrap_fail, (unsigned long long)unwrap_fail, - (unsigned long long)no_peer_drop); - return 1; - } - - /* ASSOCIATE control EOF => proxy session torn down => exit cleanly. */ - if (FD_ISSET(tcp_fd, &rfds)) { - uint8_t b; - if (recv(tcp_fd, &b, 1, 0) <= 0) { - fprintf(stderr, "udpsocks: ASSOCIATE control connection closed\n"); - close(lfd); - fprintf(stderr, - "udpsocks: forwarder stats wrap_fail=%llu unwrap_fail=%llu" - " no_peer_drop=%llu\n", - (unsigned long long)wrap_fail, (unsigned long long)unwrap_fail, - (unsigned long long)no_peer_drop); - return 0; - } - } - - /* peer -> proxy: wrap. */ - if (FD_ISSET(lfd, &rfds)) { - struct sockaddr_in from; - socklen_t flen = sizeof(from); - ssize_t n = recvfrom(lfd, rx, sizeof(rx), 0, (struct sockaddr *)&from, &flen); - if (n > 0) { - if (peer_len == 0) { - peer = from; - peer_len = flen; - if (verbose) { - fprintf(stderr, "udpsocks: learned peer 127.0.0.1:%u\n", - (unsigned)ntohs(from.sin_port)); - } - } - if ((size_t)n + (size_t)hdr_len <= sizeof(tx)) { - memcpy(tx, hdr_buf, (size_t)hdr_len); - memcpy(tx + hdr_len, rx, (size_t)n); - ssize_t sent = - sendto(udp_fd, tx, (size_t)n + (size_t)hdr_len, 0, - (const struct sockaddr *)bnd_sa, sizeof(*bnd_sa)); - if (sent < 0) wrap_fail++; - } else { - /* Datagram too large for tx buffer — drop and count. */ - wrap_fail++; - } - } - } - - /* proxy -> peer: unwrap. */ - if (FD_ISSET(udp_fd, &rfds)) { - ssize_t n = recvfrom(udp_fd, rx, sizeof(rx), 0, NULL, NULL); - if (n > 0) { - if (peer_len == 0) { - no_peer_drop++; - } else { - mq_socks5_udp_hdr_t h; - memset(&h, 0, sizeof(h)); - if (mq_socks5_parse_udp_hdr(rx, (size_t)n, &h) >= 0 && - (size_t)n >= h.hdr_len) { - ssize_t sent = sendto(lfd, rx + h.hdr_len, (size_t)n - h.hdr_len, - 0, (struct sockaddr *)&peer, peer_len); - if (sent < 0) unwrap_fail++; - } else { - unwrap_fail++; - } - } - } - } - } - - /* Signal-initiated exit — clean. */ - close(lfd); - fprintf(stderr, - "udpsocks: forwarder stats wrap_fail=%llu unwrap_fail=%llu" - " no_peer_drop=%llu\n", - (unsigned long long)wrap_fail, (unsigned long long)unwrap_fail, - (unsigned long long)no_peer_drop); - return 0; -} - -/* ── main ─────────────────────────────────────────────────────────────────────*/ - -int -main(int argc, char **argv) -{ - char proxy_host[256] = {0}; - uint16_t proxy_port = 0; - char target_host[256] = {0}; - uint16_t target_port = 0; - const char *send_arg = NULL; - int count = 1; - int timeout_ms = 3000; - int verbose = 0; - uint16_t listen_port = 0; - - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--proxy") == 0 && i + 1 < argc) { - if (parse_hostport(argv[++i], proxy_host, sizeof(proxy_host), &proxy_port) != - 0) { - fprintf(stderr, "udpsocks: bad --proxy: %s\n", argv[i]); - return 1; - } - } else if (strcmp(argv[i], "--target") == 0 && i + 1 < argc) { - if (parse_hostport(argv[++i], target_host, sizeof(target_host), - &target_port) != 0) { - fprintf(stderr, "udpsocks: bad --target: %s\n", argv[i]); - return 1; - } - } else if (strcmp(argv[i], "--send") == 0 && i + 1 < argc) { - send_arg = argv[++i]; - } else if (strcmp(argv[i], "--count") == 0 && i + 1 < argc) { - count = atoi(argv[++i]); - if (count <= 0) { - fprintf(stderr, "udpsocks: --count must be >= 1\n"); - return 1; - } - } else if (strcmp(argv[i], "--timeout-ms") == 0 && i + 1 < argc) { - timeout_ms = atoi(argv[++i]); - if (timeout_ms <= 0) { - fprintf(stderr, "udpsocks: --timeout-ms must be > 0\n"); - return 1; - } - } else if (strcmp(argv[i], "--verbose") == 0) { - verbose = 1; - } else if (strcmp(argv[i], "--listen") == 0 && i + 1 < argc) { - char *endp = NULL; - unsigned long lp = strtoul(argv[++i], &endp, 10); - if (!endp || *endp != '\0' || lp < 1 || lp > 65535) { - fprintf(stderr, "udpsocks: --listen: invalid port: %s\n", argv[i]); - usage(); - return 1; - } - listen_port = (uint16_t)lp; - } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { - usage(); - return 0; - } else { - fprintf(stderr, "udpsocks: unknown argument: %s\n", argv[i]); - usage(); - return 1; - } - } - - if (proxy_host[0] == '\0' || proxy_port == 0) { - fprintf(stderr, "udpsocks: --proxy is required\n"); - usage(); - return 1; - } - if (target_host[0] == '\0' || target_port == 0) { - fprintf(stderr, "udpsocks: --target is required\n"); - usage(); - return 1; - } - - if (listen_port != 0 && (send_arg != NULL || count != 1)) { - fprintf(stderr, "udpsocks: --listen is mutually exclusive with --send/--count\n"); - usage(); - return 2; - } - - /* ── Build payload ──────────────────────────────────────────────────────*/ - uint8_t *payload = NULL; - size_t plen = 0; - int payload_check = 0; /* 1 = verify echo response matches payload */ - - if (send_arg != NULL) { - /* Try opening as file first. */ - FILE *probe = fopen(send_arg, "r"); - if (probe) { - fclose(probe); - ssize_t n = load_hex_file(send_arg, &payload); - if (n < 0) { - fprintf(stderr, "udpsocks: failed to load hex file: %s\n", send_arg); - return 1; - } - plen = (size_t)n; - payload_check = 1; - } else { - /* Interpret as integer size. */ - char *endp = NULL; - long sz = strtol(send_arg, &endp, 10); - if (!endp || *endp != '\0' || sz <= 0 || sz > 65535) { - fprintf( - stderr, - "udpsocks: --send: not a readable file and not a valid size: %s\n", - send_arg); - return 1; - } - plen = (size_t)sz; - payload = gen_pattern(plen); - if (!payload) { - fprintf(stderr, "udpsocks: malloc failed\n"); - return 1; - } - payload_check = 1; - } - } else { - /* Default: 8-byte deterministic pattern. */ - plen = 8; - payload = gen_pattern(plen); - if (!payload) { - fprintf(stderr, "udpsocks: malloc failed\n"); - return 1; - } - payload_check = 1; - } - - /* ── Build mq_socks5_req_t for the target ───────────────────────────────*/ - mq_socks5_req_t dst; - memset(&dst, 0, sizeof(dst)); - dst.port = target_port; - - /* Try parsing as IPv4 literal. */ - struct in_addr ipv4_addr; - if (inet_pton(AF_INET, target_host, &ipv4_addr) == 1) { - dst.atype = MQ_ADDR_IPV4; - dst.host_len = 4; - memcpy(dst.host, &ipv4_addr.s_addr, 4); /* network byte order */ - } else { - /* Treat as domain. */ - size_t dlen = strlen(target_host); - if (dlen == 0 || dlen > MQ_MAX_HOST) { - fprintf(stderr, "udpsocks: target host too long\n"); - free(payload); - return 1; - } - dst.atype = MQ_ADDR_DOMAIN; - dst.host_len = dlen; - memcpy(dst.host, target_host, dlen); - } - - /* ── TCP connect to SOCKS5 proxy ────────────────────────────────────────*/ - int tcp_fd = socket(AF_INET, SOCK_STREAM, 0); - if (tcp_fd < 0) { - perror("udpsocks: socket(TCP)"); - free(payload); - return 1; - } - - struct sockaddr_in proxy_sa; - memset(&proxy_sa, 0, sizeof(proxy_sa)); - proxy_sa.sin_family = AF_INET; - proxy_sa.sin_port = htons(proxy_port); - if (inet_pton(AF_INET, proxy_host, &proxy_sa.sin_addr) != 1) { - fprintf(stderr, "udpsocks: invalid proxy IP: %s\n", proxy_host); - close(tcp_fd); - free(payload); - return 1; - } - - if (connect(tcp_fd, (struct sockaddr *)&proxy_sa, sizeof(proxy_sa)) != 0) { - perror("udpsocks: connect"); - close(tcp_fd); - free(payload); - return 1; - } - - /* ── SOCKS5 greeting (VER=5, NMETHODS=1, METHOD=0 NO-AUTH) ─────────────*/ - { - uint8_t greeting[] = {0x05, 0x01, 0x00}; - if (write_all(tcp_fd, greeting, sizeof(greeting)) != 0) { - fprintf(stderr, "udpsocks: send greeting failed\n"); - close(tcp_fd); - free(payload); - return 1; - } - uint8_t method_reply[2]; - if (read_exact(tcp_fd, method_reply, 2) != 0) { - fprintf(stderr, "udpsocks: read method reply failed\n"); - close(tcp_fd); - free(payload); - return 1; - } - if (method_reply[0] != 0x05 || method_reply[1] != 0x00) { - fprintf(stderr, "udpsocks: method reply rejected (VER=%02x METHOD=%02x)\n", - method_reply[0], method_reply[1]); - close(tcp_fd); - free(payload); - return 1; - } - } - - /* ── SOCKS5 ASSOCIATE request (CMD=0x03, DST=0.0.0.0:0) ────────────────*/ - { - /* VER CMD RSV ATYP IPv4(0.0.0.0) PORT(0) */ - uint8_t assoc_req[] = {0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0, 0, 0}; - if (write_all(tcp_fd, assoc_req, sizeof(assoc_req)) != 0) { - fprintf(stderr, "udpsocks: send ASSOCIATE request failed\n"); - close(tcp_fd); - free(payload); - return 1; - } - } - - /* ── Read ASSOCIATE reply — hand-parse the 10-byte IPv4 response ────────*/ - /* RFC 1928 §6: VER(1) REP(1) RSV(1) ATYP(1) BND.ADDR BND.PORT(2) - * We expect ATYP=0x01 (IPv4) → total 10 bytes. - * If ATYP=0x03 (domain) we'd need to read the length byte first, but - * mqproxy always replies IPv4 for ASSOCIATE. */ - uint8_t assoc_reply[10]; - if (read_exact(tcp_fd, assoc_reply, 4) != 0) { - fprintf(stderr, "udpsocks: read ASSOCIATE reply header failed\n"); - close(tcp_fd); - free(payload); - return 1; - } - if (assoc_reply[0] != 0x05) { - fprintf(stderr, "udpsocks: ASSOCIATE reply: bad VER=0x%02x\n", assoc_reply[0]); - close(tcp_fd); - free(payload); - return 1; - } - if (assoc_reply[1] != 0x00) { - fprintf(stderr, "udpsocks: ASSOCIATE reply: REP=0x%02x (failed)\n", - assoc_reply[1]); - close(tcp_fd); - free(payload); - return 1; - } - - uint8_t bnd_atyp = assoc_reply[3]; - uint32_t bnd_ip_be = 0; - uint16_t bnd_port = 0; - - if (bnd_atyp == 0x01) { - /* IPv4 — read 4+2 = 6 more bytes. */ - uint8_t rest[6]; - if (read_exact(tcp_fd, rest, 6) != 0) { - fprintf(stderr, "udpsocks: read ASSOCIATE reply BND failed\n"); - close(tcp_fd); - free(payload); - return 1; - } - memcpy(&bnd_ip_be, rest, 4); - bnd_port = (uint16_t)((rest[4] << 8) | rest[5]); - } else if (bnd_atyp == 0x03) { - /* Domain — read length byte, then N bytes + 2 port bytes. */ - uint8_t dlen_byte; - if (read_exact(tcp_fd, &dlen_byte, 1) != 0) { - fprintf(stderr, "udpsocks: read ASSOCIATE reply domain length failed\n"); - close(tcp_fd); - free(payload); - return 1; - } - uint8_t domain_and_port[256 + 2]; - if (read_exact(tcp_fd, domain_and_port, (size_t)dlen_byte + 2) != 0) { - fprintf(stderr, "udpsocks: read ASSOCIATE reply domain+port failed\n"); - close(tcp_fd); - free(payload); - return 1; - } - /* We need an IP to sendto — try to resolve, but for loopback e2e the - * proxy should have replied with an IPv4 BND. Fail clearly. */ - fprintf(stderr, - "udpsocks: ASSOCIATE reply ATYP=domain not supported for UDP socket\n"); - close(tcp_fd); - free(payload); - return 1; - } else if (bnd_atyp == 0x04) { - /* IPv6 — read 16+2 = 18 bytes. */ - uint8_t rest[18]; - if (read_exact(tcp_fd, rest, 18) != 0) { - fprintf(stderr, "udpsocks: read ASSOCIATE reply BND (IPv6) failed\n"); - close(tcp_fd); - free(payload); - return 1; - } - fprintf(stderr, "udpsocks: ASSOCIATE reply ATYP=IPv6 not supported\n"); - close(tcp_fd); - free(payload); - return 1; - } else { - fprintf(stderr, "udpsocks: ASSOCIATE reply unknown ATYP=0x%02x\n", bnd_atyp); - close(tcp_fd); - free(payload); - return 1; - } - - if (bnd_port == 0) { - fprintf(stderr, "udpsocks: ASSOCIATE reply BND.PORT=0 (invalid)\n"); - close(tcp_fd); - free(payload); - return 1; - } - - /* ── Create UDP socket ──────────────────────────────────────────────────*/ - int udp_fd = socket(AF_INET, SOCK_DGRAM, 0); - if (udp_fd < 0) { - perror("udpsocks: socket(UDP)"); - close(tcp_fd); - free(payload); - return 1; - } - - /* Bind to 127.0.0.1:0 so the proxy can reach us back. */ - struct sockaddr_in udp_self; - memset(&udp_self, 0, sizeof(udp_self)); - udp_self.sin_family = AF_INET; - inet_pton(AF_INET, "127.0.0.1", &udp_self.sin_addr); - udp_self.sin_port = 0; - if (bind(udp_fd, (struct sockaddr *)&udp_self, sizeof(udp_self)) != 0) { - perror("udpsocks: bind(UDP)"); - close(udp_fd); - close(tcp_fd); - free(payload); - return 1; - } - - /* BND.ADDR endpoint to sendto. */ - struct sockaddr_in bnd_sa; - memset(&bnd_sa, 0, sizeof(bnd_sa)); - bnd_sa.sin_family = AF_INET; - bnd_sa.sin_addr.s_addr = bnd_ip_be; /* already network byte order */ - bnd_sa.sin_port = htons(bnd_port); - - /* ── Pre-build the SOCKS5 UDP encapsulation header ─────────────────────*/ - /* Max header: RSV(2)+FRAG(1)+ATYP(1)+LEN(1)+255+PORT(2) = 262 */ - uint8_t hdr_buf[300]; - int hdr_len = mq_socks5_build_udp_hdr(hdr_buf, sizeof(hdr_buf), &dst); - if (hdr_len < 0) { - fprintf(stderr, "udpsocks: mq_socks5_build_udp_hdr failed\n"); - close(udp_fd); - close(tcp_fd); - free(payload); - return 1; - } - - /* ── Forwarder mode (--listen) ──────────────────────────────────────────*/ - if (listen_port != 0) { - int frc = run_forwarder(listen_port, tcp_fd, udp_fd, &bnd_sa, hdr_buf, hdr_len, - verbose); - close(udp_fd); - close(tcp_fd); - free(payload); - return frc; - } - - /* ── Build full outgoing datagram: header + payload ─────────────────────*/ - size_t dgram_len = (size_t)hdr_len + plen; - if (dgram_len > 65535) { - fprintf(stderr, "udpsocks: datagram too large (%zu)\n", dgram_len); - close(udp_fd); - close(tcp_fd); - free(payload); - return 1; - } - uint8_t *dgram = (uint8_t *)malloc(dgram_len); - if (!dgram) { - fprintf(stderr, "udpsocks: malloc dgram failed\n"); - close(udp_fd); - close(tcp_fd); - free(payload); - return 1; - } - memcpy(dgram, hdr_buf, (size_t)hdr_len); - if (plen > 0) memcpy(dgram + hdr_len, payload, plen); - - /* Receive buffer — sized to hold the largest possible echo (header + payload). */ - size_t rxbuf_sz = 65535 + 300; - uint8_t *rxbuf = (uint8_t *)malloc(rxbuf_sz); - if (!rxbuf) { - fprintf(stderr, "udpsocks: malloc rxbuf failed\n"); - free(dgram); - close(udp_fd); - close(tcp_fd); - free(payload); - return 1; - } - - /* ── Send/receive loop ──────────────────────────────────────────────────*/ - int ok_count = 0; - int fail_count = 0; - - for (int iter = 0; iter < count; iter++) { - /* Send the datagram to BND. */ - ssize_t sent = sendto(udp_fd, dgram, dgram_len, 0, (struct sockaddr *)&bnd_sa, - sizeof(bnd_sa)); - if (sent < 0) { - fprintf(stderr, "udpsocks: sendto failed (iter %d): %s\n", iter + 1, - strerror(errno)); - fail_count++; - continue; - } - - /* Wait for a response within timeout_ms. */ - int rdy = wait_readable(udp_fd, timeout_ms); - if (rdy <= 0) { - if (rdy == 0) { - fprintf(stderr, "udpsocks: timeout waiting for response (iter %d)\n", - iter + 1); - } else { - fprintf(stderr, "udpsocks: select error (iter %d): %s\n", iter + 1, - strerror(errno)); - } - fail_count++; - continue; - } - - ssize_t rn = recvfrom(udp_fd, rxbuf, rxbuf_sz, 0, NULL, NULL); - if (rn < 0) { - fprintf(stderr, "udpsocks: recvfrom failed (iter %d): %s\n", iter + 1, - strerror(errno)); - fail_count++; - continue; - } - - /* Strip SOCKS5 UDP encapsulation header. */ - mq_socks5_udp_hdr_t rx_hdr; - memset(&rx_hdr, 0, sizeof(rx_hdr)); - int parsed = mq_socks5_parse_udp_hdr(rxbuf, (size_t)rn, &rx_hdr); - if (parsed < 0) { - fprintf(stderr, - "udpsocks: mq_socks5_parse_udp_hdr failed (iter %d, ret=%d)\n", - iter + 1, parsed); - fail_count++; - continue; - } - - size_t inner_len = (size_t)rn - rx_hdr.hdr_len; - const uint8_t *inner = rxbuf + rx_hdr.hdr_len; - - /* Hex dump inner payload to stdout (always). */ - hex_dump(inner, inner_len); - printf("\n"); - fflush(stdout); - - /* Verify echo match if requested. */ - int match = 1; - if (payload_check) { - if (inner_len != plen) { - match = 0; - } else if (plen > 0 && memcmp(inner, payload, plen) != 0) { - match = 0; - } - } - - if (match) { - if (verbose) { - fprintf(stderr, "udpsocks: OK %zu bytes (iter %d)\n", inner_len, - iter + 1); - } else { - fprintf(stderr, "OK %zu bytes\n", inner_len); - } - ok_count++; - } else { - fprintf(stderr, "udpsocks: MISMATCH (iter %d): got %zu bytes, expected %zu\n", - iter + 1, inner_len, plen); - if (verbose) { - fprintf(stderr, " sent: "); - for (size_t k = 0; k < plen && k < 64; k++) - fprintf(stderr, "%02x", payload[k]); - if (plen > 64) fprintf(stderr, "..."); - fprintf(stderr, "\n"); - fprintf(stderr, " recv: "); - for (size_t k = 0; k < inner_len && k < 64; k++) - fprintf(stderr, "%02x", inner[k]); - if (inner_len > 64) fprintf(stderr, "..."); - fprintf(stderr, "\n"); - } - fail_count++; - } - } - - free(rxbuf); - free(dgram); - free(payload); - close(udp_fd); - close(tcp_fd); - - if (fail_count > 0) { - fprintf(stderr, "udpsocks: %d/%d responses OK, %d failed\n", ok_count, count, - fail_count); - return 1; - } - return 0; -} diff --git a/tests/mqtest.h b/tests/mqtest.h deleted file mode 100644 index 540b6560..00000000 --- a/tests/mqtest.h +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#ifndef MQTEST_H -#define MQTEST_H -#include -#include -#include - -static int mq_test_failures; - -#define MQ_CHECK(cond) \ - do { \ - if (!(cond)) { \ - fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ - mq_test_failures++; \ - } \ - } while (0) - -#define MQ_CHECK_EQ_INT(a, b) \ - do { \ - long long _a = (long long)(a), _b = (long long)(b); \ - if (_a != _b) { \ - fprintf(stderr, "FAIL %s:%d: %s (%lld) != %s (%lld)\n", __FILE__, __LINE__, \ - #a, _a, #b, _b); \ - mq_test_failures++; \ - } \ - } while (0) - -#define MQ_CHECK_MEM(a, b, n) \ - do { \ - if (memcmp((a), (b), (n)) != 0) { \ - fprintf(stderr, "FAIL %s:%d: memcmp %s != %s\n", __FILE__, __LINE__, #a, \ - #b); \ - mq_test_failures++; \ - } \ - } while (0) - -#define MQ_TEST_MAIN(body) \ - int main(void) \ - { \ - body; \ - if (mq_test_failures) { \ - fprintf(stderr, "%d failure(s)\n", mq_test_failures); \ - return 1; \ - } \ - printf("OK\n"); \ - return 0; \ - } -#endif diff --git a/tests/nghttp2_link_probe.c b/tests/nghttp2_link_probe.c deleted file mode 100644 index 53f8066c..00000000 --- a/tests/nghttp2_link_probe.c +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* nghttp2_link_probe — minimal link/runtime guard for the nghttp2 dependency - * (Phase 7 Slice 3 Task 4). - * - * Confirms the build can both COMPILE against and LINK + - * RUN a call into the chosen libnghttp2 (system .so in the dynamic build, the - * multiarch libnghttp2.a in the MQPROXY_STATIC_XQUIC build). Prints the runtime - * version string and exits 0. Permanent, low-cost guard: it fails loudly if the - * nghttp2 dependency ever silently drops out of the build, independent of the - * heavier H2 MITM adapter tests. */ -#include -#include - -int -main(void) -{ - nghttp2_info *info = nghttp2_version(0); - if (info == NULL || info->version_str == NULL) { - fprintf(stderr, "nghttp2_version() returned NULL\n"); - return 1; - } - printf("nghttp2 link probe OK: version %s (version_num=0x%08x)\n", info->version_str, - info->version_num); - return 0; -} diff --git a/tests/packaging/smoke_deb.sh b/tests/packaging/smoke_deb.sh new file mode 100644 index 00000000..b1483b4f --- /dev/null +++ b/tests/packaging/smoke_deb.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Run only on a disposable native systemd CI runner, as root. +set -euo pipefail +[[ $EUID == 0 ]] || { echo 'run as root on a disposable runner'; exit 1; } +DEB="${1:?deb path}" +ROOT=$(cd "$(dirname "$0")/../.." && pwd) +cleanup() { + systemctl stop mqproxy-client@sp5client mqproxy-server@sp5server || true + dpkg -r mqproxy || true + rm -f /etc/mqproxy/sp5{server,client}.conf /etc/mqproxy/sp5test.{crt,key} +} +trap cleanup EXIT +# Reassert dpkg after dependency repair; broken maintainer scripts must fail. +dpkg -i "$DEB" || { apt-get -f install -y; dpkg -i "$DEB"; } +id mqproxy +test -d /etc/mqproxy +install -o mqproxy -g mqproxy -m 0644 "$ROOT/tests/certs/test.crt" /etc/mqproxy/sp5test.crt +install -o mqproxy -g mqproxy -m 0600 "$ROOT/tests/certs/test.key" /etc/mqproxy/sp5test.key +printf '[Interface]\nListen = 127.0.0.1:14433\n[TLS]\nCert = /etc/mqproxy/sp5test.crt\nKey = /etc/mqproxy/sp5test.key\n[Auth]\nKey = citoken\n' > /etc/mqproxy/sp5server.conf +printf '[Server]\nAddress = 127.0.0.1:14433\n[Auth]\nKey = citoken\n[Ingress]\nSocks5 = 127.0.0.1:11080\n' > /etc/mqproxy/sp5client.conf +chown mqproxy:mqproxy /etc/mqproxy/sp5{server,client}.conf +chmod 0600 /etc/mqproxy/sp5{server,client}.conf +systemctl daemon-reload +systemctl start mqproxy-server@sp5server mqproxy-client@sp5client +sleep 2 +for unit in mqproxy-server@sp5server mqproxy-client@sp5client; do + journalctl -u "$unit" --no-pager -n 30 + systemctl is-active "$unit" +done +# A real SOCKS request proves the two hardened services can relay traffic. +WORK=$(mktemp -d) +python3 -m http.server 18089 --bind 127.0.0.1 --directory "$WORK" > "$WORK/http.log" 2>&1 & +HTTP_PID=$! +trap 'kill "$HTTP_PID" 2>/dev/null || true; rm -rf "$WORK"; cleanup' EXIT +printf 'sp5 service relay\n' > "$WORK/probe" +for _attempt in {1..20}; do + if curl --fail --silent --noproxy '' --socks5-hostname 127.0.0.1:11080 \ + --max-time 2 http://127.0.0.1:18089/probe > "$WORK/received"; then break; fi + sleep 1 +done +cmp "$WORK/probe" "$WORK/received" +echo 'OK: deb installation and hardened server/client relay' diff --git a/tests/packaging/verify_archives.py b/tests/packaging/verify_archives.py new file mode 100644 index 00000000..92217963 --- /dev/null +++ b/tests/packaging/verify_archives.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +"""Verify the actual tar/deb payloads cannot install builder-owned writable files.""" +import subprocess +import sys +import tarfile +import tempfile + + +def verify(archive): + members = archive.getmembers() + assert members, "empty archive" + for member in members: + assert member.uid == member.gid == 0, (member.name, "not root-owned") + assert member.isdir() or member.isfile(), (member.name, "unexpected type") + executable = member.name.endswith(("/bin/mqproxy", "/postinst", "/postrm")) + expected = 0o755 if member.isdir() or executable else 0o644 + assert member.mode == expected, (member.name, oct(member.mode), oct(expected)) + + +with tarfile.open(sys.argv[1]) as archive: + verify(archive) +for option in ("--fsys-tarfile", "--ctrl-tarfile"): + with tempfile.TemporaryFile() as payload: + subprocess.run(["dpkg-deb", option, sys.argv[2]], stdout=payload, check=True) + payload.seek(0) + with tarfile.open(fileobj=payload) as archive: + verify(archive) +print("PASS: tar/deb ownership and permissions") diff --git a/tests/packaging/verify_install.sh b/tests/packaging/verify_install.sh index 731b4af3..0b8fbb28 100755 --- a/tests/packaging/verify_install.sh +++ b/tests/packaging/verify_install.sh @@ -1,25 +1,7 @@ #!/usr/bin/env bash -# Verify packaging install layout + static self-containment. -# Usage: verify_install.sh -# -# Requires the build to have been configured with -DCMAKE_INSTALL_PREFIX=/usr -# (the @CMAKE_INSTALL_PREFIX@ in the unit ExecStart is baked at *configure* time, -# so a --prefix at install time would not match). On any other prefix this SKIPs. +# Verify a staged Rust installation (also usable after dpkg-deb --extract). set -euo pipefail -BUILD="${1:?build dir}" -[ -x "$BUILD/mqproxy" ] || { echo "SKIP: no mqproxy in $BUILD"; exit 77; } - -PREFIX="$(sed -n 's/^CMAKE_INSTALL_PREFIX:[^=]*=//p' "$BUILD/CMakeCache.txt" 2>/dev/null || true)" -if [ "$PREFIX" != "/usr" ]; then - echo "SKIP: packaging CTest expects -DCMAKE_INSTALL_PREFIX=/usr (got '${PREFIX:-unset}')" - exit 77 -fi - -STAGE="$(mktemp -d)"; trap 'rm -rf "$STAGE"' EXIT -# No --prefix: honor the configured /usr so the staged tree matches the baked ExecStart. -DESTDIR="$STAGE" cmake --install "$BUILD" >/dev/null - -fail=0 +STAGE="${1:?staged installation root}" for f in \ usr/bin/mqproxy \ usr/lib/systemd/system/mqproxy-server@.service \ @@ -29,21 +11,18 @@ for f in \ usr/share/doc/mqproxy/server.conf.example \ usr/share/doc/mqproxy/client.conf.example \ usr/share/doc/mqproxy/LICENSE \ + usr/share/doc/mqproxy/NOTICE \ usr/share/doc/mqproxy/third-party/xquic.txt \ usr/share/doc/mqproxy/third-party/boringssl.txt \ - usr/share/doc/mqproxy/third-party/nghttp2.txt ; do - [ -e "$STAGE/$f" ] || { echo "MISSING: $f"; fail=1; } + usr/share/doc/mqproxy/third-party/RUST-DEPENDENCIES.txt; do + test -s "$STAGE/$f" || { echo "MISSING: $f"; exit 1; } done - -# Self-containment: must not need libxquic. (libssl/libcrypto/libcurl/libevent are -# legitimate distro deps — do NOT broaden this grep to match them.) -if ldd "$STAGE/usr/bin/mqproxy" | grep -qi xquic; then - echo "FAIL: staged binary links libxquic (expected static)"; fail=1 +# These are linked in-process or replaced by Rust libraries. +if readelf -d "$STAGE/usr/bin/mqproxy" | grep -Ei 'NEEDED.*(xquic|ssl|crypto|curl|event|nghttp2)'; then + echo 'FAIL: unexpected shared protocol library'; exit 1 fi - -grep -q '^ExecStart=/usr/bin/mqproxy server ' \ - "$STAGE/usr/lib/systemd/system/mqproxy-server@.service" \ - || { echo "FAIL: server ExecStart not substituted to /usr"; fail=1; } - -[ "$fail" -eq 0 ] && echo "OK: packaging install verified" -exit "$fail" +for mode in server client; do + grep -q "^ExecStart=/usr/bin/mqproxy $mode " "$STAGE/usr/lib/systemd/system/mqproxy-$mode@.service" +done +bash "$(dirname "$0")/../test_cli_help.sh" "$STAGE/usr/bin/mqproxy" +echo 'OK: Rust packaging install verified' diff --git a/tests/test_backoff.c b/tests/test_backoff.c deleted file mode 100644 index 6bbfb7e5..00000000 --- a/tests/test_backoff.c +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "util/mq_backoff.h" -#include "mqtest.h" - -#define BASE 250u -#define CAP 30000u - -/* ---- 1. Doubling sequence (small attempts, production values) ------------ */ -static void -test_doubling_sequence(void) -{ - MQ_CHECK_EQ_INT(mq_backoff_ms(BASE, CAP, 0), 250LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(BASE, CAP, 1), 500LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(BASE, CAP, 2), 1000LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(BASE, CAP, 3), 2000LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(BASE, CAP, 4), 4000LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(BASE, CAP, 5), 8000LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(BASE, CAP, 6), 16000LL); -} - -/* ---- 2. Saturation: value must be capped at CAP once doubling exceeds it - */ -static void -test_saturates_at_cap(void) -{ - /* attempt=7 → 250 << 7 = 32000 > 30000 → saturated */ - MQ_CHECK(mq_backoff_ms(BASE, CAP, 7) == CAP); - MQ_CHECK(mq_backoff_ms(BASE, CAP, 8) == CAP); - MQ_CHECK(mq_backoff_ms(BASE, CAP, 10) == CAP); -} - -/* ---- 3. No shift-overflow at attempt=31 and attempt=64 ------------------- */ -static void -test_no_overflow_large_attempts(void) -{ - /* shift is clamped to 31; 250 << 31 = 536870912000 >> CAP → cap */ - MQ_CHECK(mq_backoff_ms(BASE, CAP, 31) == CAP); - MQ_CHECK(mq_backoff_ms(BASE, CAP, 64) == CAP); -} - -/* ---- 4. Parametric: generic base/cap cross-check ------------------------- */ -static void -test_generic(void) -{ - /* base=1, cap=8: sequence 1,2,4,8,8,8 */ - MQ_CHECK_EQ_INT(mq_backoff_ms(1, 8, 0), 1LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(1, 8, 1), 2LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(1, 8, 2), 4LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(1, 8, 3), 8LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(1, 8, 4), 8LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(1, 8, 31), 8LL); - - /* base == cap: always returns cap regardless of attempt */ - MQ_CHECK_EQ_INT(mq_backoff_ms(100, 100, 0), 100LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(100, 100, 5), 100LL); - MQ_CHECK_EQ_INT(mq_backoff_ms(100, 100, 31), 100LL); -} - -MQ_TEST_MAIN({ - test_doubling_sequence(); - test_saturates_at_cap(); - test_no_overflow_large_attempts(); - test_generic(); -}) diff --git a/tests/test_buf.c b/tests/test_buf.c deleted file mode 100644 index d1bbb714..00000000 --- a/tests/test_buf.c +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "util/mq_buf.h" -#include "mqtest.h" -#include - -/* ---- helpers to avoid preprocessor comma splitting in MQ_TEST_MAIN ---- */ - -static void -test_size_constant(void) -{ - /* Verify MQ_BUF_SIZE is exactly 65536 */ - MQ_CHECK_EQ_INT(MQ_BUF_SIZE, 65536); -} - -static void -test_fresh_buf(void) -{ - mq_buf_t b; - mq_buf_reset(&b); - MQ_CHECK_EQ_INT(mq_buf_len(&b), 0); - MQ_CHECK_EQ_INT(mq_buf_space(&b), 65536); -} - -static void -test_write_and_read(void) -{ - mq_buf_t b; - mq_buf_reset(&b); - - /* write 100 bytes via write_ptr + commit */ - static const uint8_t src[100] = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, - 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100}; - - memcpy(mq_buf_write_ptr(&b), src, 100); - mq_buf_commit(&b, 100); - - MQ_CHECK_EQ_INT(mq_buf_len(&b), 100); - MQ_CHECK_EQ_INT(mq_buf_space(&b), 65436); - MQ_CHECK_MEM(mq_buf_read_ptr(&b), src, 100); -} - -static void -test_consume_partial(void) -{ - mq_buf_t b; - mq_buf_reset(&b); - - static const uint8_t src[100] = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, - 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100}; - - memcpy(mq_buf_write_ptr(&b), src, 100); - mq_buf_commit(&b, 100); - - /* consume 40: 60 readable remain, no compaction */ - mq_buf_consume(&b, 40); - MQ_CHECK_EQ_INT(mq_buf_len(&b), 60); - /* space is still 65436 (tail space, not mid-buffer) */ - MQ_CHECK_EQ_INT(mq_buf_space(&b), 65436); - /* remaining readable bytes are src[40..99] */ - MQ_CHECK_MEM(mq_buf_read_ptr(&b), src + 40, 60); -} - -static void -test_consume_all_compacts(void) -{ - mq_buf_t b; - mq_buf_reset(&b); - - static const uint8_t src[100] = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, - 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100}; - - memcpy(mq_buf_write_ptr(&b), src, 100); - mq_buf_commit(&b, 100); - - mq_buf_consume(&b, 40); - /* consume the remaining 60 → full drain → compact */ - mq_buf_consume(&b, 60); - MQ_CHECK_EQ_INT(mq_buf_len(&b), 0); - MQ_CHECK_EQ_INT(mq_buf_space(&b), 65536); /* full space after compaction */ -} - -static void -test_fill_to_capacity(void) -{ - mq_buf_t b; - mq_buf_reset(&b); - - /* commit the full 65536 bytes */ - mq_buf_commit(&b, 65536); - MQ_CHECK_EQ_INT(mq_buf_len(&b), 65536); - MQ_CHECK_EQ_INT(mq_buf_space(&b), 0); - - /* consume all → compact */ - mq_buf_consume(&b, 65536); - MQ_CHECK_EQ_INT(mq_buf_len(&b), 0); - MQ_CHECK_EQ_INT(mq_buf_space(&b), 65536); -} - -MQ_TEST_MAIN({ - test_size_constant(); - test_fresh_buf(); - test_write_and_read(); - test_consume_partial(); - test_consume_all_compacts(); - test_fill_to_capacity(); -}) diff --git a/tests/test_cli_help.sh b/tests/test_cli_help.sh index 6d9a47ca..0711f851 100755 --- a/tests/test_cli_help.sh +++ b/tests/test_cli_help.sh @@ -39,7 +39,7 @@ rc=$? [ "$rc" -eq 0 ] || fail "'client --help' exited $rc (want 0)" for flag in "--server" "--token" "--socks5" "--http-connect" "--gateway" "--path" \ "--keepalive-idle" "--reconnect" "--no-reconnect" "--reconnect-max-backoff" \ - "--metrics-interval" "--config" \ + "--metrics-interval" "--config" "--instance-id" \ "--mitm" "--ca-cert" "--ca-key" "--ignore-host" "--ignore-hosts"; do echo "$out" | grep -q -- "$flag" || fail "'client --help' output missing '$flag'" done @@ -51,7 +51,7 @@ out=$("$BIN" server --help 2>&1) rc=$? [ "$rc" -eq 0 ] || fail "'server --help' exited $rc (want 0)" for flag in "--listen" "--token" "--origin-ca" "--no-gateway" "--udp-idle-timeout" "--no-udp" \ - "--metrics-interval" "--config"; do + "--metrics-interval" "--config" "--instance-id"; do echo "$out" | grep -q -- "$flag" || fail "'server --help' output missing '$flag'" done diff --git a/tests/test_clienthello.c b/tests/test_clienthello.c deleted file mode 100644 index aa378bfb..00000000 --- a/tests/test_clienthello.c +++ /dev/null @@ -1,436 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// Unit tests for mq_clienthello_parse — the TLS ClientHello SNI/ALPN peek -// parser. Input here is ATTACKER-CONTROLLED network data: every case that is -// not a well-formed ClientHello must hard-fail (NOT_TLS / INVALID) or ask for -// more bytes (NEED_MORE), and NONE may read past `len` (ASan gate). -#include "mqtest.h" -#include "ingress/mq_clienthello.h" -#include -#include - -// --------------------------------------------------------------------------- -// ClientHello byte-array builder (test-only). Builds a single TLS plaintext -// record carrying one ClientHello handshake, with optional SNI + ALPN. -// --------------------------------------------------------------------------- - -typedef struct { - uint8_t buf[2048]; - size_t len; -} chb_t; - -static void -chb_u8(chb_t *b, uint8_t v) -{ - b->buf[b->len++] = v; -} - -static void -chb_u16(chb_t *b, uint16_t v) -{ - b->buf[b->len++] = (uint8_t)(v >> 8); - b->buf[b->len++] = (uint8_t)(v & 0xff); -} - -static void -chb_bytes(chb_t *b, const uint8_t *p, size_t n) -{ - memcpy(b->buf + b->len, p, n); - b->len += n; -} - -// Build the extensions block body (without the outer u16 length) into `ext`. -// want_sni: 0 = omit, 1 = include server_name=host -// alpn_mode: 0 = omit ALPN, 1 = ALPN with h2 + http/1.1, 2 = ALPN http/1.1 only -static void -build_extensions(chb_t *ext, int want_sni, const char *host, int alpn_mode) -{ - if (want_sni) { - size_t hlen = strlen(host); - // server_name extension (type 0x0000) - chb_u16(ext, 0x0000); - // ext_data: server_name_list (u16) { name_type(u8)=0 + host_name(u16) host } - uint16_t list_len = (uint16_t)(1 + 2 + hlen); - chb_u16(ext, (uint16_t)(2 + list_len)); // ext_data length - chb_u16(ext, list_len); // server_name_list length - chb_u8(ext, 0x00); // name_type = host_name - chb_u16(ext, (uint16_t)hlen); // host_name length - chb_bytes(ext, (const uint8_t *)host, hlen); - } - if (alpn_mode != 0) { - // application_layer_protocol_negotiation extension (type 0x0010 = 16) - chb_u16(ext, 0x0010); - chb_t list; - list.len = 0; - if (alpn_mode == 1) { - chb_u8(&list, 2); - chb_bytes(&list, (const uint8_t *)"h2", 2); - chb_u8(&list, 8); - chb_bytes(&list, (const uint8_t *)"http/1.1", 8); - } else { // alpn_mode == 2: http/1.1 only - chb_u8(&list, 8); - chb_bytes(&list, (const uint8_t *)"http/1.1", 8); - } - // ext_data: ProtocolNameList length (u16) + list - chb_u16(ext, (uint16_t)(2 + list.len)); - chb_u16(ext, (uint16_t)list.len); - chb_bytes(ext, list.buf, list.len); - } -} - -// Build the raw handshake message (4-byte handshake header + ClientHello body) -// into `hs`, WITHOUT the surrounding TLS record. Used by both the single-record -// builder and the multi-record (fragmented) builders below. -static void -build_handshake(chb_t *hs, int want_sni, const char *host, int alpn_mode) -{ - chb_t body; - body.len = 0; - chb_u16(&body, 0x0303); // legacy_version TLS 1.2 - static const uint8_t random32[32] = {0}; - chb_bytes(&body, random32, 32); // random - chb_u8(&body, 0x00); // session_id length = 0 - chb_u16(&body, 0x0002); // cipher_suites length = 2 - chb_u16(&body, 0x1301); // TLS_AES_128_GCM_SHA256 - chb_u8(&body, 0x01); // compression_methods length = 1 - chb_u8(&body, 0x00); // null compression - - chb_t ext; - ext.len = 0; - build_extensions(&ext, want_sni, host, alpn_mode); - chb_u16(&body, (uint16_t)ext.len); // extensions length - chb_bytes(&body, ext.buf, ext.len); - - hs->len = 0; - chb_u8(hs, 0x01); // msg_type = ClientHello - chb_u8(hs, (uint8_t)((body.len >> 16) & 0xff)); - chb_u8(hs, (uint8_t)((body.len >> 8) & 0xff)); - chb_u8(hs, (uint8_t)(body.len & 0xff)); - chb_bytes(hs, body.buf, body.len); -} - -// Wrap a raw byte slice into a single TLS record of the given content_type and -// append it to `out`. -static void -append_record(chb_t *out, uint8_t content_type, const uint8_t *p, size_t n) -{ - chb_u8(out, content_type); - chb_u16(out, 0x0301); // legacy record version - chb_u16(out, (uint16_t)n); - chb_bytes(out, p, n); -} - -// Build a full record+handshake ClientHello. Returns it in `out`. -static void -build_clienthello(chb_t *out, int want_sni, const char *host, int alpn_mode) -{ - chb_t hs; - build_handshake(&hs, want_sni, host, alpn_mode); - // Single TLS handshake record carrying the whole ClientHello. - out->len = 0; - append_record(out, 0x16, hs.buf, hs.len); -} - -// --------------------------------------------------------------------------- -// Cases -// --------------------------------------------------------------------------- - -static void -test_good_sni_and_h2(void) -{ - chb_t b; - build_clienthello(&b, 1, "example.com", 1); - mq_clienthello_t out; - memset(&out, 0xff, sizeof(out)); - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_OK); - MQ_CHECK(strcmp(out.sni, "example.com") == 0); - MQ_CHECK_EQ_INT(out.has_alpn, 1); - MQ_CHECK_EQ_INT(out.has_h2, 1); -} - -static void -test_alpn_without_h2(void) -{ - chb_t b; - build_clienthello(&b, 1, "example.com", 2); // http/1.1 only - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_OK); - MQ_CHECK(strcmp(out.sni, "example.com") == 0); - MQ_CHECK_EQ_INT(out.has_alpn, 1); - MQ_CHECK_EQ_INT(out.has_h2, 0); -} - -static void -test_sni_absent(void) -{ - chb_t b; - build_clienthello(&b, 0, NULL, 1); // no SNI, has ALPN h2 - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_OK); - MQ_CHECK_EQ_INT((int)strlen(out.sni), 0); - MQ_CHECK_EQ_INT(out.has_alpn, 1); - MQ_CHECK_EQ_INT(out.has_h2, 1); -} - -static void -test_no_alpn(void) -{ - chb_t b; - build_clienthello(&b, 1, "host.test", 0); // SNI present, no ALPN - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_OK); - MQ_CHECK(strcmp(out.sni, "host.test") == 0); - MQ_CHECK_EQ_INT(out.has_alpn, 0); - MQ_CHECK_EQ_INT(out.has_h2, 0); -} - -static void -test_truncated_prefix(void) -{ - // A valid ClientHello but only the first 20 bytes delivered → NEED_MORE. - chb_t b; - build_clienthello(&b, 1, "example.com", 1); - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(b.buf, 20, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_NEED_MORE); -} - -static void -test_empty(void) -{ - // Zero bytes → not enough to even read the record header → NEED_MORE. - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse((const uint8_t *)"", 0, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_NEED_MORE); -} - -static void -test_not_tls(void) -{ - // First byte != 0x16 (here an HTTP/1.1 plaintext GET) → NOT_TLS. - const uint8_t http[] = "GET / HTTP/1.1\r\nHost: x\r\n\r\n"; - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(http, sizeof(http) - 1, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_NOT_TLS); -} - -static void -test_sslv2_garbage(void) -{ - // SSLv2-style ClientHello starts with a high bit set length, not 0x16. - const uint8_t sslv2[] = {0x80, 0x2e, 0x01, 0x00, 0x02, 0x00, 0x15}; - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(sslv2, sizeof(sslv2), &out); - MQ_CHECK_EQ_INT(r, MQ_CH_NOT_TLS); -} - -static void -test_handshake_not_clienthello(void) -{ - // Valid record header, complete fragment, but msg_type != 0x01 (here 0x02 - // ServerHello) → structurally impossible for our peek → INVALID. - chb_t b; - b.len = 0; - chb_u8(&b, 0x16); - chb_u16(&b, 0x0301); - chb_u16(&b, 0x0004); // fragment length = 4 - chb_u8(&b, 0x02); // ServerHello - chb_u8(&b, 0x00); - chb_u8(&b, 0x00); - chb_u8(&b, 0x00); // 24-bit handshake length = 0 - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_INVALID); -} - -static void -test_ext_length_overflow(void) -{ - // Build a good ClientHello, then corrupt the extensions length to claim far - // more bytes than are present. Must NEVER OOB-read; result is NEED_MORE - // (claimed inner length runs past `len`) — never OK, never a crash. - chb_t b; - build_clienthello(&b, 1, "example.com", 1); - // The record fragment length is bytes [3..4]; inflate it so the handshake - // claims more than we have, but keep `len` short. - b.buf[3] = 0xff; - b.buf[4] = 0xff; - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK(r == MQ_CH_NEED_MORE || r == MQ_CH_INVALID); -} - -static void -test_inner_ext_overruns_complete_block(void) -{ - // A FULLY-PRESENT, single-record ClientHello whose inner extensions-block - // length field overruns the (complete) handshake body → structurally - // impossible → INVALID (no OOB). This is an overrun WITHIN a complete - // enclosing field, which reassembly does not turn into NEED_MORE: there are - // no missing bytes, the framing is self-contradictory. - chb_t b; - build_clienthello(&b, 1, "example.com", 1); - // Compute the extensions-block u16 length offset exactly. Within the body the - // fixed prefix is legacy_version(2) + random(32) + sid_len(1) + cs_len(2) + - // cs(2) + cm_len(1) + cm(1) = 41 bytes, then the extensions-block u16 length. - // The body itself starts at record_hdr(5) + handshake_hdr(4) = 9. - const size_t ext_total_off = 9 + 41; - MQ_CHECK(ext_total_off + 1 < b.len); - // Inflate it so it claims far more than the handshake body can hold — but keep - // every byte of the buffer present (record + handshake fully complete). - b.buf[ext_total_off] = 0xff; - b.buf[ext_total_off + 1] = 0xff; - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_INVALID); -} - -static void -test_long_sni_rejected(void) -{ - // host_name > 253 chars → SNI skipped (out.sni empty), parse still OK. - char host[300]; - memset(host, 'a', 254); - host[254] = '\0'; - chb_t b; - build_clienthello(&b, 1, host, 1); - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_OK); - MQ_CHECK_EQ_INT((int)strlen(out.sni), 0); // over-long → skipped, empty - MQ_CHECK_EQ_INT(out.has_h2, 1); -} - -static void -test_len_over_cap_rejected(void) -{ - // Top-level DoS guard: any buffer larger than the 8 KiB cap is rejected - // outright (INVALID), before any record parsing — len == 8193 is one over. - // The record header declares a fragment far larger than the buffer, so - // WITHOUT the top-level cap this would parse to NEED_MORE; the cap must - // turn it into INVALID. First byte is a valid handshake type (not NOT_TLS). - static uint8_t big[8193]; - memset(big, 0x00, sizeof(big)); - big[0] = 0x16; // handshake content type - big[1] = 0x03; // legacy_version high - big[2] = 0x01; // legacy_version low - big[3] = 0xff; // fragment length high (0xffff >> len) - big[4] = 0xff; // fragment length low - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(big, sizeof(big), &out); - MQ_CHECK_EQ_INT(r, MQ_CH_INVALID); -} - -static void -test_record_frag_over_cap_rejected(void) -{ - // A valid 5-byte record header whose declared fragment length makes - // 5 + frag_len > 8192, with only the header actually present. The cap - // rejects (INVALID) BEFORE reassembly — it must NOT degrade to NEED_MORE. - // frag_len = 8188 → 5 + 8188 = 8193 > 8192. - uint8_t hdr[5]; - hdr[0] = 0x16; // handshake - hdr[1] = 0x03; // legacy_version high - hdr[2] = 0x01; // legacy_version low - hdr[3] = (uint8_t)(8188 >> 8); // frag_len high - hdr[4] = (uint8_t)(8188 & 0xff); // frag_len low - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(hdr, sizeof(hdr), &out); - MQ_CHECK_EQ_INT(r, MQ_CH_INVALID); -} - -static void -test_split_record_first_half_need_more(void) -{ - // RFC 8446 §5.1: a handshake message MAY be fragmented across multiple TLS - // handshake records. Build the ClientHello, then carry only its FIRST half in - // record 1. The 24-bit handshake length declares more than record 1 carries, - // so the parser must ask for more — NOT reject as INVALID. - chb_t hs; - build_handshake(&hs, 1, "split.example.com", 1); - size_t half = hs.len / 2; - MQ_CHECK(half > 4 && half < hs.len); // ensure a real boundary mid-body - chb_t b; - b.len = 0; - append_record(&b, 0x16, hs.buf, half); - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_NEED_MORE); -} - -static void -test_split_record_both_halves_ok(void) -{ - // Same fragmented ClientHello, now both records present (handshake split - // across a record boundary). The parser must reassemble across the boundary - // and extract SNI + has_h2 correctly. The split point lands mid-body so the - // extensions (and thus SNI/ALPN) straddle the two records. - chb_t hs; - build_handshake(&hs, 1, "split.example.com", 1); - size_t half = hs.len / 2; - chb_t b; - b.len = 0; - append_record(&b, 0x16, hs.buf, half); // record 1 - append_record(&b, 0x16, hs.buf + half, hs.len - half); // record 2 - mq_clienthello_t out; - memset(&out, 0xff, sizeof(out)); - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_OK); - MQ_CHECK(strcmp(out.sni, "split.example.com") == 0); - MQ_CHECK_EQ_INT(out.has_alpn, 1); - MQ_CHECK_EQ_INT(out.has_h2, 1); -} - -static void -test_split_record_many_fragments_ok(void) -{ - // Pathological-but-legal fragmentation: chop the handshake into 1-byte - // records. Reassembly must still recover the complete ClientHello. - chb_t hs; - build_handshake(&hs, 1, "tiny.example.com", 1); - chb_t b; - b.len = 0; - for (size_t i = 0; i < hs.len; i++) { - append_record(&b, 0x16, hs.buf + i, 1); - } - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_OK); - MQ_CHECK(strcmp(out.sni, "tiny.example.com") == 0); - MQ_CHECK_EQ_INT(out.has_h2, 1); -} - -static void -test_interleaved_non_handshake_record_invalid(void) -{ - // A non-handshake record (content_type 23 = application_data) interleaved - // BETWEEN handshake records while the ClientHello is still incomplete is a - // protocol violation → INVALID (never NEED_MORE, never an OOB read). - chb_t hs; - build_handshake(&hs, 1, "evil.example.com", 1); - size_t half = hs.len / 2; - chb_t b; - b.len = 0; - append_record(&b, 0x16, hs.buf, half); // handshake, partial - const uint8_t junk[4] = {0xde, 0xad, 0xbe, 0xef}; - append_record(&b, 0x17, junk, sizeof(junk)); // application_data mid-handshake - append_record(&b, 0x16, hs.buf + half, hs.len - half); // continuation - mq_clienthello_t out; - mq_ch_result_t r = mq_clienthello_parse(b.buf, b.len, &out); - MQ_CHECK_EQ_INT(r, MQ_CH_INVALID); -} - -MQ_TEST_MAIN(test_good_sni_and_h2(); test_alpn_without_h2(); test_sni_absent(); - test_no_alpn(); test_truncated_prefix(); test_empty(); test_not_tls(); - test_sslv2_garbage(); test_handshake_not_clienthello(); - test_ext_length_overflow(); test_inner_ext_overruns_complete_block(); - test_long_sni_rejected(); test_len_over_cap_rejected(); - test_record_frag_over_cap_rejected(); - test_split_record_first_half_need_more(); test_split_record_both_halves_ok(); - test_split_record_many_fragments_ok(); - test_interleaved_non_handshake_record_invalid();) diff --git a/tests/test_config.c b/tests/test_config.c deleted file mode 100644 index f9bdb7bc..00000000 --- a/tests/test_config.c +++ /dev/null @@ -1,266 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -#include "config/mq_config.h" -#include "mqtest.h" -#include -#include -#include -#include -#include - -/* write text to a fresh temp file via mkstemp; fills path_out (template form) */ -static void -write_tmp(char *path_out, size_t n, const char *text) -{ - snprintf(path_out, n, "/tmp/mqcfg_test_XXXXXX"); - int fd = mkstemp(path_out); - MQ_CHECK(fd >= 0); - FILE *f = fdopen(fd, "w"); - MQ_CHECK(f != NULL); - fputs(text, f); - fclose(f); -} - -static void -test_defaults(void) -{ - mq_file_config_t c; - mq_config_defaults(&c); - MQ_CHECK_EQ_INT(c.max_conns, 16); /* numeric default */ - MQ_CHECK(strcmp(c.client_id, "mqproxy") == 0); /* string default */ - MQ_CHECK_EQ_INT(c.n_paths, 0); /* repeatable empty */ -} - -static void -test_server_roundtrip(void) -{ - char p[64]; - write_tmp(p, sizeof(p), - "[Interface]\n" - "Listen = 0.0.0.0:4433\n" - "MaxConns = 64\n" - "[TLS]\n" - "Cert = /e/c.pem\n" - "Key = /e/c.key\n" - "[Auth]\n" - "Key = s3cr3t\n" - "[Multipath]\n" - "CC = bbr\n" - "Scheduler = minrtt\n" - "[Log]\n" - "QLog = /tmp/q\n"); - mq_file_config_t c; - mq_config_defaults(&c); - MQ_CHECK_EQ_INT(mq_config_load(&c, p, 1 /*server*/), 0); - MQ_CHECK(strcmp(c.listen, "0.0.0.0:4433") == 0); - MQ_CHECK(strcmp(c.qlog, "/tmp/q") == 0); - MQ_CHECK_EQ_INT(c.max_conns, 64); - MQ_CHECK(strcmp(c.cert, "/e/c.pem") == 0); - MQ_CHECK(strcmp(c.key, "/e/c.key") == 0); - MQ_CHECK(strcmp(c.token, "s3cr3t") == 0); - MQ_CHECK(strcmp(c.cc, "bbr") == 0); - MQ_CHECK(strcmp(c.scheduler, "minrtt") == 0); - remove(p); -} - -static void -test_client_roundtrip(void) -{ - char p[64]; - /* bracketed IPv6 Address exercises the scanner's bracket+colon handling - * (spec §7 IPv6 round-trip; parse_ip_port validation happens later in CLI) */ - write_tmp(p, sizeof(p), - "[Server]\nAddress = [2001:db8::1]:443\nClientId = edge\n" - "[Auth]\nKey = tok\n" - "[Ingress]\nSocks5 = 127.0.0.1:1080\n" - "TProxy = 127.0.0.1:8443\nMode = tproxy\nFwmark = 7\nTable = 200\n" - "Dport = 8080\nSetupRedirect = true\nSkipUid = 1000\n" - "[Interface]\nReconnect = false\nKeepaliveIdle = 15\n" - "[Multipath]\nPath = 10.0.0.1\nPath = 10.0.0.2\n"); - mq_file_config_t c; - mq_config_defaults(&c); - MQ_CHECK_EQ_INT(mq_config_load(&c, p, 0 /*client*/), 0); - MQ_CHECK(strcmp(c.server_addr, "[2001:db8::1]:443") == 0); - MQ_CHECK(strcmp(c.client_id, "edge") == 0); - MQ_CHECK(strcmp(c.socks5, "127.0.0.1:1080") == 0); - /* [Ingress] transparent-capture keys (Phase 7 Slice 1) round-trip. */ - MQ_CHECK(strcmp(c.tproxy, "127.0.0.1:8443") == 0); - MQ_CHECK(strcmp(c.tproxy_mode, "tproxy") == 0); - MQ_CHECK_EQ_INT(c.tproxy_fwmark, 7); - MQ_CHECK_EQ_INT(c.tproxy_table, 200); - MQ_CHECK_EQ_INT(c.tproxy_dport, 8080); - MQ_CHECK_EQ_INT(c.setup_redirect, 1); - MQ_CHECK_EQ_INT(c.tproxy_skip_uid, 1000); - MQ_CHECK_EQ_INT(c.reconnect, 0); - MQ_CHECK_EQ_INT(c.keepalive_idle_s, 15); - MQ_CHECK_EQ_INT(c.n_paths, 2); - MQ_CHECK(strcmp(c.paths[0], "10.0.0.1") == 0); - MQ_CHECK(strcmp(c.paths[1], "10.0.0.2") == 0); - remove(p); -} - -static void -test_bool_variants(void) -{ - char p[64]; - write_tmp(p, sizeof(p), - "[Gateway]\nEnabled = yes\nMasquerade = true\n[UDP]\nEnabled = 0\n"); - mq_file_config_t c; - mq_config_defaults(&c); - MQ_CHECK_EQ_INT(c.masquerade, 0); /* default off (memset) */ - mq_config_load(&c, p, 1); - MQ_CHECK_EQ_INT(c.gateway_enabled, 1); /* yes */ - MQ_CHECK_EQ_INT(c.masquerade, 1); /* true */ - MQ_CHECK_EQ_INT(c.udp_enabled, 0); /* 0 = off */ - remove(p); -} - -static void -test_path_cap(void) -{ - char p[64]; - char big[512] = "[Multipath]\n"; - for (int i = 0; i < 10; i++) - strcat(big, "Path = x\n"); /* 10 > cap 8 */ - write_tmp(p, sizeof(p), big); - mq_file_config_t c; - mq_config_defaults(&c); - mq_config_load(&c, p, 0); - MQ_CHECK_EQ_INT(c.n_paths, MQ_CONFIG_MAX_PATHS); /* capped at 8 */ - remove(p); -} - -static void -test_lenient_and_comments(void) -{ - char p[64]; - write_tmp( - p, sizeof(p), - "# comment\n; also comment\n" - "[Interface]\nMaxConns = notanumber\n" /* bad value -> keep default 16 */ - "[Bogus]\nFoo = bar\n" /* unknown section -> warn */ - "[Auth]\nUnknownKey = x\nKey =\n" /* unknown key -> warn; empty Key ignored */ - "[TLS]\nCert = /c.pem\n"); /* a real value still applies */ - mq_file_config_t c; - mq_config_defaults(&c); - MQ_CHECK_EQ_INT(mq_config_load(&c, p, 1), 0); /* readable -> 0 despite warns */ - MQ_CHECK_EQ_INT(c.max_conns, 16); /* bad value kept default */ - MQ_CHECK(c.token[0] == '\0'); /* empty value ignored, not "" set */ - MQ_CHECK(strcmp(c.cert, "/c.pem") == 0); - remove(p); -} - -static void -test_partial_file_keeps_defaults(void) -{ - /* defaults < file: keys absent from the file retain mq_config_defaults values. - * Falsifiable, pure — covers the precedence floor without exec'ing the binary. */ - char p[64]; - write_tmp(p, sizeof(p), "[Interface]\nListen = :4433\n"); /* only Listen set */ - mq_file_config_t c; - mq_config_defaults(&c); - mq_config_load(&c, p, 1); - MQ_CHECK(strcmp(c.listen, ":4433") == 0); /* file value applied */ - MQ_CHECK_EQ_INT(c.max_conns, 16); /* untouched -> default */ - MQ_CHECK_EQ_INT(c.gateway_enabled, 1); /* untouched -> default */ - remove(p); -} - -static void -test_perms_warning(void) -{ - char p[64]; - write_tmp(p, sizeof(p), "[Auth]\nKey = tok\n"); - chmod(p, 0644); - MQ_CHECK_EQ_INT(mq_config_perms_insecure(p), 1); /* group/world readable */ - chmod(p, 0600); - MQ_CHECK_EQ_INT(mq_config_perms_insecure(p), 0); /* owner-only */ - MQ_CHECK_EQ_INT(mq_config_perms_insecure("/no/such/file"), 0); /* can't stat */ - remove(p); -} - -static void -test_mitm_section(void) -{ - /* [Mitm] is client-only; Enabled bool + CACert/CAKey paths + repeatable - * IgnoreHosts (mirrors [Multipath] Path accumulation). */ - char p[64]; - write_tmp(p, sizeof(p), - "[Mitm]\n" - "Enabled = true\n" - "CACert = /c\n" - "CAKey = /k\n" - "IgnoreHosts = .apple.com\n" - "IgnoreHosts = signal.org\n"); - mq_file_config_t c; - mq_config_defaults(&c); - MQ_CHECK_EQ_INT(c.mitm_enabled, 0); /* default off (memset) */ - MQ_CHECK_EQ_INT(mq_config_load(&c, p, 0 /*client*/), 0); - MQ_CHECK_EQ_INT(c.mitm_enabled, 1); - MQ_CHECK(strcmp(c.ca_cert, "/c") == 0); - MQ_CHECK(strcmp(c.ca_key, "/k") == 0); - MQ_CHECK_EQ_INT(c.n_ignore_hosts, 2); - MQ_CHECK(strcmp(c.ignore_hosts[0], ".apple.com") == 0); - MQ_CHECK(strcmp(c.ignore_hosts[1], "signal.org") == 0); - remove(p); -} - -static void -test_mitm_server_mode_ignored(void) -{ - /* server-mode load warns + ignores all [Mitm] keys (client-only). */ - char p[64]; - write_tmp(p, sizeof(p), - "[Mitm]\n" - "Enabled = true\n" - "CACert = /c\n" - "CAKey = /k\n" - "IgnoreHosts = .apple.com\n"); - mq_file_config_t c; - mq_config_defaults(&c); - MQ_CHECK_EQ_INT(mq_config_load(&c, p, 1 /*server*/), 0); - MQ_CHECK_EQ_INT(c.mitm_enabled, 0); /* ignored */ - MQ_CHECK(c.ca_cert[0] == '\0'); - MQ_CHECK(c.ca_key[0] == '\0'); - MQ_CHECK_EQ_INT(c.n_ignore_hosts, 0); - remove(p); -} - -static void -test_mitm_enabled_missing_cacert(void) -{ - /* Enabled=true with no CACert: config layer keeps ca_cert empty (no error - * here — the --mitm-requires-CA check is enforced in CLI, Task 15). */ - char p[64]; - write_tmp(p, sizeof(p), "[Mitm]\nEnabled = true\n"); - mq_file_config_t c; - mq_config_defaults(&c); - MQ_CHECK_EQ_INT(mq_config_load(&c, p, 0 /*client*/), 0); - MQ_CHECK_EQ_INT(c.mitm_enabled, 1); - MQ_CHECK(c.ca_cert[0] == '\0'); /* surfaced as empty; CLI enforces */ - MQ_CHECK(c.ca_key[0] == '\0'); - remove(p); -} - -static void -test_missing_file_fatal(void) -{ - mq_file_config_t c; - mq_config_defaults(&c); - MQ_CHECK_EQ_INT(mq_config_load(&c, "/no/such/file.conf", 1), -1); -} - -MQ_TEST_MAIN({ - test_defaults(); - test_server_roundtrip(); - test_client_roundtrip(); - test_bool_variants(); - test_path_cap(); - test_lenient_and_comments(); - test_partial_file_keeps_defaults(); - test_mitm_section(); - test_mitm_server_mode_ignored(); - test_mitm_enabled_missing_cacert(); - test_perms_warning(); - test_missing_file_fatal(); -}) diff --git a/tests/test_defrag.c b/tests/test_defrag.c deleted file mode 100644 index e1760933..00000000 --- a/tests/test_defrag.c +++ /dev/null @@ -1,560 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "proxy/mq_defrag.h" -#include "wire/mq_udp_msg.h" -#include "mqtest.h" -#include -#include - -/* Helper: build a header with frag_count==1 (single unfragmented packet). */ -static mq_udp_msg_hdr_t -make_hdr(uint16_t packet_id, uint8_t frag_id, uint8_t frag_count) -{ - mq_udp_msg_hdr_t h; - h.session_id = 1; - h.packet_id = packet_id; - h.flags = 0; - h.frag_id = frag_id; - h.frag_count = frag_count; - return h; -} - -/* ---- 1. Single-frag passthrough ----------------------------------------- */ -static void -test_single_frag_passthrough(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - uint8_t payload[] = {0x01, 0x02, 0x03}; - mq_udp_msg_hdr_t h = make_hdr(10, 0, 1); - - uint8_t *out = NULL; - size_t out_len = 0; - int r = mq_defrag_feed(d, &h, payload, sizeof payload, &out, &out_len); - MQ_CHECK_EQ_INT(r, 1); - MQ_CHECK(out != NULL); - MQ_CHECK_EQ_INT((long long)out_len, 3LL); - MQ_CHECK_MEM(out, payload, 3); - free(out); - - mq_defrag_free(d); -} - -/* ---- 2. 2-frag assembly (in-order) -------------------------------------- */ -static void -test_two_frag_in_order(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - uint8_t f0[] = {0xAA, 0xBB}; - uint8_t f1[] = {0xCC, 0xDD, 0xEE}; - uint8_t expected[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE}; - - mq_udp_msg_hdr_t h0 = make_hdr(20, 0, 2); - mq_udp_msg_hdr_t h1 = make_hdr(20, 1, 2); - - uint8_t *out = NULL; - size_t out_len = 0; - - int r = mq_defrag_feed(d, &h0, f0, sizeof f0, &out, &out_len); - MQ_CHECK_EQ_INT(r, 0); - - r = mq_defrag_feed(d, &h1, f1, sizeof f1, &out, &out_len); - MQ_CHECK_EQ_INT(r, 1); - MQ_CHECK(out != NULL); - MQ_CHECK_EQ_INT((long long)out_len, 5LL); - MQ_CHECK_MEM(out, expected, 5); - free(out); - - mq_defrag_free(d); -} - -/* ---- 3. 2-frag assembly (reverse order) --------------------------------- */ -static void -test_two_frag_reverse_order(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - uint8_t f0[] = {0x11, 0x22}; - uint8_t f1[] = {0x33, 0x44}; - uint8_t expected[] = {0x11, 0x22, 0x33, 0x44}; - - mq_udp_msg_hdr_t h0 = make_hdr(30, 0, 2); - mq_udp_msg_hdr_t h1 = make_hdr(30, 1, 2); - - uint8_t *out = NULL; - size_t out_len = 0; - - /* Feed frag 1 first */ - int r = mq_defrag_feed(d, &h1, f1, sizeof f1, &out, &out_len); - MQ_CHECK_EQ_INT(r, 0); - - /* Then frag 0 completes */ - r = mq_defrag_feed(d, &h0, f0, sizeof f0, &out, &out_len); - MQ_CHECK_EQ_INT(r, 1); - MQ_CHECK(out != NULL); - MQ_CHECK_EQ_INT((long long)out_len, 4LL); - MQ_CHECK_MEM(out, expected, 4); - free(out); - - mq_defrag_free(d); -} - -/* ---- 4. Two packet_ids interleaved -------------------------------------- */ -static void -test_two_packet_ids_interleaved(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - /* pkt A: id=40, 2 frags; pkt B: id=41, 2 frags. Feed interleaved. */ - uint8_t a0[] = {0xA0}; - uint8_t a1[] = {0xA1}; - uint8_t b0[] = {0xB0}; - uint8_t b1[] = {0xB1}; - - mq_udp_msg_hdr_t ha0 = make_hdr(40, 0, 2); - mq_udp_msg_hdr_t ha1 = make_hdr(40, 1, 2); - mq_udp_msg_hdr_t hb0 = make_hdr(41, 0, 2); - mq_udp_msg_hdr_t hb1 = make_hdr(41, 1, 2); - - uint8_t *out = NULL; - size_t out_len = 0; - - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &ha0, a0, 1, &out, &out_len), 0); - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &hb0, b0, 1, &out, &out_len), 0); - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &ha1, a1, 1, &out, &out_len), 1); - MQ_CHECK(out_len == 2 && out[0] == 0xA0 && out[1] == 0xA1); - free(out); - out = NULL; - - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &hb1, b1, 1, &out, &out_len), 1); - MQ_CHECK(out_len == 2 && out[0] == 0xB0 && out[1] == 0xB1); - free(out); - - mq_defrag_free(d); -} - -/* ---- 5. 5 distinct packet_ids → LRU evict ------------------------------ */ -static void -test_lru_evict(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - /* Feed frag 0 of 5 different packet_ids (2-frag each). - * The 5th packet_id must evict the LRU slot (the one for packet_id 0). - * After the eviction, the slot for id=0 is gone; feeding its frag 1 - * should allocate a *new* slot (returns 0, not complete yet). */ - uint8_t payload[] = {0xFF}; - uint8_t *out = NULL; - size_t out_len = 0; - - /* Fill 4 slots: ids 100..103 */ - for (uint16_t id = 100; id < 104; id++) { - mq_udp_msg_hdr_t h = make_hdr(id, 0, 2); - int r = mq_defrag_feed(d, &h, payload, 1, &out, &out_len); - MQ_CHECK_EQ_INT(r, 0); - } - - /* Touch id=101 to make it recently used (so id=100 remains LRU) */ - { - mq_udp_msg_hdr_t h = make_hdr(101, 0, 2); /* duplicate frag → 0 */ - int r = mq_defrag_feed(d, &h, payload, 1, &out, &out_len); - MQ_CHECK_EQ_INT(r, 0); /* duplicate ignored, still 0 */ - } - - /* Feed id=104 → must evict LRU (id=100). Returns 0 (incomplete). */ - { - mq_udp_msg_hdr_t h = make_hdr(104, 0, 2); - int r = mq_defrag_feed(d, &h, payload, 1, &out, &out_len); - MQ_CHECK_EQ_INT(r, 0); - } - - /* Complete id=104 → should work (the slot is present) */ - { - mq_udp_msg_hdr_t h = make_hdr(104, 1, 2); - int r = mq_defrag_feed(d, &h, payload, 1, &out, &out_len); - MQ_CHECK_EQ_INT(r, 1); - MQ_CHECK(out != NULL && out_len == 2); - free(out); - out = NULL; - } - - /* id=100 slot was evicted. Sending frag 1 of id=100 should start a - * *new* slot (returns 0, can't complete with just frag 1 of 2). */ - { - mq_udp_msg_hdr_t h = make_hdr(100, 1, 2); - int r = mq_defrag_feed(d, &h, payload, 1, &out, &out_len); - MQ_CHECK_EQ_INT(r, 0); /* new slot, not complete */ - } - - mq_defrag_free(d); -} - -/* ---- 6. Duplicate frag ignored (return 0) ------------------------------ */ -static void -test_duplicate_frag_ignored(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - uint8_t payload[] = {0x55}; - mq_udp_msg_hdr_t h = make_hdr(50, 0, 3); - - uint8_t *out = NULL; - size_t out_len = 0; - - /* First feed: accepted */ - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h, payload, 1, &out, &out_len), 0); - /* Duplicate: silently ignored, not an error */ - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h, payload, 1, &out, &out_len), 0); - /* Another duplicate */ - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h, payload, 1, &out, &out_len), 0); - - mq_defrag_free(d); -} - -/* ---- 7. frag_count mismatch → slot drop (return -1) ------------------- */ -static void -test_frag_count_mismatch_drops_slot(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - uint8_t payload[] = {0x01}; - uint8_t *out = NULL; - size_t out_len = 0; - - /* First frag: frag_count=3 → slot created */ - mq_udp_msg_hdr_t h1 = make_hdr(60, 0, 3); - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h1, payload, 1, &out, &out_len), 0); - - /* Second frag: frag_count=2 (mismatch) → -1, slot dropped */ - mq_udp_msg_hdr_t h2 = make_hdr(60, 1, 2); - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h2, payload, 1, &out, &out_len), -1); - - /* Slot should now be gone; a new frag for id=60 starts fresh (returns 0) */ - mq_udp_msg_hdr_t h3 = make_hdr(60, 0, 2); - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h3, payload, 1, &out, &out_len), 0); - - mq_defrag_free(d); -} - -/* ---- 8. frag_id >= frag_count → drop (return -1) ---------------------- */ -static void -test_frag_id_out_of_range(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - uint8_t payload[] = {0x01}; - uint8_t *out = NULL; - size_t out_len = 0; - - /* frag_id == frag_count (== 2) → invalid */ - mq_udp_msg_hdr_t h = make_hdr(70, 2, 2); - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h, payload, 1, &out, &out_len), -1); - - /* frag_id > frag_count */ - mq_udp_msg_hdr_t h2 = make_hdr(71, 5, 2); - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h2, payload, 1, &out, &out_len), -1); - - mq_defrag_free(d); -} - -/* ---- 9. frag_count == 0 → drop (return -1) ----------------------------- */ -static void -test_frag_count_zero_rejected(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - uint8_t payload[] = {0x01}; - uint8_t *out = NULL; - size_t out_len = 0; - - mq_udp_msg_hdr_t h = make_hdr(80, 0, 0); - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h, payload, 1, &out, &out_len), -1); - - mq_defrag_free(d); -} - -/* ---- 10. Reassembled > 65535 → slot drop (return -1) ------------------- */ -static void -test_reassembled_too_large(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - /* 2-frag packet: each frag is 32768 bytes → total 65536 > 65535. */ - static uint8_t big[32768]; - memset(big, 0xAB, sizeof big); - - mq_udp_msg_hdr_t h0 = make_hdr(90, 0, 2); - mq_udp_msg_hdr_t h1 = make_hdr(90, 1, 2); - - uint8_t *out = NULL; - size_t out_len = 0; - - int r0 = mq_defrag_feed(d, &h0, big, sizeof big, &out, &out_len); - /* First frag (32768) is below 65535; slot accepted → 0 */ - MQ_CHECK_EQ_INT(r0, 0); - - /* Second frag would push total to 65536 → slot dropped → -1 */ - int r1 = mq_defrag_feed(d, &h1, big, sizeof big, &out, &out_len); - MQ_CHECK_EQ_INT(r1, -1); - - mq_defrag_free(d); -} - -/* ---- 11. packet_id wrap: completed id reused as new packet -------------- */ -static void -test_packet_id_wrap(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - uint8_t payload_a[] = {0xAA}; - uint8_t payload_b[] = {0xBB}; - uint8_t *out = NULL; - size_t out_len = 0; - - /* First use of packet_id=200: single-frag → completes immediately. */ - mq_udp_msg_hdr_t h1 = make_hdr(200, 0, 1); - int r = mq_defrag_feed(d, &h1, payload_a, 1, &out, &out_len); - MQ_CHECK_EQ_INT(r, 1); - MQ_CHECK(out != NULL && out_len == 1 && out[0] == 0xAA); - free(out); - out = NULL; - - /* Second use of packet_id=200: new single-frag (slot was released) → 1. */ - mq_udp_msg_hdr_t h2 = make_hdr(200, 0, 1); - r = mq_defrag_feed(d, &h2, payload_b, 1, &out, &out_len); - MQ_CHECK_EQ_INT(r, 1); - MQ_CHECK(out != NULL && out_len == 1 && out[0] == 0xBB); - free(out); - - mq_defrag_free(d); -} - -/* ---- 12. Single-frag zero-length payload -------------------------------- */ -static void -test_single_frag_zero_len(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - /* len==0 single-frag: returns 1, *out non-NULL (freeable), out_len==0 */ - uint8_t dummy[1] = {0}; - mq_udp_msg_hdr_t h = make_hdr(300, 0, 1); - uint8_t *out = NULL; - size_t out_len = 99; /* sentinel */ - - int r = mq_defrag_feed(d, &h, dummy, 0, &out, &out_len); - MQ_CHECK_EQ_INT(r, 1); - MQ_CHECK(out != NULL); /* must be freeable */ - MQ_CHECK_EQ_INT((long long)out_len, 0LL); - free(out); - - mq_defrag_free(d); -} - -/* ---- T1. Payload-copy proof ---------------------------------------------- */ -/* Feed frag 0, clobber the source buffer, feed frag 1, clobber that buffer - * too, then verify the assembled output still has the ORIGINAL bytes. */ -static void -test_payload_copy_proof(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - uint8_t src0[] = {0x11, 0x22, 0x33}; - uint8_t src1[] = {0x44, 0x55, 0x66}; - uint8_t expected[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; - - mq_udp_msg_hdr_t h0 = make_hdr(400, 0, 2); - mq_udp_msg_hdr_t h1 = make_hdr(400, 1, 2); - - uint8_t *out = NULL; - size_t out_len = 0; - - /* Feed frag 0, then clobber the source buffer */ - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h0, src0, sizeof src0, &out, &out_len), 0); - memset(src0, 0xFF, sizeof src0); - - /* Feed frag 1 (completing), then clobber that buffer too */ - int r = mq_defrag_feed(d, &h1, src1, sizeof src1, &out, &out_len); - memset(src1, 0xFF, sizeof src1); - - MQ_CHECK_EQ_INT(r, 1); - MQ_CHECK(out != NULL); - MQ_CHECK_EQ_INT((long long)out_len, 6LL); - /* Output must still contain the original bytes, not 0xFF */ - MQ_CHECK_MEM(out, expected, 6); - free(out); - - mq_defrag_free(d); -} - -/* ---- T2. LRU survivors complete with correct content --------------------- */ -/* After the 5th packet_id evicts the LRU slot (id=100), the three surviving - * non-evicted slots (ids 101, 102, 103) should each complete correctly. */ -static void -test_lru_survivors_complete(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - /* Each packet_id i gets frag 0 with payload {i_lo, i_hi}. */ - uint8_t *out = NULL; - size_t out_len = 0; - - /* Fill all 4 slots with ids 100..103, frag 0 of 2. */ - for (uint16_t id = 100; id <= 103; id++) { - uint8_t p[2] = {(uint8_t)(id & 0xFF), 0xF0}; - mq_udp_msg_hdr_t h = make_hdr(id, 0, 2); - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h, p, 2, &out, &out_len), 0); - } - - /* Touch id=101 so id=100 stays LRU. */ - { - uint8_t p[2] = {0x65, 0xF0}; /* duplicate frag → ignored */ - mq_udp_msg_hdr_t h = make_hdr(101, 0, 2); - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h, p, 2, &out, &out_len), 0); - } - - /* id=104 evicts id=100. */ - { - uint8_t p[2] = {0x68, 0xF1}; - mq_udp_msg_hdr_t h = make_hdr(104, 0, 2); - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &h, p, 2, &out, &out_len), 0); - } - - /* Complete surviving ids 101, 102, 103 and check content. */ - for (uint16_t id = 101; id <= 103; id++) { - uint8_t completing[2] = {0xA0, (uint8_t)(id & 0xFF)}; - mq_udp_msg_hdr_t h = make_hdr(id, 1, 2); - int r = mq_defrag_feed(d, &h, completing, 2, &out, &out_len); - MQ_CHECK_EQ_INT(r, 1); - MQ_CHECK(out != NULL); - MQ_CHECK_EQ_INT((long long)out_len, 4LL); - /* frag 0: {id_lo, 0xF0}, frag 1: {0xA0, id_lo} */ - uint8_t expected[4] = {(uint8_t)(id & 0xFF), 0xF0, 0xA0, (uint8_t)(id & 0xFF)}; - MQ_CHECK_MEM(out, expected, 4); - free(out); - out = NULL; - } - - mq_defrag_free(d); -} - -/* ---- T3. packet_id reuse via multi-frag ---------------------------------- */ -/* Same packet_id X completes a 2-frag assembly, then is reused for a NEW - * 2-frag assembly with DIFFERENT content and completes correctly. */ -static void -test_packet_id_wrap_multi_frag(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - uint8_t *out = NULL; - size_t out_len = 0; - const uint16_t PID = 500; - - /* First assembly with packet_id=PID */ - uint8_t a0[] = {0x01, 0x02}; - uint8_t a1[] = {0x03, 0x04}; - uint8_t expected_a[] = {0x01, 0x02, 0x03, 0x04}; - - mq_udp_msg_hdr_t ha0 = make_hdr(PID, 0, 2); - mq_udp_msg_hdr_t ha1 = make_hdr(PID, 1, 2); - - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &ha0, a0, sizeof a0, &out, &out_len), 0); - int r = mq_defrag_feed(d, &ha1, a1, sizeof a1, &out, &out_len); - MQ_CHECK_EQ_INT(r, 1); - MQ_CHECK(out != NULL); - MQ_CHECK_EQ_INT((long long)out_len, 4LL); - MQ_CHECK_MEM(out, expected_a, 4); - free(out); - out = NULL; - - /* Second assembly with same packet_id=PID, different content */ - uint8_t b0[] = {0xAA, 0xBB, 0xCC}; - uint8_t b1[] = {0xDD, 0xEE}; - uint8_t expected_b[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE}; - - mq_udp_msg_hdr_t hb0 = make_hdr(PID, 0, 2); - mq_udp_msg_hdr_t hb1 = make_hdr(PID, 1, 2); - - MQ_CHECK_EQ_INT(mq_defrag_feed(d, &hb0, b0, sizeof b0, &out, &out_len), 0); - r = mq_defrag_feed(d, &hb1, b1, sizeof b1, &out, &out_len); - MQ_CHECK_EQ_INT(r, 1); - MQ_CHECK(out != NULL); - MQ_CHECK_EQ_INT((long long)out_len, 5LL); - MQ_CHECK_MEM(out, expected_b, 5); - free(out); - - mq_defrag_free(d); -} - -/* ---- T4. Exactly-65535 bytes → accepted ---------------------------------- */ -/* A 2-frag reassembly whose frags total exactly 65535 bytes must be ACCEPTED - * (65536 is already tested as a rejection boundary). */ -static void -test_reassembled_exactly_65535(void) -{ - mq_defrag_t *d = mq_defrag_new(); - MQ_CHECK(d != NULL); - - /* frag 0: 32767 bytes; frag 1: 32768 bytes → total 65535 */ - static uint8_t f0[32767]; - static uint8_t f1[32768]; - memset(f0, 0x5A, sizeof f0); - memset(f1, 0xA5, sizeof f1); - - mq_udp_msg_hdr_t h0 = make_hdr(600, 0, 2); - mq_udp_msg_hdr_t h1 = make_hdr(600, 1, 2); - - uint8_t *out = NULL; - size_t out_len = 0; - - int r0 = mq_defrag_feed(d, &h0, f0, sizeof f0, &out, &out_len); - MQ_CHECK_EQ_INT(r0, 0); - - int r1 = mq_defrag_feed(d, &h1, f1, sizeof f1, &out, &out_len); - MQ_CHECK_EQ_INT(r1, 1); /* must be accepted, not rejected */ - MQ_CHECK(out != NULL); - MQ_CHECK_EQ_INT((long long)out_len, 65535LL); - /* Spot-check: first and last bytes of each segment */ - MQ_CHECK_EQ_INT((long long)out[0], 0x5ALL); - MQ_CHECK_EQ_INT((long long)out[32766], 0x5ALL); - MQ_CHECK_EQ_INT((long long)out[32767], 0xA5LL); - MQ_CHECK_EQ_INT((long long)out[65534], 0xA5LL); - free(out); - - mq_defrag_free(d); -} - -MQ_TEST_MAIN({ - test_single_frag_passthrough(); - test_two_frag_in_order(); - test_two_frag_reverse_order(); - test_two_packet_ids_interleaved(); - test_lru_evict(); - test_duplicate_frag_ignored(); - test_frag_count_mismatch_drops_slot(); - test_frag_id_out_of_range(); - test_frag_count_zero_rejected(); - test_reassembled_too_large(); - test_packet_id_wrap(); - test_single_frag_zero_len(); - test_payload_copy_proof(); - test_lru_survivors_complete(); - test_packet_id_wrap_multi_frag(); - test_reassembled_exactly_65535(); -}) diff --git a/tests/test_gw_cache.c b/tests/test_gw_cache.c deleted file mode 100644 index 363a5579..00000000 --- a/tests/test_gw_cache.c +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include "gateway/mq_gw_cache.h" -#include "mqtest.h" -#include - -static const mq_gw_cache_hdr_t H1[] = {{"content-type", "text/plain"}}; - -static void -test_insert_lookup_hit(void) -{ - mq_gw_cache_t *c = mq_gw_cache_new(1 << 20, 1 << 20); - MQ_CHECK(c != NULL); - MQ_CHECK_EQ_INT(mq_gw_cache_insert(c, "GET http://o/a", 200, H1, 1, - (const uint8_t *)"hello", 5, 10000, 0), - 0); - const mq_gw_cache_entry_t *e = - mq_gw_cache_lookup(c, "GET http://o/a", 5000); /* fresh */ - MQ_CHECK(e != NULL); - MQ_CHECK_EQ_INT(e->status, 200); - MQ_CHECK_EQ_INT((int)e->body_len, 5); - MQ_CHECK_EQ_INT(memcmp(e->body, "hello", 5), 0); - MQ_CHECK(mq_gw_cache_lookup(c, "GET http://o/MISSING", 5000) == NULL); - mq_gw_cache_free(c); -} - -static void -test_expiry(void) -{ - mq_gw_cache_t *c = mq_gw_cache_new(1 << 20, 1 << 20); - mq_gw_cache_insert(c, "GET http://o/a", 200, H1, 1, (const uint8_t *)"x", 1, 1000, 0); - MQ_CHECK(mq_gw_cache_lookup(c, "GET http://o/a", 999) != NULL); /* fresh < ttl */ - MQ_CHECK(mq_gw_cache_lookup(c, "GET http://o/a", 1000) == - NULL); /* expired (now>=stored+ttl) */ - MQ_CHECK(mq_gw_cache_lookup(c, "GET http://o/a", 2000) == NULL); /* and dropped */ - mq_gw_cache_free(c); -} - -static void -test_lru_evict(void) -{ - /* small cap: each ~ key+body fits 2 but not 3 */ - mq_gw_cache_t *c = mq_gw_cache_new(64, 64); - mq_gw_cache_insert(c, "GET http://o/aaaaaaaa", 200, NULL, 0, - (const uint8_t *)"123456", 6, 100000, 0); - mq_gw_cache_insert(c, "GET http://o/bbbbbbbb", 200, NULL, 0, - (const uint8_t *)"123456", 6, 100000, 0); - /* touch a → MRU */ - MQ_CHECK(mq_gw_cache_lookup(c, "GET http://o/aaaaaaaa", 1) != NULL); - /* insert c → evicts LRU (b), keeps a */ - mq_gw_cache_insert(c, "GET http://o/cccccccc", 200, NULL, 0, - (const uint8_t *)"123456", 6, 100000, 0); - MQ_CHECK(mq_gw_cache_lookup(c, "GET http://o/aaaaaaaa", 1) != - NULL); /* survived (was MRU) */ - MQ_CHECK(mq_gw_cache_lookup(c, "GET http://o/bbbbbbbb", 1) == NULL); /* evicted */ - mq_gw_cache_free(c); -} - -static void -test_per_object_cap(void) -{ - mq_gw_cache_t *c = mq_gw_cache_new(1 << 20, 8 /* tiny per-obj */); - MQ_CHECK_EQ_INT(mq_gw_cache_insert(c, "GET http://o/a", 200, NULL, 0, - (const uint8_t *)"way too big body", 16, 1000, 0), - -1); /* refused */ - MQ_CHECK(mq_gw_cache_lookup(c, "GET http://o/a", 1) == NULL); - mq_gw_cache_free(c); -} - -static void -test_disabled(void) -{ - MQ_CHECK(mq_gw_cache_new(0, 0) == NULL); /* 0 total ⇒ disabled */ -} - -static void -test_replace_dup_key(void) -{ - mq_gw_cache_t *c = mq_gw_cache_new(1 << 20, 1 << 20); - mq_gw_cache_insert(c, "GET http://o/a", 200, NULL, 0, (const uint8_t *)"old", 3, - 100000, 0); - mq_gw_cache_insert(c, "GET http://o/a", 200, NULL, 0, (const uint8_t *)"newer", 5, - 100000, 0); - const mq_gw_cache_entry_t *e = mq_gw_cache_lookup(c, "GET http://o/a", 1); - MQ_CHECK(e != NULL && e->body_len == 5 && memcmp(e->body, "newer", 5) == 0); - mq_gw_cache_free(c); -} - -MQ_TEST_MAIN({ - test_insert_lookup_hit(); - test_expiry(); - test_lru_evict(); - test_per_object_cap(); - test_disabled(); - test_replace_dup_key(); -}) diff --git a/tests/test_gw_h2_adapter.c b/tests/test_gw_h2_adapter.c deleted file mode 100644 index bb009d88..00000000 --- a/tests/test_gw_h2_adapter.c +++ /dev/null @@ -1,1549 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors - -/* test_gw_h2_adapter.c — Phase 7 MITM Slice 3 Task 5. - * - * Drives a real nghttp2 CLIENT session through an in-test memory pipe against - * the mq_gw_h2_adapter SERVER session and asserts the SETTINGS handshake - * completes and the server advertised the §5.2 resource limits. - * - * Wire shuttle (no sockets, no TLS): - * client nghttp2_session_mem_send -> adapter mq_gw_h2_adapter_recv - * adapter send_cb buffer -> client nghttp2_session_mem_recv - * - * We assert the limits from the CLIENT's perspective: after the client has - * processed the server's SETTINGS frame, nghttp2_session_get_remote_settings on - * the CLIENT returns exactly what the SERVER advertised — the real wire - * behavior. The adapter binds a CAPTURING STUB vtable (no tunnel); the skeleton - * does no request handling, so the stub ops are never invoked here. */ - -#include -#include -#include /* strtol */ -#include -#include /* strcasecmp / strncasecmp */ - -#include "gateway/mq_gw_h2_adapter.h" -#include "mqtest.h" - -/* §5.2 NORMATIVE limits the adapter must advertise. */ -#define EXP_MAX_CONCURRENT_STREAMS 128u -#define EXP_MAX_FRAME_SIZE 16384u -#define EXP_HEADER_TABLE_SIZE 4096u -#define EXP_MAX_HEADER_LIST_SIZE 16384u - -/* ── adapter send_cb: capture the server's outbound plaintext ────────────────*/ - -typedef struct { - uint8_t buf[256 * 1024]; /* large enough to hold a full flush incl. a 64 KiB - * response body + framing in one want_write pass */ - size_t len; -} sink_buf_t; - -static ssize_t -adapter_send(void *io, const uint8_t *p, size_t n) -{ - sink_buf_t *s = (sink_buf_t *)io; - if (s->len + n > sizeof(s->buf)) return -1; - memcpy(s->buf + s->len, p, n); - s->len += n; - return (ssize_t)n; -} - -/* ── capturing stub submit vtable (never called by the skeleton) ─────────────*/ - -typedef struct { - int prevalidate_calls; - int req_begin_calls; -} stub_state_t; - -static mq_gw_reject_reason_t -stub_prevalidate(void *u, const mq_h3_header_t *h, size_t n, int *status) -{ - stub_state_t *st = (stub_state_t *)u; - (void)h; - (void)n; - (void)status; - st->prevalidate_calls++; - return MQ_GW_OK; -} - -static mq_gw_xreq_t * -stub_req_begin(void *u, const mq_gw_req_head_t *head, const mq_gw_sink_ops_t *sink, - void *sink_user, int *err_status, mq_gw_reject_reason_t *reason) -{ - stub_state_t *st = (stub_state_t *)u; - (void)head; - (void)sink; - (void)sink_user; - (void)err_status; - (void)reason; - st->req_begin_calls++; - return NULL; -} - -static int -stub_req_body(mq_gw_xreq_t *r, const uint8_t *p, size_t len) -{ - (void)r; - (void)p; - (void)len; - return 0; -} -static void -stub_req_body_done(mq_gw_xreq_t *r) -{ - (void)r; -} -static void -stub_req_drained(mq_gw_xreq_t *r) -{ - (void)r; -} -static void -stub_req_aborted(mq_gw_xreq_t *r) -{ - (void)r; -} - -static const char * -stub_auth_token(void *u) -{ - (void)u; - return "test-token"; -} - -static const mq_gw_submit_ops_t g_stub_ops = { - .prevalidate = stub_prevalidate, - .req_begin = stub_req_begin, - .req_body = stub_req_body, - .req_body_done = stub_req_body_done, - .req_drained = stub_req_drained, - .req_aborted = stub_req_aborted, - .auth_token = stub_auth_token, -}; - -/* ── capturing stub for the Task 6 demux tests ─────────────────────────────── - * - * Records the FINAL request head the adapter hands to the boundary: the - * pseudo-headers and a DEEP COPY of the final header list (browser headers minus - * X-Mq-*, PLUS the injected controls). Deep copy is mandatory — the adapter's - * per-stream arena is freed on stream close, so the captured pointers would - * dangle otherwise. auth_token returns "tok" → injected value must be - * "Bearer tok" (codex H3). */ - -#define CAP_MAX_HDRS 64 -#define CAP_STR 512 - -typedef struct { - int prevalidate_calls; - int req_begin_calls; - - /* captured final head (deep copy) */ - char method[CAP_STR], scheme[CAP_STR], authority[CAP_STR], path[CAP_STR]; - char names[CAP_MAX_HDRS][CAP_STR]; - char values[CAP_MAX_HDRS][CAP_STR]; - size_t n_hdrs; - int64_t content_length; - - /* what prevalidate observed (header count) — proves the strip ran first. */ - int prevalidate_saw_browser_xmq; /* a browser x-mq-* survived into prevalidate */ - - /* injectable prevalidate verdict (default OK). */ - mq_gw_reject_reason_t prevalidate_verdict; - int prevalidate_status; - - /* Task 7: captured sink + sink_user + the fake xreq handle, so a test acting - * as the core can drive the response path. */ - const mq_gw_sink_ops_t *sink; - void *sink_user; - mq_gw_xreq_t *xreq; /* the sentinel we returned from req_begin */ - int req_drained_calls; /* how many times the adapter called req_drained */ - - /* Task 8: request-body upload capture + injectable pause. */ - uint8_t up_body[256 * 1024]; /* uploaded bytes, in delivery order */ - size_t up_len; - int req_body_done_calls; /* req_body_done fired (stream end) */ - /* When >0, req_body returns -1 (pause) ONCE after up_len reaches/exceeds this - * many bytes, then clears the trip so the resumed feed is accepted. Models a - * stalled tunnel that backpressures the browser, then drains. */ - size_t pause_after; - int paused_once; /* the single pause has fired (no further pausing) */ - int req_body_calls; - - /* UAF-hardening regression (Slice 3): how many times the adapter called - * req_aborted (on_stream_close / adapter_free "local gone" signal). A stream - * that completed cleanly (resp_finish ran → the adapter NULLed its borrowed - * xreq) MUST NOT be aborted at teardown — this counter asserts that. */ - int req_aborted_calls; -} cap_state_t; - -static int -cap_has_xmq_other_than_auth_fwd(const mq_h3_header_t *h, size_t n) -{ - /* Any x-mq-* that is NOT the two injected controls counts as a survivor. */ - for (size_t i = 0; i < n; i++) { - const char *nm = h[i].name; - if (strncasecmp(nm, "x-mq-", 5) != 0) continue; - if (strcasecmp(nm, "x-mq-auth") == 0) continue; - if (strcasecmp(nm, "x-mq-forward-cookie") == 0) continue; - return 1; - } - return 0; -} - -static mq_gw_reject_reason_t -cap_prevalidate(void *u, const mq_h3_header_t *h, size_t n, int *status) -{ - cap_state_t *st = (cap_state_t *)u; - st->prevalidate_calls++; - /* Detect a SURVIVING browser x-mq-* (other than the two injected) OR a - * duplicate x-mq-auth — both would be a §4.5 violation. */ - if (cap_has_xmq_other_than_auth_fwd(h, n)) st->prevalidate_saw_browser_xmq = 1; - int auth_seen = 0; - for (size_t i = 0; i < n; i++) - if (strcasecmp(h[i].name, "x-mq-auth") == 0) auth_seen++; - if (auth_seen > 1) st->prevalidate_saw_browser_xmq = 1; /* dup-control surface */ - if (status) - *status = st->prevalidate_verdict == MQ_GW_OK ? 200 : st->prevalidate_status; - return st->prevalidate_verdict; -} - -static void -cap_copy(char *dst, const char *src) -{ - size_t i = 0; - for (; src && src[i] && i < CAP_STR - 1; i++) - dst[i] = src[i]; - dst[i] = '\0'; -} - -static mq_gw_xreq_t * -cap_req_begin(void *u, const mq_gw_req_head_t *head, const mq_gw_sink_ops_t *sink, - void *sink_user, int *err_status, mq_gw_reject_reason_t *reason) -{ - cap_state_t *st = (cap_state_t *)u; - (void)err_status; - (void)reason; - st->req_begin_calls++; - /* Task 7: stash the sink + sink_user so a core-role test can drive the - * response path; the adapter passes the per-stream context as sink_user. */ - st->sink = sink; - st->sink_user = sink_user; - cap_copy(st->method, head->method); - cap_copy(st->scheme, head->scheme); - cap_copy(st->authority, head->authority); - cap_copy(st->path, head->path); - st->content_length = head->content_length; - st->n_hdrs = 0; - for (size_t i = 0; i < head->n_headers && st->n_hdrs < CAP_MAX_HDRS; i++) { - cap_copy(st->names[st->n_hdrs], head->headers[i].name); - cap_copy(st->values[st->n_hdrs], head->headers[i].value); - st->n_hdrs++; - } - /* Return a non-NULL sentinel so the adapter stores it as the stream's xreq. - * For the Task 7 response test the adapter passes this same handle back to - * req_drained, so the core-role test can observe download backpressure - * resume. The header-only Task 6 tests never dereference it. */ - st->xreq = (mq_gw_xreq_t *)st; - return st->xreq; -} - -static int -cap_req_body(mq_gw_xreq_t *r, const uint8_t *p, size_t len) -{ - cap_state_t *st = (cap_state_t *)r; /* xreq sentinel == cap_state_t* */ - st->req_body_calls++; - /* Capture the bytes IN ORDER (the contract: bytes are consumed even when we - * return -1/pause, so we always record them — never re-deliver). */ - if (st->up_len + len <= sizeof(st->up_body)) { - memcpy(st->up_body + st->up_len, p, len); - st->up_len += len; - } - /* Injectable single pause: once the upload reaches pause_after bytes, return - * -1 (pause) EXACTLY once. The resumed feed must then be accepted (0). */ - if (st->pause_after > 0 && !st->paused_once && st->up_len >= st->pause_after) { - st->paused_once = 1; - return -1; /* pause: stop feeding more body until resume_read */ - } - return 0; /* accepted; keep reading */ -} -static void -cap_req_body_done(mq_gw_xreq_t *r) -{ - cap_state_t *st = (cap_state_t *)r; - st->req_body_done_calls++; -} -static void -cap_req_drained(mq_gw_xreq_t *r) -{ - /* The adapter passes back the xreq sentinel == the cap_state_t*. Count the - * resume so the Task 7 backpressure test can assert the download pump is - * re-kicked once the H2 send buffer drains below the per-stream cap. */ - cap_state_t *st = (cap_state_t *)r; - if (st) st->req_drained_calls++; -} -static void -cap_req_aborted(mq_gw_xreq_t *r) -{ - /* The adapter passes back the xreq sentinel == the cap_state_t*. Count the - * abort so the UAF-hardening test can assert a cleanly-finished stream is - * NEVER aborted (its xreq was NULLed by resp_finish). */ - cap_state_t *st = (cap_state_t *)r; - if (st) st->req_aborted_calls++; -} -static const char * -cap_auth_token(void *u) -{ - (void)u; - return "tok"; /* injected value must be "Bearer tok" (codex H3) */ -} - -static const mq_gw_submit_ops_t g_cap_ops = { - .prevalidate = cap_prevalidate, - .req_begin = cap_req_begin, - .req_body = cap_req_body, - .req_body_done = cap_req_body_done, - .req_drained = cap_req_drained, - .req_aborted = cap_req_aborted, - .auth_token = cap_auth_token, -}; - -/* Variant whose configured token is ALREADY "Bearer "-prefixed. The adapter must - * STRIP the leading "Bearer " before re-prefixing, so the injected x-mq-auth is - * EXACTLY "Bearer tok" — never "Bearer Bearer tok" (codex H3 dedup branch). */ -static const char * -cap_auth_token_prefixed(void *u) -{ - (void)u; - return "Bearer tok"; -} - -static const mq_gw_submit_ops_t g_cap_ops_prefixed = { - .prevalidate = cap_prevalidate, - .req_begin = cap_req_begin, - .req_body = cap_req_body, - .req_body_done = cap_req_body_done, - .req_drained = cap_req_drained, - .req_aborted = cap_req_aborted, - .auth_token = cap_auth_token_prefixed, -}; - -/* Look up a captured final header by name (case-insensitive). Returns value or - * NULL; *count receives how many times the name appears. */ -static const char * -cap_find(const cap_state_t *st, const char *name, int *count) -{ - const char *v = NULL; - int c = 0; - for (size_t i = 0; i < st->n_hdrs; i++) { - if (strcasecmp(st->names[i], name) == 0) { - if (!v) v = st->values[i]; - c++; - } - } - if (count) *count = c; - return v; -} - -/* ── client send_cb: capture the client's outbound plaintext ─────────────────*/ - -static ssize_t -client_send(nghttp2_session *s, const uint8_t *data, size_t length, int flags, - void *user_data) -{ - sink_buf_t *cb = (sink_buf_t *)user_data; - (void)s; - (void)flags; - if (cb->len + length > sizeof(cb->buf)) return NGHTTP2_ERR_CALLBACK_FAILURE; - memcpy(cb->buf + cb->len, data, length); - cb->len += length; - return (ssize_t)length; -} - -/* ── the test ────────────────────────────────────────────────────────────────*/ - -static void -test_settings_handshake(void) -{ - sink_buf_t srv_out = {0}; /* bytes the adapter (server) wants to send */ - stub_state_t stub = {0}; - - mq_gw_h2_adapter_t *a = - mq_gw_h2_adapter_new(&g_stub_ops, &stub, adapter_send, &srv_out); - MQ_CHECK(a != NULL); - if (!a) return; - - /* Real nghttp2 CLIENT session. */ - nghttp2_session_callbacks *cbs = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs), 0); - nghttp2_session_callbacks_set_send_callback(cbs, client_send); - - sink_buf_t cli_out = {0}; /* bytes the client wants to send */ - nghttp2_session *cli = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli, cbs, &cli_out), 0); - nghttp2_session_callbacks_del(cbs); - - /* Client submits its own SETTINGS + a simple GET to drive the connection. */ - nghttp2_settings_entry iv[] = { - {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, - }; - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli, NGHTTP2_FLAG_NONE, iv, 1), 0); - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"GET", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"example.com", 10, 11, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - }; - int sid = nghttp2_submit_request(cli, NULL, nva, 4, NULL, NULL); - MQ_CHECK(sid > 0); - - /* Shuttle a few rounds: client -> adapter -> client. */ - for (int round = 0; round < 4; round++) { - /* Drive client output. */ - cli_out.len = 0; - MQ_CHECK_EQ_INT(nghttp2_session_send(cli), 0); - if (cli_out.len > 0) { - MQ_CHECK_EQ_INT(mq_gw_h2_adapter_recv(a, cli_out.buf, cli_out.len), 0); - } - - /* Drive adapter output. */ - srv_out.len = 0; - MQ_CHECK_EQ_INT(mq_gw_h2_adapter_want_write(a), 0); - if (srv_out.len > 0) { - ssize_t r = nghttp2_session_mem_recv(cli, srv_out.buf, srv_out.len); - MQ_CHECK(r >= 0 && (size_t)r == srv_out.len); - } - } - - /* From the CLIENT's view, remote_settings == what the SERVER advertised. */ - uint32_t mcs = - nghttp2_session_get_remote_settings(cli, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); - uint32_t mfs = - nghttp2_session_get_remote_settings(cli, NGHTTP2_SETTINGS_MAX_FRAME_SIZE); - uint32_t hts = - nghttp2_session_get_remote_settings(cli, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE); - /* Non-vacuous: nghttp2's default MAX_HEADER_LIST_SIZE is unlimited/large, so - * ==16384 proves the server actually advertised the §5.2 limit. */ - uint32_t mhls = - nghttp2_session_get_remote_settings(cli, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE); - - MQ_CHECK_EQ_INT(mcs, EXP_MAX_CONCURRENT_STREAMS); - MQ_CHECK_EQ_INT(mfs, EXP_MAX_FRAME_SIZE); - MQ_CHECK_EQ_INT(hts, EXP_HEADER_TABLE_SIZE); - MQ_CHECK_EQ_INT(mhls, EXP_MAX_HEADER_LIST_SIZE); - /* MAX_CONCURRENT_STREAMS must be bounded (<= 128 per §5.2). */ - MQ_CHECK(mcs <= EXP_MAX_CONCURRENT_STREAMS); - - nghttp2_session_del(cli); - mq_gw_h2_adapter_free(a); -} - -/* ── Task 6 demux / §4.5 header-policy / NUL / header-bomb tests ─────────────*/ - -/* Client-side stream-close capture: records that the server RST'd the stream and - * with which error code (NGHTTP2_NO_ERROR == clean / not reset). */ -typedef struct { - sink_buf_t out; - int closed; - uint32_t error_code; -} cli_ud_t; - -static int -cli_on_stream_close(nghttp2_session *s, int32_t sid, uint32_t error_code, void *user_data) -{ - (void)s; - (void)sid; - cli_ud_t *ud = (cli_ud_t *)user_data; - ud->closed = 1; - ud->error_code = error_code; - return 0; -} - -/* Drive one client request (nva[ncnt]) through the adapter and shuttle until - * quiescent. `submit_body` selects request-with-body (so END_STREAM is NOT on - * the HEADERS frame; for these header-only tests we use no body → END_STREAM on - * headers). Returns the client stream id (>0) or <0. Fills *adapter so the - * caller can free; fills the cli_ud_t via user_data. */ -static void -drive_request(const mq_gw_submit_ops_t *ops, void *ops_user, const nghttp2_nv *nva, - size_t ncnt, cli_ud_t *cud, int *out_sid) -{ - sink_buf_t srv_out = {0}; - mq_gw_h2_adapter_t *a = mq_gw_h2_adapter_new(ops, ops_user, adapter_send, &srv_out); - MQ_CHECK(a != NULL); - if (!a) { - *out_sid = -1; - return; - } - - nghttp2_session_callbacks *cbs = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs), 0); - nghttp2_session_callbacks_set_send_callback(cbs, client_send); - nghttp2_session_callbacks_set_on_stream_close_callback(cbs, cli_on_stream_close); - - nghttp2_session *cli = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli, cbs, cud), 0); - nghttp2_session_callbacks_del(cbs); - - nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli, NGHTTP2_FLAG_NONE, iv, 1), 0); - - int sid = nghttp2_submit_request(cli, NULL, nva, ncnt, NULL, NULL); - MQ_CHECK(sid > 0); - *out_sid = sid; - - for (int round = 0; round < 6; round++) { - cud->out.len = 0; - MQ_CHECK_EQ_INT(nghttp2_session_send(cli), 0); - if (cud->out.len > 0) { - /* recv may fail (fatal) if the adapter GOAWAYs; tolerate but stop. */ - if (mq_gw_h2_adapter_recv(a, cud->out.buf, cud->out.len) != 0) break; - } - srv_out.len = 0; - MQ_CHECK_EQ_INT(mq_gw_h2_adapter_want_write(a), 0); - if (srv_out.len > 0) { - ssize_t r = nghttp2_session_mem_recv(cli, srv_out.buf, srv_out.len); - MQ_CHECK(r >= 0 && (size_t)r == srv_out.len); - } - } - - nghttp2_session_del(cli); - mq_gw_h2_adapter_free(a); -} - -/* §4.5 happy-path: hostile browser X-Mq-* dropped; Cookie + Authorization - * forwarded; exactly one injected x-mq-auth "Bearer tok" + one - * x-mq-forward-cookie "true"; pseudo-headers mapped; no surviving control. */ -static void -test_demux_header_policy(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - cli_ud_t cud = {0}; - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"GET", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/path", 5, 5, NGHTTP2_NV_FLAG_NONE}, - /* hostile controls — must be dropped TOTALLY */ - {(uint8_t *)"x-mq-cache", (uint8_t *)"999999", 10, 6, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"x-mq-auth", (uint8_t *)"Bearer attacker", 9, 15, - NGHTTP2_NV_FLAG_NONE}, - /* real browser headers — must be forwarded */ - {(uint8_t *)"cookie", (uint8_t *)"sid=1", 6, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"authorization", (uint8_t *)"Bearer real", 13, 11, - NGHTTP2_NV_FLAG_NONE}, - }; - int sid = 0; - drive_request(&g_cap_ops, &st, nva, sizeof(nva) / sizeof(nva[0]), &cud, &sid); - - /* The request materialized exactly once. */ - MQ_CHECK_EQ_INT(st.req_begin_calls, 1); - MQ_CHECK_EQ_INT(st.prevalidate_calls, 1); - /* prevalidate must NOT have seen a surviving browser x-mq-* or a dup auth. */ - MQ_CHECK_EQ_INT(st.prevalidate_saw_browser_xmq, 0); - - /* pseudo-headers mapped. */ - MQ_CHECK(strcmp(st.method, "GET") == 0); - MQ_CHECK(strcmp(st.scheme, "https") == 0); - MQ_CHECK(strcmp(st.authority, "site.example") == 0); - MQ_CHECK(strcmp(st.path, "/path") == 0); - - /* injected controls — exactly one each, correct values. */ - int n_auth = 0, n_fwd = 0, n_cache = 0; - const char *auth = cap_find(&st, "x-mq-auth", &n_auth); - const char *fwd = cap_find(&st, "x-mq-forward-cookie", &n_fwd); - (void)cap_find(&st, "x-mq-cache", &n_cache); - MQ_CHECK_EQ_INT(n_auth, 1); - MQ_CHECK(auth && strcmp(auth, "Bearer tok") == 0); - MQ_CHECK_EQ_INT(n_fwd, 1); - MQ_CHECK(fwd && strcmp(fwd, "true") == 0); - /* No X-Mq-Cache injected, and the hostile one was dropped. */ - MQ_CHECK_EQ_INT(n_cache, 0); - - /* real browser headers forwarded verbatim. */ - int n_cookie = 0, n_authz = 0; - const char *cookie = cap_find(&st, "cookie", &n_cookie); - const char *authz = cap_find(&st, "authorization", &n_authz); - MQ_CHECK_EQ_INT(n_cookie, 1); - MQ_CHECK(cookie && strcmp(cookie, "sid=1") == 0); - MQ_CHECK_EQ_INT(n_authz, 1); - MQ_CHECK(authz && strcmp(authz, "Bearer real") == 0); - - /* No browser x-mq-* survived into the final list (only the two injected - * controls). Rebuild a temporary view from the captured names to assert. */ - { - mq_h3_header_t view[CAP_MAX_HDRS]; - for (size_t i = 0; i < st.n_hdrs; i++) { - view[i].name = st.names[i]; - view[i].value = st.values[i]; - } - MQ_CHECK_EQ_INT(cap_has_xmq_other_than_auth_fwd(view, st.n_hdrs), 0); - } -} - -/* Embedded NUL in a header value → the stream is rejected (RST), NOT truncated. - * Defense-in-depth: nghttp2's own HTTP-messaging validation (check_header_value) - * rejects the NUL at HPACK-inflate time and RSTs the stream before END_HEADERS, - * and our on_header arena_dup scan rejects it independently (codex H1) if that - * validation were ever disabled. EITHER way the request must NEVER materialize - * (no truncated-then-forwarded value reaching the strlen-based boundary). */ -static void -test_demux_embedded_nul_rejected(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - cli_ud_t cud = {0}; - - static const uint8_t badval[] = {'a', 'b', 0x00, 'c'}; - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"GET", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"x-evil", (uint8_t *)badval, 6, sizeof(badval), - NGHTTP2_NV_FLAG_NO_INDEX}, - }; - int sid = 0; - drive_request(&g_cap_ops, &st, nva, sizeof(nva) / sizeof(nva[0]), &cud, &sid); - - /* Must NOT materialize — neither truncated-then-forwarded nor accepted. */ - MQ_CHECK_EQ_INT(st.req_begin_calls, 0); -} - -/* Header-bomb: cumulative inbound header-list size > MQ_H2_MAX_HEADER_LIST_SIZE - * (16384, RFC 7540 §6.5.2 accounting: name+value+32 per field) → stream RST - * BEFORE materialization. We send one giant header value to blow the cap. */ -static void -test_demux_header_bomb_rejected(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - cli_ud_t cud = {0}; - - /* 20000-byte value > 16384 cap even before per-field overhead/name. nghttp2 - * MAX_FRAME_SIZE is 16384, so this header spans CONTINUATION frames — the - * accounting must trip mid-block. */ - static uint8_t big[20000]; - memset(big, 'x', sizeof(big)); - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"GET", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"x-big", big, 5, sizeof(big), NGHTTP2_NV_FLAG_NONE}, - }; - int sid = 0; - drive_request(&g_cap_ops, &st, nva, sizeof(nva) / sizeof(nva[0]), &cud, &sid); - - MQ_CHECK_EQ_INT(st.req_begin_calls, 0); -} - -/* prevalidate rejects (e.g. dup-control) → no req_begin, stream not materialized - * as a successful request. */ -static void -test_demux_prevalidate_reject(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_REJ_DUP_CONTROL; - st.prevalidate_status = 400; - cli_ud_t cud = {0}; - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"GET", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - }; - int sid = 0; - drive_request(&g_cap_ops, &st, nva, sizeof(nva) / sizeof(nva[0]), &cud, &sid); - - MQ_CHECK(st.prevalidate_calls >= 1); - MQ_CHECK_EQ_INT(st.req_begin_calls, 0); -} - -/* §4.5 / codex H3: when the configured auth_token already carries a "Bearer " - * prefix, the adapter strips it before re-prefixing so the injected x-mq-auth is - * EXACTLY "Bearer tok" — NOT "Bearer Bearer tok" (which would corrupt the shared - * secret). A regression that dropped the strip would yield the doubled value and - * fail the strcmp below. */ -static void -test_demux_auth_bearer_dedup(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - cli_ud_t cud = {0}; - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"GET", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - }; - int sid = 0; - drive_request(&g_cap_ops_prefixed, &st, nva, sizeof(nva) / sizeof(nva[0]), &cud, - &sid); - - /* Request materialized once and the dedup branch ran. */ - MQ_CHECK_EQ_INT(st.req_begin_calls, 1); - MQ_CHECK_EQ_INT(st.prevalidate_calls, 1); - - int n_auth = 0; - const char *auth = cap_find(&st, "x-mq-auth", &n_auth); - MQ_CHECK_EQ_INT(n_auth, 1); - MQ_CHECK(auth && strcmp(auth, "Bearer tok") == 0); -} - -/* ── Task 7: response path (sink ops → H2 HEADERS/DATA) + download backpressure ─ - * - * The §5.2 per-stream response send-queue cap the adapter enforces. resp_body - * must return 0 (highwater) once the queue is full, and call req_drained once it - * drains below the cap. Kept in lockstep with MQ_H2_RESP_QUEUE_CAP in the - * adapter. */ -#define EXP_RESP_QUEUE_CAP (64u * 1024u) - -/* Response-capturing nghttp2 CLIENT: records :status, the response headers, and - * the body bytes in arrival order for one stream. */ -typedef struct { - sink_buf_t out; /* bytes the client wants to send (driven by client_send) */ - - int status; - char names[CAP_MAX_HDRS][CAP_STR]; - char values[CAP_MAX_HDRS][CAP_STR]; - size_t n_hdrs; - - uint8_t body[256 * 1024]; - size_t body_len; - int stream_closed; -} resp_cli_t; - -static int -resp_cli_on_header(nghttp2_session *s, const nghttp2_frame *frame, const uint8_t *name, - size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, - void *user_data) -{ - (void)s; - (void)frame; - (void)flags; - resp_cli_t *rc = (resp_cli_t *)user_data; - if (namelen == 7 && memcmp(name, ":status", 7) == 0) { - rc->status = (int)strtol((const char *)value, NULL, 10); - return 0; - } - if (rc->n_hdrs < CAP_MAX_HDRS) { - size_t nl = namelen < CAP_STR - 1 ? namelen : CAP_STR - 1; - size_t vl = valuelen < CAP_STR - 1 ? valuelen : CAP_STR - 1; - memcpy(rc->names[rc->n_hdrs], name, nl); - rc->names[rc->n_hdrs][nl] = '\0'; - memcpy(rc->values[rc->n_hdrs], value, vl); - rc->values[rc->n_hdrs][vl] = '\0'; - rc->n_hdrs++; - } - return 0; -} - -static int -resp_cli_on_data_chunk(nghttp2_session *s, uint8_t flags, int32_t sid, - const uint8_t *data, size_t len, void *user_data) -{ - (void)s; - (void)flags; - (void)sid; - resp_cli_t *rc = (resp_cli_t *)user_data; - if (rc->body_len + len <= sizeof(rc->body)) { - memcpy(rc->body + rc->body_len, data, len); - rc->body_len += len; - } - return 0; -} - -static int -resp_cli_on_stream_close(nghttp2_session *s, int32_t sid, uint32_t error_code, - void *user_data) -{ - (void)s; - (void)sid; - (void)error_code; - resp_cli_t *rc = (resp_cli_t *)user_data; - rc->stream_closed = 1; - return 0; -} - -/* Find a captured response header by name (case-insensitive). */ -static const char * -resp_cli_find(const resp_cli_t *rc, const char *name) -{ - for (size_t i = 0; i < rc->n_hdrs; i++) - if (strcasecmp(rc->names[i], name) == 0) return rc->values[i]; - return NULL; -} - -/* Pump: client send -> adapter recv ; adapter want_write -> client recv. */ -static void -shuttle(mq_gw_h2_adapter_t *a, nghttp2_session *cli, resp_cli_t *rc, sink_buf_t *srv_out) -{ - for (int round = 0; round < 8; round++) { - rc->out.len = 0; - MQ_CHECK_EQ_INT(nghttp2_session_send(cli), 0); - if (rc->out.len > 0) { - if (mq_gw_h2_adapter_recv(a, rc->out.buf, rc->out.len) != 0) break; - } - srv_out->len = 0; - MQ_CHECK_EQ_INT(mq_gw_h2_adapter_want_write(a), 0); - if (srv_out->len > 0) { - ssize_t r = nghttp2_session_mem_recv(cli, srv_out->buf, srv_out->len); - MQ_CHECK(r >= 0 && (size_t)r == srv_out->len); - } - } -} - -/* The full response path: the test plays the CORE, driving the captured sink to - * deliver a 200 + headers + body, and asserts the nghttp2 CLIENT receives it. - * Then a backpressure case: feed a body exceeding the per-stream cap, assert - * resp_body returns 0 (highwater), drain via want_write→client, assert the - * adapter called req_drained so the core can resume, feed the rest, assert it - * arrives. */ -static void -test_response_path_and_backpressure(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - resp_cli_t rc = {0}; - - sink_buf_t srv_out = {0}; - mq_gw_h2_adapter_t *a = mq_gw_h2_adapter_new(&g_cap_ops, &st, adapter_send, &srv_out); - MQ_CHECK(a != NULL); - if (!a) return; - - nghttp2_session_callbacks *cbs = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs), 0); - nghttp2_session_callbacks_set_send_callback(cbs, client_send); - nghttp2_session_callbacks_set_on_header_callback(cbs, resp_cli_on_header); - nghttp2_session_callbacks_set_on_data_chunk_recv_callback(cbs, - resp_cli_on_data_chunk); - nghttp2_session_callbacks_set_on_stream_close_callback(cbs, resp_cli_on_stream_close); - - nghttp2_session *cli = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli, cbs, &rc), 0); - nghttp2_session_callbacks_del(cbs); - - nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli, NGHTTP2_FLAG_NONE, iv, 1), 0); - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"GET", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - }; - int sid = nghttp2_submit_request(cli, NULL, nva, 4, NULL, NULL); - MQ_CHECK(sid > 0); - - /* Drive the request through so the adapter materializes it and captures the - * sink. */ - shuttle(a, cli, &rc, &srv_out); - MQ_CHECK_EQ_INT(st.req_begin_calls, 1); - MQ_CHECK(st.sink != NULL); - MQ_CHECK(st.sink_user != NULL); - - /* ── core delivers the response head + a small body chunk + finish ──────── */ - const mq_h3_header_t rhdrs[] = { - {"content-type", "text/plain"}, - {"x-demo", "yes"}, - }; - int hr = st.sink->resp_head(st.sink_user, 200, rhdrs, 2, MQ_GW_BODY_STREAM); - MQ_CHECK(hr >= 0); - - static const uint8_t chunk[] = "hello world"; - int wr = st.sink->resp_body(st.sink_user, chunk, sizeof(chunk) - 1); - MQ_CHECK(wr == (int)(sizeof(chunk) - 1)); /* accept path returns byte count */ - - st.sink->resp_finish(st.sink_user); - - shuttle(a, cli, &rc, &srv_out); - - MQ_CHECK_EQ_INT(rc.status, 200); - MQ_CHECK(resp_cli_find(&rc, "content-type") && - strcmp(resp_cli_find(&rc, "content-type"), "text/plain") == 0); - MQ_CHECK(resp_cli_find(&rc, "x-demo") && - strcmp(resp_cli_find(&rc, "x-demo"), "yes") == 0); - MQ_CHECK_EQ_INT((int)rc.body_len, (int)(sizeof(chunk) - 1)); - MQ_CHECK(memcmp(rc.body, chunk, sizeof(chunk) - 1) == 0); - MQ_CHECK(rc.stream_closed); /* END_STREAM after resp_finish */ - - mq_gw_h2_adapter_free(a); - nghttp2_session_del(cli); - - /* ── backpressure case (rigor IMP-1): fresh request, oversized body ─────── */ - cap_state_t st2 = {0}; - st2.prevalidate_verdict = MQ_GW_OK; - resp_cli_t rc2 = {0}; - sink_buf_t srv_out2 = {0}; - mq_gw_h2_adapter_t *a2 = - mq_gw_h2_adapter_new(&g_cap_ops, &st2, adapter_send, &srv_out2); - MQ_CHECK(a2 != NULL); - if (!a2) return; - - nghttp2_session_callbacks *cbs2 = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs2), 0); - nghttp2_session_callbacks_set_send_callback(cbs2, client_send); - nghttp2_session_callbacks_set_on_header_callback(cbs2, resp_cli_on_header); - nghttp2_session_callbacks_set_on_data_chunk_recv_callback(cbs2, - resp_cli_on_data_chunk); - nghttp2_session_callbacks_set_on_stream_close_callback(cbs2, - resp_cli_on_stream_close); - - nghttp2_session *cli2 = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli2, cbs2, &rc2), 0); - nghttp2_session_callbacks_del(cbs2); - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli2, NGHTTP2_FLAG_NONE, iv, 1), 0); - - int sid2 = nghttp2_submit_request(cli2, NULL, nva, 4, NULL, NULL); - MQ_CHECK(sid2 > 0); - shuttle(a2, cli2, &rc2, &srv_out2); - MQ_CHECK_EQ_INT(st2.req_begin_calls, 1); - MQ_CHECK(st2.sink != NULL); - - int hr2 = st2.sink->resp_head(st2.sink_user, 200, rhdrs, 2, MQ_GW_BODY_STREAM); - MQ_CHECK(hr2 >= 0); - - /* Fill the per-stream send queue exactly to the cap. */ - static uint8_t big[EXP_RESP_QUEUE_CAP]; - for (size_t i = 0; i < sizeof(big); i++) - big[i] = (uint8_t)(i & 0xff); - int w1 = st2.sink->resp_body(st2.sink_user, big, sizeof(big)); - MQ_CHECK_EQ_INT(w1, (int)sizeof(big)); /* whole cap accepted */ - - /* Next byte must hit highwater (queue full) → return 0, NOT enqueued. */ - uint8_t extra = 0xAB; - int w2 = st2.sink->resp_body(st2.sink_user, &extra, 1); - MQ_CHECK_EQ_INT(w2, 0); /* highwater */ - - /* Drain: the adapter flushes via want_write; once the queue drops below the - * cap it must call req_drained so the core can resume. */ - shuttle(a2, cli2, &rc2, &srv_out2); - MQ_CHECK(st2.req_drained_calls >= 1); - - /* Core resumes: feed the previously-rejected byte + finish. */ - int w3 = st2.sink->resp_body(st2.sink_user, &extra, 1); - MQ_CHECK_EQ_INT(w3, 1); - st2.sink->resp_finish(st2.sink_user); - shuttle(a2, cli2, &rc2, &srv_out2); - - /* All cap bytes + the trailing extra arrived, in order. */ - MQ_CHECK_EQ_INT((int)rc2.body_len, (int)(sizeof(big) + 1)); - MQ_CHECK(memcmp(rc2.body, big, sizeof(big)) == 0); - MQ_CHECK(rc2.body[sizeof(big)] == extra); - MQ_CHECK(rc2.stream_closed); - - mq_gw_h2_adapter_free(a2); - nghttp2_session_del(cli2); -} - -/* ── Slice 3 UAF-hardening: req_aborted accounting at adapter teardown ───────── - * - * The adapter borrows s->xreq (the gwc owns it). The terminal sink ops - * resp_finish / resp_abort NULL s->xreq so on_stream_close / adapter_free do NOT - * call req_aborted on a freed xreq (UAF). These two cases lock that contract in: - * - * (a) CLEAN finish: materialize a stream, drive resp_head+resp_finish, then - * free the adapter while the stream is still on its live list → req_aborted - * must fire ZERO times (resp_finish NULLed the borrowed handle). This is - * non-vacuous: it FAILS if resp_finish stops NULLing s->xreq. - * (b) IN-FLIGHT: materialize a stream and free the adapter WITHOUT finishing → - * req_aborted must fire exactly once (genuinely in-flight stream aborted). - * - * Helper: drive a fresh request to materialization and return the adapter + the - * captured sink (via st) with the stream still open (no client END_STREAM body, - * so the stream stays on a->streams until adapter_free). */ -static mq_gw_h2_adapter_t * -materialize_open_stream(cap_state_t *st, nghttp2_session **out_cli, sink_buf_t *srv_out) -{ - st->prevalidate_verdict = MQ_GW_OK; - resp_cli_t rc = {0}; /* local sink for the client's outbound bytes */ - - mq_gw_h2_adapter_t *a = mq_gw_h2_adapter_new(&g_cap_ops, st, adapter_send, srv_out); - MQ_CHECK(a != NULL); - if (!a) return NULL; - - nghttp2_session_callbacks *cbs = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs), 0); - nghttp2_session_callbacks_set_send_callback(cbs, client_send); - nghttp2_session *cli = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli, cbs, &rc), 0); - nghttp2_session_callbacks_del(cbs); - - nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli, NGHTTP2_FLAG_NONE, iv, 1), 0); - - /* GET with NO body → END_STREAM rides the HEADERS, the request materializes, - * but the server-side stream stays OPEN (we never send a response that closes - * it) so it is still on a->streams at adapter_free. */ - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"GET", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - }; - int sid = nghttp2_submit_request(cli, NULL, nva, 4, NULL, NULL); - MQ_CHECK(sid > 0); - shuttle(a, cli, &rc, srv_out); - - MQ_CHECK_EQ_INT(st->req_begin_calls, 1); - MQ_CHECK(st->sink != NULL && st->sink_user != NULL); - *out_cli = cli; - return a; -} - -static void -test_teardown_clean_finish_no_abort(void) -{ - /* (a) clean finish → ZERO aborts at adapter teardown. */ - cap_state_t st = {0}; - sink_buf_t srv_out = {0}; - nghttp2_session *cli = NULL; - mq_gw_h2_adapter_t *a = materialize_open_stream(&st, &cli, &srv_out); - if (!a) return; - - const mq_h3_header_t rhdrs[] = {{"content-type", "text/plain"}}; - int hr = st.sink->resp_head(st.sink_user, 200, rhdrs, 1, MQ_GW_BODY_STREAM); - MQ_CHECK(hr >= 0); - st.sink->resp_finish(st.sink_user); /* clean completion → adapter NULLs s->xreq */ - - /* Tear the adapter down with the stream still on its live list. Because - * resp_finish NULLed the borrowed xreq, adapter_free must NOT call req_aborted. - * (Non-vacuous: removing s->xreq=NULL from resp_finish flips this to 1.) */ - mq_gw_h2_adapter_free(a); - MQ_CHECK_EQ_INT(st.req_aborted_calls, 0); - - nghttp2_session_del(cli); - - /* (b) in-flight → exactly ONE abort: a genuinely live stream IS aborted. */ - cap_state_t st2 = {0}; - sink_buf_t srv_out2 = {0}; - nghttp2_session *cli2 = NULL; - mq_gw_h2_adapter_t *a2 = materialize_open_stream(&st2, &cli2, &srv_out2); - if (!a2) { - nghttp2_session_del(cli2); - return; - } - /* No resp_finish/resp_abort — xreq still borrowed → teardown must abort it. */ - mq_gw_h2_adapter_free(a2); - MQ_CHECK_EQ_INT(st2.req_aborted_calls, 1); - - nghttp2_session_del(cli2); -} - -/* ── Task 8: request-body UPLOAD + backpressure + reject→error mapping ───────── */ - -/* Client-side upload data provider: streams a fixed buffer as the request body, - * setting END_STREAM (DATA_FLAG_EOF) on the final frame. */ -typedef struct { - const uint8_t *data; - size_t len; - size_t off; -} up_src_t; - -static ssize_t -up_read_cb(nghttp2_session *s, int32_t sid, uint8_t *buf, size_t length, - uint32_t *data_flags, nghttp2_data_source *source, void *user_data) -{ - (void)s; - (void)sid; - (void)user_data; - up_src_t *us = (up_src_t *)source->ptr; - size_t rem = us->len - us->off; - size_t n = rem < length ? rem : length; - memcpy(buf, us->data + us->off, n); - us->off += n; - if (us->off >= us->len) *data_flags |= NGHTTP2_DATA_FLAG_EOF; /* → END_STREAM */ - return (ssize_t)n; -} - -/* Upload happy-path: a POST with a body → the stub req_body receives the exact - * bytes in order, and req_body_done fires at stream end. */ -static void -test_upload_body_delivered(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - resp_cli_t rc = {0}; - - sink_buf_t srv_out = {0}; - mq_gw_h2_adapter_t *a = mq_gw_h2_adapter_new(&g_cap_ops, &st, adapter_send, &srv_out); - MQ_CHECK(a != NULL); - if (!a) return; - - nghttp2_session_callbacks *cbs = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs), 0); - nghttp2_session_callbacks_set_send_callback(cbs, client_send); - nghttp2_session *cli = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli, cbs, &rc), 0); - nghttp2_session_callbacks_del(cbs); - - nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli, NGHTTP2_FLAG_NONE, iv, 1), 0); - - /* A body that spans multiple DATA frames (3 * 16 KiB + a tail). */ - static uint8_t body[3 * 16384 + 777]; - for (size_t i = 0; i < sizeof(body); i++) - body[i] = (uint8_t)((i * 31u + 7u) & 0xff); - up_src_t us = {body, sizeof(body), 0}; - nghttp2_data_provider prd = {.source.ptr = &us, .read_callback = up_read_cb}; - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"POST", 7, 4, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/upload", 5, 7, NGHTTP2_NV_FLAG_NONE}, - }; - int sid = nghttp2_submit_request(cli, NULL, nva, 4, &prd, NULL); - MQ_CHECK(sid > 0); - - shuttle(a, cli, &rc, &srv_out); - - MQ_CHECK_EQ_INT(st.req_begin_calls, 1); - /* Exact bytes, in order. */ - MQ_CHECK_EQ_INT((int)st.up_len, (int)sizeof(body)); - MQ_CHECK(memcmp(st.up_body, body, sizeof(body)) == 0); - /* Stream end → req_body_done fired exactly once. */ - MQ_CHECK_EQ_INT(st.req_body_done_calls, 1); - - mq_gw_h2_adapter_free(a); - nghttp2_session_del(cli); -} - -/* Upload backpressure (codex H — -1 = pause): the stub req_body returns -1 after - * the first chunk → the adapter pauses the inbound read (no more body delivered) - * and throttles the browser. The test then drives resume_read; the remaining - * bytes MUST be delivered and NONE lost (total received == total sent, in - * order). */ -static void -test_upload_backpressure_no_loss(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - st.pause_after = 1; /* pause as soon as the first body byte arrives */ - resp_cli_t rc = {0}; - - sink_buf_t srv_out = {0}; - mq_gw_h2_adapter_t *a = mq_gw_h2_adapter_new(&g_cap_ops, &st, adapter_send, &srv_out); - MQ_CHECK(a != NULL); - if (!a) return; - - nghttp2_session_callbacks *cbs = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs), 0); - nghttp2_session_callbacks_set_send_callback(cbs, client_send); - nghttp2_session *cli = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli, cbs, &rc), 0); - nghttp2_session_callbacks_del(cbs); - - nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli, NGHTTP2_FLAG_NONE, iv, 1), 0); - - static uint8_t body[5 * 16384 + 123]; /* spans several DATA frames */ - for (size_t i = 0; i < sizeof(body); i++) - body[i] = (uint8_t)((i * 17u + 3u) & 0xff); - up_src_t us = {body, sizeof(body), 0}; - nghttp2_data_provider prd = {.source.ptr = &us, .read_callback = up_read_cb}; - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"POST", 7, 4, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/upload", 5, 7, NGHTTP2_NV_FLAG_NONE}, - }; - int sid = nghttp2_submit_request(cli, NULL, nva, 4, &prd, NULL); - MQ_CHECK(sid > 0); - - /* Drive until the adapter pauses. The pause stops the inbound read; the - * client's remaining body is held by H2 flow control. After shuttle the stub - * has been paused (paused_once) and req_body_done has NOT yet fired (more - * body outstanding). */ - shuttle(a, cli, &rc, &srv_out); - MQ_CHECK_EQ_INT(st.req_begin_calls, 1); - MQ_CHECK_EQ_INT(st.paused_once, 1); - MQ_CHECK_EQ_INT(st.req_body_done_calls, 0); /* not done yet — throttled */ - MQ_CHECK(st.up_len < sizeof(body)); /* did NOT receive it all yet */ - size_t at_pause = st.up_len; - - /* Core resumes the upload read. */ - MQ_CHECK(st.sink != NULL); - st.sink->resume_read(st.sink_user); - - /* Pump again: the buffered tail + the rest of the browser's body must now - * flow through to req_body, lossless and in order. */ - shuttle(a, cli, &rc, &srv_out); - - MQ_CHECK(st.up_len > at_pause); /* the resume actually delivered more */ - MQ_CHECK_EQ_INT((int)st.up_len, (int)sizeof(body)); /* ALL bytes, none lost */ - MQ_CHECK(memcmp(st.up_body, body, sizeof(body)) == 0); /* in order */ - MQ_CHECK_EQ_INT(st.req_body_done_calls, 1); /* stream end */ - - mq_gw_h2_adapter_free(a); - nghttp2_session_del(cli); -} - -/* ── content_length framing (codex-1 High): the adapter must set head.content_length - * so the core makes the correct FIN-on-headers decision. - * - * END_STREAM on HEADERS (bodyless GET/HEAD) → content_length == 0 (no body → - * the core sends headers WITH fin). - * no END_STREAM on HEADERS (body follows) → content_length == -1 (streaming → - * the core must NOT fin the headers; it waits for req_body / req_body_done). - * - * The previous placeholder ALWAYS set -1, so a bodyless GET (-1) tripped the - * core's `<= 0` no_body test (correct by accident) BUT a POST with a body ALSO - * got -1 → mis-classified, and (after the req_begin `== 0` fix) a bodyless GET - * must report 0 or it would hang waiting for a body. These two tests assert the - * adapter reports the END_STREAM-on-HEADERS bit faithfully via content_length. */ - -/* Bodyless GET (END_STREAM on HEADERS) → content_length == 0. */ -static void -test_content_length_bodyless_is_zero(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - cli_ud_t cud = {0}; - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"GET", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - }; - int sid = 0; - /* No data provider in drive_request → END_STREAM rides the HEADERS frame. */ - drive_request(&g_cap_ops, &st, nva, sizeof(nva) / sizeof(nva[0]), &cud, &sid); - - MQ_CHECK_EQ_INT(st.req_begin_calls, 1); - /* The defining assertion: a bodyless request reports content_length == 0 so - * the core fins on headers (and so the post-fix `== 0` req_begin test keeps - * fin-on-headers for it — a -1 here would hang the core waiting for a body). */ - MQ_CHECK_EQ_INT((int)st.content_length, 0); - /* Sanity: a bodyless request also fires req_body_done (END_STREAM seen). */ - MQ_CHECK_EQ_INT(st.req_body_done_calls, 1); -} - -/* POST with a body (no END_STREAM on HEADERS; a DATA frame follows) → - * content_length == -1 (streaming). This is the regression that was BROKEN: the - * placeholder set -1 too, but the core's `<= 0` test then FIN'd the upstream - * request before the body. The fix is two-sided (adapter reports -1 for "has - * body", core treats -1 as "do NOT fin"); this test pins the adapter half. */ -static void -test_content_length_with_body_is_stream(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - resp_cli_t rc = {0}; - - sink_buf_t srv_out = {0}; - mq_gw_h2_adapter_t *a = mq_gw_h2_adapter_new(&g_cap_ops, &st, adapter_send, &srv_out); - MQ_CHECK(a != NULL); - if (!a) return; - - nghttp2_session_callbacks *cbs = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs), 0); - nghttp2_session_callbacks_set_send_callback(cbs, client_send); - nghttp2_session *cli = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli, cbs, &rc), 0); - nghttp2_session_callbacks_del(cbs); - - nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli, NGHTTP2_FLAG_NONE, iv, 1), 0); - - static const uint8_t body[] = "field=value&x=1"; - up_src_t us = {body, sizeof(body) - 1, 0}; - nghttp2_data_provider prd = {.source.ptr = &us, .read_callback = up_read_cb}; - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"POST", 7, 4, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/submit", 5, 7, NGHTTP2_NV_FLAG_NONE}, - }; - /* Pass the data provider → END_STREAM is NOT on the HEADERS frame. */ - int sid = nghttp2_submit_request(cli, NULL, nva, 4, &prd, NULL); - MQ_CHECK(sid > 0); - - shuttle(a, cli, &rc, &srv_out); - - MQ_CHECK_EQ_INT(st.req_begin_calls, 1); - /* Defining assertion: a request WITH a body reports content_length == -1 - * (streaming) so the core does NOT fin the headers and instead delivers the - * body via req_body + req_body_done. */ - MQ_CHECK_EQ_INT((int)st.content_length, -1); - /* The body still flows through (sanity — the upload path is unchanged). */ - MQ_CHECK_EQ_INT((int)st.up_len, (int)(sizeof(body) - 1)); - MQ_CHECK(memcmp(st.up_body, body, sizeof(body) - 1) == 0); - MQ_CHECK_EQ_INT(st.req_body_done_calls, 1); - - mq_gw_h2_adapter_free(a); - nghttp2_session_del(cli); -} - -/* ── §5.2 / codex Low: protocol limit enforcement via the live nghttp2 client ── - * - * (i) MAX_CONCURRENT_STREAMS=128: the 129th simultaneously-open stream must be - * refused. nghttp2 enforces the peer's advertised limit on the CLIENT side — - * once the client has the server's SETTINGS, nghttp2_submit_request past the - * limit still returns a stream id but the stream is held "idle/pending" and - * never opened; nghttp2_session_get_outbound_queue_size / the refused-stream - * accounting surfaces it. We assert the observable outcome: after driving 129 - * requests, the adapter materialized AT MOST 128 (the 129th did not reach - * req_begin) — the cap is load-bearing. - */ -static void -test_max_concurrent_streams_enforced(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - resp_cli_t rc = {0}; - - sink_buf_t srv_out = {0}; - mq_gw_h2_adapter_t *a = mq_gw_h2_adapter_new(&g_cap_ops, &st, adapter_send, &srv_out); - MQ_CHECK(a != NULL); - if (!a) return; - - nghttp2_session_callbacks *cbs = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs), 0); - nghttp2_session_callbacks_set_send_callback(cbs, client_send); - nghttp2_session *cli = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli, cbs, &rc), 0); - nghttp2_session_callbacks_del(cbs); - - nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 200}}; - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli, NGHTTP2_FLAG_NONE, iv, 1), 0); - - /* First drive the SETTINGS handshake so the client learns the server's - * MAX_CONCURRENT_STREAMS=128 before we open streams (else nghttp2 would let - * all 129 out optimistically and the server would RST the excess). */ - shuttle(a, cli, &rc, &srv_out); - - /* Open 129 streams WITHOUT END_STREAM (data provider) so they stay open and - * count against the concurrency limit. The 129th must not materialize. */ - static const uint8_t b1[] = "x"; - for (int i = 0; i < 129; i++) { - up_src_t us = {b1, sizeof(b1) - 1, 0}; - /* fresh provider per request — never EOF until we let it (we don't here, - * so each stream stays open). Use a non-EOF provider: report 0 bytes with - * no EOF would defer; instead send 1 byte and DON'T set EOF. */ - nghttp2_data_provider prd = {.source.ptr = &us, .read_callback = up_read_cb}; - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"POST", 7, 4, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - }; - nghttp2_submit_request(cli, NULL, nva, 4, &prd, NULL); - shuttle(a, cli, &rc, &srv_out); - } - - /* nghttp2 (client) holds the 129th stream pending locally — it never reaches - * the server, so the adapter materialized AT MOST 128 requests. The cap is - * therefore observably enforced (a regression that dropped the SETTINGS would - * let all 129 materialize). */ - MQ_CHECK(st.req_begin_calls <= 128); - MQ_CHECK(st.req_begin_calls >= 1); /* non-vacuous: at least some opened */ - - mq_gw_h2_adapter_free(a); - nghttp2_session_del(cli); -} - -/* (ii) MAX_FRAME_SIZE=16384: a DATA frame larger than 16 KiB must be rejected. - * nghttp2 enforces the peer's advertised MAX_FRAME_SIZE on the SENDING side: a - * client that tries to emit a frame larger than the server advertised gets a - * NGHTTP2_ERR_FRAME_SIZE_ERROR from its own data provider path / the session - * goes into error. We assert the observable outcome: a >16 KiB single DATA frame - * never delivers its oversized payload intact as one frame — the body either - * arrives split into <=16 KiB frames (nghttp2 auto-splits to honor the cap) or - * the stream errors. EITHER way the 16 KiB cap is honored on the wire. We assert - * that NO single DATA chunk the adapter handed the core exceeded 16384 bytes. */ -static void -test_max_frame_size_enforced(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - resp_cli_t rc = {0}; - - sink_buf_t srv_out = {0}; - mq_gw_h2_adapter_t *a = mq_gw_h2_adapter_new(&g_cap_ops, &st, adapter_send, &srv_out); - MQ_CHECK(a != NULL); - if (!a) return; - - nghttp2_session_callbacks *cbs = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs), 0); - nghttp2_session_callbacks_set_send_callback(cbs, client_send); - nghttp2_session *cli = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli, cbs, &rc), 0); - nghttp2_session_callbacks_del(cbs); - - nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli, NGHTTP2_FLAG_NONE, iv, 1), 0); - - /* Learn the server's MAX_FRAME_SIZE=16384 first. */ - shuttle(a, cli, &rc, &srv_out); - - /* A body bigger than one max frame. nghttp2 MUST split it so no single DATA - * frame exceeds 16384, honoring the server's advertised cap. */ - static uint8_t body[40000]; - for (size_t i = 0; i < sizeof(body); i++) - body[i] = (uint8_t)(i & 0xff); - up_src_t us = {body, sizeof(body), 0}; - nghttp2_data_provider prd = {.source.ptr = &us, .read_callback = up_read_cb}; - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"POST", 7, 4, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - }; - int sid = nghttp2_submit_request(cli, NULL, nva, 4, &prd, NULL); - MQ_CHECK(sid > 0); - - shuttle(a, cli, &rc, &srv_out); - - /* The whole body arrived (nghttp2 split it across <=16 KiB frames). The §5.2 - * cap held: had a regression advertised a larger MAX_FRAME_SIZE the wire would - * have carried bigger frames, but the protocol minimum/cap is what we asserted - * the adapter advertises (test_settings_handshake) and what nghttp2 honors. */ - MQ_CHECK_EQ_INT((int)st.up_len, (int)sizeof(body)); - MQ_CHECK(memcmp(st.up_body, body, sizeof(body)) == 0); - MQ_CHECK_EQ_INT(st.req_body_done_calls, 1); - - mq_gw_h2_adapter_free(a); - nghttp2_session_del(cli); -} - -/* rigor test-precision: ISOLATE the app-side cumulative MQ_H2_MAX_HEADER_LIST_SIZE - * accounting. The existing header-bomb test sends ONE 20000-byte header, which the - * arena single-field cap / nghttp2 could also catch. Here EVERY individual header - * is small (~500 bytes name+value, well under any single-field/arena limit and - * under nghttp2's defaults) but the CUMULATIVE size (40 fields * ~(500+32) ≈ 21 KiB) - * exceeds MQ_H2_MAX_HEADER_LIST_SIZE (16384). ONLY the adapter's on_header - * cumulative accounting can trip this → the stream must be RST (no req_begin). This - * proves the app-side accounting is load-bearing (nghttp2 1.59.0 does not auto- - * enforce the advertised SETTINGS_MAX_HEADER_LIST_SIZE on inbound blocks). */ -static void -test_header_bomb_cumulative_many_medium(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_OK; - cli_ud_t cud = {0}; - - /* 40 headers, each value ~500 bytes. 40 * (name~6 + value~500 + 32) ≈ 21 KiB - * cumulative > 16384 cap, but each field (~538 incl. overhead) is far below the - * 16 KiB arena single-field limit and below nghttp2's internal per-field caps. */ - enum { NHDR = 40, VLEN = 500 }; - static char vbuf[VLEN + 1]; - memset(vbuf, 'y', VLEN); - vbuf[VLEN] = '\0'; - static char names[NHDR][8]; - for (int i = 0; i < NHDR; i++) { - names[i][0] = 'h'; - names[i][1] = '-'; - names[i][2] = (char)('a' + (i / 10)); - names[i][3] = (char)('0' + (i % 10)); - names[i][4] = '\0'; - } - - nghttp2_nv nva[4 + NHDR]; - nva[0] = - (nghttp2_nv){(uint8_t *)":method", (uint8_t *)"GET", 7, 3, NGHTTP2_NV_FLAG_NONE}; - nva[1] = (nghttp2_nv){(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, - NGHTTP2_NV_FLAG_NONE}; - nva[2] = (nghttp2_nv){(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}; - nva[3] = (nghttp2_nv){(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}; - for (int i = 0; i < NHDR; i++) { - nva[4 + i] = (nghttp2_nv){(uint8_t *)names[i], (uint8_t *)vbuf, strlen(names[i]), - VLEN, NGHTTP2_NV_FLAG_NONE}; - } - - int sid = 0; - drive_request(&g_cap_ops, &st, nva, 4 + NHDR, &cud, &sid); - - /* The cumulative cap tripped in on_header → RST before materialization. */ - MQ_CHECK_EQ_INT(st.req_begin_calls, 0); -} - -/* Reject → X-Mq-Error mapping (§5/§5.2): force MQ_GW_REJ_HEADER_TOO_LONG via the - * stub prevalidate → the H2 client receives a response whose x-mq-error value is - * byte-for-byte "header-too-long" (the adapter renders the error response). */ -static void -test_reject_error_mapping(void) -{ - cap_state_t st = {0}; - st.prevalidate_verdict = MQ_GW_REJ_HEADER_TOO_LONG; - st.prevalidate_status = 400; - resp_cli_t rc = {0}; - - sink_buf_t srv_out = {0}; - mq_gw_h2_adapter_t *a = mq_gw_h2_adapter_new(&g_cap_ops, &st, adapter_send, &srv_out); - MQ_CHECK(a != NULL); - if (!a) return; - - nghttp2_session_callbacks *cbs = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs), 0); - nghttp2_session_callbacks_set_send_callback(cbs, client_send); - nghttp2_session_callbacks_set_on_header_callback(cbs, resp_cli_on_header); - nghttp2_session_callbacks_set_on_data_chunk_recv_callback(cbs, - resp_cli_on_data_chunk); - nghttp2_session_callbacks_set_on_stream_close_callback(cbs, resp_cli_on_stream_close); - nghttp2_session *cli = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli, cbs, &rc), 0); - nghttp2_session_callbacks_del(cbs); - - nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli, NGHTTP2_FLAG_NONE, iv, 1), 0); - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"GET", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"site.example", 10, 12, - NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - }; - int sid = nghttp2_submit_request(cli, NULL, nva, 4, NULL, NULL); - MQ_CHECK(sid > 0); - - shuttle(a, cli, &rc, &srv_out); - - /* prevalidate rejected → no req_begin; the adapter rendered an error - * RESPONSE (not a bare RST), carrying x-mq-error: header-too-long. */ - MQ_CHECK_EQ_INT(st.req_begin_calls, 0); - MQ_CHECK_EQ_INT(rc.status, 400); - const char *xerr = resp_cli_find(&rc, "x-mq-error"); - MQ_CHECK(xerr != NULL); - MQ_CHECK(xerr && strcmp(xerr, "header-too-long") == 0); /* byte-for-byte */ - MQ_CHECK(rc.stream_closed); - - mq_gw_h2_adapter_free(a); - nghttp2_session_del(cli); -} - -MQ_TEST_MAIN(test_settings_handshake(); test_demux_header_policy(); - test_demux_embedded_nul_rejected(); test_demux_header_bomb_rejected(); - test_demux_prevalidate_reject(); test_demux_auth_bearer_dedup(); - test_response_path_and_backpressure(); test_teardown_clean_finish_no_abort(); - test_upload_body_delivered(); test_upload_backpressure_no_loss(); - test_content_length_bodyless_is_zero(); - test_content_length_with_body_is_stream(); - test_max_concurrent_streams_enforced(); test_max_frame_size_enforced(); - test_header_bomb_cumulative_many_medium(); test_reject_error_mapping();) diff --git a/tests/test_gw_headers.c b/tests/test_gw_headers.c deleted file mode 100644 index 27d0ecec..00000000 --- a/tests/test_gw_headers.c +++ /dev/null @@ -1,699 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "gateway/mq_gw_headers.h" -#include "gateway/mq_http1.h" -#include "mqtest.h" -#include -#include - -/* =================================================================== - * mq_gw_parse_target - * =================================================================== */ - -/* small helper: parse a NUL-terminated literal, return rc */ -static int -pt(const char *s, mq_gw_target_t *out) -{ - return mq_gw_parse_target(s, strlen(s), out); -} - -/* ---- https with path+query preserved ---- */ -static void -test_target_https_full(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://example.com/foo/bar?x=1&y=2", &t), 0); - MQ_CHECK_EQ_INT(strcmp(t.scheme, "https"), 0); - MQ_CHECK_EQ_INT(strcmp(t.authority, "example.com"), 0); - MQ_CHECK_EQ_INT(strcmp(t.path, "/foo/bar?x=1&y=2"), 0); -} - -/* ---- plain http ---- */ -static void -test_target_http(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("http://example.com/x", &t), 0); - MQ_CHECK_EQ_INT(strcmp(t.scheme, "http"), 0); - MQ_CHECK_EQ_INT(strcmp(t.authority, "example.com"), 0); - MQ_CHECK_EQ_INT(strcmp(t.path, "/x"), 0); -} - -/* ---- no path → "/" ---- */ -static void -test_target_no_path(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://example.com", &t), 0); - MQ_CHECK_EQ_INT(strcmp(t.authority, "example.com"), 0); - MQ_CHECK_EQ_INT(strcmp(t.path, "/"), 0); -} - -/* ---- "?q=1" only (no path segment) → "/?q=1" ---- */ -static void -test_target_query_only(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://example.com?q=1", &t), 0); - MQ_CHECK_EQ_INT(strcmp(t.authority, "example.com"), 0); - MQ_CHECK_EQ_INT(strcmp(t.path, "/?q=1"), 0); -} - -/* ---- explicit port preserved in authority ---- */ -static void -test_target_port(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://h:8443/x", &t), 0); - MQ_CHECK_EQ_INT(strcmp(t.authority, "h:8443"), 0); - MQ_CHECK_EQ_INT(strcmp(t.path, "/x"), 0); -} - -/* ---- bracketed IPv6 with port ---- */ -static void -test_target_ipv6(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://[::1]:443/", &t), 0); - MQ_CHECK_EQ_INT(strcmp(t.authority, "[::1]:443"), 0); - MQ_CHECK_EQ_INT(strcmp(t.path, "/"), 0); -} - -/* ---- bracketed IPv6 without port ---- */ -static void -test_target_ipv6_no_port(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://[2001:db8::1]/p", &t), 0); - MQ_CHECK_EQ_INT(strcmp(t.authority, "[2001:db8::1]"), 0); - MQ_CHECK_EQ_INT(strcmp(t.path, "/p"), 0); -} - -/* ---- bad port (non-digit) → reject ---- */ -static void -test_target_bad_port(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://h:12ab/x", &t), -1); -} - -/* ---- empty port "h:" → reject ---- */ -static void -test_target_empty_port(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://h:/x", &t), -1); -} - -/* ---- unsupported scheme ftp → reject ---- */ -static void -test_target_ftp(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("ftp://example.com/x", &t), -1); -} - -/* ---- uppercase scheme rejected (documented: lowercase only) ---- */ -static void -test_target_upper_scheme(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("HTTPS://example.com/x", &t), -1); -} - -/* ---- empty authority → reject ---- */ -static void -test_target_empty_authority(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https:///path", &t), -1); -} - -/* ---- userinfo (u@h) → reject (credential-injection surface) ---- */ -static void -test_target_userinfo(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://u@h/x", &t), -1); - MQ_CHECK_EQ_INT(pt("https://u:p@h/x", &t), -1); -} - -/* ---- space in path → reject ---- */ -static void -test_target_space_in_path(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://h/a b", &t), -1); -} - -/* ---- control byte 0x1f in path → reject ---- */ -static void -test_target_ctrl_in_path(void) -{ - mq_gw_target_t t; - static const char S[] = "https://h/a\x1f" - "b"; - MQ_CHECK_EQ_INT(mq_gw_parse_target(S, sizeof(S) - 1, &t), -1); -} - -/* ---- control byte / space in authority → reject ---- */ -static void -test_target_ctrl_in_authority(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://h ost/x", &t), -1); - static const char S[] = "https://h\x01" - "ost/x"; - MQ_CHECK_EQ_INT(mq_gw_parse_target(S, sizeof(S) - 1, &t), -1); -} - -/* ---- fragment → reject (fetch target with fragment is a caller bug) ---- */ -static void -test_target_fragment(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://h/x#frag", &t), -1); - /* fragment with no path */ - MQ_CHECK_EQ_INT(pt("https://h#frag", &t), -1); -} - -/* ---- authority > 255 bytes → reject ---- */ -static void -test_target_authority_too_long(void) -{ - char buf[8 + 300 + 8]; - int n = snprintf(buf, sizeof buf, "https://"); - for (int i = 0; i < 256; i++) /* 256 > 255 */ - buf[n++] = 'a'; - n += snprintf(buf + n, sizeof buf - (size_t)n, "/x"); - mq_gw_target_t t; - MQ_CHECK_EQ_INT(mq_gw_parse_target(buf, (size_t)n, &t), -1); -} - -/* ---- path+query > 1023 bytes → reject ---- */ -static void -test_target_path_too_long(void) -{ - char buf[8 + 16 + 1200]; - int n = snprintf(buf, sizeof buf, "https://h/"); - for (int i = 0; i < 1024; i++) /* path becomes 1+1024 = 1025 > 1023 */ - buf[n++] = 'a'; - mq_gw_target_t t; - MQ_CHECK_EQ_INT(mq_gw_parse_target(buf, (size_t)n, &t), -1); -} - -/* ---- empty input → reject ---- */ -static void -test_target_empty(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(mq_gw_parse_target("", 0, &t), -1); -} - -/* ---- [DEFECT-1] stray '[' not at position 0 → authority injection → reject ---- */ -static void -test_target_stray_open_bracket(void) -{ - mq_gw_target_t t; - /* '[' at offset 3 in authority: the '/' after the bracket is swallowed - * and the entire "foo[bar/x" would reach the backend as host — reject. */ - MQ_CHECK_EQ_INT(pt("https://foo[bar/x", &t), -1); -} - -/* ---- [DEFECT-2] non-bracket host with embedded colon (h:80:90) → reject ---- */ -static void -test_target_double_colon_port(void) -{ - mq_gw_target_t t; - /* last-colon split leaves "h:80" as host which contains ':' — illegal */ - MQ_CHECK_EQ_INT(pt("https://h:80:90/x", &t), -1); -} - -/* ---- [DEFECT-3] leading ']' with no opening '[' → reject ---- */ -static void -test_target_lone_close_bracket(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://]nobracket/x", &t), -1); - MQ_CHECK_EQ_INT(pt("https://]/x", &t), -1); -} - -/* ---- IPv6: junk immediately after ']' (no ':') → reject ---- */ -static void -test_target_ipv6_junk_after_bracket(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://[::1]x80/", &t), -1); -} - -/* ---- IPv6: empty port after ']' → reject ---- */ -static void -test_target_ipv6_empty_port(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://[::1]:/", &t), -1); -} - -/* ---- IPv6: non-digit port after ']' → reject ---- */ -static void -test_target_ipv6_bad_port(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://[::1]:80x/", &t), -1); -} - -/* ---- IPv6: unclosed '[' → reject ---- */ -static void -test_target_ipv6_unclosed(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://[unclosed/", &t), -1); -} - -/* ---- IPv6: empty bracket host '[]:80' → reject ---- */ -static void -test_target_ipv6_empty_host(void) -{ - mq_gw_target_t t; - MQ_CHECK_EQ_INT(pt("https://[]:80/", &t), -1); -} - -/* =================================================================== - * mq_gw_parse_method - * =================================================================== */ - -static void -test_method_lower(void) -{ - char out[16]; - MQ_CHECK_EQ_INT(mq_gw_parse_method("get", 3, out), 0); - MQ_CHECK_EQ_INT(strcmp(out, "GET"), 0); -} - -static void -test_method_mixed(void) -{ - char out[16]; - MQ_CHECK_EQ_INT(mq_gw_parse_method("PuT", 3, out), 0); - MQ_CHECK_EQ_INT(strcmp(out, "PUT"), 0); -} - -/* token chars (RFC 7230) acceptable, e.g. with '-' '!' '.' */ -static void -test_method_token_chars(void) -{ - char out[16]; - MQ_CHECK_EQ_INT(mq_gw_parse_method("M-E.T!", 6, out), 0); - MQ_CHECK_EQ_INT(strcmp(out, "M-E.T!"), 0); -} - -/* 16-char method → reject (>15) */ -static void -test_method_too_long(void) -{ - char out[16]; - MQ_CHECK_EQ_INT(mq_gw_parse_method("ABCDEFGHIJKLMNOP", 16, out), -1); -} - -/* 15-char method → ok (boundary) */ -static void -test_method_15_ok(void) -{ - char out[16]; - MQ_CHECK_EQ_INT(mq_gw_parse_method("ABCDEFGHIJKLMNO", 15, out), 0); - MQ_CHECK_EQ_INT(strcmp(out, "ABCDEFGHIJKLMNO"), 0); -} - -/* space in method → reject (not a token char) */ -static void -test_method_space(void) -{ - char out[16]; - MQ_CHECK_EQ_INT(mq_gw_parse_method("GE T", 4, out), -1); -} - -/* empty method → reject */ -static void -test_method_empty(void) -{ - char out[16]; - MQ_CHECK_EQ_INT(mq_gw_parse_method("", 0, out), -1); -} - -/* =================================================================== - * strip predicates - * =================================================================== */ - -/* helper to call by NUL-terminated name */ -static int -sc(const char *n) -{ - return mq_gw_strip_client(n, strlen(n), 0); -} -static int -scf(const char *n) -{ - return mq_gw_strip_client(n, strlen(n), 1); -} -static int -ss(const char *n) -{ - return mq_gw_strip_server(n, strlen(n)); -} -static int -sh(const char *n) -{ - return mq_gw_strip_hop(n, strlen(n)); -} - -/* hop-by-hop names: stripped by all three predicates, mixed case */ -static void -test_strip_hop_names(void) -{ - static const char *hop[] = { - "Connection", "keep-alive", "Proxy-Authenticate", "proxy-authorization", - "TE", "Trailer", "Transfer-Encoding", "Upgrade", - }; - for (size_t i = 0; i < sizeof(hop) / sizeof(hop[0]); i++) { - MQ_CHECK_EQ_INT(sh(hop[i]), 1); - MQ_CHECK_EQ_INT(sc(hop[i]), 1); /* client strips hop too */ - MQ_CHECK_EQ_INT(ss(hop[i]), 1); /* server strips hop too */ - } - /* mixed case variant */ - MQ_CHECK_EQ_INT(sh("CoNnEcTiOn"), 1); - MQ_CHECK_EQ_INT(sh("TRANSFER-ENCODING"), 1); -} - -/* client→tunnel: X-Mq-*, Host, Content-Length, Cookie all stripped */ -static void -test_strip_client_names(void) -{ - MQ_CHECK_EQ_INT(sc("X-Mq-Target"), 1); - MQ_CHECK_EQ_INT(sc("X-Mq-Anything"), 1); - MQ_CHECK_EQ_INT(sc("x-mq-auth"), 1); - MQ_CHECK_EQ_INT(sc("Host"), 1); - MQ_CHECK_EQ_INT(sc("host"), 1); - MQ_CHECK_EQ_INT(sc("Content-Length"), 1); - MQ_CHECK_EQ_INT(sc("Cookie"), 1); - MQ_CHECK_EQ_INT(sc("COOKIE"), 1); -} - -/* server→origin: x-mq-* stripped, but Host/Content-Length/Cookie NOT */ -static void -test_strip_server_names(void) -{ - MQ_CHECK_EQ_INT(ss("X-Mq-Target"), 1); - MQ_CHECK_EQ_INT(ss("x-mq-auth"), 1); - /* server does NOT strip these (only x-mq-* + hop-by-hop) */ - MQ_CHECK_EQ_INT(ss("Host"), 0); - MQ_CHECK_EQ_INT(ss("Cookie"), 0); - MQ_CHECK_EQ_INT(ss("Content-Length"), 0); -} - -/* CRITICAL: Authorization is default-forwarded — stripped by NONE */ -static void -test_authorization_forwarded(void) -{ - MQ_CHECK_EQ_INT(sc("Authorization"), 0); - MQ_CHECK_EQ_INT(ss("Authorization"), 0); - MQ_CHECK_EQ_INT(sh("Authorization"), 0); - /* mixed case too */ - MQ_CHECK_EQ_INT(sc("authorization"), 0); -} - -/* Accept: ordinary header, stripped by none */ -static void -test_accept_forwarded(void) -{ - MQ_CHECK_EQ_INT(sc("Accept"), 0); - MQ_CHECK_EQ_INT(ss("Accept"), 0); - MQ_CHECK_EQ_INT(sh("Accept"), 0); -} - -/* Cookie: client strips, server does not */ -static void -test_cookie_asymmetry(void) -{ - MQ_CHECK_EQ_INT(sc("Cookie"), 1); - MQ_CHECK_EQ_INT(ss("Cookie"), 0); -} - -/* Content-Length: client strips but it is NOT hop-by-hop */ -static void -test_content_length_not_hop(void) -{ - MQ_CHECK_EQ_INT(sc("Content-Length"), 1); - MQ_CHECK_EQ_INT(sh("Content-Length"), 0); -} - -/* X-Mq prefix boundary: "X-Mqx" is NOT x-mq-* (needs "x-mq-" prefix) */ -static void -test_xmq_prefix_boundary(void) -{ - /* "X-Mq" exactly and "X-Mqq" should NOT match the X-Mq-* family. */ - MQ_CHECK_EQ_INT(sc("X-Mq"), 0); - MQ_CHECK_EQ_INT(sc("X-Mqq"), 0); - MQ_CHECK_EQ_INT(ss("X-Mq"), 0); -} - -/* =================================================================== - * mq_gw_has_dup_xmq - * =================================================================== */ - -/* tiny req builder: set header slices directly (no parser needed) */ -static void -set_h(mq_http1_req_t *r, size_t i, const char *n, const char *v) -{ - r->h[i].n = n; - r->h[i].nl = strlen(n); - r->h[i].v = v; - r->h[i].vl = strlen(v); -} - -/* two X-Mq-Auth → dup */ -static void -test_dup_two_xmq(void) -{ - mq_http1_req_t r; - memset(&r, 0, sizeof r); - set_h(&r, 0, "X-Mq-Auth", "a"); - set_h(&r, 1, "X-Mq-Auth", "b"); - r.nh = 2; - MQ_CHECK_EQ_INT(mq_gw_has_dup_xmq(&r), 1); -} - -/* X-Mq-Auth + X-Mq-Target (different x-mq names) → not dup */ -static void -test_dup_distinct_xmq(void) -{ - mq_http1_req_t r; - memset(&r, 0, sizeof r); - set_h(&r, 0, "X-Mq-Auth", "a"); - set_h(&r, 1, "X-Mq-Target", "https://h/"); - r.nh = 2; - MQ_CHECK_EQ_INT(mq_gw_has_dup_xmq(&r), 0); -} - -/* two Accept (non x-mq) → not dup (only x-mq-* counted) */ -static void -test_dup_non_xmq(void) -{ - mq_http1_req_t r; - memset(&r, 0, sizeof r); - set_h(&r, 0, "Accept", "a"); - set_h(&r, 1, "Accept", "b"); - r.nh = 2; - MQ_CHECK_EQ_INT(mq_gw_has_dup_xmq(&r), 0); -} - -/* case-insensitive dup: x-mq-auth + X-MQ-AUTH → dup */ -static void -test_dup_case_insensitive(void) -{ - mq_http1_req_t r; - memset(&r, 0, sizeof r); - set_h(&r, 0, "x-mq-auth", "a"); - set_h(&r, 1, "X-MQ-AUTH", "b"); - r.nh = 2; - MQ_CHECK_EQ_INT(mq_gw_has_dup_xmq(&r), 1); -} - -/* no headers → not dup */ -static void -test_dup_empty(void) -{ - mq_http1_req_t r; - memset(&r, 0, sizeof r); - r.nh = 0; - MQ_CHECK_EQ_INT(mq_gw_has_dup_xmq(&r), 0); -} - -/* =================================================================== - * mq_gw_forward_cookie_requested - * =================================================================== */ - -static void -test_forward_cookie_requested(void) -{ - mq_http1_req_t r; - set_h(&r, 0, "X-Mq-Forward-Cookie", "true"); - r.nh = 1; - MQ_CHECK_EQ_INT(mq_gw_forward_cookie_requested(&r), 1); - set_h(&r, 0, "x-mq-forward-cookie", "TRUE"); - r.nh = 1; /* name + value case-insensitive */ - MQ_CHECK_EQ_INT(mq_gw_forward_cookie_requested(&r), 1); - /* No OWS-padded case: mq_http1_parse_req OWS-trims values before the helper sees - * them, so the helper sees only trimmed values and intentionally does not trim. */ - set_h(&r, 0, "X-Mq-Forward-Cookie", "false"); - r.nh = 1; - MQ_CHECK_EQ_INT(mq_gw_forward_cookie_requested(&r), 0); - set_h(&r, 0, "X-Mq-Forward-Cookie", "1"); - r.nh = 1; - MQ_CHECK_EQ_INT(mq_gw_forward_cookie_requested(&r), 0); - set_h(&r, 0, "X-Mq-Forward-Cookie", ""); - r.nh = 1; - MQ_CHECK_EQ_INT(mq_gw_forward_cookie_requested(&r), 0); - set_h(&r, 0, "Accept", "*/*"); - r.nh = 1; /* header absent */ - MQ_CHECK_EQ_INT(mq_gw_forward_cookie_requested(&r), 0); -} - -static void -test_cookie_forward_optin(void) -{ - /* forward_cookie=1 (scf): ONLY Cookie's strip decision flips; every other strip is - * unaffected by the flag, and the opt-in header itself is still stripped. - * (sc("Cookie")==1 for forward_cookie=0 is already covered by test_cookie_asymmetry / - * test_strip_client_names.) */ - MQ_CHECK_EQ_INT(scf("Cookie"), 0); /* opted in: NOT stripped */ - MQ_CHECK_EQ_INT(scf("cookie"), 0); /* case-insensitive */ - MQ_CHECK_EQ_INT(scf("Host"), 1); /* a non-Cookie strip is flag-independent */ - MQ_CHECK_EQ_INT(scf("X-Mq-Forward-Cookie"), - 1); /* the opt-in header is still stripped */ -} - -/* =================================================================== - * mq_gw_status_from_curl - * =================================================================== */ - -static void -test_curl_map(void) -{ - MQ_CHECK_EQ_INT(mq_gw_status_from_curl(MQ_GW_CURL_RESOLVE), 502); - MQ_CHECK_EQ_INT(mq_gw_status_from_curl(MQ_GW_CURL_CONNECT), 502); - MQ_CHECK_EQ_INT(mq_gw_status_from_curl(MQ_GW_CURL_TIMEDOUT), 504); - MQ_CHECK_EQ_INT(mq_gw_status_from_curl(MQ_GW_CURL_PEER_VERIFY), 502); - /* raw constants per design §10.1 */ - MQ_CHECK_EQ_INT(mq_gw_status_from_curl(6), 502); - MQ_CHECK_EQ_INT(mq_gw_status_from_curl(7), 502); - MQ_CHECK_EQ_INT(mq_gw_status_from_curl(28), 504); - MQ_CHECK_EQ_INT(mq_gw_status_from_curl(60), 502); - /* unknown / unexpected → 502 */ - MQ_CHECK_EQ_INT(mq_gw_status_from_curl(999), 502); - /* 0 (CURLE_OK) — should not be called with success; defensive 502 */ - MQ_CHECK_EQ_INT(mq_gw_status_from_curl(0), 502); -} - -/* =================================================================== - * mq_gw_parse_http_ver - * =================================================================== */ - -static void -test_parse_http_ver(void) -{ - MQ_CHECK_EQ_INT(mq_gw_parse_http_ver("h3", 2), MQ_HTTP_VER_H3); - MQ_CHECK_EQ_INT(mq_gw_parse_http_ver("H3", 2), MQ_HTTP_VER_H3); /* case-insensitive */ - MQ_CHECK_EQ_INT(mq_gw_parse_http_ver("h2", 2), MQ_HTTP_VER_H2); - MQ_CHECK_EQ_INT(mq_gw_parse_http_ver("h1", 2), MQ_HTTP_VER_H1); - MQ_CHECK_EQ_INT(mq_gw_parse_http_ver("h0", 2), MQ_HTTP_VER_DEFAULT); /* unknown */ - MQ_CHECK_EQ_INT(mq_gw_parse_http_ver("h3x", 3), - MQ_HTTP_VER_DEFAULT); /* valid prefix + junk */ - MQ_CHECK_EQ_INT(mq_gw_parse_http_ver("http3", 5), MQ_HTTP_VER_DEFAULT); /* unknown */ - MQ_CHECK_EQ_INT(mq_gw_parse_http_ver("", 0), MQ_HTTP_VER_DEFAULT); /* empty */ -} - -/* =================================================================== - * mq_gw_parse_cache_ttl - * =================================================================== */ - -static void -test_parse_cache_ttl(void) -{ - /* returns ttl seconds (>0) for valid; 0 for invalid/absent. */ - MQ_CHECK_EQ_INT(mq_gw_parse_cache_ttl("1", 1), 1); - MQ_CHECK_EQ_INT(mq_gw_parse_cache_ttl("60", 2), 60); - MQ_CHECK_EQ_INT(mq_gw_parse_cache_ttl("31536000", 8), 31536000); /* 1y = MAX */ - MQ_CHECK_EQ_INT(mq_gw_parse_cache_ttl("0", 1), 0); /* 0 = invalid (not >=1) */ - MQ_CHECK_EQ_INT(mq_gw_parse_cache_ttl("", 0), 0); /* empty */ - MQ_CHECK_EQ_INT(mq_gw_parse_cache_ttl("x", 1), 0); /* non-numeric */ - MQ_CHECK_EQ_INT(mq_gw_parse_cache_ttl("-5", 2), 0); /* sign */ - MQ_CHECK_EQ_INT(mq_gw_parse_cache_ttl("60 ", 3), 0); /* trailing junk */ - MQ_CHECK_EQ_INT(mq_gw_parse_cache_ttl("31536001", 8), 0); /* > MAX */ - MQ_CHECK_EQ_INT(mq_gw_parse_cache_ttl("99999999999999999999", 20), 0); /* overflow */ -} - -MQ_TEST_MAIN({ - /* target */ - test_target_https_full(); - test_target_http(); - test_target_no_path(); - test_target_query_only(); - test_target_port(); - test_target_ipv6(); - test_target_ipv6_no_port(); - test_target_bad_port(); - test_target_empty_port(); - test_target_ftp(); - test_target_upper_scheme(); - test_target_empty_authority(); - test_target_userinfo(); - test_target_space_in_path(); - test_target_ctrl_in_path(); - test_target_ctrl_in_authority(); - test_target_fragment(); - test_target_authority_too_long(); - test_target_path_too_long(); - test_target_empty(); - /* bracket/colon defects */ - test_target_stray_open_bracket(); - test_target_double_colon_port(); - test_target_lone_close_bracket(); - test_target_ipv6_junk_after_bracket(); - test_target_ipv6_empty_port(); - test_target_ipv6_bad_port(); - test_target_ipv6_unclosed(); - test_target_ipv6_empty_host(); - /* method */ - test_method_lower(); - test_method_mixed(); - test_method_token_chars(); - test_method_too_long(); - test_method_15_ok(); - test_method_space(); - test_method_empty(); - /* strip */ - test_strip_hop_names(); - test_strip_client_names(); - test_strip_server_names(); - test_authorization_forwarded(); - test_accept_forwarded(); - test_cookie_asymmetry(); - test_content_length_not_hop(); - test_xmq_prefix_boundary(); - /* dup */ - test_dup_two_xmq(); - test_dup_distinct_xmq(); - test_dup_non_xmq(); - test_dup_case_insensitive(); - test_dup_empty(); - /* forward cookie */ - test_forward_cookie_requested(); - test_cookie_forward_optin(); - /* curl map */ - test_curl_map(); - /* origin http version parse */ - test_parse_http_ver(); - /* X-Mq-Cache ttl parse */ - test_parse_cache_ttl(); -}) diff --git a/tests/test_gw_req_format.c b/tests/test_gw_req_format.c deleted file mode 100644 index 7eabfe5a..00000000 --- a/tests/test_gw_req_format.c +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "mqtest.h" /* MQ_CHECK / MQ_TEST_MAIN (NOT C assert — vacuous under -DNDEBUG) */ -#include "gateway/mq_gw_metrics.h" - -static mq_gw_req_metrics_t -base(void) -{ - mq_gw_req_metrics_t m = { - .method = "GET", - .status = 200, - .authority = "example.com", - .path = "/big.bin", - .req_bytes = 0, - .resp_bytes = 104857600, - .ttfb_ms = 42, - .duration_ms = 1200, - .origin_protocol = "h2", - .origin_tls = "ok", - .content_encoding = "gzip", - .cache = "bypass", - .origin_reuse = 0, - .origin_connect_ms = 7, - .mp_state = 1, - .completion_ms = 1201, - .reset_reason = "", - }; - return m; -} - -static void -test_clean(void) -{ - char buf[MQ_GW_REQ_LINE_CAP]; - mq_gw_req_metrics_t m = base(); - int n = mq_gw_format_req_line(buf, sizeof(buf), "ab12", 42, &m); - MQ_CHECK(n > 0); - MQ_CHECK(strstr(buf, "mq.req cid=ab12 sid=42 ") == buf); - MQ_CHECK(strstr(buf, "method=GET status=200")); - MQ_CHECK(strstr(buf, "authority=\"example.com\"")); - MQ_CHECK(strstr(buf, "path=\"/big.bin\"")); - MQ_CHECK(strstr(buf, "resp_bytes=104857600")); - MQ_CHECK(strstr(buf, "ttfb_ms=42 duration_ms=1200")); - MQ_CHECK(strstr(buf, "origin_protocol=h2 origin_tls=ok")); - MQ_CHECK(strstr(buf, "origin_tls=ok content_encoding=gzip")); - MQ_CHECK(strstr(buf, "cache=bypass origin_reuse=0")); - MQ_CHECK(strstr(buf, "origin_reuse=0 origin_connect_ms=7")); - MQ_CHECK(strstr(buf, "mp_state=1 completion_ms=1201")); - MQ_CHECK(strstr(buf, "reset=\"\"")); -} - -static void -test_unknowns_and_reset(void) -{ - char buf[MQ_GW_REQ_LINE_CAP]; - mq_gw_req_metrics_t m = base(); - m.method = "-"; - m.status = 0; - m.authority = "-"; - m.path = "-"; - m.ttfb_ms = -1; - m.duration_ms = -1; - m.completion_ms = -1; - m.origin_protocol = "none"; - m.origin_tls = "connect_fail"; - m.reset_reason = "client-reset"; - int n = mq_gw_format_req_line(buf, sizeof(buf), "0", 7, &m); - MQ_CHECK(n > 0); - MQ_CHECK(strstr(buf, "ttfb_ms=-1 duration_ms=-1")); - MQ_CHECK(strstr(buf, "reset=\"client-reset\"")); - MQ_CHECK(strstr(buf, "origin_tls=connect_fail")); -} - -static void -test_quote_escape(void) -{ - char buf[MQ_GW_REQ_LINE_CAP]; - mq_gw_req_metrics_t m = base(); - m.path = "/a\"b"; /* embedded quote must be escaped */ - int n = mq_gw_format_req_line(buf, sizeof(buf), "0", 1, &m); - MQ_CHECK(n > 0); - MQ_CHECK(strstr(buf, "path=\"/a\\\"b\"")); - - /* backslash must also be escaped: /a\b → path="/a\\b" */ - m.path = "/a\\b"; - n = mq_gw_format_req_line(buf, sizeof(buf), "0", 1, &m); - MQ_CHECK(n > 0); - MQ_CHECK(strstr(buf, "path=\"/a\\\\b\"")); -} - -static void -test_truncation(void) -{ - char buf[40]; /* far too small */ - mq_gw_req_metrics_t m = base(); - int n = mq_gw_format_req_line(buf, sizeof(buf), "ab12", 42, &m); - MQ_CHECK(n == -1); -} - -static void -test_reuse(void) -{ - char buf[MQ_GW_REQ_LINE_CAP]; - mq_gw_req_metrics_t m = base(); - m.origin_reuse = 1; - m.origin_connect_ms = 0; /* handshake skipped on a reused connection */ - int n = mq_gw_format_req_line(buf, sizeof(buf), "0", 9, &m); - MQ_CHECK(n > 0); - MQ_CHECK(strstr(buf, "origin_reuse=1 origin_connect_ms=0")); -} - -static void -test_path_cap(void) -{ - char buf[MQ_GW_REQ_LINE_CAP]; - char longpath[MQ_GW_REQ_PATH_CAP + 100]; - memset(longpath, 'x', sizeof(longpath) - 1); - longpath[0] = '/'; - longpath[sizeof(longpath) - 1] = '\0'; - mq_gw_req_metrics_t m = base(); - m.path = longpath; - int n = mq_gw_format_req_line(buf, sizeof(buf), "0", 1, &m); - MQ_CHECK(n > 0); /* capped, NOT truncated to -1 */ - MQ_CHECK(strstr(buf, "\xE2\x80\xA6")); /* truncation marker (U+2026) */ -} - -static void -test_content_encoding_none(void) -{ - char buf[MQ_GW_REQ_LINE_CAP]; - mq_gw_req_metrics_t m = base(); - m.content_encoding = "none"; - int n = mq_gw_format_req_line(buf, sizeof(buf), "0", 5, &m); - MQ_CHECK(n > 0); - MQ_CHECK(strstr(buf, "content_encoding=none")); -} - -MQ_TEST_MAIN({ - test_clean(); - test_unknowns_and_reset(); - test_quote_escape(); - test_truncation(); - test_path_cap(); - test_reuse(); - test_content_encoding_none(); -}) diff --git a/tests/test_h3_req_stats.c b/tests/test_h3_req_stats.c deleted file mode 100644 index 1e9ec399..00000000 --- a/tests/test_h3_req_stats.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "mqtest.h" /* MQ_CHECK / MQ_TEST_MAIN */ -#include "transport/mq_h3.h" - -MQ_TEST_MAIN({ - xqc_request_stats_t st; - MQ_CHECK(mq_h3_req_get_stats(NULL, &st) == -1); - /* a non-NULL req with NULL inner handle is exercised in the gateway - * integration (test_gw_server); here we only pin the NULL contract. */ - MQ_CHECK(mq_h3_req_stream_id(NULL) == 0); -}) diff --git a/tests/test_http1.c b/tests/test_http1.c deleted file mode 100644 index d953aab2..00000000 --- a/tests/test_http1.c +++ /dev/null @@ -1,628 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "gateway/mq_http1.h" -#include "mqtest.h" -#include -#include - -/* --------------------------------------------------------------------------- - * Helper: find a header slice by (case-insensitive) name. Returns index or -1. - * ------------------------------------------------------------------------- */ -static int -find_h(const mq_http1_req_t *r, const char *name) -{ - size_t nl = strlen(name); - for (size_t i = 0; i < r->nh; i++) { - if (r->h[i].nl == nl) { - int eq = 1; - for (size_t j = 0; j < nl; j++) { - char a = r->h[i].n[j], b = name[j]; - if (a >= 'A' && a <= 'Z') a = (char)(a + 32); - if (b >= 'A' && b <= 'Z') b = (char)(b + 32); - if (a != b) { - eq = 0; - break; - } - } - if (eq) return (int)i; - } - } - return -1; -} - -/* ---- normal POST /_mqproxy/fetch with headers + Content-Length ---- */ -static void -test_normal_post(void) -{ - static const char REQ[] = "POST /_mqproxy/fetch HTTP/1.1\r\n" - "Host: localhost\r\n" - "Content-Type: application/json\r\n" - "Content-Length: 11\r\n" - "\r\n" - "hello world"; /* body — not consumed by head parse */ - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_DONE); - MQ_CHECK_EQ_INT(strcmp(r.method, "POST"), 0); - MQ_CHECK_EQ_INT(strcmp(r.path, "/_mqproxy/fetch"), 0); - MQ_CHECK_EQ_INT(r.content_length, 11); - MQ_CHECK_EQ_INT(r.has_chunked_te, 0); - MQ_CHECK_EQ_INT(r.nh, 3); - - int idx = find_h(&r, "content-type"); - MQ_CHECK(idx >= 0); - if (idx >= 0) { - MQ_CHECK_EQ_INT(r.h[idx].vl, strlen("application/json")); - MQ_CHECK_MEM(r.h[idx].v, "application/json", r.h[idx].vl); - } - /* head_len = bytes up to and including blank line (not the body). */ - MQ_CHECK_EQ_INT(r.head_len, (size_t)((sizeof(REQ) - 1) - 11)); -} - -/* ---- OWS trimming: value with leading/trailing spaces+tabs ---- */ -static void -test_ows_trim(void) -{ - static const char REQ[] = "GET / HTTP/1.1\r\n" - "X-Trim: \t value here \t\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_DONE); - int idx = find_h(&r, "x-trim"); - MQ_CHECK(idx >= 0); - if (idx >= 0) { - MQ_CHECK_EQ_INT(r.h[idx].vl, strlen("value here")); - MQ_CHECK_MEM(r.h[idx].v, "value here", r.h[idx].vl); - } -} - -/* ---- split arrival: prefix -> NEED_MORE, full -> DONE, same out ---- */ -static void -test_split_arrival(void) -{ - static const char FULL[] = "POST /_mqproxy/fetch HTTP/1.1\r\n" - "Content-Length: 0\r\n" - "\r\n"; - size_t full_len = sizeof(FULL) - 1; - - /* Feed everything except the final LF (terminator incomplete). */ - mq_http1_req_t r1; - mq_http1_status_t st1 = mq_http1_parse_req((const uint8_t *)FULL, full_len - 1, &r1); - MQ_CHECK_EQ_INT(st1, MQ_HTTP1_NEED_MORE); - - /* Feed the whole thing → DONE. */ - mq_http1_req_t r2; - mq_http1_status_t st2 = mq_http1_parse_req((const uint8_t *)FULL, full_len, &r2); - MQ_CHECK_EQ_INT(st2, MQ_HTTP1_DONE); - MQ_CHECK_EQ_INT(strcmp(r2.method, "POST"), 0); - MQ_CHECK_EQ_INT(strcmp(r2.path, "/_mqproxy/fetch"), 0); - MQ_CHECK_EQ_INT(r2.content_length, 0); - MQ_CHECK_EQ_INT(r2.head_len, full_len); -} - -/* ---- obs-fold (continuation line) → BAD (smuggling vector) ---- */ -static void -test_obs_fold(void) -{ - static const char REQ[] = "GET / HTTP/1.1\r\n" - "X-Fold: a\r\n" - " continued\r\n" /* leading SP = obs-fold */ - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); - - static const char REQ_TAB[] = "GET / HTTP/1.1\r\n" - "X-Fold: a\r\n" - "\tcontinued\r\n" /* leading HTAB = obs-fold */ - "\r\n"; - mq_http1_req_t r2; - st = mq_http1_parse_req((const uint8_t *)REQ_TAB, sizeof(REQ_TAB) - 1, &r2); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- method too long (>15) → BAD ---- */ -static void -test_method_too_long(void) -{ - static const char REQ[] = "ABCDEFGHIJKLMNOP / HTTP/1.1\r\n\r\n"; /* 16 chars */ - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- non-token method char → BAD ---- */ -static void -test_method_non_token(void) -{ - static const char REQ[] = "PO()ST / HTTP/1.1\r\n\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- path not starting with '/' → BAD ---- */ -static void -test_path_not_slash(void) -{ - static const char REQ[] = "GET http://x/ HTTP/1.1\r\n\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- duplicate Content-Length: identical → BAD ---- */ -static void -test_cl_dup_identical(void) -{ - static const char REQ[] = "POST / HTTP/1.1\r\n" - "Content-Length: 5\r\n" - "Content-Length: 5\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- duplicate Content-Length: differing → BAD ---- */ -static void -test_cl_dup_differing(void) -{ - static const char REQ[] = "POST / HTTP/1.1\r\n" - "Content-Length: 5\r\n" - "Content-Length: 6\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- Content-Length non-numeric → BAD ---- */ -static void -test_cl_non_numeric(void) -{ - static const char REQ[] = "POST / HTTP/1.1\r\n" - "Content-Length: 12x\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- Content-Length empty → BAD ---- */ -static void -test_cl_empty(void) -{ - static const char REQ[] = "POST / HTTP/1.1\r\n" - "Content-Length: \r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- Content-Length valid big value (fits int64) ---- */ -static void -test_cl_big(void) -{ - static const char REQ[] = "POST / HTTP/1.1\r\n" - "Content-Length: 9223372036854775807\r\n" /* INT64_MAX */ - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_DONE); - MQ_CHECK_EQ_INT(r.content_length, (int64_t)9223372036854775807LL); -} - -/* ---- Content-Length overflow (> INT64_MAX) → BAD ---- */ -static void -test_cl_overflow(void) -{ - static const char REQ[] = "POST / HTTP/1.1\r\n" - "Content-Length: 99999999999999999999\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- Transfer-Encoding chunked detection (mixed case) ---- */ -static void -test_te_chunked(void) -{ - static const char REQ[] = "POST / HTTP/1.1\r\n" - "Transfer-Encoding: Chunked\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_DONE); - MQ_CHECK_EQ_INT(r.has_chunked_te, 1); - MQ_CHECK_EQ_INT(r.content_length, -1); -} - -/* ---- TE chunked among a comma list (gzip, chunked) ---- */ -static void -test_te_chunked_list(void) -{ - static const char REQ[] = "POST / HTTP/1.1\r\n" - "Transfer-Encoding: gzip, chunked\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_DONE); - MQ_CHECK_EQ_INT(r.has_chunked_te, 1); -} - -/* ---- CL + TE simultaneously: both populated; caller decides ---- */ -static void -test_cl_and_te(void) -{ - static const char REQ[] = "POST / HTTP/1.1\r\n" - "Content-Length: 5\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_DONE); - MQ_CHECK_EQ_INT(r.content_length, 5); - MQ_CHECK_EQ_INT(r.has_chunked_te, 1); -} - -/* ---- Content-Length absent → -1 ---- */ -static void -test_cl_absent(void) -{ - static const char REQ[] = "GET / HTTP/1.1\r\nHost: x\r\n\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_DONE); - MQ_CHECK_EQ_INT(r.content_length, -1); -} - -/* ---- >64 headers → BAD ---- */ -static void -test_too_many_headers(void) -{ - char buf[4096]; - int n = snprintf(buf, sizeof buf, "GET / HTTP/1.1\r\n"); - for (int i = 0; i < 65; i++) /* 65 > 64 */ - n += snprintf(buf + n, sizeof buf - (size_t)n, "X-H%d: v\r\n", i); - n += snprintf(buf + n, sizeof buf - (size_t)n, "\r\n"); - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)buf, (size_t)n, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- exactly 64 headers → DONE (boundary) ---- */ -static void -test_exactly_64_headers(void) -{ - char buf[4096]; - int n = snprintf(buf, sizeof buf, "GET / HTTP/1.1\r\n"); - for (int i = 0; i < 64; i++) - n += snprintf(buf + n, sizeof buf - (size_t)n, "X-H%d: v\r\n", i); - n += snprintf(buf + n, sizeof buf - (size_t)n, "\r\n"); - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)buf, (size_t)n, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_DONE); - MQ_CHECK_EQ_INT(r.nh, 64); -} - -/* ---- head exceeds max (16 KiB) with no terminator → BAD ---- */ -static void -test_head_too_big(void) -{ - /* 17 KiB of a never-terminating request line. */ - size_t cap = 17 * 1024; - char *buf = malloc(cap); - memset(buf, 'a', cap); - memcpy(buf, "GET /", 5); /* keep it superficially valid-looking */ - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)buf, cap, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); - free(buf); -} - -/* ---- embedded NUL in value → BAD ---- */ -static void -test_embedded_nul(void) -{ - /* "X-Bad: a\0b" — NUL inside the value. Build manually (can't use strlen). */ - static const char REQ[] = "GET / HTTP/1.1\r\n" - "X-Bad: a\0b\r\n" - "\r\n"; - /* sizeof includes the trailing implicit NUL; we want the exact crafted len. */ - size_t len = sizeof(REQ) - 1; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, len, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- embedded bare CR in value → BAD ---- */ -static void -test_embedded_cr(void) -{ - static const char REQ[] = "GET / HTTP/1.1\r\n" - "X-Bad: a\rb\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- header with no colon → BAD ---- */ -static void -test_header_no_colon(void) -{ - static const char REQ[] = "GET / HTTP/1.1\r\n" - "NoColonHere\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- empty header name (leading colon) → BAD ---- */ -static void -test_header_empty_name(void) -{ - static const char REQ[] = "GET / HTTP/1.1\r\n" - ": value\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- non-token char in header name → BAD ---- */ -static void -test_header_name_non_token(void) -{ - static const char REQ[] = "GET / HTTP/1.1\r\n" - "Bad Name: v\r\n" /* space in name */ - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- duplicate non-CL header names preserved in order (not collapsed) ---- */ -static void -test_dup_headers_preserved(void) -{ - static const char REQ[] = "GET / HTTP/1.1\r\n" - "X-Mq-Tag: a\r\n" - "X-Mq-Tag: b\r\n" - "\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_DONE); - MQ_CHECK_EQ_INT(r.nh, 2); - MQ_CHECK_EQ_INT(r.h[0].vl, 1); - MQ_CHECK_MEM(r.h[0].v, "a", 1); - MQ_CHECK_EQ_INT(r.h[1].vl, 1); - MQ_CHECK_MEM(r.h[1].v, "b", 1); -} - -/* ---- embedded NUL in path → BAD (NUL-injection/truncation vector) ---- */ -static void -test_path_nul(void) -{ - /* "GET /a\0b HTTP/1.1\r\n\r\n" — must use sizeof, not strlen. */ - static const char REQ[] = "GET /a\0b HTTP/1.1\r\n\r\n"; - size_t len = sizeof(REQ) - 1; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, len, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- control byte 0x1f in path → BAD (same injection class) ---- */ -static void -test_path_ctrl_1f(void) -{ - /* "GET /a\x1fb HTTP/1.1\r\n\r\n" — split to avoid \x1fb being treated as - * a single (out-of-range) hex escape. */ - static const char REQ[] = "GET /a\x1f" - "b HTTP/1.1\r\n\r\n"; - size_t len = sizeof(REQ) - 1; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, len, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- DEL (0x7f) in path → BAD (same injection class) ---- */ -static void -test_path_del(void) -{ - /* "GET /a\x7fb HTTP/1.1\r\n\r\n" — split to avoid \x7fb being treated as - * a single (out-of-range) hex escape by some compilers. */ - static const char REQ[] = "GET /a\x7f" - "b HTTP/1.1\r\n\r\n"; - size_t len = sizeof(REQ) - 1; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, len, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- oversized path (> path buffer) → BAD ---- */ -static void -test_path_too_long(void) -{ - char buf[2048]; - int n = snprintf(buf, sizeof buf, "GET /"); - for (int i = 0; i < 1100; i++) /* path > 1024 */ - buf[n++] = 'a'; - n += snprintf(buf + n, sizeof buf - (size_t)n, " HTTP/1.1\r\n\r\n"); - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)buf, (size_t)n, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ---- missing HTTP version field → BAD ---- */ -static void -test_no_version(void) -{ - static const char REQ[] = "GET /\r\n\r\n"; - mq_http1_req_t r; - mq_http1_status_t st = mq_http1_parse_req((const uint8_t *)REQ, sizeof(REQ) - 1, &r); - MQ_CHECK_EQ_INT(st, MQ_HTTP1_BAD); -} - -/* ==================== serializer ==================== */ - -/* ---- write_status exact format ---- */ -static void -test_write_status(void) -{ - char buf[64]; - memset(buf, 0xAB, sizeof buf); - int n = mq_http1_write_status(buf, sizeof buf, 200, "OK"); - static const char expect[] = "HTTP/1.1 200 OK\r\n"; - MQ_CHECK_EQ_INT(n, (int)(sizeof(expect) - 1)); - MQ_CHECK_MEM(buf, expect, sizeof(expect) - 1); - - int n2 = mq_http1_write_status(buf, sizeof buf, 411, "Length Required"); - static const char expect2[] = "HTTP/1.1 411 Length Required\r\n"; - MQ_CHECK_EQ_INT(n2, (int)(sizeof(expect2) - 1)); - MQ_CHECK_MEM(buf, expect2, sizeof(expect2) - 1); -} - -/* ---- write_status cap too small → negative, nothing written ---- */ -static void -test_write_status_too_small(void) -{ - char buf[8]; - memset(buf, 0xAB, sizeof buf); - int n = mq_http1_write_status(buf, sizeof buf, 200, "OK"); /* needs 17 */ - MQ_CHECK(n < 0); - MQ_CHECK_EQ_INT((unsigned char)buf[0], 0xAB); -} - -/* ---- write_header exact format ---- */ -static void -test_write_header(void) -{ - char buf[64]; - memset(buf, 0xAB, sizeof buf); - int n = mq_http1_write_header(buf, sizeof buf, "Content-Length", "42"); - static const char expect[] = "Content-Length: 42\r\n"; - MQ_CHECK_EQ_INT(n, (int)(sizeof(expect) - 1)); - MQ_CHECK_MEM(buf, expect, sizeof(expect) - 1); -} - -/* ---- write_header cap too small → negative, nothing written ---- */ -static void -test_write_header_too_small(void) -{ - char buf[4]; - memset(buf, 0xAB, sizeof buf); - int n = mq_http1_write_header(buf, sizeof buf, "Content-Length", "42"); - MQ_CHECK(n < 0); - MQ_CHECK_EQ_INT((unsigned char)buf[0], 0xAB); -} - -/* ---- write_header rejects CR/LF in name or value (header injection) ---- */ -static void -test_write_header_rejects_crlf(void) -{ - char buf[128]; - /* CR / LF in the value → reject, nothing written. */ - memset(buf, 0xAB, sizeof buf); - MQ_CHECK(mq_http1_write_header(buf, sizeof buf, "X-Test", "a\rb") < 0); - MQ_CHECK_EQ_INT((unsigned char)buf[0], 0xAB); - MQ_CHECK(mq_http1_write_header(buf, sizeof buf, "X-Test", "a\nb") < 0); - MQ_CHECK(mq_http1_write_header(buf, sizeof buf, "X-Test", "a\r\nEvil: x") < 0); - /* CR / LF in the name → reject. */ - MQ_CHECK(mq_http1_write_header(buf, sizeof buf, "X-Te\rst", "v") < 0); - MQ_CHECK(mq_http1_write_header(buf, sizeof buf, "X-Te\nst", "v") < 0); - /* A clean header still serializes. */ - int n = mq_http1_write_header(buf, sizeof buf, "X-Test", "clean value"); - MQ_CHECK(n > 0); - MQ_CHECK_MEM(buf, "X-Test: clean value\r\n", (size_t)n); -} - -/* ---- chunk_frame exact bytes for a known 5-byte payload ---- */ -static void -test_chunk_frame(void) -{ - char buf[64]; - size_t n = mq_http1_chunk_frame(buf, sizeof buf, (const uint8_t *)"hello", 5); - static const char expect[] = "5\r\nhello\r\n"; - MQ_CHECK_EQ_INT(n, sizeof(expect) - 1); - MQ_CHECK_MEM(buf, expect, sizeof(expect) - 1); -} - -/* ---- chunk_frame hex for length > 0xf (26 -> "1a") ---- */ -static void -test_chunk_frame_hex(void) -{ - char payload[26]; - memset(payload, 'z', sizeof payload); - char buf[64]; - size_t n = - mq_http1_chunk_frame(buf, sizeof buf, (const uint8_t *)payload, sizeof payload); - static const char prefix[] = "1a\r\n"; /* lowercase hex of 26 */ - MQ_CHECK(n >= sizeof(prefix) - 1); - MQ_CHECK_MEM(buf, prefix, sizeof(prefix) - 1); - /* total = "1a\r\n" + 26 + "\r\n" = 4 + 26 + 2 = 32 */ - MQ_CHECK_EQ_INT(n, 4 + 26 + 2); - MQ_CHECK_MEM(buf + 4, payload, 26); - MQ_CHECK_MEM(buf + 4 + 26, "\r\n", 2); -} - -/* ---- chunk_frame returns 0 when cap too small ---- */ -static void -test_chunk_frame_too_small(void) -{ - char buf[6]; /* needs 10 for a 5-byte payload */ - memset(buf, 0xAB, sizeof buf); - size_t n = mq_http1_chunk_frame(buf, sizeof buf, (const uint8_t *)"hello", 5); - MQ_CHECK_EQ_INT(n, 0); - MQ_CHECK_EQ_INT((unsigned char)buf[0], 0xAB); -} - -MQ_TEST_MAIN({ - /* parser */ - test_normal_post(); - test_ows_trim(); - test_split_arrival(); - test_obs_fold(); - test_method_too_long(); - test_method_non_token(); - test_path_not_slash(); - test_path_nul(); - test_path_ctrl_1f(); - test_path_del(); - test_cl_dup_identical(); - test_cl_dup_differing(); - test_cl_non_numeric(); - test_cl_empty(); - test_cl_big(); - test_cl_overflow(); - test_te_chunked(); - test_te_chunked_list(); - test_cl_and_te(); - test_cl_absent(); - test_too_many_headers(); - test_exactly_64_headers(); - test_head_too_big(); - test_embedded_nul(); - test_embedded_cr(); - test_header_no_colon(); - test_header_empty_name(); - test_header_name_non_token(); - test_dup_headers_preserved(); - test_path_too_long(); - test_no_version(); - /* serializer */ - test_write_status(); - test_write_status_too_small(); - test_write_header(); - test_write_header_too_small(); - test_write_header_rejects_crlf(); - test_chunk_frame(); - test_chunk_frame_hex(); - test_chunk_frame_too_small(); -}) diff --git a/tests/test_http_connect.c b/tests/test_http_connect.c deleted file mode 100644 index 3980bb7f..00000000 --- a/tests/test_http_connect.c +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "ingress/mq_http_connect.h" -#include "mqtest.h" -#include -#include - -/* ---- request fixtures ---- */ -static const char REQ_DOMAIN[] = - "CONNECT example.com:443 HTTP/1.1\r\nHost: example.com\r\n\r\n"; -static const char REQ_IPV6[] = "CONNECT [2001:db8::1]:443 HTTP/1.1\r\n\r\n"; -static const char REQ_IPV4[] = "CONNECT 93.184.216.34:80 HTTP/1.1\r\n\r\n"; -static const char REQ_NOTERM[] = "CONNECT example.com:443 HTTP/1.1\r\n"; -static const char REQ_GET[] = "GET / HTTP/1.1\r\n\r\n"; -static const char REQ_NOPORT[] = "CONNECT example.com HTTP/1.1\r\n\r\n"; - -static void -test_domain(void) -{ - mq_http_target_t out; - memset(&out, 0, sizeof out); - size_t hlen = 0; - mq_http_status_t st = mq_http_connect_parse((const uint8_t *)REQ_DOMAIN, - strlen(REQ_DOMAIN), &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_CONNECT_DONE); - MQ_CHECK_EQ_INT(out.atype, MQ_ADDR_DOMAIN); - MQ_CHECK_EQ_INT(out.host_len, 11); - MQ_CHECK_MEM(out.host, "example.com", 11); - MQ_CHECK_EQ_INT(out.port, 443); - MQ_CHECK_EQ_INT(hlen, strlen(REQ_DOMAIN)); -} - -static void -test_ipv6(void) -{ - mq_http_target_t out; - memset(&out, 0, sizeof out); - size_t hlen = 0; - mq_http_status_t st = - mq_http_connect_parse((const uint8_t *)REQ_IPV6, strlen(REQ_IPV6), &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_CONNECT_DONE); - MQ_CHECK_EQ_INT(out.atype, MQ_ADDR_IPV6); - MQ_CHECK_EQ_INT(out.host_len, 16); - MQ_CHECK_EQ_INT(out.port, 443); - - uint8_t expect[16]; - MQ_CHECK_EQ_INT(inet_pton(AF_INET6, "2001:db8::1", expect), 1); - MQ_CHECK_MEM(out.host, expect, 16); -} - -static void -test_ipv4(void) -{ - mq_http_target_t out; - memset(&out, 0, sizeof out); - size_t hlen = 0; - mq_http_status_t st = - mq_http_connect_parse((const uint8_t *)REQ_IPV4, strlen(REQ_IPV4), &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_CONNECT_DONE); - MQ_CHECK_EQ_INT(out.atype, MQ_ADDR_IPV4); - MQ_CHECK_EQ_INT(out.host_len, 4); - static const uint8_t expect[4] = {93, 184, 216, 34}; - MQ_CHECK_MEM(out.host, expect, 4); - MQ_CHECK_EQ_INT(out.port, 80); -} - -static void -test_fragmented(void) -{ - mq_http_target_t out; - memset(&out, 0, sizeof out); - size_t hlen = 0; - mq_http_status_t st = mq_http_connect_parse((const uint8_t *)REQ_NOTERM, - strlen(REQ_NOTERM), &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_NEED_MORE); - - st = mq_http_connect_parse((const uint8_t *)REQ_DOMAIN, strlen(REQ_DOMAIN), &out, - &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_CONNECT_DONE); - MQ_CHECK_EQ_INT(out.port, 443); -} - -static void -test_unsupported(void) -{ - mq_http_target_t out; - memset(&out, 0, sizeof out); - size_t hlen = 0; - mq_http_status_t st = - mq_http_connect_parse((const uint8_t *)REQ_GET, strlen(REQ_GET), &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_UNSUPPORTED); -} - -static void -test_missing_port(void) -{ - mq_http_target_t out; - memset(&out, 0, sizeof out); - size_t hlen = 0; - mq_http_status_t st = mq_http_connect_parse((const uint8_t *)REQ_NOPORT, - strlen(REQ_NOPORT), &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_BAD); -} - -/* Fix #1 (TDD-RED): unclosed bracket must be BAD */ -static void -test_unclosed_bracket(void) -{ - /* "[::1:443" — starts with '[' but closing ']' is absent */ - static const char req[] = "CONNECT [::1:443 HTTP/1.1\r\n\r\n"; - mq_http_target_t out; - memset(&out, 0, sizeof out); - size_t hlen = 0; - mq_http_status_t st = - mq_http_connect_parse((const uint8_t *)req, strlen(req), &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_BAD); -} - -/* Over-long domain: 256-char host > MQ_MAX_HOST=255 → BAD */ -static void -test_overlong_domain(void) -{ - /* Build "CONNECT <256 a's>:443 HTTP/1.1\r\n\r\n" */ - char req[512]; - memset(req, 0, sizeof req); - int n = snprintf(req, sizeof req, "CONNECT "); - for (int i = 0; i < 256; i++) - req[n++] = 'a'; - n += snprintf(req + n, sizeof req - (size_t)n, ":443 HTTP/1.1\r\n\r\n"); - - mq_http_target_t out; - memset(&out, 0, sizeof out); - size_t hlen = 0; - mq_http_status_t st = - mq_http_connect_parse((const uint8_t *)req, (size_t)n, &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_BAD); -} - -/* Port boundaries */ -static void -test_port_boundaries(void) -{ - mq_http_target_t out; - size_t hlen; - mq_http_status_t st; - - /* :1 — minimum valid port → DONE */ - static const char req_min[] = "CONNECT example.com:1 HTTP/1.1\r\n\r\n"; - memset(&out, 0, sizeof out); - hlen = 0; - st = mq_http_connect_parse((const uint8_t *)req_min, strlen(req_min), &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_CONNECT_DONE); - MQ_CHECK_EQ_INT(out.port, 1); - - /* :65535 — maximum valid port → DONE */ - static const char req_max[] = "CONNECT example.com:65535 HTTP/1.1\r\n\r\n"; - memset(&out, 0, sizeof out); - hlen = 0; - st = mq_http_connect_parse((const uint8_t *)req_max, strlen(req_max), &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_CONNECT_DONE); - MQ_CHECK_EQ_INT(out.port, 65535); - - /* :65536 — invalid port → BAD */ - static const char req_over[] = "CONNECT example.com:65536 HTTP/1.1\r\n\r\n"; - memset(&out, 0, sizeof out); - hlen = 0; - st = mq_http_connect_parse((const uint8_t *)req_over, strlen(req_over), &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_BAD); - - /* empty port (host: before space) → BAD */ - static const char req_empty_port[] = "CONNECT example.com: HTTP/1.1\r\n\r\n"; - memset(&out, 0, sizeof out); - hlen = 0; - st = mq_http_connect_parse((const uint8_t *)req_empty_port, strlen(req_empty_port), - &out, &hlen); - MQ_CHECK_EQ_INT(st, MQ_HTTP_BAD); -} - -/* mq_http_build_200 with cap one byte too small → returns 0, writes nothing */ -static void -test_build_200_too_small(void) -{ - static const char expect[] = "HTTP/1.1 200 Connection Established\r\n\r\n"; - size_t full = strlen(expect); - char buf[64]; - memset(buf, 0xAB, sizeof buf); /* sentinel pattern */ - size_t n = mq_http_build_200(buf, full - 1); - MQ_CHECK_EQ_INT(n, 0); - /* No byte past cap should have been written — check the sentinel. */ - unsigned char sentinel = 0xAB; - MQ_CHECK_EQ_INT((unsigned char)buf[0], sentinel); -} - -/* mq_http_status_line(MQ_TCP_DNS_FAILED) must contain "502" */ -static void -test_status_line_dns_failed(void) -{ - const char *line = mq_http_status_line(MQ_TCP_DNS_FAILED); - MQ_CHECK(strstr(line, "502") != NULL); -} - -static void -test_build_200(void) -{ - char buf[64]; - size_t n = mq_http_build_200(buf, sizeof buf); - static const char expect[] = "HTTP/1.1 200 Connection Established\r\n\r\n"; - MQ_CHECK_EQ_INT(n, strlen(expect)); - MQ_CHECK_MEM(buf, expect, n); -} - -static void -test_status_line(void) -{ - const char *line = mq_http_status_line(MQ_TCP_TIMEOUT); - MQ_CHECK(strstr(line, "504") != NULL); - MQ_CHECK(strstr(mq_http_status_line(MQ_TCP_POLICY_DENIED), "403") != NULL); - MQ_CHECK(strstr(mq_http_status_line(MQ_TCP_CONN_REFUSED), "502") != NULL); -} - -MQ_TEST_MAIN({ - test_domain(); - test_ipv6(); - test_ipv4(); - test_fragmented(); - test_unsupported(); - test_missing_port(); - test_unclosed_bracket(); - test_overlong_domain(); - test_port_boundaries(); - test_build_200(); - test_build_200_too_small(); - test_status_line(); - test_status_line_dns_failed(); -}) diff --git a/tests/test_ignore_hosts.c b/tests/test_ignore_hosts.c deleted file mode 100644 index 96ad4a07..00000000 --- a/tests/test_ignore_hosts.c +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// Unit tests for mq_ignore_hosts — the MITM "leave-this-flow-opaque" host list. -// Entries are normalized with the list's OWN rules (lowercase + single -// trailing-dot strip, leading '.' PRESERVED as the suffix marker). The SNI -// passed to match() is ALREADY mq_mitm_normalize_sni-normalized by the caller -// (lowercase, no leading/trailing dot), so match() only compares. -#include "mqtest.h" -#include "mitm/mq_ignore_hosts.h" -#include - -static void -test_basic_exact_and_suffix(void) -{ - mq_ignore_hosts_t *l = mq_ignore_hosts_new(); - MQ_CHECK(l != NULL); - MQ_CHECK_EQ_INT(mq_ignore_hosts_add(l, ".apple.com"), 0); - MQ_CHECK_EQ_INT(mq_ignore_hosts_add(l, "signal.org"), 0); - - // Leading-dot entry ".apple.com" = all subdomains, NOT the apex. - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "gateway.apple.com"), 1); - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "a.b.apple.com"), 1); - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "apple.com"), 0); - // "notapple.com" ends with "apple.com" but the char before is 't', not '.' - // — the ".apple.com" suffix must NOT match. - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "notapple.com"), 0); - - // Exact entry "signal.org" = apex only. - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "signal.org"), 1); - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "m.signal.org"), 0); - - // SIGNAL.ORG: the caller normalizes to "signal.org" before calling match. - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "signal.org"), 1); - - mq_ignore_hosts_free(l); -} - -static void -test_empty_list(void) -{ - mq_ignore_hosts_t *l = mq_ignore_hosts_new(); - MQ_CHECK(l != NULL); - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "anything.example"), 0); - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, ""), 0); - mq_ignore_hosts_free(l); -} - -static void -test_normalization(void) -{ - mq_ignore_hosts_t *l = mq_ignore_hosts_new(); - MQ_CHECK(l != NULL); - // Uppercase entry is lowercased on add; matched against normalized SNI. - MQ_CHECK_EQ_INT(mq_ignore_hosts_add(l, "EXAMPLE.COM"), 0); - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "example.com"), 1); - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "EXAMPLE.COM"), - 0); // caller passes lowercase - - // Trailing dot on an exact entry is stripped → matches dot-free SNI. - MQ_CHECK_EQ_INT(mq_ignore_hosts_add(l, "trailing.test."), 0); - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "trailing.test"), 1); - - // Trailing dot on a leading-dot entry: ".sub.test." → ".sub.test". - MQ_CHECK_EQ_INT(mq_ignore_hosts_add(l, ".SUB.TEST."), 0); - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "x.sub.test"), 1); - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "sub.test"), 0); - - mq_ignore_hosts_free(l); -} - -static void -test_invalid_inputs(void) -{ - mq_ignore_hosts_t *l = mq_ignore_hosts_new(); - MQ_CHECK(l != NULL); - MQ_CHECK(mq_ignore_hosts_add(l, NULL) < 0); - MQ_CHECK(mq_ignore_hosts_add(l, "") < 0); - // A bare "." (just a leading-dot marker with no host) is invalid. - MQ_CHECK(mq_ignore_hosts_add(l, ".") < 0); - // Over-long pattern is rejected. - char big[512]; - for (size_t i = 0; i < sizeof(big) - 1; i++) - big[i] = 'a'; - big[sizeof(big) - 1] = '\0'; - MQ_CHECK(mq_ignore_hosts_add(l, big) < 0); - - // NULL list / NULL sni are safe (no match, no crash). - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(NULL, "x"), 0); - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, NULL), 0); - - mq_ignore_hosts_free(l); - mq_ignore_hosts_free(NULL); // free(NULL) is a no-op -} - -static void -test_full_list(void) -{ - mq_ignore_hosts_t *l = mq_ignore_hosts_new(); - MQ_CHECK(l != NULL); - char host[64]; - int added = 0; - for (int i = 0; i < MQ_IGNORE_HOSTS_MAX; i++) { - snprintf(host, sizeof(host), "host%d.test", i); - if (mq_ignore_hosts_add(l, host) == 0) added++; - } - MQ_CHECK_EQ_INT(added, MQ_IGNORE_HOSTS_MAX); - // One more must be rejected (list full). - MQ_CHECK(mq_ignore_hosts_add(l, "overflow.test") < 0); - // The entries that did fit still match. - MQ_CHECK_EQ_INT(mq_ignore_hosts_match(l, "host0.test"), 1); - mq_ignore_hosts_free(l); -} - -MQ_TEST_MAIN(test_basic_exact_and_suffix(); test_empty_list(); test_normalization(); - test_invalid_inputs(); test_full_list();) diff --git a/tests/test_max_conns.c b/tests/test_max_conns.c deleted file mode 100644 index 932310c7..00000000 --- a/tests/test_max_conns.c +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -// -// Unit test for the mq_transport connection-count cap API (pre-auth DoS). -// Pure counter logic — no handshake, no sockets. - -#include "mqtest.h" -#include "transport/mq_transport.h" - -MQ_TEST_MAIN({ - mq_transport_t *t = mq_transport_new(/*is_server=*/0); - MQ_CHECK(t != NULL); - - /* Default: no cap configured -> never at limit. */ - MQ_CHECK_EQ_INT(mq_transport_n_conns(t), 0); - MQ_CHECK_EQ_INT(mq_transport_conn_at_limit(t), 0); - - /* Cap of 2: admit two, third is at-limit. */ - mq_transport_set_max_conns(t, 2); - MQ_CHECK_EQ_INT(mq_transport_conn_at_limit(t), 0); - mq_transport_conn_inc(t); - MQ_CHECK_EQ_INT(mq_transport_n_conns(t), 1); - MQ_CHECK_EQ_INT(mq_transport_conn_at_limit(t), 0); - mq_transport_conn_inc(t); - MQ_CHECK_EQ_INT(mq_transport_n_conns(t), 2); - MQ_CHECK_EQ_INT(mq_transport_conn_at_limit(t), 1); /* full */ - - /* Release one -> a slot frees. */ - mq_transport_conn_dec(t); - MQ_CHECK_EQ_INT(mq_transport_n_conns(t), 1); - MQ_CHECK_EQ_INT(mq_transport_conn_at_limit(t), 0); - - /* dec saturates at 0 (never underflows). */ - mq_transport_conn_dec(t); - mq_transport_conn_dec(t); - MQ_CHECK_EQ_INT(mq_transport_n_conns(t), 0); - - /* max_conns == 0 disables the cap even at a high count. */ - mq_transport_set_max_conns(t, 0); - for (int i = 0; i < 100; i++) { - mq_transport_conn_inc(t); - } - MQ_CHECK_EQ_INT(mq_transport_conn_at_limit(t), 0); - - mq_transport_free(t); -}); diff --git a/tests/test_metrics_format.c b/tests/test_metrics_format.c deleted file mode 100644 index 22a6fd48..00000000 --- a/tests/test_metrics_format.c +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta -/* test_metrics_format.c — Phase 5c: pure logfmt formatters. No I/O. */ -#include "mqtest.h" -#include -#include -#include "transport/mq_conn.h" - -static void -test_path_line(void) -{ - xqc_path_metrics_t p; - memset(&p, 0, sizeof(p)); - p.path_id = 1; - p.path_state = 2; /* ACTIVE */ - p.path_srtt = 12345; /* usec -> 12 ms (integer division) */ - p.path_est_bw = 6029312; /* bytes/sec */ - p.path_send_bytes = 1287654; - p.path_recv_bytes = 83120; - p.path_lost_count = 14; - - char buf[MQ_METRICS_LINE_CAP]; - int n = mq_conn_format_path_line(buf, sizeof(buf), &p); - MQ_CHECK(n > 0); - MQ_CHECK(strcmp(buf, - "mq.path id=1 state=2 srtt_ms=12 bw_Bps=6029312 " - "sent=1287654 recv=83120 lost=14 min_rtt_ms=0 cwnd=0 inflight=0") == - 0); - - /* Populated-value sub-case: verify min_rtt/cwnd/inflight render correctly. */ - xqc_path_metrics_t p2; - memset(&p2, 0, sizeof(p2)); - p2.path_min_rtt = 12000; /* usec -> 12 ms */ - p2.path_cwnd = 65535; - p2.path_bytes_in_flight = 4096; - char buf2[MQ_METRICS_LINE_CAP]; - int n2 = mq_conn_format_path_line(buf2, sizeof(buf2), &p2); - MQ_CHECK(n2 > 0); - MQ_CHECK(strstr(buf2, "min_rtt_ms=12 ") != NULL); - MQ_CHECK(strstr(buf2, "cwnd=65535 ") != NULL); - MQ_CHECK(strstr(buf2, "inflight=4096") != NULL); -} - -static void -test_conn_line(void) -{ - xqc_conn_stats_t st; - memset(&st, 0, sizeof(st)); - st.mp_state = 1; - st.paths_info_count = 2; - st.total_app_bytes = 1320044; - st.standby_path_app_bytes = 0; - - char buf[MQ_METRICS_LINE_CAP]; - int n = mq_conn_format_conn_line(buf, sizeof(buf), &st); - MQ_CHECK(n > 0); - MQ_CHECK( - strcmp(buf, "mq.conn mp_state=1 paths=2 app_bytes=1320044 standby_bytes=0") == 0); -} - -static void -test_truncation(void) -{ - xqc_path_metrics_t p; - memset(&p, 0, sizeof(p)); - p.path_id = 1; - char tiny[8]; - /* Too small to hold the line -> snprintf truncates -> return -1. */ - MQ_CHECK_EQ_INT(mq_conn_format_path_line(tiny, sizeof(tiny), &p), -1); -} - -static void -test_metrics_format(void) -{ - test_path_line(); - test_conn_line(); - test_truncation(); -} - -MQ_TEST_MAIN(test_metrics_format()) diff --git a/tests/test_mitm_conn.c b/tests/test_mitm_conn.c deleted file mode 100644 index 62f9aff2..00000000 --- a/tests/test_mitm_conn.c +++ /dev/null @@ -1,355 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// Unit tests for mq_mitm_conn's DECISION seam (Phase 7 Slice 3 Task 10): given a -// fully-drained ClientHello buffer + the recovered orig-dst, route to the opaque -// relay (ignore-hosts hit) or the MITM path (miss), and hard-fail on no/invalid -// SNI. The seam is exercised WITHOUT sockets/TLS: we feed a synthetic ClientHello -// to mq_mitm_conn_decide_for_test with a stub opaque_open (capturing its args) -// and an injected MITM-start hook (capturing branch selection). The live libevent -// drain (on_readable/on_deadline) is covered by the Chunk 6 e2e, not here. -#include "mqtest.h" - -#include "mitm/mq_ignore_hosts.h" -#include "mitm/mq_mitm_conn.h" -#include "wire/mq_wire.h" - -#include -#include -#include - -// A real libevent base (Task 11: mq_mitm_ctx_new now creates a deferred-free -// event, so the base must be valid even though the decision seam never runs the -// loop — teardown is drained synchronously by mq_mitm_ctx_free). -static struct event_base *g_base; - -// Decision-seam entry: now declared with its real mq_mitm_route_t signature in -// mq_mitm_conn.h (carry-forward fix #1 — previously an incompatible local -// `extern int`). The route constants below mirror mq_mitm_route_t so the -// MQ_CHECK_EQ_INT comparisons read clearly. -typedef enum { R_OPAQUE = 0, R_MITM = 1, R_FAIL = -1 } route_t; - -// --------------------------------------------------------------------------- -// ClientHello byte-array builder (copied from tests/test_clienthello.c). -// --------------------------------------------------------------------------- -typedef struct { - uint8_t buf[2048]; - size_t len; -} chb_t; - -static void -chb_u8(chb_t *b, uint8_t v) -{ - b->buf[b->len++] = v; -} -static void -chb_u16(chb_t *b, uint16_t v) -{ - b->buf[b->len++] = (uint8_t)(v >> 8); - b->buf[b->len++] = (uint8_t)(v & 0xff); -} -static void -chb_bytes(chb_t *b, const uint8_t *p, size_t n) -{ - memcpy(b->buf + b->len, p, n); - b->len += n; -} - -static void -build_extensions(chb_t *ext, int want_sni, const char *host, int alpn_mode) -{ - if (want_sni) { - size_t hlen = strlen(host); - chb_u16(ext, 0x0000); - uint16_t list_len = (uint16_t)(1 + 2 + hlen); - chb_u16(ext, (uint16_t)(2 + list_len)); - chb_u16(ext, list_len); - chb_u8(ext, 0x00); - chb_u16(ext, (uint16_t)hlen); - chb_bytes(ext, (const uint8_t *)host, hlen); - } - if (alpn_mode != 0) { - chb_u16(ext, 0x0010); - chb_t list; - list.len = 0; - if (alpn_mode == 1) { - chb_u8(&list, 2); - chb_bytes(&list, (const uint8_t *)"h2", 2); - chb_u8(&list, 8); - chb_bytes(&list, (const uint8_t *)"http/1.1", 8); - } else { - chb_u8(&list, 8); - chb_bytes(&list, (const uint8_t *)"http/1.1", 8); - } - chb_u16(ext, (uint16_t)(2 + list.len)); - chb_u16(ext, (uint16_t)list.len); - chb_bytes(ext, list.buf, list.len); - } -} - -static void -build_clienthello(chb_t *out, int want_sni, const char *host, int alpn_mode) -{ - chb_t body; - body.len = 0; - chb_u16(&body, 0x0303); - static const uint8_t random32[32] = {0}; - chb_bytes(&body, random32, 32); - chb_u8(&body, 0x00); - chb_u16(&body, 0x0002); - chb_u16(&body, 0x1301); - chb_u8(&body, 0x01); - chb_u8(&body, 0x00); - - chb_t ext; - ext.len = 0; - build_extensions(&ext, want_sni, host, alpn_mode); - chb_u16(&body, (uint16_t)ext.len); - chb_bytes(&body, ext.buf, ext.len); - - chb_t hs; - hs.len = 0; - chb_u8(&hs, 0x01); - chb_u8(&hs, (uint8_t)((body.len >> 16) & 0xff)); - chb_u8(&hs, (uint8_t)((body.len >> 8) & 0xff)); - chb_u8(&hs, (uint8_t)(body.len & 0xff)); - chb_bytes(&hs, body.buf, body.len); - - out->len = 0; - chb_u8(out, 0x16); - chb_u16(out, 0x0301); - chb_u16(out, (uint16_t)hs.len); - chb_bytes(out, hs.buf, hs.len); -} - -// --------------------------------------------------------------------------- -// Stubs / captures -// --------------------------------------------------------------------------- - -static struct { - int called; - uint8_t host[16]; - size_t host_len; - mq_addr_type_t atype; - uint16_t port; - int local_fd; - uint8_t prebuf[2048]; - size_t prebuf_len; -} g_opaque; - -static void -stub_opaque_open(void *core, const uint8_t *host, size_t host_len, mq_addr_type_t atype, - uint16_t port, int local_fd, const uint8_t *prebuf, size_t prebuf_len, - void *user, mq_tcp_open_cb cb) -{ - (void)core; - (void)user; - (void)cb; - g_opaque.called++; - g_opaque.host_len = - host_len <= sizeof(g_opaque.host) ? host_len : sizeof(g_opaque.host); - memcpy(g_opaque.host, host, g_opaque.host_len); - g_opaque.atype = atype; - g_opaque.port = port; - g_opaque.local_fd = local_fd; - g_opaque.prebuf_len = - prebuf_len <= sizeof(g_opaque.prebuf) ? prebuf_len : sizeof(g_opaque.prebuf); - if (prebuf) memcpy(g_opaque.prebuf, prebuf, g_opaque.prebuf_len); -} - -static struct { - int called; - char sni[256]; - size_t buf_len; - int local_fd; -} g_mitm; - -static void -mitm_hook(void *hook_user, int local_fd, const uint8_t *buf, size_t len, - const char *normalized_sni) -{ - (void)hook_user; - (void)buf; - g_mitm.called++; - g_mitm.local_fd = local_fd; - g_mitm.buf_len = len; - if (normalized_sni) { - strncpy(g_mitm.sni, normalized_sni, sizeof(g_mitm.sni) - 1); - g_mitm.sni[sizeof(g_mitm.sni) - 1] = '\0'; - } else { - g_mitm.sni[0] = '\0'; - } -} - -static void -reset_captures(void) -{ - memset(&g_opaque, 0, sizeof(g_opaque)); - memset(&g_mitm, 0, sizeof(g_mitm)); -} - -// The recovered orig-dst the tproxy listener would pass: 93.184.216.34:443 -// (deliberately NOT the SNI's resolved address — we assert it is forwarded -// UNCHANGED and the SNI is never substituted as the host). -static const uint8_t ORIG_IP[4] = {93, 184, 216, 34}; -#define ORIG_PORT 443 - -// --------------------------------------------------------------------------- -// Cases -// --------------------------------------------------------------------------- - -// ignore-hosts HIT → opaque relay called with the drained buffer as prebuf and -// the ORIGINAL host/port (not the SNI). -static void -test_ignore_hit_routes_opaque(void) -{ - reset_captures(); - mq_ignore_hosts_t *ign = mq_ignore_hosts_new(); - MQ_CHECK(ign != NULL); - MQ_CHECK(mq_ignore_hosts_add(ign, "example.com") == 0); // exact apex - - mq_mitm_ctx_t *ctx = - mq_mitm_ctx_new(NULL, ign, NULL, stub_opaque_open, (void *)0xC0FFEE, g_base); - MQ_CHECK(ctx != NULL); - mq_mitm_ctx_set_mitm_hook_for_test(ctx, mitm_hook, NULL); - - chb_t b; - build_clienthello(&b, 1, "example.com", 1); // SNI=example.com, ALPN h2 - - int r = mq_mitm_conn_decide_for_test(ctx, b.buf, b.len, ORIG_IP, sizeof(ORIG_IP), - MQ_ADDR_IPV4, ORIG_PORT, /*local_fd=*/-1); - MQ_CHECK_EQ_INT(r, R_OPAQUE); - MQ_CHECK_EQ_INT(g_opaque.called, 1); - MQ_CHECK_EQ_INT(g_mitm.called, 0); - // ORIGINAL orig-dst forwarded unchanged (NOT the SNI). - MQ_CHECK_EQ_INT(g_opaque.atype, MQ_ADDR_IPV4); - MQ_CHECK_EQ_INT(g_opaque.port, ORIG_PORT); - MQ_CHECK_EQ_INT(g_opaque.host_len, sizeof(ORIG_IP)); - MQ_CHECK_MEM(g_opaque.host, ORIG_IP, sizeof(ORIG_IP)); - MQ_CHECK_EQ_INT(g_opaque.local_fd, -1); - // The drained ClientHello is replayed verbatim as prebuf. - MQ_CHECK_EQ_INT(g_opaque.prebuf_len, (long long)b.len); - MQ_CHECK_MEM(g_opaque.prebuf, b.buf, b.len); - - mq_mitm_ctx_free(ctx); - mq_ignore_hosts_free(ign); -} - -// Suffix entry ".example.com" matches a strict subdomain → opaque. -static void -test_ignore_suffix_routes_opaque(void) -{ - reset_captures(); - mq_ignore_hosts_t *ign = mq_ignore_hosts_new(); - MQ_CHECK(mq_ignore_hosts_add(ign, ".example.com") == 0); // suffix - - mq_mitm_ctx_t *ctx = mq_mitm_ctx_new(NULL, ign, NULL, stub_opaque_open, NULL, g_base); - mq_mitm_ctx_set_mitm_hook_for_test(ctx, mitm_hook, NULL); - - chb_t b; - build_clienthello(&b, 1, "api.example.com", 1); - int r = mq_mitm_conn_decide_for_test(ctx, b.buf, b.len, ORIG_IP, sizeof(ORIG_IP), - MQ_ADDR_IPV4, ORIG_PORT, -1); - MQ_CHECK_EQ_INT(r, R_OPAQUE); - MQ_CHECK_EQ_INT(g_opaque.called, 1); - MQ_CHECK_EQ_INT(g_mitm.called, 0); - - mq_mitm_ctx_free(ctx); - mq_ignore_hosts_free(ign); -} - -// ignore-hosts MISS → MITM branch SELECTED (observed via the injected hook). -static void -test_ignore_miss_routes_mitm(void) -{ - reset_captures(); - mq_ignore_hosts_t *ign = mq_ignore_hosts_new(); - MQ_CHECK(mq_ignore_hosts_add(ign, "signal.org") == 0); // not our SNI - - mq_mitm_ctx_t *ctx = mq_mitm_ctx_new(NULL, ign, NULL, stub_opaque_open, NULL, g_base); - mq_mitm_ctx_set_mitm_hook_for_test(ctx, mitm_hook, NULL); - - chb_t b; - build_clienthello(&b, 1, "Example.COM", 1); // mixed case → normalizes - int r = mq_mitm_conn_decide_for_test(ctx, b.buf, b.len, ORIG_IP, sizeof(ORIG_IP), - MQ_ADDR_IPV4, ORIG_PORT, -1); - MQ_CHECK_EQ_INT(r, R_MITM); - MQ_CHECK_EQ_INT(g_mitm.called, 1); - MQ_CHECK_EQ_INT(g_opaque.called, 0); - MQ_CHECK(strcmp(g_mitm.sni, "example.com") == 0); // normalized lowercase - MQ_CHECK_EQ_INT(g_mitm.buf_len, (long long)b.len); - - mq_mitm_ctx_free(ctx); - mq_ignore_hosts_free(ign); -} - -// NULL ignore-hosts list → nothing opaque → MITM. -static void -test_null_ignore_routes_mitm(void) -{ - reset_captures(); - mq_mitm_ctx_t *ctx = - mq_mitm_ctx_new(NULL, NULL, NULL, stub_opaque_open, NULL, g_base); - mq_mitm_ctx_set_mitm_hook_for_test(ctx, mitm_hook, NULL); - - chb_t b; - build_clienthello(&b, 1, "example.com", 1); - int r = mq_mitm_conn_decide_for_test(ctx, b.buf, b.len, ORIG_IP, sizeof(ORIG_IP), - MQ_ADDR_IPV4, ORIG_PORT, -1); - MQ_CHECK_EQ_INT(r, R_MITM); - MQ_CHECK_EQ_INT(g_mitm.called, 1); - MQ_CHECK_EQ_INT(g_opaque.called, 0); - - mq_mitm_ctx_free(ctx); -} - -// No SNI in the ClientHello → hard-fail (no opaque, no MITM). -static void -test_no_sni_hard_fail(void) -{ - reset_captures(); - mq_mitm_ctx_t *ctx = - mq_mitm_ctx_new(NULL, NULL, NULL, stub_opaque_open, NULL, g_base); - mq_mitm_ctx_set_mitm_hook_for_test(ctx, mitm_hook, NULL); - - chb_t b; - build_clienthello(&b, 0, NULL, 1); // no SNI, ALPN h2 - int r = mq_mitm_conn_decide_for_test(ctx, b.buf, b.len, ORIG_IP, sizeof(ORIG_IP), - MQ_ADDR_IPV4, ORIG_PORT, -1); - MQ_CHECK_EQ_INT(r, R_FAIL); - MQ_CHECK_EQ_INT(g_opaque.called, 0); - MQ_CHECK_EQ_INT(g_mitm.called, 0); - - mq_mitm_ctx_free(ctx); -} - -// Non-TLS / incomplete leading bytes → hard-fail (parse not OK). -static void -test_not_tls_hard_fail(void) -{ - reset_captures(); - mq_mitm_ctx_t *ctx = - mq_mitm_ctx_new(NULL, NULL, NULL, stub_opaque_open, NULL, g_base); - mq_mitm_ctx_set_mitm_hook_for_test(ctx, mitm_hook, NULL); - - const uint8_t junk[] = "GET / HTTP/1.1\r\nHost: x\r\n\r\n"; - int r = mq_mitm_conn_decide_for_test(ctx, junk, sizeof(junk) - 1, ORIG_IP, - sizeof(ORIG_IP), MQ_ADDR_IPV4, ORIG_PORT, -1); - MQ_CHECK_EQ_INT(r, R_FAIL); - MQ_CHECK_EQ_INT(g_opaque.called, 0); - MQ_CHECK_EQ_INT(g_mitm.called, 0); - - mq_mitm_ctx_free(ctx); -} - -MQ_TEST_MAIN({ - g_base = event_base_new(); - MQ_CHECK(g_base != NULL); - test_ignore_hit_routes_opaque(); - test_ignore_suffix_routes_opaque(); - test_ignore_miss_routes_mitm(); - test_null_ignore_routes_mitm(); - test_no_sni_hard_fail(); - test_not_tls_hard_fail(); - event_base_free(g_base); -}) diff --git a/tests/test_mitm_core.c b/tests/test_mitm_core.c deleted file mode 100644 index 98cdc10b..00000000 --- a/tests/test_mitm_core.c +++ /dev/null @@ -1,751 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -#include "mqtest.h" -#include "mitm/mq_mitm_core.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -// Internal (no header) — forward-declared for direct unit testing. -extern int mq_mitm_normalize_sni(const char *, size_t, char[256]); -extern X509 *mq_mitm_forge_for_test(mq_mitm_core_t *core, const char *norm_sni); -// Forge with a caller-chosen keyUsage extension string (e.g. "critical,cRLSign") -// — otherwise identical to the production profile. CA-signed. Test-only hook used -// to prove the SSL-server purpose gate (I1) is load-bearing. -extern X509 *mq_mitm_forge_ku_for_test(mq_mitm_core_t *core, const char *norm_sni, - const char *ku_str); -// Task 5: bounded LRU leaf cache. Returns a NEW ref to the caller (X509_up_ref on -// hit; on miss the freshly-forged leaf is up_ref'd into the cache and the caller -// gets the original ref). Caller MUST X509_free the returned X509. -extern X509 *mq_mitm_cache_get_or_forge_for_test(mq_mitm_core_t *core, - const char *norm_sni); - -static void -test_sni_norm(void) -{ - char out[256]; - MQ_CHECK(mq_mitm_normalize_sni("Example.COM", 11, out) == 0); - MQ_CHECK(strcmp(out, "example.com") == 0); - MQ_CHECK(mq_mitm_normalize_sni("host.example.com.", 17, out) == 0); // trailing dot - MQ_CHECK(strcmp(out, "host.example.com") == 0); - MQ_CHECK(mq_mitm_normalize_sni("", 0, out) == -1); // empty - MQ_CHECK(mq_mitm_normalize_sni("*.example.com", 13, out) == -1); // wildcard - MQ_CHECK(mq_mitm_normalize_sni("203.0.113.5", 11, out) == -1); // IPv4 literal - MQ_CHECK(mq_mitm_normalize_sni("a\tb.com", 7, out) == -1); // control char - - // Length boundary: exactly 253 accepted, 254 rejected. Built from valid - // (<= 63-byte) labels so ONLY the name-length cap is exercised, not the - // per-label cap. 253 = 63+1+63+1+63+1+61; 254 = 63+1+63+1+63+1+62. - char n253[254]; - memset(n253, 'a', 63); - n253[63] = '.'; - memset(n253 + 64, 'a', 63); - n253[127] = '.'; - memset(n253 + 128, 'a', 63); - n253[191] = '.'; - memset(n253 + 192, 'a', 61); - n253[253] = '\0'; - MQ_CHECK(mq_mitm_normalize_sni(n253, 253, out) == 0); - char n254[255]; - memset(n254, 'a', 63); - n254[63] = '.'; - memset(n254 + 64, 'a', 63); - n254[127] = '.'; - memset(n254 + 128, 'a', 63); - n254[191] = '.'; - memset(n254 + 192, 'a', 62); - n254[254] = '\0'; - MQ_CHECK(mq_mitm_normalize_sni(n254, 254, out) == -1); - // Leading dot / double dot rejected (empty label). - MQ_CHECK(mq_mitm_normalize_sni(".example.com", 12, out) == -1); - MQ_CHECK(mq_mitm_normalize_sni("a..b.com", 8, out) == -1); - // Trailing EMPTY label: only ONE root dot is stripped, so a multi-dot suffix - // leaves a trailing '.' that must be rejected (not asserted/aborted, not let - // through as "example.com."). Regression guard for the SNI normalization - // bypass / debug-build abort. - MQ_CHECK(mq_mitm_normalize_sni("example.com..", 13, out) == -1); - MQ_CHECK(mq_mitm_normalize_sni("example.com...", 14, out) == -1); - MQ_CHECK(mq_mitm_normalize_sni(".", 1, out) == -1); // bare root → empty after strip - MQ_CHECK(mq_mitm_normalize_sni("..", 2, out) == - -1); // strip one dot → ".", empty label -} - -static void -test_create_valid(void) -{ - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, NULL); - MQ_CHECK(c != NULL); - mq_mitm_core_destroy(c); -} - -static void -test_reject_non_ca(void) -{ - // matched CA:FALSE leaf pair → rejected for exactly one reason: not a CA - MQ_CHECK(mq_mitm_core_create(MITM_LEAF_CRT, MITM_LEAF_KEY, NULL) == NULL); -} - -static void -test_reject_v1_ca(void) -{ - // X509v1 cert (no extensions). X509_check_ca() treats it as CA-eligible, so - // this exercises the explicit-basicConstraints gate specifically. Signed by - // the CA key (so the key-match check would pass) → the ONLY rejection reason - // is the missing basicConstraints extension. Defensively chmod 0600 so the - // perms gate on the key doesn't pre-empt the cert-eligibility path. - chmod(MITM_CA_KEY, 0600); - MQ_CHECK(mq_mitm_core_create(MITM_V1_CA_CRT, MITM_CA_KEY, NULL) == NULL); -} - -static void -test_reject_key_mismatch(void) -{ - // valid P-256 CA cert + a DIFFERENT same-curve (P-256) key → exercises the - // real X509_check_private_key mismatch path (not an algorithm shortcut). - // chmod 0600 defensively (MINOR-2) so the perms gate doesn't pre-empt the - // mismatch path and falsely green this case. - chmod(MITM_LEAF_KEY, 0600); - MQ_CHECK(mq_mitm_core_create(MITM_CA_CRT, MITM_LEAF_KEY, NULL) == NULL); -} - -static void -test_reject_encrypted_key(void) -{ - // MINOR-2: the perms gate rejects group/other access BEFORE PEM parsing, so the - // encrypted-PEM path only gets exercised when the fixture is owner-only. openssl - // emits key files 0600 regardless of umask, but chmod defensively so a future - // umask/tooling change can't silently turn this into a perms-rejection false pass. - chmod(MITM_CA_ENC_KEY, 0600); - MQ_CHECK(mq_mitm_core_create(MITM_CA_CRT, MITM_CA_ENC_KEY, NULL) == NULL); -} - -static void -test_reject_symlink_key(void) -{ - char link[] = "/tmp/mq_mitm_link_XXXXXX"; - int fd = mkstemp(link); - MQ_CHECK(fd >= 0); - close(fd); - unlink(link); - MQ_CHECK(symlink(MITM_CA_KEY, link) == 0); - MQ_CHECK(mq_mitm_core_create(MITM_CA_CRT, link, NULL) == NULL); // O_NOFOLLOW rejects - unlink(link); -} - -static void -test_reject_world_readable_key(void) -{ - // copy CA key to a 0644 temp file → fstat perms check must reject - char path[] = "/tmp/mq_mitm_key_XXXXXX"; - int fd = mkstemp(path); - MQ_CHECK(fd >= 0); - FILE *src = fopen(MITM_CA_KEY, "rb"); - MQ_CHECK(src != NULL); - char buf[4096]; - size_t n; - while ((n = fread(buf, 1, sizeof buf, src)) > 0) - MQ_CHECK(write(fd, buf, n) == (ssize_t)n); - fclose(src); - close(fd); - MQ_CHECK(chmod(path, 0644) == 0); - MQ_CHECK(mq_mitm_core_create(MITM_CA_CRT, path, NULL) == NULL); - unlink(path); -} - -// ---- Task 4: leaf forge ---- - -// Build an X509_STORE trusting the test CA, then verify `leaf` against it. -// When set_purpose != 0 the SSL-server purpose is enforced; when 0, only -// trust/time/signature are checked (used to prove the purpose gate is the sole -// reason a negative fails). Returns the X509_verify_cert result (1 == ok) and, on -// failure, writes the verify-error code into *err (else 0). -static int -verify_leaf_with_ca(X509 *leaf, int set_purpose, int *err) -{ - if (err) *err = 0; - FILE *fp = fopen(MITM_CA_CRT, "rb"); - MQ_CHECK(fp != NULL); - X509 *ca = PEM_read_X509(fp, NULL, NULL, NULL); - fclose(fp); - MQ_CHECK(ca != NULL); - - X509_STORE *store = X509_STORE_new(); - MQ_CHECK(store != NULL); - MQ_CHECK(X509_STORE_add_cert(store, ca) == 1); - - X509_STORE_CTX *ctx = X509_STORE_CTX_new(); - MQ_CHECK(ctx != NULL); - MQ_CHECK(X509_STORE_CTX_init(ctx, store, leaf, NULL) == 1); - if (set_purpose) - MQ_CHECK(X509_STORE_CTX_set_purpose(ctx, X509_PURPOSE_SSL_SERVER) == 1); - - int rv = X509_verify_cert(ctx); - if (rv != 1 && err) *err = X509_STORE_CTX_get_error(ctx); - - X509_STORE_CTX_free(ctx); - X509_STORE_free(store); - X509_free(ca); - return rv; -} - -// Extract the single SAN DNS name from `leaf` into out (NUL-terminated). Returns -// 0 on success, -1 if no SAN DNS entry present. -// NOTE: returns only the FIRST DNS SAN — sufficient because the forge emits -// exactly one SAN. -static int -leaf_san_dns(X509 *leaf, char *out, size_t outsz) -{ - GENERAL_NAMES *gens = X509_get_ext_d2i(leaf, NID_subject_alt_name, NULL, NULL); - if (!gens) return -1; - int rc = -1; - for (size_t i = 0; i < (size_t)sk_GENERAL_NAME_num(gens); i++) { - const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i); - int type = 0; - const ASN1_IA5STRING *ia5 = GENERAL_NAME_get0_value((GENERAL_NAME *)gn, &type); - if (type == GEN_DNS && ia5) { - int len = ASN1_STRING_length(ia5); - const unsigned char *data = ASN1_STRING_get0_data(ia5); - if (len >= 0 && (size_t)len < outsz) { - memcpy(out, data, (size_t)len); - out[len] = '\0'; - rc = 0; - } - break; - } - } - GENERAL_NAMES_free(gens); - return rc; -} - -// Read the leaf's subject CN into out. Returns 0 on success. -static int -leaf_cn(X509 *leaf, char *out, int outsz) -{ - X509_NAME *subj = X509_get_subject_name(leaf); - if (!subj) return -1; - int n = X509_NAME_get_text_by_NID(subj, NID_commonName, out, outsz); - return n >= 0 ? 0 : -1; -} - -static void -test_forge_chains_to_ca(void) -{ - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, NULL); - MQ_CHECK(c != NULL); - - X509 *leaf = mq_mitm_forge_for_test(c, "host.example.com"); - MQ_CHECK(leaf != NULL); - - int err = 0; - MQ_CHECK(verify_leaf_with_ca(leaf, 1, &err) == 1); // chains to CA, SSL-server purpose - - // SAN DNS == the (already-normalized) name. - char san[256]; - MQ_CHECK(leaf_san_dns(leaf, san, sizeof san) == 0); - MQ_CHECK(strcmp(san, "host.example.com") == 0); - - // CA:FALSE and CN == "mqproxy-mitm". - MQ_CHECK(X509_check_ca(leaf) == 0); - char cn[64]; - MQ_CHECK(leaf_cn(leaf, cn, (int)sizeof cn) == 0); - MQ_CHECK(strcmp(cn, "mqproxy-mitm") == 0); - - X509_free(leaf); - mq_mitm_core_destroy(c); -} - -// I1 — prove the SSL-server purpose gate is load-bearing (not vacuous). Forge a -// leaf that is CA-signed and otherwise valid, but whose keyUsage omits ALL THREE -// of digitalSignature/keyEncipherment/keyAgreement (here: cRLSign only). Per -// BoringSSL v3_purp.cc, X509v3_KU_TLS accepts ANY of those three, so omitting -// only digitalSignature would still PASS — all three must be absent. Such a leaf -// must FAIL X509_verify_cert with X509_V_ERR_INVALID_PURPOSE. -static void -test_forge_purpose_gate_is_load_bearing(void) -{ - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, NULL); - MQ_CHECK(c != NULL); - - // keyUsage omits ALL THREE TLS-server usages (cRLSign only) → otherwise valid, - // CA-signed leaf that must fail the SSL-server purpose check. - X509 *leaf = mq_mitm_forge_ku_for_test(c, "host.example.com", "critical,cRLSign"); - MQ_CHECK(leaf != NULL); - - // Sanity: it really does chain (signature/trust/time all fine) with NO purpose - // set — so the only possible failure reason below is the purpose gate, not a - // broken negative. - MQ_CHECK(verify_leaf_with_ca(leaf, 0, NULL) == 1); - - int err = 0; - int rv = verify_leaf_with_ca(leaf, 1, &err); // now enforce the SSL-server purpose - MQ_CHECK(rv != 1); // must FAIL - MQ_CHECK(err == X509_V_ERR_INVALID_PURPOSE); // for the right reason - - X509_free(leaf); - mq_mitm_core_destroy(c); -} - -// ---- Task 5: LRU leaf cache with TTL expiry ---- - -// Return the leaf's serial as a heap BIGNUM (caller BN_free's). Serial equality -// is the durable cache-identity invariant (NOT pointer identity, which a future -// refactor could break by returning a duplicated cert). -static BIGNUM * -leaf_serial_bn(X509 *leaf) -{ - const ASN1_INTEGER *s = X509_get_serialNumber(leaf); - MQ_CHECK(s != NULL); - BIGNUM *bn = ASN1_INTEGER_to_BN(s, NULL); - MQ_CHECK(bn != NULL); - return bn; -} - -// Controllable test clock (the cache + forge read time via core->now_fn). -static time_t g_fake_now = 1000000000; // fixed base -static time_t -fake_now(void) -{ - return g_fake_now; -} - -static void -test_cache_hit_same_serial(void) -{ - mq_mitm_opts_t opts = {.cache_size = 8, .leaf_ttl_sec = 86400}; - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, &opts); - MQ_CHECK(c != NULL); - g_fake_now = 1000000000; - mq_mitm_core_set_clock_for_test(c, fake_now); - - X509 *a = mq_mitm_cache_get_or_forge_for_test(c, "host.example.com"); - X509 *b = mq_mitm_cache_get_or_forge_for_test(c, "host.example.com"); - MQ_CHECK(a != NULL && b != NULL); - - BIGNUM *sa = leaf_serial_bn(a), *sb = leaf_serial_bn(b); - MQ_CHECK(BN_cmp(sa, sb) == 0); // cache hit → same serial → one forge - BN_free(sa); - BN_free(sb); - - X509_free(a); - X509_free(b); - mq_mitm_core_destroy(c); -} - -static void -test_cache_distinct_sni_distinct_serial(void) -{ - mq_mitm_opts_t opts = {.cache_size = 8, .leaf_ttl_sec = 86400}; - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, &opts); - MQ_CHECK(c != NULL); - g_fake_now = 1000000000; - mq_mitm_core_set_clock_for_test(c, fake_now); - - X509 *a = mq_mitm_cache_get_or_forge_for_test(c, "one.example.com"); - X509 *b = mq_mitm_cache_get_or_forge_for_test(c, "two.example.com"); - MQ_CHECK(a != NULL && b != NULL); - - BIGNUM *sa = leaf_serial_bn(a), *sb = leaf_serial_bn(b); - MQ_CHECK(BN_cmp(sa, sb) != 0); // distinct SNI → distinct serial - BN_free(sa); - BN_free(sb); - - X509_free(a); - X509_free(b); - mq_mitm_core_destroy(c); -} - -static void -test_cache_lru_eviction(void) -{ - // cache_size=2: insert 3 distinct SNIs; the first is LRU and gets evicted. - // Re-requesting it → miss → reforge → new serial. - mq_mitm_opts_t opts = {.cache_size = 2, .leaf_ttl_sec = 86400}; - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, &opts); - MQ_CHECK(c != NULL); - g_fake_now = 1000000000; - mq_mitm_core_set_clock_for_test(c, fake_now); - - X509 *first1 = mq_mitm_cache_get_or_forge_for_test(c, "a.example.com"); - BIGNUM *s_first1 = leaf_serial_bn(first1); - - X509 *bb = mq_mitm_cache_get_or_forge_for_test(c, "b.example.com"); // fill - X509 *cc = mq_mitm_cache_get_or_forge_for_test(c, "c.example.com"); // evicts "a" - - X509 *first2 = - mq_mitm_cache_get_or_forge_for_test(c, "a.example.com"); // miss → reforge - BIGNUM *s_first2 = leaf_serial_bn(first2); - - MQ_CHECK(BN_cmp(s_first1, s_first2) != 0); // evicted → new serial - - BN_free(s_first1); - BN_free(s_first2); - X509_free(first1); - X509_free(bb); - X509_free(cc); - X509_free(first2); - mq_mitm_core_destroy(c); -} - -static void -test_cache_disabled(void) -{ - // cache_size=0 → caching disabled: two requests for the same SNI always forge. - mq_mitm_opts_t opts = {.cache_size = 0, .leaf_ttl_sec = 86400}; - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, &opts); - MQ_CHECK(c != NULL); - g_fake_now = 1000000000; - mq_mitm_core_set_clock_for_test(c, fake_now); - - X509 *a = mq_mitm_cache_get_or_forge_for_test(c, "host.example.com"); - X509 *b = mq_mitm_cache_get_or_forge_for_test(c, "host.example.com"); - MQ_CHECK(a != NULL && b != NULL); - - BIGNUM *sa = leaf_serial_bn(a), *sb = leaf_serial_bn(b); - MQ_CHECK(BN_cmp(sa, sb) != 0); // no caching → distinct serials - BN_free(sa); - BN_free(sb); - - X509_free(a); - X509_free(b); - mq_mitm_core_destroy(c); -} - -static void -test_cache_ttl_expiry(void) -{ - // Forge at t0; advance the clock past not_after; the same SNI must reforge. - mq_mitm_opts_t opts = {.cache_size = 8, .leaf_ttl_sec = 60}; // clamped min TTL - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, &opts); - MQ_CHECK(c != NULL); - g_fake_now = 1000000000; - mq_mitm_core_set_clock_for_test(c, fake_now); - - X509 *a = mq_mitm_cache_get_or_forge_for_test(c, "host.example.com"); - BIGNUM *sa = leaf_serial_bn(a); - - // Advance well past not_after (now + 60). Even with the near-expiry SKEW this - // is unambiguously expired. - g_fake_now += 600; - - X509 *b = mq_mitm_cache_get_or_forge_for_test(c, "host.example.com"); - BIGNUM *sb = leaf_serial_bn(b); - - MQ_CHECK(BN_cmp(sa, sb) != 0); // expired → reforged → new serial - - BN_free(sa); - BN_free(sb); - X509_free(a); - X509_free(b); - mq_mitm_core_destroy(c); -} - -// Lock the SKEW freshness boundary (off-by-one). Freshness is the STRICT test -// `now < not_after - SKEW` (SKEW = MQ_MITM_CACHE_SKEW_SEC = 30s). With ttl=120 -// (> 2*SKEW so the boundary is unambiguous), not_after = t0+120 and the boundary -// instant is t0+90. At EXACTLY the boundary (t0+90) the entry is NOT fresh → must -// REFORGE; one second earlier (t0+89) it is still fresh → cache HIT. -static void -test_cache_skew_boundary(void) -{ - const int SKEW = 30; // == MQ_MITM_CACHE_SKEW_SEC in mq_mitm_core.c - const int TTL = 120; // > 2*SKEW so not_after-SKEW is unambiguous - - // --- At exactly not_after - SKEW (t0+90): boundary is NOT fresh → reforge. --- - { - mq_mitm_opts_t opts = {.cache_size = 8, .leaf_ttl_sec = TTL}; - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, &opts); - MQ_CHECK(c != NULL); - g_fake_now = 1000000000; - mq_mitm_core_set_clock_for_test(c, fake_now); - - X509 *a = mq_mitm_cache_get_or_forge_for_test(c, "host.example.com"); - BIGNUM *sa = leaf_serial_bn(a); - - // Advance to EXACTLY not_after - SKEW (= t0 + TTL - SKEW = t0 + 90). - g_fake_now += TTL - SKEW; - - X509 *b = mq_mitm_cache_get_or_forge_for_test(c, "host.example.com"); - BIGNUM *sb = leaf_serial_bn(b); - - MQ_CHECK(BN_cmp(sa, sb) != 0); // boundary not fresh → reforged → new serial - - BN_free(sa); - BN_free(sb); - X509_free(a); - X509_free(b); - mq_mitm_core_destroy(c); - } - - // --- One second before the boundary (t0+89): still fresh → cache HIT. --- - { - mq_mitm_opts_t opts = {.cache_size = 8, .leaf_ttl_sec = TTL}; - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, &opts); - MQ_CHECK(c != NULL); - g_fake_now = 1000000000; - mq_mitm_core_set_clock_for_test(c, fake_now); - - X509 *a = mq_mitm_cache_get_or_forge_for_test(c, "host.example.com"); - BIGNUM *sa = leaf_serial_bn(a); - - // Advance to one second BEFORE the boundary (t0 + 89). - g_fake_now += TTL - SKEW - 1; - - X509 *b = mq_mitm_cache_get_or_forge_for_test(c, "host.example.com"); - BIGNUM *sb = leaf_serial_bn(b); - - MQ_CHECK(BN_cmp(sa, sb) == 0); // still fresh → cache hit → same serial - - BN_free(sa); - BN_free(sb); - X509_free(a); - X509_free(b); - mq_mitm_core_destroy(c); - } -} - -// ---- Task 6: server SSL_CTX + new_ssl (callbacks installed; full handshake = Task 7) -// ---- - -// new_ssl() must yield a non-NULL, server-mode SSL* (accept state, select-cert + -// ALPN callbacks installed on the shared SSL_CTX). The actual handshake exercise -// is Task 7; here we only prove new_ssl produces a server-mode SSL. -static void -test_new_ssl_is_server(void) -{ - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, NULL); - MQ_CHECK(c != NULL); - - SSL *s = mq_mitm_core_new_ssl(c); - MQ_CHECK(s != NULL); - MQ_CHECK(SSL_is_server(s) == 1); - - SSL_free(s); // caller owns s; free BEFORE destroy - mq_mitm_core_destroy(c); -} - -// Lifetime: destroying the core while an SSL it produced is STILL ALIVE must not -// dangle the core pointer that SSL stashed in ex-data. The refcount keeps the -// core alive until the last SSL_free, then frees it exactly once. Run under -// ASan/LSan this proves no use-after-free (destroy-before-free) and no leak -// (freed exactly once on the final SSL_free). -static void -test_core_outlives_live_ssl(void) -{ - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, NULL); - MQ_CHECK(c != NULL); - - SSL *s = mq_mitm_core_new_ssl(c); - MQ_CHECK(s != NULL); - - // Reverse the usual order: drop the owner ref FIRST, while s is still live. - mq_mitm_core_destroy(c); // core must survive (s still holds a ref) - SSL_free(s); // last ref → core actually torn down here -} - -// ---- Task 7: in-process memory-BIO handshake proof (the headline deliverable) -// ---- - -// Result of one in-process handshake attempt. `ok` is 1 iff BOTH sides completed -// SSL_do_handshake; on success `alpn` holds the client-selected protocol (or "") -// and `verify_result` is the client's SSL_get_verify_result. -typedef struct { - int ok; - char alpn[16]; - long verify_result; -} hs_result_t; - -// Build a client SSL_CTX trusting the test CA (out-of-band, per spec — NOT relying -// on a server-sent chain). Caller SSL_CTX_free's it. -static SSL_CTX * -make_client_ctx(void) -{ - SSL_CTX *cctx = SSL_CTX_new(TLS_client_method()); - MQ_CHECK(cctx != NULL); - MQ_CHECK(SSL_CTX_load_verify_locations(cctx, MITM_CA_CRT, NULL) == 1); - SSL_CTX_set_verify(cctx, SSL_VERIFY_PEER, NULL); - return cctx; -} - -// Drive a full TLS handshake with NO sockets between the module-under-test server -// SSL (mq_mitm_core_new_ssl) and a local client SSL. `client_alpn` is the wire -// protocol list to offer (e.g. "\x02h2"), or NULL for no ALPN extension. `sni` is -// the host to send via SNI, or NULL for no SNI. DEADLOCK-PROOF (I3): each iteration -// drives BOTH sides unconditionally; the bio-pair coupling moves bytes; only a -// non-WANT_READ/WANT_WRITE error is fatal; a hard iteration cap fails (not hangs) -// the test on a stall. -// `out_peer` (optional): on a successful handshake, receives a NEW ref to the -// server-presented leaf (caller MUST X509_free). NULL = don't capture; not set -// when out_peer is NULL or the handshake failed. -static hs_result_t -do_handshake(mq_mitm_core_t *core, const unsigned char *client_alpn, - size_t client_alpn_len, const char *sni, X509 **out_peer) -{ - static const int K_MAX_HS_ITERS = 64; - hs_result_t res = {0, "", 0}; - - SSL *server = mq_mitm_core_new_ssl(core); - MQ_CHECK(server != NULL); - - SSL_CTX *cctx = make_client_ctx(); - SSL *client = SSL_new(cctx); - MQ_CHECK(client != NULL); - SSL_set_connect_state(client); - - if (client_alpn) - MQ_CHECK(SSL_set_alpn_protos(client, client_alpn, (unsigned)client_alpn_len) == - 0); - if (sni) MQ_CHECK(SSL_set_tlsext_host_name(client, sni) == 1); - - // Memory BIO pair: ownership of `a`/`b` transfers to each SSL on SSL_set_bio, - // so they are freed by SSL_free — do NOT free them separately. - BIO *a = NULL, *b = NULL; - if (BIO_new_bio_pair(&a, 65536, &b, 65536) != 1) { - // Allocation failed before any ownership transfer — free what we made. - if (a) BIO_free(a); - if (b) BIO_free(b); - SSL_free(server); - SSL_free(client); - SSL_CTX_free(cctx); - MQ_CHECK(0 && "BIO_new_bio_pair failed"); - return res; - } - SSL_set_bio(server, a, a); - SSL_set_bio(client, b, b); - - int client_done = 0, server_done = 0, fatal = 0; - int iters = 0; - for (; iters < K_MAX_HS_ITERS && !(client_done && server_done) && !fatal; iters++) { - if (!client_done) { - int r = SSL_do_handshake(client); - if (r == 1) { - client_done = 1; - } else { - int e = SSL_get_error(client, r); - if (e != SSL_ERROR_WANT_READ && e != SSL_ERROR_WANT_WRITE) fatal = 1; - } - } - if (!server_done) { - int r = SSL_do_handshake(server); - if (r == 1) { - server_done = 1; - } else { - int e = SSL_get_error(server, r); - if (e != SSL_ERROR_WANT_READ && e != SSL_ERROR_WANT_WRITE) fatal = 1; - } - } - } - MQ_CHECK(iters < K_MAX_HS_ITERS); // a stall FAILS the test instead of hanging CI - - if (client_done && server_done) { - res.ok = 1; - res.verify_result = SSL_get_verify_result(client); - const unsigned char *p = NULL; - unsigned plen = 0; - SSL_get0_alpn_selected(client, &p, &plen); // non-owning - if (p && plen > 0 && plen < sizeof res.alpn) { - memcpy(res.alpn, p, plen); - res.alpn[plen] = '\0'; - } - if (out_peer) *out_peer = SSL_get_peer_certificate(client); // NEW ref → X509_free - } - - SSL_free(server); // frees BIO a - SSL_free(client); // frees BIO b - SSL_CTX_free(cctx); - return res; -} - -// Positive case: client offers h2 + valid SNI → handshake completes, verify ok, -// ALPN == "h2", and the presented leaf chains to the CA with SAN == the SNI. -static void -test_handshake_positive_h2(void) -{ - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, NULL); - MQ_CHECK(c != NULL); - - static const unsigned char ALPN_H2[] = {2, 'h', '2'}; - X509 *peer = NULL; - hs_result_t r = do_handshake(c, ALPN_H2, sizeof ALPN_H2, "host.example.com", &peer); - - MQ_CHECK(r.ok == 1); - MQ_CHECK(r.verify_result == X509_V_OK); - MQ_CHECK(strcmp(r.alpn, "h2") == 0); - - // The leaf the server presented must independently verify against the CA and - // carry SAN DNS == the requested SNI (peer cert captured via do_handshake's - // out-param — a NEW ref we must free). - MQ_CHECK(peer != NULL); - int err = 0; - MQ_CHECK(verify_leaf_with_ca(peer, 1, &err) == 1); - char san[256]; - MQ_CHECK(leaf_san_dns(peer, san, sizeof san) == 0); - MQ_CHECK(strcmp(san, "host.example.com") == 0); - X509_free(peer); - - mq_mitm_core_destroy(c); -} - -// ALPN negatives: client offering ONLY http/1.1, or NO ALPN at all → the server's -// no-ALPN guard / h2-only selector rejects → handshake FAILS. -static void -test_handshake_alpn_negatives(void) -{ - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, NULL); - MQ_CHECK(c != NULL); - - static const unsigned char ALPN_H1[] = {8, 'h', 't', 't', 'p', '/', '1', '.', '1'}; - hs_result_t only_h1 = - do_handshake(c, ALPN_H1, sizeof ALPN_H1, "host.example.com", NULL); - MQ_CHECK(only_h1.ok == 0); // no h2 overlap → ALPN alert fatal - - hs_result_t no_alpn = do_handshake(c, NULL, 0, "host.example.com", NULL); - MQ_CHECK(no_alpn.ok == 0); // no-ALPN guard → select_cert_error - - mq_mitm_core_destroy(c); -} - -// SNI negatives (M5 — exercised THROUGH the live select_cert callback, not just the -// Task 3 pure fn): no SNI, IP-literal, wildcard, control-char → all rejected by the -// normalizer inside select_cb → handshake FAILS. -static void -test_handshake_sni_negatives(void) -{ - mq_mitm_core_t *c = mq_mitm_core_create(MITM_CA_CRT, MITM_CA_KEY, NULL); - MQ_CHECK(c != NULL); - - static const unsigned char ALPN_H2[] = {2, 'h', '2'}; - - hs_result_t no_sni = do_handshake(c, ALPN_H2, sizeof ALPN_H2, NULL, NULL); - MQ_CHECK(no_sni.ok == 0); // no SNI → normalize fails - - hs_result_t ip_sni = do_handshake(c, ALPN_H2, sizeof ALPN_H2, "203.0.113.5", NULL); - MQ_CHECK(ip_sni.ok == 0); // IP literal rejected - - hs_result_t wild_sni = - do_handshake(c, ALPN_H2, sizeof ALPN_H2, "*.example.com", NULL); - MQ_CHECK(wild_sni.ok == 0); // wildcard rejected - - hs_result_t ctrl_sni = do_handshake(c, ALPN_H2, sizeof ALPN_H2, "a\tb.com", NULL); - MQ_CHECK(ctrl_sni.ok == 0); // control char rejected - - mq_mitm_core_destroy(c); -} - -MQ_TEST_MAIN(test_sni_norm(); test_create_valid(); test_reject_non_ca(); - test_reject_v1_ca(); test_reject_key_mismatch(); test_reject_encrypted_key(); - test_reject_symlink_key(); test_reject_world_readable_key(); - test_forge_chains_to_ca(); test_forge_purpose_gate_is_load_bearing(); - test_cache_hit_same_serial(); test_cache_distinct_sni_distinct_serial(); - test_cache_lru_eviction(); test_cache_disabled(); test_cache_ttl_expiry(); - test_cache_skew_boundary(); test_new_ssl_is_server(); - test_core_outlives_live_ssl(); test_handshake_positive_h2(); - test_handshake_alpn_negatives(); test_handshake_sni_negatives();) diff --git a/tests/test_mitm_teardown.c b/tests/test_mitm_teardown.c deleted file mode 100644 index 2e0134f4..00000000 --- a/tests/test_mitm_teardown.c +++ /dev/null @@ -1,332 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// Unit tests for the LIVE MITM conn registry + ordered teardown + re-entrancy -// (Phase 7 Slice 3 Task 11), WITHOUT a real TLS handshake. -// -// We build N real mq_gw_h2_adapter SERVER sessions, drive a real nghttp2 CLIENT -// request through each so each adapter holds ONE in-flight stream with a non-NULL -// xreq (a request WITH a body but NO END_STREAM → the stream stays open). Then we -// register each adapter in a live MITM conn via mq_mitm_conn_make_live_for_test -// and call mq_mitm_ctx_free. -// -// ASSERTION: mq_mitm_ctx_free drains the registry; each conn's teardown frees its -// H2 adapter, and mq_gw_h2_adapter_free drives submit->req_aborted on every -// in-flight xreq EXACTLY ONCE (the adapter→core "local gone" direction, codex -// H8). ASan/LSan must be clean (no UAF, no leak, no double-free) — which is the -// real point of the test. -// -// The SSL is NULL in this seam (no real handshake): the teardown path tolerates a -// NULL ssl (SSL_free(NULL) is a no-op) and a real local_fd from a socketpair -// (closed in teardown). The live TLS handshake itself is covered by the e2e. -#include "mqtest.h" - -#include -#include -#include -#include -#include - -#include "gateway/mq_gw_h2_adapter.h" -#include "mitm/mq_mitm_conn.h" -#include "wire/mq_wire.h" - -#include - -// ── per-stub state: counts req_aborted per fake xreq ───────────────────────── -// -// One stub_state_t per adapter. Its address is the xreq sentinel returned from -// req_begin, so cap_req_aborted(r) can bump the right counter and we can assert -// "exactly once per stream". - -typedef struct { - int aborted_calls; // how many times req_aborted fired on THIS xreq -} stub_state_t; - -static mq_gw_reject_reason_t -stub_prevalidate(void *u, const mq_h3_header_t *h, size_t n, int *status) -{ - (void)u; - (void)h; - (void)n; - if (status) *status = 200; - return MQ_GW_OK; -} - -static mq_gw_xreq_t * -stub_req_begin(void *u, const mq_gw_req_head_t *head, const mq_gw_sink_ops_t *sink, - void *sink_user, int *err_status, mq_gw_reject_reason_t *reason) -{ - (void)head; - (void)sink; - (void)sink_user; - (void)err_status; - (void)reason; - // Return the stub_state_t* as the xreq sentinel. - return (mq_gw_xreq_t *)u; -} - -static int -stub_req_body(mq_gw_xreq_t *r, const uint8_t *p, size_t len) -{ - (void)r; - (void)p; - (void)len; - return 0; // accept; never pause -} -static void -stub_req_body_done(mq_gw_xreq_t *r) -{ - (void)r; -} -static void -stub_req_drained(mq_gw_xreq_t *r) -{ - (void)r; -} -static void -stub_req_aborted(mq_gw_xreq_t *r) -{ - stub_state_t *st = (stub_state_t *)r; - if (st) st->aborted_calls++; -} -static const char * -stub_auth_token(void *u) -{ - (void)u; - return "tok"; -} - -static const mq_gw_submit_ops_t g_stub_ops = { - .prevalidate = stub_prevalidate, - .req_begin = stub_req_begin, - .req_body = stub_req_body, - .req_body_done = stub_req_body_done, - .req_drained = stub_req_drained, - .req_aborted = stub_req_aborted, - .auth_token = stub_auth_token, -}; - -// ── byte shuttle (no sockets/TLS for the adapter itself) ───────────────────── - -typedef struct { - uint8_t buf[64 * 1024]; - size_t len; -} sink_buf_t; - -static ssize_t -adapter_send(void *io, const uint8_t *p, size_t n) -{ - sink_buf_t *s = (sink_buf_t *)io; - if (s->len + n > sizeof(s->buf)) return -1; - memcpy(s->buf + s->len, p, n); - s->len += n; - return (ssize_t)n; -} - -static ssize_t -client_send(nghttp2_session *s, const uint8_t *data, size_t length, int flags, - void *user_data) -{ - sink_buf_t *cb = (sink_buf_t *)user_data; - (void)s; - (void)flags; - if (cb->len + length > sizeof(cb->buf)) return NGHTTP2_ERR_CALLBACK_FAILURE; - memcpy(cb->buf + cb->len, data, length); - cb->len += length; - return (ssize_t)length; -} - -// A deferred data provider: the client request carries a body that never ends, so -// the server-side stream stays OPEN (in-flight) — never END_STREAM, never closed. -static ssize_t -defer_body(nghttp2_session *s, int32_t sid, uint8_t *buf, size_t length, - uint32_t *data_flags, nghttp2_data_source *src, void *ud) -{ - (void)s; - (void)sid; - (void)buf; - (void)length; - (void)data_flags; - (void)src; - (void)ud; - return NGHTTP2_ERR_DEFERRED; // nothing to send yet → stream stays open -} - -// Build one adapter holding one in-flight stream whose xreq == &st. Returns the -// adapter (caller hands it to a live conn). -static mq_gw_h2_adapter_t * -make_inflight_adapter(stub_state_t *st) -{ - sink_buf_t srv_out = {0}; - mq_gw_h2_adapter_t *a = mq_gw_h2_adapter_new(&g_stub_ops, st, adapter_send, &srv_out); - MQ_CHECK(a != NULL); - - nghttp2_session_callbacks *cbs = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_callbacks_new(&cbs), 0); - nghttp2_session_callbacks_set_send_callback(cbs, client_send); - - sink_buf_t cli_out = {0}; - nghttp2_session *cli = NULL; - MQ_CHECK_EQ_INT(nghttp2_session_client_new(&cli, cbs, &cli_out), 0); - nghttp2_session_callbacks_del(cbs); - - nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - MQ_CHECK_EQ_INT(nghttp2_submit_settings(cli, NGHTTP2_FLAG_NONE, iv, 1), 0); - - const nghttp2_nv nva[] = { - {(uint8_t *)":method", (uint8_t *)"POST", 7, 4, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":scheme", (uint8_t *)"https", 7, 5, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":authority", (uint8_t *)"example.com", 10, 11, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)":path", (uint8_t *)"/", 5, 1, NGHTTP2_NV_FLAG_NONE}, - }; - nghttp2_data_provider prd = {.source = {.ptr = NULL}, .read_callback = defer_body}; - int sid = nghttp2_submit_request(cli, NULL, nva, 4, &prd, NULL); - MQ_CHECK(sid > 0); - - // Shuttle until the adapter has materialized the request (req_begin → xreq set). - for (int round = 0; round < 6; round++) { - cli_out.len = 0; - MQ_CHECK_EQ_INT(nghttp2_session_send(cli), 0); - if (cli_out.len > 0) - MQ_CHECK_EQ_INT(mq_gw_h2_adapter_recv(a, cli_out.buf, cli_out.len), 0); - - srv_out.len = 0; - MQ_CHECK_EQ_INT(mq_gw_h2_adapter_want_write(a), 0); - if (srv_out.len > 0) { - ssize_t r = nghttp2_session_mem_recv(cli, srv_out.buf, srv_out.len); - MQ_CHECK(r >= 0 && (size_t)r == srv_out.len); - } - } - - nghttp2_session_del(cli); - return a; -} - -// Stub opaque_open (never invoked in these MITM-only tests, but required by -// mq_mitm_ctx_new). -static void -stub_opaque_open(void *core, const uint8_t *host, size_t host_len, mq_addr_type_t atype, - uint16_t port, int local_fd, const uint8_t *prebuf, size_t prebuf_len, - void *user, mq_tcp_open_cb cb) -{ - (void)core; - (void)host; - (void)host_len; - (void)atype; - (void)port; - (void)local_fd; - (void)prebuf; - (void)prebuf_len; - (void)user; - (void)cb; -} - -// N live MITM conns, each with one in-flight stream → mq_mitm_ctx_free → each -// stream's req_aborted fires exactly once; clean teardown (ASan/LSan). -static void -test_ctx_free_aborts_each_inflight_once(void) -{ - enum { N = 4 }; - struct event_base *base = event_base_new(); - MQ_CHECK(base != NULL); - - mq_mitm_ctx_t *ctx = mq_mitm_ctx_new(NULL, NULL, NULL, stub_opaque_open, NULL, base); - MQ_CHECK(ctx != NULL); - - stub_state_t st[N] = {{0}}; - int fds[N]; - - for (int i = 0; i < N; i++) { - mq_gw_h2_adapter_t *a = make_inflight_adapter(&st[i]); - MQ_CHECK(a != NULL); - // A real fd (socketpair end) so teardown's close() touches a valid fd. - int sp[2]; - MQ_CHECK_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sp), 0); - fds[i] = sp[1]; - close(sp[0]); // we only need the conn-owned end to be a real fd - MQ_CHECK_EQ_INT(mq_mitm_conn_make_live_for_test(ctx, a, /*ssl=*/NULL, fds[i]), 0); - } - - // Tear everything down. Each conn: adapter_free (→ req_aborted per stream) → - // SSL_free(NULL) → close(fd) → unlink → free. - mq_mitm_ctx_free(ctx); - - for (int i = 0; i < N; i++) - MQ_CHECK_EQ_INT(st[i].aborted_calls, 1); // exactly once per in-flight stream - - event_base_free(base); -} - -// A live conn with NO adapter and fd=-1 still tears down cleanly (exercises the -// NULL-adapter / no-fd branch of conn_destroy). -static void -test_ctx_free_empty_conn(void) -{ - struct event_base *base = event_base_new(); - MQ_CHECK(base != NULL); - mq_mitm_ctx_t *ctx = mq_mitm_ctx_new(NULL, NULL, NULL, stub_opaque_open, NULL, base); - MQ_CHECK(ctx != NULL); - - MQ_CHECK_EQ_INT( - mq_mitm_conn_make_live_for_test(ctx, /*adapter=*/NULL, /*ssl=*/NULL, /*fd=*/-1), - 0); - mq_mitm_ctx_free(ctx); - event_base_free(base); -} - -// H-1: the LIVE-phase idle timer fires → conn teardown via conn_close (deferred -// free). We build N live conns (each with one in-flight stream), arm the idle -// timer with a zero timeout, then run the loop: on_idle fires for each, routes -// through conn_close (deferred), and the next loop iteration reaps via free_ev. -// ASSERTION: each conn was actually torn down by the idle path (req_aborted fired -// exactly once per in-flight stream), and ASan/LSan is clean (no UAF from the -// timer firing into the conn, no leak). -static void -test_live_idle_timeout_tears_down(void) -{ - enum { N = 3 }; - struct event_base *base = event_base_new(); - MQ_CHECK(base != NULL); - - mq_mitm_ctx_t *ctx = mq_mitm_ctx_new(NULL, NULL, NULL, stub_opaque_open, NULL, base); - MQ_CHECK(ctx != NULL); - - stub_state_t st[N] = {{0}}; - for (int i = 0; i < N; i++) { - mq_gw_h2_adapter_t *a = make_inflight_adapter(&st[i]); - MQ_CHECK(a != NULL); - int sp[2]; - MQ_CHECK_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sp), 0); - close(sp[0]); - MQ_CHECK_EQ_INT(mq_mitm_conn_make_live_for_test(ctx, a, /*ssl=*/NULL, sp[1]), 0); - } - - // Arm the idle timer (zero timeout) on every live conn. - MQ_CHECK_EQ_INT(mq_mitm_conn_arm_idle_now_for_test(ctx), N); - - // Drive expiry: first pass fires each on_idle → conn_close (queues deferred - // free + activates free_ev); subsequent passes reap via free_ev. Loop until - // the base is idle so both the timers AND the deferred reap have run. - for (int i = 0; i < 8 && event_base_loop(base, EVLOOP_NONBLOCK) == 0; i++) { - // event_base_loop returns 1 when no events remain pending — stop then. - if (event_base_get_num_events(base, EVENT_BASE_COUNT_ACTIVE | - EVENT_BASE_COUNT_ADDED) == 0) - break; - } - - // Every in-flight stream was aborted exactly once → teardown ran via the idle - // path. (If the idle timer had NOT torn the conn down, these would be 0.) - for (int i = 0; i < N; i++) - MQ_CHECK_EQ_INT(st[i].aborted_calls, 1); - - // ctx_free now has an empty registry (all conns reaped). Still must be clean. - mq_mitm_ctx_free(ctx); - event_base_free(base); -} - -MQ_TEST_MAIN({ - test_ctx_free_aborts_each_inflight_once(); - test_ctx_free_empty_conn(); - test_live_idle_timeout_tears_down(); -}) diff --git a/tests/test_origdst.c b/tests/test_origdst.c deleted file mode 100644 index e9100cbf..00000000 --- a/tests/test_origdst.c +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta and mqproxy contributors -// -// Unit tests for mq_origdst_to_target. The syscall wrappers (tproxy/redirect) -// need a live socket and are covered by Task 9 e2e — not tested here. -#include "mqtest.h" -#include "ingress/mq_origdst.h" -#include "wire/mq_wire.h" -#include -#include -#include -#include - -static void -test_ipv4(void) -{ - struct sockaddr_storage ss; - memset(&ss, 0, sizeof(ss)); - struct sockaddr_in *s4 = (struct sockaddr_in *)&ss; - s4->sin_family = AF_INET; - s4->sin_port = htons(443); - inet_pton(AF_INET, "93.184.216.34", &s4->sin_addr); - - uint8_t host[16]; - size_t host_len = 0; - int atype = -1; - uint16_t port = 0; - int rc = mq_origdst_to_target(&ss, host, sizeof(host), &host_len, &atype, &port); - - MQ_CHECK_EQ_INT(rc, 0); - MQ_CHECK_EQ_INT(atype, MQ_ADDR_IPV4); - MQ_CHECK_EQ_INT((int)port, 443); - MQ_CHECK_EQ_INT((int)host_len, 4); - const uint8_t want[4] = {93, 184, 216, 34}; - MQ_CHECK_MEM(host, want, 4); -} - -static void -test_ipv6(void) -{ - struct sockaddr_storage ss; - memset(&ss, 0, sizeof(ss)); - struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)&ss; - s6->sin6_family = AF_INET6; - s6->sin6_port = htons(8080); - inet_pton(AF_INET6, "2001:db8::1", &s6->sin6_addr); - - uint8_t host[16]; - size_t host_len = 0; - int atype = -1; - uint16_t port = 0; - int rc = mq_origdst_to_target(&ss, host, sizeof(host), &host_len, &atype, &port); - - MQ_CHECK_EQ_INT(rc, 0); - MQ_CHECK_EQ_INT(atype, MQ_ADDR_IPV6); - MQ_CHECK_EQ_INT((int)port, 8080); - MQ_CHECK_EQ_INT((int)host_len, 16); - uint8_t want[16]; - inet_pton(AF_INET6, "2001:db8::1", want); - MQ_CHECK_MEM(host, want, 16); -} - -static void -test_short_buffer(void) -{ - struct sockaddr_storage ss; - memset(&ss, 0, sizeof(ss)); - struct sockaddr_in *s4 = (struct sockaddr_in *)&ss; - s4->sin_family = AF_INET; - s4->sin_port = htons(80); - inet_pton(AF_INET, "1.2.3.4", &s4->sin_addr); - - uint8_t host[4]; - size_t host_len = 0; - int atype = 0; - uint16_t port = 0; - int rc = mq_origdst_to_target(&ss, host, 3, &host_len, &atype, &port); - MQ_CHECK_EQ_INT(rc, -1); -} - -static void -test_unsupported_family(void) -{ - struct sockaddr_storage ss; - memset(&ss, 0, sizeof(ss)); - ss.ss_family = AF_UNIX; - - uint8_t host[16]; - size_t host_len = 0; - int atype = 0; - uint16_t port = 0; - int rc = mq_origdst_to_target(&ss, host, sizeof(host), &host_len, &atype, &port); - MQ_CHECK_EQ_INT(rc, -1); -} - -MQ_TEST_MAIN(test_ipv4(); test_ipv6(); test_short_buffer(); test_unsupported_family();) diff --git a/tests/test_relay.c b/tests/test_relay.c deleted file mode 100644 index e68f4654..00000000 --- a/tests/test_relay.c +++ /dev/null @@ -1,411 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "proxy/mq_relay.h" -#include "mqtest.h" -#include - -/* - * In-memory I/O double for the relay. - * - * Read side: - * - `in` holds `in_len` bytes; `in_pos` is the read cursor. - * - `read_chunk` caps how many bytes a single read returns (0 = unlimited). - * - `read_wb_every`: if >0, every `read_wb_every`-th read call returns a - * would-block (no bytes) instead of data, then resumes. Models a source - * that intermittently has nothing ready. - * - When `in_pos == in_len`, read reports clean EOF (unless `no_eof`). - * - `read_err`: if set, the read at the `read_err`-th call returns a hard - * error (-1). (1 = first read errors.) - * - `read_eof_with_data`: if set, the read that drains the last input bytes - * sets `*eof=1` in the SAME call as the data (real sockets do this). - * - * Write side: - * - `out` accumulates written bytes; `out_len` is the count. - * - `write_budget`: max bytes this side will accept before would-blocking. - * Decremented as bytes are accepted. When 0, write would-blocks. - * Refill by assigning `write_budget` to simulate a writable edge. - * - `write_err`: if set, the next write returns a hard error (-1). - */ -typedef struct { - const unsigned char *in; - size_t in_len; - size_t in_pos; - size_t read_chunk; - int read_wb_every; - int read_calls; - int no_eof; - int read_err; /* error on the read_err-th read call (1 = first) */ - int read_eof_with_data; /* deliver final bytes AND *eof=1 in one call */ - - unsigned char out[1024]; - size_t out_len; - long write_budget; - int write_err; -} mem_io_t; - -static long -mem_read(void *io, unsigned char *buf, size_t cap, int *eof, int *wb) -{ - mem_io_t *m = (mem_io_t *)io; - m->read_calls++; - - if (m->read_err > 0 && m->read_calls == m->read_err) { - return -1; - } - - if (m->read_wb_every > 0 && (m->read_calls % m->read_wb_every) == 0) { - *wb = 1; - return 0; - } - - size_t avail = m->in_len - m->in_pos; - if (avail == 0) { - if (m->no_eof) { - *wb = 1; - return 0; - } - *eof = 1; - return 0; - } - - size_t n = avail; - if (n > cap) n = cap; - if (m->read_chunk > 0 && n > m->read_chunk) n = m->read_chunk; - - memcpy(buf, m->in + m->in_pos, n); - m->in_pos += n; - if (m->read_eof_with_data && !m->no_eof && m->in_pos == m->in_len) { - *eof = 1; /* final bytes and EOF in the same read */ - } - return (long)n; -} - -static long -mem_write(void *io, const unsigned char *buf, size_t len, int *wb) -{ - mem_io_t *m = (mem_io_t *)io; - - if (m->write_err) { - return -1; - } - - if (m->write_budget <= 0) { - *wb = 1; - return 0; - } - - size_t n = len; - if ((long)n > m->write_budget) n = (size_t)m->write_budget; - - /* guard against overflowing the test buffer */ - if (m->out_len + n > sizeof(m->out)) n = sizeof(m->out) - m->out_len; - - memcpy(m->out + m->out_len, buf, n); - m->out_len += n; - m->write_budget -= (long)n; - - if (n < len) *wb = 1; /* accepted some, but budget exhausted */ - return (long)n; -} - -static void -mem_init(mem_io_t *m) -{ - memset(m, 0, sizeof(*m)); -} - -/* ---- on_done counter ---- */ -static int g_done_count; -static void -on_done(mq_relay_t *r, void *user) -{ - (void)r; - (void)user; - g_done_count++; -} - -static mq_relay_t * -make_relay(mem_io_t *a, mem_io_t *b) -{ - mq_relay_cfg_t cfg = {0}; - cfg.a_io = a; - cfg.b_io = b; - cfg.a_read = mem_read; - cfg.b_read = mem_read; - cfg.a_write = mem_write; - cfg.b_write = mem_write; - cfg.on_done = on_done; - cfg.user = NULL; - return mq_relay_new(&cfg); -} - -/* Case 1: happy path both directions. */ -static void -test_happy_both_directions(void) -{ - g_done_count = 0; - mem_io_t a, b; - mem_init(&a); - mem_init(&b); - - static const unsigned char a_in[] = "hello"; - static const unsigned char b_in[] = "world"; - a.in = a_in; - a.in_len = 5; - b.in = b_in; - b.in_len = 5; - a.write_budget = 1024; /* B->A drains into A's writer */ - b.write_budget = 1024; /* A->B drains into B's writer */ - - mq_relay_t *r = make_relay(&a, &b); - - /* drive both readable edges, then both writable edges */ - mq_relay_on_a_readable(r); - mq_relay_on_b_readable(r); - mq_relay_on_a_writable(r); - mq_relay_on_b_writable(r); - - MQ_CHECK_EQ_INT(b.out_len, 5); - MQ_CHECK_MEM(b.out, "hello", 5); /* A->B */ - MQ_CHECK_EQ_INT(a.out_len, 5); - MQ_CHECK_MEM(a.out, "world", 5); /* B->A */ - MQ_CHECK_EQ_INT(g_done_count, 1); - - mq_relay_free(r); -} - -/* Case 2: backpressure on B's writer. */ -static void -test_backpressure(void) -{ - g_done_count = 0; - mem_io_t a, b; - mem_init(&a); - mem_init(&b); - - static unsigned char a_in[100]; - for (int i = 0; i < 100; i++) - a_in[i] = (unsigned char)i; - a.in = a_in; - a.in_len = 100; - a.no_eof = 1; /* keep A's source open so direction can't finish yet */ - a.write_budget = 0; /* B->A unused here */ - - b.in_len = 0; - b.no_eof = 1; /* B never EOFs in this test */ - b.write_budget = 10; /* B accepts only 10 bytes, then would-block */ - - mq_relay_t *r = make_relay(&a, &b); - - /* A becomes readable: read all 100 into ab, write 10, then would-block */ - mq_relay_on_a_readable(r); - - MQ_CHECK_EQ_INT(b.out_len, 10); /* only 10 written so far */ - MQ_CHECK_MEM(b.out, a_in, 10); - MQ_CHECK_EQ_INT(g_done_count, 0); /* not finished */ - - /* Restore budget and signal B writable: drain the rest */ - b.write_budget = 1024; - mq_relay_on_b_writable(r); - - MQ_CHECK_EQ_INT(b.out_len, 100); - MQ_CHECK_MEM(b.out, a_in, 100); - MQ_CHECK_EQ_INT(g_done_count, 0); /* sources never EOF, so never done */ - - mq_relay_free(r); -} - -/* Case 3: EOF one side, other still flowing. */ -static void -test_eof_one_side(void) -{ - g_done_count = 0; - mem_io_t a, b; - mem_init(&a); - mem_init(&b); - - static const unsigned char a_in[] = "hi"; - static const unsigned char b_in[] = "morebytes"; - a.in = a_in; - a.in_len = 2; /* A EOFs after "hi" */ - b.in = b_in; - b.in_len = 9; /* B still flowing */ - a.write_budget = 1024; - b.write_budget = 1024; - - mq_relay_t *r = make_relay(&a, &b); - - /* Drive A side fully: A->B finishes (A EOF + drained) */ - mq_relay_on_a_readable(r); - MQ_CHECK_EQ_INT(b.out_len, 2); - MQ_CHECK_MEM(b.out, "hi", 2); - MQ_CHECK_EQ_INT(g_done_count, 0); /* B->A not finished yet */ - - /* Now drive B side: B->A finishes too */ - mq_relay_on_b_readable(r); - MQ_CHECK_EQ_INT(a.out_len, 9); - MQ_CHECK_MEM(a.out, "morebytes", 9); - MQ_CHECK_EQ_INT(g_done_count, 1); /* both finished now */ - - mq_relay_free(r); -} - -/* Case 4: hard write error. */ -static void -test_hard_error(void) -{ - g_done_count = 0; - mem_io_t a, b; - mem_init(&a); - mem_init(&b); - - static const unsigned char a_in[] = "data"; - a.in = a_in; - a.in_len = 4; - a.no_eof = 0; - b.write_err = 1; /* B's writer always errors */ - - mq_relay_t *r = make_relay(&a, &b); - - mq_relay_on_a_readable(r); - MQ_CHECK_EQ_INT(g_done_count, 1); /* error -> done once */ - - int reads_before = a.read_calls; - /* further edges are no-ops */ - mq_relay_on_a_readable(r); - mq_relay_on_b_writable(r); - MQ_CHECK_EQ_INT(a.read_calls, reads_before); /* no more reads */ - MQ_CHECK_EQ_INT(g_done_count, 1); /* still exactly once */ - - mq_relay_free(r); -} - -/* Case 5: hard READ error on A's source -> done once, no further B output. */ -static void -test_read_hard_error(void) -{ - g_done_count = 0; - mem_io_t a, b; - mem_init(&a); - mem_init(&b); - - static const unsigned char a_in[] = "abcdefghij"; /* 10 bytes */ - a.in = a_in; - a.in_len = 10; - a.read_chunk = 4; /* first read delivers 4 bytes... */ - a.read_err = 2; /* ...second read returns a hard error */ - b.write_budget = 1024; - - mq_relay_t *r = make_relay(&a, &b); - - mq_relay_on_a_readable(r); - - /* First read delivered 4 bytes to B; second read errored -> done once. */ - MQ_CHECK_EQ_INT(b.out_len, 4); - MQ_CHECK_MEM(b.out, "abcd", 4); - MQ_CHECK_EQ_INT(g_done_count, 1); - - /* After the error, further edges must not deliver more data or fire done. */ - size_t out_after_err = b.out_len; - mq_relay_on_a_readable(r); - mq_relay_on_b_writable(r); - MQ_CHECK_EQ_INT(b.out_len, out_after_err); /* B receives nothing further */ - MQ_CHECK_EQ_INT(g_done_count, 1); /* still exactly once */ - - mq_relay_free(r); -} - -/* Case 6: read would-block + chunked delivery, then EOF. */ -static void -test_read_would_block_chunked(void) -{ - g_done_count = 0; - mem_io_t a, b; - mem_init(&a); - mem_init(&b); - - static const unsigned char a_in[] = "the quick brown fox jumps"; /* 25 bytes */ - a.in = a_in; - a.in_len = 25; - a.read_chunk = 3; /* small chunks */ - a.read_wb_every = 3; /* every 3rd read would-blocks (no bytes now) */ - b.write_budget = 1024; - a.write_budget = 1024; - /* B's source is empty: it EOFs immediately, so the B->A direction can - finish. Drive its readable edge once up front to mark b_eof. */ - - mq_relay_t *r = make_relay(&a, &b); - - mq_relay_on_b_readable(r); /* B source EOFs immediately (empty) */ - - /* One readable edge pumps until the source would-blocks (no EOF yet). */ - mq_relay_on_a_readable(r); - MQ_CHECK(b.out_len < 25); /* would-block stopped us short */ - MQ_CHECK_EQ_INT(g_done_count, 0); /* not done: A not at EOF */ - MQ_CHECK_MEM(b.out, a_in, b.out_len); - - /* Follow-up readable edges after the would-block: keep draining the source. - A single pump_dir already loops past intermittent would-block once data - resumes within the same call, but extra edges model the readiness - notifications a real loop would deliver. */ - for (int i = 0; i < 20 && a.in_pos < a.in_len; i++) { - mq_relay_on_a_readable(r); - } - - /* Full input delivered to B, exactly once, and done fires after EOF+drain. */ - MQ_CHECK_EQ_INT(b.out_len, 25); - MQ_CHECK_MEM(b.out, a_in, 25); - MQ_CHECK_EQ_INT(g_done_count, 1); - - /* Idempotent: another edge does not re-fire done. */ - mq_relay_on_a_readable(r); - MQ_CHECK_EQ_INT(g_done_count, 1); - - mq_relay_free(r); -} - -/* Case 7: data + EOF in the same read call. */ -static void -test_data_and_eof_same_read(void) -{ - g_done_count = 0; - mem_io_t a, b; - mem_init(&a); - mem_init(&b); - - static const unsigned char a_in[] = "final"; /* 5 bytes */ - a.in = a_in; - a.in_len = 5; - a.read_eof_with_data = 1; /* one read returns the bytes AND *eof=1 */ - b.write_budget = 1024; - a.write_budget = 1024; - /* B's source is empty -> EOFs immediately so the B->A direction finishes. */ - - mq_relay_t *r = make_relay(&a, &b); - - mq_relay_on_b_readable(r); /* B source EOFs immediately (empty) */ - mq_relay_on_a_readable(r); - - /* The bytes from the data+EOF read are delivered, and done fires once. */ - MQ_CHECK_EQ_INT(a.read_calls, 1); /* delivered in a single read */ - MQ_CHECK_EQ_INT(b.out_len, 5); - MQ_CHECK_MEM(b.out, "final", 5); - MQ_CHECK_EQ_INT(g_done_count, 1); - - /* Idempotent. */ - mq_relay_on_a_readable(r); - MQ_CHECK_EQ_INT(g_done_count, 1); - - mq_relay_free(r); -} - -MQ_TEST_MAIN({ - test_happy_both_directions(); - test_backpressure(); - test_eof_one_side(); - test_hard_error(); - test_read_hard_error(); - test_read_would_block_chunked(); - test_data_and_eof_same_read(); -}) diff --git a/tests/test_sched.c b/tests/test_sched.c deleted file mode 100644 index d5eecbe3..00000000 --- a/tests/test_sched.c +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -/* tests/test_sched.c — mq_sched_from_string / mq_sched_name unit tests. */ - -#include - -#include "transport/mq_conn.h" -#include "mqtest.h" - -static void -test_valid_schedulers(void) -{ - int ok; - - ok = -1; - MQ_CHECK_EQ_INT(mq_sched_from_string("minrtt", &ok), MQ_SCHED_MINRTT); - MQ_CHECK_EQ_INT(ok, 1); - - ok = -1; - MQ_CHECK_EQ_INT(mq_sched_from_string("backup", &ok), MQ_SCHED_BACKUP); - MQ_CHECK_EQ_INT(ok, 1); - - ok = -1; - MQ_CHECK_EQ_INT(mq_sched_from_string("wlb", &ok), MQ_SCHED_WLB); - MQ_CHECK_EQ_INT(ok, 1); -} - -static void -test_invalid_schedulers(void) -{ - int ok; - - ok = -1; - MQ_CHECK_EQ_INT(mq_sched_from_string("bogus", &ok), MQ_SCHED_DEFAULT); - MQ_CHECK_EQ_INT(ok, 0); - - ok = -1; - MQ_CHECK_EQ_INT(mq_sched_from_string(NULL, &ok), MQ_SCHED_DEFAULT); - MQ_CHECK_EQ_INT(ok, 0); -} - -static void -test_null_ok_ptr(void) -{ - /* ok == NULL must not crash */ - MQ_CHECK_EQ_INT(mq_sched_from_string("minrtt", NULL), MQ_SCHED_MINRTT); -} - -static void -test_sched_names(void) -{ - MQ_CHECK(strcmp(mq_sched_name(MQ_SCHED_MINRTT), "minrtt") == 0); - MQ_CHECK(strcmp(mq_sched_name(MQ_SCHED_BACKUP), "backup") == 0); - MQ_CHECK(strcmp(mq_sched_name(MQ_SCHED_WLB), "wlb") == 0); -} - -MQ_TEST_MAIN({ - test_valid_schedulers(); - test_invalid_schedulers(); - test_null_ok_ptr(); - test_sched_names(); -}) diff --git a/tests/test_smoke.c b/tests/test_smoke.c deleted file mode 100644 index 8ae0a6e9..00000000 --- a/tests/test_smoke.c +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "mqtest.h" -MQ_TEST_MAIN({ MQ_CHECK_EQ_INT(1 + 1, 2); }) diff --git a/tests/test_socks5.c b/tests/test_socks5.c deleted file mode 100644 index 46ddfced..00000000 --- a/tests/test_socks5.c +++ /dev/null @@ -1,711 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "ingress/mq_socks5.h" -#include "mqtest.h" - -/* ---- byte-array helpers ---- */ -static const uint8_t GREETING_OK[] = {0x05, 0x01, 0x00}; -static const uint8_t GREETING_MULTI_OK[] = {0x05, 0x02, 0x02, 0x00}; -static const uint8_t GREETING_NO_NOAUTH[] = {0x05, 0x01, 0x02}; -static const uint8_t GREETING_BADVER[] = {0x04, 0x01, 0x00}; - -static const uint8_t REQ_IPV4[] = {0x05, 0x01, 0x00, 0x01, 1, 2, 3, 4, 0x00, 0x50}; -static const uint8_t IPV4_HOST[] = {1, 2, 3, 4}; - -static const uint8_t REQ_DOMAIN[] = {0x05, 0x01, 0x00, 0x03, 11, 'e', 'x', 'a', 'm', - 'p', 'l', 'e', '.', 'c', 'o', 'm', 0x01, 0xBB}; - -static const uint8_t REQ_IPV6[] = {0x05, 0x01, 0x00, 0x04, 0x20, 0x01, 0x0d, 0xb8, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0x1F, 0x90}; -static const uint8_t IPV6_HOST[] = {0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1}; - -static const uint8_t REQ_CMD_BIND[] = {0x05, 0x02, 0x00, 0x01, 1, 2, 3, 4, 0x00, 0x50}; - -/* ATYP=0x02 is reserved/unassigned — must be MQ_SOCKS5_UNSUPPORTED */ -static const uint8_t REQ_BAD_ATYP[] = {0x05, 0x01, 0x00, 0x02, 1, 2, 3, 4, 0x00, 0x50}; - -/* request with wrong VER (0x04) — must be MQ_SOCKS5_ERROR */ -static const uint8_t REQ_BADVER[] = {0x04, 0x01, 0x00, 0x01, 1, 2, 3, 4, 0x00, 0x50}; - -/* ---- greeting ---- */ -static void -test_greeting_ok(void) -{ - mq_socks5_parser_t p; - mq_socks5_parser_init(&p); - size_t consumed = 0; - mq_socks5_req_t out; - mq_socks5_status_t st = - mq_socks5_feed(&p, GREETING_OK, sizeof GREETING_OK, &consumed, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_GREETING_DONE); - MQ_CHECK_EQ_INT(consumed, sizeof GREETING_OK); -} - -static void -test_greeting_multi_method_ok(void) -{ - mq_socks5_parser_t p; - mq_socks5_parser_init(&p); - size_t consumed = 0; - mq_socks5_req_t out; - mq_socks5_status_t st = - mq_socks5_feed(&p, GREETING_MULTI_OK, sizeof GREETING_MULTI_OK, &consumed, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_GREETING_DONE); - MQ_CHECK_EQ_INT(consumed, sizeof GREETING_MULTI_OK); -} - -static void -test_greeting_no_noauth(void) -{ - mq_socks5_parser_t p; - mq_socks5_parser_init(&p); - size_t consumed = 0; - mq_socks5_req_t out; - mq_socks5_status_t st = mq_socks5_feed(&p, GREETING_NO_NOAUTH, - sizeof GREETING_NO_NOAUTH, &consumed, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_UNSUPPORTED); -} - -static void -test_greeting_bad_version(void) -{ - mq_socks5_parser_t p; - mq_socks5_parser_init(&p); - size_t consumed = 0; - mq_socks5_req_t out; - mq_socks5_status_t st = - mq_socks5_feed(&p, GREETING_BADVER, sizeof GREETING_BADVER, &consumed, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_ERROR); -} - -static void -test_greeting_need_more(void) -{ - mq_socks5_parser_t p; - mq_socks5_parser_init(&p); - size_t consumed = 99; - mq_socks5_req_t out; - /* VER + NMETHODS=1 but methods byte missing */ - mq_socks5_status_t st = mq_socks5_feed(&p, GREETING_OK, 2, &consumed, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_NEED_MORE); - MQ_CHECK_EQ_INT(consumed, 0); -} - -/* ---- requests (greeting first) ---- */ -static mq_socks5_status_t -do_request(const uint8_t *buf, size_t len, mq_socks5_req_t *out) -{ - mq_socks5_parser_t p; - mq_socks5_parser_init(&p); - size_t consumed = 0; - mq_socks5_req_t g; - mq_socks5_status_t st = - mq_socks5_feed(&p, GREETING_OK, sizeof GREETING_OK, &consumed, &g); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_GREETING_DONE); - consumed = 0; - return mq_socks5_feed(&p, buf, len, &consumed, out); -} - -static void -test_request_ipv4(void) -{ - mq_socks5_req_t out; - memset(&out, 0, sizeof out); - mq_socks5_status_t st = do_request(REQ_IPV4, sizeof REQ_IPV4, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_REQUEST_DONE); - MQ_CHECK_EQ_INT(out.atype, MQ_ADDR_IPV4); - MQ_CHECK_EQ_INT(out.host_len, 4); - MQ_CHECK_MEM(out.host, IPV4_HOST, 4); - MQ_CHECK_EQ_INT(out.port, 80); -} - -static void -test_request_domain(void) -{ - mq_socks5_req_t out; - memset(&out, 0, sizeof out); - mq_socks5_status_t st = do_request(REQ_DOMAIN, sizeof REQ_DOMAIN, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_REQUEST_DONE); - MQ_CHECK_EQ_INT(out.atype, MQ_ADDR_DOMAIN); - MQ_CHECK_EQ_INT(out.host_len, 11); - MQ_CHECK_MEM(out.host, "example.com", 11); - MQ_CHECK_EQ_INT(out.port, 443); -} - -static void -test_request_ipv6(void) -{ - mq_socks5_req_t out; - memset(&out, 0, sizeof out); - mq_socks5_status_t st = do_request(REQ_IPV6, sizeof REQ_IPV6, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_REQUEST_DONE); - MQ_CHECK_EQ_INT(out.atype, MQ_ADDR_IPV6); - MQ_CHECK_EQ_INT(out.host_len, 16); - MQ_CHECK_MEM(out.host, IPV6_HOST, 16); - MQ_CHECK_EQ_INT(out.port, 0x1F90); -} - -static void -test_request_fragmented(void) -{ - mq_socks5_parser_t p; - mq_socks5_parser_init(&p); - size_t consumed = 0; - mq_socks5_req_t out; - memset(&out, 0, sizeof out); - mq_socks5_status_t st = - mq_socks5_feed(&p, GREETING_OK, sizeof GREETING_OK, &consumed, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_GREETING_DONE); - - /* first slice: 5 bytes of a 10-byte IPv4 request */ - consumed = 99; - st = mq_socks5_feed(&p, REQ_IPV4, 5, &consumed, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_NEED_MORE); - MQ_CHECK_EQ_INT(consumed, 0); - - /* second slice: whole request now available */ - consumed = 0; - st = mq_socks5_feed(&p, REQ_IPV4, sizeof REQ_IPV4, &consumed, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_REQUEST_DONE); - MQ_CHECK_EQ_INT(out.atype, MQ_ADDR_IPV4); - MQ_CHECK_MEM(out.host, IPV4_HOST, 4); - MQ_CHECK_EQ_INT(out.port, 80); -} - -static void -test_request_cmd_unsupported(void) -{ - mq_socks5_req_t out; - memset(&out, 0, sizeof out); - mq_socks5_status_t st = do_request(REQ_CMD_BIND, sizeof REQ_CMD_BIND, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_UNSUPPORTED_CMD); -} - -static void -test_request_bad_atyp(void) -{ - mq_socks5_req_t out; - memset(&out, 0, sizeof out); - mq_socks5_status_t st = do_request(REQ_BAD_ATYP, sizeof REQ_BAD_ATYP, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_UNSUPPORTED_ATYP); -} - -static void -test_request_bad_version(void) -{ - mq_socks5_req_t out; - memset(&out, 0, sizeof out); - mq_socks5_status_t st = do_request(REQ_BADVER, sizeof REQ_BADVER, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_ERROR); -} - -static void -test_greeting_one_byte(void) -{ - mq_socks5_parser_t p; - mq_socks5_parser_init(&p); - size_t consumed = 99; - mq_socks5_req_t out; - /* feed only 1 byte — must need more, consuming nothing */ - mq_socks5_status_t st = mq_socks5_feed(&p, GREETING_OK, 1, &consumed, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_NEED_MORE); - MQ_CHECK_EQ_INT(consumed, 0); -} - -/* ---- reply builders ---- */ -static void -test_method_reply(void) -{ - uint8_t out[2]; - size_t n = mq_socks5_build_method_reply(out, 1); - MQ_CHECK_EQ_INT(n, 2); - MQ_CHECK_EQ_INT(out[0], 0x05); - MQ_CHECK_EQ_INT(out[1], 0x00); - - n = mq_socks5_build_method_reply(out, 0); - MQ_CHECK_EQ_INT(n, 2); - MQ_CHECK_EQ_INT(out[0], 0x05); - MQ_CHECK_EQ_INT(out[1], 0xFF); -} - -static void -test_connect_reply(void) -{ - uint8_t out[10]; - static const uint8_t expect[10] = {0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}; - size_t n = mq_socks5_build_connect_reply(out, 0x00); - MQ_CHECK_EQ_INT(n, 10); - MQ_CHECK_MEM(out, expect, 10); - - n = mq_socks5_build_connect_reply(out, 0x05); - MQ_CHECK_EQ_INT(n, 10); - MQ_CHECK_EQ_INT(out[1], 0x05); - MQ_CHECK_EQ_INT(out[3], 0x01); -} - -static void -test_reply_code(void) -{ - MQ_CHECK_EQ_INT(mq_socks5_reply_code(MQ_TCP_OK), 0x00); - MQ_CHECK_EQ_INT(mq_socks5_reply_code(MQ_TCP_CONN_REFUSED), 0x05); - MQ_CHECK_EQ_INT(mq_socks5_reply_code(MQ_TCP_DNS_FAILED), 0x04); - MQ_CHECK_EQ_INT(mq_socks5_reply_code(MQ_TCP_TIMEOUT), 0x06); - MQ_CHECK_EQ_INT(mq_socks5_reply_code(MQ_TCP_POLICY_DENIED), 0x02); -} - -/* ---- ASSOCIATE (CMD 0x03) ---- */ - -/* CMD=0x03, DST=0.0.0.0:0 (typical client sends zeros) */ -static const uint8_t REQ_ASSOCIATE_V4_ZERO[] = {0x05, 0x03, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00}; -/* CMD=0x03, DST=192.168.1.1:1234 */ -static const uint8_t REQ_ASSOCIATE_V4[] = {0x05, 0x03, 0x00, 0x01, 192, - 168, 1, 1, 0x04, 0xD2}; -/* CMD=0x03, DST=domain "example.com":443 */ -static const uint8_t REQ_ASSOCIATE_DOMAIN[] = {0x05, 0x03, 0x00, 0x03, 11, 'e', - 'x', 'a', 'm', 'p', 'l', 'e', - '.', 'c', 'o', 'm', 0x01, 0xBB}; -/* CMD=0x03, DST=IPv6 2001:db8::1:8080 */ -static const uint8_t REQ_ASSOCIATE_V6[] = {0x05, 0x03, 0x00, 0x04, 0x20, 0x01, 0x0d, 0xb8, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0x01, 0x1F, 0x90}; - -static void -test_associate_ipv4_zero_dst(void) -{ - mq_socks5_req_t out; - memset(&out, 0xFF, sizeof out); - mq_socks5_status_t st = - do_request(REQ_ASSOCIATE_V4_ZERO, sizeof REQ_ASSOCIATE_V4_ZERO, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_ASSOCIATE_DONE); - MQ_CHECK_EQ_INT(out.atype, MQ_ADDR_IPV4); - MQ_CHECK_EQ_INT(out.host_len, 4); - MQ_CHECK_EQ_INT(out.port, 0); - static const uint8_t zeros[4] = {0, 0, 0, 0}; - MQ_CHECK_MEM(out.host, zeros, 4); -} - -static void -test_associate_ipv4_dst(void) -{ - mq_socks5_req_t out; - memset(&out, 0, sizeof out); - mq_socks5_status_t st = do_request(REQ_ASSOCIATE_V4, sizeof REQ_ASSOCIATE_V4, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_ASSOCIATE_DONE); - MQ_CHECK_EQ_INT(out.atype, MQ_ADDR_IPV4); - MQ_CHECK_EQ_INT(out.host_len, 4); - static const uint8_t ip[] = {192, 168, 1, 1}; - MQ_CHECK_MEM(out.host, ip, 4); - MQ_CHECK_EQ_INT(out.port, 1234); -} - -static void -test_associate_domain_dst(void) -{ - mq_socks5_req_t out; - memset(&out, 0, sizeof out); - mq_socks5_status_t st = - do_request(REQ_ASSOCIATE_DOMAIN, sizeof REQ_ASSOCIATE_DOMAIN, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_ASSOCIATE_DONE); - MQ_CHECK_EQ_INT(out.atype, MQ_ADDR_DOMAIN); - MQ_CHECK_EQ_INT(out.host_len, 11); - MQ_CHECK_MEM(out.host, "example.com", 11); - MQ_CHECK_EQ_INT(out.port, 443); -} - -static void -test_associate_ipv6_dst(void) -{ - mq_socks5_req_t out; - memset(&out, 0, sizeof out); - mq_socks5_status_t st = do_request(REQ_ASSOCIATE_V6, sizeof REQ_ASSOCIATE_V6, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_ASSOCIATE_DONE); - MQ_CHECK_EQ_INT(out.atype, MQ_ADDR_IPV6); - MQ_CHECK_EQ_INT(out.host_len, 16); - static const uint8_t ipv6[] = {0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0x01}; - MQ_CHECK_MEM(out.host, ipv6, 16); - MQ_CHECK_EQ_INT(out.port, 0x1F90); -} - -/* CMD=0x02 (BIND) is still unsupported — not ASSOCIATE */ -static void -test_request_bind_still_unsupported(void) -{ - mq_socks5_req_t out; - memset(&out, 0, sizeof out); - mq_socks5_status_t st = do_request(REQ_CMD_BIND, sizeof REQ_CMD_BIND, &out); - MQ_CHECK_EQ_INT(st, MQ_SOCKS5_UNSUPPORTED_CMD); -} - -/* ---- ASSOCIATE reply ---- */ -static void -test_associate_reply_zero(void) -{ - uint8_t out[10]; - memset(out, 0xFF, sizeof out); - size_t n = mq_socks5_build_associate_reply(out, 0, 0); - MQ_CHECK_EQ_INT(n, 10); - static const uint8_t expect[10] = {0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}; - MQ_CHECK_MEM(out, expect, 10); -} - -static void -test_associate_reply_with_addr(void) -{ - uint8_t out[10]; - memset(out, 0xFF, sizeof out); - /* BND.ADDR = 10.0.0.1 (0x0A000001 big-endian), BND.PORT = 1080 (0x0438) */ - size_t n = mq_socks5_build_associate_reply(out, 0x0A000001u, 1080); - MQ_CHECK_EQ_INT(n, 10); - MQ_CHECK_EQ_INT(out[0], 0x05); /* VER */ - MQ_CHECK_EQ_INT(out[1], 0x00); /* REP=success */ - MQ_CHECK_EQ_INT(out[2], 0x00); /* RSV */ - MQ_CHECK_EQ_INT(out[3], 0x01); /* ATYP=IPv4 */ - MQ_CHECK_EQ_INT(out[4], 0x0A); /* 10 */ - MQ_CHECK_EQ_INT(out[5], 0x00); /* 0 */ - MQ_CHECK_EQ_INT(out[6], 0x00); /* 0 */ - MQ_CHECK_EQ_INT(out[7], 0x01); /* 1 */ - MQ_CHECK_EQ_INT(out[8], 0x04); /* port high */ - MQ_CHECK_EQ_INT(out[9], 0x38); /* port low */ -} - -/* connect_reply is unaffected by refactor */ -static void -test_connect_reply_unchanged(void) -{ - uint8_t out[10]; - static const uint8_t expect[10] = {0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}; - size_t n = mq_socks5_build_connect_reply(out, 0x00); - MQ_CHECK_EQ_INT(n, 10); - MQ_CHECK_MEM(out, expect, 10); -} - -/* ---- UDP encapsulation header ---- */ - -/* IPv4 UDP header: RSV=0 FRAG=0 ATYP=1 ADDR=1.2.3.4 PORT=80 */ -static const uint8_t UDP_HDR_IPV4[] = { - 0x00, 0x00, /* RSV */ - 0x00, /* FRAG */ - 0x01, /* ATYP=IPv4 */ - 0x01, 0x02, 0x03, 0x04, /* ADDR */ - 0x00, 0x50 /* PORT=80 */ -}; - -/* IPv6 UDP header */ -static const uint8_t UDP_HDR_IPV6[] = { - 0x00, 0x00, /* RSV */ - 0x00, /* FRAG */ - 0x04, /* ATYP=IPv6 */ - 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* ADDR */ - 0x1F, 0x90 /* PORT=8080 */ -}; - -/* Domain UDP header */ -static const uint8_t UDP_HDR_DOMAIN[] = { - 0x00, 0x00, /* RSV */ - 0x00, /* FRAG */ - 0x03, /* ATYP=domain */ - 0x0B, /* LEN=11 */ - 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', /* ADDR */ - 0x01, 0xBB /* PORT=443 */ -}; - -/* Empty domain UDP header: ATYP=domain, LEN=0 (no domain bytes) */ -static const uint8_t UDP_HDR_EMPTY_DOMAIN[] = { - 0x00, 0x00, /* RSV */ - 0x00, /* FRAG */ - 0x03, /* ATYP=domain */ - 0x00, /* LEN=0 (empty domain) */ - 0x00, 0x50 /* PORT=80 */ -}; - -static void -test_parse_udp_hdr_empty_domain(void) -{ - mq_socks5_udp_hdr_t h; - memset(&h, 0, sizeof h); - int n = - mq_socks5_parse_udp_hdr(UDP_HDR_EMPTY_DOMAIN, sizeof UDP_HDR_EMPTY_DOMAIN, &h); - MQ_CHECK_EQ_INT(n, (int)sizeof UDP_HDR_EMPTY_DOMAIN); - MQ_CHECK_EQ_INT(h.hdr_len, sizeof UDP_HDR_EMPTY_DOMAIN); - MQ_CHECK_EQ_INT(h.dst.atype, MQ_ADDR_DOMAIN); - MQ_CHECK_EQ_INT(h.dst.host_len, 0); - MQ_CHECK_EQ_INT(h.dst.port, 80); -} - -static void -test_parse_udp_hdr_ipv4(void) -{ - mq_socks5_udp_hdr_t h; - memset(&h, 0, sizeof h); - int n = mq_socks5_parse_udp_hdr(UDP_HDR_IPV4, sizeof UDP_HDR_IPV4, &h); - MQ_CHECK_EQ_INT(n, (int)sizeof UDP_HDR_IPV4); - MQ_CHECK_EQ_INT(h.hdr_len, sizeof UDP_HDR_IPV4); - MQ_CHECK_EQ_INT(h.dst.atype, MQ_ADDR_IPV4); - MQ_CHECK_EQ_INT(h.dst.host_len, 4); - static const uint8_t addr[] = {1, 2, 3, 4}; - MQ_CHECK_MEM(h.dst.host, addr, 4); - MQ_CHECK_EQ_INT(h.dst.port, 80); -} - -static void -test_parse_udp_hdr_ipv6(void) -{ - mq_socks5_udp_hdr_t h; - memset(&h, 0, sizeof h); - int n = mq_socks5_parse_udp_hdr(UDP_HDR_IPV6, sizeof UDP_HDR_IPV6, &h); - MQ_CHECK_EQ_INT(n, (int)sizeof UDP_HDR_IPV6); - MQ_CHECK_EQ_INT(h.dst.atype, MQ_ADDR_IPV6); - MQ_CHECK_EQ_INT(h.dst.host_len, 16); - static const uint8_t addr[] = {0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1}; - MQ_CHECK_MEM(h.dst.host, addr, 16); - MQ_CHECK_EQ_INT(h.dst.port, 0x1F90); -} - -static void -test_parse_udp_hdr_domain(void) -{ - mq_socks5_udp_hdr_t h; - memset(&h, 0, sizeof h); - int n = mq_socks5_parse_udp_hdr(UDP_HDR_DOMAIN, sizeof UDP_HDR_DOMAIN, &h); - MQ_CHECK_EQ_INT(n, (int)sizeof UDP_HDR_DOMAIN); - MQ_CHECK_EQ_INT(h.dst.atype, MQ_ADDR_DOMAIN); - MQ_CHECK_EQ_INT(h.dst.host_len, 11); - MQ_CHECK_MEM(h.dst.host, "example.com", 11); - MQ_CHECK_EQ_INT(h.dst.port, 443); -} - -static void -test_parse_udp_hdr_frag_nonzero(void) -{ - /* FRAG=1: must return -2 */ - static const uint8_t buf[] = {0x00, 0x00, 0x01, 0x01, 1, 2, 3, 4, 0x00, 0x50}; - mq_socks5_udp_hdr_t h; - int n = mq_socks5_parse_udp_hdr(buf, sizeof buf, &h); - MQ_CHECK_EQ_INT(n, -2); -} - -static void -test_parse_udp_hdr_rsv_nonzero(void) -{ - /* RSV=0x0001: must return -1 (strict reject) */ - static const uint8_t buf[] = {0x00, 0x01, 0x00, 0x01, 1, 2, 3, 4, 0x00, 0x50}; - mq_socks5_udp_hdr_t h; - int n = mq_socks5_parse_udp_hdr(buf, sizeof buf, &h); - MQ_CHECK_EQ_INT(n, -1); -} - -static void -test_parse_udp_hdr_rsv_high_nonzero(void) -{ - /* RSV=0x0100: must return -1 */ - static const uint8_t buf[] = {0x01, 0x00, 0x00, 0x01, 1, 2, 3, 4, 0x00, 0x50}; - mq_socks5_udp_hdr_t h; - int n = mq_socks5_parse_udp_hdr(buf, sizeof buf, &h); - MQ_CHECK_EQ_INT(n, -1); -} - -/* Truncation sweep: feed 0..N-1 bytes of a valid IPv4 header, all must return -1 */ -static void -test_parse_udp_hdr_truncation_sweep_ipv4(void) -{ - mq_socks5_udp_hdr_t h; - for (size_t i = 0; i < sizeof UDP_HDR_IPV4; i++) { - int n = mq_socks5_parse_udp_hdr(UDP_HDR_IPV4, i, &h); - MQ_CHECK_EQ_INT(n, -1); - } -} - -/* Truncation sweep for domain header (variable-length, more interesting) */ -static void -test_parse_udp_hdr_truncation_sweep_domain(void) -{ - mq_socks5_udp_hdr_t h; - for (size_t i = 0; i < sizeof UDP_HDR_DOMAIN; i++) { - int n = mq_socks5_parse_udp_hdr(UDP_HDR_DOMAIN, i, &h); - MQ_CHECK_EQ_INT(n, -1); - } -} - -/* Truncation sweep: feed 0..N-1 bytes of a valid IPv6 header, all must return -1 */ -static void -test_parse_udp_hdr_truncation_sweep_ipv6(void) -{ - mq_socks5_udp_hdr_t h; - for (size_t i = 0; i < sizeof UDP_HDR_IPV6; i++) { - int n = mq_socks5_parse_udp_hdr(UDP_HDR_IPV6, i, &h); - MQ_CHECK_EQ_INT(n, -1); - } -} - -/* Bad ATYP in UDP header */ -static void -test_parse_udp_hdr_bad_atyp(void) -{ - static const uint8_t buf[] = {0x00, 0x00, 0x00, 0x02, 1, 2, 3, 4, 0x00, 0x50}; - mq_socks5_udp_hdr_t h; - int n = mq_socks5_parse_udp_hdr(buf, sizeof buf, &h); - MQ_CHECK_EQ_INT(n, -1); -} - -/* ---- build_udp_hdr ---- */ -static void -test_build_udp_hdr_ipv4_roundtrip(void) -{ - /* build then parse — should recover the same dst */ - mq_socks5_req_t src; - src.atype = MQ_ADDR_IPV4; - src.host_len = 4; - src.host[0] = 10; - src.host[1] = 0; - src.host[2] = 0; - src.host[3] = 1; - src.port = 53; - - uint8_t buf[32]; - int n = mq_socks5_build_udp_hdr(buf, sizeof buf, &src); - MQ_CHECK_EQ_INT(n, 10); /* RSV(2)+FRAG(1)+ATYP(1)+IPv4(4)+PORT(2) */ - - mq_socks5_udp_hdr_t h; - int n2 = mq_socks5_parse_udp_hdr(buf, (size_t)n, &h); - MQ_CHECK_EQ_INT(n2, n); - MQ_CHECK_EQ_INT(h.dst.atype, MQ_ADDR_IPV4); - MQ_CHECK_EQ_INT(h.dst.host_len, 4); - MQ_CHECK_MEM(h.dst.host, src.host, 4); - MQ_CHECK_EQ_INT(h.dst.port, 53); -} - -static void -test_build_udp_hdr_ipv6_roundtrip(void) -{ - mq_socks5_req_t src; - src.atype = MQ_ADDR_IPV6; - src.host_len = 16; - for (int i = 0; i < 16; i++) - src.host[i] = (uint8_t)i; - src.port = 12345; - - uint8_t buf[64]; - int n = mq_socks5_build_udp_hdr(buf, sizeof buf, &src); - MQ_CHECK_EQ_INT(n, 22); /* RSV(2)+FRAG(1)+ATYP(1)+IPv6(16)+PORT(2) */ - - mq_socks5_udp_hdr_t h; - int n2 = mq_socks5_parse_udp_hdr(buf, (size_t)n, &h); - MQ_CHECK_EQ_INT(n2, n); - MQ_CHECK_EQ_INT(h.dst.atype, MQ_ADDR_IPV6); - MQ_CHECK_EQ_INT(h.dst.host_len, 16); - MQ_CHECK_MEM(h.dst.host, src.host, 16); - MQ_CHECK_EQ_INT(h.dst.port, 12345); -} - -static void -test_build_udp_hdr_domain_roundtrip(void) -{ - mq_socks5_req_t src; - src.atype = MQ_ADDR_DOMAIN; - src.host_len = 7; - memcpy(src.host, "foo.bar", 7); - src.port = 8080; - - uint8_t buf[64]; - int n = mq_socks5_build_udp_hdr(buf, sizeof buf, &src); - MQ_CHECK_EQ_INT(n, 14); /* RSV(2)+FRAG(1)+ATYP(1)+LEN(1)+7+PORT(2) */ - - mq_socks5_udp_hdr_t h; - int n2 = mq_socks5_parse_udp_hdr(buf, (size_t)n, &h); - MQ_CHECK_EQ_INT(n2, n); - MQ_CHECK_EQ_INT(h.dst.atype, MQ_ADDR_DOMAIN); - MQ_CHECK_EQ_INT(h.dst.host_len, 7); - MQ_CHECK_MEM(h.dst.host, "foo.bar", 7); - MQ_CHECK_EQ_INT(h.dst.port, 8080); -} - -static void -test_build_udp_hdr_cap_too_small(void) -{ - mq_socks5_req_t src; - src.atype = MQ_ADDR_IPV4; - src.host_len = 4; - src.host[0] = 1; - src.host[1] = 2; - src.host[2] = 3; - src.host[3] = 4; - src.port = 80; - - uint8_t buf[9]; /* need 10, have 9 */ - int n = mq_socks5_build_udp_hdr(buf, sizeof buf, &src); - MQ_CHECK_EQ_INT(n, -1); -} - -/* check RSV=0 and FRAG=0 in built header */ -static void -test_build_udp_hdr_rsv_frag_zero(void) -{ - mq_socks5_req_t src; - src.atype = MQ_ADDR_IPV4; - src.host_len = 4; - src.host[0] = 0; - src.host[1] = 0; - src.host[2] = 0; - src.host[3] = 0; - src.port = 0; - - uint8_t buf[16]; - int n = mq_socks5_build_udp_hdr(buf, sizeof buf, &src); - MQ_CHECK_EQ_INT(n, 10); - MQ_CHECK_EQ_INT(buf[0], 0x00); /* RSV high */ - MQ_CHECK_EQ_INT(buf[1], 0x00); /* RSV low */ - MQ_CHECK_EQ_INT(buf[2], 0x00); /* FRAG */ -} - -MQ_TEST_MAIN({ - test_greeting_ok(); - test_greeting_multi_method_ok(); - test_greeting_no_noauth(); - test_greeting_bad_version(); - test_greeting_need_more(); - test_request_ipv4(); - test_request_domain(); - test_request_ipv6(); - test_request_fragmented(); - test_request_cmd_unsupported(); - test_request_bad_atyp(); - test_request_bad_version(); - test_greeting_one_byte(); - test_method_reply(); - test_connect_reply(); - test_reply_code(); - /* ASSOCIATE parse */ - test_associate_ipv4_zero_dst(); - test_associate_ipv4_dst(); - test_associate_domain_dst(); - test_associate_ipv6_dst(); - test_request_bind_still_unsupported(); - /* ASSOCIATE reply */ - test_associate_reply_zero(); - test_associate_reply_with_addr(); - test_connect_reply_unchanged(); - /* UDP encapsulation header parse */ - test_parse_udp_hdr_empty_domain(); - test_parse_udp_hdr_ipv4(); - test_parse_udp_hdr_ipv6(); - test_parse_udp_hdr_domain(); - test_parse_udp_hdr_frag_nonzero(); - test_parse_udp_hdr_rsv_nonzero(); - test_parse_udp_hdr_rsv_high_nonzero(); - test_parse_udp_hdr_truncation_sweep_ipv4(); - test_parse_udp_hdr_truncation_sweep_domain(); - test_parse_udp_hdr_truncation_sweep_ipv6(); - test_parse_udp_hdr_bad_atyp(); - /* build_udp_hdr */ - test_build_udp_hdr_ipv4_roundtrip(); - test_build_udp_hdr_ipv6_roundtrip(); - test_build_udp_hdr_domain_roundtrip(); - test_build_udp_hdr_cap_too_small(); - test_build_udp_hdr_rsv_frag_zero(); -}) diff --git a/tests/test_udp_msg.c b/tests/test_udp_msg.c deleted file mode 100644 index 57d08d0e..00000000 --- a/tests/test_udp_msg.c +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "wire/mq_udp_msg.h" -#include "mqtest.h" - -/* ---- encode→decode round-trip (fixed-length BE) ---- */ -static void -test_hdr_roundtrip(void) -{ - mq_udp_msg_hdr_t in; - in.session_id = 0xDEADBEEFU; - in.packet_id = 0x1234U; - in.flags = 0xABU; - in.frag_id = 7U; - in.frag_count = 12U; - - uint8_t buf[MQ_UDP_MSG_HDR]; - MQ_CHECK_EQ_INT(mq_udp_msg_encode_hdr(buf, &in), 0); - - mq_udp_msg_hdr_t out; - MQ_CHECK_EQ_INT(mq_udp_msg_decode_hdr(buf, MQ_UDP_MSG_HDR, &out), 0); - - MQ_CHECK_EQ_INT((long long)out.session_id, (long long)in.session_id); - MQ_CHECK_EQ_INT((long long)out.packet_id, (long long)in.packet_id); - MQ_CHECK_EQ_INT((long long)out.flags, (long long)in.flags); - MQ_CHECK_EQ_INT((long long)out.frag_id, (long long)in.frag_id); - MQ_CHECK_EQ_INT((long long)out.frag_count, (long long)in.frag_count); -} - -/* ---- BE byte-order: hand-crafted bytes, not just round-trip ---- */ -static void -test_hdr_byte_order(void) -{ - /* session_id=0x01020304, packet_id=0x0506, flags=0x07, frag_id=0x08, frag_count=0x09 - */ - uint8_t wire[MQ_UDP_MSG_HDR] = {0x01, 0x02, 0x03, 0x04, /* session_id BE */ - 0x05, 0x06, /* packet_id BE */ - 0x07, /* flags */ - 0x08, /* frag_id */ - 0x09}; /* frag_count */ - mq_udp_msg_hdr_t h; - MQ_CHECK_EQ_INT(mq_udp_msg_decode_hdr(wire, MQ_UDP_MSG_HDR, &h), 0); - MQ_CHECK_EQ_INT((long long)h.session_id, 0x01020304LL); - MQ_CHECK_EQ_INT((long long)h.packet_id, 0x0506LL); - MQ_CHECK_EQ_INT((long long)h.flags, 0x07LL); - MQ_CHECK_EQ_INT((long long)h.frag_id, 0x08LL); - MQ_CHECK_EQ_INT((long long)h.frag_count, 0x09LL); - - /* also verify that encode produces the same bytes */ - uint8_t out[MQ_UDP_MSG_HDR]; - MQ_CHECK_EQ_INT(mq_udp_msg_encode_hdr(out, &h), 0); - MQ_CHECK_MEM(out, wire, MQ_UDP_MSG_HDR); -} - -/* ---- decode rejects len < 9 ---- */ -static void -test_hdr_decode_truncation(void) -{ - mq_udp_msg_hdr_t in; - in.session_id = 1; - in.packet_id = 2; - in.flags = 0; - in.frag_id = 0; - in.frag_count = 1; - uint8_t buf[MQ_UDP_MSG_HDR]; - MQ_CHECK_EQ_INT(mq_udp_msg_encode_hdr(buf, &in), 0); - - mq_udp_msg_hdr_t out; - for (size_t k = 0; k < MQ_UDP_MSG_HDR; k++) { - MQ_CHECK_EQ_INT(mq_udp_msg_decode_hdr(buf, k, &out), -1); - } - /* exactly 9 bytes must succeed */ - MQ_CHECK_EQ_INT(mq_udp_msg_decode_hdr(buf, MQ_UDP_MSG_HDR, &out), 0); -} - -/* ---- collect frags from split ---- */ -typedef struct { - mq_udp_msg_hdr_t hdrs[512]; - const uint8_t *slices[512]; - size_t lens[512]; - size_t count; -} frag_log_t; - -static void -collect_emit(const mq_udp_msg_hdr_t *h, const uint8_t *p, size_t len, void *user) -{ - frag_log_t *log = (frag_log_t *)user; - if (log->count < 512) { - log->hdrs[log->count] = *h; - log->slices[log->count] = p; - log->lens[log->count] = len; - log->count++; - } -} - -/* ---- frag split: 2500-byte payload, mss=1000 → 3 frags ---- */ -static void -test_split_3frags(void) -{ - static uint8_t payload[2500]; - for (size_t i = 0; i < sizeof payload; i++) - payload[i] = (uint8_t)(i & 0xFF); - - frag_log_t log; - log.count = 0; - int r = - mq_udp_msg_split(0xAABBCCDDU, 0x0042U, payload, 2500, 1000, collect_emit, &log); - MQ_CHECK_EQ_INT(r, 0); - MQ_CHECK_EQ_INT((long long)log.count, 3LL); - - /* sizes: 1000, 1000, 500 */ - MQ_CHECK_EQ_INT((long long)log.lens[0], 1000LL); - MQ_CHECK_EQ_INT((long long)log.lens[1], 1000LL); - MQ_CHECK_EQ_INT((long long)log.lens[2], 500LL); - - /* same session_id and packet_id across all frags */ - for (int i = 0; i < 3; i++) { - MQ_CHECK_EQ_INT((long long)log.hdrs[i].session_id, (long long)0xAABBCCDDU); - MQ_CHECK_EQ_INT((long long)log.hdrs[i].packet_id, 0x0042LL); - MQ_CHECK_EQ_INT((long long)log.hdrs[i].frag_count, 3LL); - MQ_CHECK_EQ_INT((long long)log.hdrs[i].frag_id, (long long)i); - } - - /* slices must point into the original payload */ - MQ_CHECK(log.slices[0] == payload); - MQ_CHECK(log.slices[1] == payload + 1000); - MQ_CHECK(log.slices[2] == payload + 2000); -} - -/* ---- frag split: payload <= mss → 1 frag ---- */ -static void -test_split_1frag(void) -{ - static uint8_t payload[64]; - - frag_log_t log; - log.count = 0; - int r = mq_udp_msg_split(1U, 2U, payload, 64, 1000, collect_emit, &log); - MQ_CHECK_EQ_INT(r, 0); - MQ_CHECK_EQ_INT((long long)log.count, 1LL); - MQ_CHECK_EQ_INT((long long)log.lens[0], 64LL); - MQ_CHECK_EQ_INT((long long)log.hdrs[0].frag_id, 0LL); - MQ_CHECK_EQ_INT((long long)log.hdrs[0].frag_count, 1LL); -} - -/* ---- frag split: zero-length payload → 1 frag of length 0 ---- - * Design doc is silent on len==0; we define 1 frag of 0 bytes (consistent - * with the "1 frag" rule for payload <= mss). */ -static void -test_split_empty_payload(void) -{ - static uint8_t dummy[1]; - - frag_log_t log; - log.count = 0; - int r = mq_udp_msg_split(1U, 0U, dummy, 0, 1000, collect_emit, &log); - MQ_CHECK_EQ_INT(r, 0); - MQ_CHECK_EQ_INT((long long)log.count, 1LL); - MQ_CHECK_EQ_INT((long long)log.lens[0], 0LL); - MQ_CHECK_EQ_INT((long long)log.hdrs[0].frag_id, 0LL); - MQ_CHECK_EQ_INT((long long)log.hdrs[0].frag_count, 1LL); -} - -/* ---- boundary: exactly 255 frags succeeds ---- */ -static void -test_split_255frags_ok(void) -{ - /* 255 * 1000 = 255000 bytes, mss=1000 → exactly 255 frags */ - static uint8_t payload[255 * 1000]; - - frag_log_t log; - log.count = 0; - int r = mq_udp_msg_split(1U, 0U, payload, 255 * 1000, 1000, collect_emit, &log); - MQ_CHECK_EQ_INT(r, 0); - MQ_CHECK_EQ_INT((long long)log.count, 255LL); - MQ_CHECK_EQ_INT((long long)log.hdrs[0].frag_count, 255LL); - MQ_CHECK_EQ_INT((long long)log.hdrs[254].frag_id, 254LL); -} - -/* ---- boundary: 256 frags needed → -1, no emit calls ---- */ -static void -test_split_256frags_rejected(void) -{ - /* 255*1000 + 1 bytes, mss=1000 → ceil = 256 frags → must reject */ - static uint8_t payload[255 * 1000 + 1]; - - frag_log_t log; - log.count = 0; - int r = mq_udp_msg_split(1U, 0U, payload, 255 * 1000 + 1, 1000, collect_emit, &log); - MQ_CHECK_EQ_INT(r, -1); - MQ_CHECK_EQ_INT((long long)log.count, 0LL); /* no emit on error */ -} - -/* ---- mss_payload == 0 → -1, no emit calls ---- */ -static void -test_split_mss_zero_rejected(void) -{ - static uint8_t payload[64]; - - frag_log_t log; - log.count = 0; - int r = mq_udp_msg_split(1U, 0U, payload, 64, 0, collect_emit, &log); - MQ_CHECK_EQ_INT(r, -1); - MQ_CHECK_EQ_INT((long long)log.count, 0LL); -} - -/* ---- integer overflow in frag-count math: len near SIZE_MAX → -1, no emit ---- - * len + mss_payload - 1 overflows size_t when len is near SIZE_MAX, wrapping - * nfrags to 0 and silently dropping the message. Must return -1 instead. */ -static void -test_split_len_size_max_rejected(void) -{ - /* Pass a small real buffer; the function must NOT dereference it on the - * rejection path — len is huge but the guard must fire before any emit. */ - static uint8_t dummy[8]; - - frag_log_t log; - log.count = 0; - int r = mq_udp_msg_split(1U, 0U, dummy, SIZE_MAX, 8, collect_emit, &log); - MQ_CHECK_EQ_INT(r, -1); - MQ_CHECK_EQ_INT((long long)log.count, 0LL); -} - -MQ_TEST_MAIN({ - test_hdr_roundtrip(); - test_hdr_byte_order(); - test_hdr_decode_truncation(); - test_split_3frags(); - test_split_1frag(); - test_split_empty_payload(); - test_split_255frags_ok(); - test_split_256frags_rejected(); - test_split_mss_zero_rejected(); - test_split_len_size_max_rejected(); -}) diff --git a/tests/test_varint.c b/tests/test_varint.c deleted file mode 100644 index f3fc17dc..00000000 --- a/tests/test_varint.c +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "wire/mq_varint.h" -#include "mqtest.h" - -static void -roundtrip(uint64_t v, int expect_len) -{ - uint8_t buf[8]; - int n = mq_varint_encode(buf, sizeof buf, v); - MQ_CHECK_EQ_INT(n, expect_len); - uint64_t out = 0; - int m = mq_varint_decode(buf, (size_t)n, &out); - MQ_CHECK_EQ_INT(m, expect_len); - MQ_CHECK_EQ_INT(out, v); -} - -/* Wire-vector tests: encode and compare exact bytes from RFC 9000 §16 */ -static void -test_wire_vectors(void) -{ - uint8_t buf[8]; - - /* v=37 → 1-byte: 0x25 */ - { - uint8_t want[] = {0x25}; - MQ_CHECK_EQ_INT(mq_varint_encode(buf, sizeof buf, 37), 1); - MQ_CHECK_MEM(buf, want, 1); - } - /* v=15293 → 2-byte: 0x7B 0xBD */ - { - uint8_t want[] = {0x7B, 0xBD}; - MQ_CHECK_EQ_INT(mq_varint_encode(buf, sizeof buf, 15293), 2); - MQ_CHECK_MEM(buf, want, 2); - } - /* v=494878333 → 4-byte: 0x9D 0x7F 0x3E 0x7D */ - { - uint8_t want[] = {0x9D, 0x7F, 0x3E, 0x7D}; - MQ_CHECK_EQ_INT(mq_varint_encode(buf, sizeof buf, 494878333), 4); - MQ_CHECK_MEM(buf, want, 4); - } -} - -/* mq_varint_len boundary checks */ -static void -test_varint_len(void) -{ - MQ_CHECK_EQ_INT(mq_varint_len(63), 1); - MQ_CHECK_EQ_INT(mq_varint_len(64), 2); - MQ_CHECK_EQ_INT(mq_varint_len(16384), 4); - MQ_CHECK_EQ_INT(mq_varint_len(1073741824), 8); -} - -MQ_TEST_MAIN({ - roundtrip(0, 1); - roundtrip(63, 1); - roundtrip(64, 2); - roundtrip(16383, 2); - roundtrip(16384, 4); - roundtrip(1073741823, 4); - roundtrip(1073741824, 8); - roundtrip(4611686018427387903ULL, 8); - - uint8_t buf[8]; - MQ_CHECK_EQ_INT(mq_varint_encode(buf, 0, 5), -1); - uint64_t out; - MQ_CHECK_EQ_INT(mq_varint_decode(buf, 0, &out), -1); - uint8_t two[1] = {0x40}; - MQ_CHECK_EQ_INT(mq_varint_decode(two, 1, &out), -1); - - /* mq_varint_len boundary assertions (RFC 9000 §16) */ - test_varint_len(); - - /* Wire-vector assertions: exact bytes from RFC 9000 §16 examples */ - test_wire_vectors(); - - /* Out-of-range guard: values > 2^62-1 must return -1 */ - MQ_CHECK_EQ_INT(mq_varint_encode(buf, sizeof buf, 0x4000000000000000ULL), -1); -}) diff --git a/tests/test_wire.c b/tests/test_wire.c deleted file mode 100644 index 0c7793db..00000000 --- a/tests/test_wire.c +++ /dev/null @@ -1,710 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2026 mp0rta - -#include "wire/mq_wire.h" -#include "wire/mq_varint.h" -#include "mqtest.h" - -/* ---- AUTH_REQUEST round-trip ---- */ -static void -test_auth_req_roundtrip(void) -{ - uint8_t buf[512]; - mq_auth_req_t in; - memset(&in, 0, sizeof in); - in.version = 1; - strcpy(in.client_id, "client-abc"); - strcpy(in.auth_token, "secrettoken-1234567890"); - in.features = 0xDEADBEEF; - - int n = mq_encode_auth_req(buf, sizeof buf, &in); - MQ_CHECK(n > 0); - - mq_auth_req_t out; - memset(&out, 0, sizeof out); - int m = mq_decode_auth_req(buf, (size_t)n, &out); - MQ_CHECK_EQ_INT(m, n); - MQ_CHECK_EQ_INT(out.version, in.version); - MQ_CHECK(strcmp(out.client_id, in.client_id) == 0); - MQ_CHECK(strcmp(out.auth_token, in.auth_token) == 0); - MQ_CHECK_EQ_INT(out.features, in.features); -} - -/* ---- AUTH_RESPONSE round-trip for all error codes ---- */ -static void -auth_resp_roundtrip_one(mq_status_t status, mq_auth_err_t err) -{ - uint8_t buf[512]; - mq_auth_resp_t in; - memset(&in, 0, sizeof in); - in.status = status; - in.error_code = err; - strcpy(in.server_id, "server-xyz"); - in.features = 0x12345; - - int n = mq_encode_auth_resp(buf, sizeof buf, &in); - MQ_CHECK(n > 0); - - mq_auth_resp_t out; - memset(&out, 0, sizeof out); - int m = mq_decode_auth_resp(buf, (size_t)n, &out); - MQ_CHECK_EQ_INT(m, n); - MQ_CHECK_EQ_INT(out.status, in.status); - MQ_CHECK_EQ_INT(out.error_code, in.error_code); - MQ_CHECK(strcmp(out.server_id, in.server_id) == 0); - MQ_CHECK_EQ_INT(out.features, in.features); -} - -static void -test_auth_resp_roundtrip(void) -{ - auth_resp_roundtrip_one(MQ_STATUS_OK, MQ_AUTH_OK); - auth_resp_roundtrip_one(MQ_STATUS_ERROR, MQ_AUTH_FAILED); - auth_resp_roundtrip_one(MQ_STATUS_ERROR, MQ_AUTH_TOKEN_EXPIRED); - auth_resp_roundtrip_one(MQ_STATUS_ERROR, MQ_AUTH_POLICY_DENIED); -} - -/* ---- CONNECT_TCP_REQUEST round-trip, all 3 address types ---- */ -static void -connect_tcp_req_roundtrip(mq_addr_type_t at, const uint8_t *host, size_t host_len) -{ - uint8_t buf[512]; - mq_connect_tcp_req_t in; - memset(&in, 0, sizeof in); - in.flags = 0xAA55; - in.address_type = at; - memcpy(in.host, host, host_len); - in.host_len = host_len; - in.port = 8443; - - int n = mq_encode_connect_tcp_req(buf, sizeof buf, &in); - MQ_CHECK(n > 0); - - mq_connect_tcp_req_t out; - memset(&out, 0, sizeof out); - int m = mq_decode_connect_tcp_req(buf, (size_t)n, &out); - MQ_CHECK_EQ_INT(m, n); - MQ_CHECK_EQ_INT(out.flags, in.flags); - MQ_CHECK_EQ_INT(out.address_type, in.address_type); - MQ_CHECK_EQ_INT(out.host_len, in.host_len); - MQ_CHECK_MEM(out.host, in.host, host_len); - MQ_CHECK_EQ_INT(out.port, in.port); -} - -static void -test_connect_tcp_req_roundtrip(void) -{ - uint8_t v4[4] = {192, 168, 0, 1}; - connect_tcp_req_roundtrip(MQ_ADDR_IPV4, v4, sizeof v4); - - const char *dom = "example.com"; - connect_tcp_req_roundtrip(MQ_ADDR_DOMAIN, (const uint8_t *)dom, strlen(dom)); - - uint8_t v6[16] = {0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}; - connect_tcp_req_roundtrip(MQ_ADDR_IPV6, v6, sizeof v6); -} - -/* ---- CONNECT_TCP_RESPONSE round-trip ---- */ -static void -connect_tcp_resp_roundtrip(mq_status_t status, mq_tcp_err_t err, const char *msg) -{ - uint8_t buf[512]; - mq_connect_tcp_resp_t in; - memset(&in, 0, sizeof in); - in.status = status; - in.error_code = err; - strcpy(in.message, msg); - in.message_len = strlen(msg); - - int n = mq_encode_connect_tcp_resp(buf, sizeof buf, &in); - MQ_CHECK(n > 0); - - mq_connect_tcp_resp_t out; - memset(&out, 0, sizeof out); - int m = mq_decode_connect_tcp_resp(buf, (size_t)n, &out); - MQ_CHECK_EQ_INT(m, n); - MQ_CHECK_EQ_INT(out.status, in.status); - MQ_CHECK_EQ_INT(out.error_code, in.error_code); - MQ_CHECK_EQ_INT(out.message_len, in.message_len); - MQ_CHECK(strcmp(out.message, in.message) == 0); -} - -static void -test_connect_tcp_resp_roundtrip(void) -{ - connect_tcp_resp_roundtrip(MQ_STATUS_OK, MQ_TCP_OK, "connected"); - connect_tcp_resp_roundtrip(MQ_STATUS_ERROR, MQ_TCP_DNS_FAILED, "dns lookup failed"); - connect_tcp_resp_roundtrip(MQ_STATUS_ERROR, MQ_TCP_CONN_REFUSED, "refused"); - connect_tcp_resp_roundtrip(MQ_STATUS_ERROR, MQ_TCP_TIMEOUT, "timed out"); - connect_tcp_resp_roundtrip(MQ_STATUS_ERROR, MQ_TCP_POLICY_DENIED, "denied by policy"); -} - -/* ---- Malformed: truncation ---- */ -static void -test_truncation(void) -{ - uint8_t buf[512]; - mq_auth_req_t in; - memset(&in, 0, sizeof in); - in.version = 1; - strcpy(in.client_id, "abc"); - strcpy(in.auth_token, "token"); - in.features = 7; - int n = mq_encode_auth_req(buf, sizeof buf, &in); - MQ_CHECK(n > 0); - - mq_auth_req_t out; - /* every strictly shorter prefix must fail */ - for (int k = 0; k < n; k++) { - MQ_CHECK_EQ_INT(mq_decode_auth_req(buf, (size_t)k, &out), -1); - } -} - -/* ---- Malformed: host length field > MQ_MAX_HOST ---- */ -static void -test_host_too_long(void) -{ - /* hand-craft CONNECT_TCP_REQUEST with declared host length 256 (>255). */ - uint8_t buf[64]; - size_t off = 0; - int r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* flags */ - off += (size_t)r; - buf[off++] = MQ_ADDR_DOMAIN; /* address_type */ - r = mq_varint_encode(buf + off, sizeof buf - off, 256); /* host length 256 */ - off += (size_t)r; - /* no need to supply all the bytes; decoder must reject on the length cap */ - - mq_connect_tcp_req_t out; - MQ_CHECK_EQ_INT(mq_decode_connect_tcp_req(buf, off, &out), -1); -} - -/* ---- Malformed: invalid address_type 0x02 ---- */ -static void -test_bad_addr_type(void) -{ - uint8_t buf[64]; - size_t off = 0; - int r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* flags */ - off += (size_t)r; - buf[off++] = 0x02; /* invalid address_type */ - r = mq_varint_encode(buf + off, sizeof buf - off, 4); /* host length */ - off += (size_t)r; - buf[off++] = 10; - buf[off++] = 0; - buf[off++] = 0; - buf[off++] = 1; - buf[off++] = 0x01; /* port hi */ - buf[off++] = 0xBB; /* port lo */ - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* padding_length */ - off += (size_t)r; - - mq_connect_tcp_req_t out; - MQ_CHECK_EQ_INT(mq_decode_connect_tcp_req(buf, off, &out), -1); -} - -/* ---- Forward-compat: non-zero padding_length + trailing pad bytes ---- */ -static void -test_padding_skipped(void) -{ - uint8_t buf[64]; - size_t off = 0; - int r = mq_varint_encode(buf + off, sizeof buf - off, 0xAA55); /* flags */ - off += (size_t)r; - buf[off++] = MQ_ADDR_IPV4; - r = mq_varint_encode(buf + off, sizeof buf - off, 4); /* host len */ - off += (size_t)r; - buf[off++] = 192; - buf[off++] = 168; - buf[off++] = 0; - buf[off++] = 1; - buf[off++] = 0x20; /* port 8443 hi */ - buf[off++] = 0xFB; /* port 8443 lo */ - r = mq_varint_encode(buf + off, sizeof buf - off, 5); /* padding_length = 5 */ - off += (size_t)r; - for (int i = 0; i < 5; i++) - buf[off++] = 0xEE; /* pad bytes */ - - mq_connect_tcp_req_t out; - memset(&out, 0, sizeof out); - int m = mq_decode_connect_tcp_req(buf, off, &out); - MQ_CHECK_EQ_INT(m, (int)off); /* consumes everything incl. padding */ - MQ_CHECK_EQ_INT(out.flags, 0xAA55); - MQ_CHECK_EQ_INT(out.address_type, MQ_ADDR_IPV4); - MQ_CHECK_EQ_INT(out.host_len, 4); - MQ_CHECK_EQ_INT(out.port, 8443); - uint8_t want[4] = {192, 168, 0, 1}; - MQ_CHECK_MEM(out.host, want, 4); -} - -/* ---- Forward-compat: padding_length overflowing buffer is rejected ---- */ -static void -test_padding_overflow(void) -{ - uint8_t buf[64]; - size_t off = 0; - int r = mq_varint_encode(buf + off, sizeof buf - off, 0); - off += (size_t)r; - buf[off++] = MQ_ADDR_IPV4; - r = mq_varint_encode(buf + off, sizeof buf - off, 4); - off += (size_t)r; - buf[off++] = 1; - buf[off++] = 2; - buf[off++] = 3; - buf[off++] = 4; - buf[off++] = 0x00; - buf[off++] = 0x50; - r = mq_varint_encode(buf + off, sizeof buf - off, 99); /* claims 99 pad bytes */ - off += (size_t)r; - /* but supply none */ - - mq_connect_tcp_req_t out; - MQ_CHECK_EQ_INT(mq_decode_connect_tcp_req(buf, off, &out), -1); -} - -/* ---- UDP_SESSION_OPEN round-trip, all 3 address types ---- */ -static void -udp_session_open_roundtrip(mq_addr_type_t at, const uint8_t *host, size_t host_len) -{ - uint8_t buf[512]; - mq_udp_session_open_t in; - memset(&in, 0, sizeof in); - in.session_id = 0xDEAD1234U; - in.flags = 0; - in.address_type = at; - memcpy(in.host, host, host_len); - in.host_len = host_len; - in.port = 5353; - in.idle_timeout_ms = 30000; - - int n = mq_encode_udp_session_open(buf, sizeof buf, &in); - MQ_CHECK(n > 0); - - mq_udp_session_open_t out; - memset(&out, 0, sizeof out); - int m = mq_decode_udp_session_open(buf, (size_t)n, &out); - MQ_CHECK_EQ_INT(m, n); - MQ_CHECK_EQ_INT((long long)out.session_id, (long long)in.session_id); - MQ_CHECK_EQ_INT((long long)out.flags, (long long)in.flags); - MQ_CHECK_EQ_INT(out.address_type, in.address_type); - MQ_CHECK_EQ_INT((long long)out.host_len, (long long)in.host_len); - MQ_CHECK_MEM(out.host, in.host, host_len); - MQ_CHECK_EQ_INT(out.port, in.port); - MQ_CHECK_EQ_INT((long long)out.idle_timeout_ms, (long long)in.idle_timeout_ms); -} - -static void -test_udp_session_open_roundtrip(void) -{ - uint8_t v4[4] = {8, 8, 8, 8}; - udp_session_open_roundtrip(MQ_ADDR_IPV4, v4, sizeof v4); - - const char *dom = "dns.example.com"; - udp_session_open_roundtrip(MQ_ADDR_DOMAIN, (const uint8_t *)dom, strlen(dom)); - - uint8_t v6[16] = {0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}; - udp_session_open_roundtrip(MQ_ADDR_IPV6, v6, sizeof v6); - - /* idle_timeout_ms = 0 (server default) */ - uint8_t v4b[4] = {1, 2, 3, 4}; - mq_udp_session_open_t in; - memset(&in, 0, sizeof in); - in.session_id = 1; - in.address_type = MQ_ADDR_IPV4; - memcpy(in.host, v4b, 4); - in.host_len = 4; - in.port = 53; - in.idle_timeout_ms = 0; - uint8_t buf[512]; - int n = mq_encode_udp_session_open(buf, sizeof buf, &in); - MQ_CHECK(n > 0); - mq_udp_session_open_t out; - memset(&out, 0, sizeof out); - int m = mq_decode_udp_session_open(buf, (size_t)n, &out); - MQ_CHECK_EQ_INT(m, n); - MQ_CHECK_EQ_INT((long long)out.idle_timeout_ms, 0LL); -} - -/* ---- UDP_SESSION_RESP round-trip, including non-empty message ---- */ -static void -udp_session_resp_roundtrip(mq_status_t status, mq_udp_err_t err, const char *msg, - uint64_t idle_ms) -{ - uint8_t buf[512]; - mq_udp_session_resp_t in; - memset(&in, 0, sizeof in); - in.status = status; - in.error_code = err; - in.message_len = strlen(msg); - memcpy(in.message, msg, in.message_len); - in.idle_timeout_ms = idle_ms; - - int n = mq_encode_udp_session_resp(buf, sizeof buf, &in); - MQ_CHECK(n > 0); - - mq_udp_session_resp_t out; - memset(&out, 0, sizeof out); - int m = mq_decode_udp_session_resp(buf, (size_t)n, &out); - MQ_CHECK_EQ_INT(m, n); - MQ_CHECK_EQ_INT(out.status, in.status); - MQ_CHECK_EQ_INT(out.error_code, in.error_code); - MQ_CHECK_EQ_INT((long long)out.message_len, (long long)in.message_len); - MQ_CHECK_MEM(out.message, in.message, in.message_len); - MQ_CHECK_EQ_INT((long long)out.idle_timeout_ms, (long long)in.idle_timeout_ms); -} - -static void -test_udp_session_resp_roundtrip(void) -{ - /* empty message */ - udp_session_resp_roundtrip(MQ_STATUS_OK, MQ_UDP_OK, "", 60000); - /* non-empty message — fixes variable-length string codec */ - udp_session_resp_roundtrip(MQ_STATUS_ERROR, MQ_UDP_DNS_FAILED, - "dns lookup failed for host", 0); - udp_session_resp_roundtrip(MQ_STATUS_ERROR, MQ_UDP_SOCKET_FAILED, "socket bind error", - 0); - udp_session_resp_roundtrip(MQ_STATUS_ERROR, MQ_UDP_POLICY_DENIED, "denied by policy", - 0); - udp_session_resp_roundtrip(MQ_STATUS_ERROR, MQ_UDP_SESSION_LIMIT, - "session limit reached", 0); - /* non-empty message with OK status and server-applied timeout */ - udp_session_resp_roundtrip(MQ_STATUS_OK, MQ_UDP_OK, "udp relay ready", 45000); -} - -/* ---- Truncation: UDP_SESSION_OPEN all offsets ---- */ -static void -test_udp_session_open_truncation(void) -{ - uint8_t buf[512]; - mq_udp_session_open_t in; - memset(&in, 0, sizeof in); - in.session_id = 42; - in.flags = 0; - in.address_type = MQ_ADDR_DOMAIN; - const char *dom = "example.org"; - memcpy(in.host, dom, strlen(dom)); - in.host_len = strlen(dom); - in.port = 5353; - in.idle_timeout_ms = 10000; - - int n = mq_encode_udp_session_open(buf, sizeof buf, &in); - MQ_CHECK(n > 0); - - mq_udp_session_open_t out; - for (int k = 0; k < n; k++) { - MQ_CHECK_EQ_INT(mq_decode_udp_session_open(buf, (size_t)k, &out), -1); - } -} - -/* ---- Truncation: UDP_SESSION_RESP all offsets ---- */ -static void -test_udp_session_resp_truncation(void) -{ - uint8_t buf[512]; - mq_udp_session_resp_t in; - memset(&in, 0, sizeof in); - in.status = MQ_STATUS_ERROR; - in.error_code = MQ_UDP_DNS_FAILED; - const char *msg = "dns error"; - in.message_len = strlen(msg); - memcpy(in.message, msg, in.message_len); - in.idle_timeout_ms = 0; - - int n = mq_encode_udp_session_resp(buf, sizeof buf, &in); - MQ_CHECK(n > 0); - - mq_udp_session_resp_t out; - for (int k = 0; k < n; k++) { - MQ_CHECK_EQ_INT(mq_decode_udp_session_resp(buf, (size_t)k, &out), -1); - } -} - -/* ---- host_len > 255 rejected by encoder ---- */ -static void -test_udp_session_open_host_too_long(void) -{ - uint8_t buf[512]; - mq_udp_session_open_t in; - memset(&in, 0, sizeof in); - in.session_id = 1; - in.address_type = MQ_ADDR_DOMAIN; - in.host_len = 256; /* > MQ_MAX_HOST */ - in.port = 53; - MQ_CHECK_EQ_INT(mq_encode_udp_session_open(buf, sizeof buf, &in), -1); -} - -/* ---- host_len > 255 in wire rejected by decoder ---- */ -static void -test_udp_session_open_decode_host_too_long(void) -{ - uint8_t buf[64]; - size_t off = 0; - int r; - r = mq_varint_encode(buf + off, sizeof buf - off, 1); /* session_id */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* flags */ - off += (size_t)r; - buf[off++] = MQ_ADDR_DOMAIN; /* address_type */ - r = mq_varint_encode(buf + off, sizeof buf - off, 256); /* host len 256 */ - off += (size_t)r; - /* no host bytes needed — decoder rejects on cap */ - - mq_udp_session_open_t out; - MQ_CHECK_EQ_INT(mq_decode_udp_session_open(buf, off, &out), -1); -} - -/* ---- Strict session_id decode: session_id > UINT32_MAX is rejected ---- */ -static void -test_udp_session_open_session_id_overflow(void) -{ - /* Hand-craft an OPEN frame where session_id = 0x100000000 (UINT32_MAX+1). - * The decoder must reject this with -1 instead of silently truncating. */ - uint8_t buf[128]; - size_t off = 0; - int r; - r = mq_varint_encode(buf + off, sizeof buf - off, - 0x100000000ULL); /* session_id = UINT32_MAX + 1 */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* flags */ - off += (size_t)r; - buf[off++] = MQ_ADDR_IPV4; /* address_type */ - r = mq_varint_encode(buf + off, sizeof buf - off, 4); /* host len */ - off += (size_t)r; - buf[off++] = 1; - buf[off++] = 2; - buf[off++] = 3; - buf[off++] = 4; - buf[off++] = 0x00; - buf[off++] = 0x50; /* port 80 */ - r = mq_varint_encode(buf + off, sizeof buf - off, 5000); /* idle_timeout_ms */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* padding_length */ - off += (size_t)r; - - mq_udp_session_open_t out; - MQ_CHECK_EQ_INT(mq_decode_udp_session_open(buf, off, &out), -1); -} - -/* ---- Malformed: invalid address_type in UDP_SESSION_OPEN is rejected ---- */ -static void -test_udp_session_open_bad_addr_type(void) -{ - uint8_t buf[64]; - size_t off = 0; - int r; - r = mq_varint_encode(buf + off, sizeof buf - off, 1); /* session_id */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* flags */ - off += (size_t)r; - buf[off++] = 0x05; /* invalid address_type */ - r = mq_varint_encode(buf + off, sizeof buf - off, 4); /* host len */ - off += (size_t)r; - buf[off++] = 10; - buf[off++] = 0; - buf[off++] = 0; - buf[off++] = 1; - buf[off++] = 0x01; /* port hi */ - buf[off++] = 0xBB; /* port lo */ - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* idle_timeout_ms */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* padding_length */ - off += (size_t)r; - - mq_udp_session_open_t out; - MQ_CHECK_EQ_INT(mq_decode_udp_session_open(buf, off, &out), -1); -} - -/* ---- Padding skip: UDP_SESSION_OPEN decoder accepts non-zero padding ---- */ -static void -test_udp_session_open_padding_skipped(void) -{ - /* Hand-craft an OPEN frame with padding_length = 3 */ - uint8_t buf[128]; - size_t off = 0; - int r; - r = mq_varint_encode(buf + off, sizeof buf - off, 7); /* session_id */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* flags */ - off += (size_t)r; - buf[off++] = MQ_ADDR_IPV4; /* address_type */ - r = mq_varint_encode(buf + off, sizeof buf - off, 4); /* host len */ - off += (size_t)r; - buf[off++] = 1; - buf[off++] = 2; - buf[off++] = 3; - buf[off++] = 4; - buf[off++] = 0x00; - buf[off++] = 0x35; /* port 53 */ - r = mq_varint_encode(buf + off, sizeof buf - off, 5000); /* idle_timeout_ms */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 3); /* padding_length = 3 */ - off += (size_t)r; - buf[off++] = 0xAA; - buf[off++] = 0xBB; - buf[off++] = 0xCC; - - mq_udp_session_open_t out; - memset(&out, 0, sizeof out); - int m = mq_decode_udp_session_open(buf, off, &out); - MQ_CHECK_EQ_INT(m, (int)off); - MQ_CHECK_EQ_INT((long long)out.session_id, 7LL); - MQ_CHECK_EQ_INT(out.address_type, MQ_ADDR_IPV4); - MQ_CHECK_EQ_INT(out.port, 53); - MQ_CHECK_EQ_INT((long long)out.idle_timeout_ms, 5000LL); -} - -/* ---- Padding skip: UDP_SESSION_RESP decoder accepts non-zero padding ---- */ -static void -test_udp_session_resp_padding_skipped(void) -{ - uint8_t buf[128]; - size_t off = 0; - int r; - buf[off++] = (uint8_t)MQ_STATUS_OK; /* status */ - r = mq_varint_encode(buf + off, sizeof buf - off, MQ_UDP_OK); /* error_code */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 2); /* msg len = 2 */ - off += (size_t)r; - buf[off++] = 'O'; - buf[off++] = 'K'; - r = mq_varint_encode(buf + off, sizeof buf - off, 60000); /* idle_timeout_ms */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 4); /* padding_length = 4 */ - off += (size_t)r; - buf[off++] = 0; - buf[off++] = 0; - buf[off++] = 0; - buf[off++] = 0; - - mq_udp_session_resp_t out; - memset(&out, 0, sizeof out); - int m = mq_decode_udp_session_resp(buf, off, &out); - MQ_CHECK_EQ_INT(m, (int)off); - MQ_CHECK_EQ_INT(out.status, MQ_STATUS_OK); - MQ_CHECK_EQ_INT(out.error_code, MQ_UDP_OK); - MQ_CHECK_EQ_INT((long long)out.message_len, 2LL); - MQ_CHECK_EQ_INT((long long)out.idle_timeout_ms, 60000LL); -} - -/* ---- Boundary: wire leak prevention ---- */ - -/* Case 1: encode with error_code == MQ_UDP_CLOSED must return -1 */ -static void -test_udp_resp_encode_closed_rejected(void) -{ - uint8_t buf[512]; - mq_udp_session_resp_t in; - memset(&in, 0, sizeof in); - in.status = MQ_STATUS_ERROR; - in.error_code = MQ_UDP_CLOSED; /* must not go on wire */ - MQ_CHECK_EQ_INT(mq_encode_udp_session_resp(buf, sizeof buf, &in), -1); -} - -/* Case 2: decode with wire error_code >= 5 must return -1 */ -static void -test_udp_resp_decode_error_code_oor(void) -{ - uint8_t buf[128]; - size_t off = 0; - int r; - buf[off++] = (uint8_t)MQ_STATUS_ERROR; - r = mq_varint_encode(buf + off, sizeof buf - off, - 5); /* error_code = 5, out of range */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* msg len 0 */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* idle_timeout_ms 0 */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* padding_length 0 */ - off += (size_t)r; - - mq_udp_session_resp_t out; - MQ_CHECK_EQ_INT(mq_decode_udp_session_resp(buf, off, &out), -1); -} - -/* Case 3a: status/error_code inconsistency — MQ_STATUS_OK + error != OK */ -static void -test_udp_resp_encode_status_mismatch_ok_with_error(void) -{ - uint8_t buf[512]; - mq_udp_session_resp_t in; - memset(&in, 0, sizeof in); - in.status = MQ_STATUS_OK; - in.error_code = MQ_UDP_DNS_FAILED; /* mismatch: OK + non-zero error */ - MQ_CHECK_EQ_INT(mq_encode_udp_session_resp(buf, sizeof buf, &in), -1); -} - -/* Case 3b: MQ_STATUS_ERROR + MQ_UDP_OK mismatch */ -static void -test_udp_resp_encode_status_mismatch_error_with_ok(void) -{ - uint8_t buf[512]; - mq_udp_session_resp_t in; - memset(&in, 0, sizeof in); - in.status = MQ_STATUS_ERROR; - in.error_code = MQ_UDP_OK; /* mismatch: ERROR + ok error_code */ - MQ_CHECK_EQ_INT(mq_encode_udp_session_resp(buf, sizeof buf, &in), -1); -} - -/* Case 3c: decode mismatch — MQ_STATUS_OK + error != OK on wire */ -static void -test_udp_resp_decode_status_mismatch(void) -{ - uint8_t buf[128]; - size_t off = 0; - int r; - - /* OK status but error_code = 1 */ - buf[off++] = (uint8_t)MQ_STATUS_OK; - r = mq_varint_encode(buf + off, sizeof buf - off, 1); /* MQ_UDP_DNS_FAILED */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* msg len 0 */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* idle_timeout_ms 0 */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* padding_length 0 */ - off += (size_t)r; - - mq_udp_session_resp_t out; - MQ_CHECK_EQ_INT(mq_decode_udp_session_resp(buf, off, &out), -1); - - /* ERROR status but error_code = 0 */ - off = 0; - buf[off++] = (uint8_t)MQ_STATUS_ERROR; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* MQ_UDP_OK */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* msg len 0 */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* idle_timeout_ms 0 */ - off += (size_t)r; - r = mq_varint_encode(buf + off, sizeof buf - off, 0); /* padding_length 0 */ - off += (size_t)r; - - MQ_CHECK_EQ_INT(mq_decode_udp_session_resp(buf, off, &out), -1); -} - -MQ_TEST_MAIN({ - test_auth_req_roundtrip(); - test_auth_resp_roundtrip(); - test_connect_tcp_req_roundtrip(); - test_connect_tcp_resp_roundtrip(); - test_truncation(); - test_host_too_long(); - test_bad_addr_type(); - test_padding_skipped(); - test_padding_overflow(); - test_udp_session_open_roundtrip(); - test_udp_session_resp_roundtrip(); - test_udp_session_open_truncation(); - test_udp_session_resp_truncation(); - test_udp_session_open_host_too_long(); - test_udp_session_open_decode_host_too_long(); - test_udp_session_open_session_id_overflow(); - test_udp_session_open_bad_addr_type(); - test_udp_session_open_padding_skipped(); - test_udp_session_resp_padding_skipped(); - test_udp_resp_encode_closed_rejected(); - test_udp_resp_decode_error_code_oor(); - test_udp_resp_encode_status_mismatch_ok_with_error(); - test_udp_resp_encode_status_mismatch_error_with_ok(); - test_udp_resp_decode_status_mismatch(); -}) diff --git a/third_party/nghttp2.LICENSE b/third_party/nghttp2.LICENSE deleted file mode 100644 index 80201792..00000000 --- a/third_party/nghttp2.LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -The MIT License - -Copyright (c) 2012, 2014, 2015, 2016 Tatsuhiro Tsujikawa -Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/website/guide/building.md b/website/guide/building.md index ad6b605a..18964f10 100644 --- a/website/guide/building.md +++ b/website/guide/building.md @@ -1,37 +1,17 @@ -# Building from Source +# Building from source -**Requirements:** `cmake`, `make`, a C11 compiler, `git`, the `openssl` CLI (used at configure time to generate the bundled test certificates), `libevent`, `libcurl` dev headers (e.g. `libcurl4-openssl-dev` — the gateway's origin client), `libnghttp2` dev headers (`libnghttp2-dev` — HTTP/2 termination for the TLS MITM L7 path; the static binary additionally needs the bundled `libnghttp2.a`), and `golang` (BoringSSL's build needs Go). Network access is required on first build (BoringSSL is cloned). - -On Debian/Ubuntu: +Install Rust with rustup; `rust-toolchain.toml` pins the supported toolchain. +Native dependencies are a C/C++ toolchain, CMake, Go and Git for the vendored +xquic/BoringSSL build. Cargo builds and statically links them automatically. ```bash -sudo apt-get install -y build-essential cmake git openssl \ - libevent-dev libcurl4-openssl-dev libnghttp2-dev golang-go -``` - -::: tip Runtime packaging dependency -The dynamically-linked binary depends on the nghttp2 shared library (Debian/Ubuntu package `libnghttp2-14`). The `-DMQPROXY_STATIC_XQUIC=ON` packaging binary instead statically links `libnghttp2.a` and so carries no runtime nghttp2 dependency. -::: - -## Build steps - -```bash -# 1. Clone with the xquic submodule +sudo apt-get install -y build-essential cmake git golang-go git clone --recursive https://github.com/mp0rta/mqproxy.git cd mqproxy -# (if you cloned without --recursive: git submodule update --init --recursive) - -# 2. Build the in-tree xquic (BoringSSL + xquic, with qlog enabled) -# Produces third_party/xquic/build/libxquic.so. Pins BoringSSL to a known commit. -./scripts/build-xquic.sh - -# 3. Configure + build mqproxy against that xquic -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" -cmake --build build -j"$(nproc)" - -# The binary is at build/mqproxy -./build/mqproxy --help +git submodule update --init --recursive +cargo build --release --locked -p mqproxy +./target/release/mqproxy --help ``` -Once built, head to the [Quick Start](./quick-start) to run a server/client pair locally. To run mqproxy as a managed service, see [Installation](./installation). +The application is Rust. HTTP/3 uses h3wire; origin HTTP/1.1 and HTTP/2 use +hyper and rustls. xquic and its nested BoringSSL remain pinned submodules. diff --git a/website/guide/http-gateway.md b/website/guide/http-gateway.md index 9c6628a8..d1c271a0 100644 --- a/website/guide/http-gateway.md +++ b/website/guide/http-gateway.md @@ -29,7 +29,7 @@ The server also pools and reuses origin connections across requests. The gateway is enabled on the server by default. On the client, add `--gateway` (works with or without `--socks5`): ```bash -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --gateway 127.0.0.1:8080 diff --git a/website/guide/installation.md b/website/guide/installation.md index 046c0161..a1a336ce 100644 --- a/website/guide/installation.md +++ b/website/guide/installation.md @@ -4,11 +4,11 @@ mqproxy ships systemd template units so each instance runs as a hardened, unpriv ## Install from a `.deb` -Prebuilt `amd64` and `arm64` packages are attached to each [GitHub Release](https://github.com/mp0rta/mqproxy/releases). The binary is self-contained (xquic + BoringSSL + nghttp2 statically linked), so it depends only on the system libevent/libcurl. +Prebuilt `amd64` and `arm64` packages are attached to each [GitHub Release](https://github.com/mp0rta/mqproxy/releases). The Rust binary statically links xquic, BoringSSL and its Rust dependencies; system runtime dependencies are recorded in the package metadata. ```bash # pick the .deb for your architecture from the latest release -sudo dpkg -i mqproxy__amd64.deb # or _arm64.deb +sudo apt install ./mqproxy__amd64.deb # or _arm64.deb ``` The package installs `/usr/bin/mqproxy`, the `mqproxy-server@` / `mqproxy-client@` systemd template units, and creates the unprivileged `mqproxy` user plus `/etc/mqproxy` (via the bundled `sysusers.d`/`tmpfiles.d`, applied in the package's `postinst`). Continue from the per-instance config steps below — the configure/enable steps are identical; only the build-from-source steps are skipped. @@ -18,13 +18,9 @@ The package installs `/usr/bin/mqproxy`, the `mqproxy-server@` / `mqproxy-client Build a self-contained binary (xquic + BoringSSL statically linked, so the installed binary has no non-standard runtime deps) and install it: ```bash -# -DMQPROXY_STATIC_XQUIC statically links xquic+BoringSSL; the install prefix is -# baked into the unit ExecStart at *configure* time, so set it now (not at --install). -cmake -S . -B build \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" \ - -DMQPROXY_STATIC_XQUIC=ON -DCMAKE_INSTALL_PREFIX=/usr -cmake --build build --target mqproxy_cli -j -sudo cmake --install build # → /usr/bin/mqproxy, units, sysusers.d, tmpfiles.d +sudo apt-get install -y dpkg-dev python3 +python3 packaging/build.py +sudo apt install ./target/dist/mqproxy_*.deb ``` Create the `mqproxy` system user and its directories (declared by the bundled `sysusers.d`/`tmpfiles.d`): diff --git a/website/guide/quick-start.md b/website/guide/quick-start.md index 876d8c44..07316452 100644 --- a/website/guide/quick-start.md +++ b/website/guide/quick-start.md @@ -5,11 +5,11 @@ Run a server and a client locally, then send TCP traffic through the client's SO ```bash # Server — listens for MPQUIC on UDP :4433. --cert/--key are required; # the repo ships a self-signed test cert under tests/certs for local use. -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ --cert tests/certs/test.crt --key tests/certs/test.key # Client — connects to the server, exposes a local SOCKS5 listener on :1080. -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 \ --token secret123 \ --socks5 127.0.0.1:1080 @@ -29,7 +29,7 @@ The test certificate under `tests/certs` is for local testing only. For real dep Bind additional local IPs as MPQUIC paths (e.g. WiFi + LTE) by repeating `--path`; the stream is then aggregated across them: ```bash -./build/mqproxy client \ +./target/release/mqproxy client \ --server :4433 --token secret123 \ --socks5 127.0.0.1:1080 \ --path 192.168.1.50 \ diff --git a/website/guide/tcp-proxy.md b/website/guide/tcp-proxy.md index 56d84aff..d764ad14 100644 --- a/website/guide/tcp-proxy.md +++ b/website/guide/tcp-proxy.md @@ -26,11 +26,11 @@ The client exposes the TCP proxy through either (or both) of: ```bash # Server — listens for MPQUIC on UDP :4433. --cert/--key are required; # the repo ships a self-signed test cert under tests/certs for local use. -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ --cert tests/certs/test.crt --key tests/certs/test.key # Client — connects to the server, exposes a local SOCKS5 listener on :1080. -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 \ --token secret123 \ --socks5 127.0.0.1:1080 @@ -45,7 +45,7 @@ curl --socks5-hostname 127.0.0.1:1080 https://example.com/ Or expose an HTTP CONNECT ingress and use it as a proxy: ```bash -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --http-connect 127.0.0.1:3128 @@ -57,7 +57,7 @@ curl --proxy http://127.0.0.1:3128 https://example.com/ Repeat `--path` to bind additional local IPs as MPQUIC paths; each TCP flow's stream is then aggregated across them: ```bash -./build/mqproxy client \ +./target/release/mqproxy client \ --server :4433 --token secret123 \ --socks5 127.0.0.1:1080 \ --path 192.168.1.50 \ diff --git a/website/guide/tls-mitm.md b/website/guide/tls-mitm.md index a2d72f80..fae935d2 100644 --- a/website/guide/tls-mitm.md +++ b/website/guide/tls-mitm.md @@ -48,11 +48,11 @@ openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ ```bash # Server — unchanged; the gateway origin bridge does the origin fetch. -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ --cert /etc/mqproxy/tls/server.pem --key /etc/mqproxy/tls/server.key # Client — transparent capture + MITM. Requires --tproxy and a signing CA. -sudo ./build/mqproxy client \ +sudo ./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --tproxy 127.0.0.1:12443 --setup-redirect \ --mitm \ diff --git a/website/guide/transparent-capture.md b/website/guide/transparent-capture.md index 3c1b32f1..dfa93080 100644 --- a/website/guide/transparent-capture.md +++ b/website/guide/transparent-capture.md @@ -15,10 +15,10 @@ Transparent capture installs kernel firewall rules, so it needs root or `CAP_NET ```bash # Server — no change from the regular config. -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 # Client — transparent listener on :12443; self-installs nft rules (needs root). -sudo ./build/mqproxy client \ +sudo ./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --tproxy 127.0.0.1:12443 \ --setup-redirect @@ -36,7 +36,7 @@ curl https://example.com/ When the router stack already owns the firewall and policy-routing rules (any setup that places a `TPROXY` target in `PREROUTING` and marks the packets), leave `--setup-redirect` OFF and let mqproxy just provide the listener — match its `--tproxy-fwmark`/`--tproxy-table` to whatever the rules use: ```bash -sudo ./build/mqproxy client \ +sudo ./target/release/mqproxy client \ --server :4433 --token secret123 \ --tproxy 0.0.0.0:12443 \ --tproxy-mode tproxy \ diff --git a/website/guide/udp-relay.md b/website/guide/udp-relay.md index 9351bc30..a7823e6a 100644 --- a/website/guide/udp-relay.md +++ b/website/guide/udp-relay.md @@ -25,11 +25,11 @@ UDP relay is exposed on the **same `--socks5` listener** — any SOCKS5 client t ```bash # Server (UDP relay on by default; 30s idle timeout shown) -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ --udp-idle-timeout 30 # Client — the SOCKS5 listener handles both TCP and UDP -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --socks5 127.0.0.1:1080 ``` @@ -38,5 +38,5 @@ UDP relay is exposed on the **same `--socks5` listener** — any SOCKS5 client t ```bash # Relay a UDP packet to a target through the client's SOCKS5 listener -./build/udpsocks --proxy 127.0.0.1:1080 --target 8.8.8.8:53 --send 32 --count 1 +./target/release/examples/udpsocks --proxy 127.0.0.1:1080 --target 8.8.8.8:53 --send 32 --count 1 ``` diff --git a/website/ja/guide/building.md b/website/ja/guide/building.md index 9d80d5ac..9d1e5cc3 100644 --- a/website/ja/guide/building.md +++ b/website/ja/guide/building.md @@ -1,37 +1,17 @@ # ソースからのビルド -**必要なもの:** `cmake`、`make`、C11 コンパイラ、`git`、`openssl` CLI(同梱のテスト証明書を configure 時に生成するために使用)、`libevent`、`libcurl` の開発ヘッダ(例: `libcurl4-openssl-dev` — ゲートウェイのオリジンクライアント)、`libnghttp2` の開発ヘッダ(`libnghttp2-dev` — TLS MITM L7 経路の HTTP/2 終端用。静的バイナリにはさらに同梱の `libnghttp2.a` が必要)、そして `golang`(BoringSSL のビルドに Go が必要)。初回ビルド時はネットワークアクセスが必要です(BoringSSL がクローンされます)。 - -Debian/Ubuntu の場合: +Rust は rustup で導入してください。`rust-toolchain.toml` でバージョンを固定しています。 +xquic と BoringSSL のために C/C++ コンパイラ、CMake、Go が必要です。 +Cargo が両ライブラリをビルドして静的リンクします。 ```bash -sudo apt-get install -y build-essential cmake git openssl \ - libevent-dev libcurl4-openssl-dev libnghttp2-dev golang-go -``` - -::: tip ランタイムのパッケージ依存 -動的リンクされたバイナリは nghttp2 共有ライブラリ(Debian/Ubuntu パッケージ `libnghttp2-14`)に依存します。`-DMQPROXY_STATIC_XQUIC=ON` のパッケージングバイナリは代わりに `libnghttp2.a` を静的リンクするため、nghttp2 のランタイム依存を持ちません。 -::: - -## ビルド手順 - -```bash -# 1. xquic サブモジュールごとクローン +sudo apt-get install -y build-essential cmake git golang-go git clone --recursive https://github.com/mp0rta/mqproxy.git cd mqproxy -# (--recursive なしでクローンした場合: git submodule update --init --recursive) - -# 2. ツリー内の xquic をビルド (BoringSSL + xquic、qlog 有効) -# third_party/xquic/build/libxquic.so を生成。BoringSSL を既知のコミットに固定。 -./scripts/build-xquic.sh - -# 3. その xquic に対して mqproxy を configure + ビルド -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" -cmake --build build -j"$(nproc)" - -# バイナリは build/mqproxy にあります -./build/mqproxy --help +git submodule update --init --recursive +cargo build --release --locked -p mqproxy +./target/release/mqproxy --help ``` -ビルドが完了したら、[クイックスタート](./quick-start) でサーバー/クライアントのペアをローカルで動かしてください。mqproxy をマネージドサービスとして動かすには [インストール](./installation) を参照してください。 +アプリケーションは Rust 製です。HTTP/3 は h3wire、オリジンの HTTP/1.1・HTTP/2 は +hyper と rustls を使います。xquic とその配下の BoringSSL は固定した submodule として保持します。 diff --git a/website/ja/guide/http-gateway.md b/website/ja/guide/http-gateway.md index 88245dc8..6baeb3a8 100644 --- a/website/ja/guide/http-gateway.md +++ b/website/ja/guide/http-gateway.md @@ -29,7 +29,7 @@ mqproxy-client ──────── MPQUIC 上の HTTP/3 (ALPN h3) ─── ゲートウェイはサーバー側でデフォルト有効です。クライアント側では `--gateway` を追加します(`--socks5` の有無を問わず動作)。 ```bash -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --gateway 127.0.0.1:8080 diff --git a/website/ja/guide/installation.md b/website/ja/guide/installation.md index 9d4802f9..bf1e1e3e 100644 --- a/website/ja/guide/installation.md +++ b/website/ja/guide/installation.md @@ -4,11 +4,11 @@ mqproxy は systemd のテンプレートユニットを同梱しており、各 ## `.deb` からインストール -ビルド済みの `amd64` および `arm64` パッケージが各 [GitHub Release](https://github.com/mp0rta/mqproxy/releases) に添付されています。バイナリは自己完結(xquic + BoringSSL + nghttp2 を静的リンク)なので、依存するのはシステムの libevent/libcurl のみです。 +ビルド済みの `amd64` および `arm64` パッケージが各 [GitHub Release](https://github.com/mp0rta/mqproxy/releases) に添付されています。Rust バイナリは xquic・BoringSSL・Rust 依存ライブラリを静的リンクします。システム側の実行時依存はパッケージのメタデータに記録されます。 ```bash # 最新リリースから自分のアーキテクチャの .deb を選ぶ -sudo dpkg -i mqproxy__amd64.deb # または _arm64.deb +sudo apt install ./mqproxy__amd64.deb # または _arm64.deb ``` パッケージは `/usr/bin/mqproxy`、`mqproxy-server@` / `mqproxy-client@` の systemd テンプレートユニットをインストールし、非特権の `mqproxy` ユーザーと `/etc/mqproxy` を作成します(同梱の `sysusers.d`/`tmpfiles.d` 経由、パッケージの `postinst` で適用)。以下のインスタンスごとの設定手順から続けてください — configure/enable の手順は同一で、ソースからのビルド手順だけが省略されます。 @@ -18,13 +18,9 @@ sudo dpkg -i mqproxy__amd64.deb # または _arm64.deb 自己完結バイナリ(xquic + BoringSSL を静的リンク、インストール後のバイナリは非標準のランタイム依存を持たない)をビルドしてインストールします。 ```bash -# -DMQPROXY_STATIC_XQUIC は xquic+BoringSSL を静的リンク。インストール接頭辞は -# *configure* 時にユニットの ExecStart に焼き込まれるため、今ここで設定する (--install 時ではない)。 -cmake -S . -B build \ - -DXQUIC_BUILD_DIR="$PWD/third_party/xquic/build" \ - -DMQPROXY_STATIC_XQUIC=ON -DCMAKE_INSTALL_PREFIX=/usr -cmake --build build --target mqproxy_cli -j -sudo cmake --install build # → /usr/bin/mqproxy, ユニット, sysusers.d, tmpfiles.d +sudo apt-get install -y dpkg-dev python3 +python3 packaging/build.py +sudo apt install ./target/dist/mqproxy_*.deb ``` `mqproxy` システムユーザーとそのディレクトリを作成します(同梱の `sysusers.d`/`tmpfiles.d` で宣言)。 diff --git a/website/ja/guide/quick-start.md b/website/ja/guide/quick-start.md index be81cbc6..867e2be2 100644 --- a/website/ja/guide/quick-start.md +++ b/website/ja/guide/quick-start.md @@ -5,11 +5,11 @@ ```bash # サーバー — UDP :4433 で MPQUIC を待ち受け。--cert/--key は必須。 # リポジトリには tests/certs にローカル用の自己署名テスト証明書が同梱されています。 -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ --cert tests/certs/test.crt --key tests/certs/test.key # クライアント — サーバーへ接続し、ローカルの SOCKS5 リスナーを :1080 で公開。 -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 \ --token secret123 \ --socks5 127.0.0.1:1080 @@ -29,7 +29,7 @@ curl --socks5-hostname 127.0.0.1:1080 https://example.com/ `--path` を繰り返すことで、追加のローカル IP を MPQUIC パスとしてバインドできます(例: WiFi + LTE)。するとストリームがそれらにわたってアグリゲーションされます。 ```bash -./build/mqproxy client \ +./target/release/mqproxy client \ --server :4433 --token secret123 \ --socks5 127.0.0.1:1080 \ --path 192.168.1.50 \ diff --git a/website/ja/guide/tcp-proxy.md b/website/ja/guide/tcp-proxy.md index 4e6cbf80..c88382af 100644 --- a/website/ja/guide/tcp-proxy.md +++ b/website/ja/guide/tcp-proxy.md @@ -26,11 +26,11 @@ mqproxy-client ──────────────── MPQUIC (マル ```bash # サーバー — UDP :4433 で MPQUIC を待ち受け。--cert/--key は必須。 # リポジトリには tests/certs にローカル用の自己署名テスト証明書が同梱されています。 -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ --cert tests/certs/test.crt --key tests/certs/test.key # クライアント — サーバーへ接続し、ローカルの SOCKS5 リスナーを :1080 で公開。 -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 \ --token secret123 \ --socks5 127.0.0.1:1080 @@ -45,7 +45,7 @@ curl --socks5-hostname 127.0.0.1:1080 https://example.com/ あるいは HTTP CONNECT イングレスを公開してプロキシとして使います。 ```bash -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --http-connect 127.0.0.1:3128 @@ -57,7 +57,7 @@ curl --proxy http://127.0.0.1:3128 https://example.com/ `--path` を繰り返して追加のローカル IP を MPQUIC パスとしてバインドすると、各 TCP フローのストリームがそれらにわたってアグリゲーションされます。 ```bash -./build/mqproxy client \ +./target/release/mqproxy client \ --server :4433 --token secret123 \ --socks5 127.0.0.1:1080 \ --path 192.168.1.50 \ diff --git a/website/ja/guide/tls-mitm.md b/website/ja/guide/tls-mitm.md index 396f674c..ff238ea4 100644 --- a/website/ja/guide/tls-mitm.md +++ b/website/ja/guide/tls-mitm.md @@ -48,11 +48,11 @@ openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ ```bash # サーバー — 変更なし。ゲートウェイのオリジンブリッジがオリジン取得を行う。 -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ --cert /etc/mqproxy/tls/server.pem --key /etc/mqproxy/tls/server.key # クライアント — 透過キャプチャ + MITM。--tproxy と署名 CA が必要。 -sudo ./build/mqproxy client \ +sudo ./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --tproxy 127.0.0.1:12443 --setup-redirect \ --mitm \ diff --git a/website/ja/guide/transparent-capture.md b/website/ja/guide/transparent-capture.md index ef6f7767..c82bf29a 100644 --- a/website/ja/guide/transparent-capture.md +++ b/website/ja/guide/transparent-capture.md @@ -15,10 +15,10 @@ ```bash # サーバー — 通常の設定から変更なし。 -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 # クライアント — :12443 で透過リスナー。nft ルールを自己インストール (root が必要)。 -sudo ./build/mqproxy client \ +sudo ./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --tproxy 127.0.0.1:12443 \ --setup-redirect @@ -36,7 +36,7 @@ curl https://example.com/ ルータスタックが既にファイアウォールとポリシールーティングのルールを所有している場合(`PREROUTING` に `TPROXY` ターゲットを置きパケットをマークする任意のセットアップ)、`--setup-redirect` を OFF のままにし、mqproxy にはリスナーの提供だけをさせます — その `--tproxy-fwmark`/`--tproxy-table` をルールが使う値に合わせてください。 ```bash -sudo ./build/mqproxy client \ +sudo ./target/release/mqproxy client \ --server :4433 --token secret123 \ --tproxy 0.0.0.0:12443 \ --tproxy-mode tproxy \ diff --git a/website/ja/guide/udp-relay.md b/website/ja/guide/udp-relay.md index 34edf243..9bc8a365 100644 --- a/website/ja/guide/udp-relay.md +++ b/website/ja/guide/udp-relay.md @@ -25,11 +25,11 @@ UDP リレーは **同じ `--socks5` リスナー** 上で公開されます — ```bash # サーバー (UDP リレーはデフォルト ON。30 秒アイドルタイムアウトを表示) -./build/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ +./target/release/mqproxy server --listen 0.0.0.0:4433 --token secret123 \ --udp-idle-timeout 30 # クライアント — SOCKS5 リスナーが TCP と UDP の両方を処理 -./build/mqproxy client \ +./target/release/mqproxy client \ --server 127.0.0.1:4433 --token secret123 \ --socks5 127.0.0.1:1080 ``` @@ -38,5 +38,5 @@ UDP リレーは **同じ `--socks5` リスナー** 上で公開されます — ```bash # クライアントの SOCKS5 リスナー経由で UDP パケットをターゲットへリレー -./build/udpsocks --proxy 127.0.0.1:1080 --target 8.8.8.8:53 --send 32 --count 1 +./target/release/examples/udpsocks --proxy 127.0.0.1:1080 --target 8.8.8.8:53 --send 32 --count 1 ```