From cdbc06a6396f2db6cad041d7edff293c88476403 Mon Sep 17 00:00:00 2001
From: Aliaksandr Valialkin <valyala@victoriametrics.com>
Date: Thu, 26 Oct 2023 00:29:51 +0200
Subject: [PATCH] lib/promscrape: do not add a suggestion for enabling TCP6 in
 error message when the dial address is TCPv4

---
 lib/promscrape/statconn.go      | 43 +++++++++++++++++++++++++--
 lib/promscrape/statconn_test.go | 51 +++++++++++++++++++++++++++++++++
 2 files changed, 92 insertions(+), 2 deletions(-)
 create mode 100644 lib/promscrape/statconn_test.go

diff --git a/lib/promscrape/statconn.go b/lib/promscrape/statconn.go
index 8757342ccd..7aaf285809 100644
--- a/lib/promscrape/statconn.go
+++ b/lib/promscrape/statconn.go
@@ -4,6 +4,8 @@ import (
 	"context"
 	"fmt"
 	"net"
+	"strconv"
+	"strings"
 	"sync"
 	"sync/atomic"
 	"time"
@@ -22,7 +24,7 @@ func statStdDial(ctx context.Context, _, addr string) (net.Conn, error) {
 	dialsTotal.Inc()
 	if err != nil {
 		dialErrors.Inc()
-		if !netutil.TCP6Enabled() {
+		if !netutil.TCP6Enabled() && !isTCPv4Addr(addr) {
 			err = fmt.Errorf("%w; try -enableTCP6 command-line flag if you scrape ipv6 addresses", err)
 		}
 		return nil, err
@@ -60,7 +62,7 @@ func newStatDialFunc(proxyURL *proxy.URL, ac *promauth.Config) (fasthttp.DialFun
 		dialsTotal.Inc()
 		if err != nil {
 			dialErrors.Inc()
-			if !netutil.TCP6Enabled() {
+			if !netutil.TCP6Enabled() && !isTCPv4Addr(addr) {
 				err = fmt.Errorf("%w; try -enableTCP6 command-line flag if you scrape ipv6 addresses", err)
 			}
 			return nil, err
@@ -121,3 +123,40 @@ var (
 	connBytesRead    = metrics.NewCounter(`vm_promscrape_conn_bytes_read_total`)
 	connBytesWritten = metrics.NewCounter(`vm_promscrape_conn_bytes_written_total`)
 )
+
+func isTCPv4Addr(addr string) bool {
+	s := addr
+	for i := 0; i < 3; i++ {
+		n := strings.IndexByte(s, '.')
+		if n < 0 {
+			return false
+		}
+		if !isUint8NumString(s[:n]) {
+			return false
+		}
+		s = s[n+1:]
+	}
+	n := strings.IndexByte(s, ':')
+	if n < 0 {
+		return false
+	}
+	if !isUint8NumString(s[:n]) {
+		return false
+	}
+	s = s[n+1:]
+
+	// Verify TCP port
+	n, err := strconv.Atoi(s)
+	if err != nil {
+		return false
+	}
+	return n >= 0 && n < (1<<16)
+}
+
+func isUint8NumString(s string) bool {
+	n, err := strconv.Atoi(s)
+	if err != nil {
+		return false
+	}
+	return n >= 0 && n < (1<<8)
+}
diff --git a/lib/promscrape/statconn_test.go b/lib/promscrape/statconn_test.go
new file mode 100644
index 0000000000..37a2ecf3c6
--- /dev/null
+++ b/lib/promscrape/statconn_test.go
@@ -0,0 +1,51 @@
+package promscrape
+
+import (
+	"testing"
+)
+
+func TestIsTCPv4Addr(t *testing.T) {
+	f := func(addr string, resultExpected bool) {
+		t.Helper()
+		result := isTCPv4Addr(addr)
+		if result != resultExpected {
+			t.Fatalf("unexpected result for isIPv4Addr(%q); got %v; want %v", addr, result, resultExpected)
+		}
+	}
+
+	// empty addr
+	f("", false)
+
+	// too small number of octets
+	f("foobar", false)
+	f("1", false)
+	f("1.2", false)
+	f("1.2.3", false)
+	f("1.2.3.", false)
+
+	// non-numeric octets
+	f("foo.bar.baz.aaa", false)
+
+	// non-numeric last value
+	f("1.2.3.foo", false)
+
+	// negative value
+	f("1.2.3.-4", false)
+
+	// missing port
+	f("1.2.3.4", false)
+
+	// invalid port
+	f("1.2.3.4:foo", false)
+
+	// too big octet
+	f("1.2.3.444:5", false)
+
+	// too big port
+	f("1.2.3.4:152344", false)
+
+	// normal TCPv4 addr
+	f("1.2.3.4:5", true)
+	f("0.0.0.0:80", true)
+	f("1.2.3.4:65535", true)
+}