Skip to content

Commit

Permalink
A simpler way to detect when a live packet source is ready
Browse files Browse the repository at this point in the history
If you start termshark like this:

$ termshark -i eth0

then the UI will not start until packets are seen. I thought this
provided a better experience than launching the UI immediately and
having it remain empty - there might be information on the terminal, so
best not clear that away immediately to show no information and a moving
spinner. The user will see this in the terminal:

(The termshark UI will start when packets are detected...)

When termshark starts in this mode, it runs a few processes; something
like:

(1) dumpcap -i eth0 -w <tmpfile>

(2) tail -f tmpfile | ...

(3) ... tshark -i - -T psml

Prior to this change, I detected the presence of packets by watching for
the creation of the file <tmpfile>. But this doesn't work very well
because dumpcap often (not always??) creates an empty file with pcap
headers very quickly, even though there are no packets in the file
yet. And it's no ideal relying on this behavior.

This change uses a different approach. Instead I have a callback from
the PSML loader when PSML headers are received. I set that callback up
to a close a channel used by the main loop in termshark.go. When the
channel is closed, I start the UI.

This also has the nice effect of further simplifying and reducing the
size of termshark.go.
  • Loading branch information
gcla committed Dec 30, 2020
1 parent ab0e8d6 commit 410a81a
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 56 deletions.
57 changes: 1 addition & 56 deletions cmd/termshark/termshark.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"time"

"github.com/blang/semver"
"github.com/fsnotify/fsnotify"
"github.com/gcla/gowid"
"github.com/gcla/termshark/v2"
"github.com/gcla/termshark/v2/capinfo"
Expand Down Expand Up @@ -703,10 +702,7 @@ func cmain() int {

// Buffered because I might send something in this goroutine
startUIChan := make(chan struct{}, 1)
// Used to cancel the display of a message telling the user why there is no UI yet.
detectMsgChan := make(chan struct{}, 1)

var iwatcher *fsnotify.Watcher
var ifaceTmpFile string

if len(pcap.FileSystemSources(psrcs)) == 0 {
Expand All @@ -716,34 +712,7 @@ func cmain() int {
}
ifaceTmpFile = pcap.TempPcapFile(srcNames...)

iwatcher, err = fsnotify.NewWatcher()
if err != nil {
fmt.Fprintf(os.Stderr, "Could not start filesystem watcher: %v\n", err)
return 1
}
defer func() {
if iwatcher != nil {
iwatcher.Close()
}
}()

// Don't start the UI until this file is created. When listening on a pipe,
// termshark will start a process similar to:
//
// dumpcap -i /dev/fd/3 -w ~/.cache/pcaps/tmp123.pcap
//
// dumpcap will not actually create that file until it has data to write to it.
// So we watch for the creation of that file, and until then, don't launch the UI.
// Then if the feeding process needs input first e.g. sudo tcpdump needs password,
// there won't be a conflict for reading /dev/tty.
//
if err := iwatcher.Add(termshark.PcapDir()); err != nil { //&& !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Could not set up watcher for %s: %v\n", termshark.PcapDir(), err)
return 1
}

fmt.Printf("(The termshark UI will start when packets are detected...)\n")

} else {
// Start UI right away, reading from a file
startUIChan <- struct{}{}
Expand Down Expand Up @@ -839,6 +808,7 @@ func cmain() int {
ifacePcapFilename = ifaceTmpFile
ui.PcapScheduler.RequestLoadInterfaces(psrcs, captureFilter, displayFilter, ifaceTmpFile,
pcap.HandlerList{
&ui.SignalPackets{C: startUIChan},
ui.MakeSaveRecents("", displayFilter, app),
ui.MakePacketViewUpdater(app),
ui.MakeUpdateCurrentCaptureInTitle(app),
Expand Down Expand Up @@ -888,8 +858,6 @@ Loop:
var psmlFinChan <-chan struct{}
var ifaceFinChan <-chan struct{}
var pdmlFinChan <-chan struct{}
var tmpPcapWatcherChan <-chan fsnotify.Event
var tmpPcapWatcherErrorsChan <-chan error
var tcellEvents <-chan tcell.Event
var afterRenderEvents <-chan gowid.IAfterRenderEvent
// For setting struct views empty. This isn't done as soon as a load is initiated because
Expand Down Expand Up @@ -963,14 +931,6 @@ Loop:
opsChan = ui.PcapScheduler.OperationsChan
}

// This tracks a temporary pcap file which is populated by dumpcap when termshark is
// reading from a fifo. If iwatcher is nil, it means we've got data and don't need to
// monitor any more.
if iwatcher != nil {
tmpPcapWatcherChan = iwatcher.Events
tmpPcapWatcherErrorsChan = iwatcher.Errors
}

// Only process tcell and gowid events if the UI is running.
if ui.Running {
tcellEvents = app.TCellEvents
Expand All @@ -994,18 +954,6 @@ Loop:
ui.Fin.Advance()
app.Redraw()

case we := <-tmpPcapWatcherChan:
if strings.Contains(we.Name, ifaceTmpFile) {
log.Infof("Pcap file %v has appeared - launching UI", we.Name)
iwatcher.Close()
iwatcher = nil
startUIChan <- struct{}{}
}

case err := <-tmpPcapWatcherErrorsChan:
fmt.Fprintf(os.Stderr, "Unexpected watcher error for %s: %v", ifaceTmpFile, err)
return 1

case <-startUIChan:
log.Infof("Launching termshark UI")

Expand Down Expand Up @@ -1068,11 +1016,8 @@ Loop:
ui.Running = true
startedSuccessfully = true

close(startUIChan)
startUIChan = nil // make sure it's not triggered again

close(detectMsgChan) // don't display the message about waiting for the UI

defer func() {
// Do this to make sure the program quits quickly if quit is invoked
// mid-load. It's safe to call this if a pcap isn't being loaded.
Expand Down
15 changes: 15 additions & 0 deletions pcap/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ type IAfterEnd interface {
AfterEnd()
}

type IPsmlHeader interface {
OnPsmlHeader()
}

type IUnpack interface {
Unpack() []interface{}
}
Expand Down Expand Up @@ -84,6 +88,17 @@ func HandleError(err error, cb interface{}) bool {
return res
}

func handlePsmlHeader(cb interface{}) bool {
res := false
if !HandleUnpack(cb, handlePsmlHeader) {
if c, ok := cb.(IPsmlHeader); ok {
c.OnPsmlHeader()
res = true
}
}
return res
}

func handleClear(cb interface{}) bool {
res := false
if !HandleUnpack(cb, handleClear) {
Expand Down
1 change: 1 addition & 0 deletions pcap/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -1986,6 +1986,7 @@ func (c *Loader) loadPsmlAsync(cb interface{}) {
c.Lock()
c.PacketPsmlHeaders = append(c.PacketPsmlHeaders, string(tok))
c.Unlock()
handlePsmlHeader(cb)
} else {
curPsml = append(curPsml, string(format.TranslateHexCodes(tok)))
empty = false
Expand Down
17 changes: 17 additions & 0 deletions ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -2378,6 +2378,23 @@ func (t SaveRecents) BeforeBegin() {

//======================================================================


type SignalPackets struct {
done bool
C chan struct{}
}

var _ pcap.IPsmlHeader = (*SignalPackets)(nil)

func (t *SignalPackets) OnPsmlHeader() {
if !t.done {
close(t.C)
t.done = true
}
}

//======================================================================

type checkGlobalJumpAfterPsml struct {
App gowid.IApp
Jump termshark.GlobalJumpPos
Expand Down

0 comments on commit 410a81a

Please sign in to comment.