名前付きパイプ(mkfifo):消費者が書き込み側に書き込まず、読み取り側(消費者)の接続が切断されたことをMacosがどのように検出しますか?

名前付きパイプ(mkfifo):消費者が書き込み側に書き込まず、読み取り側(消費者)の接続が切断されたことをMacosがどのように検出しますか?

メモ:これはおおよそ生産者代わりに、消費者の接続切断検出消費者生産者の接続が切断されたことが検出されました(EOFとも呼ばれます)。生産者は長い間データを書き込めないかもしれませんが、消費者の接続が失われたことをすぐに知りたいと思います。

一般的なイベントシーケンス:

  1. コンシューマ(Wireshark)は名前付きパイプを作成し(使用してmkfifo)、読み取りの終わりを開きます。
  2. 生産者(いわゆるWireshark外部キャプチャプログラム、extcapとも呼ばれます)が起動し、名前付きパイプの名前/パスを渡します。
  3. プロデューサは書き込み側をO_WRONLYとして開き、最初にいくつかのデータをパイプに書き込みます。
  4. クリケット
  5. ユーザーが Wireshark で停止ボタンを押すと、Wireshark はパイプの読み取り端を閉じます。
  6. ???生産者に送信するデータがないにもかかわらず、消費者が名前付きパイプで接続が切断されたかどうかを生産者が検出しようとしていますか?

Linux

Linux のプロデューサで fd をselect読み取る fd セット内の名前付きパイプの書き込みの終わりとして使用するシステムコールを使用すると、コンシューマの接続が切断され、読み取れるようになったときに fd の書き込みの終わりが返されます。

アップルシステム

しかし、macosでは、名前付きパイプの書き込み側fdプロデューサーがデータを書き込むたびに読み取れるようになります(sic!)。もちろんいいえ消費者の接続が切断された後に読むことができるようになります。

編集:fd setエラーを追加しても状況は変わりません。 fd set エラーは発生しません。 /編集する

長い間書き込みが行われていませんが、ユーザーがWiresharkのロギングを停止しているため、SIGPIPEなしでmacosの名前付きパイプでコンシューマの切断を検出する方法についてのアイデアはありますか?

プロデューサー 名前付きパイプから消費者の接続を切断する検出

https://github.com/siemens/cshargextcap/blob/macos/pipe/checker_notwin.go

package pipe

import (
    "os"

    "golang.org/x/sys/unix"

    log "github.com/sirupsen/logrus"
)

// WaitTillBreak continuously checks a fifo/pipe to see when it breaks. When
// called, WaitTillBreak blocks until the fifo/pipe finally has broken.
//
// This implementation leverages [syscall.Select].
func WaitTillBreak(fifo *os.File) {
    log.Debug("constantly monitoring packet capture fifo status...")
    fds := unix.FdSet{}
    for {
        // Check the fifo becomming readable, which signals that it has been
        // closed. In this case, ex-termi-nate ;) Oh, and remember to correctly
        // initialize the fdset each time before calling select() ... well, just
        // because that's a good idea to do. :(
        fds.Set(int(fifo.Fd()))
        n, err := unix.Select(
            int(fifo.Fd())+1, // highest fd is our file descriptor.
            &fds, nil, nil,   // only watch readable.
            nil, // no timeout, ever.
        )
        if n != 0 || err != nil {
            // Either the pipe was broken by Wireshark, or we did break it on
            // purpose in the piping process. Anyway, we're done.
            log.Debug("capture fifo broken, stopped monitoring.")
            return
        }
    }
}

MacOSで誤動作を生成する単体テスト

https://github.com/siemens/cshargextcap/blob/macos/pipe/checker_notwin_test.goWaitTillBreak--Named Pipe の消費者側を実際に閉じるまで返すべきではないアサーション失敗です。

package pipe

import (
    "io"
    "os"
    "time"

    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"
    . "github.com/thediveo/success"
    "golang.org/x/sys/unix"
)

var _ = Describe("pipes", func() {

    It("detects on the write end when a pipe breaks", func() {
        // As Wireshark uses a named pipe it passes an extcap its name (path)
        // and then expects the extcap to open this named pipe for writing
        // packet capture data into it. For this test we simulate Wireshark
        // closing its reading end and we must properly detect this situation on
        // our writing end of the pipe.
        By("creating a temporary named pipe/fifo and opening its ends")
        tmpfifodir := Successful(os.MkdirTemp("", "test-fifo-*"))
        defer os.RemoveAll(tmpfifodir)

        fifoname := tmpfifodir + "/fifo"
        unix.Mkfifo(fifoname, 0660)
        wch := make(chan *os.File)
        go func() {
            defer GinkgoRecover()
            wch <- Successful(os.OpenFile(fifoname, os.O_WRONLY, os.ModeNamedPipe))
        }()

        rch := make(chan *os.File)
        go func() {
            defer GinkgoRecover()
            rch <- Successful(os.OpenFile(fifoname, os.O_RDONLY, os.ModeNamedPipe))
        }()

        var r, w *os.File
        Eventually(rch).Should(Receive(&r))
        Eventually(wch).Should(Receive(&w))
        defer w.Close()

        go func() {
            defer GinkgoRecover()
            By("continously draining the read end of the pipe into /dev/null")
            null := Successful(os.OpenFile("/dev/null", os.O_WRONLY, 0))
            defer null.Close()
            io.Copy(null, r)
            By("pipe draining done")
        }()

        go func() {
            defer GinkgoRecover()
            time.Sleep(2 * time.Second)
            By("closing read end of pipe")
            Expect(r.Close()).To(Succeed())
        }()

        go func() {
            defer GinkgoRecover()
            time.Sleep(300 * time.Microsecond)
            By("writing some data into the pipe")
            w.WriteString("Wireshark rulez")
        }()

        By("waiting for pipe to break")
        start := time.Now()
        WaitTillBreak(w)
        Expect(time.Since(start).Milliseconds()).To(
            BeNumerically(">", 1900), "pipe wasn't broken yet")
    })

})

アンケートベースのバージョン

これはmacOSでも機能せず、何も返しませんPOLLERR。しかし、Linuxではうまくいきます。

package pipe

import (
    "os"

    "golang.org/x/sys/unix"

    log "github.com/sirupsen/logrus"
)

// WaitTillBreak continuously checks a fifo/pipe to see when it breaks. When
// called, WaitTillBreak blocks until the fifo/pipe finally has broken.
//
// This implementation leverages [unix.Poll].
func WaitTillBreak(fifo *os.File) {
    log.Debug("constantly monitoring packet capture fifo status...")
    fds := []unix.PollFd{
        {
            Fd:     int32(fifo.Fd()),
            Events: 0,
        },
    }
    for {
        // Check the fifo becomming readable, which signals that it has been
        // closed. In this case, ex-termi-nate ;) Oh, and remember to correctly
        // initialize the fdset each time before calling select() ... well, just
        // because that's a good idea to do. :(
        n, err := unix.Poll(fds, 1000 /*ms*/)
        if err != nil {
            if err == unix.EINTR {
                continue
            }
            log.Debugf("capture fifo broken, reason: %s", err.Error())
            return
        }
        if n <= 0 {
            continue
        }
        log.Debugf("poll: %+v", fds)
        if fds[0].Revents&unix.POLLERR != 0 {
            // Either the pipe was broken by Wireshark, or we did break it on
            // purpose in the piping process. Anyway, we're done.
            log.Debug("capture fifo broken, stopped monitoring.")
            return
        }
    }
}

関連情報