diff --git a/README.md b/README.md index b751646..107e971 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,8 @@ The file `network_exporter.yml` can be either edited before building the docker # Main Config conf: refresh: 15m - nameserver: 192.168.0.1:53 + nameserver: 192.168.0.1:53 # Optional + nameserver_timeout: 250ms # Optional # Specific Protocol settings icmp: diff --git a/config/config.go b/config/config.go index 2eb2e23..e139f5d 100644 --- a/config/config.go +++ b/config/config.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "net" "os" "regexp" "strings" @@ -11,6 +12,7 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/syepes/network_exporter/pkg/common" + yaml "gopkg.in/yaml.v3" ) @@ -27,31 +29,32 @@ type Targets []struct { } type HTTPGet struct { - Interval duration `yaml:"interval" json:"interval"` - Timeout duration `yaml:"timeout" json:"timeout"` + Interval duration `yaml:"interval" json:"interval" default:"15s"` + Timeout duration `yaml:"timeout" json:"timeout" default:"14s"` } type TCP struct { - Interval duration `yaml:"interval" json:"interval"` - Timeout duration `yaml:"timeout" json:"timeout"` + Interval duration `yaml:"interval" json:"interval" default:"5s"` + Timeout duration `yaml:"timeout" json:"timeout" default:"4s"` } type MTR struct { - Interval duration `yaml:"interval" json:"interval"` - Timeout duration `yaml:"timeout" json:"timeout"` - MaxHops int `yaml:"max-hops" json:"max-hops"` - Count int `yaml:"count" json:"count"` + Interval duration `yaml:"interval" json:"interval" default:"5s"` + Timeout duration `yaml:"timeout" json:"timeout" default:"4s"` + MaxHops int `yaml:"max-hops" json:"max-hops" default:"30"` + Count int `yaml:"count" json:"count" default:"10"` } type ICMP struct { - Interval duration `yaml:"interval" json:"interval"` - Timeout duration `yaml:"timeout" json:"timeout"` - Count int `yaml:"count" json:"count"` + Interval duration `yaml:"interval" json:"interval" default:"5s"` + Timeout duration `yaml:"timeout" json:"timeout" default:"4s"` + Count int `yaml:"count" json:"count" default:"10"` } type Conf struct { - Refresh duration `yaml:"refresh" json:"refresh"` - Nameserver string `yaml:"nameserver" json:"nameserver"` + Refresh duration `yaml:"refresh" json:"refresh" default:"0s"` + Nameserver string `yaml:"nameserver" json:"nameserver"` + NameserverTimeout duration `yaml:"nameserver_timeout" json:"nameserver_timeout" default:"250ms"` } type Config struct { @@ -74,6 +77,12 @@ func (b *extraKV) UnmarshalYAML(unmarshal func(interface{}) error) error { return unmarshal(&b.Kv) } +// SafeConfig Safe configuration reload +type Resolver struct { + Resolver *net.Resolver + Timeout time.Duration +} + // SafeConfig Safe configuration reload type SafeConfig struct { Cfg *Config @@ -169,6 +178,9 @@ func (sc *SafeConfig) ReloadConfig(logger log.Logger, confFile string) (err erro } // Config precheck + if c.ICMP.Interval <= 0 || c.MTR.Interval <= 0 || c.TCP.Interval <= 0 || c.HTTPGet.Interval <= 0 { + return fmt.Errorf("intervals (icmp,mtr,tcp,http_get) must be >0") + } if c.MTR.MaxHops < 0 || c.MTR.MaxHops > 65500 { return fmt.Errorf("mtr.max-hops must be between 0 and 65500") } diff --git a/main.go b/main.go index bf470d8..4f17a92 100644 --- a/main.go +++ b/main.go @@ -25,7 +25,7 @@ import ( "github.com/syepes/network_exporter/pkg/common" ) -const version string = "1.7.1" +const version string = "1.7.2" var ( listenAddress = kingpin.Flag("web.listen-address", "The address to listen on for HTTP requests").Default(":9427").String() @@ -66,16 +66,16 @@ func main() { resolver := getResolver() monitorPING = monitor.NewPing(logger, sc, resolver, icmpID) - monitorPING.AddTargets() + go monitorPING.AddTargets() monitorMTR = monitor.NewMTR(logger, sc, resolver, icmpID) - monitorMTR.AddTargets() + go monitorMTR.AddTargets() monitorTCP = monitor.NewTCPPort(logger, sc, resolver) - monitorTCP.AddTargets() + go monitorTCP.AddTargets() monitorHTTPGet = monitor.NewHTTPGet(logger, sc, resolver) - monitorHTTPGet.AddTargets() + go monitorHTTPGet.AddTargets() go startConfigRefresh() @@ -142,18 +142,18 @@ func startServer() { level.Error(logger).Log("msg", "Could not start http", "err", http.ListenAndServe(*listenAddress, mux)) } -func getResolver() *net.Resolver { +func getResolver() *config.Resolver { if sc.Cfg.Conf.Nameserver == "" { level.Info(logger).Log("msg", "Configured default DNS resolver") - return net.DefaultResolver + return &config.Resolver{Resolver: net.DefaultResolver, Timeout: sc.Cfg.Conf.NameserverTimeout.Duration()} } level.Info(logger).Log("msg", "Configured custom DNS resolver") dialer := func(ctx context.Context, network, address string) (net.Conn, error) { - d := net.Dialer{Timeout: 3 * time.Second} + d := net.Dialer{Timeout: sc.Cfg.Conf.NameserverTimeout.Duration()} return d.DialContext(ctx, "udp", sc.Cfg.Conf.Nameserver) } - return &net.Resolver{PreferGo: true, Dial: dialer} + return &config.Resolver{Resolver: &net.Resolver{PreferGo: true, Dial: dialer}, Timeout: sc.Cfg.Conf.NameserverTimeout.Duration()} } func expVars(w http.ResponseWriter, r *http.Request) { diff --git a/monitor/monitor_http.go b/monitor/monitor_http.go index 516d712..8717e08 100644 --- a/monitor/monitor_http.go +++ b/monitor/monitor_http.go @@ -2,7 +2,6 @@ package monitor import ( "fmt" - "net" "net/url" "sync" "time" @@ -19,7 +18,7 @@ import ( type HTTPGet struct { logger log.Logger sc *config.SafeConfig - resolver *net.Resolver + resolver *config.Resolver interval time.Duration timeout time.Duration targets map[string]*target.HTTPGet @@ -27,7 +26,7 @@ type HTTPGet struct { } // NewHTTPGet creates and configures a new Monitoring HTTPGet instance -func NewHTTPGet(logger log.Logger, sc *config.SafeConfig, resolver *net.Resolver) *HTTPGet { +func NewHTTPGet(logger log.Logger, sc *config.SafeConfig, resolver *config.Resolver) *HTTPGet { if logger == nil { logger = log.NewNopLogger() } diff --git a/monitor/monitor_mtr.go b/monitor/monitor_mtr.go index 165be0e..1dea305 100644 --- a/monitor/monitor_mtr.go +++ b/monitor/monitor_mtr.go @@ -1,8 +1,8 @@ package monitor import ( + "context" "fmt" - "net" "sync" "time" @@ -18,7 +18,7 @@ import ( type MTR struct { logger log.Logger sc *config.SafeConfig - resolver *net.Resolver + resolver *config.Resolver icmpID *common.IcmpID interval time.Duration timeout time.Duration @@ -29,7 +29,7 @@ type MTR struct { } // NewMTR creates and configures a new Monitoring MTR instance -func NewMTR(logger log.Logger, sc *config.SafeConfig, resolver *net.Resolver, icmpID *common.IcmpID) *MTR { +func NewMTR(logger log.Logger, sc *config.SafeConfig, resolver *config.Resolver, icmpID *common.IcmpID) *MTR { if logger == nil { logger = log.NewNopLogger() } @@ -104,7 +104,7 @@ func (p *MTR) AddTargetDelayed(name string, host string, srcAddr string, labels defer p.mtx.Unlock() // Resolve hostnames - ipAddrs, err := common.DestAddrs(host, p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), host, p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { return err } @@ -181,7 +181,7 @@ func (p *MTR) CheckActiveTargets() (err error) { if target.Name != targetName { continue } - ipAddrs, err := common.DestAddrs(target.Host, p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), target.Host, p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { return err } diff --git a/monitor/monitor_ping.go b/monitor/monitor_ping.go index c04220a..1031b64 100644 --- a/monitor/monitor_ping.go +++ b/monitor/monitor_ping.go @@ -1,8 +1,8 @@ package monitor import ( + "context" "fmt" - "net" "sync" "time" @@ -18,7 +18,7 @@ import ( type PING struct { logger log.Logger sc *config.SafeConfig - resolver *net.Resolver + resolver *config.Resolver icmpID *common.IcmpID interval time.Duration timeout time.Duration @@ -28,7 +28,7 @@ type PING struct { } // NewPing creates and configures a new Monitoring ICMP instance -func NewPing(logger log.Logger, sc *config.SafeConfig, resolver *net.Resolver, icmpID *common.IcmpID) *PING { +func NewPing(logger log.Logger, sc *config.SafeConfig, resolver *config.Resolver, icmpID *common.IcmpID) *PING { if logger == nil { logger = log.NewNopLogger() } @@ -66,7 +66,7 @@ func (p *PING) AddTargets() { targetConfigTmp := []string{} for _, v := range p.sc.Cfg.Targets { if v.Type == "ICMP" || v.Type == "ICMP+MTR" { - ipAddrs, err := common.DestAddrs(v.Host, p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), v.Host, p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { level.Warn(p.logger).Log("type", "ICMP", "func", "AddTargets", "msg", fmt.Sprintf("Skipping resolve target: %s", v.Host), "err", err) } @@ -82,7 +82,7 @@ func (p *PING) AddTargets() { for _, targetName := range targetAdd { for _, target := range p.sc.Cfg.Targets { if target.Type == "ICMP" || target.Type == "ICMP+MTR" { - ipAddrs, err := common.DestAddrs(target.Host, p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), target.Host, p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { level.Warn(p.logger).Log("type", "ICMP", "func", "AddTargets", "msg", fmt.Sprintf("Skipping resolve target: %s", target.Host), "err", err) } @@ -136,7 +136,7 @@ func (p *PING) DelTargets() { targetConfigTmp := []string{} for _, v := range p.sc.Cfg.Targets { if v.Type == "ICMP" || v.Type == "ICMP+MTR" { - ipAddrs, err := common.DestAddrs(v.Host, p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), v.Host, p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { level.Warn(p.logger).Log("type", "ICMP", "func", "DelTargets", "msg", fmt.Sprintf("Skipping resolve target: %s", v.Host), "err", err) } @@ -191,7 +191,7 @@ func (p *PING) CheckActiveTargets() (err error) { if target.Name != targetName { continue } - ipAddrs, err := common.DestAddrs(target.Host, p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), target.Host, p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { return err } @@ -207,7 +207,7 @@ func (p *PING) CheckActiveTargets() (err error) { p.RemoveTarget(targetName + " " + targetIp) - ipAddrs, err := common.DestAddrs(target.Host, p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), target.Host, p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { level.Warn(p.logger).Log("type", "ICMP", "func", "CheckActiveTargets", "msg", fmt.Sprintf("Skipping resolve target: %s", target.Host), "err", err) } diff --git a/monitor/monitor_tcp.go b/monitor/monitor_tcp.go index 329b66a..f982926 100644 --- a/monitor/monitor_tcp.go +++ b/monitor/monitor_tcp.go @@ -1,8 +1,8 @@ package monitor import ( + "context" "fmt" - "net" "strings" "sync" "time" @@ -19,7 +19,7 @@ import ( type TCPPort struct { logger log.Logger sc *config.SafeConfig - resolver *net.Resolver + resolver *config.Resolver interval time.Duration timeout time.Duration targets map[string]*target.TCPPort @@ -27,7 +27,7 @@ type TCPPort struct { } // NewTCPPort creates and configures a new Monitoring TCP instance -func NewTCPPort(logger log.Logger, sc *config.SafeConfig, resolver *net.Resolver) *TCPPort { +func NewTCPPort(logger log.Logger, sc *config.SafeConfig, resolver *config.Resolver) *TCPPort { if logger == nil { logger = log.NewNopLogger() } @@ -68,7 +68,7 @@ func (p *TCPPort) AddTargets() { level.Warn(p.logger).Log("type", "TCP", "func", "AddTargets", "msg", fmt.Sprintf("Skipping target, could not identify host: %v (%v)", v.Host, v.Name)) continue } - ipAddrs, err := common.DestAddrs(conn[0], p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), conn[0], p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { level.Warn(p.logger).Log("type", "TCP", "func", "AddTargets", "msg", fmt.Sprintf("Skipping resolve target: %s", v.Host), "err", err) } @@ -89,7 +89,7 @@ func (p *TCPPort) AddTargets() { level.Warn(p.logger).Log("type", "TCP", "func", "AddTargets", "msg", fmt.Sprintf("Skipping target, could not identify host: %v (%v)", target.Host, target.Name)) continue } - ipAddrs, err := common.DestAddrs(conn[0], p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), conn[0], p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { level.Warn(p.logger).Log("type", "TCP", "func", "AddTargets", "msg", fmt.Sprintf("Skipping resolve target: %s", target.Name), "err", err) } @@ -102,7 +102,7 @@ func (p *TCPPort) AddTargets() { level.Warn(p.logger).Log("type", "TCP", "func", "AddTargets", "msg", fmt.Sprintf("Skipping target, could not identify host: %v (%v)", target.Host, target.Name)) continue } - ipAddrs, err := common.DestAddrs(conn[0], p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), conn[0], p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { level.Warn(p.logger).Log("type", "TCP", "func", "AddTargets", "msg", fmt.Sprintf("Skipping resolve target: %s", target.Host), "err", err) } @@ -158,7 +158,7 @@ func (p *TCPPort) DelTargets() { level.Warn(p.logger).Log("type", "TCP", "func", "DelTargets", "msg", fmt.Sprintf("Skipping target, could not identify host: %v (%v)", v.Host, v.Name)) continue } - ipAddrs, err := common.DestAddrs(conn[0], p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), conn[0], p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { level.Warn(p.logger).Log("type", "TCP", "func", "DelTargets", "msg", fmt.Sprintf("Skipping resolve target: %s", v.Host), "err", err) } @@ -213,7 +213,7 @@ func (p *TCPPort) CheckActiveTargets() (err error) { if target.Name != targetName { continue } - ipAddrs, err := common.DestAddrs(strings.Split(target.Host, ":")[0], p.resolver) + ipAddrs, err := common.DestAddrs(context.Background(), strings.Split(target.Host, ":")[0], p.resolver.Resolver, p.resolver.Timeout) if err != nil || len(ipAddrs) == 0 { return err } diff --git a/network_exporter.yml b/network_exporter.yml index 49b5eaf..761cc3a 100644 --- a/network_exporter.yml +++ b/network_exporter.yml @@ -1,7 +1,8 @@ # sdsdsd conf: refresh: 15m - nameserver: 8.8.8.8:53 + # nameserver: 8.8.8.8:53 # Optional + nameserver_timeout: 250ms # Optional icmp: interval: 3s @@ -29,6 +30,9 @@ targets: probe: - hostname1 - hostname2 + labels: + dc: home + rack: a1 - name: google-dns1 host: 8.8.8.8 type: ICMP @@ -40,4 +44,12 @@ targets: type: ICMP+MTR - name: cloudflare-dns-https host: 1.1.1.1:443 + source_ip: 192.168.1.1 type: TCP + - name: download-file-64M + host: http://test-debit.free.fr/65536.rnd + type: HTTPGet + - name: download-file-64M-proxy + host: http://test-debit.free.fr/65536.rnd + type: HTTPGet + proxy: http://localhost:3128 diff --git a/pkg/common/func.go b/pkg/common/func.go index 98e1974..1abbc98 100644 --- a/pkg/common/func.go +++ b/pkg/common/func.go @@ -20,21 +20,21 @@ func SrvRecordCheck(record string) bool { func SrvRecordHosts(record string) ([]string, error) { record_split := strings.Split(record, ".") - service := record_split[0][1:] + service := record_split[0][1:] proto := record_split[1][1:] - _, members, err := net.LookupSRV(service, proto, strings.Join(record_split[2:],".")) + _, members, err := net.LookupSRV(service, proto, strings.Join(record_split[2:], ".")) if err != nil { return nil, fmt.Errorf("resolving target: %v", err) } hosts := []string{} if proto == "tcp" { for _, host := range members { - hosts = append(hosts, fmt.Sprintf("%s:%d", host.Target[:len(host.Target) - 1], host.Port)) + hosts = append(hosts, fmt.Sprintf("%s:%d", host.Target[:len(host.Target)-1], host.Port)) } } else { for _, host := range members { - hosts = append(hosts, host.Target[:len(host.Target) - 1]) + hosts = append(hosts, host.Target[:len(host.Target)-1]) } } @@ -42,12 +42,15 @@ func SrvRecordHosts(record string) ([]string, error) { } // DestAddrs resolve the hostname to all it'ss IP's -func DestAddrs(host string, resolver *net.Resolver) ([]string, error) { +func DestAddrs(ctx context.Context, host string, resolver *net.Resolver, timeout time.Duration) ([]string, error) { ipAddrs := make([]string, 0) - addrs, err := resolver.LookupIPAddr(context.Background(), host) + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + addrs, err := resolver.LookupIPAddr(ctx, host) if err != nil { - return nil, fmt.Errorf("Resolving target: %v", err) + return nil, fmt.Errorf("resolving target: %v", err) } // Validate IPs @@ -187,7 +190,7 @@ func HasListDuplicates(m []string) (string, error) { for v := range m { if tmp[m[v]] { - return m[v], fmt.Errorf("Found duplicated record: %s", m[v]) + return m[v], fmt.Errorf("found duplicated record: %s", m[v]) } tmp[m[v]] = true }