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:
Andy Dixon
2026-10-09 17:34:32 +01:00
commit 52da3c13f6
26 changed files with 4335 additions and 0 deletions
+107
View File
@@ -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
}