The ICMP probe requires elevated privileges to function:
* *Windows*: Administrator privileges are required.
-* *Linux*: root user _or_ `CAP_NET_RAW` capability is required.
- * Can be set by executing `setcap cap_net_raw+ep blackbox_exporter`
-* *BSD / OS X*: root user is required.
+* *Linux*: either a user with a group within `net.ipv4.ping_group_range`, the
+ `CAP_NET_RAW` capability or the root user is required.
+ * Your distribution may configure `net.ipv4.ping_group_range` by default in
+ `/etc/sysctl.conf` or similar. If not you can set
+ `net.ipv4.ping_group_range = 0 2147483647` to allow any user the ability
+ to use ping.
+ * Alternatively the capability can be set by executing `setcap cap_net_raw+ep
+ blackbox_exporter`
+* *BSD*: root user is required.
+* *OS X*: No additional privileges are needed.
[circleci]: https://circleci.com/gh/prometheus/blackbox_exporter
[hub]: https://hub.docker.com/r/prom/blackbox-exporter/
"math/rand"
"net"
"os"
+ "runtime"
"sync"
"time"
setupStart := time.Now()
level.Info(logger).Log("msg", "Creating socket")
+
+ unprivileged := false
+ // Unprivileged sockets are supported on Darwin and Linux only.
+ tryUnprivileged := runtime.GOOS == "darwin" || runtime.GOOS == "linux"
+
if ip.IP.To4() == nil {
requestType = ipv6.ICMPTypeEchoRequest
replyType = ipv6.ICMPTypeEchoReply
if srcIP == nil {
srcIP = net.ParseIP("::")
}
- icmpConn, err := icmp.ListenPacket("ip6:ipv6-icmp", srcIP.String())
- if err != nil {
- level.Error(logger).Log("msg", "Error listening to socket", "err", err)
- return
+
+ var icmpConn *icmp.PacketConn
+ if tryUnprivileged {
+ // "udp" here means unprivileged -- not the protocol "udp".
+ icmpConn, err = icmp.ListenPacket("udp6", srcIP.String())
+ if err != nil {
+ level.Debug(logger).Log("msg", "Unable to do unprivileged listen on socket, will attempt privileged", "err", err)
+ } else {
+ unprivileged = true
+ }
+ }
+
+ if !unprivileged {
+ icmpConn, err = icmp.ListenPacket("ip6:ipv6-icmp", srcIP.String())
+ if err != nil {
+ level.Error(logger).Log("msg", "Error listening to socket", "err", err)
+ return
+ }
}
socket = icmpConn
if srcIP == nil {
srcIP = net.ParseIP("0.0.0.0")
}
- icmpConn, err := net.ListenPacket("ip4:icmp", srcIP.String())
- if err != nil {
- level.Error(logger).Log("msg", "Error listening to socket", "err", err)
- return
+
+ var icmpConn *icmp.PacketConn
+ // If the user has set the don't fragment option we cannot use unprivileged
+ // sockets as it is not possible to set IP header level options.
+ if tryUnprivileged && !module.ICMP.DontFragment {
+ icmpConn, err = icmp.ListenPacket("udp4", srcIP.String())
+ if err != nil {
+ level.Debug(logger).Log("msg", "Unable to do unprivileged listen on socket, will attempt privileged", "err", err)
+ } else {
+ unprivileged = true
+ }
+ }
+
+ if !unprivileged {
+ icmpConn, err = icmp.ListenPacket("ip4:icmp", srcIP.String())
+ if err != nil {
+ level.Error(logger).Log("msg", "Error listening to socket", "err", err)
+ return
+ }
}
if module.ICMP.DontFragment {
defer socket.Close()
+ var dst net.Addr = ip
+ if unprivileged {
+ dst = &net.UDPAddr{IP: ip.IP, Zone: ip.Zone}
+ }
+
var data []byte
if module.ICMP.PayloadSize != 0 {
data = make([]byte, module.ICMP.PayloadSize)
level.Error(logger).Log("msg", "Error marshalling packet", "err", err)
return
}
+
durationGaugeVec.WithLabelValues("setup").Add(time.Since(setupStart).Seconds())
level.Info(logger).Log("msg", "Writing out packet")
rttStart := time.Now()
- if _, err = socket.WriteTo(wb, ip); err != nil {
+ if _, err = socket.WriteTo(wb, dst); err != nil {
level.Warn(logger).Log("msg", "Error writing to socket", "err", err)
return
}
- // Reply should be the same except for the message type.
+ // Reply should be the same except for the message type and ID if
+ // unprivileged sockets were used and the kernel used its own.
wm.Type = replyType
+ // Unprivileged cannot set IDs on Linux.
+ idUnknown := unprivileged && runtime.GOOS == "linux"
+ if idUnknown {
+ body.ID = 0
+ }
wb, err = wm.Marshal(nil)
if err != nil {
level.Error(logger).Log("msg", "Error marshalling packet", "err", err)
return
}
+ if idUnknown {
+ // If the ID is unknown (due to unprivileged sockets) we also cannot know
+ // the checksum in userspace.
+ wb[2] = 0
+ wb[3] = 0
+ }
+
rb := make([]byte, 65536)
deadline, _ := ctx.Deadline()
if err := socket.SetReadDeadline(deadline); err != nil {
level.Error(logger).Log("msg", "Error reading from socket", "err", err)
continue
}
- if peer.String() != ip.String() {
+ if peer.String() != dst.String() {
continue
}
- if replyType == ipv6.ICMPTypeEchoReply {
+ if idUnknown {
+ // Clear the ID from the packet, as the kernel will have replaced it (and
+ // kept track of our packet for us, hence clearing is safe).
+ rb[4] = 0
+ rb[5] = 0
+ }
+ if idUnknown || replyType == ipv6.ICMPTypeEchoReply {
// Clear checksum to make comparison succeed.
rb[2] = 0
rb[3] = 0