@@ -2,24 +2,15 @@ package nebula
22
33import (
44 "context"
5- "encoding/binary"
65 "errors"
76 "log/slog"
87 "net/netip"
98 "time"
109
11- "github.com/google/gopacket/layers"
12- "golang.org/x/net/ipv6"
13-
1410 "github.com/slackhq/nebula/firewall"
1511 "github.com/slackhq/nebula/header"
1612 "github.com/slackhq/nebula/overlay/batch"
1713 "github.com/slackhq/nebula/udp"
18- "golang.org/x/net/ipv4"
19- )
20-
21- const (
22- minFwPacketLen = 4
2314)
2415
2516func (f * Interface ) readOutsidePackets (via ViaSender , out []byte , packet []byte , h * header.H , fwPacket * firewall.Packet , parsedRx * batch.RxParsed , lhf * LightHouseHandler , nb []byte , q int , localCache firewall.ConntrackCache , meta udp.RxMeta ) {
@@ -319,196 +310,17 @@ func (f *Interface) handleEncrypted(ci *ConnectionState, via ViaSender, h *heade
319310 return true
320311}
321312
322- var (
323- ErrPacketTooShort = errors .New ("packet is too short" )
324- ErrUnknownIPVersion = errors .New ("packet is an unknown ip version" )
325- ErrIPv4InvalidHeaderLength = errors .New ("invalid ipv4 header length" )
326- ErrIPv4PacketTooShort = errors .New ("ipv4 packet is too short" )
327- ErrIPv6PacketTooShort = errors .New ("ipv6 packet is too short" )
328- ErrIPv6CouldNotFindPayload = errors .New ("could not find payload in ipv6 packet" )
329- )
330-
331- // newPacket validates and parses the interesting bits for the firewall out of the ip and sub protocol headers
332- // newPacket parses data into a fully-hydrated firewall.Packet — kept as a
333- // thin wrapper around newPacketKey + Hydrate so there's one source of
334- // parse logic. Callers that don't need the netip.Addr-rich form (e.g.
335- // conntrack-only paths) should use newPacketKey directly.
313+ // newPacket validates an IP packet and fills fp via the canonical batch
314+ // parser. Tests use this entry point directly; production paths use
315+ // batch.ParsePacket and Hydrate themselves so they can keep both the dense
316+ // PacketKey (for conntrack lookup) and the Packet form (for rule matching)
317+ // without an extra round-trip.
336318func newPacket (data []byte , incoming bool , fp * firewall.Packet ) error {
337- key , err := newPacketKey ( data , incoming )
338- if err != nil {
319+ var parsed batch. RxParsed
320+ if err := batch . ParsePacket ( data , incoming , & parsed ); err != nil {
339321 return err
340322 }
341- key .Hydrate (fp )
342- return nil
343- }
344-
345- // newPacketKey parses data into a dense firewall.PacketKey. Hot path: no
346- // netip.Addr construction, no unique.Handle interning. Caller decides
347- // whether to also Hydrate to a Packet (for rule matching) or pass the key
348- // straight to conntrack.
349- func newPacketKey (data []byte , incoming bool ) (firewall.PacketKey , error ) {
350- var k firewall.PacketKey
351- if len (data ) < 1 {
352- return k , ErrPacketTooShort
353- }
354- switch int ((data [0 ] >> 4 ) & 0x0f ) {
355- case ipv4 .Version :
356- return k , parseV4Key (data , incoming , & k )
357- case ipv6 .Version :
358- k .IsV6 = true
359- return k , parseV6Key (data , incoming , & k )
360- }
361- return k , ErrUnknownIPVersion
362- }
363-
364- func parseV6Key (data []byte , incoming bool , k * firewall.PacketKey ) error {
365- dataLen := len (data )
366- if dataLen < ipv6 .HeaderLen {
367- return ErrIPv6PacketTooShort
368- }
369-
370- if incoming {
371- copy (k .RemoteAddr [:], data [8 :24 ])
372- copy (k .LocalAddr [:], data [24 :40 ])
373- } else {
374- copy (k .LocalAddr [:], data [8 :24 ])
375- copy (k .RemoteAddr [:], data [24 :40 ])
376- }
377-
378- protoAt := 6
379- offset := ipv6 .HeaderLen
380- next := 0
381- for {
382- if protoAt >= dataLen {
383- break
384- }
385- proto := layers .IPProtocol (data [protoAt ])
386-
387- switch proto {
388- case layers .IPProtocolESP , layers .IPProtocolNoNextHeader :
389- k .Protocol = uint8 (proto )
390- k .RemotePort = 0
391- k .LocalPort = 0
392- k .Fragment = false
393- return nil
394-
395- case layers .IPProtocolICMPv6 :
396- if dataLen < offset + 6 {
397- return ErrIPv6PacketTooShort
398- }
399- k .Protocol = uint8 (proto )
400- k .LocalPort = 0
401- switch data [offset + 1 ] {
402- case layers .ICMPv6TypeEchoRequest , layers .ICMPv6TypeEchoReply :
403- k .RemotePort = binary .BigEndian .Uint16 (data [offset + 4 : offset + 6 ])
404- default :
405- k .RemotePort = 0
406- }
407- k .Fragment = false
408- return nil
409-
410- case layers .IPProtocolTCP , layers .IPProtocolUDP :
411- if dataLen < offset + 4 {
412- return ErrIPv6PacketTooShort
413- }
414- k .Protocol = uint8 (proto )
415- if incoming {
416- k .RemotePort = binary .BigEndian .Uint16 (data [offset : offset + 2 ])
417- k .LocalPort = binary .BigEndian .Uint16 (data [offset + 2 : offset + 4 ])
418- } else {
419- k .LocalPort = binary .BigEndian .Uint16 (data [offset : offset + 2 ])
420- k .RemotePort = binary .BigEndian .Uint16 (data [offset + 2 : offset + 4 ])
421- }
422- k .Fragment = false
423- return nil
424-
425- case layers .IPProtocolIPv6Fragment :
426- if dataLen < offset + 8 {
427- return ErrIPv6PacketTooShort
428- }
429- fragmentOffset := binary .BigEndian .Uint16 (data [offset + 2 :offset + 4 ]) &^ uint16 (0x7 )
430- if fragmentOffset != 0 {
431- k .Protocol = data [offset ]
432- k .Fragment = true
433- k .RemotePort = 0
434- k .LocalPort = 0
435- return nil
436- }
437- next = 8
438-
439- case layers .IPProtocolAH :
440- if dataLen <= offset + 1 {
441- break
442- }
443- next = int (data [offset + 1 ]+ 2 ) << 2
444-
445- default :
446- if dataLen <= offset + 1 {
447- break
448- }
449- next = int (data [offset + 1 ]+ 1 ) << 3
450- }
451-
452- if next <= 0 {
453- next = 8
454- }
455- protoAt = offset
456- offset = offset + next
457- }
458-
459- return ErrIPv6CouldNotFindPayload
460- }
461-
462- func parseV4Key (data []byte , incoming bool , k * firewall.PacketKey ) error {
463- if len (data ) < ipv4 .HeaderLen {
464- return ErrIPv4PacketTooShort
465- }
466- ihl := int (data [0 ]& 0x0f ) << 2
467- if ihl < ipv4 .HeaderLen {
468- return ErrIPv4InvalidHeaderLength
469- }
470-
471- flagsfrags := binary .BigEndian .Uint16 (data [6 :8 ])
472- k .Fragment = (flagsfrags & 0x1FFF ) != 0
473- k .Protocol = data [9 ]
474-
475- minLen := ihl
476- if ! k .Fragment {
477- if k .Protocol == firewall .ProtoICMP {
478- minLen += minFwPacketLen + 2
479- } else {
480- minLen += minFwPacketLen
481- }
482- }
483- if len (data ) < minLen {
484- return ErrIPv4InvalidHeaderLength
485- }
486-
487- // Dense form: v4 in low 4 bytes, rest zero. Matches the coalescer's
488- // flowKey convention so the two stay byte-identical for the same flow.
489- if incoming {
490- copy (k .RemoteAddr [:4 ], data [12 :16 ])
491- copy (k .LocalAddr [:4 ], data [16 :20 ])
492- } else {
493- copy (k .LocalAddr [:4 ], data [12 :16 ])
494- copy (k .RemoteAddr [:4 ], data [16 :20 ])
495- }
496-
497- switch {
498- case k .Fragment :
499- k .RemotePort = 0
500- k .LocalPort = 0
501- case k .Protocol == firewall .ProtoICMP :
502- k .RemotePort = binary .BigEndian .Uint16 (data [ihl + 4 : ihl + 6 ])
503- k .LocalPort = 0
504- case incoming :
505- k .RemotePort = binary .BigEndian .Uint16 (data [ihl : ihl + 2 ])
506- k .LocalPort = binary .BigEndian .Uint16 (data [ihl + 2 : ihl + 4 ])
507- default :
508- k .LocalPort = binary .BigEndian .Uint16 (data [ihl : ihl + 2 ])
509- k .RemotePort = binary .BigEndian .Uint16 (data [ihl + 2 : ihl + 4 ])
510- }
511-
323+ parsed .Key .Hydrate (fp )
512324 return nil
513325}
514326
@@ -598,7 +410,7 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
598410 // firewall.Drop's fast path uses Key alone and only hydrates fwPacket
599411 // from Key on the slow path.
600412 * fwPacket = firewall.Packet {}
601- err = batch .ParseInbound (out , parsedRx )
413+ err = batch .ParsePacket (out , true , parsedRx )
602414 if err != nil {
603415 hostinfo .logger (f .l ).Warn ("Error while validating inbound packet" ,
604416 "error" , err ,
0 commit comments