Compare commits

..

2 Commits

Author SHA1 Message Date
Jay Wren 97977982cb whatif? 2025-12-12 15:32:28 -05:00
Jay Wren 69ed6646f1 dont call writeTo4 if writing to ipv6 2025-12-10 16:56:06 -05:00
36 changed files with 117 additions and 1547 deletions
+5 -5
View File
@@ -24,7 +24,7 @@ jobs:
mv build/*.tar.gz release
- name: Upload artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v5
with:
name: linux-latest
path: release
@@ -55,7 +55,7 @@ jobs:
mv dist\windows\wintun build\dist\windows\
- name: Upload artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v5
with:
name: windows-latest
path: build
@@ -104,7 +104,7 @@ jobs:
fi
- name: Upload artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v5
with:
name: darwin-latest
path: ./release/*
@@ -128,7 +128,7 @@ jobs:
- name: Download artifacts
if: ${{ env.HAS_DOCKER_CREDS == 'true' }}
uses: actions/download-artifact@v7
uses: actions/download-artifact@v6
with:
name: linux-latest
path: artifacts
@@ -163,7 +163,7 @@ jobs:
- uses: actions/checkout@v6
- name: Download artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v6
with:
path: artifacts
+2 -2
View File
@@ -45,7 +45,7 @@ jobs:
- name: Build test mobile
run: make build-test-mobile
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v5
with:
name: e2e packet flow linux-latest
path: e2e/mermaid/linux-latest
@@ -125,7 +125,7 @@ jobs:
- name: End 2 end
run: make e2evv
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v5
with:
name: e2e packet flow ${{ matrix.os }}
path: e2e/mermaid/${{ matrix.os }}
+1 -34
View File
@@ -7,37 +7,6 @@ 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.
@@ -775,9 +744,7 @@ created.)
- Initial public release.
[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
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.0...HEAD
[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
-1
View File
@@ -119,7 +119,6 @@ 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
+4 -11
View File
@@ -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: nil,
publicKey: make([]byte, len(rc.Details.PublicKey)),
isCA: rc.Details.IsCA,
curve: rc.Details.Curve,
},
@@ -437,19 +437,12 @@ 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 {
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)
nc.details.publicKey = publicKey
}
copy(nc.details.publicKey, rc.Details.PublicKey)
var ip netip.Addr
for i, rawIp := range rc.Details.Ips {
if i%2 == 0 {
-56
View File
@@ -62,62 +62,6 @@ 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)
+1 -7
View File
@@ -592,13 +592,7 @@ func unmarshalCertificateV2(b []byte, publicKey []byte, curve Curve) (*certifica
// Maybe grab the public key
var rawPublicKey cryptobyte.String
if len(publicKey) > 0 {
// 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)
rawPublicKey = publicKey
} else if !input.ReadOptionalASN1(&rawPublicKey, nil, TagCertPublicKey) {
return nil, ErrBadFormat
}
-52
View File
@@ -76,58 +76,6 @@ 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)
+1 -1
View File
@@ -79,7 +79,7 @@ qrlJ69wer3ZUHFXA
assert.Nil(t, k)
assert.Equal(t, rest, invalidPem)
// Fail due to invalid PEM format, because
// Fail due to ivalid 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")
-1
View File
@@ -21,7 +21,6 @@ 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")
+5 -5
View File
@@ -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 invalid PEM format, because
// Fail due to ivalid 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 invalid PEM format, because
// Fail due to ivalid 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 invalid PEM format, because
// Fail due to ivalid 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 invalid PEM format, because
// Fail due to ivalid 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 invalid PEM format, because
// Fail due to ivalid PEM format, because
// it's missing the requisite pre-encapsulation boundary.
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
assert.Nil(t, k)
+1 -1
View File
@@ -200,7 +200,7 @@ func Test_ca(t *testing.T) {
assert.Empty(t, b)
assert.Len(t, lKey, 64)
// test when reading password results in an error
// test when reading passsword results in an error
os.Remove(keyF.Name())
os.Remove(crtF.Name())
ob.Reset()
-4
View File
@@ -144,10 +144,6 @@ 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.
+9 -9
View File
@@ -12,27 +12,27 @@ 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.70
github.com/miekg/dns v1.1.68
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.4
github.com/sirupsen/logrus v1.9.3
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.47.0
golang.org/x/crypto v0.45.0
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090
golang.org/x/net v0.49.0
golang.org/x/net v0.47.0
golang.org/x/sync v0.19.0
golang.org/x/sys v0.40.0
golang.org/x/term v0.39.0
golang.org/x/sys v0.39.0
golang.org/x/term v0.38.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
golang.zx2c4.com/wireguard/windows v0.5.3
google.golang.org/protobuf v1.36.11
google.golang.org/protobuf v1.36.10
gopkg.in/yaml.v3 v3.0.1
gvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe
)
@@ -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.31.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.40.0 // indirect
golang.org/x/tools v0.33.0 // indirect
)
+19 -18
View File
@@ -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.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
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/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.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
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.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
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/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.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
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=
@@ -206,13 +206,14 @@ 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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
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=
@@ -223,8 +224,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
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.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
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=
@@ -243,8 +244,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.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/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=
-14
View File
@@ -1,7 +1,6 @@
package nebula
import (
"bytes"
"net/netip"
"time"
@@ -167,13 +166,6 @@ 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())
@@ -543,12 +535,6 @@ 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).
+1 -139
View File
@@ -9,75 +9,8 @@ import (
"github.com/slackhq/nebula/iputil"
"github.com/slackhq/nebula/noiseutil"
"github.com/slackhq/nebula/routing"
"github.com/slackhq/nebula/udp"
)
// consumeInsidePacketBatched is a variant of consumeInsidePacket that queues
// outgoing packets into pendingPackets instead of sending them immediately.
// The caller is responsible for flushing pendingPackets with WriteBatch.
func (f *Interface) consumeInsidePacketBatched(packet []byte, fwPacket *firewall.Packet, nb, out []byte, q int, localCache firewall.ConntrackCache, pendingPackets *[]udp.BatchPacket) {
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)
}
return
}
// Ignore local broadcast packets
if f.dropLocalBroadcast {
if f.myBroadcastAddrsTable.Contains(fwPacket.RemoteAddr) {
return
}
}
if f.myVpnAddrsTable.Contains(fwPacket.RemoteAddr) {
if immediatelyForwardToSelf {
_, err := f.readers[q].Write(packet)
if err != nil {
f.l.WithError(err).Error("Failed to forward to tun")
}
}
return
}
// Ignore multicast packets
if f.dropMulticast && fwPacket.RemoteAddr.IsMulticast() {
return
}
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")
}
return
}
if !ready {
return
}
dropReason := f.firewall.Drop(*fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)
if dropReason == nil {
f.sendNoMetricsBatched(header.Message, 0, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, packet, nb, out, q, pendingPackets)
} else {
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")
}
}
}
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 {
@@ -136,6 +69,7 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
dropReason := f.firewall.Drop(*fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)
if dropReason == nil {
f.sendNoMetrics(header.Message, 0, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, packet, nb, out, q)
} else {
f.rejectInside(packet, out, q)
if f.l.Level >= logrus.DebugLevel {
@@ -476,75 +410,3 @@ func (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType
}
}
}
// sendNoMetricsBatched is like sendNoMetrics but queues the packet for batched sending
// instead of sending immediately. The caller must flush pendingPackets with WriteBatch.
func (f *Interface) sendNoMetricsBatched(t header.MessageType, st header.MessageSubType, ci *ConnectionState, hostinfo *HostInfo, remote netip.AddrPort, p, nb, out []byte, q int, pendingPackets *[]udp.BatchPacket) {
if ci.eKey == nil {
return
}
useRelay := !remote.IsValid() && !hostinfo.remote.IsValid()
fullOut := out
if useRelay {
if len(out) < header.Len {
out = out[:header.Len]
}
out = out[header.Len:]
}
if noiseutil.EncryptLockNeeded {
ci.writeLock.Lock()
}
c := ci.messageCounter.Add(1)
out = header.Encode(out, header.Version, t, st, hostinfo.remoteIndexId, c)
f.connectionManager.Out(hostinfo)
if t != header.CloseTunnel && 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, p, c, nb)
if noiseutil.EncryptLockNeeded {
ci.writeLock.Unlock()
}
if err != nil {
hostinfo.logger(f.l).WithError(err).
WithField("udpAddr", remote).WithField("counter", c).
WithField("attemptedCounter", c).
Error("Failed to encrypt outgoing packet")
return
}
// Queue the packet for batched sending
var addr netip.AddrPort
if remote.IsValid() {
addr = remote
} else if hostinfo.remote.IsValid() {
addr = hostinfo.remote
} else {
// Relay path - send immediately, not batched
for _, relayIP := range hostinfo.relayState.CopyRelayIps() {
relayHostInfo, relay, err := f.hostMap.QueryVpnAddrsRelayFor(hostinfo.vpnAddrs, relayIP)
if err != nil {
hostinfo.relayState.DeleteRelay(relayIP)
hostinfo.logger(f.l).WithField("relay", relayIP).WithError(err).Info("sendNoMetricsBatched failed to find HostInfo")
continue
}
f.SendVia(relayHostInfo, relay, out, nb, fullOut[:header.Len+len(out)], true)
break
}
return
}
// Copy the payload since the buffer will be reused
payload := make([]byte, len(out))
copy(payload, out)
*pendingPackets = append(*pendingPackets, udp.BatchPacket{Payload: payload, Addr: addr})
}
+23 -126
View File
@@ -48,8 +48,6 @@ type InterfaceConfig struct {
ConntrackCacheTimeout time.Duration
l *logrus.Logger
tunBatchSize int // batch size for TUN read/write batching, 0 to disable
}
type Interface struct {
@@ -79,8 +77,7 @@ type Interface struct {
reQueryEvery atomic.Uint32
reQueryWait atomic.Int64
sendRecvErrorConfig recvErrorConfig
acceptRecvErrorConfig recvErrorConfig
sendRecvErrorConfig sendRecvErrorConfig
// rebindCount is used to decide if an active tunnel should trigger a punch notification through a lighthouse
rebindCount int8
@@ -88,9 +85,8 @@ type Interface struct {
conntrackCacheTimeout time.Duration
writers []udp.Conn
readers []io.ReadWriteCloser
tunBatchSize int // batch size for TUN read/write batching
writers []udp.Conn
readers []io.ReadWriteCloser
metricHandshakes metrics.Histogram
messageMetrics *MessageMetrics
@@ -114,34 +110,34 @@ type EncWriter interface {
GetCertState() *CertState
}
type recvErrorConfig uint8
type sendRecvErrorConfig uint8
const (
recvErrorAlways recvErrorConfig = iota
recvErrorNever
recvErrorPrivate
sendRecvErrorAlways sendRecvErrorConfig = iota
sendRecvErrorNever
sendRecvErrorPrivate
)
func (s recvErrorConfig) ShouldRecvError(endpoint netip.AddrPort) bool {
func (s sendRecvErrorConfig) ShouldSendRecvError(endpoint netip.AddrPort) bool {
switch s {
case recvErrorPrivate:
case sendRecvErrorPrivate:
return endpoint.Addr().IsPrivate()
case recvErrorAlways:
case sendRecvErrorAlways:
return true
case recvErrorNever:
case sendRecvErrorNever:
return false
default:
panic(fmt.Errorf("invalid recvErrorConfig value: %d", s))
panic(fmt.Errorf("invalid sendRecvErrorConfig value: %d", s))
}
}
func (s recvErrorConfig) String() string {
func (s sendRecvErrorConfig) String() string {
switch s {
case recvErrorAlways:
case sendRecvErrorAlways:
return "always"
case recvErrorNever:
case sendRecvErrorNever:
return "never"
case recvErrorPrivate:
case sendRecvErrorPrivate:
return "private"
default:
return fmt.Sprintf("invalid(%d)", s)
@@ -190,7 +186,6 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
relayManager: c.relayManager,
connectionManager: c.connectionManager,
conntrackCacheTimeout: c.ConntrackCacheTimeout,
tunBatchSize: c.tunBatchSize,
metricHandshakes: metrics.GetOrRegisterHistogram("handshakes", nil, metrics.NewExpDecaySample(1028, 0.015)),
messageMetrics: c.MessageMetrics,
@@ -248,15 +243,6 @@ func (f *Interface) activate() {
f.readers[i] = reader
}
// Enable batch reading on all readers if batch size > 1
if f.tunBatchSize > 1 {
for i := 0; i < f.routines; i++ {
if err := overlay.EnableBatchReading(f.readers[i]); err != nil {
f.l.WithError(err).WithField("routine", i).Warn("Failed to enable batch reading, falling back to single reads")
}
}
}
if err := f.inside.Activate(); err != nil {
f.inside.Close()
f.l.Fatal(err)
@@ -300,21 +286,13 @@ func (f *Interface) listenOut(i int) {
func (f *Interface) listenIn(reader io.ReadWriteCloser, i int) {
runtime.LockOSThread()
conntrackCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
// Check if batch reading is available and enabled
batchReader := overlay.AsBatchReader(reader)
if batchReader != nil && f.tunBatchSize > 1 {
f.listenInBatched(reader, batchReader, i, conntrackCache)
return
}
// Fallback to single-packet reading
packet := make([]byte, mtu)
out := make([]byte, mtu)
fwPacket := &firewall.Packet{}
nb := make([]byte, 12, 12)
conntrackCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
for {
n, err := reader.Read(packet)
if err != nil {
@@ -331,58 +309,9 @@ func (f *Interface) listenIn(reader io.ReadWriteCloser, i int) {
}
}
func (f *Interface) listenInBatched(reader io.ReadWriteCloser, batchReader overlay.BatchReader, i int, conntrackCache *firewall.ConntrackCacheTicker) {
batchSize := f.tunBatchSize
// Pre-allocate buffers for batch reading
packets := make([][]byte, batchSize)
for j := range packets {
packets[j] = make([]byte, mtu)
}
sizes := make([]int, batchSize)
// Pre-allocate buffers for packet processing
out := make([]byte, mtu)
fwPacket := &firewall.Packet{}
nb := make([]byte, 12, 12)
// Pre-allocate buffer for batched UDP writes
pendingPackets := make([]udp.BatchPacket, 0, batchSize)
for {
// Read a batch of packets from TUN
n, err := batchReader.ReadBatch(packets, sizes)
if err != nil {
if errors.Is(err, os.ErrClosed) && f.closed.Load() {
return
}
f.l.WithError(err).Error("Error while reading outbound packets")
os.Exit(2)
}
if n == 0 {
continue
}
// Process all packets in the batch
cache := conntrackCache.Get(f.l)
for j := 0; j < n; j++ {
f.consumeInsidePacketBatched(packets[j][:sizes[j]], fwPacket, nb, out, i, cache, &pendingPackets)
}
// Flush all pending UDP writes
if len(pendingPackets) > 0 {
f.writers[i].WriteBatch(pendingPackets)
pendingPackets = pendingPackets[:0]
}
}
}
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)
@@ -446,16 +375,16 @@ func (f *Interface) reloadSendRecvError(c *config.C) {
switch stringValue {
case "always":
f.sendRecvErrorConfig = recvErrorAlways
f.sendRecvErrorConfig = sendRecvErrorAlways
case "never":
f.sendRecvErrorConfig = recvErrorNever
f.sendRecvErrorConfig = sendRecvErrorNever
case "private":
f.sendRecvErrorConfig = recvErrorPrivate
f.sendRecvErrorConfig = sendRecvErrorPrivate
default:
if c.GetBool("listen.send_recv_error", true) {
f.sendRecvErrorConfig = recvErrorAlways
f.sendRecvErrorConfig = sendRecvErrorAlways
} else {
f.sendRecvErrorConfig = recvErrorNever
f.sendRecvErrorConfig = sendRecvErrorNever
}
}
@@ -464,30 +393,6 @@ 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)
@@ -559,14 +464,6 @@ 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()
+9 -8
View File
@@ -1,6 +1,7 @@
package nebula
import (
"context"
"encoding/binary"
"fmt"
"net/netip"
@@ -41,14 +42,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(t.Context(), l, c, cs, nil, nil)
_, err := NewLightHouseFromConfig(context.Background(), 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(t.Context(), l, c, cs, nil, nil)
_, err = NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
require.EqualError(t, err, "lighthouse 10.128.0.3 does not have a static_host_map entry")
}
@@ -70,7 +71,7 @@ func TestReloadLighthouseInterval(t *testing.T) {
}
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
require.NoError(t, err)
lh.ifce = &mockEncWriter{}
@@ -98,7 +99,7 @@ func BenchmarkLighthouseHandleRequest(b *testing.B) {
}
c := config.NewC(l)
lh, err := NewLightHouseFromConfig(b.Context(), l, c, cs, nil, nil)
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
require.NoError(b, err)
hAddr := netip.MustParseAddrPort("4.5.6.7:12345")
@@ -201,7 +202,7 @@ func TestLighthouse_Memory(t *testing.T) {
myVpnNetworks: []netip.Prefix{myVpnNet},
myVpnNetworksTable: nt,
}
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
lh.ifce = &mockEncWriter{}
require.NoError(t, err)
lhh := lh.NewRequestHandler()
@@ -287,7 +288,7 @@ func TestLighthouse_reload(t *testing.T) {
myVpnNetworksTable: nt,
}
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
require.NoError(t, err)
nc := map[string]any{
@@ -522,7 +523,7 @@ func TestLighthouse_Dont_Delete_Static_Hosts(t *testing.T) {
myVpnNetworks: []netip.Prefix{myVpnNet},
myVpnNetworksTable: nt,
}
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
require.NoError(t, err)
lh.ifce = &mockEncWriter{}
@@ -588,7 +589,7 @@ func TestLighthouse_DeletesWork(t *testing.T) {
myVpnNetworks: []netip.Prefix{myVpnNet},
myVpnNetworksTable: nt,
}
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
require.NoError(t, err)
lh.ifce = &mockEncWriter{}
-2
View File
@@ -250,7 +250,6 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
punchy: punchy,
ConntrackCacheTimeout: conntrackCacheTimeout,
l: l,
tunBatchSize: c.GetInt("listen.batch", 64),
}
var ifce *Interface
@@ -266,7 +265,6 @@ 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
View File
@@ -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 provides a
// NewGCMTLS is the internal method used with boringcrypto that provices 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)
+1 -8
View File
@@ -516,7 +516,7 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
}
func (f *Interface) maybeSendRecvError(endpoint netip.AddrPort, index uint32) {
if f.sendRecvErrorConfig.ShouldRecvError(endpoint) {
if f.sendRecvErrorConfig.ShouldSendRecvError(endpoint) {
f.sendRecvError(endpoint, index)
}
}
@@ -534,13 +534,6 @@ 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).
-35
View File
@@ -16,38 +16,3 @@ type Device interface {
SupportsMultiqueue() bool
NewMultiQueueReader() (io.ReadWriteCloser, error)
}
// BatchReader is an optional interface that devices can implement
// to support reading multiple packets in a single batch operation.
// This can significantly reduce syscall overhead under high load.
type BatchReader interface {
// ReadBatch reads up to len(packets) packets into the provided buffers.
// Each packet is read into packets[i] and its length is stored in sizes[i].
// Returns the number of packets read, or an error.
// A return of (0, nil) indicates no packets were available (non-blocking).
ReadBatch(packets [][]byte, sizes []int) (int, error)
}
// AsBatchReader returns a BatchReader if the reader supports batch operations,
// otherwise returns nil.
func AsBatchReader(r io.ReadWriteCloser) BatchReader {
if br, ok := r.(BatchReader); ok {
return br
}
return nil
}
// BatchEnabler is an optional interface for devices that need explicit
// enabling of batch read support (e.g., setting non-blocking mode).
type BatchEnabler interface {
EnableBatchReading() error
}
// EnableBatchReading enables batch reading on the device if supported.
// Returns nil if the device doesn't support or need explicit enabling.
func EnableBatchReading(d interface{}) error {
if be, ok := d.(BatchEnabler); ok {
return be.EnableBatchReading()
}
return nil
}
-9
View File
@@ -12,15 +12,6 @@ import (
const DefaultMTU = 1300
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)
+1 -1
View File
@@ -266,7 +266,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{
+5 -421
View File
@@ -10,7 +10,6 @@ import (
"net/netip"
"os"
"strings"
"sync"
"sync/atomic"
"time"
"unsafe"
@@ -24,11 +23,6 @@ import (
"golang.org/x/sys/unix"
)
const (
// virtioNetHdrLen is the length of virtio_net_hdr (without mergeable buffers)
virtioNetHdrLen = 10
)
type tun struct {
io.ReadWriteCloser
fd int
@@ -39,13 +33,6 @@ type tun struct {
TXQueueLen int
deviceIndex int
ioctlFd uintptr
nonBlocking bool // true if fd is in non-blocking mode
vnetHdr bool // true if IFF_VNET_HDR is enabled on the TUN device
// readBuf is used when vnetHdr is enabled to read the full packet+header
// before stripping the header. This is needed because caller-provided
// buffers are sized for MTU but kernel writes MTU+10 with virtio header.
readBuf []byte
Routes atomic.Pointer[[]Route]
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
@@ -53,11 +40,6 @@ 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,23 +47,6 @@ func (t *tun) Networks() []netip.Prefix {
return t.vpnNetworks
}
// tunVnetHdrSupported checks if the kernel supports IFF_VNET_HDR on TUN devices
func tunVnetHdrSupported() bool {
fd, err := unix.Open("/dev/net/tun", unix.O_RDONLY, 0)
if err != nil {
return false
}
defer unix.Close(fd)
var features uint32
err = ioctl(uintptr(fd), uintptr(unix.TUNGETFEATURES), uintptr(unsafe.Pointer(&features)))
if err != nil {
return false
}
return features&unix.IFF_VNET_HDR != 0
}
type ifReq struct {
Name [16]byte
Flags uint16
@@ -136,35 +101,17 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, multiqueu
}
}
// Check if VNET_HDR is supported before trying to use it
useVnetHdr := tunVnetHdrSupported()
var req ifReq
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI)
if multiqueue {
req.Flags |= unix.IFF_MULTI_QUEUE
}
if useVnetHdr {
req.Flags |= unix.IFF_VNET_HDR
}
nameStr := c.GetString("tun.dev", "")
copy(req.Name[:], nameStr)
copy(req.Name[:], c.GetString("tun.dev", ""))
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
return nil, &NameError{
Name: nameStr,
Underlying: err,
}
return nil, err
}
name := strings.Trim(string(req.Name[:]), "\x00")
// Track if VNET_HDR is in use
// Note: We don't call TUNSETOFFLOAD - just handle the headers manually
vnetHdrEnabled := useVnetHdr
if vnetHdrEnabled {
l.Info("TUN VNET_HDR enabled")
}
file := os.NewFile(uintptr(fd), "/dev/net/tun")
t, err := newTunGeneric(c, l, file, vpnNetworks)
if err != nil {
@@ -172,13 +119,6 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, multiqueu
}
t.Device = name
t.vnetHdr = vnetHdrEnabled
// Allocate read buffer for virtio header handling
// Buffer needs to be large enough for virtio header + max packet
if t.vnetHdr {
t.readBuf = make([]byte, t.MaxMTU+virtioNetHdrLen)
}
return t, nil
}
@@ -191,7 +131,6 @@ 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,
}
@@ -225,13 +164,6 @@ 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)
@@ -289,172 +221,21 @@ func (t *tun) SupportsMultiqueue() bool {
}
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
fd, err := unix.Open("/dev/net/tun", os.O_RDWR|unix.O_NONBLOCK, 0)
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)
if t.vnetHdr {
req.Flags |= unix.IFF_VNET_HDR
}
copy(req.Name[:], t.Device)
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
return nil, err
}
reader := &tunBatchReader{fd: fd, device: t.Device, vnetHdr: t.vnetHdr}
if t.vnetHdr {
reader.readBuf = make([]byte, t.MaxMTU+virtioNetHdrLen)
}
return reader, nil
}
file := os.NewFile(uintptr(fd), "/dev/net/tun")
// tunBatchReader implements BatchReader for efficient batch packet reading
type tunBatchReader struct {
fd int
device string
vnetHdr bool
readBuf []byte // internal buffer for virtio header handling
}
func (r *tunBatchReader) Read(b []byte) (int, error) {
// Choose buffer: use internal buffer for vnetHdr, caller's buffer otherwise
readBuf := b
if r.vnetHdr {
readBuf = r.readBuf
}
// Use poll to wait for data, then read
for {
n, err := unix.Read(r.fd, readBuf)
if err == nil {
if r.vnetHdr && n > virtioNetHdrLen {
packetLen := n - virtioNetHdrLen
copy(b, readBuf[virtioNetHdrLen:n])
return packetLen, nil
}
if r.vnetHdr {
return 0, nil // No packet data
}
return n, nil
}
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
// Wait for data
pfds := []unix.PollFd{{Fd: int32(r.fd), Events: unix.POLLIN}}
_, err = unix.Poll(pfds, -1)
if err != nil {
if err == unix.EINTR {
continue
}
return 0, err
}
continue
}
return n, err
}
}
func (r *tunBatchReader) Write(b []byte) (int, error) {
if !r.vnetHdr {
return unix.Write(r.fd, b)
}
// Use writev to prepend virtio header without copying the packet data
// Header is all zeros = no GSO, no checksum offload
var hdr [virtioNetHdrLen]byte
bufs := [][]byte{hdr[:], b}
n, err := unix.Writev(r.fd, bufs)
if err != nil {
return 0, err
}
// Return only the packet bytes written (exclude header)
if n > virtioNetHdrLen {
return n - virtioNetHdrLen, nil
}
return 0, nil
}
func (r *tunBatchReader) Close() error {
return unix.Close(r.fd)
}
// ReadBatch reads up to len(packets) packets from the TUN device.
// It drains all available packets without blocking, using poll() only
// when no packets have been read yet.
func (r *tunBatchReader) ReadBatch(packets [][]byte, sizes []int) (int, error) {
count := 0
maxPackets := len(packets)
if len(sizes) < maxPackets {
maxPackets = len(sizes)
}
// Choose read buffer based on vnetHdr
readBuf := packets[0] // Will be updated in loop for non-vnetHdr
if r.vnetHdr {
readBuf = r.readBuf
}
for count < maxPackets {
if !r.vnetHdr {
readBuf = packets[count]
}
n, err := unix.Read(r.fd, readBuf)
if err == nil && n > 0 {
if r.vnetHdr {
if n > virtioNetHdrLen {
packetLen := n - virtioNetHdrLen
copy(packets[count], readBuf[virtioNetHdrLen:n])
sizes[count] = packetLen
} else {
// Malformed packet (no data after header), skip
continue
}
} else {
sizes[count] = n
}
count++
continue
}
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
// No more packets available
if count > 0 {
// We have some packets, return them
return count, nil
}
// No packets yet, wait for at least one
pfds := []unix.PollFd{{Fd: int32(r.fd), Events: unix.POLLIN}}
_, err = unix.Poll(pfds, -1)
if err != nil {
if err == unix.EINTR {
continue
}
return 0, err
}
continue
}
if err != nil {
if count > 0 {
// Return what we have
return count, nil
}
return 0, err
}
if n == 0 {
if count > 0 {
return count, nil
}
return 0, io.EOF
}
}
return count, nil
return file, nil
}
func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
@@ -463,27 +244,6 @@ func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
}
func (t *tun) Write(b []byte) (int, error) {
if !t.vnetHdr {
return t.writeSimple(b)
}
// Use writev to prepend virtio header without copying the packet data
// Header is all zeros = no GSO, no checksum offload
var hdr [virtioNetHdrLen]byte
bufs := [][]byte{hdr[:], b}
n, err := unix.Writev(t.fd, bufs)
if err != nil {
return 0, err
}
// Return only the packet bytes written (exclude header)
if n > virtioNetHdrLen {
return n - virtioNetHdrLen, nil
}
return 0, nil
}
func (t *tun) writeSimple(b []byte) (int, error) {
var nn int
maximum := len(b)
@@ -506,177 +266,6 @@ func (t *tun) writeSimple(b []byte) (int, error) {
}
}
// EnableBatchReading sets the TUN fd to non-blocking mode to enable batch reading.
// This should be called before using ReadBatch.
func (t *tun) EnableBatchReading() error {
if t.nonBlocking {
return nil
}
err := unix.SetNonblock(t.fd, true)
if err != nil {
return err
}
t.nonBlocking = true
return nil
}
// Read overrides the default Read to handle non-blocking mode and virtio headers
func (t *tun) Read(b []byte) (int, error) {
if !t.vnetHdr {
return t.readSimple(b)
}
// With VNET_HDR, read into internal buffer (which has space for header)
// then copy packet data to caller's buffer
if !t.nonBlocking {
n, err := t.ReadWriteCloser.Read(t.readBuf)
if err != nil {
return 0, err
}
if n <= virtioNetHdrLen {
return 0, nil // No packet data
}
packetLen := n - virtioNetHdrLen
copy(b, t.readBuf[virtioNetHdrLen:n])
return packetLen, nil
}
// Non-blocking read with poll
for {
n, err := unix.Read(t.fd, t.readBuf)
if err == nil {
if n <= virtioNetHdrLen {
return 0, nil // No packet data
}
packetLen := n - virtioNetHdrLen
copy(b, t.readBuf[virtioNetHdrLen:n])
return packetLen, nil
}
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
pfds := []unix.PollFd{{Fd: int32(t.fd), Events: unix.POLLIN}}
_, err = unix.Poll(pfds, -1)
if err != nil {
if err == unix.EINTR {
continue
}
return 0, err
}
continue
}
return n, err
}
}
func (t *tun) readSimple(b []byte) (int, error) {
if !t.nonBlocking {
return t.ReadWriteCloser.Read(b)
}
for {
n, err := unix.Read(t.fd, b)
if err == nil {
return n, nil
}
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
pfds := []unix.PollFd{{Fd: int32(t.fd), Events: unix.POLLIN}}
_, err = unix.Poll(pfds, -1)
if err != nil {
if err == unix.EINTR {
continue
}
return 0, err
}
continue
}
return n, err
}
}
// ReadBatch reads up to len(packets) packets from the TUN device.
// EnableBatchReading must be called first.
func (t *tun) ReadBatch(packets [][]byte, sizes []int) (int, error) {
if !t.nonBlocking {
// Fallback to single read if non-blocking not enabled
n, err := t.Read(packets[0])
if err != nil {
return 0, err
}
sizes[0] = n
return 1, nil
}
count := 0
maxPackets := len(packets)
if len(sizes) < maxPackets {
maxPackets = len(sizes)
}
// Choose read buffer based on vnetHdr
// With vnetHdr, we need to read into internal buffer (has space for header)
// then copy packet data to caller's buffer
readBuf := packets[0] // Will be updated in the loop
if t.vnetHdr {
readBuf = t.readBuf
}
for count < maxPackets {
if !t.vnetHdr {
readBuf = packets[count]
}
n, err := unix.Read(t.fd, readBuf)
if err == nil && n > 0 {
if t.vnetHdr {
if n > virtioNetHdrLen {
packetLen := n - virtioNetHdrLen
copy(packets[count], readBuf[virtioNetHdrLen:n])
sizes[count] = packetLen
} else {
// Malformed packet (no data after header), skip
continue
}
} else {
sizes[count] = n
}
count++
continue
}
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
// No more packets available
if count > 0 {
return count, nil
}
// No packets yet, wait for at least one
pfds := []unix.PollFd{{Fd: int32(t.fd), Events: unix.POLLIN}}
_, err = unix.Poll(pfds, -1)
if err != nil {
if err == unix.EINTR {
continue
}
return 0, err
}
continue
}
if err != nil {
if count > 0 {
return count, nil
}
return 0, err
}
if n == 0 {
if count > 0 {
return count, nil
}
return 0, io.EOF
}
}
return count, nil
}
func (t *tun) deviceBytes() (o [16]byte) {
for i, c := range t.Device {
o[i] = byte(c)
@@ -1084,18 +673,14 @@ 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)
}
@@ -1110,7 +695,6 @@ func (t *tun) Close() error {
if t.ioctlFd > 0 {
_ = os.NewFile(t.ioctlFd, "ioctlFd").Close()
t.ioctlFd = 0
}
return nil
+1 -4
View File
@@ -74,10 +74,7 @@ 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, &NameError{
Name: deviceName,
Underlying: fmt.Errorf("create TUN device failed: %w", err),
}
return nil, fmt.Errorf("create TUN device failed: %w", err)
}
}
t.tun = tunDevice.(*wintun.NativeTun)
+1 -1
View File
@@ -6,7 +6,7 @@ import (
)
const (
// Sentinel value
// Sentinal value
BucketNotCalculated = -1
)
-18
View File
@@ -13,22 +13,13 @@ type EncReader func(
payload []byte,
)
// BatchPacket represents a single packet in a batch write operation
type BatchPacket struct {
Payload []byte
Addr netip.AddrPort
}
type Conn interface {
Rebind() error
LocalAddr() (netip.AddrPort, error)
ListenOut(r EncReader)
WriteTo(b []byte, addr netip.AddrPort) error
WriteBatch(pkts []BatchPacket) (int, error)
ReloadConfig(c *config.C)
SupportsMultipleReaders() bool
SupportsGSO() bool
SupportsGRO() bool
Close() error
}
@@ -46,18 +37,9 @@ func (NoopConn) ListenOut(_ EncReader) {
func (NoopConn) SupportsMultipleReaders() bool {
return false
}
func (NoopConn) SupportsGSO() bool {
return false
}
func (NoopConn) SupportsGRO() bool {
return false
}
func (NoopConn) WriteTo(_ []byte, _ netip.AddrPort) error {
return nil
}
func (NoopConn) WriteBatch(pkts []BatchPacket) (int, error) {
return len(pkts), nil
}
func (NoopConn) ReloadConfig(_ *config.C) {
return
}
-17
View File
@@ -188,14 +188,6 @@ func (u *StdConn) SupportsMultipleReaders() bool {
return false
}
func (u *StdConn) SupportsGSO() bool {
return false
}
func (u *StdConn) SupportsGRO() bool {
return false
}
func (u *StdConn) Rebind() error {
var err error
if u.isV4 {
@@ -210,12 +202,3 @@ func (u *StdConn) Rebind() error {
return nil
}
func (u *StdConn) WriteBatch(pkts []BatchPacket) (int, error) {
for i := range pkts {
if err := u.WriteTo(pkts[i].Payload, pkts[i].Addr); err != nil {
return i, err
}
}
return len(pkts), nil
}
+2 -31
View File
@@ -10,11 +10,9 @@ package udp
import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"time"
"github.com/sirupsen/logrus"
"github.com/slackhq/nebula/config"
@@ -76,22 +74,12 @@ 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 {
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
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
return
}
r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n])
@@ -101,20 +89,3 @@ func (u *GenericConn) ListenOut(r EncReader) {
func (u *GenericConn) SupportsMultipleReaders() bool {
return false
}
func (u *GenericConn) SupportsGSO() bool {
return false
}
func (u *GenericConn) SupportsGRO() bool {
return false
}
func (u *GenericConn) WriteBatch(pkts []BatchPacket) (int, error) {
for i := range pkts {
if err := u.WriteTo(pkts[i].Payload, pkts[i].Addr); err != nil {
return i, err
}
}
return len(pkts), nil
}
+8 -362
View File
@@ -5,7 +5,6 @@ package udp
import (
"encoding/binary"
"errors"
"fmt"
"net"
"net/netip"
@@ -19,12 +18,10 @@ import (
)
type StdConn struct {
sysFd int
isV4 bool
l *logrus.Logger
batch int
gsoSupported bool
groSupported bool
sysFd int
isV4 bool
l *logrus.Logger
batch int
}
func maybeIPV4(ip net.IP) (net.IP, bool) {
@@ -35,78 +32,6 @@ func maybeIPV4(ip net.IP) (net.IP, bool) {
return ip, false
}
// supportsUDPOffload checks if the kernel supports UDP GSO (Generic Segmentation Offload)
// by attempting to get the UDP_SEGMENT socket option.
func supportsUDPOffload(fd int) bool {
_, err := unix.GetsockoptInt(fd, unix.IPPROTO_UDP, unix.UDP_SEGMENT)
return err == nil
}
// supportsUDPGRO checks if the kernel supports UDP GRO (Generic Receive Offload)
// and attempts to enable it on the socket.
func supportsUDPGRO(fd int) bool {
// Try to enable UDP_GRO
err := unix.SetsockoptInt(fd, unix.IPPROTO_UDP, unix.UDP_GRO, 1)
return err == nil
}
const (
// Maximum number of datagrams that can be coalesced with GSO/GRO
udpSegmentMaxDatagrams = 64
// Maximum size of a GRO coalesced packet (64KB is the practical limit)
// This is udpSegmentMaxDatagrams * MTU but capped at 65535
groMaxPacketSize = 65535
)
// setGSOSize writes a UDP_SEGMENT control message to the provided buffer
// with the given segment size. Returns the actual control message length.
func setGSOSize(control []byte, gsoSize uint16) int {
// Build the cmsghdr structure
cmsgLen := unix.CmsgLen(2) // 2 bytes for uint16 segment size
cmsg := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
cmsg.Level = unix.IPPROTO_UDP
cmsg.Type = unix.UDP_SEGMENT
cmsg.SetLen(cmsgLen)
// Write the segment size after the header (after cmsghdr)
binary.NativeEndian.PutUint16(control[unix.SizeofCmsghdr:], gsoSize)
return unix.CmsgSpace(2) // aligned size
}
// getGROSize parses a control message buffer to extract the UDP_GRO segment size.
// Returns 0 if no GRO control message is present (meaning the packet is not coalesced).
func getGROSize(control []byte, controlLen int) uint16 {
if controlLen < unix.SizeofCmsghdr {
return 0
}
// Parse control messages
for offset := 0; offset < controlLen; {
if offset+unix.SizeofCmsghdr > controlLen {
break
}
cmsg := (*unix.Cmsghdr)(unsafe.Pointer(&control[offset]))
cmsgDataLen := int(cmsg.Len) - unix.SizeofCmsghdr
if cmsgDataLen < 0 {
break
}
if cmsg.Level == unix.IPPROTO_UDP && cmsg.Type == unix.UDP_GRO {
if cmsgDataLen >= 2 {
return binary.NativeEndian.Uint16(control[offset+unix.SizeofCmsghdr:])
}
}
// Move to next control message (aligned)
offset += unix.CmsgSpace(cmsgDataLen)
}
return 0
}
func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
af := unix.AF_INET6
if ip.Is4() {
@@ -144,31 +69,13 @@ 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)
}
gsoSupported := supportsUDPOffload(fd)
if gsoSupported {
l.Info("UDP GSO offload is supported")
}
groSupported := supportsUDPGRO(fd)
if groSupported {
l.Info("UDP GRO offload is supported and enabled")
}
return &StdConn{sysFd: fd, isV4: ip.Is4(), l: l, batch: batch, gsoSupported: gsoSupported, groSupported: groSupported}, err
return &StdConn{sysFd: fd, isV4: ip.Is4(), l: l, batch: batch}, err
}
func (u *StdConn) SupportsMultipleReaders() bool {
return true
}
func (u *StdConn) SupportsGSO() bool {
return u.gsoSupported
}
func (u *StdConn) SupportsGRO() bool {
return u.groSupported
}
func (u *StdConn) Rebind() error {
return nil
}
@@ -218,27 +125,13 @@ func (u *StdConn) LocalAddr() (netip.AddrPort, error) {
func (u *StdConn) ListenOut(r EncReader) {
var ip netip.Addr
msgs, buffers, names, controls := u.PrepareRawMessages(u.batch)
msgs, buffers, names := u.PrepareRawMessages(u.batch)
read := u.ReadMulti
if u.batch == 1 {
read = u.ReadSingle
}
// Store the original control buffer size for resetting after each read
controlLen := 0
if u.groSupported && len(controls) > 0 && len(controls[0]) > 0 {
controlLen = len(controls[0])
}
for {
// Reset Controllen before each read - the kernel updates this field
// after recvmsg to indicate actual received control data length
if controlLen > 0 {
for i := range msgs {
setMsghdrControllen(&msgs[i].Hdr, controlLen)
}
}
n, err := read(msgs)
if err != nil {
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
@@ -246,36 +139,13 @@ func (u *StdConn) ListenOut(r EncReader) {
}
for i := 0; i < n; i++ {
// Extract source address
// 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])
}
srcAddr := netip.AddrPortFrom(ip.Unmap(), binary.BigEndian.Uint16(names[i][2:4]))
// Check for GRO coalesced packet
totalLen := int(msgs[i].Len)
segmentSize := uint16(0)
if controlLen > 0 {
segmentSize = getGROSize(controls[i], getMsghdrControllen(&msgs[i].Hdr))
}
if segmentSize > 0 && totalLen > int(segmentSize) {
// This is a GRO coalesced packet - split it into individual datagrams
for offset := 0; offset < totalLen; {
packetLen := int(segmentSize)
if offset+packetLen > totalLen {
// Last packet may be smaller
packetLen = totalLen - offset
}
r(srcAddr, buffers[i][offset:offset+packetLen])
offset += packetLen
}
} else {
// Single packet, no coalescing
r(srcAddr, buffers[i][:totalLen])
}
r(netip.AddrPortFrom(ip.Unmap(), binary.BigEndian.Uint16(names[i][2:4])), buffers[i][:msgs[i].Len])
}
}
}
@@ -354,10 +224,6 @@ func (u *StdConn) writeTo6(b []byte, ip netip.AddrPort) error {
}
func (u *StdConn) writeTo4(b []byte, ip netip.AddrPort) error {
if !ip.Addr().Is4() {
return ErrInvalidIPv6RemoteForSocket
}
var rsa unix.RawSockaddrInet4
rsa.Family = unix.AF_INET
rsa.Addr = ip.Addr().As4()
@@ -443,226 +309,6 @@ func (u *StdConn) Close() error {
return syscall.Close(u.sysFd)
}
func (u *StdConn) WriteBatch(pkts []BatchPacket) (int, error) {
if len(pkts) == 0 {
return 0, nil
}
// If GSO is supported, try to coalesce packets to the same destination
if u.gsoSupported {
return u.writeBatchGSO(pkts)
}
return u.writeBatchSendmmsg(pkts)
}
// writeBatchSendmmsg sends packets using sendmmsg without GSO coalescing
func (u *StdConn) writeBatchSendmmsg(pkts []BatchPacket) (int, error) {
msgs := make([]rawMessage, len(pkts))
iovecs := make([]iovec, len(pkts))
var names4 []unix.RawSockaddrInet4
var names6 []unix.RawSockaddrInet6
if u.isV4 {
names4 = make([]unix.RawSockaddrInet4, len(pkts))
} else {
names6 = make([]unix.RawSockaddrInet6, len(pkts))
}
for i := range pkts {
setIovecBase(&iovecs[i], &pkts[i].Payload[0])
setIovecLen(&iovecs[i], len(pkts[i].Payload))
msgs[i].Hdr.Iov = &iovecs[i]
setMsghdrIovlen(&msgs[i].Hdr, 1)
if u.isV4 {
names4[i].Family = unix.AF_INET
names4[i].Addr = pkts[i].Addr.Addr().As4()
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&names4[i].Port))[:], pkts[i].Addr.Port())
msgs[i].Hdr.Name = (*byte)(unsafe.Pointer(&names4[i]))
msgs[i].Hdr.Namelen = unix.SizeofSockaddrInet4
} else {
names6[i].Family = unix.AF_INET6
names6[i].Addr = pkts[i].Addr.Addr().As16()
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&names6[i].Port))[:], pkts[i].Addr.Port())
msgs[i].Hdr.Name = (*byte)(unsafe.Pointer(&names6[i]))
msgs[i].Hdr.Namelen = unix.SizeofSockaddrInet6
}
}
var sent int
for sent < len(msgs) {
n, _, errno := unix.Syscall6(
unix.SYS_SENDMMSG,
uintptr(u.sysFd),
uintptr(unsafe.Pointer(&msgs[sent])),
uintptr(len(msgs)-sent),
0,
0,
0,
)
if errno == unix.EINTR {
continue
}
if errno != 0 {
return sent, &net.OpError{Op: "sendmmsg", Err: errno}
}
sent += int(n)
}
return sent, nil
}
// writeBatchGSO sends packets using GSO coalescing when possible.
// Packets to the same destination with the same size are coalesced into a single
// GSO message. Mixed destinations or sizes fall back to individual sendmmsg calls.
func (u *StdConn) writeBatchGSO(pkts []BatchPacket) (int, error) {
// Group packets by destination and try to coalesce
totalSent := 0
i := 0
for i < len(pkts) {
// Find a run of packets to the same destination with compatible sizes
startIdx := i
dst := pkts[i].Addr
segmentSize := len(pkts[i].Payload)
// Count how many packets we can coalesce (same destination, same size except possibly last)
coalescedCount := 1
totalSize := segmentSize
for i+coalescedCount < len(pkts) && coalescedCount < udpSegmentMaxDatagrams {
next := pkts[i+coalescedCount]
if next.Addr != dst {
break
}
nextSize := len(next.Payload)
// For GSO, all packets except the last must have the same size
// The last packet can be smaller (but not larger)
if nextSize != segmentSize {
// Check if this could be the last packet (smaller is ok)
if nextSize < segmentSize && i+coalescedCount == len(pkts)-1 {
coalescedCount++
totalSize += nextSize
}
break
}
coalescedCount++
totalSize += nextSize
}
// If we have multiple packets to coalesce, use GSO
if coalescedCount > 1 {
err := u.sendGSO(pkts[startIdx:startIdx+coalescedCount], dst, segmentSize, totalSize)
if err != nil {
// If GSO fails (e.g., EIO due to NIC not supporting checksum offload),
// disable GSO and fall back to sendmmsg for the rest
if isGSOError(err) {
u.l.WithError(err).Warn("GSO send failed, disabling GSO for this connection")
u.gsoSupported = false
// Send remaining packets with sendmmsg
remaining, rerr := u.writeBatchSendmmsg(pkts[startIdx:])
return totalSent + remaining, rerr
}
return totalSent, err
}
totalSent += coalescedCount
i += coalescedCount
} else {
// Single packet, send without GSO overhead
err := u.WriteTo(pkts[i].Payload, pkts[i].Addr)
if err != nil {
return totalSent, err
}
totalSent++
i++
}
}
return totalSent, nil
}
// sendGSO sends coalesced packets using UDP GSO
func (u *StdConn) sendGSO(pkts []BatchPacket, dst netip.AddrPort, segmentSize, totalSize int) error {
// Allocate a buffer large enough for all packet payloads
coalescedBuf := make([]byte, totalSize)
offset := 0
for _, pkt := range pkts {
copy(coalescedBuf[offset:], pkt.Payload)
offset += len(pkt.Payload)
}
// Prepare control message with GSO segment size
control := make([]byte, unix.CmsgSpace(2))
controlLen := setGSOSize(control, uint16(segmentSize))
// Prepare the iovec
iov := iovec{}
setIovecBase(&iov, &coalescedBuf[0])
setIovecLen(&iov, totalSize)
// Prepare the msghdr
var hdr msghdr
hdr.Iov = &iov
setMsghdrIovlen(&hdr, 1)
hdr.Control = &control[0]
setMsghdrControllen(&hdr, controlLen)
// Declare sockaddr at function scope so it remains valid for the syscall
// (must not go out of scope before the syscall is made)
var rsa4 unix.RawSockaddrInet4
var rsa6 unix.RawSockaddrInet6
// Set destination address
if u.isV4 {
rsa4.Family = unix.AF_INET
rsa4.Addr = dst.Addr().As4()
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&rsa4.Port))[:], dst.Port())
hdr.Name = (*byte)(unsafe.Pointer(&rsa4))
hdr.Namelen = unix.SizeofSockaddrInet4
} else {
rsa6.Family = unix.AF_INET6
rsa6.Addr = dst.Addr().As16()
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&rsa6.Port))[:], dst.Port())
hdr.Name = (*byte)(unsafe.Pointer(&rsa6))
hdr.Namelen = unix.SizeofSockaddrInet6
}
for {
_, _, errno := unix.Syscall6(
unix.SYS_SENDMSG,
uintptr(u.sysFd),
uintptr(unsafe.Pointer(&hdr)),
0,
0,
0,
0,
)
if errno == unix.EINTR {
continue
}
if errno != 0 {
return &net.OpError{Op: "sendmsg", Err: errno}
}
return nil
}
}
// isGSOError returns true if the error indicates GSO is not supported by the NIC
func isGSOError(err error) bool {
var opErr *net.OpError
if !errors.As(err, &opErr) {
return false
}
// EIO typically means the NIC doesn't support checksum offload required for GSO
return errors.Is(opErr.Err, unix.EIO)
}
func NewUDPStatsEmitter(udpConns []Conn) func() {
// Check if our kernel supports SO_MEMINFO before registering the gauges
var udpGauges [][unix.SK_MEMINFO_VARS]metrics.Gauge
+3 -43
View File
@@ -30,26 +30,13 @@ type rawMessage struct {
Len uint32
}
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte, [][]byte) {
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
msgs := make([]rawMessage, n)
buffers := make([][]byte, n)
names := make([][]byte, n)
controls := make([][]byte, n)
// Use larger buffers if GRO is enabled to hold coalesced packets
bufSize := MTU
if u.groSupported {
bufSize = groMaxPacketSize
}
// Control buffer size for receiving UDP_GRO segment size
controlSize := 0
if u.groSupported {
controlSize = unix.CmsgSpace(2) // space for uint16 segment size
}
for i := range msgs {
buffers[i] = make([]byte, bufSize)
buffers[i] = make([]byte, MTU)
names[i] = make([]byte, unix.SizeofSockaddrInet6)
vs := []iovec{
@@ -61,34 +48,7 @@ func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte, [
msgs[i].Hdr.Name = &names[i][0]
msgs[i].Hdr.Namelen = uint32(len(names[i]))
// Set up control message buffer for GRO
if controlSize > 0 {
controls[i] = make([]byte, controlSize)
msgs[i].Hdr.Control = &controls[i][0]
msgs[i].Hdr.Controllen = uint32(controlSize)
}
}
return msgs, buffers, names, controls
}
func setIovecBase(iov *iovec, base *byte) {
iov.Base = base
}
func setIovecLen(iov *iovec, l int) {
iov.Len = uint32(l)
}
func setMsghdrIovlen(hdr *msghdr, l int) {
hdr.Iovlen = uint32(l)
}
func setMsghdrControllen(hdr *msghdr, l int) {
hdr.Controllen = uint32(l)
}
func getMsghdrControllen(hdr *msghdr) int {
return int(hdr.Controllen)
return msgs, buffers, names
}
+3 -43
View File
@@ -33,26 +33,13 @@ type rawMessage struct {
Pad0 [4]byte
}
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte, [][]byte) {
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
msgs := make([]rawMessage, n)
buffers := make([][]byte, n)
names := make([][]byte, n)
controls := make([][]byte, n)
// Use larger buffers if GRO is enabled to hold coalesced packets
bufSize := MTU
if u.groSupported {
bufSize = groMaxPacketSize
}
// Control buffer size for receiving UDP_GRO segment size
controlSize := 0
if u.groSupported {
controlSize = unix.CmsgSpace(2) // space for uint16 segment size
}
for i := range msgs {
buffers[i] = make([]byte, bufSize)
buffers[i] = make([]byte, MTU)
names[i] = make([]byte, unix.SizeofSockaddrInet6)
vs := []iovec{
@@ -64,34 +51,7 @@ func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte, [
msgs[i].Hdr.Name = &names[i][0]
msgs[i].Hdr.Namelen = uint32(len(names[i]))
// Set up control message buffer for GRO
if controlSize > 0 {
controls[i] = make([]byte, controlSize)
msgs[i].Hdr.Control = &controls[i][0]
msgs[i].Hdr.Controllen = uint64(controlSize)
}
}
return msgs, buffers, names, controls
}
func setIovecBase(iov *iovec, base *byte) {
iov.Base = base
}
func setIovecLen(iov *iovec, l int) {
iov.Len = uint64(l)
}
func setMsghdrIovlen(hdr *msghdr, l int) {
hdr.Iovlen = uint64(l)
}
func setMsghdrControllen(hdr *msghdr, l int) {
hdr.Controllen = uint64(l)
}
func getMsghdrControllen(hdr *msghdr) int {
return int(hdr.Controllen)
return msgs, buffers, names
}
+10 -40
View File
@@ -14,7 +14,6 @@ import (
"sync"
"sync/atomic"
"syscall"
"time"
"unsafe"
"github.com/sirupsen/logrus"
@@ -67,7 +66,7 @@ func NewRIOListener(l *logrus.Logger, addr netip.Addr, port int) (*RIOConn, erro
u := &RIOConn{l: l}
err := u.bind(l, &windows.SockaddrInet6{Addr: addr.As16(), Port: port})
err := u.bind(&windows.SockaddrInet6{Addr: addr.As16(), Port: port})
if err != nil {
return nil, fmt.Errorf("bind: %w", err)
}
@@ -83,11 +82,11 @@ func NewRIOListener(l *logrus.Logger, addr netip.Addr, port int) (*RIOConn, erro
return u, nil
}
func (u *RIOConn) bind(l *logrus.Logger, sa windows.Sockaddr) error {
func (u *RIOConn) bind(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 fmt.Errorf("winrio.Socket error: %w", err)
return err
}
// Enable v4 for this socket
@@ -101,40 +100,35 @@ func (u *RIOConn) bind(l *logrus.Logger, 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 {
// 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")
return err
}
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 {
// 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")
return err
}
err = u.rx.Open()
if err != nil {
return fmt.Errorf("error rx.Open(): %w", err)
return err
}
err = u.tx.Open()
if err != nil {
return fmt.Errorf("error tx.Open(): %w", err)
return err
}
u.rq, err = winrio.CreateRequestQueue(u.sock, packetsPerRing, 1, packetsPerRing, 1, u.rx.cq, u.tx.cq, 0)
if err != nil {
return fmt.Errorf("error CreateRequestQueue: %w", err)
return err
}
err = windows.Bind(u.sock, sa)
if err != nil {
return fmt.Errorf("error windows.Bind(): %w", err)
return err
}
return nil
@@ -143,22 +137,15 @@ func (u *RIOConn) bind(l *logrus.Logger, 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
}
// 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")
}
u.l.WithError(err).Error("unexpected udp socket receive error")
continue
}
@@ -332,29 +319,12 @@ func (u *RIOConn) SupportsMultipleReaders() bool {
return false
}
func (u *RIOConn) SupportsGSO() bool {
return false
}
func (u *RIOConn) SupportsGRO() bool {
return false
}
func (u *RIOConn) Rebind() error {
return nil
}
func (u *RIOConn) ReloadConfig(*config.C) {}
func (u *RIOConn) WriteBatch(pkts []BatchPacket) (int, error) {
for i := range pkts {
if err := u.WriteTo(pkts[i].Payload, pkts[i].Addr); err != nil {
return i, err
}
}
return len(pkts), nil
}
func (u *RIOConn) Close() error {
if !u.isOpen.CompareAndSwap(true, false) {
return nil
-17
View File
@@ -131,14 +131,6 @@ func (u *TesterConn) SupportsMultipleReaders() bool {
return false
}
func (u *TesterConn) SupportsGSO() bool {
return false
}
func (u *TesterConn) SupportsGRO() bool {
return false
}
func (u *TesterConn) Rebind() error {
return nil
}
@@ -150,12 +142,3 @@ func (u *TesterConn) Close() error {
}
return nil
}
func (u *TesterConn) WriteBatch(pkts []BatchPacket) (int, error) {
for i := range pkts {
if err := u.WriteTo(pkts[i].Payload, pkts[i].Addr); err != nil {
return i, err
}
}
return len(pkts), nil
}