packet.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. package firewall
  2. import (
  3. "encoding/binary"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "net/netip"
  8. "github.com/google/gopacket/layers"
  9. "golang.org/x/net/ipv4"
  10. "golang.org/x/net/ipv6"
  11. )
  12. type m = map[string]any
  13. const (
  14. ProtoAny = 0 // When we want to handle HOPOPT (0) we can change this, if ever
  15. ProtoTCP = 6
  16. ProtoUDP = 17
  17. ProtoICMP = 1
  18. ProtoICMPv6 = 58
  19. PortAny = 0 // Special value for matching `port: any`
  20. PortFragment = -1 // Special value for matching `port: fragment`
  21. minFwPacketLen = 4
  22. )
  23. var (
  24. ErrPacketTooShort = errors.New("packet is too short")
  25. ErrUnknownIPVersion = errors.New("packet is an unknown ip version")
  26. ErrIPv4InvalidHeaderLength = errors.New("invalid ipv4 header length")
  27. ErrIPv4PacketTooShort = errors.New("ipv4 packet is too short")
  28. ErrIPv6PacketTooShort = errors.New("ipv6 packet is too short")
  29. ErrIPv6CouldNotFindPayload = errors.New("could not find payload in ipv6 packet")
  30. )
  31. type Packet struct {
  32. LocalAddr netip.Addr
  33. RemoteAddr netip.Addr
  34. LocalPort uint16
  35. RemotePort uint16
  36. Protocol uint8
  37. Fragment bool
  38. }
  39. func (fp *Packet) Copy() *Packet {
  40. return &Packet{
  41. LocalAddr: fp.LocalAddr,
  42. RemoteAddr: fp.RemoteAddr,
  43. LocalPort: fp.LocalPort,
  44. RemotePort: fp.RemotePort,
  45. Protocol: fp.Protocol,
  46. Fragment: fp.Fragment,
  47. }
  48. }
  49. func (fp Packet) MarshalJSON() ([]byte, error) {
  50. var proto string
  51. switch fp.Protocol {
  52. case ProtoTCP:
  53. proto = "tcp"
  54. case ProtoICMP:
  55. proto = "icmp"
  56. case ProtoUDP:
  57. proto = "udp"
  58. default:
  59. proto = fmt.Sprintf("unknown %v", fp.Protocol)
  60. }
  61. return json.Marshal(m{
  62. "LocalAddr": fp.LocalAddr.String(),
  63. "RemoteAddr": fp.RemoteAddr.String(),
  64. "LocalPort": fp.LocalPort,
  65. "RemotePort": fp.RemotePort,
  66. "Protocol": proto,
  67. "Fragment": fp.Fragment,
  68. })
  69. }
  70. func parseV6(data []byte, incoming bool, fp *Packet) error {
  71. dataLen := len(data)
  72. if dataLen < ipv6.HeaderLen {
  73. return ErrIPv6PacketTooShort
  74. }
  75. if incoming {
  76. fp.RemoteAddr, _ = netip.AddrFromSlice(data[8:24])
  77. fp.LocalAddr, _ = netip.AddrFromSlice(data[24:40])
  78. } else {
  79. fp.LocalAddr, _ = netip.AddrFromSlice(data[8:24])
  80. fp.RemoteAddr, _ = netip.AddrFromSlice(data[24:40])
  81. }
  82. protoAt := 6 // NextHeader is at 6 bytes into the ipv6 header
  83. offset := ipv6.HeaderLen // Start at the end of the ipv6 header
  84. next := 0
  85. for {
  86. if protoAt >= dataLen {
  87. break
  88. }
  89. proto := layers.IPProtocol(data[protoAt])
  90. switch proto {
  91. case layers.IPProtocolICMPv6, layers.IPProtocolESP, layers.IPProtocolNoNextHeader:
  92. fp.Protocol = uint8(proto)
  93. fp.RemotePort = 0
  94. fp.LocalPort = 0
  95. fp.Fragment = false
  96. return nil
  97. case layers.IPProtocolTCP, layers.IPProtocolUDP:
  98. if dataLen < offset+4 {
  99. return ErrIPv6PacketTooShort
  100. }
  101. fp.Protocol = uint8(proto)
  102. if incoming {
  103. fp.RemotePort = binary.BigEndian.Uint16(data[offset : offset+2])
  104. fp.LocalPort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
  105. } else {
  106. fp.LocalPort = binary.BigEndian.Uint16(data[offset : offset+2])
  107. fp.RemotePort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
  108. }
  109. fp.Fragment = false
  110. return nil
  111. case layers.IPProtocolIPv6Fragment:
  112. // Fragment header is 8 bytes, need at least offset+4 to read the offset field
  113. if dataLen < offset+8 {
  114. return ErrIPv6PacketTooShort
  115. }
  116. // Check if this is the first fragment
  117. fragmentOffset := binary.BigEndian.Uint16(data[offset+2:offset+4]) &^ uint16(0x7) // Remove the reserved and M flag bits
  118. if fragmentOffset != 0 {
  119. // Non-first fragment, use what we have now and stop processing
  120. fp.Protocol = data[offset]
  121. fp.Fragment = true
  122. fp.RemotePort = 0
  123. fp.LocalPort = 0
  124. return nil
  125. }
  126. // The next loop should be the transport layer since we are the first fragment
  127. next = 8 // Fragment headers are always 8 bytes
  128. case layers.IPProtocolAH:
  129. // Auth headers, used by IPSec, have a different meaning for header length
  130. if dataLen <= offset+1 {
  131. break
  132. }
  133. next = int(data[offset+1]+2) << 2
  134. default:
  135. // Normal ipv6 header length processing
  136. if dataLen <= offset+1 {
  137. break
  138. }
  139. next = int(data[offset+1]+1) << 3
  140. }
  141. if next <= 0 {
  142. // Safety check, each ipv6 header has to be at least 8 bytes
  143. next = 8
  144. }
  145. protoAt = offset
  146. offset = offset + next
  147. }
  148. return ErrIPv6CouldNotFindPayload
  149. }
  150. func parseV4(data []byte, incoming bool, fp *Packet) error {
  151. // Do we at least have an ipv4 header worth of data?
  152. if len(data) < ipv4.HeaderLen {
  153. return ErrIPv4PacketTooShort
  154. }
  155. // Adjust our start position based on the advertised ip header length
  156. ihl := int(data[0]&0x0f) << 2
  157. // Well-formed ip header length?
  158. if ihl < ipv4.HeaderLen {
  159. return ErrIPv4InvalidHeaderLength
  160. }
  161. // Check if this is the second or further fragment of a fragmented packet.
  162. flagsfrags := binary.BigEndian.Uint16(data[6:8])
  163. fp.Fragment = (flagsfrags & 0x1FFF) != 0
  164. // Firewall handles protocol checks
  165. fp.Protocol = data[9]
  166. // Accounting for a variable header length, do we have enough data for our src/dst tuples?
  167. minLen := ihl
  168. if !fp.Fragment && fp.Protocol != ProtoICMP {
  169. minLen += minFwPacketLen
  170. }
  171. if len(data) < minLen {
  172. return ErrIPv4InvalidHeaderLength
  173. }
  174. // Firewall packets are locally oriented
  175. if incoming {
  176. fp.RemoteAddr, _ = netip.AddrFromSlice(data[12:16])
  177. fp.LocalAddr, _ = netip.AddrFromSlice(data[16:20])
  178. if fp.Fragment || fp.Protocol == ProtoICMP {
  179. fp.RemotePort = 0
  180. fp.LocalPort = 0
  181. } else {
  182. fp.RemotePort = binary.BigEndian.Uint16(data[ihl : ihl+2])
  183. fp.LocalPort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
  184. }
  185. } else {
  186. fp.LocalAddr, _ = netip.AddrFromSlice(data[12:16])
  187. fp.RemoteAddr, _ = netip.AddrFromSlice(data[16:20])
  188. if fp.Fragment || fp.Protocol == ProtoICMP {
  189. fp.RemotePort = 0
  190. fp.LocalPort = 0
  191. } else {
  192. fp.LocalPort = binary.BigEndian.Uint16(data[ihl : ihl+2])
  193. fp.RemotePort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
  194. }
  195. }
  196. return nil
  197. }
  198. // NewPacket validates and parses the interesting bits for the firewall out of the ip and sub protocol headers
  199. func NewPacket(data []byte, incoming bool, fp *Packet) error {
  200. if len(data) < 1 {
  201. return ErrPacketTooShort
  202. }
  203. version := int((data[0] >> 4) & 0x0f)
  204. switch version {
  205. case ipv4.Version:
  206. return parseV4(data, incoming, fp)
  207. case ipv6.Version:
  208. return parseV6(data, incoming, fp)
  209. }
  210. return ErrUnknownIPVersion
  211. }