From 8d6a455df42b016ed2f7071e70718cad940937f9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Andrei=20Tudor=20C=C4=83lin?= Date: Fri, 21 Sep 2018 03:32:47 +0200 Subject: [PATCH] net: don't use splice for unix{packet,gram} connections As pointed out in the aftermath of CL 113997, splice is not supported for SOCK_SEQPACKET or SOCK_DGRAM unix sockets. Don't call poll.Splice in those cases. Change-Id: Ieab18fb0ae706fdeb249e3f54d51a3292e3ead62 Reviewed-on: https://go-review.googlesource.com/136635 Run-TryBot: Tobias Klauser TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/net/splice_linux.go | 5 +++- src/net/splice_test.go | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/net/splice_linux.go b/src/net/splice_linux.go index 8a4d55af62..69c3f65770 100644 --- a/src/net/splice_linux.go +++ b/src/net/splice_linux.go @@ -11,7 +11,7 @@ import ( // splice transfers data from r to c using the splice system call to minimize // copies from and to userspace. c must be a TCP connection. Currently, splice -// is only enabled if r is a TCP or Unix connection. +// is only enabled if r is a TCP or a stream-oriented Unix connection. // // If splice returns handled == false, it has performed no work. func splice(c *netFD, r io.Reader) (written int64, err error, handled bool) { @@ -28,6 +28,9 @@ func splice(c *netFD, r io.Reader) (written int64, err error, handled bool) { if tc, ok := r.(*TCPConn); ok { s = tc.fd } else if uc, ok := r.(*UnixConn); ok { + if uc.fd.net != "unix" { + return 0, nil, false + } s = uc.fd } else { return 0, nil, false diff --git a/src/net/splice_test.go b/src/net/splice_test.go index 93e8b1f8cc..4c300172c5 100644 --- a/src/net/splice_test.go +++ b/src/net/splice_test.go @@ -24,6 +24,8 @@ func TestSplice(t *testing.T) { t.Skip("skipping unix-to-tcp tests") } t.Run("unix-to-tcp", func(t *testing.T) { testSplice(t, "unix", "tcp") }) + t.Run("no-unixpacket", testSpliceNoUnixpacket) + t.Run("no-unixgram", testSpliceNoUnixgram) } func testSplice(t *testing.T, upNet, downNet string) { @@ -208,6 +210,56 @@ func testSpliceIssue25985(t *testing.T, upNet, downNet string) { wg.Wait() } +func testSpliceNoUnixpacket(t *testing.T) { + clientUp, serverUp, err := spliceTestSocketPair("unixpacket") + if err != nil { + t.Fatal(err) + } + defer clientUp.Close() + defer serverUp.Close() + clientDown, serverDown, err := spliceTestSocketPair("tcp") + if err != nil { + t.Fatal(err) + } + defer clientDown.Close() + defer serverDown.Close() + // If splice called poll.Splice here, we'd get err == syscall.EINVAL + // and handled == false. If poll.Splice gets an EINVAL on the first + // try, it assumes the kernel it's running on doesn't support splice + // for unix sockets and returns handled == false. This works for our + // purposes by somewhat of an accident, but is not entirely correct. + // + // What we want is err == nil and handled == false, i.e. we never + // called poll.Splice, because we know the unix socket's network. + _, err, handled := splice(serverDown.(*TCPConn).fd, serverUp) + if err != nil || handled != false { + t.Fatalf("got err = %v, handled = %t, want nil error, handled == false", err, handled) + } +} + +func testSpliceNoUnixgram(t *testing.T) { + addr, err := ResolveUnixAddr("unixgram", testUnixAddr()) + if err != nil { + t.Fatal(err) + } + up, err := ListenUnixgram("unixgram", addr) + if err != nil { + t.Fatal(err) + } + defer up.Close() + clientDown, serverDown, err := spliceTestSocketPair("tcp") + if err != nil { + t.Fatal(err) + } + defer clientDown.Close() + defer serverDown.Close() + // Analogous to testSpliceNoUnixpacket. + _, err, handled := splice(serverDown.(*TCPConn).fd, up) + if err != nil || handled != false { + t.Fatalf("got err = %v, handled = %t, want nil error, handled == false", err, handled) + } +} + func BenchmarkSplice(b *testing.B) { testHookUninstaller.Do(uninstallTestHooks) -- 2.50.0