mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 09:57:00 +02:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5cc3ff594a | |||
| 42bee7cf17 | |||
| 02d8bcac68 | |||
| 0b02d982b2 | |||
| e1e92f017c | |||
| e5f60fa54f | |||
| bf49e78243 | |||
| 72a40007ea | |||
| ac3bd9cdd0 | |||
| 88379b89f5 | |||
| 1283ff0db4 | |||
| 523209ec0b | |||
| a4a6143b6a | |||
| 1b2d639b14 | |||
| 9933970e67 | |||
| d7a3f01465 | |||
| 69259e6307 | |||
| 2f71d6b22d | |||
| 3ec527e42c | |||
| 2d16940232 | |||
| cba294ffa4 | |||
| 48406f85da | |||
| 14a1af132e |
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
name: Build Linux/BSD All
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
mv build/*.tar.gz release
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: linux-latest
|
||||
path: release
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
name: Build Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
mv dist\windows\wintun build\dist\windows\
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: windows-latest
|
||||
path: build
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
HAS_SIGNING_CREDS: ${{ secrets.AC_USERNAME != '' }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
|
||||
- name: Import certificates
|
||||
if: env.HAS_SIGNING_CREDS == 'true'
|
||||
uses: Apple-Actions/import-codesign-certs@v5
|
||||
uses: Apple-Actions/import-codesign-certs@v6
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
|
||||
p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: darwin-latest
|
||||
path: ./release/*
|
||||
@@ -124,11 +124,11 @@ jobs:
|
||||
# be overwritten
|
||||
- name: Checkout code
|
||||
if: ${{ env.HAS_DOCKER_CREDS == 'true' }}
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download artifacts
|
||||
if: ${{ env.HAS_DOCKER_CREDS == 'true' }}
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: linux-latest
|
||||
path: artifacts
|
||||
@@ -160,10 +160,10 @@ jobs:
|
||||
needs: [build-linux, build-darwin, build-windows]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: Build test mobile
|
||||
run: make build-test-mobile
|
||||
|
||||
- uses: actions/upload-artifact@v5
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: e2e packet flow linux-latest
|
||||
path: e2e/mermaid/linux-latest
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
os: [windows-latest, macos-latest]
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
- name: End 2 end
|
||||
run: make e2evv
|
||||
|
||||
- uses: actions/upload-artifact@v5
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: e2e packet flow ${{ matrix.os }}
|
||||
path: e2e/mermaid/${{ matrix.os }}
|
||||
|
||||
+34
-1
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.10.2] - 2026-01-21
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix panic when using `use_system_route_table` that was introduced in v1.10.1. (#1580)
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix some typos in comments. (#1582)
|
||||
- Dependency updates. (#1581)
|
||||
|
||||
## [1.10.1] - 2026-01-16
|
||||
|
||||
See the [v1.10.1](https://github.com/slackhq/nebula/milestone/26?closed=1) milestone for a complete list of changes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a bug where an unsafe route derived from the system route table could be lost on a config reload. (#1573)
|
||||
- Fix the PEM banner for ECDSA P256 public keys. (#1552)
|
||||
- Fix a regression on Windows from 1.9.x where nebula could fall back to a less performant UDP listener if
|
||||
non-critical ioctls failed. (#1568)
|
||||
- Fix a bug in handshake processing when a peer sends an unexpected public key. (#1566)
|
||||
|
||||
### Added
|
||||
|
||||
- Add a config option to control accepting `recv_error` packets which defaults to `always`. (#1569)
|
||||
|
||||
### Changed
|
||||
|
||||
- Various dependency updates. (#1541, #1549, #1550, #1557, #1558, #1560, #1561, #1570, #1571)
|
||||
|
||||
## [1.10.0] - 2025-12-04
|
||||
|
||||
See the [v1.10.0](https://github.com/slackhq/nebula/milestone/16?closed=1) milestone for a complete list of changes.
|
||||
@@ -744,7 +775,9 @@ created.)
|
||||
|
||||
- Initial public release.
|
||||
|
||||
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.0...HEAD
|
||||
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.2...HEAD
|
||||
[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
|
||||
[1.9.7]: https://github.com/slackhq/nebula/releases/tag/v1.9.7
|
||||
[1.9.6]: https://github.com/slackhq/nebula/releases/tag/v1.9.6
|
||||
|
||||
@@ -119,6 +119,7 @@ func (cc *CachedCertificate) String() string {
|
||||
// Recombine will attempt to unmarshal a certificate received in a handshake.
|
||||
// Handshakes save space by placing the peers public key in a different part of the packet, we have to
|
||||
// reassemble the actual certificate structure with that in mind.
|
||||
// Implementations MUST assert the public key is not in the raw certificate bytes if the passed in public key is not empty.
|
||||
func Recombine(v Version, rawCertBytes, publicKey []byte, curve Curve) (Certificate, error) {
|
||||
if publicKey == nil {
|
||||
return nil, ErrNoPeerStaticKey
|
||||
|
||||
+11
-4
@@ -426,7 +426,7 @@ func unmarshalCertificateV1(b []byte, publicKey []byte) (*certificateV1, error)
|
||||
unsafeNetworks: make([]netip.Prefix, len(rc.Details.Subnets)/2),
|
||||
notBefore: time.Unix(rc.Details.NotBefore, 0),
|
||||
notAfter: time.Unix(rc.Details.NotAfter, 0),
|
||||
publicKey: make([]byte, len(rc.Details.PublicKey)),
|
||||
publicKey: nil,
|
||||
isCA: rc.Details.IsCA,
|
||||
curve: rc.Details.Curve,
|
||||
},
|
||||
@@ -437,12 +437,19 @@ func unmarshalCertificateV1(b []byte, publicKey []byte) (*certificateV1, error)
|
||||
copy(nc.details.groups, rc.Details.Groups)
|
||||
nc.details.issuer = hex.EncodeToString(rc.Details.Issuer)
|
||||
|
||||
// If a public key is passed in as an argument, the certificate pubkey must be empty
|
||||
// and the passed-in pubkey copied into the cert.
|
||||
if len(publicKey) > 0 {
|
||||
nc.details.publicKey = publicKey
|
||||
if len(rc.Details.PublicKey) != 0 {
|
||||
return nil, ErrCertPubkeyPresent
|
||||
}
|
||||
nc.details.publicKey = make([]byte, len(publicKey))
|
||||
copy(nc.details.publicKey, publicKey)
|
||||
} else {
|
||||
nc.details.publicKey = make([]byte, len(rc.Details.PublicKey))
|
||||
copy(nc.details.publicKey, rc.Details.PublicKey)
|
||||
}
|
||||
|
||||
copy(nc.details.publicKey, rc.Details.PublicKey)
|
||||
|
||||
var ip netip.Addr
|
||||
for i, rawIp := range rc.Details.Ips {
|
||||
if i%2 == 0 {
|
||||
|
||||
+63
-1
@@ -62,6 +62,62 @@ func TestCertificateV1_Marshal(t *testing.T) {
|
||||
assert.Equal(t, nc.Groups(), nc2.Groups())
|
||||
}
|
||||
|
||||
func TestCertificateV1_Unmarshal(t *testing.T) {
|
||||
t.Parallel()
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
after := time.Now().Add(time.Second * 60).Round(time.Second)
|
||||
pubKey := []byte("1234567890abcedfghij1234567890ab")
|
||||
invalidPubkey := []byte("00000000000000000000000000000000")
|
||||
|
||||
nc := certificateV1{
|
||||
details: detailsV1{
|
||||
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: false,
|
||||
issuer: "1234567890abcedfghij1234567890ab",
|
||||
},
|
||||
signature: []byte("1234567890abcedfghij1234567890ab"),
|
||||
}
|
||||
|
||||
// This certificate has a pubkey included
|
||||
certWithPubkey, err := nc.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
// This certificate is missing the pubkey section
|
||||
certWithoutPubkey, err := nc.MarshalForHandshakes()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Cert has no pubkey and no pubkey passed in must fail to validate
|
||||
isNil, err := unmarshalCertificateV1(certWithoutPubkey, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
// Cert has different pubkey than one passed in must fail
|
||||
isNil, err = unmarshalCertificateV1(certWithPubkey, invalidPubkey)
|
||||
require.Nil(t, isNil)
|
||||
require.Error(t, err)
|
||||
|
||||
// Cert has pubkey and no pubkey argument works ok
|
||||
_, err = unmarshalCertificateV1(certWithPubkey, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Cert has no pubkey and valid, correctly signed pubkey passed in
|
||||
nc2, err := unmarshalCertificateV1(certWithoutPubkey, pubKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, pubKey, nc2.PublicKey())
|
||||
}
|
||||
|
||||
func TestCertificateV1_PublicKeyPem(t *testing.T) {
|
||||
t.Parallel()
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
@@ -99,13 +155,19 @@ func TestCertificateV1_PublicKeyPem(t *testing.T) {
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAA=
|
||||
-----END NEBULA P256 PUBLIC KEY-----
|
||||
`)
|
||||
|
||||
pubP256KeyPemCA := []byte(`-----BEGIN NEBULA ECDSA P256 PUBLIC KEY-----
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAA=
|
||||
-----END NEBULA ECDSA P256 PUBLIC KEY-----
|
||||
`)
|
||||
pubP256Key, _, _, err := UnmarshalPublicKeyFromPEM(pubP256KeyPem)
|
||||
require.NoError(t, err)
|
||||
nc.details.curve = Curve_P256
|
||||
nc.details.publicKey = pubP256Key
|
||||
assert.Equal(t, Curve_P256, nc.Curve())
|
||||
assert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPem))
|
||||
assert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPemCA))
|
||||
assert.True(t, nc.IsCA())
|
||||
|
||||
nc.details.isCA = false
|
||||
|
||||
+7
-1
@@ -592,7 +592,13 @@ func unmarshalCertificateV2(b []byte, publicKey []byte, curve Curve) (*certifica
|
||||
// Maybe grab the public key
|
||||
var rawPublicKey cryptobyte.String
|
||||
if len(publicKey) > 0 {
|
||||
rawPublicKey = publicKey
|
||||
// If a public key is passed in, then the handshake certificate must
|
||||
// not have a public key present
|
||||
if input.PeekASN1Tag(TagCertPublicKey) {
|
||||
return nil, ErrCertPubkeyPresent
|
||||
}
|
||||
rawPublicKey = make(cryptobyte.String, len(publicKey))
|
||||
copy(rawPublicKey, publicKey)
|
||||
} else if !input.ReadOptionalASN1(&rawPublicKey, nil, TagCertPublicKey) {
|
||||
return nil, ErrBadFormat
|
||||
}
|
||||
|
||||
+60
-1
@@ -76,6 +76,58 @@ func TestCertificateV2_Marshal(t *testing.T) {
|
||||
assert.Equal(t, nc.Groups(), nc2.Groups())
|
||||
}
|
||||
|
||||
func TestCertificateV2_Unmarshal(t *testing.T) {
|
||||
t.Parallel()
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
after := time.Now().Add(time.Second * 60).Round(time.Second)
|
||||
pubKey := []byte("1234567890abcedfghij1234567890ab")
|
||||
|
||||
nc := certificateV2{
|
||||
details: detailsV2{
|
||||
name: "testing",
|
||||
networks: []netip.Prefix{
|
||||
mustParsePrefixUnmapped("10.1.1.2/16"),
|
||||
mustParsePrefixUnmapped("10.1.1.1/24"),
|
||||
},
|
||||
unsafeNetworks: []netip.Prefix{
|
||||
mustParsePrefixUnmapped("9.1.1.3/16"),
|
||||
mustParsePrefixUnmapped("9.1.1.2/24"),
|
||||
},
|
||||
groups: []string{"test-group1", "test-group2", "test-group3"},
|
||||
notBefore: before,
|
||||
notAfter: after,
|
||||
isCA: false,
|
||||
issuer: "1234567890abcdef1234567890abcdef",
|
||||
},
|
||||
signature: []byte("1234567890abcdef1234567890abcdef"),
|
||||
publicKey: pubKey,
|
||||
}
|
||||
|
||||
db, err := nc.details.Marshal()
|
||||
require.NoError(t, err)
|
||||
nc.rawDetails = db
|
||||
|
||||
certWithPubkey, err := nc.Marshal()
|
||||
require.NoError(t, err)
|
||||
//t.Log("Cert size:", len(b))
|
||||
certWithoutPubkey, err := nc.MarshalForHandshakes()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Cert must not have a pubkey if one is passed in as an argument
|
||||
_, err = unmarshalCertificateV2(certWithPubkey, pubKey, Curve_CURVE25519)
|
||||
require.ErrorIs(t, err, ErrCertPubkeyPresent)
|
||||
|
||||
// Certs must have pubkeys
|
||||
_, err = unmarshalCertificateV2(certWithoutPubkey, nil, Curve_CURVE25519)
|
||||
require.ErrorIs(t, err, ErrBadFormat)
|
||||
|
||||
// Ensure proper unmarshal if a pubkey is passed in
|
||||
nc2, err := unmarshalCertificateV2(certWithoutPubkey, pubKey, Curve_CURVE25519)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, nc.PublicKey(), nc2.PublicKey())
|
||||
}
|
||||
|
||||
func TestCertificateV2_PublicKeyPem(t *testing.T) {
|
||||
t.Parallel()
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
@@ -114,12 +166,19 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAA=
|
||||
-----END NEBULA P256 PUBLIC KEY-----
|
||||
`)
|
||||
|
||||
pubP256KeyPemCA := []byte(`-----BEGIN NEBULA ECDSA P256 PUBLIC KEY-----
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAA=
|
||||
-----END NEBULA ECDSA P256 PUBLIC KEY-----
|
||||
`)
|
||||
|
||||
pubP256Key, _, _, err := UnmarshalPublicKeyFromPEM(pubP256KeyPem)
|
||||
require.NoError(t, err)
|
||||
nc.curve = Curve_P256
|
||||
nc.publicKey = pubP256Key
|
||||
assert.Equal(t, Curve_P256, nc.Curve())
|
||||
assert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPem))
|
||||
assert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPemCA))
|
||||
assert.True(t, nc.IsCA())
|
||||
|
||||
nc.details.isCA = false
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ qrlJ69wer3ZUHFXA
|
||||
assert.Nil(t, k)
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
|
||||
// Fail due to ivalid PEM format, because
|
||||
// Fail due to invalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest)
|
||||
require.EqualError(t, err, "input did not contain a valid PEM encoded block")
|
||||
|
||||
@@ -21,6 +21,7 @@ var (
|
||||
ErrPrivateKeyEncrypted = errors.New("private key must be decrypted")
|
||||
ErrCaNotFound = errors.New("could not find ca for the certificate")
|
||||
ErrUnknownVersion = errors.New("certificate version unrecognized")
|
||||
ErrCertPubkeyPresent = errors.New("certificate has unexpected pubkey present")
|
||||
|
||||
ErrInvalidPEMBlock = errors.New("input did not contain a valid PEM encoded block")
|
||||
ErrInvalidPEMCertificateBanner = errors.New("bytes did not contain a proper certificate banner")
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ func MarshalSigningPublicKeyToPEM(curve Curve, b []byte) []byte {
|
||||
case Curve_CURVE25519:
|
||||
return pem.EncodeToMemory(&pem.Block{Type: Ed25519PublicKeyBanner, Bytes: b})
|
||||
case Curve_P256:
|
||||
return pem.EncodeToMemory(&pem.Block{Type: P256PublicKeyBanner, Bytes: b})
|
||||
return pem.EncodeToMemory(&pem.Block{Type: ECDSAP256PublicKeyBanner, Bytes: b})
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
+5
-5
@@ -44,7 +44,7 @@ bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
require.EqualError(t, err, "bytes did not contain a proper certificate banner")
|
||||
|
||||
// Fail due to ivalid PEM format, because
|
||||
// Fail due to invalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
cert, rest, err = UnmarshalCertificateFromPEM(rest)
|
||||
assert.Nil(t, cert)
|
||||
@@ -106,7 +106,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
require.EqualError(t, err, "bytes did not contain a proper Ed25519/ECDSA private key banner")
|
||||
|
||||
// Fail due to ivalid PEM format, because
|
||||
// Fail due to invalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
k, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)
|
||||
assert.Nil(t, k)
|
||||
@@ -168,7 +168,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
require.EqualError(t, err, "bytes did not contain a proper private key banner")
|
||||
|
||||
// Fail due to ivalid PEM format, because
|
||||
// Fail due to invalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
k, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)
|
||||
assert.Nil(t, k)
|
||||
@@ -221,7 +221,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
require.EqualError(t, err, "bytes did not contain a proper public key banner")
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
|
||||
// Fail due to ivalid PEM format, because
|
||||
// Fail due to invalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
|
||||
assert.Nil(t, k)
|
||||
@@ -299,7 +299,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
require.EqualError(t, err, "bytes did not contain a proper public key banner")
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
|
||||
// Fail due to ivalid PEM format, because
|
||||
// Fail due to invalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
|
||||
assert.Nil(t, k)
|
||||
|
||||
@@ -200,7 +200,7 @@ func Test_ca(t *testing.T) {
|
||||
assert.Empty(t, b)
|
||||
assert.Len(t, lKey, 64)
|
||||
|
||||
// test when reading passsword results in an error
|
||||
// test when reading password results in an error
|
||||
os.Remove(keyF.Name())
|
||||
os.Remove(crtF.Name())
|
||||
ob.Reset()
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package main
|
||||
|
||||
const NoSuchFileError = "no such file or directory"
|
||||
const NoSuchDirError = "no such file or directory"
|
||||
@@ -0,0 +1,4 @@
|
||||
package main
|
||||
|
||||
const NoSuchFileError = "no such file or directory"
|
||||
const NoSuchDirError = "no such file or directory"
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/slackhq/nebula/cert"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/test"
|
||||
"github.com/slackhq/nebula/test/device"
|
||||
"github.com/slackhq/nebula/udp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -52,7 +53,7 @@ func Test_NewConnectionManagerTest(t *testing.T) {
|
||||
lh := newTestLighthouse()
|
||||
ifce := &Interface{
|
||||
hostMap: hostMap,
|
||||
inside: &test.NoopTun{},
|
||||
inside: &device.NoopTun{},
|
||||
outside: &udp.NoopConn{},
|
||||
firewall: &Firewall{},
|
||||
lightHouse: lh,
|
||||
@@ -135,7 +136,7 @@ func Test_NewConnectionManagerTest2(t *testing.T) {
|
||||
lh := newTestLighthouse()
|
||||
ifce := &Interface{
|
||||
hostMap: hostMap,
|
||||
inside: &test.NoopTun{},
|
||||
inside: &device.NoopTun{},
|
||||
outside: &udp.NoopConn{},
|
||||
firewall: &Firewall{},
|
||||
lightHouse: lh,
|
||||
@@ -220,7 +221,7 @@ func Test_NewConnectionManager_DisconnectInactive(t *testing.T) {
|
||||
lh := newTestLighthouse()
|
||||
ifce := &Interface{
|
||||
hostMap: hostMap,
|
||||
inside: &test.NoopTun{},
|
||||
inside: &device.NoopTun{},
|
||||
outside: &udp.NoopConn{},
|
||||
firewall: &Firewall{},
|
||||
lightHouse: lh,
|
||||
@@ -347,7 +348,7 @@ func Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) {
|
||||
lh := newTestLighthouse()
|
||||
ifce := &Interface{
|
||||
hostMap: hostMap,
|
||||
inside: &test.NoopTun{},
|
||||
inside: &device.NoopTun{},
|
||||
outside: &udp.NoopConn{},
|
||||
firewall: &Firewall{},
|
||||
lightHouse: lh,
|
||||
|
||||
+1
-1
@@ -304,7 +304,7 @@ func assertTunnel(t testing.TB, vpnIpA, vpnIpB netip.Addr, controlA, controlB *n
|
||||
assertUdpPacket(t, []byte("Hello from A"), aPacket, vpnIpA, vpnIpB, 90, 80)
|
||||
}
|
||||
|
||||
func assertHostInfoPair(t *testing.T, addrA, addrB netip.AddrPort, vpnNetsA, vpnNetsB []netip.Prefix, controlA, controlB *nebula.Control) {
|
||||
func assertHostInfoPair(t testing.TB, addrA, addrB netip.AddrPort, vpnNetsA, vpnNetsB []netip.Prefix, controlA, controlB *nebula.Control) {
|
||||
// Get both host infos
|
||||
//TODO: CERT-V2 we may want to loop over each vpnAddr and assert all the things
|
||||
hBinA := controlA.GetHostInfoByVpnAddr(vpnNetsB[0].Addr(), false)
|
||||
|
||||
@@ -144,6 +144,10 @@ listen:
|
||||
# valid values: always, never, private
|
||||
# This setting is reloadable.
|
||||
#send_recv_error: always
|
||||
# Similar to send_recv_error, this option lets you configure if you want to accept "recv_error" packets from remote hosts.
|
||||
# valid values: always, never, private
|
||||
# This setting is reloadable.
|
||||
#accept_recv_error: always
|
||||
# The so_sock option is a Linux-specific feature that allows all outgoing Nebula packets to be tagged with a specific identifier.
|
||||
# This tagging enables IP rule-based filtering. For example, it supports 0.0.0.0/0 unsafe_routes,
|
||||
# allowing for more precise routing decisions based on the packet tags. Default is 0 meaning no mark is set.
|
||||
|
||||
+14
-5
@@ -81,8 +81,14 @@ type FirewallConntrack struct {
|
||||
|
||||
Conns map[firewall.Packet]*conn
|
||||
TimerWheel *TimerWheel[firewall.Packet]
|
||||
|
||||
// purgeCounter tracks lookups to trigger periodic purge instead of every lookup
|
||||
purgeCounter uint32
|
||||
}
|
||||
|
||||
// purgeInterval defines how many lookups between purge attempts
|
||||
const conntrackPurgeInterval = 1024
|
||||
|
||||
// FirewallTable is the entry point for a rule, the evaluation order is:
|
||||
// Proto AND port AND (CA SHA or CA name) AND local CIDR AND (group OR groups OR name OR remote CIDR)
|
||||
type FirewallTable struct {
|
||||
@@ -492,14 +498,17 @@ func (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.CAPool,
|
||||
conntrack := f.Conntrack
|
||||
conntrack.Lock()
|
||||
|
||||
// Purge every time we test
|
||||
ep, has := conntrack.TimerWheel.Purge()
|
||||
if has {
|
||||
f.evict(ep)
|
||||
// Periodic purge instead of every lookup (major CPU savings)
|
||||
conntrack.purgeCounter++
|
||||
if conntrack.purgeCounter >= conntrackPurgeInterval {
|
||||
conntrack.purgeCounter = 0
|
||||
ep, has := conntrack.TimerWheel.Purge()
|
||||
if has {
|
||||
f.evict(ep)
|
||||
}
|
||||
}
|
||||
|
||||
c, ok := conntrack.Conns[fp]
|
||||
|
||||
if !ok {
|
||||
conntrack.Unlock()
|
||||
return false
|
||||
|
||||
@@ -12,29 +12,29 @@ require (
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/kardianos/service v1.2.4
|
||||
github.com/miekg/dns v1.1.68
|
||||
github.com/miekg/dns v1.1.70
|
||||
github.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b
|
||||
github.com/nbrownus/go-metrics-prometheus v0.0.0-20210712211119-974a6260965f
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.45.0
|
||||
golang.org/x/crypto v0.47.0
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090
|
||||
golang.org/x/net v0.47.0
|
||||
golang.org/x/sync v0.18.0
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/term v0.37.0
|
||||
golang.org/x/net v0.49.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sys v0.40.0
|
||||
golang.org/x/term v0.39.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
google.golang.org/protobuf v1.36.10
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -49,7 +49,7 @@ require (
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
)
|
||||
|
||||
@@ -83,8 +83,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||
github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
|
||||
github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b h1:J/AzCvg5z0Hn1rqZUJjpbzALUmkKX0Zwbc/i4fw7Sfk=
|
||||
github.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -131,8 +131,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=
|
||||
@@ -162,16 +162,16 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -182,8 +182,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -191,8 +191,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -206,34 +206,33 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -244,8 +243,8 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -259,5 +258,5 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe h1:fre4i6mv4iBuz5lCMOzHD1rH1ljqHWSICFmZRbbgp3g=
|
||||
gvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU=
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
@@ -166,6 +167,13 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(remoteCert.Certificate.PublicKey(), ci.H.PeerStatic()) {
|
||||
f.l.WithField("from", via).
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||
WithField("cert", remoteCert).Info("public key mismatch between certificate and handshake")
|
||||
return
|
||||
}
|
||||
|
||||
if remoteCert.Certificate.Version() != ci.myCert.Version() {
|
||||
// We started off using the wrong certificate version, lets see if we can match the version that was sent to us
|
||||
myCertOtherVersion := cs.getCertificate(remoteCert.Certificate.Version())
|
||||
@@ -535,6 +543,12 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
e.Info("Invalid certificate from host")
|
||||
return true
|
||||
}
|
||||
if !bytes.Equal(remoteCert.Certificate.PublicKey(), ci.H.PeerStatic()) {
|
||||
f.l.WithField("from", via).
|
||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||
WithField("cert", remoteCert).Info("public key mismatch between certificate and handshake")
|
||||
return true
|
||||
}
|
||||
|
||||
if len(remoteCert.Certificate.Networks()) == 0 {
|
||||
f.l.WithError(err).WithField("from", via).
|
||||
|
||||
@@ -2,6 +2,8 @@ package nebula
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/firewall"
|
||||
@@ -11,6 +13,258 @@ import (
|
||||
"github.com/slackhq/nebula/routing"
|
||||
)
|
||||
|
||||
// preEncryptionPacket holds packet data before batch encryption
|
||||
type preEncryptionPacket struct {
|
||||
hostinfo *HostInfo
|
||||
ci *ConnectionState
|
||||
packet []byte
|
||||
out []byte
|
||||
}
|
||||
|
||||
// Pool for preEncryptionBatch slices to reduce allocations
|
||||
var preEncryptionBatchPool = sync.Pool{
|
||||
New: func() any {
|
||||
// Pre-allocate with reasonable capacity
|
||||
batch := make([]preEncryptionPacket, 0, 128)
|
||||
return &batch
|
||||
},
|
||||
}
|
||||
|
||||
// consumeInsidePackets processes multiple packets in a batch for improved performance
|
||||
// packets: slice of packet buffers to process
|
||||
// sizes: slice of packet sizes
|
||||
// count: number of packets to process
|
||||
// outs: slice of output buffers (one per packet) with virtio headroom
|
||||
// q: queue index
|
||||
// localCache: firewall conntrack cache
|
||||
// batchPackets: pre-allocated slice for accumulating encrypted packets
|
||||
// batchAddrs: pre-allocated slice for accumulating destination addresses
|
||||
func (f *Interface) consumeInsidePackets(packets [][]byte, sizes []int, count int, outs [][]byte, nb []byte, q int, localCache firewall.ConntrackCache, batchPackets *[][]byte, batchAddrs *[]netip.AddrPort) {
|
||||
// Reusable per-packet state
|
||||
fwPacket := &firewall.Packet{}
|
||||
|
||||
// Reset batch accumulation slices (reuse capacity)
|
||||
*batchPackets = (*batchPackets)[:0]
|
||||
*batchAddrs = (*batchAddrs)[:0]
|
||||
|
||||
// Collect packets for batched encryption
|
||||
preEncryptionBatch := make([]preEncryptionPacket, 0, count)
|
||||
|
||||
// Process each packet in the batch
|
||||
for i := 0; i < count; i++ {
|
||||
packet := packets[i][:sizes[i]]
|
||||
out := outs[i]
|
||||
|
||||
// Inline the consumeInsidePacket logic for better performance
|
||||
err := 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)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Ignore local broadcast packets
|
||||
if f.dropLocalBroadcast {
|
||||
if f.myBroadcastAddrsTable.Contains(fwPacket.RemoteAddr) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if f.myVpnAddrsTable.Contains(fwPacket.RemoteAddr) {
|
||||
// Immediately forward packets from self to self.
|
||||
if immediatelyForwardToSelf {
|
||||
_, err := f.readers[q].Write(packet)
|
||||
if err != nil {
|
||||
f.l.WithError(err).Error("Failed to forward to tun")
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Ignore multicast packets
|
||||
if f.dropMulticast && fwPacket.RemoteAddr.IsMulticast() {
|
||||
continue
|
||||
}
|
||||
|
||||
hostinfo, ready := f.getOrHandshakeConsiderRouting(fwPacket, func(hh *HandshakeHostInfo) {
|
||||
hh.cachePacket(f.l, header.Message, 0, packet, f.sendMessageNow, f.cachedPacketMetrics)
|
||||
})
|
||||
|
||||
if hostinfo == nil {
|
||||
f.rejectInside(packet, out, q)
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
f.l.WithField("vpnAddr", fwPacket.RemoteAddr).
|
||||
WithField("fwPacket", fwPacket).
|
||||
Debugln("dropping outbound packet, vpnAddr not in our vpn networks or in unsafe networks")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !ready {
|
||||
continue
|
||||
}
|
||||
|
||||
dropReason := f.firewall.Drop(*fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)
|
||||
if dropReason != nil {
|
||||
f.rejectInside(packet, out, q)
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
hostinfo.logger(f.l).
|
||||
WithField("fwPacket", fwPacket).
|
||||
WithField("reason", dropReason).
|
||||
Debugln("dropping outbound packet")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Prepare packet for batch encryption
|
||||
ci := hostinfo.ConnectionState
|
||||
if ci.eKey == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this needs relay - if so, send immediately and skip batching
|
||||
useRelay := !hostinfo.remote.IsValid()
|
||||
if useRelay {
|
||||
// Handle relay sends individually (less common path)
|
||||
f.sendNoMetrics(header.Message, 0, ci, hostinfo, netip.AddrPort{}, packet, nb, out, q)
|
||||
continue
|
||||
}
|
||||
|
||||
// Collect for batched encryption
|
||||
preEncryptionBatch = append(preEncryptionBatch, preEncryptionPacket{
|
||||
hostinfo: hostinfo,
|
||||
ci: ci,
|
||||
packet: packet,
|
||||
out: out,
|
||||
})
|
||||
}
|
||||
|
||||
// BATCH ENCRYPTION: Process all collected packets
|
||||
if len(preEncryptionBatch) > 0 {
|
||||
f.encryptBatch(preEncryptionBatch, nb, batchPackets, batchAddrs)
|
||||
}
|
||||
|
||||
// Send all accumulated packets in one batch
|
||||
if len(*batchPackets) > 0 {
|
||||
batchSize := len(*batchPackets)
|
||||
n, err := f.writers[q].WriteMulti(*batchPackets, *batchAddrs)
|
||||
if err != nil {
|
||||
f.l.WithError(err).WithField("sent", n).WithField("total", batchSize).Error("Failed to send batch")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encryptBatch processes multiple packets, grouping by ConnectionState to reduce lock acquisitions
|
||||
func (f *Interface) encryptBatch(batch []preEncryptionPacket, nb []byte, batchPackets *[][]byte, batchAddrs *[]netip.AddrPort) {
|
||||
lockStart := time.Now()
|
||||
lockAcquisitions := int64(0)
|
||||
|
||||
if noiseutil.EncryptLockNeeded {
|
||||
// Group packets by ConnectionState to minimize lock acquisitions
|
||||
// Process packets in order but batch lock acquisitions per CI
|
||||
var currentCI *ConnectionState
|
||||
var lockHeld bool
|
||||
|
||||
for i := range batch {
|
||||
ci := batch[i].ci
|
||||
hostinfo := batch[i].hostinfo
|
||||
|
||||
// Validate packet data to prevent nil pointer dereference
|
||||
if ci == nil || hostinfo == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Switch locks if we're moving to a different ConnectionState
|
||||
if ci != currentCI {
|
||||
if lockHeld {
|
||||
currentCI.writeLock.Unlock()
|
||||
}
|
||||
ci.writeLock.Lock()
|
||||
lockAcquisitions++
|
||||
currentCI = ci
|
||||
lockHeld = true
|
||||
}
|
||||
|
||||
c := ci.messageCounter.Add(1)
|
||||
out := header.Encode(batch[i].out, header.Version, header.Message, 0, hostinfo.remoteIndexId, c)
|
||||
f.connectionManager.Out(hostinfo)
|
||||
|
||||
// Query lighthouse if needed
|
||||
if hostinfo.lastRebindCount != f.rebindCount {
|
||||
f.lightHouse.QueryServer(hostinfo.vpnAddrs[0])
|
||||
hostinfo.lastRebindCount = f.rebindCount
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).Debug("Lighthouse update triggered for punch due to rebind counter")
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
out, err = ci.eKey.EncryptDanger(out, out, batch[i].packet, c, nb)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).
|
||||
WithField("counter", c).
|
||||
Error("Failed to encrypt outgoing packet")
|
||||
continue
|
||||
}
|
||||
|
||||
// Add to output batches
|
||||
*batchPackets = append(*batchPackets, out)
|
||||
*batchAddrs = append(*batchAddrs, hostinfo.remote)
|
||||
}
|
||||
|
||||
// Release final lock
|
||||
if lockHeld {
|
||||
currentCI.writeLock.Unlock()
|
||||
}
|
||||
} else {
|
||||
// No locks needed - process directly
|
||||
for i := range batch {
|
||||
ci := batch[i].ci
|
||||
hostinfo := batch[i].hostinfo
|
||||
|
||||
// Validate packet data to prevent nil pointer dereference
|
||||
if ci == nil || hostinfo == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
c := ci.messageCounter.Add(1)
|
||||
out := header.Encode(batch[i].out, header.Version, header.Message, 0, hostinfo.remoteIndexId, c)
|
||||
f.connectionManager.Out(hostinfo)
|
||||
|
||||
// Query lighthouse if needed
|
||||
if hostinfo.lastRebindCount != f.rebindCount {
|
||||
f.lightHouse.QueryServer(hostinfo.vpnAddrs[0])
|
||||
hostinfo.lastRebindCount = f.rebindCount
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).Debug("Lighthouse update triggered for punch due to rebind counter")
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
out, err = ci.eKey.EncryptDanger(out, out, batch[i].packet, c, nb)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).
|
||||
WithField("counter", c).
|
||||
Error("Failed to encrypt outgoing packet")
|
||||
continue
|
||||
}
|
||||
|
||||
// Add to output batches
|
||||
*batchPackets = append(*batchPackets, out)
|
||||
*batchAddrs = append(*batchAddrs, hostinfo.remote)
|
||||
}
|
||||
}
|
||||
|
||||
// Record metrics
|
||||
encryptionTime := time.Since(lockStart)
|
||||
if noiseutil.EncryptLockNeeded {
|
||||
f.batchMetrics.lockAcquisitions.Inc(lockAcquisitions)
|
||||
}
|
||||
f.batchMetrics.encryptionTime.Update(encryptionTime.Nanoseconds())
|
||||
f.batchMetrics.batchSize.Update(int64(len(batch)))
|
||||
}
|
||||
|
||||
func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet, nb, out []byte, q int, localCache firewall.ConntrackCache) {
|
||||
err := newPacket(packet, false, fwPacket)
|
||||
if err != nil {
|
||||
|
||||
+112
-35
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
@@ -22,6 +21,7 @@ import (
|
||||
)
|
||||
|
||||
const mtu = 9001
|
||||
const virtioNetHdrLen = overlay.VirtioNetHdrLen
|
||||
|
||||
type InterfaceConfig struct {
|
||||
HostMap *HostMap
|
||||
@@ -50,6 +50,13 @@ type InterfaceConfig struct {
|
||||
l *logrus.Logger
|
||||
}
|
||||
|
||||
type batchMetrics struct {
|
||||
udpReadSize metrics.Histogram
|
||||
encryptionTime metrics.Histogram // Time spent in encryption (including lock waits)
|
||||
batchSize metrics.Histogram // Dynamic batch sizes being used
|
||||
lockAcquisitions metrics.Counter // Number of lock acquisitions (should be minimal)
|
||||
}
|
||||
|
||||
type Interface struct {
|
||||
hostMap *HostMap
|
||||
outside udp.Conn
|
||||
@@ -77,7 +84,8 @@ type Interface struct {
|
||||
reQueryEvery atomic.Uint32
|
||||
reQueryWait atomic.Int64
|
||||
|
||||
sendRecvErrorConfig sendRecvErrorConfig
|
||||
sendRecvErrorConfig recvErrorConfig
|
||||
acceptRecvErrorConfig recvErrorConfig
|
||||
|
||||
// rebindCount is used to decide if an active tunnel should trigger a punch notification through a lighthouse
|
||||
rebindCount int8
|
||||
@@ -86,11 +94,12 @@ type Interface struct {
|
||||
conntrackCacheTimeout time.Duration
|
||||
|
||||
writers []udp.Conn
|
||||
readers []io.ReadWriteCloser
|
||||
readers []overlay.BatchReadWriter
|
||||
|
||||
metricHandshakes metrics.Histogram
|
||||
messageMetrics *MessageMetrics
|
||||
cachedPacketMetrics *cachedPacketMetrics
|
||||
batchMetrics *batchMetrics
|
||||
|
||||
l *logrus.Logger
|
||||
}
|
||||
@@ -110,34 +119,34 @@ type EncWriter interface {
|
||||
GetCertState() *CertState
|
||||
}
|
||||
|
||||
type sendRecvErrorConfig uint8
|
||||
type recvErrorConfig uint8
|
||||
|
||||
const (
|
||||
sendRecvErrorAlways sendRecvErrorConfig = iota
|
||||
sendRecvErrorNever
|
||||
sendRecvErrorPrivate
|
||||
recvErrorAlways recvErrorConfig = iota
|
||||
recvErrorNever
|
||||
recvErrorPrivate
|
||||
)
|
||||
|
||||
func (s sendRecvErrorConfig) ShouldSendRecvError(endpoint netip.AddrPort) bool {
|
||||
func (s recvErrorConfig) ShouldRecvError(endpoint netip.AddrPort) bool {
|
||||
switch s {
|
||||
case sendRecvErrorPrivate:
|
||||
case recvErrorPrivate:
|
||||
return endpoint.Addr().IsPrivate()
|
||||
case sendRecvErrorAlways:
|
||||
case recvErrorAlways:
|
||||
return true
|
||||
case sendRecvErrorNever:
|
||||
case recvErrorNever:
|
||||
return false
|
||||
default:
|
||||
panic(fmt.Errorf("invalid sendRecvErrorConfig value: %d", s))
|
||||
panic(fmt.Errorf("invalid recvErrorConfig value: %d", s))
|
||||
}
|
||||
}
|
||||
|
||||
func (s sendRecvErrorConfig) String() string {
|
||||
func (s recvErrorConfig) String() string {
|
||||
switch s {
|
||||
case sendRecvErrorAlways:
|
||||
case recvErrorAlways:
|
||||
return "always"
|
||||
case sendRecvErrorNever:
|
||||
case recvErrorNever:
|
||||
return "never"
|
||||
case sendRecvErrorPrivate:
|
||||
case recvErrorPrivate:
|
||||
return "private"
|
||||
default:
|
||||
return fmt.Sprintf("invalid(%d)", s)
|
||||
@@ -177,7 +186,7 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
|
||||
routines: c.routines,
|
||||
version: c.version,
|
||||
writers: make([]udp.Conn, c.routines),
|
||||
readers: make([]io.ReadWriteCloser, c.routines),
|
||||
readers: make([]overlay.BatchReadWriter, c.routines),
|
||||
myVpnNetworks: cs.myVpnNetworks,
|
||||
myVpnNetworksTable: cs.myVpnNetworksTable,
|
||||
myVpnAddrs: cs.myVpnAddrs,
|
||||
@@ -193,6 +202,12 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
|
||||
sent: metrics.GetOrRegisterCounter("hostinfo.cached_packets.sent", nil),
|
||||
dropped: metrics.GetOrRegisterCounter("hostinfo.cached_packets.dropped", nil),
|
||||
},
|
||||
batchMetrics: &batchMetrics{
|
||||
udpReadSize: metrics.GetOrRegisterHistogram("batch.udp_read_size", nil, metrics.NewUniformSample(1024)),
|
||||
encryptionTime: metrics.GetOrRegisterHistogram("batch.encryption_time_ns", nil, metrics.NewUniformSample(1024)),
|
||||
batchSize: metrics.GetOrRegisterHistogram("batch.size", nil, metrics.NewUniformSample(1024)),
|
||||
lockAcquisitions: metrics.GetOrRegisterCounter("batch.lock_acquisitions", nil),
|
||||
},
|
||||
|
||||
l: c.l,
|
||||
}
|
||||
@@ -232,7 +247,7 @@ func (f *Interface) activate() {
|
||||
metrics.GetOrRegisterGauge("routines", nil).Update(int64(f.routines))
|
||||
|
||||
// Prepare n tun queues
|
||||
var reader io.ReadWriteCloser = f.inside
|
||||
var reader overlay.BatchReadWriter = f.inside
|
||||
for i := 0; i < f.routines; i++ {
|
||||
if i > 0 {
|
||||
reader, err = f.inside.NewMultiQueueReader()
|
||||
@@ -273,45 +288,75 @@ func (f *Interface) listenOut(i int) {
|
||||
|
||||
ctCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
|
||||
lhh := f.lightHouse.NewRequestHandler()
|
||||
plaintext := make([]byte, udp.MTU)
|
||||
|
||||
// Pre-allocate output buffers for batch processing
|
||||
batchSize := li.BatchSize()
|
||||
outs := make([][]byte, batchSize)
|
||||
for idx := range outs {
|
||||
// Allocate full buffer with virtio header space
|
||||
outs[idx] = make([]byte, virtioNetHdrLen, virtioNetHdrLen+udp.MTU)
|
||||
}
|
||||
|
||||
h := &header.H{}
|
||||
fwPacket := &firewall.Packet{}
|
||||
nb := make([]byte, 12, 12)
|
||||
nb := make([]byte, 12)
|
||||
|
||||
li.ListenOutBatch(func(addrs []netip.AddrPort, payloads [][]byte, count int) {
|
||||
f.readOutsidePacketsBatch(addrs, payloads, count, outs[:count], nb, i, h, fwPacket, lhh, ctCache.Get(f.l))
|
||||
|
||||
li.ListenOut(func(fromUdpAddr netip.AddrPort, payload []byte) {
|
||||
f.readOutsidePackets(ViaSender{UdpAddr: fromUdpAddr}, plaintext[:0], payload, h, fwPacket, lhh, nb, i, ctCache.Get(f.l))
|
||||
})
|
||||
}
|
||||
|
||||
func (f *Interface) listenIn(reader io.ReadWriteCloser, i int) {
|
||||
func (f *Interface) listenIn(reader overlay.BatchReadWriter, i int) {
|
||||
runtime.LockOSThread()
|
||||
|
||||
packet := make([]byte, mtu)
|
||||
out := make([]byte, mtu)
|
||||
fwPacket := &firewall.Packet{}
|
||||
nb := make([]byte, 12, 12)
|
||||
batchSize := reader.BatchSize()
|
||||
|
||||
// Allocate buffers for batch reading
|
||||
bufs := make([][]byte, batchSize)
|
||||
for idx := range bufs {
|
||||
bufs[idx] = make([]byte, mtu)
|
||||
}
|
||||
sizes := make([]int, batchSize)
|
||||
|
||||
// Allocate output buffers for batch processing (one per packet)
|
||||
// Each has virtio header headroom to avoid copies on write
|
||||
outs := make([][]byte, batchSize)
|
||||
for idx := range outs {
|
||||
outBuf := make([]byte, virtioNetHdrLen+mtu)
|
||||
outs[idx] = outBuf[virtioNetHdrLen:] // Slice starting after headroom
|
||||
}
|
||||
|
||||
// Pre-allocate batch accumulation buffers for sending
|
||||
batchPackets := make([][]byte, 0, batchSize)
|
||||
batchAddrs := make([]netip.AddrPort, 0, batchSize)
|
||||
|
||||
// Pre-allocate nonce buffer (reused for all encryptions)
|
||||
nb := make([]byte, 12)
|
||||
|
||||
conntrackCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
|
||||
|
||||
for {
|
||||
n, err := reader.Read(packet)
|
||||
n, err := reader.BatchRead(bufs, sizes)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrClosed) && f.closed.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
f.l.WithError(err).Error("Error while reading outbound packet")
|
||||
f.l.WithError(err).Error("Error while batch reading outbound packets")
|
||||
// This only seems to happen when something fatal happens to the fd, so exit.
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
f.consumeInsidePacket(packet[:n], fwPacket, nb, out, i, conntrackCache.Get(f.l))
|
||||
// Process all packets in the batch at once
|
||||
f.consumeInsidePackets(bufs, sizes, n, outs, nb, i, conntrackCache.Get(f.l), &batchPackets, &batchAddrs)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Interface) RegisterConfigChangeCallbacks(c *config.C) {
|
||||
c.RegisterReloadCallback(f.reloadFirewall)
|
||||
c.RegisterReloadCallback(f.reloadSendRecvError)
|
||||
c.RegisterReloadCallback(f.reloadAcceptRecvError)
|
||||
c.RegisterReloadCallback(f.reloadDisconnectInvalid)
|
||||
c.RegisterReloadCallback(f.reloadMisc)
|
||||
|
||||
@@ -375,16 +420,16 @@ func (f *Interface) reloadSendRecvError(c *config.C) {
|
||||
|
||||
switch stringValue {
|
||||
case "always":
|
||||
f.sendRecvErrorConfig = sendRecvErrorAlways
|
||||
f.sendRecvErrorConfig = recvErrorAlways
|
||||
case "never":
|
||||
f.sendRecvErrorConfig = sendRecvErrorNever
|
||||
f.sendRecvErrorConfig = recvErrorNever
|
||||
case "private":
|
||||
f.sendRecvErrorConfig = sendRecvErrorPrivate
|
||||
f.sendRecvErrorConfig = recvErrorPrivate
|
||||
default:
|
||||
if c.GetBool("listen.send_recv_error", true) {
|
||||
f.sendRecvErrorConfig = sendRecvErrorAlways
|
||||
f.sendRecvErrorConfig = recvErrorAlways
|
||||
} else {
|
||||
f.sendRecvErrorConfig = sendRecvErrorNever
|
||||
f.sendRecvErrorConfig = recvErrorNever
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,6 +438,30 @@ func (f *Interface) reloadSendRecvError(c *config.C) {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Interface) reloadAcceptRecvError(c *config.C) {
|
||||
if c.InitialLoad() || c.HasChanged("listen.accept_recv_error") {
|
||||
stringValue := c.GetString("listen.accept_recv_error", "always")
|
||||
|
||||
switch stringValue {
|
||||
case "always":
|
||||
f.acceptRecvErrorConfig = recvErrorAlways
|
||||
case "never":
|
||||
f.acceptRecvErrorConfig = recvErrorNever
|
||||
case "private":
|
||||
f.acceptRecvErrorConfig = recvErrorPrivate
|
||||
default:
|
||||
if c.GetBool("listen.accept_recv_error", true) {
|
||||
f.acceptRecvErrorConfig = recvErrorAlways
|
||||
} else {
|
||||
f.acceptRecvErrorConfig = recvErrorNever
|
||||
}
|
||||
}
|
||||
|
||||
f.l.WithField("acceptRecvError", f.acceptRecvErrorConfig.String()).
|
||||
Info("Loaded accept_recv_error config")
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Interface) reloadMisc(c *config.C) {
|
||||
if c.HasChanged("counters.try_promote") {
|
||||
n := c.GetUint32("counters.try_promote", defaultPromoteEvery)
|
||||
@@ -464,6 +533,14 @@ func (f *Interface) Close() error {
|
||||
f.l.WithError(err).Error("Error while closing udp socket")
|
||||
}
|
||||
}
|
||||
for i, r := range f.readers {
|
||||
if i == 0 {
|
||||
continue // f.readers[0] is f.inside, which we want to save for last
|
||||
}
|
||||
if err := r.Close(); err != nil {
|
||||
f.l.WithError(err).Error("Error while closing tun reader")
|
||||
}
|
||||
}
|
||||
|
||||
// Release the tun device
|
||||
return f.inside.Close()
|
||||
|
||||
+8
-9
@@ -1,7 +1,6 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
@@ -42,14 +41,14 @@ func Test_lhStaticMapping(t *testing.T) {
|
||||
c := config.NewC(l)
|
||||
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1}}
|
||||
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
||||
_, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
_, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
lh2 := "10.128.0.3"
|
||||
c = config.NewC(l)
|
||||
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1, lh2}}
|
||||
c.Settings["static_host_map"] = map[string]any{lh1: []any{"100.1.1.1:4242"}}
|
||||
_, err = NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
_, err = NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
require.EqualError(t, err, "lighthouse 10.128.0.3 does not have a static_host_map entry")
|
||||
}
|
||||
|
||||
@@ -71,7 +70,7 @@ func TestReloadLighthouseInterval(t *testing.T) {
|
||||
}
|
||||
|
||||
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
require.NoError(t, err)
|
||||
lh.ifce = &mockEncWriter{}
|
||||
|
||||
@@ -99,7 +98,7 @@ func BenchmarkLighthouseHandleRequest(b *testing.B) {
|
||||
}
|
||||
|
||||
c := config.NewC(l)
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(b.Context(), l, c, cs, nil, nil)
|
||||
require.NoError(b, err)
|
||||
|
||||
hAddr := netip.MustParseAddrPort("4.5.6.7:12345")
|
||||
@@ -202,7 +201,7 @@ func TestLighthouse_Memory(t *testing.T) {
|
||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||
myVpnNetworksTable: nt,
|
||||
}
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
lh.ifce = &mockEncWriter{}
|
||||
require.NoError(t, err)
|
||||
lhh := lh.NewRequestHandler()
|
||||
@@ -288,7 +287,7 @@ func TestLighthouse_reload(t *testing.T) {
|
||||
myVpnNetworksTable: nt,
|
||||
}
|
||||
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
nc := map[string]any{
|
||||
@@ -523,7 +522,7 @@ func TestLighthouse_Dont_Delete_Static_Hosts(t *testing.T) {
|
||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||
myVpnNetworksTable: nt,
|
||||
}
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
require.NoError(t, err)
|
||||
lh.ifce = &mockEncWriter{}
|
||||
|
||||
@@ -589,7 +588,7 @@ func TestLighthouse_DeletesWork(t *testing.T) {
|
||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||
myVpnNetworksTable: nt,
|
||||
}
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
require.NoError(t, err)
|
||||
lh.ifce = &mockEncWriter{}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
||||
|
||||
for i := 0; i < routines; i++ {
|
||||
l.Infof("listening on %v", netip.AddrPortFrom(listenHost, uint16(port)))
|
||||
udpServer, err := udp.NewListener(l, listenHost, port, routines > 1, c.GetInt("listen.batch", 64))
|
||||
udpServer, err := udp.NewListener(l, listenHost, port, routines > 1, c.GetInt("listen.batch", 128))
|
||||
if err != nil {
|
||||
return nil, util.NewContextualError("Failed to open udp listener", m{"queue": i}, err)
|
||||
}
|
||||
@@ -265,6 +265,7 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
||||
ifce.RegisterConfigChangeCallbacks(c)
|
||||
ifce.reloadDisconnectInvalid(c)
|
||||
ifce.reloadSendRecvError(c)
|
||||
ifce.reloadAcceptRecvError(c)
|
||||
|
||||
handshakeManager.f = ifce
|
||||
go handshakeManager.Run(ctx)
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ const EncryptLockNeeded = true
|
||||
// NewGCMTLS is no longer exposed in go1.19+, so we need to link it in
|
||||
// See: https://github.com/golang/go/issues/56326
|
||||
//
|
||||
// NewGCMTLS is the internal method used with boringcrypto that provices a
|
||||
// NewGCMTLS is the internal method used with boringcrypto that provides a
|
||||
// validated mode of AES-GCM which enforces the nonce is strictly
|
||||
// monotonically increasing. This is the TLS 1.2 specification for nonce
|
||||
// generation (which also matches the method used by the Noise Protocol)
|
||||
|
||||
+122
-8
@@ -102,7 +102,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
||||
relay: relay,
|
||||
IsRelayed: true,
|
||||
}
|
||||
f.readOutsidePackets(via, out[:0], signedPayload, h, fwPacket, lhf, nb, q, localCache)
|
||||
f.readOutsidePackets(via, out[:virtioNetHdrLen], signedPayload, h, fwPacket, lhf, nb, q, localCache)
|
||||
return
|
||||
case ForwardingType:
|
||||
// Find the target HostInfo relay object
|
||||
@@ -145,7 +145,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
||||
}
|
||||
|
||||
//TODO: assert via is not relayed
|
||||
lhf.HandleRequest(via.UdpAddr, hostinfo.vpnAddrs, d, f)
|
||||
lhf.HandleRequest(via.UdpAddr, hostinfo.vpnAddrs, d[virtioNetHdrLen:], f)
|
||||
|
||||
// Fallthrough to the bottom to record incoming traffic
|
||||
|
||||
@@ -167,7 +167,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
||||
// This testRequest might be from TryPromoteBest, so we should roam
|
||||
// to the new IP address before responding
|
||||
f.handleHostRoaming(hostinfo, via)
|
||||
f.send(header.Test, header.TestReply, ci, hostinfo, d, nb, out)
|
||||
f.send(header.Test, header.TestReply, ci, hostinfo, d[virtioNetHdrLen:], nb, out)
|
||||
}
|
||||
|
||||
// Fallthrough to the bottom to record incoming traffic
|
||||
@@ -210,7 +210,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
||||
return
|
||||
}
|
||||
|
||||
f.relayManager.HandleControlMsg(hostinfo, d, f)
|
||||
f.relayManager.HandleControlMsg(hostinfo, d[virtioNetHdrLen:], f)
|
||||
|
||||
default:
|
||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||
@@ -481,9 +481,11 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
|
||||
return false
|
||||
}
|
||||
|
||||
err = newPacket(out, true, fwPacket)
|
||||
packetData := out[virtioNetHdrLen:]
|
||||
|
||||
err = newPacket(packetData, true, fwPacket)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).WithField("packet", out).
|
||||
hostinfo.logger(f.l).WithError(err).WithField("packet", packetData).
|
||||
Warnf("Error while validating inbound packet")
|
||||
return false
|
||||
}
|
||||
@@ -498,7 +500,7 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
|
||||
if dropReason != nil {
|
||||
// NOTE: We give `packet` as the `out` here since we already decrypted from it and we don't need it anymore
|
||||
// This gives us a buffer to build the reject packet in
|
||||
f.rejectOutside(out, hostinfo.ConnectionState, hostinfo, nb, packet, q)
|
||||
f.rejectOutside(packetData, hostinfo.ConnectionState, hostinfo, nb, packet, q)
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
hostinfo.logger(f.l).WithField("fwPacket", fwPacket).
|
||||
WithField("reason", dropReason).
|
||||
@@ -516,7 +518,7 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
|
||||
}
|
||||
|
||||
func (f *Interface) maybeSendRecvError(endpoint netip.AddrPort, index uint32) {
|
||||
if f.sendRecvErrorConfig.ShouldSendRecvError(endpoint) {
|
||||
if f.sendRecvErrorConfig.ShouldRecvError(endpoint) {
|
||||
f.sendRecvError(endpoint, index)
|
||||
}
|
||||
}
|
||||
@@ -534,6 +536,13 @@ func (f *Interface) sendRecvError(endpoint netip.AddrPort, index uint32) {
|
||||
}
|
||||
|
||||
func (f *Interface) handleRecvError(addr netip.AddrPort, h *header.H) {
|
||||
if !f.acceptRecvErrorConfig.ShouldRecvError(addr) {
|
||||
f.l.WithField("index", h.RemoteIndex).
|
||||
WithField("udpAddr", addr).
|
||||
Debug("Recv error received, ignoring")
|
||||
return
|
||||
}
|
||||
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
f.l.WithField("index", h.RemoteIndex).
|
||||
WithField("udpAddr", addr).
|
||||
@@ -555,3 +564,108 @@ func (f *Interface) handleRecvError(addr netip.AddrPort, h *header.H) {
|
||||
// We also delete it from pending hostmap to allow for fast reconnect.
|
||||
f.handshakeManager.DeleteHostInfo(hostinfo)
|
||||
}
|
||||
|
||||
// readOutsidePacketsBatch processes multiple packets received from UDP in a batch
|
||||
// and writes all successfully decrypted packets to TUN in a single operation
|
||||
func (f *Interface) readOutsidePacketsBatch(addrs []netip.AddrPort, payloads [][]byte, count int, outs [][]byte, nb []byte, q int, h *header.H, fwPacket *firewall.Packet, lhf *LightHouseHandler, localCache firewall.ConntrackCache) {
|
||||
// Pre-allocate slice for accumulating successful decryptions
|
||||
tunPackets := make([][]byte, 0, count)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
payload := payloads[i]
|
||||
addr := addrs[i]
|
||||
out := outs[i]
|
||||
|
||||
// Parse header
|
||||
err := h.Parse(payload)
|
||||
if err != nil {
|
||||
if len(payload) > 1 {
|
||||
f.l.WithField("packet", payload).Infof("Error while parsing inbound packet from %s: %s", addr, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if addr.IsValid() {
|
||||
if f.myVpnNetworksTable.Contains(addr.Addr()) {
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
f.l.WithField("udpAddr", addr).Debug("Refusing to process double encrypted packet")
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var hostinfo *HostInfo
|
||||
if h.Type == header.Message && h.Subtype == header.MessageRelay {
|
||||
hostinfo = f.hostMap.QueryRelayIndex(h.RemoteIndex)
|
||||
} else {
|
||||
hostinfo = f.hostMap.QueryIndex(h.RemoteIndex)
|
||||
}
|
||||
|
||||
var ci *ConnectionState
|
||||
if hostinfo != nil {
|
||||
ci = hostinfo.ConnectionState
|
||||
}
|
||||
|
||||
switch h.Type {
|
||||
case header.Message:
|
||||
if !f.handleEncrypted(ci, ViaSender{UdpAddr: addr}, h) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch h.Subtype {
|
||||
case header.MessageNone:
|
||||
// Decrypt packet
|
||||
out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, payload[:header.Len], payload[header.Len:], h.MessageCounter, nb)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).Error("Failed to decrypt packet")
|
||||
continue
|
||||
}
|
||||
|
||||
packetData := out[virtioNetHdrLen:]
|
||||
|
||||
err = newPacket(packetData, true, fwPacket)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).WithField("packet", packetData).Warnf("Error while validating inbound packet")
|
||||
continue
|
||||
}
|
||||
|
||||
if !hostinfo.ConnectionState.window.Update(f.l, h.MessageCounter) {
|
||||
hostinfo.logger(f.l).WithField("fwPacket", fwPacket).Debugln("dropping out of window packet")
|
||||
continue
|
||||
}
|
||||
|
||||
dropReason := f.firewall.Drop(*fwPacket, true, hostinfo, f.pki.GetCAPool(), localCache)
|
||||
if dropReason != nil {
|
||||
f.rejectOutside(packetData, hostinfo.ConnectionState, hostinfo, nb, payload, q)
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
hostinfo.logger(f.l).WithField("fwPacket", fwPacket).WithField("reason", dropReason).Debugln("dropping inbound packet")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
f.connectionManager.In(hostinfo)
|
||||
// Add to batch for TUN write
|
||||
tunPackets = append(tunPackets, out)
|
||||
|
||||
case header.MessageRelay:
|
||||
// Skip relay packets in batch mode for now (less common path)
|
||||
f.readOutsidePackets(ViaSender{UdpAddr: addr}, out[:virtioNetHdrLen], payload, h, fwPacket, lhf, nb, q, localCache)
|
||||
|
||||
default:
|
||||
hostinfo.logger(f.l).Debugf("unexpected message subtype %d", h.Subtype)
|
||||
}
|
||||
|
||||
default:
|
||||
// Handle non-Message types using single-packet path
|
||||
f.readOutsidePackets(ViaSender{UdpAddr: addr}, out[:virtioNetHdrLen], payload, h, fwPacket, lhf, nb, q, localCache)
|
||||
}
|
||||
}
|
||||
|
||||
if len(tunPackets) > 0 {
|
||||
n, err := f.readers[q].WriteBatch(tunPackets, virtioNetHdrLen)
|
||||
if err != nil {
|
||||
f.l.WithError(err).WithField("sent", n).WithField("total", len(tunPackets)).Error("Failed to batch write to tun")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+16
-2
@@ -7,12 +7,26 @@ import (
|
||||
"github.com/slackhq/nebula/routing"
|
||||
)
|
||||
|
||||
type Device interface {
|
||||
// BatchReadWriter extends io.ReadWriteCloser with batch I/O operations
|
||||
type BatchReadWriter interface {
|
||||
io.ReadWriteCloser
|
||||
|
||||
// BatchRead reads multiple packets at once
|
||||
BatchRead(bufs [][]byte, sizes []int) (int, error)
|
||||
|
||||
// WriteBatch writes multiple packets at once
|
||||
WriteBatch(bufs [][]byte, offset int) (int, error)
|
||||
|
||||
// BatchSize returns the optimal batch size for this device
|
||||
BatchSize() int
|
||||
}
|
||||
|
||||
type Device interface {
|
||||
BatchReadWriter
|
||||
Activate() error
|
||||
Networks() []netip.Prefix
|
||||
Name() string
|
||||
RoutesFor(netip.Addr) routing.Gateways
|
||||
SupportsMultiqueue() bool
|
||||
NewMultiQueueReader() (io.ReadWriteCloser, error)
|
||||
NewMultiQueueReader() (BatchReadWriter, error)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,16 @@ import (
|
||||
)
|
||||
|
||||
const DefaultMTU = 1300
|
||||
const VirtioNetHdrLen = 10 // Size of virtio_net_hdr structure
|
||||
|
||||
type NameError struct {
|
||||
Name string
|
||||
Underlying error
|
||||
}
|
||||
|
||||
func (e *NameError) Error() string {
|
||||
return fmt.Sprintf("could not set tun device name: %s because %s", e.Name, e.Underlying)
|
||||
}
|
||||
|
||||
// TODO: We may be able to remove routines
|
||||
type DeviceFactory func(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error)
|
||||
|
||||
+24
-1
@@ -99,6 +99,29 @@ func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (t *tun) NewMultiQueueReader() (BatchReadWriter, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for android")
|
||||
}
|
||||
|
||||
func (t *tun) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
n, err := t.Read(bufs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (t *tun) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
for i, buf := range bufs {
|
||||
_, err := t.Write(buf[offset:])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
func (t *tun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
+27
-1
@@ -553,6 +553,32 @@ func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (t *tun) NewMultiQueueReader() (BatchReadWriter, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for darwin")
|
||||
}
|
||||
|
||||
// BatchRead reads a single packet (batch size 1 for non-Linux platforms)
|
||||
func (t *tun) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
n, err := t.Read(bufs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// WriteBatch writes packets individually (no batching for non-Linux platforms)
|
||||
func (t *tun) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
for i, buf := range bufs {
|
||||
_, err := t.Write(buf[offset:])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
// BatchSize returns 1 for non-Linux platforms (no batching)
|
||||
func (t *tun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
+27
-1
@@ -109,10 +109,36 @@ func (t *disabledTun) SupportsMultiqueue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *disabledTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (t *disabledTun) NewMultiQueueReader() (BatchReadWriter, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// BatchRead reads a single packet (batch size 1 for disabled tun)
|
||||
func (t *disabledTun) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
n, err := t.Read(bufs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// WriteBatch writes packets individually (no batching for disabled tun)
|
||||
func (t *disabledTun) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
for i, buf := range bufs {
|
||||
_, err := t.Write(buf[offset:])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
// BatchSize returns 1 for disabled tun (no batching)
|
||||
func (t *disabledTun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (t *disabledTun) Close() error {
|
||||
if t.read != nil {
|
||||
close(t.read)
|
||||
|
||||
+28
-3
@@ -7,7 +7,6 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
@@ -266,7 +265,7 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
|
||||
}
|
||||
|
||||
// Set the device name
|
||||
ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
|
||||
_ = ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
|
||||
}
|
||||
|
||||
t := &tun{
|
||||
@@ -454,10 +453,36 @@ func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (t *tun) NewMultiQueueReader() (BatchReadWriter, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for freebsd")
|
||||
}
|
||||
|
||||
// BatchRead reads a single packet (batch size 1 for FreeBSD)
|
||||
func (t *tun) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
n, err := t.Read(bufs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// WriteBatch writes packets individually (no batching for FreeBSD)
|
||||
func (t *tun) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
for i, buf := range bufs {
|
||||
_, err := t.Write(buf[offset:])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
// BatchSize returns 1 for FreeBSD (no batching)
|
||||
func (t *tun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (t *tun) addRoutes(logErrors bool) error {
|
||||
routes := *t.Routes.Load()
|
||||
for _, r := range routes {
|
||||
|
||||
+24
-1
@@ -155,6 +155,29 @@ func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (t *tun) NewMultiQueueReader() (BatchReadWriter, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for ios")
|
||||
}
|
||||
|
||||
func (t *tun) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
n, err := t.Read(bufs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (t *tun) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
for i, buf := range bufs {
|
||||
_, err := t.Write(buf[offset:])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
func (t *tun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
+256
-38
@@ -9,7 +9,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
@@ -21,10 +21,12 @@ import (
|
||||
"github.com/slackhq/nebula/util"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
wgtun "golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
type tun struct {
|
||||
io.ReadWriteCloser
|
||||
wgDevice wgtun.Device
|
||||
fd int
|
||||
Device string
|
||||
vpnNetworks []netip.Prefix
|
||||
@@ -40,6 +42,11 @@ type tun struct {
|
||||
useSystemRoutes bool
|
||||
useSystemRoutesBufferSize int
|
||||
|
||||
// These are routes learned from `tun.use_system_route_table`
|
||||
// stored here to make it easier to restore them after a reload
|
||||
routesFromSystem map[netip.Prefix]routing.Gateways
|
||||
routesFromSystemLock sync.Mutex
|
||||
|
||||
l *logrus.Logger
|
||||
}
|
||||
|
||||
@@ -65,59 +72,154 @@ type ifreqQLEN struct {
|
||||
pad [8]byte
|
||||
}
|
||||
|
||||
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {
|
||||
file := os.NewFile(uintptr(deviceFd), "/dev/net/tun")
|
||||
// wgDeviceWrapper wraps a wireguard Device to implement io.ReadWriteCloser
|
||||
// This allows multiqueue readers to use the same wireguard Device batching as the main device
|
||||
type wgDeviceWrapper struct {
|
||||
dev wgtun.Device
|
||||
buf []byte // Reusable buffer for single packet reads
|
||||
}
|
||||
|
||||
func (w *wgDeviceWrapper) Read(b []byte) (int, error) {
|
||||
// Use wireguard Device's batch API for single packet
|
||||
bufs := [][]byte{b}
|
||||
sizes := make([]int, 1)
|
||||
n, err := w.dev.Read(bufs, sizes, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return sizes[0], nil
|
||||
}
|
||||
|
||||
func (w *wgDeviceWrapper) Write(b []byte) (int, error) {
|
||||
// Buffer b should have virtio header space (10 bytes) at the beginning
|
||||
// The decrypted packet data starts at offset 10
|
||||
// Pass the full buffer to WireGuard with offset=virtioNetHdrLen
|
||||
bufs := [][]byte{b}
|
||||
n, err := w.dev.Write(bufs, VirtioNetHdrLen)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, io.ErrShortWrite
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (w *wgDeviceWrapper) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
// Pass all buffers to WireGuard's batch write
|
||||
return w.dev.Write(bufs, offset)
|
||||
}
|
||||
|
||||
func (w *wgDeviceWrapper) Close() error {
|
||||
return w.dev.Close()
|
||||
}
|
||||
|
||||
// BatchRead implements batching for multiqueue readers
|
||||
func (w *wgDeviceWrapper) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
// The zero here is offset.
|
||||
return w.dev.Read(bufs, sizes, 0)
|
||||
}
|
||||
|
||||
// BatchSize returns the optimal batch size
|
||||
func (w *wgDeviceWrapper) BatchSize() int {
|
||||
return w.dev.BatchSize()
|
||||
}
|
||||
|
||||
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {
|
||||
wgDev, name, err := wgtun.CreateUnmonitoredTUNFromFD(deviceFd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create TUN from FD: %w", err)
|
||||
}
|
||||
|
||||
file := wgDev.File()
|
||||
t, err := newTunGeneric(c, l, file, vpnNetworks)
|
||||
if err != nil {
|
||||
_ = wgDev.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.Device = "tun0"
|
||||
t.wgDevice = wgDev
|
||||
t.Device = name
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, multiqueue bool) (*tun, error) {
|
||||
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
// If /dev/net/tun doesn't exist, try to create it (will happen in docker)
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll("/dev/net", 0755)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/dev/net/tun doesn't exist, failed to mkdir -p /dev/net: %w", err)
|
||||
}
|
||||
err = unix.Mknod("/dev/net/tun", unix.S_IFCHR|0600, int(unix.Mkdev(10, 200)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create /dev/net/tun: %w", err)
|
||||
}
|
||||
|
||||
fd, err = unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("created /dev/net/tun, but still failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
// Check if /dev/net/tun exists, create if needed (for docker containers)
|
||||
if _, err := os.Stat("/dev/net/tun"); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll("/dev/net", 0755); err != nil {
|
||||
return nil, fmt.Errorf("/dev/net/tun doesn't exist, failed to mkdir -p /dev/net: %w", err)
|
||||
}
|
||||
if err := unix.Mknod("/dev/net/tun", unix.S_IFCHR|0600, int(unix.Mkdev(10, 200))); err != nil {
|
||||
return nil, fmt.Errorf("failed to create /dev/net/tun: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var req ifReq
|
||||
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI)
|
||||
if multiqueue {
|
||||
req.Flags |= unix.IFF_MULTI_QUEUE
|
||||
}
|
||||
copy(req.Name[:], c.GetString("tun.dev", ""))
|
||||
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := strings.Trim(string(req.Name[:]), "\x00")
|
||||
devName := c.GetString("tun.dev", "")
|
||||
mtu := c.GetInt("tun.mtu", DefaultMTU)
|
||||
|
||||
file := os.NewFile(uintptr(fd), "/dev/net/tun")
|
||||
t, err := newTunGeneric(c, l, file, vpnNetworks)
|
||||
// Create TUN device manually to support multiqueue
|
||||
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var req ifReq
|
||||
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI | unix.IFF_VNET_HDR)
|
||||
if multiqueue {
|
||||
req.Flags |= unix.IFF_MULTI_QUEUE
|
||||
}
|
||||
copy(req.Name[:], devName)
|
||||
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
||||
unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set nonblocking
|
||||
if err = unix.SetNonblock(fd, true); err != nil {
|
||||
unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Enable TCP and UDP offload (TSO/GRO) for performance
|
||||
// This allows the kernel to handle segmentation/coalescing
|
||||
const (
|
||||
tunTCPOffloads = unix.TUN_F_CSUM | unix.TUN_F_TSO4 | unix.TUN_F_TSO6
|
||||
tunUDPOffloads = unix.TUN_F_USO4 | unix.TUN_F_USO6
|
||||
)
|
||||
offloads := tunTCPOffloads | tunUDPOffloads
|
||||
if err = unix.IoctlSetInt(fd, unix.TUNSETOFFLOAD, offloads); err != nil {
|
||||
// Log warning but don't fail - offload is optional
|
||||
l.WithError(err).Warn("Failed to enable TUN offload (TSO/GRO), performance may be reduced")
|
||||
}
|
||||
|
||||
file := os.NewFile(uintptr(fd), "/dev/net/tun")
|
||||
|
||||
// Create wireguard device from file descriptor
|
||||
wgDev, err := wgtun.CreateTUNFromFile(file, mtu)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return nil, fmt.Errorf("failed to create TUN from file: %w", err)
|
||||
}
|
||||
|
||||
name, err := wgDev.Name()
|
||||
if err != nil {
|
||||
_ = wgDev.Close()
|
||||
return nil, fmt.Errorf("failed to get TUN device name: %w", err)
|
||||
}
|
||||
|
||||
// file is now owned by wgDev, get a new reference
|
||||
file = wgDev.File()
|
||||
t, err := newTunGeneric(c, l, file, vpnNetworks)
|
||||
if err != nil {
|
||||
_ = wgDev.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.wgDevice = wgDev
|
||||
t.Device = name
|
||||
|
||||
return t, nil
|
||||
@@ -131,6 +233,7 @@ func newTunGeneric(c *config.C, l *logrus.Logger, file *os.File, vpnNetworks []n
|
||||
TXQueueLen: c.GetInt("tun.tx_queue", 500),
|
||||
useSystemRoutes: c.GetBool("tun.use_system_route_table", false),
|
||||
useSystemRoutesBufferSize: c.GetInt("tun.use_system_route_table_buffer_size", 0),
|
||||
routesFromSystem: map[netip.Prefix]routing.Gateways{},
|
||||
l: l,
|
||||
}
|
||||
|
||||
@@ -164,6 +267,13 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Bring along any routes learned from the system route table on reload
|
||||
t.routesFromSystemLock.Lock()
|
||||
for dst, gw := range t.routesFromSystem {
|
||||
routeTree.Insert(dst, gw)
|
||||
}
|
||||
t.routesFromSystemLock.Unlock()
|
||||
|
||||
oldDefaultMTU := t.DefaultMTU
|
||||
oldMaxMTU := t.MaxMTU
|
||||
newDefaultMTU := c.GetInt("tun.mtu", DefaultMTU)
|
||||
@@ -220,22 +330,44 @@ func (t *tun) SupportsMultiqueue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (t *tun) NewMultiQueueReader() (BatchReadWriter, error) {
|
||||
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var req ifReq
|
||||
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI | unix.IFF_MULTI_QUEUE)
|
||||
// MUST match the flags used in newTun - includes IFF_VNET_HDR
|
||||
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI | unix.IFF_VNET_HDR | unix.IFF_MULTI_QUEUE)
|
||||
copy(req.Name[:], t.Device)
|
||||
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
||||
unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set nonblocking mode - CRITICAL for proper netpoller integration
|
||||
if err = unix.SetNonblock(fd, true); err != nil {
|
||||
unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get MTU from main device
|
||||
mtu := t.MaxMTU
|
||||
if mtu == 0 {
|
||||
mtu = DefaultMTU
|
||||
}
|
||||
|
||||
file := os.NewFile(uintptr(fd), "/dev/net/tun")
|
||||
|
||||
return file, nil
|
||||
// Create wireguard Device from the file descriptor (just like the main device)
|
||||
wgDev, err := wgtun.CreateTUNFromFile(file, mtu)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return nil, fmt.Errorf("failed to create multiqueue TUN device: %w", err)
|
||||
}
|
||||
|
||||
// Return a wrapper that uses the wireguard Device for all I/O
|
||||
return &wgDeviceWrapper{dev: wgDev}, nil
|
||||
}
|
||||
|
||||
func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
|
||||
@@ -243,7 +375,68 @@ func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *tun) Read(b []byte) (int, error) {
|
||||
if t.wgDevice != nil {
|
||||
// Use wireguard device which handles virtio headers internally
|
||||
bufs := [][]byte{b}
|
||||
sizes := make([]int, 1)
|
||||
n, err := t.wgDevice.Read(bufs, sizes, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return sizes[0], nil
|
||||
}
|
||||
|
||||
// Fallback: direct read from file (shouldn't happen in normal operation)
|
||||
return t.ReadWriteCloser.Read(b)
|
||||
}
|
||||
|
||||
// BatchRead reads multiple packets at once for improved performance
|
||||
// bufs: slice of buffers to read into
|
||||
// sizes: slice that will be filled with packet sizes
|
||||
// Returns number of packets read
|
||||
func (t *tun) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
if t.wgDevice != nil {
|
||||
return t.wgDevice.Read(bufs, sizes, 0)
|
||||
}
|
||||
|
||||
// Fallback: single packet read
|
||||
n, err := t.ReadWriteCloser.Read(bufs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// BatchSize returns the optimal number of packets to read/write in a batch
|
||||
func (t *tun) BatchSize() int {
|
||||
if t.wgDevice != nil {
|
||||
return t.wgDevice.BatchSize()
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (t *tun) Write(b []byte) (int, error) {
|
||||
if t.wgDevice != nil {
|
||||
// Buffer b should have virtio header space (10 bytes) at the beginning
|
||||
// The decrypted packet data starts at offset 10
|
||||
// Pass the full buffer to WireGuard with offset=virtioNetHdrLen
|
||||
bufs := [][]byte{b}
|
||||
n, err := t.wgDevice.Write(bufs, VirtioNetHdrLen)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, io.ErrShortWrite
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Fallback: direct write (shouldn't happen in normal operation)
|
||||
var nn int
|
||||
maximum := len(b)
|
||||
|
||||
@@ -266,6 +459,22 @@ func (t *tun) Write(b []byte) (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// WriteBatch writes multiple packets to the TUN device in a single syscall
|
||||
func (t *tun) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
if t.wgDevice != nil {
|
||||
return t.wgDevice.Write(bufs, offset)
|
||||
}
|
||||
|
||||
// Fallback: write individually (shouldn't happen in normal operation)
|
||||
for i, buf := range bufs {
|
||||
_, err := t.Write(buf)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
func (t *tun) deviceBytes() (o [16]byte) {
|
||||
for i, c := range t.Device {
|
||||
o[i] = byte(c)
|
||||
@@ -673,14 +882,18 @@ func (t *tun) updateRoutes(r netlink.RouteUpdate) {
|
||||
|
||||
newTree := t.routeTree.Load().Clone()
|
||||
|
||||
t.routesFromSystemLock.Lock()
|
||||
if r.Type == unix.RTM_NEWROUTE {
|
||||
t.l.WithField("destination", dst).WithField("via", gateways).Info("Adding route")
|
||||
t.routesFromSystem[dst] = gateways
|
||||
newTree.Insert(dst, gateways)
|
||||
|
||||
} else {
|
||||
t.l.WithField("destination", dst).WithField("via", gateways).Info("Removing route")
|
||||
delete(t.routesFromSystem, dst)
|
||||
newTree.Delete(dst)
|
||||
}
|
||||
t.routesFromSystemLock.Unlock()
|
||||
t.routeTree.Store(newTree)
|
||||
}
|
||||
|
||||
@@ -689,12 +902,17 @@ func (t *tun) Close() error {
|
||||
close(t.routeChan)
|
||||
}
|
||||
|
||||
if t.wgDevice != nil {
|
||||
_ = t.wgDevice.Close()
|
||||
}
|
||||
|
||||
if t.ReadWriteCloser != nil {
|
||||
_ = t.ReadWriteCloser.Close()
|
||||
}
|
||||
|
||||
if t.ioctlFd > 0 {
|
||||
_ = os.NewFile(t.ioctlFd, "ioctlFd").Close()
|
||||
t.ioctlFd = 0
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+24
-2
@@ -6,7 +6,6 @@ package overlay
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"regexp"
|
||||
@@ -394,10 +393,33 @@ func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (t *tun) NewMultiQueueReader() (BatchReadWriter, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for netbsd")
|
||||
}
|
||||
|
||||
func (t *tun) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
n, err := t.Read(bufs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (t *tun) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
for i, buf := range bufs {
|
||||
_, err := t.Write(buf[offset:])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
func (t *tun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (t *tun) addRoutes(logErrors bool) error {
|
||||
routes := *t.Routes.Load()
|
||||
|
||||
|
||||
+24
-2
@@ -6,7 +6,6 @@ package overlay
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"regexp"
|
||||
@@ -314,10 +313,33 @@ func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (t *tun) NewMultiQueueReader() (BatchReadWriter, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for openbsd")
|
||||
}
|
||||
|
||||
func (t *tun) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
n, err := t.Read(bufs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (t *tun) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
for i, buf := range bufs {
|
||||
_, err := t.Write(buf[offset:])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
func (t *tun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (t *tun) addRoutes(logErrors bool) error {
|
||||
routes := *t.Routes.Load()
|
||||
|
||||
|
||||
+35
-3
@@ -109,8 +109,12 @@ func (t *TestTun) Write(b []byte) (n int, err error) {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
packet := make([]byte, len(b), len(b))
|
||||
copy(packet, b)
|
||||
// Skip virtio header (consistent with production Linux tun)
|
||||
// The buffer b has VirtioNetHdrLen bytes of header followed by the actual packet
|
||||
data := b[VirtioNetHdrLen:]
|
||||
|
||||
packet := make([]byte, len(data))
|
||||
copy(packet, data)
|
||||
t.TxPackets <- packet
|
||||
return len(b), nil
|
||||
}
|
||||
@@ -136,6 +140,34 @@ func (t *TestTun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *TestTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (t *TestTun) NewMultiQueueReader() (BatchReadWriter, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented")
|
||||
}
|
||||
|
||||
func (t *TestTun) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
n, err := t.Read(bufs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (t *TestTun) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
if t.closed.Load() {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
for _, buf := range bufs {
|
||||
// Strip the header at offset and send directly to channel
|
||||
data := buf[offset:]
|
||||
packet := make([]byte, len(data))
|
||||
copy(packet, data)
|
||||
t.TxPackets <- packet
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
func (t *TestTun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
+31
-3
@@ -6,7 +6,6 @@ package overlay
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -74,7 +73,10 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
|
||||
l.WithError(err).Debug("Failed to create wintun device, retrying")
|
||||
tunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create TUN device failed: %w", err)
|
||||
return nil, &NameError{
|
||||
Name: deviceName,
|
||||
Underlying: fmt.Errorf("create TUN device failed: %w", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
t.tun = tunDevice.(*wintun.NativeTun)
|
||||
@@ -238,10 +240,36 @@ func (t *winTun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *winTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (t *winTun) NewMultiQueueReader() (BatchReadWriter, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for windows")
|
||||
}
|
||||
|
||||
// BatchRead reads a single packet (batch size 1 for Windows)
|
||||
func (t *winTun) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
n, err := t.Read(bufs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// WriteBatch writes packets individually (no batching for Windows)
|
||||
func (t *winTun) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
for i, buf := range bufs {
|
||||
_, err := t.Write(buf[offset:])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
// BatchSize returns 1 for Windows (no batching)
|
||||
func (t *winTun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (t *winTun) Close() error {
|
||||
// It seems that the Windows networking stack doesn't like it when we destroy interfaces that have active routes,
|
||||
// so to be certain, just remove everything before destroying.
|
||||
|
||||
+27
-1
@@ -50,10 +50,36 @@ func (d *UserDevice) SupportsMultiqueue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *UserDevice) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (d *UserDevice) NewMultiQueueReader() (BatchReadWriter, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// BatchRead reads a single packet (batch size 1 for UserDevice)
|
||||
func (d *UserDevice) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
n, err := d.Read(bufs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// WriteBatch writes packets individually (no batching for UserDevice)
|
||||
func (d *UserDevice) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
for i, buf := range bufs {
|
||||
_, err := d.Write(buf[offset:])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
// BatchSize returns 1 for UserDevice (no batching)
|
||||
func (d *UserDevice) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (d *UserDevice) Pipe() (*io.PipeReader, *io.PipeWriter) {
|
||||
return d.inboundReader, d.outboundWriter
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// Sentinal value
|
||||
// Sentinel value
|
||||
BucketNotCalculated = -1
|
||||
)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package test
|
||||
package device
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/netip"
|
||||
|
||||
"github.com/slackhq/nebula/overlay"
|
||||
"github.com/slackhq/nebula/routing"
|
||||
)
|
||||
|
||||
@@ -38,10 +39,25 @@ func (NoopTun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (NoopTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
func (NoopTun) NewMultiQueueReader() (overlay.BatchReadWriter, error) {
|
||||
return nil, errors.New("unsupported")
|
||||
}
|
||||
|
||||
func (NoopTun) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// BatchRead implements BatchReadWriter interface
|
||||
func (NoopTun) BatchRead(bufs [][]byte, sizes []int) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// WriteBatch implements BatchReadWriter interface
|
||||
func (NoopTun) WriteBatch(bufs [][]byte, offset int) (int, error) {
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
// BatchSize implements BatchReadWriter interface
|
||||
func (NoopTun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
+22
-6
@@ -13,13 +13,21 @@ type EncReader func(
|
||||
payload []byte,
|
||||
)
|
||||
|
||||
type EncBatchReader func(
|
||||
addrs []netip.AddrPort,
|
||||
payloads [][]byte,
|
||||
count int,
|
||||
)
|
||||
|
||||
type Conn interface {
|
||||
Rebind() error
|
||||
LocalAddr() (netip.AddrPort, error)
|
||||
ListenOut(r EncReader)
|
||||
ListenOutBatch(r EncBatchReader)
|
||||
WriteTo(b []byte, addr netip.AddrPort) error
|
||||
WriteMulti(packets [][]byte, addrs []netip.AddrPort) (int, error)
|
||||
ReloadConfig(c *config.C)
|
||||
SupportsMultipleReaders() bool
|
||||
BatchSize() int
|
||||
Close() error
|
||||
}
|
||||
|
||||
@@ -31,17 +39,25 @@ func (NoopConn) Rebind() error {
|
||||
func (NoopConn) LocalAddr() (netip.AddrPort, error) {
|
||||
return netip.AddrPort{}, nil
|
||||
}
|
||||
func (NoopConn) ListenOut(_ EncReader) {
|
||||
return
|
||||
}
|
||||
|
||||
func (NoopConn) ListenOut(_ EncReader) {}
|
||||
|
||||
func (NoopConn) SupportsMultipleReaders() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (NoopConn) ListenOutBatch(_ EncBatchReader) {}
|
||||
|
||||
func (NoopConn) WriteTo(_ []byte, _ netip.AddrPort) error {
|
||||
return nil
|
||||
}
|
||||
func (NoopConn) ReloadConfig(_ *config.C) {
|
||||
return
|
||||
func (NoopConn) WriteMulti(_ [][]byte, _ []netip.AddrPort) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (NoopConn) ReloadConfig(_ *config.C) {}
|
||||
|
||||
func (NoopConn) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
func (NoopConn) Close() error {
|
||||
return nil
|
||||
|
||||
@@ -140,6 +140,17 @@ func (u *StdConn) WriteTo(b []byte, ap netip.AddrPort) error {
|
||||
}
|
||||
}
|
||||
|
||||
// WriteMulti sends multiple packets - fallback implementation without sendmmsg
|
||||
func (u *StdConn) WriteMulti(packets [][]byte, addrs []netip.AddrPort) (int, error) {
|
||||
for i := range packets {
|
||||
err := u.WriteTo(packets[i], addrs[i])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(packets), nil
|
||||
}
|
||||
|
||||
func (u *StdConn) LocalAddr() (netip.AddrPort, error) {
|
||||
a := u.UDPConn.LocalAddr()
|
||||
|
||||
@@ -188,6 +199,34 @@ func (u *StdConn) SupportsMultipleReaders() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ListenOutBatch - fallback to single-packet reads for Darwin
|
||||
func (u *StdConn) ListenOutBatch(r EncBatchReader) {
|
||||
buffer := make([]byte, MTU)
|
||||
addrs := make([]netip.AddrPort, 1)
|
||||
payloads := make([][]byte, 1)
|
||||
|
||||
for {
|
||||
// Just read one packet at a time and call batch callback with count=1
|
||||
n, rua, err := u.ReadFromUDPAddrPort(buffer)
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||
return
|
||||
}
|
||||
|
||||
u.l.WithError(err).Error("unexpected udp socket receive error")
|
||||
}
|
||||
|
||||
addrs[0] = netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port())
|
||||
payloads[0] = buffer[:n]
|
||||
r(addrs, payloads, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *StdConn) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (u *StdConn) Rebind() error {
|
||||
var err error
|
||||
if u.isV4 {
|
||||
|
||||
+49
-2
@@ -10,9 +10,11 @@ package udp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/config"
|
||||
@@ -74,12 +76,22 @@ type rawMessage struct {
|
||||
func (u *GenericConn) ListenOut(r EncReader) {
|
||||
buffer := make([]byte, MTU)
|
||||
|
||||
var lastRecvErr time.Time
|
||||
|
||||
for {
|
||||
// Just read one packet at a time
|
||||
n, rua, err := u.ReadFromUDPAddrPort(buffer)
|
||||
if err != nil {
|
||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||
return
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||
return
|
||||
}
|
||||
// Dampen unexpected message warns to once per minute
|
||||
if lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {
|
||||
lastRecvErr = time.Now()
|
||||
u.l.WithError(err).Warn("unexpected udp socket receive error")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n])
|
||||
@@ -89,3 +101,38 @@ func (u *GenericConn) ListenOut(r EncReader) {
|
||||
func (u *GenericConn) SupportsMultipleReaders() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ListenOutBatch - fallback to single-packet reads for generic platforms
|
||||
func (u *GenericConn) ListenOutBatch(r EncBatchReader) {
|
||||
buffer := make([]byte, MTU)
|
||||
addrs := make([]netip.AddrPort, 1)
|
||||
payloads := make([][]byte, 1)
|
||||
|
||||
for {
|
||||
// Just read one packet at a time and call batch callback with count=1
|
||||
n, rua, err := u.ReadFromUDPAddrPort(buffer)
|
||||
if err != nil {
|
||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||
return
|
||||
}
|
||||
|
||||
addrs[0] = netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port())
|
||||
payloads[0] = buffer[:n]
|
||||
r(addrs, payloads, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteMulti sends multiple packets - fallback implementation
|
||||
func (u *GenericConn) WriteMulti(packets [][]byte, addrs []netip.AddrPort) (int, error) {
|
||||
for i := range packets {
|
||||
err := u.WriteTo(packets[i], addrs[i])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(packets), nil
|
||||
}
|
||||
|
||||
func (u *GenericConn) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
+174
-4
@@ -22,6 +22,11 @@ type StdConn struct {
|
||||
isV4 bool
|
||||
l *logrus.Logger
|
||||
batch int
|
||||
|
||||
// Pre-allocated buffers for batch writes (sized for IPv6, works for both)
|
||||
writeMsgs []rawMessage
|
||||
writeIovecs []iovec
|
||||
writeNames [][]byte
|
||||
}
|
||||
|
||||
func maybeIPV4(ip net.IP) (net.IP, bool) {
|
||||
@@ -69,7 +74,26 @@ func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch in
|
||||
return nil, fmt.Errorf("unable to bind to socket: %s", err)
|
||||
}
|
||||
|
||||
return &StdConn{sysFd: fd, isV4: ip.Is4(), l: l, batch: batch}, err
|
||||
c := &StdConn{sysFd: fd, isV4: ip.Is4(), l: l, batch: batch}
|
||||
|
||||
// Pre-allocate write message structures for batching (sized for IPv6, works for both)
|
||||
c.writeMsgs = make([]rawMessage, batch)
|
||||
c.writeIovecs = make([]iovec, batch)
|
||||
c.writeNames = make([][]byte, batch)
|
||||
|
||||
for i := range c.writeMsgs {
|
||||
// Allocate for IPv6 size (larger than IPv4, works for both)
|
||||
c.writeNames[i] = make([]byte, unix.SizeofSockaddrInet6)
|
||||
|
||||
// Point to the iovec in the slice
|
||||
c.writeMsgs[i].Hdr.Iov = &c.writeIovecs[i]
|
||||
c.writeMsgs[i].Hdr.Iovlen = 1
|
||||
|
||||
c.writeMsgs[i].Hdr.Name = &c.writeNames[i][0]
|
||||
// Namelen will be set appropriately in writeMulti4/writeMulti6
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (u *StdConn) SupportsMultipleReaders() bool {
|
||||
@@ -122,7 +146,7 @@ func (u *StdConn) LocalAddr() (netip.AddrPort, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *StdConn) ListenOut(r EncReader) {
|
||||
func (u *StdConn) ListenOutBatch(r EncBatchReader) {
|
||||
var ip netip.Addr
|
||||
|
||||
msgs, buffers, names := u.PrepareRawMessages(u.batch)
|
||||
@@ -131,6 +155,12 @@ func (u *StdConn) ListenOut(r EncReader) {
|
||||
read = u.ReadSingle
|
||||
}
|
||||
|
||||
udpBatchHist := metrics.GetOrRegisterHistogram("batch.udp_read_size", nil, metrics.NewUniformSample(1024))
|
||||
|
||||
// Pre-allocate slices for batch callback
|
||||
addrs := make([]netip.AddrPort, u.batch)
|
||||
payloads := make([][]byte, u.batch)
|
||||
|
||||
for {
|
||||
n, err := read(msgs)
|
||||
if err != nil {
|
||||
@@ -138,15 +168,21 @@ func (u *StdConn) ListenOut(r EncReader) {
|
||||
return
|
||||
}
|
||||
|
||||
udpBatchHist.Update(int64(n))
|
||||
|
||||
// Prepare batch data
|
||||
for i := 0; i < n; i++ {
|
||||
// Its ok to skip the ok check here, the slicing is the only error that can occur and it will panic
|
||||
if u.isV4 {
|
||||
ip, _ = netip.AddrFromSlice(names[i][4:8])
|
||||
} else {
|
||||
ip, _ = netip.AddrFromSlice(names[i][8:24])
|
||||
}
|
||||
r(netip.AddrPortFrom(ip.Unmap(), binary.BigEndian.Uint16(names[i][2:4])), buffers[i][:msgs[i].Len])
|
||||
addrs[i] = netip.AddrPortFrom(ip.Unmap(), binary.BigEndian.Uint16(names[i][2:4]))
|
||||
payloads[i] = buffers[i][:msgs[i].Len]
|
||||
}
|
||||
|
||||
// Call batch callback with all packets
|
||||
r(addrs, payloads, n)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +234,19 @@ func (u *StdConn) WriteTo(b []byte, ip netip.AddrPort) error {
|
||||
return u.writeTo6(b, ip)
|
||||
}
|
||||
|
||||
func (u *StdConn) WriteMulti(packets [][]byte, addrs []netip.AddrPort) (int, error) {
|
||||
if len(packets) != len(addrs) {
|
||||
return 0, fmt.Errorf("packets and addrs length mismatch")
|
||||
}
|
||||
if len(packets) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if u.isV4 {
|
||||
return u.writeMulti4(packets, addrs)
|
||||
}
|
||||
return u.writeMulti6(packets, addrs)
|
||||
}
|
||||
|
||||
func (u *StdConn) writeTo6(b []byte, ip netip.AddrPort) error {
|
||||
var rsa unix.RawSockaddrInet6
|
||||
rsa.Family = unix.AF_INET6
|
||||
@@ -252,6 +301,123 @@ func (u *StdConn) writeTo4(b []byte, ip netip.AddrPort) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *StdConn) writeMulti4(packets [][]byte, addrs []netip.AddrPort) (int, error) {
|
||||
sent := 0
|
||||
for sent < len(packets) {
|
||||
// Determine batch size based on remaining packets and buffer capacity
|
||||
batchSize := len(packets) - sent
|
||||
if batchSize > len(u.writeMsgs) {
|
||||
batchSize = len(u.writeMsgs)
|
||||
}
|
||||
|
||||
// Use pre-allocated buffers
|
||||
msgs := u.writeMsgs[:batchSize]
|
||||
iovecs := u.writeIovecs[:batchSize]
|
||||
names := u.writeNames[:batchSize]
|
||||
|
||||
// Setup message structures for this batch
|
||||
for i := 0; i < batchSize; i++ {
|
||||
pktIdx := sent + i
|
||||
if !addrs[pktIdx].Addr().Is4() {
|
||||
return sent + i, ErrInvalidIPv6RemoteForSocket
|
||||
}
|
||||
|
||||
// Setup the packet buffer
|
||||
iovecs[i].Base = &packets[pktIdx][0]
|
||||
iovecs[i].Len = uint(len(packets[pktIdx]))
|
||||
|
||||
// Setup the destination address
|
||||
rsa := (*unix.RawSockaddrInet4)(unsafe.Pointer(&names[i][0]))
|
||||
rsa.Family = unix.AF_INET
|
||||
rsa.Addr = addrs[pktIdx].Addr().As4()
|
||||
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&rsa.Port))[:], addrs[pktIdx].Port())
|
||||
|
||||
// Set the appropriate address length for IPv4
|
||||
msgs[i].Hdr.Namelen = unix.SizeofSockaddrInet4
|
||||
}
|
||||
|
||||
// Send this batch
|
||||
nsent, _, err := unix.Syscall6(
|
||||
unix.SYS_SENDMMSG,
|
||||
uintptr(u.sysFd),
|
||||
uintptr(unsafe.Pointer(&msgs[0])),
|
||||
uintptr(batchSize),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
|
||||
if err != 0 {
|
||||
return sent + int(nsent), &net.OpError{Op: "sendmmsg", Err: err}
|
||||
}
|
||||
|
||||
sent += int(nsent)
|
||||
if int(nsent) < batchSize {
|
||||
// Couldn't send all packets in batch, return what we sent
|
||||
return sent, nil
|
||||
}
|
||||
}
|
||||
|
||||
return sent, nil
|
||||
}
|
||||
|
||||
func (u *StdConn) writeMulti6(packets [][]byte, addrs []netip.AddrPort) (int, error) {
|
||||
sent := 0
|
||||
for sent < len(packets) {
|
||||
// Determine batch size based on remaining packets and buffer capacity
|
||||
batchSize := len(packets) - sent
|
||||
if batchSize > len(u.writeMsgs) {
|
||||
batchSize = len(u.writeMsgs)
|
||||
}
|
||||
|
||||
// Use pre-allocated buffers
|
||||
msgs := u.writeMsgs[:batchSize]
|
||||
iovecs := u.writeIovecs[:batchSize]
|
||||
names := u.writeNames[:batchSize]
|
||||
|
||||
// Setup message structures for this batch
|
||||
for i := 0; i < batchSize; i++ {
|
||||
pktIdx := sent + i
|
||||
|
||||
// Setup the packet buffer
|
||||
iovecs[i].Base = &packets[pktIdx][0]
|
||||
iovecs[i].Len = uint(len(packets[pktIdx]))
|
||||
|
||||
// Setup the destination address
|
||||
rsa := (*unix.RawSockaddrInet6)(unsafe.Pointer(&names[i][0]))
|
||||
rsa.Family = unix.AF_INET6
|
||||
rsa.Addr = addrs[pktIdx].Addr().As16()
|
||||
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&rsa.Port))[:], addrs[pktIdx].Port())
|
||||
|
||||
// Set the appropriate address length for IPv6
|
||||
msgs[i].Hdr.Namelen = unix.SizeofSockaddrInet6
|
||||
}
|
||||
|
||||
// Send this batch
|
||||
nsent, _, err := unix.Syscall6(
|
||||
unix.SYS_SENDMMSG,
|
||||
uintptr(u.sysFd),
|
||||
uintptr(unsafe.Pointer(&msgs[0])),
|
||||
uintptr(batchSize),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
|
||||
if err != 0 {
|
||||
return sent + int(nsent), &net.OpError{Op: "sendmmsg", Err: err}
|
||||
}
|
||||
|
||||
sent += int(nsent)
|
||||
if int(nsent) < batchSize {
|
||||
// Couldn't send all packets in batch, return what we sent
|
||||
return sent, nil
|
||||
}
|
||||
}
|
||||
|
||||
return sent, nil
|
||||
}
|
||||
|
||||
func (u *StdConn) ReloadConfig(c *config.C) {
|
||||
b := c.GetInt("listen.read_buffer", 0)
|
||||
if b > 0 {
|
||||
@@ -309,6 +475,10 @@ func (u *StdConn) getMemInfo(meminfo *[unix.SK_MEMINFO_VARS]uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *StdConn) BatchSize() int {
|
||||
return u.batch
|
||||
}
|
||||
|
||||
func (u *StdConn) Close() error {
|
||||
return syscall.Close(u.sysFd)
|
||||
}
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
type iovec struct {
|
||||
Base *byte
|
||||
Len uint32
|
||||
Len uint
|
||||
}
|
||||
|
||||
type msghdr struct {
|
||||
@@ -40,7 +40,7 @@ func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
|
||||
names[i] = make([]byte, unix.SizeofSockaddrInet6)
|
||||
|
||||
vs := []iovec{
|
||||
{Base: &buffers[i][0], Len: uint32(len(buffers[i]))},
|
||||
{Base: &buffers[i][0], Len: uint(len(buffers[i]))},
|
||||
}
|
||||
|
||||
msgs[i].Hdr.Iov = &vs[0]
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
type iovec struct {
|
||||
Base *byte
|
||||
Len uint64
|
||||
Len uint
|
||||
}
|
||||
|
||||
type msghdr struct {
|
||||
@@ -43,7 +43,7 @@ func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
|
||||
names[i] = make([]byte, unix.SizeofSockaddrInet6)
|
||||
|
||||
vs := []iovec{
|
||||
{Base: &buffers[i][0], Len: uint64(len(buffers[i]))},
|
||||
{Base: &buffers[i][0], Len: uint(len(buffers[i]))},
|
||||
}
|
||||
|
||||
msgs[i].Hdr.Iov = &vs[0]
|
||||
|
||||
+67
-10
@@ -14,6 +14,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -66,7 +67,7 @@ func NewRIOListener(l *logrus.Logger, addr netip.Addr, port int) (*RIOConn, erro
|
||||
|
||||
u := &RIOConn{l: l}
|
||||
|
||||
err := u.bind(&windows.SockaddrInet6{Addr: addr.As16(), Port: port})
|
||||
err := u.bind(l, &windows.SockaddrInet6{Addr: addr.As16(), Port: port})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bind: %w", err)
|
||||
}
|
||||
@@ -82,11 +83,11 @@ func NewRIOListener(l *logrus.Logger, addr netip.Addr, port int) (*RIOConn, erro
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (u *RIOConn) bind(sa windows.Sockaddr) error {
|
||||
func (u *RIOConn) bind(l *logrus.Logger, sa windows.Sockaddr) error {
|
||||
var err error
|
||||
u.sock, err = winrio.Socket(windows.AF_INET6, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("winrio.Socket error: %w", err)
|
||||
}
|
||||
|
||||
// Enable v4 for this socket
|
||||
@@ -100,35 +101,40 @@ func (u *RIOConn) bind(sa windows.Sockaddr) error {
|
||||
size := uint32(unsafe.Sizeof(flag))
|
||||
err = syscall.WSAIoctl(syscall.Handle(u.sock), syscall.SIO_UDP_CONNRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
// This is a best-effort to prevent errors from being returned by the udp recv operation.
|
||||
// Quietly log a failure and continue.
|
||||
l.WithError(err).Debug("failed to set UDP_CONNRESET ioctl")
|
||||
}
|
||||
|
||||
ret = 0
|
||||
flag = 0
|
||||
size = uint32(unsafe.Sizeof(flag))
|
||||
SIO_UDP_NETRESET := uint32(syscall.IOC_IN | syscall.IOC_VENDOR | 15)
|
||||
err = syscall.WSAIoctl(syscall.Handle(u.sock), SIO_UDP_NETRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
// This is a best-effort to prevent errors from being returned by the udp recv operation.
|
||||
// Quietly log a failure and continue.
|
||||
l.WithError(err).Debug("failed to set UDP_NETRESET ioctl")
|
||||
}
|
||||
|
||||
err = u.rx.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error rx.Open(): %w", err)
|
||||
}
|
||||
|
||||
err = u.tx.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error tx.Open(): %w", err)
|
||||
}
|
||||
|
||||
u.rq, err = winrio.CreateRequestQueue(u.sock, packetsPerRing, 1, packetsPerRing, 1, u.rx.cq, u.tx.cq, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error CreateRequestQueue: %w", err)
|
||||
}
|
||||
|
||||
err = windows.Bind(u.sock, sa)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error windows.Bind(): %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -137,15 +143,22 @@ func (u *RIOConn) bind(sa windows.Sockaddr) error {
|
||||
func (u *RIOConn) ListenOut(r EncReader) {
|
||||
buffer := make([]byte, MTU)
|
||||
|
||||
var lastRecvErr time.Time
|
||||
|
||||
for {
|
||||
// Just read one packet at a time
|
||||
n, rua, err := u.receive(buffer)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||
return
|
||||
}
|
||||
u.l.WithError(err).Error("unexpected udp socket receive error")
|
||||
// Dampen unexpected message warns to once per minute
|
||||
if lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {
|
||||
lastRecvErr = time.Now()
|
||||
u.l.WithError(err).Warn("unexpected udp socket receive error")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -325,6 +338,50 @@ func (u *RIOConn) Rebind() error {
|
||||
|
||||
func (u *RIOConn) ReloadConfig(*config.C) {}
|
||||
|
||||
// BatchSize returns 1 since RIO reads packets one at a time
|
||||
func (u *RIOConn) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// ListenOutBatch - fallback to single-packet reads for RIO
|
||||
func (u *RIOConn) ListenOutBatch(r EncBatchReader) {
|
||||
buffer := make([]byte, MTU)
|
||||
addrs := make([]netip.AddrPort, 1)
|
||||
payloads := make([][]byte, 1)
|
||||
|
||||
var lastRecvErr time.Time
|
||||
|
||||
for {
|
||||
n, rua, err := u.receive(buffer)
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||
return
|
||||
}
|
||||
if lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {
|
||||
lastRecvErr = time.Now()
|
||||
u.l.WithError(err).Warn("unexpected udp socket receive error")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
addrs[0] = netip.AddrPortFrom(netip.AddrFrom16(rua.Addr).Unmap(), (rua.Port>>8)|((rua.Port&0xff)<<8))
|
||||
payloads[0] = buffer[:n]
|
||||
r(addrs, payloads, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteMulti sends multiple packets - fallback implementation
|
||||
func (u *RIOConn) WriteMulti(packets [][]byte, addrs []netip.AddrPort) (int, error) {
|
||||
for i := range packets {
|
||||
err := u.WriteTo(packets[i], addrs[i])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(packets), nil
|
||||
}
|
||||
|
||||
func (u *RIOConn) Close() error {
|
||||
if !u.isOpen.CompareAndSwap(true, false) {
|
||||
return nil
|
||||
|
||||
@@ -116,6 +116,31 @@ func (u *TesterConn) ListenOut(r EncReader) {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *TesterConn) ListenOutBatch(r EncBatchReader) {
|
||||
addrs := make([]netip.AddrPort, 1)
|
||||
payloads := make([][]byte, 1)
|
||||
|
||||
for {
|
||||
p, ok := <-u.RxPackets
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
addrs[0] = p.From
|
||||
payloads[0] = p.Data
|
||||
r(addrs, payloads, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *TesterConn) WriteMulti(packets [][]byte, addrs []netip.AddrPort) (int, error) {
|
||||
for i := range packets {
|
||||
err := u.WriteTo(packets[i], addrs[i])
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(packets), nil
|
||||
}
|
||||
|
||||
func (u *TesterConn) ReloadConfig(*config.C) {}
|
||||
|
||||
func NewUDPStatsEmitter(_ []Conn) func() {
|
||||
@@ -131,6 +156,10 @@ func (u *TesterConn) SupportsMultipleReaders() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *TesterConn) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (u *TesterConn) Rebind() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user