Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(*PacketSource).packetsToChannel() error handling is incomplete #1143

Open
gaukas opened this issue Nov 13, 2023 · 1 comment
Open

(*PacketSource).packetsToChannel() error handling is incomplete #1143

gaukas opened this issue Nov 13, 2023 · 1 comment

Comments

@gaukas
Copy link

gaukas commented Nov 13, 2023

TL;DR

The error handling in (*PacketSource).packetsToChannel() is incomplete and may lead to starvation. A catch-all rule is missing.

Minimal Example

Implementation

	f, _ := os.Open("../testdata/somepackets.pcap")
	defer f.Close()
	r, _ := pcapgo.NewReader(f)

	pktsrc := gopacket.NewPacketSource(r, r.LinkType())
	
	f.Close() // close the reader here

	for packet := range pktsrc.Packets() { // this will block after the last packets
		// do something
	}

Behavior

The program hangs instead of terminating.

Analysis

gopacket/packet.go

Lines 815 to 845 in 32ee382

func (p *PacketSource) packetsToChannel() {
defer close(p.c)
for {
packet, err := p.NextPacket()
if err == nil {
p.c <- packet
continue
}
// Immediately retry for temporary network errors
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
continue
}
// Immediately retry for EAGAIN
if err == syscall.EAGAIN {
continue
}
// Immediately break for known unrecoverable errors
if err == io.EOF || err == io.ErrUnexpectedEOF ||
err == io.ErrNoProgress || err == io.ErrClosedPipe || err == io.ErrShortBuffer ||
err == syscall.EBADF ||
strings.Contains(err.Error(), "use of closed file") {
break
}
// Sleep briefly and try again
time.Sleep(time.Millisecond * time.Duration(5))
}
}

The function implementation missed the case where the returned error is not matched to any known cases.

In the current latest version of Go (1.21.4), (*os.File).Read() returns read /path/to/the/file: file already closed, which wraps os.ErrClosed and could be tested with errors.Is(err, os.ErrClosed), but is not equal to any existing error. Therefore it will not break out of the for loop and the channel will never be closed.

Proposed fix

  • Testing against known errors should use errors.Is or its equivalent, not err == ErrToCompare.
  • Should also add a catch-all case for unknown errors, since it is possible for the user to use a custom io.Reader.

I'd be happy to open a PR if the proposed fix sounds reasonable.

@gaukas
Copy link
Author

gaukas commented Nov 13, 2023

Just realized errors.Is is added in Go 1.13. Since the go.mod of this package still specifies go 1.12, either we will create version-dependent packet.go file for Go 1.13 and above, or we shall bump up the minimum required version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant