Guidance for AI coding agents working in the Intra repository.
Intra is an Android app (app.intra, Google Play) that runs a local VPN to
encrypt DNS lookups via DNS-over-HTTPS (DoH) and to defeat some forms of
network-level TLS blocking (TCP/TLS splitting). It is developed by Jigsaw.
The codebase is a hybrid Java + Go project:
- Java/Android UI and system layer —
Android/app/src/main/java/app/intra/ - Go network backend —
Android/app/src/go/, compiled into an.aarwithgomobile bindand consumed by the Java code as thebackend/protectJava packages.
go.mod Go module, named "localhost/Intra" (root of the repo)
tools.go Pins gomobile/gobind as tool dependencies (build tag: tools)
scripts/dbip_shrink.py Helper to shrink the DB-IP country database
.github/workflows/go_test.yml CI: go test -v -race ./...
Android/
build.gradle Root Gradle build (AGP 8.5.1, strict dependency locking)
gradle.properties SDK versions: compile/target 35, min 21
app/build.gradle App build + custom `compileGoBackend` / `ensureGoMobile` tasks
app/src/main/java/app/intra/
net/dns/ DNS packet parsing (DnsPacket)
net/doh/ DoH probing/racing and query Transaction model
net/go/ Bridge to the Go backend (GoVpnAdapter, GoIntraListener, GoProber)
sys/ VpnService, VpnController, persistent state, tile service
sys/firebase/ Analytics, Crashlytics, Remote Config wrappers
ui/ Activities, fragments, settings, charts
app/src/main/res/ Layouts + heavily localized strings (many values-* dirs)
app/src/test/ JVM unit tests (JUnit 4, Mockito)
app/src/androidTest/ Instrumented tests (androidx.test)
app/src/go/
backend/ gomobile-exported API (Session, DoHServer, listeners)
intra/ Core tunnel: stream dialer, packet proxy, TCP/UDP, SNI reporter
intra/split/ TCP split / TLS fragmentation retrier
intra/protect/ Wrapper for Android VpnService.protect()
doh/ DoH resolver, client auth, padding
doh/ipmap/ IP address cache per hostname
tuntap/ TUN file-descriptor helper
logging/ slog-based logging helpers
graph TD
UI["Java UI (app.intra.ui)"] --> VPN["IntraVpnService / VpnController"]
VPN --> ADAPTER["GoVpnAdapter (app.intra.net.go)"]
ADAPTER -->|gomobile AAR| BACKEND["Go package: backend"]
BACKEND --> TUNNEL["intra.Tunnel"]
TUNNEL --> SD["intraStreamDialer (TCP)"]
TUNNEL --> PP["intraPacketProxy (UDP)"]
SD --> SPLIT["split: TCP split / TLS fragmentation"]
SD --> DOH["doh.Resolver (DNS-over-HTTPS)"]
PP --> DOH
TUNNEL --> LWIP["Outline SDK lwip2transport"]
TUN["Android TUN fd"] --> TUNNEL
Key flow: IntraVpnService establishes the VPN and hands the TUN file
descriptor to GoVpnAdapter, which calls backend.ConnectSession(...). The Go
side wraps the fd (tuntap.MakeTunDeviceFromFD),
builds an intra.Tunnel,
and relays packets through the Outline SDK lwIP stack. DNS traffic addressed to
the "fake DNS" address is diverted to the configured doh.Resolver; TCP
traffic optionally gets split/fragmented; everything else is forwarded.
Go (run from the repository root):
go build ./...
go vet ./...
go test ./... # CI runs: go test -v -race ./...
gofmt -l . # must output nothingAndroid (run from Android/):
./gradlew assembleDebug # also triggers ensureGoMobile + compileGoBackend
./gradlew test # JVM unit tests
./gradlew connectedAndroidTest # instrumented tests (needs a device/emulator)
./gradlew check
./gradlew :app:compileGoBackend # build only the Go AARImportant
A Go toolchain and the Android SDK/NDK are required for the Gradle build.
Android/app/build.gradle parses the Go version out of the root go.mod and
sets GOTOOLCHAIN explicitly, because gomobile ignores go.mod.
- Module path is
localhost/Intra. Internal imports look likelocalhost/Intra/Android/app/src/go/doh. Do not "fix" this to a GitHub path. backendis the only package the Java code may touch. See backend/doc.go. There is ongoing work (see TODOs in backend/tunnel.go) to stop re-exporting theintrapackage.- gomobile type restrictions apply to anything exported from
backend: onlybool, numeric types,string,[]byte, and pointers to exported structs / interfaces defined in the bound packages. No slices of strings, no maps, no generics. Existing code works around this (e.g.NewDoHServertakes a comma-separatedipsStrinstead of[]string). - Only these Go packages are bound:
backend,intra,intra/split,intra/protect(seegoSourcePackagesinAndroid/app/build.gradle). Adding a new package that Java must see requires updating that list. - Logging: use the
loggingpackage (logging.Debug,logging.Debugf, …), notlogorfmt.Print. The*fvariants skip formatting when the level is disabled. Java code usesapp.intra.sys.firebase.LogWrapper. - Memory:
backend/init.gosetsdebug.SetGCPercent(10)on purpose — mobile memory pressure. Be conservative with allocations on the data path. - Socket protection: any Go code that opens an external socket must go
through
protect.MakeDialer/protect.MakeListenConfig, otherwise traffic loops back into the VPN. - Concurrency: the tunnel is heavily concurrent; swappable resolvers use
atomic.Pointer. CI runs with-race; keep it clean. - License header: every new Go and Java file starts with the Apache 2.0 header ("Copyright Jigsaw Operations LLC").
- Java style: 2-space indent, Java 8 source/target level, AndroidX.
- Dependency locking is STRICT. Changing Gradle dependencies requires
regenerating lockfiles (
./gradlew ... --write-locks). - Localization:
res/values/strings.xmlis the source of truth; translatedvalues-*directories are managed externally — do not hand-edit them. - Firebase is enabled only in release builds (
FIREBASE_ENABLEDresource bool).google-services.jsonis checked in; release signing needs an untrackedAndroid/keystore.properties.
Commit messages follow Conventional Commits with an optional scope, e.g.:
fix: skip zero-len writes in split retrier (#519)
build(deps): upgrade Outline SDK to v0.0.23 (#605)
doh: use tls session cache
All changes go through GitHub pull requests against master and require review
(see CONTRIBUTING.md; a
Google CLA is required).
gofmt -l .is empty andgo vet ./...passes.go test -race ./...passes from the repo root.- If Java/resources changed:
cd Android && ./gradlew assembleDebug test. - If the
backendAPI changed: confirm the callers inAndroid/app/src/main/java/app/intra/net/go/still compile, and that the new signatures are gomobile-compatible. - New files carry the Apache 2.0 license header.