Core packages: config validation, network renderers, root helper, auth, SQLite store, capture engine
Capture engine streams dumpcap pcapng into verbatim, hashed segments with per-segment tshark summaries; display filters, PDML dissection with verified byte ranges, export and import. Tests run in a Debian trixie container.
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Node is one line of the protocol tree sent to the browser.
|
||||
type Node struct {
|
||||
Label string `json:"label"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Pos int `json:"pos"`
|
||||
Size int `json:"size"`
|
||||
Verified bool `json:"verified"` // bytes at pos..pos+size are proven to belong to this field
|
||||
Children []Node `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type pdmlNode struct {
|
||||
XMLName xml.Name
|
||||
Name string `xml:"name,attr"`
|
||||
Show string `xml:"show,attr"`
|
||||
Showname string `xml:"showname,attr"`
|
||||
Value string `xml:"value,attr"`
|
||||
Unmasked string `xml:"unmaskedvalue,attr"`
|
||||
Pos string `xml:"pos,attr"`
|
||||
Size string `xml:"size,attr"`
|
||||
Hide string `xml:"hide,attr"`
|
||||
Children []pdmlNode `xml:",any"`
|
||||
}
|
||||
|
||||
// ParsePDML converts tshark PDML for a single packet into a tree. frame holds
|
||||
// the original captured bytes, used to verify each field's byte range.
|
||||
func ParsePDML(b []byte, frame []byte) ([]Node, error) {
|
||||
var doc struct {
|
||||
Packets []struct {
|
||||
Protos []pdmlNode `xml:",any"`
|
||||
} `xml:"packet"`
|
||||
}
|
||||
d := xml.NewDecoder(bytes.NewReader(b))
|
||||
d.Strict = false
|
||||
if err := d.Decode(&doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(doc.Packets) == 0 {
|
||||
return nil, errors.New("packet not found")
|
||||
}
|
||||
var out []Node
|
||||
for _, p := range doc.Packets[0].Protos {
|
||||
if p.Name == "geninfo" || p.Hide == "yes" {
|
||||
continue
|
||||
}
|
||||
n, _ := convert(p, frame)
|
||||
out = append(out, n)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// convert returns the node and whether any field in its subtree contradicts the frame bytes.
|
||||
func convert(p pdmlNode, frame []byte) (Node, bool) {
|
||||
n := Node{Field: p.Name}
|
||||
n.Label = p.Showname
|
||||
if n.Label == "" {
|
||||
n.Label = p.Show
|
||||
}
|
||||
if n.Label == "" {
|
||||
n.Label = p.Name
|
||||
}
|
||||
n.Pos, _ = strconv.Atoi(p.Pos)
|
||||
n.Size, _ = strconv.Atoi(p.Size)
|
||||
inRange := n.Size > 0 && n.Pos >= 0 && n.Pos+n.Size <= len(frame)
|
||||
raw := p.Value
|
||||
if p.Unmasked != "" {
|
||||
raw = p.Unmasked // bitfields: value is the masked number, unmaskedvalue the bytes
|
||||
}
|
||||
contradicts := false
|
||||
if v, err := hex.DecodeString(raw); err == nil && len(v) > 0 && len(v) == n.Size {
|
||||
if inRange && bytes.Equal(v, frame[n.Pos:n.Pos+n.Size]) {
|
||||
n.Verified = true
|
||||
} else {
|
||||
contradicts = true
|
||||
}
|
||||
}
|
||||
matched, mismatched := false, contradicts
|
||||
for _, c := range p.Children {
|
||||
if c.Hide == "yes" {
|
||||
continue
|
||||
}
|
||||
cn, cm := convert(c, frame)
|
||||
if c.XMLName.Local != "proto" { // a nested (e.g. reassembled) protocol is judged on its own
|
||||
matched = matched || cn.Verified
|
||||
mismatched = mismatched || cm
|
||||
}
|
||||
n.Children = append(n.Children, cn)
|
||||
}
|
||||
// Protocols carry no value: trust their range only if their own fields verify
|
||||
// against the frame bytes and none contradict it (rules out reassembled sources).
|
||||
if raw == "" && inRange && matched && !mismatched {
|
||||
n.Verified = true
|
||||
}
|
||||
if p.Name == "frame" && inRange {
|
||||
n.Verified = true
|
||||
}
|
||||
return n, mismatched
|
||||
}
|
||||
Reference in New Issue
Block a user