Skip to content

Commit

Permalink
Merge pull request #767 from mackerelio/improve-check-tcp
Browse files Browse the repository at this point in the history
[check-tcp] Supports option to monitor that ports are closed.
  • Loading branch information
ne-sachirou authored Sep 21, 2023
2 parents 0f93286 + 06922b7 commit 5cdf376
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 7 deletions.
1 change: 1 addition & 0 deletions check-tcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ command = ["check-tcp", "-H", "localhost", "-p", "4224", "-w", "3", "-c", "5"]
-c, --critical= Response time to result in critical status (seconds)
-E, --escape Can use \n, \r, \t or \ in send or quit string. Must come before send or quit option. By default, nothing added to send, \r\n added to end of quit
-W, --error-warning Set the error level to warning when exiting with unexpected error (default: critical). In the case of request succeeded, evaluation result of -c option eval takes priority.
-C, --expect-closed Verify that the port/unixsock is closed. If the port/unixsock is closed, OK; if open, follow the ErrWarning flag. This option only verifies the connection.
```

## For more information
Expand Down
37 changes: 30 additions & 7 deletions check-tcp/lib/check-tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ type tcpOpts struct {
Service string `long:"service" description:"Service name. e.g. ftp, smtp, pop, imap and so on"`
Hostname string `short:"H" long:"hostname" description:"Host name or IP Address"`
exchange
Timeout float64 `short:"t" long:"timeout" default:"10" description:"Seconds before connection times out"`
MaxBytes int `short:"m" long:"maxbytes" description:"Close connection once more than this number of bytes are received"`
Delay float64 `short:"d" long:"delay" description:"Seconds to wait between sending string and polling for response"`
Warning float64 `short:"w" long:"warning" description:"Response time to result in warning status (seconds)"`
Critical float64 `short:"c" long:"critical" description:"Response time to result in critical status (seconds)"`
Escape bool `short:"E" long:"escape" description:"Can use \\n, \\r, \\t or \\ in send or quit string. Must come before send or quit option. By default, nothing added to send, \\r\\n added to end of quit"`
ErrWarning bool `short:"W" long:"error-warning" description:"Set the error level to warning when exiting with unexpected error (default: critical). In the case of request succeeded, evaluation result of -c option eval takes priority."`
Timeout float64 `short:"t" long:"timeout" default:"10" description:"Seconds before connection times out"`
MaxBytes int `short:"m" long:"maxbytes" description:"Close connection once more than this number of bytes are received"`
Delay float64 `short:"d" long:"delay" description:"Seconds to wait between sending string and polling for response"`
Warning float64 `short:"w" long:"warning" description:"Response time to result in warning status (seconds)"`
Critical float64 `short:"c" long:"critical" description:"Response time to result in critical status (seconds)"`
Escape bool `short:"E" long:"escape" description:"Can use \\n, \\r, \\t or \\ in send or quit string. Must come before send or quit option. By default, nothing added to send, \\r\\n added to end of quit"`
ErrWarning bool `short:"W" long:"error-warning" description:"Set the error level to warning when exiting with unexpected error (default: critical). In the case of request succeeded, evaluation result of -c option eval takes priority."`
ExpectClosed bool `short:"C" long:"expect-closed" description:"Verify that the port/unixsock is closed. If the port/unixsock is closed, OK; if open, follow the ErrWarning flag. This option only verifies the connection."`
}

type exchange struct {
Expand Down Expand Up @@ -179,13 +180,35 @@ func (opts *tcpOpts) run() *checkers.Checker {

conn, err := dial(proto, addr, opts.SSL, opts.NoCheckCertificate, timeout)
if err != nil {
if opts.ExpectClosed {
var msg string
if opts.UnixSock == "" {
msg = fmt.Sprintf("Verified that the port is closed. (port=%d)", opts.Port)
} else {
msg = fmt.Sprintf("Verified that the unixsock is closed. (sock=%s)", opts.UnixSock)
}
return checkers.Ok(msg)
}
if opts.ErrWarning {
return checkers.Warning(err.Error())
}
return checkers.Critical(err.Error())
}
defer conn.Close()

if opts.ExpectClosed {
var msg string
if opts.UnixSock == "" {
msg = fmt.Sprintf("Unexpectedly open port. (port=%d)", opts.Port)
} else {
msg = fmt.Sprintf("Unexpectedly open unixsock. (sock=%s)", opts.UnixSock)
}
if opts.ErrWarning {
return checkers.Warning(msg)
}
return checkers.Critical(msg)
}

if opts.Send != "" {
err := write(conn, []byte(opts.Send), timeout)
if err != nil {
Expand Down
100 changes: 100 additions & 0 deletions check-tcp/lib/check-tcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,35 @@ func TestHTTP(t *testing.T) {
assert.Regexp(t, `seconds response time on`, ckr.Message, "Unexpected response")
}
testOverCritWithErrWarn()

errMsgWithExpectClosed := fmt.Sprintf("Unexpectedly open port. (port=%s)", port)

testOkIfPortClosed := func() {
opts, err := parseArgs([]string{"-H", host, "-p", "1", "--expect-closed"})
assert.Equal(t, nil, err, "no errors")
ckr := opts.run()
fmt.Println(ckr)
assert.Equal(t, checkers.OK, ckr.Status, "Verified that the port is closed. (port=1)")
}
testOkIfPortClosed()

testCriticalIfPortOpened := func() {
opts, err := parseArgs([]string{"-H", host, "-p", port, "--expect-closed"})
assert.Equal(t, nil, err, "no errors")
ckr := opts.run()
fmt.Println(ckr)
assert.Equal(t, checkers.CRITICAL, ckr.Status, errMsgWithExpectClosed)
}
testCriticalIfPortOpened()

testWarningIfPortOpened := func() {
opts, err := parseArgs([]string{"-H", host, "-p", port, "--expect-closed", "--error-warning"})
assert.Equal(t, nil, err, "no errors")
ckr := opts.run()
fmt.Println(ckr)
assert.Equal(t, checkers.WARNING, ckr.Status, errMsgWithExpectClosed)
}
testWarningIfPortOpened()
}

func TestUnixDomainSocket(t *testing.T) {
Expand Down Expand Up @@ -290,6 +319,35 @@ func TestUnixDomainSocket(t *testing.T) {
assert.Regexp(t, `seconds response time on`, ckr.Message, "Unexpected response")
}
testOverCrit()

errMsgWithExpectClosed := fmt.Sprintf("Unexpectedly open unixsock. (sock=%s)", sock)

testOkIfUnixSocketClosed := func() {
opts, err := parseArgs([]string{"-U", "/foo/bar.sock", "--send", `PING`, "-E", "-e", "OKOK", "--expect-closed"})
assert.Equal(t, nil, err, "no errors")
ckr := opts.run()
fmt.Println(ckr)
assert.Equal(t, checkers.OK, ckr.Status, "Verified that the unixsock is closed. (sock=/foo/bar.sock)")
}
testOkIfUnixSocketClosed()

testCriticalIfUnixSocketOpened := func() {
opts, err := parseArgs([]string{"-U", sock, "--send", `PING`, "-E", "-e", "OKOK", "--expect-closed"})
assert.Equal(t, nil, err, "no errors")
ckr := opts.run()
fmt.Println(ckr)
assert.Equal(t, checkers.CRITICAL, ckr.Status, errMsgWithExpectClosed)
}
testCriticalIfUnixSocketOpened()

testWarningIfUnixSocketOpened := func() {
opts, err := parseArgs([]string{"-U", sock, "--send", `PING`, "-E", "-e", "OKOK", "--expect-closed", "--error-warning"})
assert.Equal(t, nil, err, "no errors")
ckr := opts.run()
fmt.Println(ckr)
assert.Equal(t, checkers.WARNING, ckr.Status, errMsgWithExpectClosed)
}
testWarningIfUnixSocketOpened()
}

func TestHTTPIPv6(t *testing.T) {
Expand Down Expand Up @@ -356,3 +414,45 @@ func TestHTTPIPv6(t *testing.T) {
}
testOverCrit()
}

func TestExpectClosedWithPort(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
time.Sleep(time.Second / 5)
w.WriteHeader(200)
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "OKOK")
}))
defer ts.Close()

u, _ := url.Parse(ts.URL)
host, port, _ := net.SplitHostPort(u.Host)

errMsg := fmt.Sprintf("Unexpectedly open port. (port=%s)", port)

testOkIfPortClosed := func() {
opts, err := parseArgs([]string{"-H", host, "-p", "1", "--expect-closed"})
assert.Equal(t, nil, err, "no errors")
ckr := opts.run()
fmt.Println(ckr)
assert.Equal(t, checkers.OK, ckr.Status, "Verified that the port(1) is closed.")
}
testOkIfPortClosed()

testCriticalIfPortOpened := func() {
opts, err := parseArgs([]string{"-H", host, "-p", port, "--expect-closed"})
assert.Equal(t, nil, err, "no errors")
ckr := opts.run()
fmt.Println(ckr)
assert.Equal(t, checkers.CRITICAL, ckr.Status, errMsg)
}
testCriticalIfPortOpened()

testWarningIfPortOpened := func() {
opts, err := parseArgs([]string{"-H", host, "-p", port, "--expect-closed", "--error-warning"})
assert.Equal(t, nil, err, "no errors")
ckr := opts.run()
fmt.Println(ckr)
assert.Equal(t, checkers.WARNING, ckr.Status, errMsg)
}
testWarningIfPortOpened()
}

0 comments on commit 5cdf376

Please sign in to comment.