mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 17:47:02 +02:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef1739bec4 | |||
| 030b7e2763 | |||
| 6b6a4bc1cc | |||
| 30db76ed79 | |||
| 15333f9fed | |||
| 42bee7cf17 | |||
| 02d8bcac68 | |||
| 0b02d982b2 | |||
| e1e92f017c | |||
| e5f60fa54f | |||
| bf49e78243 | |||
| 72a40007ea | |||
| ac3bd9cdd0 | |||
| 88379b89f5 | |||
| 1283ff0db4 | |||
| 523209ec0b | |||
| a4a6143b6a | |||
| 1b2d639b14 | |||
| 9933970e67 | |||
| d7a3f01465 | |||
| 69259e6307 | |||
| 2f71d6b22d | |||
| 3ec527e42c | |||
| 2d16940232 | |||
| cba294ffa4 | |||
| 48406f85da | |||
| 14a1af132e | |||
| 59e24b98bd | |||
| 56067afca2 |
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
name: Build Linux/BSD All
|
name: Build Linux/BSD All
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
mv build/*.tar.gz release
|
mv build/*.tar.gz release
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: linux-latest
|
name: linux-latest
|
||||||
path: release
|
path: release
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
name: Build Windows
|
name: Build Windows
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
mv dist\windows\wintun build\dist\windows\
|
mv dist\windows\wintun build\dist\windows\
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: windows-latest
|
name: windows-latest
|
||||||
path: build
|
path: build
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
HAS_SIGNING_CREDS: ${{ secrets.AC_USERNAME != '' }}
|
HAS_SIGNING_CREDS: ${{ secrets.AC_USERNAME != '' }}
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Import certificates
|
- name: Import certificates
|
||||||
if: env.HAS_SIGNING_CREDS == 'true'
|
if: env.HAS_SIGNING_CREDS == 'true'
|
||||||
uses: Apple-Actions/import-codesign-certs@v5
|
uses: Apple-Actions/import-codesign-certs@v6
|
||||||
with:
|
with:
|
||||||
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
|
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
|
||||||
p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
|
p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||||
@@ -104,7 +104,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: darwin-latest
|
name: darwin-latest
|
||||||
path: ./release/*
|
path: ./release/*
|
||||||
@@ -124,11 +124,11 @@ jobs:
|
|||||||
# be overwritten
|
# be overwritten
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
if: ${{ env.HAS_DOCKER_CREDS == 'true' }}
|
if: ${{ env.HAS_DOCKER_CREDS == 'true' }}
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
if: ${{ env.HAS_DOCKER_CREDS == 'true' }}
|
if: ${{ env.HAS_DOCKER_CREDS == 'true' }}
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: linux-latest
|
name: linux-latest
|
||||||
path: artifacts
|
path: artifacts
|
||||||
@@ -160,10 +160,10 @@ jobs:
|
|||||||
needs: [build-linux, build-darwin, build-windows]
|
needs: [build-linux, build-darwin, build-windows]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
- name: Build test mobile
|
- name: Build test mobile
|
||||||
run: make build-test-mobile
|
run: make build-test-mobile
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v5
|
- uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: e2e packet flow linux-latest
|
name: e2e packet flow linux-latest
|
||||||
path: e2e/mermaid/linux-latest
|
path: e2e/mermaid/linux-latest
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
@@ -98,7 +98,7 @@ jobs:
|
|||||||
os: [windows-latest, macos-latest]
|
os: [windows-latest, macos-latest]
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
@@ -125,7 +125,7 @@ jobs:
|
|||||||
- name: End 2 end
|
- name: End 2 end
|
||||||
run: make e2evv
|
run: make e2evv
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v5
|
- uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: e2e packet flow ${{ matrix.os }}
|
name: e2e packet flow ${{ matrix.os }}
|
||||||
path: e2e/mermaid/${{ matrix.os }}
|
path: e2e/mermaid/${{ matrix.os }}
|
||||||
|
|||||||
+113
-3
@@ -7,12 +7,116 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
### Changed
|
||||||
|
|
||||||
- `default_local_cidr_any` now defaults to false, meaning that any firewall rule
|
- 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.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for ipv6 and multiple ipv4/6 addresses in the overlay.
|
||||||
|
A new v2 ASN.1 based certificate format.
|
||||||
|
Certificates now have a unified interface for external implementations.
|
||||||
|
(#1212, #1216, #1345, #1359, #1381, #1419, #1464, #1466, #1451, #1476, #1467, #1481, #1399, #1488, #1492, #1495, #1468, #1521, #1535, #1538)
|
||||||
|
- Add the ability to mark packets on linux to better target nebula packets in iptables/nftables. (#1331)
|
||||||
|
- Add ECMP support for `unsafe_routes`. (#1332)
|
||||||
|
- PKCS11 support for P256 keys when built with `pkcs11` tag (#1153, #1482)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **NOTE**: `default_local_cidr_any` now defaults to false, meaning that any firewall rule
|
||||||
intended to target an `unsafe_routes` entry must explicitly declare it via the
|
intended to target an `unsafe_routes` entry must explicitly declare it via the
|
||||||
`local_cidr` field. This is almost always the intended behavior. This flag is
|
`local_cidr` field. This is almost always the intended behavior. This flag is
|
||||||
deprecated and will be removed in a future release.
|
deprecated and will be removed in a future release. (#1373)
|
||||||
|
- Improve logging when a relay is in use on an inbound packet. (#1533)
|
||||||
|
- Avoid fatal errors if `rountines` is > 1 on systems that don't support more than 1 routine. (#1531)
|
||||||
|
- Log a warning if a firewall rule contains an `any` that negates a more restrictive filter. (#1513)
|
||||||
|
- Accept encrypted CA passphrase from an environment variable. (#1421)
|
||||||
|
- Allow handshaking with any trusted remote. (#1509)
|
||||||
|
- Log only the count of blocklisted certificate fingerprints instead of the entire list. (#1525)
|
||||||
|
- Don't fatal when the ssh server is unable to be configured successfully. (#1520)
|
||||||
|
- Update to build against go v1.25. (#1483)
|
||||||
|
- Allow projects using `nebula` as a library with userspace networking to configure the `logger` and build version. (#1239)
|
||||||
|
- Upgrade to `yaml.v3`. (#1148, #1371, #1438, #1478)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix a potential bug with udp ipv4 only on darwin. (#1532)
|
||||||
|
- Improve lost packet statistics. (#1441, #1537)
|
||||||
|
- Honor `remote_allow_list` in hole punch response. (#1186)
|
||||||
|
- Fix a panic when `tun.use_system_route_table` is `true` and a route lacks a destination. (#1437)
|
||||||
|
- Fix an issue when `tun.use_system_route_table: true` could result in heavy CPU utilization when many thousands of routes
|
||||||
|
are present. (#1326)
|
||||||
|
- Fix tests for 32 bit machines. (#1394)
|
||||||
|
- Fix a possible 32bit integer underflow in config handling. (#1353)
|
||||||
|
- Fix moving a udp address from one vpn address to another in the `static_host_map`
|
||||||
|
which could cause rapid re-handshaking with an incorrect remote. (#1259)
|
||||||
|
- Improve smoke tests in environments where the docker network is not the default. (#1347)
|
||||||
|
|
||||||
|
## [1.9.7] - 2025-10-10
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fix an issue where Nebula could incorrectly accept and process a packet from an erroneous source IP when the sender's
|
||||||
|
certificate is configured with unsafe_routes (cert v1/v2) or multiple IPs (cert v2). (#1494)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Disable sending `recv_error` messages when a packet is received outside the allowable counter window. (#1459)
|
||||||
|
- Improve error messages and remove some unnecessary fatal conditions in the Windows and generic udp listener. (#1453)
|
||||||
|
|
||||||
|
## [1.9.6] - 2025-7-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support dropping inactive tunnels. This is disabled by default in this release but can be enabled with `tunnels.drop_inactive`. See example config for more details. (#1413)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix Darwin freeze due to presence of some Network Extensions (#1426)
|
||||||
|
- Ensure the same relay tunnel is always used when multiple relay tunnels are present (#1422)
|
||||||
|
- Fix Windows freeze due to ICMP error handling (#1412)
|
||||||
|
- Fix relay migration panic (#1403)
|
||||||
|
|
||||||
|
## [1.9.5] - 2024-12-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Gracefully ignore v2 certificates. (#1282)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix relays that refuse to re-establish after one of the remote tunnel pairs breaks. (#1277)
|
||||||
|
|
||||||
## [1.9.4] - 2024-09-09
|
## [1.9.4] - 2024-09-09
|
||||||
|
|
||||||
@@ -671,7 +775,13 @@ created.)
|
|||||||
|
|
||||||
- Initial public release.
|
- Initial public release.
|
||||||
|
|
||||||
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.9.4...HEAD
|
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.2...HEAD
|
||||||
|
[1.10.2]: https://github.com/slackhq/nebula/releases/tag/v1.10.2
|
||||||
|
[1.10.1]: https://github.com/slackhq/nebula/releases/tag/v1.10.1
|
||||||
|
[1.10.0]: https://github.com/slackhq/nebula/releases/tag/v1.10.0
|
||||||
|
[1.9.7]: https://github.com/slackhq/nebula/releases/tag/v1.9.7
|
||||||
|
[1.9.6]: https://github.com/slackhq/nebula/releases/tag/v1.9.6
|
||||||
|
[1.9.5]: https://github.com/slackhq/nebula/releases/tag/v1.9.5
|
||||||
[1.9.4]: https://github.com/slackhq/nebula/releases/tag/v1.9.4
|
[1.9.4]: https://github.com/slackhq/nebula/releases/tag/v1.9.4
|
||||||
[1.9.3]: https://github.com/slackhq/nebula/releases/tag/v1.9.3
|
[1.9.3]: https://github.com/slackhq/nebula/releases/tag/v1.9.3
|
||||||
[1.9.2]: https://github.com/slackhq/nebula/releases/tag/v1.9.2
|
[1.9.2]: https://github.com/slackhq/nebula/releases/tag/v1.9.2
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ func (cc *CachedCertificate) String() string {
|
|||||||
// Recombine will attempt to unmarshal a certificate received in a handshake.
|
// 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
|
// 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.
|
// 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) {
|
func Recombine(v Version, rawCertBytes, publicKey []byte, curve Curve) (Certificate, error) {
|
||||||
if publicKey == nil {
|
if publicKey == nil {
|
||||||
return nil, ErrNoPeerStaticKey
|
return nil, ErrNoPeerStaticKey
|
||||||
|
|||||||
+10
-3
@@ -426,7 +426,7 @@ func unmarshalCertificateV1(b []byte, publicKey []byte) (*certificateV1, error)
|
|||||||
unsafeNetworks: make([]netip.Prefix, len(rc.Details.Subnets)/2),
|
unsafeNetworks: make([]netip.Prefix, len(rc.Details.Subnets)/2),
|
||||||
notBefore: time.Unix(rc.Details.NotBefore, 0),
|
notBefore: time.Unix(rc.Details.NotBefore, 0),
|
||||||
notAfter: time.Unix(rc.Details.NotAfter, 0),
|
notAfter: time.Unix(rc.Details.NotAfter, 0),
|
||||||
publicKey: make([]byte, len(rc.Details.PublicKey)),
|
publicKey: nil,
|
||||||
isCA: rc.Details.IsCA,
|
isCA: rc.Details.IsCA,
|
||||||
curve: rc.Details.Curve,
|
curve: rc.Details.Curve,
|
||||||
},
|
},
|
||||||
@@ -437,11 +437,18 @@ func unmarshalCertificateV1(b []byte, publicKey []byte) (*certificateV1, error)
|
|||||||
copy(nc.details.groups, rc.Details.Groups)
|
copy(nc.details.groups, rc.Details.Groups)
|
||||||
nc.details.issuer = hex.EncodeToString(rc.Details.Issuer)
|
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(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
|
var ip netip.Addr
|
||||||
for i, rawIp := range rc.Details.Ips {
|
for i, rawIp := range rc.Details.Ips {
|
||||||
|
|||||||
+63
-1
@@ -62,6 +62,62 @@ func TestCertificateV1_Marshal(t *testing.T) {
|
|||||||
assert.Equal(t, nc.Groups(), nc2.Groups())
|
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) {
|
func TestCertificateV1_PublicKeyPem(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||||
@@ -99,13 +155,19 @@ func TestCertificateV1_PublicKeyPem(t *testing.T) {
|
|||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
AAAAAAAAAAAAAAAAAAAAAAA=
|
AAAAAAAAAAAAAAAAAAAAAAA=
|
||||||
-----END NEBULA P256 PUBLIC KEY-----
|
-----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)
|
pubP256Key, _, _, err := UnmarshalPublicKeyFromPEM(pubP256KeyPem)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
nc.details.curve = Curve_P256
|
nc.details.curve = Curve_P256
|
||||||
nc.details.publicKey = pubP256Key
|
nc.details.publicKey = pubP256Key
|
||||||
assert.Equal(t, Curve_P256, nc.Curve())
|
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())
|
assert.True(t, nc.IsCA())
|
||||||
|
|
||||||
nc.details.isCA = false
|
nc.details.isCA = false
|
||||||
|
|||||||
+7
-1
@@ -592,7 +592,13 @@ func unmarshalCertificateV2(b []byte, publicKey []byte, curve Curve) (*certifica
|
|||||||
// Maybe grab the public key
|
// Maybe grab the public key
|
||||||
var rawPublicKey cryptobyte.String
|
var rawPublicKey cryptobyte.String
|
||||||
if len(publicKey) > 0 {
|
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) {
|
} else if !input.ReadOptionalASN1(&rawPublicKey, nil, TagCertPublicKey) {
|
||||||
return nil, ErrBadFormat
|
return nil, ErrBadFormat
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-1
@@ -76,6 +76,58 @@ func TestCertificateV2_Marshal(t *testing.T) {
|
|||||||
assert.Equal(t, nc.Groups(), nc2.Groups())
|
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) {
|
func TestCertificateV2_PublicKeyPem(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||||
@@ -114,12 +166,19 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|||||||
AAAAAAAAAAAAAAAAAAAAAAA=
|
AAAAAAAAAAAAAAAAAAAAAAA=
|
||||||
-----END NEBULA P256 PUBLIC KEY-----
|
-----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)
|
pubP256Key, _, _, err := UnmarshalPublicKeyFromPEM(pubP256KeyPem)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
nc.curve = Curve_P256
|
nc.curve = Curve_P256
|
||||||
nc.publicKey = pubP256Key
|
nc.publicKey = pubP256Key
|
||||||
assert.Equal(t, Curve_P256, nc.Curve())
|
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())
|
assert.True(t, nc.IsCA())
|
||||||
|
|
||||||
nc.details.isCA = false
|
nc.details.isCA = false
|
||||||
|
|||||||
+1
-1
@@ -79,7 +79,7 @@ qrlJ69wer3ZUHFXA
|
|||||||
assert.Nil(t, k)
|
assert.Nil(t, k)
|
||||||
assert.Equal(t, rest, invalidPem)
|
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.
|
// it's missing the requisite pre-encapsulation boundary.
|
||||||
curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest)
|
curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest)
|
||||||
require.EqualError(t, err, "input did not contain a valid PEM encoded block")
|
require.EqualError(t, err, "input did not contain a valid PEM encoded block")
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ var (
|
|||||||
ErrPrivateKeyEncrypted = errors.New("private key must be decrypted")
|
ErrPrivateKeyEncrypted = errors.New("private key must be decrypted")
|
||||||
ErrCaNotFound = errors.New("could not find ca for the certificate")
|
ErrCaNotFound = errors.New("could not find ca for the certificate")
|
||||||
ErrUnknownVersion = errors.New("certificate version unrecognized")
|
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")
|
ErrInvalidPEMBlock = errors.New("input did not contain a valid PEM encoded block")
|
||||||
ErrInvalidPEMCertificateBanner = errors.New("bytes did not contain a proper certificate banner")
|
ErrInvalidPEMCertificateBanner = errors.New("bytes did not contain a proper certificate banner")
|
||||||
|
|||||||
+1
-1
@@ -86,7 +86,7 @@ func MarshalSigningPublicKeyToPEM(curve Curve, b []byte) []byte {
|
|||||||
case Curve_CURVE25519:
|
case Curve_CURVE25519:
|
||||||
return pem.EncodeToMemory(&pem.Block{Type: Ed25519PublicKeyBanner, Bytes: b})
|
return pem.EncodeToMemory(&pem.Block{Type: Ed25519PublicKeyBanner, Bytes: b})
|
||||||
case Curve_P256:
|
case Curve_P256:
|
||||||
return pem.EncodeToMemory(&pem.Block{Type: P256PublicKeyBanner, Bytes: b})
|
return pem.EncodeToMemory(&pem.Block{Type: ECDSAP256PublicKeyBanner, Bytes: b})
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -44,7 +44,7 @@ bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
|
|||||||
assert.Equal(t, rest, invalidPem)
|
assert.Equal(t, rest, invalidPem)
|
||||||
require.EqualError(t, err, "bytes did not contain a proper certificate banner")
|
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.
|
// it's missing the requisite pre-encapsulation boundary.
|
||||||
cert, rest, err = UnmarshalCertificateFromPEM(rest)
|
cert, rest, err = UnmarshalCertificateFromPEM(rest)
|
||||||
assert.Nil(t, cert)
|
assert.Nil(t, cert)
|
||||||
@@ -106,7 +106,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|||||||
assert.Equal(t, rest, invalidPem)
|
assert.Equal(t, rest, invalidPem)
|
||||||
require.EqualError(t, err, "bytes did not contain a proper Ed25519/ECDSA private key banner")
|
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.
|
// it's missing the requisite pre-encapsulation boundary.
|
||||||
k, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)
|
k, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)
|
||||||
assert.Nil(t, k)
|
assert.Nil(t, k)
|
||||||
@@ -168,7 +168,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
|||||||
assert.Equal(t, rest, invalidPem)
|
assert.Equal(t, rest, invalidPem)
|
||||||
require.EqualError(t, err, "bytes did not contain a proper private key banner")
|
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.
|
// it's missing the requisite pre-encapsulation boundary.
|
||||||
k, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)
|
k, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)
|
||||||
assert.Nil(t, k)
|
assert.Nil(t, k)
|
||||||
@@ -221,7 +221,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
|||||||
require.EqualError(t, err, "bytes did not contain a proper public key banner")
|
require.EqualError(t, err, "bytes did not contain a proper public key banner")
|
||||||
assert.Equal(t, rest, invalidPem)
|
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.
|
// it's missing the requisite pre-encapsulation boundary.
|
||||||
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
|
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
|
||||||
assert.Nil(t, k)
|
assert.Nil(t, k)
|
||||||
@@ -299,7 +299,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
|||||||
require.EqualError(t, err, "bytes did not contain a proper public key banner")
|
require.EqualError(t, err, "bytes did not contain a proper public key banner")
|
||||||
assert.Equal(t, rest, invalidPem)
|
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.
|
// it's missing the requisite pre-encapsulation boundary.
|
||||||
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
|
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
|
||||||
assert.Nil(t, k)
|
assert.Nil(t, k)
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ func Test_ca(t *testing.T) {
|
|||||||
assert.Empty(t, b)
|
assert.Empty(t, b)
|
||||||
assert.Len(t, lKey, 64)
|
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(keyF.Name())
|
||||||
os.Remove(crtF.Name())
|
os.Remove(crtF.Name())
|
||||||
ob.Reset()
|
ob.Reset()
|
||||||
|
|||||||
+38
-8
@@ -25,11 +25,12 @@ import (
|
|||||||
|
|
||||||
func BenchmarkHotPath(b *testing.B) {
|
func BenchmarkHotPath(b *testing.B) {
|
||||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||||
myControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version1, ca, caKey, "me", "10.128.0.1/24", nil)
|
myControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "me", "10.128.0.1/24", nil)
|
||||||
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "them", "10.128.0.2/24", nil)
|
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "them", "10.128.0.2/24", nil)
|
||||||
|
|
||||||
// Put their info in our lighthouse
|
// Put their info in our lighthouse
|
||||||
myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
||||||
|
theirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)
|
||||||
|
|
||||||
// Start the servers
|
// Start the servers
|
||||||
myControl.Start()
|
myControl.Start()
|
||||||
@@ -38,6 +39,9 @@ func BenchmarkHotPath(b *testing.B) {
|
|||||||
r := router.NewR(b, myControl, theirControl)
|
r := router.NewR(b, myControl, theirControl)
|
||||||
r.CancelFlowLogs()
|
r.CancelFlowLogs()
|
||||||
|
|
||||||
|
assertTunnel(b, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
myControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("Hi from me"))
|
myControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("Hi from me"))
|
||||||
_ = r.RouteForAllUntilTxTun(theirControl)
|
_ = r.RouteForAllUntilTxTun(theirControl)
|
||||||
@@ -47,6 +51,39 @@ func BenchmarkHotPath(b *testing.B) {
|
|||||||
theirControl.Stop()
|
theirControl.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkHotPathRelay(b *testing.B) {
|
||||||
|
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||||
|
myControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version1, ca, caKey, "me ", "10.128.0.1/24", m{"relay": m{"use_relays": true}})
|
||||||
|
relayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "relay ", "10.128.0.128/24", m{"relay": m{"am_relay": true}})
|
||||||
|
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "them ", "10.128.0.2/24", m{"relay": m{"use_relays": true}})
|
||||||
|
|
||||||
|
// Teach my how to get to the relay and that their can be reached via the relay
|
||||||
|
myControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)
|
||||||
|
myControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})
|
||||||
|
relayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
||||||
|
|
||||||
|
// Build a router so we don't have to reason who gets which packet
|
||||||
|
r := router.NewR(b, myControl, relayControl, theirControl)
|
||||||
|
r.CancelFlowLogs()
|
||||||
|
|
||||||
|
// Start the servers
|
||||||
|
myControl.Start()
|
||||||
|
relayControl.Start()
|
||||||
|
theirControl.Start()
|
||||||
|
|
||||||
|
assertTunnel(b, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
myControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("Hi from me"))
|
||||||
|
_ = r.RouteForAllUntilTxTun(theirControl)
|
||||||
|
}
|
||||||
|
|
||||||
|
myControl.Stop()
|
||||||
|
theirControl.Stop()
|
||||||
|
relayControl.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
func TestGoodHandshake(t *testing.T) {
|
func TestGoodHandshake(t *testing.T) {
|
||||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||||
myControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "me", "10.128.0.1/24", nil)
|
myControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "me", "10.128.0.1/24", nil)
|
||||||
@@ -1341,13 +1378,6 @@ func TestGoodHandshakeUnsafeDest(t *testing.T) {
|
|||||||
"tun": m{
|
"tun": m{
|
||||||
"unsafe_routes": []m{route},
|
"unsafe_routes": []m{route},
|
||||||
},
|
},
|
||||||
"firewall": m{
|
|
||||||
"unsafe_outbound": []m{{
|
|
||||||
"port": "any",
|
|
||||||
"proto": "any",
|
|
||||||
"host": "any",
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
myControl, myVpnIpNet, myUdpAddr, myConfig := newSimpleServer(cert.Version2, ca, caKey, "me", "10.128.0.1/24", myCfg)
|
myControl, myVpnIpNet, myUdpAddr, myConfig := newSimpleServer(cert.Version2, ca, caKey, "me", "10.128.0.1/24", myCfg)
|
||||||
t.Logf("my config %v", myConfig)
|
t.Logf("my config %v", myConfig)
|
||||||
|
|||||||
+6
-8
@@ -85,9 +85,8 @@ func newSimpleServerWithUdpAndUnsafeNetworks(v cert.Version, caCrt cert.Certific
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
var unsafeNetworks []netip.Prefix
|
var unsafeNetworks []netip.Prefix
|
||||||
var firewallUnsafeInbound []m
|
|
||||||
if sUnsafeNetworks != "" {
|
if sUnsafeNetworks != "" {
|
||||||
firewallUnsafeInbound = []m{{
|
firewallInbound = []m{{
|
||||||
"proto": "any",
|
"proto": "any",
|
||||||
"port": "any",
|
"port": "any",
|
||||||
"host": "any",
|
"host": "any",
|
||||||
@@ -124,7 +123,6 @@ func newSimpleServerWithUdpAndUnsafeNetworks(v cert.Version, caCrt cert.Certific
|
|||||||
"host": "any",
|
"host": "any",
|
||||||
}},
|
}},
|
||||||
"inbound": firewallInbound,
|
"inbound": firewallInbound,
|
||||||
"unsafe_inbound": firewallUnsafeInbound,
|
|
||||||
},
|
},
|
||||||
//"handshakes": m{
|
//"handshakes": m{
|
||||||
// "try_interval": "1s",
|
// "try_interval": "1s",
|
||||||
@@ -294,7 +292,7 @@ func deadline(t *testing.T, seconds time.Duration) doneCb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertTunnel(t *testing.T, vpnIpA, vpnIpB netip.Addr, controlA, controlB *nebula.Control, r *router.R) {
|
func assertTunnel(t testing.TB, vpnIpA, vpnIpB netip.Addr, controlA, controlB *nebula.Control, r *router.R) {
|
||||||
// Send a packet from them to me
|
// Send a packet from them to me
|
||||||
controlB.InjectTunUDPPacket(vpnIpA, 80, vpnIpB, 90, []byte("Hi from B"))
|
controlB.InjectTunUDPPacket(vpnIpA, 80, vpnIpB, 90, []byte("Hi from B"))
|
||||||
bPacket := r.RouteForAllUntilTxTun(controlA)
|
bPacket := r.RouteForAllUntilTxTun(controlA)
|
||||||
@@ -306,7 +304,7 @@ func assertTunnel(t *testing.T, vpnIpA, vpnIpB netip.Addr, controlA, controlB *n
|
|||||||
assertUdpPacket(t, []byte("Hello from A"), aPacket, vpnIpA, vpnIpB, 90, 80)
|
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
|
// Get both host infos
|
||||||
//TODO: CERT-V2 we may want to loop over each vpnAddr and assert all the things
|
//TODO: CERT-V2 we may want to loop over each vpnAddr and assert all the things
|
||||||
hBinA := controlA.GetHostInfoByVpnAddr(vpnNetsB[0].Addr(), false)
|
hBinA := controlA.GetHostInfoByVpnAddr(vpnNetsB[0].Addr(), false)
|
||||||
@@ -327,7 +325,7 @@ func assertHostInfoPair(t *testing.T, addrA, addrB netip.AddrPort, vpnNetsA, vpn
|
|||||||
assert.Equal(t, hBinA.RemoteIndex, hAinB.LocalIndex, "Host B remote index does not match host A local index")
|
assert.Equal(t, hBinA.RemoteIndex, hAinB.LocalIndex, "Host B remote index does not match host A local index")
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertUdpPacket(t *testing.T, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
func assertUdpPacket(t testing.TB, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
||||||
if toIp.Is6() {
|
if toIp.Is6() {
|
||||||
assertUdpPacket6(t, expected, b, fromIp, toIp, fromPort, toPort)
|
assertUdpPacket6(t, expected, b, fromIp, toIp, fromPort, toPort)
|
||||||
} else {
|
} else {
|
||||||
@@ -335,7 +333,7 @@ func assertUdpPacket(t *testing.T, expected, b []byte, fromIp, toIp netip.Addr,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertUdpPacket6(t *testing.T, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
func assertUdpPacket6(t testing.TB, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
||||||
packet := gopacket.NewPacket(b, layers.LayerTypeIPv6, gopacket.Lazy)
|
packet := gopacket.NewPacket(b, layers.LayerTypeIPv6, gopacket.Lazy)
|
||||||
v6 := packet.Layer(layers.LayerTypeIPv6).(*layers.IPv6)
|
v6 := packet.Layer(layers.LayerTypeIPv6).(*layers.IPv6)
|
||||||
assert.NotNil(t, v6, "No ipv6 data found")
|
assert.NotNil(t, v6, "No ipv6 data found")
|
||||||
@@ -354,7 +352,7 @@ func assertUdpPacket6(t *testing.T, expected, b []byte, fromIp, toIp netip.Addr,
|
|||||||
assert.Equal(t, expected, data.Payload(), "Data was incorrect")
|
assert.Equal(t, expected, data.Payload(), "Data was incorrect")
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertUdpPacket4(t *testing.T, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
func assertUdpPacket4(t testing.TB, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
||||||
packet := gopacket.NewPacket(b, layers.LayerTypeIPv4, gopacket.Lazy)
|
packet := gopacket.NewPacket(b, layers.LayerTypeIPv4, gopacket.Lazy)
|
||||||
v4 := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
v4 := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
||||||
assert.NotNil(t, v4, "No ipv4 data found")
|
assert.NotNil(t, v4, "No ipv4 data found")
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ listen:
|
|||||||
# valid values: always, never, private
|
# valid values: always, never, private
|
||||||
# This setting is reloadable.
|
# This setting is reloadable.
|
||||||
#send_recv_error: always
|
#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.
|
# 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,
|
# 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.
|
# allowing for more precise routing decisions based on the packet tags. Default is 0 meaning no mark is set.
|
||||||
|
|||||||
+26
-96
@@ -23,15 +23,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FirewallInterface interface {
|
type FirewallInterface interface {
|
||||||
AddRule(unsafe, incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr string, caName string, caSha string) error
|
AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr string, caName string, caSha string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type conn struct {
|
type conn struct {
|
||||||
Expires time.Time // Time when this conntrack entry will expire
|
Expires time.Time // Time when this conntrack entry will expire
|
||||||
|
|
||||||
// record why the original connection passed the firewall, so we can re-validate after ruleset changes.
|
// record why the original connection passed the firewall, so we can re-validate
|
||||||
|
// after ruleset changes. Note, rulesVersion is a uint16 so that these two
|
||||||
|
// fields pack for free after the uint32 above
|
||||||
incoming bool
|
incoming bool
|
||||||
unsafe bool
|
|
||||||
rulesVersion uint16
|
rulesVersion uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +42,6 @@ type Firewall struct {
|
|||||||
|
|
||||||
InRules *FirewallTable
|
InRules *FirewallTable
|
||||||
OutRules *FirewallTable
|
OutRules *FirewallTable
|
||||||
UnsafeInRules *FirewallTable
|
|
||||||
UnsafeOutRules *FirewallTable
|
|
||||||
|
|
||||||
InSendReject bool
|
InSendReject bool
|
||||||
OutSendReject bool
|
OutSendReject bool
|
||||||
@@ -55,7 +54,7 @@ type Firewall struct {
|
|||||||
|
|
||||||
// routableNetworks describes the vpn addresses as well as any unsafe networks issued to us in the certificate.
|
// routableNetworks describes the vpn addresses as well as any unsafe networks issued to us in the certificate.
|
||||||
// The vpn addresses are a full bit match while the unsafe networks only match the prefix
|
// The vpn addresses are a full bit match while the unsafe networks only match the prefix
|
||||||
routableNetworks *bart.Table[NetworkType]
|
routableNetworks *bart.Lite
|
||||||
|
|
||||||
// assignedNetworks is a list of vpn networks assigned to us in the certificate.
|
// assignedNetworks is a list of vpn networks assigned to us in the certificate.
|
||||||
assignedNetworks []netip.Prefix
|
assignedNetworks []netip.Prefix
|
||||||
@@ -150,16 +149,17 @@ func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.D
|
|||||||
tmax = defaultTimeout
|
tmax = defaultTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
routableNetworks := new(bart.Table[NetworkType])
|
routableNetworks := new(bart.Lite)
|
||||||
var assignedNetworks []netip.Prefix
|
var assignedNetworks []netip.Prefix
|
||||||
for _, network := range c.Networks() {
|
for _, network := range c.Networks() {
|
||||||
routableNetworks.Insert(netip.PrefixFrom(network.Addr(), network.Addr().BitLen()), NetworkTypeVPN)
|
nprefix := netip.PrefixFrom(network.Addr(), network.Addr().BitLen())
|
||||||
|
routableNetworks.Insert(nprefix)
|
||||||
assignedNetworks = append(assignedNetworks, network)
|
assignedNetworks = append(assignedNetworks, network)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasUnsafeNetworks := false
|
hasUnsafeNetworks := false
|
||||||
for _, n := range c.UnsafeNetworks() {
|
for _, n := range c.UnsafeNetworks() {
|
||||||
routableNetworks.Insert(n, NetworkTypeUnsafe)
|
routableNetworks.Insert(n)
|
||||||
hasUnsafeNetworks = true
|
hasUnsafeNetworks = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,8 +170,6 @@ func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.D
|
|||||||
},
|
},
|
||||||
InRules: newFirewallTable(),
|
InRules: newFirewallTable(),
|
||||||
OutRules: newFirewallTable(),
|
OutRules: newFirewallTable(),
|
||||||
UnsafeInRules: newFirewallTable(),
|
|
||||||
UnsafeOutRules: newFirewallTable(),
|
|
||||||
TCPTimeout: tcpTimeout,
|
TCPTimeout: tcpTimeout,
|
||||||
UDPTimeout: UDPTimeout,
|
UDPTimeout: UDPTimeout,
|
||||||
DefaultTimeout: defaultTimeout,
|
DefaultTimeout: defaultTimeout,
|
||||||
@@ -214,7 +212,6 @@ func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firew
|
|||||||
|
|
||||||
fw.defaultLocalCIDRAny = c.GetBool("firewall.default_local_cidr_any", false)
|
fw.defaultLocalCIDRAny = c.GetBool("firewall.default_local_cidr_any", false)
|
||||||
|
|
||||||
//TODO: do we also need firewall.unsafe_inbound_action and firewall.unsafe_outbound_action?
|
|
||||||
inboundAction := c.GetString("firewall.inbound_action", "drop")
|
inboundAction := c.GetString("firewall.inbound_action", "drop")
|
||||||
switch inboundAction {
|
switch inboundAction {
|
||||||
case "reject":
|
case "reject":
|
||||||
@@ -237,26 +234,12 @@ func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firew
|
|||||||
fw.OutSendReject = false
|
fw.OutSendReject = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// outbound rules
|
err := AddFirewallRulesFromConfig(l, false, c, fw)
|
||||||
err := AddFirewallRulesFromConfig(l, false, false, c, fw)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsafe outbound rules
|
err = AddFirewallRulesFromConfig(l, true, c, fw)
|
||||||
err = AddFirewallRulesFromConfig(l, true, false, c, fw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// inbound rules
|
|
||||||
err = AddFirewallRulesFromConfig(l, false, true, c, fw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafe inbound rules
|
|
||||||
err = AddFirewallRulesFromConfig(l, true, true, c, fw)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -265,11 +248,11 @@ func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firew
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddRule properly creates the in memory rule structure for a firewall table.
|
// AddRule properly creates the in memory rule structure for a firewall table.
|
||||||
func (f *Firewall) AddRule(unsafe, incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {
|
func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {
|
||||||
// We need this rule string because we generate a hash. Removing this will break firewall reload.
|
// We need this rule string because we generate a hash. Removing this will break firewall reload.
|
||||||
ruleString := fmt.Sprintf(
|
ruleString := fmt.Sprintf(
|
||||||
"unsafe: %v, incoming: %v, proto: %v, startPort: %v, endPort: %v, groups: %v, host: %v, ip: %v, localIp: %v, caName: %v, caSha: %s",
|
"incoming: %v, proto: %v, startPort: %v, endPort: %v, groups: %v, host: %v, ip: %v, localIp: %v, caName: %v, caSha: %s",
|
||||||
unsafe, incoming, proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha,
|
incoming, proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha,
|
||||||
)
|
)
|
||||||
f.rules += ruleString + "\n"
|
f.rules += ruleString + "\n"
|
||||||
|
|
||||||
@@ -277,12 +260,8 @@ func (f *Firewall) AddRule(unsafe, incoming bool, proto uint8, startPort int32,
|
|||||||
if !incoming {
|
if !incoming {
|
||||||
direction = "outgoing"
|
direction = "outgoing"
|
||||||
}
|
}
|
||||||
|
f.l.WithField("firewallRule", m{"direction": direction, "proto": proto, "startPort": startPort, "endPort": endPort, "groups": groups, "host": host, "cidr": cidr, "localCidr": localCidr, "caName": caName, "caSha": caSha}).
|
||||||
fields := m{"direction": direction, "proto": proto, "startPort": startPort, "endPort": endPort, "groups": groups, "host": host, "cidr": cidr, "localCidr": localCidr, "caName": caName, "caSha": caSha}
|
Info("Firewall rule added")
|
||||||
if unsafe {
|
|
||||||
fields["unsafe"] = true
|
|
||||||
}
|
|
||||||
f.l.WithField("firewallRule", fields).Info("Firewall rule added")
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ft *FirewallTable
|
ft *FirewallTable
|
||||||
@@ -290,19 +269,10 @@ func (f *Firewall) AddRule(unsafe, incoming bool, proto uint8, startPort int32,
|
|||||||
)
|
)
|
||||||
|
|
||||||
if incoming {
|
if incoming {
|
||||||
if unsafe {
|
|
||||||
ft = f.UnsafeInRules
|
|
||||||
} else {
|
|
||||||
ft = f.InRules
|
ft = f.InRules
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if unsafe {
|
|
||||||
ft = f.UnsafeOutRules
|
|
||||||
} else {
|
} else {
|
||||||
ft = f.OutRules
|
ft = f.OutRules
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
switch proto {
|
switch proto {
|
||||||
case firewall.ProtoTCP:
|
case firewall.ProtoTCP:
|
||||||
@@ -338,22 +308,13 @@ func (f *Firewall) GetRuleHashes() string {
|
|||||||
return "SHA:" + f.GetRuleHash() + ",FNV:" + strconv.FormatUint(uint64(f.GetRuleHashFNV()), 10)
|
return "SHA:" + f.GetRuleHash() + ",FNV:" + strconv.FormatUint(uint64(f.GetRuleHashFNV()), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddFirewallRulesFromConfig(l *logrus.Logger, unsafe, inbound bool, c *config.C, fw FirewallInterface) error {
|
func AddFirewallRulesFromConfig(l *logrus.Logger, inbound bool, c *config.C, fw FirewallInterface) error {
|
||||||
var table string
|
var table string
|
||||||
if inbound {
|
if inbound {
|
||||||
if unsafe {
|
|
||||||
table = "firewall.unsafe_inbound"
|
|
||||||
} else {
|
|
||||||
table = "firewall.inbound"
|
table = "firewall.inbound"
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if unsafe {
|
|
||||||
table = "firewall.unsafe_outbound"
|
|
||||||
} else {
|
} else {
|
||||||
table = "firewall.outbound"
|
table = "firewall.outbound"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
r := c.Get(table)
|
r := c.Get(table)
|
||||||
if r == nil {
|
if r == nil {
|
||||||
@@ -425,7 +386,7 @@ func AddFirewallRulesFromConfig(l *logrus.Logger, unsafe, inbound bool, c *confi
|
|||||||
l.Warnf("%s rule #%v; %s", table, i, warning)
|
l.Warnf("%s rule #%v; %s", table, i, warning)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fw.AddRule(unsafe, inbound, proto, startPort, endPort, r.Groups, r.Host, r.Cidr, r.LocalCidr, r.CAName, r.CASha)
|
err = fw.AddRule(inbound, proto, startPort, endPort, r.Groups, r.Host, r.Cidr, r.LocalCidr, r.CAName, r.CASha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s rule #%v; `%s`", table, i, err)
|
return fmt.Errorf("%s rule #%v; `%s`", table, i, err)
|
||||||
}
|
}
|
||||||
@@ -448,9 +409,6 @@ func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var remoteNetworkType NetworkType
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
// Make sure remote address matches nebula certificate, and determine how to treat it
|
// Make sure remote address matches nebula certificate, and determine how to treat it
|
||||||
if h.networks == nil {
|
if h.networks == nil {
|
||||||
// Simple case: Certificate has one address and no unsafe networks
|
// Simple case: Certificate has one address and no unsafe networks
|
||||||
@@ -458,14 +416,13 @@ func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *
|
|||||||
f.metrics(incoming).droppedRemoteAddr.Inc(1)
|
f.metrics(incoming).droppedRemoteAddr.Inc(1)
|
||||||
return ErrInvalidRemoteIP
|
return ErrInvalidRemoteIP
|
||||||
}
|
}
|
||||||
remoteNetworkType = NetworkTypeVPN
|
|
||||||
} else {
|
} else {
|
||||||
remoteNetworkType, ok = h.networks.Lookup(fp.RemoteAddr)
|
nwType, ok := h.networks.Lookup(fp.RemoteAddr)
|
||||||
if !ok {
|
if !ok {
|
||||||
f.metrics(incoming).droppedRemoteAddr.Inc(1)
|
f.metrics(incoming).droppedRemoteAddr.Inc(1)
|
||||||
return ErrInvalidRemoteIP
|
return ErrInvalidRemoteIP
|
||||||
}
|
}
|
||||||
switch remoteNetworkType {
|
switch nwType {
|
||||||
case NetworkTypeVPN:
|
case NetworkTypeVPN:
|
||||||
break // nothing special
|
break // nothing special
|
||||||
case NetworkTypeVPNPeer:
|
case NetworkTypeVPNPeer:
|
||||||
@@ -480,28 +437,15 @@ func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we are supposed to be handling this local ip address
|
// Make sure we are supposed to be handling this local ip address
|
||||||
localNetworkType, ok := f.routableNetworks.Lookup(fp.LocalAddr)
|
if !f.routableNetworks.Contains(fp.LocalAddr) {
|
||||||
if !ok {
|
|
||||||
f.metrics(incoming).droppedLocalAddr.Inc(1)
|
f.metrics(incoming).droppedLocalAddr.Inc(1)
|
||||||
return ErrInvalidLocalIP
|
return ErrInvalidLocalIP
|
||||||
}
|
}
|
||||||
|
|
||||||
useUnsafe := remoteNetworkType == NetworkTypeUnsafe || localNetworkType == NetworkTypeUnsafe
|
table := f.OutRules
|
||||||
|
|
||||||
var table *FirewallTable
|
|
||||||
if incoming {
|
if incoming {
|
||||||
if useUnsafe {
|
|
||||||
table = f.UnsafeInRules
|
|
||||||
} else {
|
|
||||||
table = f.InRules
|
table = f.InRules
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if useUnsafe {
|
|
||||||
table = f.UnsafeOutRules
|
|
||||||
} else {
|
|
||||||
table = f.OutRules
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We now know which firewall table to check against
|
// We now know which firewall table to check against
|
||||||
if !table.match(fp, incoming, h.ConnectionState.peerCert, caPool) {
|
if !table.match(fp, incoming, h.ConnectionState.peerCert, caPool) {
|
||||||
@@ -510,13 +454,12 @@ func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We always want to conntrack since it is a faster operation
|
// We always want to conntrack since it is a faster operation
|
||||||
f.addConn(fp, useUnsafe, incoming)
|
f.addConn(fp, incoming)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firewall) metrics(incoming bool) firewallMetrics {
|
func (f *Firewall) metrics(incoming bool) firewallMetrics {
|
||||||
//TODO: need unsafe metrics too
|
|
||||||
if incoming {
|
if incoming {
|
||||||
return f.incomingMetrics
|
return f.incomingMetrics
|
||||||
} else {
|
} else {
|
||||||
@@ -556,6 +499,7 @@ func (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.CAPool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
c, ok := conntrack.Conns[fp]
|
c, ok := conntrack.Conns[fp]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
conntrack.Unlock()
|
conntrack.Unlock()
|
||||||
return false
|
return false
|
||||||
@@ -564,20 +508,10 @@ func (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.CAPool,
|
|||||||
if c.rulesVersion != f.rulesVersion {
|
if c.rulesVersion != f.rulesVersion {
|
||||||
// This conntrack entry was for an older rule set, validate
|
// This conntrack entry was for an older rule set, validate
|
||||||
// it still passes with the current rule set
|
// it still passes with the current rule set
|
||||||
var table *FirewallTable
|
table := f.OutRules
|
||||||
if c.incoming {
|
if c.incoming {
|
||||||
if c.unsafe {
|
|
||||||
table = f.UnsafeInRules
|
|
||||||
} else {
|
|
||||||
table = f.InRules
|
table = f.InRules
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if c.unsafe {
|
|
||||||
table = f.UnsafeOutRules
|
|
||||||
} else {
|
|
||||||
table = f.OutRules
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We now know which firewall table to check against
|
// We now know which firewall table to check against
|
||||||
if !table.match(fp, c.incoming, h.ConnectionState.peerCert, caPool) {
|
if !table.match(fp, c.incoming, h.ConnectionState.peerCert, caPool) {
|
||||||
@@ -585,7 +519,6 @@ func (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.CAPool,
|
|||||||
h.logger(f.l).
|
h.logger(f.l).
|
||||||
WithField("fwPacket", fp).
|
WithField("fwPacket", fp).
|
||||||
WithField("incoming", c.incoming).
|
WithField("incoming", c.incoming).
|
||||||
WithField("unsafe", c.unsafe).
|
|
||||||
WithField("rulesVersion", f.rulesVersion).
|
WithField("rulesVersion", f.rulesVersion).
|
||||||
WithField("oldRulesVersion", c.rulesVersion).
|
WithField("oldRulesVersion", c.rulesVersion).
|
||||||
Debugln("dropping old conntrack entry, does not match new ruleset")
|
Debugln("dropping old conntrack entry, does not match new ruleset")
|
||||||
@@ -599,7 +532,6 @@ func (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.CAPool,
|
|||||||
h.logger(f.l).
|
h.logger(f.l).
|
||||||
WithField("fwPacket", fp).
|
WithField("fwPacket", fp).
|
||||||
WithField("incoming", c.incoming).
|
WithField("incoming", c.incoming).
|
||||||
WithField("unsafe", c.unsafe).
|
|
||||||
WithField("rulesVersion", f.rulesVersion).
|
WithField("rulesVersion", f.rulesVersion).
|
||||||
WithField("oldRulesVersion", c.rulesVersion).
|
WithField("oldRulesVersion", c.rulesVersion).
|
||||||
Debugln("keeping old conntrack entry, does match new ruleset")
|
Debugln("keeping old conntrack entry, does match new ruleset")
|
||||||
@@ -626,7 +558,7 @@ func (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.CAPool,
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firewall) addConn(fp firewall.Packet, unsafe, incoming bool) {
|
func (f *Firewall) addConn(fp firewall.Packet, incoming bool) {
|
||||||
var timeout time.Duration
|
var timeout time.Duration
|
||||||
c := &conn{}
|
c := &conn{}
|
||||||
|
|
||||||
@@ -649,7 +581,6 @@ func (f *Firewall) addConn(fp firewall.Packet, unsafe, incoming bool) {
|
|||||||
// Record which rulesVersion allowed this connection, so we can retest after
|
// Record which rulesVersion allowed this connection, so we can retest after
|
||||||
// firewall reload
|
// firewall reload
|
||||||
c.incoming = incoming
|
c.incoming = incoming
|
||||||
c.unsafe = unsafe
|
|
||||||
c.rulesVersion = f.rulesVersion
|
c.rulesVersion = f.rulesVersion
|
||||||
c.Expires = time.Now().Add(timeout)
|
c.Expires = time.Now().Add(timeout)
|
||||||
conntrack.Conns[fp] = c
|
conntrack.Conns[fp] = c
|
||||||
@@ -1006,7 +937,6 @@ func convertRule(l *logrus.Logger, p any, table string, i int) (rule, error) {
|
|||||||
r.Code = toString("code", m)
|
r.Code = toString("code", m)
|
||||||
r.Proto = toString("proto", m)
|
r.Proto = toString("proto", m)
|
||||||
r.Host = toString("host", m)
|
r.Host = toString("host", m)
|
||||||
//TODO: create an alias to remote_cidr and deprecate cidr?
|
|
||||||
r.Cidr = toString("cidr", m)
|
r.Cidr = toString("cidr", m)
|
||||||
r.LocalCidr = toString("local_cidr", m)
|
r.LocalCidr = toString("local_cidr", m)
|
||||||
r.CAName = toString("ca_name", m)
|
r.CAName = toString("ca_name", m)
|
||||||
|
|||||||
+66
-68
@@ -73,65 +73,65 @@ func TestFirewall_AddRule(t *testing.T) {
|
|||||||
ti6, err := netip.ParsePrefix("fd12::34/128")
|
ti6, err := netip.ParsePrefix("fd12::34/128")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoTCP, 1, 1, []string{}, "", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoTCP, 1, 1, []string{}, "", "", "", "", ""))
|
||||||
// An empty rule is any
|
// An empty rule is any
|
||||||
assert.True(t, fw.InRules.TCP[1].Any.Any.Any)
|
assert.True(t, fw.InRules.TCP[1].Any.Any.Any)
|
||||||
assert.Empty(t, fw.InRules.TCP[1].Any.Groups)
|
assert.Empty(t, fw.InRules.TCP[1].Any.Groups)
|
||||||
assert.Empty(t, fw.InRules.TCP[1].Any.Hosts)
|
assert.Empty(t, fw.InRules.TCP[1].Any.Hosts)
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "", ""))
|
||||||
assert.Nil(t, fw.InRules.UDP[1].Any.Any)
|
assert.Nil(t, fw.InRules.UDP[1].Any.Any)
|
||||||
assert.Contains(t, fw.InRules.UDP[1].Any.Groups[0].Groups, "g1")
|
assert.Contains(t, fw.InRules.UDP[1].Any.Groups[0].Groups, "g1")
|
||||||
assert.Empty(t, fw.InRules.UDP[1].Any.Hosts)
|
assert.Empty(t, fw.InRules.UDP[1].Any.Hosts)
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoICMP, 1, 1, []string{}, "h1", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 1, 1, []string{}, "h1", "", "", "", ""))
|
||||||
assert.Nil(t, fw.InRules.ICMP[1].Any.Any)
|
assert.Nil(t, fw.InRules.ICMP[1].Any.Any)
|
||||||
assert.Empty(t, fw.InRules.ICMP[1].Any.Groups)
|
assert.Empty(t, fw.InRules.ICMP[1].Any.Groups)
|
||||||
assert.Contains(t, fw.InRules.ICMP[1].Any.Hosts, "h1")
|
assert.Contains(t, fw.InRules.ICMP[1].Any.Hosts, "h1")
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, false, firewall.ProtoAny, 1, 1, []string{}, "", ti.String(), "", "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti.String(), "", "", ""))
|
||||||
assert.Nil(t, fw.OutRules.AnyProto[1].Any.Any)
|
assert.Nil(t, fw.OutRules.AnyProto[1].Any.Any)
|
||||||
_, ok := fw.OutRules.AnyProto[1].Any.CIDR.Get(ti)
|
_, ok := fw.OutRules.AnyProto[1].Any.CIDR.Get(ti)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, false, firewall.ProtoAny, 1, 1, []string{}, "", ti6.String(), "", "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti6.String(), "", "", ""))
|
||||||
assert.Nil(t, fw.OutRules.AnyProto[1].Any.Any)
|
assert.Nil(t, fw.OutRules.AnyProto[1].Any.Any)
|
||||||
_, ok = fw.OutRules.AnyProto[1].Any.CIDR.Get(ti6)
|
_, ok = fw.OutRules.AnyProto[1].Any.CIDR.Get(ti6)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, false, firewall.ProtoAny, 1, 1, []string{}, "", "", ti.String(), "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", "", ti.String(), "", ""))
|
||||||
assert.NotNil(t, fw.OutRules.AnyProto[1].Any.Any)
|
assert.NotNil(t, fw.OutRules.AnyProto[1].Any.Any)
|
||||||
ok = fw.OutRules.AnyProto[1].Any.Any.LocalCIDR.Get(ti)
|
ok = fw.OutRules.AnyProto[1].Any.Any.LocalCIDR.Get(ti)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, false, firewall.ProtoAny, 1, 1, []string{}, "", "", ti6.String(), "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", "", ti6.String(), "", ""))
|
||||||
assert.NotNil(t, fw.OutRules.AnyProto[1].Any.Any)
|
assert.NotNil(t, fw.OutRules.AnyProto[1].Any.Any)
|
||||||
ok = fw.OutRules.AnyProto[1].Any.Any.LocalCIDR.Get(ti6)
|
ok = fw.OutRules.AnyProto[1].Any.Any.LocalCIDR.Get(ti6)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "ca-name", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "ca-name", ""))
|
||||||
assert.Contains(t, fw.InRules.UDP[1].CANames, "ca-name")
|
assert.Contains(t, fw.InRules.UDP[1].CANames, "ca-name")
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "", "ca-sha"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "", "ca-sha"))
|
||||||
assert.Contains(t, fw.InRules.UDP[1].CAShas, "ca-sha")
|
assert.Contains(t, fw.InRules.UDP[1].CAShas, "ca-sha")
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, false, firewall.ProtoAny, 0, 0, []string{}, "any", "", "", "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "any", "", "", "", ""))
|
||||||
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
anyIp, err := netip.ParsePrefix("0.0.0.0/0")
|
anyIp, err := netip.ParsePrefix("0.0.0.0/0")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NoError(t, fw.AddRule(false, false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp.String(), "", "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp.String(), "", "", ""))
|
||||||
assert.Nil(t, fw.OutRules.AnyProto[0].Any.Any)
|
assert.Nil(t, fw.OutRules.AnyProto[0].Any.Any)
|
||||||
table, ok := fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("1.1.1.1"))
|
table, ok := fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("1.1.1.1"))
|
||||||
assert.True(t, table.Any)
|
assert.True(t, table.Any)
|
||||||
@@ -142,7 +142,7 @@ func TestFirewall_AddRule(t *testing.T) {
|
|||||||
anyIp6, err := netip.ParsePrefix("::/0")
|
anyIp6, err := netip.ParsePrefix("::/0")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NoError(t, fw.AddRule(false, false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp6.String(), "", "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp6.String(), "", "", ""))
|
||||||
assert.Nil(t, fw.OutRules.AnyProto[0].Any.Any)
|
assert.Nil(t, fw.OutRules.AnyProto[0].Any.Any)
|
||||||
table, ok = fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("9::9"))
|
table, ok = fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("9::9"))
|
||||||
assert.True(t, table.Any)
|
assert.True(t, table.Any)
|
||||||
@@ -150,29 +150,29 @@ func TestFirewall_AddRule(t *testing.T) {
|
|||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, false, firewall.ProtoAny, 0, 0, []string{}, "", "any", "", "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "any", "", "", ""))
|
||||||
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, false, firewall.ProtoAny, 0, 0, []string{}, "", "", anyIp.String(), "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "", anyIp.String(), "", ""))
|
||||||
assert.False(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
assert.False(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
||||||
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("1.1.1.1")))
|
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("1.1.1.1")))
|
||||||
assert.False(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("9::9")))
|
assert.False(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("9::9")))
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, false, firewall.ProtoAny, 0, 0, []string{}, "", "", anyIp6.String(), "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "", anyIp6.String(), "", ""))
|
||||||
assert.False(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
assert.False(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
||||||
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("9::9")))
|
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("9::9")))
|
||||||
assert.False(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("1.1.1.1")))
|
assert.False(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("1.1.1.1")))
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, false, firewall.ProtoAny, 0, 0, []string{}, "", "", "any", "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "", "any", "", ""))
|
||||||
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
||||||
|
|
||||||
// Test error conditions
|
// Test error conditions
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.Error(t, fw.AddRule(false, true, math.MaxUint8, 0, 0, []string{}, "", "", "", "", ""))
|
require.Error(t, fw.AddRule(true, math.MaxUint8, 0, 0, []string{}, "", "", "", "", ""))
|
||||||
require.Error(t, fw.AddRule(false, true, firewall.ProtoAny, 10, 0, []string{}, "", "", "", "", ""))
|
require.Error(t, fw.AddRule(true, firewall.ProtoAny, 10, 0, []string{}, "", "", "", "", ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_Drop(t *testing.T) {
|
func TestFirewall_Drop(t *testing.T) {
|
||||||
@@ -208,7 +208,7 @@ func TestFirewall_Drop(t *testing.T) {
|
|||||||
h.buildNetworks(myVpnNetworksTable, &c)
|
h.buildNetworks(myVpnNetworksTable, &c)
|
||||||
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
// Drop outbound
|
// Drop outbound
|
||||||
@@ -227,28 +227,28 @@ func TestFirewall_Drop(t *testing.T) {
|
|||||||
|
|
||||||
// ensure signer doesn't get in the way of group checks
|
// ensure signer doesn't get in the way of group checks
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
|
||||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
||||||
|
|
||||||
// test caSha doesn't drop on match
|
// test caSha doesn't drop on match
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
||||||
|
|
||||||
// ensure ca name doesn't get in the way of group checks
|
// ensure ca name doesn't get in the way of group checks
|
||||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
|
||||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
||||||
|
|
||||||
// test caName doesn't drop on match
|
// test caName doesn't drop on match
|
||||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ func TestFirewall_DropV6(t *testing.T) {
|
|||||||
h.buildNetworks(myVpnNetworksTable, &c)
|
h.buildNetworks(myVpnNetworksTable, &c)
|
||||||
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
// Drop outbound
|
// Drop outbound
|
||||||
@@ -306,28 +306,28 @@ func TestFirewall_DropV6(t *testing.T) {
|
|||||||
|
|
||||||
// ensure signer doesn't get in the way of group checks
|
// ensure signer doesn't get in the way of group checks
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
|
||||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
||||||
|
|
||||||
// test caSha doesn't drop on match
|
// test caSha doesn't drop on match
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
||||||
|
|
||||||
// ensure ca name doesn't get in the way of group checks
|
// ensure ca name doesn't get in the way of group checks
|
||||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
|
||||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
||||||
|
|
||||||
// test caName doesn't drop on match
|
// test caName doesn't drop on match
|
||||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,7 +532,7 @@ func TestFirewall_Drop2(t *testing.T) {
|
|||||||
h1.buildNetworks(myVpnNetworksTable, c1.Certificate)
|
h1.buildNetworks(myVpnNetworksTable, c1.Certificate)
|
||||||
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"default-group", "test-group"}, "", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group", "test-group"}, "", "", "", "", ""))
|
||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
// h1/c1 lacks the proper groups
|
// h1/c1 lacks the proper groups
|
||||||
@@ -612,8 +612,8 @@ func TestFirewall_Drop3(t *testing.T) {
|
|||||||
h3.buildNetworks(myVpnNetworksTable, c3.Certificate)
|
h3.buildNetworks(myVpnNetworksTable, c3.Certificate)
|
||||||
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 1, 1, []string{}, "host1", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "host1", "", "", "", ""))
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 1, 1, []string{}, "", "", "", "", "signer-sha"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "", "", "", "signer-sha"))
|
||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
// c1 should pass because host match
|
// c1 should pass because host match
|
||||||
@@ -627,7 +627,7 @@ func TestFirewall_Drop3(t *testing.T) {
|
|||||||
|
|
||||||
// Test a remote address match
|
// Test a remote address match
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 1, 1, []string{}, "", "1.2.3.4/24", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "1.2.3.4/24", "", "", ""))
|
||||||
require.NoError(t, fw.Drop(p, true, &h1, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h1, cp, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,7 +665,7 @@ func TestFirewall_Drop3V6(t *testing.T) {
|
|||||||
// Test a remote address match
|
// Test a remote address match
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 1, 1, []string{}, "", "fd12::34/120", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "fd12::34/120", "", "", ""))
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,7 +704,7 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
|||||||
h.buildNetworks(myVpnNetworksTable, c.Certificate)
|
h.buildNetworks(myVpnNetworksTable, c.Certificate)
|
||||||
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
// Drop outbound
|
// Drop outbound
|
||||||
@@ -717,7 +717,7 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
|||||||
|
|
||||||
oldFw := fw
|
oldFw := fw
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 10, 10, []string{"any"}, "", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 10, 10, []string{"any"}, "", "", "", "", ""))
|
||||||
fw.Conntrack = oldFw.Conntrack
|
fw.Conntrack = oldFw.Conntrack
|
||||||
fw.rulesVersion = oldFw.rulesVersion + 1
|
fw.rulesVersion = oldFw.rulesVersion + 1
|
||||||
|
|
||||||
@@ -726,7 +726,7 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
|||||||
|
|
||||||
oldFw = fw
|
oldFw = fw
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 11, 11, []string{"any"}, "", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 11, 11, []string{"any"}, "", "", "", "", ""))
|
||||||
fw.Conntrack = oldFw.Conntrack
|
fw.Conntrack = oldFw.Conntrack
|
||||||
fw.rulesVersion = oldFw.rulesVersion + 1
|
fw.rulesVersion = oldFw.rulesVersion + 1
|
||||||
|
|
||||||
@@ -765,7 +765,7 @@ func TestFirewall_DropIPSpoofing(t *testing.T) {
|
|||||||
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
|
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 1, 1, []string{}, "", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "", "", "", ""))
|
||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
// Packet spoofed by `c1`. Note that the remote addr is not a valid one.
|
// Packet spoofed by `c1`. Note that the remote addr is not a valid one.
|
||||||
@@ -958,28 +958,28 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
|||||||
conf := config.NewC(l)
|
conf := config.NewC(l)
|
||||||
mf := &mockFirewall{}
|
mf := &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "tcp", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "tcp", "host": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, false, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoTCP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoTCP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding udp rule
|
// Test adding udp rule
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "udp", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "udp", "host": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, false, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoUDP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoUDP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding icmp rule
|
// Test adding icmp rule
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, false, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding any rule
|
// Test adding any rule
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with cidr
|
// Test adding rule with cidr
|
||||||
@@ -987,14 +987,14 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
|||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr.String()}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr.String()}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr.String(), localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr.String(), localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with local_cidr
|
// Test adding rule with local_cidr
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr.String()}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr.String()}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr.String()}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr.String()}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with cidr ipv6
|
// Test adding rule with cidr ipv6
|
||||||
@@ -1002,75 +1002,75 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
|||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr6.String()}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr6.String()}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr6.String(), localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr6.String(), localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with any cidr
|
// Test adding rule with any cidr
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "any"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "any"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "any", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "any", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with junk cidr
|
// Test adding rule with junk cidr
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "junk/junk"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "junk/junk"}}}
|
||||||
require.EqualError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf), "firewall.inbound rule #0; cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
|
require.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
|
||||||
|
|
||||||
// Test adding rule with local_cidr ipv6
|
// Test adding rule with local_cidr ipv6
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr6.String()}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr6.String()}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr6.String()}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr6.String()}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with any local_cidr
|
// Test adding rule with any local_cidr
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "any"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "any"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, localIp: "any"}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, localIp: "any"}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with junk local_cidr
|
// Test adding rule with junk local_cidr
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "junk/junk"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "junk/junk"}}}
|
||||||
require.EqualError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf), "firewall.inbound rule #0; local_cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
|
require.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; local_cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
|
||||||
|
|
||||||
// Test adding rule with ca_sha
|
// Test adding rule with ca_sha
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_sha": "12312313123"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_sha": "12312313123"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caSha: "12312313123"}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caSha: "12312313123"}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with ca_name
|
// Test adding rule with ca_name
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_name": "root01"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_name": "root01"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caName: "root01"}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caName: "root01"}, mf.lastCall)
|
||||||
|
|
||||||
// Test single group
|
// Test single group
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test single groups
|
// Test single groups
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test multiple AND groups
|
// Test multiple AND groups
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": []string{"a", "b"}}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": []string{"a", "b"}}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a", "b"}, ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a", "b"}, ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test Add error
|
// Test Add error
|
||||||
@@ -1078,7 +1078,7 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
|||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
mf.nextCallReturn = errors.New("test error")
|
mf.nextCallReturn = errors.New("test error")
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
|
||||||
require.EqualError(t, AddFirewallRulesFromConfig(l, false, true, conf, mf), "firewall.inbound rule #0; `test error`")
|
require.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; `test error`")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_convertRule(t *testing.T) {
|
func TestFirewall_convertRule(t *testing.T) {
|
||||||
@@ -1251,7 +1251,7 @@ func newSetupFromCert(t *testing.T, l *logrus.Logger, c dummyCert) testsetup {
|
|||||||
myVpnNetworksTable.Insert(prefix)
|
myVpnNetworksTable.Insert(prefix)
|
||||||
}
|
}
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(false, true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
||||||
|
|
||||||
return testsetup{
|
return testsetup{
|
||||||
c: c,
|
c: c,
|
||||||
@@ -1332,14 +1332,13 @@ func TestFirewall_Drop_EnforceIPMatch(t *testing.T) {
|
|||||||
tc.p.LocalAddr = netip.MustParseAddr("192.168.0.3")
|
tc.p.LocalAddr = netip.MustParseAddr("192.168.0.3")
|
||||||
tc.err = ErrNoMatchingRule
|
tc.err = ErrNoMatchingRule
|
||||||
tc.Test(t, unsafeSetup.fw) //should hit firewall and bounce off
|
tc.Test(t, unsafeSetup.fw) //should hit firewall and bounce off
|
||||||
require.NoError(t, unsafeSetup.fw.AddRule(true, true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", unsafePrefix.String(), "", ""))
|
require.NoError(t, unsafeSetup.fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", unsafePrefix.String(), "", ""))
|
||||||
tc.err = nil
|
tc.err = nil
|
||||||
tc.Test(t, unsafeSetup.fw) //should pass
|
tc.Test(t, unsafeSetup.fw) //should pass
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type addRuleCall struct {
|
type addRuleCall struct {
|
||||||
unsafe bool
|
|
||||||
incoming bool
|
incoming bool
|
||||||
proto uint8
|
proto uint8
|
||||||
startPort int32
|
startPort int32
|
||||||
@@ -1357,9 +1356,8 @@ type mockFirewall struct {
|
|||||||
nextCallReturn error
|
nextCallReturn error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mf *mockFirewall) AddRule(unsafe, incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, ip, localIp, caName string, caSha string) error {
|
func (mf *mockFirewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, ip, localIp, caName string, caSha string) error {
|
||||||
mf.lastCall = addRuleCall{
|
mf.lastCall = addRuleCall{
|
||||||
unsafe: unsafe,
|
|
||||||
incoming: incoming,
|
incoming: incoming,
|
||||||
proto: proto,
|
proto: proto,
|
||||||
startPort: startPort,
|
startPort: startPort,
|
||||||
|
|||||||
@@ -12,27 +12,27 @@ require (
|
|||||||
github.com/gogo/protobuf v1.3.2
|
github.com/gogo/protobuf v1.3.2
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/kardianos/service v1.2.4
|
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/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b
|
||||||
github.com/nbrownus/go-metrics-prometheus v0.0.0-20210712211119-974a6260965f
|
github.com/nbrownus/go-metrics-prometheus v0.0.0-20210712211119-974a6260965f
|
||||||
github.com/prometheus/client_golang v1.23.2
|
github.com/prometheus/client_golang v1.23.2
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
|
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/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6
|
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
go.yaml.in/yaml/v3 v3.0.4
|
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/exp v0.0.0-20230725093048-515e97ebf090
|
||||||
golang.org/x/net v0.47.0
|
golang.org/x/net v0.49.0
|
||||||
golang.org/x/sync v0.18.0
|
golang.org/x/sync v0.19.0
|
||||||
golang.org/x/sys v0.38.0
|
golang.org/x/sys v0.40.0
|
||||||
golang.org/x/term v0.37.0
|
golang.org/x/term v0.39.0
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
|
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
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
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe
|
gvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe
|
||||||
)
|
)
|
||||||
@@ -49,7 +49,7 @@ require (
|
|||||||
github.com/prometheus/procfs v0.16.1 // indirect
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.2 // 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/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.33.0 // indirect
|
golang.org/x/tools v0.40.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
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/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.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
|
||||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
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 h1:J/AzCvg5z0Hn1rqZUJjpbzALUmkKX0Zwbc/i4fw7Sfk=
|
||||||
github.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
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=
|
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.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
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.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
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 h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
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=
|
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-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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
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 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
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/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.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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.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.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
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-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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-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-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.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
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/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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-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-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.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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
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-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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-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-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-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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
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.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.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
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.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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
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-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-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.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-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.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
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.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.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
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/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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
+74
-65
@@ -1,6 +1,7 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -99,11 +100,11 @@ func ixHandshakeStage0(f *Interface, hh *HandshakeHostInfo) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet []byte, h *header.H) {
|
func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H) {
|
||||||
cs := f.pki.getCertState()
|
cs := f.pki.getCertState()
|
||||||
crt := cs.GetDefaultCertificate()
|
crt := cs.GetDefaultCertificate()
|
||||||
if crt == nil {
|
if crt == nil {
|
||||||
f.l.WithField("udpAddr", addr).
|
f.l.WithField("from", via).
|
||||||
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).
|
||||||
WithField("certVersion", cs.initiatingVersion).
|
WithField("certVersion", cs.initiatingVersion).
|
||||||
Error("Unable to handshake with host because no certificate is available")
|
Error("Unable to handshake with host because no certificate is available")
|
||||||
@@ -112,7 +113,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
|
|
||||||
ci, err := NewConnectionState(f.l, cs, crt, false, noise.HandshakeIX)
|
ci, err := NewConnectionState(f.l, cs, crt, false, noise.HandshakeIX)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.WithError(err).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("from", via).
|
||||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
Error("Failed to create connection state")
|
Error("Failed to create connection state")
|
||||||
return
|
return
|
||||||
@@ -123,7 +124,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
|
|
||||||
msg, _, _, err := ci.H.ReadMessage(nil, packet[header.Len:])
|
msg, _, _, err := ci.H.ReadMessage(nil, packet[header.Len:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.WithError(err).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("from", via).
|
||||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
Error("Failed to call noise.ReadMessage")
|
Error("Failed to call noise.ReadMessage")
|
||||||
return
|
return
|
||||||
@@ -132,7 +133,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
hs := &NebulaHandshake{}
|
hs := &NebulaHandshake{}
|
||||||
err = hs.Unmarshal(msg)
|
err = hs.Unmarshal(msg)
|
||||||
if err != nil || hs.Details == nil {
|
if err != nil || hs.Details == nil {
|
||||||
f.l.WithError(err).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("from", via).
|
||||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
Error("Failed unmarshal handshake message")
|
Error("Failed unmarshal handshake message")
|
||||||
return
|
return
|
||||||
@@ -140,7 +141,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
|
|
||||||
rc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())
|
rc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.WithError(err).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("from", via).
|
||||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
Info("Handshake did not contain a certificate")
|
Info("Handshake did not contain a certificate")
|
||||||
return
|
return
|
||||||
@@ -153,7 +154,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
fp = "<error generating certificate fingerprint>"
|
fp = "<error generating certificate fingerprint>"
|
||||||
}
|
}
|
||||||
|
|
||||||
e := f.l.WithError(err).WithField("udpAddr", addr).
|
e := f.l.WithError(err).WithField("from", via).
|
||||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
WithField("certVpnNetworks", rc.Networks()).
|
WithField("certVpnNetworks", rc.Networks()).
|
||||||
WithField("certFingerprint", fp)
|
WithField("certFingerprint", fp)
|
||||||
@@ -166,13 +167,20 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
return
|
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() {
|
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
|
// 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())
|
myCertOtherVersion := cs.getCertificate(remoteCert.Certificate.Version())
|
||||||
if myCertOtherVersion == nil {
|
if myCertOtherVersion == nil {
|
||||||
if f.l.Level >= logrus.DebugLevel {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.WithError(err).WithFields(m{
|
f.l.WithError(err).WithFields(m{
|
||||||
"udpAddr": addr,
|
"from": via,
|
||||||
"handshake": m{"stage": 1, "style": "ix_psk0"},
|
"handshake": m{"stage": 1, "style": "ix_psk0"},
|
||||||
"cert": remoteCert,
|
"cert": remoteCert,
|
||||||
}).Debug("Might be unable to handshake with host due to missing certificate version")
|
}).Debug("Might be unable to handshake with host due to missing certificate version")
|
||||||
@@ -184,7 +192,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(remoteCert.Certificate.Networks()) == 0 {
|
if len(remoteCert.Certificate.Networks()) == 0 {
|
||||||
f.l.WithError(err).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("from", via).
|
||||||
WithField("cert", remoteCert).
|
WithField("cert", remoteCert).
|
||||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
Info("No networks in certificate")
|
Info("No networks in certificate")
|
||||||
@@ -201,7 +209,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
vpnAddrs := make([]netip.Addr, len(vpnNetworks))
|
vpnAddrs := make([]netip.Addr, len(vpnNetworks))
|
||||||
for i, network := range vpnNetworks {
|
for i, network := range vpnNetworks {
|
||||||
if f.myVpnAddrsTable.Contains(network.Addr()) {
|
if f.myVpnAddrsTable.Contains(network.Addr()) {
|
||||||
f.l.WithField("vpnNetworks", vpnNetworks).WithField("udpAddr", addr).
|
f.l.WithField("vpnNetworks", vpnNetworks).WithField("from", via).
|
||||||
WithField("certName", certName).
|
WithField("certName", certName).
|
||||||
WithField("certVersion", certVersion).
|
WithField("certVersion", certVersion).
|
||||||
WithField("fingerprint", fingerprint).
|
WithField("fingerprint", fingerprint).
|
||||||
@@ -215,18 +223,18 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if addr.IsValid() {
|
if !via.IsRelayed {
|
||||||
// addr can be invalid when the tunnel is being relayed.
|
|
||||||
// We only want to apply the remote allow list for direct tunnels here
|
// We only want to apply the remote allow list for direct tunnels here
|
||||||
if !f.lightHouse.GetRemoteAllowList().AllowAll(vpnAddrs, addr.Addr()) {
|
if !f.lightHouse.GetRemoteAllowList().AllowAll(vpnAddrs, via.UdpAddr.Addr()) {
|
||||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
|
Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
myIndex, err := generateIndex(f.l)
|
myIndex, err := generateIndex(f.l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.WithError(err).WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
WithField("certName", certName).
|
WithField("certName", certName).
|
||||||
WithField("certVersion", certVersion).
|
WithField("certVersion", certVersion).
|
||||||
WithField("fingerprint", fingerprint).
|
WithField("fingerprint", fingerprint).
|
||||||
@@ -251,7 +259,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
|
|
||||||
msgRxL := f.l.WithFields(m{
|
msgRxL := f.l.WithFields(m{
|
||||||
"vpnAddrs": vpnAddrs,
|
"vpnAddrs": vpnAddrs,
|
||||||
"udpAddr": addr,
|
"from": via,
|
||||||
"certName": certName,
|
"certName": certName,
|
||||||
"certVersion": certVersion,
|
"certVersion": certVersion,
|
||||||
"fingerprint": fingerprint,
|
"fingerprint": fingerprint,
|
||||||
@@ -283,7 +291,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
|
|
||||||
hsBytes, err := hs.Marshal()
|
hsBytes, err := hs.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
WithField("certName", certName).
|
WithField("certName", certName).
|
||||||
WithField("certVersion", certVersion).
|
WithField("certVersion", certVersion).
|
||||||
WithField("fingerprint", fingerprint).
|
WithField("fingerprint", fingerprint).
|
||||||
@@ -295,7 +303,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
nh := header.Encode(make([]byte, header.Len), header.Version, header.Handshake, header.HandshakeIXPSK0, hs.Details.InitiatorIndex, 2)
|
nh := header.Encode(make([]byte, header.Len), header.Version, header.Handshake, header.HandshakeIXPSK0, hs.Details.InitiatorIndex, 2)
|
||||||
msg, dKey, eKey, err := ci.H.WriteMessage(nh, hsBytes)
|
msg, dKey, eKey, err := ci.H.WriteMessage(nh, hsBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
WithField("certName", certName).
|
WithField("certName", certName).
|
||||||
WithField("certVersion", certVersion).
|
WithField("certVersion", certVersion).
|
||||||
WithField("fingerprint", fingerprint).
|
WithField("fingerprint", fingerprint).
|
||||||
@@ -303,7 +311,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).Error("Failed to call noise.WriteMessage")
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).Error("Failed to call noise.WriteMessage")
|
||||||
return
|
return
|
||||||
} else if dKey == nil || eKey == nil {
|
} else if dKey == nil || eKey == nil {
|
||||||
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
WithField("certName", certName).
|
WithField("certName", certName).
|
||||||
WithField("certVersion", certVersion).
|
WithField("certVersion", certVersion).
|
||||||
WithField("fingerprint", fingerprint).
|
WithField("fingerprint", fingerprint).
|
||||||
@@ -329,7 +337,9 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
ci.eKey = NewNebulaCipherState(eKey)
|
ci.eKey = NewNebulaCipherState(eKey)
|
||||||
|
|
||||||
hostinfo.remotes = f.lightHouse.QueryCache(vpnAddrs)
|
hostinfo.remotes = f.lightHouse.QueryCache(vpnAddrs)
|
||||||
hostinfo.SetRemote(addr)
|
if !via.IsRelayed {
|
||||||
|
hostinfo.SetRemote(via.UdpAddr)
|
||||||
|
}
|
||||||
hostinfo.buildNetworks(f.myVpnNetworksTable, remoteCert.Certificate)
|
hostinfo.buildNetworks(f.myVpnNetworksTable, remoteCert.Certificate)
|
||||||
|
|
||||||
existing, err := f.handshakeManager.CheckAndComplete(hostinfo, 0, f)
|
existing, err := f.handshakeManager.CheckAndComplete(hostinfo, 0, f)
|
||||||
@@ -337,7 +347,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
switch err {
|
switch err {
|
||||||
case ErrAlreadySeen:
|
case ErrAlreadySeen:
|
||||||
// Update remote if preferred
|
// Update remote if preferred
|
||||||
if existing.SetRemoteIfPreferred(f.hostMap, addr) {
|
if existing.SetRemoteIfPreferred(f.hostMap, via) {
|
||||||
// Send a test packet to ensure the other side has also switched to
|
// Send a test packet to ensure the other side has also switched to
|
||||||
// the preferred remote
|
// the preferred remote
|
||||||
f.SendMessageToVpnAddr(header.Test, header.TestRequest, vpnAddrs[0], []byte(""), make([]byte, 12, 12), make([]byte, mtu))
|
f.SendMessageToVpnAddr(header.Test, header.TestRequest, vpnAddrs[0], []byte(""), make([]byte, 12, 12), make([]byte, mtu))
|
||||||
@@ -345,21 +355,21 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
|
|
||||||
msg = existing.HandshakePacket[2]
|
msg = existing.HandshakePacket[2]
|
||||||
f.messageMetrics.Tx(header.Handshake, header.MessageSubType(msg[1]), 1)
|
f.messageMetrics.Tx(header.Handshake, header.MessageSubType(msg[1]), 1)
|
||||||
if addr.IsValid() {
|
if !via.IsRelayed {
|
||||||
err := f.outside.WriteTo(msg, addr)
|
err := f.outside.WriteTo(msg, via.UdpAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.WithField("vpnAddrs", existing.vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithField("vpnAddrs", existing.vpnAddrs).WithField("from", via).
|
||||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("cached", true).
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("cached", true).
|
||||||
WithError(err).Error("Failed to send handshake message")
|
WithError(err).Error("Failed to send handshake message")
|
||||||
} else {
|
} else {
|
||||||
f.l.WithField("vpnAddrs", existing.vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithField("vpnAddrs", existing.vpnAddrs).WithField("from", via).
|
||||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("cached", true).
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("cached", true).
|
||||||
Info("Handshake message sent")
|
Info("Handshake message sent")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if via == nil {
|
if via.relay == nil {
|
||||||
f.l.Error("Handshake send failed: both addr and via are nil.")
|
f.l.Error("Handshake send failed: both addr and via.relay are nil.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
|
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
|
||||||
@@ -371,7 +381,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
}
|
}
|
||||||
case ErrExistingHostInfo:
|
case ErrExistingHostInfo:
|
||||||
// This means there was an existing tunnel and this handshake was older than the one we are currently based on
|
// This means there was an existing tunnel and this handshake was older than the one we are currently based on
|
||||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
WithField("certName", certName).
|
WithField("certName", certName).
|
||||||
WithField("certVersion", certVersion).
|
WithField("certVersion", certVersion).
|
||||||
WithField("oldHandshakeTime", existing.lastHandshakeTime).
|
WithField("oldHandshakeTime", existing.lastHandshakeTime).
|
||||||
@@ -387,7 +397,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
return
|
return
|
||||||
case ErrLocalIndexCollision:
|
case ErrLocalIndexCollision:
|
||||||
// This means we failed to insert because of collision on localIndexId. Just let the next handshake packet retry
|
// This means we failed to insert because of collision on localIndexId. Just let the next handshake packet retry
|
||||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
WithField("certName", certName).
|
WithField("certName", certName).
|
||||||
WithField("certVersion", certVersion).
|
WithField("certVersion", certVersion).
|
||||||
WithField("fingerprint", fingerprint).
|
WithField("fingerprint", fingerprint).
|
||||||
@@ -400,7 +410,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
default:
|
default:
|
||||||
// Shouldn't happen, but just in case someone adds a new error type to CheckAndComplete
|
// Shouldn't happen, but just in case someone adds a new error type to CheckAndComplete
|
||||||
// And we forget to update it here
|
// And we forget to update it here
|
||||||
f.l.WithError(err).WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
WithField("certName", certName).
|
WithField("certName", certName).
|
||||||
WithField("certVersion", certVersion).
|
WithField("certVersion", certVersion).
|
||||||
WithField("fingerprint", fingerprint).
|
WithField("fingerprint", fingerprint).
|
||||||
@@ -414,30 +424,23 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
|
|
||||||
// Do the send
|
// Do the send
|
||||||
f.messageMetrics.Tx(header.Handshake, header.MessageSubType(msg[1]), 1)
|
f.messageMetrics.Tx(header.Handshake, header.MessageSubType(msg[1]), 1)
|
||||||
if addr.IsValid() {
|
if !via.IsRelayed {
|
||||||
err = f.outside.WriteTo(msg, addr)
|
err = f.outside.WriteTo(msg, via.UdpAddr)
|
||||||
|
log := f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
|
WithField("certName", certName).
|
||||||
|
WithField("certVersion", certVersion).
|
||||||
|
WithField("fingerprint", fingerprint).
|
||||||
|
WithField("issuer", issuer).
|
||||||
|
WithField("initiatorIndex", hs.Details.InitiatorIndex).WithField("responderIndex", hs.Details.ResponderIndex).
|
||||||
|
WithField("remoteIndex", h.RemoteIndex).WithField("handshake", m{"stage": 2, "style": "ix_psk0"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
log.WithError(err).Error("Failed to send handshake")
|
||||||
WithField("certName", certName).
|
|
||||||
WithField("certVersion", certVersion).
|
|
||||||
WithField("fingerprint", fingerprint).
|
|
||||||
WithField("issuer", issuer).
|
|
||||||
WithField("initiatorIndex", hs.Details.InitiatorIndex).WithField("responderIndex", hs.Details.ResponderIndex).
|
|
||||||
WithField("remoteIndex", h.RemoteIndex).WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
|
||||||
WithError(err).Error("Failed to send handshake")
|
|
||||||
} else {
|
} else {
|
||||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
log.Info("Handshake message sent")
|
||||||
WithField("certName", certName).
|
|
||||||
WithField("certVersion", certVersion).
|
|
||||||
WithField("fingerprint", fingerprint).
|
|
||||||
WithField("issuer", issuer).
|
|
||||||
WithField("initiatorIndex", hs.Details.InitiatorIndex).WithField("responderIndex", hs.Details.ResponderIndex).
|
|
||||||
WithField("remoteIndex", h.RemoteIndex).WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
|
||||||
Info("Handshake message sent")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if via == nil {
|
if via.relay == nil {
|
||||||
f.l.Error("Handshake send failed: both addr and via are nil.")
|
f.l.Error("Handshake send failed: both addr and via.relay are nil.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
|
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
|
||||||
@@ -462,7 +465,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *HandshakeHostInfo, packet []byte, h *header.H) bool {
|
func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packet []byte, h *header.H) bool {
|
||||||
if hh == nil {
|
if hh == nil {
|
||||||
// Nothing here to tear down, got a bogus stage 2 packet
|
// Nothing here to tear down, got a bogus stage 2 packet
|
||||||
return true
|
return true
|
||||||
@@ -472,10 +475,10 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
|
|||||||
defer hh.Unlock()
|
defer hh.Unlock()
|
||||||
|
|
||||||
hostinfo := hh.hostinfo
|
hostinfo := hh.hostinfo
|
||||||
if addr.IsValid() {
|
if !via.IsRelayed {
|
||||||
// The vpnAddr we know about is the one we tried to handshake with, use it to apply the remote allow list.
|
// The vpnAddr we know about is the one we tried to handshake with, use it to apply the remote allow list.
|
||||||
if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, addr.Addr()) {
|
if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, via.UdpAddr.Addr()) {
|
||||||
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -483,7 +486,7 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
|
|||||||
ci := hostinfo.ConnectionState
|
ci := hostinfo.ConnectionState
|
||||||
msg, eKey, dKey, err := ci.H.ReadMessage(nil, packet[header.Len:])
|
msg, eKey, dKey, err := ci.H.ReadMessage(nil, packet[header.Len:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("header", h).
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("header", h).
|
||||||
Error("Failed to call noise.ReadMessage")
|
Error("Failed to call noise.ReadMessage")
|
||||||
|
|
||||||
@@ -492,7 +495,7 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
|
|||||||
// near future
|
// near future
|
||||||
return false
|
return false
|
||||||
} else if dKey == nil || eKey == nil {
|
} else if dKey == nil || eKey == nil {
|
||||||
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
Error("Noise did not arrive at a key")
|
Error("Noise did not arrive at a key")
|
||||||
|
|
||||||
@@ -504,7 +507,7 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
|
|||||||
hs := &NebulaHandshake{}
|
hs := &NebulaHandshake{}
|
||||||
err = hs.Unmarshal(msg)
|
err = hs.Unmarshal(msg)
|
||||||
if err != nil || hs.Details == nil {
|
if err != nil || hs.Details == nil {
|
||||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).Error("Failed unmarshal handshake message")
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).Error("Failed unmarshal handshake message")
|
||||||
|
|
||||||
// The handshake state machine is complete, if things break now there is no chance to recover. Tear down and start again
|
// The handshake state machine is complete, if things break now there is no chance to recover. Tear down and start again
|
||||||
@@ -513,7 +516,7 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
|
|||||||
|
|
||||||
rc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())
|
rc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.WithError(err).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("from", via).
|
||||||
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
Info("Handshake did not contain a certificate")
|
Info("Handshake did not contain a certificate")
|
||||||
@@ -527,7 +530,7 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
|
|||||||
fp = "<error generating certificate fingerprint>"
|
fp = "<error generating certificate fingerprint>"
|
||||||
}
|
}
|
||||||
|
|
||||||
e := f.l.WithError(err).WithField("udpAddr", addr).
|
e := f.l.WithError(err).WithField("from", via).
|
||||||
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
WithField("certFingerprint", fp).
|
WithField("certFingerprint", fp).
|
||||||
@@ -540,9 +543,15 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
|
|||||||
e.Info("Invalid certificate from host")
|
e.Info("Invalid certificate from host")
|
||||||
return true
|
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 {
|
if len(remoteCert.Certificate.Networks()) == 0 {
|
||||||
f.l.WithError(err).WithField("udpAddr", addr).
|
f.l.WithError(err).WithField("from", via).
|
||||||
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||||
WithField("cert", remoteCert).
|
WithField("cert", remoteCert).
|
||||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
@@ -565,8 +574,8 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
|
|||||||
ci.eKey = NewNebulaCipherState(eKey)
|
ci.eKey = NewNebulaCipherState(eKey)
|
||||||
|
|
||||||
// Make sure the current udpAddr being used is set for responding
|
// Make sure the current udpAddr being used is set for responding
|
||||||
if addr.IsValid() {
|
if !via.IsRelayed {
|
||||||
hostinfo.SetRemote(addr)
|
hostinfo.SetRemote(via.UdpAddr)
|
||||||
} else {
|
} else {
|
||||||
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
|
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
|
||||||
}
|
}
|
||||||
@@ -588,7 +597,7 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
|
|||||||
// Ensure the right host responded
|
// Ensure the right host responded
|
||||||
if !correctHostResponded {
|
if !correctHostResponded {
|
||||||
f.l.WithField("intendedVpnAddrs", hostinfo.vpnAddrs).WithField("haveVpnNetworks", vpnNetworks).
|
f.l.WithField("intendedVpnAddrs", hostinfo.vpnAddrs).WithField("haveVpnNetworks", vpnNetworks).
|
||||||
WithField("udpAddr", addr).
|
WithField("from", via).
|
||||||
WithField("certName", certName).
|
WithField("certName", certName).
|
||||||
WithField("certVersion", certVersion).
|
WithField("certVersion", certVersion).
|
||||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
@@ -602,7 +611,7 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
|
|||||||
f.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], func(newHH *HandshakeHostInfo) {
|
f.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], func(newHH *HandshakeHostInfo) {
|
||||||
// Block the current used address
|
// Block the current used address
|
||||||
newHH.hostinfo.remotes = hostinfo.remotes
|
newHH.hostinfo.remotes = hostinfo.remotes
|
||||||
newHH.hostinfo.remotes.BlockRemote(addr)
|
newHH.hostinfo.remotes.BlockRemote(via)
|
||||||
|
|
||||||
f.l.WithField("blockedUdpAddrs", newHH.hostinfo.remotes.CopyBlockedRemotes()).
|
f.l.WithField("blockedUdpAddrs", newHH.hostinfo.remotes.CopyBlockedRemotes()).
|
||||||
WithField("vpnNetworks", vpnNetworks).
|
WithField("vpnNetworks", vpnNetworks).
|
||||||
@@ -625,7 +634,7 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
|
|||||||
ci.window.Update(f.l, 2)
|
ci.window.Update(f.l, 2)
|
||||||
|
|
||||||
duration := time.Since(hh.startTime).Nanoseconds()
|
duration := time.Since(hh.startTime).Nanoseconds()
|
||||||
msgRxL := f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
msgRxL := f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
WithField("certName", certName).
|
WithField("certName", certName).
|
||||||
WithField("certVersion", certVersion).
|
WithField("certVersion", certVersion).
|
||||||
WithField("fingerprint", fingerprint).
|
WithField("fingerprint", fingerprint).
|
||||||
|
|||||||
@@ -136,11 +136,11 @@ func (hm *HandshakeManager) Run(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hm *HandshakeManager) HandleIncoming(addr netip.AddrPort, via *ViaSender, packet []byte, h *header.H) {
|
func (hm *HandshakeManager) HandleIncoming(via ViaSender, packet []byte, h *header.H) {
|
||||||
// First remote allow list check before we know the vpnIp
|
// First remote allow list check before we know the vpnIp
|
||||||
if addr.IsValid() {
|
if !via.IsRelayed {
|
||||||
if !hm.lightHouse.GetRemoteAllowList().AllowUnknownVpnAddr(addr.Addr()) {
|
if !hm.lightHouse.GetRemoteAllowList().AllowUnknownVpnAddr(via.UdpAddr.Addr()) {
|
||||||
hm.l.WithField("udpAddr", addr).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
hm.l.WithField("from", via).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,11 +149,11 @@ func (hm *HandshakeManager) HandleIncoming(addr netip.AddrPort, via *ViaSender,
|
|||||||
case header.HandshakeIXPSK0:
|
case header.HandshakeIXPSK0:
|
||||||
switch h.MessageCounter {
|
switch h.MessageCounter {
|
||||||
case 1:
|
case 1:
|
||||||
ixHandshakeStage1(hm.f, addr, via, packet, h)
|
ixHandshakeStage1(hm.f, via, packet, h)
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
newHostinfo := hm.queryIndex(h.RemoteIndex)
|
newHostinfo := hm.queryIndex(h.RemoteIndex)
|
||||||
tearDown := ixHandshakeStage2(hm.f, addr, via, newHostinfo, packet, h)
|
tearDown := ixHandshakeStage2(hm.f, via, newHostinfo, packet, h)
|
||||||
if tearDown && newHostinfo != nil {
|
if tearDown && newHostinfo != nil {
|
||||||
hm.DeleteHostInfo(newHostinfo.hostinfo)
|
hm.DeleteHostInfo(newHostinfo.hostinfo)
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-6
@@ -1,7 +1,9 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -276,9 +278,25 @@ type HostInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ViaSender struct {
|
type ViaSender struct {
|
||||||
|
UdpAddr netip.AddrPort
|
||||||
relayHI *HostInfo // relayHI is the host info object of the relay
|
relayHI *HostInfo // relayHI is the host info object of the relay
|
||||||
remoteIdx uint32 // remoteIdx is the index included in the header of the received packet
|
remoteIdx uint32 // remoteIdx is the index included in the header of the received packet
|
||||||
relay *Relay // relay contains the rest of the relay information, including the PeerIP of the host trying to communicate with us.
|
relay *Relay // relay contains the rest of the relay information, including the PeerIP of the host trying to communicate with us.
|
||||||
|
IsRelayed bool // IsRelayed is true if the packet was sent through a relay
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ViaSender) String() string {
|
||||||
|
if v.IsRelayed {
|
||||||
|
return fmt.Sprintf("%s (relayed)", v.UdpAddr)
|
||||||
|
}
|
||||||
|
return v.UdpAddr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ViaSender) MarshalJSON() ([]byte, error) {
|
||||||
|
if v.IsRelayed {
|
||||||
|
return json.Marshal(m{"relay": v.UdpAddr})
|
||||||
|
}
|
||||||
|
return json.Marshal(m{"direct": v.UdpAddr})
|
||||||
}
|
}
|
||||||
|
|
||||||
type cachedPacket struct {
|
type cachedPacket struct {
|
||||||
@@ -694,6 +712,7 @@ func (i *HostInfo) GetCert() *cert.CachedCertificate {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Maybe use ViaSender here?
|
||||||
func (i *HostInfo) SetRemote(remote netip.AddrPort) {
|
func (i *HostInfo) SetRemote(remote netip.AddrPort) {
|
||||||
// We copy here because we likely got this remote from a source that reuses the object
|
// We copy here because we likely got this remote from a source that reuses the object
|
||||||
if i.remote != remote {
|
if i.remote != remote {
|
||||||
@@ -704,14 +723,14 @@ func (i *HostInfo) SetRemote(remote netip.AddrPort) {
|
|||||||
|
|
||||||
// SetRemoteIfPreferred returns true if the remote was changed. The lastRoam
|
// SetRemoteIfPreferred returns true if the remote was changed. The lastRoam
|
||||||
// time on the HostInfo will also be updated.
|
// time on the HostInfo will also be updated.
|
||||||
func (i *HostInfo) SetRemoteIfPreferred(hm *HostMap, newRemote netip.AddrPort) bool {
|
func (i *HostInfo) SetRemoteIfPreferred(hm *HostMap, via ViaSender) bool {
|
||||||
if !newRemote.IsValid() {
|
if via.IsRelayed {
|
||||||
// relays have nil udp Addrs
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
currentRemote := i.remote
|
currentRemote := i.remote
|
||||||
if !currentRemote.IsValid() {
|
if !currentRemote.IsValid() {
|
||||||
i.SetRemote(newRemote)
|
i.SetRemote(via.UdpAddr)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,7 +743,7 @@ func (i *HostInfo) SetRemoteIfPreferred(hm *HostMap, newRemote netip.AddrPort) b
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.Contains(newRemote.Addr()) {
|
if l.Contains(via.UdpAddr.Addr()) {
|
||||||
newIsPreferred = true
|
newIsPreferred = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -734,7 +753,7 @@ func (i *HostInfo) SetRemoteIfPreferred(hm *HostMap, newRemote netip.AddrPort) b
|
|||||||
i.lastRoam = time.Now()
|
i.lastRoam = time.Now()
|
||||||
i.lastRoamRemote = currentRemote
|
i.lastRoamRemote = currentRemote
|
||||||
|
|
||||||
i.SetRemote(newRemote)
|
i.SetRemote(via.UdpAddr)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,75 @@ import (
|
|||||||
"github.com/slackhq/nebula/iputil"
|
"github.com/slackhq/nebula/iputil"
|
||||||
"github.com/slackhq/nebula/noiseutil"
|
"github.com/slackhq/nebula/noiseutil"
|
||||||
"github.com/slackhq/nebula/routing"
|
"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) {
|
func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet, nb, out []byte, q int, localCache firewall.ConntrackCache) {
|
||||||
err := newPacket(packet, false, fwPacket)
|
err := newPacket(packet, false, fwPacket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -69,7 +136,6 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
|
|||||||
dropReason := f.firewall.Drop(*fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)
|
dropReason := f.firewall.Drop(*fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)
|
||||||
if dropReason == nil {
|
if dropReason == nil {
|
||||||
f.sendNoMetrics(header.Message, 0, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, packet, nb, out, q)
|
f.sendNoMetrics(header.Message, 0, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, packet, nb, out, q)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
f.rejectInside(packet, out, q)
|
f.rejectInside(packet, out, q)
|
||||||
if f.l.Level >= logrus.DebugLevel {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
@@ -410,3 +476,75 @@ 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})
|
||||||
|
}
|
||||||
|
|||||||
+125
-22
@@ -48,6 +48,8 @@ type InterfaceConfig struct {
|
|||||||
|
|
||||||
ConntrackCacheTimeout time.Duration
|
ConntrackCacheTimeout time.Duration
|
||||||
l *logrus.Logger
|
l *logrus.Logger
|
||||||
|
|
||||||
|
tunBatchSize int // batch size for TUN read/write batching, 0 to disable
|
||||||
}
|
}
|
||||||
|
|
||||||
type Interface struct {
|
type Interface struct {
|
||||||
@@ -77,7 +79,8 @@ type Interface struct {
|
|||||||
reQueryEvery atomic.Uint32
|
reQueryEvery atomic.Uint32
|
||||||
reQueryWait atomic.Int64
|
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 is used to decide if an active tunnel should trigger a punch notification through a lighthouse
|
||||||
rebindCount int8
|
rebindCount int8
|
||||||
@@ -87,6 +90,7 @@ type Interface struct {
|
|||||||
|
|
||||||
writers []udp.Conn
|
writers []udp.Conn
|
||||||
readers []io.ReadWriteCloser
|
readers []io.ReadWriteCloser
|
||||||
|
tunBatchSize int // batch size for TUN read/write batching
|
||||||
|
|
||||||
metricHandshakes metrics.Histogram
|
metricHandshakes metrics.Histogram
|
||||||
messageMetrics *MessageMetrics
|
messageMetrics *MessageMetrics
|
||||||
@@ -110,34 +114,34 @@ type EncWriter interface {
|
|||||||
GetCertState() *CertState
|
GetCertState() *CertState
|
||||||
}
|
}
|
||||||
|
|
||||||
type sendRecvErrorConfig uint8
|
type recvErrorConfig uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
sendRecvErrorAlways sendRecvErrorConfig = iota
|
recvErrorAlways recvErrorConfig = iota
|
||||||
sendRecvErrorNever
|
recvErrorNever
|
||||||
sendRecvErrorPrivate
|
recvErrorPrivate
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s sendRecvErrorConfig) ShouldSendRecvError(endpoint netip.AddrPort) bool {
|
func (s recvErrorConfig) ShouldRecvError(endpoint netip.AddrPort) bool {
|
||||||
switch s {
|
switch s {
|
||||||
case sendRecvErrorPrivate:
|
case recvErrorPrivate:
|
||||||
return endpoint.Addr().IsPrivate()
|
return endpoint.Addr().IsPrivate()
|
||||||
case sendRecvErrorAlways:
|
case recvErrorAlways:
|
||||||
return true
|
return true
|
||||||
case sendRecvErrorNever:
|
case recvErrorNever:
|
||||||
return false
|
return false
|
||||||
default:
|
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 {
|
switch s {
|
||||||
case sendRecvErrorAlways:
|
case recvErrorAlways:
|
||||||
return "always"
|
return "always"
|
||||||
case sendRecvErrorNever:
|
case recvErrorNever:
|
||||||
return "never"
|
return "never"
|
||||||
case sendRecvErrorPrivate:
|
case recvErrorPrivate:
|
||||||
return "private"
|
return "private"
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("invalid(%d)", s)
|
return fmt.Sprintf("invalid(%d)", s)
|
||||||
@@ -186,6 +190,7 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
|
|||||||
relayManager: c.relayManager,
|
relayManager: c.relayManager,
|
||||||
connectionManager: c.connectionManager,
|
connectionManager: c.connectionManager,
|
||||||
conntrackCacheTimeout: c.ConntrackCacheTimeout,
|
conntrackCacheTimeout: c.ConntrackCacheTimeout,
|
||||||
|
tunBatchSize: c.tunBatchSize,
|
||||||
|
|
||||||
metricHandshakes: metrics.GetOrRegisterHistogram("handshakes", nil, metrics.NewExpDecaySample(1028, 0.015)),
|
metricHandshakes: metrics.GetOrRegisterHistogram("handshakes", nil, metrics.NewExpDecaySample(1028, 0.015)),
|
||||||
messageMetrics: c.MessageMetrics,
|
messageMetrics: c.MessageMetrics,
|
||||||
@@ -243,6 +248,15 @@ func (f *Interface) activate() {
|
|||||||
f.readers[i] = reader
|
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 {
|
if err := f.inside.Activate(); err != nil {
|
||||||
f.inside.Close()
|
f.inside.Close()
|
||||||
f.l.Fatal(err)
|
f.l.Fatal(err)
|
||||||
@@ -279,20 +293,28 @@ func (f *Interface) listenOut(i int) {
|
|||||||
nb := make([]byte, 12, 12)
|
nb := make([]byte, 12, 12)
|
||||||
|
|
||||||
li.ListenOut(func(fromUdpAddr netip.AddrPort, payload []byte) {
|
li.ListenOut(func(fromUdpAddr netip.AddrPort, payload []byte) {
|
||||||
f.readOutsidePackets(fromUdpAddr, nil, plaintext[:0], payload, h, fwPacket, lhh, nb, i, ctCache.Get(f.l))
|
f.readOutsidePackets(ViaSender{UdpAddr: fromUdpAddr}, plaintext[:0], payload, h, fwPacket, lhh, nb, i, ctCache.Get(f.l))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) listenIn(reader io.ReadWriteCloser, i int) {
|
func (f *Interface) listenIn(reader io.ReadWriteCloser, i int) {
|
||||||
runtime.LockOSThread()
|
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)
|
packet := make([]byte, mtu)
|
||||||
out := make([]byte, mtu)
|
out := make([]byte, mtu)
|
||||||
fwPacket := &firewall.Packet{}
|
fwPacket := &firewall.Packet{}
|
||||||
nb := make([]byte, 12, 12)
|
nb := make([]byte, 12, 12)
|
||||||
|
|
||||||
conntrackCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
n, err := reader.Read(packet)
|
n, err := reader.Read(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -309,9 +331,58 @@ 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) {
|
func (f *Interface) RegisterConfigChangeCallbacks(c *config.C) {
|
||||||
c.RegisterReloadCallback(f.reloadFirewall)
|
c.RegisterReloadCallback(f.reloadFirewall)
|
||||||
c.RegisterReloadCallback(f.reloadSendRecvError)
|
c.RegisterReloadCallback(f.reloadSendRecvError)
|
||||||
|
c.RegisterReloadCallback(f.reloadAcceptRecvError)
|
||||||
c.RegisterReloadCallback(f.reloadDisconnectInvalid)
|
c.RegisterReloadCallback(f.reloadDisconnectInvalid)
|
||||||
c.RegisterReloadCallback(f.reloadMisc)
|
c.RegisterReloadCallback(f.reloadMisc)
|
||||||
|
|
||||||
@@ -375,16 +446,16 @@ func (f *Interface) reloadSendRecvError(c *config.C) {
|
|||||||
|
|
||||||
switch stringValue {
|
switch stringValue {
|
||||||
case "always":
|
case "always":
|
||||||
f.sendRecvErrorConfig = sendRecvErrorAlways
|
f.sendRecvErrorConfig = recvErrorAlways
|
||||||
case "never":
|
case "never":
|
||||||
f.sendRecvErrorConfig = sendRecvErrorNever
|
f.sendRecvErrorConfig = recvErrorNever
|
||||||
case "private":
|
case "private":
|
||||||
f.sendRecvErrorConfig = sendRecvErrorPrivate
|
f.sendRecvErrorConfig = recvErrorPrivate
|
||||||
default:
|
default:
|
||||||
if c.GetBool("listen.send_recv_error", true) {
|
if c.GetBool("listen.send_recv_error", true) {
|
||||||
f.sendRecvErrorConfig = sendRecvErrorAlways
|
f.sendRecvErrorConfig = recvErrorAlways
|
||||||
} else {
|
} else {
|
||||||
f.sendRecvErrorConfig = sendRecvErrorNever
|
f.sendRecvErrorConfig = recvErrorNever
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,6 +464,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) {
|
func (f *Interface) reloadMisc(c *config.C) {
|
||||||
if c.HasChanged("counters.try_promote") {
|
if c.HasChanged("counters.try_promote") {
|
||||||
n := c.GetUint32("counters.try_promote", defaultPromoteEvery)
|
n := c.GetUint32("counters.try_promote", defaultPromoteEvery)
|
||||||
@@ -464,6 +559,14 @@ func (f *Interface) Close() error {
|
|||||||
f.l.WithError(err).Error("Error while closing udp socket")
|
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
|
// Release the tun device
|
||||||
return f.inside.Close()
|
return f.inside.Close()
|
||||||
|
|||||||
+8
-9
@@ -1,7 +1,6 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -42,14 +41,14 @@ func Test_lhStaticMapping(t *testing.T) {
|
|||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1}}
|
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1}}
|
||||||
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
||||||
_, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
_, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
lh2 := "10.128.0.3"
|
lh2 := "10.128.0.3"
|
||||||
c = config.NewC(l)
|
c = config.NewC(l)
|
||||||
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1, lh2}}
|
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"}}
|
c.Settings["static_host_map"] = map[string]any{lh1: []any{"100.1.1.1:4242"}}
|
||||||
_, err = NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
_, err = NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
require.EqualError(t, err, "lighthouse 10.128.0.3 does not have a static_host_map entry")
|
require.EqualError(t, err, "lighthouse 10.128.0.3 does not have a static_host_map entry")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +70,7 @@ func TestReloadLighthouseInterval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
||||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
lh.ifce = &mockEncWriter{}
|
lh.ifce = &mockEncWriter{}
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ func BenchmarkLighthouseHandleRequest(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(b.Context(), l, c, cs, nil, nil)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
|
||||||
hAddr := netip.MustParseAddrPort("4.5.6.7:12345")
|
hAddr := netip.MustParseAddrPort("4.5.6.7:12345")
|
||||||
@@ -202,7 +201,7 @@ func TestLighthouse_Memory(t *testing.T) {
|
|||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||||
myVpnNetworksTable: nt,
|
myVpnNetworksTable: nt,
|
||||||
}
|
}
|
||||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
lh.ifce = &mockEncWriter{}
|
lh.ifce = &mockEncWriter{}
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
lhh := lh.NewRequestHandler()
|
lhh := lh.NewRequestHandler()
|
||||||
@@ -288,7 +287,7 @@ func TestLighthouse_reload(t *testing.T) {
|
|||||||
myVpnNetworksTable: nt,
|
myVpnNetworksTable: nt,
|
||||||
}
|
}
|
||||||
|
|
||||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
nc := map[string]any{
|
nc := map[string]any{
|
||||||
@@ -523,7 +522,7 @@ func TestLighthouse_Dont_Delete_Static_Hosts(t *testing.T) {
|
|||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||||
myVpnNetworksTable: nt,
|
myVpnNetworksTable: nt,
|
||||||
}
|
}
|
||||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
lh.ifce = &mockEncWriter{}
|
lh.ifce = &mockEncWriter{}
|
||||||
|
|
||||||
@@ -589,7 +588,7 @@ func TestLighthouse_DeletesWork(t *testing.T) {
|
|||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||||
myVpnNetworksTable: nt,
|
myVpnNetworksTable: nt,
|
||||||
}
|
}
|
||||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
lh.ifce = &mockEncWriter{}
|
lh.ifce = &mockEncWriter{}
|
||||||
|
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
|||||||
punchy: punchy,
|
punchy: punchy,
|
||||||
ConntrackCacheTimeout: conntrackCacheTimeout,
|
ConntrackCacheTimeout: conntrackCacheTimeout,
|
||||||
l: l,
|
l: l,
|
||||||
|
tunBatchSize: c.GetInt("listen.batch", 64),
|
||||||
}
|
}
|
||||||
|
|
||||||
var ifce *Interface
|
var ifce *Interface
|
||||||
@@ -265,6 +266,7 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
|||||||
ifce.RegisterConfigChangeCallbacks(c)
|
ifce.RegisterConfigChangeCallbacks(c)
|
||||||
ifce.reloadDisconnectInvalid(c)
|
ifce.reloadDisconnectInvalid(c)
|
||||||
ifce.reloadSendRecvError(c)
|
ifce.reloadSendRecvError(c)
|
||||||
|
ifce.reloadAcceptRecvError(c)
|
||||||
|
|
||||||
handshakeManager.f = ifce
|
handshakeManager.f = ifce
|
||||||
go handshakeManager.Run(ctx)
|
go handshakeManager.Run(ctx)
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ const EncryptLockNeeded = true
|
|||||||
// NewGCMTLS is no longer exposed in go1.19+, so we need to link it in
|
// NewGCMTLS is no longer exposed in go1.19+, so we need to link it in
|
||||||
// See: https://github.com/golang/go/issues/56326
|
// 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
|
// validated mode of AES-GCM which enforces the nonce is strictly
|
||||||
// monotonically increasing. This is the TLS 1.2 specification for nonce
|
// monotonically increasing. This is the TLS 1.2 specification for nonce
|
||||||
// generation (which also matches the method used by the Noise Protocol)
|
// generation (which also matches the method used by the Noise Protocol)
|
||||||
|
|||||||
+49
-35
@@ -19,21 +19,21 @@ const (
|
|||||||
minFwPacketLen = 4
|
minFwPacketLen = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *Interface) readOutsidePackets(ip netip.AddrPort, via *ViaSender, out []byte, packet []byte, h *header.H, fwPacket *firewall.Packet, lhf *LightHouseHandler, nb []byte, q int, localCache firewall.ConntrackCache) {
|
func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte, h *header.H, fwPacket *firewall.Packet, lhf *LightHouseHandler, nb []byte, q int, localCache firewall.ConntrackCache) {
|
||||||
err := h.Parse(packet)
|
err := h.Parse(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Hole punch packets are 0 or 1 byte big, so lets ignore printing those errors
|
// Hole punch packets are 0 or 1 byte big, so lets ignore printing those errors
|
||||||
if len(packet) > 1 {
|
if len(packet) > 1 {
|
||||||
f.l.WithField("packet", packet).Infof("Error while parsing inbound packet from %s: %s", ip, err)
|
f.l.WithField("packet", packet).Infof("Error while parsing inbound packet from %s: %s", via, err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//l.Error("in packet ", header, packet[HeaderLen:])
|
//l.Error("in packet ", header, packet[HeaderLen:])
|
||||||
if ip.IsValid() {
|
if !via.IsRelayed {
|
||||||
if f.myVpnNetworksTable.Contains(ip.Addr()) {
|
if f.myVpnNetworksTable.Contains(via.UdpAddr.Addr()) {
|
||||||
if f.l.Level >= logrus.DebugLevel {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.WithField("udpAddr", ip).Debug("Refusing to process double encrypted packet")
|
f.l.WithField("from", via).Debug("Refusing to process double encrypted packet")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -54,8 +54,7 @@ func (f *Interface) readOutsidePackets(ip netip.AddrPort, via *ViaSender, out []
|
|||||||
|
|
||||||
switch h.Type {
|
switch h.Type {
|
||||||
case header.Message:
|
case header.Message:
|
||||||
// TODO handleEncrypted sends directly to addr on error. Handle this in the tunneling case.
|
if !f.handleEncrypted(ci, via, h) {
|
||||||
if !f.handleEncrypted(ci, ip, h) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +78,7 @@ func (f *Interface) readOutsidePackets(ip netip.AddrPort, via *ViaSender, out []
|
|||||||
// Successfully validated the thing. Get rid of the Relay header.
|
// Successfully validated the thing. Get rid of the Relay header.
|
||||||
signedPayload = signedPayload[header.Len:]
|
signedPayload = signedPayload[header.Len:]
|
||||||
// Pull the Roaming parts up here, and return in all call paths.
|
// Pull the Roaming parts up here, and return in all call paths.
|
||||||
f.handleHostRoaming(hostinfo, ip)
|
f.handleHostRoaming(hostinfo, via)
|
||||||
// Track usage of both the HostInfo and the Relay for the received & authenticated packet
|
// Track usage of both the HostInfo and the Relay for the received & authenticated packet
|
||||||
f.connectionManager.In(hostinfo)
|
f.connectionManager.In(hostinfo)
|
||||||
f.connectionManager.RelayUsed(h.RemoteIndex)
|
f.connectionManager.RelayUsed(h.RemoteIndex)
|
||||||
@@ -96,7 +95,14 @@ func (f *Interface) readOutsidePackets(ip netip.AddrPort, via *ViaSender, out []
|
|||||||
case TerminalType:
|
case TerminalType:
|
||||||
// If I am the target of this relay, process the unwrapped packet
|
// If I am the target of this relay, process the unwrapped packet
|
||||||
// From this recursive point, all these variables are 'burned'. We shouldn't rely on them again.
|
// From this recursive point, all these variables are 'burned'. We shouldn't rely on them again.
|
||||||
f.readOutsidePackets(netip.AddrPort{}, &ViaSender{relayHI: hostinfo, remoteIdx: relay.RemoteIndex, relay: relay}, out[:0], signedPayload, h, fwPacket, lhf, nb, q, localCache)
|
via = ViaSender{
|
||||||
|
UdpAddr: via.UdpAddr,
|
||||||
|
relayHI: hostinfo,
|
||||||
|
remoteIdx: relay.RemoteIndex,
|
||||||
|
relay: relay,
|
||||||
|
IsRelayed: true,
|
||||||
|
}
|
||||||
|
f.readOutsidePackets(via, out[:0], signedPayload, h, fwPacket, lhf, nb, q, localCache)
|
||||||
return
|
return
|
||||||
case ForwardingType:
|
case ForwardingType:
|
||||||
// Find the target HostInfo relay object
|
// Find the target HostInfo relay object
|
||||||
@@ -126,31 +132,32 @@ func (f *Interface) readOutsidePackets(ip netip.AddrPort, via *ViaSender, out []
|
|||||||
|
|
||||||
case header.LightHouse:
|
case header.LightHouse:
|
||||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||||
if !f.handleEncrypted(ci, ip, h) {
|
if !f.handleEncrypted(ci, via, h) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).WithError(err).WithField("udpAddr", ip).
|
hostinfo.logger(f.l).WithError(err).WithField("from", via).
|
||||||
WithField("packet", packet).
|
WithField("packet", packet).
|
||||||
Error("Failed to decrypt lighthouse packet")
|
Error("Failed to decrypt lighthouse packet")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lhf.HandleRequest(ip, hostinfo.vpnAddrs, d, f)
|
//TODO: assert via is not relayed
|
||||||
|
lhf.HandleRequest(via.UdpAddr, hostinfo.vpnAddrs, d, f)
|
||||||
|
|
||||||
// Fallthrough to the bottom to record incoming traffic
|
// Fallthrough to the bottom to record incoming traffic
|
||||||
|
|
||||||
case header.Test:
|
case header.Test:
|
||||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||||
if !f.handleEncrypted(ci, ip, h) {
|
if !f.handleEncrypted(ci, via, h) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).WithError(err).WithField("udpAddr", ip).
|
hostinfo.logger(f.l).WithError(err).WithField("from", via).
|
||||||
WithField("packet", packet).
|
WithField("packet", packet).
|
||||||
Error("Failed to decrypt test packet")
|
Error("Failed to decrypt test packet")
|
||||||
return
|
return
|
||||||
@@ -159,7 +166,7 @@ func (f *Interface) readOutsidePackets(ip netip.AddrPort, via *ViaSender, out []
|
|||||||
if h.Subtype == header.TestRequest {
|
if h.Subtype == header.TestRequest {
|
||||||
// This testRequest might be from TryPromoteBest, so we should roam
|
// This testRequest might be from TryPromoteBest, so we should roam
|
||||||
// to the new IP address before responding
|
// to the new IP address before responding
|
||||||
f.handleHostRoaming(hostinfo, ip)
|
f.handleHostRoaming(hostinfo, via)
|
||||||
f.send(header.Test, header.TestReply, ci, hostinfo, d, nb, out)
|
f.send(header.Test, header.TestReply, ci, hostinfo, d, nb, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,34 +177,34 @@ func (f *Interface) readOutsidePackets(ip netip.AddrPort, via *ViaSender, out []
|
|||||||
|
|
||||||
case header.Handshake:
|
case header.Handshake:
|
||||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||||
f.handshakeManager.HandleIncoming(ip, via, packet, h)
|
f.handshakeManager.HandleIncoming(via, packet, h)
|
||||||
return
|
return
|
||||||
|
|
||||||
case header.RecvError:
|
case header.RecvError:
|
||||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||||
f.handleRecvError(ip, h)
|
f.handleRecvError(via.UdpAddr, h)
|
||||||
return
|
return
|
||||||
|
|
||||||
case header.CloseTunnel:
|
case header.CloseTunnel:
|
||||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||||
if !f.handleEncrypted(ci, ip, h) {
|
if !f.handleEncrypted(ci, via, h) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hostinfo.logger(f.l).WithField("udpAddr", ip).
|
hostinfo.logger(f.l).WithField("from", via).
|
||||||
Info("Close tunnel received, tearing down.")
|
Info("Close tunnel received, tearing down.")
|
||||||
|
|
||||||
f.closeTunnel(hostinfo)
|
f.closeTunnel(hostinfo)
|
||||||
return
|
return
|
||||||
|
|
||||||
case header.Control:
|
case header.Control:
|
||||||
if !f.handleEncrypted(ci, ip, h) {
|
if !f.handleEncrypted(ci, via, h) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).WithError(err).WithField("udpAddr", ip).
|
hostinfo.logger(f.l).WithError(err).WithField("from", via).
|
||||||
WithField("packet", packet).
|
WithField("packet", packet).
|
||||||
Error("Failed to decrypt Control packet")
|
Error("Failed to decrypt Control packet")
|
||||||
return
|
return
|
||||||
@@ -207,11 +214,11 @@ func (f *Interface) readOutsidePackets(ip netip.AddrPort, via *ViaSender, out []
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||||
hostinfo.logger(f.l).Debugf("Unexpected packet received from %s", ip)
|
hostinfo.logger(f.l).Debugf("Unexpected packet received from %s", via)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f.handleHostRoaming(hostinfo, ip)
|
f.handleHostRoaming(hostinfo, via)
|
||||||
|
|
||||||
f.connectionManager.In(hostinfo)
|
f.connectionManager.In(hostinfo)
|
||||||
}
|
}
|
||||||
@@ -230,36 +237,36 @@ func (f *Interface) sendCloseTunnel(h *HostInfo) {
|
|||||||
f.send(header.CloseTunnel, 0, h.ConnectionState, h, []byte{}, make([]byte, 12, 12), make([]byte, mtu))
|
f.send(header.CloseTunnel, 0, h.ConnectionState, h, []byte{}, make([]byte, 12, 12), make([]byte, mtu))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) handleHostRoaming(hostinfo *HostInfo, udpAddr netip.AddrPort) {
|
func (f *Interface) handleHostRoaming(hostinfo *HostInfo, via ViaSender) {
|
||||||
if udpAddr.IsValid() && hostinfo.remote != udpAddr {
|
if !via.IsRelayed && hostinfo.remote != via.UdpAddr {
|
||||||
if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, udpAddr.Addr()) {
|
if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, via.UdpAddr.Addr()) {
|
||||||
hostinfo.logger(f.l).WithField("newAddr", udpAddr).Debug("lighthouse.remote_allow_list denied roaming")
|
hostinfo.logger(f.l).WithField("newAddr", via.UdpAddr).Debug("lighthouse.remote_allow_list denied roaming")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hostinfo.lastRoam.IsZero() && udpAddr == hostinfo.lastRoamRemote && time.Since(hostinfo.lastRoam) < RoamingSuppressSeconds*time.Second {
|
if !hostinfo.lastRoam.IsZero() && via.UdpAddr == hostinfo.lastRoamRemote && time.Since(hostinfo.lastRoam) < RoamingSuppressSeconds*time.Second {
|
||||||
if f.l.Level >= logrus.DebugLevel {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
hostinfo.logger(f.l).WithField("udpAddr", hostinfo.remote).WithField("newAddr", udpAddr).
|
hostinfo.logger(f.l).WithField("udpAddr", hostinfo.remote).WithField("newAddr", via.UdpAddr).
|
||||||
Debugf("Suppressing roam back to previous remote for %d seconds", RoamingSuppressSeconds)
|
Debugf("Suppressing roam back to previous remote for %d seconds", RoamingSuppressSeconds)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hostinfo.logger(f.l).WithField("udpAddr", hostinfo.remote).WithField("newAddr", udpAddr).
|
hostinfo.logger(f.l).WithField("udpAddr", hostinfo.remote).WithField("newAddr", via.UdpAddr).
|
||||||
Info("Host roamed to new udp ip/port.")
|
Info("Host roamed to new udp ip/port.")
|
||||||
hostinfo.lastRoam = time.Now()
|
hostinfo.lastRoam = time.Now()
|
||||||
hostinfo.lastRoamRemote = hostinfo.remote
|
hostinfo.lastRoamRemote = hostinfo.remote
|
||||||
hostinfo.SetRemote(udpAddr)
|
hostinfo.SetRemote(via.UdpAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleEncrypted returns true if a packet should be processed, false otherwise
|
// handleEncrypted returns true if a packet should be processed, false otherwise
|
||||||
func (f *Interface) handleEncrypted(ci *ConnectionState, addr netip.AddrPort, h *header.H) bool {
|
func (f *Interface) handleEncrypted(ci *ConnectionState, via ViaSender, h *header.H) bool {
|
||||||
// If connectionstate does not exist, send a recv error, if possible, to encourage a fast reconnect
|
// If connectionstate does not exist, send a recv error, if possible, to encourage a fast reconnect
|
||||||
if ci == nil {
|
if ci == nil {
|
||||||
if addr.IsValid() {
|
if !via.IsRelayed {
|
||||||
f.maybeSendRecvError(addr, h.RemoteIndex)
|
f.maybeSendRecvError(via.UdpAddr, h.RemoteIndex)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -509,7 +516,7 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) maybeSendRecvError(endpoint netip.AddrPort, index uint32) {
|
func (f *Interface) maybeSendRecvError(endpoint netip.AddrPort, index uint32) {
|
||||||
if f.sendRecvErrorConfig.ShouldSendRecvError(endpoint) {
|
if f.sendRecvErrorConfig.ShouldRecvError(endpoint) {
|
||||||
f.sendRecvError(endpoint, index)
|
f.sendRecvError(endpoint, index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -527,6 +534,13 @@ func (f *Interface) sendRecvError(endpoint netip.AddrPort, index uint32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) handleRecvError(addr netip.AddrPort, h *header.H) {
|
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 {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.WithField("index", h.RemoteIndex).
|
f.l.WithField("index", h.RemoteIndex).
|
||||||
WithField("udpAddr", addr).
|
WithField("udpAddr", addr).
|
||||||
|
|||||||
@@ -16,3 +16,38 @@ type Device interface {
|
|||||||
SupportsMultiqueue() bool
|
SupportsMultiqueue() bool
|
||||||
NewMultiQueueReader() (io.ReadWriteCloser, error)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ import (
|
|||||||
|
|
||||||
const DefaultMTU = 1300
|
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
|
// TODO: We may be able to remove routines
|
||||||
type DeviceFactory func(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error)
|
type DeviceFactory func(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error)
|
||||||
|
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the device name
|
// Set the device name
|
||||||
ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
|
_ = ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
|
||||||
}
|
}
|
||||||
|
|
||||||
t := &tun{
|
t := &tun{
|
||||||
|
|||||||
+421
-5
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -23,6 +24,11 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// virtioNetHdrLen is the length of virtio_net_hdr (without mergeable buffers)
|
||||||
|
virtioNetHdrLen = 10
|
||||||
|
)
|
||||||
|
|
||||||
type tun struct {
|
type tun struct {
|
||||||
io.ReadWriteCloser
|
io.ReadWriteCloser
|
||||||
fd int
|
fd int
|
||||||
@@ -33,6 +39,13 @@ type tun struct {
|
|||||||
TXQueueLen int
|
TXQueueLen int
|
||||||
deviceIndex int
|
deviceIndex int
|
||||||
ioctlFd uintptr
|
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]
|
Routes atomic.Pointer[[]Route]
|
||||||
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
||||||
@@ -40,6 +53,11 @@ type tun struct {
|
|||||||
useSystemRoutes bool
|
useSystemRoutes bool
|
||||||
useSystemRoutesBufferSize int
|
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
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +65,23 @@ func (t *tun) Networks() []netip.Prefix {
|
|||||||
return t.vpnNetworks
|
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 {
|
type ifReq struct {
|
||||||
Name [16]byte
|
Name [16]byte
|
||||||
Flags uint16
|
Flags uint16
|
||||||
@@ -101,17 +136,35 @@ 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
|
var req ifReq
|
||||||
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI)
|
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI)
|
||||||
if multiqueue {
|
if multiqueue {
|
||||||
req.Flags |= unix.IFF_MULTI_QUEUE
|
req.Flags |= unix.IFF_MULTI_QUEUE
|
||||||
}
|
}
|
||||||
copy(req.Name[:], c.GetString("tun.dev", ""))
|
if useVnetHdr {
|
||||||
|
req.Flags |= unix.IFF_VNET_HDR
|
||||||
|
}
|
||||||
|
|
||||||
|
nameStr := c.GetString("tun.dev", "")
|
||||||
|
copy(req.Name[:], nameStr)
|
||||||
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
||||||
return nil, err
|
return nil, &NameError{
|
||||||
|
Name: nameStr,
|
||||||
|
Underlying: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
name := strings.Trim(string(req.Name[:]), "\x00")
|
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")
|
file := os.NewFile(uintptr(fd), "/dev/net/tun")
|
||||||
t, err := newTunGeneric(c, l, file, vpnNetworks)
|
t, err := newTunGeneric(c, l, file, vpnNetworks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -119,6 +172,13 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, multiqueu
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Device = name
|
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
|
return t, nil
|
||||||
}
|
}
|
||||||
@@ -131,6 +191,7 @@ func newTunGeneric(c *config.C, l *logrus.Logger, file *os.File, vpnNetworks []n
|
|||||||
TXQueueLen: c.GetInt("tun.tx_queue", 500),
|
TXQueueLen: c.GetInt("tun.tx_queue", 500),
|
||||||
useSystemRoutes: c.GetBool("tun.use_system_route_table", false),
|
useSystemRoutes: c.GetBool("tun.use_system_route_table", false),
|
||||||
useSystemRoutesBufferSize: c.GetInt("tun.use_system_route_table_buffer_size", 0),
|
useSystemRoutesBufferSize: c.GetInt("tun.use_system_route_table_buffer_size", 0),
|
||||||
|
routesFromSystem: map[netip.Prefix]routing.Gateways{},
|
||||||
l: l,
|
l: l,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +225,13 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
|||||||
return err
|
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
|
oldDefaultMTU := t.DefaultMTU
|
||||||
oldMaxMTU := t.MaxMTU
|
oldMaxMTU := t.MaxMTU
|
||||||
newDefaultMTU := c.GetInt("tun.mtu", DefaultMTU)
|
newDefaultMTU := c.GetInt("tun.mtu", DefaultMTU)
|
||||||
@@ -221,21 +289,172 @@ func (t *tun) SupportsMultiqueue() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||||
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
fd, err := unix.Open("/dev/net/tun", os.O_RDWR|unix.O_NONBLOCK, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var req ifReq
|
var req ifReq
|
||||||
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI | unix.IFF_MULTI_QUEUE)
|
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)
|
copy(req.Name[:], t.Device)
|
||||||
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
file := os.NewFile(uintptr(fd), "/dev/net/tun")
|
reader := &tunBatchReader{fd: fd, device: t.Device, vnetHdr: t.vnetHdr}
|
||||||
|
if t.vnetHdr {
|
||||||
|
reader.readBuf = make([]byte, t.MaxMTU+virtioNetHdrLen)
|
||||||
|
}
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
return file, nil
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
|
func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
|
||||||
@@ -244,6 +463,27 @@ func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Write(b []byte) (int, error) {
|
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
|
var nn int
|
||||||
maximum := len(b)
|
maximum := len(b)
|
||||||
|
|
||||||
@@ -266,6 +506,177 @@ func (t *tun) Write(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) {
|
func (t *tun) deviceBytes() (o [16]byte) {
|
||||||
for i, c := range t.Device {
|
for i, c := range t.Device {
|
||||||
o[i] = byte(c)
|
o[i] = byte(c)
|
||||||
@@ -673,14 +1084,18 @@ func (t *tun) updateRoutes(r netlink.RouteUpdate) {
|
|||||||
|
|
||||||
newTree := t.routeTree.Load().Clone()
|
newTree := t.routeTree.Load().Clone()
|
||||||
|
|
||||||
|
t.routesFromSystemLock.Lock()
|
||||||
if r.Type == unix.RTM_NEWROUTE {
|
if r.Type == unix.RTM_NEWROUTE {
|
||||||
t.l.WithField("destination", dst).WithField("via", gateways).Info("Adding route")
|
t.l.WithField("destination", dst).WithField("via", gateways).Info("Adding route")
|
||||||
|
t.routesFromSystem[dst] = gateways
|
||||||
newTree.Insert(dst, gateways)
|
newTree.Insert(dst, gateways)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
t.l.WithField("destination", dst).WithField("via", gateways).Info("Removing route")
|
t.l.WithField("destination", dst).WithField("via", gateways).Info("Removing route")
|
||||||
|
delete(t.routesFromSystem, dst)
|
||||||
newTree.Delete(dst)
|
newTree.Delete(dst)
|
||||||
}
|
}
|
||||||
|
t.routesFromSystemLock.Unlock()
|
||||||
t.routeTree.Store(newTree)
|
t.routeTree.Store(newTree)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,6 +1110,7 @@ func (t *tun) Close() error {
|
|||||||
|
|
||||||
if t.ioctlFd > 0 {
|
if t.ioctlFd > 0 {
|
||||||
_ = os.NewFile(t.ioctlFd, "ioctlFd").Close()
|
_ = os.NewFile(t.ioctlFd, "ioctlFd").Close()
|
||||||
|
t.ioctlFd = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -74,7 +74,10 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
|
|||||||
l.WithError(err).Debug("Failed to create wintun device, retrying")
|
l.WithError(err).Debug("Failed to create wintun device, retrying")
|
||||||
tunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)
|
tunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create TUN device failed: %w", err)
|
return nil, &NameError{
|
||||||
|
Name: deviceName,
|
||||||
|
Underlying: fmt.Errorf("create TUN device failed: %w", err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.tun = tunDevice.(*wintun.NativeTun)
|
t.tun = tunDevice.(*wintun.NativeTun)
|
||||||
|
|||||||
+5
-5
@@ -338,21 +338,21 @@ func (r *RemoteList) CopyCache() *CacheMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BlockRemote locks and records the address as bad, it will be excluded from the deduplicated address list
|
// BlockRemote locks and records the address as bad, it will be excluded from the deduplicated address list
|
||||||
func (r *RemoteList) BlockRemote(bad netip.AddrPort) {
|
func (r *RemoteList) BlockRemote(bad ViaSender) {
|
||||||
if !bad.IsValid() {
|
if bad.IsRelayed {
|
||||||
// relays can have nil udp Addrs
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Lock()
|
r.Lock()
|
||||||
defer r.Unlock()
|
defer r.Unlock()
|
||||||
|
|
||||||
// Check if we already blocked this addr
|
// Check if we already blocked this addr
|
||||||
if r.unlockedIsBad(bad) {
|
if r.unlockedIsBad(bad.UdpAddr) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// We copy here because we are taking something else's memory and we can't trust everything
|
// We copy here because we are taking something else's memory and we can't trust everything
|
||||||
r.badRemotes = append(r.badRemotes, bad)
|
r.badRemotes = append(r.badRemotes, bad.UdpAddr)
|
||||||
|
|
||||||
// Mark the next interaction must recollect/dedupe
|
// Mark the next interaction must recollect/dedupe
|
||||||
r.shouldRebuild = true
|
r.shouldRebuild = true
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Sentinal value
|
// Sentinel value
|
||||||
BucketNotCalculated = -1
|
BucketNotCalculated = -1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+18
@@ -13,13 +13,22 @@ type EncReader func(
|
|||||||
payload []byte,
|
payload []byte,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// BatchPacket represents a single packet in a batch write operation
|
||||||
|
type BatchPacket struct {
|
||||||
|
Payload []byte
|
||||||
|
Addr netip.AddrPort
|
||||||
|
}
|
||||||
|
|
||||||
type Conn interface {
|
type Conn interface {
|
||||||
Rebind() error
|
Rebind() error
|
||||||
LocalAddr() (netip.AddrPort, error)
|
LocalAddr() (netip.AddrPort, error)
|
||||||
ListenOut(r EncReader)
|
ListenOut(r EncReader)
|
||||||
WriteTo(b []byte, addr netip.AddrPort) error
|
WriteTo(b []byte, addr netip.AddrPort) error
|
||||||
|
WriteBatch(pkts []BatchPacket) (int, error)
|
||||||
ReloadConfig(c *config.C)
|
ReloadConfig(c *config.C)
|
||||||
SupportsMultipleReaders() bool
|
SupportsMultipleReaders() bool
|
||||||
|
SupportsGSO() bool
|
||||||
|
SupportsGRO() bool
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,9 +46,18 @@ func (NoopConn) ListenOut(_ EncReader) {
|
|||||||
func (NoopConn) SupportsMultipleReaders() bool {
|
func (NoopConn) SupportsMultipleReaders() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
func (NoopConn) SupportsGSO() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (NoopConn) SupportsGRO() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
func (NoopConn) WriteTo(_ []byte, _ netip.AddrPort) error {
|
func (NoopConn) WriteTo(_ []byte, _ netip.AddrPort) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (NoopConn) WriteBatch(pkts []BatchPacket) (int, error) {
|
||||||
|
return len(pkts), nil
|
||||||
|
}
|
||||||
func (NoopConn) ReloadConfig(_ *config.C) {
|
func (NoopConn) ReloadConfig(_ *config.C) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,14 @@ func (u *StdConn) SupportsMultipleReaders() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *StdConn) SupportsGSO() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *StdConn) SupportsGRO() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (u *StdConn) Rebind() error {
|
func (u *StdConn) Rebind() error {
|
||||||
var err error
|
var err error
|
||||||
if u.isV4 {
|
if u.isV4 {
|
||||||
@@ -202,3 +210,12 @@ func (u *StdConn) Rebind() error {
|
|||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ package udp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
@@ -74,13 +76,23 @@ type rawMessage struct {
|
|||||||
func (u *GenericConn) ListenOut(r EncReader) {
|
func (u *GenericConn) ListenOut(r EncReader) {
|
||||||
buffer := make([]byte, MTU)
|
buffer := make([]byte, MTU)
|
||||||
|
|
||||||
|
var lastRecvErr time.Time
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Just read one packet at a time
|
// Just read one packet at a time
|
||||||
n, rua, err := u.ReadFromUDPAddrPort(buffer)
|
n, rua, err := u.ReadFromUDPAddrPort(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, net.ErrClosed) {
|
||||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||||
return
|
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])
|
r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n])
|
||||||
}
|
}
|
||||||
@@ -89,3 +101,20 @@ func (u *GenericConn) ListenOut(r EncReader) {
|
|||||||
func (u *GenericConn) SupportsMultipleReaders() bool {
|
func (u *GenericConn) SupportsMultipleReaders() bool {
|
||||||
return false
|
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
|
||||||
|
}
|
||||||
|
|||||||
+354
-4
@@ -5,6 +5,7 @@ package udp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -22,6 +23,8 @@ type StdConn struct {
|
|||||||
isV4 bool
|
isV4 bool
|
||||||
l *logrus.Logger
|
l *logrus.Logger
|
||||||
batch int
|
batch int
|
||||||
|
gsoSupported bool
|
||||||
|
groSupported bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybeIPV4(ip net.IP) (net.IP, bool) {
|
func maybeIPV4(ip net.IP) (net.IP, bool) {
|
||||||
@@ -32,6 +35,78 @@ func maybeIPV4(ip net.IP) (net.IP, bool) {
|
|||||||
return ip, false
|
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) {
|
func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
||||||
af := unix.AF_INET6
|
af := unix.AF_INET6
|
||||||
if ip.Is4() {
|
if ip.Is4() {
|
||||||
@@ -69,13 +144,31 @@ func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch in
|
|||||||
return nil, fmt.Errorf("unable to bind to socket: %s", err)
|
return nil, fmt.Errorf("unable to bind to socket: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &StdConn{sysFd: fd, isV4: ip.Is4(), l: l, batch: batch}, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) SupportsMultipleReaders() bool {
|
func (u *StdConn) SupportsMultipleReaders() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *StdConn) SupportsGSO() bool {
|
||||||
|
return u.gsoSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *StdConn) SupportsGRO() bool {
|
||||||
|
return u.groSupported
|
||||||
|
}
|
||||||
|
|
||||||
func (u *StdConn) Rebind() error {
|
func (u *StdConn) Rebind() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -125,13 +218,27 @@ func (u *StdConn) LocalAddr() (netip.AddrPort, error) {
|
|||||||
func (u *StdConn) ListenOut(r EncReader) {
|
func (u *StdConn) ListenOut(r EncReader) {
|
||||||
var ip netip.Addr
|
var ip netip.Addr
|
||||||
|
|
||||||
msgs, buffers, names := u.PrepareRawMessages(u.batch)
|
msgs, buffers, names, controls := u.PrepareRawMessages(u.batch)
|
||||||
read := u.ReadMulti
|
read := u.ReadMulti
|
||||||
if u.batch == 1 {
|
if u.batch == 1 {
|
||||||
read = u.ReadSingle
|
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 {
|
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)
|
n, err := read(msgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||||
@@ -139,13 +246,36 @@ func (u *StdConn) ListenOut(r EncReader) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
// Its ok to skip the ok check here, the slicing is the only error that can occur and it will panic
|
// Extract source address
|
||||||
if u.isV4 {
|
if u.isV4 {
|
||||||
ip, _ = netip.AddrFromSlice(names[i][4:8])
|
ip, _ = netip.AddrFromSlice(names[i][4:8])
|
||||||
} else {
|
} else {
|
||||||
ip, _ = netip.AddrFromSlice(names[i][8:24])
|
ip, _ = netip.AddrFromSlice(names[i][8:24])
|
||||||
}
|
}
|
||||||
r(netip.AddrPortFrom(ip.Unmap(), binary.BigEndian.Uint16(names[i][2:4])), buffers[i][:msgs[i].Len])
|
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])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,6 +443,226 @@ func (u *StdConn) Close() error {
|
|||||||
return syscall.Close(u.sysFd)
|
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() {
|
func NewUDPStatsEmitter(udpConns []Conn) func() {
|
||||||
// Check if our kernel supports SO_MEMINFO before registering the gauges
|
// Check if our kernel supports SO_MEMINFO before registering the gauges
|
||||||
var udpGauges [][unix.SK_MEMINFO_VARS]metrics.Gauge
|
var udpGauges [][unix.SK_MEMINFO_VARS]metrics.Gauge
|
||||||
|
|||||||
+43
-3
@@ -30,13 +30,26 @@ type rawMessage struct {
|
|||||||
Len uint32
|
Len uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
|
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte, [][]byte) {
|
||||||
msgs := make([]rawMessage, n)
|
msgs := make([]rawMessage, n)
|
||||||
buffers := make([][]byte, n)
|
buffers := make([][]byte, n)
|
||||||
names := 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 {
|
for i := range msgs {
|
||||||
buffers[i] = make([]byte, MTU)
|
buffers[i] = make([]byte, bufSize)
|
||||||
names[i] = make([]byte, unix.SizeofSockaddrInet6)
|
names[i] = make([]byte, unix.SizeofSockaddrInet6)
|
||||||
|
|
||||||
vs := []iovec{
|
vs := []iovec{
|
||||||
@@ -48,7 +61,34 @@ func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
|
|||||||
|
|
||||||
msgs[i].Hdr.Name = &names[i][0]
|
msgs[i].Hdr.Name = &names[i][0]
|
||||||
msgs[i].Hdr.Namelen = uint32(len(names[i]))
|
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
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-3
@@ -33,13 +33,26 @@ type rawMessage struct {
|
|||||||
Pad0 [4]byte
|
Pad0 [4]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
|
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte, [][]byte) {
|
||||||
msgs := make([]rawMessage, n)
|
msgs := make([]rawMessage, n)
|
||||||
buffers := make([][]byte, n)
|
buffers := make([][]byte, n)
|
||||||
names := 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 {
|
for i := range msgs {
|
||||||
buffers[i] = make([]byte, MTU)
|
buffers[i] = make([]byte, bufSize)
|
||||||
names[i] = make([]byte, unix.SizeofSockaddrInet6)
|
names[i] = make([]byte, unix.SizeofSockaddrInet6)
|
||||||
|
|
||||||
vs := []iovec{
|
vs := []iovec{
|
||||||
@@ -51,7 +64,34 @@ func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
|
|||||||
|
|
||||||
msgs[i].Hdr.Name = &names[i][0]
|
msgs[i].Hdr.Name = &names[i][0]
|
||||||
msgs[i].Hdr.Namelen = uint32(len(names[i]))
|
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
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-10
@@ -14,6 +14,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@@ -66,7 +67,7 @@ func NewRIOListener(l *logrus.Logger, addr netip.Addr, port int) (*RIOConn, erro
|
|||||||
|
|
||||||
u := &RIOConn{l: l}
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("bind: %w", err)
|
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
|
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
|
var err error
|
||||||
u.sock, err = winrio.Socket(windows.AF_INET6, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
|
u.sock, err = winrio.Socket(windows.AF_INET6, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("winrio.Socket error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable v4 for this socket
|
// Enable v4 for this socket
|
||||||
@@ -100,35 +101,40 @@ func (u *RIOConn) bind(sa windows.Sockaddr) error {
|
|||||||
size := uint32(unsafe.Sizeof(flag))
|
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)
|
err = syscall.WSAIoctl(syscall.Handle(u.sock), syscall.SIO_UDP_CONNRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0)
|
||||||
if err != nil {
|
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
|
ret = 0
|
||||||
flag = 0
|
flag = 0
|
||||||
size = uint32(unsafe.Sizeof(flag))
|
size = uint32(unsafe.Sizeof(flag))
|
||||||
SIO_UDP_NETRESET := uint32(syscall.IOC_IN | syscall.IOC_VENDOR | 15)
|
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)
|
err = syscall.WSAIoctl(syscall.Handle(u.sock), SIO_UDP_NETRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0)
|
||||||
if err != nil {
|
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()
|
err = u.rx.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("error rx.Open(): %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.tx.Open()
|
err = u.tx.Open()
|
||||||
if err != nil {
|
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)
|
u.rq, err = winrio.CreateRequestQueue(u.sock, packetsPerRing, 1, packetsPerRing, 1, u.rx.cq, u.tx.cq, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("error CreateRequestQueue: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = windows.Bind(u.sock, sa)
|
err = windows.Bind(u.sock, sa)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("error windows.Bind(): %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -137,15 +143,22 @@ func (u *RIOConn) bind(sa windows.Sockaddr) error {
|
|||||||
func (u *RIOConn) ListenOut(r EncReader) {
|
func (u *RIOConn) ListenOut(r EncReader) {
|
||||||
buffer := make([]byte, MTU)
|
buffer := make([]byte, MTU)
|
||||||
|
|
||||||
|
var lastRecvErr time.Time
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Just read one packet at a time
|
// Just read one packet at a time
|
||||||
n, rua, err := u.receive(buffer)
|
n, rua, err := u.receive(buffer)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, net.ErrClosed) {
|
if errors.Is(err, net.ErrClosed) {
|
||||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||||
return
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,12 +332,29 @@ func (u *RIOConn) SupportsMultipleReaders() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *RIOConn) SupportsGSO() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RIOConn) SupportsGRO() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (u *RIOConn) Rebind() error {
|
func (u *RIOConn) Rebind() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *RIOConn) ReloadConfig(*config.C) {}
|
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 {
|
func (u *RIOConn) Close() error {
|
||||||
if !u.isOpen.CompareAndSwap(true, false) {
|
if !u.isOpen.CompareAndSwap(true, false) {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -131,6 +131,14 @@ func (u *TesterConn) SupportsMultipleReaders() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *TesterConn) SupportsGSO() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TesterConn) SupportsGRO() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (u *TesterConn) Rebind() error {
|
func (u *TesterConn) Rebind() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -142,3 +150,12 @@ func (u *TesterConn) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user