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.
184 lines
4.5 KiB
Go
184 lines
4.5 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"net/http"
|
|
"path/filepath"
|
|
"time"
|
|
"unsafe"
|
|
|
|
jnipkg "github.com/tailscale/tailscale-android/pkg/jni"
|
|
"tailscale.com/logpolicy"
|
|
"tailscale.com/logtail"
|
|
"tailscale.com/logtail/filch"
|
|
"tailscale.com/net/interfaces"
|
|
"tailscale.com/smallzstd"
|
|
"tailscale.com/types/logger"
|
|
"tailscale.com/types/logid"
|
|
"tailscale.com/util/clientmetric"
|
|
"tailscale.com/util/must"
|
|
)
|
|
|
|
import "C"
|
|
|
|
var (
|
|
// googleClass is a global reference to the com.tailscale.ipn.Google class.
|
|
googleClass jnipkg.Class
|
|
)
|
|
|
|
const defaultMTU = 1280 // minimalMTU from wgengine/userspace.go
|
|
|
|
const (
|
|
logPrefKey = "privatelogid"
|
|
loginMethodPrefKey = "loginmethod"
|
|
customLoginServerPrefKey = "customloginserver"
|
|
)
|
|
|
|
type ConnectEvent struct {
|
|
Enable bool
|
|
}
|
|
|
|
func main() {
|
|
a := &App{
|
|
jvm: (*jnipkg.JVM)(unsafe.Pointer(javaVM())),
|
|
appCtx: jnipkg.Object(appContext()),
|
|
}
|
|
|
|
err := a.loadJNIGlobalClassRefs()
|
|
if err != nil {
|
|
fatalErr(err)
|
|
}
|
|
|
|
a.store = newStateStore(a.jvm, a.appCtx)
|
|
interfaces.RegisterInterfaceGetter(a.getInterfaces)
|
|
go func() {
|
|
ctx := context.Background()
|
|
if err := a.runBackend(ctx); err != nil {
|
|
fatalErr(err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func fatalErr(err error) {
|
|
// TODO: expose in UI.
|
|
log.Printf("fatal error: %v", err)
|
|
}
|
|
|
|
// osVersion returns android.os.Build.VERSION.RELEASE. " [nogoogle]" is appended
|
|
// if Google Play services are not compiled in.
|
|
func (a *App) osVersion() string {
|
|
var version string
|
|
err := jnipkg.Do(a.jvm, func(env *jnipkg.Env) error {
|
|
cls := jnipkg.GetObjectClass(env, a.appCtx)
|
|
m := jnipkg.GetMethodID(env, cls, "getOSVersion", "()Ljava/lang/String;")
|
|
n, err := jnipkg.CallObjectMethod(env, a.appCtx, m)
|
|
version = jnipkg.GoString(env, jnipkg.String(n))
|
|
return err
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return version
|
|
}
|
|
|
|
// modelName return the MANUFACTURER + MODEL from
|
|
// android.os.Build.
|
|
func (a *App) modelName() string {
|
|
var model string
|
|
err := jnipkg.Do(a.jvm, func(env *jnipkg.Env) error {
|
|
cls := jnipkg.GetObjectClass(env, a.appCtx)
|
|
m := jnipkg.GetMethodID(env, cls, "getModelName", "()Ljava/lang/String;")
|
|
n, err := jnipkg.CallObjectMethod(env, a.appCtx, m)
|
|
model = jnipkg.GoString(env, jnipkg.String(n))
|
|
return err
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return model
|
|
}
|
|
|
|
func (a *App) isChromeOS() bool {
|
|
var chromeOS bool
|
|
err := jnipkg.Do(a.jvm, func(env *jnipkg.Env) error {
|
|
cls := jnipkg.GetObjectClass(env, a.appCtx)
|
|
m := jnipkg.GetMethodID(env, cls, "isChromeOS", "()Z")
|
|
b, err := jnipkg.CallBooleanMethod(env, a.appCtx, m)
|
|
chromeOS = b
|
|
return err
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return chromeOS
|
|
}
|
|
|
|
func googleSignInEnabled() bool {
|
|
return googleClass != 0
|
|
}
|
|
|
|
// Loads the global JNI class references. Failures here are fatal if the
|
|
// class ref is required for the app to function.
|
|
func (a *App) loadJNIGlobalClassRefs() error {
|
|
return jnipkg.Do(a.jvm, func(env *jnipkg.Env) error {
|
|
loader := jnipkg.ClassLoaderFor(env, a.appCtx)
|
|
cl, err := jnipkg.LoadClass(env, loader, "com.tailscale.ipn.Google")
|
|
if err != nil {
|
|
// Ignore load errors; the Google class is not included in F-Droid builds.
|
|
return nil
|
|
}
|
|
googleClass = jnipkg.Class(jnipkg.NewGlobalRef(env, jnipkg.Object(cl)))
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// SetupLogs sets up remote logging.
|
|
func (b *backend) setupLogs(logDir string, logID logid.PrivateID, logf logger.Logf) {
|
|
if b.netMon == nil {
|
|
panic("netMon must be created prior to SetupLogs")
|
|
}
|
|
transport := logpolicy.NewLogtailTransport(logtail.DefaultHost, b.netMon, log.Printf)
|
|
|
|
logcfg := logtail.Config{
|
|
Collection: logtail.CollectionNode,
|
|
PrivateID: logID,
|
|
Stderr: log.Writer(),
|
|
MetricsDelta: clientmetric.EncodeLogTailMetricsDelta,
|
|
IncludeProcID: true,
|
|
IncludeProcSequence: true,
|
|
NewZstdEncoder: func() logtail.Encoder {
|
|
return must.Get(smallzstd.NewEncoder(nil))
|
|
},
|
|
HTTPC: &http.Client{Transport: transport},
|
|
}
|
|
logcfg.FlushDelayFn = func() time.Duration { return 2 * time.Minute }
|
|
|
|
filchOpts := filch.Options{
|
|
ReplaceStderr: true,
|
|
}
|
|
|
|
var filchErr error
|
|
if logDir != "" {
|
|
logPath := filepath.Join(logDir, "ipn.log.")
|
|
logcfg.Buffer, filchErr = filch.New(logPath, filchOpts)
|
|
}
|
|
|
|
b.logger = logtail.NewLogger(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)
|
|
}
|
|
}
|