mirror of
https://github.com/bettercap/bettercap
synced 2025-08-19 21:13:18 -07:00
refact: refactored can dbc logic
This commit is contained in:
parent
31d93e7c39
commit
840f819484
6 changed files with 214 additions and 108 deletions
|
@ -6,7 +6,6 @@ import (
|
||||||
|
|
||||||
"github.com/bettercap/bettercap/v2/session"
|
"github.com/bettercap/bettercap/v2/session"
|
||||||
"github.com/hashicorp/go-bexpr"
|
"github.com/hashicorp/go-bexpr"
|
||||||
"go.einride.tech/can/pkg/descriptor"
|
|
||||||
"go.einride.tech/can/pkg/socketcan"
|
"go.einride.tech/can/pkg/socketcan"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,19 +16,17 @@ type CANModule struct {
|
||||||
transport string
|
transport string
|
||||||
filter string
|
filter string
|
||||||
filterExpr *bexpr.Evaluator
|
filterExpr *bexpr.Evaluator
|
||||||
dbcPath string
|
dbc *DBC
|
||||||
dbc *descriptor.Database
|
conn net.Conn
|
||||||
|
recv *socketcan.Receiver
|
||||||
conn net.Conn
|
send *socketcan.Transmitter
|
||||||
recv *socketcan.Receiver
|
|
||||||
send *socketcan.Transmitter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCanModule(s *session.Session) *CANModule {
|
func NewCanModule(s *session.Session) *CANModule {
|
||||||
mod := &CANModule{
|
mod := &CANModule{
|
||||||
SessionModule: session.NewSessionModule("can", s),
|
SessionModule: session.NewSessionModule("can", s),
|
||||||
dbcPath: "",
|
|
||||||
filter: "",
|
filter: "",
|
||||||
|
dbc: &DBC{},
|
||||||
filterExpr: nil,
|
filterExpr: nil,
|
||||||
transport: "can",
|
transport: "can",
|
||||||
deviceName: "can0",
|
deviceName: "can0",
|
||||||
|
@ -45,11 +42,6 @@ func NewCanModule(s *session.Session) *CANModule {
|
||||||
"",
|
"",
|
||||||
"Network type, can be 'can' for SocketCAN or 'udp'."))
|
"Network type, can be 'can' for SocketCAN or 'udp'."))
|
||||||
|
|
||||||
mod.AddParam(session.NewStringParameter("can.dbc_path",
|
|
||||||
mod.dbcPath,
|
|
||||||
"",
|
|
||||||
"Optional path to DBC file for decoding."))
|
|
||||||
|
|
||||||
mod.AddParam(session.NewStringParameter("can.filter",
|
mod.AddParam(session.NewStringParameter("can.filter",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
@ -61,6 +53,18 @@ func NewCanModule(s *session.Session) *CANModule {
|
||||||
return mod.Start()
|
return mod.Start()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
mod.AddHandler(session.NewModuleHandler("can.load_dbc PATH", "",
|
||||||
|
"Start CAN-bus discovery.",
|
||||||
|
func(args []string) error {
|
||||||
|
return mod.Start()
|
||||||
|
}))
|
||||||
|
|
||||||
|
mod.AddHandler(session.NewModuleHandler("can.dbc.load NAME", "can.dbc.load (.+)",
|
||||||
|
"Load a DBC file from the list of available ones or from disk.",
|
||||||
|
func(args []string) error {
|
||||||
|
return mod.dbcLoad(args[0])
|
||||||
|
}))
|
||||||
|
|
||||||
mod.AddHandler(session.NewModuleHandler("can.recon off", "",
|
mod.AddHandler(session.NewModuleHandler("can.recon off", "",
|
||||||
"Stop CAN-bus discovery.",
|
"Stop CAN-bus discovery.",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
|
|
169
modules/can/can_dbc.go
Normal file
169
modules/can/can_dbc.go
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
package can
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/evilsocket/islazy/str"
|
||||||
|
"go.einride.tech/can"
|
||||||
|
"go.einride.tech/can/pkg/descriptor"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBC struct {
|
||||||
|
sync.RWMutex
|
||||||
|
|
||||||
|
path string
|
||||||
|
db *descriptor.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbc *DBC) Loaded() bool {
|
||||||
|
dbc.RLock()
|
||||||
|
defer dbc.RUnlock()
|
||||||
|
|
||||||
|
return dbc.db != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbc *DBC) LoadFile(mod *CANModule, path string) error {
|
||||||
|
input, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't read %s: %v", path, err)
|
||||||
|
}
|
||||||
|
return dbc.LoadData(mod, path, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbc *DBC) LoadData(mod *CANModule, name string, input []byte) error {
|
||||||
|
dbc.Lock()
|
||||||
|
defer dbc.Unlock()
|
||||||
|
|
||||||
|
mod.Debug("compiling %s ...", name)
|
||||||
|
|
||||||
|
result, err := dbcCompile(name, input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't compile %s: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, warning := range result.Warnings {
|
||||||
|
mod.Warning("%v", warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbc.path = name
|
||||||
|
dbc.db = result.Database
|
||||||
|
|
||||||
|
mod.Info("%s loaded", name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbc *DBC) Parse(mod *CANModule, frame can.Frame, msg *Message) bool {
|
||||||
|
dbc.RLock()
|
||||||
|
defer dbc.RUnlock()
|
||||||
|
|
||||||
|
// did we load any DBC database?
|
||||||
|
if dbc.db == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the database contains this message id
|
||||||
|
if message, found := dbc.db.Message(frame.ID); found {
|
||||||
|
msg.Name = message.Name
|
||||||
|
|
||||||
|
// find source full info in DBC nodes
|
||||||
|
sourceName := message.SenderNode
|
||||||
|
sourceDesc := ""
|
||||||
|
if sender, found := dbc.db.Node(message.SenderNode); found {
|
||||||
|
sourceName = sender.Name
|
||||||
|
sourceDesc = sender.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
// add CAN source if new
|
||||||
|
_, msg.Source = mod.Session.CAN.AddIfNew(sourceName, sourceDesc, frame.Data[:])
|
||||||
|
|
||||||
|
msg.Signals = make(map[string]string)
|
||||||
|
|
||||||
|
// parse signals
|
||||||
|
for _, signal := range message.Signals {
|
||||||
|
var value string
|
||||||
|
|
||||||
|
if signal.Length <= 32 && signal.IsFloat {
|
||||||
|
value = fmt.Sprintf("%f", signal.UnmarshalFloat(frame.Data))
|
||||||
|
} else if signal.Length == 1 {
|
||||||
|
value = fmt.Sprintf("%v", signal.UnmarshalBool(frame.Data))
|
||||||
|
} else if signal.IsSigned {
|
||||||
|
value = fmt.Sprintf("%d", signal.UnmarshalSigned(frame.Data))
|
||||||
|
} else {
|
||||||
|
value = fmt.Sprintf("%d", signal.UnmarshalUnsigned(frame.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Signals[signal.Name] = str.Trim(fmt.Sprintf("%s %s", value, signal.Unit))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbc *DBC) MessagesBySender(senderId string) []*descriptor.Message {
|
||||||
|
dbc.RLock()
|
||||||
|
defer dbc.RUnlock()
|
||||||
|
|
||||||
|
fromSender := make([]*descriptor.Message, 0)
|
||||||
|
|
||||||
|
if dbc.db == nil {
|
||||||
|
return fromSender
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range dbc.db.Messages {
|
||||||
|
if msg.SenderNode == senderId {
|
||||||
|
fromSender = append(fromSender, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromSender
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbc *DBC) MessageById(frameID uint32) *descriptor.Message {
|
||||||
|
dbc.RLock()
|
||||||
|
defer dbc.RUnlock()
|
||||||
|
|
||||||
|
if dbc.db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if message, found := dbc.db.Message(frameID); found {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbc *DBC) Messages() []*descriptor.Message {
|
||||||
|
dbc.RLock()
|
||||||
|
defer dbc.RUnlock()
|
||||||
|
|
||||||
|
if dbc.db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbc.db.Messages
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbc *DBC) Senders() []string {
|
||||||
|
dbc.RLock()
|
||||||
|
defer dbc.RUnlock()
|
||||||
|
|
||||||
|
senders := make([]string, 0)
|
||||||
|
if dbc.db == nil {
|
||||||
|
return senders
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq := make(map[string]bool)
|
||||||
|
for _, msg := range dbc.db.Messages {
|
||||||
|
uniq[msg.SenderNode] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for sender, _ := range uniq {
|
||||||
|
senders = append(senders, sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
return senders
|
||||||
|
}
|
6
modules/can/can_dbc_load.go
Normal file
6
modules/can/can_dbc_load.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package can
|
||||||
|
|
||||||
|
func (mod *CANModule) dbcLoad(name string) error {
|
||||||
|
// load as file
|
||||||
|
return mod.dbc.LoadFile(mod, name)
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ import (
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"go.einride.tech/can"
|
"go.einride.tech/can"
|
||||||
"go.einride.tech/can/pkg/descriptor"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (mod *CANModule) Fuzz(id string) error {
|
func (mod *CANModule) Fuzz(id string) error {
|
||||||
|
@ -25,15 +24,9 @@ func (mod *CANModule) Fuzz(id string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if mod.dbc != nil {
|
if mod.dbc != nil {
|
||||||
// not a number, use as node name
|
// not a number, use as node name
|
||||||
fromSender := make([]*descriptor.Message, 0)
|
fromSender := mod.dbc.MessagesBySender(id)
|
||||||
for _, msg := range mod.dbc.Messages {
|
|
||||||
if msg.SenderNode == id {
|
|
||||||
fromSender = append(fromSender, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fromSender) == 0 {
|
if len(fromSender) == 0 {
|
||||||
return fmt.Errorf("no messages defined in DBC file for node %s", id)
|
return fmt.Errorf("no messages defined in DBC file for node %s, available nodes: %s", id, mod.dbc.Senders())
|
||||||
}
|
}
|
||||||
|
|
||||||
idx := rng.Intn(len(fromSender))
|
idx := rng.Intn(len(fromSender))
|
||||||
|
@ -46,8 +39,8 @@ func (mod *CANModule) Fuzz(id string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have a DBC
|
// if we have a DBC
|
||||||
if mod.dbc != nil {
|
if mod.dbc.Loaded() {
|
||||||
if message, found := mod.dbc.Message(uint32(frameID)); found {
|
if message := mod.dbc.MessageById(uint32(frameID)); message != nil {
|
||||||
mod.Info("found as %s", message.Name)
|
mod.Info("found as %s", message.Name)
|
||||||
|
|
||||||
dataLen = int(message.Length)
|
dataLen = int(message.Length)
|
||||||
|
@ -57,7 +50,7 @@ func (mod *CANModule) Fuzz(id string) error {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
avail := []string{}
|
avail := []string{}
|
||||||
for _, msg := range mod.dbc.Messages {
|
for _, msg := range mod.dbc.Messages() {
|
||||||
avail = append(avail, fmt.Sprintf("%d (%s)", msg.ID, msg.Name))
|
avail = append(avail, fmt.Sprintf("%d (%s)", msg.ID, msg.Name))
|
||||||
}
|
}
|
||||||
return fmt.Errorf("message with id %d not found in DBC file, available ids: %v", frameID, strings.Join(avail, ", "))
|
return fmt.Errorf("message with id %d not found in DBC file, available ids: %v", frameID, strings.Join(avail, ", "))
|
||||||
|
@ -70,7 +63,7 @@ func (mod *CANModule) Fuzz(id string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mod.Warning("no can.dbc_path is set, creating frame with %d bytes of random data", dataLen)
|
mod.Warning("no dbc loaded, creating frame with %d bytes of random data", dataLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
frame := can.Frame{
|
frame := can.Frame{
|
||||||
|
|
|
@ -2,12 +2,9 @@ package can
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/bettercap/bettercap/v2/network"
|
"github.com/bettercap/bettercap/v2/network"
|
||||||
"github.com/bettercap/bettercap/v2/session"
|
"github.com/bettercap/bettercap/v2/session"
|
||||||
"github.com/evilsocket/islazy/str"
|
|
||||||
"github.com/evilsocket/islazy/tui"
|
"github.com/evilsocket/islazy/tui"
|
||||||
"github.com/hashicorp/go-bexpr"
|
"github.com/hashicorp/go-bexpr"
|
||||||
"go.einride.tech/can"
|
"go.einride.tech/can"
|
||||||
|
@ -21,28 +18,6 @@ type Message struct {
|
||||||
Signals map[string]string
|
Signals map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mod *CANModule) compileDBC() error {
|
|
||||||
input, err := os.ReadFile(mod.dbcPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't read %s: %v", mod.dbcPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod.Info("compiling %s ...", mod.dbcPath)
|
|
||||||
|
|
||||||
result, err := dbcCompile(mod.dbcPath, input)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't compile %s: %v", mod.dbcPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, warning := range result.Warnings {
|
|
||||||
mod.Warning("%v", warning)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod.dbc = result.Database
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mod *CANModule) Configure() error {
|
func (mod *CANModule) Configure() error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -54,20 +29,10 @@ func (mod *CANModule) Configure() error {
|
||||||
return err
|
return err
|
||||||
} else if mod.transport != "can" && mod.transport != "udp" {
|
} else if mod.transport != "can" && mod.transport != "udp" {
|
||||||
return errors.New("invalid transport")
|
return errors.New("invalid transport")
|
||||||
} else if err, mod.dbcPath = mod.StringParam("can.dbc_path"); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err, mod.filter = mod.StringParam("can.filter"); err != nil {
|
} else if err, mod.filter = mod.StringParam("can.filter"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if mod.dbcPath != "" {
|
|
||||||
if err := mod.compileDBC(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mod.Warning("no can.dbc_path specified, messages won't be parsed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mod.filter != "" {
|
if mod.filter != "" {
|
||||||
if mod.filterExpr, err = bexpr.CreateEvaluator(mod.filter); err != nil {
|
if mod.filterExpr, err = bexpr.CreateEvaluator(mod.filter); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -85,49 +50,7 @@ func (mod *CANModule) Configure() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mod *CANModule) onFrame(frame can.Frame) {
|
func (mod *CANModule) isFilteredOut(frame can.Frame, msg Message) bool {
|
||||||
msg := Message{
|
|
||||||
Frame: frame,
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we have a DBC database
|
|
||||||
if mod.dbc != nil {
|
|
||||||
// if the database contains this message id
|
|
||||||
if message, found := mod.dbc.Message(frame.ID); found {
|
|
||||||
msg.Name = message.Name
|
|
||||||
|
|
||||||
// find source full info in DBC nodes
|
|
||||||
sourceName := message.SenderNode
|
|
||||||
sourceDesc := ""
|
|
||||||
if sender, found := mod.dbc.Node(message.SenderNode); found {
|
|
||||||
sourceName = sender.Name
|
|
||||||
sourceDesc = sender.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
// add CAN source if new
|
|
||||||
_, msg.Source = mod.Session.CAN.AddIfNew(sourceName, sourceDesc, frame.Data[:])
|
|
||||||
|
|
||||||
msg.Signals = make(map[string]string)
|
|
||||||
|
|
||||||
// parse signals
|
|
||||||
for _, signal := range message.Signals {
|
|
||||||
var value string
|
|
||||||
|
|
||||||
if signal.Length <= 32 && signal.IsFloat {
|
|
||||||
value = fmt.Sprintf("%f", signal.UnmarshalFloat(frame.Data))
|
|
||||||
} else if signal.Length == 1 {
|
|
||||||
value = fmt.Sprintf("%v", signal.UnmarshalBool(frame.Data))
|
|
||||||
} else if signal.IsSigned {
|
|
||||||
value = fmt.Sprintf("%d", signal.UnmarshalSigned(frame.Data))
|
|
||||||
} else {
|
|
||||||
value = fmt.Sprintf("%d", signal.UnmarshalUnsigned(frame.Data))
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Signals[signal.Name] = str.Trim(fmt.Sprintf("%s %s", value, signal.Unit))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we have an active filter
|
// if we have an active filter
|
||||||
if mod.filter != "" {
|
if mod.filter != "" {
|
||||||
if res, err := mod.filterExpr.Evaluate(map[string]interface{}{
|
if res, err := mod.filterExpr.Evaluate(map[string]interface{}{
|
||||||
|
@ -137,11 +60,24 @@ func (mod *CANModule) onFrame(frame can.Frame) {
|
||||||
mod.Error("error evaluating '%s': %v", mod.filter, err)
|
mod.Error("error evaluating '%s': %v", mod.filter, err)
|
||||||
} else if !res {
|
} else if !res {
|
||||||
mod.Debug("skipping can message %+v", msg)
|
mod.Debug("skipping can message %+v", msg)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod.Session.Events.Add("can.message", msg)
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mod *CANModule) onFrame(frame can.Frame) {
|
||||||
|
msg := Message{
|
||||||
|
Frame: frame,
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to parse with DBC if we have any
|
||||||
|
mod.dbc.Parse(mod, frame, &msg)
|
||||||
|
|
||||||
|
if !mod.isFilteredOut(frame, msg) {
|
||||||
|
mod.Session.Events.Add("can.message", msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canPrompt = "{br}{fw}{env.can.device} {fb}{reset} {bold}» {reset}"
|
const canPrompt = "{br}{fw}{env.can.device} {fb}{reset} {bold}» {reset}"
|
||||||
|
@ -173,8 +109,6 @@ func (mod *CANModule) Stop() error {
|
||||||
mod.conn = nil
|
mod.conn = nil
|
||||||
mod.recv = nil
|
mod.recv = nil
|
||||||
mod.send = nil
|
mod.send = nil
|
||||||
mod.dbc = nil
|
|
||||||
mod.dbcPath = ""
|
|
||||||
mod.filter = ""
|
mod.filter = ""
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue