Skip to content

[client] Android debug bundle support#5888

Merged
pappz merged 7 commits intomainfrom
fix/android-debug-bundle
Apr 20, 2026
Merged

[client] Android debug bundle support#5888
pappz merged 7 commits intomainfrom
fix/android-debug-bundle

Conversation

@pappz
Copy link
Copy Markdown
Collaborator

@pappz pappz commented Apr 14, 2026

Describe your changes

Add Android debug bundle support with Troubleshoot UI

  • debug.go: Replace inline log collection with platform-dispatched addPlatformLog(); add TempDir to GeneratorDependencies and BundleGenerator
  • debug_android.go: New — dumps logcat via /system/bin/logcat -d
  • debug_nonandroid.go: New — existing file-based log + systemd fallback
  • platform_files.go: Add CacheDir() to PlatformFiles interface
  • mobile_dependency.go: Add TempDir field
  • engine.go: Thread TempDir through EngineConfig and handleBundle
  • connect.go: Thread cacheDir through RunOnAndroid and createEngineConfig
  • client.go (android): Add DebugBundle(PlatformFiles, bool) method with config-from-disk fallback

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • New Features

    • Debug bundle generation and upload for easier troubleshooting (includes optional latest sync response and client metrics when connected).
    • Optional anonymization to protect sensitive data in bundles.
    • Platform-specific log collection for Android and non-Android devices.
    • Bundles use the app cache directory for temporary files, enforce a 2-minute upload timeout, and are cleaned up automatically.
  • Chores

    • Tooling: updated lint installer reference.

pappz added 3 commits April 14, 2026 18:38
Use app-provided cache directory for os.CreateTemp instead of
os.TempDir() which resolves to /data/local/tmp/ on Android — a
directory not writable by regular apps.

Thread TempDir through GeneratorDependencies -> BundleGenerator and
MobileDependency -> EngineConfig so the Android client can pass its
cache directory from PlatformFiles.CacheDir().
Move log collection into platform-dispatched addPlatformLog():
- Android: dumps logcat buffer via /system/bin/logcat -d
- Other platforms: existing file-based log + systemd fallback
Accept PlatformFiles parameter so debug bundle can be generated
even when the engine is not running by loading config from disk.
Pass anonymize flag from the UI.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 017fbcbb-304e-462d-9be6-e25fc3ad3dff

📥 Commits

Reviewing files that changed from the base of the PR and between 0920e6a and aa1c194.

📒 Files selected for processing (1)
  • client/android/client.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/android/client.go

📝 Walkthrough

Walkthrough

Adds Android debug-bundle creation and upload, persists cacheDir and config on Client with concurrency protection, threads a temp/cache dir from PlatformFiles → ConnectClient → MobileDependency → Engine → debug generator, and splits platform log collection into Android and non-Android implementations.

Changes

Cohort / File(s) Summary
Android client surface
client/android/client.go, client/android/platform_files.go
Added (*Client) DebugBundle(platformFiles PlatformFiles, anonymize bool) (string, error) which snapshots client state, builds a debug bundle (optionally including latest sync + metrics), uploads to types.DefaultBundleURL via cfg.ManagementURL with a 2-minute timeout, and deletes the temp file. Client now holds config *profilemanager.Config, cacheDir string, and stateMu sync.RWMutex; Run/RunWithoutLogin persist cfg/cacheDir/connectClient and pass cacheDir into RunOnAndroid. PlatformFiles gained CacheDir() string.
Connect / mobile plumbing
client/internal/connect.go, client/internal/mobile_dependency.go, client/internal/engine.go
ConnectClient.RunOnAndroid(..., stateFilePath, cacheDir string) signature updated to accept cacheDir; MobileDependency gained TempDir string; EngineConfig gained TempDir string; cacheDir is propagated into MobileDependency.TempDir and then into EngineConfig.TempDir during engine setup.
Debug bundle core
client/internal/debug/debug.go
GeneratorDependencies now includes TempDir and BundleGenerator stores tempDir; Generate() creates temp bundle files in provided temp dir; unified platform log handling via addPlatformLog() (removed previous slices/util special-case logic).
Platform log collection
client/internal/debug/debug_android.go, client/internal/debug/debug_nonandroid.go
Added Android addPlatformLog() that runs /system/bin/logcat -d and writes logcat.txt (supports anonymization pipeline). Added non-Android addPlatformLog() that tries g.addLogfile() and falls back to systemd logs if needed.
Engine debug handling
client/internal/engine.go
When handling remote debug bundle requests, EngineConfig.TempDir is passed into debug.GeneratorDependencies.
Tooling
Makefile
Updated golangci-lint install path to github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest.

