init.rc 中使用 socket 命令

一、服务端创建

以 netd 为例,其启动脚本如下:

service netd /system/bin/netd
    class main
    socket dnsproxyd stream 0660 root inet
    socket mdns stream 0660 root system
    socket fwmarkd stream 0660 root inet
    onrestart restart zygote
    onrestart restart zygote_secondary
    # b/121354779: netd itself is not updatable, but on startup it dlopen()s the resolver library
    # from the DNS resolver APEX. Mark it as updatable so init won't start it until all APEX
    # packages are ready.
    updatable

socket 命令的格式如下:

socket <name> <type> <perm> [ <user> [ <group> ] ]
name: 名称
type: 类型,可以取值为 stream、dgram 或 seqpacket
perm: 权限
user: 用户
group: 组
服务启动后,会创建一个名为 /dev/socket/<name> 的文件节点,然后把它的fd传给启动程序

对于上面的启动脚本,会创建如下文件节点:

sq80x_64:/ # ls /dev/socket/ -l
srw-rw---- 1 root        inet          0 2021-05-14 14:27 dnsproxyd
srw-rw---- 1 root        inet          0 2021-05-14 14:27 fwmarkd
srw-rw---- 1 root        system        0 2021-05-14 14:27 mdns

socket 创建代码为 system/core/init/util.cpp 文件中 CreateSocket 函数:

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;
        }
    }

    android::base::unique_fd fd(socket(PF_UNIX, type, 0));    // 创建 Unix 域套接字
    if (fd < 0) {
        PLOG(ERROR) << "Failed to open socket '" << name << "'";
        return -1;
    }

    if (socketcon) setsockcreatecon(NULL);

    struct sockaddr_un addr;
    memset(&addr, 0 , sizeof(addr));
    addr.sun_family = AF_UNIX;    // 地址家族为 AF_UNIX,ANDROID_SOCKET_DIR 为 /dev/socket
    snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%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;
    }

    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;
}

在 netd 中使用 socket:

for (const auto& sock :
     {DNSPROXYLISTENER_SOCKET_NAME, FwmarkServer::SOCKET_NAME, MDnsSdListener::SOCKET_NAME}) {
    setCloseOnExec(sock);
}

以上 for 循环会遍历所有 socket name,然后调用 setCloseOnExec 函数:

void setCloseOnExec(const char *sock) {
    int fd = android_get_control_socket(sock);
    int flags = fcntl(fd, F_GETFD, 0);
    if (flags == -1) {
        ALOGE("Can't get fd flags for control socket %s", sock);
        flags = 0;
    }
    flags |= FD_CLOEXEC;
    if (fcntl(fd, F_SETFD, flags) == -1) {
        ALOGE("Can't set control socket %s to FD_CLOEXEC", sock);
    }
}

此函数作用:当使用 fork 创建子进程,子进程执行 exec 后,会自动关闭 fd。
上面的函数可以看到 android_get_control_socket 可以获取启动脚本定义的 socket 的 fd。

二、服务端监听消息

以 fwmarkd 套接字为例,分析使用套接字实现监听的过程,首先 fwmarkd 套接字在 FwmarkServer 类中使用:

class FwmarkServer : public SocketListener

FwmarkServer 类继承至 SocketListener,其构造函数如下:

FwmarkServer::FwmarkServer(NetworkController* networkController, EventReporter* eventReporter,
                           TrafficController* trafficCtrl)
    : SocketListener(SOCKET_NAME, true),
      mNetworkController(networkController),
      mEventReporter(eventReporter),
      mTrafficCtrl(trafficCtrl) {}

这里调用 SocketListener 的构造函数:

SocketListener::SocketListener(const char *socketName, bool listen) {
    init(socketName, -1, listen, false);
}

通过 init 函数初始化成员变量:

void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) {
    mListen = listen;
    mSocketName = socketName;
    mSock = socketFd;
    mUseCmdNum = useCmdNum;
    pthread_mutex_init(&mClientsLock, nullptr);
}

此时 mSocketName 赋值为 “fwmarkd”,mSock 赋值为 -1,mListen 赋值为 true。当启动监听时,会调用 startListener 函数:

int SocketListener::startListener(int backlog) {
    if (!mSocketName && mSock == -1) {
        SLOGE("Failed to start unbound listener");
        errno = EINVAL;
        return -1;
    } else if (mSocketName) {     // mSocketName 不为空
        if ((mSock = android_get_control_socket(mSocketName)) < 0) { // 通过套接字名字,获取对应的 fd。
            SLOGE("Obtaining file descriptor socket '%s' failed: %s",
                 mSocketName, strerror(errno));
            return -1;
        }
        SLOGV("got mSock = %d for %s", mSock, mSocketName);
        fcntl(mSock, F_SETFD, FD_CLOEXEC);    // 与 setCloseOnExec 函数功能相同
    }

    if (mListen && listen(mSock, backlog) < 0) {    // 监听套接字
        SLOGE("Unable to listen on socket (%s)", strerror(errno));
        return -1;
    } else if (!mListen)
        mClients[mSock] = new SocketClient(mSock, false, mUseCmdNum);

    if (pipe2(mCtrlPipe, O_CLOEXEC)) {    // 创建管道,用于线程同步
        SLOGE("pipe failed (%s)", strerror(errno));
        return -1;
    }

    if (pthread_create(&mThread, nullptr, SocketListener::threadStart, this)) { // 启动进程处理客户端连接
        SLOGE("pthread_create (%s)", strerror(errno));
        return -1;
    }

    return 0;
}

线程运行的函数:

void SocketListener::runListener() {
    while (true) {
        std::vector<pollfd> fds;    // poll 文件描述符集

        pthread_mutex_lock(&mClientsLock);
        fds.reserve(2 + mClients.size());
        fds.push_back({.fd = mCtrlPipe[0], .events = POLLIN});     // 将管道的一端加入 poll 文件集[0]
        if (mListen) fds.push_back({.fd = mSock, .events = POLLIN});    // 建 socket 添加到 poll 文件集[1]
        for (auto pair : mClients) {    // 客户端也添加到 poll 文件集
            // NB: calling out to an other object with mClientsLock held (safe)
            const int fd = pair.second->getSocket();
            if (fd != pair.first) SLOGE("fd mismatch: %d != %d", fd, pair.first);
            fds.push_back({.fd = fd, .events = POLLIN});
        }
        pthread_mutex_unlock(&mClientsLock);

        SLOGV("mListen=%d, mSocketName=%s", mListen, mSocketName);
        int rc = TEMP_FAILURE_RETRY(poll(fds.data(), fds.size(), -1));    // 执行 poll
        if (rc < 0) {
            SLOGE("poll failed (%s) mListen=%d", strerror(errno), mListen);
            sleep(1);
            continue;
        }

        if (fds[0].revents & (POLLIN | POLLERR)) {
            char c = CtrlPipe_Shutdown;
            TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
            if (c == CtrlPipe_Shutdown) {
                break;
            }
            continue;
        }
        if (mListen && (fds[1].revents & (POLLIN | POLLERR))) {
            int c = TEMP_FAILURE_RETRY(accept4(mSock, nullptr, nullptr, SOCK_CLOEXEC));    // 当有客户端连接时,执行 accept4
            if (c < 0) {
                SLOGE("accept failed (%s)", strerror(errno));
                sleep(1);
                continue;
            }
            pthread_mutex_lock(&mClientsLock);
            mClients[c] = new SocketClient(c, true, mUseCmdNum);    // 为客户端创建一个 SocketClient 对象用于通信。
            pthread_mutex_unlock(&mClientsLock);
        }

        // Add all active clients to the pending list first, so we can release
        // the lock before invoking the callbacks.
        std::vector<SocketClient*> pending;
        pthread_mutex_lock(&mClientsLock);
        const int size = fds.size();
        for (int i = mListen ? 2 : 1; i < size; ++i) { // 编译所有的客户端(不包括管道[0]和服务端socket)
            const struct pollfd& p = fds[i];
            if (p.revents & (POLLIN | POLLERR)) {
                auto it = mClients.find(p.fd);    // 执行的 unordered_map 的find 函数,返回迭代器
                if (it == mClients.end()) {
                    SLOGE("fd vanished: %d", p.fd);
                    continue;
                }
                SocketClient* c = it->second;    // 从迭代器中取出值
                pending.push_back(c);    // 将有数据就绪的客户端添加到 pending 集合
                c->incRef();
            }
        }
        pthread_mutex_unlock(&mClientsLock);

        for (SocketClient* c : pending) {    // 集中处理所有客户端消息
            // Process it, if false is returned, remove from the map
            SLOGV("processing fd %d", c->getSocket());
            if (!onDataAvailable(c)) {    // 调用 onDataAvailable 函数处理消息
                release(c, false);
            }
            c->decRef();
        }
    }
}

消息处理是通过 onDataAvailable 函数完成,此函数在子类中实现:

bool FwmarkServer::onDataAvailable(SocketClient* client) {
    int socketFd = -1;
    int error = processClient(client, &socketFd);
    if (socketFd >= 0) {
        close(socketFd);
    }

    // Always send a response even if there were connection errors or read errors, so that we don't
    // inadvertently cause the client to hang (which always waits for a response).
    client->sendData(&error, sizeof(error));

    // Always close the client connection (by returning false). This prevents a DoS attack where
    // the client issues multiple commands on the same connection, never reading the responses,
    // causing its receive buffer to fill up, and thus causing our client->sendData() to block.
    return false;
}

对于 SocketClient 类,不再做过多分析,仅贴出发送消息的函数申明:

// Send null-terminated C strings:
int sendMsg(int code, const char *msg, bool addErrno);
int sendMsg(int code, const char *msg, bool addErrno, bool useCmdNum);
int sendMsg(const char *msg);

// Provides a mechanism to send a response code to the client.
// Sends the code and a null character.
int sendCode(int code);

// Provides a mechanism to send binary data to client.
// Sends the code and a null character, followed by 4 bytes of
// big-endian length, and the data.
int sendBinaryMsg(int code, const void *data, int len);

// Sending binary data:
int sendData(const void *data, int len);
// iovec contents not preserved through call
int sendDatav(struct iovec *iov, int iovcnt);

注意在 SocketListener 类中还定义了一个广播函数,用于向所有客户端发送消息:

void sendBroadcast(int code, const char *msg, bool addErrno);

三、客户端发送消息

FwmarkClient 中封装了消息发送函数:

const sockaddr_un FWMARK_SERVER_PATH = {AF_UNIX, "/dev/socket/fwmarkd"};    // 目标地址

int FwmarkClient::send(FwmarkCommand* data, int fd, FwmarkConnectInfo* connectInfo) {
    mChannel = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);    // 创建 Unix 域套接字
    if (mChannel == -1) {
        return -errno;
    }

    if (TEMP_FAILURE_RETRY(connect(mChannel, reinterpret_cast<const sockaddr*>(&FWMARK_SERVER_PATH), 
                                   sizeof(FWMARK_SERVER_PATH))) == -1) {            // 发起网络连接
        // If we are unable to connect to the fwmark server, assume there's no error. This protects
        // against future changes if the fwmark server goes away.
        // TODO: This means that fd will very likely be misrouted. See if we can delete this in a
        //       separate CL.
        return 0;
    }

    // 构造数据
    iovec iov[2] = {
        { data, sizeof(*data) },
        { connectInfo, (connectInfo ? sizeof(*connectInfo) : 0) },
    };
    msghdr message;
    memset(&message, 0, sizeof(message));
    message.msg_iov = iov;
    message.msg_iovlen = std::size(iov);

    union {
        cmsghdr cmh;
        char cmsg[CMSG_SPACE(sizeof(fd))];
    } cmsgu;

    if (commandHasFd(data->cmdId)) {
        memset(cmsgu.cmsg, 0, sizeof(cmsgu.cmsg));
        message.msg_control = cmsgu.cmsg;
        message.msg_controllen = sizeof(cmsgu.cmsg);

        cmsghdr* const cmsgh = CMSG_FIRSTHDR(&message);
        cmsgh->cmsg_len = CMSG_LEN(sizeof(fd));
        cmsgh->cmsg_level = SOL_SOCKET;
        cmsgh->cmsg_type = SCM_RIGHTS;
        memcpy(CMSG_DATA(cmsgh), &fd, sizeof(fd));
    }

    if (TEMP_FAILURE_RETRY(sendmsg(mChannel, &message, 0)) == -1) {    //发送消息
        return -errno;
    }

    int error = 0;

    if (TEMP_FAILURE_RETRY(recv(mChannel, &error, sizeof(error), 0)) == -1) {    //接收服务端反馈
        return -errno;
    }

    return error;
}

在调用 connect 函数发起网络连接,地址为 sockaddr_un 类型,使用套接字的设备节点作为地址标识。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翻滚吧香香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值