Created
October 28, 2012 19:56
-
-
Save bilange/3969670 to your computer and use it in GitHub Desktop.
Host checker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* TCP Host checker | |
* Eric Belanger // github.com/bilange // Twitter: @bilange | |
* | |
* I needed a quick diagnostic tool to troubleshoot a specific issue with my | |
* Ubuntu installation at home: apparently after a long, sustained TCP | |
* connection (thing SSH or VPN), somehow the system throws its hands in the | |
* air and refuses to talk to the same HOST, until I reboot my PC. | |
* | |
* I basically needed to know when a remote host would stop responding to my | |
* requests. Creating a Go program could be seen as overkill, but I wanted to | |
* push on my Go knowledge, and thought it was a very good opportunity. | |
* | |
* I have commented my code for peer review and for learning purposes. | |
* | |
* This MAY be NOT the only way to do this task (especially when you think | |
* about the "flag" Go package), so feel free to fork, base upon and | |
* experiment. Hack away! :D | |
* | |
* Technical note: I originally wanted to include an optional ICMP Echo (ping) | |
* method of checking, however it seems sending an ICMP echo request somehow | |
* needs root (google "golang ping" for the juicy details). As I intent to | |
* deploy this on non-root accounts for scripting purposes, i dropped this | |
* 'native' programatically generated ping feature. I could however launch a | |
* subprocess calling the OS' "ping" command and check for the result, however | |
* this is highly operating system's dependant (and very variable). I leave | |
* that as an exercice for the reader. | |
*/ | |
package main | |
import ( | |
"fmt" | |
"strconv" | |
"time" | |
"net" | |
"os" | |
"os/signal" | |
) | |
//Note that the variables values here reflects the 'default' behaviour, until | |
//the caller says otherwise when invoking the command parameters | |
var Host string = "apple.com" //Where we should connect to (host) | |
var Port int = 80 //Where we should connect to (port) | |
var Times int = -1 //How many times we're checking (<= 0 == infinite) | |
var TimesDone int = 0 //Counter keeping track how many times we checked so far | |
var Delay int = 300 //Interval, in seconds, between each checks. | |
var ExitCodeSuccess int = 0 //-r sets the exit code on host response (success), for scripting | |
var ExitCodeFailure int = 1 //-n sets the exit code on host non-response (failure), for scripting | |
var Verbose bool = false //Should we print out stuff on screen? | |
var AlmostQuiet bool = false //Only print "host is up" / "host is down". | |
var HostIsUp bool = false //Was the host up at the last checkup? | |
//This function is called for every integer based commandline parameter we need | |
//to parse. If parsing of variable v fails, integer d will be used. | |
func parseInt(v string, d int) int { | |
rtn64, err := strconv.ParseInt(v, 10, 32) | |
if err != nil { return d } | |
return int(rtn64) | |
} | |
func usage() { | |
fmt.Printf("Usage: %s [options] <target IP or hostname>\nwhere <options> is any combination of these:\n\n%s\n", os.Args[0], | |
`-p, --port Specifies a TCP port to check (Ex.: -p 22) | |
Defaults to 80 (HTTP) | |
-r, --response A succesful check will return this exit code (Ex.: -r 0) | |
-n, --noresponse An unsuccesful check will return this exit code (Ex.: -r 1) | |
-t, --times Number of checks done. Omit this parameter will | |
cause one check, for scripting purposes. Set | |
this to 0 or less for an infinite check. Any multiple | |
check amount will enable verbosity on screen. | |
-d, --delay Delays every check by that many seconds (Ex.: -d 60) | |
-v, --verbose Force verbosity when doing one single check | |
-a, --almostquiet Only print "host is down" / "host is up" | |
By default, this program will check for a TCP connection on apple.com:80, | |
every five minutes, printing results on screen. | |
`) | |
os.Exit(0) | |
} | |
//Shortcut function for verbose printing, displaying the time in the | |
//format "[Sun Oct 28 15:43:33 2012]" | |
func timestamp() string { | |
return "["+time.Now().Format(time.ANSIC)+"] " | |
} | |
//Returns true if we need to print out on screen | |
//printInAlmostQuietMode is basically an override that permits printing, whether | |
//we're in verbose mode or not. | |
func stdoutRequired() bool { | |
switch { | |
//case Times > 1 && AlmostQuiet == true: return true | |
case Verbose == true && Times == 1: return true | |
case Verbose == false && Times == 1: return false | |
case Verbose == true && Times != 1: return true | |
case Verbose == false && Times != 1: return false | |
} | |
return false | |
} | |
func main() { | |
//If the user hasnt specified ANYTHING, spit out the usage documenation and exit | |
if len(os.Args) == 1 { usage() } | |
//Parse the commandline parameters. I used my own parser instead of the | |
//provided "flag" Go package because the parameters are optional here, and I | |
//needed one single argument without being a "dashed" option. (The hostname) | |
for i := 1; i<len(os.Args); i++ { | |
var arg = os.Args[i] | |
switch arg { | |
case "-h","--help": | |
usage() | |
case "-p","--port": | |
i++ | |
if i == len(os.Args)-1 { continue } //Reached end of the slice | |
Port = parseInt(os.Args[i], 80) | |
continue | |
case "-v","--verbose": | |
Verbose = true | |
fmt.Println(timestamp(), "Verbose mode on.") | |
case "-a","--almostquiet": | |
AlmostQuiet = true | |
case "-t","--times": | |
i++ | |
if i == len(os.Args)-1 { continue } //Reached end of the slice | |
Times = parseInt(os.Args[i], 80) | |
continue | |
case "-d","--delay": | |
i++ | |
if i == len(os.Args)-1 { continue } //Reached end of the slice | |
Delay = parseInt(os.Args[i], 300) | |
continue | |
case "-r","--response": | |
i++ | |
if i == len(os.Args)-1 { continue } //Reached end of the slice | |
ExitCodeSuccess = parseInt(os.Args[i], 0) | |
continue | |
case "-n","--noresponse": | |
i++ | |
if i >= len(os.Args) { continue } //Reached end of the slice | |
ExitCodeFailure = parseInt(os.Args[i], 1) | |
continue | |
default: | |
Host = arg | |
} | |
} | |
//Safe checking of incompatible settings | |
if Times != 1 && Verbose == false { | |
fmt.Println(timestamp(), "Non-single check specified without verbosity; a brief message will appear for every check.") | |
AlmostQuiet = true | |
} | |
if AlmostQuiet == true && Verbose == true { | |
fmt.Println(timestamp(), "Ambiguous parameters: AlmostQuiet AND verbose specified: Verbose wins.") | |
AlmostQuiet = false | |
} | |
if (Delay < 30) { | |
if AlmostQuiet == false { | |
fmt.Println(timestamp(), "** WARNING ** Causing too much traffic is usually frown upon by sysadmins. Use with care!") | |
} | |
} | |
if stdoutRequired() && AlmostQuiet == false { | |
fmt.Printf("%s Settings used: \n\nHost: %s, Port: %d\nSuccess Exit Code: %d, Failure Exit Code: %d\nDelay: %ds, Times Checked: %d, Verbose: %t, Almost quiet: %t\n\n", | |
timestamp(), Host, Port, ExitCodeSuccess, ExitCodeFailure, Delay, Times, Verbose, AlmostQuiet) | |
} | |
//This part of code is heavily inspired from: | |
//http://stackoverflow.com/questions/11268943/golang-is-it-possible-to-capture-a-ctrlc-sigterm-signal-and-run-a-cleanup-fu | |
c := make (chan os.Signal, 1) | |
signal.Notify(c, os.Interrupt) | |
go func(){ | |
for sig := range c { | |
if stdoutRequired() { fmt.Printf("^C hit, ") } | |
switch HostIsUp { | |
case true: | |
if AlmostQuiet == true || stdoutRequired() { fmt.Printf("host is UP.\n" ) } | |
os.Exit(ExitCodeSuccess) | |
case false: | |
if AlmostQuiet == true || stdoutRequired() { fmt.Printf("host is DOWN.\n") } | |
os.Exit(ExitCodeFailure) | |
} | |
if stdoutRequired() { fmt.Printf("Exiting. (%v)\n", sig) } | |
} | |
}() | |
for { //Loops until we have reached the amount of times the user wanted to check up the host. | |
//Note that exiting is handled just above this for loop. | |
if stdoutRequired() == true { fmt.Printf("%sAttempting to connect to %s:%d... ",timestamp(), Host, Port) } | |
if Times > 0 { TimesDone++ } //Only keep track of the times we check when we need it :) | |
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", Host, Port)) | |
if err != nil { //An error indicates that net.Dial couldn't reach the host. | |
HostIsUp = false | |
} else { | |
if stdoutRequired() { fmt.Printf("Connected. ") } | |
conn.Close() | |
HostIsUp = true | |
} | |
if HostIsUp == true { | |
if AlmostQuiet == true || stdoutRequired() { fmt.Printf("Host is UP!\n") } | |
if Times > 0 && TimesDone >= Times { //Finite check | |
os.Exit(ExitCodeSuccess) | |
} | |
} | |
if HostIsUp == false { | |
if AlmostQuiet == true || stdoutRequired() { fmt.Printf("Host is DOWN!\n") } | |
if Times > 0 && TimesDone >= Times { //Finite check | |
os.Exit(ExitCodeFailure) | |
} | |
} | |
time.Sleep(time.Duration(Delay) * time.Second) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment