Comunication between Host and Docker Container using FIFO pipes by bind mount (Linux)

huangapple go评论59阅读模式
英文:

Comunication between Host and Docker Container using FIFO pipes by bind mount (Linux)

问题

我的容器在绑定挂载的目录中创建了FIFO管道,但当容器读取或写入该管道时,主机无法接收到数据,反之亦然。具有类似权限的管道的创建或打开可以用于主机内部和容器内部的通信,但在主机和容器之间无法正常工作。

该管道是在容器内的cpp程序中创建和读写的。管道使用了666和777权限进行创建,并使用O_RDWR打开。类似的cpp程序在主机上进行读写。在容器内部和主机内部,这些程序都可以正常工作,问题只发生在主机与容器之间的通信中。

docker run -it --name broken-container -v /tmp:/app2 cppdocker

英文:

My container creates fifo pipe in the bind mounted dir but container when reads or writes to this pipe, the host cant receive it or vice versa. with similar permissions to create or open pipe works for communication within host and within container. But it does not work in between host and container.

The pipe is created and reads/writes in cpp program within container. Pipe created with both 666 and 777 permissions and opened with O_RDWR. Similar cpp programs reads/writes in the host. Again these program works fine within container and within host the problem happens only in host communication.

docker run -it --name broken-container -v /tmp:/app2 cppdocker

答案1

得分: 0

在阅读了你对其他用户提出的问题的回复后,我怀疑你遇到的问题是你在调用 mknod()(或在创建常规文件/目录时调用 creat()/mkdir())时提供的文件权限仍然受到了你进程的 umask 掩码的影响。再加上 Docker 在不同用户下运行容器,与你从外部使用的用户不同,很可能会导致 "权限被拒绝" 的情况。

除了 "不起作用" 之外,你没有发布任何错误消息,而你正在使用的示例代码也没有包含任何对你使用的系统调用进行错误检查的部分。在调用操作系统函数时一定要检查错误! 即使这样,如果你通过 strace 运行程序,就会注意到失败情况,它会为你提供一些关于哪些操作失败的反馈。

假设我的猜测是正确的,你真的因为 umask 导致了实际的权限问题,我对你发布的示例代码进行了三处更改:

  • 我通过 perror() 添加了非常简单的错误检查,这样当出现问题时,至少会得到一些反馈。
  • 我修改了 read()write() 系统调用,以正确处理 EINTR 的特殊情况,以及 write() 系统调用,以确保循环直到写入所有字节为止(在使用阻塞读写时,这被认为是最佳实践)。
  • 在接收方打开 FIFO 后(也是创建时),我添加了 fchmod(),以确保 FIFO 的文件模式实际上已更改为所需的值 0777。(我建议在调用 mknod 之前执行此操作,而不是更改进程的 umask。)

你的接收方代码已调整如下:

#include <iostream>
#include <cstring>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

int main()
{
    static char const* pipeName = "/inside/container/my_pipe";

    int rc = mkfifo(pipeName, 0777);
    // 忽略 EEXIST(文件已存在),因为这并不是真正的错误
    if (rc < 0 && errno != EEXIST) {
        perror("mkfifo");
        return 1;
    }

    int fd = open(pipeName, O_RDWR);
    if (fd < 0) {
        perror("open");
        return 2;
    }

    // 强制设备的权限为 0777(因为进程的 umask 可能会导致实际权限较低)
    rc = fchmod(fd, 0777);
    if (rc < 0) {
        perror("fchmod");
        close(fd);
        return 2;
    }

    char buffer[80];

    // 在阻塞模式下适当处理 "中断系统调用"
    // 使用循环
    ssize_t bytesRead = -1;
    errno = EINTR;
    while (bytesRead < 0 && errno == EINTR)
        bytesRead = read(fd, buffer, sizeof(buffer) - 1);

    if (bytesRead < 0) {
        perror("read");
        close(fd);
        return 3;
    }

    // 对于足够长的消息,内核可以自由地拆分由另一侧发送的任何数据,因此这个
    // 初始读取可能仅包含部分数据。在设计应用程序时要记住这一点。

    buffer[bytesRead] = '
#include <iostream>
#include <cstring>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

int main()
{
    static char const* pipeName = "/inside/container/my_pipe";

    int rc = mkfifo(pipeName, 0777);
    // 忽略 EEXIST(文件已存在),因为这并不是真正的错误
    if (rc < 0 && errno != EEXIST) {
        perror("mkfifo");
        return 1;
    }

    int fd = open(pipeName, O_RDWR);
    if (fd < 0) {
        perror("open");
        return 2;
    }

    // 强制设备的权限为 0777(因为进程的 umask 可能会导致实际权限较低)
    rc = fchmod(fd, 0777);
    if (rc < 0) {
        perror("fchmod");
        close(fd);
        return 2;
    }

    char buffer[80];

    // 在阻塞模式下适当处理 "中断系统调用"
    // 使用循环
    ssize_t bytesRead = -1;
    errno = EINTR;
    while (bytesRead < 0 && errno == EINTR)
        bytesRead = read(fd, buffer, sizeof(buffer) - 1);

    if (bytesRead < 0) {
        perror("read");
        close(fd);
        return 3;
    }

    // 对于足够长的消息,内核可以自由地拆分由另一侧发送的任何数据,因此这个
    // 初始读取可能仅包含部分数据。在设计应用程序时要记住这一点。

    buffer[bytesRead] = '\0';
    close(fd);

    std::cout << "Received message: " << buffer << std::endl;

    return 0;
}
'
;
close(fd); std::cout << "Received message: " << buffer << std::endl; return 0; }

你的发送方代码已调整如下:

#include <iostream>
#include <cstring>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

int main()
{
    static char const* pipeName = "/outside/container/my_pipe"; // 管道文件路径
    int fd = open(pipeName, O_RDWR);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    static char const* message = "hello";
    char const* ptr = message;
    size_t remaining = strlen(message) + 1;

    // 循环确保我们写入所有数据
    // 由于 write() 本身是阻塞的,所以这似乎是期望的行为?
    // 对于像这个示例这样的短消息,这通常只会执行一次循环。
    while (remaining > 0) {
        ssize_t bytesWritten = write(fd, ptr, remaining);
        if (bytesWritten < 0) {
            // 正确处理 "中断系统调用" 条件
            if (errno == EINTR)
                continue;
            perror("write");
            close(fd);
            return 2;
        }
        remaining -= static_cast<size_t>(bytesWritten);
        ptr += static_cast<size_t>(bytesWritten);
    }

    close(fd);
    return 0;
}

在我的系统上运行这些代码与标准的 Docker 容器(没有定制)一起运行。

如果我的猜测是错误的,不是因为 umask,你至少会得到一个详细的错误消息,说明为什么操作失败。

英文:

After reading your comments in response to questions from other users, I suspect you're running into the issue that the file permissions you supply to mknod() (or creat()/mkdir() when creating regular files / directories) are still masked by your process's umask. Combine that with the fact docker runs the containers under a different user than the user you're using from the outside, you're likely running into a "permission denied" situation.

Other than "does not work" you didn't post any error message, and the example code you are using doesn't contain any error checking of the system calls you are using. Always check for errors when calling operating system functions! Even so, you'd have noticed the failures had you run your programs through strace, which would give you some feedback as to which operations fail.

Assuming that my guess here is correct and you are really running into actual permission problems due to the umask, I've taken the example code you've posted and changed three things:

  • I've added very trivial error checking to it via perror() so that you get at least some feedback when something fails.
  • I've changed the read() and write() system calls to properly handle the EINTR corner case, as well as the write() system call to loop until all bytes have been written. (This is considered best practice when using blocking reads/writes.)
  • I've added fchmod() after opening the FIFO on the receiver side (where it's also created) to ensure that the file mode of the FIFO is actually changed to the desired value 0777. (I would recommend to do this over changing the umask of the process, though you could also do that before calling mknod.)

Your receiver code adjusted:

#include &lt;iostream&gt;
#include &lt;cstring&gt;
#include &lt;fcntl.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
#include &lt;errno.h&gt;

int main()
{
    static char const* pipeName = &quot;/inside/container/my_pipe&quot;;

    int rc = mkfifo(pipeName, 0777);
    // ignore EEXIST (file already exists)
    // since that isn&#39;t really an error
    if (rc &lt; 0 &amp;&amp; errno != EEXIST) {
        perror(&quot;mkfifo&quot;);
        return 1;
    }

    int fd = open(pipeName, O_RDWR);
    if (fd &lt; 0) {
        perror(&quot;open&quot;);
        return 2;
    }

    // force 0777 permissions on the device (since the
    // process&#39;s umask likely caused the actual permissions
    // to be lesser)
    rc = fchmod(fd, 0777);
    if (rc &lt; 0) {
        perror(&quot;fchmod&quot;);
        close(fd);
        return 2;
    }

    char buffer[80];

    // Properly handle &quot;interrupted system call&quot; in blocking mode
    // with a loop
    ssize_t bytesRead = -1;
    errno = EINTR;
    while (bytesRead &lt; 0 &amp;&amp; errno == EINTR)
        bytesRead = read(fd, buffer, sizeof(buffer) - 1);

    if (bytesRead &lt; 0) {
        perror(&quot;read&quot;);
        close(fd);
        return 3;
    }

    // For long enough messages the kernel is free to split
    // up any data that was sent by the other side, so this
    // initial read may only contain partial data. Keep that
    // in mind when designing your application.

    buffer[bytesRead] = &#39;\0&#39;;
    close(fd);

    std::cout &lt;&lt; &quot;Received message: &quot; &lt;&lt; buffer &lt;&lt; std::endl;

    return 0;
}

Your sender code adjusted:

#include &lt;iostream&gt;
#include &lt;cstring&gt;
#include &lt;fcntl.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
#include &lt;errno.h&gt;
int main()
{
static char const* pipeName = &quot;/outside/container/my_pipe&quot;; // Pipe file path
int fd = open(pipeName, O_RDWR);
if (fd &lt; 0) {
perror(&quot;open&quot;);
return 1;
}
static char const* message = &quot;hello&quot;;
char const* ptr = message;
size_t remaining = strlen(message) + 1;
// Loop to ensure that we write all of the data
// Since write() is blocking anyway this appears
// to be the desired behavior?.
// For short messages like this example this will
// typically execute the loop only once.
while (remaining &gt; 0) {
ssize_t bytesWritten = write(fd, ptr, remaining);
if (bytesWritten &lt; 0) {
// Properly handle the &quot;interrupted system call&quot;
// condition
if (errno == EINTR)
continue;
perror(&quot;write&quot;);
close(fd);
return 2;
}
remaining -= static_cast&lt;size_t&gt;(bytesWritten);
ptr += static_cast&lt;size_t&gt;(bytesWritten);
}
close(fd);
return 0;
}

Running this on my system with a standard Docker container (no customization) works.

And if my guess is wrong and it's not the umask you'll at least get a proper error message why the operation failed.

huangapple
  • 本文由 发表于 2023年6月15日 23:47:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76483408.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定