packet.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package firewall
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/netip"
  6. )
  7. type m = map[string]any
  8. const (
  9. ProtoAny = 0 // When we want to handle HOPOPT (0) we can change this, if ever
  10. ProtoTCP = 6
  11. ProtoUDP = 17
  12. ProtoICMP = 1
  13. ProtoICMPv6 = 58
  14. PortAny = 0 // Special value for matching `port: any`
  15. PortFragment = -1 // Special value for matching `port: fragment`
  16. )
  17. // PacketKey is the firewall's conntrack and ConntrackCache map key — the
  18. // dense form of the 5-tuple plus the protocol and fragment flag the
  19. // firewall actually discriminates flows on. Kept separate from Packet so
  20. // the conntrack-hit fast path doesn't pay for hashing the unique.Handle
  21. // each netip.Addr carries, and so the inbound parser can skip the
  22. // AddrFrom4/AddrFrom16 calls until rule matching actually needs them.
  23. //
  24. // Superset of the coalescer's flowKey shape (same 5-tuple, just in
  25. // Local/Remote orientation rather than wire src/dst).
  26. type PacketKey struct {
  27. LocalAddr [16]byte
  28. RemoteAddr [16]byte
  29. LocalPort uint16
  30. RemotePort uint16
  31. IsV6 bool
  32. Protocol uint8
  33. Fragment bool
  34. }
  35. type Packet struct {
  36. LocalAddr netip.Addr
  37. RemoteAddr netip.Addr
  38. // LocalPort is the destination port for incoming traffic, or the source port for outgoing. Zero for ICMP.
  39. LocalPort uint16
  40. // RemotePort is the source port for incoming traffic, or the destination port for outgoing.
  41. // For ICMP, it's the "identifier". This is only used for connection tracking, actual firewall rules will not filter on ICMP identifier
  42. RemotePort uint16
  43. Protocol uint8
  44. Fragment bool
  45. }
  46. // Key derives a PacketKey from a populated Packet. Used by the few code
  47. // paths that have a Packet but no Key in hand (e.g. tests). Both inbound
  48. // and outbound production parsers write straight into a PacketKey via
  49. // batch.ParsePacket, so this function is rarely on the hot path.
  50. func (fp *Packet) Key() PacketKey {
  51. k := PacketKey{
  52. Protocol: fp.Protocol,
  53. Fragment: fp.Fragment,
  54. }
  55. k.LocalPort = fp.LocalPort
  56. k.RemotePort = fp.RemotePort
  57. k.IsV6 = !fp.LocalAddr.Is4()
  58. if k.IsV6 {
  59. k.LocalAddr = fp.LocalAddr.As16()
  60. k.RemoteAddr = fp.RemoteAddr.As16()
  61. } else {
  62. v4 := fp.LocalAddr.As4()
  63. copy(k.LocalAddr[:4], v4[:])
  64. v4 = fp.RemoteAddr.As4()
  65. copy(k.RemoteAddr[:4], v4[:])
  66. }
  67. return k
  68. }
  69. // Hydrate fills fp's netip.Addr fields and copies the rest from k. Called
  70. // by the firewall slow path when conntrack misses and rule matching needs
  71. // the rich Packet form (CIDR lookups, family checks). The fast path skips
  72. // this entirely.
  73. func (k *PacketKey) Hydrate(fp *Packet) {
  74. fp.LocalPort = k.LocalPort
  75. fp.RemotePort = k.RemotePort
  76. fp.Protocol = k.Protocol
  77. fp.Fragment = k.Fragment
  78. if k.IsV6 {
  79. fp.LocalAddr = netip.AddrFrom16(k.LocalAddr)
  80. fp.RemoteAddr = netip.AddrFrom16(k.RemoteAddr)
  81. } else {
  82. var v4 [4]byte
  83. copy(v4[:], k.LocalAddr[:4])
  84. fp.LocalAddr = netip.AddrFrom4(v4)
  85. copy(v4[:], k.RemoteAddr[:4])
  86. fp.RemoteAddr = netip.AddrFrom4(v4)
  87. }
  88. }
  89. func (k *PacketKey) GetRemoteAddr() netip.Addr {
  90. if k.IsV6 {
  91. return netip.AddrFrom16(k.RemoteAddr)
  92. } else {
  93. var v4 [4]byte
  94. copy(v4[:], k.RemoteAddr[:4])
  95. return netip.AddrFrom4(v4)
  96. }
  97. }
  98. func (fp *Packet) Copy() *Packet {
  99. return &Packet{
  100. LocalAddr: fp.LocalAddr,
  101. RemoteAddr: fp.RemoteAddr,
  102. LocalPort: fp.LocalPort,
  103. RemotePort: fp.RemotePort,
  104. Protocol: fp.Protocol,
  105. Fragment: fp.Fragment,
  106. }
  107. }
  108. func (fp Packet) MarshalJSON() ([]byte, error) {
  109. var proto string
  110. switch fp.Protocol {
  111. case ProtoTCP:
  112. proto = "tcp"
  113. case ProtoICMP:
  114. proto = "icmp"
  115. case ProtoICMPv6:
  116. proto = "icmpv6"
  117. case ProtoUDP:
  118. proto = "udp"
  119. default:
  120. proto = fmt.Sprintf("unknown %v", fp.Protocol)
  121. }
  122. return json.Marshal(m{
  123. "LocalAddr": fp.LocalAddr.String(),
  124. "RemoteAddr": fp.RemoteAddr.String(),
  125. "LocalPort": fp.LocalPort,
  126. "RemotePort": fp.RemotePort,
  127. "Protocol": proto,
  128. "Fragment": fp.Fragment,
  129. })
  130. }