Documentation
¶
Overview ¶
Package model defines the core domain types shared by every stage of the tracehound pipeline: capture, flow assembly, fingerprinting, and detection.
This package deliberately has no dependencies outside the standard library. Everything above it (decoders, detectors, storage, API) depends on model, and model depends on nothing, which keeps the detection logic trivially testable without a network interface or a capture library in the loop.
Index ¶
- Constants
- Variables
- func KeyFor(p *Packet) (FlowKey, Direction)
- func NewFlowKey(src netip.Addr, srcPort uint16, dst netip.Addr, dstPort uint16, proto Protocol) (FlowKey, Direction)
- type Alert
- type Device
- type Direction
- type Flow
- type FlowKey
- type MAC
- type Packet
- type ParseError
- type Protocol
- type Severity
- type Technique
Constants ¶
const ( TCPFin uint8 = 1 << 0 TCPSyn uint8 = 1 << 1 TCPRst uint8 = 1 << 2 TCPPsh uint8 = 1 << 3 TCPAck uint8 = 1 << 4 TCPUrg uint8 = 1 << 5 )
TCP flag bits, matching the wire layout of the TCP header's flag octet.
Variables ¶
var ( TechAppLayerWebProto = Technique{ID: "T1071.001", Name: "Application Layer Protocol: Web Protocols", Tactic: "command-and-control"} TechAppLayerDNS = Technique{ID: "T1071.004", Name: "Application Layer Protocol: DNS", Tactic: "command-and-control"} TechExfilOverC2 = Technique{ID: "T1041", Name: "Exfiltration Over C2 Channel", Tactic: "exfiltration"} TechExfilDNS = Technique{ID: "T1048.003", Name: "Exfiltration Over Alternative Protocol", Tactic: "exfiltration"} TechNetworkScan = Technique{ID: "T1046", Name: "Network Service Discovery", Tactic: "discovery"} TechRemoteSysDiscov = Technique{ID: "T1018", Name: "Remote System Discovery", Tactic: "discovery"} TechEncryptedChannel = Technique{ID: "T1573", Name: "Encrypted Channel", Tactic: "command-and-control"} TechNonStandardPort = Technique{ID: "T1571", Name: "Non-Standard Port", Tactic: "command-and-control"} TechProtocolTunnel = Technique{ID: "T1572", Name: "Protocol Tunneling", Tactic: "command-and-control"} )
Common ATT&CK techniques referenced by the built-in detectors, defined once so rule authors and detectors cannot drift on naming.
Functions ¶
Types ¶
type Alert ¶
type Alert struct {
ID string `json:"id"`
Time time.Time `json:"time"`
RuleID string `json:"rule_id"`
Detector string `json:"detector"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
Severity Severity `json:"severity"`
Techniques []Technique `json:"techniques,omitempty"`
Src netip.Addr `json:"src,omitzero"`
Dst netip.Addr `json:"dst,omitzero"`
DstPort uint16 `json:"dst_port,omitempty"`
Proto string `json:"proto,omitempty"`
// Score is the detector's confidence in [0,1]. It is deliberately separate
// from Severity: severity is how bad this would be if true, score is how
// sure we are that it is true.
Score float64 `json:"score"`
// Evidence holds the supporting measurements. Values are restricted to
// JSON-native types so the API can pass them through untouched.
Evidence map[string]any `json:"evidence,omitempty"`
}
Alert is a single detection emitted by a detector.
Evidence carries the detector-specific numbers that justify the verdict -- the periodicity score, the entropy value, the ports touched. An analyst who cannot see why a tool fired will stop trusting the tool, so every detector in tracehound is required to show its work.
type Device ¶
type Device struct {
Addr netip.Addr `json:"addr"`
MAC string `json:"mac,omitempty"`
Hostname string `json:"hostname,omitempty"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
// JA4s is the set of distinct TLS client fingerprints this host has
// presented. More than a handful usually means a multi-tenant host — or an
// implant using a different TLS stack than the browser next to it.
JA4s []string `json:"ja4s,omitempty"`
BytesSent uint64 `json:"bytes_sent"`
BytesRecv uint64 `json:"bytes_recv"`
Flows uint64 `json:"flows"`
}
Device is a host observed on the monitored network, keyed by IP address. The passive fingerprints collected here are what turn a packet firehose into an asset inventory: JA4 hashes identify the TLS stack, which in practice identifies the application (and often the malware family).
type Direction ¶
type Direction uint8
Direction indicates which way a packet is travelling relative to the flow's canonical A/B ordering.
type Flow ¶
type Flow struct {
Key FlowKey `json:"-"`
Client netip.Addr `json:"client"`
ClientPort uint16 `json:"client_port"`
Server netip.Addr `json:"server"`
ServerPort uint16 `json:"server_port"`
Proto Protocol `json:"-"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
PacketsToServer uint64 `json:"packets_to_server"`
PacketsToClient uint64 `json:"packets_to_client"`
BytesToServer uint64 `json:"bytes_to_server"`
BytesToClient uint64 `json:"bytes_to_client"`
// TCPFlagsSeen is the union of every TCP flag octet observed on the flow.
// A flow with SYN but no SYN-ACK, for example, was never established.
TCPFlagsSeen uint8 `json:"-"`
// Application-layer attributes, populated opportunistically by decoders.
SNI string `json:"sni,omitempty"`
ALPN string `json:"alpn,omitempty"`
JA4 string `json:"ja4,omitempty"`
JA3 string `json:"ja3,omitempty"`
// contains filtered or unexported fields
}
Flow is an accumulating record of one bidirectional conversation.
Client/Server are assigned from the first packet observed: the sender of the first packet is treated as the client. For TCP this is corrected when a SYN is seen, since the SYN sender is authoritatively the client even if we joined the capture mid-conversation.
func (*Flow) Established ¶
Established reports whether a TCP handshake was completed on this flow. Non-TCP flows are always reported as established.
func (*Flow) ProtoString ¶
ProtoString renders the flow's protocol for JSON and display.
type FlowKey ¶
FlowKey identifies a bidirectional conversation.
The two endpoints are stored in a canonical order (A < B by address, then by port) so that packets travelling in either direction hash to the same key. This is what lets a single map lookup find the flow regardless of who sent the packet — the alternative, keying on (src,dst) and probing twice, doubles the hash cost on the hottest path in the program.
type MAC ¶
type MAC [6]byte
MAC is a link-layer address. Stored as a fixed array so Packet stays allocation-free and usable as a map key component.
type Packet ¶
type Packet struct {
Timestamp time.Time
SrcMAC MAC
DstMAC MAC
Src netip.Addr
Dst netip.Addr
SrcPort uint16
DstPort uint16
Proto Protocol
// TCPFlags is the raw flag octet; zero for non-TCP packets.
TCPFlags uint8
// CaptureLength is the number of bytes actually captured, WireLength the
// size of the frame on the wire. They differ when a snaplen truncates.
CaptureLength int
WireLength int
// Payload is the transport-layer payload (TCP/UDP data), not including
// headers. Nil when the packet carries no payload or was truncated.
Payload []byte
}
Packet is a decoded network packet reduced to the fields tracehound reasons about. It is passed by value through the pipeline; Payload aliases the capture buffer and is only valid until the next read from the same source. Anything that needs to outlive the current packet must copy it.
type ParseError ¶
ParseError reports an unrecognised enum value in a rule file.
func (*ParseError) Error ¶
func (e *ParseError) Error() string
type Protocol ¶
type Protocol uint8
Protocol is an IANA IP protocol number. We define our own rather than reuse the capture library's type so that detectors never import gopacket.
type Severity ¶
type Severity int
Severity ranks an alert for triage. The string forms match the vocabulary used by Sigma and most SIEMs so rule packs port across cleanly.
func (Severity) MarshalText ¶
MarshalText renders severity as its lowercase name in JSON and YAML.
func (*Severity) UnmarshalText ¶
UnmarshalText parses a severity name, so rule files can say `severity: high`.
type Technique ¶
type Technique struct {
ID string `json:"id" yaml:"id"` // e.g. "T1071.004"
Name string `json:"name" yaml:"name"` // e.g. "Application Layer Protocol: DNS"
Tactic string `json:"tactic" yaml:"tactic"` // e.g. "command-and-control"
}
Technique is a MITRE ATT&CK technique reference attached to an alert.
Mapping every detection to ATT&CK is not decoration: it is what lets a SOC answer "which parts of the kill chain can we actually see?" and it is the first thing a detection engineer will look for in a tool like this.