diff --git a/.github/workflows/gofmt.yml b/.github/workflows/gofmt.yml index 51575623..4d57c7b2 100644 --- a/.github/workflows/gofmt.yml +++ b/.github/workflows/gofmt.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 35d72dea..9ce1d5e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/.github/workflows/smoke-extra.yml b/.github/workflows/smoke-extra.yml index 966bddd2..24f899ab 100644 --- a/.github/workflows/smoke-extra.yml +++ b/.github/workflows/smoke-extra.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 0ccae47b..4994a87e 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 050a68e4..aeaea294 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0efa7959..104b52e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [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 +764,8 @@ created.) - Initial public release. -[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.0...HEAD +[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.1...HEAD +[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 diff --git a/cert/cert.go b/cert/cert.go index 855815a7..9d40e625 100644 --- a/cert/cert.go +++ b/cert/cert.go @@ -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 diff --git a/cert/cert_v1.go b/cert/cert_v1.go index 09a181d6..c32f409a 100644 --- a/cert/cert_v1.go +++ b/cert/cert_v1.go @@ -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 { diff --git a/cert/cert_v1_test.go b/cert/cert_v1_test.go index 3b7d5859..6e374634 100644 --- a/cert/cert_v1_test.go +++ b/cert/cert_v1_test.go @@ -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 diff --git a/cert/cert_v2.go b/cert/cert_v2.go index ac21cb13..4648c496 100644 --- a/cert/cert_v2.go +++ b/cert/cert_v2.go @@ -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 } diff --git a/cert/cert_v2_test.go b/cert/cert_v2_test.go index 84362efe..e2a6b98d 100644 --- a/cert/cert_v2_test.go +++ b/cert/cert_v2_test.go @@ -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 diff --git a/cert/crypto_test.go b/cert/crypto_test.go index 174b2419..bf6e0654 100644 --- a/cert/crypto_test.go +++ b/cert/crypto_test.go @@ -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") diff --git a/cert/errors.go b/cert/errors.go index 99006756..8c480a14 100644 --- a/cert/errors.go +++ b/cert/errors.go @@ -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") diff --git a/cert/pem.go b/cert/pem.go index a5aabdce..8942c23a 100644 --- a/cert/pem.go +++ b/cert/pem.go @@ -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 } diff --git a/cert/pem_test.go b/cert/pem_test.go index ff4410ce..310c57a3 100644 --- a/cert/pem_test.go +++ b/cert/pem_test.go @@ -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) diff --git a/cmd/nebula-cert/ca_test.go b/cmd/nebula-cert/ca_test.go index cd3f0bf9..779d3a2d 100644 --- a/cmd/nebula-cert/ca_test.go +++ b/cmd/nebula-cert/ca_test.go @@ -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() diff --git a/e2e/helpers_test.go b/e2e/helpers_test.go index 7a802c99..39843efe 100644 --- a/e2e/helpers_test.go +++ b/e2e/helpers_test.go @@ -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) diff --git a/examples/config.yml b/examples/config.yml index b98b32cc..b713be4c 100644 --- a/examples/config.yml +++ b/examples/config.yml @@ -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. diff --git a/go.mod b/go.mod index e927eb8b..1c564d03 100644 --- a/go.mod +++ b/go.mod @@ -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.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/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 ) @@ -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/mod v0.31.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.33.0 // indirect + golang.org/x/tools v0.40.0 // indirect ) diff --git a/go.sum b/go.sum index 3679fac6..c4613e01 100644 --- a/go.sum +++ b/go.sum @@ -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,14 +206,13 @@ 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= @@ -224,8 +223,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.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= @@ -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= diff --git a/handshake_ix.go b/handshake_ix.go index 25a0c371..4382fafc 100644 --- a/handshake_ix.go +++ b/handshake_ix.go @@ -1,6 +1,7 @@ package nebula import ( + "bytes" "net/netip" "time" @@ -176,6 +177,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()) @@ -602,6 +610,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). diff --git a/interface.go b/interface.go index 395f725b..6bf91f84 100644 --- a/interface.go +++ b/interface.go @@ -77,7 +77,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 @@ -122,34 +123,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) @@ -326,6 +327,7 @@ func (f *Interface) listenIn(reader io.ReadWriteCloser, i int) { 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) @@ -389,16 +391,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 } } @@ -407,6 +409,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) diff --git a/main.go b/main.go index 2c2d9b82..1da3c562 100644 --- a/main.go +++ b/main.go @@ -298,6 +298,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) diff --git a/noiseutil/boring.go b/noiseutil/boring.go index e9ad19bb..2129af71 100644 --- a/noiseutil/boring.go +++ b/noiseutil/boring.go @@ -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) diff --git a/outside.go b/outside.go index 32ce1af2..0a9767ae 100644 --- a/outside.go +++ b/outside.go @@ -525,7 +525,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) } } @@ -543,6 +543,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). diff --git a/overlay/tun_linux.go b/overlay/tun_linux.go index 32bf51f5..ea666f86 100644 --- a/overlay/tun_linux.go +++ b/overlay/tun_linux.go @@ -10,6 +10,7 @@ import ( "net/netip" "os" "strings" + "sync" "sync/atomic" "time" "unsafe" @@ -40,6 +41,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 } @@ -131,6 +137,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 +171,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) @@ -673,14 +687,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) } diff --git a/routing/gateway.go b/routing/gateway.go index 59d38a91..88cf0933 100644 --- a/routing/gateway.go +++ b/routing/gateway.go @@ -6,7 +6,7 @@ import ( ) const ( - // Sentinal value + // Sentinel value BucketNotCalculated = -1 ) diff --git a/udp/udp_generic.go b/udp/udp_generic.go index 3cefc904..e9dad6c5 100644 --- a/udp/udp_generic.go +++ b/udp/udp_generic.go @@ -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]) diff --git a/udp/udp_rio_windows.go b/udp/udp_rio_windows.go index 1d602d01..3d60f34c 100644 --- a/udp/udp_rio_windows.go +++ b/udp/udp_rio_windows.go @@ -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 }