From 4bc7b669c971bc7f4b1bd7b9df900cb97fe35b8b Mon Sep 17 00:00:00 2001 From: Goutham Veeramachaneni Date: Fri, 6 Oct 2017 16:03:46 +0530 Subject: [PATCH] Advanced ICMP options (#240) Add payload to ICMP Probe Add the option to send packets with DF-Bit set. Signed-off-by: Goutham Veeramachaneni --- CONFIGURATION.md | 6 +++ config/config.go | 9 ++++- prober/icmp.go | 102 +++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 108 insertions(+), 9 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 1ba3c55..3f66584 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -168,6 +168,12 @@ validate_additional_rrs: # The preferred IP protocol of the ICMP probe (ip4, ip6). [ preferred_ip_protocol: | default = "ip6" ] +# Set the DF-bit in the IP-header. Only works with ip4 and on *nix systems. +[ dont_fragment: | default = false ] + +# The size of the payload. +[ payload_size: ] + ``` ### diff --git a/config/config.go b/config/config.go index 81b504f..299889e 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io/ioutil" + "runtime" "strings" "sync" "time" @@ -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"` } @@ -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 } diff --git a/prober/icmp.go b/prober/icmp.go index 052cae5..dcb9096 100644 --- a/prober/icmp.go +++ b/prober/icmp.go @@ -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 ) @@ -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") + } 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{ @@ -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 @@ -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) + 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) +} -- 2.25.1