From 6e599a413f0a0a8c695c2dfb538c1310e95fdec7 Mon Sep 17 00:00:00 2001 From: Paolo Invernizzi Date: Tue, 9 Jul 2024 00:39:47 +0200 Subject: [PATCH] Add starting table component and refactor --- cmd/bisturi/main.go | 17 ----- protocols/eth.go | 2 +- tui/models/bisturi_model.go | 132 ++++++++++++++++++++++------------ tui/models/interfaces_list.go | 2 +- tui/models/packets_table.go | 73 +++++++++++++++++++ tui/models/start_menu.go | 12 ++-- 6 files changed, 169 insertions(+), 69 deletions(-) create mode 100644 tui/models/packets_table.go diff --git a/cmd/bisturi/main.go b/cmd/bisturi/main.go index d3cfa9c..9d1e067 100644 --- a/cmd/bisturi/main.go +++ b/cmd/bisturi/main.go @@ -12,21 +12,4 @@ func main() { if _, err := p.Run(); err != nil { log.Fatal("Error running program:", err) } - - // // SYS_SOCKET syscall - // rs, err := sockets.NewRawSocket(protocol) - // if err != nil { - // log.Fatalf("Failed to open raw socket: %v", err) - // } - // defer rs.Close() - - // // bind the socket to the network interface - // rs.Bind(*networkInterface) - // if err != nil { - // log.Fatalf("Failed to bind socket: %v", err) - // } - // log.Printf("listening for %s packets on interface: %s\n", protocol, networkInterface.Name) - - // // SYS_RECVFROM syscall - // rs.ReadPackets() } diff --git a/protocols/eth.go b/protocols/eth.go index f48766d..0e6fc05 100644 --- a/protocols/eth.go +++ b/protocols/eth.go @@ -43,5 +43,5 @@ func EthFrameFromBytes(raw []byte) (*EthernetFrame, error) { func (f *EthernetFrame) Info() string { etv := etherTypesValues[f.etherType] - return fmt.Sprintf("%s Ethernet Frame from MAC %s to MAC %s", f.sourceMAC, f.destinationMAC, etv) + return fmt.Sprintf("%s Ethernet Frame from MAC %s to MAC %s", etv, f.sourceMAC, f.destinationMAC) } diff --git a/tui/models/bisturi_model.go b/tui/models/bisturi_model.go index b59b6e8..85e9b70 100644 --- a/tui/models/bisturi_model.go +++ b/tui/models/bisturi_model.go @@ -3,7 +3,9 @@ package tui import ( "fmt" "net" + "strings" + "github.com/NamelessOne91/bisturi/sockets" "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -12,25 +14,23 @@ import ( type step uint8 const ( - setupStep step = iota - ifaceStep - protoStep - doneStep + retrieveIfaces step = iota + selectIface + selectProtocol + receivePackets ) -type errMsg struct { - err error -} - -func (e errMsg) Error() string { return e.err.Error() } +type errMsg error type bisturiModel struct { step step - startMenu startMenuModel spinner spinner.Model - selectedInterface *net.Interface + startMenu startMenuModel + packetsTable packetsTablemodel + selectedInterface net.Interface selectedProtocol string selectedEthType uint16 + rawSocket *sockets.RawSocket err error } @@ -39,74 +39,118 @@ func NewBisturiModel() *bisturiModel { s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("#00cc99")) return &bisturiModel{ - step: setupStep, - spinner: s, - startMenu: startMenuModel{}, + step: retrieveIfaces, + spinner: s, } } func (m bisturiModel) Init() tea.Cmd { - return tea.Sequence(m.spinner.Tick, fetchInterfaces()) + return tea.Batch(m.spinner.Tick, fetchInterfaces()) } -func (m *bisturiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd +func (m bisturiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch m.step { + case retrieveIfaces: + return m.updateLoading(msg) + case selectIface, selectProtocol: + return m.updateStartMenuSelection(msg) + case receivePackets: + return m.updateReceivingPacket(msg) + default: + return m, nil + } +} +func (m *bisturiModel) updateLoading(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case errMsg: - m.err = msg.err + m.err = msg return m, tea.Quit case networkInterfacesMsg: m.startMenu = newStartMenuModel(msg) - m.step = ifaceStep + m.step = selectIface + return m, nil - case selectedInterfaceMsg: + case tea.KeyMsg: + switch msg.String() { + case "q", "esc", "ctrl+c": + return m, tea.Quit + } + } + var cmd tea.Cmd + m.spinner, cmd = m.spinner.Update(msg) + return m, cmd +} + +func (m *bisturiModel) updateStartMenuSelection(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + m.startMenu, cmd = m.startMenu.Update(msg) + + switch msg := msg.(type) { + case selectedIfaceItemMsg: iface, err := net.InterfaceByName(msg.name) if err != nil { - return m, func() tea.Msg { - return errMsg{err: err} - } + m.err = err + return m, tea.Quit } - m.selectedInterface = iface - m.step = protoStep - m.startMenu.step = protoStep - return m, nil + m.selectedInterface = *iface + m.step = selectProtocol - case selectedProtocolMsg: - m.selectedProtocol = msg.protocol - m.selectedEthType = msg.ethTytpe - m.step = doneStep return m, nil - case tea.KeyMsg: - switch msg.String() { - case "q", "esc", "ctrl+c": + case selectedProtocolItemMsg: + // SYS_SOCKET syscall + rs, err := sockets.NewRawSocket(msg.name, msg.ethType) + if err != nil { + return m, tea.Quit + } + // bind the socket to the network interface + err = rs.Bind(m.selectedInterface) + if err != nil { + m.err = err return m, tea.Quit } + m.selectedProtocol = msg.name + m.selectedEthType = msg.ethType + m.rawSocket = rs + m.step = receivePackets + m.packetsTable = newPacketsTable() - case spinner.TickMsg: - m.spinner, cmd = m.spinner.Update(msg) - return m, cmd + return m, nil } + return m, cmd +} + +func (m *bisturiModel) updateReceivingPacket(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + m.packetsTable, cmd = m.packetsTable.Update(msg) - m.startMenu, cmd = m.startMenu.Update(msg) return m, cmd } func (m bisturiModel) View() string { if m.err != nil { + if m.rawSocket != nil { + m.rawSocket.Close() + } return fmt.Sprintf("Error: %s\n", m.err) } + sb := strings.Builder{} switch m.step { - case ifaceStep, protoStep: - return m.startMenu.View() - case doneStep: - return fmt.Sprintf("Receiving packet on %s - %s\n", m.selectedInterface.Name, m.selectedProtocol) + case retrieveIfaces: + sb.WriteString(fmt.Sprintf("\n\n %s Retrieving network interfaces...\n\n", m.spinner.View())) + case selectIface, selectProtocol: + sb.WriteString(m.startMenu.View()) + case receivePackets: + sb.WriteString(fmt.Sprintf("Receiving %s packets on %s ...\n", m.selectedProtocol, m.selectedInterface.Name)) + sb.WriteString(m.packetsTable.View()) default: - return fmt.Sprintf("\n\n %s Retrieving network interfaces...\n\n", m.spinner.View()) + sb.WriteString("The program is in an unkowqn state\n") } - + return sb.String() } diff --git a/tui/models/interfaces_list.go b/tui/models/interfaces_list.go index 019d3b6..d7e000a 100644 --- a/tui/models/interfaces_list.go +++ b/tui/models/interfaces_list.go @@ -83,7 +83,7 @@ func fetchInterfaces() tea.Cmd { return func() tea.Msg { ifaces, err := net.Interfaces() if err != nil { - return errMsg{err: err} + return errMsg(err) } return networkInterfacesMsg(ifaces) } diff --git a/tui/models/packets_table.go b/tui/models/packets_table.go new file mode 100644 index 0000000..effc8a5 --- /dev/null +++ b/tui/models/packets_table.go @@ -0,0 +1,73 @@ +package tui + +import ( + "strings" + + "github.com/NamelessOne91/bisturi/protocols" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/evertras/bubble-table/table" +) + +const ( + columnKeyProtocol = "protocol" + columnKeyInterface = "interface" + columnKeyInfo = "info" +) + +type packetsTablemodel struct { + table table.Model + cachedRows []table.Row + packetsChan <-chan protocols.IPPacket +} + +func newPacketsTable() packetsTablemodel { + rows := make([]table.Row, 0, 20) + + return packetsTablemodel{ + cachedRows: rows, + table: table.New([]table.Column{ + table.NewColumn(columnKeyInterface, "Interface", 20), + table.NewColumn(columnKeyProtocol, "Protocol", 20), + table.NewColumn(columnKeyProtocol, "Info", 50), + }). + WithRows(rows). + WithBaseStyle(lipgloss.NewStyle(). + BorderForeground(lipgloss.Color("#00cc99")). + Foreground(lipgloss.Color("#00cc99")). + Align(lipgloss.Center), + ), + } +} + +func (m packetsTablemodel) Init() tea.Cmd { + return nil +} + +func (m packetsTablemodel) Update(msg tea.Msg) (packetsTablemodel, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + + m.table, cmd = m.table.Update(msg) + cmds = append(cmds, cmd) + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "esc", "q": + cmds = append(cmds, tea.Quit) + } + } + + return m, tea.Batch(cmds...) +} + +func (m packetsTablemodel) View() string { + sb := strings.Builder{} + + sb.WriteString(m.table.View()) + + return sb.String() +} diff --git a/tui/models/start_menu.go b/tui/models/start_menu.go index f452783..b6ff30f 100644 --- a/tui/models/start_menu.go +++ b/tui/models/start_menu.go @@ -30,7 +30,7 @@ func newStartMenuModel(interfaces []net.Interface) startMenuModel { plm := newProtocolsListModel(listWidth, listHeight) return startMenuModel{ - step: ifaceStep, + step: selectIface, ifaceList: il, protoList: plm, } @@ -42,10 +42,10 @@ func (m startMenuModel) Init() tea.Cmd { func (m startMenuModel) Update(msg tea.Msg) (startMenuModel, tea.Cmd) { switch m.step { - case ifaceStep: + case selectIface: model, cmd := m.ifaceList.Update(msg) if i, ok := msg.(selectedIfaceItemMsg); ok { - m.step = protoStep + m.step = selectProtocol return m, func() tea.Msg { return selectedInterfaceMsg{ name: i.name, @@ -55,7 +55,7 @@ func (m startMenuModel) Update(msg tea.Msg) (startMenuModel, tea.Cmd) { m.ifaceList = model return m, cmd - case protoStep: + case selectProtocol: model, cmd := m.protoList.Update(msg) if p, ok := msg.(selectedProtocolItemMsg); ok { return m, func() tea.Msg { @@ -74,9 +74,9 @@ func (m startMenuModel) Update(msg tea.Msg) (startMenuModel, tea.Cmd) { func (m startMenuModel) View() string { var s string switch m.step { - case ifaceStep: + case selectIface: s = m.ifaceList.l.View() - case protoStep: + case selectProtocol: s = m.protoList.l.View() default: return "Unkown step"