HTTP: Add Skip Resolve Phase With Proxy option (#944)
authorJulien Pivotto <roidelapluie@o11y.eu>
Wed, 13 Jul 2022 12:15:28 +0000 (14:15 +0200)
committerGitHub <noreply@github.com>
Wed, 13 Jul 2022 12:15:28 +0000 (14:15 +0200)
* HTTP: Add Skip Resolve Phase With Proxy option

Signed-off-by: Julien Pivotto <roidelapluie@o11y.eu>
Co-authored-by: Suraj Nath <9503187+electron0zero@users.noreply.github.com>
CONFIGURATION.md
config/config.go
prober/http.go
prober/http_test.go

index 35e8446fe68814a1a2d9439f986edcd3a24f2fe4..1143c8961e249d5cefea2f9cd5a03e410c1f7afa 100644 (file)
@@ -114,6 +114,9 @@ The other placeholders are specified separately.
   # HTTP proxy server to use to connect to the targets.
   [ proxy_url: <string> ]
 
+  # Skip DNS resolution and URL change when an HTTP proxy (proxy_url) is set.
+  [ skip_resolve_phase_with_proxy: <boolean> | default = false ]
+
   # OAuth 2.0 configuration to use to connect to the targets.
   oauth2:
       [ <oauth2> ]
@@ -128,7 +131,6 @@ The other placeholders are specified separately.
   # The body of the HTTP request used in probe.
   body: [ <string> ]
 
-
 ```
 
 #### <http_header_match_spec>
index 8094b29e16bb960cd9a4b9e96d61ec73218b61cc..7a238244a0bdba0981a0764cb13eb51d49c7f68b 100644 (file)
@@ -210,6 +210,7 @@ type HTTPProbe struct {
        ValidHTTPVersions            []string                `yaml:"valid_http_versions,omitempty"`
        IPProtocol                   string                  `yaml:"preferred_ip_protocol,omitempty"`
        IPProtocolFallback           bool                    `yaml:"ip_protocol_fallback,omitempty"`
+       SkipResolvePhaseWithProxy    bool                    `yaml:"skip_resolve_phase_with_proxy,omitempty"`
        NoFollowRedirects            *bool                   `yaml:"no_follow_redirects,omitempty"`
        FailIfSSL                    bool                    `yaml:"fail_if_ssl,omitempty"`
        FailIfNotSSL                 bool                    `yaml:"fail_if_not_ssl,omitempty"`
index 99d38a2bf592281448d80ff8bcef1f831ab35eb7..802bdc9311df1d212d7659f781907bd3f8648a6e 100644 (file)
@@ -333,11 +333,15 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
        targetHost := targetURL.Hostname()
        targetPort := targetURL.Port()
 
-       ip, lookupTime, err := chooseProtocol(ctx, module.HTTP.IPProtocol, module.HTTP.IPProtocolFallback, targetHost, registry, logger)
-       durationGaugeVec.WithLabelValues("resolve").Add(lookupTime)
-       if err != nil {
-               level.Error(logger).Log("msg", "Error resolving address", "err", err)
-               return false
+       var ip *net.IPAddr
+       if !module.HTTP.SkipResolvePhaseWithProxy || module.HTTP.HTTPClientConfig.ProxyURL.URL == nil {
+               var lookupTime float64
+               ip, lookupTime, err = chooseProtocol(ctx, module.HTTP.IPProtocol, module.HTTP.IPProtocolFallback, targetHost, registry, logger)
+               durationGaugeVec.WithLabelValues("resolve").Add(lookupTime)
+               if err != nil {
+                       level.Error(logger).Log("msg", "Error resolving address", "err", err)
+                       return false
+               }
        }
 
        // Do not move the following variable to global scope. The cases.Caser returned by
@@ -404,16 +408,18 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
                httpConfig.Method = "GET"
        }
 
-       // Replace the host field in the URL with the IP we resolved.
        origHost := targetURL.Host
-       if targetPort == "" {
-               if strings.Contains(ip.String(), ":") {
-                       targetURL.Host = "[" + ip.String() + "]"
+       if ip != nil {
+               // Replace the host field in the URL with the IP we resolved.
+               if targetPort == "" {
+                       if strings.Contains(ip.String(), ":") {
+                               targetURL.Host = "[" + ip.String() + "]"
+                       } else {
+                               targetURL.Host = ip.String()
+                       }
                } else {
-                       targetURL.Host = ip.String()
+                       targetURL.Host = net.JoinHostPort(ip.String(), targetPort)
                }
-       } else {
-               targetURL.Host = net.JoinHostPort(ip.String(), targetPort)
        }
 
        var body io.Reader
index cd277618aa33b4158661ebae5365d4f97f4fa6d5..078475c1464c836ec459b120e31599caa82e38d2 100644 (file)
@@ -25,6 +25,7 @@ import (
        "io/ioutil"
        "net/http"
        "net/http/httptest"
+       "net/url"
        "os"
        "strconv"
        "strings"
@@ -1338,3 +1339,72 @@ func TestCookieJar(t *testing.T) {
                t.Fatalf("Redirect test failed unexpectedly, got %s", body)
        }
 }
+
+func TestSkipResolvePhase(t *testing.T) {
+       if testing.Short() {
+               t.Skip("skipping network dependent test")
+       }
+       ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+               w.WriteHeader(http.StatusNoContent)
+       }))
+       defer ts.Close()
+
+       t.Run("Without Proxy", func(t *testing.T) {
+               registry := prometheus.NewRegistry()
+               testCTX, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+               defer cancel()
+               result := ProbeHTTP(testCTX, ts.URL,
+                       config.Module{Timeout: time.Second, HTTP: config.HTTPProbe{IPProtocolFallback: true, HTTPClientConfig: pconfig.DefaultHTTPClientConfig, SkipResolvePhaseWithProxy: true}}, registry, log.NewNopLogger())
+               if !result {
+                       t.Fatalf("Probe unsuccessful")
+               }
+               mfs, err := registry.Gather()
+               if err != nil {
+                       t.Fatal(err)
+               }
+               expectedMetrics := map[string]map[string]map[string]struct{}{
+                       "probe_http_duration_seconds": {
+                               "phase": {
+                                       "connect":    {},
+                                       "processing": {},
+                                       "resolve":    {},
+                                       "transfer":   {},
+                                       "tls":        {},
+                               },
+                       },
+               }
+
+               checkMetrics(expectedMetrics, mfs, t)
+       })
+       t.Run("With Proxy", func(t *testing.T) {
+               registry := prometheus.NewRegistry()
+               testCTX, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+               defer cancel()
+               httpCfg := pconfig.DefaultHTTPClientConfig
+               u, err := url.Parse("http://127.0.0.1:3128")
+               if err != nil {
+                       t.Fatalf(err.Error())
+               }
+               httpCfg.ProxyURL = pconfig.URL{
+                       URL: u,
+               }
+               ProbeHTTP(testCTX, ts.URL,
+                       config.Module{Timeout: time.Second, HTTP: config.HTTPProbe{IPProtocolFallback: true, HTTPClientConfig: httpCfg, SkipResolvePhaseWithProxy: true}}, registry, log.NewNopLogger())
+               mfs, err := registry.Gather()
+               if err != nil {
+                       t.Fatal(err)
+               }
+               expectedMetrics := map[string]map[string]map[string]struct{}{
+                       "probe_http_duration_seconds": {
+                               "phase": {
+                                       "connect":    {},
+                                       "processing": {},
+                                       "transfer":   {},
+                                       "tls":        {},
+                               },
+                       },
+               }
+
+               checkMetrics(expectedMetrics, mfs, t)
+       })
+}