Add ICMP support
authorBrian Brazil <brian.brazil@robustperception.io>
Sat, 5 Sep 2015 19:16:32 +0000 (20:16 +0100)
committerBrian Brazil <brian.brazil@robustperception.io>
Sat, 5 Sep 2015 19:16:32 +0000 (20:16 +0100)
AUTHORS.md [new file with mode: 0644]
blackbox.yml
icmp.go [new file with mode: 0644]
main.go

diff --git a/AUTHORS.md b/AUTHORS.md
new file mode 100644 (file)
index 0000000..a50e8e7
--- /dev/null
@@ -0,0 +1,12 @@
+The Prometheus project was started by Matt T. Proud (emeritus) and
+Julius Volz in 2012.
+
+Maintainers of this repository:
+
+* Brian Brazil <brian.brazil@robustperception.io>
+
+The following individuals have contributed code to this repository
+(listed in alphabetical order):
+
+* Brian Brazil <brian.brazil@robustperception.io>
+
index dee0bf0b1bf472e553531afca074b9e80d3cd4fa..3b4a8508602c60433535d932f1223306711c8cda 100644 (file)
@@ -6,3 +6,6 @@ modules:
   tcpconnect:
     prober: tcp
     timeout: 5s
+  icmp:
+    prober: icmp
+    timeout: 5s
diff --git a/icmp.go b/icmp.go
new file mode 100644 (file)
index 0000000..04c40d3
--- /dev/null
+++ b/icmp.go
@@ -0,0 +1,95 @@
+package main
+
+import (
+       "net"
+       "net/http"
+       "os"
+       "sync"
+  "time"
+       "golang.org/x/net/internal/iana"
+       "golang.org/x/net/icmp"
+       "golang.org/x/net/ipv4"
+
+  "github.com/prometheus/log"
+)
+
+var (
+  icmpSequence uint16
+  icmpSequenceMutex sync.Mutex
+
+)
+
+func getICMPSequence() uint16 {
+  icmpSequenceMutex.Lock()
+  defer icmpSequenceMutex.Unlock()
+  icmpSequence+=1
+  return icmpSequence
+}
+
+func probeICMP(target string, w http.ResponseWriter, module Module) (success bool) {
+  deadline := time.Now().Add(module.Timeout)
+       socket, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
+       if err != nil {
+    log.Errorf("Error listening to socket: %s", err)
+               return
+       }
+       defer socket.Close()
+
+  ip, err := net.ResolveIPAddr("ip4", target)
+  if err != nil {
+    log.Errorf("Error resolving address %s: %s", target, err)
+               return
+       }
+
+  seq := getICMPSequence()
+  pid := os.Getpid() & 0xffff
+
+       wm := icmp.Message{
+               Type: ipv4.ICMPTypeEcho, Code: 0,
+               Body: &icmp.Echo{
+                       ID: pid, Seq: int(seq),
+                       Data: []byte("Prometheus Blackbox Exporter"),
+               },
+       }
+       wb, err := wm.Marshal(nil)
+  if err != nil {
+    log.Errorf("Error marshalling packet for %s: %s", target, err)
+               return
+       }
+  if _, err := socket.WriteTo(wb, ip); err != nil {
+    log.Errorf("Error writing to socker for %s: %s", target, err)
+    return
+  }
+
+  rb := make([]byte, 1500)
+  if err := socket.SetReadDeadline(deadline); err != nil {
+    log.Errorf("Error setting socket deadline for %s: %s", target, err)
+    return
+  }
+  for {
+    n, peer, err := socket.ReadFrom(rb)
+    if err != nil {
+      if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
+        log.Infof("Timeout reading from socket for %s: %s", target, err)
+        return
+      }
+      log.Errorf("Error reading from socket for %s: %s", target, err)
+      continue
+    }
+    if peer.String() != ip.String() {
+      continue
+    }
+    rm, err := icmp.ParseMessage(iana.ProtocolICMP, rb[:n])
+    if err != nil {
+      log.Warnf("Error parsing ICMP message for %s: %s", target, err)
+      continue
+    }
+    if rm.Type == ipv4.ICMPTypeEchoReply {
+        // The ICMP package does not support unmarshalling
+        // messages, so assume this is the right sequence number.
+        success = true
+        return
+    }
+  }
+       return
+}
diff --git a/main.go b/main.go
index 92756760427c6e057f62253921aade5abe084dc4..6d96a1f47cf8ccaa30ad499f6dac3cc14f909fa9 100644 (file)
--- a/main.go
+++ b/main.go
@@ -24,6 +24,7 @@ type Module struct {
        Timeout time.Duration `yaml:"timeout"`
        HTTP    HTTPProbe     `yaml:"http"`
        TCP     TCPProbe      `yaml:"tcp"`
+       ICMP    ICMPProbe     `yaml:"icmp"`
 }
 
 type HTTPProbe struct {
@@ -37,9 +38,13 @@ type HTTPProbe struct {
 type TCPProbe struct {
 }
 
+type ICMPProbe struct {
+}
+
 var Probers = map[string]func(string, http.ResponseWriter, Module) bool{
        "http": probeHTTP,
        "tcp":  probeTCP,
+       "icmp":  probeICMP,
 }
 
 func probeHandler(w http.ResponseWriter, r *http.Request, config *Config) {
@@ -58,8 +63,13 @@ func probeHandler(w http.ResponseWriter, r *http.Request, config *Config) {
                http.Error(w, fmt.Sprintf("Unkown module %s", moduleName), 400)
                return
        }
+       prober, ok := Probers[module.Prober]
+       if !ok {
+               http.Error(w, fmt.Sprintf("Unkown prober %s", module.Prober), 400)
+               return
+       }
        start := time.Now()
-       success := Probers[module.Prober](target, w, module)
+       success := prober(target, w, module)
        fmt.Fprintf(w, "probe_duration_seconds %f\n", float64(time.Now().Sub(start))/1e9)
        if success {
                fmt.Fprintf(w, "probe_success %d\n", 1)