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)