Skip to content

Commit 7dd96ca

Browse files
authored
Fix missing Allow LAN access toggle (#735)
Fix Allow LAN access toggle when exit node is used for all devices older than android 13 - API 33 Fixes tailscale/tailscale#16187 Move ranges_calc.go Restore excludeRoute Conditionally calculate allowed IP range for older devices, but keep using excludeRoute on API 33+ Change getSDKInt return type from Int to Long to match generated go Ensure address is always network address Signed-off-by: Pawloland <59684145+Pawloland@users.noreply.github.com>
1 parent 23dcc59 commit 7dd96ca

6 files changed

Lines changed: 412 additions & 16 deletions

File tree

android/src/main/java/com/tailscale/ipn/App.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
314314

315315
override fun getOSVersion(): String = Build.VERSION.RELEASE
316316

317+
override fun getSDKInt(): Long = Build.VERSION.SDK_INT.toLong()
318+
317319
override fun isChromeOS(): Boolean {
318320
return packageManager.hasSystemFeature("android.hardware.type.pc")
319321
}

android/src/main/java/com/tailscale/ipn/ui/view/ExitNodePicker.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,7 @@ fun ExitNodePicker(
100100
}
101101
}
102102

103-
// https://developer.android.com/reference/android/net/VpnService.Builder#excludeRoute(android.net.IpPrefix) - excludeRoute is only supported in API 33+, so don't show the option if allow LAN access is not enabled.
104-
if (!allowLanAccessMDMDisposition.value.hiddenFromUser &&
105-
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
103+
if (!allowLanAccessMDMDisposition.value.hiddenFromUser) {
106104
item(key = "allowLANAccess") {
107105
Lists.SectionDivider()
108106

libtailscale/interfaces.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ type AppContext interface {
3636
// GetOSVersion gets the Android version.
3737
GetOSVersion() (string, error)
3838

39+
// GetSDKInt returns the Android SDK_INT (android.os.Build.VERSION.SDK_INT).
40+
GetSDKInt() (int, error)
41+
3942
// GetDeviceName gets the Android device's user-set name, or hardware model name as a fallback.
4043
GetDeviceName() (string, error)
4144

libtailscale/net.go

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"syscall"
1414

1515
"github.com/tailscale/tailscale-android/libtailscale/ifaceparse"
16+
rangescalc "github.com/tailscale/tailscale-android/libtailscale/ranges_calc"
1617
"github.com/tailscale/wireguard-go/tun"
1718
"tailscale.com/net/dns"
1819
"tailscale.com/net/netmon"
@@ -134,25 +135,69 @@ func (b *backend) updateTUN(rcfg *router.Config, dcfg *dns.OSConfig) (err error)
134135
b.logger.Logf("updateTUN: set nameservers")
135136
}
136137

137-
for _, route := range rcfg.Routes {
138-
// Normalize route address; Builder.addRoute does not accept non-zero masked bits.
139-
route = route.Masked()
140-
if err := builder.AddRoute(route.Addr().String(), int32(route.Bits())); err != nil {
141-
return err
142-
}
138+
// Decide whether to use ExcludeRoute (API 33+) or compute included prefixes
139+
// and pass them to AddRoute (older APIs).
140+
useExclude := false
141+
if sdk, err := b.appCtx.GetSDKInt(); err == nil && sdk >= 33 {
142+
useExclude = true
143143
}
144144

145-
for _, route := range rcfg.LocalRoutes {
146-
addr := route.Addr()
147-
if addr.IsLoopback() {
148-
continue // Skip the loopback addresses since VpnService throws an exception for those (both IPv4 and IPv6) - see https://android.googlesource.com/platform/frameworks/base/+/c741553/core/java/android/net/VpnService.java#303
145+
if useExclude {
146+
// For API 33+, use ExcludeRoute for LocalRoutes and AddRoute for Routes.
147+
for _, route := range rcfg.Routes {
148+
// Normalize route address; Builder.addRoute does not accept non-zero masked bits.
149+
route = route.Masked()
150+
if err := builder.AddRoute(route.Addr().String(), int32(route.Bits())); err != nil {
151+
return err
152+
}
153+
}
154+
155+
for _, route := range rcfg.LocalRoutes {
156+
addr := route.Addr()
157+
if addr.IsLoopback() {
158+
continue // Skip the loopback addresses since VpnService throws an exception for those (both IPv4 and IPv6) - see https://android.googlesource.com/platform/frameworks/base/+/c741553/core/java/android/net/VpnService.java#303
159+
}
160+
route = route.Masked()
161+
if err := builder.ExcludeRoute(route.Addr().String(), int32(route.Bits())); err != nil {
162+
return err
163+
}
149164
}
150-
route = route.Masked()
151-
if err := builder.ExcludeRoute(route.Addr().String(), int32(route.Bits())); err != nil {
165+
166+
b.logger.Logf("updateTUN: added %d routes (exclude-mode), localRoutes=%d", len(rcfg.Routes), len(rcfg.LocalRoutes))
167+
} else {
168+
// Older APIs: compute allowed-minus-disallowed prefixes and AddRoute them.
169+
prefixesV4, prefixesV6, err := rangescalc.Calculate(rcfg.Routes, rcfg.LocalRoutes)
170+
if err != nil {
171+
b.logger.Logf("updateTUN: route calculation error: %v", err)
152172
return err
153173
}
174+
175+
for _, route := range prefixesV4 {
176+
route = route.Masked()
177+
if err := builder.AddRoute(route.Addr().String(), int32(route.Bits())); err != nil {
178+
return err
179+
}
180+
}
181+
for _, route := range prefixesV6 {
182+
route = route.Masked()
183+
if err := builder.AddRoute(route.Addr().String(), int32(route.Bits())); err != nil {
184+
return err
185+
}
186+
}
187+
188+
b.logger.Logf(
189+
"updateTUN: added routes: v4=%d v6=%d total=%d (input routes=%d, localRoutes=%d)",
190+
len(prefixesV4),
191+
len(prefixesV6),
192+
len(prefixesV4)+len(prefixesV6),
193+
len(rcfg.Routes),
194+
len(rcfg.LocalRoutes),
195+
)
196+
b.logger.Logf("updateTUN: input routes: %v", rcfg.Routes)
197+
b.logger.Logf("updateTUN: input local routes: %v", rcfg.LocalRoutes)
198+
b.logger.Logf("updateTUN: effective routes v4: %v", prefixesV4)
199+
b.logger.Logf("updateTUN: effective routes v6: %v", prefixesV6)
154200
}
155-
b.logger.Logf("updateTUN: added %d routes", len(rcfg.Routes))
156201

157202
for _, addr := range rcfg.LocalAddrs {
158203
if err := builder.AddAddress(addr.Addr().String(), int32(addr.Bits())); err != nil {
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
package ranges_calc
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
"net/netip"
7+
"sort"
8+
)
9+
10+
// Internal representation of an IP range [Start, End] (inclusive)
11+
type ipRange struct {
12+
Start netip.Addr
13+
End netip.Addr
14+
}
15+
16+
// space describes the address space (32 for IPv4, 128 for IPv6)
17+
type space struct {
18+
bits uint
19+
}
20+
21+
// ---------- netip.Addr <-> big.Int ----------
22+
func (s space) addrToInt(a netip.Addr) *big.Int {
23+
if s.bits == 32 {
24+
b := a.As4()
25+
return new(big.Int).SetBytes(b[:])
26+
}
27+
b := a.As16()
28+
return new(big.Int).SetBytes(b[:])
29+
}
30+
31+
func (s space) intToAddr(i *big.Int) netip.Addr {
32+
b := i.FillBytes(make([]byte, s.bits/8))
33+
if s.bits == 32 {
34+
var a [4]byte
35+
copy(a[:], b)
36+
return netip.AddrFrom4(a)
37+
}
38+
var a [16]byte
39+
copy(a[:], b)
40+
return netip.AddrFrom16(a)
41+
}
42+
43+
// ---------- merge overlapping ranges ----------
44+
func (s space) mergeRanges(ranges []ipRange) []ipRange {
45+
if len(ranges) == 0 {
46+
return nil
47+
}
48+
sort.Slice(ranges, func(i, j int) bool {
49+
return ranges[i].Start.Compare(ranges[j].Start) < 0
50+
})
51+
merged := []ipRange{ranges[0]}
52+
one := big.NewInt(1)
53+
for _, r := range ranges[1:] {
54+
last := &merged[len(merged)-1]
55+
lastEnd := s.addrToInt(last.End)
56+
curStart := s.addrToInt(r.Start)
57+
if curStart.Cmp(new(big.Int).Add(lastEnd, one)) <= 0 {
58+
if r.End.Compare(last.End) > 0 {
59+
last.End = r.End
60+
}
61+
} else {
62+
merged = append(merged, r)
63+
}
64+
}
65+
return merged
66+
}
67+
68+
// ---------- range -> minimal number of CIDRs ----------
69+
// Every IP range defined by a start and end address can be represented
70+
// by one or more CIDR prefixes. This function calculates the minimal set of CIDR
71+
// prefixes that cover the given range.
72+
func (s space) rangeToCIDRs(r ipRange) []netip.Prefix {
73+
var result []netip.Prefix
74+
cur := s.addrToInt(r.Start)
75+
last := s.addrToInt(r.End)
76+
one := big.NewInt(1)
77+
78+
for cur.Cmp(last) <= 0 {
79+
// Find the largest power-of-2 block starting at cur
80+
var maxSize uint
81+
for size := uint(0); size <= s.bits; size++ {
82+
block := new(big.Int).Lsh(one, size)
83+
if new(big.Int).And(cur, new(big.Int).Sub(block, one)).Cmp(big.NewInt(0)) != 0 {
84+
break
85+
}
86+
maxSize = size
87+
}
88+
89+
// Shrink maxSize if it would go past last
90+
for {
91+
block := new(big.Int).Lsh(one, maxSize)
92+
lastAddr := new(big.Int).Add(cur, new(big.Int).Sub(block, one))
93+
if lastAddr.Cmp(last) <= 0 {
94+
break
95+
}
96+
if maxSize == 0 {
97+
break
98+
}
99+
maxSize--
100+
}
101+
102+
prefixLen := int(s.bits - maxSize)
103+
result = append(result, netip.PrefixFrom(s.intToAddr(cur), prefixLen))
104+
cur = cur.Add(cur, new(big.Int).Lsh(one, maxSize))
105+
}
106+
107+
return result
108+
}
109+
110+
// ---------- CIDR -> range ----------
111+
// prefixToRange converts a netip.Prefix to an ipRange with Start and End addresses.
112+
// Start is the network address and End is the broadcast address.
113+
func (s space) prefixToRange(p netip.Prefix) ipRange {
114+
p = p.Masked()
115+
start := s.addrToInt(p.Addr())
116+
hostBits := int(s.bits) - p.Bits()
117+
size := new(big.Int).Lsh(big.NewInt(1), uint(hostBits))
118+
size.Sub(size, big.NewInt(1))
119+
end := new(big.Int).Add(start, size)
120+
return ipRange{Start: p.Addr(), End: s.intToAddr(end)}
121+
}
122+
123+
// ---------- helper: subtract disallowed from allowed ----------
124+
func (s space) subtractRanges(allowed []ipRange, disallowed []ipRange) []ipRange {
125+
if len(allowed) == 0 {
126+
return nil
127+
}
128+
if len(disallowed) == 0 {
129+
return allowed
130+
}
131+
132+
var result []ipRange
133+
for _, a := range allowed {
134+
cur := []ipRange{a}
135+
for _, d := range disallowed {
136+
cur2 := []ipRange{}
137+
for _, r := range cur {
138+
cur2 = append(cur2, s.subtractOneRange(r, d)...)
139+
}
140+
cur = cur2
141+
if len(cur) == 0 {
142+
break
143+
}
144+
}
145+
result = append(result, cur...)
146+
}
147+
return s.mergeRanges(result)
148+
}
149+
150+
// subtractOneRange subtracts a single disallowed range from a single allowed range
151+
func (s space) subtractOneRange(allowed ipRange, disallowed ipRange) []ipRange {
152+
aStart := s.addrToInt(allowed.Start)
153+
aEnd := s.addrToInt(allowed.End)
154+
dStart := s.addrToInt(disallowed.Start)
155+
dEnd := s.addrToInt(disallowed.End)
156+
one := big.NewInt(1)
157+
158+
// No overlap
159+
if aEnd.Cmp(dStart) < 0 || aStart.Cmp(dEnd) > 0 {
160+
return []ipRange{allowed}
161+
}
162+
163+
var result []ipRange
164+
165+
// left side
166+
if aStart.Cmp(dStart) < 0 {
167+
result = append(result, ipRange{
168+
Start: allowed.Start,
169+
End: s.intToAddr(new(big.Int).Sub(dStart, one)),
170+
})
171+
}
172+
173+
// right side
174+
if aEnd.Cmp(dEnd) > 0 {
175+
result = append(result, ipRange{
176+
Start: s.intToAddr(new(big.Int).Add(dEnd, one)),
177+
End: allowed.End,
178+
})
179+
}
180+
181+
return result
182+
}
183+
184+
// rangesCalc performs the calculation: Routes (allowed) minus LocalRoutes (disallowed)
185+
type rangesCalc struct {
186+
allowed []netip.Prefix
187+
disallowed []netip.Prefix
188+
}
189+
190+
func newRangesCalc(routes, localRoutes []netip.Prefix) *rangesCalc {
191+
return &rangesCalc{allowed: routes, disallowed: localRoutes}
192+
}
193+
194+
const maxCalculatedRoutes = 500
195+
196+
// calculate computes allowed routes (Routes minus LocalRoutes) and returns
197+
// separate IPv4 and IPv6 prefix lists. If the resulting route set exceeds
198+
// a conservative cap, an error is returned so the caller can fail fast.
199+
func (rc *rangesCalc) calculate() (ipv4 []netip.Prefix, ipv6 []netip.Prefix, err error) {
200+
var out4 []netip.Prefix
201+
var out6 []netip.Prefix
202+
203+
// Collect IPv4 and IPv6 separately
204+
var allowed4 []ipRange
205+
var disallowed4 []ipRange
206+
var allowed6 []ipRange
207+
var disallowed6 []ipRange
208+
209+
for _, p := range rc.allowed {
210+
if p.Addr().Is4() {
211+
s := space{bits: 32}
212+
r := s.prefixToRange(p)
213+
allowed4 = append(allowed4, r)
214+
} else {
215+
s := space{bits: 128}
216+
r := s.prefixToRange(p)
217+
allowed6 = append(allowed6, r)
218+
}
219+
}
220+
221+
for _, p := range rc.disallowed {
222+
// Skip loopback prefixes; mirror behavior of ExcludeRoutes handling.
223+
if p.Addr().IsLoopback() {
224+
continue
225+
}
226+
if p.Addr().Is4() {
227+
s := space{bits: 32}
228+
r := s.prefixToRange(p)
229+
disallowed4 = append(disallowed4, r)
230+
} else {
231+
s := space{bits: 128}
232+
r := s.prefixToRange(p)
233+
disallowed6 = append(disallowed6, r)
234+
}
235+
}
236+
237+
// Process IPv4
238+
if len(allowed4) > 0 {
239+
s := space{bits: 32}
240+
mergedAllowed := s.mergeRanges(allowed4)
241+
mergedDisallowed := s.mergeRanges(disallowed4)
242+
finalAllowed := s.subtractRanges(mergedAllowed, mergedDisallowed)
243+
for _, r := range finalAllowed {
244+
for _, pref := range s.rangeToCIDRs(r) {
245+
out4 = append(out4, pref)
246+
}
247+
}
248+
}
249+
250+
// Process IPv6
251+
if len(allowed6) > 0 {
252+
s := space{bits: 128}
253+
mergedAllowed := s.mergeRanges(allowed6)
254+
mergedDisallowed := s.mergeRanges(disallowed6)
255+
finalAllowed := s.subtractRanges(mergedAllowed, mergedDisallowed)
256+
for _, r := range finalAllowed {
257+
for _, pref := range s.rangeToCIDRs(r) {
258+
out6 = append(out6, pref)
259+
}
260+
}
261+
}
262+
263+
total := len(out4) + len(out6)
264+
if total > maxCalculatedRoutes {
265+
return nil, nil, fmt.Errorf("calculated routes (%d) exceed cap (%d)", total, maxCalculatedRoutes)
266+
}
267+
268+
return out4, out6, nil
269+
}
270+
271+
// Calculate is the exported helper that computes effective allowed prefixes
272+
// given allowed routes and localRoutes to exclude.
273+
func Calculate(routes, localRoutes []netip.Prefix) (ipv4 []netip.Prefix, ipv6 []netip.Prefix, err error) {
274+
rc := newRangesCalc(routes, localRoutes)
275+
return rc.calculate()
276+
}

0 commit comments

Comments
 (0)