|
| 1 | +package config |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "net" |
| 7 | + "strings" |
| 8 | + "time" |
| 9 | + |
| 10 | + "github.com/digineo/go-ping" |
| 11 | + mon "github.com/digineo/go-ping/monitor" |
| 12 | + "github.com/jimyag/log" |
| 13 | +) |
| 14 | + |
| 15 | +// Config represents configuration for the exporter. |
| 16 | +type Config struct { |
| 17 | + Targets []TargetConfig `toml:"targets"` |
| 18 | + Ping struct { |
| 19 | + Interval time.Duration ` toml:"interval"` // Interval for ICMP echo requests |
| 20 | + Timeout time.Duration `toml:"timeout"` // Timeout for ICMP echo request |
| 21 | + History int `toml:"history_size"` // Number of results to remember per target |
| 22 | + Size uint16 `toml:"payload_size"` // Payload size for ICMP echo requests |
| 23 | + } `toml:"ping"` |
| 24 | + |
| 25 | + DNS struct { |
| 26 | + Refresh time.Duration `toml:"refresh"` // Interval for refreshing DNS records and updating targets accordingly (0 if disabled) |
| 27 | + NameServer string `toml:"name_server"` // DNS server used to resolve hostname of targets |
| 28 | + } `yaml:"dns"` |
| 29 | + |
| 30 | + Web struct { |
| 31 | + Address string `toml:"address"` // Address on which to expose metrics and web interface |
| 32 | + MetricsPath string `toml:"metrics_path"` // Path under which to expose metrics |
| 33 | + } `toml:"web"` |
| 34 | + GlobalLabels map[string]string `toml:"global_labels"` |
| 35 | +} |
| 36 | + |
| 37 | +func (c *Config) Default() { |
| 38 | + if c.Ping.Interval == 0 { |
| 39 | + c.Ping.Interval = 5 * time.Second |
| 40 | + } |
| 41 | + if c.Ping.Timeout == 0 { |
| 42 | + c.Ping.Timeout = 4 * time.Second |
| 43 | + } |
| 44 | + if c.Ping.History == 0 { |
| 45 | + c.Ping.History = 10 |
| 46 | + } |
| 47 | + if c.Ping.Size == 0 { |
| 48 | + c.Ping.Size = 56 |
| 49 | + } |
| 50 | + |
| 51 | + if c.DNS.Refresh == 0 { |
| 52 | + c.DNS.Refresh = 1 * time.Minute |
| 53 | + } |
| 54 | + |
| 55 | + if c.Web.Address == "" { |
| 56 | + c.Web.Address = ":9113" |
| 57 | + } |
| 58 | + if c.Web.MetricsPath == "" { |
| 59 | + c.Web.MetricsPath = "/metrics" |
| 60 | + } |
| 61 | + if c.GlobalLabels == nil { |
| 62 | + c.GlobalLabels = make(map[string]string) |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +func (c *Config) Verify() { |
| 67 | + if c.Ping.History < 1 { |
| 68 | + log.Panic().Msg("ping.history-size must be greater than 0") |
| 69 | + } |
| 70 | + if c.Ping.Size > 65500 { |
| 71 | + log.Panic().Msg("ping.size must be between 0 and 65500") |
| 72 | + } |
| 73 | + |
| 74 | + if len(c.Targets) == 0 { |
| 75 | + log.Panic().Msg("No targets specified") |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +func (c *Config) SetupResolver() *net.Resolver { |
| 80 | + if c.DNS.NameServer == "" { |
| 81 | + return net.DefaultResolver |
| 82 | + } |
| 83 | + |
| 84 | + if !strings.HasSuffix(c.DNS.NameServer, ":53") { |
| 85 | + c.DNS.NameServer += ":53" |
| 86 | + } |
| 87 | + dialer := func(ctx context.Context, _, _ string) (net.Conn, error) { |
| 88 | + d := net.Dialer{} |
| 89 | + return d.DialContext(ctx, "udp", c.DNS.NameServer) |
| 90 | + } |
| 91 | + |
| 92 | + return &net.Resolver{PreferGo: true, Dial: dialer} |
| 93 | +} |
| 94 | + |
| 95 | +func (cfg *Config) TargetConfigByAddr(addr string) TargetConfig { |
| 96 | + for _, t := range cfg.Targets { |
| 97 | + if t.Addr == addr { |
| 98 | + return t |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + return TargetConfig{Addr: addr} |
| 103 | +} |
| 104 | + |
| 105 | +func (c *Config) GenMonitor() (*mon.Monitor, error) { |
| 106 | + var bind4, bind6 string |
| 107 | + if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil { |
| 108 | + // ipv4 enabled |
| 109 | + ln.Close() |
| 110 | + bind4 = "0.0.0.0" |
| 111 | + } |
| 112 | + if ln, err := net.Listen("tcp6", "[::1]:0"); err == nil { |
| 113 | + // ipv6 enabled |
| 114 | + ln.Close() |
| 115 | + bind6 = "::" |
| 116 | + } |
| 117 | + pinger, err := ping.New(bind4, bind6) |
| 118 | + if err != nil { |
| 119 | + return nil, fmt.Errorf("cannot start monitoring: %w", err) |
| 120 | + } |
| 121 | + |
| 122 | + if pinger.PayloadSize() != c.Ping.Size { |
| 123 | + pinger.SetPayloadSize(c.Ping.Size) |
| 124 | + } |
| 125 | + |
| 126 | + monitor := mon.New(pinger, |
| 127 | + c.Ping.Interval, |
| 128 | + c.Ping.Timeout) |
| 129 | + monitor.HistorySize = c.Ping.History |
| 130 | + resolver := c.SetupResolver() |
| 131 | + for i, target := range c.Targets { |
| 132 | + addrs, err := resolver.LookupIPAddr(context.Background(), target.Addr) |
| 133 | + if err != nil { |
| 134 | + log.Error(err).Str("target", target.Addr).Msg("cannot resolve target address") |
| 135 | + continue |
| 136 | + } |
| 137 | + for j, addr := range addrs { |
| 138 | + key := genPingMonitorKey(target.Addr, addr) |
| 139 | + if err := monitor.AddTargetDelayed(key, addr, |
| 140 | + time.Duration(10*i+j)*time.Millisecond, |
| 141 | + ); err != nil { |
| 142 | + log.Error(err).Str("target", target.Addr). |
| 143 | + Str("key", key). |
| 144 | + Msg("cannot add target") |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + } |
| 149 | + return monitor, nil |
| 150 | +} |
| 151 | + |
| 152 | +// genPingMonitorKey returns a unique key for the monitor based on target and addr |
| 153 | +// for example: "test.host.com 192.168.2.1 v4" |
| 154 | +func genPingMonitorKey(target string, addr net.IPAddr) string { |
| 155 | + if addr.IP.To4() == nil { |
| 156 | + return fmt.Sprintf("%s %s v6", target, addr.String()) |
| 157 | + } |
| 158 | + return fmt.Sprintf("%s %s v4", target, addr.String()) |
| 159 | +} |
| 160 | + |
| 161 | +func ParseMonitorKey(key string) (string, net.IPAddr) { |
| 162 | + |
| 163 | + parts := strings.Split(key, " ") |
| 164 | + if len(parts) != 3 { |
| 165 | + log.Panic().Str("key", key).Msg("cannot parse monitor key") |
| 166 | + } |
| 167 | + host := parts[0] |
| 168 | + ip := net.ParseIP(parts[1]) |
| 169 | + if ip == nil { |
| 170 | + log.Panic().Str("key", key).Msg("unexpected ip in monitor key") |
| 171 | + } |
| 172 | + |
| 173 | + if parts[2] == "v4" && ip.To4() == nil { |
| 174 | + log.Panic().Str("key", key).Msg("unexpected ip version in monitor key") |
| 175 | + } |
| 176 | + if parts[2] == "v6" && ip.To4() != nil { |
| 177 | + log.Panic().Str("key", key).Msg("unexpected ip version in monitor key") |
| 178 | + } |
| 179 | + return host, net.IPAddr{IP: ip} |
| 180 | +} |
0 commit comments