mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 16:57:02 +02:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be58a866d9 | |||
| ed960c7fb8 | |||
| f573e8a266 |
+15
-1
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.10.3] - 2026-02-06
|
||||
|
||||
### Security
|
||||
|
||||
- Fix an issue where blocklist bypass is possible when using curve P256 since the signature can have 2 valid representations.
|
||||
Both fingerprint representations will be tested against the blocklist.
|
||||
Any newly issued P256 based certificates will have their signature clamped to the low-s form.
|
||||
Nebula will assert the low-s signature form when validating certificates in a future version. [GHSA-69x3-g4r3-p962](https://github.com/slackhq/nebula/security/advisories/GHSA-69x3-g4r3-p962)
|
||||
|
||||
### Changed
|
||||
|
||||
- Improve error reporting if nebula fails to start due to a tun device naming issue. (#1588)
|
||||
|
||||
## [1.10.2] - 2026-01-21
|
||||
|
||||
### Fixed
|
||||
@@ -775,7 +788,8 @@ created.)
|
||||
|
||||
- Initial public release.
|
||||
|
||||
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.2...HEAD
|
||||
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.3...HEAD
|
||||
[1.10.3]: https://github.com/slackhq/nebula/releases/tag/v1.10.3
|
||||
[1.10.2]: https://github.com/slackhq/nebula/releases/tag/v1.10.2
|
||||
[1.10.1]: https://github.com/slackhq/nebula/releases/tag/v1.10.1
|
||||
[1.10.0]: https://github.com/slackhq/nebula/releases/tag/v1.10.0
|
||||
|
||||
@@ -141,10 +141,23 @@ func (ncp *CAPool) VerifyCertificate(now time.Time, c Certificate) (*CachedCerti
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pre nebula v1.10.3 could generate signatures in either high or low s form and validation
|
||||
// of signatures allowed for either. Nebula v1.10.3 and beyond clamps signature generation to low-s form
|
||||
// but validation still allows for either. Since a change in the signature bytes affects the fingerprint, we
|
||||
// need to test both forms until such a time comes that we enforce low-s form on signature validation.
|
||||
fp2, err := CalculateAlternateFingerprint(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not calculate alternate fingerprint to verify: %w", err)
|
||||
}
|
||||
if fp2 != "" && ncp.IsBlocklisted(fp2) {
|
||||
return nil, ErrBlockListed
|
||||
}
|
||||
|
||||
cc := CachedCertificate{
|
||||
Certificate: c,
|
||||
InvertedGroups: make(map[string]struct{}),
|
||||
Fingerprint: fp,
|
||||
fingerprint2: fp2,
|
||||
signerFingerprint: signer.Fingerprint,
|
||||
}
|
||||
|
||||
@@ -158,6 +171,11 @@ func (ncp *CAPool) VerifyCertificate(now time.Time, c Certificate) (*CachedCerti
|
||||
// VerifyCachedCertificate is the same as VerifyCertificate other than it operates on a pre-verified structure and
|
||||
// is a cheaper operation to perform as a result.
|
||||
func (ncp *CAPool) VerifyCachedCertificate(now time.Time, c *CachedCertificate) error {
|
||||
// Check any available alternate fingerprint forms for this certificate, re P256 high-s/low-s
|
||||
if c.fingerprint2 != "" && ncp.IsBlocklisted(c.fingerprint2) {
|
||||
return ErrBlockListed
|
||||
}
|
||||
|
||||
_, err := ncp.verify(c.Certificate, now, c.Fingerprint, c.signerFingerprint)
|
||||
return err
|
||||
}
|
||||
|
||||
+43
-4
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/slackhq/nebula/cert/p256"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -170,6 +171,15 @@ func TestCertificateV1_VerifyP256(t *testing.T) {
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
// Create a copy of the cert and swap to the alternate form for the signature
|
||||
nc := c.Copy()
|
||||
b, err := p256.Swap(c.Signature())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, nc.(*certificateV1).setSignature(b))
|
||||
|
||||
_, err = caPool.VerifyCertificate(time.Now(), nc)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
caPool.ResetCertBlocklist()
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
require.NoError(t, err)
|
||||
@@ -187,7 +197,7 @@ func TestCertificateV1_VerifyP256(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
caPool = NewCAPool()
|
||||
b, err := caPool.AddCAFromPEM(caPem)
|
||||
b, err = caPool.AddCAFromPEM(caPem)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, b)
|
||||
|
||||
@@ -196,7 +206,17 @@ func TestCertificateV1_VerifyP256(t *testing.T) {
|
||||
})
|
||||
|
||||
c, _, _, _ = NewTestCert(Version1, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
cc, err := caPool.VerifyCertificate(time.Now(), c)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Reset the blocklist and block the alternate form fingerprint
|
||||
caPool.ResetCertBlocklist()
|
||||
caPool.BlocklistFingerprint(cc.fingerprint2)
|
||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
caPool.ResetCertBlocklist()
|
||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -394,6 +414,15 @@ func TestCertificateV2_VerifyP256(t *testing.T) {
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
// Create a copy of the cert and swap to the alternate form for the signature
|
||||
nc := c.Copy()
|
||||
b, err := p256.Swap(c.Signature())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, nc.(*certificateV2).setSignature(b))
|
||||
|
||||
_, err = caPool.VerifyCertificate(time.Now(), nc)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
caPool.ResetCertBlocklist()
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
require.NoError(t, err)
|
||||
@@ -411,7 +440,7 @@ func TestCertificateV2_VerifyP256(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
caPool = NewCAPool()
|
||||
b, err := caPool.AddCAFromPEM(caPem)
|
||||
b, err = caPool.AddCAFromPEM(caPem)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, b)
|
||||
|
||||
@@ -420,7 +449,17 @@ func TestCertificateV2_VerifyP256(t *testing.T) {
|
||||
})
|
||||
|
||||
c, _, _, _ = NewTestCert(Version2, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
cc, err := caPool.VerifyCertificate(time.Now(), c)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Reset the blocklist and block the alternate form fingerprint
|
||||
caPool.ResetCertBlocklist()
|
||||
caPool.BlocklistFingerprint(cc.fingerprint2)
|
||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
caPool.ResetCertBlocklist()
|
||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/slackhq/nebula/cert/p256"
|
||||
)
|
||||
|
||||
type Version uint8
|
||||
@@ -110,6 +112,9 @@ type CachedCertificate struct {
|
||||
InvertedGroups map[string]struct{}
|
||||
Fingerprint string
|
||||
signerFingerprint string
|
||||
|
||||
// A place to store a 2nd fingerprint if the certificate could have one, such as with P256
|
||||
fingerprint2 string
|
||||
}
|
||||
|
||||
func (cc *CachedCertificate) String() string {
|
||||
@@ -152,3 +157,31 @@ func Recombine(v Version, rawCertBytes, publicKey []byte, curve Curve) (Certific
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// CalculateAlternateFingerprint calculates a 2nd fingerprint representation for P256 certificates
|
||||
// CAPool blocklist testing through `VerifyCertificate` and `VerifyCachedCertificate` automatically performs this step.
|
||||
func CalculateAlternateFingerprint(c Certificate) (string, error) {
|
||||
if c.Curve() != Curve_P256 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
nc := c.Copy()
|
||||
b, err := p256.Swap(nc.Signature())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch v := nc.(type) {
|
||||
case *certificateV1:
|
||||
err = v.setSignature(b)
|
||||
case *certificateV2:
|
||||
err = v.setSignature(b)
|
||||
default:
|
||||
return "", ErrUnknownVersion
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return nc.Fingerprint()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package p256
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"filippo.io/bigmod"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
"golang.org/x/crypto/cryptobyte/asn1"
|
||||
)
|
||||
|
||||
var halfN = new(big.Int).Rsh(elliptic.P256().Params().N, 1)
|
||||
var nMod *bigmod.Modulus
|
||||
|
||||
func init() {
|
||||
n, err := bigmod.NewModulus(elliptic.P256().Params().N.Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
nMod = n
|
||||
}
|
||||
|
||||
func IsNormalized(sig []byte) (bool, error) {
|
||||
r, s, err := parseSignature(sig)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return checkLowS(r, s), nil
|
||||
}
|
||||
|
||||
func checkLowS(_, s []byte) bool {
|
||||
bigS := new(big.Int).SetBytes(s)
|
||||
// Check if S <= (N/2), because we want to include the midpoint in the set of low-s
|
||||
return bigS.Cmp(halfN) <= 0
|
||||
}
|
||||
|
||||
func swap(r, s []byte) ([]byte, []byte, error) {
|
||||
var err error
|
||||
bigS, err := bigmod.NewNat().SetBytes(s, nMod)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sNormalized := nMod.Nat().Sub(bigS, nMod)
|
||||
|
||||
return r, sNormalized.Bytes(nMod), nil
|
||||
}
|
||||
|
||||
func Normalize(sig []byte) ([]byte, error) {
|
||||
r, s, err := parseSignature(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if checkLowS(r, s) {
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
newR, newS, err := swap(r, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return encodeSignature(newR, newS)
|
||||
}
|
||||
|
||||
// Swap will change sig between its current form to the opposite high or low form.
|
||||
func Swap(sig []byte) ([]byte, error) {
|
||||
r, s, err := parseSignature(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newR, newS, err := swap(r, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return encodeSignature(newR, newS)
|
||||
}
|
||||
|
||||
// parseSignature taken exactly from crypto/ecdsa/ecdsa.go
|
||||
func parseSignature(sig []byte) (r, s []byte, err error) {
|
||||
var inner cryptobyte.String
|
||||
input := cryptobyte.String(sig)
|
||||
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
|
||||
!input.Empty() ||
|
||||
!inner.ReadASN1Integer(&r) ||
|
||||
!inner.ReadASN1Integer(&s) ||
|
||||
!inner.Empty() {
|
||||
return nil, nil, errors.New("invalid ASN.1")
|
||||
}
|
||||
return r, s, nil
|
||||
}
|
||||
|
||||
func encodeSignature(r, s []byte) ([]byte, error) {
|
||||
var b cryptobyte.Builder
|
||||
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
|
||||
addASN1IntBytes(b, r)
|
||||
addASN1IntBytes(b, s)
|
||||
})
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// addASN1IntBytes encodes in ASN.1 a positive integer represented as
|
||||
// a big-endian byte slice with zero or more leading zeroes.
|
||||
func addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) {
|
||||
for len(bytes) > 0 && bytes[0] == 0 {
|
||||
bytes = bytes[1:]
|
||||
}
|
||||
if len(bytes) == 0 {
|
||||
b.SetError(errors.New("invalid integer"))
|
||||
return
|
||||
}
|
||||
b.AddASN1(asn1.INTEGER, func(c *cryptobyte.Builder) {
|
||||
if bytes[0]&0x80 != 0 {
|
||||
c.AddUint8(0)
|
||||
}
|
||||
c.AddBytes(bytes)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package p256
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFlipping(t *testing.T) {
|
||||
priv, err1 := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err1)
|
||||
|
||||
out, err := ecdsa.SignASN1(rand.Reader, priv, []byte("big chungus"))
|
||||
require.NoError(t, err)
|
||||
|
||||
r, s, err := parseSignature(out)
|
||||
require.NoError(t, err)
|
||||
|
||||
r, s1, err := swap(r, s)
|
||||
require.NoError(t, err)
|
||||
r, s2, err := swap(r, s1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, s, s2)
|
||||
require.NotEqual(t, s, s1)
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/slackhq/nebula/cert/p256"
|
||||
)
|
||||
|
||||
// TBSCertificate represents a certificate intended to be signed.
|
||||
@@ -126,6 +128,13 @@ func (t *TBSCertificate) SignWith(signer Certificate, curve Curve, sp SignerLamb
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if curve == Curve_P256 {
|
||||
sig, err = p256.Normalize(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = c.setSignature(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/slackhq/nebula/cert/p256"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -89,3 +90,48 @@ func TestCertificateV1_SignP256(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, uc)
|
||||
}
|
||||
|
||||
func TestCertificate_SignP256_AlwaysNormalized(t *testing.T) {
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
after := time.Now().Add(time.Second * 60).Round(time.Second)
|
||||
pubKey := []byte("01234567890abcedfghij1234567890ab1234567890abcedfghij1234567890ab")
|
||||
|
||||
tbs := TBSCertificate{
|
||||
Version: Version1,
|
||||
Name: "testing",
|
||||
Networks: []netip.Prefix{
|
||||
mustParsePrefixUnmapped("10.1.1.1/24"),
|
||||
mustParsePrefixUnmapped("10.1.1.2/16"),
|
||||
},
|
||||
UnsafeNetworks: []netip.Prefix{
|
||||
mustParsePrefixUnmapped("9.1.1.2/24"),
|
||||
mustParsePrefixUnmapped("9.1.1.3/16"),
|
||||
},
|
||||
Groups: []string{"test-group1", "test-group2", "test-group3"},
|
||||
NotBefore: before,
|
||||
NotAfter: after,
|
||||
PublicKey: pubKey,
|
||||
IsCA: true,
|
||||
Curve: Curve_P256,
|
||||
}
|
||||
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
pub := elliptic.Marshal(elliptic.P256(), priv.PublicKey.X, priv.PublicKey.Y)
|
||||
rawPriv := priv.D.FillBytes(make([]byte, 32))
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
if i&1 == 1 {
|
||||
tbs.Version = Version1
|
||||
} else {
|
||||
tbs.Version = Version2
|
||||
}
|
||||
c, err := tbs.Sign(nil, Curve_P256, rawPriv)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, c)
|
||||
assert.True(t, c.CheckSignature(pub))
|
||||
normie, err := p256.IsNormalized(c.Signature())
|
||||
require.NoError(t, err)
|
||||
assert.True(t, normie)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
type m = map[string]any
|
||||
@@ -17,6 +23,17 @@ const (
|
||||
|
||||
PortAny = 0 // Special value for matching `port: any`
|
||||
PortFragment = -1 // Special value for matching `port: fragment`
|
||||
|
||||
minFwPacketLen = 4
|
||||
)
|
||||
|
||||
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")
|
||||
)
|
||||
|
||||
type Packet struct {
|
||||
@@ -60,3 +77,172 @@ func (fp Packet) MarshalJSON() ([]byte, error) {
|
||||
"Fragment": fp.Fragment,
|
||||
})
|
||||
}
|
||||
|
||||
func parseV6(data []byte, incoming bool, fp *Packet) error {
|
||||
dataLen := len(data)
|
||||
if dataLen < ipv6.HeaderLen {
|
||||
return ErrIPv6PacketTooShort
|
||||
}
|
||||
|
||||
if incoming {
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[8:24])
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[24:40])
|
||||
} else {
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[8:24])
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[24:40])
|
||||
}
|
||||
|
||||
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 protoAt >= dataLen {
|
||||
break
|
||||
}
|
||||
proto := layers.IPProtocol(data[protoAt])
|
||||
|
||||
switch proto {
|
||||
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, layers.IPProtocolUDP:
|
||||
if dataLen < offset+4 {
|
||||
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])
|
||||
}
|
||||
|
||||
fp.Fragment = false
|
||||
return nil
|
||||
|
||||
case layers.IPProtocolIPv6Fragment:
|
||||
// Fragment header is 8 bytes, need at least offset+4 to read the offset field
|
||||
if dataLen < offset+8 {
|
||||
return ErrIPv6PacketTooShort
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// The next loop should be the transport layer since we are the first fragment
|
||||
next = 8 // Fragment headers are always 8 bytes
|
||||
|
||||
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]+2) << 2
|
||||
|
||||
default:
|
||||
// Normal ipv6 header length processing
|
||||
if dataLen <= offset+1 {
|
||||
break
|
||||
}
|
||||
|
||||
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 ErrIPv6CouldNotFindPayload
|
||||
}
|
||||
|
||||
func parseV4(data []byte, incoming bool, fp *Packet) error {
|
||||
// Do we at least have an ipv4 header worth of data?
|
||||
if len(data) < 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?
|
||||
if ihl < ipv4.HeaderLen {
|
||||
return ErrIPv4InvalidHeaderLength
|
||||
}
|
||||
|
||||
// Check if this is the second or further fragment of a fragmented packet.
|
||||
flagsfrags := binary.BigEndian.Uint16(data[6:8])
|
||||
fp.Fragment = (flagsfrags & 0x1FFF) != 0
|
||||
|
||||
// Firewall handles protocol checks
|
||||
fp.Protocol = data[9]
|
||||
|
||||
// Accounting for a variable header length, do we have enough data for our src/dst tuples?
|
||||
minLen := ihl
|
||||
if !fp.Fragment && fp.Protocol != ProtoICMP {
|
||||
minLen += minFwPacketLen
|
||||
}
|
||||
if len(data) < minLen {
|
||||
return ErrIPv4InvalidHeaderLength
|
||||
}
|
||||
|
||||
// Firewall packets are locally oriented
|
||||
if incoming {
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||
if fp.Fragment || fp.Protocol == ProtoICMP {
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
} else {
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||
}
|
||||
} else {
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||
if fp.Fragment || fp.Protocol == ProtoICMP {
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
} else {
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 *Packet) error {
|
||||
if len(data) < 1 {
|
||||
return ErrPacketTooShort
|
||||
}
|
||||
|
||||
version := int((data[0] >> 4) & 0x0f)
|
||||
switch version {
|
||||
case ipv4.Version:
|
||||
return parseV4(data, incoming, fp)
|
||||
case ipv6.Version:
|
||||
return parseV6(data, incoming, fp)
|
||||
}
|
||||
return ErrUnknownIPVersion
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ go 1.25
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2
|
||||
filippo.io/bigmod v0.1.0
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
github.com/armon/go-radix v1.0.0
|
||||
github.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
filippo.io/bigmod v0.1.0 h1:UNzDk7y9ADKST+axd9skUpBQeW7fG2KrTZyOE4uGQy8=
|
||||
filippo.io/bigmod v0.1.0/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet, nb, out []byte, q int, localCache firewall.ConntrackCache) {
|
||||
err := newPacket(packet, false, fwPacket)
|
||||
err := firewall.NewPacket(packet, false, fwPacket)
|
||||
if err != nil {
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
f.l.WithField("packet", packet).Debugf("Error while validating outbound packet: %s", err)
|
||||
@@ -211,7 +211,7 @@ func (f *Interface) getOrHandshakeConsiderRouting(fwPacket *firewall.Packet, cac
|
||||
|
||||
func (f *Interface) sendMessageNow(t header.MessageType, st header.MessageSubType, hostinfo *HostInfo, p, nb, out []byte) {
|
||||
fp := &firewall.Packet{}
|
||||
err := newPacket(p, false, fp)
|
||||
err := firewall.NewPacket(p, false, fp)
|
||||
if err != nil {
|
||||
f.l.Warnf("error while parsing outgoing packet for firewall check; %v", err)
|
||||
return
|
||||
|
||||
+1
-188
@@ -1,22 +1,13 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/net/ipv6"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/firewall"
|
||||
"github.com/slackhq/nebula/header"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
const (
|
||||
minFwPacketLen = 4
|
||||
)
|
||||
|
||||
func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte, h *header.H, fwPacket *firewall.Packet, lhf *LightHouseHandler, nb []byte, q int, localCache firewall.ConntrackCache) {
|
||||
@@ -278,184 +269,6 @@ func (f *Interface) handleEncrypted(ci *ConnectionState, via ViaSender, h *heade
|
||||
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 ErrPacketTooShort
|
||||
}
|
||||
|
||||
version := int((data[0] >> 4) & 0x0f)
|
||||
switch version {
|
||||
case ipv4.Version:
|
||||
return parseV4(data, incoming, fp)
|
||||
case ipv6.Version:
|
||||
return parseV6(data, incoming, fp)
|
||||
}
|
||||
return ErrUnknownIPVersion
|
||||
}
|
||||
|
||||
func parseV6(data []byte, incoming bool, fp *firewall.Packet) error {
|
||||
dataLen := len(data)
|
||||
if dataLen < ipv6.HeaderLen {
|
||||
return ErrIPv6PacketTooShort
|
||||
}
|
||||
|
||||
if incoming {
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[8:24])
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[24:40])
|
||||
} else {
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[8:24])
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[24:40])
|
||||
}
|
||||
|
||||
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 protoAt >= dataLen {
|
||||
break
|
||||
}
|
||||
proto := layers.IPProtocol(data[protoAt])
|
||||
|
||||
switch proto {
|
||||
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, layers.IPProtocolUDP:
|
||||
if dataLen < offset+4 {
|
||||
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])
|
||||
}
|
||||
|
||||
fp.Fragment = false
|
||||
return nil
|
||||
|
||||
case layers.IPProtocolIPv6Fragment:
|
||||
// Fragment header is 8 bytes, need at least offset+4 to read the offset field
|
||||
if dataLen < offset+8 {
|
||||
return ErrIPv6PacketTooShort
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// The next loop should be the transport layer since we are the first fragment
|
||||
next = 8 // Fragment headers are always 8 bytes
|
||||
|
||||
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]+2) << 2
|
||||
|
||||
default:
|
||||
// Normal ipv6 header length processing
|
||||
if dataLen <= offset+1 {
|
||||
break
|
||||
}
|
||||
|
||||
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 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 ErrIPv4PacketTooShort
|
||||
}
|
||||
|
||||
// Adjust our start position based on the advertised ip header length
|
||||
ihl := int(data[0]&0x0f) << 2
|
||||
|
||||
// Well-formed ip header length?
|
||||
if ihl < ipv4.HeaderLen {
|
||||
return ErrIPv4InvalidHeaderLength
|
||||
}
|
||||
|
||||
// Check if this is the second or further fragment of a fragmented packet.
|
||||
flagsfrags := binary.BigEndian.Uint16(data[6:8])
|
||||
fp.Fragment = (flagsfrags & 0x1FFF) != 0
|
||||
|
||||
// Firewall handles protocol checks
|
||||
fp.Protocol = data[9]
|
||||
|
||||
// Accounting for a variable header length, do we have enough data for our src/dst tuples?
|
||||
minLen := ihl
|
||||
if !fp.Fragment && fp.Protocol != firewall.ProtoICMP {
|
||||
minLen += minFwPacketLen
|
||||
}
|
||||
if len(data) < minLen {
|
||||
return ErrIPv4InvalidHeaderLength
|
||||
}
|
||||
|
||||
// Firewall packets are locally oriented
|
||||
if incoming {
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||
if fp.Fragment || fp.Protocol == firewall.ProtoICMP {
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
} else {
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||
}
|
||||
} else {
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||
if fp.Fragment || fp.Protocol == firewall.ProtoICMP {
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
} else {
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Interface) decrypt(hostinfo *HostInfo, mc uint64, out []byte, packet []byte, h *header.H, nb []byte) ([]byte, error) {
|
||||
var err error
|
||||
out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], mc, nb)
|
||||
@@ -481,7 +294,7 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
|
||||
return false
|
||||
}
|
||||
|
||||
err = newPacket(out, true, fwPacket)
|
||||
err = firewall.NewPacket(out, true, fwPacket)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).WithField("packet", out).
|
||||
Warnf("Error while validating inbound packet")
|
||||
|
||||
+29
-29
@@ -20,13 +20,13 @@ func Test_newPacket(t *testing.T) {
|
||||
p := &firewall.Packet{}
|
||||
|
||||
// length fails
|
||||
err := newPacket([]byte{}, true, p)
|
||||
err := firewall.NewPacket([]byte{}, true, p)
|
||||
require.ErrorIs(t, err, ErrPacketTooShort)
|
||||
|
||||
err = newPacket([]byte{0x40}, true, p)
|
||||
err = firewall.NewPacket([]byte{0x40}, true, p)
|
||||
require.ErrorIs(t, err, ErrIPv4PacketTooShort)
|
||||
|
||||
err = newPacket([]byte{0x60}, true, p)
|
||||
err = firewall.NewPacket([]byte{0x60}, true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||
|
||||
// length fail with ip options
|
||||
@@ -39,15 +39,15 @@ func Test_newPacket(t *testing.T) {
|
||||
}
|
||||
|
||||
b, _ := h.Marshal()
|
||||
err = newPacket(b, true, p)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
require.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)
|
||||
|
||||
// not an ipv4 packet
|
||||
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)
|
||||
err = firewall.NewPacket([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)
|
||||
require.ErrorIs(t, err, ErrUnknownIPVersion)
|
||||
|
||||
// invalid ihl
|
||||
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)
|
||||
err = firewall.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)
|
||||
require.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)
|
||||
|
||||
// account for variable ip header length - incoming
|
||||
@@ -62,7 +62,7 @@ func Test_newPacket(t *testing.T) {
|
||||
|
||||
b, _ = h.Marshal()
|
||||
b = append(b, []byte{0, 3, 0, 4}...)
|
||||
err = newPacket(b, true, p)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
||||
@@ -84,7 +84,7 @@ func Test_newPacket(t *testing.T) {
|
||||
|
||||
b, _ = h.Marshal()
|
||||
b = append(b, []byte{0, 5, 0, 6}...)
|
||||
err = newPacket(b, false, p)
|
||||
err = firewall.NewPacket(b, false, p)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(2), p.Protocol)
|
||||
@@ -114,7 +114,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
err := gopacket.SerializeLayers(buffer, opt, &ip)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = newPacket(buffer.Bytes(), true, p)
|
||||
err = firewall.NewPacket(buffer.Bytes(), true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||
|
||||
// A v6 packet with a hop-by-hop extension
|
||||
@@ -148,12 +148,12 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
|
||||
// A full IPv6 header and 1 byte in the first extension, but missing
|
||||
// the length byte.
|
||||
err = newPacket(buffer.Bytes()[:41], true, p)
|
||||
err = firewall.NewPacket(buffer.Bytes()[:41], true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||
|
||||
// A full IPv6 header plus 1 full extension, but only 1 byte of the
|
||||
// next layer, missing length byte
|
||||
err = newPacket(buffer.Bytes()[:49], true, p)
|
||||
err = firewall.NewPacket(buffer.Bytes()[:49], true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||
|
||||
// A good ICMP packet
|
||||
@@ -173,7 +173,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = newPacket(buffer.Bytes(), true, p)
|
||||
err = firewall.NewPacket(buffer.Bytes(), true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(layers.IPProtocolICMPv6), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -185,7 +185,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
// A good ESP packet
|
||||
b := buffer.Bytes()
|
||||
b[6] = byte(layers.IPProtocolESP)
|
||||
err = newPacket(b, true, p)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(layers.IPProtocolESP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -197,7 +197,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
// A good None packet
|
||||
b = buffer.Bytes()
|
||||
b[6] = byte(layers.IPProtocolNoNextHeader)
|
||||
err = newPacket(b, true, p)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(layers.IPProtocolNoNextHeader), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -209,7 +209,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
// An unknown protocol packet
|
||||
b = buffer.Bytes()
|
||||
b[6] = 255 // 255 is a reserved protocol number
|
||||
err = newPacket(b, true, p)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||
|
||||
// A good UDP packet
|
||||
@@ -236,7 +236,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
b = buffer.Bytes()
|
||||
|
||||
// incoming
|
||||
err = newPacket(b, true, p)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -246,7 +246,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// outgoing
|
||||
err = newPacket(b, false, p)
|
||||
err = firewall.NewPacket(b, false, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||
@@ -256,14 +256,14 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// Too short UDP packet
|
||||
err = newPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
||||
err = firewall.NewPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||
|
||||
// A good TCP packet
|
||||
b[6] = byte(layers.IPProtocolTCP)
|
||||
|
||||
// incoming
|
||||
err = newPacket(b, true, p)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -273,7 +273,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// outgoing
|
||||
err = newPacket(b, false, p)
|
||||
err = firewall.NewPacket(b, false, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||
@@ -283,7 +283,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// Too short TCP packet
|
||||
err = newPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
||||
err = firewall.NewPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||
|
||||
// A good UDP packet with an AH header
|
||||
@@ -318,7 +318,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
b = append(b, ahb...)
|
||||
b = append(b, udpHeader...)
|
||||
|
||||
err = newPacket(b, true, p)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -328,12 +328,12 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// Ensure buffer bounds checking during processing
|
||||
err = newPacket(b[:41], true, p)
|
||||
err = firewall.NewPacket(b[:41], true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||
|
||||
// Invalid AH header
|
||||
b = buffer.Bytes()
|
||||
err = newPacket(b, true, p)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
||||
firstFrag = append(firstFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
|
||||
|
||||
// Test first fragment incoming
|
||||
err = newPacket(firstFrag, true, p)
|
||||
err = firewall.NewPacket(firstFrag, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
||||
@@ -391,7 +391,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// Test first fragment outgoing
|
||||
err = newPacket(firstFrag, false, p)
|
||||
err = firewall.NewPacket(firstFrag, false, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
|
||||
@@ -420,7 +420,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
||||
secondFrag = append(secondFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
|
||||
|
||||
// Test second fragment incoming
|
||||
err = newPacket(secondFrag, true, p)
|
||||
err = firewall.NewPacket(secondFrag, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
||||
@@ -430,7 +430,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
||||
assert.True(t, p.Fragment)
|
||||
|
||||
// Test second fragment outgoing
|
||||
err = newPacket(secondFrag, false, p)
|
||||
err = firewall.NewPacket(secondFrag, false, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
|
||||
@@ -440,7 +440,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
||||
assert.True(t, p.Fragment)
|
||||
|
||||
// Too short of a fragment packet
|
||||
err = newPacket(secondFrag[:len(secondFrag)-10], false, p)
|
||||
err = firewall.NewPacket(secondFrag[:len(secondFrag)-10], false, p)
|
||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||
}
|
||||
|
||||
|
||||
+30
-5
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/rcrowley/go-metrics"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/firewall"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/routing"
|
||||
)
|
||||
@@ -87,21 +88,45 @@ func (t *disabledTun) handleICMPEchoRequest(b []byte) bool {
|
||||
default:
|
||||
t.l.Debugf("tun_disabled: dropped ICMP Echo Reply response")
|
||||
}
|
||||
|
||||
if t.l.Level >= logrus.DebugLevel {
|
||||
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun responded to ICMP Echo Request")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *disabledTun) handleOtherPackets(b []byte) error {
|
||||
fp := &firewall.Packet{}
|
||||
err := firewall.NewPacket(b, true, fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out := make([]byte, len(b)) //todo do something!
|
||||
|
||||
// attempt to write it, but don't block
|
||||
select {
|
||||
case t.read <- out:
|
||||
default:
|
||||
t.l.Debugf("tun_disabled: dropped reply")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *disabledTun) Write(b []byte) (int, error) {
|
||||
t.rx.Inc(1)
|
||||
|
||||
// Check for ICMP Echo Request before spending time doing the full parsing
|
||||
if t.handleICMPEchoRequest(b) {
|
||||
return len(b), nil
|
||||
}
|
||||
if err := t.handleOtherPackets(b); err != nil {
|
||||
if t.l.Level >= logrus.DebugLevel {
|
||||
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun responded to ICMP Echo Request")
|
||||
t.l.WithField("raw", prettyPacket(b)).WithError(err).Debugf("Disabled tun failed to respond")
|
||||
}
|
||||
} else if t.l.Level >= logrus.DebugLevel {
|
||||
return len(b), nil
|
||||
}
|
||||
if t.l.Level >= logrus.DebugLevel {
|
||||
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun received unexpected payload")
|
||||
}
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user