From c2b98d686e32617474d3581384848e05ed8ce060 Mon Sep 17 00:00:00 2001 From: Gabriel Aszalos Date: Mon, 8 Feb 2021 12:31:35 +0200 Subject: [PATCH 1/5] statsd: add support for Windows Pipes This change adds support for Windows Named Pipes. They may be used when this option is [enabled in the Datadog Agent](https://github.com/DataDog/datadog-agent/pull/6830). --- statsd/pipe.go | 9 +++++++++ statsd/pipe_windows.go | 22 ++++++++++++++++++++++ statsd/statsd.go | 27 +++++++++++++++++++-------- 3 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 statsd/pipe.go create mode 100644 statsd/pipe_windows.go diff --git a/statsd/pipe.go b/statsd/pipe.go new file mode 100644 index 000000000..bfce89247 --- /dev/null +++ b/statsd/pipe.go @@ -0,0 +1,9 @@ +// +build !windows + +package statsd + +import "errors" + +func newWindowsPipeWriter(pipepath string) (statsdWriter, error) { + return nil, errors.New("Windows Named Pipes are not supported on other operating systems than Windows") +} diff --git a/statsd/pipe_windows.go b/statsd/pipe_windows.go new file mode 100644 index 000000000..a450e5370 --- /dev/null +++ b/statsd/pipe_windows.go @@ -0,0 +1,22 @@ +// +build windows + +package statsd + +import ( + "errors" + "net" + "time" + + "github.com/Microsoft/go-winio" +) + +type pipeWriter struct{ net.Conn } + +func (pipeWriter) SetWriteTimeout(_ time.Duration) error { + return errors.New("SetWriteTimeout is not supported on Windows Named Pipes") +} + +func newWindowsPipeWriter(pipepath string) (*pipeWriter, error) { + conn, err := winio.DialPipe(pipepath, nil) + return &pipeWriter{conn}, err +} diff --git a/statsd/statsd.go b/statsd/statsd.go index da433e637..a1362ac11 100644 --- a/statsd/statsd.go +++ b/statsd/statsd.go @@ -55,6 +55,12 @@ traffic instead of UDP. */ const UnixAddressPrefix = "unix://" +/* +WindowsPipeAddressPrefix holds the prefix to use to enable Windows Named Pipes +traffic instead of UDP. +*/ +const WindowsPipeAddressPrefix = `\\.\pipe\` + /* ddEnvTagsMapping is a mapping of each "DD_" prefixed environment variable to a specific tag name. @@ -94,8 +100,9 @@ const ( ) const ( - WriterNameUDP string = "udp" - WriterNameUDS string = "uds" + WriterNameUDP string = "udp" + WriterNameUDS string = "uds" + WriterWindowsPipe string = "pipe" ) type metric struct { @@ -222,17 +229,21 @@ type ClientMetrics struct { var _ ClientInterface = &Client{} func resolveAddr(addr string) (statsdWriter, string, error) { - if !strings.HasPrefix(addr, UnixAddressPrefix) { + switch { + case strings.HasPrefix(addr, WindowsPipeAddressPrefix): + w, err := newWindowsPipeWriter(addr) + return w, WriterWindowsPipe, err + case strings.HasPrefix(addr, UnixAddressPrefix): + w, err := newUDSWriter(addr[len(UnixAddressPrefix):]) + return w, WriterNameUDS, err + default: w, err := newUDPWriter(addr) return w, WriterNameUDP, err } - - w, err := newUDSWriter(addr[len(UnixAddressPrefix):]) - return w, WriterNameUDS, err } -// New returns a pointer to a new Client given an addr in the format "hostname:port" or -// "unix:///path/to/socket". +// New returns a pointer to a new Client given an addr in the format "hostname:port" for UDP, +// "unix:///path/to/socket" for UDS or "\\.\pipe\path\to\pipe" for Windows Named Pipes. func New(addr string, options ...Option) (*Client, error) { o, err := resolveOptions(options) if err != nil { From 7bee10d0901c1248875f3522426758ab8291dbf4 Mon Sep 17 00:00:00 2001 From: Gabriel Aszalos Date: Tue, 9 Feb 2021 11:34:47 +0200 Subject: [PATCH 2/5] add a test --- statsd/pipe_windows_test.go | 59 +++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 statsd/pipe_windows_test.go diff --git a/statsd/pipe_windows_test.go b/statsd/pipe_windows_test.go new file mode 100644 index 000000000..6d29d327d --- /dev/null +++ b/statsd/pipe_windows_test.go @@ -0,0 +1,59 @@ +// +build windows + +package statsd + +import ( + "io/ioutil" + "log" + "os" + "sync" + "testing" + + "github.com/Microsoft/go-winio" +) + +func TestPipeWriter(t *testing.T) { + f, err := ioutil.TempFile("", "test-pipe-") + if err != nil { + log.Fatal(err) + } + defer os.Remove(f.Name()) + pipepath := WindowsPipeAddressPrefix + f.Name() + ln, err := winio.ListenPipe(pipepath, &winio.PipeConfig{ + SecurityDescriptor: "D:AI(A;;GA;;;WD)", + InputBufferSize: 1_000_000, + }) + if err != nil { + log.Fatal(err) + } + out := make(chan string) + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + conn, err := ln.Accept() + if err != nil { + log.Fatal(err) + } + buf := make([]byte, 512) + n, err := conn.Read(buf) + if err != nil { + log.Fatal(err) + } + out <- string(buf[:n]) + conn.Close() + }() + + client, err := New(pipepath) + if err != nil { + log.Fatal(err) + } + if err := client.Gauge("metric", 1, []string{"key:val"}, 1); err != nil { + log.Fatal(err) + } + got := <-out + if exp := "metric:1|g|#key:val"; got != exp { + t.Fatalf("Expected %q, got %q", exp, got) + } + wg.Wait() // wait to close conn and goroutine +} From 1b5ff5610f11f633934a35059c5b773c53f27a04 Mon Sep 17 00:00:00 2001 From: Gabriel Aszalos Date: Tue, 9 Feb 2021 13:34:38 +0200 Subject: [PATCH 3/5] simplify test, remove waitgroup --- statsd/pipe_windows_test.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/statsd/pipe_windows_test.go b/statsd/pipe_windows_test.go index 6d29d327d..f30a542b7 100644 --- a/statsd/pipe_windows_test.go +++ b/statsd/pipe_windows_test.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "log" "os" - "sync" "testing" "github.com/Microsoft/go-winio" @@ -27,10 +26,7 @@ func TestPipeWriter(t *testing.T) { log.Fatal(err) } out := make(chan string) - var wg sync.WaitGroup - wg.Add(1) go func() { - defer wg.Done() conn, err := ln.Accept() if err != nil { log.Fatal(err) @@ -40,8 +36,8 @@ func TestPipeWriter(t *testing.T) { if err != nil { log.Fatal(err) } - out <- string(buf[:n]) conn.Close() + out <- string(buf[:n]) }() client, err := New(pipepath) @@ -55,5 +51,4 @@ func TestPipeWriter(t *testing.T) { if exp := "metric:1|g|#key:val"; got != exp { t.Fatalf("Expected %q, got %q", exp, got) } - wg.Wait() // wait to close conn and goroutine } From b2a2d623d64e932ba7d980c11391e26cbecbde88 Mon Sep 17 00:00:00 2001 From: Gabriel Aszalos Date: Wed, 10 Feb 2021 11:27:36 +0200 Subject: [PATCH 4/5] Update statsd/pipe.go Co-authored-by: Lucas Pimentel-Ordyna --- statsd/pipe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/statsd/pipe.go b/statsd/pipe.go index bfce89247..0d098a182 100644 --- a/statsd/pipe.go +++ b/statsd/pipe.go @@ -5,5 +5,5 @@ package statsd import "errors" func newWindowsPipeWriter(pipepath string) (statsdWriter, error) { - return nil, errors.New("Windows Named Pipes are not supported on other operating systems than Windows") + return nil, errors.New("Windows Named Pipes are only supported on Windows") } From be73f71593ab04eaa2b3fc62761ba82f09fe867f Mon Sep 17 00:00:00 2001 From: Gabriel Aszalos Date: Wed, 10 Feb 2021 11:29:05 +0200 Subject: [PATCH 5/5] improve SetWriteTimeout comment --- statsd/statsd.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/statsd/statsd.go b/statsd/statsd.go index a1362ac11..c743a1e98 100644 --- a/statsd/statsd.go +++ b/statsd/statsd.go @@ -378,7 +378,8 @@ func NewBuffered(addr string, buflen int) (*Client, error) { return New(addr, WithMaxMessagesPerPayload(buflen)) } -// SetWriteTimeout allows the user to set a custom UDS write timeout. Not supported for UDP. +// SetWriteTimeout allows the user to set a custom UDS write timeout. Not supported for UDP +// or Windows Pipes. func (c *Client) SetWriteTimeout(d time.Duration) error { if c == nil { return ErrNoClient