|
|
|
@ -60,6 +60,10 @@ type Options struct {
|
|
|
|
|
// frontend disconnecting. If true, the server keeps running on
|
|
|
|
|
// its existing state, and accepts new frontend connections. If
|
|
|
|
|
// false, the server dumps its state and becomes idle.
|
|
|
|
|
//
|
|
|
|
|
// To support CLI connections (notably, "tailscale status"),
|
|
|
|
|
// the actual definition of "disconnect" is when the
|
|
|
|
|
// connection count transitions from 1 to 0.
|
|
|
|
|
SurviveDisconnects bool
|
|
|
|
|
|
|
|
|
|
// DebugMux, if non-nil, specifies an HTTP ServeMux in which
|
|
|
|
@ -71,24 +75,84 @@ type Options struct {
|
|
|
|
|
ErrorMessage string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func pump(logf logger.Logf, ctx context.Context, bs *ipn.BackendServer, s net.Conn) {
|
|
|
|
|
defer logf("Control connection done.")
|
|
|
|
|
// server is an IPN backend and its set of 0 or more active connections
|
|
|
|
|
// talking to an IPN backend.
|
|
|
|
|
type server struct {
|
|
|
|
|
resetOnZero bool // call bs.Reset on transition from 1->0 connections
|
|
|
|
|
|
|
|
|
|
bsMu sync.Mutex // lock order: bsMu, then mu
|
|
|
|
|
bs *ipn.BackendServer
|
|
|
|
|
|
|
|
|
|
for ctx.Err() == nil && !bs.GotQuit {
|
|
|
|
|
msg, err := ipn.ReadMsg(s)
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
clients map[net.Conn]bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) {
|
|
|
|
|
s.addConn(c)
|
|
|
|
|
logf("incoming control connection")
|
|
|
|
|
defer s.removeAndCloseConn(c)
|
|
|
|
|
for ctx.Err() == nil {
|
|
|
|
|
msg, err := ipn.ReadMsg(c)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logf("ReadMsg: %v", err)
|
|
|
|
|
break
|
|
|
|
|
if ctx.Err() == nil {
|
|
|
|
|
logf("ReadMsg: %v", err)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
err = bs.GotCommandMsg(msg)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.bsMu.Lock()
|
|
|
|
|
if err := s.bs.GotCommandMsg(msg); err != nil {
|
|
|
|
|
logf("GotCommandMsg: %v", err)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
gotQuit := s.bs.GotQuit
|
|
|
|
|
s.bsMu.Unlock()
|
|
|
|
|
if gotQuit {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e wgengine.Engine) error {
|
|
|
|
|
func (s *server) addConn(c net.Conn) {
|
|
|
|
|
s.mu.Lock()
|
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
if s.clients == nil {
|
|
|
|
|
s.clients = map[net.Conn]bool{}
|
|
|
|
|
}
|
|
|
|
|
s.clients[c] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *server) removeAndCloseConn(c net.Conn) {
|
|
|
|
|
s.mu.Lock()
|
|
|
|
|
delete(s.clients, c)
|
|
|
|
|
remain := len(s.clients)
|
|
|
|
|
s.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
if remain == 0 && s.resetOnZero {
|
|
|
|
|
s.bsMu.Lock()
|
|
|
|
|
s.bs.Reset()
|
|
|
|
|
s.bsMu.Unlock()
|
|
|
|
|
}
|
|
|
|
|
c.Close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *server) stopAll() {
|
|
|
|
|
s.mu.Lock()
|
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
for c := range s.clients {
|
|
|
|
|
safesocket.ConnCloseRead(c)
|
|
|
|
|
safesocket.ConnCloseWrite(c)
|
|
|
|
|
}
|
|
|
|
|
s.clients = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *server) writeToClients(b []byte) {
|
|
|
|
|
s.mu.Lock()
|
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
for c := range s.clients {
|
|
|
|
|
ipn.WriteMsg(c, b)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Run(ctx context.Context, logf logger.Logf, logid string, opts Options, e wgengine.Engine) error {
|
|
|
|
|
runDone := make(chan struct{})
|
|
|
|
|
defer close(runDone)
|
|
|
|
|
|
|
|
|
@ -97,12 +161,18 @@ func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e w
|
|
|
|
|
return fmt.Errorf("safesocket.Listen: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Go listeners can't take a context, close it instead.
|
|
|
|
|
server := &server{
|
|
|
|
|
resetOnZero: !opts.SurviveDisconnects,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// When the context is closed or when we return, whichever is first, close our listner
|
|
|
|
|
// and all open connections.
|
|
|
|
|
go func() {
|
|
|
|
|
select {
|
|
|
|
|
case <-rctx.Done():
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
case <-runDone:
|
|
|
|
|
}
|
|
|
|
|
server.stopAll()
|
|
|
|
|
listen.Close()
|
|
|
|
|
}()
|
|
|
|
|
logf("Listening on %v", listen.Addr())
|
|
|
|
@ -110,11 +180,11 @@ func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e w
|
|
|
|
|
bo := backoff.NewBackoff("ipnserver", logf)
|
|
|
|
|
|
|
|
|
|
if opts.ErrorMessage != "" {
|
|
|
|
|
for i := 1; rctx.Err() == nil; i++ {
|
|
|
|
|
for i := 1; ctx.Err() == nil; i++ {
|
|
|
|
|
s, err := listen.Accept()
|
|
|
|
|
if err != nil {
|
|
|
|
|
logf("%d: Accept: %v", i, err)
|
|
|
|
|
bo.BackOff(rctx, err)
|
|
|
|
|
bo.BackOff(ctx, err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
serverToClient := func(b []byte) {
|
|
|
|
@ -127,7 +197,7 @@ func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e w
|
|
|
|
|
s.Read(make([]byte, 1))
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
return rctx.Err()
|
|
|
|
|
return ctx.Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var store ipn.StateStore
|
|
|
|
@ -144,6 +214,7 @@ func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e w
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("NewLocalBackend: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer b.Shutdown()
|
|
|
|
|
b.SetDecompressor(func() (controlclient.Decompressor, error) {
|
|
|
|
|
return smallzstd.NewDecoder(nil)
|
|
|
|
|
})
|
|
|
|
@ -157,17 +228,10 @@ func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e w
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var s net.Conn
|
|
|
|
|
serverToClient := func(b []byte) {
|
|
|
|
|
if s != nil { // TODO: racy access to s?
|
|
|
|
|
ipn.WriteMsg(s, b)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bs := ipn.NewBackendServer(logf, b, serverToClient)
|
|
|
|
|
server.bs = ipn.NewBackendServer(logf, b, server.writeToClients)
|
|
|
|
|
|
|
|
|
|
if opts.AutostartStateKey != "" {
|
|
|
|
|
bs.GotCommand(&ipn.Command{
|
|
|
|
|
server.bs.GotCommand(&ipn.Command{
|
|
|
|
|
Version: version.LONG,
|
|
|
|
|
Start: &ipn.StartArgs{
|
|
|
|
|
Opts: ipn.Options{
|
|
|
|
@ -178,54 +242,18 @@ func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e w
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
oldS net.Conn
|
|
|
|
|
ctx context.Context
|
|
|
|
|
cancel context.CancelFunc
|
|
|
|
|
)
|
|
|
|
|
stopAll := func() {
|
|
|
|
|
// Currently we only support one client connection at a time.
|
|
|
|
|
// Theoretically we could allow multiple clients, by passing
|
|
|
|
|
// notifications to all of them and accepting commands from
|
|
|
|
|
// any of them, but there doesn't seem to be much need for
|
|
|
|
|
// that right now.
|
|
|
|
|
if oldS != nil {
|
|
|
|
|
cancel()
|
|
|
|
|
safesocket.ConnCloseRead(oldS)
|
|
|
|
|
safesocket.ConnCloseWrite(oldS)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 1; rctx.Err() == nil; i++ {
|
|
|
|
|
s, err = listen.Accept()
|
|
|
|
|
for i := 1; ctx.Err() == nil; i++ {
|
|
|
|
|
c, err := listen.Accept()
|
|
|
|
|
if err != nil {
|
|
|
|
|
logf("%d: Accept: %v", i, err)
|
|
|
|
|
bo.BackOff(rctx, err)
|
|
|
|
|
if ctx.Err() == nil {
|
|
|
|
|
logf("ipnserver: Accept: %v", err)
|
|
|
|
|
bo.BackOff(ctx, err)
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
logf("%d: Incoming control connection.", i)
|
|
|
|
|
stopAll()
|
|
|
|
|
|
|
|
|
|
ctx, cancel = context.WithCancel(rctx)
|
|
|
|
|
oldS = s
|
|
|
|
|
|
|
|
|
|
go func(ctx context.Context, s net.Conn, i int) {
|
|
|
|
|
logf := logger.WithPrefix(logf, fmt.Sprintf("%d: ", i))
|
|
|
|
|
pump(logf, ctx, bs, s)
|
|
|
|
|
if !opts.SurviveDisconnects || bs.GotQuit {
|
|
|
|
|
bs.Reset()
|
|
|
|
|
s.Close()
|
|
|
|
|
}
|
|
|
|
|
// Quitting not allowed, just keep going.
|
|
|
|
|
bs.GotQuit = false
|
|
|
|
|
}(ctx, s, i)
|
|
|
|
|
|
|
|
|
|
bo.BackOff(ctx, nil)
|
|
|
|
|
go server.serveConn(ctx, c, logger.WithPrefix(logf, fmt.Sprintf("ipnserver: conn%d: ", i)))
|
|
|
|
|
}
|
|
|
|
|
stopAll()
|
|
|
|
|
|
|
|
|
|
b.Shutdown()
|
|
|
|
|
return rctx.Err()
|
|
|
|
|
return ctx.Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BabysitProc(ctx context.Context, args []string, logf logger.Logf) {
|
|
|
|
|