Skip to content
Snippets Groups Projects
record.go 3.61 KiB
Newer Older
  • Learn to ignore specific revisions
  • package flow
    
    
    import (
    	"encoding/binary"
    	"fmt"
    	"io"
    	"net"
    	"strconv"
    
    
    // IPv6Type value as defined in IEEE 802: https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
    
    const IPv6Type = 0x86DD
    
    type HumanBytes uint64
    
    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
    
    	SrcPort  uint16
    	DstPort  uint16
    
    	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
    
    // record structure as parsed from eBPF
    
    // 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 {
    
    	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