Files
ctrld/nameservers_linux.go
Ginder Singh 1688ff61e9 Add mobile sandbox optimizations for v1.5.3
- Skip systemd-resolved initialization on Android (ChromeOS crash fix)
- Skip system DNS discovery commands on iOS mobile (sandbox restrictions)
- Skip route-based DNS discovery on Android
- Skip systemd resolver on Android
- Add mobile platform checks to prevent sandbox access violations

These changes ensure ctrld works correctly in mobile sandboxed environments
where system commands and file access are restricted.
2026-07-14 11:23:26 -04:00

172 lines
3.3 KiB
Go

package ctrld
import (
"bufio"
"bytes"
"encoding/hex"
"net"
"net/netip"
"os"
"runtime"
"strings"
"tailscale.com/net/netmon"
"github.com/Control-D-Inc/ctrld/internal/dns/resolvconffile"
)
const (
v4RouteFile = "/proc/net/route"
v6RouteFile = "/proc/net/ipv6_route"
)
func dnsFns() []dnsFn {
return []dnsFn{dnsFromResolvConf, dns4, dns6, dnsFromSystemdResolver}
}
func dns4() []string {
// Skip route-based DNS discovery on Android
if runtime.GOOS == "android" {
return nil
}
f, err := os.Open(v4RouteFile)
if err != nil {
return nil
}
defer f.Close()
var dns []string
seen := make(map[string]bool)
vis := virtualInterfaces()
s := bufio.NewScanner(f)
first := true
for s.Scan() {
if first {
first = false
continue
}
fields := bytes.Fields(s.Bytes())
if len(fields) < 2 {
continue
}
// Skip virtual interfaces.
if vis.contains(string(bytes.TrimSpace(fields[0]))) {
continue
}
gw := make([]byte, net.IPv4len)
// Third fields is gateway.
if _, err := hex.Decode(gw, fields[2]); err != nil {
continue
}
ip := net.IPv4(gw[3], gw[2], gw[1], gw[0])
if ip.Equal(net.IPv4zero) || seen[ip.String()] {
continue
}
seen[ip.String()] = true
dns = append(dns, ip.String())
}
return dns
}
func dns6() []string {
// Skip route-based DNS discovery on Android
if runtime.GOOS == "android" {
return nil
}
f, err := os.Open(v6RouteFile)
if err != nil {
return nil
}
defer f.Close()
var dns []string
vis := virtualInterfaces()
s := bufio.NewScanner(f)
for s.Scan() {
fields := bytes.Fields(s.Bytes())
if len(fields) < 4 {
continue
}
// Skip virtual interfaces.
if vis.contains(string(bytes.TrimSpace(fields[len(fields)-1]))) {
continue
}
gw := make([]byte, net.IPv6len)
// Fifth fields is gateway.
if _, err := hex.Decode(gw, fields[4]); err != nil {
continue
}
ip := net.IP(gw)
if ip.Equal(net.IPv6zero) {
continue
}
dns = append(dns, ip.String())
}
return dns
}
func dnsFromSystemdResolver() []string {
// Skip systemd resolver on Android
if runtime.GOOS == "android" {
return nil
}
c, err := resolvconffile.ParseFile("/run/systemd/resolve/resolv.conf")
if err != nil {
return nil
}
ns := make([]string, 0, len(c.Nameservers))
for _, nameserver := range c.Nameservers {
ns = append(ns, nameserver.String())
}
return ns
}
type set map[string]struct{}
func (s *set) add(e string) {
(*s)[e] = struct{}{}
}
func (s *set) contains(e string) bool {
_, ok := (*s)[e]
return ok
}
// virtualInterfaces returns a set of virtual interfaces on current machine.
func virtualInterfaces() set {
s := make(set)
entries, _ := os.ReadDir("/sys/devices/virtual/net")
for _, entry := range entries {
if entry.IsDir() {
s.add(strings.TrimSpace(entry.Name()))
}
}
return s
}
// validInterfacesMap returns a set containing non virtual interfaces.
// TODO: deduplicated with cmd/cli/net_linux.go in v2.
func validInterfaces() set {
m := make(map[string]struct{})
vis := virtualInterfaces()
netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) {
if _, existed := vis[i.Name]; existed {
return
}
m[i.Name] = struct{}{}
})
// Fallback to default route interface if found nothing.
if len(m) == 0 {
defaultRoute, err := netmon.DefaultRoute()
if err != nil {
return m
}
m[defaultRoute.InterfaceName] = struct{}{}
}
return m
}