package socket import ( "crypto/tls" "errors" "fmt" "io" "net" "os" "runtime" "strings" "sync" "testing" "time" "github.com/stretchr/testify/require" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" "github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/metric" _ "github.com/influxdata/telegraf/plugins/parsers/all" "github.com/influxdata/telegraf/plugins/parsers/influx" "github.com/influxdata/telegraf/testutil" ) var pki = testutil.NewPKI("../../../testutil/pki") func TestListenData(t *testing.T) { messages := [][]byte{ []byte("test,foo=bar v=1i 123456789\ntest,foo=baz v=2i 123456790\n"), []byte("test,foo=zab v=3i 123456791\n"), } expectedTemplates := []telegraf.Metric{ metric.New( "test", map[string]string{"foo": "bar"}, map[string]interface{}{"v": int64(1)}, time.Unix(0, 123456789), ), metric.New( "test", map[string]string{"foo": "baz"}, map[string]interface{}{"v": int64(2)}, time.Unix(0, 123456790), ), metric.New( "test", map[string]string{"foo": "zab"}, map[string]interface{}{"v": int64(3)}, time.Unix(0, 123456791), ), } tests := []struct { name string schema string buffersize config.Size encoding string }{ { name: "TCP", schema: "tcp", buffersize: config.Size(1024), }, { name: "TCP with TLS", schema: "tcp+tls", }, { name: "TCP with gzip encoding", schema: "tcp", buffersize: config.Size(1024), encoding: "gzip", }, { name: "UDP", schema: "udp", buffersize: config.Size(1024), }, { name: "UDP with gzip encoding", schema: "udp", buffersize: config.Size(1024), encoding: "gzip", }, { name: "unix socket", schema: "unix", buffersize: config.Size(1024), }, { name: "unix socket with TLS", schema: "unix+tls", }, { name: "unix socket with gzip encoding", schema: "unix", encoding: "gzip", }, { name: "unixgram socket", schema: "unixgram", buffersize: config.Size(1024), }, } serverTLS := pki.TLSServerConfig() clientTLS := pki.TLSClientConfig() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { proto := strings.TrimSuffix(tt.schema, "+tls") // Prepare the address and socket if needed var sockPath string var serviceAddress string var tlsCfg *tls.Config switch proto { case "tcp", "udp": serviceAddress = proto + "://" + "127.0.0.1:0" case "unix", "unixgram": if runtime.GOOS == "windows" { t.Skip("Skipping on Windows, as unixgram sockets are not supported") } // Create a socket sockPath = testutil.TempSocket(t) f, err := os.Create(sockPath) require.NoError(t, err) defer f.Close() serviceAddress = proto + "://" + sockPath } // Setup the configuration according to test specification cfg := &Config{ ContentEncoding: tt.encoding, ReadBufferSize: tt.buffersize, } if strings.HasSuffix(tt.schema, "tls") { cfg.ServerConfig = *serverTLS var err error tlsCfg, err = clientTLS.TLSConfig() require.NoError(t, err) } // Create the socket sock, err := cfg.NewSocket(serviceAddress, &SplitConfig{}, &testutil.Logger{}) require.NoError(t, err) // Create callbacks parser := &influx.Parser{} require.NoError(t, parser.Init()) var acc testutil.Accumulator onData := func(remote net.Addr, data []byte, _ time.Time) { m, err := parser.Parse(data) require.NoError(t, err) addr, _, err := net.SplitHostPort(remote.String()) if err != nil { addr = remote.String() } for i := range m { m[i].AddTag("source", addr) } acc.AddMetrics(m) } onError := func(err error) { acc.AddError(err) } // Start the listener require.NoError(t, sock.Setup()) sock.Listen(onData, onError) defer sock.Close() addr := sock.Address() // Create a noop client // Server is async, so verify no errors at the end. client, err := createClient(serviceAddress, addr, tlsCfg) require.NoError(t, err) require.NoError(t, client.Close()) // Setup the client for submitting data client, err = createClient(serviceAddress, addr, tlsCfg) require.NoError(t, err) // Conditionally add the source address to the expectation expected := make([]telegraf.Metric, 0, len(expectedTemplates)) for _, tmpl := range expectedTemplates { m := tmpl.Copy() switch proto { case "tcp", "udp": laddr := client.LocalAddr().String() addr, _, err := net.SplitHostPort(laddr) if err != nil { addr = laddr } m.AddTag("source", addr) case "unix", "unixgram": m.AddTag("source", sockPath) } expected = append(expected, m) } // Send the data with the correct encoding encoder, err := internal.NewContentEncoder(tt.encoding) require.NoError(t, err) for i, msg := range messages { m, err := encoder.Encode(msg) require.NoErrorf(t, err, "encoding failed for msg %d", i) _, err = client.Write(m) require.NoErrorf(t, err, "sending msg %d failed", i) } // Test the resulting metrics and compare against expected results require.Eventuallyf(t, func() bool { acc.Lock() defer acc.Unlock() return acc.NMetrics() >= uint64(len(expected)) }, time.Second, 100*time.Millisecond, "did not receive metrics (%d)", acc.NMetrics()) actual := acc.GetTelegrafMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics()) }) } } func TestListenConnection(t *testing.T) { messages := [][]byte{ []byte("test,foo=bar v=1i 123456789\ntest,foo=baz v=2i 123456790\n"), []byte("test,foo=zab v=3i 123456791\n"), } expectedTemplates := []telegraf.Metric{ metric.New( "test", map[string]string{"foo": "bar"}, map[string]interface{}{"v": int64(1)}, time.Unix(0, 123456789), ), metric.New( "test", map[string]string{"foo": "baz"}, map[string]interface{}{"v": int64(2)}, time.Unix(0, 123456790), ), metric.New( "test", map[string]string{"foo": "zab"}, map[string]interface{}{"v": int64(3)}, time.Unix(0, 123456791), ), } tests := []struct { name string schema string buffersize config.Size encoding string }{ { name: "TCP", schema: "tcp", buffersize: config.Size(1024), }, { name: "TCP with TLS", schema: "tcp+tls", }, { name: "TCP with gzip encoding", schema: "tcp", buffersize: config.Size(1024), encoding: "gzip", }, { name: "UDP", schema: "udp", buffersize: config.Size(1024), }, { name: "UDP with gzip encoding", schema: "udp", buffersize: config.Size(1024), encoding: "gzip", }, { name: "unix socket", schema: "unix", buffersize: config.Size(1024), }, { name: "unix socket with TLS", schema: "unix+tls", }, { name: "unix socket with gzip encoding", schema: "unix", encoding: "gzip", }, { name: "unixgram socket", schema: "unixgram", buffersize: config.Size(1024), }, } serverTLS := pki.TLSServerConfig() clientTLS := pki.TLSClientConfig() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { proto := strings.TrimSuffix(tt.schema, "+tls") // Prepare the address and socket if needed var sockPath string var serviceAddress string var tlsCfg *tls.Config switch proto { case "tcp", "udp": serviceAddress = proto + "://" + "127.0.0.1:0" case "unix", "unixgram": if runtime.GOOS == "windows" { t.Skip("Skipping on Windows, as unixgram sockets are not supported") } // Create a socket sockPath = testutil.TempSocket(t) f, err := os.Create(sockPath) require.NoError(t, err) defer f.Close() serviceAddress = proto + "://" + sockPath } // Setup the configuration according to test specification cfg := &Config{ ContentEncoding: tt.encoding, ReadBufferSize: tt.buffersize, } if strings.HasSuffix(tt.schema, "tls") { cfg.ServerConfig = *serverTLS var err error tlsCfg, err = clientTLS.TLSConfig() require.NoError(t, err) } // Create the socket sock, err := cfg.NewSocket(serviceAddress, &SplitConfig{}, &testutil.Logger{}) require.NoError(t, err) // Create callbacks parser := &influx.Parser{} require.NoError(t, parser.Init()) var acc testutil.Accumulator onConnection := func(remote net.Addr, reader io.ReadCloser) { data, err := io.ReadAll(reader) require.NoError(t, err) m, err := parser.Parse(data) require.NoError(t, err) addr, _, err := net.SplitHostPort(remote.String()) if err != nil { addr = remote.String() } for i := range m { m[i].AddTag("source", addr) } acc.AddMetrics(m) } onError := func(err error) { acc.AddError(err) } // Start the listener require.NoError(t, sock.Setup()) sock.ListenConnection(onConnection, onError) defer sock.Close() addr := sock.Address() // Create a noop client // Server is async, so verify no errors at the end. client, err := createClient(serviceAddress, addr, tlsCfg) require.NoError(t, err) require.NoError(t, client.Close()) // Setup the client for submitting data client, err = createClient(serviceAddress, addr, tlsCfg) require.NoError(t, err) // Conditionally add the source address to the expectation expected := make([]telegraf.Metric, 0, len(expectedTemplates)) for _, tmpl := range expectedTemplates { m := tmpl.Copy() switch proto { case "tcp", "udp": laddr := client.LocalAddr().String() addr, _, err := net.SplitHostPort(laddr) if err != nil { addr = laddr } m.AddTag("source", addr) case "unix", "unixgram": m.AddTag("source", sockPath) } expected = append(expected, m) } // Send the data with the correct encoding encoder, err := internal.NewContentEncoder(tt.encoding) require.NoError(t, err) for i, msg := range messages { m, err := encoder.Encode(msg) require.NoErrorf(t, err, "encoding failed for msg %d", i) _, err = client.Write(m) require.NoErrorf(t, err, "sending msg %d failed", i) } client.Close() // Test the resulting metrics and compare against expected results require.Eventuallyf(t, func() bool { acc.Lock() defer acc.Unlock() return acc.NMetrics() >= uint64(len(expected)) }, time.Second, 100*time.Millisecond, "did not receive metrics (%d)", acc.NMetrics()) actual := acc.GetTelegrafMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics()) }) } } func TestClosingConnections(t *testing.T) { // Setup the configuration cfg := &Config{ ReadBufferSize: 1024, } // Create the socket serviceAddress := "tcp://127.0.0.1:0" logger := &testutil.CaptureLogger{} sock, err := cfg.NewSocket(serviceAddress, &SplitConfig{}, logger) require.NoError(t, err) // Create callbacks parser := &influx.Parser{} require.NoError(t, parser.Init()) var acc testutil.Accumulator onData := func(_ net.Addr, data []byte, _ time.Time) { m, err := parser.Parse(data) require.NoError(t, err) acc.AddMetrics(m) } onError := func(err error) { acc.AddError(err) } // Start the listener require.NoError(t, sock.Setup()) sock.Listen(onData, onError) defer sock.Close() addr := sock.Address() // Create a noop client client, err := createClient(serviceAddress, addr, nil) require.NoError(t, err) _, err = client.Write([]byte("test value=42i\n")) require.NoError(t, err) require.Eventually(t, func() bool { acc.Lock() defer acc.Unlock() return acc.NMetrics() >= 1 }, time.Second, 100*time.Millisecond, "did not receive metric") // This has to be a stream-listener... listener, ok := sock.listener.(*streamListener) require.True(t, ok) listener.Lock() conns := listener.connections listener.Unlock() require.NotZero(t, conns) sock.Close() // Verify that plugin.Stop() closed the client's connection require.NoError(t, client.SetReadDeadline(time.Now().Add(time.Second))) buf := []byte{1} _, err = client.Read(buf) require.Equal(t, err, io.EOF) require.Empty(t, logger.Errors()) require.Empty(t, logger.Warnings()) } func TestMaxConnections(t *testing.T) { if runtime.GOOS == "darwin" { t.Skip("Skipping on darwin due to missing socket options") } // Setup the configuration period := config.Duration(10 * time.Millisecond) cfg := &Config{ MaxConnections: 5, KeepAlivePeriod: &period, } // Create the socket serviceAddress := "tcp://127.0.0.1:0" sock, err := cfg.NewSocket(serviceAddress, nil, &testutil.Logger{}) require.NoError(t, err) // Create callback var errs []error var mu sync.Mutex onData := func(_ net.Addr, _ []byte, _ time.Time) {} onError := func(err error) { mu.Lock() errs = append(errs, err) mu.Unlock() } // Start the listener require.NoError(t, sock.Setup()) sock.Listen(onData, onError) defer sock.Close() addr := sock.Address() // Create maximum number of connections and write some data. All of this // should succeed... clients := make([]*net.TCPConn, 0, cfg.MaxConnections) for i := 0; i < int(cfg.MaxConnections); i++ { c, err := net.DialTCP("tcp", nil, addr.(*net.TCPAddr)) require.NoError(t, err) require.NoError(t, c.SetWriteBuffer(0)) require.NoError(t, c.SetNoDelay(true)) clients = append(clients, c) _, err = c.Write([]byte("test value=42i\n")) require.NoError(t, err) } func() { mu.Lock() defer mu.Unlock() require.Empty(t, errs) }() // Create another client. This should fail because we already reached the // connection limit and the connection should be closed... client, err := net.DialTCP("tcp", nil, addr.(*net.TCPAddr)) require.NoError(t, err) require.NoError(t, client.SetWriteBuffer(0)) require.NoError(t, client.SetNoDelay(true)) require.Eventually(t, func() bool { mu.Lock() defer mu.Unlock() return len(errs) > 0 }, 3*time.Second, 100*time.Millisecond) func() { mu.Lock() defer mu.Unlock() require.Len(t, errs, 1) require.ErrorContains(t, errs[0], "too many connections") errs = make([]error, 0) }() require.Eventually(t, func() bool { _, err := client.Write([]byte("fail\n")) return err != nil }, 3*time.Second, 100*time.Millisecond) _, err = client.Write([]byte("test\n")) require.Error(t, err) // Check other connections are still good for _, c := range clients { _, err := c.Write([]byte("test\n")) require.NoError(t, err) } func() { mu.Lock() defer mu.Unlock() require.Empty(t, errs) }() // Close the first client and check if we can connect now require.NoError(t, clients[0].Close()) client, err = net.DialTCP("tcp", nil, addr.(*net.TCPAddr)) require.NoError(t, err) require.NoError(t, client.SetWriteBuffer(0)) require.NoError(t, client.SetNoDelay(true)) _, err = client.Write([]byte("success\n")) require.NoError(t, err) // Close all connections require.NoError(t, client.Close()) for _, c := range clients[1:] { require.NoError(t, c.Close()) } // Close the clients and check the connection counter listener, ok := sock.listener.(*streamListener) require.True(t, ok) require.Eventually(t, func() bool { listener.Lock() conns := listener.connections listener.Unlock() return conns == 0 }, 3*time.Second, 100*time.Millisecond) // Close the socket and check again... sock.Close() listener.Lock() conns := listener.connections listener.Unlock() require.Zero(t, conns) } func TestNoSplitter(t *testing.T) { messages := [][]byte{ []byte("test,foo=bar v"), []byte("=1i 123456789\ntest,foo=baz v=2i 123456790\ntest,foo=zab v=3i 123456791\n"), } expectedTemplates := []telegraf.Metric{ metric.New( "test", map[string]string{"foo": "bar"}, map[string]interface{}{"v": int64(1)}, time.Unix(0, 123456789), ), metric.New( "test", map[string]string{"foo": "baz"}, map[string]interface{}{"v": int64(2)}, time.Unix(0, 123456790), ), metric.New( "test", map[string]string{"foo": "zab"}, map[string]interface{}{"v": int64(3)}, time.Unix(0, 123456791), ), } // Prepare the address and socket if needed serviceAddress := "tcp://127.0.0.1:0" // Setup the configuration according to test specification cfg := &Config{} // Create the socket sock, err := cfg.NewSocket(serviceAddress, nil, &testutil.Logger{}) require.NoError(t, err) // Create callbacks parser := &influx.Parser{} require.NoError(t, parser.Init()) var acc testutil.Accumulator onConnection := func(remote net.Addr, reader io.ReadCloser) { data, err := io.ReadAll(reader) require.NoError(t, err) m, err := parser.Parse(data) require.NoError(t, err) addr, _, err := net.SplitHostPort(remote.String()) if err != nil { addr = remote.String() } for i := range m { m[i].AddTag("source", addr) } acc.AddMetrics(m) } onError := func(err error) { acc.AddError(err) } // Start the listener require.NoError(t, sock.Setup()) sock.ListenConnection(onConnection, onError) defer sock.Close() addr := sock.Address() // Create a noop client // Server is async, so verify no errors at the end. client, err := createClient(serviceAddress, addr, nil) require.NoError(t, err) require.NoError(t, client.Close()) // Setup the client for submitting data client, err = createClient(serviceAddress, addr, nil) require.NoError(t, err) // Conditionally add the source address to the expectation expected := make([]telegraf.Metric, 0, len(expectedTemplates)) for _, tmpl := range expectedTemplates { m := tmpl.Copy() laddr := client.LocalAddr().String() addr, _, err := net.SplitHostPort(laddr) if err != nil { addr = laddr } m.AddTag("source", addr) expected = append(expected, m) } // Send the data for i, msg := range messages { _, err = client.Write(msg) time.Sleep(100 * time.Millisecond) require.NoErrorf(t, err, "sending msg %d failed", i) } client.Close() // Test the resulting metrics and compare against expected results require.Eventuallyf(t, func() bool { acc.Lock() defer acc.Unlock() return acc.NMetrics() >= uint64(len(expected)) }, time.Second, 100*time.Millisecond, "did not receive metrics (%d)", acc.NMetrics()) actual := acc.GetTelegrafMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics()) } func TestTLSMemLeak(t *testing.T) { // For issue https://github.com/influxdata/telegraf/issues/15509 // Prepare the address and socket if needed serviceAddress := "tcp://127.0.0.1:0" // Setup a TLS socket to trigger the issue cfg := &Config{ ServerConfig: *pki.TLSServerConfig(), } // Create the socket sock, err := cfg.NewSocket(serviceAddress, nil, &testutil.Logger{}) require.NoError(t, err) // Create callbacks onConnection := func(_ net.Addr, reader io.ReadCloser) { //nolint:errcheck // We are not interested in the data so ignore all errors io.Copy(io.Discard, reader) } // Start the listener require.NoError(t, sock.Setup()) sock.ListenConnection(onConnection, nil) defer sock.Close() addr := sock.Address() // Setup the client side TLS tlsCfg, err := pki.TLSClientConfig().TLSConfig() require.NoError(t, err) // Define a single client write sequence data := []byte("test value=42i") write := func() error { conn, err := tls.Dial("tcp", addr.String(), tlsCfg) if err != nil { return err } defer conn.Close() _, err = conn.Write(data) return err } // Define a test with the given number of connections maxConcurrency := runtime.GOMAXPROCS(0) testCycle := func(connections int) (uint64, error) { var mu sync.Mutex var errs []error var wg sync.WaitGroup for count := 1; count < connections; count++ { wg.Add(1) go func() { defer wg.Done() if err := write(); err != nil { mu.Lock() errs = append(errs, err) mu.Unlock() } }() if count%maxConcurrency == 0 { wg.Wait() mu.Lock() if len(errs) > 0 { mu.Unlock() return 0, errors.Join(errs...) } mu.Unlock() } } //nolint:revive // We need to actively run the garbage collector to get reliable measurements runtime.GC() var stats runtime.MemStats runtime.ReadMemStats(&stats) return stats.HeapObjects, nil } // Measure the memory usage after a short warmup and after some time. // The final number of heap objects should not exceed the number of // runs by a save margin // Warmup, do a low number of runs to initialize all data structures // taking them out of the equation. initial, err := testCycle(100) require.NoError(t, err) // Do some more runs and make sure the memory growth is bound final, err := testCycle(2000) require.NoError(t, err) require.Less(t, final, 3*initial) } func createClient(endpoint string, addr net.Addr, tlsCfg *tls.Config) (net.Conn, error) { // Determine the protocol in a crude fashion parts := strings.SplitN(endpoint, "://", 2) if len(parts) != 2 { return nil, fmt.Errorf("invalid endpoint %q", endpoint) } protocol := parts[0] if tlsCfg == nil { return net.Dial(protocol, addr.String()) } if protocol == "unix" { tlsCfg.InsecureSkipVerify = true } return tls.Dial(protocol, addr.String(), tlsCfg) }