Newer
Older
import (
"encoding/binary"
"fmt"
"io"
"net"
"strconv"
"time"
const MacLen = 6
// IPv6Type value as defined in IEEE 802: https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
const IPv6Type = 0x86DD
type MacAddr [MacLen]uint8
type Direction uint8
type TransportProtocol uint8
// IPAddr encodes v4 and v6 IPs with a fixed length.
// IPv4 addresses are encoded as IPv6 addresses with prefix ::ffff/96
// as described in https://datatracker.ietf.org/doc/html/rfc4038#section-4.2
// (same behavior as Go's net.IP type)
type IPAddr [net.IPv6len]uint8
type DataLink struct {
SrcMac MacAddr
DstMac MacAddr
}
type Network struct {
SrcAddr IPAddr
DstAddr IPAddr
type Transport struct {
Protocol TransportProtocol `json:"Proto"`
}
// what identifies a flow
type key struct {
Protocol uint16 `json:"Etype"`
Direction Direction
DataLink DataLink
Network Network
Transport Transport
// TODO: add TOS field
// it's important to emphasize that the fields in this structure have to coincide,
// byte by byte, with the flow structure in the bpf/flow.h file
// TODO: generate flow.h file from this structure
type rawRecord struct {
key
Bytes HumanBytes
// Record contains accumulated metrics from a flow
type Record struct {
rawRecord
TimeFlowStart time.Time
TimeFlowEnd time.Time
Interface string
Packets int
func (r *Record) Accumulate(src *Record) {
// assuming that the src record is later in time than the destination record
r.TimeFlowEnd = src.TimeFlowStart
r.Bytes += src.Bytes
r.Packets += src.Packets
// IP returns the net.IP equivalent object
func (ip *IPAddr) IP() net.IP {
return ip[:]
}
// IntEncodeV4 encodes an IPv4 address as an integer (in network encoding, big endian).
// It assumes that the passed IP is already IPv4. Otherwise it would just encode the
// last 4 bytes of an IPv6 address
func (ip *IPAddr) IntEncodeV4() uint32 {
return binary.BigEndian.Uint32(ip[net.IPv6len-net.IPv4len : net.IPv6len])
}
func (p TransportProtocol) String() string {
switch p {
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
case 0:
return "IP"
case 1:
return "ICMP"
case 2:
return "IGMP"
case 4:
return "IPIP"
case 6:
return "TCP"
case 8:
return "EGP"
case 12:
return "PUP"
case 17:
return "UDP"
case 22:
return "IDP"
case 29:
return "TP"
case 33:
return "DCCP"
case 41:
return "IPV6"
case 46:
return "RSVP"
case 136:
return "UDPLITE"
default:
return "other"
}
}
func (p TransportProtocol) MarshalJSON() ([]byte, error) {
return []byte("\"" + p.String() + "\""), nil
}
const (
kibi = 1024
mibi = kibi * 1024
gibi = mibi * 1024
)
func (b HumanBytes) String() string {
if b < kibi {
return strconv.FormatUint(uint64(b), 10)
}
if b < mibi {
return fmt.Sprintf("%.2f KiB", float64(b)/float64(kibi))
}
if b < gibi {
return fmt.Sprintf("%.2f MiB", float64(b)/float64(mibi))
}
return fmt.Sprintf("%.2f MiB", float64(b)/float64(gibi))
}
func (m *MacAddr) String() string {
return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", m[0], m[1], m[2], m[3], m[4], m[5])
}
func (m *MacAddr) MarshalJSON() ([]byte, error) {
return []byte("\"" + m.String() + "\""), nil
}
func (d Direction) MarshalJSON() ([]byte, error) {
switch d {
case 0:
return []byte(`"INGRESS"`), nil
case 1:
return []byte(`"EGRESS"`), nil
default:
return []byte(`"UNKNOWN"`), nil
}
}
// ReadFrom reads a Record from a binary source, in LittleEndian order
func ReadFrom(reader io.Reader) (*Record, error) {
var fr rawRecord
err := binary.Read(reader, binary.LittleEndian, &fr)
return &Record{rawRecord: fr}, err