@ -7,6 +7,7 @@ package testcontrol
import (
import (
"bytes"
"bytes"
"context"
crand "crypto/rand"
crand "crypto/rand"
"encoding/binary"
"encoding/binary"
"encoding/json"
"encoding/json"
@ -46,6 +47,7 @@ type Server struct {
mux * http . ServeMux
mux * http . ServeMux
mu sync . Mutex
mu sync . Mutex
cond * sync . Cond // lazily initialized by condLocked
pubKey wgkey . Key
pubKey wgkey . Key
privKey wgkey . Private
privKey wgkey . Private
nodes map [ tailcfg . NodeKey ] * tailcfg . Node
nodes map [ tailcfg . NodeKey ] * tailcfg . Node
@ -68,6 +70,47 @@ func (s *Server) NumNodes() int {
return len ( s . nodes )
return len ( s . nodes )
}
}
// condLocked lazily initializes and returns s.cond.
// s.mu must be held.
func ( s * Server ) condLocked ( ) * sync . Cond {
if s . cond == nil {
s . cond = sync . NewCond ( & s . mu )
}
return s . cond
}
// AwaitNodeInMapRequest waits for node k to be stuck in a map poll.
// It returns an error if and only if the context is done first.
func ( s * Server ) AwaitNodeInMapRequest ( ctx context . Context , k tailcfg . NodeKey ) error {
s . mu . Lock ( )
defer s . mu . Unlock ( )
cond := s . condLocked ( )
done := make ( chan struct { } )
defer close ( done )
go func ( ) {
select {
case <- done :
case <- ctx . Done ( ) :
cond . Broadcast ( )
}
} ( )
for {
node := s . nodeLocked ( k )
if node == nil {
return errors . New ( "unknown node key" )
}
if _ , ok := s . updates [ node . ID ] ; ok {
return nil
}
cond . Wait ( )
if err := ctx . Err ( ) ; err != nil {
return err
}
}
}
// AddPingRequest sends the ping pr to nodeKeyDst. It reports whether it did so. That is,
// AddPingRequest sends the ping pr to nodeKeyDst. It reports whether it did so. That is,
// it reports whether nodeKeyDst was connected.
// it reports whether nodeKeyDst was connected.
func ( s * Server ) AddPingRequest ( nodeKeyDst tailcfg . NodeKey , pr * tailcfg . PingRequest ) bool {
func ( s * Server ) AddPingRequest ( nodeKeyDst tailcfg . NodeKey , pr * tailcfg . PingRequest ) bool {
@ -85,8 +128,7 @@ func (s *Server) AddPingRequest(nodeKeyDst tailcfg.NodeKey, pr *tailcfg.PingRequ
s . pingReqsToAdd [ nodeKeyDst ] = pr
s . pingReqsToAdd [ nodeKeyDst ] = pr
nodeID := node . ID
nodeID := node . ID
oldUpdatesCh := s . updates [ nodeID ]
oldUpdatesCh := s . updates [ nodeID ]
sendUpdate ( oldUpdatesCh , updateDebugInjection )
return sendUpdate ( oldUpdatesCh , updateDebugInjection )
return true
}
}
type AuthPath struct {
type AuthPath struct {
@ -414,17 +456,19 @@ func (s *Server) updateLocked(source string, peers []tailcfg.NodeID) {
}
}
// sendUpdate sends updateType to dst if dst is non-nil and
// sendUpdate sends updateType to dst if dst is non-nil and
// has capacity.
// has capacity. It reports whether a value was sent.
func sendUpdate ( dst chan <- updateType , updateType updateType ) {
func sendUpdate ( dst chan <- updateType , updateType updateType ) bool {
if dst == nil {
if dst == nil {
return
return false
}
}
// The dst channel has a buffer size of 1.
// The dst channel has a buffer size of 1.
// If we fail to insert an update into the buffer that
// If we fail to insert an update into the buffer that
// means there is already an update pending.
// means there is already an update pending.
select {
select {
case dst <- updateType :
case dst <- updateType :
return true
default :
default :
return false
}
}
}
}
@ -489,6 +533,7 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey tailcfg.M
sendUpdate ( oldUpdatesCh , updateSelfChanged )
sendUpdate ( oldUpdatesCh , updateSelfChanged )
}
}
s . updateLocked ( "serveMap" , peersToUpdate )
s . updateLocked ( "serveMap" , peersToUpdate )
s . condLocked ( ) . Broadcast ( )
s . mu . Unlock ( )
s . mu . Unlock ( )
// ReadOnly implies no streaming, as it doesn't
// ReadOnly implies no streaming, as it doesn't