Skip to content

Commit

Permalink
More correct ipv6 header parsing (#1323)
Browse files Browse the repository at this point in the history
  • Loading branch information
nbrownus authored Feb 4, 2025
1 parent e4daed3 commit fbff6a1
Show file tree
Hide file tree
Showing 2 changed files with 545 additions and 81 deletions.
103 changes: 60 additions & 43 deletions outside.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package nebula
import (
"encoding/binary"
"errors"
"fmt"
"net/netip"
"time"

Expand Down Expand Up @@ -271,10 +270,19 @@ func (f *Interface) handleEncrypted(ci *ConnectionState, addr netip.AddrPort, h
return true
}

var (
ErrPacketTooShort = errors.New("packet is too short")
ErrUnknownIPVersion = errors.New("packet is an unknown ip version")
ErrIPv4InvalidHeaderLength = errors.New("invalid ipv4 header length")
ErrIPv4PacketTooShort = errors.New("ipv4 packet is too short")
ErrIPv6PacketTooShort = errors.New("ipv6 packet is too short")
ErrIPv6CouldNotFindPayload = errors.New("could not find payload in ipv6 packet")
)

// newPacket validates and parses the interesting bits for the firewall out of the ip and sub protocol headers
func newPacket(data []byte, incoming bool, fp *firewall.Packet) error {
if len(data) < 1 {
return errors.New("packet too short")
return ErrPacketTooShort
}

version := int((data[0] >> 4) & 0x0f)
Expand All @@ -284,13 +292,13 @@ func newPacket(data []byte, incoming bool, fp *firewall.Packet) error {
case ipv6.Version:
return parseV6(data, incoming, fp)
}
return fmt.Errorf("packet is an unknown ip version: %v", version)
return ErrUnknownIPVersion
}

func parseV6(data []byte, incoming bool, fp *firewall.Packet) error {
dataLen := len(data)
if dataLen < ipv6.HeaderLen {
return fmt.Errorf("ipv6 packet is less than %v bytes", ipv4.HeaderLen)
return ErrIPv6PacketTooShort
}

if incoming {
Expand All @@ -301,29 +309,29 @@ func parseV6(data []byte, incoming bool, fp *firewall.Packet) error {
fp.RemoteAddr, _ = netip.AddrFromSlice(data[24:40])
}

//TODO: CERT-V2 whats a reasonable number of extension headers to attempt to parse?
//https://www.ietf.org/archive/id/draft-ietf-6man-eh-limits-00.html
protoAt := 6
offset := 40
for i := 0; i < 24; i++ {
protoAt := 6 // NextHeader is at 6 bytes into the ipv6 header
offset := ipv6.HeaderLen // Start at the end of the ipv6 header
next := 0
for {
if dataLen < offset {
break
}

proto := layers.IPProtocol(data[protoAt])
//fmt.Println(proto, protoAt)
switch proto {
case layers.IPProtocolICMPv6:
case layers.IPProtocolICMPv6, layers.IPProtocolESP, layers.IPProtocolNoNextHeader:
fp.Protocol = uint8(proto)
fp.RemotePort = 0
fp.LocalPort = 0
fp.Fragment = false
return nil

case layers.IPProtocolTCP:
case layers.IPProtocolTCP, layers.IPProtocolUDP:
if dataLen < offset+4 {
return fmt.Errorf("ipv6 packet was too small")
return ErrIPv6PacketTooShort
}

fp.Protocol = uint8(proto)
if incoming {
fp.RemotePort = binary.BigEndian.Uint16(data[offset : offset+2])
Expand All @@ -332,62 +340,71 @@ func parseV6(data []byte, incoming bool, fp *firewall.Packet) error {
fp.LocalPort = binary.BigEndian.Uint16(data[offset : offset+2])
fp.RemotePort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
}

fp.Fragment = false
return nil

case layers.IPProtocolUDP:
if dataLen < offset+4 {
return fmt.Errorf("ipv6 packet was too small")
case layers.IPProtocolIPv6Fragment:
// Fragment header is 8 bytes, need at least offset+4 to read the offset field
if dataLen < offset+8 {
return ErrIPv6PacketTooShort
}
fp.Protocol = uint8(proto)
if incoming {
fp.RemotePort = binary.BigEndian.Uint16(data[offset : offset+2])
fp.LocalPort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
} else {
fp.LocalPort = binary.BigEndian.Uint16(data[offset : offset+2])
fp.RemotePort = binary.BigEndian.Uint16(data[offset+2 : offset+4])

// Check if this is the first fragment
fragmentOffset := binary.BigEndian.Uint16(data[offset+2:offset+4]) &^ uint16(0x7) // Remove the reserved and M flag bits
if fragmentOffset != 0 {
// Non-first fragment, use what we have now and stop processing
fp.Protocol = data[offset]
fp.Fragment = true
fp.RemotePort = 0
fp.LocalPort = 0
return nil
}
fp.Fragment = false
return nil

case layers.IPProtocolIPv6Fragment:
//TODO: CERT-V2 can we determine the protocol?
fp.RemotePort = 0
fp.LocalPort = 0
fp.Fragment = true
return nil
// The next loop should be the transport layer since we are the first fragment
next = 8 // Fragment headers are always 8 bytes

default:
case layers.IPProtocolAH:
// Auth headers, used by IPSec, have a different meaning for header length
if dataLen < offset+1 {
break
}

next := int(data[offset+1]) * 8
if next == 0 {
// each extension is at least 8 bytes
next = 8
next = int(data[offset+1]+2) << 2

default:
// Normal ipv6 header length processing
if dataLen < offset+1 {
break
}

protoAt = offset
offset = offset + next
next = int(data[offset+1]+1) << 3
}

if next <= 0 {
// Safety check, each ipv6 header has to be at least 8 bytes
next = 8
}

protoAt = offset
offset = offset + next
}

return fmt.Errorf("could not find payload in ipv6 packet")
return ErrIPv6CouldNotFindPayload
}

func parseV4(data []byte, incoming bool, fp *firewall.Packet) error {
// Do we at least have an ipv4 header worth of data?
if len(data) < ipv4.HeaderLen {
return fmt.Errorf("ipv4 packet is less than %v bytes", ipv4.HeaderLen)
return ErrIPv4PacketTooShort
}

// Adjust our start position based on the advertised ip header length
ihl := int(data[0]&0x0f) << 2

// Well formed ip header length?
// Well-formed ip header length?
if ihl < ipv4.HeaderLen {
return fmt.Errorf("ipv4 packet had an invalid header length: %v", ihl)
return ErrIPv4InvalidHeaderLength
}

// Check if this is the second or further fragment of a fragmented packet.
Expand All @@ -403,7 +420,7 @@ func parseV4(data []byte, incoming bool, fp *firewall.Packet) error {
minLen += minFwPacketLen
}
if len(data) < minLen {
return fmt.Errorf("ipv4 packet is less than %v bytes, ip header len: %v", minLen, ihl)
return ErrIPv4InvalidHeaderLength
}

// Firewall packets are locally oriented
Expand Down Expand Up @@ -501,7 +518,7 @@ func (f *Interface) sendRecvError(endpoint netip.AddrPort, index uint32) {
f.messageMetrics.Tx(header.RecvError, 0, 1)

b := header.Encode(make([]byte, header.Len), header.Version, header.RecvError, 0, index, 0)
f.outside.WriteTo(b, endpoint)
_ = f.outside.WriteTo(b, endpoint)
if f.l.Level >= logrus.DebugLevel {
f.l.WithField("index", index).
WithField("udpAddr", endpoint).
Expand Down
Loading

0 comments on commit fbff6a1

Please sign in to comment.