Feat: display pid and support customize unit in the table widget
This commit is contained in:
parent
a2c0365b36
commit
9c30fc0819
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
> *A modern alternative network traffic sniffer inspired by [bandwhich](https://github.com/imsnif/bandwhich)(Rust) and [nethogs](https://github.com/raboof/nethogs)(C++).*
|
> *A modern alternative network traffic sniffer inspired by [bandwhich](https://github.com/imsnif/bandwhich)(Rust) and [nethogs](https://github.com/raboof/nethogs)(C++).*
|
||||||
|
|
||||||
https://user-images.githubusercontent.com/19553554/142255776-4f01f06f-af79-4fb1-820f-7a00c71416d3.mov
|
https://user-images.githubusercontent.com/19553554/142890205-62980e37-5861-4161-9669-737317573aa1.mov
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
|
|
|
||||||
4
cli.go
4
cli.go
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "v0.2.0"
|
const version = "v0.3.0"
|
||||||
|
|
||||||
func NewApp() *cobra.Command {
|
func NewApp() *cobra.Command {
|
||||||
defaultOpts := DefaultOptions()
|
defaultOpts := DefaultOptions()
|
||||||
|
|
@ -58,7 +58,7 @@ func NewApp() *cobra.Command {
|
||||||
app.Flags().BoolVarP(&opt.DisableDNSResolve, "no-dns-resolve", "n", defaultOpts.DisableDNSResolve, "disable the DNS resolution")
|
app.Flags().BoolVarP(&opt.DisableDNSResolve, "no-dns-resolve", "n", defaultOpts.DisableDNSResolve, "disable the DNS resolution")
|
||||||
app.Flags().Int32SliceVarP(&opt.Pids, "pids", "p", defaultOpts.Pids, "pids to watch, empty stands for all pids")
|
app.Flags().Int32SliceVarP(&opt.Pids, "pids", "p", defaultOpts.Pids, "pids to watch, empty stands for all pids")
|
||||||
app.Flags().IntVarP(&mode, "mode", "m", int(defaultOpts.ViewMode), "view mode of sniffer (0: bytes 1: packets 2: processes)")
|
app.Flags().IntVarP(&mode, "mode", "m", int(defaultOpts.ViewMode), "view mode of sniffer (0: bytes 1: packets 2: processes)")
|
||||||
app.Flags().StringVarP(&unit, "unit", "u", defaultOpts.Unit.String(), "unit of traffic stats in processes mode, optional: B, KB, MB, GB")
|
app.Flags().StringVarP(&unit, "unit", "u", defaultOpts.Unit.String(), "unit of traffic stats, optional: B, Kb, KB, Mb, MB, Gb, GB")
|
||||||
|
|
||||||
app.Flags().PrintDefaults()
|
app.Flags().PrintDefaults()
|
||||||
return app
|
return app
|
||||||
|
|
|
||||||
|
|
@ -58,18 +58,17 @@ func (lc *lsofConn) GetOpenSockets(pids ...int32) (OpenSockets, error) {
|
||||||
if len(fields) < 10 {
|
if len(fields) < 10 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
procName := strings.ReplaceAll(fields[0], "\\x20", " ")
|
|
||||||
|
|
||||||
|
procName := strings.ReplaceAll(fields[0], "\\x20", " ")
|
||||||
|
pid, _ := strconv.Atoi(fields[1])
|
||||||
if len(pids) > 0 {
|
if len(pids) > 0 {
|
||||||
pid, err := strconv.Atoi(fields[1])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !set[int32(pid)] {
|
if !set[int32(pid)] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
procInfo := ProcessInfo{Pid: pid, Name: procName}
|
||||||
|
|
||||||
switch fields[8] {
|
switch fields[8] {
|
||||||
case "TCP":
|
case "TCP":
|
||||||
addr := strings.Split(fields[9], "->")
|
addr := strings.Split(fields[9], "->")
|
||||||
|
|
@ -84,7 +83,7 @@ func (lc *lsofConn) GetOpenSockets(pids ...int32) (OpenSockets, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sockets[LocalSocket{IP: ipport[0], Port: uint16(port), Protocol: ProtoTCP}] = procName
|
sockets[LocalSocket{IP: ipport[0], Port: uint16(port), Protocol: ProtoTCP}] = procInfo
|
||||||
|
|
||||||
case "UDP":
|
case "UDP":
|
||||||
ipport := strings.Split(fields[9], ":")
|
ipport := strings.Split(fields[9], ":")
|
||||||
|
|
@ -96,7 +95,7 @@ func (lc *lsofConn) GetOpenSockets(pids ...int32) (OpenSockets, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sockets[LocalSocket{IP: ipport[0], Port: uint16(port), Protocol: ProtoUDP}] = procName
|
sockets[LocalSocket{IP: ipport[0], Port: uint16(port), Protocol: ProtoUDP}] = procInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -215,8 +215,8 @@ func (nl *netlinkConn) sockdiagSend(proto, family uint8, states uint32) (skfd in
|
||||||
return skfd, nil
|
return skfd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nl *netlinkConn) sockdiagRecv(skfd, proto int, inodeMap map[uint32]string) (map[LocalSocket]string, error) {
|
func (nl *netlinkConn) sockdiagRecv(skfd, proto int, inodeMap map[uint32]string) (OpenSockets, error) {
|
||||||
sockets := make(map[LocalSocket]string)
|
sockets := make(OpenSockets)
|
||||||
buffer := make([]byte, os.Getpagesize())
|
buffer := make([]byte, os.Getpagesize())
|
||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
|
|
@ -242,6 +242,11 @@ loop:
|
||||||
m := (*inetDiagMsg)(unsafe.Pointer(&msg.Data[0]))
|
m := (*inetDiagMsg)(unsafe.Pointer(&msg.Data[0]))
|
||||||
srcIP, _ := nl.ipHex2String(m.IDiagFamily, m.ID.IdiagSrc)
|
srcIP, _ := nl.ipHex2String(m.IDiagFamily, m.ID.IdiagSrc)
|
||||||
|
|
||||||
|
procInfo := ProcessInfo{
|
||||||
|
Pid: int(msg.Header.Pid),
|
||||||
|
Name: inodeMap[m.IDiagInode],
|
||||||
|
}
|
||||||
|
|
||||||
var p Protocol
|
var p Protocol
|
||||||
switch proto {
|
switch proto {
|
||||||
case syscall.IPPROTO_TCP:
|
case syscall.IPPROTO_TCP:
|
||||||
|
|
@ -249,15 +254,15 @@ loop:
|
||||||
case syscall.IPPROTO_UDP:
|
case syscall.IPPROTO_UDP:
|
||||||
p = ProtoUDP
|
p = ProtoUDP
|
||||||
}
|
}
|
||||||
sockets[LocalSocket{IP: srcIP, Port: uint16(m.ID.IdiagSport.Int()), Protocol: p}] = inodeMap[m.IDiagInode]
|
sockets[LocalSocket{IP: srcIP, Port: uint16(m.ID.IdiagSport.Int()), Protocol: p}] = procInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sockets, nil
|
return sockets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nl *netlinkConn) getOpenSockets(inodeMap map[uint32]string) (map[LocalSocket]string, error) {
|
func (nl *netlinkConn) getOpenSockets(inodeMap map[uint32]string) (OpenSockets, error) {
|
||||||
sockets := make(map[LocalSocket]string)
|
sockets := make(OpenSockets)
|
||||||
|
|
||||||
type Req struct {
|
type Req struct {
|
||||||
Protocol int
|
Protocol int
|
||||||
|
|
|
||||||
|
|
@ -28,16 +28,21 @@ func (ps *psutilConn) getOpenSockets(pids ...int32) (OpenSockets, error) {
|
||||||
return openSockets, nil
|
return openSockets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *psutilConn) getProcName(pid int32) string {
|
func (ps *psutilConn) getProcName(pid int32) ProcessInfo {
|
||||||
|
procInfo := ProcessInfo{Name: unknownProcessName}
|
||||||
|
|
||||||
proc, err := process.NewProcess(pid)
|
proc, err := process.NewProcess(pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return unknownProcessName
|
return procInfo
|
||||||
}
|
}
|
||||||
exe, err := proc.Exe()
|
exe, err := proc.Exe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return unknownProcessName
|
return procInfo
|
||||||
}
|
}
|
||||||
return filepath.Base(exe)
|
|
||||||
|
procInfo.Pid = int(pid)
|
||||||
|
procInfo.Name = filepath.Base(exe)
|
||||||
|
return procInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *psutilConn) getConnections(proto Protocol, openSockets OpenSockets, pids ...int32) error {
|
func (ps *psutilConn) getConnections(proto Protocol, openSockets OpenSockets, pids ...int32) error {
|
||||||
|
|
|
||||||
12
pcap.go
12
pcap.go
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -28,7 +29,16 @@ type Connection struct {
|
||||||
Remote RemoteSocket
|
Remote RemoteSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenSockets map[LocalSocket]string
|
type ProcessInfo struct {
|
||||||
|
Pid int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProcessInfo) String() string {
|
||||||
|
return fmt.Sprintf("<%d>:%s", p.Pid, p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenSockets map[LocalSocket]ProcessInfo
|
||||||
type Utilization map[Connection]*ConnectionInfo
|
type Utilization map[Connection]*ConnectionInfo
|
||||||
|
|
||||||
type SocketFetcher interface {
|
type SocketFetcher interface {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ type Options struct {
|
||||||
// Pids to watch in processes mode
|
// Pids to watch in processes mode
|
||||||
Pids []int32
|
Pids []int32
|
||||||
|
|
||||||
// Unit of stats in processes mode, optional: B, KB, MB, GB
|
// Unit of stats in processes mode, optional: B, Kb, KB, Mb, MB, Gb, GB
|
||||||
Unit Unit
|
Unit Unit
|
||||||
|
|
||||||
// DisableDNSResolve decides whether if disable the DNS resolution
|
// DisableDNSResolve decides whether if disable the DNS resolution
|
||||||
|
|
|
||||||
2
stat.go
2
stat.go
|
|
@ -176,7 +176,7 @@ func (s *StatsManager) getProcName(openSockets OpenSockets, localSocket LocalSoc
|
||||||
|
|
||||||
v, ok := openSockets[cloned]
|
v, ok := openSockets[cloned]
|
||||||
if ok {
|
if ok {
|
||||||
return v
|
return v.String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return unknownProcessName
|
return unknownProcessName
|
||||||
|
|
|
||||||
20
ui.go
20
ui.go
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/chenjiandongx/termui/v3"
|
"github.com/chenjiandongx/termui/v3"
|
||||||
|
|
@ -44,13 +43,16 @@ type Unit string
|
||||||
const (
|
const (
|
||||||
UnitB Unit = "B"
|
UnitB Unit = "B"
|
||||||
UnitKB Unit = "KB"
|
UnitKB Unit = "KB"
|
||||||
|
UnitKb Unit = "Kb"
|
||||||
UnitMB Unit = "MB"
|
UnitMB Unit = "MB"
|
||||||
|
UnitMb Unit = "Mb"
|
||||||
UnitGB Unit = "GB"
|
UnitGB Unit = "GB"
|
||||||
|
UnitGb Unit = "Gb"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (u Unit) Validate() error {
|
func (u Unit) Validate() error {
|
||||||
switch u {
|
switch u {
|
||||||
case UnitB, UnitKB, UnitMB, UnitGB:
|
case UnitB, UnitKB, UnitKb, UnitMB, UnitMb, UnitGB, UnitGb:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("invalid unit %s", u)
|
return fmt.Errorf("invalid unit %s", u)
|
||||||
|
|
@ -67,10 +69,16 @@ func (u Unit) Ratio() float64 {
|
||||||
ratio = 1
|
ratio = 1
|
||||||
case UnitKB:
|
case UnitKB:
|
||||||
ratio = 1024
|
ratio = 1024
|
||||||
|
case UnitKb:
|
||||||
|
ratio = 1024 / 8
|
||||||
case UnitMB:
|
case UnitMB:
|
||||||
ratio = 1024 * 1024
|
ratio = 1024 * 1024
|
||||||
|
case UnitMb:
|
||||||
|
ratio = 1024 * 1024 / 8
|
||||||
case UnitGB:
|
case UnitGB:
|
||||||
ratio = 1024 * 1024 * 1024
|
ratio = 1024 * 1024 * 1024
|
||||||
|
case UnitGb:
|
||||||
|
ratio = 1024 * 1024 * 1024 / 8
|
||||||
}
|
}
|
||||||
return ratio
|
return ratio
|
||||||
}
|
}
|
||||||
|
|
@ -122,6 +130,7 @@ func NewUIComponent(opt Options) *UIComponent {
|
||||||
remoteAddrs: newTable("Remote Address"),
|
remoteAddrs: newTable("Remote Address"),
|
||||||
connections: newTable("Connections"),
|
connections: newTable("Connections"),
|
||||||
mode: opt.ViewMode,
|
mode: opt.ViewMode,
|
||||||
|
unit: opt.Unit,
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
ui.viewer = &PlotViewer{
|
ui.viewer = &PlotViewer{
|
||||||
|
|
@ -327,6 +336,7 @@ type TableViewer struct {
|
||||||
grid *termui.Grid
|
grid *termui.Grid
|
||||||
shiftIdx int
|
shiftIdx int
|
||||||
mode ViewMode
|
mode ViewMode
|
||||||
|
unit Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tv *TableViewer) Setup() {
|
func (tv *TableViewer) Setup() {
|
||||||
|
|
@ -352,7 +362,7 @@ func (tv *TableViewer) humanizeNum(n int) string {
|
||||||
var s string
|
var s string
|
||||||
switch tv.mode {
|
switch tv.mode {
|
||||||
case ModeTableBytes:
|
case ModeTableBytes:
|
||||||
s = strings.ReplaceAll(humanize.IBytes(uint64(n)), " ", "")
|
s = fmt.Sprintf("%.1f%s", float64(n)/tv.unit.Ratio(), tv.unit.String())
|
||||||
case ModeTablePackets:
|
case ModeTablePackets:
|
||||||
s = humanize.Comma(int64(n))
|
s = humanize.Comma(int64(n))
|
||||||
}
|
}
|
||||||
|
|
@ -387,7 +397,7 @@ func (tv *TableViewer) updateProcesses(snapshot *Snapshot) {
|
||||||
rows = append(rows, []string{r.ProcessName, strconv.Itoa(r.Data.ConnCount), up + " / " + down})
|
rows = append(rows, []string{r.ProcessName, strconv.Itoa(r.Data.ConnCount), up + " / " + down})
|
||||||
}
|
}
|
||||||
|
|
||||||
header := []string{"Process", "Connections", "Up / Down"}
|
header := []string{"<Pid>:Process", "Connections", "Up / Down"}
|
||||||
tv.processes.Rows = [][]string{header, make([]string, 3)}
|
tv.processes.Rows = [][]string{header, make([]string, 3)}
|
||||||
tv.processes.Rows = append(tv.processes.Rows, rows...)
|
tv.processes.Rows = append(tv.processes.Rows, rows...)
|
||||||
}
|
}
|
||||||
|
|
@ -435,7 +445,7 @@ func (tv *TableViewer) updateConnections(snapshot *Snapshot) {
|
||||||
rows = append(rows, []string{conn, r.Data.ProcessName, up + " / " + down})
|
rows = append(rows, []string{conn, r.Data.ProcessName, up + " / " + down})
|
||||||
}
|
}
|
||||||
|
|
||||||
header := []string{"Connections", "Process", "Up / Down"}
|
header := []string{"Connections", "<Pid>:Process", "Up / Down"}
|
||||||
tv.connections.Rows = [][]string{header, make([]string, 3)}
|
tv.connections.Rows = [][]string{header, make([]string, 3)}
|
||||||
tv.connections.Rows = append(tv.connections.Rows, rows...)
|
tv.connections.Rows = append(tv.connections.Rows, rows...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue