wip: migrate to mono-repo. SPN has already been moved to spn/

This commit is contained in:
Patrick Pacher
2024-03-15 11:55:13 +01:00
parent b30fd00ccf
commit 8579430db9
577 changed files with 35981 additions and 818 deletions

View File

@@ -0,0 +1,35 @@
package packet
import "fmt"
// BandwidthUpdate holds an update to the seen bandwidth of a connection.
type BandwidthUpdate struct {
ConnID string
BytesReceived uint64
BytesSent uint64
Method BandwidthUpdateMethod
}
// BandwidthUpdateMethod defines how the bandwidth data of a bandwidth update should be interpreted.
type BandwidthUpdateMethod uint8
// Bandwidth Update Methods.
const (
Absolute BandwidthUpdateMethod = iota
Additive
)
func (bu *BandwidthUpdate) String() string {
return fmt.Sprintf("%s: %dB recv | %dB sent [%s]", bu.ConnID, bu.BytesReceived, bu.BytesSent, bu.Method)
}
func (bum BandwidthUpdateMethod) String() string {
switch bum {
case Absolute:
return "absolute"
case Additive:
return "additive"
default:
return "unknown"
}
}

View File

@@ -0,0 +1,117 @@
package packet
import (
"errors"
"fmt"
)
// Basic Types.
type (
// IPVersion represents an IP version.
IPVersion uint8
// IPProtocol represents an IP protocol.
IPProtocol uint8
// Verdict describes the decision on a packet.
Verdict uint8
)
// Basic Constants.
const (
IPv4 = IPVersion(4)
IPv6 = IPVersion(6)
InBound = true
OutBound = false
ICMP = IPProtocol(1)
IGMP = IPProtocol(2)
TCP = IPProtocol(6)
UDP = IPProtocol(17)
ICMPv6 = IPProtocol(58)
UDPLite = IPProtocol(136)
RAW = IPProtocol(255)
AnyHostInternalProtocol61 = IPProtocol(61)
)
// Verdicts.
const (
DROP Verdict = iota
BLOCK
ACCEPT
STOLEN
QUEUE
REPEAT
STOP
)
// ErrFailedToLoadPayload is returned by GetPayload if it failed for an unspecified reason, or is not implemented on the current system.
var ErrFailedToLoadPayload = errors.New("could not load packet payload")
// ByteSize returns the byte size of the ip (IPv4 = 4 bytes, IPv6 = 16).
func (v IPVersion) ByteSize() int {
switch v {
case IPv4:
return 4
case IPv6:
return 16
}
return 0
}
// String returns the string representation of the IP version: "IPv4" or "IPv6".
func (v IPVersion) String() string {
switch v {
case IPv4:
return "IPv4"
case IPv6:
return "IPv6"
}
return fmt.Sprintf("<unknown ip version, %d>", uint8(v))
}
// String returns the string representation (abbreviation) of the protocol.
func (p IPProtocol) String() string {
switch p {
case RAW:
return "RAW"
case TCP:
return "TCP"
case UDP:
return "UDP"
case UDPLite:
return "UDPLite"
case ICMP:
return "ICMP"
case ICMPv6:
return "ICMPv6"
case IGMP:
return "IGMP"
case AnyHostInternalProtocol61:
fallthrough
default:
return fmt.Sprintf("<unknown protocol, %d>", uint8(p))
}
}
// String returns the string representation of the verdict.
func (v Verdict) String() string {
switch v {
case DROP:
return "DROP"
case BLOCK:
return "BLOCK"
case ACCEPT:
return "ACCEPT"
case STOLEN:
return "STOLEN"
case QUEUE:
return "QUEUE"
case REPEAT:
return "REPEAT"
case STOP:
return "STOP"
default:
return fmt.Sprintf("<unsupported verdict, %d>", uint8(v))
}
}

View File

@@ -0,0 +1,77 @@
package packet
import (
"errors"
"fmt"
)
// InfoPacket does not represent an actual packet, but only holds metadata.
// Implements the packet.Packet interface.
type InfoPacket struct {
Base
}
// NewInfoPacket returns a new InfoPacket with the given info.
func NewInfoPacket(info Info) *InfoPacket {
return &InfoPacket{
Base{
info: info,
},
}
}
// InfoOnly returns whether the packet is informational only and does not
// represent an actual packet.
func (pkt *InfoPacket) InfoOnly() bool {
return true
}
// LoadPacketData does nothing on Linux, as data is always fully parsed.
func (pkt *InfoPacket) LoadPacketData() error {
return fmt.Errorf("%w: info-only packet", ErrFailedToLoadPayload)
}
// ErrInfoOnlyPacket is returned for unsupported operations on an info-only packet.
var ErrInfoOnlyPacket = errors.New("info-only packet")
// Accept does nothing on an info-only packet.
func (pkt *InfoPacket) Accept() error {
return ErrInfoOnlyPacket
}
// Block does nothing on an info-only packet.
func (pkt *InfoPacket) Block() error {
return ErrInfoOnlyPacket
}
// Drop does nothing on an info-only packet.
func (pkt *InfoPacket) Drop() error {
return ErrInfoOnlyPacket
}
// PermanentAccept does nothing on an info-only packet.
func (pkt *InfoPacket) PermanentAccept() error {
return ErrInfoOnlyPacket
}
// PermanentBlock does nothing on an info-only packet.
func (pkt *InfoPacket) PermanentBlock() error {
return ErrInfoOnlyPacket
}
// PermanentDrop does nothing on an info-only packet.
func (pkt *InfoPacket) PermanentDrop() error {
return ErrInfoOnlyPacket
}
// RerouteToNameserver does nothing on an info-only packet.
func (pkt *InfoPacket) RerouteToNameserver() error {
return ErrInfoOnlyPacket
}
// RerouteToTunnel does nothing on an info-only packet.
func (pkt *InfoPacket) RerouteToTunnel() error {
return ErrInfoOnlyPacket
}
var _ Packet = &InfoPacket{}

View File

