Repository: prometheus/blackbox_exporter
Pull request #981 (2022-10-25 15:29:49)

Add hostname parameter for TCP probe
This adds the ability to set the TLS server name for TCP probes using the hostname parameter just like #823 did for HTTP probes. It was mentioned by @Nothing4You in #624, and its something I need for monitoring in my environment as well.

electron0zero: looking for comments from @mem @roidelapluie, will merge if there are no comments :)
gitanuj: This seems like a missing feature for tcp probes (since http probes already expose hostname). Would love to see this ship soon!

Patch:
diff --git a/prober/handler.go b/prober/handler.go
index f449cac..2a8a1d9 100644
--- a/prober/handler.go
+++ b/prober/handler.go
@@ -107,6 +107,12 @@ func Handler(w http.ResponseWriter, r *http.Request, c *config.Config, logger lo
 		}
 	}
 
+	if module.Prober == "tcp" && hostname != "" {
+		if module.TCP.TLSConfig.ServerName == "" {
+			module.TCP.TLSConfig.ServerName = hostname
+		}
+	}
+
 	sl := newScrapeLogger(logger, moduleName, target)
 	level.Info(sl).Log("msg", "Beginning probe", "probe", module.Prober, "timeout_seconds", timeoutSeconds)
 


Test patch:
diff --git a/prober/handler_test.go b/prober/handler_test.go
index a144fb9..40039d4 100644
--- a/prober/handler_test.go
+++ b/prober/handler_test.go
@@ -16,8 +16,10 @@ package prober
 import (
 	"bytes"
 	"fmt"
+	"net"
 	"net/http"
 	"net/http/httptest"
+	"strconv"
 	"strings"
 	"testing"
 	"time"
@@ -203,3 +205,55 @@ func TestHostnameParam(t *testing.T) {
 		t.Errorf("probe request handler returned wrong status code: %v, want %v", status, http.StatusBadRequest)
 	}
 }
+
+func TestTCPHostnameParam(t *testing.T) {
+	c := &config.Config{
+		Modules: map[string]config.Module{
+			"tls_connect": {
+				Prober:  "tcp",
+				Timeout: 10 * time.Second,
+				TCP: config.TCPProbe{
+					TLS:        true,
+					IPProtocol: "ip4",
+					TLSConfig:  pconfig.TLSConfig{InsecureSkipVerify: true},
+				},
+			},
+		},
+	}
+
+	// check that 'hostname' parameter make its way to server_name in the tls_config
+	hostname := "foo.example.com"
+
+	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if r.Host != hostname {
+			t.Errorf("Unexpected Host: expected %q, got %q.", hostname, r.Host)
+		}
+		w.WriteHeader(http.StatusOK)
+	}))
+	defer ts.Close()
+
+	requrl := fmt.Sprintf("?module=tls_connect&debug=true&hostname=%s&target=%s", hostname, ts.Listener.Addr().(*net.TCPAddr).IP.String()+":"+strconv.Itoa(ts.Listener.Addr().(*net.TCPAddr).Port))
+
+	req, err := http.NewRequest("GET", requrl, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	rr := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil)
+	})
+
+	handler.ServeHTTP(rr, req)
+
+	if status := rr.Code; status != http.StatusOK {
+		t.Errorf("probe request handler returned wrong status code: %v, want %v", status, http.StatusOK)
+	}
+
+	// check debug output to confirm the server_name is set in tls_config and matches supplied hostname
+	if !strings.Contains(rr.Body.String(), "server_name: "+hostname) {
+		t.Errorf("probe failed, response body: %v", rr.Body.String())
+	}
+
+}