outside_test.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. package nebula
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "net"
  6. "net/netip"
  7. "testing"
  8. "github.com/google/gopacket"
  9. "github.com/google/gopacket/layers"
  10. "github.com/slackhq/nebula/firewall"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/require"
  13. "golang.org/x/net/ipv4"
  14. "golang.org/x/net/ipv6"
  15. )
  16. func Test_newPacket(t *testing.T) {
  17. p := &firewall.ParsedPacket{}
  18. // length fails
  19. err := newPacket([]byte{}, true, p)
  20. require.ErrorIs(t, err, ErrPacketTooShort)
  21. err = newPacket([]byte{0x40}, true, p)
  22. require.ErrorIs(t, err, ErrIPv4PacketTooShort)
  23. err = newPacket([]byte{0x60}, true, p)
  24. require.ErrorIs(t, err, ErrIPv6PacketTooShort)
  25. // length fail with ip options
  26. h := ipv4.Header{
  27. Version: 1,
  28. Len: 100,
  29. Src: net.IPv4(10, 0, 0, 1),
  30. Dst: net.IPv4(10, 0, 0, 2),
  31. Options: []byte{0, 1, 0, 2},
  32. }
  33. b, _ := h.Marshal()
  34. err = newPacket(b, true, p)
  35. require.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)
  36. // not an ipv4 packet
  37. err = newPacket([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)
  38. require.ErrorIs(t, err, ErrUnknownIPVersion)
  39. // invalid ihl
  40. err = newPacket([]byte{4<<4 | (8 >> 2 & 0x0f), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)
  41. require.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)
  42. // account for variable ip header length - incoming
  43. h = ipv4.Header{
  44. Version: 1,
  45. Len: 100,
  46. Src: net.IPv4(10, 0, 0, 1),
  47. Dst: net.IPv4(10, 0, 0, 2),
  48. Options: []byte{0, 1, 0, 2},
  49. Protocol: firewall.ProtoTCP,
  50. }
  51. b, _ = h.Marshal()
  52. b = append(b, []byte{0, 3, 0, 4}...)
  53. err = newPacket(b, true, p)
  54. require.NoError(t, err)
  55. assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
  56. assert.Equal(t, netip.MustParseAddr("10.0.0.2"), p.LocalAddr)
  57. assert.Equal(t, netip.MustParseAddr("10.0.0.1"), p.RemoteAddr)
  58. assert.Equal(t, uint16(3), p.RemotePort)
  59. assert.Equal(t, uint16(4), p.LocalPort)
  60. assert.False(t, p.Fragment)
  61. // account for variable ip header length - outgoing
  62. h = ipv4.Header{
  63. Version: 1,
  64. Protocol: 2,
  65. Len: 100,
  66. Src: net.IPv4(10, 0, 0, 1),
  67. Dst: net.IPv4(10, 0, 0, 2),
  68. Options: []byte{0, 1, 0, 2},
  69. }
  70. b, _ = h.Marshal()
  71. b = append(b, []byte{0, 5, 0, 6}...)
  72. err = newPacket(b, false, p)
  73. require.NoError(t, err)
  74. assert.Equal(t, uint8(2), p.Protocol)
  75. assert.Equal(t, netip.MustParseAddr("10.0.0.1"), p.LocalAddr)
  76. assert.Equal(t, netip.MustParseAddr("10.0.0.2"), p.RemoteAddr)
  77. assert.Equal(t, uint16(6), p.RemotePort)
  78. assert.Equal(t, uint16(5), p.LocalPort)
  79. assert.False(t, p.Fragment)
  80. }
  81. func Test_newPacket_v6(t *testing.T) {
  82. p := &firewall.ParsedPacket{}
  83. // invalid ipv6
  84. ip := layers.IPv6{
  85. Version: 6,
  86. HopLimit: 128,
  87. SrcIP: net.IPv6linklocalallrouters,
  88. DstIP: net.IPv6linklocalallnodes,
  89. }
  90. buffer := gopacket.NewSerializeBuffer()
  91. opt := gopacket.SerializeOptions{
  92. ComputeChecksums: false,
  93. FixLengths: false,
  94. }
  95. err := gopacket.SerializeLayers(buffer, opt, &ip)
  96. require.NoError(t, err)
  97. err = newPacket(buffer.Bytes(), true, p)
  98. require.ErrorIs(t, err, ErrIPv6PacketTooShort)
  99. // A v6 packet with a hop-by-hop extension
  100. // ICMPv6 Payload (Echo Request)
  101. icmpLayer := layers.ICMPv6{
  102. TypeCode: layers.CreateICMPv6TypeCode(layers.ICMPv6TypeEchoRequest, 0),
  103. }
  104. // Hop-by-Hop Extension Header
  105. hopOption := layers.IPv6HopByHopOption{}
  106. hopOption.OptionData = []byte{0, 0, 0, 0}
  107. hopByHop := layers.IPv6HopByHop{}
  108. hopByHop.Options = append(hopByHop.Options, &hopOption)
  109. ip = layers.IPv6{
  110. Version: 6,
  111. HopLimit: 128,
  112. NextHeader: layers.IPProtocolIPv6Destination,
  113. SrcIP: net.IPv6linklocalallrouters,
  114. DstIP: net.IPv6linklocalallnodes,
  115. }
  116. buffer.Clear()
  117. err = gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{
  118. ComputeChecksums: false,
  119. FixLengths: true,
  120. }, &ip, &hopByHop, &icmpLayer)
  121. if err != nil {
  122. panic(err)
  123. }
  124. // Ensure buffer length checks during parsing with the next 2 tests.
  125. // A full IPv6 header and 1 byte in the first extension, but missing
  126. // the length byte.
  127. err = newPacket(buffer.Bytes()[:41], true, p)
  128. require.ErrorIs(t, err, ErrIPv6PacketTooShort)
  129. // A full IPv6 header plus 1 full extension, but only 1 byte of the
  130. // next layer, missing length byte
  131. err = newPacket(buffer.Bytes()[:49], true, p)
  132. require.ErrorIs(t, err, ErrIPv6PacketTooShort)
  133. err = nil
  134. // A good ICMP packet
  135. ip = layers.IPv6{
  136. Version: 6,
  137. NextHeader: layers.IPProtocolICMPv6,
  138. HopLimit: 128,
  139. SrcIP: net.IPv6linklocalallrouters,
  140. DstIP: net.IPv6linklocalallnodes,
  141. }
  142. icmp := layers.ICMPv6{
  143. TypeCode: layers.CreateICMPv6TypeCode(layers.ICMPv6TypeEchoRequest, 0),
  144. Checksum: 0x1234,
  145. }
  146. buffer.Clear()
  147. require.NoError(t, gopacket.SerializeLayers(buffer, opt, &ip, &icmp))
  148. require.Error(t, newPacket(buffer.Bytes(), true, p))
  149. buffer.Clear()
  150. echo := layers.ICMPv6Echo{
  151. Identifier: 0xabcd,
  152. SeqNumber: 1234,
  153. }
  154. require.NoError(t, gopacket.SerializeLayers(buffer, opt, &ip, &icmp, &echo))
  155. require.NoError(t, newPacket(buffer.Bytes(), true, p))
  156. assert.Equal(t, uint8(layers.IPProtocolICMPv6), p.Protocol)
  157. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
  158. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
  159. assert.Equal(t, uint16(0xabcd), p.RemotePort)
  160. assert.Equal(t, uint16(0), p.LocalPort)
  161. assert.False(t, p.Fragment)
  162. // A minimal 4 byte non-echo ICMPv6 message (type, code, checksum), no identifier to read
  163. icmpMin := make([]byte, ipv6.HeaderLen+4)
  164. copy(icmpMin, buffer.Bytes()[:ipv6.HeaderLen])
  165. icmpMin[6] = byte(layers.IPProtocolICMPv6)
  166. icmpMin[ipv6.HeaderLen] = 1 // type 1, destination unreachable, not echo
  167. err = newPacket(icmpMin, true, p)
  168. require.NoError(t, err)
  169. assert.Equal(t, uint8(layers.IPProtocolICMPv6), p.Protocol)
  170. assert.Equal(t, uint16(0), p.RemotePort)
  171. assert.Equal(t, uint16(0), p.LocalPort)
  172. assert.False(t, p.Fragment)
  173. // A good ESP packet
  174. b := buffer.Bytes()
  175. b[6] = byte(layers.IPProtocolESP)
  176. err = newPacket(b, true, p)
  177. require.NoError(t, err)
  178. assert.Equal(t, uint8(layers.IPProtocolESP), p.Protocol)
  179. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
  180. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
  181. assert.Equal(t, uint16(0), p.RemotePort)
  182. assert.Equal(t, uint16(0), p.LocalPort)
  183. assert.False(t, p.Fragment)
  184. // A good None packet
  185. b = buffer.Bytes()
  186. b[6] = byte(layers.IPProtocolNoNextHeader)
  187. err = newPacket(b, true, p)
  188. require.NoError(t, err)
  189. assert.Equal(t, uint8(layers.IPProtocolNoNextHeader), p.Protocol)
  190. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
  191. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
  192. assert.Equal(t, uint16(0), p.RemotePort)
  193. assert.Equal(t, uint16(0), p.LocalPort)
  194. assert.False(t, p.Fragment)
  195. // An unknown protocol packet, we don't dissect it so we fail closed on its true protocol with no ports
  196. b = buffer.Bytes()
  197. b[6] = 255 // 255 is a reserved protocol number
  198. err = newPacket(b, true, p)
  199. require.NoError(t, err)
  200. assert.Equal(t, uint8(255), p.Protocol)
  201. assert.Equal(t, uint16(0), p.RemotePort)
  202. assert.Equal(t, uint16(0), p.LocalPort)
  203. assert.False(t, p.Fragment)
  204. // A good UDP packet
  205. ip = layers.IPv6{
  206. Version: 6,
  207. NextHeader: firewall.ProtoUDP,
  208. HopLimit: 128,
  209. SrcIP: net.IPv6linklocalallrouters,
  210. DstIP: net.IPv6linklocalallnodes,
  211. }
  212. udp := layers.UDP{
  213. SrcPort: layers.UDPPort(36123),
  214. DstPort: layers.UDPPort(22),
  215. }
  216. err = udp.SetNetworkLayerForChecksum(&ip)
  217. require.NoError(t, err)
  218. buffer.Clear()
  219. err = gopacket.SerializeLayers(buffer, opt, &ip, &udp, gopacket.Payload([]byte{0xde, 0xad, 0xbe, 0xef}))
  220. if err != nil {
  221. panic(err)
  222. }
  223. b = buffer.Bytes()
  224. // incoming
  225. err = newPacket(b, true, p)
  226. require.NoError(t, err)
  227. assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
  228. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
  229. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
  230. assert.Equal(t, uint16(36123), p.RemotePort)
  231. assert.Equal(t, uint16(22), p.LocalPort)
  232. assert.False(t, p.Fragment)
  233. // outgoing
  234. err = newPacket(b, false, p)
  235. require.NoError(t, err)
  236. assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
  237. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
  238. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
  239. assert.Equal(t, uint16(36123), p.LocalPort)
  240. assert.Equal(t, uint16(22), p.RemotePort)
  241. assert.False(t, p.Fragment)
  242. // Too short UDP packet
  243. err = newPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
  244. require.ErrorIs(t, err, ErrIPv6PacketTooShort)
  245. // A good TCP packet
  246. b[6] = byte(layers.IPProtocolTCP)
  247. // incoming
  248. err = newPacket(b, true, p)
  249. require.NoError(t, err)
  250. assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
  251. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
  252. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
  253. assert.Equal(t, uint16(36123), p.RemotePort)
  254. assert.Equal(t, uint16(22), p.LocalPort)
  255. assert.False(t, p.Fragment)
  256. // outgoing
  257. err = newPacket(b, false, p)
  258. require.NoError(t, err)
  259. assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
  260. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
  261. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
  262. assert.Equal(t, uint16(36123), p.LocalPort)
  263. assert.Equal(t, uint16(22), p.RemotePort)
  264. assert.False(t, p.Fragment)
  265. // Too short TCP packet
  266. err = newPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
  267. require.ErrorIs(t, err, ErrIPv6PacketTooShort)
  268. // A good UDP packet with an AH header
  269. ip = layers.IPv6{
  270. Version: 6,
  271. NextHeader: layers.IPProtocolAH,
  272. HopLimit: 128,
  273. SrcIP: net.IPv6linklocalallrouters,
  274. DstIP: net.IPv6linklocalallnodes,
  275. }
  276. ah := layers.IPSecAH{
  277. AuthenticationData: []byte{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef},
  278. }
  279. ah.NextHeader = layers.IPProtocolUDP
  280. udpHeader := []byte{
  281. 0x8d, 0x1b, // Source port 36123
  282. 0x00, 0x16, // Destination port 22
  283. 0x00, 0x00, // Length
  284. 0x00, 0x00, // Checksum
  285. }
  286. buffer.Clear()
  287. err = ip.SerializeTo(buffer, opt)
  288. if err != nil {
  289. panic(err)
  290. }
  291. b = buffer.Bytes()
  292. ahb := serializeAH(&ah)
  293. b = append(b, ahb...)
  294. b = append(b, udpHeader...)
  295. err = newPacket(b, true, p)
  296. require.NoError(t, err)
  297. assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
  298. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
  299. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
  300. assert.Equal(t, uint16(36123), p.RemotePort)
  301. assert.Equal(t, uint16(22), p.LocalPort)
  302. assert.False(t, p.Fragment)
  303. // Ensure buffer bounds checking during processing, a truncated AH header can't reach the payload
  304. err = newPacket(b[:41], true, p)
  305. require.ErrorIs(t, err, ErrIPv6PacketTooShort)
  306. // Invalid AH header
  307. b = buffer.Bytes()
  308. err = newPacket(b, true, p)
  309. require.ErrorIs(t, err, ErrIPv6PacketTooShort)
  310. }
  311. func Test_newPacket_ipv6Fragment(t *testing.T) {
  312. p := &firewall.ParsedPacket{}
  313. ip := &layers.IPv6{
  314. Version: 6,
  315. NextHeader: layers.IPProtocolIPv6Fragment,
  316. HopLimit: 64,
  317. SrcIP: net.IPv6linklocalallrouters,
  318. DstIP: net.IPv6linklocalallnodes,
  319. }
  320. // First fragment
  321. fragHeader1 := []byte{
  322. uint8(layers.IPProtocolUDP), // Next Header (UDP)
  323. 0x00, // Reserved
  324. 0x00, // Fragment Offset high byte (0)
  325. 0x01, // Fragment Offset low byte & flags (M=1)
  326. 0x00, 0x00, 0x00, 0x01, // Identification
  327. }
  328. udpHeader := []byte{
  329. 0x8d, 0x1b, // Source port 36123
  330. 0x00, 0x16, // Destination port 22
  331. 0x00, 0x00, // Length
  332. 0x00, 0x00, // Checksum
  333. }
  334. buffer := gopacket.NewSerializeBuffer()
  335. opts := gopacket.SerializeOptions{
  336. ComputeChecksums: true,
  337. FixLengths: true,
  338. }
  339. err := ip.SerializeTo(buffer, opts)
  340. if err != nil {
  341. t.Fatal(err)
  342. }
  343. firstFrag := buffer.Bytes()
  344. firstFrag = append(firstFrag, fragHeader1...)
  345. firstFrag = append(firstFrag, udpHeader...)
  346. firstFrag = append(firstFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
  347. // Test first fragment incoming
  348. err = newPacket(firstFrag, true, p)
  349. require.NoError(t, err)
  350. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
  351. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
  352. assert.Equal(t, uint8(layers.IPProtocolUDP), p.Protocol)
  353. assert.Equal(t, uint16(36123), p.RemotePort)
  354. assert.Equal(t, uint16(22), p.LocalPort)
  355. assert.False(t, p.Fragment)
  356. // Test first fragment outgoing
  357. err = newPacket(firstFrag, false, p)
  358. require.NoError(t, err)
  359. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
  360. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
  361. assert.Equal(t, uint8(layers.IPProtocolUDP), p.Protocol)
  362. assert.Equal(t, uint16(36123), p.LocalPort)
  363. assert.Equal(t, uint16(22), p.RemotePort)
  364. assert.False(t, p.Fragment)
  365. // Second fragment
  366. fragHeader2 := []byte{
  367. uint8(layers.IPProtocolUDP), // Next Header (UDP)
  368. 0x00, // Reserved
  369. 0xb9, // Fragment Offset high byte (185)
  370. 0x01, // Fragment Offset low byte & flags (M=1)
  371. 0x00, 0x00, 0x00, 0x01, // Identification
  372. }
  373. buffer.Clear()
  374. err = ip.SerializeTo(buffer, opts)
  375. if err != nil {
  376. t.Fatal(err)
  377. }
  378. secondFrag := buffer.Bytes()
  379. secondFrag = append(secondFrag, fragHeader2...)
  380. secondFrag = append(secondFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
  381. // Test second fragment incoming
  382. err = newPacket(secondFrag, true, p)
  383. require.NoError(t, err)
  384. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
  385. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
  386. assert.Equal(t, uint8(layers.IPProtocolUDP), p.Protocol)
  387. assert.Equal(t, uint16(0), p.RemotePort)
  388. assert.Equal(t, uint16(0), p.LocalPort)
  389. assert.True(t, p.Fragment)
  390. // Test second fragment outgoing
  391. err = newPacket(secondFrag, false, p)
  392. require.NoError(t, err)
  393. assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
  394. assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
  395. assert.Equal(t, uint8(layers.IPProtocolUDP), p.Protocol)
  396. assert.Equal(t, uint16(0), p.LocalPort)
  397. assert.Equal(t, uint16(0), p.RemotePort)
  398. assert.True(t, p.Fragment)
  399. // Too short of a fragment packet
  400. err = newPacket(secondFrag[:len(secondFrag)-10], false, p)
  401. require.ErrorIs(t, err, ErrIPv6PacketTooShort)
  402. }
  403. func BenchmarkParseV6(b *testing.B) {
  404. // Regular UDP packet
  405. ip := &layers.IPv6{
  406. Version: 6,
  407. NextHeader: layers.IPProtocolUDP,
  408. HopLimit: 64,
  409. SrcIP: net.IPv6linklocalallrouters,
  410. DstIP: net.IPv6linklocalallnodes,
  411. }
  412. udp := &layers.UDP{
  413. SrcPort: layers.UDPPort(36123),
  414. DstPort: layers.UDPPort(22),
  415. }
  416. buffer := gopacket.NewSerializeBuffer()
  417. opts := gopacket.SerializeOptions{
  418. ComputeChecksums: false,
  419. FixLengths: true,
  420. }
  421. err := gopacket.SerializeLayers(buffer, opts, ip, udp)
  422. if err != nil {
  423. b.Fatal(err)
  424. }
  425. normalPacket := buffer.Bytes()
  426. // First Fragment packet
  427. ipFrag := &layers.IPv6{
  428. Version: 6,
  429. NextHeader: layers.IPProtocolIPv6Fragment,
  430. HopLimit: 64,
  431. SrcIP: net.IPv6linklocalallrouters,
  432. DstIP: net.IPv6linklocalallnodes,
  433. }
  434. fragHeader := []byte{
  435. uint8(layers.IPProtocolUDP), // Next Header (UDP)
  436. 0x00, // Reserved
  437. 0x00, // Fragment Offset high byte (0)
  438. 0x01, // Fragment Offset low byte & flags (M=1)
  439. 0x00, 0x00, 0x00, 0x01, // Identification
  440. }
  441. udpHeader := []byte{
  442. 0x8d, 0x7b, // Source port 36123
  443. 0x00, 0x16, // Destination port 22
  444. 0x00, 0x00, // Length
  445. 0x00, 0x00, // Checksum
  446. }
  447. buffer.Clear()
  448. err = ipFrag.SerializeTo(buffer, opts)
  449. if err != nil {
  450. b.Fatal(err)
  451. }
  452. firstFrag := buffer.Bytes()
  453. firstFrag = append(firstFrag, fragHeader...)
  454. firstFrag = append(firstFrag, udpHeader...)
  455. firstFrag = append(firstFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
  456. // Second Fragment packet
  457. fragHeader[2] = 0xb9 // offset 185
  458. buffer.Clear()
  459. err = ipFrag.SerializeTo(buffer, opts)
  460. if err != nil {
  461. b.Fatal(err)
  462. }
  463. secondFrag := buffer.Bytes()
  464. secondFrag = append(secondFrag, fragHeader...)
  465. secondFrag = append(secondFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
  466. fp := &firewall.ParsedPacket{}
  467. b.Run("Normal", func(b *testing.B) {
  468. for i := 0; i < b.N; i++ {
  469. if err = parseV6(normalPacket, true, fp); err != nil {
  470. b.Fatal(err)
  471. }
  472. }
  473. })
  474. b.Run("FirstFragment", func(b *testing.B) {
  475. for i := 0; i < b.N; i++ {
  476. if err = parseV6(firstFrag, true, fp); err != nil {
  477. b.Fatal(err)
  478. }
  479. }
  480. })
  481. b.Run("SecondFragment", func(b *testing.B) {
  482. for i := 0; i < b.N; i++ {
  483. if err = parseV6(secondFrag, true, fp); err != nil {
  484. b.Fatal(err)
  485. }
  486. }
  487. })
  488. // Evil packet
  489. evilPacket := &layers.IPv6{
  490. Version: 6,
  491. NextHeader: layers.IPProtocolIPv6HopByHop,
  492. HopLimit: 64,
  493. SrcIP: net.IPv6linklocalallrouters,
  494. DstIP: net.IPv6linklocalallnodes,
  495. }
  496. hopHeader := []byte{
  497. uint8(layers.IPProtocolIPv6HopByHop), // Next Header (HopByHop)
  498. 0x00, // Length
  499. 0x00, 0x00, // Options and padding
  500. 0x00, 0x00, 0x00, 0x00, // More options and padding
  501. }
  502. lastHopHeader := []byte{
  503. uint8(layers.IPProtocolUDP), // Next Header (UDP)
  504. 0x00, // Length
  505. 0x00, 0x00, // Options and padding
  506. 0x00, 0x00, 0x00, 0x00, // More options and padding
  507. }
  508. buffer.Clear()
  509. err = evilPacket.SerializeTo(buffer, opts)
  510. if err != nil {
  511. b.Fatal(err)
  512. }
  513. evilBytes := buffer.Bytes()
  514. for range 200 {
  515. evilBytes = append(evilBytes, hopHeader...)
  516. }
  517. evilBytes = append(evilBytes, lastHopHeader...)
  518. evilBytes = append(evilBytes, udpHeader...)
  519. evilBytes = append(evilBytes, []byte{0xde, 0xad, 0xbe, 0xef}...)
  520. b.Run("200 HopByHop headers", func(b *testing.B) {
  521. for i := 0; i < b.N; i++ {
  522. if err = parseV6(evilBytes, false, fp); err != nil {
  523. b.Fatal(err)
  524. }
  525. }
  526. })
  527. }
  528. // Ensure authentication data is a multiple of 8 bytes by padding if necessary
  529. func padAuthData(authData []byte) []byte {
  530. // Length of Authentication Data must be a multiple of 8 bytes
  531. paddingLength := (8 - (len(authData) % 8)) % 8 // Only pad if necessary
  532. if paddingLength > 0 {
  533. authData = append(authData, make([]byte, paddingLength)...)
  534. }
  535. return authData
  536. }
  537. // Custom function to manually serialize IPSecAH for both IPv4 and IPv6
  538. func serializeAH(ah *layers.IPSecAH) []byte {
  539. buf := new(bytes.Buffer)
  540. // Ensure Authentication Data is a multiple of 8 bytes
  541. ah.AuthenticationData = padAuthData(ah.AuthenticationData)
  542. // Calculate Payload Length (in 32-bit words, minus 2)
  543. payloadLen := uint8((12+len(ah.AuthenticationData))/4) - 2
  544. // Serialize fields
  545. if err := binary.Write(buf, binary.BigEndian, ah.NextHeader); err != nil {
  546. panic(err)
  547. }
  548. if err := binary.Write(buf, binary.BigEndian, payloadLen); err != nil {
  549. panic(err)
  550. }
  551. if err := binary.Write(buf, binary.BigEndian, ah.Reserved); err != nil {
  552. panic(err)
  553. }
  554. if err := binary.Write(buf, binary.BigEndian, ah.SPI); err != nil {
  555. panic(err)
  556. }
  557. if err := binary.Write(buf, binary.BigEndian, ah.Seq); err != nil {
  558. panic(err)
  559. }
  560. if len(ah.AuthenticationData) > 0 {
  561. if err := binary.Write(buf, binary.BigEndian, ah.AuthenticationData); err != nil {
  562. panic(err)
  563. }
  564. }
  565. return buf.Bytes()
  566. }
  567. // Test_newPacket_v6ExtHeaderOverflow is a regression test for the IPv6 extension-header
  568. // length uint8 overflow in parseV6. A Destination-Options header with HdrExtLen=255 spans
  569. // (255+1)*8 = 2048 bytes, so the real transport header sits at offset 2088. Before the fix
  570. // the advance was computed in uint8 and wrapped to 0 (then clamped to 8), so the firewall
  571. // read the transport header ~2KB too early from attacker-controlled option bytes while the
  572. // host OS parses the real header, a firewall port/proto bypass. The fix makes parseV6 land
  573. // on the same offset the host does.
  574. func Test_newPacket_v6ExtHeaderOverflow(t *testing.T) {
  575. p := &firewall.ParsedPacket{}
  576. const (
  577. hdrLen = 40 // IPv6 header
  578. extLen = 2048 // (255+1)*8, the true Destination-Options header size
  579. realTCPAt = hdrLen + extLen // 2088, where the host reads the transport header
  580. forgedTCPAt = hdrLen + 8 // 48, where the pre-fix wrapped+clamped walk landed
  581. )
  582. pkt := make([]byte, realTCPAt+4)
  583. pkt[0] = 0x60 // version 6
  584. pkt[6] = byte(layers.IPProtocolIPv6Destination) // NextHeader -> Destination Options
  585. pkt[40] = byte(firewall.ProtoTCP) // Dest-Options NextHeader -> TCP
  586. pkt[41] = 255 // HdrExtLen = 255
  587. // Forged transport header at the pre-fix (wrong) offset: dst port 443.
  588. binary.BigEndian.PutUint16(pkt[forgedTCPAt+2:forgedTCPAt+4], 443)
  589. // Real transport header at the offset the host actually uses: dst port 22.
  590. binary.BigEndian.PutUint16(pkt[realTCPAt+2:realTCPAt+4], 22)
  591. require.NoError(t, newPacket(pkt, true, p))
  592. assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
  593. // LocalPort is the destination port for incoming traffic. It must be the real port (22)
  594. // the host delivers to, not the forged 443 at the overflowed offset.
  595. assert.Equal(t, uint16(22), p.LocalPort, "firewall must parse the real transport header, not the overflowed offset")
  596. }
  597. // Test_newPacket_v6ExtHeaderPastBuffer is a regression test for an extension header whose declared length
  598. // advances the walk past the end of the packet. The upper layer protocol's header isn't actually present,
  599. // so parseV6 must drop the packet rather than classify it as the terminal protocol with no ports.
  600. func Test_newPacket_v6ExtHeaderPastBuffer(t *testing.T) {
  601. p := &firewall.ParsedPacket{}
  602. pkt := make([]byte, 48)
  603. pkt[0] = 0x60
  604. pkt[6] = byte(layers.IPProtocolIPv6Destination) // Destination Options
  605. pkt[7] = 64 // hop limit
  606. pkt[40] = byte(layers.IPProtocolSCTP) // Dest Options next header = SCTP
  607. pkt[41] = 255 // declared length (255+1)*8 = 2048, past the 48 byte buffer
  608. require.ErrorIs(t, newPacket(pkt, true, p), ErrIPv6PacketTooShort)
  609. }
  610. // Test_newPacket_v6ExtHeaderConfusion is a regression test for parseV6 walking any unrecognized
  611. // Next Header as if it were an ipv6 extension header. A real upper layer protocol Nebula doesn't
  612. // dissect (SCTP here) is not walkable, so applying the (len+1)*8 formula marched into the SCTP
  613. // payload and landed on a byte that looked like UDP, forging a protocol/port pair the firewall
  614. // would trust while the host delivered the real SCTP datagram. The fix fails closed: the packet
  615. // is classified as its true protocol with no ports, so it only matches an `any` rule.
  616. func Test_newPacket_v6ExtHeaderConfusion(t *testing.T) {
  617. p := &firewall.ParsedPacket{}
  618. pkt := make([]byte, 52)
  619. pkt[0] = 0x60 // version 6
  620. pkt[6] = byte(layers.IPProtocolSCTP) // NextHeader = SCTP, a real protocol, not an extension header
  621. pkt[7] = 64 // hop limit
  622. // Real SCTP header at offset 40. Pre-fix parseV6 walked SCTP as an extension header: byte 41 (0x00, the
  623. // low byte of the src port below) was read as the header length, giving next=(0+1)*8=8, which landed the
  624. // walk on byte 40 (0x11), misread as NextHeader=UDP, then bytes 48-51 as ports.
  625. binary.BigEndian.PutUint16(pkt[40:42], 0x1100) // SCTP src port; byte 40=0x11, byte 41=0x00
  626. binary.BigEndian.PutUint16(pkt[42:44], 445) // SCTP dst port, never read by parseV6
  627. binary.BigEndian.PutUint16(pkt[48:50], 53) // SCTP checksum bytes, pre-fix forged RemotePort
  628. binary.BigEndian.PutUint16(pkt[50:52], 53) // pre-fix forged LocalPort
  629. require.NoError(t, newPacket(pkt, true, p))
  630. assert.Equal(t, uint8(layers.IPProtocolSCTP), p.Protocol, "must classify as the true protocol, not the forged UDP")
  631. assert.Equal(t, uint16(0), p.RemotePort)
  632. assert.Equal(t, uint16(0), p.LocalPort)
  633. assert.False(t, p.Fragment)
  634. // Same confusion, but the unknown protocol sits after a real extension header. The HopByHop is walked
  635. // correctly, then SCTP must still fail closed instead of being walked into its own payload. Protocol is
  636. // the only assertion that discriminates the fix here, a regression that walked SCTP would misclassify it.
  637. chained := make([]byte, 60)
  638. chained[0] = 0x60 // version 6
  639. chained[6] = byte(layers.IPProtocolIPv6HopByHop) // NextHeader = HopByHop extension
  640. chained[7] = 64 // hop limit
  641. chained[40] = byte(layers.IPProtocolSCTP) // HopByHop NextHeader = SCTP
  642. chained[41] = 0 // HopByHop length 0 -> 8 bytes, SCTP begins at offset 48
  643. binary.BigEndian.PutUint16(chained[48:50], 0x1100) // SCTP src port, pre-fix forged NextHeader/length bait
  644. binary.BigEndian.PutUint16(chained[50:52], 445) // SCTP dst port, never read by parseV6
  645. require.NoError(t, newPacket(chained, true, p))
  646. assert.Equal(t, uint8(layers.IPProtocolSCTP), p.Protocol, "must fail closed on the unknown protocol after the extension header")
  647. assert.Equal(t, uint16(0), p.RemotePort)
  648. assert.Equal(t, uint16(0), p.LocalPort)
  649. assert.False(t, p.Fragment)
  650. }
  651. // Test_newPacket_parsedFields pins the ParsedPacket byproducts the RX
  652. // batcher consumes: IPHdrLen (the true L4 offset) and FragAny (any fragment
  653. // shape at all — unlike Packet.Fragment, which is port-oriented and true
  654. // only for non-first fragments).
  655. func Test_newPacket_parsedFields(t *testing.T) {
  656. p := &firewall.ParsedPacket{}
  657. // Plain IPv4 TCP, IHL 20: L4 offset 20, no fragment shape.
  658. v4 := make([]byte, 28)
  659. v4[0] = 0x45
  660. v4[9] = firewall.ProtoTCP
  661. binary.BigEndian.PutUint16(v4[6:8], 0x4000) // DF only
  662. require.NoError(t, newPacket(v4, true, p))
  663. assert.Equal(t, 20, p.IPHdrLen)
  664. assert.False(t, p.FragAny)
  665. assert.False(t, p.Fragment)
  666. // IPv4 first fragment (MF set, offset 0): the firewall can read ports
  667. // (Fragment false) but the coalescer must not touch it (FragAny true).
  668. ff := make([]byte, 28)
  669. ff[0] = 0x45
  670. ff[9] = firewall.ProtoUDP
  671. binary.BigEndian.PutUint16(ff[6:8], 0x2000) // MF, offset 0
  672. require.NoError(t, newPacket(ff, true, p))
  673. assert.False(t, p.Fragment)
  674. assert.True(t, p.FragAny)
  675. assert.Equal(t, 20, p.IPHdrLen)
  676. // IPv4 non-first fragment (nonzero offset): both flags set.
  677. nf := make([]byte, 28)
  678. nf[0] = 0x45
  679. nf[9] = firewall.ProtoUDP
  680. binary.BigEndian.PutUint16(nf[6:8], 0x00b9)
  681. require.NoError(t, newPacket(nf, true, p))
  682. assert.True(t, p.Fragment)
  683. assert.True(t, p.FragAny)
  684. // IPv4 with options (IHL 24): IPHdrLen tracks the real L4 offset.
  685. opts := make([]byte, 32)
  686. opts[0] = 0x46
  687. opts[9] = firewall.ProtoTCP
  688. binary.BigEndian.PutUint16(opts[6:8], 0x4000)
  689. require.NoError(t, newPacket(opts, true, p))
  690. assert.Equal(t, 24, p.IPHdrLen)
  691. assert.False(t, p.FragAny)
  692. // Plain IPv6 TCP: L4 at 40.
  693. v6 := make([]byte, 60)
  694. v6[0] = 0x60
  695. v6[6] = firewall.ProtoTCP
  696. require.NoError(t, newPacket(v6, true, p))
  697. assert.Equal(t, 40, p.IPHdrLen)
  698. assert.False(t, p.FragAny)
  699. // IPv6 hop-by-hop then TCP: IPHdrLen lands past the extension header.
  700. hbh := make([]byte, 60)
  701. hbh[0] = 0x60
  702. hbh[6] = 0 // hop-by-hop
  703. hbh[40] = firewall.ProtoTCP
  704. hbh[41] = 0 // HdrExtLen 0 -> 8-byte header
  705. require.NoError(t, newPacket(hbh, true, p))
  706. assert.Equal(t, 48, p.IPHdrLen)
  707. assert.False(t, p.FragAny)
  708. // IPv6 first fragment: terminal proto resolved, FragAny set, Fragment not.
  709. f6 := make([]byte, 60)
  710. f6[0] = 0x60
  711. f6[6] = 44 // fragment extension header
  712. f6[40] = firewall.ProtoUDP
  713. require.NoError(t, newPacket(f6, true, p))
  714. assert.True(t, p.FragAny)
  715. assert.False(t, p.Fragment)
  716. assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
  717. // IPv6 non-first fragment: both set, walk stops at the fragment header.
  718. f6n := make([]byte, 60)
  719. f6n[0] = 0x60
  720. f6n[6] = 44
  721. f6n[40] = firewall.ProtoUDP
  722. binary.BigEndian.PutUint16(f6n[42:44], 0x0008)
  723. require.NoError(t, newPacket(f6n, true, p))
  724. assert.True(t, p.Fragment)
  725. assert.True(t, p.FragAny)
  726. }