@@ -0,0 +1,266 @@
package packet
import (
"context"
"fmt"
"net"
"github.com/google/gopacket"
)
// Base is a base structure for satisfying the Packet interface.
type Base struct {
ctx context.Context
info Info
connID string
layers gopacket.Packet
layer3Data []byte
layer5Data []byte
}
// FastTrackedByIntegration returns whether the packet has been fast-track
// accepted by the OS integration.
func (pkt *Base) FastTrackedByIntegration() bool {
return false
}
// InfoOnly returns whether the packet is informational only and does not
// represent an actual packet.
func (pkt *Base) InfoOnly() bool {
return false
}
// ExpectInfo returns whether the next packet is expected to be informational only.
func (pkt *Base) ExpectInfo() bool {
return false
}
// SetCtx sets the packet context.
func (pkt *Base) SetCtx(ctx context.Context) {
pkt.ctx = ctx
}
// Ctx returns the packet context.
func (pkt *Base) Ctx() context.Context {
return pkt.ctx
}
// Info returns the packet Info.
func (pkt *Base) Info() *Info {
return &pkt.info
}
// SetPacketInfo sets a new packet Info. This must only used when initializing the packet structure.
func (pkt *Base) SetPacketInfo(packetInfo Info) {
pkt.info = packetInfo
}
// SetInbound sets a the packet direction to inbound. This must only used when initializing the packet structure.
func (pkt *Base) SetInbound() {
pkt.info.Inbound = true
}
// SetOutbound sets a the packet direction to outbound. This must only used when initializing the packet structure.
func (pkt *Base) SetOutbound() {
pkt.info.Inbound = false
}
// IsInbound checks if the packet is inbound.
func (pkt *Base) IsInbound() bool {
return pkt.info.Inbound
}
// IsOutbound checks if the packet is outbound.
func (pkt *Base) IsOutbound() bool {
return !pkt.info.Inbound
}
// HasPorts checks if the packet has a protocol that uses ports.
func (pkt *Base) HasPorts() bool {
switch pkt.info.Protocol {
case TCP:
return true
case UDP, UDPLite:
return true
case ICMP, ICMPv6, IGMP, RAW, AnyHostInternalProtocol61:
fallthrough
default:
return false
}
}
// LoadPacketData loads packet data from the integration, if not yet done.
func (pkt *Base) LoadPacketData() error {
return ErrFailedToLoadPayload
}
// Layers returns the parsed layer data.
func (pkt *Base) Layers() gopacket.Packet {
return pkt.layers
}
// Raw returns the raw Layer 3 Network Data.
func (pkt *Base) Raw() []byte {
return pkt.layer3Data
}
// Payload returns the raw Layer 5 Network Data.
func (pkt *Base) Payload() []byte {
return pkt.layer5Data
}
// GetConnectionID returns the link ID for this packet.
func (pkt *Base) GetConnectionID() string {
if pkt.connID == "" {
pkt.connID = pkt.info.CreateConnectionID()
}
return pkt.connID
}
// MatchesAddress checks if a the packet matches a given endpoint (remote or local) in protocol, network and port.
//
// Comparison matrix:
//
// ====== IN OUT
//
// Local Dst Src
// Remote Src Dst
// .
func (pkt *Base) MatchesAddress(remote bool, protocol IPProtocol, network *net.IPNet, port uint16) bool {
if pkt.info.Protocol != protocol {
return false
}
if pkt.info.Inbound != remote {
if !network.Contains(pkt.info.Src) {
return false
}
if pkt.info.SrcPort != port {
return false
}
} else {
if !network.Contains(pkt.info.Dst) {
return false
}
if pkt.info.DstPort != port {
return false
}
}
return true
}
// MatchesIP checks if a the packet matches a given endpoint (remote or local) IP.
//
// Comparison matrix:
//
// ====== IN OUT
//
// Local Dst Src
// Remote Src Dst
// .
func (pkt *Base) MatchesIP(endpoint bool, network *net.IPNet) bool {
if pkt.info.Inbound != endpoint {
if network.Contains(pkt.info.Src) {
return true
}
} else {
if network.Contains(pkt.info.Dst) {
return true
}
}
return false
}
// FORMATTING
func (pkt *Base) String() string {
return pkt.FmtPacket()
}
// FmtPacket returns the most important information about the packet as a string.
func (pkt *Base) FmtPacket() string {
if pkt.info.Protocol == TCP || pkt.info.Protocol == UDP {
if pkt.info.Inbound {
return fmt.Sprintf("IN %s %s:%d <-> %s:%d", pkt.info.Protocol, pkt.info.Dst, pkt.info.DstPort, pkt.info.Src, pkt.info.SrcPort)
}
return fmt.Sprintf("OUT %s %s:%d <-> %s:%d", pkt.info.Protocol, pkt.info.Src, pkt.info.SrcPort, pkt.info.Dst, pkt.info.DstPort)
}
if pkt.info.Inbound {
return fmt.Sprintf("IN %s %s <-> %s", pkt.info.Protocol, pkt.info.Dst, pkt.info.Src)
}
return fmt.Sprintf("OUT %s %s <-> %s", pkt.info.Protocol, pkt.info.Src, pkt.info.Dst)
}
// FmtProtocol returns the protocol as a string.
func (pkt *Base) FmtProtocol() string {
return pkt.info.Protocol.String()
}
// FmtRemoteIP returns the remote IP address as a string.
func (pkt *Base) FmtRemoteIP() string {
if pkt.info.Inbound {
return pkt.info.Src.String()
}
return pkt.info.Dst.String()
}
// FmtRemotePort returns the remote port as a string.
func (pkt *Base) FmtRemotePort() string {
if pkt.info.SrcPort != 0 {
if pkt.info.Inbound {
return fmt.Sprintf("%d", pkt.info.SrcPort)
}
return fmt.Sprintf("%d", pkt.info.DstPort)
}
return "-"
}
// FmtRemoteAddress returns the full remote address (protocol, IP, port) as a string.
func (pkt *Base) FmtRemoteAddress() string {
return fmt.Sprintf("%s:%s:%s", pkt.info.Protocol.String(), pkt.FmtRemoteIP(), pkt.FmtRemotePort())
}
// Packet is an interface to a network packet to provide object behavior the same across all systems.
type Packet interface {
// Verdicts.
Accept() error
Block() error
Drop() error
PermanentAccept() error
PermanentBlock() error
PermanentDrop() error
RerouteToNameserver() error
RerouteToTunnel() error
FastTrackedByIntegration() bool
InfoOnly() bool
ExpectInfo() bool
// Info.
SetCtx(context.Context)
Ctx() context.Context
Info() *Info
SetPacketInfo(Info)
IsInbound() bool
IsOutbound() bool
SetInbound()
SetOutbound()
HasPorts() bool
GetConnectionID() string
// Payload.
LoadPacketData() error
Layers() gopacket.Packet
Raw() []byte
Payload() []byte
// Matching.
MatchesAddress(bool, IPProtocol, *net.IPNet, uint16) bool
MatchesIP(bool, *net.IPNet) bool
// Formatting.
String() string
FmtPacket() string
FmtProtocol() string
FmtRemoteIP() string
FmtRemotePort() string
FmtRemoteAddress() string
}

