github.com/grafana/synthetic-monitoring-agent@v0.64.2/internal/prober/icmp/utils.go (about)

     1  // Copyright 2016 The Prometheus Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package icmp
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"hash/fnv"
    20  	"net"
    21  	"time"
    22  
    23  	"github.com/go-kit/kit/log"       //nolint:staticcheck // TODO(mem): replace in BBE
    24  	"github.com/go-kit/kit/log/level" //nolint:staticcheck // TODO(mem): replace in BBE
    25  
    26  	"github.com/prometheus/client_golang/prometheus"
    27  )
    28  
    29  var protocolToGauge = map[string]float64{
    30  	"ip4": 4,
    31  	"ip6": 6,
    32  }
    33  
    34  // Returns the IP for the ipProtocol and lookup time.
    35  func chooseProtocol(ctx context.Context, ipProtocol string, fallbackIPProtocol bool, target string, retries int, registry *prometheus.Registry, logger log.Logger) (ip *net.IPAddr, lookupTime float64, err error) {
    36  	var fallbackProtocol string
    37  
    38  	probeDNSLookupTimeSeconds := prometheus.NewGauge(prometheus.GaugeOpts{
    39  		Name: "probe_dns_lookup_time_seconds",
    40  		Help: "Returns the time taken for probe dns lookup in seconds",
    41  	})
    42  
    43  	probeIPProtocolGauge := prometheus.NewGauge(prometheus.GaugeOpts{
    44  		Name: "probe_ip_protocol",
    45  		Help: "Specifies whether probe ip protocol is IP4 or IP6",
    46  	})
    47  
    48  	probeIPAddrHash := prometheus.NewGauge(prometheus.GaugeOpts{
    49  		Name: "probe_ip_addr_hash",
    50  		Help: "Specifies the hash of IP address. It's useful to detect if the IP address changes.",
    51  	})
    52  
    53  	registry.MustRegister(probeIPProtocolGauge)
    54  	registry.MustRegister(probeDNSLookupTimeSeconds)
    55  	registry.MustRegister(probeIPAddrHash)
    56  
    57  	if ipProtocol == "ip6" || ipProtocol == "" {
    58  		ipProtocol = "ip6"
    59  		fallbackProtocol = "ip4"
    60  	} else {
    61  		ipProtocol = "ip4"
    62  		fallbackProtocol = "ip6"
    63  	}
    64  
    65  	_ = level.Info(logger).Log("msg", "Resolving target address", "ip_protocol", ipProtocol)
    66  	resolveStart := time.Now()
    67  
    68  	defer func() {
    69  		lookupTime = time.Since(resolveStart).Seconds()
    70  		probeDNSLookupTimeSeconds.Add(lookupTime)
    71  	}()
    72  
    73  	resolver := &net.Resolver{}
    74  
    75  	for ; retries >= 0; retries-- {
    76  		if !fallbackIPProtocol {
    77  			ips, err := resolver.LookupIP(ctx, ipProtocol, target)
    78  			if err == nil {
    79  				for _, ip := range ips {
    80  					_ = level.Info(logger).Log("msg", "Resolved target address", "ip", ip.String())
    81  
    82  					probeIPProtocolGauge.Set(protocolToGauge[ipProtocol])
    83  					probeIPAddrHash.Set(ipHash(ip))
    84  
    85  					return &net.IPAddr{IP: ip}, lookupTime, nil
    86  				}
    87  			}
    88  
    89  			_ = level.Warn(logger).Log("msg", "Resolution with IP protocol failed", "err", err)
    90  
    91  			if isRetryableError(err) {
    92  				continue
    93  			} else {
    94  				break
    95  			}
    96  		}
    97  
    98  		ips, err := resolver.LookupIPAddr(ctx, target)
    99  		if err != nil {
   100  			_ = level.Warn(logger).Log("msg", "Resolution with IP protocol failed", "err", err)
   101  
   102  			if isRetryableError(err) {
   103  				continue
   104  			} else {
   105  				break
   106  			}
   107  		}
   108  
   109  		// Return the IP in the requested protocol.
   110  		fallbackIdx := int(-1)
   111  
   112  		for i, ip := range ips {
   113  			switch ipProtocol {
   114  			case "ip4":
   115  				if ip.IP.To4() != nil {
   116  					_ = level.Info(logger).Log("msg", "Resolved target address", "ip", ip.String())
   117  
   118  					probeIPProtocolGauge.Set(4)
   119  					probeIPAddrHash.Set(ipHash(ip.IP))
   120  
   121  					return &ip, lookupTime, nil
   122  				}
   123  
   124  				// ip4 as fallback
   125  				fallbackIdx = i
   126  
   127  			case "ip6":
   128  				if ip.IP.To4() == nil {
   129  					_ = level.Info(logger).Log("msg", "Resolved target address", "ip", ip.String())
   130  
   131  					probeIPProtocolGauge.Set(6)
   132  					probeIPAddrHash.Set(ipHash(ip.IP))
   133  
   134  					return &ip, lookupTime, nil
   135  				}
   136  
   137  				// ip6 as fallback
   138  				fallbackIdx = i
   139  			}
   140  		}
   141  
   142  		// Unable to find ip and no fallback set.
   143  		if fallbackIdx == -1 || !fallbackIPProtocol {
   144  			_ = level.Error(logger).Log("msg", "unable to find ip; no fallback")
   145  			break
   146  		}
   147  
   148  		// Use fallback ip protocol.
   149  		if fallbackProtocol == "ip4" {
   150  			probeIPProtocolGauge.Set(4)
   151  		} else {
   152  			probeIPProtocolGauge.Set(6)
   153  		}
   154  
   155  		fallback := ips[fallbackIdx]
   156  		probeIPAddrHash.Set(ipHash(fallback.IP))
   157  		_ = level.Info(logger).Log("msg", "Resolved target address", "ip", fallback.String())
   158  
   159  		return &fallback, lookupTime, nil
   160  	}
   161  
   162  	return nil, 0.0, fmt.Errorf("unable to find ip")
   163  }
   164  
   165  func ipHash(ip net.IP) float64 {
   166  	h := fnv.New32a()
   167  	_, _ = h.Write(ip)
   168  
   169  	return float64(h.Sum32())
   170  }
   171  
   172  func isRetryableError(err error) bool {
   173  	if err == nil {
   174  		return false
   175  	}
   176  
   177  	// Retry on timeouts.
   178  	if ne, ok := err.(net.Error); ok && ne.Timeout() {
   179  		return true
   180  	}
   181  
   182  	if dnsErr, ok := err.(*net.DNSError); ok {
   183  		// Retry on DNS lookup errors.
   184  		if dnsErr.IsTimeout || dnsErr.IsTemporary {
   185  			return true
   186  		}
   187  	}
   188  
   189  	// Don't retry on other errors.
   190  	return false
   191  }