mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-01 22:15:51 +00:00
0c69b4e00d
Implement rate limiting on log messages Addresses issue #317, where logs can get spammed with the same message nonstop. Created a rate limiting closure on logging functions, which limits the number of messages being logged per second based on format string. To keep memory usage as constant as possible, the previous cache purging at periodic time intervals has been replaced by an LRU that discards the oldest string when the capacity of the cache is reached. Signed-off-by: Wendi Yu <wendi.yu@yahoo.ca>
134 lines
3.8 KiB
Go
134 lines
3.8 KiB
Go
// 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.
|
|
|
|
// The tailscaled program is the Tailscale client daemon. It's configured
|
|
// and controlled via the tailscale CLI program.
|
|
//
|
|
// It primarily supports Linux, though other systems will likely be
|
|
// supported in the future.
|
|
package main // import "tailscale.com/cmd/tailscaled"
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"net/http"
|
|
"net/http/pprof"
|
|
"runtime"
|
|
|
|
"github.com/apenwarr/fixconsole"
|
|
"github.com/pborman/getopt/v2"
|
|
"tailscale.com/ipn/ipnserver"
|
|
"tailscale.com/logpolicy"
|
|
"tailscale.com/paths"
|
|
"tailscale.com/types/logger"
|
|
"tailscale.com/wgengine"
|
|
"tailscale.com/wgengine/magicsock"
|
|
)
|
|
|
|
// globalStateKey is the ipn.StateKey that tailscaled loads on
|
|
// startup.
|
|
//
|
|
// We have to support multiple state keys for other OSes (Windows in
|
|
// particular), but right now Unix daemons run with a single
|
|
// node-global state. To keep open the option of having per-user state
|
|
// later, the global state key doesn't look like a username.
|
|
const globalStateKey = "_daemon"
|
|
|
|
var defaultTunName = "tailscale0"
|
|
|
|
func init() {
|
|
if runtime.GOOS == "openbsd" {
|
|
defaultTunName = "tun"
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
fake := getopt.BoolLong("fake", 0, "fake tunnel+routing instead of tuntap")
|
|
debug := getopt.StringLong("debug", 0, "", "Address of debug server")
|
|
tunname := getopt.StringLong("tun", 0, defaultTunName, "tunnel interface name")
|
|
listenport := getopt.Uint16Long("port", 'p', magicsock.DefaultPort, "WireGuard port (0=autoselect)")
|
|
statepath := getopt.StringLong("state", 0, paths.DefaultTailscaledStateFile(), "Path of state file")
|
|
socketpath := getopt.StringLong("socket", 's', paths.DefaultTailscaledSocket(), "Path of the service unix socket")
|
|
|
|
logf := wgengine.RusagePrefixLog(log.Printf)
|
|
logf = logger.RateLimitedFn(logf, 1, 1, 100)
|
|
|
|
err := fixconsole.FixConsoleIfNeeded()
|
|
if err != nil {
|
|
logf("fixConsoleOutput: %v", err)
|
|
}
|
|
pol := logpolicy.New("tailnode.log.tailscale.io")
|
|
|
|
getopt.Parse()
|
|
if len(getopt.Args()) > 0 {
|
|
log.Fatalf("too many non-flag arguments: %#v", getopt.Args()[0])
|
|
}
|
|
|
|
if *statepath == "" {
|
|
log.Fatalf("--state is required")
|
|
}
|
|
|
|
if *socketpath == "" {
|
|
log.Fatalf("--socket is required")
|
|
}
|
|
|
|
var debugMux *http.ServeMux
|
|
if *debug != "" {
|
|
debugMux = newDebugMux()
|
|
go runDebugServer(debugMux, *debug)
|
|
}
|
|
|
|
var e wgengine.Engine
|
|
if *fake {
|
|
e, err = wgengine.NewFakeUserspaceEngine(logf, 0)
|
|
} else {
|
|
e, err = wgengine.NewUserspaceEngine(logf, *tunname, *listenport)
|
|
}
|
|
if err != nil {
|
|
log.Fatalf("wgengine.New: %v", err)
|
|
}
|
|
e = wgengine.NewWatchdog(e)
|
|
|
|
opts := ipnserver.Options{
|
|
SocketPath: *socketpath,
|
|
Port: 41112,
|
|
StatePath: *statepath,
|
|
AutostartStateKey: globalStateKey,
|
|
LegacyConfigPath: paths.LegacyConfigPath,
|
|
SurviveDisconnects: true,
|
|
DebugMux: debugMux,
|
|
}
|
|
err = ipnserver.Run(context.Background(), logf, pol.PublicID.String(), opts, e)
|
|
if err != nil {
|
|
log.Fatalf("tailscaled: %v", err)
|
|
}
|
|
|
|
// TODO(crawshaw): It would be nice to start a timeout context the moment a signal
|
|
// is received and use that timeout to give us a moment to finish uploading logs
|
|
// here. But the signal is handled inside ipnserver.Run, so some plumbing is needed.
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
pol.Shutdown(ctx)
|
|
}
|
|
|
|
func newDebugMux() *http.ServeMux {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
|
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
|
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
|
return mux
|
|
}
|
|
|
|
func runDebugServer(mux *http.ServeMux, addr string) {
|
|
srv := &http.Server{
|
|
Addr: addr,
|
|
Handler: mux,
|
|
}
|
|
if err := srv.ListenAndServe(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|