refact: refactored can dbc logic

This commit is contained in:
Simone Margaritelli 2024-08-23 16:03:35 +02:00
commit 840f819484
6 changed files with 214 additions and 108 deletions

View file

@ -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
View 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
}

View file

@ -0,0 +1,6 @@
package can
func (mod *CANModule) dbcLoad(name string) error {
// load as file
return mod.dbc.LoadFile(mod, name)
}

View file

@ -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{

View file

@ -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 = ""
} }
}) })