Skip to content

Commit f9fcb74

Browse files
committed
parse?
1 parent 80b07cf commit f9fcb74

9 files changed

Lines changed: 150 additions & 295 deletions

File tree

firewall.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,8 @@ func (f *Firewall) Drop(key firewall.PacketKey, fp *firewall.Packet, incoming bo
437437

438438
// Conntrack miss → rule matching needs the rich Packet form. Hydrate
439439
// from the key if the caller passed a zero-valued fp (the inbound path
440-
// after ParseInbound). Outbound callers fill fp via newPacket and skip
441-
// this hop.
440+
// after batch.ParsePacket). Outbound callers Hydrate themselves and
441+
// skip this hop.
442442
if !fp.LocalAddr.IsValid() {
443443
key.Hydrate(fp)
444444
}

firewall/packet.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ type Packet struct {
5050
Fragment bool
5151
}
5252

53-
// Key derives a PacketKey from a populated Packet. Used by the outgoing
54-
// path (inside.go) which still parses into a full Packet via newPacket
55-
// before the firewall check; the inbound path skips this hop entirely by
56-
// having its parser write straight into the PacketKey.
53+
// Key derives a PacketKey from a populated Packet. Used by the few code
54+
// paths that have a Packet but no Key in hand (e.g. tests). Both inbound
55+
// and outbound production parsers write straight into a PacketKey via
56+
// batch.ParsePacket, so this function is rarely on the hot path.
5757
func (fp *Packet) Key() PacketKey {
5858
k := PacketKey{
5959
Protocol: fp.Protocol,

inside.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ func (f *Interface) consumeInsidePacket(pkt tio.Packet, fwPacket *firewall.Packe
2525
//
2626
// pkt.Bytes is either one IP datagram (GSO zero) or a TSO/USO
2727
// superpacket. In both cases the L3+L4 headers at the start describe
28-
// the same 5-tuple every segment will share, so a single newPacket /
28+
// the same 5-tuple every segment will share, so a single parse +
2929
// firewall check covers the whole superpacket.
3030
packet := pkt.Bytes
31-
key, err := newPacketKey(packet, false)
32-
if err != nil {
31+
var parsed batch.RxParsed
32+
if err := batch.ParsePacket(packet, false, &parsed); err != nil {
3333
if f.l.Enabled(context.Background(), slog.LevelDebug) {
3434
f.l.Debug("Error while validating outbound packet",
3535
"packet", packet,
@@ -39,7 +39,7 @@ func (f *Interface) consumeInsidePacket(pkt tio.Packet, fwPacket *firewall.Packe
3939
return
4040
}
4141

42-
key.Hydrate(fwPacket)
42+
parsed.Key.Hydrate(fwPacket)
4343

4444
// Ignore local broadcast packets
4545
if f.dropLocalBroadcast {
@@ -107,7 +107,7 @@ func (f *Interface) consumeInsidePacket(pkt tio.Packet, fwPacket *firewall.Packe
107107
return
108108
}
109109

110-
dropReason := f.firewall.Drop(key, fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)
110+
dropReason := f.firewall.Drop(parsed.Key, fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)
111111
if dropReason == nil {
112112
f.sendInsideMessage(hostinfo, pkt, nb, sendBatch, rejectBuf, q)
113113
} else {
@@ -394,16 +394,16 @@ func (f *Interface) getOrHandshakeConsiderRouting(fwPacket *firewall.Packet, cac
394394
}
395395

396396
func (f *Interface) sendMessageNow(t header.MessageType, st header.MessageSubType, hostinfo *HostInfo, p, nb, out []byte) {
397-
key, err := newPacketKey(p, false)
398-
if err != nil {
397+
var parsed batch.RxParsed
398+
if err := batch.ParsePacket(p, false, &parsed); err != nil {
399399
f.l.Warn("error while parsing outgoing packet for firewall check", "error", err)
400400
return
401401
}
402402
fp := &firewall.Packet{}
403-
key.Hydrate(fp)
403+
parsed.Key.Hydrate(fp)
404404

405405
// check if packet is in outbound fw rules
406-
dropReason := f.firewall.Drop(key, fp, false, hostinfo, f.pki.GetCAPool(), nil)
406+
dropReason := f.firewall.Drop(parsed.Key, fp, false, hostinfo, f.pki.GetCAPool(), nil)
407407
if dropReason != nil {
408408
if f.l.Enabled(context.Background(), slog.LevelDebug) {
409409
f.l.Debug("dropping cached packet",

outside.go

Lines changed: 9 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,15 @@ package nebula
22

33
import (
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

2516
func (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.
336318
func 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

Comments
 (0)