2023-05-10 07:24:05 +00:00
package hscontrol
2021-08-13 09:33:50 +00:00
import (
2021-12-31 19:51:20 +00:00
"context"
"fmt"
2021-08-18 22:24:22 +00:00
"net/http"
2024-02-08 16:28:19 +00:00
"strings"
2021-08-13 09:33:50 +00:00
"time"
2024-02-08 16:28:19 +00:00
"github.com/juanfont/headscale/hscontrol/db"
2023-05-26 10:26:34 +00:00
"github.com/juanfont/headscale/hscontrol/mapper"
2023-05-21 16:37:59 +00:00
"github.com/juanfont/headscale/hscontrol/types"
2021-08-13 09:33:50 +00:00
"github.com/rs/zerolog/log"
2023-12-09 17:09:24 +00:00
xslices "golang.org/x/exp/slices"
2024-02-08 16:28:19 +00:00
"gorm.io/gorm"
2024-02-12 08:11:17 +00:00
"tailscale.com/envknob"
2021-08-13 09:33:50 +00:00
"tailscale.com/tailcfg"
)
2021-11-14 17:31:51 +00:00
const (
2022-07-12 10:27:28 +00:00
keepAliveInterval = 60 * time . Second
2021-11-14 17:31:51 +00:00
)
2022-05-16 12:59:46 +00:00
type contextKey string
2023-09-24 11:42:05 +00:00
const nodeNameContextKey = contextKey ( "nodeName" )
2022-05-16 12:59:46 +00:00
2023-06-21 09:29:52 +00:00
type UpdateNode func ( )
func logPollFunc (
mapRequest tailcfg . MapRequest ,
2023-09-24 11:42:05 +00:00
node * types . Node ,
2024-02-16 09:32:23 +00:00
) ( func ( string ) , func ( string ) , func ( error , string ) ) {
2023-06-21 09:29:52 +00:00
return func ( msg string ) {
2024-02-16 09:32:23 +00:00
log . Trace ( ) .
Caller ( ) .
Bool ( "readOnly" , mapRequest . ReadOnly ) .
Bool ( "omitPeers" , mapRequest . OmitPeers ) .
Bool ( "stream" , mapRequest . Stream ) .
Str ( "node_key" , node . NodeKey . ShortString ( ) ) .
Str ( "node" , node . Hostname ) .
Msg ( msg )
} ,
func ( msg string ) {
log . Warn ( ) .
2023-06-21 09:29:52 +00:00
Caller ( ) .
Bool ( "readOnly" , mapRequest . ReadOnly ) .
Bool ( "omitPeers" , mapRequest . OmitPeers ) .
Bool ( "stream" , mapRequest . Stream ) .
2023-11-19 21:37:04 +00:00
Str ( "node_key" , node . NodeKey . ShortString ( ) ) .
2023-09-24 11:42:05 +00:00
Str ( "node" , node . Hostname ) .
2023-06-21 09:29:52 +00:00
Msg ( msg )
} ,
func ( err error , msg string ) {
log . Error ( ) .
Caller ( ) .
Bool ( "readOnly" , mapRequest . ReadOnly ) .
Bool ( "omitPeers" , mapRequest . OmitPeers ) .
Bool ( "stream" , mapRequest . Stream ) .
2023-11-19 21:37:04 +00:00
Str ( "node_key" , node . NodeKey . ShortString ( ) ) .
2023-09-24 11:42:05 +00:00
Str ( "node" , node . Hostname ) .
2023-06-21 09:29:52 +00:00
Err ( err ) .
Msg ( msg )
}
}
2023-11-23 07:31:33 +00:00
// handlePoll ensures the node gets the appropriate updates from either
// polling or immediate responses.
2023-08-09 20:56:21 +00:00
//
//nolint:gocyclo
2023-06-06 15:14:56 +00:00
func ( h * Headscale ) handlePoll (
2022-06-26 10:06:25 +00:00
writer http . ResponseWriter ,
2022-09-04 13:14:12 +00:00
ctx context . Context ,
2023-09-24 11:42:05 +00:00
node * types . Node ,
2022-08-14 20:57:03 +00:00
mapRequest tailcfg . MapRequest ,
2022-06-20 10:30:51 +00:00
) {
2024-02-16 09:32:23 +00:00
logTrace , logWarn , logErr := logPollFunc ( mapRequest , node )
2023-09-11 11:18:31 +00:00
2023-12-09 17:09:24 +00:00
// This is the mechanism where the node gives us information about its
2023-09-11 16:45:46 +00:00
// current configuration.
//
2023-09-11 11:18:31 +00:00
// If OmitPeers is true, Stream is false, and ReadOnly is false,
// then te server will let clients update their endpoints without
// breaking existing long-polling (Stream == true) connections.
// In this case, the server can omit the entire response; the client
// only checks the HTTP response status code.
2023-12-09 17:09:24 +00:00
// TODO(kradalby): remove ReadOnly when we only support capVer 68+
2023-09-11 11:18:31 +00:00
if mapRequest . OmitPeers && ! mapRequest . Stream && ! mapRequest . ReadOnly {
log . Info ( ) .
Caller ( ) .
Bool ( "readOnly" , mapRequest . ReadOnly ) .
Bool ( "omitPeers" , mapRequest . OmitPeers ) .
Bool ( "stream" , mapRequest . Stream ) .
2023-11-19 21:37:04 +00:00
Str ( "node_key" , node . NodeKey . ShortString ( ) ) .
2023-09-24 11:42:05 +00:00
Str ( "node" , node . Hostname ) .
2023-11-23 07:31:33 +00:00
Int ( "cap_ver" , int ( mapRequest . Version ) ) .
2023-12-09 17:09:24 +00:00
Msg ( "Received update" )
2023-09-11 11:18:31 +00:00
2023-12-09 17:09:24 +00:00
change := node . PeerChangeFromMapRequest ( mapRequest )
2023-09-24 11:42:05 +00:00
2023-12-09 17:09:24 +00:00
online := h . nodeNotifier . IsConnected ( node . MachineKey )
change . Online = & online
2023-09-11 11:18:31 +00:00
2023-12-09 17:09:24 +00:00
node . ApplyPeerChange ( & change )
hostInfoChange := node . Hostinfo . Equal ( mapRequest . Hostinfo )
logTracePeerChange ( node . Hostname , hostInfoChange , & change )
// Check if the Hostinfo of the node has changed.
// If it has changed, check if there has been a change tod
// the routable IPs of the host and update update them in
// the database. Then send a Changed update
// (containing the whole node object) to peers to inform about
// the route change.
// If the hostinfo has changed, but not the routes, just update
// hostinfo and let the function continue.
if ! hostInfoChange {
oldRoutes := node . Hostinfo . RoutableIPs
newRoutes := mapRequest . Hostinfo . RoutableIPs
oldServicesCount := len ( node . Hostinfo . Services )
newServicesCount := len ( mapRequest . Hostinfo . Services )
node . Hostinfo = mapRequest . Hostinfo
sendUpdate := false
// Route changes come as part of Hostinfo, which means that
// when an update comes, the Node Route logic need to run.
// This will require a "change" in comparison to a "patch",
// which is more costly.
if ! xslices . Equal ( oldRoutes , newRoutes ) {
var err error
sendUpdate , err = h . db . SaveNodeRoutes ( node )
if err != nil {
logErr ( err , "Error processing node routes" )
http . Error ( writer , "" , http . StatusInternalServerError )
return
}
2024-01-18 15:36:47 +00:00
if h . ACLPolicy != nil {
// update routes with peer information
2024-02-08 16:28:19 +00:00
update , err := h . db . EnableAutoApprovedRoutes ( h . ACLPolicy , node )
2024-01-18 15:36:47 +00:00
if err != nil {
logErr ( err , "Error running auto approved routes" )
}
2024-02-08 16:28:19 +00:00
if update != nil {
sendUpdate = true
}
2024-01-18 15:36:47 +00:00
}
2023-12-09 17:09:24 +00:00
}
// Services is mostly useful for discovery and not critical,
// except for peerapi, which is how nodes talk to eachother.
// If peerapi was not part of the initial mapresponse, we
// need to make sure its sent out later as it is needed for
// Taildrop.
// TODO(kradalby): Length comparison is a bit naive, replace.
if oldServicesCount != newServicesCount {
sendUpdate = true
}
if sendUpdate {
2024-02-08 16:28:19 +00:00
if err := h . db . DB . Save ( node ) . Error ; err != nil {
2023-12-09 17:09:24 +00:00
logErr ( err , "Failed to persist/update node in the database" )
http . Error ( writer , "" , http . StatusInternalServerError )
return
}
2024-01-18 16:30:25 +00:00
// Send an update to all peers to propagate the new routes
// available.
2023-12-09 17:09:24 +00:00
stateUpdate := types . StateUpdate {
Type : types . StatePeerChanged ,
ChangeNodes : types . Nodes { node } ,
Message : "called from handlePoll -> update -> new hostinfo" ,
}
if stateUpdate . Valid ( ) {
2024-02-08 16:28:19 +00:00
ctx := types . NotifyCtx ( context . Background ( ) , "poll-nodeupdate-peers-hostinfochange" , node . Hostname )
2023-12-09 17:09:24 +00:00
h . nodeNotifier . NotifyWithIgnore (
2024-02-08 16:28:19 +00:00
ctx ,
2023-12-09 17:09:24 +00:00
stateUpdate ,
node . MachineKey . String ( ) )
}
2024-01-18 16:30:25 +00:00
// Send an update to the node itself with to ensure it
// has an updated packetfilter allowing the new route
// if it is defined in the ACL.
selfUpdate := types . StateUpdate {
Type : types . StateSelfUpdate ,
ChangeNodes : types . Nodes { node } ,
}
if selfUpdate . Valid ( ) {
2024-02-08 16:28:19 +00:00
ctx := types . NotifyCtx ( context . Background ( ) , "poll-nodeupdate-self-hostinfochange" , node . Hostname )
2024-01-18 16:30:25 +00:00
h . nodeNotifier . NotifyByMachineKey (
2024-02-08 16:28:19 +00:00
ctx ,
2024-01-18 16:30:25 +00:00
selfUpdate ,
node . MachineKey )
}
2023-12-09 17:09:24 +00:00
return
}
2023-09-11 11:18:31 +00:00
}
2024-02-08 16:28:19 +00:00
if err := h . db . DB . Save ( node ) . Error ; err != nil {
2023-12-09 17:09:24 +00:00
logErr ( err , "Failed to persist/update node in the database" )
2023-09-11 16:45:46 +00:00
http . Error ( writer , "" , http . StatusInternalServerError )
return
}
2023-12-09 17:09:24 +00:00
stateUpdate := types . StateUpdate {
2024-02-18 22:22:18 +00:00
Type : types . StatePeerChangedPatch ,
ChangePatches : [ ] * tailcfg . PeerChange { & change } ,
2023-12-09 17:09:24 +00:00
}
if stateUpdate . Valid ( ) {
2024-02-08 16:28:19 +00:00
ctx := types . NotifyCtx ( context . Background ( ) , "poll-nodeupdate-peers-patch" , node . Hostname )
2023-12-09 17:09:24 +00:00
h . nodeNotifier . NotifyWithIgnore (
2024-02-08 16:28:19 +00:00
ctx ,
2023-12-09 17:09:24 +00:00
stateUpdate ,
node . MachineKey . String ( ) )
}
2023-09-11 11:18:31 +00:00
writer . WriteHeader ( http . StatusOK )
if f , ok := writer . ( http . Flusher ) ; ok {
f . Flush ( )
}
return
2023-12-09 17:09:24 +00:00
} else if mapRequest . OmitPeers && ! mapRequest . Stream && mapRequest . ReadOnly {
2023-09-11 11:18:31 +00:00
// ReadOnly is whether the client just wants to fetch the
// MapResponse, without updating their Endpoints. The
// Endpoints field will be ignored and LastSeen will not be
// updated and peers will not be notified of changes.
//
// The intended use is for clients to discover the DERP map at
// start-up before their first real endpoint update.
} else if mapRequest . OmitPeers && ! mapRequest . Stream && mapRequest . ReadOnly {
2023-11-23 07:31:33 +00:00
h . handleLiteRequest ( writer , node , mapRequest )
2023-09-11 11:18:31 +00:00
return
} else if mapRequest . OmitPeers && mapRequest . Stream {
logErr ( nil , "Ignoring request, don't know how to handle it" )
return
2023-07-26 15:54:19 +00:00
}
2023-12-09 17:09:24 +00:00
change := node . PeerChangeFromMapRequest ( mapRequest )
// A stream is being set up, the node is Online
online := true
change . Online = & online
node . ApplyPeerChange ( & change )
// Only save HostInfo if changed, update routes if changed
// TODO(kradalby): Remove when capver is over 68
if ! node . Hostinfo . Equal ( mapRequest . Hostinfo ) {
oldRoutes := node . Hostinfo . RoutableIPs
newRoutes := mapRequest . Hostinfo . RoutableIPs
node . Hostinfo = mapRequest . Hostinfo
if ! xslices . Equal ( oldRoutes , newRoutes ) {
_ , err := h . db . SaveNodeRoutes ( node )
if err != nil {
logErr ( err , "Error processing node routes" )
http . Error ( writer , "" , http . StatusInternalServerError )
return
}
}
}
2024-02-08 16:28:19 +00:00
if err := h . db . DB . Save ( node ) . Error ; err != nil {
2023-12-09 17:09:24 +00:00
logErr ( err , "Failed to persist/update node in the database" )
http . Error ( writer , "" , http . StatusInternalServerError )
return
}
2023-06-21 09:29:52 +00:00
2024-02-12 08:11:17 +00:00
// Set up the client stream
h . pollNetMapStreamWG . Add ( 1 )
defer h . pollNetMapStreamWG . Done ( )
// Use a buffered channel in case a node is not fully ready
// to receive a message to make sure we dont block the entire
// notifier.
// 12 is arbitrarily chosen.
chanSize := 3
if size , ok := envknob . LookupInt ( "HEADSCALE_TUNING_POLL_QUEUE_SIZE" ) ; ok {
chanSize = size
}
updateChan := make ( chan types . StateUpdate , chanSize )
defer closeChanWithLog ( updateChan , node . Hostname , "updateChan" )
// Register the node's update channel
h . nodeNotifier . AddNode ( node . MachineKey , updateChan )
defer h . nodeNotifier . RemoveNode ( node . MachineKey )
2023-08-09 20:20:05 +00:00
// When a node connects to control, list the peers it has at
// that given point, further updates are kept in memory in
// the Mapper, which lives for the duration of the polling
// session.
2023-09-24 11:42:05 +00:00
peers , err := h . db . ListPeers ( node )
2023-08-09 20:20:05 +00:00
if err != nil {
logErr ( err , "Failed to list peers when opening poller" )
http . Error ( writer , "" , http . StatusInternalServerError )
return
}
2024-02-12 08:11:17 +00:00
isConnected := h . nodeNotifier . ConnectedMap ( )
2023-12-09 17:09:24 +00:00
for _ , peer := range peers {
2024-02-12 08:11:17 +00:00
online := isConnected [ peer . MachineKey ]
2023-12-09 17:09:24 +00:00
peer . IsOnline = & online
}
2023-06-22 08:01:17 +00:00
mapp := mapper . NewMapper (
2023-09-24 11:42:05 +00:00
node ,
2023-08-09 20:20:05 +00:00
peers ,
2023-05-26 10:26:34 +00:00
h . DERPMap ,
h . cfg . BaseDomain ,
h . cfg . DNSConfig ,
h . cfg . LogTail . Enabled ,
h . cfg . RandomizeClientPort ,
)
2022-02-06 16:55:12 +00:00
// update ACLRules with peer informations (to update server tags if necessary)
2023-05-21 16:37:59 +00:00
if h . ACLPolicy != nil {
2022-08-24 10:53:55 +00:00
// update routes with peer information
2024-02-08 16:28:19 +00:00
// This state update is ignored as it will be sent
// as part of the whole node
// TODO(kradalby): figure out if that is actually correct
_ , err = h . db . EnableAutoApprovedRoutes ( h . ACLPolicy , node )
2022-11-25 15:29:45 +00:00
if err != nil {
2023-06-21 09:29:52 +00:00
logErr ( err , "Error running auto approved routes" )
2022-11-25 15:29:45 +00:00
}
2022-02-06 16:55:12 +00:00
}
2022-08-24 10:53:55 +00:00
2024-02-16 09:32:23 +00:00
logTrace ( "Sending initial map" )
2021-08-18 22:24:22 +00:00
2023-09-24 11:42:05 +00:00
mapResp , err := mapp . FullMapResponse ( mapRequest , node , h . ACLPolicy )
2023-07-26 11:55:03 +00:00
if err != nil {
logErr ( err , "Failed to create MapResponse" )
http . Error ( writer , "" , http . StatusInternalServerError )
return
}
2023-06-21 09:29:52 +00:00
// Send the client an update to make sure we send an initial mapresponse
_ , err = writer . Write ( mapResp )
if err != nil {
logErr ( err , "Could not write the map response" )
return
}
if flusher , ok := writer . ( http . Flusher ) ; ok {
flusher . Flush ( )
} else {
return
}
2021-08-18 22:24:22 +00:00
2023-12-09 17:09:24 +00:00
stateUpdate := types . StateUpdate {
Type : types . StatePeerChanged ,
ChangeNodes : types . Nodes { node } ,
Message : "called from handlePoll -> new node added" ,
}
if stateUpdate . Valid ( ) {
2024-02-08 16:28:19 +00:00
ctx := types . NotifyCtx ( context . Background ( ) , "poll-newnode-peers" , node . Hostname )
2023-12-09 17:09:24 +00:00
h . nodeNotifier . NotifyWithIgnore (
2024-02-08 16:28:19 +00:00
ctx ,
2023-12-09 17:09:24 +00:00
stateUpdate ,
node . MachineKey . String ( ) )
}
2023-09-11 11:18:31 +00:00
2024-02-08 16:28:19 +00:00
if len ( node . Routes ) > 0 {
go h . pollFailoverRoutes ( logErr , "new node" , node )
}
2023-06-21 09:29:52 +00:00
keepAliveTicker := time . NewTicker ( keepAliveInterval )
2024-02-08 16:28:19 +00:00
ctx , cancel := context . WithCancel ( context . WithValue ( ctx , nodeNameContextKey , node . Hostname ) )
2022-06-20 10:30:51 +00:00
defer cancel ( )
2022-04-09 22:37:13 +00:00
2022-06-20 19:40:28 +00:00
for {
2024-02-16 09:32:23 +00:00
logTrace ( "Waiting for update on stream channel" )
2021-08-18 22:24:22 +00:00
select {
2023-06-21 09:29:52 +00:00
case <- keepAliveTicker . C :
2023-09-24 11:42:05 +00:00
data , err := mapp . KeepAliveResponse ( mapRequest , node )
2021-08-13 09:33:50 +00:00
if err != nil {
2023-06-21 09:29:52 +00:00
logErr ( err , "Error generating the keep alive msg" )
2021-11-14 15:46:09 +00:00
2022-06-20 19:40:28 +00:00
return
2021-08-13 09:33:50 +00:00
}
2023-06-21 09:29:52 +00:00
_ , err = writer . Write ( data )
if err != nil {
logErr ( err , "Cannot write keep alive message" )
2022-06-26 10:25:26 +00:00
2023-06-21 09:29:52 +00:00
return
2022-06-26 10:25:26 +00:00
}
2023-06-21 09:29:52 +00:00
if flusher , ok := writer . ( http . Flusher ) ; ok {
flusher . Flush ( )
} else {
2023-07-24 06:58:51 +00:00
log . Error ( ) . Msg ( "Failed to create http flusher" )
2022-06-20 19:40:28 +00:00
return
2021-08-21 15:52:19 +00:00
}
2021-10-04 16:28:07 +00:00
2023-09-11 11:18:31 +00:00
// This goroutine is not ideal, but we have a potential issue here
// where it blocks too long and that holds up updates.
// One alternative is to split these different channels into
// goroutines, but then you might have a problem without a lock
// if a keepalive is written at the same time as an update.
2023-12-09 17:09:24 +00:00
go h . updateNodeOnlineStatus ( true , node )
2021-11-14 15:46:09 +00:00
2023-06-29 10:20:22 +00:00
case update := <- updateChan :
2024-02-16 09:32:23 +00:00
logTrace ( "Received update" )
2023-09-11 11:18:31 +00:00
now := time . Now ( )
2023-07-24 06:58:51 +00:00
2023-06-29 10:20:22 +00:00
var data [ ] byte
var err error
2024-01-18 16:30:25 +00:00
// Ensure the node object is updated, for example, there
// might have been a hostinfo update in a sidechannel
// which contains data needed to generate a map response.
node , err = h . db . GetNodeByMachineKey ( node . MachineKey )
if err != nil {
logErr ( err , "Could not get machine from db" )
return
}
2024-02-08 16:28:19 +00:00
startMapResp := time . Now ( )
2023-06-29 10:20:22 +00:00
switch update . Type {
2023-12-09 17:09:24 +00:00
case types . StateFullUpdate :
2024-02-16 09:32:23 +00:00
logTrace ( "Sending Full MapResponse" )
2023-12-09 17:09:24 +00:00
data , err = mapp . FullMapResponse ( mapRequest , node , h . ACLPolicy )
2023-06-29 10:20:22 +00:00
case types . StatePeerChanged :
2024-02-16 09:32:23 +00:00
logTrace ( fmt . Sprintf ( "Sending Changed MapResponse: %s" , update . Message ) )
2023-12-09 17:09:24 +00:00
2024-02-08 16:28:19 +00:00
isConnectedMap := h . nodeNotifier . ConnectedMap ( )
2023-12-09 17:09:24 +00:00
for _ , node := range update . ChangeNodes {
// If a node is not reported to be online, it might be
// because the value is outdated, check with the notifier.
// However, if it is set to Online, and not in the notifier,
// this might be because it has announced itself, but not
// reached the stage to actually create the notifier channel.
if node . IsOnline != nil && ! * node . IsOnline {
2024-02-08 16:28:19 +00:00
isOnline := isConnectedMap [ node . MachineKey ]
2023-12-09 17:09:24 +00:00
node . IsOnline = & isOnline
}
}
data , err = mapp . PeerChangedResponse ( mapRequest , node , update . ChangeNodes , h . ACLPolicy , update . Message )
case types . StatePeerChangedPatch :
2024-02-16 09:32:23 +00:00
logTrace ( "Sending PeerChangedPatch MapResponse" )
2023-12-09 17:09:24 +00:00
data , err = mapp . PeerChangedPatchResponse ( mapRequest , node , update . ChangePatches , h . ACLPolicy )
2023-06-29 10:20:22 +00:00
case types . StatePeerRemoved :
2024-02-16 09:32:23 +00:00
logTrace ( "Sending PeerRemoved MapResponse" )
2023-09-24 11:42:05 +00:00
data , err = mapp . PeerRemovedResponse ( mapRequest , node , update . Removed )
2024-01-05 09:41:56 +00:00
case types . StateSelfUpdate :
if len ( update . ChangeNodes ) == 1 {
2024-02-16 09:32:23 +00:00
logTrace ( "Sending SelfUpdate MapResponse" )
2024-01-05 09:41:56 +00:00
node = update . ChangeNodes [ 0 ]
2024-02-08 16:28:19 +00:00
data , err = mapp . LiteMapResponse ( mapRequest , node , h . ACLPolicy , types . SelfUpdateIdentifier )
2024-01-05 09:41:56 +00:00
} else {
2024-02-16 09:32:23 +00:00
logWarn ( "SelfUpdate contained too many nodes, this is likely a bug in the code, please report." )
2024-01-05 09:41:56 +00:00
}
2023-06-29 10:20:22 +00:00
case types . StateDERPUpdated :
2024-02-16 09:32:23 +00:00
logTrace ( "Sending DERPUpdate MapResponse" )
2023-09-24 11:42:05 +00:00
data , err = mapp . DERPMapResponse ( mapRequest , node , update . DERPMap )
2023-06-29 10:20:22 +00:00
}
2021-08-13 09:33:50 +00:00
if err != nil {
2023-06-29 10:20:22 +00:00
logErr ( err , "Could not get the create map update" )
2021-11-14 15:46:09 +00:00
2022-06-20 19:40:28 +00:00
return
2021-08-13 09:33:50 +00:00
}
2022-06-20 19:40:28 +00:00
2024-02-08 16:28:19 +00:00
log . Trace ( ) . Str ( "node" , node . Hostname ) . TimeDiff ( "timeSpent" , time . Now ( ) , startMapResp ) . Str ( "mkey" , node . MachineKey . String ( ) ) . Int ( "type" , int ( update . Type ) ) . Msg ( "finished making map response" )
2023-12-09 17:09:24 +00:00
// Only send update if there is change
if data != nil {
2024-02-08 16:28:19 +00:00
startWrite := time . Now ( )
2023-12-09 17:09:24 +00:00
_ , err = writer . Write ( data )
if err != nil {
logErr ( err , "Could not write the map response" )
2021-11-14 15:46:09 +00:00
2023-12-09 17:09:24 +00:00
updateRequestsSentToNode . WithLabelValues ( node . User . Name , node . Hostname , "failed" ) .
Inc ( )
2023-07-24 06:58:51 +00:00
2023-12-09 17:09:24 +00:00
return
}
2021-11-14 15:46:09 +00:00
2023-12-09 17:09:24 +00:00
if flusher , ok := writer . ( http . Flusher ) ; ok {
flusher . Flush ( )
} else {
log . Error ( ) . Msg ( "Failed to create http flusher" )
2023-06-21 09:29:52 +00:00
2023-09-11 11:18:31 +00:00
return
}
2024-02-08 16:28:19 +00:00
log . Trace ( ) . Str ( "node" , node . Hostname ) . TimeDiff ( "timeSpent" , time . Now ( ) , startWrite ) . Str ( "mkey" , node . MachineKey . String ( ) ) . Int ( "type" , int ( update . Type ) ) . Msg ( "finished writing mapresp to node" )
2023-06-21 09:29:52 +00:00
2023-12-09 17:09:24 +00:00
log . Info ( ) .
Caller ( ) .
Bool ( "readOnly" , mapRequest . ReadOnly ) .
Bool ( "omitPeers" , mapRequest . OmitPeers ) .
Bool ( "stream" , mapRequest . Stream ) .
Str ( "node_key" , node . NodeKey . ShortString ( ) ) .
Str ( "machine_key" , node . MachineKey . ShortString ( ) ) .
Str ( "node" , node . Hostname ) .
TimeDiff ( "timeSpent" , time . Now ( ) , now ) .
Msg ( "update sent" )
}
2023-06-21 09:29:52 +00:00
case <- ctx . Done ( ) :
2024-02-16 09:32:23 +00:00
logTrace ( "The client has closed the connection" )
2023-06-21 09:29:52 +00:00
2023-12-09 17:09:24 +00:00
go h . updateNodeOnlineStatus ( false , node )
2023-09-11 11:18:31 +00:00
2023-12-09 17:09:24 +00:00
// Failover the node's routes if any.
2024-02-08 16:28:19 +00:00
go h . pollFailoverRoutes ( logErr , "node closing connection" , node )
2021-08-18 22:24:22 +00:00
2022-06-20 19:40:28 +00:00
// The connection has been closed, so we can stop polling.
return
2022-06-23 17:40:07 +00:00
case <- h . shutdownChan :
2024-02-16 09:32:23 +00:00
logTrace ( "The long-poll handler is shutting down" )
2022-06-26 10:06:25 +00:00
2022-06-23 17:40:07 +00:00
return
2021-08-13 09:33:50 +00:00
}
2022-06-20 10:30:51 +00:00
}
2021-08-13 09:33:50 +00:00
}
2021-08-18 22:24:22 +00:00
2024-02-08 16:28:19 +00:00
func ( h * Headscale ) pollFailoverRoutes ( logErr func ( error , string ) , where string , node * types . Node ) {
update , err := db . Write ( h . db . DB , func ( tx * gorm . DB ) ( * types . StateUpdate , error ) {
return db . EnsureFailoverRouteIsAvailable ( tx , h . nodeNotifier . ConnectedMap ( ) , node )
} )
if err != nil {
logErr ( err , fmt . Sprintf ( "failed to ensure failover routes, %s" , where ) )
return
}
if update != nil && ! update . Empty ( ) && update . Valid ( ) {
ctx := types . NotifyCtx ( context . Background ( ) , fmt . Sprintf ( "poll-%s-routes-ensurefailover" , strings . ReplaceAll ( where , " " , "-" ) ) , node . Hostname )
h . nodeNotifier . NotifyWithIgnore ( ctx , * update , node . MachineKey . String ( ) )
}
}
2023-12-09 17:09:24 +00:00
// updateNodeOnlineStatus records the last seen status of a node and notifies peers
// about change in their online/offline status.
// It takes a StateUpdateType of either StatePeerOnlineChanged or StatePeerOfflineChanged.
func ( h * Headscale ) updateNodeOnlineStatus ( online bool , node * types . Node ) {
now := time . Now ( )
node . LastSeen = & now
statusUpdate := types . StateUpdate {
Type : types . StatePeerChangedPatch ,
ChangePatches : [ ] * tailcfg . PeerChange {
{
NodeID : tailcfg . NodeID ( node . ID ) ,
Online : & online ,
LastSeen : & now ,
} ,
} ,
}
if statusUpdate . Valid ( ) {
2024-02-08 16:28:19 +00:00
ctx := types . NotifyCtx ( context . Background ( ) , "poll-nodeupdate-onlinestatus" , node . Hostname )
h . nodeNotifier . NotifyWithIgnore ( ctx , statusUpdate , node . MachineKey . String ( ) )
2023-12-09 17:09:24 +00:00
}
2024-02-08 16:28:19 +00:00
err := h . db . DB . Transaction ( func ( tx * gorm . DB ) error {
return db . UpdateLastSeen ( tx , node . ID , * node . LastSeen )
} )
2023-12-09 17:09:24 +00:00
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "Cannot update node LastSeen" )
return
}
}
2023-09-24 11:42:05 +00:00
func closeChanWithLog [ C chan [ ] byte | chan struct { } | chan types . StateUpdate ] ( channel C , node , name string ) {
2022-04-09 22:37:13 +00:00
log . Trace ( ) .
Str ( "handler" , "PollNetMap" ) .
2023-09-24 11:42:05 +00:00
Str ( "node" , node ) .
2022-04-09 22:37:13 +00:00
Str ( "channel" , "Done" ) .
Msg ( fmt . Sprintf ( "Closing %s channel" , name ) )
close ( channel )
}
2023-09-11 11:18:31 +00:00
func ( h * Headscale ) handleLiteRequest (
writer http . ResponseWriter ,
2023-09-24 11:42:05 +00:00
node * types . Node ,
2023-09-11 11:18:31 +00:00
mapRequest tailcfg . MapRequest ,
) {
2024-02-16 09:32:23 +00:00
logTrace , _ , logErr := logPollFunc ( mapRequest , node )
2023-09-11 11:18:31 +00:00
mapp := mapper . NewMapper (
2023-09-24 11:42:05 +00:00
node ,
types . Nodes { } ,
2023-09-11 11:18:31 +00:00
h . DERPMap ,
h . cfg . BaseDomain ,
h . cfg . DNSConfig ,
h . cfg . LogTail . Enabled ,
h . cfg . RandomizeClientPort ,
)
2024-02-16 09:32:23 +00:00
logTrace ( "Client asked for a lite update, responding without peers" )
2023-09-11 11:18:31 +00:00
2023-09-24 11:42:05 +00:00
mapResp , err := mapp . LiteMapResponse ( mapRequest , node , h . ACLPolicy )
2023-09-11 11:18:31 +00:00
if err != nil {
logErr ( err , "Failed to create MapResponse" )
http . Error ( writer , "" , http . StatusInternalServerError )
return
}
writer . Header ( ) . Set ( "Content-Type" , "application/json; charset=utf-8" )
writer . WriteHeader ( http . StatusOK )
_ , err = writer . Write ( mapResp )
if err != nil {
logErr ( err , "Failed to write response" )
}
}
2023-12-09 17:09:24 +00:00
func logTracePeerChange ( hostname string , hostinfoChange bool , change * tailcfg . PeerChange ) {
trace := log . Trace ( ) . Str ( "node_id" , change . NodeID . String ( ) ) . Str ( "hostname" , hostname )
if change . Key != nil {
trace = trace . Str ( "node_key" , change . Key . ShortString ( ) )
}
if change . DiscoKey != nil {
trace = trace . Str ( "disco_key" , change . DiscoKey . ShortString ( ) )
}
if change . Online != nil {
trace = trace . Bool ( "online" , * change . Online )
}
if change . Endpoints != nil {
eps := make ( [ ] string , len ( change . Endpoints ) )
for idx , ep := range change . Endpoints {
eps [ idx ] = ep . String ( )
}
trace = trace . Strs ( "endpoints" , eps )
}
if hostinfoChange {
trace = trace . Bool ( "hostinfo_changed" , hostinfoChange )
}
if change . DERPRegion != 0 {
trace = trace . Int ( "derp_region" , change . DERPRegion )
}
trace . Time ( "last_seen" , * change . LastSeen ) . Msg ( "PeerChange received" )
}