View File

@@ -0,0 +1,76 @@
package packet
import (
"fmt"
"net"
"time"
)
// Info holds IP and TCP/UDP header information.
type Info struct {
Inbound bool
InTunnel bool
Version IPVersion
Protocol IPProtocol
SrcPort, DstPort uint16
Src, Dst net.IP
PID int
SeenAt time.Time
}
// LocalIP returns the local IP of the packet.
func (pi *Info) LocalIP() net.IP {
if pi.Inbound {
return pi.Dst
}
return pi.Src
}
// RemoteIP returns the remote IP of the packet.
func (pi *Info) RemoteIP() net.IP {
if pi.Inbound {
return pi.Src
}
return pi.Dst
}
// LocalPort returns the local port of the packet.
func (pi *Info) LocalPort() uint16 {
if pi.Inbound {
return pi.DstPort
}
return pi.SrcPort
}
// RemotePort returns the remote port of the packet.
func (pi *Info) RemotePort() uint16 {
if pi.Inbound {
return pi.SrcPort
}
return pi.DstPort
}
// CreateConnectionID creates a connection ID.
// In most circumstances, this method should not be used directly, but
// packet.GetConnectionID() should be called instead.
func (pi *Info) CreateConnectionID() string {
return CreateConnectionID(pi.Protocol, pi.Src, pi.SrcPort, pi.Dst, pi.DstPort, pi.Inbound)
}
// CreateConnectionID creates a connection ID.
func CreateConnectionID(protocol IPProtocol, src net.IP, srcPort uint16, dst net.IP, dstPort uint16, inbound bool) string {
// TODO: make this ID not depend on the packet direction for better support for forwarded packets.
if protocol == TCP || protocol == UDP {
if inbound {
return fmt.Sprintf("%d-%s-%d-%s-%d", protocol, dst, dstPort, src, srcPort)
}
return fmt.Sprintf("%d-%s-%d-%s-%d", protocol, src, srcPort, dst, dstPort)
}
if inbound {
return fmt.Sprintf("%d-%s-%s", protocol, dst, src)
}
return fmt.Sprintf("%d-%s-%s", protocol, src, dst)
}

View File

