Files
netbird/client/android/split_tunnel.go
eYey 945b0b6be2 Store the Android split tunnelling settings per profile (#7349)
Which applications the tunnel carries belongs with the rest of a
profile's preferences rather than on the Android side, so the choice
follows the profile the user is on.

Adds a split tunnel store beside the SSH session store, over a
"split-tunnel" namespace holding the mode and both selections. The two
selections are kept apart because the platform applies an allow list or
a deny list and never both, and so that switching mode does not throw
away the picks made in the other one.

Only the store itself needs the android build tag; the rest stays
untagged so it is covered by the package's host tests.
2026-08-31 10:29:05 +02:00

107 lines
2.6 KiB
Go

package android
// Split tunnelling modes, stored as strings so an unknown value written by a
// newer build degrades to "off" rather than to some other mode's behaviour.
const (
SplitTunnelModeOff = "off"
SplitTunnelModeExclude = "exclude"
SplitTunnelModeInclude = "include"
)
type splitTunnelSection struct {
Mode string `json:"mode"`
Excluded []string `json:"excluded"`
Included []string `json:"included"`
}
// PackageList wraps []string for gomobile compatibility.
type PackageList struct {
items []string
}
// NewPackageList creates an empty list to fill via Add.
func NewPackageList() *PackageList {
return &PackageList{}
}
// Add appends a package name, ignoring empty ones.
func (l *PackageList) Add(s string) {
if s == "" {
return
}
l.items = append(l.items, s)
}
// Size returns the number of entries.
func (l *PackageList) Size() int {
return len(l.items)
}
// Get returns the entry at index i, or an empty string when out of range.
func (l *PackageList) Get(i int) string {
if i < 0 || i >= len(l.items) {
return ""
}
return l.items[i]
}
// SplitTunnelSettings is one profile's choice of which applications the tunnel
// carries. The two selections are kept apart because the platform applies one
// or the other and never both, and so that switching mode does not throw away
// the picks made in the other one.
type SplitTunnelSettings struct {
Mode string
Excluded *PackageList
Included *PackageList
}
// NewSplitTunnelSettings creates settings that carry every application.
func NewSplitTunnelSettings() *SplitTunnelSettings {
return &SplitTunnelSettings{
Mode: SplitTunnelModeOff,
Excluded: NewPackageList(),
Included: NewPackageList(),
}
}
func packagesOf(list *PackageList) []string {
if list == nil {
return nil
}
out := make([]string, 0, len(list.items))
out = append(out, list.items...)
return out
}
func normalizeSplitTunnelMode(mode string) string {
switch mode {
case SplitTunnelModeExclude, SplitTunnelModeInclude:
return mode
default:
return SplitTunnelModeOff
}
}
func settingsFromSection(section splitTunnelSection) *SplitTunnelSettings {
out := NewSplitTunnelSettings()
out.Mode = normalizeSplitTunnelMode(section.Mode)
for _, pkg := range section.Excluded {
out.Excluded.Add(pkg)
}
for _, pkg := range section.Included {
out.Included.Add(pkg)
}
return out
}
func sectionFromSettings(settings *SplitTunnelSettings) splitTunnelSection {
if settings == nil {
settings = NewSplitTunnelSettings()
}
return splitTunnelSection{
Mode: normalizeSplitTunnelMode(settings.Mode),
Excluded: packagesOf(settings.Excluded),
Included: packagesOf(settings.Included),
}
}