readme: net_raw net_admin #826
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: | |
| - main | |
| schedule: | |
| - cron: '0 6 * * 1' | |
| workflow_dispatch: | |
| env: | |
| GO_VERSION: "stable" | |
| APP: "stunmesh" | |
| jobs: | |
| codegen: | |
| name: Codegen | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: false | |
| - run: go install github.com/google/wire/cmd/wire@latest | |
| - run: wire . | |
| - name: git diff | |
| run: | | |
| git diff --compact-summary --exit-code || \ | |
| (echo; echo "Unexpected difference in directories after code generation. Run 'wire .' command and commit."; exit 1) | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| goos: [linux, darwin, freebsd, windows] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Lint | |
| uses: ./.github/actions/lint | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| goos: ${{ matrix.goos }} | |
| # One stable check the ruleset can require in place of every Lint (<goos>) | |
| # cell, so adding or dropping a lint platform never touches branch protection. | |
| lint-required: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| needs: lint | |
| if: always() | |
| steps: | |
| - name: All lint platforms passed | |
| run: '[ "${{ needs.lint.result }}" = "success" ]' | |
| test: | |
| name: Test (${{ matrix.os }}) | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { os: linux, runner: ubuntu-latest } | |
| - { os: darwin, runner: macos-26 } | |
| - { os: freebsd, runner: ubuntu-latest } # host; tests run in a VM | |
| - { os: windows, runner: windows-latest } | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Test | |
| uses: ./.github/actions/test | |
| with: | |
| os: ${{ matrix.os }} | |
| go-version: ${{ env.GO_VERSION }} | |
| # One stable check the ruleset can require in place of every Test (<os>) cell. | |
| test-required: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| needs: test | |
| if: always() | |
| steps: | |
| - name: All test platforms passed | |
| run: '[ "${{ needs.test.result }}" = "success" ]' | |
| build: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| needs: | |
| - lint-required | |
| - test-required | |
| strategy: | |
| matrix: | |
| os: [linux, darwin, freebsd, windows] | |
| arch: [amd64, arm64, arm, mipsle, mips] | |
| embedca: [0, 1] | |
| exclude: | |
| - os: darwin | |
| arch: arm | |
| - os: darwin | |
| arch: mipsle | |
| - os: darwin | |
| arch: mips | |
| - os: darwin | |
| embedca: 1 | |
| - os: freebsd | |
| arch: arm | |
| - os: freebsd | |
| arch: mipsle | |
| - os: freebsd | |
| arch: mips | |
| - os: freebsd | |
| embedca: 1 | |
| - os: windows | |
| arch: arm | |
| - os: windows | |
| arch: mipsle | |
| - os: windows | |
| arch: mips | |
| - os: windows | |
| embedca: 1 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Build | |
| uses: ./.github/actions/build | |
| id: build | |
| with: | |
| os: ${{ matrix.os }} | |
| arch: ${{ matrix.arch }} | |
| app-name: ${{ env.APP }} | |
| go-version: ${{ env.GO_VERSION }} | |
| embedca: ${{ matrix.embedca }} | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ steps.build.outputs.binary-name }} | |
| path: ${{ steps.build.outputs.binary-name }} | |
| # One stable check for the whole build matrix, so the ruleset requires Build | |
| # once instead of listing individual os/arch cells. | |
| build-required: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: always() | |
| steps: | |
| - name: All build cells passed | |
| run: '[ "${{ needs.build.result }}" = "success" ]' | |
| build-plugins: | |
| name: Build Plugins | |
| runs-on: ubuntu-latest | |
| needs: | |
| - lint-required | |
| - test-required | |
| strategy: | |
| matrix: | |
| os: [linux, darwin, freebsd] | |
| arch: [amd64, arm64, arm, mipsle, mips] | |
| exclude: | |
| - os: darwin | |
| arch: arm | |
| - os: darwin | |
| arch: mipsle | |
| - os: darwin | |
| arch: mips | |
| - os: freebsd | |
| arch: arm | |
| - os: freebsd | |
| arch: mipsle | |
| - os: freebsd | |
| arch: mips | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Build All Plugins | |
| uses: ./.github/actions/build-all-plugins | |
| id: build-plugins | |
| with: | |
| os: ${{ matrix.os }} | |
| arch: ${{ matrix.arch }} | |
| go-version: ${{ env.GO_VERSION }} | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ steps.build-plugins.outputs.artifact-name }} | |
| path: plugins_dist/* | |
| if-no-files-found: warn | |
| # One stable check for the plugin build matrix, mirroring build-required. | |
| build-plugins-required: | |
| name: Build Plugins | |
| runs-on: ubuntu-latest | |
| needs: build-plugins | |
| if: always() | |
| steps: | |
| - name: All plugin build cells passed | |
| run: '[ "${{ needs.build-plugins.result }}" = "success" ]' | |
| # Shell-protocol smoke tests for the contrib plugins (cloudflare-shell, | |
| # opendht, opendht-shell) plus `go test` for the separate cloudflare Go | |
| # module. Not a matrix, so this job's own name is already a stable check -- | |
| # no paired -required gate needed. It exercises contrib/ scripts, not the | |
| # core build, so it isn't in e2e's needs. | |
| contrib-test: | |
| name: Contrib Smoke Tests | |
| runs-on: ubuntu-latest | |
| needs: | |
| - lint-required | |
| - test-required | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - run: make contrib-test | |
| android-aar: | |
| name: Build Android AAR | |
| runs-on: ubuntu-latest | |
| needs: | |
| - lint-required | |
| - test-required | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Build AAR | |
| id: aar | |
| uses: ./.github/actions/build-aar | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| # mobile/ and internal/mobilebind are excluded from every default build | |
| # by the mobile tag, so neither the Lint nor the Test job above sees | |
| # them; lint and test them here instead, under the same tags as `make | |
| # lint-mobile` (MOBILE_TAGS = builtin_all + mobile, so the real builtin | |
| # implementations are linted, not their stubs). Run via the action | |
| # directly (like the main Lint job) rather than `make lint-mobile`, | |
| # since this job doesn't otherwise install golangci-lint. | |
| - name: Lint | |
| uses: golangci/golangci-lint-action@v9 | |
| with: | |
| version: latest | |
| args: --build-tags=builtin_all,mobile ./mobile/... ./internal/mobilebind/... | |
| - name: Test | |
| run: make mobile-test | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: stunmesh-aar | |
| path: | | |
| stunmesh-android-*.aar | |
| stunmesh-android-*-sources.jar | |
| # One stable check the ruleset can require for the mobile core, in place of | |
| # each platform's job. An ios cell would join android-aar here. | |
| mobile: | |
| name: Mobile | |
| runs-on: ubuntu-latest | |
| needs: android-aar | |
| if: always() | |
| steps: | |
| - name: All mobile platforms passed | |
| run: '[ "${{ needs.android-aar.result }}" = "success" ]' | |
| docker: | |
| # Publish only on a real merge to main -- not on PRs, and not on the weekly | |
| # schedule or a manual dispatch, which should build and test but not push. | |
| if: github.event_name == 'push' | |
| name: Build Docker container | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| # e2e-required transitively covers lint/test/build/build-plugins and the | |
| # realnet report. | |
| needs: e2e-required | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Build and Push Docker | |
| uses: ./.github/actions/docker | |
| with: | |
| app-name: ${{ env.APP }} | |
| docker-token: ${{ secrets.DOCKER_TOKEN }} | |
| ghcr-token: ${{ secrets.GITHUB_TOKEN }} | |
| repository-owner: ${{ github.repository_owner }} | |
| # Runtime test of the real publish -> opendht -> establish pipeline with two | |
| # live WireGuard interfaces, running the exact binary the build stage produced. | |
| # It depends on the public dhtproxy endpoints and outbound STUN, so a flaky | |
| # endpoint can block merges. The action branches per os; freebsd runs in a VM | |
| # on a Linux host (arm64 -> arm64 host for native virt). See test/e2e/. | |
| e2e: | |
| name: E2E (${{ matrix.os }}/${{ matrix.arch }}) | |
| # build-plugins-required is a gate, not an artifact source: e2e uses the | |
| # built-in opendht, not the contrib plugins. | |
| needs: | |
| - build-required | |
| - build-plugins-required | |
| strategy: | |
| fail-fast: false | |
| # Arch coverage narrows deliberately across the three e2e layers (build: 4 arches -> e2e: 2 -> e2e-realnet: 1 per subject OS) as runner cost/availability tighten. | |
| matrix: | |
| include: | |
| - { os: linux, arch: amd64, runner: ubuntu-24.04 } | |
| - { os: linux, arch: arm64, runner: ubuntu-24.04-arm } | |
| - { os: darwin, arch: arm64, runner: macos-26 } | |
| - { os: darwin, arch: amd64, runner: macos-26-intel } | |
| - { os: freebsd, arch: amd64, runner: ubuntu-latest } | |
| - { os: freebsd, arch: arm64, runner: ubuntu-24.04-arm } | |
| - { os: windows, arch: amd64, runner: windows-latest } | |
| - { os: windows, arch: arm64, runner: windows-11-arm } | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: ./.github/actions/e2e | |
| with: | |
| os: ${{ matrix.os }} | |
| arch: ${{ matrix.arch }} | |
| app-name: ${{ env.APP }} | |
| # Netns test of the wgproxy relay: two namespaces, kernel WireGuard on both | |
| # sides, the proxy fronting side A. Runs from source (go test), no build | |
| # artifact, no internet dependency. The relay throughput floor is enforced | |
| # here too, pinned to ubuntu-24.04 so the numbers stay comparable. See | |
| # test/e2e/proxy/. | |
| e2e-proxy: | |
| name: E2E (linux/proxy) | |
| runs-on: ubuntu-24.04 | |
| needs: | |
| - build-required | |
| - build-plugins-required | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Install tools | |
| run: sudo apt-get update && sudo apt-get install -y wireguard-tools | |
| - name: Relay throughput floor | |
| run: STUNMESH_BENCH_FLOOR=1 go test ./internal/wgproxy -run TestRelayThroughputFloor -count=1 | |
| - name: Run proxy e2e | |
| run: sudo -E env "PATH=$PATH" STUNMESH_E2E_PROXY=1 go test ./test/e2e/proxy -v -count=1 | |
| # Running go under sudo leaves root-owned files in the build cache and | |
| # workspace; restore ownership so later steps and git cleanup succeed. | |
| - name: Fix cache ownership | |
| if: always() | |
| run: | | |
| for dir in "$HOME/.cache/go-build" "$GITHUB_WORKSPACE"; do | |
| if [ -d "$dir" ]; then | |
| sudo chown -R "$(id -u):$(id -g)" "$dir" | |
| fi | |
| done | |
| # Two-VM real-network e2e: for every subject OS, two runners behind real | |
| # cloud NAT punch a hole and hold a tunnel; where the crash-bunker netns | |
| # exists (Linux) the subject then proves STUN still escapes a covering | |
| # WireGuard default route. Runs beside e2e/e2e-proxy on the same gates. | |
| # One pair per subject OS, each against its own fixed Linux anchor, so a | |
| # failure is attributable to the subject side by construction. | |
| # | |
| # The peer cells carry continue-on-error because cloud runners give no | |
| # NAT-behavior guarantee -- both sides may land in one region and connect | |
| # trivially, or SNAT may behave symmetrically and traversal fails through | |
| # no fault of ours. The report job is the single genuine verdict: it hard- | |
| # fails only on what is ours to break (results missing, endpoint round-trip | |
| # broken, canary corrupted), and it feeds e2e-required like every other | |
| # e2e cell. See test/e2e/realnet/. | |
| e2e-realnet-keys: | |
| name: E2E realnet (keys) | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| timeout-minutes: 5 | |
| needs: | |
| - build-required | |
| - build-plugins-required | |
| outputs: | |
| linux_anchor_key: ${{ steps.gen.outputs.linux_anchor_key }} | |
| linux_anchor_pub: ${{ steps.gen.outputs.linux_anchor_pub }} | |
| linux_subject_key: ${{ steps.gen.outputs.linux_subject_key }} | |
| linux_subject_pub: ${{ steps.gen.outputs.linux_subject_pub }} | |
| darwin_anchor_key: ${{ steps.gen.outputs.darwin_anchor_key }} | |
| darwin_anchor_pub: ${{ steps.gen.outputs.darwin_anchor_pub }} | |
| darwin_subject_key: ${{ steps.gen.outputs.darwin_subject_key }} | |
| darwin_subject_pub: ${{ steps.gen.outputs.darwin_subject_pub }} | |
| freebsd_anchor_key: ${{ steps.gen.outputs.freebsd_anchor_key }} | |
| freebsd_anchor_pub: ${{ steps.gen.outputs.freebsd_anchor_pub }} | |
| freebsd_subject_key: ${{ steps.gen.outputs.freebsd_subject_key }} | |
| freebsd_subject_pub: ${{ steps.gen.outputs.freebsd_subject_pub }} | |
| windows_anchor_key: ${{ steps.gen.outputs.windows_anchor_key }} | |
| windows_anchor_pub: ${{ steps.gen.outputs.windows_anchor_pub }} | |
| windows_subject_key: ${{ steps.gen.outputs.windows_subject_key }} | |
| windows_subject_pub: ${{ steps.gen.outputs.windows_subject_pub }} | |
| steps: | |
| - name: Install wireguard-tools | |
| run: sudo apt-get update && sudo apt-get install -y wireguard-tools | |
| # The keys are ephemeral and worthless once the run ends, so passing | |
| # them through job outputs is intentional: it removes the cross-job | |
| # secret exchange entirely. Per-run public keys also namespace the | |
| # storage keys, so neither concurrent runs nor the pairs of one run | |
| # can collide. | |
| - name: Generate keypairs | |
| id: gen | |
| run: | | |
| for pair in linux darwin freebsd windows; do | |
| for role in anchor subject; do | |
| key=$(wg genkey) | |
| echo "${pair}_${role}_key=$key" >> "$GITHUB_OUTPUT" | |
| echo "${pair}_${role}_pub=$(echo "$key" | wg pubkey)" >> "$GITHUB_OUTPUT" | |
| done | |
| done | |
| # The fixed far end of each pair: always Linux, always the same | |
| # split-tunnel raw-socket configuration. The hold and handshake windows | |
| # are wider for the pairs whose subject boots slowly (brew, the FreeBSD | |
| # VM, the WireGuard installer). | |
| e2e-realnet-anchor: | |
| name: E2E realnet (anchor for ${{ matrix.pair }}) | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| needs: e2e-realnet-keys | |
| timeout-minutes: 20 | |
| # actions:read lets the anchor poll its subject job's status and release | |
| # the hold early; the hold window then only runs out in full when the | |
| # subject never reports. | |
| permissions: | |
| contents: read | |
| actions: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| pair: [linux, darwin, freebsd, windows] | |
| env: | |
| ANCHOR_HOLD_SECS: ${{ matrix.pair == 'linux' && '600' || '900' }} | |
| HANDSHAKE_TIMEOUT: ${{ matrix.pair == 'linux' && '300' || '780' }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: ./.github/actions/e2e-realnet | |
| with: | |
| role: anchor | |
| os: linux | |
| arch: amd64 | |
| app-name: ${{ env.APP }} | |
| pair: ${{ matrix.pair }} | |
| go-version: ${{ env.GO_VERSION }} | |
| wg-private-key: ${{ needs.e2e-realnet-keys.outputs[format('{0}_anchor_key', matrix.pair)] }} | |
| peer-public-key: ${{ needs.e2e-realnet-keys.outputs[format('{0}_subject_pub', matrix.pair)] }} | |
| # The side under test, one row per supported OS. freebsd runs in a VM on a | |
| # Linux host, mirroring the same-host e2e. | |
| e2e-realnet-subject: | |
| name: E2E realnet (${{ matrix.pair }}) | |
| runs-on: ${{ matrix.runner }} | |
| continue-on-error: true | |
| needs: e2e-realnet-keys | |
| timeout-minutes: ${{ matrix.timeout }} | |
| # actions:read is the mirror of the anchor's: the subject waits for its | |
| # anchor job to be running before the handshake window starts ticking. | |
| permissions: | |
| contents: read | |
| actions: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { pair: linux, os: linux, arch: amd64, runner: ubuntu-latest, timeout: 12 } | |
| - { pair: darwin, os: darwin, arch: arm64, runner: macos-26, timeout: 20 } | |
| - { pair: freebsd, os: freebsd, arch: amd64, runner: ubuntu-latest, timeout: 20 } | |
| - { pair: windows, os: windows, arch: amd64, runner: windows-latest, timeout: 20 } | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: ./.github/actions/e2e-realnet | |
| with: | |
| role: subject | |
| os: ${{ matrix.os }} | |
| arch: ${{ matrix.arch }} | |
| app-name: ${{ env.APP }} | |
| pair: ${{ matrix.pair }} | |
| go-version: ${{ env.GO_VERSION }} | |
| wg-private-key: ${{ needs.e2e-realnet-keys.outputs[format('{0}_subject_key', matrix.pair)] }} | |
| peer-public-key: ${{ needs.e2e-realnet-keys.outputs[format('{0}_anchor_pub', matrix.pair)] }} | |
| # The single genuine realnet verdict, over every pair's uploaded results. | |
| # Matrix rows share one job-output namespace, which is why the conclusions | |
| # travel as artifacts rather than outputs. | |
| e2e-realnet-report: | |
| name: E2E realnet | |
| runs-on: ubuntu-latest | |
| needs: | |
| - e2e-realnet-anchor | |
| - e2e-realnet-subject | |
| if: always() | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install jq | |
| run: sudo apt-get update && sudo apt-get install -y jq | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| pattern: realnet-results-* | |
| path: realnet-results | |
| - name: Combined verdict | |
| env: | |
| REALNET_PAIRS: linux darwin freebsd windows | |
| run: sh test/e2e/realnet/report.sh realnet-results | |
| # One stable check aggregating the e2e matrix, the proxy cell and the | |
| # realnet verdict, mirroring the other summary gates. The ruleset requires | |
| # it, so every cell must pass to merge. The realnet peer cells stay | |
| # advisory individually; their report job is what carries the verdict here. | |
| e2e-required: | |
| name: E2E | |
| runs-on: ubuntu-latest | |
| needs: | |
| - e2e | |
| - e2e-proxy | |
| - e2e-realnet-report | |
| if: always() | |
| steps: | |
| - name: All e2e platforms passed | |
| run: | | |
| [ "${{ needs.e2e.result }}" = "success" ] && \ | |
| [ "${{ needs.e2e-proxy.result }}" = "success" ] && \ | |
| [ "${{ needs.e2e-realnet-report.result }}" = "success" ] |