Sequence Diagram

sequenceDiagram
    participant Client as Android Client
    participant Connect as ConnectClient
    participant Engine as Engine
    participant Generator as BundleGenerator
    participant Upload as Management / Upload

    Client->>Connect: RunOnAndroid(..., stateFile, cacheDir)
    Connect->>Engine: setup (EngineConfig.TempDir := cacheDir)
    Client->>Generator: NewBundleGenerator(deps{TempDir := cacheDir, ...})
    Client->>Generator: Generate(anonymize?, include sync/metrics)
    Generator->>Generator: addPlatformLog() (logcat or logfile/fallback)
    Generator->>Generator: createArchive() -> file in TempDir
    Generator-->>Client: bundle file path
    Client->>Upload: POST bundle -> cfg.ManagementURL (2m timeout)
    Upload-->>Client: upload response / key
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested reviewers

  • mlsmaycon

"I hopped through temp dirs, logs in tow,
Collected traces from where Androids go,
Zipped whispers, scrubbed names, and sealed the clue,
Sent it off fast — a rabbit's job well-done, woo-hoo! 🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding Android debug bundle support, which aligns with the substantial additions to debug generation and platform-specific log collection in the changeset.
Description check ✅ Passed The description covers required sections with detailed file-by-file changes, correctly identifies the PR as both a feature enhancement and refactor, addresses documentation appropriately, and explains rationale for all key modifications including concurrency protection and platform-specific logging.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/android-debug-bundle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pappz pappz marked this pull request as ready for review April 14, 2026 17:24
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/android/client.go`:
- Around line 200-220: DebugBundle currently ignores
PlatformFiles.StateFilePath(), causing the bundle generator to use the default
service-manager state.json; update the DebugBundle function to pass
platformFiles.StateFilePath() into debug.GeneratorDependencies (add a StateFile
or StateFilePath field) and ensure the code path that calls addStateFile()
consumes that field (modify addStateFile to accept the provided state path
instead of resolving the default). Change references in DebugBundle, the
debug.GeneratorDependencies struct, and addStateFile() to thread
platformFiles.StateFilePath() through to the bundle creation.
- Around line 254-256: The call to debug.UploadDebugBundle currently uses
context.Background() which can block indefinitely; change it to use a
caller-provided context (e.g., pass ctx into the surrounding function) or wrap a
provided context with a timeout using context.WithTimeout and cancel; replace
debug.UploadDebugBundle(context.Background(), ...) with
debug.UploadDebugBundle(ctx, ...) or debug.UploadDebugBundle(ctxWithTimeout,
...) and ensure you defer cancel() and propagate the context to callers so
uploads are bounded.

In `@client/android/platform_files.go`:
- Around line 7-10: You widened the exported PlatformFiles interface by adding
CacheDir(), which is a breaking change for existing Android embedders; revert
PlatformFiles to only expose ConfigurationFilePath() and StateFilePath(), and
introduce a new interface (e.g., PlatformFilesWithCache or PlatformFilesV2) that
adds CacheDir(); update call sites to detect and use CacheDir() via a type
assertion against the new interface (or provide an adapter/helper that falls
back safely) rather than changing the original PlatformFiles API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c91e1ecc-7fe1-4885-be7f-9a4ee0f92a69

📥 Commits

Reviewing files that changed from the base of the PR and between e804a70 and a35ecf9.

📒 Files selected for processing (8)
  • client/android/client.go
  • client/android/platform_files.go
  • client/internal/connect.go
  • client/internal/debug/debug.go
  • client/internal/debug/debug_android.go
  • client/internal/debug/debug_nonandroid.go
  • client/internal/engine.go
  • client/internal/mobile_dependency.go

Comment thread client/android/client.go
Comment on lines +200 to +220
func (c *Client) DebugBundle(platformFiles PlatformFiles, anonymize bool) (string, error) {
cfg := c.config
cacheDir := c.cacheDir

// If the engine hasn't been started, load config from disk
if cfg == nil {
var err error
cfg, err = profilemanager.UpdateOrCreateConfig(profilemanager.ConfigInput{
ConfigPath: platformFiles.ConfigurationFilePath(),
})
if err != nil {
return "", fmt.Errorf("load config: %w", err)
}
cacheDir = platformFiles.CacheDir()
}

deps := debug.GeneratorDependencies{
InternalConfig: cfg,
StatusRecorder: c.recorder,
TempDir: cacheDir,
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

StateFilePath() is still ignored for Android debug bundles.

DebugBundle receives platformFiles, but only uses the config path and cache dir. The bundle generator still resolves state.json from the default service-manager location, so Troubleshoot bundles created from the Android app will miss the real sandboxed state file. Please thread platformFiles.StateFilePath() into GeneratorDependencies and make addStateFile() use it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/android/client.go` around lines 200 - 220, DebugBundle currently
ignores PlatformFiles.StateFilePath(), causing the bundle generator to use the
default service-manager state.json; update the DebugBundle function to pass
platformFiles.StateFilePath() into debug.GeneratorDependencies (add a StateFile
or StateFilePath field) and ensure the code path that calls addStateFile()
consumes that field (modify addStateFile to accept the provided state path
instead of resolving the default). Change references in DebugBundle, the
debug.GeneratorDependencies struct, and addStateFile() to thread
platformFiles.StateFilePath() through to the bundle creation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Android, addStateFile() calls NewServiceManager("").GetStatePath() which resolves to /var/lib/netbird/state.json (the Linux default), not the app's actual state file path that Java passes via PlatformFiles.StateFilePath(). The bundle silently skips the state file since that path doesn't exist on Android.
However, this is a pre-existing issue in the upstream debug.go, not something introduced by this PR. The fix would involve adding a StatePath field to GeneratorDependencies and threading it through addStateFile() with a fallback to the current ServiceManager lookup when empty. I'll address this in a follow-up to keep this PR focused.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pappz, thanks for the detailed explanation — that confirms this is a pre-existing gap in debug.go, not a regression introduced here. The fix approach you've described (adding a StatePath field to GeneratorDependencies with a fallback to NewServiceManager("").GetStatePath() when empty) sounds right. Happy to keep this PR focused and defer to a follow-up.

