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 }