From f235fcc640969bddd3c0280cebb1efc691be557d Mon Sep 17 00:00:00 2001 From: Sven Rebhan <36194019+srebhan@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:23:34 +0100 Subject: [PATCH] feat(outputs.graphite): Allow to set the local address to bind (#14628) --- plugins/outputs/graphite/README.md | 6 ++++++ plugins/outputs/graphite/graphite.go | 24 +++++++++++++++++++++++ plugins/outputs/graphite/graphite_test.go | 17 ++++++++++++++++ plugins/outputs/graphite/sample.conf | 6 ++++++ 4 files changed, 53 insertions(+) diff --git a/plugins/outputs/graphite/README.md b/plugins/outputs/graphite/README.md index 7bb3b1189..a74e8b0a5 100644 --- a/plugins/outputs/graphite/README.md +++ b/plugins/outputs/graphite/README.md @@ -26,8 +26,14 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ## If multiple endpoints are configured, the output will be load balanced. ## Only one of the endpoints will be written to with each iteration. servers = ["localhost:2003"] + + ## Local address to bind when connecting to the server + ## If empty or not set, the local address is automatically chosen. + # local_address = "" + ## Prefix metrics name prefix = "" + ## Graphite output template ## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md template = "host.tags.measurement.field" diff --git a/plugins/outputs/graphite/graphite.go b/plugins/outputs/graphite/graphite.go index 975c817a6..0e83b070b 100644 --- a/plugins/outputs/graphite/graphite.go +++ b/plugins/outputs/graphite/graphite.go @@ -9,6 +9,7 @@ import ( "io" "math/rand" "net" + "strconv" "strings" "time" @@ -37,6 +38,7 @@ type Graphite struct { GraphiteStrictRegex string `toml:"graphite_strict_sanitize_regex"` // URL is only for backwards compatibility Servers []string `toml:"servers"` + LocalAddr string `toml:"local_address"` Prefix string `toml:"prefix"` Template string `toml:"template"` Templates []string `toml:"templates"` @@ -104,6 +106,28 @@ func (g *Graphite) Connect() error { // Dialer with timeout d := net.Dialer{Timeout: time.Duration(g.Timeout)} + if g.LocalAddr != "" { + // Resolve the local address into IP address and the given port if any + addr, sPort, err := net.SplitHostPort(g.LocalAddr) + if err != nil && !strings.Contains(err.Error(), "missing port") { + return fmt.Errorf("invalid local address: %w", err) + } + local, err := net.ResolveIPAddr("ip", addr) + if err != nil { + return fmt.Errorf("cannot resolve local address: %w", err) + } + + var port int + if sPort != "" { + p, err := strconv.ParseUint(sPort, 10, 16) + if err != nil { + return fmt.Errorf("invalid port: %w", err) + } + port = int(p) + } + + d.LocalAddr = &net.TCPAddr{IP: local.IP, Port: port, Zone: local.Zone} + } // Get secure connection if tls config is set var conn net.Conn diff --git a/plugins/outputs/graphite/graphite_test.go b/plugins/outputs/graphite/graphite_test.go index eff673789..62a024da4 100644 --- a/plugins/outputs/graphite/graphite_test.go +++ b/plugins/outputs/graphite/graphite_test.go @@ -585,6 +585,23 @@ func TestGraphiteOkWithTagsAndSeparatorUnderscore(t *testing.T) { require.NoError(t, err) } +func TestGraphiteLocalAddress(t *testing.T) { + t.Log("Starting server") + server, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + defer server.Close() + + plugin := Graphite{ + Servers: []string{server.Addr().String()}, + LocalAddr: "localhost", + Prefix: "my.prefix", + Log: testutil.Logger{}, + } + require.NoError(t, plugin.Init()) + require.NoError(t, plugin.Connect()) + require.NoError(t, plugin.Close()) +} + func TestIntegration(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") diff --git a/plugins/outputs/graphite/sample.conf b/plugins/outputs/graphite/sample.conf index 01f3a4b4e..d25495617 100644 --- a/plugins/outputs/graphite/sample.conf +++ b/plugins/outputs/graphite/sample.conf @@ -4,8 +4,14 @@ ## If multiple endpoints are configured, the output will be load balanced. ## Only one of the endpoints will be written to with each iteration. servers = ["localhost:2003"] + + ## Local address to bind when connecting to the server + ## If empty or not set, the local address is automatically chosen. + # local_address = "" + ## Prefix metrics name prefix = "" + ## Graphite output template ## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md template = "host.tags.measurement.field"