Would you like me to open a GitHub issue to track this? It would capture the context: addStateFile() on Android resolves to /var/lib/netbird/state.json via NewServiceManager instead of the app-sandbox path from PlatformFiles.StateFilePath(), and the fix involves threading StatePath through GeneratorDependencies.


✏️ Learnings added
Learnt from: pappz
Repo: netbirdio/netbird PR: 5888
File: client/android/client.go:200-220
Timestamp: 2026-04-14T17:54:53.373Z
Learning: In netbirdio/netbird, `addStateFile()` in `client/internal/debug/debug.go` calls `NewServiceManager("").GetStatePath()` which resolves to `/var/lib/netbird/state.json` (the Linux default). On Android, this path does not exist; the real state file path is provided by `PlatformFiles.StateFilePath()`. The fix is to add a `StatePath` field to `GeneratorDependencies` and thread it through `addStateFile()` with a fallback to the `ServiceManager` lookup when empty. This is a pre-existing issue in the upstream debug.go (not introduced by PR `#5888`) and is deferred to a follow-up PR.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: lixmal
Repo: netbirdio/netbird PR: 5826
File: client/internal/portforward/manager.go:35-38
Timestamp: 2026-04-08T14:55:48.251Z
Learning: In `client/internal/portforward/manager.go` (netbirdio/netbird), persisting permanent-lease (ttl=0/UPnP error 725 fallback) port mapping state for crash-recovery cleanup is intentionally deferred to a follow-up PR. The blocker is that `State.Cleanup` requires NAT gateway re-discovery via `nat.DiscoverGateway`, which can block startup for ~10 seconds when no gateway is present, affecting all clients. The TODO comment at line ~35 documents this constraint. Do not flag the missing state persistence for permanent leases as a blocking issue in this PR.

