Android 中的 Unix Domain Socket 使用解析

源码中的 rc 文件里面定义了很多 socket:

bash 复制代码
# ./system/core/logd/logd.rc
service logd /system/bin/logd
    socket logd stream 0666 logd logd
    socket logdr seqpacket 0666 logd logd
    socket logdw dgram+passcred 0222 logd logd
    file /proc/kmsg r
    file /dev/kmsg w
    user logd
    group logd system package_info readproc
    capabilities SYSLOG AUDIT_CONTROL SETGID
    writepid /dev/cpuset/system-background/tasks

socket 命令的格式如下:

bash 复制代码
socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]

name: 名称
type: 类型,可以取值为 stream、dgram 或 seqpacket
perm: 权限
user: 用户
group: 组
  • socket 命令会创建一个 unix domain socket, 路径为 /dev/socket/name , 并将 fd 保存在环境变量中。

  • type 只能是 "dgram", "stream" or "seqpacket"。

  • user 和 group 默认值是 0。

  • seclabel 是这个 socket 的 SELinux security context, 它的默认值是 service 的 security context 或者基于其可执行文件的 security context。

  • 在代码中,可以通过 libcutils 库提供的 android_get_control_socket 函数来获取这个 socket 的 fd。

/dev/socket/ 目录下有很多文件:

bash 复制代码
ls /dev/socket/
adbd        logd  property_service      traced_consumer     zygote           
audioserver logdr statsdw               traced_producer     zygote_secondary 
dnsproxyd   logdw tombstoned_crash      usap_pool_primary   
fwmarkd     mdns  tombstoned_intercept  usap_pool_secondary 
lmkd        mdnsd tombstoned_java_trace wpa_wlan0

这些文件都是由 init 程序解析 rc 文件生产的。

我们来看一下生成过程:

解析 rc 文件的过程中会调用 CreateAndPublish 函数创建 socket:

cpp 复制代码
// system/core/init/descriptors.cpp

void DescriptorInfo::CreateAndPublish(const std::string& globalContext) const {
  // Create
  const std::string& contextStr = context_.empty() ? globalContext : context_;

  // 调用 Create 初始化 socket
  int fd = Create(contextStr);
  if (fd < 0) return;

  // Publish
  std::string publishedName = key() + name_;
  std::for_each(publishedName.begin(), publishedName.end(),
                [] (char& c) { c = isalnum(c) ? c : '_'; });

  // 将 socket 的名字与 fd 保存到环境变量中
  std::string val = std::to_string(fd);
  setenv(publishedName.c_str(), val.c_str(), 1);

  // make sure we don't close on exec
  fcntl(fd, F_SETFD, 0);
}


// 进一步调用 CreateSocket
int SocketInfo::Create(const std::string& context) const {
    auto types = android::base::Split(type(), "+");
    int flags =
        ((types[0] == "stream" ? SOCK_STREAM : (types[0] == "dgram" ? SOCK_DGRAM : SOCK_SEQPACKET)));
    bool passcred = types.size() > 1 && types[1] == "passcred";
    // 进一步调用 CreateSocket
    return CreateSocket(name().c_str(), flags, passcred, perm(), uid(), gid(), context.c_str());
}


//  初始化 socket  
int CreateSocket(const char* name, int type, bool passcred, mode_t perm, uid_t uid, gid_t gid,
                 const char* socketcon) {
    if (socketcon) {
        if (setsockcreatecon(socketcon) == -1) {
            PLOG(ERROR) << "setsockcreatecon(\"" << socketcon << "\") failed";
            return -1;
        }
    }

    // 初始化 Unix domain socket
    android::base::unique_fd fd(socket(PF_UNIX, type, 0));
    if (fd < 0) {
        PLOG(ERROR) << "Failed to open socket '" << name << "'";
        return -1;
    }

    if (socketcon) setsockcreatecon(NULL);

    // 地址家族为 AF_UNIX,ANDROID_SOCKET_DIR 为 /dev/socket
    struct sockaddr_un addr;
    memset(&addr, 0 , sizeof(addr));
    addr.sun_family = AF_UNIX;
    snprintf(addr.sun_path, sizeof(addr.sun_path),  "/%s",
             name);

    if ((unlink(addr.sun_path) != 0) && (errno != ENOENT)) {
        PLOG(ERROR) << "Failed to unlink old socket '" << name << "'";
        return -1;
    }

    std::string secontext;
    if (SelabelLookupFileContext(addr.sun_path, S_IFSOCK, &secontext) && !secontext.empty()) {
        setfscreatecon(secontext.c_str());
    }

    if (passcred) {
        int on = 1;
        if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) {
            PLOG(ERROR) << "Failed to set SO_PASSCRED '" << name << "'";
            return -1;
        }
    }

    // 绑定本地地址
    int ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));
    int savederrno = errno;

    if (!secontext.empty()) {
        setfscreatecon(nullptr);
    }

    if (ret) {
        errno = savederrno;
        PLOG(ERROR) << "Failed to bind socket '" << name << "'";
        goto out_unlink;
    }

    // 根据声明修改 owner 和 group
    if (lchown(addr.sun_path, uid, gid)) {
        PLOG(ERROR) << "Failed to lchown socket '" << addr.sun_path << "'";
        goto out_unlink;
    }

    // 根据声明修改权限
    if (fchmodat(AT_FDCWD, addr.sun_path, perm, AT_SYMLINK_NOFOLLOW)) {
        PLOG(ERROR) << "Failed to fchmodat socket '" << addr.sun_path << "'";
        goto out_unlink;
    }

    LOG(INFO) << "Created socket '" << addr.sun_path << "'"
              << ", mode " << std::oct << perm << std::dec
              << ", user " << uid
              << ", group " << gid;

    return fd.release();

out_unlink:
    unlink(addr.sun_path);
    return -1;
}

对于在 rc 文件中配置好的 socket,在系统启动时就初始化好了,当我们使用 socket 的时候,可以直接通过 android_get_control_socket 函数从环境变量中获取到 socket fd:

cpp 复制代码
int android_get_control_socket(const char* name) {

    // 从环境变量中获取 socket 对应的 fd
    int fd = __android_get_control_from_env(ANDROID_SOCKET_ENV_PREFIX, name);

    if (fd < 0) return fd;

    // Compare to UNIX domain socket name, must match!
    struct sockaddr_un addr;
    socklen_t addrlen = sizeof(addr);
    int ret = getsockname(fd, (struct sockaddr*)&addr, &addrlen);
    if (ret < 0) return -1;

    constexpr char prefix[] = ANDROID_SOCKET_DIR "/";
    constexpr size_t prefix_size = sizeof(prefix) - sizeof('\0');
    if ((strncmp(addr.sun_path, prefix, prefix_size) == 0) &&
        (strcmp(addr.sun_path + prefix_size, name) == 0)) {
        // It is what we think it is
        return fd;
    }
    return -1;
}

参考资料

关于

我叫阿豪,2015 年本科毕业于国防科学技术大学指挥信息系统专业,毕业后从事信息化装备的研发工作,工作内容主要涉及 Android Framework 与 Linux Kernel。

如果你对 Android Framework 感兴趣或者正在学习 Android Framework,可以关注我的微信公众号和抖音,我会持续分享我的学习经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui