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

Advanced ICMP options #240

Merged
merged 3 commits into from
Oct 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ validate_additional_rrs:
# The preferred IP protocol of the ICMP probe (ip4, ip6).
[ preferred_ip_protocol: <string> | default = "ip6" ]

# Set the DF-bit in the IP-header. Only works with ip4 and on *nix systems.
[ dont_fragment: <boolean> | default = false ]

# The size of the payload.
[ payload_size: <int> ]

```

### <tls_config>
Expand Down
9 changes: 8 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"runtime"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -96,7 +97,8 @@ type TCPProbe struct {

type ICMPProbe struct {
PreferredIPProtocol string `yaml:"preferred_ip_protocol,omitempty"` // Defaults to "ip6".

PayloadSize int `yaml:"payload_size,omitempty"`
DontFragment bool `yaml:"dont_fragment,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
Expand Down Expand Up @@ -215,6 +217,11 @@ func (s *ICMPProbe) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal((*plain)(s)); err != nil {
return err
}

if runtime.GOOS == "windows" && s.DontFragment {
return errors.New("\"dont_fragment\" is not supported on windows platforms")
}

if err := checkOverflow(s.XXX, "icmp probe"); err != nil {
return err
}
Expand Down
102 changes: 94 additions & 8 deletions prober/icmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func getICMPSequence() uint16 {

func ProbeICMP(ctx context.Context, target string, module config.Module, registry *prometheus.Registry, logger log.Logger) (success bool) {
var (
socket *icmp.PacketConn
socket net.PacketConn
requestType icmp.Type
replyType icmp.Type
)
Expand All @@ -62,23 +62,52 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr
if ip.IP.To4() == nil {
requestType = ipv6.ICMPTypeEchoRequest
replyType = ipv6.ICMPTypeEchoReply

socket, err = icmp.ListenPacket("ip6:ipv6-icmp", "::")
if err != nil {
level.Error(logger).Log("msg", "Error listening to socket", "err", err)
return
}
} else {
requestType = ipv4.ICMPTypeEcho
replyType = ipv4.ICMPTypeEchoReply
socket, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
}

if err != nil {
level.Error(logger).Log("msg", "Error listening to socket", "err", err)
return
if !module.ICMP.DontFragment {
socket, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
level.Error(logger).Log("msg", "Error listening to socket", "err", err)
return
}
} else {
s, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
level.Error(logger).Log("msg", "Error listening to socket", "err", err)
return
}

rc, err := ipv4.NewRawConn(s)
if err != nil {
level.Error(logger).Log("msg", "cannot construct raw connection", "err", err)
return
}
socket = &dfConn{c: rc}
}
}

defer socket.Close()

var data []byte
if module.ICMP.PayloadSize != 0 {
data = make([]byte, module.ICMP.PayloadSize)
copy(data, "Prometheus Blackbox Exporter")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick, but it might make sense to include the exporter version in here.

} else {
data = []byte("Prometheus Blackbox Exporter")
}

body := &icmp.Echo{
ID: os.Getpid() & 0xffff,
Seq: int(getICMPSequence()),
Data: []byte("Prometheus Blackbox Exporter"),
Data: data,
}
level.Info(logger).Log("msg", "Creating ICMP packet", "seq", body.Seq, "id", body.ID)
wm := icmp.Message{
Expand Down Expand Up @@ -106,7 +135,7 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr
return
}

rb := make([]byte, 1500)
rb := make([]byte, 65536)
if err := socket.SetReadDeadline(deadline); err != nil {
level.Error(logger).Log("msg", "Error setting socket deadline", "err", err)
return
Expand Down Expand Up @@ -136,3 +165,60 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr
}
}
}

type dfConn struct {
c *ipv4.RawConn
}

func (c *dfConn) ReadFrom(b []byte) (int, net.Addr, error) {
h, p, _, err := c.c.ReadFrom(b)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work on Windows, which is a supported platform.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you propose I move about this? Different files for nix and windows? Or error when reading config?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First see if this can be done with Windows, we should be aiming for feature parity

if err != nil {
return 0, nil, err
}

copy(b, p)
n := len(b)
if len(p) < len(b) {
n = len(p)
}
return n, &net.IPAddr{IP: h.Src}, nil
}

func (d *dfConn) WriteTo(b []byte, addr net.Addr) (int, error) {
ipAddr, err := net.ResolveIPAddr(addr.Network(), addr.String())
if err != nil {
return 0, err
}

dfHeader := &ipv4.Header{
Version: ipv4.Version,
Len: ipv4.HeaderLen,
Protocol: 1,
TotalLen: ipv4.HeaderLen + len(b),
Flags: ipv4.DontFragment,
TTL: 64,
Dst: ipAddr.IP,
}

return len(b), d.c.WriteTo(dfHeader, b, nil)
}

func (d *dfConn) Close() error {
return d.c.Close()
}

func (d *dfConn) LocalAddr() net.Addr {
return nil
}

func (d *dfConn) SetDeadline(t time.Time) error {
return d.c.SetDeadline(t)
}

func (d *dfConn) SetReadDeadline(t time.Time) error {
return d.c.SetReadDeadline(t)
}

func (d *dfConn) SetWriteDeadline(t time.Time) error {
return d.c.SetWriteDeadline(t)
}