Learnt from: pappz
Repo: netbirdio/netbird PR: 5888
File: client/android/platform_files.go:7-10
Timestamp: 2026-04-14T17:51:30.929Z
Learning: In the netbirdio/netbird repository, treat the Android client API in `client/android/**/*.go` (e.g., exported interfaces like `PlatformFiles`) as having no versioning or stability guarantees, since it is not a semver-stable public API. Because the repo pins explicit submodule versions, widening these exported interfaces (such as adding new methods to `PlatformFiles`) should not be reviewed/flagged as a breaking API change. Only flag changes where they break behavior within the pinned implementation, not where the interface is merely extended.

Learnt from: siriobalmelli
Repo: netbirdio/netbird PR: 5504
File: client/internal/dns/host_darwin.go:75-79
Timestamp: 2026-03-13T09:37:39.641Z
Learning: In netbirdio/netbird, `stateManager.UpdateState()` failures are handled with log.Errorf/log.Warnf and execution continues (never return an error). The sole error path is `errStateNotRegistered`, which is a programming bug not a runtime condition. All call sites across routemanager, engine_ssh, dns/systemd_linux, dns/network_manager_unix, dns/resolvconf_unix, dns/host_windows, and updatemanager follow this log-and-continue convention. Do not flag log-and-continue on UpdateState as a bug in this codebase.

Learnt from: bcmmbaga
Repo: netbirdio/netbird PR: 5441
File: management/server/user.go:745-749
Timestamp: 2026-02-24T19:32:13.189Z
Learning: In Go codebases like netbirdio/netbird, methods returning (T, error) should follow the convention: if an error is returned, propagate it and return early. When the caller handles the error immediately, explicit nil checks on the returned value are unnecessary; rely on the error to guard control flow and only inspect the value when err is nil. This guidance applies broadly to Store methods such as GetUserByUserID in Go code; prefer early return on error and avoid redundant nil checks after a successful error path.

Learnt from: lixmal
Repo: netbirdio/netbird PR: 5530
File: proxy/internal/udp/relay.go:276-278
Timestamp: 2026-03-07T13:59:16.506Z
Learning: For Go 1.25+ projects, use sync.WaitGroup.Go() to run goroutines because it handles Add(1) and defer Done() automatically. Do not flag wg.Go(...) as a compilation error in codebases that require Go 1.25 or newer. If supporting older Go versions, gate such usage with build tags or avoid wg.Go() to maintain compatibility; verify the module targets Go 1.25+ before adopting this pattern.

