Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for "rootless" ping #642

Merged
merged 2 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,15 @@ scrape_configs:
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 the root user, the `CAP_NET_RAW` capability or a user with a
dgl marked this conversation as resolved.
Show resolved Hide resolved
group within net.ipv4.ping_group_range is required.
* The capability can be set by executing `setcap cap_net_raw+ep blackbox_exporter`
* 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.
* *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/
Expand Down
81 changes: 69 additions & 12 deletions prober/icmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"math/rand"
"net"
"os"
"runtime"
"sync"
"time"

Expand Down Expand Up @@ -98,17 +99,36 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr

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
Expand All @@ -119,10 +139,23 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr
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 tryUnprivileged && !module.ICMP.DontFragment {
brian-brazil marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand All @@ -139,6 +172,11 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr

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)
Expand All @@ -164,22 +202,35 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr
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 the kernel
dgl marked this conversation as resolved.
Show resolved Hide resolved
// 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 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 {
Expand All @@ -197,10 +248,16 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr
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
Expand Down