From 0b5ec525bb1ff3e382058570d9d59fe112d86db6 Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Fri, 25 Sep 2020 03:41:06 -0300 Subject: [PATCH] Add probe_icmp_reply_ttl_total IPv4 TTL (IPv6 hoplimit) identifies the number of routers used by an arriving packet. It can be used to detect routing problems. Signed-off-by: Luiz Angelo Daros de Luca --- prober/icmp.go | 54 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/prober/icmp.go b/prober/icmp.go index 1e3410a83..8ceb11bb0 100644 --- a/prober/icmp.go +++ b/prober/icmp.go @@ -80,6 +80,11 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr } registry.MustRegister(durationGaugeVec) + ttlGauge := prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "probe_icmp_reply_ttl_total", + Help: "Number of router hops returning from the target", + }) + registry.MustRegister(ttlGauge) ip, lookupTime, err := chooseProtocol(ctx, module.ICMP.IPProtocol, module.ICMP.IPProtocolFallback, target, registry, logger) if err != nil { @@ -104,6 +109,8 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr // Unprivileged sockets are supported on Darwin and Linux only. tryUnprivileged := runtime.GOOS == "darwin" || runtime.GOOS == "linux" + var icmpConn *icmp.PacketConn + var v4RawConn *v4Conn if ip.IP.To4() == nil { requestType = ipv6.ICMPTypeEchoRequest replyType = ipv6.ICMPTypeEchoReply @@ -112,7 +119,6 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr srcIP = net.ParseIP("::") } - var icmpConn *icmp.PacketConn if tryUnprivileged { // "udp" here means unprivileged -- not the protocol "udp". icmpConn, err = icmp.ListenPacket("udp6", srcIP.String()) @@ -131,6 +137,7 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr } } + icmpConn.IPv6PacketConn().SetControlMessage(ipv6.FlagHopLimit, true) socket = icmpConn } else { requestType = ipv4.ICMPTypeEcho @@ -143,21 +150,21 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr if module.ICMP.DontFragment { // 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. - icmpConn, err := net.ListenPacket("ip4:icmp", srcIP.String()) + netConn, err := net.ListenPacket("ip4:icmp", srcIP.String()) if err != nil { level.Error(logger).Log("msg", "Error listening to socket", "err", err) return } - rc, err := ipv4.NewRawConn(icmpConn) + rc, err := ipv4.NewRawConn(netConn) if err != nil { level.Error(logger).Log("msg", "Error creating raw connection", "err", err) return } - socket = &v4Conn{c: rc, df: true} + v4RawConn = &v4Conn{c: rc, df: true} + socket = v4RawConn + rc.SetControlMessage(ipv4.FlagTTL, true) } else { - var icmpConn *icmp.PacketConn - if tryUnprivileged { icmpConn, err = icmp.ListenPacket("udp4", srcIP.String()) if err != nil { @@ -174,8 +181,8 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr return } } - socket = icmpConn + icmpConn.IPv4PacketConn().SetControlMessage(ipv4.FlagTTL, true) } } @@ -249,7 +256,24 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr } level.Info(logger).Log("msg", "Waiting for reply packets") for { - n, peer, err := socket.ReadFrom(rb) + var n int + var peer net.Addr + var err error + var ttl int + + if ip.IP.To4() == nil { + var cm *ipv6.ControlMessage + n, cm, peer, err = icmpConn.IPv6PacketConn().ReadFrom(rb) + ttl = cm.HopLimit + } else { + var cm *ipv4.ControlMessage + if icmpConn != nil { + n, cm, peer, err = icmpConn.IPv4PacketConn().ReadFrom(rb) + } else { + n, cm, peer, err = v4RawConn.ReadFromCM(rb) + } + ttl = cm.TTL + } if err != nil { if nerr, ok := err.(net.Error); ok && nerr.Timeout() { level.Warn(logger).Log("msg", "Timeout reading from socket", "err", err) @@ -274,6 +298,7 @@ func ProbeICMP(ctx context.Context, target string, module config.Module, registr } if bytes.Equal(rb[:n], wb) { durationGaugeVec.WithLabelValues("rtt").Add(time.Since(rttStart).Seconds()) + ttlGauge.Set(float64(ttl)) level.Info(logger).Log("msg", "Found matching reply packet") return true } @@ -287,10 +312,10 @@ type v4Conn struct { src net.IP } -func (c *v4Conn) ReadFrom(b []byte) (int, net.Addr, error) { - h, p, _, err := c.c.ReadFrom(b) +func (c *v4Conn) ReadFromCM(b []byte) (int, *ipv4.ControlMessage, net.Addr, error) { + h, p, cm, err := c.c.ReadFrom(b) if err != nil { - return 0, nil, err + return 0, nil, nil, err } copy(b, p) @@ -298,7 +323,12 @@ func (c *v4Conn) ReadFrom(b []byte) (int, net.Addr, error) { if len(p) < len(b) { n = len(p) } - return n, &net.IPAddr{IP: h.Src}, nil + return n, cm, &net.IPAddr{IP: h.Src}, nil +} + +func (c *v4Conn) ReadFrom(b []byte) (int, net.Addr, error) { + n, _, peer, err := c.ReadFromCM(b) + return n, peer, err } func (d *v4Conn) WriteTo(b []byte, addr net.Addr) (int, error) {