Repository: netty/netty
Issue: Support abstract domain sockets using epoll on linux
Labels: feature

Version: 5.0.0.Alpha2

Context:
I am trying to write a client for dbus on linux and need to support abstract domain sockets to properly connect to the native dbus servers.

It is currently not possible to create an abstract domain socket using the epoll selector. While creating normal domain sockets works fine, an attempt to prepend a `\0` to open an abstract socket leads to an IOException when binding or connecting since the `GetStringUTFChars` used in the [source](https://github.com/netty/netty/blob/master/transport-native-epoll/src/main/c/io_netty_channel_epoll_Native.c#L1491) will encode the `\0` character. There could also be issues with the `strlen` and `strncpy` operations if this was moved to a different encode operation.

```
$ java -version
openjdk version "1.8.0_51"
OpenJDK Runtime Environment (build 1.8.0_51-b16)
OpenJDK 64-Bit Server VM (build 25.51-b03, mixed mode)
```

Operating System: Arch Linux 64-bit

```
$ uname -a
Linux ylt 4.1.4-1-ARCH #1 SMP PREEMPT Mon Aug 3 21:30:37 UTC 2015 x86_64 GNU/Linux
```


Issue comments:
@yawkat can you tell me what you mean with "abstract domain socket" ?

See [this man page](http://man7.org/linux/man-pages/man7/unix.7.html). Basically, domain socket names allow `\0` bytes in them, and on linux, a domain socket starting with `\0` does not appear in the file system and is called abstract. 

[socat](http://linux.die.net/man/1/socat) supports these through a special `ABSTRACT` address family. [dbus](http://dbus.freedesktop.org/doc/dbus-specification.html)  supports them through an `abstract` property in their address system. 

Sorry for not linking the exact paragraphs, I'm on mobile. 

@yawkat so I guess using 'GetStringChars' would be right here... I'm correct ?

Wouldn't that return an array of 16-bit numbers? Casting that to 8 bit would just give you lots of 0s.

I'm guessing the right way would either be decoding it as ascii or passing through the byte array directly instead of the string. Also need to replace the string operations because they rely on a trailing 0.

@yawkat good point. 

Okay, had nothing else to do so I tried my hand on a fix: #4092

Fixed by https://github.com/netty/netty/pull/4092


Pull request review comments:
Use : getBytes(CharsetUtil.UTF8)

Use : getBytes(CharsetUtil.UTF8)


Patch that resolved the issue:
diff --git a/transport-native-epoll/src/main/c/io_netty_channel_epoll_Native.c b/transport-native-epoll/src/main/c/io_netty_channel_epoll_Native.c
index 480bf50a1c7..4dc579ee9ef 100644
--- a/transport-native-epoll/src/main/c/io_netty_channel_epoll_Native.c
+++ b/transport-native-epoll/src/main/c/io_netty_channel_epoll_Native.c
@@ -1482,21 +1482,29 @@ JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_socketDomain(JNIEnv* e
     return fd;
 }
 
-JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_bindDomainSocket(JNIEnv* env, jclass clazz, jint fd, jstring socketPath) {
+// macro to calculate the length of a sockaddr_un struct for a given path length.
+// see sys/un.h#SUN_LEN, this is modified to allow nul bytes
+#define _UNIX_ADDR_LENGTH(path_len) (((struct sockaddr_un *) 0)->sun_path) + path_len
+
+JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_bindDomainSocket(JNIEnv* env, jclass clazz, jint fd, jbyteArray socketPath) {
     struct sockaddr_un addr;
 
     memset(&addr, 0, sizeof(addr));
     addr.sun_family = AF_UNIX;
 
-    const char* socket_path = (*env)->GetStringUTFChars(env, socketPath, 0);
-    memcpy(addr.sun_path, socket_path, strlen(socket_path));
+    const jbyte* socket_path = (*env)->GetByteArrayElements(env, socketPath, 0);
+    jint socket_path_len = (*env)->GetArrayLength(env, socketPath);
+    if (socket_path_len > sizeof(addr.sun_path)) {
+        socket_path_len = sizeof(addr.sun_path);
+    }
+    memcpy(addr.sun_path, socket_path, socket_path_len);
 
     if (unlink(socket_path) == -1 && errno != ENOENT) {
         return -errno;
     }
 
-    int res = bind(fd, (struct sockaddr*) &addr, sizeof(addr));
-    (*env)->ReleaseStringUTFChars(env, socketPath, socket_path);
+    int res = bind(fd, (struct sockaddr*) &addr, _UNIX_ADDR_LENGTH(socket_path_len));
+    (*env)->ReleaseByteArrayElements(env, socketPath, socket_path, 0);
 
     if (res == -1) {
         return -errno;
@@ -1504,22 +1512,27 @@ JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_bindDomainSocket(JNIEn
     return res;
 }
 
-JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_connectDomainSocket(JNIEnv* env, jclass clazz, jint fd, jstring socketPath) {
+JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_connectDomainSocket(JNIEnv* env, jclass clazz, jint fd, jbyteArray socketPath) {
     struct sockaddr_un addr;
+    jint socket_path_len;
 
     memset(&addr, 0, sizeof(addr));
     addr.sun_family = AF_UNIX;
 
-    const char* socket_path = (*env)->GetStringUTFChars(env, socketPath, 0);
-    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+    const jbyte* socket_path = (*env)->GetByteArrayElements(env, socketPath, 0);
+    socket_path_len = (*env)->GetArrayLength(env, socketPath);
+    if (socket_path_len > sizeof(addr.sun_path)) {
+        socket_path_len = sizeof(addr.sun_path);
+    }
+    memcpy(addr.sun_path, socket_path, socket_path_len);
 
     int res;
     int err;
     do {
-        res = connect(fd, (struct sockaddr*) &addr, sizeof(addr));
+        res = connect(fd, (struct sockaddr*) &addr, _UNIX_ADDR_LENGTH(socket_path_len));
     } while (res == -1 && ((err = errno) == EINTR));
 
-    (*env)->ReleaseStringUTFChars(env, socketPath, socket_path);
+    (*env)->ReleaseByteArrayElements(env, socketPath, socket_path, 0);
 
     if (res < 0) {
         return -err;
@@ -1688,4 +1701,4 @@ JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_splice0(JNIEnv* env, j
 
 JNIEXPORT jlong JNICALL Java_io_netty_channel_epoll_Native_ssizeMax(JNIEnv* env, jclass clazz) {
     return SSIZE_MAX;
-}
\ No newline at end of file
+}
diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/Native.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/Native.java
index 492d3f86804..4def6e42b0e 100644
--- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/Native.java
+++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/Native.java
@@ -19,6 +19,7 @@
 import io.netty.channel.ChannelException;
 import io.netty.channel.DefaultFileRegion;
 import io.netty.channel.unix.DomainSocketAddress;
+import io.netty.util.CharsetUtil;
 import io.netty.util.internal.EmptyArrays;
 import io.netty.util.internal.NativeLibraryLoader;
 import io.netty.util.internal.PlatformDependent;
@@ -435,7 +436,7 @@ public static void bind(int fd, SocketAddress socketAddress) throws IOException
             }
         } else if (socketAddress instanceof DomainSocketAddress) {
             DomainSocketAddress addr = (DomainSocketAddress) socketAddress;
-            int res = bindDomainSocket(fd, addr.path());
+            int res = bindDomainSocket(fd, addr.path().getBytes(CharsetUtil.UTF_8));
             if (res < 0) {
                 throw newIOException("bind", res);
             }
@@ -445,7 +446,7 @@ public static void bind(int fd, SocketAddress socketAddress) throws IOException
     }
 
     private static native int bind(int fd, byte[] address, int scopeId, int port);
-    private static native int bindDomainSocket(int fd, String path);
+    private static native int bindDomainSocket(int fd, byte[] path);
 
     public static void listen(int fd, int backlog) throws IOException {
         int res = listen0(fd, backlog);
@@ -464,7 +465,7 @@ public static boolean connect(int fd, SocketAddress socketAddress) throws IOExce
             res = connect(fd, address.address, address.scopeId, inetSocketAddress.getPort());
         } else if (socketAddress instanceof DomainSocketAddress) {
             DomainSocketAddress unixDomainSocketAddress = (DomainSocketAddress) socketAddress;
-            res = connectDomainSocket(fd, unixDomainSocketAddress.path());
+            res = connectDomainSocket(fd, unixDomainSocketAddress.path().getBytes(CharsetUtil.UTF_8));
         } else {
             throw new Error("Unexpected SocketAddress implementation " + socketAddress);
         }
@@ -479,7 +480,7 @@ public static boolean connect(int fd, SocketAddress socketAddress) throws IOExce
     }
 
     private static native int connect(int fd, byte[] address, int scopeId, int port);
-    private static native int connectDomainSocket(int fd, String path);
+    private static native int connectDomainSocket(int fd, byte[] path);
 
     public static boolean finishConnect(int fd) throws IOException {
         int res = finishConnect0(fd);


Test patch:
diff --git a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java
new file mode 100644
index 00000000000..ea0deb94a65
--- /dev/null
+++ b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.channel.epoll;
+
+import io.netty.channel.unix.DomainSocketAddress;
+import java.net.SocketAddress;
+import java.util.UUID;
+
+public class EpollAbstractDomainSocketEchoTest extends EpollDomainSocketEchoTest {
+    @Override
+    protected SocketAddress newSocketAddress() {
+        // these don't actually show up in the file system so creating a temp file isn't reliable
+        return new DomainSocketAddress("\0/tmp/" + UUID.randomUUID());
+    }
+}