记一次 Android 关于 Unix abstract socket 的连接问题

2020-12-23 19:43:12 +08:00
 howellz

Keywords

Android Unix abstract namespace socket LocalSocket LocalServerSocket length 地址长度

正文

在一个项目中,需要 native 的程序对 java 的LocalServerSocket发起连接。即 Java 端监听某个 Unix 套接字,等待 native 的程序连接。结果死活连不上,总是提示Connection Refused(110)

这是 Java 端的代码示意,非常简单:

public void listen() {
    String name = "myname";
    mSocket = new LocalServerSocket(name);
    LocalSocket client = mSocket.accept();
    ...
}

发现客户端如果用 java 代码连接很容易就连接上了:

public void connect() {
    String name = "myname";
    LocalSocket client = new LocalSocket();
    client.connect(new LocalSocketAddress(name));
    Log.d("client", "connected to " + name);
}

但是客户端如果使用如下的 c 代码就连接不上:

#define SOKET_NAME "@myname"
void connect() {
    char* name = SOKET_NAME;
    struct sockaddr_un addr;
    int sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("failed to create socket");
        return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOKET_NAME, sizeof(addr.sun_path)-1);
    if (name[0] == '@') 
       addr.sun_path[0] = '\0';
	
    if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("failed to connect");
        close(sock);
        return -1;
    }
    ...
}

这段代码主要参考权威 man UNIX(7): https://man7.org/linux/man-pages/man7/unix.7.html

注意,这个 SOCKET_NAME 中的第一个字符 @是一个占位符,其实后面被'\0'取代了,符合abstract unix socket的地址要求。

一直输出:

failed to connect: Connection refused

同样的代码在普通的 Linux 系统上就可以正常工作,百思不得其姐姐。最后查看LocalSocket的实现代码,发现在 Android 的LocalSocket实现中,使用了如下的代码来确定 address 的长度:

*alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;

这样看来,** 直接传递sizeof(struct sockaddr_un)作为地址长度是不合适的,应该传递的长度不包括结构体中 sun_path[]数组后的全零 padding 。**

修改代码如下:

#define SOKET_NAME "@myname"
void connect() {
    char* name = SOKET_NAME;
    struct sockaddr_un addr;

    // add this variant
    int alen;

    int sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("failed to create socket");
        return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOKET_NAME, sizeof(addr.sun_path)-1);
    if (name[0] == '@') 
        addr.sun_path[0] = '\0';

    // Added this line 
    alen = offsetof(struct sockaddr_un, sun_path) + strlen(name);
	
    // change sizeof(addr) to alen
    if (connect(sock, (struct sockaddr *)&addr, alen) < 0) {
        perror("failed to connect");
        close(sock);
        return -1;
    }
}

再次运行,可以连接上了。

对于 Android 的实现,可以参考如下代码: https://android-opengrok.bangnimang.net/android-9.0.0_r61/xref/system/core/libcutils/socket_local_client_unix.cpp?r=db87e6d1#111

4594 次点击
所在节点    Android
1 条回复
tkl
2020-12-24 12:49:37 +08:00
分析的可以
另 百思不得其姐姐?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/738374

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX