• libcontainer AF_UNIX套接字 + SCM_RIGHTS


    func RecvFd(socket *os.File) (*os.File, error) {
            // For some reason, unix.Recvmsg uses the length rather than the capacity
            // when passing the msg_controllen and other attributes to recvmsg.  So we
            // have to actually set the length.
            name := make([]byte, MaxNameLen)
            oob := make([]byte, oobSpace)
    
            sockfd := socket.Fd()
            n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, 0)
            if err != nil {
                    return nil, err
            }
    
            if n >= MaxNameLen || oobn != oobSpace {
                    return nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn)
            }
    
            // Truncate.
            name = name[:n]
            oob = oob[:oobn]
    
            scms, err := unix.ParseSocketControlMessage(oob)
            if err != nil {
                    return nil, err
            }
            if len(scms) != 1 {
                    return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms))
            }
            scm := scms[0]
    
            fds, err := unix.ParseUnixRights(&scm)
            if err != nil {
                    return nil, err
            }
            if len(fds) != 1 {
                    return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds))
            }
            fd := uintptr(fds[0])
    
            return os.NewFile(fd, string(name)), nil
    }
    
    // SendFd sends a file descriptor over the given AF_UNIX socket. In
    // addition, the file.Name() of the given file will also be sent as
    // non-auxiliary data in the same payload (allowing to send contextual
    // information for a file descriptor).
    func SendFd(socket *os.File, name string, fd uintptr) error {
            if len(name) >= MaxNameLen {
                    return fmt.Errorf("sendfd: filename too long: %s", name)
            }
            oob := unix.UnixRights(int(fd))
            return unix.Sendmsg(int(socket.Fd()), []byte(name), oob, nil, 0)
    }
    / RecvFd waits for a file descriptor to be sent over the given AF_UNIX
    // socket. The file name of the remote file descriptor will be recreated
    // locally (it is sent as non-auxiliary data in the same payload).
    func RecvFd(socket *os.File) (*os.File, error) {
        // For some reason, unix.Recvmsg uses the length rather than the capacity
        // when passing the msg_controllen and other attributes to recvmsg.  So we
        // have to actually set the length.
        name := make([]byte, MaxNameLen)
        oob := make([]byte, oobSpace)
    
        sockfd := socket.Fd()
        n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, 0)
        if err != nil {
            return nil, err
        }
    
        if n >= MaxNameLen || oobn != oobSpace {
            return nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn)
        }
    
        // Truncate.
        name = name[:n]
        oob = oob[:oobn]
    
        scms, err := unix.ParseSocketControlMessage(oob)
        if err != nil {
            return nil, err
        }
        if len(scms) != 1 {
            return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms))
        }
        scm := scms[0]
    
        fds, err := unix.ParseUnixRights(&scm)
        if err != nil {
            return nil, err
        }
        if len(fds) != 1 {
            return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds))
        }
        fd := uintptr(fds[0])
    
        return os.NewFile(fd, string(name)), nil
    }
    
    // SendFd sends a file descriptor over the given AF_UNIX socket. In
    // addition, the file.Name() of the given file will also be sent as
    // non-auxiliary data in the same payload (allowing to send contextual
    // information for a file descriptor).
    func SendFd(socket *os.File, name string, fd uintptr) error {
        if len(name) >= MaxNameLen {
            return fmt.Errorf("sendfd: filename too long: %s", name)
        }
        oob := unix.UnixRights(int(fd))
        return unix.Sendmsg(int(socket.Fd()), []byte(name), oob, nil, 0)
    }
    // NewAgentClient creates a new agent gRPC client and handles both unix and vsock addresses.
    //
    // Supported sock address formats are:
    //   - unix://<unix socket path>
    //   - vsock://<cid>:<port>
    //   - <unix socket path>
    //   - hvsock://<path>:<port>. Firecracker implements the virtio-vsock device
    //     model, and mediates communication between AF_UNIX sockets (on the host end)
    //     and AF_VSOCK sockets (on the guest end).
    func NewAgentClient(ctx context.Context, sock string, enableYamux bool) (*AgentClient, error) {
            grpcAddr, parsedAddr, err := parse(sock)
            if err != nil {
                    return nil, err
            }
            dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithBlock()}
            dialOpts = append(dialOpts, grpc.WithDialer(agentDialer(parsedAddr, enableYamux)))
    
            var tracer opentracing.Tracer
    
            span := opentracing.SpanFromContext(ctx)
    
            // If the context contains a trace span, trace all client comms
            if span != nil {
                    tracer = span.Tracer()
    
                    dialOpts = append(dialOpts,
                            grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(tracer)))
                    dialOpts = append(dialOpts,
                            grpc.WithStreamInterceptor(otgrpc.OpenTracingStreamClientInterceptor(tracer)))
            }
    
            ctx, cancel := context.WithTimeout(ctx, defaultDialTimeout)
            defer cancel()
            conn, err := grpc.DialContext(ctx, grpcAddr, dialOpts...)
            if err != nil {
                    return nil, err
            }
    
            return &AgentClient{
                    AgentServiceClient: agentgrpc.NewAgentServiceClient(conn),
                    HealthClient:       agentgrpc.NewHealthClient(conn),
                    conn:               conn,
            }, nil
    }

    fd 可以通过 Unix domain socket 传出去,可以参考 runc 的: https://github.com/opencontainers/runc/blob/master/libcontainer/utils/cmsg.go

     https://blog.cloudflare.com/know-your-scm_rights/

    // UnixRights encodes a set of open file descriptors into a socket
    // control message for sending to another process.
    func UnixRights(fds ...int) []byte {
        datalen := len(fds) * 4
        b := make([]byte, CmsgSpace(datalen))
        h := (*Cmsghdr)(unsafe.Pointer(&b[0]))
        h.Level = SOL_SOCKET
        h.Type = SCM_RIGHTS
        h.SetLen(CmsgLen(datalen))
        for i, fd := range fds {
            *(*int32)(h.data(4 * uintptr(i))) = int32(fd)
        }
        return b
    }
    
    // ParseUnixRights decodes a socket control message that contains an
    // integer array of open file descriptors from another process.
    func ParseUnixRights(m *SocketControlMessage) ([]int, error) {
        if m.Header.Level != SOL_SOCKET {
            return nil, EINVAL
        }
        if m.Header.Type != SCM_RIGHTS {
            return nil, EINVAL
        }
        fds := make([]int, len(m.Data)>>2)
        for i, j := 0, 0; i < len(m.Data); i += 4 {
            fds[j] = int(*(*int32)(unsafe.Pointer(&m.Data[i])))
            j++
        }
        return fds, nil
    }

    遇到这样一个需求:一个进程将自己的标准输入、标准输出和标准错误输出映射到另外一个进程相应的位置。带着对 Unix Domain Socket 的朦胧认识,写了一个简单的实现原型:

    其中,outlet 是“贡献”标准输入输出的进程,inner 是“抢占” outlet 标准输入输出的进程。整体上代码还比较好理解,只是如果需要发文文件描述符,需要注意 send_fds函数中 cmsg->cmsg_type = SCM_RIGHTS; 一行,必须制定 cmsg_type为 SCM_RIGHTS。

    分别编译两个程序文件:

    执行 outlet,先输出 “write from outlet” 后阻塞,等待 inner 进程的连接:

    然后再启动 inner 进程,inner 进程输出“4 5 6”,并在 outlet 的标准输出输出 “write from inner“:

    也有 “write from inner“:

    这样就大功告成了。

    inner 之所以输出 ”4 5 6“ 是比较好奇传递过来的文件描述符到底是长啥样的,这行打印看代码可以知道是打印了接收到的文件描述符的值,也就是创建了几个新的文件描述符么——就像侦探推理一样,没有揭开谜底前各种好奇,一旦揭开谜底了反倒趣味全无了。

    P.S. unix(7)中其实交代了传递文件描述符的效果跟 dup2 差不多的,还怪自己平常没有好好 RTFM 啊:

  • 相关阅读:
    Git批量删除的方法
    第五课 森さんは 七時に 起きます
    第一课 李さんは  中国人です
    如何设置上传文件控件 input type="file" 的 默认值
    如何用程序提交一个上传文件的请求
    MVC Beta 做的网站实践总结(上)
    泛型单一模式
    提高web站点性能的最佳实践
    扩展GridView实现的一个自定义无刷新分页,排序,支持多种数据源的控件TwfGridView
    [导入]将字符串中连续的空格转换为一个空格
  • 原文地址:https://www.cnblogs.com/dream397/p/14011196.html
Copyright © 2020-2023  润新知