|
|
|
|
@ -503,12 +503,80 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() ( |
|
|
|
|
}() |
|
|
|
|
logf("Listening on %v", listen.Addr()) |
|
|
|
|
|
|
|
|
|
var store ipn.StateStore |
|
|
|
|
if opts.StatePath != "" { |
|
|
|
|
store, err = ipn.NewFileStore(opts.StatePath) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("ipn.NewFileStore(%q): %v", opts.StatePath, err) |
|
|
|
|
} |
|
|
|
|
if opts.AutostartStateKey == "" { |
|
|
|
|
autoStartKey, err := store.ReadState(ipn.ServerModeStartKey) |
|
|
|
|
if err != nil && err != ipn.ErrStateNotExist { |
|
|
|
|
return fmt.Errorf("calling ReadState on %s: %w", opts.StatePath, err) |
|
|
|
|
} |
|
|
|
|
key := string(autoStartKey) |
|
|
|
|
if strings.HasPrefix(key, "user-") { |
|
|
|
|
uid := strings.TrimPrefix(key, "user-") |
|
|
|
|
u, err := server.lookupUserFromID(uid) |
|
|
|
|
if err != nil { |
|
|
|
|
logf("ipnserver: found server mode auto-start key %q; failed to load user: %v", key, err) |
|
|
|
|
} else { |
|
|
|
|
logf("ipnserver: found server mode auto-start key %q (user %s)", key, u.Username) |
|
|
|
|
server.serverModeUser = u |
|
|
|
|
} |
|
|
|
|
opts.AutostartStateKey = ipn.StateKey(key) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
store = &ipn.MemoryStore{} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bo := backoff.NewBackoff("ipnserver", logf, 30*time.Second) |
|
|
|
|
var unservedConn net.Conn // if non-nil, accepted, but hasn't served yet
|
|
|
|
|
|
|
|
|
|
eng, err := getEngine() |
|
|
|
|
if err != nil { |
|
|
|
|
logf("ipnserver: initial getEngine call: %v", err) |
|
|
|
|
|
|
|
|
|
// Issue 1187: on Windows, in unattended mode,
|
|
|
|
|
// sometimes we try 5 times and fail to create the
|
|
|
|
|
// engine before the system's ready. Hack until the
|
|
|
|
|
// bug if fixed properly: if we're running in
|
|
|
|
|
// unattended mode on Windows, keep trying forever,
|
|
|
|
|
// waiting for the machine to be ready (networking to
|
|
|
|
|
// come up?) and then dial our own safesocket TCP
|
|
|
|
|
// listener to wake up the usual mechanism that lets
|
|
|
|
|
// us surface getEngine errors to UI clients. (We
|
|
|
|
|
// don't want to just call getEngine in a loop without
|
|
|
|
|
// the listener.Accept, as we do want to handle client
|
|
|
|
|
// connections so we can tell them about errors)
|
|
|
|
|
|
|
|
|
|
bootRaceWaitForEngine, bootRaceWaitForEngineCancel := context.WithTimeout(context.Background(), time.Minute) |
|
|
|
|
if runtime.GOOS == "windows" && opts.AutostartStateKey != "" { |
|
|
|
|
logf("ipnserver: in unattended mode, waiting for engine availability") |
|
|
|
|
getEngine = getEngineUntilItWorksWrapper(getEngine) |
|
|
|
|
// Wait for it to be ready.
|
|
|
|
|
go func() { |
|
|
|
|
defer bootRaceWaitForEngineCancel() |
|
|
|
|
t0 := time.Now() |
|
|
|
|
for { |
|
|
|
|
time.Sleep(10 * time.Second) |
|
|
|
|
if _, err := getEngine(); err != nil { |
|
|
|
|
logf("ipnserver: unattended mode engine load: %v", err) |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
c, err := net.Dial("tcp", listen.Addr().String()) |
|
|
|
|
logf("ipnserver: engine created after %v; waking up Accept: Dial error: %v", time.Since(t0).Round(time.Second), err) |
|
|
|
|
if err == nil { |
|
|
|
|
c.Close() |
|
|
|
|
} |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
} else { |
|
|
|
|
bootRaceWaitForEngineCancel() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for i := 1; ctx.Err() == nil; i++ { |
|
|
|
|
c, err := listen.Accept() |
|
|
|
|
if err != nil { |
|
|
|
|
@ -516,6 +584,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() ( |
|
|
|
|
bo.BackOff(ctx, err) |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
<-bootRaceWaitForEngine.Done() |
|
|
|
|
logf("ipnserver: try%d: trying getEngine again...", i) |
|
|
|
|
eng, err = getEngine() |
|
|
|
|
if err == nil { |
|
|
|
|
@ -538,34 +607,6 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() ( |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var store ipn.StateStore |
|
|
|
|
if opts.StatePath != "" { |
|
|
|
|
store, err = ipn.NewFileStore(opts.StatePath) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("ipn.NewFileStore(%q): %v", opts.StatePath, err) |
|
|
|
|
} |
|
|
|
|
if opts.AutostartStateKey == "" { |
|
|
|
|
autoStartKey, err := store.ReadState(ipn.ServerModeStartKey) |
|
|
|
|
if err != nil && err != ipn.ErrStateNotExist { |
|
|
|
|
return fmt.Errorf("calling ReadState on %s: %w", opts.StatePath, err) |
|
|
|
|
} |
|
|
|
|
key := string(autoStartKey) |
|
|
|
|
if strings.HasPrefix(key, "user-") { |
|
|
|
|
uid := strings.TrimPrefix(key, "user-") |
|
|
|
|
u, err := server.lookupUserFromID(uid) |
|
|
|
|
if err != nil { |
|
|
|
|
logf("ipnserver: found server mode auto-start key %q; failed to load user: %v", key, err) |
|
|
|
|
} else { |
|
|
|
|
logf("ipnserver: found server mode auto-start key %q (user %s)", key, u.Username) |
|
|
|
|
server.serverModeUser = u |
|
|
|
|
} |
|
|
|
|
opts.AutostartStateKey = ipn.StateKey(key) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
store = &ipn.MemoryStore{} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
b, err := ipn.NewLocalBackend(logf, logid, store, eng) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("NewLocalBackend: %v", err) |
|
|
|
|
@ -756,6 +797,27 @@ func FixedEngine(eng wgengine.Engine) func() (wgengine.Engine, error) { |
|
|
|
|
return func() (wgengine.Engine, error) { return eng, nil } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// getEngineUntilItWorksWrapper returns a getEngine wrapper that does
|
|
|
|
|
// not call getEngine concurrently and stops calling getEngine once
|
|
|
|
|
// it's returned a working engine.
|
|
|
|
|
func getEngineUntilItWorksWrapper(getEngine func() (wgengine.Engine, error)) func() (wgengine.Engine, error) { |
|
|
|
|
var mu sync.Mutex |
|
|
|
|
var engGood wgengine.Engine |
|
|
|
|
return func() (wgengine.Engine, error) { |
|
|
|
|
mu.Lock() |
|
|
|
|
defer mu.Unlock() |
|
|
|
|
if engGood != nil { |
|
|
|
|
return engGood, nil |
|
|
|
|
} |
|
|
|
|
e, err := getEngine() |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
engGood = e |
|
|
|
|
return e, nil |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type dummyAddr string |
|
|
|
|
type oneConnListener struct { |
|
|
|
|
conn net.Conn |
|
|
|
|
|