diff --git a/docs/LICENSE_OF_DEPENDENCIES.md b/docs/LICENSE_OF_DEPENDENCIES.md index da4a76bae1e95..c5cd70c650f29 100644 --- a/docs/LICENSE_OF_DEPENDENCIES.md +++ b/docs/LICENSE_OF_DEPENDENCIES.md @@ -323,6 +323,8 @@ following works: - github.com/russross/blackfriday [BSD 2-Clause "Simplified" License](https://github.com/russross/blackfriday/blob/master/LICENSE.txt) - github.com/safchain/ethtool [Apache License 2.0](https://github.com/safchain/ethtool/blob/master/LICENSE) - github.com/samber/lo [MIT License](https://github.com/samber/lo/blob/master/LICENSE) +- github.com/seancfoley/bintree [Apache License 2.0](https://github.com/seancfoley/bintree/blob/master/LICENSE) +- github.com/seancfoley/ipaddress-go [Apache License 2.0](https://github.com/seancfoley/ipaddress-go/blob/master/LICENSE) - github.com/shirou/gopsutil [BSD 3-Clause Clear License](https://github.com/shirou/gopsutil/blob/master/LICENSE) - github.com/shoenig/go-m1cpu [Mozilla Public License 2.0](https://github.com/shoenig/go-m1cpu/blob/main/LICENSE) - github.com/shopspring/decimal [MIT License](https://github.com/shopspring/decimal/blob/master/LICENSE) diff --git a/go.mod b/go.mod index 21ab845fbab52..e5e690d15a2c7 100644 --- a/go.mod +++ b/go.mod @@ -169,6 +169,7 @@ require ( github.com/robinson/gos7 v0.0.0-20240315073918-1f14519e4846 github.com/safchain/ethtool v0.3.0 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 + github.com/seancfoley/ipaddress-go v1.6.0 github.com/sensu/sensu-go/api/core/v2 v2.16.0 github.com/shirou/gopsutil/v3 v3.24.4 github.com/showwin/speedtest-go v1.7.7 @@ -436,6 +437,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/samber/lo v1.38.1 // indirect + github.com/seancfoley/bintree v1.3.1 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/signalfx/com_signalfx_metrics_protobuf v0.0.3 // indirect diff --git a/go.sum b/go.sum index a8ceb8d9fc67a..beec791ee578d 100644 --- a/go.sum +++ b/go.sum @@ -2111,6 +2111,10 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21 h1:yWfiTPwYxB0l5fGMhl/G+liULu github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seancfoley/bintree v1.3.1 h1:cqmmQK7Jm4aw8gna0bP+huu5leVOgHGSJBEpUx3EXGI= +github.com/seancfoley/bintree v1.3.1/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU= +github.com/seancfoley/ipaddress-go v1.6.0 h1:9z7yGmOnV4P2ML/dlR/kCJiv5tp8iHOOetJvxJh/R5w= +github.com/seancfoley/ipaddress-go v1.6.0/go.mod h1:TQRZgv+9jdvzHmKoPGBMxyiaVmoI0rYpfEk8Q/sL/Iw= github.com/sensu/sensu-go/api/core/v2 v2.16.0 h1:HOq4rFkQ1S5ZjxmMTLc5J5mAbECrnKWvtXXbMqr3j9s= github.com/sensu/sensu-go/api/core/v2 v2.16.0/go.mod h1:MjM7+MCGEyTAgaZ589SiGHwYiaYF7N/58dU0J070u/0= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= diff --git a/plugins/inputs/http_response/http_response.go b/plugins/inputs/http_response/http_response.go index ceea8d52a2407..a068caf2a9b35 100644 --- a/plugins/inputs/http_response/http_response.go +++ b/plugins/inputs/http_response/http_response.go @@ -16,13 +16,15 @@ import ( "time" "unicode/utf8" - "github.com/benbjohnson/clock" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" "github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/plugins/common/cookie" "github.com/influxdata/telegraf/plugins/common/tls" "github.com/influxdata/telegraf/plugins/inputs" + + "github.com/benbjohnson/clock" + "github.com/seancfoley/ipaddress-go/ipaddr" ) //go:embed sample.conf @@ -36,40 +38,45 @@ const ( // HTTPResponse struct type HTTPResponse struct { - Address string `toml:"address" deprecated:"1.12.0;1.35.0;use 'urls' instead"` - URLs []string `toml:"urls"` - HTTPProxy string `toml:"http_proxy"` - Body string + Address string `toml:"address" deprecated:"1.12.0;1.35.0;use 'urls' instead"` + URLs []string `toml:"urls"` + HTTPProxy string `toml:"http_proxy"` + Body string `toml:"body"` BodyForm map[string][]string `toml:"body_form"` - Method string - ResponseTimeout config.Duration - HTTPHeaderTags map[string]string `toml:"http_header_tags"` - Headers map[string]string - FollowRedirects bool + Method string `toml:"method"` + ResponseTimeout config.Duration `toml:"response_timeout"` + HTTPHeaderTags map[string]string `toml:"http_header_tags"` + Headers map[string]string `toml:"headers"` + FollowRedirects bool `toml:"follow_redirects"` // Absolute path to file with Bearer token BearerToken string `toml:"bearer_token"` ResponseBodyField string `toml:"response_body_field"` ResponseBodyMaxSize config.Size `toml:"response_body_max_size"` - ResponseStringMatch string - ResponseStatusCode int - Interface string + ResponseStringMatch string `toml:"response_string_match"` + ResponseStatusCode int `toml:"response_status_code"` + Interface string `toml:"interface"` // HTTP Basic Auth Credentials Username config.Secret `toml:"username"` Password config.Secret `toml:"password"` tls.ClientConfig cookie.CookieAuthConfig - Log telegraf.Logger + Log telegraf.Logger `toml:"-"` compiledStringMatch *regexp.Regexp - client httpClient + clients []client +} + +type client struct { + httpClient httpClient + address string } type httpClient interface { Do(req *http.Request) (*http.Response, error) } -// Set the proxy. A configured proxy overwrites the system wide proxy. +// Set the proxy. A configured proxy overwrites the system-wide proxy. func getProxyFunc(httpProxy string) func(*http.Request) (*url.URL, error) { if httpProxy == "" { return http.ProxyFromEnvironment @@ -85,9 +92,9 @@ func getProxyFunc(httpProxy string) func(*http.Request) (*url.URL, error) { } } -// createHTTPClient creates an http client which will timeout at the specified +// createHTTPClient creates an http client which will time out at the specified // timeout period and can follow redirects if specified -func (h *HTTPResponse) createHTTPClient() (*http.Client, error) { +func (h *HTTPResponse) createHTTPClient(address url.URL) (*http.Client, error) { tlsCfg, err := h.ClientConfig.TLSConfig() if err != nil { return nil, err @@ -96,7 +103,7 @@ func (h *HTTPResponse) createHTTPClient() (*http.Client, error) { dialer := &net.Dialer{} if h.Interface != "" { - dialer.LocalAddr, err = localAddress(h.Interface) + dialer.LocalAddr, err = localAddress(h.Interface, address) if err != nil { return nil, err } @@ -127,7 +134,7 @@ func (h *HTTPResponse) createHTTPClient() (*http.Client, error) { return client, nil } -func localAddress(interfaceName string) (net.Addr, error) { +func localAddress(interfaceName string, address url.URL) (net.Addr, error) { i, err := net.InterfaceByName(interfaceName) if err != nil { return nil, err @@ -138,14 +145,48 @@ func localAddress(interfaceName string) (net.Addr, error) { return nil, err } + urlInIPv6 := isURLInIPv6(address) for _, addr := range addrs { if naddr, ok := addr.(*net.IPNet); ok { - // leaving port set to zero to let kernel pick - return &net.TCPAddr{IP: naddr.IP}, nil + ipNetInIPv6 := isIPNetInIPv6(naddr) + + // choose interface address in the same format as server address + if ipNetInIPv6 == urlInIPv6 { + // leaving port set to zero to let kernel pick, but set zone for IPv6 format + if urlInIPv6 { + return &net.TCPAddr{IP: naddr.IP, Zone: interfaceName}, nil + } + return &net.TCPAddr{IP: naddr.IP}, nil + } } } - return nil, fmt.Errorf("cannot create local address for interface %q", interfaceName) + return nil, fmt.Errorf("cannot create local address for interface %q and server address %q", interfaceName, address.String()) +} + +// isURLInIPv6 returns true only when URL is in IPv6 format. +// For other cases (host part of url cannot be successfully validated, doesn't contain address at all or is in IPv4 format), it returns false. +func isURLInIPv6(address url.URL) bool { + host := ipaddr.NewHostName(address.Host) + err := host.Validate() + if err == nil { + if host.IsAddress() { + return host.AsAddress().IsIPv6() + } + } + + return false +} + +// isIPNetInIPv6 returns true only when IPNet can be represented in IPv6 format. +// For other cases (address cannot be successfully parsed or is in IPv4 format), it returns false. +func isIPNetInIPv6(address *net.IPNet) bool { + ipAddr, err := ipaddr.NewIPAddressFromNetIPNet(address) + if err == nil { + return ipAddr.IsIPv6() + } + + return false } func setResult(resultString string, fields map[string]interface{}, tags map[string]string) { @@ -196,10 +237,10 @@ func setError(err error, fields map[string]interface{}, tags map[string]string) } // HTTPGather gathers all fields and returns any errors it encounters -func (h *HTTPResponse) httpGather(u string) (map[string]interface{}, map[string]string, error) { +func (h *HTTPResponse) httpGather(cl client) (map[string]interface{}, map[string]string, error) { // Prepare fields and tags fields := make(map[string]interface{}) - tags := map[string]string{"server": u, "method": h.Method} + tags := map[string]string{"server": cl.address, "method": h.Method} var body io.Reader if h.Body != "" { @@ -214,7 +255,7 @@ func (h *HTTPResponse) httpGather(u string) (map[string]interface{}, map[string] body = strings.NewReader(values.Encode()) } - request, err := http.NewRequest(h.Method, u, body) + request, err := http.NewRequest(h.Method, cl.address, body) if err != nil { return nil, nil, err } @@ -245,14 +286,14 @@ func (h *HTTPResponse) httpGather(u string) (map[string]interface{}, map[string] // Start Timer start := time.Now() - resp, err := h.client.Do(request) + resp, err := cl.httpClient.Do(request) responseTime := time.Since(start).Seconds() // If an error in returned, it means we are dealing with a network error, as // HTTP error codes do not generate errors in the net/http library if err != nil { // Log error - h.Log.Debugf("Network error while polling %s: %s", u, err.Error()) + h.Log.Debugf("Network error while polling %s: %s", cl.address, err.Error()) // Get error details if setError(err, fields, tags) == nil { @@ -352,10 +393,9 @@ func (*HTTPResponse) SampleConfig() string { return sampleConfig } -// Gather gets all metric fields and tags and returns any errors it encounters -func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error { - // Compile the body regex if it exist - if h.compiledStringMatch == nil { +func (h *HTTPResponse) Init() error { + // Compile the body regex if it exists + if h.ResponseStringMatch != "" { var err error h.compiledStringMatch, err = regexp.Compile(h.ResponseStringMatch) if err != nil { @@ -367,7 +407,6 @@ func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error { if h.ResponseTimeout < config.Duration(time.Second) { h.ResponseTimeout = config.Duration(time.Second * 5) } - // Check send and expected string if h.Method == "" { h.Method = "GET" } @@ -380,32 +419,37 @@ func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error { } } - if h.client == nil { - client, err := h.createHTTPClient() - if err != nil { - return err - } - h.client = client - } - + h.clients = make([]client, 0, len(h.URLs)) for _, u := range h.URLs { addr, err := url.Parse(u) if err != nil { - acc.AddError(err) - continue + return fmt.Errorf("%q is not a valid address: %w", u, err) } if addr.Scheme != "http" && addr.Scheme != "https" { - acc.AddError(errors.New("only http and https are supported")) - continue + return fmt.Errorf("%q is not a valid address: only http and https types are supported", u) + } + + cl, err := h.createHTTPClient(*addr) + if err != nil { + return err } + h.clients = append(h.clients, client{httpClient: cl, address: u}) + } + + return nil +} + +// Gather gets all metric fields and tags and returns any errors it encounters +func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error { + for _, c := range h.clients { // Prepare data var fields map[string]interface{} var tags map[string]string // Gather data - fields, tags, err = h.httpGather(u) + fields, tags, err := h.httpGather(c) if err != nil { acc.AddError(err) continue diff --git a/plugins/inputs/http_response/http_response_test.go b/plugins/inputs/http_response/http_response_test.go index f9e55cad4dcf1..88b2d6eb38723 100644 --- a/plugins/inputs/http_response/http_response_test.go +++ b/plugins/inputs/http_response/http_response_test.go @@ -1,7 +1,3 @@ -//go:build !windows - -// TODO: Windows - should be enabled for Windows when https://github.com/influxdata/telegraf/issues/8451 is fixed - package http_response import ( @@ -200,9 +196,10 @@ func TestHeaders(t *testing.T) { "Host": "Hello", }, } + var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -239,8 +236,8 @@ func TestFields(t *testing.T) { } var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -278,8 +275,8 @@ func TestResponseBodyField(t *testing.T) { } var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -313,8 +310,8 @@ func TestResponseBodyField(t *testing.T) { } acc = testutil.Accumulator{} - err = h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields = map[string]interface{}{ "result_type": "body_read_error", @@ -349,6 +346,7 @@ func TestResponseBodyFormField(t *testing.T) { } var acc testutil.Accumulator + require.NoError(t, h.Init()) require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ @@ -387,8 +385,8 @@ func TestResponseBodyMaxSize(t *testing.T) { } var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "result_type": "body_read_error", @@ -421,8 +419,8 @@ func TestHTTPHeaderTags(t *testing.T) { } var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -456,8 +454,8 @@ func TestHTTPHeaderTags(t *testing.T) { } acc = testutil.Accumulator{} - err = h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedTags = map[string]interface{}{ "server": nil, @@ -479,8 +477,8 @@ func TestHTTPHeaderTags(t *testing.T) { } acc = testutil.Accumulator{} - err = h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields = map[string]interface{}{ "result_type": "connection_failed", @@ -538,8 +536,8 @@ func TestInterface(t *testing.T) { } var acc testutil.Accumulator - err = h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -574,9 +572,10 @@ func TestRedirects(t *testing.T) { }, FollowRedirects: true, } + var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -605,9 +604,10 @@ func TestRedirects(t *testing.T) { }, FollowRedirects: true, } + acc = testutil.Accumulator{} - err = h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields = map[string]interface{}{ "result_type": "connection_failed", @@ -642,9 +642,10 @@ func TestMethod(t *testing.T) { }, FollowRedirects: true, } + var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -673,9 +674,10 @@ func TestMethod(t *testing.T) { }, FollowRedirects: true, } + acc = testutil.Accumulator{} - err = h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields = map[string]interface{}{ "http_response_code": http.StatusMethodNotAllowed, @@ -705,9 +707,10 @@ func TestMethod(t *testing.T) { }, FollowRedirects: true, } + acc = testutil.Accumulator{} - err = h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields = map[string]interface{}{ "http_response_code": http.StatusMethodNotAllowed, @@ -742,9 +745,10 @@ func TestBody(t *testing.T) { }, FollowRedirects: true, } + var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -772,9 +776,10 @@ func TestBody(t *testing.T) { }, FollowRedirects: true, } + acc = testutil.Accumulator{} - err = h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields = map[string]interface{}{ "http_response_code": http.StatusBadRequest, @@ -808,9 +813,10 @@ func TestStringMatch(t *testing.T) { }, FollowRedirects: true, } + var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -846,9 +852,10 @@ func TestStringMatchJson(t *testing.T) { }, FollowRedirects: true, } + var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -886,8 +893,8 @@ func TestStringMatchFail(t *testing.T) { } var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -926,9 +933,10 @@ func TestTimeout(t *testing.T) { }, FollowRedirects: true, } + var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "result_type": "timeout", @@ -962,13 +970,7 @@ func TestBadRegex(t *testing.T) { FollowRedirects: true, } - var acc testutil.Accumulator - err := h.Gather(&acc) - require.Error(t, err) - - absentFields := []string{"http_response_code", "response_time", "content_length", "response_string_match", "result_type", "result_code"} - absentTags := []string{"status_code", "result", "server", "method"} - checkOutput(t, &acc, nil, nil, absentFields, absentTags) + require.ErrorContains(t, h.Init(), "failed to compile regular expression") } type fakeClient struct { @@ -981,6 +983,10 @@ func (f *fakeClient) Do(_ *http.Request) (*http.Response, error) { } func TestNetworkErrors(t *testing.T) { + cl := client{ + httpClient: &fakeClient{err: &url.Error{Err: &net.OpError{Err: &net.DNSError{Err: "DNS error"}}}}, + address: "", + } // DNS error h := &HTTPResponse{ Log: testutil.Logger{}, @@ -989,12 +995,12 @@ func TestNetworkErrors(t *testing.T) { Method: "GET", ResponseTimeout: config.Duration(time.Second * 20), FollowRedirects: false, - client: &fakeClient{err: &url.Error{Err: &net.OpError{Err: &net.DNSError{Err: "DNS error"}}}}, + clients: []client{cl}, } var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "result_type": "dns_error", @@ -1020,8 +1026,8 @@ func TestNetworkErrors(t *testing.T) { } acc = testutil.Accumulator{} - err = h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields = map[string]interface{}{ "result_type": "connection_failed", @@ -1053,9 +1059,10 @@ func TestContentLength(t *testing.T) { }, FollowRedirects: true, } + var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -1084,9 +1091,10 @@ func TestContentLength(t *testing.T) { }, FollowRedirects: true, } + acc = testutil.Accumulator{} - err = h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields = map[string]interface{}{ "http_response_code": http.StatusOK, @@ -1116,14 +1124,14 @@ func TestRedirect(t *testing.T) { require.NoError(t, err) }) - plugin := &HTTPResponse{ + h := &HTTPResponse{ URLs: []string{ts.URL}, ResponseStringMatch: "test", } var acc testutil.Accumulator - err := plugin.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expected := []telegraf.Metric{ testutil.MustMetric( @@ -1175,8 +1183,8 @@ func TestBasicAuth(t *testing.T) { } var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -1208,8 +1216,8 @@ func TestStatusCodeMatchFail(t *testing.T) { } var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusNoContent, @@ -1241,8 +1249,8 @@ func TestStatusCodeMatch(t *testing.T) { } var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusNoContent, @@ -1275,8 +1283,8 @@ func TestStatusCodeAndStringMatch(t *testing.T) { } var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, @@ -1310,8 +1318,8 @@ func TestStatusCodeAndStringMatchFail(t *testing.T) { } var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) expectedFields := map[string]interface{}{ "http_response_code": http.StatusNoContent, @@ -1348,9 +1356,11 @@ func TestSNI(t *testing.T) { ServerName: "super-special-hostname.example.com", }, } + var acc testutil.Accumulator - err := h.Gather(&acc) - require.NoError(t, err) + require.NoError(t, h.Init()) + require.NoError(t, h.Gather(&acc)) + expectedFields := map[string]interface{}{ "http_response_code": http.StatusOK, "result_type": "success", @@ -1367,3 +1377,90 @@ func TestSNI(t *testing.T) { absentFields := []string{"response_string_match"} checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil) } + +func Test_isURLInIPv6(t *testing.T) { + tests := []struct { + address url.URL + want bool + }{ + { + address: parseURL(t, "http://[2001:db8:a0b:12f0::1]/index.html"), + want: true, + }, { + address: parseURL(t, "http://[2001:db8:a0b:12f0::1]:80/index.html"), + want: true, + }, { + address: parseURL(t, "https://[2001:db8:a0b:12f0::1%25eth0]:15000/"), // `%25` escapes `%` + want: true, + }, { + address: parseURL(t, "https://2001:0db8:0001:0000:0000:0ab9:C0A8:0102"), + want: true, + }, { + address: parseURL(t, "http://[2607:f8b0:4005:802::1007]/"), + want: true, + }, { + address: parseURL(t, "https://127.0.0.1"), + want: false, + }, { + address: parseURL(t, "https://google.com"), + want: false, + }, { + address: parseURL(t, "https://thispagemayexist.ornot/index.html"), + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.address.String(), func(t *testing.T) { + if got := isURLInIPv6(tt.address); got != tt.want { + t.Errorf("isURLInIPv6() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_isIPNetInIPv6(t *testing.T) { + tests := []struct { + address *net.IPNet + want bool + }{ + { + address: &net.IPNet{ + IP: net.IPv4(127, 0, 0, 1), + Mask: net.CIDRMask(8, 32), + }, + want: false, + }, { + address: &net.IPNet{ + IP: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + Mask: net.CIDRMask(128, 128), + }, + want: true, + }, { + address: &net.IPNet{ + IP: net.IPv4(192, 168, 0, 1), + Mask: net.CIDRMask(24, 32), + }, + want: false, + }, { + address: &net.IPNet{ + IP: net.ParseIP("fe80::43ac:7835:471a:faba"), + Mask: net.CIDRMask(64, 128), + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.address.String(), func(t *testing.T) { + if got := isIPNetInIPv6(tt.address); got != tt.want { + t.Errorf("isIPNetInIPv6() = %v, want %v", got, tt.want) + } + }) + } +} + +func parseURL(t *testing.T, address string) url.URL { + u, err := url.Parse(address) + require.NoError(t, err) + require.NotNil(t, u) + return *u +}