@ -8,9 +8,7 @@ package main // import "tailscale.com/cmd/tailscale"
import (
import (
"context"
"context"
"encoding/json"
"fmt"
"fmt"
"io/ioutil"
"log"
"log"
"net"
"net"
"os"
"os"
@ -19,12 +17,20 @@ import (
"github.com/apenwarr/fixconsole"
"github.com/apenwarr/fixconsole"
"github.com/pborman/getopt/v2"
"github.com/pborman/getopt/v2"
"tailscale.com/atomicfile"
"tailscale.com/ipn"
"tailscale.com/ipn"
"tailscale.com/logpolicy"
"tailscale.com/logpolicy"
"tailscale.com/safesocket"
"tailscale.com/safesocket"
)
)
// 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"
// pump receives backend messages on conn and pushes them into bc.
// pump receives backend messages on conn and pushes them into bc.
func pump ( ctx context . Context , bc * ipn . BackendClient , conn net . Conn ) {
func pump ( ctx context . Context , bc * ipn . BackendClient , conn net . Conn ) {
defer log . Printf ( "Control connection done.\n" )
defer log . Printf ( "Control connection done.\n" )
@ -45,34 +51,26 @@ func main() {
log . Printf ( "fixConsoleOutput: %v\n" , err )
log . Printf ( "fixConsoleOutput: %v\n" , err )
}
}
config := getopt . StringLong ( "config" , 'f' , "" , "path to config file" )
server := getopt . StringLong ( "server" , 's' , "https://login.tailscale.com" , "URL to tailcontrol server" )
server := getopt . StringLong ( "server" , 's' , "https://login.tailscale.com" , "URL to tailcontrol server" )
nuroutes := getopt . BoolLong ( "no-single-routes" , 'N' , "disallow (non-subnet) routes to single nodes" )
nuroutes := getopt . BoolLong ( "no-single-routes" , 'N' , "disallow (non-subnet) routes to single nodes" )
r routes := getopt . BoolLong ( "remote-routes" , 'R' , "a llow routing subnets to remote nodes")
r outeall := getopt . BoolLong ( "remote-routes" , 'R' , "a ccept routes advertised by remote nodes")
droutes := getopt . BoolLong ( "default-routes" , 'D' , "allow default route on remote node ")
nopf := getopt . BoolLong ( "no-packet-filter" , 'F' , "disable packet filter ")
getopt . Parse ( )
getopt . Parse ( )
if * config == "" {
pol := logpolicy . New ( "tailnode.log.tailscale.io" , "tailscale" )
logpolicy . New ( "tailnode.log.tailscale.io" , "tailscale" )
log . Fatal ( "no --config provided" )
}
if len ( getopt . Args ( ) ) > 0 {
if len ( getopt . Args ( ) ) > 0 {
log . Fatalf ( "too many non-flag arguments: %#v" , getopt . Args ( ) [ 0 ] )
log . Fatalf ( "too many non-flag arguments: %#v" , getopt . Args ( ) [ 0 ] )
}
}
pol := logpolicy . New ( "tailnode.log.tailscale.io" , * config )
defer pol . Close ( )
defer pol . Close ( )
localCfg , err := loadConfig ( * config )
if err != nil {
log . Fatal ( err )
}
// TODO(apenwarr): fix different semantics between prefs and uflags
// TODO(apenwarr): fix different semantics between prefs and uflags
// TODO(apenwarr): allow setting/using CorpDNS
// TODO(apenwarr): allow setting/using CorpDNS
prefs := & localCfg
prefs := ipn . Prefs {
prefs . WantRunning = true
WantRunning : true ,
prefs . RouteAll = * rroutes || * droutes
RouteAll : * routeall ,
prefs . AllowSingleHosts = ! * nuroutes
AllowSingleHosts : ! * nuroutes ,
UsePacketFilter : ! * nopf ,
}
c , err := safesocket . Connect ( "" , "Tailscale" , "tailscaled" , 41112 )
c , err := safesocket . Connect ( "" , "Tailscale" , "tailscaled" , 41112 )
if err != nil {
if err != nil {
@ -83,6 +81,7 @@ func main() {
}
}
ctx , cancel := context . WithCancel ( context . Background ( ) )
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
go func ( ) {
go func ( ) {
interrupt := make ( chan os . Signal , 1 )
interrupt := make ( chan os . Signal , 1 )
@ -92,11 +91,11 @@ func main() {
} ( )
} ( )
bc := ipn . NewBackendClient ( log . Printf , clientToServer )
bc := ipn . NewBackendClient ( log . Printf , clientToServer )
bc . SetPrefs ( prefs )
opts := ipn . Options {
opts := ipn . Options {
Prefs: prefs ,
StateKey: globalStateKey ,
ServerURL : * server ,
ServerURL : * server ,
Notify : func ( n ipn . Notify ) {
Notify : func ( n ipn . Notify ) {
log . Printf ( "Notify: %v\n" , n )
if n . ErrMessage != nil {
if n . ErrMessage != nil {
log . Fatalf ( "backend error: %v\n" , * n . ErrMessage )
log . Fatalf ( "backend error: %v\n" , * n . ErrMessage )
}
}
@ -108,41 +107,22 @@ func main() {
fmt . Fprintf ( os . Stderr , "\nTo authorize your machine, visit (as admin):\n\n\t%s/admin/machines\n\n" , * server )
fmt . Fprintf ( os . Stderr , "\nTo authorize your machine, visit (as admin):\n\n\t%s/admin/machines\n\n" , * server )
case ipn . Starting , ipn . Running :
case ipn . Starting , ipn . Running :
// Done full authentication process
// Done full authentication process
fmt . Fprintf ( os . Stderr , "\ntailscaled is authenticated, nothing more to do.\n\n" )
cancel ( )
cancel ( )
}
}
}
}
if url := n . BrowseToURL ; url != nil {
if url := n . BrowseToURL ; url != nil {
fmt . Fprintf ( os . Stderr , "\nTo authenticate, visit:\n\n\t%s\n\n" , * url )
fmt . Fprintf ( os . Stderr , "\nTo authenticate, visit:\n\n\t%s\n\n" , * url )
}
}
if p := n . Prefs ; p != nil {
prefs = p
saveConfig ( * config , * p )
}
} ,
} ,
}
}
// We still have to Start right now because it's the only way to
// set up notifications and whatnot. This causes a bunch of churn
// every time the CLI touches anything.
//
// TODO(danderson): redo the frontend/backend API to assume
// ephemeral frontends that read/modify/write state, once
// Windows/Mac state is moved into backend.
bc . Start ( opts )
bc . Start ( opts )
pump ( ctx , bc , c )
pump ( ctx , bc , c )
}
}
func loadConfig ( path string ) ( ipn . Prefs , error ) {
b , err := ioutil . ReadFile ( path )
if os . IsNotExist ( err ) {
log . Printf ( "config %s does not exist" , path )
return ipn . NewPrefs ( ) , nil
}
return ipn . PrefsFromBytes ( b , false )
}
func saveConfig ( path string , prefs ipn . Prefs ) error {
if path == "" {
return nil
}
b , err := json . MarshalIndent ( prefs , "" , "\t" )
if err != nil {
return fmt . Errorf ( "save config: %v" , err )
}
if err := atomicfile . WriteFile ( path , b , 0666 ) ; err != nil {
return fmt . Errorf ( "save config: %v" , err )
}
return nil
}