packet.go 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. package firewall
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. mathrand "math/rand"
  6. "net/netip"
  7. )
  8. type m = map[string]any
  9. const (
  10. ProtoAny = 0 // When we want to handle HOPOPT (0) we can change this, if ever
  11. ProtoTCP = 6
  12. ProtoUDP = 17
  13. ProtoICMP = 1
  14. ProtoICMPv6 = 58
  15. PortAny = 0 // Special value for matching `port: any`
  16. PortFragment = -1 // Special value for matching `port: fragment`
  17. )
  18. type Packet struct {
  19. LocalAddr netip.Addr
  20. RemoteAddr netip.Addr
  21. // LocalPort is the destination port for incoming traffic, or the source port for outgoing. Zero for ICMP.
  22. LocalPort uint16
  23. // RemotePort is the source port for incoming traffic, or the destination port for outgoing.
  24. // For ICMP, it's the "identifier". This is only used for connection tracking, actual firewall rules will not filter on ICMP identifier
  25. RemotePort uint16
  26. Protocol uint8
  27. Fragment bool
  28. }
  29. func (fp *Packet) Copy() *Packet {
  30. return &Packet{
  31. LocalAddr: fp.LocalAddr,
  32. RemoteAddr: fp.RemoteAddr,
  33. LocalPort: fp.LocalPort,
  34. RemotePort: fp.RemotePort,
  35. Protocol: fp.Protocol,
  36. Fragment: fp.Fragment,
  37. }
  38. }
  39. func (fp Packet) MarshalJSON() ([]byte, error) {
  40. var proto string
  41. switch fp.Protocol {
  42. case ProtoTCP:
  43. proto = "tcp"
  44. case ProtoICMP:
  45. proto = "icmp"
  46. case ProtoICMPv6:
  47. proto = "icmpv6"
  48. case ProtoUDP:
  49. proto = "udp"
  50. default:
  51. proto = fmt.Sprintf("unknown %v", fp.Protocol)
  52. }
  53. return json.Marshal(m{
  54. "LocalAddr": fp.LocalAddr.String(),
  55. "RemoteAddr": fp.RemoteAddr.String(),
  56. "LocalPort": fp.LocalPort,
  57. "RemotePort": fp.RemotePort,
  58. "Protocol": proto,
  59. "Fragment": fp.Fragment,
  60. })
  61. }
  62. // UDPSendPort calculates the UDP port to send from when using multiport mode.
  63. // The result will be from [0, numBuckets)
  64. func (fp Packet) UDPSendPort(numBuckets int) uint16 {
  65. if numBuckets <= 1 {
  66. return 0
  67. }
  68. // If there is no port (like an ICMP packet), pick a random UDP send port
  69. if fp.LocalPort == 0 {
  70. return uint16(mathrand.Intn(numBuckets))
  71. }
  72. // A decent enough 32bit hash function
  73. // Prospecting for Hash Functions
  74. // - https://nullprogram.com/blog/2018/07/31/
  75. // - https://github.com/skeeto/hash-prospector
  76. // [16 21f0aaad 15 d35a2d97 15] = 0.10760229515479501
  77. x := (uint32(fp.LocalPort) << 16) | uint32(fp.RemotePort)
  78. x ^= x >> 16
  79. x *= 0x21f0aaad
  80. x ^= x >> 15
  81. x *= 0xd35a2d97
  82. x ^= x >> 15
  83. return uint16(x) % uint16(numBuckets)
  84. }