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

相关推荐
沐言人生4 小时前
Android10 Framework—Init进程-8.服务端属性文件创建和mmap映射
android
沐言人生4 小时前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack
沐言人生5 小时前
Android10 Framework—Init进程-7.服务端属性安全上下文序列化
android·android studio·android jetpack
追光天使5 小时前
【Mac】和【安卓手机】 通过有线方式实现投屏
android·macos·智能手机·投屏·有线
小雨cc5566ru5 小时前
uniapp+Android智慧居家养老服务平台 0fjae微信小程序
android·微信小程序·uni-app
一切皆是定数6 小时前
Android车载——VehicleHal初始化(Android 11)
android·gitee
一切皆是定数6 小时前
Android车载——VehicleHal运行流程(Android 11)
android
problc6 小时前
Android 组件化利器:WMRouter 与 DRouter 的选择与实践
android·java
图王大胜7 小时前
Android SystemUI组件(11)SystemUIVisibility解读
android·framework·systemui·visibility
服装学院的IT男11 小时前
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-2
android