2023-01-27 21:37:20 +00:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
2020-02-05 22:16:58 +00:00
2020-02-13 22:53:43 +00:00
// Package logpolicy manages the creation or reuse of logtail loggers,
// caching collection instance state on disk for use on future runs of
// programs on the same machine.
2020-02-05 22:16:58 +00:00
package logpolicy
import (
2021-12-17 20:40:24 +00:00
"bufio"
2020-03-26 03:40:24 +00:00
"bytes"
2020-02-05 22:16:58 +00:00
"context"
2020-04-04 16:20:54 +01:00
"crypto/tls"
2020-02-05 22:16:58 +00:00
"encoding/json"
2021-12-17 20:40:24 +00:00
"errors"
2020-02-19 03:21:02 +00:00
"fmt"
2021-12-16 03:07:52 +00:00
"io"
2020-02-05 22:16:58 +00:00
"log"
2020-04-04 16:20:54 +01:00
"net"
"net/http"
2024-08-26 04:37:25 +01:00
"net/netip"
2021-02-04 17:20:17 +00:00
"net/url"
2020-02-05 22:16:58 +00:00
"os"
2020-04-05 01:34:25 +01:00
"os/exec"
2020-02-05 22:16:58 +00:00
"path/filepath"
"runtime"
2020-04-09 22:46:19 +01:00
"strings"
2021-03-18 17:23:56 +00:00
"sync"
2020-04-04 16:20:54 +01:00
"time"
2020-02-05 22:16:58 +00:00
2020-12-09 23:28:31 +00:00
"golang.org/x/term"
2020-02-05 22:16:58 +00:00
"tailscale.com/atomicfile"
2022-01-24 18:52:57 +00:00
"tailscale.com/envknob"
tsd, ipnlocal, etc: add tsd.System.HealthTracker, start some plumbing
This adds a health.Tracker to tsd.System, accessible via
a new tsd.System.HealthTracker method.
In the future, that new method will return a tsd.System-specific
HealthTracker, so multiple tsnet.Servers in the same process are
isolated. For now, though, it just always returns the temporary
health.Global value. That permits incremental plumbing over a number
of changes. When the second to last health.Global reference is gone,
then the tsd.System.HealthTracker implementation can return a private
Tracker.
The primary plumbing this does is adding it to LocalBackend and its
dozen and change health calls. A few misc other callers are also
plumbed. Subsequent changes will flesh out other parts of the tree
(magicsock, controlclient, etc).
Updates #11874
Updates #4136
Change-Id: Id51e73cfc8a39110425b6dc19d18b3975eac75ce
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-26 04:29:20 +01:00
"tailscale.com/health"
2024-08-08 04:32:11 +01:00
"tailscale.com/hostinfo"
2021-12-16 03:07:52 +00:00
"tailscale.com/log/filelogger"
2020-02-05 22:16:58 +00:00
"tailscale.com/logtail"
"tailscale.com/logtail/filch"
2021-11-17 17:29:18 +00:00
"tailscale.com/net/dnscache"
"tailscale.com/net/dnsfallback"
2021-09-28 15:55:22 +01:00
"tailscale.com/net/netknob"
2023-04-18 00:01:41 +01:00
"tailscale.com/net/netmon"
2020-05-29 00:53:19 +01:00
"tailscale.com/net/netns"
2020-04-26 16:31:14 +01:00
"tailscale.com/net/tlsdial"
2020-08-13 23:25:54 +01:00
"tailscale.com/net/tshttpproxy"
2020-08-11 04:44:26 +01:00
"tailscale.com/paths"
2021-12-17 20:40:24 +00:00
"tailscale.com/safesocket"
2020-08-05 22:50:43 +01:00
"tailscale.com/types/logger"
2023-03-01 03:00:00 +00:00
"tailscale.com/types/logid"
2021-11-16 04:52:43 +00:00
"tailscale.com/util/clientmetric"
2023-03-01 03:00:00 +00:00
"tailscale.com/util/must"
2020-11-16 18:13:06 +00:00
"tailscale.com/util/racebuild"
2024-01-03 01:19:01 +00:00
"tailscale.com/util/syspolicy"
2023-08-08 17:29:35 +01:00
"tailscale.com/util/testenv"
2020-02-05 22:16:58 +00:00
"tailscale.com/version"
2022-06-04 23:51:50 +01:00
"tailscale.com/version/distro"
2020-02-05 22:16:58 +00:00
)
2021-03-18 17:23:56 +00:00
var getLogTargetOnce struct {
sync . Once
v string // URL of logs server, or empty for default
}
func getLogTarget ( ) string {
getLogTargetOnce . Do ( func ( ) {
2024-01-03 01:19:01 +00:00
envTarget , _ := os . LookupEnv ( "TS_LOG_TARGET" )
getLogTargetOnce . v , _ = syspolicy . GetString ( syspolicy . LogTarget , envTarget )
2021-03-18 17:23:56 +00:00
} )
return getLogTargetOnce . v
}
2022-09-29 21:28:51 +01:00
// LogURL is the base URL for the configured logtail server, or the default.
// It is guaranteed to not terminate with any forward slashes.
func LogURL ( ) string {
if v := getLogTarget ( ) ; v != "" {
return strings . TrimRight ( v , "/" )
}
return "https://" + logtail . DefaultHost
}
2021-12-17 20:40:24 +00:00
// LogHost returns the hostname only (without port) of the configured
// logtail server, or the default.
2022-09-29 21:28:51 +01:00
//
// Deprecated: Use LogURL instead.
2021-12-17 20:40:24 +00:00
func LogHost ( ) string {
if v := getLogTarget ( ) ; v != "" {
2022-09-28 21:45:55 +01:00
if u , err := url . Parse ( v ) ; err == nil {
return u . Hostname ( )
}
2021-12-17 20:40:24 +00:00
}
return logtail . DefaultHost
}
2020-02-13 22:53:43 +00:00
// Config represents an instance of logs in a collection.
2020-02-05 22:16:58 +00:00
type Config struct {
Collection string
2023-03-01 03:00:00 +00:00
PrivateID logid . PrivateID
PublicID logid . PublicID
2020-02-05 22:16:58 +00:00
}
2020-02-13 22:53:43 +00:00
// Policy is a logger and its public ID.
2020-02-05 22:16:58 +00:00
type Policy struct {
2020-02-13 22:53:43 +00:00
// Logtail is the logger.
2020-12-21 17:03:39 +00:00
Logtail * logtail . Logger
2020-02-13 22:53:43 +00:00
// PublicID is the logger's instance identifier.
2023-03-01 03:00:00 +00:00
PublicID logid . PublicID
2023-07-10 20:45:57 +01:00
// Logf is where to write informational messages about this Logger.
Logf logger . Logf
2020-02-05 22:16:58 +00:00
}
2022-06-07 18:09:21 +01:00
// NewConfig creates a Config with collection and a newly generated PrivateID.
func NewConfig ( collection string ) * Config {
2023-03-01 03:00:00 +00:00
id := must . Get ( logid . NewPrivateID ( ) )
2022-06-07 18:09:21 +01:00
return & Config {
Collection : collection ,
PrivateID : id ,
PublicID : id . Public ( ) ,
}
}
// Validate verifies that the Config matches the collection,
// and that the PrivateID and PublicID pair are sensible.
func ( c * Config ) Validate ( collection string ) error {
switch {
2022-07-06 02:27:31 +01:00
case c == nil :
return errors . New ( "config is nil" )
2022-06-07 18:09:21 +01:00
case c . Collection != collection :
return fmt . Errorf ( "config collection %q does not match %q" , c . Collection , collection )
case c . PrivateID . IsZero ( ) :
return errors . New ( "config has zero PrivateID" )
case c . PrivateID . Public ( ) != c . PublicID :
return errors . New ( "config PrivateID does not match PublicID" )
}
return nil
}
2020-02-13 22:53:43 +00:00
// ToBytes returns the JSON representation of c.
2020-02-05 22:16:58 +00:00
func ( c * Config ) ToBytes ( ) [ ] byte {
data , err := json . MarshalIndent ( c , "" , "\t" )
if err != nil {
2020-08-05 22:50:43 +01:00
log . Fatalf ( "logpolicy.Config marshal: %v" , err )
2020-02-05 22:16:58 +00:00
}
return data
}
2020-02-13 22:53:43 +00:00
// Save writes the JSON representation of c to stateFile.
2022-06-07 18:09:21 +01:00
func ( c * Config ) Save ( stateFile string ) error {
2020-02-05 22:16:58 +00:00
c . PublicID = c . PrivateID . Public ( )
2020-02-19 03:21:02 +00:00
if err := os . MkdirAll ( filepath . Dir ( stateFile ) , 0750 ) ; err != nil {
2020-02-13 22:53:43 +00:00
return err
}
2020-02-05 22:16:58 +00:00
data := c . ToBytes ( )
2020-02-13 22:53:43 +00:00
if err := atomicfile . WriteFile ( stateFile , data , 0600 ) ; err != nil {
return err
2020-02-05 22:16:58 +00:00
}
2020-02-13 22:53:43 +00:00
return nil
2020-02-05 22:16:58 +00:00
}
2022-06-07 18:09:21 +01:00
// ConfigFromFile reads a Config from a JSON file.
func ConfigFromFile ( statefile string ) ( * Config , error ) {
b , err := os . ReadFile ( statefile )
if err != nil {
return nil , err
}
return ConfigFromBytes ( b )
}
// ConfigFromBytes parses a Config from its JSON encoding.
2020-02-13 22:53:43 +00:00
func ConfigFromBytes ( jsonEnc [ ] byte ) ( * Config , error ) {
2020-02-05 22:16:58 +00:00
c := & Config { }
2020-02-13 22:53:43 +00:00
if err := json . Unmarshal ( jsonEnc , c ) ; err != nil {
2020-02-05 22:16:58 +00:00
return nil , err
}
return c , nil
}
2020-02-13 22:53:43 +00:00
// stderrWriter is an io.Writer that always writes to the latest
// os.Stderr, even if os.Stderr changes during the lifetime of the
// stderrWriter value.
2020-02-05 22:16:58 +00:00
type stderrWriter struct { }
2020-02-13 22:53:43 +00:00
func ( stderrWriter ) Write ( buf [ ] byte ) ( int , error ) {
2020-02-05 22:16:58 +00:00
return os . Stderr . Write ( buf )
}
type logWriter struct {
logger * log . Logger
}
2020-02-13 22:53:43 +00:00
func ( l logWriter ) Write ( buf [ ] byte ) ( int , error ) {
l . logger . Printf ( "%s" , buf )
2020-02-05 22:16:58 +00:00
return len ( buf ) , nil
}
2023-03-08 00:22:23 +00:00
// LogsDir returns the directory to use for log configuration and
2020-02-19 03:21:02 +00:00
// buffer storage.
2023-03-08 00:22:23 +00:00
func LogsDir ( logf logger . Logf ) string {
2021-07-20 22:10:11 +01:00
if d := os . Getenv ( "TS_LOGS_DIR" ) ; d != "" {
fi , err := os . Stat ( d )
if err == nil && fi . IsDir ( ) {
return d
}
}
2021-09-20 23:57:06 +01:00
switch runtime . GOOS {
case "windows" :
if version . CmdName ( ) == "tailscaled" {
// In the common case, when tailscaled is run as the Local System (as a service),
// we want to use %ProgramData% (C:\ProgramData\Tailscale), aside the
// system state config with the machine key, etc. But if that directory's
// not accessible, then it's probably because the user is running tailscaled
// as a regular user (perhaps in userspace-networking/SOCK5 mode) and we should
// just use the %LocalAppData% instead. In a user context, %LocalAppData% isn't
// subject to random deletions from Windows system updates.
dir := filepath . Join ( os . Getenv ( "ProgramData" ) , "Tailscale" )
if winProgramDataAccessible ( dir ) {
logf ( "logpolicy: using dir %v" , dir )
return dir
}
}
dir := filepath . Join ( os . Getenv ( "LocalAppData" ) , "Tailscale" )
logf ( "logpolicy: using LocalAppData dir %v" , dir )
return dir
case "linux" :
// STATE_DIRECTORY is set by systemd 240+ but we support older
// systems-d. For example, Ubuntu 18.04 (Bionic Beaver) is 237.
systemdStateDir := os . Getenv ( "STATE_DIRECTORY" )
if systemdStateDir != "" {
logf ( "logpolicy: using $STATE_DIRECTORY, %q" , systemdStateDir )
return systemdStateDir
}
2024-10-14 23:52:03 +01:00
case "js" :
logf ( "logpolicy: no logs directory in the browser" )
return ""
2020-02-19 03:21:02 +00:00
}
2020-08-11 04:44:26 +01:00
// Default to e.g. /var/lib/tailscale or /var/db/tailscale on Unix.
if d := paths . DefaultTailscaledStateFile ( ) ; d != "" {
d = filepath . Dir ( d ) // directory of e.g. "/var/lib/tailscale/tailscaled.state"
if err := os . MkdirAll ( d , 0700 ) ; err == nil {
logf ( "logpolicy: using system state directory %q" , d )
return d
}
}
2020-02-19 03:21:02 +00:00
cacheDir , err := os . UserCacheDir ( )
if err == nil {
2020-08-05 22:50:43 +01:00
d := filepath . Join ( cacheDir , "Tailscale" )
logf ( "logpolicy: using UserCacheDir, %q" , d )
return d
2020-02-19 03:21:02 +00:00
}
2020-04-05 01:18:04 +01:00
// Use the current working directory, unless we're being run by a
// service manager that sets it to /.
wd , err := os . Getwd ( )
if err == nil && wd != "/" {
2020-08-05 22:50:43 +01:00
logf ( "logpolicy: using current directory, %q" , wd )
2020-04-05 01:18:04 +01:00
return wd
}
// No idea where to put stuff. Try to create a temp dir. It'll
// mean we might lose some logs and rotate through log IDs, but
// it's something.
2022-09-15 13:06:59 +01:00
tmp , err := os . MkdirTemp ( "" , "tailscaled-log-*" )
2020-04-05 01:18:04 +01:00
if err != nil {
panic ( "no safe place found to store log state" )
}
2020-08-05 22:50:43 +01:00
logf ( "logpolicy: using temp directory, %q" , tmp )
2020-04-05 01:18:04 +01:00
return tmp
2020-02-19 03:21:02 +00:00
}
2020-03-26 03:40:24 +00:00
// runningUnderSystemd reports whether we're running under systemd.
func runningUnderSystemd ( ) bool {
if runtime . GOOS == "linux" && os . Getppid ( ) == 1 {
2022-09-15 13:06:59 +01:00
slurp , _ := os . ReadFile ( "/proc/1/stat" )
2020-03-26 03:40:24 +00:00
return bytes . HasPrefix ( slurp , [ ] byte ( "1 (systemd) " ) )
}
return false
}
2021-08-03 15:25:05 +01:00
func redirectStderrToLogPanics ( ) bool {
2022-01-24 18:52:57 +00:00
return runningUnderSystemd ( ) || envknob . Bool ( "TS_PLEASE_PANIC" )
2021-08-03 15:25:05 +01:00
}
2021-09-20 23:57:06 +01:00
// winProgramDataAccessible reports whether the directory (assumed to
// be a Windows %ProgramData% directory) is accessible to the current
// process. It's created if needed.
func winProgramDataAccessible ( dir string ) bool {
if err := os . MkdirAll ( dir , 0700 ) ; err != nil {
// TODO: windows ACLs
return false
}
// The C:\ProgramData\Tailscale directory should be locked down
// by with ACLs to only be readable by the local system so a
// regular user shouldn't be able to do this operation:
if _ , err := os . ReadDir ( dir ) ; err != nil {
return false
}
return true
}
2020-04-05 01:34:25 +01:00
// tryFixLogStateLocation is a temporary fixup for
// https://github.com/tailscale/tailscale/issues/247 . We accidentally
// wrote logging state files to /, and then later to $CACHE_DIRECTORY
// (which is incorrect because the log ID is not reconstructible if
// deleted - it's state, not cache data).
//
// If log state for cmdname exists in / or $CACHE_DIRECTORY, and no
// log state for that command exists in dir, then the log state is
2021-10-13 01:28:44 +01:00
// moved from wherever it does exist, into dir. Leftover logs state
2020-04-05 01:34:25 +01:00
// in / and $CACHE_DIRECTORY is deleted.
2023-07-10 20:45:57 +01:00
func tryFixLogStateLocation ( dir , cmdname string , logf logger . Logf ) {
2020-07-30 15:47:19 +01:00
switch runtime . GOOS {
case "linux" , "freebsd" , "openbsd" :
// These are the OSes where we might have written stuff into
// root. Others use different logic to find the logs storage
// dir.
default :
return
}
2020-04-05 01:34:25 +01:00
if cmdname == "" {
2023-07-10 20:45:57 +01:00
logf ( "[unexpected] no cmdname given to tryFixLogStateLocation, please file a bug at https://github.com/tailscale/tailscale" )
2020-04-05 01:34:25 +01:00
return
}
if dir == "/" {
// Trying to store things in / still. That's a bug, but don't
// abort hard.
2023-07-10 20:45:57 +01:00
logf ( "[unexpected] storing logging config in /, please file a bug at https://github.com/tailscale/tailscale" )
2020-04-05 01:34:25 +01:00
return
}
if os . Getuid ( ) != 0 {
// Only root could have written log configs to weird places.
return
}
// We stored logs in 2 incorrect places: either /, or CACHE_DIR
// (aka /var/cache/tailscale). We want to move files into the
// provided dir, preferring those in CACHE_DIR over those in / if
// both exist. If files already exist in dir, don't
// overwrite. Finally, once we've maybe moved files around, we
// want to delete leftovers in / and CACHE_DIR, to clean up after
// our past selves.
files := [ ] string {
fmt . Sprintf ( "%s.log.conf" , cmdname ) ,
fmt . Sprintf ( "%s.log1.txt" , cmdname ) ,
fmt . Sprintf ( "%s.log2.txt" , cmdname ) ,
}
// checks if any of the files above exist in d.
checkExists := func ( d string ) ( bool , error ) {
for _ , file := range files {
p := filepath . Join ( d , file )
_ , err := os . Stat ( p )
if os . IsNotExist ( err ) {
continue
} else if err != nil {
return false , fmt . Errorf ( "stat %q: %w" , p , err )
}
return true , nil
}
return false , nil
}
// move files from d into dir, if they exist.
moveFiles := func ( d string ) error {
for _ , file := range files {
src := filepath . Join ( d , file )
_ , err := os . Stat ( src )
if os . IsNotExist ( err ) {
continue
} else if err != nil {
return fmt . Errorf ( "stat %q: %v" , src , err )
}
dst := filepath . Join ( dir , file )
bs , err := exec . Command ( "mv" , src , dst ) . CombinedOutput ( )
if err != nil {
return fmt . Errorf ( "mv %q %q: %v (%s)" , src , dst , err , bs )
}
}
return nil
}
existsInRoot , err := checkExists ( "/" )
if err != nil {
2023-07-10 20:45:57 +01:00
logf ( "checking for configs in /: %v" , err )
2020-04-05 01:34:25 +01:00
return
}
existsInCache := false
cacheDir := os . Getenv ( "CACHE_DIRECTORY" )
if cacheDir != "" {
existsInCache , err = checkExists ( "/var/cache/tailscale" )
if err != nil {
2023-07-10 20:45:57 +01:00
logf ( "checking for configs in %s: %v" , cacheDir , err )
2020-04-05 01:34:25 +01:00
}
}
existsInDest , err := checkExists ( dir )
if err != nil {
2023-07-10 20:45:57 +01:00
logf ( "checking for configs in %s: %v" , dir , err )
2020-04-05 01:34:25 +01:00
return
}
switch {
case ! existsInRoot && ! existsInCache :
// No leftover files, nothing to do.
return
case existsInDest :
// Already have "canonical" configs, just delete any remnants
// (below).
case existsInCache :
// CACHE_DIRECTORY takes precedence over /, move files from
// there.
if err := moveFiles ( cacheDir ) ; err != nil {
2023-07-10 20:45:57 +01:00
logf ( "%v" , err )
2020-04-05 01:34:25 +01:00
return
}
case existsInRoot :
// Files from root is better than nothing.
if err := moveFiles ( "/" ) ; err != nil {
2023-07-10 20:45:57 +01:00
logf ( "%v" , err )
2020-04-05 01:34:25 +01:00
return
}
}
// If moving succeeded, or we didn't need to move files, try to
// delete any leftover files, but it's okay if we can't delete
// them for some reason.
dirs := [ ] string { }
if existsInCache {
dirs = append ( dirs , cacheDir )
}
if existsInRoot {
dirs = append ( dirs , "/" )
}
for _ , d := range dirs {
for _ , file := range files {
p := filepath . Join ( d , file )
_ , err := os . Stat ( p )
if os . IsNotExist ( err ) {
continue
} else if err != nil {
2023-07-10 20:45:57 +01:00
logf ( "stat %q: %v" , p , err )
2020-04-05 01:34:25 +01:00
return
}
if err := os . Remove ( p ) ; err != nil {
2023-07-10 20:45:57 +01:00
logf ( "rm %q: %v" , p , err )
2020-04-05 01:34:25 +01:00
}
}
}
}
2023-07-10 20:45:57 +01:00
// New returns a new log policy (a logger and its instance ID) for a given
// collection name.
//
2024-04-27 06:06:20 +01:00
// The netMon parameter is optional. It should be specified in environments where
// Tailscaled is manipulating the routing table.
2023-07-10 20:45:57 +01:00
//
// The logf parameter is optional; if non-nil, information logs (e.g. when
// migrating state) are sent to that logger, and global changes to the log
// package are avoided. If nil, logs will be printed using log.Printf.
2024-04-26 18:12:46 +01:00
func New ( collection string , netMon * netmon . Monitor , health * health . Tracker , logf logger . Logf ) * Policy {
return NewWithConfigPath ( collection , "" , "" , netMon , health , logf )
2022-09-14 00:30:40 +01:00
}
2023-07-10 20:45:57 +01:00
// NewWithConfigPath is identical to New, but uses the specified directory and
// command name. If either is empty, it derives them automatically.
2024-04-27 06:06:20 +01:00
//
// The netMon parameter is optional. It should be specified in environments where
// Tailscaled is manipulating the routing table.
2024-04-26 18:12:46 +01:00
func NewWithConfigPath ( collection , dir , cmdName string , netMon * netmon . Monitor , health * health . Tracker , logf logger . Logf ) * Policy {
2024-08-10 21:46:47 +01:00
if hostinfo . IsNATLabGuestVM ( ) {
// In NATLab Gokrazy instances, tailscaled comes up concurently with
// DHCP and the doesn't have DNS for a while. Wait for DHCP first.
awaitGokrazyNetwork ( )
}
2020-02-05 22:16:58 +00:00
var lflags int
2020-12-09 23:28:31 +00:00
if term . IsTerminal ( 2 ) || runtime . GOOS == "windows" {
2020-02-05 22:16:58 +00:00
lflags = 0
} else {
lflags = log . LstdFlags
}
2022-01-24 18:52:57 +00:00
if envknob . Bool ( "TS_DEBUG_LOG_TIME" ) {
2020-10-19 16:10:05 +01:00
lflags = log . LstdFlags | log . Lmicroseconds
}
2020-03-26 03:40:24 +00:00
if runningUnderSystemd ( ) {
// If journalctl is going to prepend its own timestamp
// anyway, no need to add one.
lflags = 0
}
2020-02-13 22:53:43 +00:00
console := log . New ( stderrWriter { } , "" , lflags )
2020-02-05 22:16:58 +00:00
2020-08-05 22:50:43 +01:00
var earlyErrBuf bytes . Buffer
2022-03-16 23:27:57 +00:00
earlyLogf := func ( format string , a ... any ) {
2020-08-05 22:50:43 +01:00
fmt . Fprintf ( & earlyErrBuf , format , a ... )
earlyErrBuf . WriteByte ( '\n' )
}
2022-09-14 00:30:40 +01:00
if dir == "" {
2023-03-08 00:22:23 +00:00
dir = LogsDir ( earlyLogf )
2022-09-14 00:30:40 +01:00
}
if cmdName == "" {
cmdName = version . CmdName ( )
}
2023-07-10 20:45:57 +01:00
useStdLogger := logf == nil
if useStdLogger {
logf = log . Printf
}
tryFixLogStateLocation ( dir , cmdName , logf )
2020-04-05 01:34:25 +01:00
2020-07-30 15:47:19 +01:00
cfgPath := filepath . Join ( dir , fmt . Sprintf ( "%s.log.conf" , cmdName ) )
2021-02-05 18:53:15 +00:00
2021-09-20 23:57:06 +01:00
if runtime . GOOS == "windows" {
switch cmdName {
case "tailscaled" :
// Tailscale 1.14 and before stored state under %LocalAppData%
// (usually "C:\WINDOWS\system32\config\systemprofile\AppData\Local"
// when tailscaled.exe is running as a non-user system service).
// However it is frequently cleared for almost any reason: Windows
// updates, System Restore, even various System Cleaner utilities.
//
// The Windows service previously ran as tailscale-ipn.exe, so
// machines which ran very old versions might still have their
// log conf named %LocalAppData%\tailscale-ipn.log.conf
//
// Machines which started using Tailscale more recently will have
// %LocalAppData%\tailscaled.log.conf
//
// Attempt to migrate the log conf to C:\ProgramData\Tailscale
oldDir := filepath . Join ( os . Getenv ( "LocalAppData" ) , "Tailscale" )
oldPath := filepath . Join ( oldDir , "tailscaled.log.conf" )
if fi , err := os . Stat ( oldPath ) ; err != nil || ! fi . Mode ( ) . IsRegular ( ) {
// *Only* if tailscaled.log.conf does not exist,
// check for tailscale-ipn.log.conf
oldPathOldCmd := filepath . Join ( oldDir , "tailscale-ipn.log.conf" )
if fi , err := os . Stat ( oldPathOldCmd ) ; err == nil && fi . Mode ( ) . IsRegular ( ) {
oldPath = oldPathOldCmd
}
2021-09-15 03:29:07 +01:00
}
2021-09-20 23:57:06 +01:00
cfgPath = paths . TryConfigFileMigration ( earlyLogf , oldPath , cfgPath )
case "tailscale-ipn" :
for _ , oldBase := range [ ] string { "wg64.log.conf" , "wg32.log.conf" } {
oldConf := filepath . Join ( dir , oldBase )
if fi , err := os . Stat ( oldConf ) ; err == nil && fi . Mode ( ) . IsRegular ( ) {
cfgPath = paths . TryConfigFileMigration ( earlyLogf , oldConf , cfgPath )
break
}
}
}
2021-02-05 18:53:15 +00:00
}
2022-06-07 18:09:21 +01:00
newc , err := ConfigFromFile ( cfgPath )
2020-02-05 22:16:58 +00:00
if err != nil {
2022-06-07 18:09:21 +01:00
earlyLogf ( "logpolicy.ConfigFromFile %v: %v" , cfgPath , err )
}
if err := newc . Validate ( collection ) ; err != nil {
2022-07-06 02:27:31 +01:00
earlyLogf ( "logpolicy.Config.Validate for %v: %v" , cfgPath , err )
newc = NewConfig ( collection )
2022-06-07 18:09:21 +01:00
if err := newc . Save ( cfgPath ) ; err != nil {
earlyLogf ( "logpolicy.Config.Save for %v: %v" , cfgPath , err )
2020-02-13 22:53:43 +00:00
}
2020-02-05 22:16:58 +00:00
}
2022-07-28 05:06:25 +01:00
conf := logtail . Config {
2024-03-21 19:20:38 +00:00
Collection : newc . Collection ,
PrivateID : newc . PrivateID ,
Stderr : logWriter { console } ,
CompressLogs : true ,
2024-04-26 18:12:46 +01:00
HTTPC : & http . Client { Transport : NewLogtailTransport ( logtail . DefaultHost , netMon , health , logf ) } ,
2020-02-05 22:16:58 +00:00
}
2021-11-16 04:52:43 +00:00
if collection == logtail . CollectionNode {
2022-07-28 05:06:25 +01:00
conf . MetricsDelta = clientmetric . EncodeLogTailMetricsDelta
conf . IncludeProcID = true
conf . IncludeProcSequence = true
2021-11-16 04:52:43 +00:00
}
2020-02-05 22:16:58 +00:00
2024-08-10 21:46:47 +01:00
if envknob . NoLogsNoSupport ( ) || testenv . InTest ( ) {
2023-07-10 20:45:57 +01:00
logf ( "You have disabled logging. Tailscale will not be able to provide support." )
2022-09-13 15:09:57 +01:00
conf . HTTPC = & http . Client { Transport : noopPretendSuccessTransport { } }
2024-09-28 12:31:00 +01:00
} else {
// Only attach an on-disk filch buffer if we are going to be sending logs.
// No reason to persist them locally just to drop them later.
attachFilchBuffer ( & conf , dir , cmdName , logf )
if val := getLogTarget ( ) ; val != "" {
logf ( "You have enabled a non-default log target. Doing without being told to by Tailscale staff or your network administrator will make getting support difficult." )
conf . BaseURL = val
u , _ := url . Parse ( val )
conf . HTTPC = & http . Client { Transport : NewLogtailTransport ( u . Host , netMon , health , logf ) }
2022-06-04 23:51:50 +01:00
}
2020-02-05 22:16:58 +00:00
}
2023-07-10 20:45:57 +01:00
lw := logtail . NewLogger ( conf , logf )
2021-12-16 03:07:52 +00:00
var logOutput io . Writer = lw
2022-07-28 05:06:25 +01:00
if runtime . GOOS == "windows" && conf . Collection == logtail . CollectionNode {
2021-12-16 03:07:52 +00:00
logID := newc . PublicID . String ( )
exe , _ := os . Executable ( )
if strings . EqualFold ( filepath . Base ( exe ) , "tailscaled.exe" ) {
diskLogf := filelogger . New ( "tailscale-service" , logID , lw . Logf )
logOutput = logger . FuncWriter ( diskLogf )
}
}
2023-07-10 20:45:57 +01:00
if useStdLogger {
log . SetFlags ( 0 ) // other log flags are set on console, not here
log . SetOutput ( logOutput )
}
2020-02-05 22:16:58 +00:00
2023-07-10 20:45:57 +01:00
logf ( "Program starting: v%v, Go %v: %#v" ,
2023-02-11 06:20:36 +00:00
version . Long ( ) ,
2020-11-16 18:13:06 +00:00
goVersion ( ) ,
2020-04-09 22:46:19 +01:00
os . Args )
2023-07-10 20:45:57 +01:00
logf ( "LogID: %v" , newc . PublicID )
2020-08-05 22:50:43 +01:00
if earlyErrBuf . Len ( ) != 0 {
2023-07-10 20:45:57 +01:00
logf ( "%s" , earlyErrBuf . Bytes ( ) )
2020-08-05 22:50:43 +01:00
}
2020-02-05 22:16:58 +00:00
return & Policy {
Logtail : lw ,
PublicID : newc . PublicID ,
2023-07-10 20:45:57 +01:00
Logf : logf ,
2020-02-05 22:16:58 +00:00
}
}
2024-09-28 12:31:00 +01:00
// attachFilchBuffer creates an on-disk ring buffer using filch and attaches
// it to the logtail config. Note that this is optional; if no buffer is set,
// logtail will use an in-memory buffer.
func attachFilchBuffer ( conf * logtail . Config , dir , cmdName string , logf logger . Logf ) {
filchOptions := filch . Options {
ReplaceStderr : redirectStderrToLogPanics ( ) ,
}
filchPrefix := filepath . Join ( dir , cmdName )
// NAS disks cannot hibernate if we're writing logs to them all the time.
// https://github.com/tailscale/tailscale/issues/3551
if runtime . GOOS == "linux" && ( distro . Get ( ) == distro . Synology || distro . Get ( ) == distro . QNAP ) {
tmpfsLogs := "/tmp/tailscale-logs"
if err := os . MkdirAll ( tmpfsLogs , 0755 ) ; err == nil {
filchPrefix = filepath . Join ( tmpfsLogs , cmdName )
filchOptions . MaxFileSize = 1 << 20
} else {
// not a fatal error, we can leave the log files on the spinning disk
logf ( "Unable to create /tmp directory for log storage: %v\n" , err )
}
}
filchBuf , filchErr := filch . New ( filchPrefix , filchOptions )
if filchBuf != nil {
conf . Buffer = filchBuf
if filchBuf . OrigStderr != nil {
conf . Stderr = filchBuf . OrigStderr
}
}
if filchErr != nil {
logf ( "filch failed: %v" , filchErr )
}
}
2022-02-13 00:22:33 +00:00
// dialLog is used by NewLogtailTransport to log the happy path of its
// own dialing.
//
// By default it goes nowhere and is only enabled when
// tailscaled's in verbose mode.
//
// log.Printf isn't used so its own logs don't loop back into logtail
// in the happy path, thus generating more logs.
var dialLog = log . New ( io . Discard , "logtail: " , log . LstdFlags | log . Lmsgprefix )
2020-12-21 18:53:18 +00:00
// SetVerbosityLevel controls the verbosity level that should be
// written to stderr. 0 is the default (not verbose). Levels 1 or higher
// are increasingly verbose.
//
// It should not be changed concurrently with log writes.
func ( p * Policy ) SetVerbosityLevel ( level int ) {
p . Logtail . SetVerbosityLevel ( level )
2022-02-13 00:22:33 +00:00
if level > 0 {
dialLog . SetOutput ( os . Stderr )
}
2020-12-21 18:53:18 +00:00
}
2020-02-05 22:16:58 +00:00
// Close immediately shuts down the logger.
func ( p * Policy ) Close ( ) {
ctx , cancel := context . WithCancel ( context . Background ( ) )
cancel ( )
p . Shutdown ( ctx )
}
// Shutdown gracefully shuts down the logger, finishing any current
// log upload if it can be done before ctx is canceled.
func ( p * Policy ) Shutdown ( ctx context . Context ) error {
if p . Logtail != nil {
2023-07-10 20:45:57 +01:00
p . Logf ( "flushing log." )
2020-02-05 22:16:58 +00:00
return p . Logtail . Shutdown ( ctx )
}
return nil
}
2020-04-04 16:20:54 +01:00
2023-04-18 00:01:41 +01:00
// MakeDialFunc creates a net.Dialer.DialContext function specialized for use
// by logtail.
2023-03-24 04:02:22 +00:00
// It does the following:
// - If DNS lookup fails, consults the bootstrap DNS list of Tailscale hostnames.
2021-11-25 19:48:29 +00:00
// - If TLS connection fails, try again using LetsEncrypt's built-in root certificate,
// for the benefit of older OS platforms which might not include it.
2023-04-18 00:01:41 +01:00
//
2024-04-27 06:06:20 +01:00
// The netMon parameter is optional. It should be specified in environments where
// Tailscaled is manipulating the routing table.
2023-07-10 20:45:57 +01:00
func MakeDialFunc ( netMon * netmon . Monitor , logf logger . Logf ) func ( ctx context . Context , netw , addr string ) ( net . Conn , error ) {
2024-04-27 06:06:20 +01:00
if netMon == nil {
netMon = netmon . NewStatic ( )
}
2023-04-18 00:01:41 +01:00
return func ( ctx context . Context , netw , addr string ) ( net . Conn , error ) {
2023-07-10 20:45:57 +01:00
return dialContext ( ctx , netw , addr , netMon , logf )
2023-04-18 00:01:41 +01:00
}
}
2023-07-10 20:45:57 +01:00
func dialContext ( ctx context . Context , netw , addr string , netMon * netmon . Monitor , logf logger . Logf ) ( net . Conn , error ) {
nd := netns . FromDialer ( logf , netMon , & net . Dialer {
2023-03-24 04:02:22 +00:00
Timeout : 30 * time . Second ,
KeepAlive : netknob . PlatformTCPKeepAlive ( ) ,
} )
t0 := time . Now ( )
c , err := nd . DialContext ( ctx , netw , addr )
d := time . Since ( t0 ) . Round ( time . Millisecond )
if err == nil {
dialLog . Printf ( "dialed %q in %v" , addr , d )
return c , nil
}
if version . IsWindowsGUI ( ) && strings . HasPrefix ( netw , "tcp" ) {
2024-06-11 03:38:10 +01:00
if c , err := safesocket . ConnectContext ( ctx , "" ) ; err == nil {
2023-03-24 04:02:22 +00:00
fmt . Fprintf ( c , "CONNECT %s HTTP/1.0\r\n\r\n" , addr )
br := bufio . NewReader ( c )
res , err := http . ReadResponse ( br , nil )
if err == nil && res . StatusCode != 200 {
err = errors . New ( res . Status )
}
if err != nil {
2023-07-10 20:45:57 +01:00
logf ( "logtail: CONNECT response error from tailscaled: %v" , err )
2023-03-24 04:02:22 +00:00
c . Close ( )
} else {
dialLog . Printf ( "connected via tailscaled" )
return c , nil
}
}
}
// If we failed to dial, try again with bootstrap DNS.
2023-07-10 20:45:57 +01:00
logf ( "logtail: dial %q failed: %v (in %v), trying bootstrap..." , addr , err , d )
2023-03-24 04:02:22 +00:00
dnsCache := & dnscache . Resolver {
Forward : dnscache . Get ( ) . Forward , // use default cache's forwarder
UseLastGood : true ,
2023-07-10 20:45:57 +01:00
LookupIPFallback : dnsfallback . MakeLookupFunc ( logf , netMon ) ,
2023-03-24 04:02:22 +00:00
}
dialer := dnscache . Dialer ( nd . DialContext , dnsCache )
c , err = dialer ( ctx , netw , addr )
if err == nil {
2023-07-10 20:45:57 +01:00
logf ( "logtail: bootstrap dial succeeded" )
2023-03-24 04:02:22 +00:00
}
return c , err
}
// NewLogtailTransport returns an HTTP Transport particularly suited to uploading
// logs to the given host name. See DialContext for details on how it works.
2023-07-10 20:45:57 +01:00
//
2024-04-27 06:06:20 +01:00
// The netMon parameter is optional. It should be specified in environments where
// Tailscaled is manipulating the routing table.
2023-07-10 20:45:57 +01:00
//
// The logf parameter is optional; if non-nil, logs are printed using the
// provided function; if nil, log.Printf will be used instead.
2024-04-26 18:12:46 +01:00
func NewLogtailTransport ( host string , netMon * netmon . Monitor , health * health . Tracker , logf logger . Logf ) http . RoundTripper {
2023-08-08 17:29:35 +01:00
if testenv . InTest ( ) {
2023-03-24 04:19:19 +00:00
return noopPretendSuccessTransport { }
}
2024-04-27 06:06:20 +01:00
if netMon == nil {
netMon = netmon . NewStatic ( )
}
2020-04-04 16:20:54 +01:00
// Start with a copy of http.DefaultTransport and tweak it a bit.
tr := http . DefaultTransport . ( * http . Transport ) . Clone ( )
2020-08-13 23:25:54 +01:00
tr . Proxy = tshttpproxy . ProxyFromEnvironment
2020-08-27 04:02:16 +01:00
tshttpproxy . SetTransportGetProxyConnectHeader ( tr )
2020-08-13 23:25:54 +01:00
2020-04-04 16:20:54 +01:00
// We do our own zstd compression on uploads, and responses never contain any payload,
// so don't send "Accept-Encoding: gzip" to save a few bytes on the wire, since there
// will never be any body to decompress:
tr . DisableCompression = true
// Log whenever we dial:
2023-07-10 20:45:57 +01:00
if logf == nil {
logf = log . Printf
}
tr . DialContext = MakeDialFunc ( netMon , logf )
2020-04-04 16:20:54 +01:00
2024-02-16 22:36:20 +00:00
// We're uploading logs ideally infrequently, with specific timing that will
// change over time. Try to keep the connection open, to avoid repeatedly
// paying the cost of TLS setup.
tr . IdleConnTimeout = time . Hour
2020-04-04 16:20:54 +01:00
// We're contacting exactly 1 hostname, so the default's 100
// max idle conns is very high for our needs. Even 2 is
// probably double what we need:
tr . MaxIdleConns = 2
// Provide knob to force HTTP/1 for log uploads.
// TODO(bradfitz): remove this debug knob once we've decided
// to upload via HTTP/1 or HTTP/2 (probably HTTP/1). Or we might just enforce
// it server-side.
2022-01-24 18:52:57 +00:00
if envknob . Bool ( "TS_DEBUG_FORCE_H1_LOGS" ) {
2020-04-04 16:20:54 +01:00
tr . TLSClientConfig = nil // DefaultTransport's was already initialized w/ h2
tr . ForceAttemptHTTP2 = false
tr . TLSNextProto = map [ string ] func ( authority string , c * tls . Conn ) http . RoundTripper { }
}
2020-04-26 16:31:14 +01:00
2024-04-26 18:12:46 +01:00
tr . TLSClientConfig = tlsdial . Config ( host , health , tr . TLSClientConfig )
2024-10-02 19:20:49 +01:00
// Force TLS 1.3 since we know log.tailscale.io supports it.
tr . TLSClientConfig . MinVersion = tls . VersionTLS13
2020-04-26 16:31:14 +01:00
2020-04-04 16:20:54 +01:00
return tr
}
2020-11-16 18:13:06 +00:00
func goVersion ( ) string {
v := strings . TrimPrefix ( runtime . Version ( ) , "go" )
if racebuild . On {
return v + "-race"
}
return v
}
2022-09-13 15:09:57 +01:00
type noopPretendSuccessTransport struct { }
func ( noopPretendSuccessTransport ) RoundTrip ( req * http . Request ) ( * http . Response , error ) {
2023-03-24 04:02:22 +00:00
io . Copy ( io . Discard , req . Body )
2022-09-13 15:09:57 +01:00
req . Body . Close ( )
return & http . Response {
StatusCode : 200 ,
Status : "200 OK" ,
} , nil
}
2024-08-10 21:46:47 +01:00
func awaitGokrazyNetwork ( ) {
if runtime . GOOS != "linux" || distro . Get ( ) != distro . Gokrazy {
return
}
ctx , cancel := context . WithTimeout ( context . Background ( ) , 30 * time . Second )
defer cancel ( )
for {
// Before DHCP finishes, the /etc/resolv.conf file has just "#MANUAL".
all , _ := os . ReadFile ( "/etc/resolv.conf" )
if bytes . Contains ( all , [ ] byte ( "nameserver " ) ) {
2024-08-26 04:37:25 +01:00
good := true
firstLine , _ , ok := strings . Cut ( string ( all ) , "\n" )
if ok {
ns , ok := strings . CutPrefix ( firstLine , "nameserver " )
if ok {
if ip , err := netip . ParseAddr ( ns ) ; err == nil && ip . Is6 ( ) && ! ip . IsLinkLocalUnicast ( ) {
good = haveGlobalUnicastIPv6 ( )
}
}
}
if good {
return
}
2024-08-10 21:46:47 +01:00
}
select {
case <- ctx . Done ( ) :
return
case <- time . After ( 500 * time . Millisecond ) :
}
}
}
2024-08-26 04:37:25 +01:00
// haveGlobalUnicastIPv6 reports whether the machine has a IPv6 non-private
// (non-ULA) global unicast address.
//
// It's only intended for use in natlab integration tests so only works on
// Linux/macOS now and not environments (such as Android) where net.Interfaces
// doesn't work directly.
func haveGlobalUnicastIPv6 ( ) bool {
ifs , _ := net . Interfaces ( )
for _ , ni := range ifs {
aa , _ := ni . Addrs ( )
for _ , a := range aa {
ipn , ok := a . ( * net . IPNet )
if ! ok {
continue
}
ip , _ := netip . AddrFromSlice ( ipn . IP )
if ip . Is6 ( ) && ip . IsGlobalUnicast ( ) && ! ip . IsPrivate ( ) {
return true
}
}
}
return false
}