Learnt from: lixmal
Repo: netbirdio/netbird PR: 5688
File: client/firewall/uspfilter/forwarder/icmp.go:268-287
Timestamp: 2026-03-25T09:21:53.814Z
Learning: When using gVisor’s `header.ICMPv6Checksum(ICMPv6ChecksumParams)` (`gvisor.dev/gvisor/pkg/tcpip/header`), the pseudo-header checksum is computed internally from `Src`, `Dst`, and `Header`. Use `PayloadCsum`/`PayloadLen` only for scattered-buffer cases where the ICMPv6 payload bytes are not included in `Header`. If `Header` already contains the complete ICMPv6 message (ICMPv6 header + all data), call `ICMPv6Checksum` with `PayloadCsum: 0` and `PayloadLen: 0` (or omit those fields). Do not pass a separately computed pseudo-header checksum (e.g., `header.PseudoHeaderChecksum(...)`) as `PayloadCsum` in that case, because it will be double-counted and produce an incorrect checksum.

Comment thread client/android/client.go Outdated
Comment thread client/android/platform_files.go
Use a 2-minute context timeout instead of context.Background()
to prevent the upload from blocking indefinitely.
Comment thread client/internal/debug/debug_android.go Outdated
Comment thread client/android/client.go Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Makefile (1)

8-8: Consider pinning golangci-lint to a specific version for better reproducibility.

The repo config is already v2-compatible (version: "2" in .golangci.yaml), but @latest in the local Makefile makes lint results depend on install date. Note that the CI workflow also uses version: latest, so local installs will stay in sync with CI, but neither is pinned to a fixed release. If stricter reproducibility is desired, pin both the Makefile and CI to the same version (e.g., v2.0.0 or later).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` at line 8, Replace the unpinned go install reference that uses
`@latest` in the Makefile (the GOBIN=$(shell pwd)/bin go install
github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest invocation) with a
specific release tag (e.g., `@v1.56.0` or another chosen v2.x.y) to ensure
reproducible linting; update the same pinned version in your CI workflow to keep
local and CI installs in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Makefile`:
- Line 8: Replace the unpinned go install reference that uses `@latest` in the
Makefile (the GOBIN=$(shell pwd)/bin go install
github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest invocation) with a
specific release tag (e.g., `@v1.56.0` or another chosen v2.x.y) to ensure
reproducible linting; update the same pinned version in your CI workflow to keep
local and CI installs in sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1d223944-dde9-4246-a698-b88f96f706c1

📥 Commits

Reviewing files that changed from the base of the PR and between f01c1ee and 0920e6a.

📒 Files selected for processing (2)
  • Makefile
  • client/internal/debug/debug_android.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/internal/debug/debug_android.go

config, cacheDir and connectClient were written in Run/RunWithoutLogin
on a background Thread and read from the UI thread (Networks) and the
TUN looper thread (RenewTun) with no synchronization. A Stop->Run cycle
could race with a concurrent DebugBundle or Networks call.
@pappz pappz requested a review from lixmal April 19, 2026 20:03
@sonarqubecloud
Copy link
Copy Markdown

@pappz pappz merged commit 7f023ce into main Apr 20, 2026
57 of 58 checks passed
@pappz pappz deleted the fix/android-debug-bundle branch April 20, 2026 09:26
MichaelUray added a commit to MichaelUray/netbird that referenced this pull request Apr 24, 2026
…-addresses

Resolved conflicts against upstream netbirdio#5906 (ios mac filter / network_addr.go
extraction) and netbirdio#5888 (DebugBundle on Android client):

- client/android/client.go: keep both OnUnderlyingNetworkChanged (ours) and
  DebugBundle (upstream), replaced non-ASCII arrow in comment with plain text.
- client/system/info.go: drop duplicated networkAddresses/isDuplicated - they
  now live in client/system/network_addr.go after upstream extraction.
- client/system/network_addr.go: adopt upstream's toNetworkAddress helper
  but keep ctx-aware signature + skipNoMacFilter so Android continues to use
  the external iFace discoverer.
- client/system/info_ios.go: add exported NetworkAddresses(ctx) shim so the
  engine call compiles on ios; the iOS body stays context-free (iOS has no
  external discoverer).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants