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 <tobias.klauser@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/src/net/splice_linux.go b/src/net/splice_linux.go
index 8a4d55a..69c3f65 100644
--- a/src/net/splice_linux.go
+++ b/src/net/splice_linux.go
@@ -11,7 +11,7 @@
// 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 @@
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 93e8b1f..4c30017 100644
--- a/src/net/splice_test.go
+++ b/src/net/splice_test.go
@@ -24,6 +24,8 @@
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 @@
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)