You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
7.5 KiB
Go
288 lines
7.5 KiB
Go
5 years ago
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"path/filepath"
|
||
|
"reflect"
|
||
|
"time"
|
||
|
|
||
|
"github.com/tailscale/wireguard-go/device"
|
||
|
"github.com/tailscale/wireguard-go/tun"
|
||
|
"golang.org/x/sys/unix"
|
||
|
"tailscale.com/ipn"
|
||
|
"tailscale.com/logpolicy"
|
||
|
"tailscale.com/logtail"
|
||
|
"tailscale.com/logtail/filch"
|
||
|
"tailscale.com/tailscale-android/jni"
|
||
|
"tailscale.com/types/logger"
|
||
|
"tailscale.com/wgengine"
|
||
|
"tailscale.com/wgengine/router"
|
||
|
"tailscale.com/wgengine/tstun"
|
||
|
)
|
||
|
|
||
|
type backend struct {
|
||
|
engine wgengine.Engine
|
||
|
backend *ipn.LocalBackend
|
||
|
logger logtail.Logger
|
||
|
devices *multiTUN
|
||
|
settings func(*router.Config) error
|
||
|
lastCfg *router.Config
|
||
|
|
||
|
jvm jni.JVM
|
||
|
}
|
||
|
|
||
|
type androidRouter struct {
|
||
|
backend *backend
|
||
|
}
|
||
|
|
||
|
const defaultMTU = 1280 // minimalMTU from wgengine/userspace.go
|
||
|
|
||
|
// errVPNNotPrepared is used when VPNService.Builder.establish returns
|
||
|
// null, either because the VPNService is not yet prepared or because
|
||
|
// VPN status was revoked.
|
||
|
var errVPNNotPrepared = errors.New("VPN service not prepared or was revoked")
|
||
|
|
||
|
func newBackend(dataDir string, jvm jni.JVM, store *stateStore, settings func(*router.Config) error) (*backend, error) {
|
||
|
logf := wgengine.RusagePrefixLog(log.Printf)
|
||
|
pol := logpolicy.New("tailnode.log.tailscale.io")
|
||
|
b := &backend{
|
||
|
jvm: jvm,
|
||
|
devices: newTUNDevices(),
|
||
|
settings: settings,
|
||
|
}
|
||
|
genRouter := func(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (router.Router, error) {
|
||
|
return &androidRouter{backend: b}, nil
|
||
|
}
|
||
|
var logID logtail.PrivateID
|
||
|
logID.UnmarshalText([]byte("dead0000dead0000dead0000dead0000dead0000dead0000dead0000dead0000"))
|
||
|
const logPrefKey = "privatelogid"
|
||
|
storedLogID, err := store.read(logPrefKey)
|
||
|
// In all failure cases we ignore any errors and continue with the dead value above.
|
||
|
if err != nil || storedLogID == nil {
|
||
|
// Read failed or there was no previous log id.
|
||
|
newLogID, err := logtail.NewPrivateID()
|
||
|
if err == nil {
|
||
|
logID = newLogID
|
||
|
enc, err := newLogID.MarshalText()
|
||
|
if err == nil {
|
||
|
store.write(logPrefKey, enc)
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
logID.UnmarshalText([]byte(storedLogID))
|
||
|
}
|
||
|
b.SetupLogs(dataDir, logID)
|
||
|
engine, err := wgengine.NewUserspaceEngineAdvanced(logf, tstun.WrapTUN(logf, b.devices), genRouter, 0)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("runBackend: NewUserspaceEngineAdvanced: %v", err)
|
||
|
}
|
||
|
local, err := ipn.NewLocalBackend(logf, pol.PublicID.String(), store, engine)
|
||
|
if err != nil {
|
||
|
engine.Close()
|
||
|
return nil, fmt.Errorf("runBackend: NewLocalBackend: %v", err)
|
||
|
}
|
||
|
b.engine = engine
|
||
|
b.backend = local
|
||
|
return b, nil
|
||
|
}
|
||
|
|
||
|
func (b *backend) Start(notify func(n ipn.Notify)) error {
|
||
|
return b.backend.Start(ipn.Options{
|
||
|
StateKey: "ipn-android",
|
||
|
Notify: notify,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (b *backend) LinkChange() {
|
||
|
if b.engine != nil {
|
||
|
b.engine.LinkChange(false)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (r *androidRouter) Up() error {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (r *androidRouter) Set(cfg *router.Config) error {
|
||
|
return r.backend.setCfg(cfg)
|
||
|
}
|
||
|
|
||
|
func (r *androidRouter) Close() error {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (b *backend) setCfg(cfg *router.Config) error {
|
||
|
return b.settings(cfg)
|
||
|
}
|
||
|
|
||
|
func (b *backend) updateTUN(service jni.Object, cfg *router.Config) error {
|
||
|
if reflect.DeepEqual(cfg, b.lastCfg) {
|
||
|
return nil
|
||
|
}
|
||
|
err := jni.Do(b.jvm, func(env jni.Env) error {
|
||
|
cls := jni.GetObjectClass(env, service)
|
||
|
m := jni.GetMethodID(env, cls, "newBuilder", "()Landroid/net/VpnService$Builder;")
|
||
|
builder, err := jni.CallObjectMethod(env, service, m)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("IPNService.newBuilder: %v", err)
|
||
|
}
|
||
|
bcls := jni.GetObjectClass(env, builder)
|
||
|
|
||
|
// builder.setMtu.
|
||
|
setMtu := jni.GetMethodID(env, bcls, "setMtu", "(I)Landroid/net/VpnService$Builder;")
|
||
|
const mtu = defaultMTU
|
||
|
if _, err := jni.CallObjectMethod(env, builder, setMtu, jni.Value(mtu)); err != nil {
|
||
|
return fmt.Errorf("VpnService.Builder.setMtu: %v", err)
|
||
|
}
|
||
|
|
||
|
// builder.addDnsServer
|
||
|
addDnsServer := jni.GetMethodID(env, bcls, "addDnsServer", "(Ljava/lang/String;)Landroid/net/VpnService$Builder;")
|
||
|
for _, dns := range cfg.DNS {
|
||
|
_, err = jni.CallObjectMethod(env,
|
||
|
builder,
|
||
|
addDnsServer,
|
||
|
jni.Value(jni.JavaString(env, dns.String())),
|
||
|
)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("VpnService.Builder.addDnsServer: %v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// builder.addSearchDomain.
|
||
|
addSearchDomain := jni.GetMethodID(env, bcls, "addSearchDomain", "(Ljava/lang/String;)Landroid/net/VpnService$Builder;")
|
||
|
for _, dom := range cfg.DNSDomains {
|
||
|
_, err = jni.CallObjectMethod(env,
|
||
|
builder,
|
||
|
addSearchDomain,
|
||
|
jni.Value(jni.JavaString(env, dom)),
|
||
|
)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("VpnService.Builder.addSearchDomain: %v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// builder.addRoute.
|
||
|
addRoute := jni.GetMethodID(env, bcls, "addRoute", "(Ljava/lang/String;I)Landroid/net/VpnService$Builder;")
|
||
|
for _, route := range cfg.Routes {
|
||
|
_, err = jni.CallObjectMethod(env,
|
||
|
builder,
|
||
|
addRoute,
|
||
|
jni.Value(jni.JavaString(env, route.IP.String())),
|
||
|
jni.Value(route.Bits),
|
||
|
)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("VpnService.Builder.addRoute: %v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// builder.addAddress.
|
||
|
addAddress := jni.GetMethodID(env, bcls, "addAddress", "(Ljava/lang/String;I)Landroid/net/VpnService$Builder;")
|
||
|
for _, addr := range cfg.LocalAddrs {
|
||
|
_, err = jni.CallObjectMethod(env,
|
||
|
builder,
|
||
|
addAddress,
|
||
|
jni.Value(jni.JavaString(env, addr.IP.String())),
|
||
|
jni.Value(addr.Bits),
|
||
|
)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("VpnService.Builder.addAddress: %v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// builder.establish.
|
||
|
establish := jni.GetMethodID(env, bcls, "establish", "()Landroid/os/ParcelFileDescriptor;")
|
||
|
parcelFD, err := jni.CallObjectMethod(env, builder, establish)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("VpnService.Builder.establish: %v", err)
|
||
|
}
|
||
|
if parcelFD == 0 {
|
||
|
return errVPNNotPrepared
|
||
|
}
|
||
|
|
||
|
// detachFd.
|
||
|
parcelCls := jni.GetObjectClass(env, parcelFD)
|
||
|
detachFd := jni.GetMethodID(env, parcelCls, "detachFd", "()I")
|
||
|
tunFD, err := jni.CallIntMethod(env, parcelFD, detachFd)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("detachFd: %v", err)
|
||
|
}
|
||
|
|
||
|
// Create TUN device.
|
||
|
tunDev, _, err := tun.CreateUnmonitoredTUNFromFD(int(tunFD))
|
||
|
if err != nil {
|
||
|
unix.Close(int(tunFD))
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
b.devices.add(tunDev)
|
||
|
|
||
|
return nil
|
||
|
})
|
||
|
if err != nil {
|
||
|
b.lastCfg = cfg
|
||
|
b.CloseTUNs()
|
||
|
return err
|
||
|
}
|
||
|
b.lastCfg = cfg
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// CloseVPN closes any active TUN devices.
|
||
|
func (b *backend) CloseTUNs() {
|
||
|
b.lastCfg = nil
|
||
|
b.devices.Shutdown()
|
||
|
}
|
||
|
|
||
|
// SetupLogs sets up remote logging.
|
||
|
func (b *backend) SetupLogs(logDir string, logID logtail.PrivateID) {
|
||
|
logcfg := logtail.Config{
|
||
|
Collection: "tailnode.log.tailscale.io",
|
||
|
PrivateID: logID,
|
||
|
}
|
||
|
logcfg.LowMemory = true
|
||
|
drainCh := make(chan struct{})
|
||
|
logcfg.DrainLogs = drainCh
|
||
|
go func() {
|
||
|
// Upload logs infrequently. Interval chosen arbitrarily.
|
||
|
// The objective is to reduce phone power use.
|
||
|
t := time.NewTicker(2 * time.Minute)
|
||
|
for range t.C {
|
||
|
select {
|
||
|
case drainCh <- struct{}{}:
|
||
|
default:
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
filchOpts := filch.Options{
|
||
|
ReplaceStderr: true,
|
||
|
}
|
||
|
|
||
|
var filchErr error
|
||
|
if logDir != "" {
|
||
|
logPath := filepath.Join(logDir, "ipn.log.")
|
||
|
logcfg.Buffer, filchErr = filch.New(logPath, filchOpts)
|
||
|
}
|
||
|
|
||
|
logf := wgengine.RusagePrefixLog(log.Printf)
|
||
|
b.logger = logtail.Log(logcfg, logf)
|
||
|
|
||
|
log.SetFlags(0)
|
||
|
log.SetOutput(b.logger)
|
||
|
|
||
|
log.Printf("goSetupLogs: success")
|
||
|
|
||
|
if logDir == "" {
|
||
|
log.Printf("SetupLogs: no logDir, storing logs in memory")
|
||
|
}
|
||
|
if filchErr != nil {
|
||
|
log.Printf("SetupLogs: filch setup failed: %v", filchErr)
|
||
|
}
|
||
|
}
|