2023-07-19 12:58:21 +00:00
const { MonitorType } = require ( "./monitor-type" ) ;
const { UP , log } = require ( "../../src/util" ) ;
const exec = require ( "child_process" ) . exec ;
/ * *
* A TailscalePing class extends the MonitorType .
* It runs Tailscale ping to monitor the status of a specific node .
* /
class TailscalePing extends MonitorType {
name = "tailscale-ping" ;
/ * *
* Checks the ping status of the URL associated with the monitor .
* It then parses the Tailscale ping command output to update the heatrbeat .
2023-08-11 07:46:41 +00:00
* @ param { object } monitor The monitor object associated with the check .
* @ param { object } heartbeat The heartbeat object to update .
* @ returns { Promise < void > }
2023-07-19 12:58:21 +00:00
* @ throws Will throw an error if checking Tailscale ping encounters any error
* /
async check ( monitor , heartbeat ) {
try {
let tailscaleOutput = await this . runTailscalePing ( monitor . hostname , monitor . interval ) ;
this . parseTailscaleOutput ( tailscaleOutput , heartbeat ) ;
} catch ( err ) {
log . debug ( "Tailscale" , err ) ;
// trigger log function somewhere to display a notification or alert to the user (but how?)
throw new Error ( ` Error checking Tailscale ping: ${ err } ` ) ;
}
}
/ * *
* Runs the Tailscale ping command to the given URL .
2023-08-11 07:46:41 +00:00
* @ param { string } hostname The hostname to ping .
* @ param { number } interval Interval to send ping
* @ returns { Promise < string > } A Promise that resolves to the output of the Tailscale ping command
2023-07-19 12:58:21 +00:00
* @ throws Will throw an error if the command execution encounters any error .
* /
async runTailscalePing ( hostname , interval ) {
let cmd = ` tailscale ping ${ hostname } ` ;
log . debug ( "Tailscale" , cmd ) ;
return new Promise ( ( resolve , reject ) => {
let timeout = interval * 1000 * 0.8 ;
exec ( cmd , { timeout : timeout } , ( error , stdout , stderr ) => {
// we may need to handle more cases if tailscale reports an error that isn't necessarily an error (such as not-logged in or DERP health-related issues)
if ( error ) {
reject ( ` Execution error: ${ error . message } ` ) ;
return ;
}
if ( stderr ) {
reject ( ` Error in output: ${ stderr } ` ) ;
return ;
}
resolve ( stdout ) ;
} ) ;
} ) ;
}
/ * *
* Parses the output of the Tailscale ping command to update the heartbeat .
2023-08-11 07:46:41 +00:00
* @ param { string } tailscaleOutput The output of the Tailscale ping command .
* @ param { object } heartbeat The heartbeat object to update .
* @ returns { void }
2023-07-19 12:58:21 +00:00
* @ throws Will throw an eror if the output contains any unexpected string .
* /
parseTailscaleOutput ( tailscaleOutput , heartbeat ) {
let lines = tailscaleOutput . split ( "\n" ) ;
for ( let line of lines ) {
if ( line . includes ( "pong from" ) ) {
heartbeat . status = UP ;
let time = line . split ( " in " ) [ 1 ] . split ( " " ) [ 0 ] ;
heartbeat . ping = parseInt ( time ) ;
heartbeat . msg = line ;
break ;
} else if ( line . includes ( "timed out" ) ) {
throw new Error ( ` Ping timed out: " ${ line } " ` ) ;
// Immediately throws upon "timed out" message, the server is expected to re-call the check function
} else if ( line . includes ( "no matching peer" ) ) {
throw new Error ( ` Nonexistant or inaccessible due to ACLs: " ${ line } " ` ) ;
} else if ( line . includes ( "is local Tailscale IP" ) ) {
throw new Error ( ` Tailscale only works if used on other machines: " ${ line } " ` ) ;
} else if ( line !== "" ) {
throw new Error ( ` Unexpected output: " ${ line } " ` ) ;
}
}
}
}
module . exports = {
TailscalePing ,
} ;