@@ -0,0 +1,160 @@
package packet
import (
"errors"
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
var layerType2IPProtocol map[gopacket.LayerType]IPProtocol
func genIPProtocolFromLayerType() {
layerType2IPProtocol = make(map[gopacket.LayerType]IPProtocol)
for k, v := range layers.IPProtocolMetadata {
layerType2IPProtocol[v.LayerType] = IPProtocol(k)
}
}
func parseIPv4(packet gopacket.Packet, info *Info) error {
if ipv4, ok := packet.NetworkLayer().(*layers.IPv4); ok {
info.Version = IPv4
info.Src = ipv4.SrcIP
info.Dst = ipv4.DstIP
info.Protocol = IPProtocol(ipv4.Protocol)
}
return nil
}
func parseIPv6(packet gopacket.Packet, info *Info) error {
if ipv6, ok := packet.NetworkLayer().(*layers.IPv6); ok {
info.Version = IPv6
info.Src = ipv6.SrcIP
info.Dst = ipv6.DstIP
// we set Protocol to NextHeader as a fallback. If TCP or
// UDP layers are detected (somewhere in the list of options)
// the Protocol field is adjusted correctly.
info.Protocol = IPProtocol(ipv6.NextHeader)
}
return nil
}
func parseTCP(packet gopacket.Packet, info *Info) error {
if tcp, ok := packet.TransportLayer().(*layers.TCP); ok {
info.Protocol = TCP
info.SrcPort = uint16(tcp.SrcPort)
info.DstPort = uint16(tcp.DstPort)
}
return nil
}
func parseUDP(packet gopacket.Packet, info *Info) error {
if udp, ok := packet.TransportLayer().(*layers.UDP); ok {
info.Protocol = UDP
info.SrcPort = uint16(udp.SrcPort)
info.DstPort = uint16(udp.DstPort)
}
return nil
}
/*
func parseUDPLite(packet gopacket.Packet, info *Info) error {
if udpLite, ok := packet.TransportLayer().(*layers.UDPLite); ok {
info.Protocol = UDPLite
info.SrcPort = uint16(udpLite.SrcPort)
info.DstPort = uint16(udpLite.DstPort)
}
return nil
}
*/
func parseICMPv4(packet gopacket.Packet, info *Info) error {
if icmp, ok := packet.Layer(layers.LayerTypeICMPv4).(*layers.ICMPv4); ok {
info.Protocol = ICMP
_ = icmp
}
return nil
}
func parseICMPv6(packet gopacket.Packet, info *Info) error {
if icmp6, ok := packet.Layer(layers.LayerTypeICMPv6).(*layers.ICMPv6); ok {
info.Protocol = ICMPv6
_ = icmp6
}
return nil
}
func parseIGMP(packet gopacket.Packet, info *Info) error {
// gopacket uses LayerTypeIGMP for v1, v2 and v3 and may thus
// either return layers.IGMP or layers.IGMPv1or2
if layer := packet.Layer(layers.LayerTypeIGMP); layer != nil {
info.Protocol = IGMP
}
return nil
}
func checkError(packet gopacket.Packet, info *Info) error {
// Check for known unparseable before checking the error layer.
if info.Protocol == AnyHostInternalProtocol61 {
return nil
}
if err := packet.ErrorLayer(); err != nil {
return err.Error()
}
return nil
}
// Parse parses an IP packet and saves the information in the given packet object.
func Parse(packetData []byte, pktBase *Base) (err error) {
if len(packetData) == 0 {
return errors.New("empty packet")
}
pktBase.layer3Data = packetData
ipVersion := packetData[0] >> 4
var networkLayerType gopacket.LayerType
switch ipVersion {
case 4:
networkLayerType = layers.LayerTypeIPv4
case 6:
networkLayerType = layers.LayerTypeIPv6
default:
return fmt.Errorf("unknown IP version or network protocol: %02x", ipVersion)
}
packet := gopacket.NewPacket(packetData, networkLayerType, gopacket.DecodeOptions{
Lazy: true,
NoCopy: true,
})
availableDecoders := []func(gopacket.Packet, *Info) error{
parseIPv4,
parseIPv6,
parseTCP,
parseUDP,
// parseUDPLite, // We don't yet support udplite.
parseICMPv4,
parseICMPv6,
parseIGMP,
checkError,
}
for _, dec := range availableDecoders {
if err := dec(packet, pktBase.Info()); err != nil {
return err
}
}
pktBase.layers = packet
if transport := packet.TransportLayer(); transport != nil {
pktBase.layer5Data = transport.LayerPayload()
}
return nil
}
func init() {
genIPProtocolFromLayerType()
}