mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 18:57:00 +02:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9253f36a3c | |||
| c9a695c2bf | |||
| 2c6f81c224 | |||
| ad37749c5e | |||
| a0f8cb2098 | |||
| d18d1aea67 | |||
| f5ff534671 | |||
| 2ea8a72d5c | |||
| 663232e1fc | |||
| 2f48529e8b | |||
| f3e1ad64cd | |||
| 1d8112a329 | |||
| 31eea0cc94 | |||
| dbba4a4c77 | |||
| 194fde45da | |||
| f46b83f2c4 |
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
name: Build Linux/BSD All
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
mv build/*.tar.gz release
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-latest
|
||||
path: release
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
name: Build Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
mv dist\windows\wintun build\dist\windows\
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-latest
|
||||
path: build
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
HAS_SIGNING_CREDS: ${{ secrets.AC_USERNAME != '' }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
|
||||
- name: Import certificates
|
||||
if: env.HAS_SIGNING_CREDS == 'true'
|
||||
uses: Apple-Actions/import-codesign-certs@v6
|
||||
uses: Apple-Actions/import-codesign-certs@v5
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
|
||||
p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: darwin-latest
|
||||
path: ./release/*
|
||||
@@ -124,11 +124,11 @@ jobs:
|
||||
# be overwritten
|
||||
- name: Checkout code
|
||||
if: ${{ env.HAS_DOCKER_CREDS == 'true' }}
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download artifacts
|
||||
if: ${{ env.HAS_DOCKER_CREDS == 'true' }}
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: linux-latest
|
||||
path: artifacts
|
||||
@@ -160,10 +160,10 @@ jobs:
|
||||
needs: [build-linux, build-darwin, build-windows]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
run: make vet
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
version: v2.5
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: Build test mobile
|
||||
run: make build-test-mobile
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: e2e packet flow linux-latest
|
||||
path: e2e/mermaid/linux-latest
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
os: [windows-latest, macos-latest]
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
run: make vet
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
version: v2.5
|
||||
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
- name: End 2 end
|
||||
run: make e2evv
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: e2e packet flow ${{ matrix.os }}
|
||||
path: e2e/mermaid/${{ matrix.os }}
|
||||
|
||||
+3
-127
@@ -7,129 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.10.3] - 2026-02-06
|
||||
|
||||
### Security
|
||||
|
||||
- Fix an issue where blocklist bypass is possible when using curve P256 since the signature can have 2 valid representations.
|
||||
Both fingerprint representations will be tested against the blocklist.
|
||||
Any newly issued P256 based certificates will have their signature clamped to the low-s form.
|
||||
Nebula will assert the low-s signature form when validating certificates in a future version. [GHSA-69x3-g4r3-p962](https://github.com/slackhq/nebula/security/advisories/GHSA-69x3-g4r3-p962)
|
||||
|
||||
### Changed
|
||||
|
||||
- Improve error reporting if nebula fails to start due to a tun device naming issue. (#1588)
|
||||
|
||||
## [1.10.2] - 2026-01-21
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix panic when using `use_system_route_table` that was introduced in v1.10.1. (#1580)
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix some typos in comments. (#1582)
|
||||
- Dependency updates. (#1581)
|
||||
|
||||
## [1.10.1] - 2026-01-16
|
||||
|
||||
See the [v1.10.1](https://github.com/slackhq/nebula/milestone/26?closed=1) milestone for a complete list of changes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a bug where an unsafe route derived from the system route table could be lost on a config reload. (#1573)
|
||||
- Fix the PEM banner for ECDSA P256 public keys. (#1552)
|
||||
- Fix a regression on Windows from 1.9.x where nebula could fall back to a less performant UDP listener if
|
||||
non-critical ioctls failed. (#1568)
|
||||
- Fix a bug in handshake processing when a peer sends an unexpected public key. (#1566)
|
||||
|
||||
### Added
|
||||
|
||||
- Add a config option to control accepting `recv_error` packets which defaults to `always`. (#1569)
|
||||
|
||||
### Changed
|
||||
|
||||
- Various dependency updates. (#1541, #1549, #1550, #1557, #1558, #1560, #1561, #1570, #1571)
|
||||
|
||||
## [1.10.0] - 2025-12-04
|
||||
|
||||
See the [v1.10.0](https://github.com/slackhq/nebula/milestone/16?closed=1) milestone for a complete list of changes.
|
||||
|
||||
### 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
|
||||
- `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
|
||||
`local_cidr` field. This is almost always the intended behavior. This flag is
|
||||
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)
|
||||
deprecated and will be removed in a future release.
|
||||
|
||||
## [1.9.4] - 2024-09-09
|
||||
|
||||
@@ -788,14 +671,7 @@ created.)
|
||||
|
||||
- Initial public release.
|
||||
|
||||
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.3...HEAD
|
||||
[1.10.3]: https://github.com/slackhq/nebula/releases/tag/v1.10.3
|
||||
[1.10.2]: https://github.com/slackhq/nebula/releases/tag/v1.10.2
|
||||
[1.10.1]: https://github.com/slackhq/nebula/releases/tag/v1.10.1
|
||||
[1.10.0]: https://github.com/slackhq/nebula/releases/tag/v1.10.0
|
||||
[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
|
||||
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.9.4...HEAD
|
||||
[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.2]: https://github.com/slackhq/nebula/releases/tag/v1.9.2
|
||||
|
||||
@@ -5,17 +5,19 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// TODO: Pretty sure this is just all sorts of racy now, we need it to be atomic
|
||||
type Bits struct {
|
||||
length uint64
|
||||
current uint64
|
||||
bits []bool
|
||||
firstSeen bool
|
||||
lostCounter metrics.Counter
|
||||
dupeCounter metrics.Counter
|
||||
outOfWindowCounter metrics.Counter
|
||||
}
|
||||
|
||||
func NewBits(bits uint64) *Bits {
|
||||
b := &Bits{
|
||||
return &Bits{
|
||||
length: bits,
|
||||
bits: make([]bool, bits, bits),
|
||||
current: 0,
|
||||
@@ -23,37 +25,34 @@ func NewBits(bits uint64) *Bits {
|
||||
dupeCounter: metrics.GetOrRegisterCounter("network.packets.duplicate", nil),
|
||||
outOfWindowCounter: metrics.GetOrRegisterCounter("network.packets.out_of_window", nil),
|
||||
}
|
||||
|
||||
// There is no counter value 0, mark it to avoid counting a lost packet later.
|
||||
b.bits[0] = true
|
||||
b.current = 0
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bits) Check(l *logrus.Logger, i uint64) bool {
|
||||
func (b *Bits) Check(l logrus.FieldLogger, i uint64) bool {
|
||||
// If i is the next number, return true.
|
||||
if i > b.current {
|
||||
if i > b.current || (i == 0 && b.firstSeen == false && b.current < b.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
// If i is within the window, check if it's been set already.
|
||||
if i > b.current-b.length || i < b.length && b.current < b.length {
|
||||
// If i is within the window, check if it's been set already. The first window will fail this check
|
||||
if i > b.current-b.length {
|
||||
return !b.bits[i%b.length]
|
||||
}
|
||||
|
||||
// If i is within the first window
|
||||
if i < b.length {
|
||||
return !b.bits[i%b.length]
|
||||
}
|
||||
|
||||
// Not within the window
|
||||
if l.Level >= logrus.DebugLevel {
|
||||
l.Debugf("rejected a packet (top) %d %d\n", b.current, i)
|
||||
}
|
||||
l.Error("rejected a packet (top) %d %d\n", b.current, i)
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Bits) Update(l *logrus.Logger, i uint64) bool {
|
||||
// If i is the next number, return true and update current.
|
||||
if i == b.current+1 {
|
||||
// Check if the oldest bit was lost since we are shifting the window by 1 and occupying it with this counter
|
||||
// The very first window can only be tracked as lost once we are on the 2nd window or greater
|
||||
if b.bits[i%b.length] == false && i > b.length {
|
||||
// Report missed packets, we can only understand what was missed after the first window has been gone through
|
||||
if i > b.length && b.bits[i%b.length] == false {
|
||||
b.lostCounter.Inc(1)
|
||||
}
|
||||
b.bits[i%b.length] = true
|
||||
@@ -61,32 +60,61 @@ func (b *Bits) Update(l *logrus.Logger, i uint64) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// If i is a jump, adjust the window, record lost, update current, and return true
|
||||
if i > b.current {
|
||||
lost := int64(0)
|
||||
// Zero out the bits between the current and the new counter value, limited by the window size,
|
||||
// since the window is shifting
|
||||
for n := b.current + 1; n <= min(i, b.current+b.length); n++ {
|
||||
if b.bits[n%b.length] == false && n > b.length {
|
||||
lost++
|
||||
}
|
||||
// If i packet is greater than current but less than the maximum length of our bitmap,
|
||||
// flip everything in between to false and move ahead.
|
||||
if i > b.current && i < b.current+b.length {
|
||||
// In between current and i need to be zero'd to allow those packets to come in later
|
||||
for n := b.current + 1; n < i; n++ {
|
||||
b.bits[n%b.length] = false
|
||||
}
|
||||
|
||||
// Only record any skipped packets as a result of the window moving further than the window length
|
||||
// Any loss within the new window will be accounted for in future calls
|
||||
lost += max(0, int64(i-b.current-b.length))
|
||||
b.bits[i%b.length] = true
|
||||
b.current = i
|
||||
//l.Debugf("missed %d packets between %d and %d\n", i-b.current, i, b.current)
|
||||
return true
|
||||
}
|
||||
|
||||
// If i is greater than the delta between current and the total length of our bitmap,
|
||||
// just flip everything in the map and move ahead.
|
||||
if i >= b.current+b.length {
|
||||
// The current window loss will be accounted for later, only record the jump as loss up until then
|
||||
lost := maxInt64(0, int64(i-b.current-b.length))
|
||||
//TODO: explain this
|
||||
if b.current == 0 {
|
||||
lost++
|
||||
}
|
||||
|
||||
for n := range b.bits {
|
||||
// Don't want to count the first window as a loss
|
||||
//TODO: this is likely wrong, we are wanting to track only the bit slots that we aren't going to track anymore and this is marking everything as missed
|
||||
//if b.bits[n] == false {
|
||||
// lost++
|
||||
//}
|
||||
b.bits[n] = false
|
||||
}
|
||||
|
||||
b.lostCounter.Inc(lost)
|
||||
|
||||
if l.Level >= logrus.DebugLevel {
|
||||
l.WithField("receiveWindow", m{"accepted": true, "currentCounter": b.current, "incomingCounter": i, "reason": "window shifting"}).
|
||||
Debug("Receive window")
|
||||
}
|
||||
b.bits[i%b.length] = true
|
||||
b.current = i
|
||||
return true
|
||||
}
|
||||
|
||||
// If i is within the current window but below the current counter,
|
||||
// Check to see if it's a duplicate
|
||||
if i > b.current-b.length || i < b.length && b.current < b.length {
|
||||
if b.current == i || b.bits[i%b.length] == true {
|
||||
// Allow for the 0 packet to come in within the first window
|
||||
if i == 0 && b.firstSeen == false && b.current < b.length {
|
||||
b.firstSeen = true
|
||||
b.bits[i%b.length] = true
|
||||
return true
|
||||
}
|
||||
|
||||
// If i is within the window of current minus length (the total pat window size),
|
||||
// allow it and flip to true but to NOT change current. We also have to account for the first window
|
||||
if ((b.current >= b.length && i > b.current-b.length) || (b.current < b.length && i < b.length)) && i <= b.current {
|
||||
if b.current == i {
|
||||
if l.Level >= logrus.DebugLevel {
|
||||
l.WithField("receiveWindow", m{"accepted": false, "currentCounter": b.current, "incomingCounter": i, "reason": "duplicate"}).
|
||||
Debug("Receive window")
|
||||
@@ -95,8 +123,18 @@ func (b *Bits) Update(l *logrus.Logger, i uint64) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.bits[i%b.length] == true {
|
||||
if l.Level >= logrus.DebugLevel {
|
||||
l.WithField("receiveWindow", m{"accepted": false, "currentCounter": b.current, "incomingCounter": i, "reason": "old duplicate"}).
|
||||
Debug("Receive window")
|
||||
}
|
||||
b.dupeCounter.Inc(1)
|
||||
return false
|
||||
}
|
||||
|
||||
b.bits[i%b.length] = true
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
// In all other cases, fail and don't change current.
|
||||
@@ -110,3 +148,11 @@ func (b *Bits) Update(l *logrus.Logger, i uint64) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func maxInt64(a, b int64) int64 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
+23
-86
@@ -15,41 +15,48 @@ func TestBits(t *testing.T) {
|
||||
assert.Len(t, b.bits, 10)
|
||||
|
||||
// This is initialized to zero - receive one. This should work.
|
||||
|
||||
assert.True(t, b.Check(l, 1))
|
||||
assert.True(t, b.Update(l, 1))
|
||||
u := b.Update(l, 1)
|
||||
assert.True(t, u)
|
||||
assert.EqualValues(t, 1, b.current)
|
||||
g := []bool{true, true, false, false, false, false, false, false, false, false}
|
||||
g := []bool{false, true, false, false, false, false, false, false, false, false}
|
||||
assert.Equal(t, g, b.bits)
|
||||
|
||||
// Receive two
|
||||
assert.True(t, b.Check(l, 2))
|
||||
assert.True(t, b.Update(l, 2))
|
||||
u = b.Update(l, 2)
|
||||
assert.True(t, u)
|
||||
assert.EqualValues(t, 2, b.current)
|
||||
g = []bool{true, true, true, false, false, false, false, false, false, false}
|
||||
g = []bool{false, true, true, false, false, false, false, false, false, false}
|
||||
assert.Equal(t, g, b.bits)
|
||||
|
||||
// Receive two again - it will fail
|
||||
assert.False(t, b.Check(l, 2))
|
||||
assert.False(t, b.Update(l, 2))
|
||||
u = b.Update(l, 2)
|
||||
assert.False(t, u)
|
||||
assert.EqualValues(t, 2, b.current)
|
||||
|
||||
// Jump ahead to 15, which should clear everything and set the 6th element
|
||||
assert.True(t, b.Check(l, 15))
|
||||
assert.True(t, b.Update(l, 15))
|
||||
u = b.Update(l, 15)
|
||||
assert.True(t, u)
|
||||
assert.EqualValues(t, 15, b.current)
|
||||
g = []bool{false, false, false, false, false, true, false, false, false, false}
|
||||
assert.Equal(t, g, b.bits)
|
||||
|
||||
// Mark 14, which is allowed because it is in the window
|
||||
assert.True(t, b.Check(l, 14))
|
||||
assert.True(t, b.Update(l, 14))
|
||||
u = b.Update(l, 14)
|
||||
assert.True(t, u)
|
||||
assert.EqualValues(t, 15, b.current)
|
||||
g = []bool{false, false, false, false, true, true, false, false, false, false}
|
||||
assert.Equal(t, g, b.bits)
|
||||
|
||||
// Mark 5, which is not allowed because it is not in the window
|
||||
assert.False(t, b.Check(l, 5))
|
||||
assert.False(t, b.Update(l, 5))
|
||||
u = b.Update(l, 5)
|
||||
assert.False(t, u)
|
||||
assert.EqualValues(t, 15, b.current)
|
||||
g = []bool{false, false, false, false, true, true, false, false, false, false}
|
||||
assert.Equal(t, g, b.bits)
|
||||
@@ -62,29 +69,10 @@ func TestBits(t *testing.T) {
|
||||
|
||||
// Walk through a few windows in order
|
||||
b = NewBits(10)
|
||||
for i := uint64(1); i <= 100; i++ {
|
||||
for i := uint64(0); i <= 100; i++ {
|
||||
assert.True(t, b.Check(l, i), "Error while checking %v", i)
|
||||
assert.True(t, b.Update(l, i), "Error while updating %v", i)
|
||||
}
|
||||
|
||||
assert.False(t, b.Check(l, 1), "Out of window check")
|
||||
}
|
||||
|
||||
func TestBitsLargeJumps(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
b := NewBits(10)
|
||||
b.lostCounter.Clear()
|
||||
|
||||
b = NewBits(10)
|
||||
b.lostCounter.Clear()
|
||||
assert.True(t, b.Update(l, 55)) // We saw packet 55 and can still track 45,46,47,48,49,50,51,52,53,54
|
||||
assert.Equal(t, int64(45), b.lostCounter.Count())
|
||||
|
||||
assert.True(t, b.Update(l, 100)) // We saw packet 55 and 100 and can still track 90,91,92,93,94,95,96,97,98,99
|
||||
assert.Equal(t, int64(89), b.lostCounter.Count())
|
||||
|
||||
assert.True(t, b.Update(l, 200)) // We saw packet 55, 100, and 200 and can still track 190,191,192,193,194,195,196,197,198,199
|
||||
assert.Equal(t, int64(188), b.lostCounter.Count())
|
||||
}
|
||||
|
||||
func TestBitsDupeCounter(t *testing.T) {
|
||||
@@ -136,7 +124,8 @@ func TestBitsOutOfWindowCounter(t *testing.T) {
|
||||
assert.False(t, b.Update(l, 0))
|
||||
assert.Equal(t, int64(1), b.outOfWindowCounter.Count())
|
||||
|
||||
assert.Equal(t, int64(19), b.lostCounter.Count()) // packet 0 wasn't lost
|
||||
//tODO: make sure lostcounter doesn't increase in orderly increment
|
||||
assert.Equal(t, int64(20), b.lostCounter.Count())
|
||||
assert.Equal(t, int64(0), b.dupeCounter.Count())
|
||||
assert.Equal(t, int64(1), b.outOfWindowCounter.Count())
|
||||
}
|
||||
@@ -148,6 +137,8 @@ func TestBitsLostCounter(t *testing.T) {
|
||||
b.dupeCounter.Clear()
|
||||
b.outOfWindowCounter.Clear()
|
||||
|
||||
//assert.True(t, b.Update(0))
|
||||
assert.True(t, b.Update(l, 0))
|
||||
assert.True(t, b.Update(l, 20))
|
||||
assert.True(t, b.Update(l, 21))
|
||||
assert.True(t, b.Update(l, 22))
|
||||
@@ -158,7 +149,7 @@ func TestBitsLostCounter(t *testing.T) {
|
||||
assert.True(t, b.Update(l, 27))
|
||||
assert.True(t, b.Update(l, 28))
|
||||
assert.True(t, b.Update(l, 29))
|
||||
assert.Equal(t, int64(19), b.lostCounter.Count()) // packet 0 wasn't lost
|
||||
assert.Equal(t, int64(20), b.lostCounter.Count())
|
||||
assert.Equal(t, int64(0), b.dupeCounter.Count())
|
||||
assert.Equal(t, int64(0), b.outOfWindowCounter.Count())
|
||||
|
||||
@@ -167,6 +158,8 @@ func TestBitsLostCounter(t *testing.T) {
|
||||
b.dupeCounter.Clear()
|
||||
b.outOfWindowCounter.Clear()
|
||||
|
||||
assert.True(t, b.Update(l, 0))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 9))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
// 10 will set 0 index, 0 was already set, no lost packets
|
||||
@@ -221,62 +214,6 @@ func TestBitsLostCounter(t *testing.T) {
|
||||
assert.Equal(t, int64(0), b.outOfWindowCounter.Count())
|
||||
}
|
||||
|
||||
func TestBitsLostCounterIssue1(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
b := NewBits(10)
|
||||
b.lostCounter.Clear()
|
||||
b.dupeCounter.Clear()
|
||||
b.outOfWindowCounter.Clear()
|
||||
|
||||
assert.True(t, b.Update(l, 4))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 1))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 9))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 2))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 3))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 5))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 6))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 7))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
// assert.True(t, b.Update(l, 8))
|
||||
assert.True(t, b.Update(l, 10))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 11))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
|
||||
assert.True(t, b.Update(l, 14))
|
||||
assert.Equal(t, int64(0), b.lostCounter.Count())
|
||||
// Issue seems to be here, we reset missing packet 8 to false here and don't increment the lost counter
|
||||
assert.True(t, b.Update(l, 19))
|
||||
assert.Equal(t, int64(1), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 12))
|
||||
assert.Equal(t, int64(1), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 13))
|
||||
assert.Equal(t, int64(1), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 15))
|
||||
assert.Equal(t, int64(1), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 16))
|
||||
assert.Equal(t, int64(1), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 17))
|
||||
assert.Equal(t, int64(1), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 18))
|
||||
assert.Equal(t, int64(1), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 20))
|
||||
assert.Equal(t, int64(1), b.lostCounter.Count())
|
||||
assert.True(t, b.Update(l, 21))
|
||||
|
||||
// We missed packet 8 above
|
||||
assert.Equal(t, int64(1), b.lostCounter.Count())
|
||||
assert.Equal(t, int64(0), b.dupeCounter.Count())
|
||||
assert.Equal(t, int64(0), b.outOfWindowCounter.Count())
|
||||
}
|
||||
|
||||
func BenchmarkBits(b *testing.B) {
|
||||
z := NewBits(10)
|
||||
for n := 0; n < b.N; n++ {
|
||||
|
||||
@@ -141,23 +141,10 @@ func (ncp *CAPool) VerifyCertificate(now time.Time, c Certificate) (*CachedCerti
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pre nebula v1.10.3 could generate signatures in either high or low s form and validation
|
||||
// of signatures allowed for either. Nebula v1.10.3 and beyond clamps signature generation to low-s form
|
||||
// but validation still allows for either. Since a change in the signature bytes affects the fingerprint, we
|
||||
// need to test both forms until such a time comes that we enforce low-s form on signature validation.
|
||||
fp2, err := CalculateAlternateFingerprint(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not calculate alternate fingerprint to verify: %w", err)
|
||||
}
|
||||
if fp2 != "" && ncp.IsBlocklisted(fp2) {
|
||||
return nil, ErrBlockListed
|
||||
}
|
||||
|
||||
cc := CachedCertificate{
|
||||
Certificate: c,
|
||||
InvertedGroups: make(map[string]struct{}),
|
||||
Fingerprint: fp,
|
||||
fingerprint2: fp2,
|
||||
signerFingerprint: signer.Fingerprint,
|
||||
}
|
||||
|
||||
@@ -171,11 +158,6 @@ func (ncp *CAPool) VerifyCertificate(now time.Time, c Certificate) (*CachedCerti
|
||||
// VerifyCachedCertificate is the same as VerifyCertificate other than it operates on a pre-verified structure and
|
||||
// is a cheaper operation to perform as a result.
|
||||
func (ncp *CAPool) VerifyCachedCertificate(now time.Time, c *CachedCertificate) error {
|
||||
// Check any available alternate fingerprint forms for this certificate, re P256 high-s/low-s
|
||||
if c.fingerprint2 != "" && ncp.IsBlocklisted(c.fingerprint2) {
|
||||
return ErrBlockListed
|
||||
}
|
||||
|
||||
_, err := ncp.verify(c.Certificate, now, c.Fingerprint, c.signerFingerprint)
|
||||
return err
|
||||
}
|
||||
|
||||
+4
-43
@@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/slackhq/nebula/cert/p256"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -171,15 +170,6 @@ func TestCertificateV1_VerifyP256(t *testing.T) {
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
// Create a copy of the cert and swap to the alternate form for the signature
|
||||
nc := c.Copy()
|
||||
b, err := p256.Swap(c.Signature())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, nc.(*certificateV1).setSignature(b))
|
||||
|
||||
_, err = caPool.VerifyCertificate(time.Now(), nc)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
caPool.ResetCertBlocklist()
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
require.NoError(t, err)
|
||||
@@ -197,7 +187,7 @@ func TestCertificateV1_VerifyP256(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
caPool = NewCAPool()
|
||||
b, err = caPool.AddCAFromPEM(caPem)
|
||||
b, err := caPool.AddCAFromPEM(caPem)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, b)
|
||||
|
||||
@@ -206,17 +196,7 @@ func TestCertificateV1_VerifyP256(t *testing.T) {
|
||||
})
|
||||
|
||||
c, _, _, _ = NewTestCert(Version1, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
|
||||
cc, err := caPool.VerifyCertificate(time.Now(), c)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Reset the blocklist and block the alternate form fingerprint
|
||||
caPool.ResetCertBlocklist()
|
||||
caPool.BlocklistFingerprint(cc.fingerprint2)
|
||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
caPool.ResetCertBlocklist()
|
||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -414,15 +394,6 @@ func TestCertificateV2_VerifyP256(t *testing.T) {
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
// Create a copy of the cert and swap to the alternate form for the signature
|
||||
nc := c.Copy()
|
||||
b, err := p256.Swap(c.Signature())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, nc.(*certificateV2).setSignature(b))
|
||||
|
||||
_, err = caPool.VerifyCertificate(time.Now(), nc)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
caPool.ResetCertBlocklist()
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
require.NoError(t, err)
|
||||
@@ -440,7 +411,7 @@ func TestCertificateV2_VerifyP256(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
caPool = NewCAPool()
|
||||
b, err = caPool.AddCAFromPEM(caPem)
|
||||
b, err := caPool.AddCAFromPEM(caPem)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, b)
|
||||
|
||||
@@ -449,17 +420,7 @@ func TestCertificateV2_VerifyP256(t *testing.T) {
|
||||
})
|
||||
|
||||
c, _, _, _ = NewTestCert(Version2, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
|
||||
cc, err := caPool.VerifyCertificate(time.Now(), c)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Reset the blocklist and block the alternate form fingerprint
|
||||
caPool.ResetCertBlocklist()
|
||||
caPool.BlocklistFingerprint(cc.fingerprint2)
|
||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
||||
require.EqualError(t, err, "certificate is in the block list")
|
||||
|
||||
caPool.ResetCertBlocklist()
|
||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/slackhq/nebula/cert/p256"
|
||||
)
|
||||
|
||||
type Version uint8
|
||||
@@ -112,9 +110,6 @@ type CachedCertificate struct {
|
||||
InvertedGroups map[string]struct{}
|
||||
Fingerprint string
|
||||
signerFingerprint string
|
||||
|
||||
// A place to store a 2nd fingerprint if the certificate could have one, such as with P256
|
||||
fingerprint2 string
|
||||
}
|
||||
|
||||
func (cc *CachedCertificate) String() string {
|
||||
@@ -124,7 +119,6 @@ func (cc *CachedCertificate) String() string {
|
||||
// Recombine will attempt to unmarshal a certificate received in a handshake.
|
||||
// Handshakes save space by placing the peers public key in a different part of the packet, we have to
|
||||
// reassemble the actual certificate structure with that in mind.
|
||||
// Implementations MUST assert the public key is not in the raw certificate bytes if the passed in public key is not empty.
|
||||
func Recombine(v Version, rawCertBytes, publicKey []byte, curve Curve) (Certificate, error) {
|
||||
if publicKey == nil {
|
||||
return nil, ErrNoPeerStaticKey
|
||||
@@ -157,31 +151,3 @@ func Recombine(v Version, rawCertBytes, publicKey []byte, curve Curve) (Certific
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// CalculateAlternateFingerprint calculates a 2nd fingerprint representation for P256 certificates
|
||||
// CAPool blocklist testing through `VerifyCertificate` and `VerifyCachedCertificate` automatically performs this step.
|
||||
func CalculateAlternateFingerprint(c Certificate) (string, error) {
|
||||
if c.Curve() != Curve_P256 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
nc := c.Copy()
|
||||
b, err := p256.Swap(nc.Signature())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch v := nc.(type) {
|
||||
case *certificateV1:
|
||||
err = v.setSignature(b)
|
||||
case *certificateV2:
|
||||
err = v.setSignature(b)
|
||||
default:
|
||||
return "", ErrUnknownVersion
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return nc.Fingerprint()
|
||||
}
|
||||
|
||||
+4
-11
@@ -426,7 +426,7 @@ func unmarshalCertificateV1(b []byte, publicKey []byte) (*certificateV1, error)
|
||||
unsafeNetworks: make([]netip.Prefix, len(rc.Details.Subnets)/2),
|
||||
notBefore: time.Unix(rc.Details.NotBefore, 0),
|
||||
notAfter: time.Unix(rc.Details.NotAfter, 0),
|
||||
publicKey: nil,
|
||||
publicKey: make([]byte, len(rc.Details.PublicKey)),
|
||||
isCA: rc.Details.IsCA,
|
||||
curve: rc.Details.Curve,
|
||||
},
|
||||
@@ -437,19 +437,12 @@ func unmarshalCertificateV1(b []byte, publicKey []byte) (*certificateV1, error)
|
||||
copy(nc.details.groups, rc.Details.Groups)
|
||||
nc.details.issuer = hex.EncodeToString(rc.Details.Issuer)
|
||||
|
||||
// If a public key is passed in as an argument, the certificate pubkey must be empty
|
||||
// and the passed-in pubkey copied into the cert.
|
||||
if len(publicKey) > 0 {
|
||||
if len(rc.Details.PublicKey) != 0 {
|
||||
return nil, ErrCertPubkeyPresent
|
||||
}
|
||||
nc.details.publicKey = make([]byte, len(publicKey))
|
||||
copy(nc.details.publicKey, publicKey)
|
||||
} else {
|
||||
nc.details.publicKey = make([]byte, len(rc.Details.PublicKey))
|
||||
copy(nc.details.publicKey, rc.Details.PublicKey)
|
||||
nc.details.publicKey = publicKey
|
||||
}
|
||||
|
||||
copy(nc.details.publicKey, rc.Details.PublicKey)
|
||||
|
||||
var ip netip.Addr
|
||||
for i, rawIp := range rc.Details.Ips {
|
||||
if i%2 == 0 {
|
||||
|
||||
+1
-63
@@ -62,62 +62,6 @@ func TestCertificateV1_Marshal(t *testing.T) {
|
||||
assert.Equal(t, nc.Groups(), nc2.Groups())
|
||||
}
|
||||
|
||||
func TestCertificateV1_Unmarshal(t *testing.T) {
|
||||
t.Parallel()
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
after := time.Now().Add(time.Second * 60).Round(time.Second)
|
||||
pubKey := []byte("1234567890abcedfghij1234567890ab")
|
||||
invalidPubkey := []byte("00000000000000000000000000000000")
|
||||
|
||||
nc := certificateV1{
|
||||
details: detailsV1{
|
||||
name: "testing",
|
||||
networks: []netip.Prefix{
|
||||
mustParsePrefixUnmapped("10.1.1.1/24"),
|
||||
mustParsePrefixUnmapped("10.1.1.2/16"),
|
||||
},
|
||||
unsafeNetworks: []netip.Prefix{
|
||||
mustParsePrefixUnmapped("9.1.1.2/24"),
|
||||
mustParsePrefixUnmapped("9.1.1.3/16"),
|
||||
},
|
||||
groups: []string{"test-group1", "test-group2", "test-group3"},
|
||||
notBefore: before,
|
||||
notAfter: after,
|
||||
publicKey: pubKey,
|
||||
isCA: false,
|
||||
issuer: "1234567890abcedfghij1234567890ab",
|
||||
},
|
||||
signature: []byte("1234567890abcedfghij1234567890ab"),
|
||||
}
|
||||
|
||||
// This certificate has a pubkey included
|
||||
certWithPubkey, err := nc.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
// This certificate is missing the pubkey section
|
||||
certWithoutPubkey, err := nc.MarshalForHandshakes()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Cert has no pubkey and no pubkey passed in must fail to validate
|
||||
isNil, err := unmarshalCertificateV1(certWithoutPubkey, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
// Cert has different pubkey than one passed in must fail
|
||||
isNil, err = unmarshalCertificateV1(certWithPubkey, invalidPubkey)
|
||||
require.Nil(t, isNil)
|
||||
require.Error(t, err)
|
||||
|
||||
// Cert has pubkey and no pubkey argument works ok
|
||||
_, err = unmarshalCertificateV1(certWithPubkey, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Cert has no pubkey and valid, correctly signed pubkey passed in
|
||||
nc2, err := unmarshalCertificateV1(certWithoutPubkey, pubKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, pubKey, nc2.PublicKey())
|
||||
}
|
||||
|
||||
func TestCertificateV1_PublicKeyPem(t *testing.T) {
|
||||
t.Parallel()
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
@@ -155,19 +99,13 @@ func TestCertificateV1_PublicKeyPem(t *testing.T) {
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAA=
|
||||
-----END NEBULA P256 PUBLIC KEY-----
|
||||
`)
|
||||
|
||||
pubP256KeyPemCA := []byte(`-----BEGIN NEBULA ECDSA P256 PUBLIC KEY-----
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAA=
|
||||
-----END NEBULA ECDSA P256 PUBLIC KEY-----
|
||||
`)
|
||||
pubP256Key, _, _, err := UnmarshalPublicKeyFromPEM(pubP256KeyPem)
|
||||
require.NoError(t, err)
|
||||
nc.details.curve = Curve_P256
|
||||
nc.details.publicKey = pubP256Key
|
||||
assert.Equal(t, Curve_P256, nc.Curve())
|
||||
assert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPemCA))
|
||||
assert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPem))
|
||||
assert.True(t, nc.IsCA())
|
||||
|
||||
nc.details.isCA = false
|
||||
|
||||
+1
-7
@@ -592,13 +592,7 @@ func unmarshalCertificateV2(b []byte, publicKey []byte, curve Curve) (*certifica
|
||||
// Maybe grab the public key
|
||||
var rawPublicKey cryptobyte.String
|
||||
if len(publicKey) > 0 {
|
||||
// If a public key is passed in, then the handshake certificate must
|
||||
// not have a public key present
|
||||
if input.PeekASN1Tag(TagCertPublicKey) {
|
||||
return nil, ErrCertPubkeyPresent
|
||||
}
|
||||
rawPublicKey = make(cryptobyte.String, len(publicKey))
|
||||
copy(rawPublicKey, publicKey)
|
||||
rawPublicKey = publicKey
|
||||
} else if !input.ReadOptionalASN1(&rawPublicKey, nil, TagCertPublicKey) {
|
||||
return nil, ErrBadFormat
|
||||
}
|
||||
|
||||
+1
-60
@@ -76,58 +76,6 @@ func TestCertificateV2_Marshal(t *testing.T) {
|
||||
assert.Equal(t, nc.Groups(), nc2.Groups())
|
||||
}
|
||||
|
||||
func TestCertificateV2_Unmarshal(t *testing.T) {
|
||||
t.Parallel()
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
after := time.Now().Add(time.Second * 60).Round(time.Second)
|
||||
pubKey := []byte("1234567890abcedfghij1234567890ab")
|
||||
|
||||
nc := certificateV2{
|
||||
details: detailsV2{
|
||||
name: "testing",
|
||||
networks: []netip.Prefix{
|
||||
mustParsePrefixUnmapped("10.1.1.2/16"),
|
||||
mustParsePrefixUnmapped("10.1.1.1/24"),
|
||||
},
|
||||
unsafeNetworks: []netip.Prefix{
|
||||
mustParsePrefixUnmapped("9.1.1.3/16"),
|
||||
mustParsePrefixUnmapped("9.1.1.2/24"),
|
||||
},
|
||||
groups: []string{"test-group1", "test-group2", "test-group3"},
|
||||
notBefore: before,
|
||||
notAfter: after,
|
||||
isCA: false,
|
||||
issuer: "1234567890abcdef1234567890abcdef",
|
||||
},
|
||||
signature: []byte("1234567890abcdef1234567890abcdef"),
|
||||
publicKey: pubKey,
|
||||
}
|
||||
|
||||
db, err := nc.details.Marshal()
|
||||
require.NoError(t, err)
|
||||
nc.rawDetails = db
|
||||
|
||||
certWithPubkey, err := nc.Marshal()
|
||||
require.NoError(t, err)
|
||||
//t.Log("Cert size:", len(b))
|
||||
certWithoutPubkey, err := nc.MarshalForHandshakes()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Cert must not have a pubkey if one is passed in as an argument
|
||||
_, err = unmarshalCertificateV2(certWithPubkey, pubKey, Curve_CURVE25519)
|
||||
require.ErrorIs(t, err, ErrCertPubkeyPresent)
|
||||
|
||||
// Certs must have pubkeys
|
||||
_, err = unmarshalCertificateV2(certWithoutPubkey, nil, Curve_CURVE25519)
|
||||
require.ErrorIs(t, err, ErrBadFormat)
|
||||
|
||||
// Ensure proper unmarshal if a pubkey is passed in
|
||||
nc2, err := unmarshalCertificateV2(certWithoutPubkey, pubKey, Curve_CURVE25519)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, nc.PublicKey(), nc2.PublicKey())
|
||||
}
|
||||
|
||||
func TestCertificateV2_PublicKeyPem(t *testing.T) {
|
||||
t.Parallel()
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
@@ -166,19 +114,12 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAA=
|
||||
-----END NEBULA P256 PUBLIC KEY-----
|
||||
`)
|
||||
|
||||
pubP256KeyPemCA := []byte(`-----BEGIN NEBULA ECDSA P256 PUBLIC KEY-----
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAA=
|
||||
-----END NEBULA ECDSA P256 PUBLIC KEY-----
|
||||
`)
|
||||
|
||||
pubP256Key, _, _, err := UnmarshalPublicKeyFromPEM(pubP256KeyPem)
|
||||
require.NoError(t, err)
|
||||
nc.curve = Curve_P256
|
||||
nc.publicKey = pubP256Key
|
||||
assert.Equal(t, Curve_P256, nc.Curve())
|
||||
assert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPemCA))
|
||||
assert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPem))
|
||||
assert.True(t, nc.IsCA())
|
||||
|
||||
nc.details.isCA = false
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ qrlJ69wer3ZUHFXA
|
||||
assert.Nil(t, k)
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
|
||||
// Fail due to invalid PEM format, because
|
||||
// Fail due to ivalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest)
|
||||
require.EqualError(t, err, "input did not contain a valid PEM encoded block")
|
||||
|
||||
@@ -21,7 +21,6 @@ var (
|
||||
ErrPrivateKeyEncrypted = errors.New("private key must be decrypted")
|
||||
ErrCaNotFound = errors.New("could not find ca for the certificate")
|
||||
ErrUnknownVersion = errors.New("certificate version unrecognized")
|
||||
ErrCertPubkeyPresent = errors.New("certificate has unexpected pubkey present")
|
||||
|
||||
ErrInvalidPEMBlock = errors.New("input did not contain a valid PEM encoded block")
|
||||
ErrInvalidPEMCertificateBanner = errors.New("bytes did not contain a proper certificate banner")
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
package p256
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"filippo.io/bigmod"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
"golang.org/x/crypto/cryptobyte/asn1"
|
||||
)
|
||||
|
||||
var halfN = new(big.Int).Rsh(elliptic.P256().Params().N, 1)
|
||||
var nMod *bigmod.Modulus
|
||||
|
||||
func init() {
|
||||
n, err := bigmod.NewModulus(elliptic.P256().Params().N.Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
nMod = n
|
||||
}
|
||||
|
||||
func IsNormalized(sig []byte) (bool, error) {
|
||||
r, s, err := parseSignature(sig)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return checkLowS(r, s), nil
|
||||
}
|
||||
|
||||
func checkLowS(_, s []byte) bool {
|
||||
bigS := new(big.Int).SetBytes(s)
|
||||
// Check if S <= (N/2), because we want to include the midpoint in the set of low-s
|
||||
return bigS.Cmp(halfN) <= 0
|
||||
}
|
||||
|
||||
func swap(r, s []byte) ([]byte, []byte, error) {
|
||||
var err error
|
||||
bigS, err := bigmod.NewNat().SetBytes(s, nMod)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sNormalized := nMod.Nat().Sub(bigS, nMod)
|
||||
|
||||
return r, sNormalized.Bytes(nMod), nil
|
||||
}
|
||||
|
||||
func Normalize(sig []byte) ([]byte, error) {
|
||||
r, s, err := parseSignature(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if checkLowS(r, s) {
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
newR, newS, err := swap(r, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return encodeSignature(newR, newS)
|
||||
}
|
||||
|
||||
// Swap will change sig between its current form to the opposite high or low form.
|
||||
func Swap(sig []byte) ([]byte, error) {
|
||||
r, s, err := parseSignature(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newR, newS, err := swap(r, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return encodeSignature(newR, newS)
|
||||
}
|
||||
|
||||
// parseSignature taken exactly from crypto/ecdsa/ecdsa.go
|
||||
func parseSignature(sig []byte) (r, s []byte, err error) {
|
||||
var inner cryptobyte.String
|
||||
input := cryptobyte.String(sig)
|
||||
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
|
||||
!input.Empty() ||
|
||||
!inner.ReadASN1Integer(&r) ||
|
||||
!inner.ReadASN1Integer(&s) ||
|
||||
!inner.Empty() {
|
||||
return nil, nil, errors.New("invalid ASN.1")
|
||||
}
|
||||
return r, s, nil
|
||||
}
|
||||
|
||||
func encodeSignature(r, s []byte) ([]byte, error) {
|
||||
var b cryptobyte.Builder
|
||||
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
|
||||
addASN1IntBytes(b, r)
|
||||
addASN1IntBytes(b, s)
|
||||
})
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// addASN1IntBytes encodes in ASN.1 a positive integer represented as
|
||||
// a big-endian byte slice with zero or more leading zeroes.
|
||||
func addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) {
|
||||
for len(bytes) > 0 && bytes[0] == 0 {
|
||||
bytes = bytes[1:]
|
||||
}
|
||||
if len(bytes) == 0 {
|
||||
b.SetError(errors.New("invalid integer"))
|
||||
return
|
||||
}
|
||||
b.AddASN1(asn1.INTEGER, func(c *cryptobyte.Builder) {
|
||||
if bytes[0]&0x80 != 0 {
|
||||
c.AddUint8(0)
|
||||
}
|
||||
c.AddBytes(bytes)
|
||||
})
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package p256
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFlipping(t *testing.T) {
|
||||
priv, err1 := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err1)
|
||||
|
||||
out, err := ecdsa.SignASN1(rand.Reader, priv, []byte("big chungus"))
|
||||
require.NoError(t, err)
|
||||
|
||||
r, s, err := parseSignature(out)
|
||||
require.NoError(t, err)
|
||||
|
||||
r, s1, err := swap(r, s)
|
||||
require.NoError(t, err)
|
||||
r, s2, err := swap(r, s1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, s, s2)
|
||||
require.NotEqual(t, s, s1)
|
||||
}
|
||||
+98
-1
@@ -1,8 +1,10 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
@@ -86,7 +88,7 @@ func MarshalSigningPublicKeyToPEM(curve Curve, b []byte) []byte {
|
||||
case Curve_CURVE25519:
|
||||
return pem.EncodeToMemory(&pem.Block{Type: Ed25519PublicKeyBanner, Bytes: b})
|
||||
case Curve_P256:
|
||||
return pem.EncodeToMemory(&pem.Block{Type: ECDSAP256PublicKeyBanner, Bytes: b})
|
||||
return pem.EncodeToMemory(&pem.Block{Type: P256PublicKeyBanner, Bytes: b})
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -138,6 +140,101 @@ func MarshalSigningPrivateKeyToPEM(curve Curve, b []byte) []byte {
|
||||
}
|
||||
}
|
||||
|
||||
// Backward compatibility functions for older API
|
||||
func MarshalX25519PublicKey(b []byte) []byte {
|
||||
return MarshalPublicKeyToPEM(Curve_CURVE25519, b)
|
||||
}
|
||||
|
||||
func MarshalX25519PrivateKey(b []byte) []byte {
|
||||
return MarshalPrivateKeyToPEM(Curve_CURVE25519, b)
|
||||
}
|
||||
|
||||
func MarshalPublicKey(curve Curve, b []byte) []byte {
|
||||
return MarshalPublicKeyToPEM(curve, b)
|
||||
}
|
||||
|
||||
func MarshalPrivateKey(curve Curve, b []byte) []byte {
|
||||
return MarshalPrivateKeyToPEM(curve, b)
|
||||
}
|
||||
|
||||
// NebulaCertificate is a compatibility wrapper for the old API
|
||||
type NebulaCertificate struct {
|
||||
Details NebulaCertificateDetails
|
||||
Signature []byte
|
||||
cert Certificate
|
||||
}
|
||||
|
||||
// NebulaCertificateDetails is a compatibility wrapper for certificate details
|
||||
type NebulaCertificateDetails struct {
|
||||
Name string
|
||||
NotBefore time.Time
|
||||
NotAfter time.Time
|
||||
PublicKey []byte
|
||||
IsCA bool
|
||||
Issuer []byte
|
||||
Curve Curve
|
||||
}
|
||||
|
||||
// UnmarshalNebulaCertificateFromPEM provides backward compatibility with the old API
|
||||
func UnmarshalNebulaCertificateFromPEM(b []byte) (*NebulaCertificate, []byte, error) {
|
||||
c, rest, err := UnmarshalCertificateFromPEM(b)
|
||||
if err != nil {
|
||||
return nil, rest, err
|
||||
}
|
||||
|
||||
issuerBytes, err := func() ([]byte, error) {
|
||||
issuer := c.Issuer()
|
||||
if issuer == "" {
|
||||
return nil, nil
|
||||
}
|
||||
decoded, err := hex.DecodeString(issuer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode issuer fingerprint: %w", err)
|
||||
}
|
||||
return decoded, nil
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, rest, err
|
||||
}
|
||||
|
||||
pubKey := c.PublicKey()
|
||||
if pubKey != nil {
|
||||
pubKey = append([]byte(nil), pubKey...)
|
||||
}
|
||||
|
||||
sig := c.Signature()
|
||||
if sig != nil {
|
||||
sig = append([]byte(nil), sig...)
|
||||
}
|
||||
|
||||
return &NebulaCertificate{
|
||||
Details: NebulaCertificateDetails{
|
||||
Name: c.Name(),
|
||||
NotBefore: c.NotBefore(),
|
||||
NotAfter: c.NotAfter(),
|
||||
PublicKey: pubKey,
|
||||
IsCA: c.IsCA(),
|
||||
Issuer: issuerBytes,
|
||||
Curve: c.Curve(),
|
||||
},
|
||||
Signature: sig,
|
||||
cert: c,
|
||||
}, rest, nil
|
||||
}
|
||||
|
||||
// IssuerString returns the issuer in hex format for compatibility
|
||||
func (n *NebulaCertificate) IssuerString() string {
|
||||
if n.Details.Issuer == nil {
|
||||
return ""
|
||||
}
|
||||
return hex.EncodeToString(n.Details.Issuer)
|
||||
}
|
||||
|
||||
// Certificate returns the underlying certificate (read-only)
|
||||
func (n *NebulaCertificate) Certificate() Certificate {
|
||||
return n.cert
|
||||
}
|
||||
|
||||
// UnmarshalPrivateKeyFromPEM will try to unmarshal the first pem block in a byte array, returning any non
|
||||
// consumed data or an error on failure
|
||||
func UnmarshalPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
|
||||
|
||||
+5
-5
@@ -44,7 +44,7 @@ bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
require.EqualError(t, err, "bytes did not contain a proper certificate banner")
|
||||
|
||||
// Fail due to invalid PEM format, because
|
||||
// Fail due to ivalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
cert, rest, err = UnmarshalCertificateFromPEM(rest)
|
||||
assert.Nil(t, cert)
|
||||
@@ -106,7 +106,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
require.EqualError(t, err, "bytes did not contain a proper Ed25519/ECDSA private key banner")
|
||||
|
||||
// Fail due to invalid PEM format, because
|
||||
// Fail due to ivalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
k, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)
|
||||
assert.Nil(t, k)
|
||||
@@ -168,7 +168,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
require.EqualError(t, err, "bytes did not contain a proper private key banner")
|
||||
|
||||
// Fail due to invalid PEM format, because
|
||||
// Fail due to ivalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
k, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)
|
||||
assert.Nil(t, k)
|
||||
@@ -221,7 +221,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
require.EqualError(t, err, "bytes did not contain a proper public key banner")
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
|
||||
// Fail due to invalid PEM format, because
|
||||
// Fail due to ivalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
|
||||
assert.Nil(t, k)
|
||||
@@ -299,7 +299,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
require.EqualError(t, err, "bytes did not contain a proper public key banner")
|
||||
assert.Equal(t, rest, invalidPem)
|
||||
|
||||
// Fail due to invalid PEM format, because
|
||||
// Fail due to ivalid PEM format, because
|
||||
// it's missing the requisite pre-encapsulation boundary.
|
||||
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
|
||||
assert.Nil(t, k)
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/slackhq/nebula/cert/p256"
|
||||
)
|
||||
|
||||
// TBSCertificate represents a certificate intended to be signed.
|
||||
@@ -128,13 +126,6 @@ func (t *TBSCertificate) SignWith(signer Certificate, curve Curve, sp SignerLamb
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if curve == Curve_P256 {
|
||||
sig, err = p256.Normalize(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = c.setSignature(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/slackhq/nebula/cert/p256"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -90,48 +89,3 @@ func TestCertificateV1_SignP256(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, uc)
|
||||
}
|
||||
|
||||
func TestCertificate_SignP256_AlwaysNormalized(t *testing.T) {
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
after := time.Now().Add(time.Second * 60).Round(time.Second)
|
||||
pubKey := []byte("01234567890abcedfghij1234567890ab1234567890abcedfghij1234567890ab")
|
||||
|
||||
tbs := TBSCertificate{
|
||||
Version: Version1,
|
||||
Name: "testing",
|
||||
Networks: []netip.Prefix{
|
||||
mustParsePrefixUnmapped("10.1.1.1/24"),
|
||||
mustParsePrefixUnmapped("10.1.1.2/16"),
|
||||
},
|
||||
UnsafeNetworks: []netip.Prefix{
|
||||
mustParsePrefixUnmapped("9.1.1.2/24"),
|
||||
mustParsePrefixUnmapped("9.1.1.3/16"),
|
||||
},
|
||||
Groups: []string{"test-group1", "test-group2", "test-group3"},
|
||||
NotBefore: before,
|
||||
NotAfter: after,
|
||||
PublicKey: pubKey,
|
||||
IsCA: true,
|
||||
Curve: Curve_P256,
|
||||
}
|
||||
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
pub := elliptic.Marshal(elliptic.P256(), priv.PublicKey.X, priv.PublicKey.Y)
|
||||
rawPriv := priv.D.FillBytes(make([]byte, 32))
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
if i&1 == 1 {
|
||||
tbs.Version = Version1
|
||||
} else {
|
||||
tbs.Version = Version2
|
||||
}
|
||||
c, err := tbs.Sign(nil, Curve_P256, rawPriv)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, c)
|
||||
assert.True(t, c.CheckSignature(pub))
|
||||
normie, err := p256.IsNormalized(c.Signature())
|
||||
require.NoError(t, err)
|
||||
assert.True(t, normie)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,33 +114,6 @@ func NewTestCert(v cert.Version, curve cert.Curve, ca cert.Certificate, key []by
|
||||
return c, pub, cert.MarshalPrivateKeyToPEM(curve, priv), pem
|
||||
}
|
||||
|
||||
func NewTestCertDifferentVersion(c cert.Certificate, v cert.Version, ca cert.Certificate, key []byte) (cert.Certificate, []byte) {
|
||||
nc := &cert.TBSCertificate{
|
||||
Version: v,
|
||||
Curve: c.Curve(),
|
||||
Name: c.Name(),
|
||||
Networks: c.Networks(),
|
||||
UnsafeNetworks: c.UnsafeNetworks(),
|
||||
Groups: c.Groups(),
|
||||
NotBefore: time.Unix(c.NotBefore().Unix(), 0),
|
||||
NotAfter: time.Unix(c.NotAfter().Unix(), 0),
|
||||
PublicKey: c.PublicKey(),
|
||||
IsCA: false,
|
||||
}
|
||||
|
||||
c, err := nc.Sign(ca, ca.Curve(), key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pem, err := c.MarshalPEM()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return c, pem
|
||||
}
|
||||
|
||||
func X25519Keypair() ([]byte, []byte) {
|
||||
privkey := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, privkey); err != nil {
|
||||
|
||||
+16
-19
@@ -173,26 +173,23 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
|
||||
|
||||
var passphrase []byte
|
||||
if !isP11 && *cf.encryption {
|
||||
passphrase = []byte(os.Getenv("NEBULA_CA_PASSPHRASE"))
|
||||
for i := 0; i < 5; i++ {
|
||||
out.Write([]byte("Enter passphrase: "))
|
||||
passphrase, err = pr.ReadPassword()
|
||||
|
||||
if err == ErrNoTerminal {
|
||||
return fmt.Errorf("out-key must be encrypted interactively")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error reading passphrase: %s", err)
|
||||
}
|
||||
|
||||
if len(passphrase) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(passphrase) == 0 {
|
||||
for i := 0; i < 5; i++ {
|
||||
out.Write([]byte("Enter passphrase: "))
|
||||
passphrase, err = pr.ReadPassword()
|
||||
|
||||
if err == ErrNoTerminal {
|
||||
return fmt.Errorf("out-key must be encrypted interactively")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error reading passphrase: %s", err)
|
||||
}
|
||||
|
||||
if len(passphrase) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(passphrase) == 0 {
|
||||
return fmt.Errorf("no passphrase specified, remove -encrypt flag to write out-key in plaintext")
|
||||
}
|
||||
return fmt.Errorf("no passphrase specified, remove -encrypt flag to write out-key in plaintext")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -171,17 +171,6 @@ func Test_ca(t *testing.T) {
|
||||
assert.Equal(t, pwPromptOb, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// test encrypted key with passphrase environment variable
|
||||
os.Remove(keyF.Name())
|
||||
os.Remove(crtF.Name())
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-version", "1", "-encrypt", "-name", "test", "-duration", "100m", "-groups", "1,2,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
||||
os.Setenv("NEBULA_CA_PASSPHRASE", string(passphrase))
|
||||
require.NoError(t, ca(args, ob, eb, testpw))
|
||||
assert.Empty(t, eb.String())
|
||||
os.Setenv("NEBULA_CA_PASSPHRASE", "")
|
||||
|
||||
// read encrypted key file and verify default params
|
||||
rb, _ = os.ReadFile(keyF.Name())
|
||||
k, _ := pem.Decode(rb)
|
||||
@@ -200,7 +189,7 @@ func Test_ca(t *testing.T) {
|
||||
assert.Empty(t, b)
|
||||
assert.Len(t, lKey, 64)
|
||||
|
||||
// test when reading password results in an error
|
||||
// test when reading passsword results in an error
|
||||
os.Remove(keyF.Name())
|
||||
os.Remove(crtF.Name())
|
||||
ob.Reset()
|
||||
|
||||
@@ -5,28 +5,10 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A version string that can be set with
|
||||
//
|
||||
// -ldflags "-X main.Build=SOMEVERSION"
|
||||
//
|
||||
// at compile-time.
|
||||
var Build string
|
||||
|
||||
func init() {
|
||||
if Build == "" {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
Build = strings.TrimPrefix(info.Main.Version, "v")
|
||||
}
|
||||
}
|
||||
|
||||
type helpError struct {
|
||||
s string
|
||||
}
|
||||
|
||||
+28
-34
@@ -43,7 +43,7 @@ type signFlags struct {
|
||||
func newSignFlags() *signFlags {
|
||||
sf := signFlags{set: flag.NewFlagSet("sign", flag.ContinueOnError)}
|
||||
sf.set.Usage = func() {}
|
||||
sf.version = sf.set.Uint("version", 0, "Optional: version of the certificate format to use. The default is to match the version of the signing CA")
|
||||
sf.version = sf.set.Uint("version", 0, "Optional: version of the certificate format to use, the default is to create both v1 and v2 certificates.")
|
||||
sf.caKeyPath = sf.set.String("ca-key", "ca.key", "Optional: path to the signing CA key")
|
||||
sf.caCertPath = sf.set.String("ca-crt", "ca.crt", "Optional: path to the signing CA cert")
|
||||
sf.name = sf.set.String("name", "", "Required: name of the cert, usually a hostname")
|
||||
@@ -116,28 +116,26 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
|
||||
// naively attempt to decode the private key as though it is not encrypted
|
||||
caKey, _, curve, err = cert.UnmarshalSigningPrivateKeyFromPEM(rawCAKey)
|
||||
if errors.Is(err, cert.ErrPrivateKeyEncrypted) {
|
||||
// ask for a passphrase until we get one
|
||||
var passphrase []byte
|
||||
passphrase = []byte(os.Getenv("NEBULA_CA_PASSPHRASE"))
|
||||
if len(passphrase) == 0 {
|
||||
// ask for a passphrase until we get one
|
||||
for i := 0; i < 5; i++ {
|
||||
out.Write([]byte("Enter passphrase: "))
|
||||
passphrase, err = pr.ReadPassword()
|
||||
for i := 0; i < 5; i++ {
|
||||
out.Write([]byte("Enter passphrase: "))
|
||||
passphrase, err = pr.ReadPassword()
|
||||
|
||||
if errors.Is(err, ErrNoTerminal) {
|
||||
return fmt.Errorf("ca-key is encrypted and must be decrypted interactively")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error reading password: %s", err)
|
||||
}
|
||||
|
||||
if len(passphrase) > 0 {
|
||||
break
|
||||
}
|
||||
if errors.Is(err, ErrNoTerminal) {
|
||||
return fmt.Errorf("ca-key is encrypted and must be decrypted interactively")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error reading password: %s", err)
|
||||
}
|
||||
if len(passphrase) == 0 {
|
||||
return fmt.Errorf("cannot open encrypted ca-key without passphrase")
|
||||
|
||||
if len(passphrase) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(passphrase) == 0 {
|
||||
return fmt.Errorf("cannot open encrypted ca-key without passphrase")
|
||||
}
|
||||
|
||||
curve, caKey, _, err = cert.DecryptAndUnmarshalSigningPrivateKey(passphrase, rawCAKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing encrypted ca-key: %s", err)
|
||||
@@ -167,10 +165,6 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
|
||||
return fmt.Errorf("ca certificate is expired")
|
||||
}
|
||||
|
||||
if version == 0 {
|
||||
version = caCert.Version()
|
||||
}
|
||||
|
||||
// if no duration is given, expire one second before the root expires
|
||||
if *sf.duration <= 0 {
|
||||
*sf.duration = time.Until(caCert.NotAfter()) - time.Second*1
|
||||
@@ -283,19 +277,21 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(*sf.duration)
|
||||
|
||||
switch version {
|
||||
case cert.Version1:
|
||||
// Make sure we have only one ipv4 address
|
||||
if version == 0 || version == cert.Version1 {
|
||||
// Make sure we at least have an ip
|
||||
if len(v4Networks) != 1 {
|
||||
return newHelpErrorf("invalid -networks definition: v1 certificates can only have a single ipv4 address")
|
||||
}
|
||||
|
||||
if len(v6Networks) > 0 {
|
||||
return newHelpErrorf("invalid -networks definition: v1 certificates can only contain ipv4 addresses")
|
||||
}
|
||||
if version == cert.Version1 {
|
||||
// If we are asked to mint a v1 certificate only then we cant just ignore any v6 addresses
|
||||
if len(v6Networks) > 0 {
|
||||
return newHelpErrorf("invalid -networks definition: v1 certificates can only be ipv4")
|
||||
}
|
||||
|
||||
if len(v6UnsafeNetworks) > 0 {
|
||||
return newHelpErrorf("invalid -unsafe-networks definition: v1 certificates can only contain ipv4 addresses")
|
||||
if len(v6UnsafeNetworks) > 0 {
|
||||
return newHelpErrorf("invalid -unsafe-networks definition: v1 certificates can only be ipv4")
|
||||
}
|
||||
}
|
||||
|
||||
t := &cert.TBSCertificate{
|
||||
@@ -325,8 +321,9 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
|
||||
}
|
||||
|
||||
crts = append(crts, nc)
|
||||
}
|
||||
|
||||
case cert.Version2:
|
||||
if version == 0 || version == cert.Version2 {
|
||||
t := &cert.TBSCertificate{
|
||||
Version: cert.Version2,
|
||||
Name: *sf.name,
|
||||
@@ -354,9 +351,6 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
|
||||
}
|
||||
|
||||
crts = append(crts, nc)
|
||||
default:
|
||||
// this should be unreachable
|
||||
return fmt.Errorf("invalid version: %d", version)
|
||||
}
|
||||
|
||||
if !isP11 && *sf.inPubPath == "" {
|
||||
|
||||
@@ -55,7 +55,7 @@ func Test_signHelp(t *testing.T) {
|
||||
" -unsafe-networks string\n"+
|
||||
" \tOptional: comma separated list of ip address and network in CIDR notation. Unsafe networks this cert can route for\n"+
|
||||
" -version uint\n"+
|
||||
" \tOptional: version of the certificate format to use. The default is to match the version of the signing CA\n",
|
||||
" \tOptional: version of the certificate format to use, the default is to create both v1 and v2 certificates.\n",
|
||||
ob.String(),
|
||||
)
|
||||
}
|
||||
@@ -204,7 +204,7 @@ func Test_signCert(t *testing.T) {
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-version", "1", "-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m", "-subnets", "100::100/100"}
|
||||
assertHelpError(t, signCert(args, ob, eb, nopw), "invalid -unsafe-networks definition: v1 certificates can only contain ipv4 addresses")
|
||||
assertHelpError(t, signCert(args, ob, eb, nopw), "invalid -unsafe-networks definition: v1 certificates can only be ipv4")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -379,15 +379,6 @@ func Test_signCert(t *testing.T) {
|
||||
assert.Equal(t, "Enter passphrase: ", ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// test with the proper password in the environment
|
||||
os.Remove(crtF.Name())
|
||||
os.Remove(keyF.Name())
|
||||
args = []string{"-version", "1", "-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
os.Setenv("NEBULA_CA_PASSPHRASE", string(passphrase))
|
||||
require.NoError(t, signCert(args, ob, eb, testpw))
|
||||
assert.Empty(t, eb.String())
|
||||
os.Setenv("NEBULA_CA_PASSPHRASE", "")
|
||||
|
||||
// test with the wrong password
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
@@ -398,17 +389,6 @@ func Test_signCert(t *testing.T) {
|
||||
assert.Equal(t, "Enter passphrase: ", ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// test with the wrong password in environment
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
|
||||
os.Setenv("NEBULA_CA_PASSPHRASE", "invalid password")
|
||||
args = []string{"-version", "1", "-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
require.EqualError(t, signCert(args, ob, eb, nopw), "error while parsing encrypted ca-key: invalid passphrase or corrupt private key")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
os.Setenv("NEBULA_CA_PASSPHRASE", "")
|
||||
|
||||
// test with the user not entering a password
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
|
||||
+10
-15
@@ -4,8 +4,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula"
|
||||
@@ -20,17 +18,6 @@ import (
|
||||
// at compile-time.
|
||||
var Build string
|
||||
|
||||
func init() {
|
||||
if Build == "" {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
Build = strings.TrimPrefix(info.Main.Version, "v")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
serviceFlag := flag.String("service", "", "Control the system service.")
|
||||
configPath := flag.String("config", "", "Path to either a file or directory to load configuration from")
|
||||
@@ -78,8 +65,16 @@ func main() {
|
||||
}
|
||||
|
||||
if !*configTest {
|
||||
ctrl.Start()
|
||||
ctrl.ShutdownBlock()
|
||||
wait, err := ctrl.Start()
|
||||
if err != nil {
|
||||
util.LogWithContextIfNeeded("Error while running", err, l)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
go ctrl.ShutdownBlock()
|
||||
wait()
|
||||
|
||||
l.Info("Goodbye")
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
|
||||
+17
-15
@@ -3,9 +3,10 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula"
|
||||
@@ -20,17 +21,6 @@ import (
|
||||
// at compile-time.
|
||||
var Build string
|
||||
|
||||
func init() {
|
||||
if Build == "" {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
Build = strings.TrimPrefix(info.Main.Version, "v")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
configPath := flag.String("config", "", "Path to either a file or directory to load configuration from")
|
||||
configTest := flag.Bool("test", false, "Test the config and print the end result. Non zero exit indicates a faulty config")
|
||||
@@ -71,10 +61,22 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
|
||||
}()
|
||||
|
||||
if !*configTest {
|
||||
ctrl.Start()
|
||||
wait, err := ctrl.Start()
|
||||
if err != nil {
|
||||
util.LogWithContextIfNeeded("Error while running", err, l)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
go ctrl.ShutdownBlock()
|
||||
notifyReady(l)
|
||||
ctrl.ShutdownBlock()
|
||||
wait()
|
||||
|
||||
l.Info("Goodbye")
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
"dario.cat/mergo"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type C struct {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/slackhq/nebula/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestConfig_Load(t *testing.T) {
|
||||
|
||||
+23
-59
@@ -354,6 +354,7 @@ func (cm *connectionManager) makeTrafficDecision(localIndex uint32, now time.Tim
|
||||
|
||||
if mainHostInfo {
|
||||
decision = tryRehandshake
|
||||
|
||||
} else {
|
||||
if cm.shouldSwapPrimary(hostinfo) {
|
||||
decision = swapPrimary
|
||||
@@ -460,10 +461,6 @@ func (cm *connectionManager) shouldSwapPrimary(current *HostInfo) bool {
|
||||
}
|
||||
|
||||
crt := cm.intf.pki.getCertState().getCertificate(current.ConnectionState.myCert.Version())
|
||||
if crt == nil {
|
||||
//my cert was reloaded away. We should definitely swap from this tunnel
|
||||
return true
|
||||
}
|
||||
// If this tunnel is using the latest certificate then we should swap it to primary for a bit and see if things
|
||||
// settle down.
|
||||
return bytes.Equal(current.ConnectionState.myCert.Signature(), crt.Signature())
|
||||
@@ -478,34 +475,31 @@ func (cm *connectionManager) swapPrimary(current, primary *HostInfo) {
|
||||
cm.hostMap.Unlock()
|
||||
}
|
||||
|
||||
// isInvalidCertificate decides if we should destroy a tunnel.
|
||||
// returns true if pki.disconnect_invalid is true and the certificate is no longer valid.
|
||||
// Blocklisted certificates will skip the pki.disconnect_invalid check and return true.
|
||||
// isInvalidCertificate will check if we should destroy a tunnel if pki.disconnect_invalid is true and
|
||||
// the certificate is no longer valid. Block listed certificates will skip the pki.disconnect_invalid
|
||||
// check and return true.
|
||||
func (cm *connectionManager) isInvalidCertificate(now time.Time, hostinfo *HostInfo) bool {
|
||||
remoteCert := hostinfo.GetCert()
|
||||
if remoteCert == nil {
|
||||
return false //don't tear down tunnels for handshakes in progress
|
||||
return false
|
||||
}
|
||||
|
||||
caPool := cm.intf.pki.GetCAPool()
|
||||
err := caPool.VerifyCachedCertificate(now, remoteCert)
|
||||
if err == nil {
|
||||
return false //cert is still valid! yay!
|
||||
} else if err == cert.ErrBlockListed { //avoiding errors.Is for speed
|
||||
// Block listed certificates should always be disconnected
|
||||
hostinfo.logger(cm.l).WithError(err).
|
||||
WithField("fingerprint", remoteCert.Fingerprint).
|
||||
Info("Remote certificate is blocked, tearing down the tunnel")
|
||||
return true
|
||||
} else if cm.intf.disconnectInvalid.Load() {
|
||||
hostinfo.logger(cm.l).WithError(err).
|
||||
WithField("fingerprint", remoteCert.Fingerprint).
|
||||
Info("Remote certificate is no longer valid, tearing down the tunnel")
|
||||
return true
|
||||
} else {
|
||||
//if we reach here, the cert is no longer valid, but we're configured to keep tunnels from now-invalid certs open
|
||||
return false
|
||||
}
|
||||
|
||||
if !cm.intf.disconnectInvalid.Load() && err != cert.ErrBlockListed {
|
||||
// Block listed certificates should always be disconnected
|
||||
return false
|
||||
}
|
||||
|
||||
hostinfo.logger(cm.l).WithError(err).
|
||||
WithField("fingerprint", remoteCert.Fingerprint).
|
||||
Info("Remote certificate is no longer valid, tearing down the tunnel")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (cm *connectionManager) sendPunch(hostinfo *HostInfo) {
|
||||
@@ -536,45 +530,15 @@ func (cm *connectionManager) sendPunch(hostinfo *HostInfo) {
|
||||
func (cm *connectionManager) tryRehandshake(hostinfo *HostInfo) {
|
||||
cs := cm.intf.pki.getCertState()
|
||||
curCrt := hostinfo.ConnectionState.myCert
|
||||
curCrtVersion := curCrt.Version()
|
||||
myCrt := cs.getCertificate(curCrtVersion)
|
||||
if myCrt == nil {
|
||||
cm.l.WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||
WithField("version", curCrtVersion).
|
||||
WithField("reason", "local certificate removed").
|
||||
Info("Re-handshaking with remote")
|
||||
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)
|
||||
myCrt := cs.getCertificate(curCrt.Version())
|
||||
if curCrt.Version() >= cs.initiatingVersion && bytes.Equal(curCrt.Signature(), myCrt.Signature()) == true {
|
||||
// The current tunnel is using the latest certificate and version, no need to rehandshake.
|
||||
return
|
||||
}
|
||||
peerCrt := hostinfo.ConnectionState.peerCert
|
||||
if peerCrt != nil && curCrtVersion < peerCrt.Certificate.Version() {
|
||||
// if our certificate version is less than theirs, and we have a matching version available, rehandshake?
|
||||
if cs.getCertificate(peerCrt.Certificate.Version()) != nil {
|
||||
cm.l.WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||
WithField("version", curCrtVersion).
|
||||
WithField("peerVersion", peerCrt.Certificate.Version()).
|
||||
WithField("reason", "local certificate version lower than peer, attempting to correct").
|
||||
Info("Re-handshaking with remote")
|
||||
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], func(hh *HandshakeHostInfo) {
|
||||
hh.initiatingVersionOverride = peerCrt.Certificate.Version()
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(curCrt.Signature(), myCrt.Signature()) {
|
||||
cm.l.WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||
WithField("reason", "local certificate is not current").
|
||||
Info("Re-handshaking with remote")
|
||||
|
||||
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)
|
||||
return
|
||||
}
|
||||
if curCrtVersion < cs.initiatingVersion {
|
||||
cm.l.WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||
WithField("reason", "current cert version < pki.initiatingVersion").
|
||||
Info("Re-handshaking with remote")
|
||||
cm.l.WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||
WithField("reason", "local certificate is not current").
|
||||
Info("Re-handshaking with remote")
|
||||
|
||||
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)
|
||||
return
|
||||
}
|
||||
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)
|
||||
}
|
||||
|
||||
+9
-2
@@ -13,7 +13,9 @@ import (
|
||||
"github.com/slackhq/nebula/noiseutil"
|
||||
)
|
||||
|
||||
const ReplayWindow = 1024
|
||||
// TODO: In a 5Gbps test, 1024 is not sufficient. With a 1400 MTU this is about 1.4Gbps of window, assuming full packets.
|
||||
// 4092 should be sufficient for 5Gbps
|
||||
const ReplayWindow = 4096
|
||||
|
||||
type ConnectionState struct {
|
||||
eKey *NebulaCipherState
|
||||
@@ -50,6 +52,11 @@ func NewConnectionState(l *logrus.Logger, cs *CertState, crt cert.Certificate, i
|
||||
}
|
||||
|
||||
static := noise.DHKey{Private: cs.privateKey, Public: crt.PublicKey()}
|
||||
|
||||
b := NewBits(ReplayWindow)
|
||||
// Clear out bit 0, we never transmit it, and we don't want it showing as packet loss
|
||||
b.Update(l, 0)
|
||||
|
||||
hs, err := noise.NewHandshakeState(noise.Config{
|
||||
CipherSuite: ncs,
|
||||
Random: rand.Reader,
|
||||
@@ -69,7 +76,7 @@ func NewConnectionState(l *logrus.Logger, cs *CertState, crt cert.Certificate, i
|
||||
ci := &ConnectionState{
|
||||
H: hs,
|
||||
initiator: initiator,
|
||||
window: NewBits(ReplayWindow),
|
||||
window: b,
|
||||
myCert: crt,
|
||||
}
|
||||
// always start the counter from 2, as packet 1 and packet 2 are handshake packets.
|
||||
|
||||
+50
-6
@@ -2,9 +2,11 @@ package nebula
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -13,6 +15,16 @@ import (
|
||||
"github.com/slackhq/nebula/overlay"
|
||||
)
|
||||
|
||||
type RunState int
|
||||
|
||||
const (
|
||||
Stopped RunState = 0 // The control has yet to be started
|
||||
Started RunState = 1 // The control has been started
|
||||
Stopping RunState = 2 // The control is stopping
|
||||
)
|
||||
|
||||
var ErrAlreadyStarted = errors.New("nebula is already started")
|
||||
|
||||
// Every interaction here needs to take extra care to copy memory and not return or use arguments "as is" when touching
|
||||
// core. This means copying IP objects, slices, de-referencing pointers and taking the actual value, etc
|
||||
|
||||
@@ -26,6 +38,9 @@ type controlHostLister interface {
|
||||
}
|
||||
|
||||
type Control struct {
|
||||
stateLock sync.Mutex
|
||||
state RunState
|
||||
|
||||
f *Interface
|
||||
l *logrus.Logger
|
||||
ctx context.Context
|
||||
@@ -49,10 +64,21 @@ type ControlHostInfo struct {
|
||||
CurrentRelaysThroughMe []netip.Addr `json:"currentRelaysThroughMe"`
|
||||
}
|
||||
|
||||
// Start actually runs nebula, this is a nonblocking call. To block use Control.ShutdownBlock()
|
||||
func (c *Control) Start() {
|
||||
// Start actually runs nebula, this is a nonblocking call.
|
||||
// The returned function can be used to wait for nebula to fully stop.
|
||||
func (c *Control) Start() (func(), error) {
|
||||
c.stateLock.Lock()
|
||||
if c.state != Stopped {
|
||||
c.stateLock.Unlock()
|
||||
return nil, ErrAlreadyStarted
|
||||
}
|
||||
|
||||
// Activate the interface
|
||||
c.f.activate()
|
||||
err := c.f.activate()
|
||||
if err != nil {
|
||||
c.stateLock.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Call all the delayed funcs that waited patiently for the interface to be created.
|
||||
if c.sshStart != nil {
|
||||
@@ -72,15 +98,33 @@ func (c *Control) Start() {
|
||||
}
|
||||
|
||||
// Start reading packets.
|
||||
c.f.run()
|
||||
c.state = Started
|
||||
c.stateLock.Unlock()
|
||||
return c.f.run(c.ctx)
|
||||
}
|
||||
|
||||
func (c *Control) State() RunState {
|
||||
c.stateLock.Lock()
|
||||
defer c.stateLock.Unlock()
|
||||
return c.state
|
||||
}
|
||||
|
||||
func (c *Control) Context() context.Context {
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
// Stop signals nebula to shutdown and close all tunnels, returns after the shutdown is complete
|
||||
// Stop is a non-blocking call that signals nebula to close all tunnels and shut down
|
||||
func (c *Control) Stop() {
|
||||
c.stateLock.Lock()
|
||||
if c.state != Started {
|
||||
c.stateLock.Unlock()
|
||||
// We are stopping or stopped already
|
||||
return
|
||||
}
|
||||
|
||||
c.state = Stopping
|
||||
c.stateLock.Unlock()
|
||||
|
||||
// Stop the handshakeManager (and other services), to prevent new tunnels from
|
||||
// being created while we're shutting them all down.
|
||||
c.cancel()
|
||||
@@ -89,7 +133,7 @@ func (c *Control) Stop() {
|
||||
if err := c.f.Close(); err != nil {
|
||||
c.l.WithError(err).Error("Close interface failed")
|
||||
}
|
||||
c.l.Info("Goodbye")
|
||||
c.state = Stopped
|
||||
}
|
||||
|
||||
// ShutdownBlock will listen for and block on term and interrupt signals, calling Control.Stop() once signalled
|
||||
|
||||
@@ -174,10 +174,6 @@ func (c *Control) GetHostmap() *HostMap {
|
||||
return c.f.hostMap
|
||||
}
|
||||
|
||||
func (c *Control) GetF() *Interface {
|
||||
return c.f
|
||||
}
|
||||
|
||||
func (c *Control) GetCertState() *CertState {
|
||||
return c.f.pki.getCertState()
|
||||
}
|
||||
|
||||
+2
-209
@@ -20,17 +20,16 @@ import (
|
||||
"github.com/slackhq/nebula/udp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
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{})
|
||||
myControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "me", "10.128.0.1/24", nil)
|
||||
myControl, myVpnIpNet, _, _ := 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)
|
||||
|
||||
// Put their info in our lighthouse
|
||||
myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
||||
theirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)
|
||||
|
||||
// Start the servers
|
||||
myControl.Start()
|
||||
@@ -39,9 +38,6 @@ func BenchmarkHotPath(b *testing.B) {
|
||||
r := router.NewR(b, myControl, theirControl)
|
||||
r.CancelFlowLogs()
|
||||
|
||||
assertTunnel(b, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, 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)
|
||||
@@ -51,39 +47,6 @@ func BenchmarkHotPath(b *testing.B) {
|
||||
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) {
|
||||
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)
|
||||
@@ -134,41 +97,6 @@ func TestGoodHandshake(t *testing.T) {
|
||||
theirControl.Stop()
|
||||
}
|
||||
|
||||
func TestGoodHandshakeNoOverlap(t *testing.T) {
|
||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
myControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "me", "10.128.0.1/24", nil)
|
||||
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "them", "2001::69/24", nil) //look ma, cross-stack!
|
||||
|
||||
// Put their info in our lighthouse
|
||||
myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
||||
|
||||
// Start the servers
|
||||
myControl.Start()
|
||||
theirControl.Start()
|
||||
|
||||
empty := []byte{}
|
||||
t.Log("do something to cause a handshake")
|
||||
myControl.GetF().SendMessageToVpnAddr(header.Test, header.MessageNone, theirVpnIpNet[0].Addr(), empty, empty, empty)
|
||||
|
||||
t.Log("Have them consume my stage 0 packet. They have a tunnel now")
|
||||
theirControl.InjectUDPPacket(myControl.GetFromUDP(true))
|
||||
|
||||
t.Log("Get their stage 1 packet")
|
||||
stage1Packet := theirControl.GetFromUDP(true)
|
||||
|
||||
t.Log("Have me consume their stage 1 packet. I have a tunnel now")
|
||||
myControl.InjectUDPPacket(stage1Packet)
|
||||
|
||||
t.Log("Wait until we see a test packet come through to make sure we give the tunnel time to complete")
|
||||
myControl.WaitForType(header.Test, 0, theirControl)
|
||||
|
||||
t.Log("Make sure our host infos are correct")
|
||||
assertHostInfoPair(t, myUdpAddr, theirUdpAddr, myVpnIpNet, theirVpnIpNet, myControl, theirControl)
|
||||
|
||||
myControl.Stop()
|
||||
theirControl.Stop()
|
||||
}
|
||||
|
||||
func TestWrongResponderHandshake(t *testing.T) {
|
||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
|
||||
@@ -536,35 +464,6 @@ func TestRelays(t *testing.T) {
|
||||
r.RenderHostmaps("Final hostmaps", myControl, relayControl, theirControl)
|
||||
}
|
||||
|
||||
func TestRelaysDontCareAboutIps(t *testing.T) {
|
||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
myControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version2, ca, caKey, "me ", "10.128.0.1/24", m{"relay": m{"use_relays": true}})
|
||||
relayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "relay ", "2001::9999/24", m{"relay": m{"am_relay": true}})
|
||||
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version2, 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(t, myControl, relayControl, theirControl)
|
||||
defer r.RenderFlow()
|
||||
|
||||
// Start the servers
|
||||
myControl.Start()
|
||||
relayControl.Start()
|
||||
theirControl.Start()
|
||||
|
||||
t.Log("Trigger a handshake from me to them via the relay")
|
||||
myControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("Hi from me"))
|
||||
|
||||
p := r.RouteForAllUntilTxTun(theirControl)
|
||||
r.Log("Assert the tunnel works")
|
||||
assertUdpPacket(t, []byte("Hi from me"), p, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)
|
||||
r.RenderHostmaps("Final hostmaps", myControl, relayControl, theirControl)
|
||||
}
|
||||
|
||||
func TestReestablishRelays(t *testing.T) {
|
||||
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}})
|
||||
@@ -1328,109 +1227,3 @@ func TestV2NonPrimaryWithLighthouse(t *testing.T) {
|
||||
myControl.Stop()
|
||||
theirControl.Stop()
|
||||
}
|
||||
|
||||
func TestV2NonPrimaryWithOffNetLighthouse(t *testing.T) {
|
||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
lhControl, lhVpnIpNet, lhUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "lh ", "2001::1/64", m{"lighthouse": m{"am_lighthouse": true}})
|
||||
|
||||
o := m{
|
||||
"static_host_map": m{
|
||||
lhVpnIpNet[0].Addr().String(): []string{lhUdpAddr.String()},
|
||||
},
|
||||
"lighthouse": m{
|
||||
"hosts": []string{lhVpnIpNet[0].Addr().String()},
|
||||
"local_allow_list": m{
|
||||
// Try and block our lighthouse updates from using the actual addresses assigned to this computer
|
||||
// If we start discovering addresses the test router doesn't know about then test traffic cant flow
|
||||
"10.0.0.0/24": true,
|
||||
"::/0": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
myControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version2, ca, caKey, "me ", "10.128.0.2/24, ff::2/64", o)
|
||||
theirControl, theirVpnIpNet, _, _ := newSimpleServer(cert.Version2, ca, caKey, "them", "10.128.0.3/24, ff::3/64", o)
|
||||
|
||||
// Build a router so we don't have to reason who gets which packet
|
||||
r := router.NewR(t, lhControl, myControl, theirControl)
|
||||
defer r.RenderFlow()
|
||||
|
||||
// Start the servers
|
||||
lhControl.Start()
|
||||
myControl.Start()
|
||||
theirControl.Start()
|
||||
|
||||
t.Log("Stand up an ipv6 tunnel between me and them")
|
||||
assert.True(t, myVpnIpNet[1].Addr().Is6())
|
||||
assert.True(t, theirVpnIpNet[1].Addr().Is6())
|
||||
assertTunnel(t, myVpnIpNet[1].Addr(), theirVpnIpNet[1].Addr(), myControl, theirControl, r)
|
||||
|
||||
lhControl.Stop()
|
||||
myControl.Stop()
|
||||
theirControl.Stop()
|
||||
}
|
||||
|
||||
func TestGoodHandshakeUnsafeDest(t *testing.T) {
|
||||
unsafePrefix := "192.168.6.0/24"
|
||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServerWithUdpAndUnsafeNetworks(cert.Version2, ca, caKey, "spooky", "10.128.0.2/24", netip.MustParseAddrPort("10.64.0.2:4242"), unsafePrefix, nil)
|
||||
route := m{"route": unsafePrefix, "via": theirVpnIpNet[0].Addr().String()}
|
||||
myCfg := m{
|
||||
"tun": m{
|
||||
"unsafe_routes": []m{route},
|
||||
},
|
||||
}
|
||||
myControl, myVpnIpNet, myUdpAddr, myConfig := newSimpleServer(cert.Version2, ca, caKey, "me", "10.128.0.1/24", myCfg)
|
||||
t.Logf("my config %v", myConfig)
|
||||
// Put their info in our lighthouse
|
||||
myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
||||
|
||||
spookyDest := netip.MustParseAddr("192.168.6.4")
|
||||
|
||||
// Start the servers
|
||||
myControl.Start()
|
||||
theirControl.Start()
|
||||
|
||||
t.Log("Send a udp packet through to begin standing up the tunnel, this should come out the other side")
|
||||
myControl.InjectTunUDPPacket(spookyDest, 80, myVpnIpNet[0].Addr(), 80, []byte("Hi from me"))
|
||||
|
||||
t.Log("Have them consume my stage 0 packet. They have a tunnel now")
|
||||
theirControl.InjectUDPPacket(myControl.GetFromUDP(true))
|
||||
|
||||
t.Log("Get their stage 1 packet so that we can play with it")
|
||||
stage1Packet := theirControl.GetFromUDP(true)
|
||||
|
||||
t.Log("I consume a garbage packet with a proper nebula header for our tunnel")
|
||||
// this should log a statement and get ignored, allowing the real handshake packet to complete the tunnel
|
||||
badPacket := stage1Packet.Copy()
|
||||
badPacket.Data = badPacket.Data[:len(badPacket.Data)-header.Len]
|
||||
myControl.InjectUDPPacket(badPacket)
|
||||
|
||||
t.Log("Have me consume their real stage 1 packet. I have a tunnel now")
|
||||
myControl.InjectUDPPacket(stage1Packet)
|
||||
|
||||
t.Log("Wait until we see my cached packet come through")
|
||||
myControl.WaitForType(1, 0, theirControl)
|
||||
|
||||
t.Log("Make sure our host infos are correct")
|
||||
assertHostInfoPair(t, myUdpAddr, theirUdpAddr, myVpnIpNet, theirVpnIpNet, myControl, theirControl)
|
||||
|
||||
t.Log("Get that cached packet and make sure it looks right")
|
||||
myCachedPacket := theirControl.GetFromTun(true)
|
||||
assertUdpPacket(t, []byte("Hi from me"), myCachedPacket, myVpnIpNet[0].Addr(), spookyDest, 80, 80)
|
||||
|
||||
//reply
|
||||
theirControl.InjectTunUDPPacket(myVpnIpNet[0].Addr(), 80, spookyDest, 80, []byte("Hi from the spookyman"))
|
||||
//wait for reply
|
||||
theirControl.WaitForType(1, 0, myControl)
|
||||
theirCachedPacket := myControl.GetFromTun(true)
|
||||
assertUdpPacket(t, []byte("Hi from the spookyman"), theirCachedPacket, spookyDest, myVpnIpNet[0].Addr(), 80, 80)
|
||||
|
||||
t.Log("Do a bidirectional tunnel test")
|
||||
r := router.NewR(t, myControl, theirControl)
|
||||
defer r.RenderFlow()
|
||||
assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
||||
|
||||
r.RenderHostmaps("Final hostmaps", myControl, theirControl)
|
||||
myControl.Stop()
|
||||
theirControl.Stop()
|
||||
}
|
||||
|
||||
+16
-161
@@ -22,14 +22,15 @@ import (
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/e2e/router"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type m = map[string]any
|
||||
|
||||
// newSimpleServer creates a nebula instance with many assumptions
|
||||
func newSimpleServer(v cert.Version, caCrt cert.Certificate, caKey []byte, name string, sVpnNetworks string, overrides m) (*nebula.Control, []netip.Prefix, netip.AddrPort, *config.C) {
|
||||
l := NewTestLogger()
|
||||
|
||||
var vpnNetworks []netip.Prefix
|
||||
for _, sn := range strings.Split(sVpnNetworks, ",") {
|
||||
vpnIpNet, err := netip.ParsePrefix(strings.TrimSpace(sn))
|
||||
@@ -55,54 +56,7 @@ func newSimpleServer(v cert.Version, caCrt cert.Certificate, caKey []byte, name
|
||||
budpIp[3] = 239
|
||||
udpAddr = netip.AddrPortFrom(netip.AddrFrom16(budpIp), 4242)
|
||||
}
|
||||
return newSimpleServerWithUdp(v, caCrt, caKey, name, sVpnNetworks, udpAddr, overrides)
|
||||
}
|
||||
|
||||
func newSimpleServerWithUdp(v cert.Version, caCrt cert.Certificate, caKey []byte, name string, sVpnNetworks string, udpAddr netip.AddrPort, overrides m) (*nebula.Control, []netip.Prefix, netip.AddrPort, *config.C) {
|
||||
return newSimpleServerWithUdpAndUnsafeNetworks(v, caCrt, caKey, name, sVpnNetworks, udpAddr, "", overrides)
|
||||
}
|
||||
|
||||
func newSimpleServerWithUdpAndUnsafeNetworks(v cert.Version, caCrt cert.Certificate, caKey []byte, name string, sVpnNetworks string, udpAddr netip.AddrPort, sUnsafeNetworks string, overrides m) (*nebula.Control, []netip.Prefix, netip.AddrPort, *config.C) {
|
||||
l := NewTestLogger()
|
||||
|
||||
var vpnNetworks []netip.Prefix
|
||||
for _, sn := range strings.Split(sVpnNetworks, ",") {
|
||||
vpnIpNet, err := netip.ParsePrefix(strings.TrimSpace(sn))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vpnNetworks = append(vpnNetworks, vpnIpNet)
|
||||
}
|
||||
|
||||
if len(vpnNetworks) == 0 {
|
||||
panic("no vpn networks")
|
||||
}
|
||||
|
||||
firewallInbound := []m{{
|
||||
"proto": "any",
|
||||
"port": "any",
|
||||
"host": "any",
|
||||
}}
|
||||
|
||||
var unsafeNetworks []netip.Prefix
|
||||
if sUnsafeNetworks != "" {
|
||||
firewallInbound = []m{{
|
||||
"proto": "any",
|
||||
"port": "any",
|
||||
"host": "any",
|
||||
"local_cidr": "0.0.0.0/0",
|
||||
}}
|
||||
|
||||
for _, sn := range strings.Split(sUnsafeNetworks, ",") {
|
||||
x, err := netip.ParsePrefix(strings.TrimSpace(sn))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
unsafeNetworks = append(unsafeNetworks, x)
|
||||
}
|
||||
}
|
||||
|
||||
_, _, myPrivKey, myPEM := cert_test.NewTestCert(v, cert.Curve_CURVE25519, caCrt, caKey, name, time.Now(), time.Now().Add(5*time.Minute), vpnNetworks, unsafeNetworks, []string{})
|
||||
_, _, myPrivKey, myPEM := cert_test.NewTestCert(v, cert.Curve_CURVE25519, caCrt, caKey, name, time.Now(), time.Now().Add(5*time.Minute), vpnNetworks, nil, []string{})
|
||||
|
||||
caB, err := caCrt.MarshalPEM()
|
||||
if err != nil {
|
||||
@@ -122,7 +76,11 @@ func newSimpleServerWithUdpAndUnsafeNetworks(v cert.Version, caCrt cert.Certific
|
||||
"port": "any",
|
||||
"host": "any",
|
||||
}},
|
||||
"inbound": firewallInbound,
|
||||
"inbound": []m{{
|
||||
"proto": "any",
|
||||
"port": "any",
|
||||
"host": "any",
|
||||
}},
|
||||
},
|
||||
//"handshakes": m{
|
||||
// "try_interval": "1s",
|
||||
@@ -171,109 +129,6 @@ func newSimpleServerWithUdpAndUnsafeNetworks(v cert.Version, caCrt cert.Certific
|
||||
return control, vpnNetworks, udpAddr, c
|
||||
}
|
||||
|
||||
// newServer creates a nebula instance with fewer assumptions
|
||||
func newServer(caCrt []cert.Certificate, certs []cert.Certificate, key []byte, overrides m) (*nebula.Control, []netip.Prefix, netip.AddrPort, *config.C) {
|
||||
l := NewTestLogger()
|
||||
|
||||
vpnNetworks := certs[len(certs)-1].Networks()
|
||||
|
||||
var udpAddr netip.AddrPort
|
||||
if vpnNetworks[0].Addr().Is4() {
|
||||
budpIp := vpnNetworks[0].Addr().As4()
|
||||
budpIp[1] -= 128
|
||||
udpAddr = netip.AddrPortFrom(netip.AddrFrom4(budpIp), 4242)
|
||||
} else {
|
||||
budpIp := vpnNetworks[0].Addr().As16()
|
||||
// beef for funsies
|
||||
budpIp[2] = 190
|
||||
budpIp[3] = 239
|
||||
udpAddr = netip.AddrPortFrom(netip.AddrFrom16(budpIp), 4242)
|
||||
}
|
||||
|
||||
caStr := ""
|
||||
for _, ca := range caCrt {
|
||||
x, err := ca.MarshalPEM()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
caStr += string(x)
|
||||
}
|
||||
certStr := ""
|
||||
for _, c := range certs {
|
||||
x, err := c.MarshalPEM()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
certStr += string(x)
|
||||
}
|
||||
|
||||
mc := m{
|
||||
"pki": m{
|
||||
"ca": caStr,
|
||||
"cert": certStr,
|
||||
"key": string(key),
|
||||
},
|
||||
//"tun": m{"disabled": true},
|
||||
"firewall": m{
|
||||
"outbound": []m{{
|
||||
"proto": "any",
|
||||
"port": "any",
|
||||
"host": "any",
|
||||
}},
|
||||
"inbound": []m{{
|
||||
"proto": "any",
|
||||
"port": "any",
|
||||
"host": "any",
|
||||
}},
|
||||
},
|
||||
//"handshakes": m{
|
||||
// "try_interval": "1s",
|
||||
//},
|
||||
"listen": m{
|
||||
"host": udpAddr.Addr().String(),
|
||||
"port": udpAddr.Port(),
|
||||
},
|
||||
"logging": m{
|
||||
"timestamp_format": fmt.Sprintf("%v 15:04:05.000000", certs[0].Name()),
|
||||
"level": l.Level.String(),
|
||||
},
|
||||
"timers": m{
|
||||
"pending_deletion_interval": 2,
|
||||
"connection_alive_interval": 2,
|
||||
},
|
||||
}
|
||||
|
||||
if overrides != nil {
|
||||
final := m{}
|
||||
err := mergo.Merge(&final, overrides, mergo.WithAppendSlice)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = mergo.Merge(&final, mc, mergo.WithAppendSlice)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mc = final
|
||||
}
|
||||
|
||||
cb, err := yaml.Marshal(mc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c := config.NewC(l)
|
||||
cStr := string(cb)
|
||||
c.LoadString(cStr)
|
||||
|
||||
control, err := nebula.Main(c, false, "e2e-test", l, nil)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return control, vpnNetworks, udpAddr, c
|
||||
}
|
||||
|
||||
type doneCb func()
|
||||
|
||||
func deadline(t *testing.T, seconds time.Duration) doneCb {
|
||||
@@ -292,7 +147,7 @@ func deadline(t *testing.T, seconds time.Duration) doneCb {
|
||||
}
|
||||
}
|
||||
|
||||
func assertTunnel(t testing.TB, vpnIpA, vpnIpB netip.Addr, controlA, controlB *nebula.Control, r *router.R) {
|
||||
func assertTunnel(t *testing.T, vpnIpA, vpnIpB netip.Addr, controlA, controlB *nebula.Control, r *router.R) {
|
||||
// Send a packet from them to me
|
||||
controlB.InjectTunUDPPacket(vpnIpA, 80, vpnIpB, 90, []byte("Hi from B"))
|
||||
bPacket := r.RouteForAllUntilTxTun(controlA)
|
||||
@@ -304,14 +159,14 @@ func assertTunnel(t testing.TB, vpnIpA, vpnIpB netip.Addr, controlA, controlB *n
|
||||
assertUdpPacket(t, []byte("Hello from A"), aPacket, vpnIpA, vpnIpB, 90, 80)
|
||||
}
|
||||
|
||||
func assertHostInfoPair(t testing.TB, addrA, addrB netip.AddrPort, vpnNetsA, vpnNetsB []netip.Prefix, controlA, controlB *nebula.Control) {
|
||||
func assertHostInfoPair(t *testing.T, addrA, addrB netip.AddrPort, vpnNetsA, vpnNetsB []netip.Prefix, controlA, controlB *nebula.Control) {
|
||||
// Get both host infos
|
||||
//TODO: CERT-V2 we may want to loop over each vpnAddr and assert all the things
|
||||
hBinA := controlA.GetHostInfoByVpnAddr(vpnNetsB[0].Addr(), false)
|
||||
require.NotNil(t, hBinA, "Host B was not found by vpnAddr in controlA")
|
||||
assert.NotNil(t, hBinA, "Host B was not found by vpnAddr in controlA")
|
||||
|
||||
hAinB := controlB.GetHostInfoByVpnAddr(vpnNetsA[0].Addr(), false)
|
||||
require.NotNil(t, hAinB, "Host A was not found by vpnAddr in controlB")
|
||||
assert.NotNil(t, hAinB, "Host A was not found by vpnAddr in controlB")
|
||||
|
||||
// Check that both vpn and real addr are correct
|
||||
assert.EqualValues(t, getAddrs(vpnNetsB), hBinA.VpnAddrs, "Host B VpnIp is wrong in control A")
|
||||
@@ -325,7 +180,7 @@ func assertHostInfoPair(t testing.TB, addrA, addrB netip.AddrPort, vpnNetsA, vpn
|
||||
assert.Equal(t, hBinA.RemoteIndex, hAinB.LocalIndex, "Host B remote index does not match host A local index")
|
||||
}
|
||||
|
||||
func assertUdpPacket(t testing.TB, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
||||
func assertUdpPacket(t *testing.T, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
||||
if toIp.Is6() {
|
||||
assertUdpPacket6(t, expected, b, fromIp, toIp, fromPort, toPort)
|
||||
} else {
|
||||
@@ -333,7 +188,7 @@ func assertUdpPacket(t testing.TB, expected, b []byte, fromIp, toIp netip.Addr,
|
||||
}
|
||||
}
|
||||
|
||||
func assertUdpPacket6(t testing.TB, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
||||
func assertUdpPacket6(t *testing.T, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
||||
packet := gopacket.NewPacket(b, layers.LayerTypeIPv6, gopacket.Lazy)
|
||||
v6 := packet.Layer(layers.LayerTypeIPv6).(*layers.IPv6)
|
||||
assert.NotNil(t, v6, "No ipv6 data found")
|
||||
@@ -352,7 +207,7 @@ func assertUdpPacket6(t testing.TB, expected, b []byte, fromIp, toIp netip.Addr,
|
||||
assert.Equal(t, expected, data.Payload(), "Data was incorrect")
|
||||
}
|
||||
|
||||
func assertUdpPacket4(t testing.TB, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
||||
func assertUdpPacket4(t *testing.T, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {
|
||||
packet := gopacket.NewPacket(b, layers.LayerTypeIPv4, gopacket.Lazy)
|
||||
v4 := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
||||
assert.NotNil(t, v4, "No ipv4 data found")
|
||||
|
||||
@@ -4,16 +4,12 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/slackhq/nebula/cert"
|
||||
"github.com/slackhq/nebula/cert_test"
|
||||
"github.com/slackhq/nebula/e2e/router"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestDropInactiveTunnels(t *testing.T) {
|
||||
@@ -59,309 +55,3 @@ func TestDropInactiveTunnels(t *testing.T) {
|
||||
myControl.Stop()
|
||||
theirControl.Stop()
|
||||
}
|
||||
|
||||
func TestCertUpgrade(t *testing.T) {
|
||||
// The goal of this test is to ensure the shortest inactivity timeout will close the tunnel on both sides
|
||||
// under ideal conditions
|
||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
caB, err := ca.MarshalPEM()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ca2, _, caKey2, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
|
||||
ca2B, err := ca2.MarshalPEM()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
caStr := fmt.Sprintf("%s\n%s", caB, ca2B)
|
||||
|
||||
myCert, _, myPrivKey, _ := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, "me", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix("10.128.0.1/24")}, nil, []string{})
|
||||
_, myCert2Pem := cert_test.NewTestCertDifferentVersion(myCert, cert.Version2, ca2, caKey2)
|
||||
|
||||
theirCert, _, theirPrivKey, _ := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, "them", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix("10.128.0.2/24")}, nil, []string{})
|
||||
theirCert2, _ := cert_test.NewTestCertDifferentVersion(theirCert, cert.Version2, ca2, caKey2)
|
||||
|
||||
myControl, myVpnIpNet, myUdpAddr, myC := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{myCert}, myPrivKey, m{})
|
||||
theirControl, theirVpnIpNet, theirUdpAddr, _ := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{theirCert, theirCert2}, theirPrivKey, m{})
|
||||
|
||||
// Share our underlay information
|
||||
myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
||||
theirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)
|
||||
|
||||
// Start the servers
|
||||
myControl.Start()
|
||||
theirControl.Start()
|
||||
|
||||
r := router.NewR(t, myControl, theirControl)
|
||||
defer r.RenderFlow()
|
||||
|
||||
r.Log("Assert the tunnel between me and them works")
|
||||
assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
||||
r.Log("yay")
|
||||
//todo ???
|
||||
time.Sleep(1 * time.Second)
|
||||
r.FlushAll()
|
||||
|
||||
mc := m{
|
||||
"pki": m{
|
||||
"ca": caStr,
|
||||
"cert": string(myCert2Pem),
|
||||
"key": string(myPrivKey),
|
||||
},
|
||||
//"tun": m{"disabled": true},
|
||||
"firewall": myC.Settings["firewall"],
|
||||
//"handshakes": m{
|
||||
// "try_interval": "1s",
|
||||
//},
|
||||
"listen": myC.Settings["listen"],
|
||||
"logging": myC.Settings["logging"],
|
||||
"timers": myC.Settings["timers"],
|
||||
}
|
||||
|
||||
cb, err := yaml.Marshal(mc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
r.Logf("reload new v2-only config")
|
||||
err = myC.ReloadConfigString(string(cb))
|
||||
assert.NoError(t, err)
|
||||
r.Log("yay, spin until their sees it")
|
||||
waitStart := time.Now()
|
||||
for {
|
||||
assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
||||
c := theirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)
|
||||
if c == nil {
|
||||
r.Log("nil")
|
||||
} else {
|
||||
version := c.Cert.Version()
|
||||
r.Logf("version %d", version)
|
||||
if version == cert.Version2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
since := time.Since(waitStart)
|
||||
if since > time.Second*10 {
|
||||
t.Fatal("Cert should be new by now")
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
r.RenderHostmaps("Final hostmaps", myControl, theirControl)
|
||||
|
||||
myControl.Stop()
|
||||
theirControl.Stop()
|
||||
}
|
||||
|
||||
func TestCertDowngrade(t *testing.T) {
|
||||
// The goal of this test is to ensure the shortest inactivity timeout will close the tunnel on both sides
|
||||
// under ideal conditions
|
||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
caB, err := ca.MarshalPEM()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ca2, _, caKey2, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
|
||||
ca2B, err := ca2.MarshalPEM()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
caStr := fmt.Sprintf("%s\n%s", caB, ca2B)
|
||||
|
||||
myCert, _, myPrivKey, myCertPem := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, "me", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix("10.128.0.1/24")}, nil, []string{})
|
||||
myCert2, _ := cert_test.NewTestCertDifferentVersion(myCert, cert.Version2, ca2, caKey2)
|
||||
|
||||
theirCert, _, theirPrivKey, _ := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, "them", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix("10.128.0.2/24")}, nil, []string{})
|
||||
theirCert2, _ := cert_test.NewTestCertDifferentVersion(theirCert, cert.Version2, ca2, caKey2)
|
||||
|
||||
myControl, myVpnIpNet, myUdpAddr, myC := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{myCert2}, myPrivKey, m{})
|
||||
theirControl, theirVpnIpNet, theirUdpAddr, _ := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{theirCert, theirCert2}, theirPrivKey, m{})
|
||||
|
||||
// Share our underlay information
|
||||
myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
||||
theirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)
|
||||
|
||||
// Start the servers
|
||||
myControl.Start()
|
||||
theirControl.Start()
|
||||
|
||||
r := router.NewR(t, myControl, theirControl)
|
||||
defer r.RenderFlow()
|
||||
|
||||
r.Log("Assert the tunnel between me and them works")
|
||||
//assertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)
|
||||
//r.Log("yay")
|
||||
assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
||||
r.Log("yay")
|
||||
//todo ???
|
||||
time.Sleep(1 * time.Second)
|
||||
r.FlushAll()
|
||||
|
||||
mc := m{
|
||||
"pki": m{
|
||||
"ca": caStr,
|
||||
"cert": string(myCertPem),
|
||||
"key": string(myPrivKey),
|
||||
},
|
||||
"firewall": myC.Settings["firewall"],
|
||||
"listen": myC.Settings["listen"],
|
||||
"logging": myC.Settings["logging"],
|
||||
"timers": myC.Settings["timers"],
|
||||
}
|
||||
|
||||
cb, err := yaml.Marshal(mc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
r.Logf("reload new v1-only config")
|
||||
err = myC.ReloadConfigString(string(cb))
|
||||
assert.NoError(t, err)
|
||||
r.Log("yay, spin until their sees it")
|
||||
waitStart := time.Now()
|
||||
for {
|
||||
assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
||||
c := theirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)
|
||||
c2 := myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false)
|
||||
if c == nil || c2 == nil {
|
||||
r.Log("nil")
|
||||
} else {
|
||||
version := c.Cert.Version()
|
||||
theirVersion := c2.Cert.Version()
|
||||
r.Logf("version %d,%d", version, theirVersion)
|
||||
if version == cert.Version1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
since := time.Since(waitStart)
|
||||
if since > time.Second*5 {
|
||||
r.Log("it is unusual that the cert is not new yet, but not a failure yet")
|
||||
}
|
||||
if since > time.Second*10 {
|
||||
r.Log("wtf")
|
||||
t.Fatal("Cert should be new by now")
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
r.RenderHostmaps("Final hostmaps", myControl, theirControl)
|
||||
|
||||
myControl.Stop()
|
||||
theirControl.Stop()
|
||||
}
|
||||
|
||||
func TestCertMismatchCorrection(t *testing.T) {
|
||||
// The goal of this test is to ensure the shortest inactivity timeout will close the tunnel on both sides
|
||||
// under ideal conditions
|
||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
ca2, _, caKey2, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
|
||||
myCert, _, myPrivKey, _ := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, "me", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix("10.128.0.1/24")}, nil, []string{})
|
||||
myCert2, _ := cert_test.NewTestCertDifferentVersion(myCert, cert.Version2, ca2, caKey2)
|
||||
|
||||
theirCert, _, theirPrivKey, _ := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, "them", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix("10.128.0.2/24")}, nil, []string{})
|
||||
theirCert2, _ := cert_test.NewTestCertDifferentVersion(theirCert, cert.Version2, ca2, caKey2)
|
||||
|
||||
myControl, myVpnIpNet, myUdpAddr, _ := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{myCert2}, myPrivKey, m{})
|
||||
theirControl, theirVpnIpNet, theirUdpAddr, _ := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{theirCert, theirCert2}, theirPrivKey, m{})
|
||||
|
||||
// Share our underlay information
|
||||
myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
||||
theirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)
|
||||
|
||||
// Start the servers
|
||||
myControl.Start()
|
||||
theirControl.Start()
|
||||
|
||||
r := router.NewR(t, myControl, theirControl)
|
||||
defer r.RenderFlow()
|
||||
|
||||
r.Log("Assert the tunnel between me and them works")
|
||||
//assertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)
|
||||
//r.Log("yay")
|
||||
assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
||||
r.Log("yay")
|
||||
//todo ???
|
||||
time.Sleep(1 * time.Second)
|
||||
r.FlushAll()
|
||||
|
||||
waitStart := time.Now()
|
||||
for {
|
||||
assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
||||
c := theirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)
|
||||
c2 := myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false)
|
||||
if c == nil || c2 == nil {
|
||||
r.Log("nil")
|
||||
} else {
|
||||
version := c.Cert.Version()
|
||||
theirVersion := c2.Cert.Version()
|
||||
r.Logf("version %d,%d", version, theirVersion)
|
||||
if version == theirVersion {
|
||||
break
|
||||
}
|
||||
}
|
||||
since := time.Since(waitStart)
|
||||
if since > time.Second*5 {
|
||||
r.Log("wtf")
|
||||
}
|
||||
if since > time.Second*10 {
|
||||
r.Log("wtf")
|
||||
t.Fatal("Cert should be new by now")
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
r.RenderHostmaps("Final hostmaps", myControl, theirControl)
|
||||
|
||||
myControl.Stop()
|
||||
theirControl.Stop()
|
||||
}
|
||||
|
||||
func TestCrossStackRelaysWork(t *testing.T) {
|
||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||
myControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version2, ca, caKey, "me ", "10.128.0.1/24,fc00::1/64", m{"relay": m{"use_relays": true}})
|
||||
relayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "relay ", "10.128.0.128/24,fc00::128/64", m{"relay": m{"am_relay": true}})
|
||||
theirUdp := netip.MustParseAddrPort("10.0.0.2:4242")
|
||||
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServerWithUdp(cert.Version2, ca, caKey, "them ", "fc00::2/64", theirUdp, m{"relay": m{"use_relays": true}})
|
||||
|
||||
//myVpnV4 := myVpnIpNet[0]
|
||||
myVpnV6 := myVpnIpNet[1]
|
||||
relayVpnV4 := relayVpnIpNet[0]
|
||||
relayVpnV6 := relayVpnIpNet[1]
|
||||
theirVpnV6 := theirVpnIpNet[0]
|
||||
|
||||
// Teach my how to get to the relay and that their can be reached via the relay
|
||||
myControl.InjectLightHouseAddr(relayVpnV4.Addr(), relayUdpAddr)
|
||||
myControl.InjectLightHouseAddr(relayVpnV6.Addr(), relayUdpAddr)
|
||||
myControl.InjectRelays(theirVpnV6.Addr(), []netip.Addr{relayVpnV6.Addr()})
|
||||
relayControl.InjectLightHouseAddr(theirVpnV6.Addr(), theirUdpAddr)
|
||||
|
||||
// Build a router so we don't have to reason who gets which packet
|
||||
r := router.NewR(t, myControl, relayControl, theirControl)
|
||||
defer r.RenderFlow()
|
||||
|
||||
// Start the servers
|
||||
myControl.Start()
|
||||
relayControl.Start()
|
||||
theirControl.Start()
|
||||
|
||||
t.Log("Trigger a handshake from me to them via the relay")
|
||||
myControl.InjectTunUDPPacket(theirVpnV6.Addr(), 80, myVpnV6.Addr(), 80, []byte("Hi from me"))
|
||||
|
||||
p := r.RouteForAllUntilTxTun(theirControl)
|
||||
r.Log("Assert the tunnel works")
|
||||
assertUdpPacket(t, []byte("Hi from me"), p, myVpnV6.Addr(), theirVpnV6.Addr(), 80, 80)
|
||||
|
||||
t.Log("reply?")
|
||||
theirControl.InjectTunUDPPacket(myVpnV6.Addr(), 80, theirVpnV6.Addr(), 80, []byte("Hi from them"))
|
||||
p = r.RouteForAllUntilTxTun(myControl)
|
||||
assertUdpPacket(t, []byte("Hi from them"), p, theirVpnV6.Addr(), myVpnV6.Addr(), 80, 80)
|
||||
|
||||
r.RenderHostmaps("Final hostmaps", myControl, relayControl, theirControl)
|
||||
//t.Log("finish up")
|
||||
//myControl.Stop()
|
||||
//theirControl.Stop()
|
||||
//relayControl.Stop()
|
||||
}
|
||||
|
||||
+9
-7
@@ -132,6 +132,13 @@ listen:
|
||||
# Sets the max number of packets to pull from the kernel for each syscall (under systems that support recvmmsg)
|
||||
# default is 64, does not support reload
|
||||
#batch: 64
|
||||
|
||||
# Control batching between UDP and TUN pipelines
|
||||
#batch:
|
||||
# inbound_size: 32 # packets to queue from UDP before handing to workers
|
||||
# outbound_size: 32 # packets to queue from TUN before handing to workers
|
||||
# flush_interval: 50us # flush partially filled batches after this duration
|
||||
# max_outstanding: 1028 # batches buffered per routine on each channel
|
||||
# Configure socket buffers for the udp side (outside), leave unset to use the system defaults. Values will be doubled by the kernel
|
||||
# Default is net.core.rmem_default and net.core.wmem_default (/proc/sys/net/core/rmem_default and /proc/sys/net/core/rmem_default)
|
||||
# Maximum is limited by memory in the system, SO_RCVBUFFORCE and SO_SNDBUFFORCE is used to avoid having to raise the system wide
|
||||
@@ -144,10 +151,6 @@ listen:
|
||||
# valid values: always, never, private
|
||||
# This setting is reloadable.
|
||||
#send_recv_error: always
|
||||
# Similar to send_recv_error, this option lets you configure if you want to accept "recv_error" packets from remote hosts.
|
||||
# valid values: always, never, private
|
||||
# This setting is reloadable.
|
||||
#accept_recv_error: always
|
||||
# The so_sock option is a Linux-specific feature that allows all outgoing Nebula packets to be tagged with a specific identifier.
|
||||
# This tagging enables IP rule-based filtering. For example, it supports 0.0.0.0/0 unsafe_routes,
|
||||
# allowing for more precise routing decisions based on the packet tags. Default is 0 meaning no mark is set.
|
||||
@@ -387,9 +390,8 @@ firewall:
|
||||
# host: `any` or a literal hostname, ie `test-host`
|
||||
# group: `any` or a literal group name, ie `default-group`
|
||||
# groups: Same as group but accepts a list of values. Multiple values are AND'd together and a certificate would have to contain all groups to pass
|
||||
# cidr: a remote CIDR, `0.0.0.0/0` is any ipv4 and `::/0` is any ipv6. `any` means any ip family and address.
|
||||
# local_cidr: a local CIDR, `0.0.0.0/0` is any ipv4 and `::/0` is any ipv6. `any` means any ip family and address.
|
||||
# This can be used to filter destinations when using unsafe_routes.
|
||||
# cidr: a remote CIDR, `0.0.0.0/0` is any ipv4 and `::/0` is any ipv6.
|
||||
# local_cidr: a local CIDR, `0.0.0.0/0` is any ipv4 and `::/0` is any ipv6. This can be used to filter destinations when using unsafe_routes.
|
||||
# By default, this is set to only the VPN (overlay) networks assigned via the certificate networks field unless `default_local_cidr_any` is set to true.
|
||||
# If there are unsafe_routes present in this config file, `local_cidr` should be set appropriately for the intended us case.
|
||||
# ca_name: An issuing CA name
|
||||
|
||||
+71
-125
@@ -8,7 +8,6 @@ import (
|
||||
"hash/fnv"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -23,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
type FirewallInterface interface {
|
||||
AddRule(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, addr, localAddr netip.Prefix, caName string, caSha string) error
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
@@ -248,11 +247,22 @@ func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firew
|
||||
}
|
||||
|
||||
// AddRule properly creates the in memory rule structure for a firewall table.
|
||||
func (f *Firewall) AddRule(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, ip, localIp netip.Prefix, caName string, caSha string) error {
|
||||
// Under gomobile, stringing a nil pointer with fmt causes an abort in debug mode for iOS
|
||||
// https://github.com/golang/go/issues/14131
|
||||
sIp := ""
|
||||
if ip.IsValid() {
|
||||
sIp = ip.String()
|
||||
}
|
||||
lIp := ""
|
||||
if localIp.IsValid() {
|
||||
lIp = localIp.String()
|
||||
}
|
||||
|
||||
// We need this rule string because we generate a hash. Removing this will break firewall reload.
|
||||
ruleString := fmt.Sprintf(
|
||||
"incoming: %v, proto: %v, startPort: %v, endPort: %v, groups: %v, host: %v, ip: %v, localIp: %v, caName: %v, caSha: %s",
|
||||
incoming, proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha,
|
||||
incoming, proto, startPort, endPort, groups, host, sIp, lIp, caName, caSha,
|
||||
)
|
||||
f.rules += ruleString + "\n"
|
||||
|
||||
@@ -260,7 +270,7 @@ func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort
|
||||
if !incoming {
|
||||
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}).
|
||||
f.l.WithField("firewallRule", m{"direction": direction, "proto": proto, "startPort": startPort, "endPort": endPort, "groups": groups, "host": host, "ip": sIp, "localIp": lIp, "caName": caName, "caSha": caSha}).
|
||||
Info("Firewall rule added")
|
||||
|
||||
var (
|
||||
@@ -287,7 +297,7 @@ func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort
|
||||
return fmt.Errorf("unknown protocol %v", proto)
|
||||
}
|
||||
|
||||
return fp.addRule(f, startPort, endPort, groups, host, cidr, localCidr, caName, caSha)
|
||||
return fp.addRule(f, startPort, endPort, groups, host, ip, localIp, caName, caSha)
|
||||
}
|
||||
|
||||
// GetRuleHash returns a hash representation of all inbound and outbound rules
|
||||
@@ -327,6 +337,7 @@ func AddFirewallRulesFromConfig(l *logrus.Logger, inbound bool, c *config.C, fw
|
||||
}
|
||||
|
||||
for i, t := range rs {
|
||||
var groups []string
|
||||
r, err := convertRule(l, t, table, i)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s rule #%v; %s", table, i, err)
|
||||
@@ -336,10 +347,23 @@ func AddFirewallRulesFromConfig(l *logrus.Logger, inbound bool, c *config.C, fw
|
||||
return fmt.Errorf("%s rule #%v; only one of port or code should be provided", table, i)
|
||||
}
|
||||
|
||||
if r.Host == "" && len(r.Groups) == 0 && r.Cidr == "" && r.LocalCidr == "" && r.CAName == "" && r.CASha == "" {
|
||||
if r.Host == "" && len(r.Groups) == 0 && r.Group == "" && r.Cidr == "" && r.LocalCidr == "" && r.CAName == "" && r.CASha == "" {
|
||||
return fmt.Errorf("%s rule #%v; at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided", table, i)
|
||||
}
|
||||
|
||||
if len(r.Groups) > 0 {
|
||||
groups = r.Groups
|
||||
}
|
||||
|
||||
if r.Group != "" {
|
||||
// Check if we have both groups and group provided in the rule config
|
||||
if len(groups) > 0 {
|
||||
return fmt.Errorf("%s rule #%v; only one of group or groups should be defined, both provided", table, i)
|
||||
}
|
||||
|
||||
groups = []string{r.Group}
|
||||
}
|
||||
|
||||
var sPort, errPort string
|
||||
if r.Code != "" {
|
||||
errPort = "code"
|
||||
@@ -368,25 +392,23 @@ func AddFirewallRulesFromConfig(l *logrus.Logger, inbound bool, c *config.C, fw
|
||||
return fmt.Errorf("%s rule #%v; proto was not understood; `%s`", table, i, r.Proto)
|
||||
}
|
||||
|
||||
if r.Cidr != "" && r.Cidr != "any" {
|
||||
_, err = netip.ParsePrefix(r.Cidr)
|
||||
var cidr netip.Prefix
|
||||
if r.Cidr != "" {
|
||||
cidr, err = netip.ParsePrefix(r.Cidr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s rule #%v; cidr did not parse; %s", table, i, err)
|
||||
}
|
||||
}
|
||||
|
||||
if r.LocalCidr != "" && r.LocalCidr != "any" {
|
||||
_, err = netip.ParsePrefix(r.LocalCidr)
|
||||
var localCidr netip.Prefix
|
||||
if r.LocalCidr != "" {
|
||||
localCidr, err = netip.ParsePrefix(r.LocalCidr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s rule #%v; local_cidr did not parse; %s", table, i, err)
|
||||
}
|
||||
}
|
||||
|
||||
if warning := r.sanity(); warning != nil {
|
||||
l.Warnf("%s rule #%v; %s", table, i, warning)
|
||||
}
|
||||
|
||||
err = fw.AddRule(inbound, proto, startPort, endPort, r.Groups, r.Host, r.Cidr, r.LocalCidr, r.CAName, r.CASha)
|
||||
err = fw.AddRule(inbound, proto, startPort, endPort, groups, r.Host, cidr, localCidr, r.CAName, r.CASha)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s rule #%v; `%s`", table, i, err)
|
||||
}
|
||||
@@ -395,10 +417,8 @@ func AddFirewallRulesFromConfig(l *logrus.Logger, inbound bool, c *config.C, fw
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrUnknownNetworkType = errors.New("unknown network type")
|
||||
var ErrPeerRejected = errors.New("remote address is not within a network that we handle")
|
||||
var ErrInvalidRemoteIP = errors.New("remote address is not in remote certificate networks")
|
||||
var ErrInvalidLocalIP = errors.New("local address is not in list of handled local addresses")
|
||||
var ErrInvalidRemoteIP = errors.New("remote IP is not in remote certificate subnets")
|
||||
var ErrInvalidLocalIP = errors.New("local IP is not in list of handled local IPs")
|
||||
var ErrNoMatchingRule = errors.New("no matching rule in firewall table")
|
||||
|
||||
// Drop returns an error if the packet should be dropped, explaining why. It
|
||||
@@ -409,31 +429,18 @@ func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make sure remote address matches nebula certificate, and determine how to treat it
|
||||
if h.networks == nil {
|
||||
// Simple case: Certificate has one address and no unsafe networks
|
||||
if h.vpnAddrs[0] != fp.RemoteAddr {
|
||||
// Make sure remote address matches nebula certificate
|
||||
if h.networks != nil {
|
||||
if !h.networks.Contains(fp.RemoteAddr) {
|
||||
f.metrics(incoming).droppedRemoteAddr.Inc(1)
|
||||
return ErrInvalidRemoteIP
|
||||
}
|
||||
} else {
|
||||
nwType, ok := h.networks.Lookup(fp.RemoteAddr)
|
||||
if !ok {
|
||||
// Simple case: Certificate has one address and no unsafe networks
|
||||
if h.vpnAddrs[0] != fp.RemoteAddr {
|
||||
f.metrics(incoming).droppedRemoteAddr.Inc(1)
|
||||
return ErrInvalidRemoteIP
|
||||
}
|
||||
switch nwType {
|
||||
case NetworkTypeVPN:
|
||||
break // nothing special
|
||||
case NetworkTypeVPNPeer:
|
||||
f.metrics(incoming).droppedRemoteAddr.Inc(1)
|
||||
return ErrPeerRejected // reject for now, one day this may have different FW rules
|
||||
case NetworkTypeUnsafe:
|
||||
break // nothing special, one day this may have different FW rules
|
||||
default:
|
||||
f.metrics(incoming).droppedRemoteAddr.Inc(1)
|
||||
return ErrUnknownNetworkType //should never happen
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we are supposed to be handling this local ip address
|
||||
@@ -633,7 +640,7 @@ func (ft *FirewallTable) match(p firewall.Packet, incoming bool, c *cert.CachedC
|
||||
return false
|
||||
}
|
||||
|
||||
func (fp firewallPort) addRule(f *Firewall, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {
|
||||
func (fp firewallPort) addRule(f *Firewall, startPort int32, endPort int32, groups []string, host string, ip, localIp netip.Prefix, caName string, caSha string) error {
|
||||
if startPort > endPort {
|
||||
return fmt.Errorf("start port was lower than end port")
|
||||
}
|
||||
@@ -646,7 +653,7 @@ func (fp firewallPort) addRule(f *Firewall, startPort int32, endPort int32, grou
|
||||
}
|
||||
}
|
||||
|
||||
if err := fp[i].addRule(f, groups, host, cidr, localCidr, caName, caSha); err != nil {
|
||||
if err := fp[i].addRule(f, groups, host, ip, localIp, caName, caSha); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -677,7 +684,7 @@ func (fp firewallPort) match(p firewall.Packet, incoming bool, c *cert.CachedCer
|
||||
return fp[firewall.PortAny].match(p, c, caPool)
|
||||
}
|
||||
|
||||
func (fc *FirewallCA) addRule(f *Firewall, groups []string, host string, cidr, localCidr, caName, caSha string) error {
|
||||
func (fc *FirewallCA) addRule(f *Firewall, groups []string, host string, ip, localIp netip.Prefix, caName, caSha string) error {
|
||||
fr := func() *FirewallRule {
|
||||
return &FirewallRule{
|
||||
Hosts: make(map[string]*firewallLocalCIDR),
|
||||
@@ -691,14 +698,14 @@ func (fc *FirewallCA) addRule(f *Firewall, groups []string, host string, cidr, l
|
||||
fc.Any = fr()
|
||||
}
|
||||
|
||||
return fc.Any.addRule(f, groups, host, cidr, localCidr)
|
||||
return fc.Any.addRule(f, groups, host, ip, localIp)
|
||||
}
|
||||
|
||||
if caSha != "" {
|
||||
if _, ok := fc.CAShas[caSha]; !ok {
|
||||
fc.CAShas[caSha] = fr()
|
||||
}
|
||||
err := fc.CAShas[caSha].addRule(f, groups, host, cidr, localCidr)
|
||||
err := fc.CAShas[caSha].addRule(f, groups, host, ip, localIp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -708,7 +715,7 @@ func (fc *FirewallCA) addRule(f *Firewall, groups []string, host string, cidr, l
|
||||
if _, ok := fc.CANames[caName]; !ok {
|
||||
fc.CANames[caName] = fr()
|
||||
}
|
||||
err := fc.CANames[caName].addRule(f, groups, host, cidr, localCidr)
|
||||
err := fc.CANames[caName].addRule(f, groups, host, ip, localIp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -740,24 +747,24 @@ func (fc *FirewallCA) match(p firewall.Packet, c *cert.CachedCertificate, caPool
|
||||
return fc.CANames[s.Certificate.Name()].match(p, c)
|
||||
}
|
||||
|
||||
func (fr *FirewallRule) addRule(f *Firewall, groups []string, host, cidr, localCidr string) error {
|
||||
func (fr *FirewallRule) addRule(f *Firewall, groups []string, host string, ip, localCIDR netip.Prefix) error {
|
||||
flc := func() *firewallLocalCIDR {
|
||||
return &firewallLocalCIDR{
|
||||
LocalCIDR: new(bart.Lite),
|
||||
}
|
||||
}
|
||||
|
||||
if fr.isAny(groups, host, cidr) {
|
||||
if fr.isAny(groups, host, ip) {
|
||||
if fr.Any == nil {
|
||||
fr.Any = flc()
|
||||
}
|
||||
|
||||
return fr.Any.addRule(f, localCidr)
|
||||
return fr.Any.addRule(f, localCIDR)
|
||||
}
|
||||
|
||||
if len(groups) > 0 {
|
||||
nlc := flc()
|
||||
err := nlc.addRule(f, localCidr)
|
||||
err := nlc.addRule(f, localCIDR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -773,34 +780,30 @@ func (fr *FirewallRule) addRule(f *Firewall, groups []string, host, cidr, localC
|
||||
if nlc == nil {
|
||||
nlc = flc()
|
||||
}
|
||||
err := nlc.addRule(f, localCidr)
|
||||
err := nlc.addRule(f, localCIDR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fr.Hosts[host] = nlc
|
||||
}
|
||||
|
||||
if cidr != "" {
|
||||
c, err := netip.ParsePrefix(cidr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nlc, _ := fr.CIDR.Get(c)
|
||||
if ip.IsValid() {
|
||||
nlc, _ := fr.CIDR.Get(ip)
|
||||
if nlc == nil {
|
||||
nlc = flc()
|
||||
}
|
||||
err = nlc.addRule(f, localCidr)
|
||||
err := nlc.addRule(f, localCIDR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fr.CIDR.Insert(c, nlc)
|
||||
fr.CIDR.Insert(ip, nlc)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fr *FirewallRule) isAny(groups []string, host string, cidr string) bool {
|
||||
if len(groups) == 0 && host == "" && cidr == "" {
|
||||
func (fr *FirewallRule) isAny(groups []string, host string, ip netip.Prefix) bool {
|
||||
if len(groups) == 0 && host == "" && !ip.IsValid() {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -814,7 +817,7 @@ func (fr *FirewallRule) isAny(groups []string, host string, cidr string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if cidr == "any" {
|
||||
if ip.IsValid() && ip.Bits() == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -866,13 +869,8 @@ func (fr *FirewallRule) match(p firewall.Packet, c *cert.CachedCertificate) bool
|
||||
return false
|
||||
}
|
||||
|
||||
func (flc *firewallLocalCIDR) addRule(f *Firewall, localCidr string) error {
|
||||
if localCidr == "any" {
|
||||
flc.Any = true
|
||||
return nil
|
||||
}
|
||||
|
||||
if localCidr == "" {
|
||||
func (flc *firewallLocalCIDR) addRule(f *Firewall, localIp netip.Prefix) error {
|
||||
if !localIp.IsValid() {
|
||||
if !f.hasUnsafeNetworks || f.defaultLocalCIDRAny {
|
||||
flc.Any = true
|
||||
return nil
|
||||
@@ -883,13 +881,12 @@ func (flc *firewallLocalCIDR) addRule(f *Firewall, localCidr string) error {
|
||||
}
|
||||
return nil
|
||||
|
||||
} else if localIp.Bits() == 0 {
|
||||
flc.Any = true
|
||||
return nil
|
||||
}
|
||||
|
||||
c, err := netip.ParsePrefix(localCidr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flc.LocalCIDR.Insert(c)
|
||||
flc.LocalCIDR.Insert(localIp)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -910,6 +907,7 @@ type rule struct {
|
||||
Code string
|
||||
Proto string
|
||||
Host string
|
||||
Group string
|
||||
Groups []string
|
||||
Cidr string
|
||||
LocalCidr string
|
||||
@@ -951,8 +949,7 @@ func convertRule(l *logrus.Logger, p any, table string, i int) (rule, error) {
|
||||
l.Warnf("%s rule #%v; group was an array with a single value, converting to simple value", table, i)
|
||||
m["group"] = v[0]
|
||||
}
|
||||
|
||||
singleGroup := toString("group", m)
|
||||
r.Group = toString("group", m)
|
||||
|
||||
if rg, ok := m["groups"]; ok {
|
||||
switch reflect.TypeOf(rg).Kind() {
|
||||
@@ -969,60 +966,9 @@ func convertRule(l *logrus.Logger, p any, table string, i int) (rule, error) {
|
||||
}
|
||||
}
|
||||
|
||||
//flatten group vs groups
|
||||
if singleGroup != "" {
|
||||
// Check if we have both groups and group provided in the rule config
|
||||
if len(r.Groups) > 0 {
|
||||
return r, fmt.Errorf("only one of group or groups should be defined, both provided")
|
||||
}
|
||||
r.Groups = []string{singleGroup}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// sanity returns an error if the rule would be evaluated in a way that would short-circuit a configured check on a wildcard value
|
||||
// rules are evaluated as "port AND proto AND (ca_sha OR ca_name) AND (host OR group OR groups OR cidr) AND local_cidr"
|
||||
func (r *rule) sanity() error {
|
||||
//port, proto, local_cidr are AND, no need to check here
|
||||
//ca_sha and ca_name don't have a wildcard value, no need to check here
|
||||
groupsEmpty := len(r.Groups) == 0
|
||||
hostEmpty := r.Host == ""
|
||||
cidrEmpty := r.Cidr == ""
|
||||
|
||||
if (groupsEmpty && hostEmpty && cidrEmpty) == true {
|
||||
return nil //no content!
|
||||
}
|
||||
|
||||
groupsHasAny := slices.Contains(r.Groups, "any")
|
||||
if groupsHasAny && len(r.Groups) > 1 {
|
||||
return fmt.Errorf("groups spec [%s] contains the group '\"any\". This rule will ignore the other groups specified", r.Groups)
|
||||
}
|
||||
|
||||
if r.Host == "any" {
|
||||
if !groupsEmpty {
|
||||
return fmt.Errorf("groups specified as %s, but host=any will match any host, regardless of groups", r.Groups)
|
||||
}
|
||||
|
||||
if !cidrEmpty {
|
||||
return fmt.Errorf("cidr specified as %s, but host=any will match any host, regardless of cidr", r.Cidr)
|
||||
}
|
||||
}
|
||||
|
||||
if groupsHasAny {
|
||||
if !hostEmpty && r.Host != "any" {
|
||||
return fmt.Errorf("groups spec [%s] contains the group '\"any\". This rule will ignore the specified host %s", r.Groups, r.Host)
|
||||
}
|
||||
if !cidrEmpty {
|
||||
return fmt.Errorf("groups spec [%s] contains the group '\"any\". This rule will ignore the specified cidr %s", r.Groups, r.Cidr)
|
||||
}
|
||||
}
|
||||
|
||||
//todo alert on cidr-any
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parsePort(s string) (startPort, endPort int32, err error) {
|
||||
if s == "any" {
|
||||
startPort = firewall.PortAny
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
type m = map[string]any
|
||||
@@ -23,17 +17,6 @@ const (
|
||||
|
||||
PortAny = 0 // Special value for matching `port: any`
|
||||
PortFragment = -1 // Special value for matching `port: fragment`
|
||||
|
||||
minFwPacketLen = 4
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPacketTooShort = errors.New("packet is too short")
|
||||
ErrUnknownIPVersion = errors.New("packet is an unknown ip version")
|
||||
ErrIPv4InvalidHeaderLength = errors.New("invalid ipv4 header length")
|
||||
ErrIPv4PacketTooShort = errors.New("ipv4 packet is too short")
|
||||
ErrIPv6PacketTooShort = errors.New("ipv6 packet is too short")
|
||||
ErrIPv6CouldNotFindPayload = errors.New("could not find payload in ipv6 packet")
|
||||
)
|
||||
|
||||
type Packet struct {
|
||||
@@ -77,172 +60,3 @@ func (fp Packet) MarshalJSON() ([]byte, error) {
|
||||
"Fragment": fp.Fragment,
|
||||
})
|
||||
}
|
||||
|
||||
func parseV6(data []byte, incoming bool, fp *Packet) error {
|
||||
dataLen := len(data)
|
||||
if dataLen < ipv6.HeaderLen {
|
||||
return ErrIPv6PacketTooShort
|
||||
}
|
||||
|
||||
if incoming {
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[8:24])
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[24:40])
|
||||
} else {
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[8:24])
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[24:40])
|
||||
}
|
||||
|
||||
protoAt := 6 // NextHeader is at 6 bytes into the ipv6 header
|
||||
offset := ipv6.HeaderLen // Start at the end of the ipv6 header
|
||||
next := 0
|
||||
for {
|
||||
if protoAt >= dataLen {
|
||||
break
|
||||
}
|
||||
proto := layers.IPProtocol(data[protoAt])
|
||||
|
||||
switch proto {
|
||||
case layers.IPProtocolICMPv6, layers.IPProtocolESP, layers.IPProtocolNoNextHeader:
|
||||
fp.Protocol = uint8(proto)
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
fp.Fragment = false
|
||||
return nil
|
||||
|
||||
case layers.IPProtocolTCP, layers.IPProtocolUDP:
|
||||
if dataLen < offset+4 {
|
||||
return ErrIPv6PacketTooShort
|
||||
}
|
||||
|
||||
fp.Protocol = uint8(proto)
|
||||
if incoming {
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[offset : offset+2])
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
|
||||
} else {
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[offset : offset+2])
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
|
||||
}
|
||||
|
||||
fp.Fragment = false
|
||||
return nil
|
||||
|
||||
case layers.IPProtocolIPv6Fragment:
|
||||
// Fragment header is 8 bytes, need at least offset+4 to read the offset field
|
||||
if dataLen < offset+8 {
|
||||
return ErrIPv6PacketTooShort
|
||||
}
|
||||
|
||||
// Check if this is the first fragment
|
||||
fragmentOffset := binary.BigEndian.Uint16(data[offset+2:offset+4]) &^ uint16(0x7) // Remove the reserved and M flag bits
|
||||
if fragmentOffset != 0 {
|
||||
// Non-first fragment, use what we have now and stop processing
|
||||
fp.Protocol = data[offset]
|
||||
fp.Fragment = true
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// The next loop should be the transport layer since we are the first fragment
|
||||
next = 8 // Fragment headers are always 8 bytes
|
||||
|
||||
case layers.IPProtocolAH:
|
||||
// Auth headers, used by IPSec, have a different meaning for header length
|
||||
if dataLen <= offset+1 {
|
||||
break
|
||||
}
|
||||
|
||||
next = int(data[offset+1]+2) << 2
|
||||
|
||||
default:
|
||||
// Normal ipv6 header length processing
|
||||
if dataLen <= offset+1 {
|
||||
break
|
||||
}
|
||||
|
||||
next = int(data[offset+1]+1) << 3
|
||||
}
|
||||
|
||||
if next <= 0 {
|
||||
// Safety check, each ipv6 header has to be at least 8 bytes
|
||||
next = 8
|
||||
}
|
||||
|
||||
protoAt = offset
|
||||
offset = offset + next
|
||||
}
|
||||
|
||||
return ErrIPv6CouldNotFindPayload
|
||||
}
|
||||
|
||||
func parseV4(data []byte, incoming bool, fp *Packet) error {
|
||||
// Do we at least have an ipv4 header worth of data?
|
||||
if len(data) < ipv4.HeaderLen {
|
||||
return ErrIPv4PacketTooShort
|
||||
}
|
||||
|
||||
// Adjust our start position based on the advertised ip header length
|
||||
ihl := int(data[0]&0x0f) << 2
|
||||
|
||||
// Well-formed ip header length?
|
||||
if ihl < ipv4.HeaderLen {
|
||||
return ErrIPv4InvalidHeaderLength
|
||||
}
|
||||
|
||||
// Check if this is the second or further fragment of a fragmented packet.
|
||||
flagsfrags := binary.BigEndian.Uint16(data[6:8])
|
||||
fp.Fragment = (flagsfrags & 0x1FFF) != 0
|
||||
|
||||
// Firewall handles protocol checks
|
||||
fp.Protocol = data[9]
|
||||
|
||||
// Accounting for a variable header length, do we have enough data for our src/dst tuples?
|
||||
minLen := ihl
|
||||
if !fp.Fragment && fp.Protocol != ProtoICMP {
|
||||
minLen += minFwPacketLen
|
||||
}
|
||||
if len(data) < minLen {
|
||||
return ErrIPv4InvalidHeaderLength
|
||||
}
|
||||
|
||||
// Firewall packets are locally oriented
|
||||
if incoming {
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||
if fp.Fragment || fp.Protocol == ProtoICMP {
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
} else {
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||
}
|
||||
} else {
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||
if fp.Fragment || fp.Protocol == ProtoICMP {
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
} else {
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewPacket validates and parses the interesting bits for the firewall out of the ip and sub protocol headers
|
||||
func NewPacket(data []byte, incoming bool, fp *Packet) error {
|
||||
if len(data) < 1 {
|
||||
return ErrPacketTooShort
|
||||
}
|
||||
|
||||
version := int((data[0] >> 4) & 0x0f)
|
||||
switch version {
|
||||
case ipv4.Version:
|
||||
return parseV4(data, incoming, fp)
|
||||
case ipv6.Version:
|
||||
return parseV6(data, incoming, fp)
|
||||
}
|
||||
return ErrUnknownIPVersion
|
||||
}
|
||||
|
||||
+76
-411
@@ -8,8 +8,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cert"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/firewall"
|
||||
@@ -73,114 +71,85 @@ func TestFirewall_AddRule(t *testing.T) {
|
||||
ti6, err := netip.ParsePrefix("fd12::34/128")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoTCP, 1, 1, []string{}, "", "", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoTCP, 1, 1, []string{}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
// An empty rule is 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.Hosts)
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
assert.Nil(t, fw.InRules.UDP[1].Any.Any)
|
||||
assert.Contains(t, fw.InRules.UDP[1].Any.Groups[0].Groups, "g1")
|
||||
assert.Empty(t, fw.InRules.UDP[1].Any.Hosts)
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 1, 1, []string{}, "h1", "", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 1, 1, []string{}, "h1", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
assert.Nil(t, fw.InRules.ICMP[1].Any.Any)
|
||||
assert.Empty(t, fw.InRules.ICMP[1].Any.Groups)
|
||||
assert.Contains(t, fw.InRules.ICMP[1].Any.Hosts, "h1")
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti.String(), "", "", ""))
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti, netip.Prefix{}, "", ""))
|
||||
assert.Nil(t, fw.OutRules.AnyProto[1].Any.Any)
|
||||
_, ok := fw.OutRules.AnyProto[1].Any.CIDR.Get(ti)
|
||||
assert.True(t, ok)
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti6.String(), "", "", ""))
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti6, netip.Prefix{}, "", ""))
|
||||
assert.Nil(t, fw.OutRules.AnyProto[1].Any.Any)
|
||||
_, ok = fw.OutRules.AnyProto[1].Any.CIDR.Get(ti6)
|
||||
assert.True(t, ok)
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", "", ti.String(), "", ""))
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", netip.Prefix{}, ti, "", ""))
|
||||
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)
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", "", ti6.String(), "", ""))
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", netip.Prefix{}, ti6, "", ""))
|
||||
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)
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "ca-name", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", netip.Prefix{}, netip.Prefix{}, "ca-name", ""))
|
||||
assert.Contains(t, fw.InRules.UDP[1].CANames, "ca-name")
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "", "ca-sha"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", netip.Prefix{}, netip.Prefix{}, "", "ca-sha"))
|
||||
assert.Contains(t, fw.InRules.UDP[1].CAShas, "ca-sha")
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "any", "", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "any", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
anyIp, err := netip.ParsePrefix("0.0.0.0/0")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp.String(), "", "", ""))
|
||||
assert.Nil(t, fw.OutRules.AnyProto[0].Any.Any)
|
||||
table, ok := fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("1.1.1.1"))
|
||||
assert.True(t, table.Any)
|
||||
table, ok = fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("9::9"))
|
||||
assert.False(t, ok)
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp, netip.Prefix{}, "", ""))
|
||||
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
anyIp6, err := netip.ParsePrefix("::/0")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp6.String(), "", "", ""))
|
||||
assert.Nil(t, fw.OutRules.AnyProto[0].Any.Any)
|
||||
table, ok = fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("9::9"))
|
||||
assert.True(t, table.Any)
|
||||
table, ok = fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("1.1.1.1"))
|
||||
assert.False(t, ok)
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "any", "", "", ""))
|
||||
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "", anyIp.String(), "", ""))
|
||||
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.False(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("9::9")))
|
||||
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "", anyIp6.String(), "", ""))
|
||||
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.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)
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "", "any", "", ""))
|
||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp6, netip.Prefix{}, "", ""))
|
||||
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
|
||||
|
||||
// Test error conditions
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||
require.Error(t, fw.AddRule(true, math.MaxUint8, 0, 0, []string{}, "", "", "", "", ""))
|
||||
require.Error(t, fw.AddRule(true, firewall.ProtoAny, 10, 0, []string{}, "", "", "", "", ""))
|
||||
require.Error(t, fw.AddRule(true, math.MaxUint8, 0, 0, []string{}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
require.Error(t, fw.AddRule(true, firewall.ProtoAny, 10, 0, []string{}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
}
|
||||
|
||||
func TestFirewall_Drop(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
ob := &bytes.Buffer{}
|
||||
l.SetOutput(ob)
|
||||
myVpnNetworksTable := new(bart.Lite)
|
||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
||||
|
||||
p := firewall.Packet{
|
||||
LocalAddr: netip.MustParseAddr("1.2.3.4"),
|
||||
RemoteAddr: netip.MustParseAddr("1.2.3.4"),
|
||||
@@ -205,10 +174,10 @@ func TestFirewall_Drop(t *testing.T) {
|
||||
},
|
||||
vpnAddrs: []netip.Addr{netip.MustParseAddr("1.2.3.4")},
|
||||
}
|
||||
h.buildNetworks(myVpnNetworksTable, &c)
|
||||
h.buildNetworks(c.networks, c.unsafeNetworks)
|
||||
|
||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
cp := cert.NewCAPool()
|
||||
|
||||
// Drop outbound
|
||||
@@ -227,28 +196,28 @@ func TestFirewall_Drop(t *testing.T) {
|
||||
|
||||
// ensure signer doesn't get in the way of group checks
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum-bad"))
|
||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
||||
|
||||
// test caSha doesn't drop on match
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum-bad"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum"))
|
||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
||||
|
||||
// ensure ca name doesn't get in the way of group checks
|
||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good-bad", ""))
|
||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
||||
|
||||
// test caName doesn't drop on match
|
||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good-bad", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good", ""))
|
||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
||||
}
|
||||
|
||||
@@ -257,9 +226,6 @@ func TestFirewall_DropV6(t *testing.T) {
|
||||
ob := &bytes.Buffer{}
|
||||
l.SetOutput(ob)
|
||||
|
||||
myVpnNetworksTable := new(bart.Lite)
|
||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("fd00::/7"))
|
||||
|
||||
p := firewall.Packet{
|
||||
LocalAddr: netip.MustParseAddr("fd12::34"),
|
||||
RemoteAddr: netip.MustParseAddr("fd12::34"),
|
||||
@@ -284,10 +250,10 @@ func TestFirewall_DropV6(t *testing.T) {
|
||||
},
|
||||
vpnAddrs: []netip.Addr{netip.MustParseAddr("fd12::34")},
|
||||
}
|
||||
h.buildNetworks(myVpnNetworksTable, &c)
|
||||
h.buildNetworks(c.networks, c.unsafeNetworks)
|
||||
|
||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
cp := cert.NewCAPool()
|
||||
|
||||
// Drop outbound
|
||||
@@ -306,28 +272,28 @@ func TestFirewall_DropV6(t *testing.T) {
|
||||
|
||||
// ensure signer doesn't get in the way of group checks
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum-bad"))
|
||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
||||
|
||||
// test caSha doesn't drop on match
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum-bad"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum"))
|
||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
||||
|
||||
// ensure ca name doesn't get in the way of group checks
|
||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good-bad", ""))
|
||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
||||
|
||||
// test caName doesn't drop on match
|
||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good-bad", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good", ""))
|
||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
||||
}
|
||||
|
||||
@@ -338,12 +304,12 @@ func BenchmarkFirewallTable_match(b *testing.B) {
|
||||
}
|
||||
|
||||
pfix := netip.MustParsePrefix("172.1.1.1/32")
|
||||
_ = ft.TCP.addRule(f, 10, 10, []string{"good-group"}, "good-host", pfix.String(), "", "", "")
|
||||
_ = ft.TCP.addRule(f, 100, 100, []string{"good-group"}, "good-host", "", pfix.String(), "", "")
|
||||
_ = ft.TCP.addRule(f, 10, 10, []string{"good-group"}, "good-host", pfix, netip.Prefix{}, "", "")
|
||||
_ = ft.TCP.addRule(f, 100, 100, []string{"good-group"}, "good-host", netip.Prefix{}, pfix, "", "")
|
||||
|
||||
pfix6 := netip.MustParsePrefix("fd11::11/128")
|
||||
_ = ft.TCP.addRule(f, 10, 10, []string{"good-group"}, "good-host", pfix6.String(), "", "", "")
|
||||
_ = ft.TCP.addRule(f, 100, 100, []string{"good-group"}, "good-host", "", pfix6.String(), "", "")
|
||||
_ = ft.TCP.addRule(f, 10, 10, []string{"good-group"}, "good-host", pfix6, netip.Prefix{}, "", "")
|
||||
_ = ft.TCP.addRule(f, 100, 100, []string{"good-group"}, "good-host", netip.Prefix{}, pfix6, "", "")
|
||||
cp := cert.NewCAPool()
|
||||
|
||||
b.Run("fail on proto", func(b *testing.B) {
|
||||
@@ -426,7 +392,7 @@ func BenchmarkFirewallTable_match(b *testing.B) {
|
||||
c := &cert.CachedCertificate{
|
||||
Certificate: &dummyCert{
|
||||
name: "nope",
|
||||
networks: []netip.Prefix{netip.MustParsePrefix("fd99::99/128")},
|
||||
networks: []netip.Prefix{netip.MustParsePrefix("fd99:99/128")},
|
||||
},
|
||||
InvertedGroups: map[string]struct{}{"nope": {}},
|
||||
}
|
||||
@@ -487,8 +453,6 @@ func TestFirewall_Drop2(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
ob := &bytes.Buffer{}
|
||||
l.SetOutput(ob)
|
||||
myVpnNetworksTable := new(bart.Lite)
|
||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
||||
|
||||
p := firewall.Packet{
|
||||
LocalAddr: netip.MustParseAddr("1.2.3.4"),
|
||||
@@ -514,7 +478,7 @@ func TestFirewall_Drop2(t *testing.T) {
|
||||
},
|
||||
vpnAddrs: []netip.Addr{network.Addr()},
|
||||
}
|
||||
h.buildNetworks(myVpnNetworksTable, c.Certificate)
|
||||
h.buildNetworks(c.Certificate.Networks(), c.Certificate.UnsafeNetworks())
|
||||
|
||||
c1 := cert.CachedCertificate{
|
||||
Certificate: &dummyCert{
|
||||
@@ -529,10 +493,10 @@ func TestFirewall_Drop2(t *testing.T) {
|
||||
peerCert: &c1,
|
||||
},
|
||||
}
|
||||
h1.buildNetworks(myVpnNetworksTable, c1.Certificate)
|
||||
h1.buildNetworks(c1.Certificate.Networks(), c1.Certificate.UnsafeNetworks())
|
||||
|
||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||
require.NoError(t, fw.AddRule(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"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
cp := cert.NewCAPool()
|
||||
|
||||
// h1/c1 lacks the proper groups
|
||||
@@ -546,8 +510,6 @@ func TestFirewall_Drop3(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
ob := &bytes.Buffer{}
|
||||
l.SetOutput(ob)
|
||||
myVpnNetworksTable := new(bart.Lite)
|
||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
||||
|
||||
p := firewall.Packet{
|
||||
LocalAddr: netip.MustParseAddr("1.2.3.4"),
|
||||
@@ -579,7 +541,7 @@ func TestFirewall_Drop3(t *testing.T) {
|
||||
},
|
||||
vpnAddrs: []netip.Addr{network.Addr()},
|
||||
}
|
||||
h1.buildNetworks(myVpnNetworksTable, c1.Certificate)
|
||||
h1.buildNetworks(c1.Certificate.Networks(), c1.Certificate.UnsafeNetworks())
|
||||
|
||||
c2 := cert.CachedCertificate{
|
||||
Certificate: &dummyCert{
|
||||
@@ -594,7 +556,7 @@ func TestFirewall_Drop3(t *testing.T) {
|
||||
},
|
||||
vpnAddrs: []netip.Addr{network.Addr()},
|
||||
}
|
||||
h2.buildNetworks(myVpnNetworksTable, c2.Certificate)
|
||||
h2.buildNetworks(c2.Certificate.Networks(), c2.Certificate.UnsafeNetworks())
|
||||
|
||||
c3 := cert.CachedCertificate{
|
||||
Certificate: &dummyCert{
|
||||
@@ -609,11 +571,11 @@ func TestFirewall_Drop3(t *testing.T) {
|
||||
},
|
||||
vpnAddrs: []netip.Addr{network.Addr()},
|
||||
}
|
||||
h3.buildNetworks(myVpnNetworksTable, c3.Certificate)
|
||||
h3.buildNetworks(c3.Certificate.Networks(), c3.Certificate.UnsafeNetworks())
|
||||
|
||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "host1", "", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "", "", "", "signer-sha"))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "host1", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-sha"))
|
||||
cp := cert.NewCAPool()
|
||||
|
||||
// c1 should pass because host match
|
||||
@@ -627,7 +589,7 @@ func TestFirewall_Drop3(t *testing.T) {
|
||||
|
||||
// Test a remote address match
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "1.2.3.4/24", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", netip.MustParsePrefix("1.2.3.4/24"), netip.Prefix{}, "", ""))
|
||||
require.NoError(t, fw.Drop(p, true, &h1, cp, nil))
|
||||
}
|
||||
|
||||
@@ -635,8 +597,6 @@ func TestFirewall_Drop3V6(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
ob := &bytes.Buffer{}
|
||||
l.SetOutput(ob)
|
||||
myVpnNetworksTable := new(bart.Lite)
|
||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("fd00::/7"))
|
||||
|
||||
p := firewall.Packet{
|
||||
LocalAddr: netip.MustParseAddr("fd12::34"),
|
||||
@@ -660,12 +620,12 @@ func TestFirewall_Drop3V6(t *testing.T) {
|
||||
},
|
||||
vpnAddrs: []netip.Addr{network.Addr()},
|
||||
}
|
||||
h.buildNetworks(myVpnNetworksTable, c.Certificate)
|
||||
h.buildNetworks(c.Certificate.Networks(), c.Certificate.UnsafeNetworks())
|
||||
|
||||
// Test a remote address match
|
||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||
cp := cert.NewCAPool()
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "fd12::34/120", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", netip.MustParsePrefix("fd12::34/120"), netip.Prefix{}, "", ""))
|
||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
||||
}
|
||||
|
||||
@@ -673,8 +633,6 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
ob := &bytes.Buffer{}
|
||||
l.SetOutput(ob)
|
||||
myVpnNetworksTable := new(bart.Lite)
|
||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
||||
|
||||
p := firewall.Packet{
|
||||
LocalAddr: netip.MustParseAddr("1.2.3.4"),
|
||||
@@ -701,10 +659,10 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
||||
},
|
||||
vpnAddrs: []netip.Addr{network.Addr()},
|
||||
}
|
||||
h.buildNetworks(myVpnNetworksTable, c.Certificate)
|
||||
h.buildNetworks(c.Certificate.Networks(), c.Certificate.UnsafeNetworks())
|
||||
|
||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
cp := cert.NewCAPool()
|
||||
|
||||
// Drop outbound
|
||||
@@ -717,7 +675,7 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
||||
|
||||
oldFw := fw
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 10, 10, []string{"any"}, "", "", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 10, 10, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
fw.Conntrack = oldFw.Conntrack
|
||||
fw.rulesVersion = oldFw.rulesVersion + 1
|
||||
|
||||
@@ -726,7 +684,7 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
||||
|
||||
oldFw = fw
|
||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 11, 11, []string{"any"}, "", "", "", "", ""))
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 11, 11, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
|
||||
fw.Conntrack = oldFw.Conntrack
|
||||
fw.rulesVersion = oldFw.rulesVersion + 1
|
||||
|
||||
@@ -734,52 +692,6 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
||||
assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrNoMatchingRule)
|
||||
}
|
||||
|
||||
func TestFirewall_DropIPSpoofing(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
ob := &bytes.Buffer{}
|
||||
l.SetOutput(ob)
|
||||
myVpnNetworksTable := new(bart.Lite)
|
||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("192.0.2.1/24"))
|
||||
|
||||
c := cert.CachedCertificate{
|
||||
Certificate: &dummyCert{
|
||||
name: "host-owner",
|
||||
networks: []netip.Prefix{netip.MustParsePrefix("192.0.2.1/24")},
|
||||
},
|
||||
}
|
||||
|
||||
c1 := cert.CachedCertificate{
|
||||
Certificate: &dummyCert{
|
||||
name: "host",
|
||||
networks: []netip.Prefix{netip.MustParsePrefix("192.0.2.2/24")},
|
||||
unsafeNetworks: []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")},
|
||||
},
|
||||
}
|
||||
h1 := HostInfo{
|
||||
ConnectionState: &ConnectionState{
|
||||
peerCert: &c1,
|
||||
},
|
||||
vpnAddrs: []netip.Addr{c1.Certificate.Networks()[0].Addr()},
|
||||
}
|
||||
h1.buildNetworks(myVpnNetworksTable, c1.Certificate)
|
||||
|
||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "", "", "", ""))
|
||||
cp := cert.NewCAPool()
|
||||
|
||||
// Packet spoofed by `c1`. Note that the remote addr is not a valid one.
|
||||
p := firewall.Packet{
|
||||
LocalAddr: netip.MustParseAddr("192.0.2.1"),
|
||||
RemoteAddr: netip.MustParseAddr("192.0.2.3"),
|
||||
LocalPort: 1,
|
||||
RemotePort: 1,
|
||||
Protocol: firewall.ProtoUDP,
|
||||
Fragment: false,
|
||||
}
|
||||
assert.Equal(t, fw.Drop(p, true, &h1, cp, nil), ErrInvalidRemoteIP)
|
||||
}
|
||||
|
||||
func BenchmarkLookup(b *testing.B) {
|
||||
ml := func(m map[string]struct{}, a [][]string) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
@@ -959,28 +871,28 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
||||
mf := &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "tcp", "host": "a"}}}
|
||||
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: netip.Prefix{}, localIp: netip.Prefix{}}, mf.lastCall)
|
||||
|
||||
// Test adding udp rule
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "udp", "host": "a"}}}
|
||||
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: netip.Prefix{}, localIp: netip.Prefix{}}, mf.lastCall)
|
||||
|
||||
// Test adding icmp rule
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
|
||||
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: netip.Prefix{}, localIp: netip.Prefix{}}, mf.lastCall)
|
||||
|
||||
// Test adding any rule
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
|
||||
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: netip.Prefix{}, localIp: netip.Prefix{}}, mf.lastCall)
|
||||
|
||||
// Test adding rule with cidr
|
||||
cidr := netip.MustParsePrefix("10.0.0.0/8")
|
||||
@@ -988,14 +900,14 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr.String()}}}
|
||||
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, localIp: netip.Prefix{}}, mf.lastCall)
|
||||
|
||||
// Test adding rule with local_cidr
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr.String()}}}
|
||||
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: netip.Prefix{}, localIp: cidr}, mf.lastCall)
|
||||
|
||||
// Test adding rule with cidr ipv6
|
||||
cidr6 := netip.MustParsePrefix("fd00::/8")
|
||||
@@ -1003,75 +915,49 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr6.String()}}}
|
||||
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)
|
||||
|
||||
// Test adding rule with any cidr
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "any"}}}
|
||||
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)
|
||||
|
||||
// Test adding rule with junk cidr
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "junk/junk"}}}
|
||||
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")
|
||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr6, localIp: netip.Prefix{}}, mf.lastCall)
|
||||
|
||||
// Test adding rule with local_cidr ipv6
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr6.String()}}}
|
||||
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)
|
||||
|
||||
// Test adding rule with any local_cidr
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "any"}}}
|
||||
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)
|
||||
|
||||
// Test adding rule with junk local_cidr
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "junk/junk"}}}
|
||||
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")
|
||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: netip.Prefix{}, localIp: cidr6}, mf.lastCall)
|
||||
|
||||
// Test adding rule with ca_sha
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_sha": "12312313123"}}}
|
||||
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: netip.Prefix{}, localIp: netip.Prefix{}, caSha: "12312313123"}, mf.lastCall)
|
||||
|
||||
// Test adding rule with ca_name
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_name": "root01"}}}
|
||||
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: netip.Prefix{}, localIp: netip.Prefix{}, caName: "root01"}, mf.lastCall)
|
||||
|
||||
// Test single group
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a"}}}
|
||||
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: netip.Prefix{}, localIp: netip.Prefix{}}, mf.lastCall)
|
||||
|
||||
// Test single groups
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": "a"}}}
|
||||
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: netip.Prefix{}, localIp: netip.Prefix{}}, mf.lastCall)
|
||||
|
||||
// Test multiple AND groups
|
||||
conf = config.NewC(l)
|
||||
mf = &mockFirewall{}
|
||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": []string{"a", "b"}}}}
|
||||
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: netip.Prefix{}, localIp: netip.Prefix{}}, mf.lastCall)
|
||||
|
||||
// Test Add error
|
||||
conf = config.NewC(l)
|
||||
@@ -1094,7 +980,7 @@ func TestFirewall_convertRule(t *testing.T) {
|
||||
r, err := convertRule(l, c, "test", 1)
|
||||
assert.Contains(t, ob.String(), "test rule #1; group was an array with a single value, converting to simple value")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"group1"}, r.Groups)
|
||||
assert.Equal(t, "group1", r.Group)
|
||||
|
||||
// Ensure group array of > 1 is errord
|
||||
ob.Reset()
|
||||
@@ -1114,228 +1000,7 @@ func TestFirewall_convertRule(t *testing.T) {
|
||||
|
||||
r, err = convertRule(l, c, "test", 1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"group1"}, r.Groups)
|
||||
}
|
||||
|
||||
func TestFirewall_convertRuleSanity(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
ob := &bytes.Buffer{}
|
||||
l.SetOutput(ob)
|
||||
|
||||
noWarningPlease := []map[string]any{
|
||||
{"group": "group1"},
|
||||
{"groups": []any{"group2"}},
|
||||
{"host": "bob"},
|
||||
{"cidr": "1.1.1.1/1"},
|
||||
{"groups": []any{"group2"}, "host": "bob"},
|
||||
{"cidr": "1.1.1.1/1", "host": "bob"},
|
||||
{"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
|
||||
{"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
|
||||
}
|
||||
for _, c := range noWarningPlease {
|
||||
r, err := convertRule(l, c, "test", 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, r.sanity(), "should not generate a sanity warning, %+v", c)
|
||||
}
|
||||
|
||||
yesWarningPlease := []map[string]any{
|
||||
{"group": "group1"},
|
||||
{"groups": []any{"group2"}},
|
||||
{"cidr": "1.1.1.1/1"},
|
||||
{"groups": []any{"group2"}, "host": "bob"},
|
||||
{"cidr": "1.1.1.1/1", "host": "bob"},
|
||||
{"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
|
||||
{"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
|
||||
}
|
||||
for _, c := range yesWarningPlease {
|
||||
c["host"] = "any"
|
||||
r, err := convertRule(l, c, "test", 1)
|
||||
require.NoError(t, err)
|
||||
err = r.sanity()
|
||||
require.Error(t, err, "I wanted a warning: %+v", c)
|
||||
}
|
||||
//reset the list
|
||||
yesWarningPlease = []map[string]any{
|
||||
{"group": "group1"},
|
||||
{"groups": []any{"group2"}},
|
||||
{"cidr": "1.1.1.1/1"},
|
||||
{"groups": []any{"group2"}, "host": "bob"},
|
||||
{"cidr": "1.1.1.1/1", "host": "bob"},
|
||||
{"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
|
||||
{"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
|
||||
}
|
||||
for _, c := range yesWarningPlease {
|
||||
r, err := convertRule(l, c, "test", 1)
|
||||
require.NoError(t, err)
|
||||
r.Groups = append(r.Groups, "any")
|
||||
err = r.sanity()
|
||||
require.Error(t, err, "I wanted a warning: %+v", c)
|
||||
}
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
h *HostInfo
|
||||
p firewall.Packet
|
||||
c cert.Certificate
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *testcase) Test(t *testing.T, fw *Firewall) {
|
||||
t.Helper()
|
||||
cp := cert.NewCAPool()
|
||||
resetConntrack(fw)
|
||||
err := fw.Drop(c.p, true, c.h, cp, nil)
|
||||
if c.err == nil {
|
||||
require.NoError(t, err, "failed to not drop remote address %s", c.p.RemoteAddr)
|
||||
} else {
|
||||
require.ErrorIs(t, c.err, err, "failed to drop remote address %s", c.p.RemoteAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func buildTestCase(setup testsetup, err error, theirPrefixes ...netip.Prefix) testcase {
|
||||
c1 := dummyCert{
|
||||
name: "host1",
|
||||
networks: theirPrefixes,
|
||||
groups: []string{"default-group"},
|
||||
issuer: "signer-shasum",
|
||||
}
|
||||
h := HostInfo{
|
||||
ConnectionState: &ConnectionState{
|
||||
peerCert: &cert.CachedCertificate{
|
||||
Certificate: &c1,
|
||||
InvertedGroups: map[string]struct{}{"default-group": {}},
|
||||
},
|
||||
},
|
||||
vpnAddrs: make([]netip.Addr, len(theirPrefixes)),
|
||||
}
|
||||
for i := range theirPrefixes {
|
||||
h.vpnAddrs[i] = theirPrefixes[i].Addr()
|
||||
}
|
||||
h.buildNetworks(setup.myVpnNetworksTable, &c1)
|
||||
p := firewall.Packet{
|
||||
LocalAddr: setup.c.Networks()[0].Addr(), //todo?
|
||||
RemoteAddr: theirPrefixes[0].Addr(),
|
||||
LocalPort: 10,
|
||||
RemotePort: 90,
|
||||
Protocol: firewall.ProtoUDP,
|
||||
Fragment: false,
|
||||
}
|
||||
return testcase{
|
||||
h: &h,
|
||||
p: p,
|
||||
c: &c1,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
type testsetup struct {
|
||||
c dummyCert
|
||||
myVpnNetworksTable *bart.Lite
|
||||
fw *Firewall
|
||||
}
|
||||
|
||||
func newSetup(t *testing.T, l *logrus.Logger, myPrefixes ...netip.Prefix) testsetup {
|
||||
c := dummyCert{
|
||||
name: "me",
|
||||
networks: myPrefixes,
|
||||
groups: []string{"default-group"},
|
||||
issuer: "signer-shasum",
|
||||
}
|
||||
|
||||
return newSetupFromCert(t, l, c)
|
||||
}
|
||||
|
||||
func newSetupFromCert(t *testing.T, l *logrus.Logger, c dummyCert) testsetup {
|
||||
myVpnNetworksTable := new(bart.Lite)
|
||||
for _, prefix := range c.Networks() {
|
||||
myVpnNetworksTable.Insert(prefix)
|
||||
}
|
||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
||||
|
||||
return testsetup{
|
||||
c: c,
|
||||
fw: fw,
|
||||
myVpnNetworksTable: myVpnNetworksTable,
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirewall_Drop_EnforceIPMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
l := test.NewLogger()
|
||||
ob := &bytes.Buffer{}
|
||||
l.SetOutput(ob)
|
||||
|
||||
myPrefix := netip.MustParsePrefix("1.1.1.1/8")
|
||||
// for now, it's okay that these are all "incoming", the logic this test tries to check doesn't care about in/out
|
||||
t.Run("allow inbound all matching", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
setup := newSetup(t, l, myPrefix)
|
||||
tc := buildTestCase(setup, nil, netip.MustParsePrefix("1.2.3.4/24"))
|
||||
tc.Test(t, setup.fw)
|
||||
})
|
||||
t.Run("allow inbound local matching", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
setup := newSetup(t, l, myPrefix)
|
||||
tc := buildTestCase(setup, ErrInvalidLocalIP, netip.MustParsePrefix("1.2.3.4/24"))
|
||||
tc.p.LocalAddr = netip.MustParseAddr("1.2.3.8")
|
||||
tc.Test(t, setup.fw)
|
||||
})
|
||||
t.Run("block inbound remote mismatched", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
setup := newSetup(t, l, myPrefix)
|
||||
tc := buildTestCase(setup, ErrInvalidRemoteIP, netip.MustParsePrefix("1.2.3.4/24"))
|
||||
tc.p.RemoteAddr = netip.MustParseAddr("9.9.9.9")
|
||||
tc.Test(t, setup.fw)
|
||||
})
|
||||
t.Run("Block a vpn peer packet", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
setup := newSetup(t, l, myPrefix)
|
||||
tc := buildTestCase(setup, ErrPeerRejected, netip.MustParsePrefix("2.2.2.2/24"))
|
||||
tc.Test(t, setup.fw)
|
||||
})
|
||||
twoPrefixes := []netip.Prefix{
|
||||
netip.MustParsePrefix("1.2.3.4/24"), netip.MustParsePrefix("2.2.2.2/24"),
|
||||
}
|
||||
t.Run("allow inbound one matching", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
setup := newSetup(t, l, myPrefix)
|
||||
tc := buildTestCase(setup, nil, twoPrefixes...)
|
||||
tc.Test(t, setup.fw)
|
||||
})
|
||||
t.Run("block inbound multimismatch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
setup := newSetup(t, l, myPrefix)
|
||||
tc := buildTestCase(setup, ErrInvalidRemoteIP, twoPrefixes...)
|
||||
tc.p.RemoteAddr = netip.MustParseAddr("9.9.9.9")
|
||||
tc.Test(t, setup.fw)
|
||||
})
|
||||
t.Run("allow inbound 2nd one matching", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
setup2 := newSetup(t, l, netip.MustParsePrefix("2.2.2.1/24"))
|
||||
tc := buildTestCase(setup2, nil, twoPrefixes...)
|
||||
tc.p.RemoteAddr = twoPrefixes[1].Addr()
|
||||
tc.Test(t, setup2.fw)
|
||||
})
|
||||
t.Run("allow inbound unsafe route", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
unsafePrefix := netip.MustParsePrefix("192.168.0.0/24")
|
||||
c := dummyCert{
|
||||
name: "me",
|
||||
networks: []netip.Prefix{myPrefix},
|
||||
unsafeNetworks: []netip.Prefix{unsafePrefix},
|
||||
groups: []string{"default-group"},
|
||||
issuer: "signer-shasum",
|
||||
}
|
||||
unsafeSetup := newSetupFromCert(t, l, c)
|
||||
tc := buildTestCase(unsafeSetup, nil, twoPrefixes...)
|
||||
tc.p.LocalAddr = netip.MustParseAddr("192.168.0.3")
|
||||
tc.err = ErrNoMatchingRule
|
||||
tc.Test(t, unsafeSetup.fw) //should hit firewall and bounce off
|
||||
require.NoError(t, unsafeSetup.fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", unsafePrefix.String(), "", ""))
|
||||
tc.err = nil
|
||||
tc.Test(t, unsafeSetup.fw) //should pass
|
||||
})
|
||||
assert.Equal(t, "group1", r.Group)
|
||||
}
|
||||
|
||||
type addRuleCall struct {
|
||||
@@ -1345,8 +1010,8 @@ type addRuleCall struct {
|
||||
endPort int32
|
||||
groups []string
|
||||
host string
|
||||
ip string
|
||||
localIp string
|
||||
ip netip.Prefix
|
||||
localIp netip.Prefix
|
||||
caName string
|
||||
caSha string
|
||||
}
|
||||
@@ -1356,7 +1021,7 @@ type mockFirewall struct {
|
||||
nextCallReturn error
|
||||
}
|
||||
|
||||
func (mf *mockFirewall) AddRule(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 netip.Prefix, localIp netip.Prefix, caName string, caSha string) error {
|
||||
mf.lastCall = addRuleCall{
|
||||
incoming: incoming,
|
||||
proto: proto,
|
||||
|
||||
@@ -4,36 +4,34 @@ go 1.25
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2
|
||||
filippo.io/bigmod v0.1.0
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
github.com/armon/go-radix v1.0.0
|
||||
github.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432
|
||||
github.com/flynn/noise v1.1.0
|
||||
github.com/gaissmai/bart v0.26.0
|
||||
github.com/gaissmai/bart v0.20.4
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/kardianos/service v1.2.4
|
||||
github.com/miekg/dns v1.1.70
|
||||
github.com/kardianos/service v1.2.2
|
||||
github.com/miekg/dns v1.1.65
|
||||
github.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b
|
||||
github.com/nbrownus/go-metrics-prometheus v0.0.0-20210712211119-974a6260965f
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.47.0
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090
|
||||
golang.org/x/net v0.49.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sys v0.40.0
|
||||
golang.org/x/term v0.39.0
|
||||
golang.org/x/net v0.39.0
|
||||
golang.org/x/sync v0.13.0
|
||||
golang.org/x/sys v0.32.0
|
||||
golang.org/x/term v0.31.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
google.golang.org/protobuf v1.36.11
|
||||
google.golang.org/protobuf v1.36.6
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe
|
||||
)
|
||||
@@ -45,12 +43,11 @@ require (
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/mod v0.23.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
golang.org/x/tools v0.30.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
filippo.io/bigmod v0.1.0 h1:UNzDk7y9ADKST+axd9skUpBQeW7fG2KrTZyOE4uGQy8=
|
||||
filippo.io/bigmod v0.1.0/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -26,8 +24,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
|
||||
github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
|
||||
github.com/gaissmai/bart v0.26.0 h1:xOZ57E9hJLBiQaSyeZa9wgWhGuzfGACgqp4BE77OkO0=
|
||||
github.com/gaissmai/bart v0.26.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
|
||||
github.com/gaissmai/bart v0.20.4 h1:Ik47r1fy3jRVU+1eYzKSW3ho2UgBVTVnUS8O993584U=
|
||||
github.com/gaissmai/bart v0.20.4/go.mod h1:cEed+ge8dalcbpi8wtS9x9m2hn/fNJH5suhdGQOHnYk=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
@@ -66,8 +64,8 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kardianos/service v1.2.4 h1:XNlGtZOYNx2u91urOdg/Kfmc+gfmuIo1Dd3rEi2OgBk=
|
||||
github.com/kardianos/service v1.2.4/go.mod h1:E4V9ufUuY82F7Ztlu1eN9VXWIQxg8NoLQlmFe0MtrXc=
|
||||
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
|
||||
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
@@ -85,8 +83,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
|
||||
github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
|
||||
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
|
||||
github.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b h1:J/AzCvg5z0Hn1rqZUJjpbzALUmkKX0Zwbc/i4fw7Sfk=
|
||||
github.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -108,24 +106,24 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
@@ -133,8 +131,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=
|
||||
@@ -145,35 +143,29 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -184,8 +176,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -193,8 +185,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -205,16 +197,18 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/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-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -225,8 +219,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -245,8 +239,8 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
+143
-147
@@ -1,8 +1,8 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/flynn/noise"
|
||||
@@ -23,17 +23,13 @@ func ixHandshakeStage0(f *Interface, hh *HandshakeHostInfo) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we're connecting to a v6 address we must use a v2 cert
|
||||
cs := f.pki.getCertState()
|
||||
v := cs.initiatingVersion
|
||||
if hh.initiatingVersionOverride != cert.VersionPre1 {
|
||||
v = hh.initiatingVersionOverride
|
||||
} else if v < cert.Version2 {
|
||||
// If we're connecting to a v6 address we should encourage use of a V2 cert
|
||||
for _, a := range hh.hostinfo.vpnAddrs {
|
||||
if a.Is6() {
|
||||
v = cert.Version2
|
||||
break
|
||||
}
|
||||
for _, a := range hh.hostinfo.vpnAddrs {
|
||||
if a.Is6() {
|
||||
v = cert.Version2
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +48,6 @@ func ixHandshakeStage0(f *Interface, hh *HandshakeHostInfo) bool {
|
||||
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).
|
||||
WithField("certVersion", v).
|
||||
Error("Unable to handshake with host because no certificate handshake bytes is available")
|
||||
return false
|
||||
}
|
||||
|
||||
ci, err := NewConnectionState(f.l, cs, crt, true, noise.HandshakeIX)
|
||||
@@ -100,20 +95,19 @@ func ixHandshakeStage0(f *Interface, hh *HandshakeHostInfo) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H) {
|
||||
func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet []byte, h *header.H) {
|
||||
cs := f.pki.getCertState()
|
||||
crt := cs.GetDefaultCertificate()
|
||||
if crt == nil {
|
||||
f.l.WithField("from", via).
|
||||
f.l.WithField("udpAddr", addr).
|
||||
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).
|
||||
WithField("certVersion", cs.initiatingVersion).
|
||||
Error("Unable to handshake with host because no certificate is available")
|
||||
return
|
||||
}
|
||||
|
||||
ci, err := NewConnectionState(f.l, cs, crt, false, noise.HandshakeIX)
|
||||
if err != nil {
|
||||
f.l.WithError(err).WithField("from", via).
|
||||
f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||
Error("Failed to create connection state")
|
||||
return
|
||||
@@ -124,7 +118,7 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
|
||||
msg, _, _, err := ci.H.ReadMessage(nil, packet[header.Len:])
|
||||
if err != nil {
|
||||
f.l.WithError(err).WithField("from", via).
|
||||
f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||
Error("Failed to call noise.ReadMessage")
|
||||
return
|
||||
@@ -133,7 +127,7 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
hs := &NebulaHandshake{}
|
||||
err = hs.Unmarshal(msg)
|
||||
if err != nil || hs.Details == nil {
|
||||
f.l.WithError(err).WithField("from", via).
|
||||
f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||
Error("Failed unmarshal handshake message")
|
||||
return
|
||||
@@ -141,7 +135,7 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
|
||||
rc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())
|
||||
if err != nil {
|
||||
f.l.WithError(err).WithField("from", via).
|
||||
f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||
Info("Handshake did not contain a certificate")
|
||||
return
|
||||
@@ -149,12 +143,12 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
|
||||
remoteCert, err := f.pki.GetCAPool().VerifyCertificate(time.Now(), rc)
|
||||
if err != nil {
|
||||
fp, fperr := rc.Fingerprint()
|
||||
if fperr != nil {
|
||||
fp, err := rc.Fingerprint()
|
||||
if err != nil {
|
||||
fp = "<error generating certificate fingerprint>"
|
||||
}
|
||||
|
||||
e := f.l.WithError(err).WithField("from", via).
|
||||
e := f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||
WithField("certVpnNetworks", rc.Networks()).
|
||||
WithField("certFingerprint", fp)
|
||||
@@ -167,49 +161,39 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(remoteCert.Certificate.PublicKey(), ci.H.PeerStatic()) {
|
||||
f.l.WithField("from", via).
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||
WithField("cert", remoteCert).Info("public key mismatch between certificate and handshake")
|
||||
return
|
||||
}
|
||||
|
||||
if remoteCert.Certificate.Version() != ci.myCert.Version() {
|
||||
// We started off using the wrong certificate version, lets see if we can match the version that was sent to us
|
||||
myCertOtherVersion := cs.getCertificate(remoteCert.Certificate.Version())
|
||||
if myCertOtherVersion == nil {
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
f.l.WithError(err).WithFields(m{
|
||||
"from": via,
|
||||
"handshake": m{"stage": 1, "style": "ix_psk0"},
|
||||
"cert": remoteCert,
|
||||
}).Debug("Might be unable to handshake with host due to missing certificate version")
|
||||
}
|
||||
} else {
|
||||
// Record the certificate we are actually using
|
||||
ci.myCert = myCertOtherVersion
|
||||
rc := cs.getCertificate(remoteCert.Certificate.Version())
|
||||
if rc == nil {
|
||||
f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).WithField("cert", remoteCert).
|
||||
Info("Unable to handshake with host due to missing certificate version")
|
||||
return
|
||||
}
|
||||
|
||||
// Record the certificate we are actually using
|
||||
ci.myCert = rc
|
||||
}
|
||||
|
||||
if len(remoteCert.Certificate.Networks()) == 0 {
|
||||
f.l.WithError(err).WithField("from", via).
|
||||
f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("cert", remoteCert).
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||
Info("No networks in certificate")
|
||||
return
|
||||
}
|
||||
|
||||
var vpnAddrs []netip.Addr
|
||||
var filteredNetworks []netip.Prefix
|
||||
certName := remoteCert.Certificate.Name()
|
||||
certVersion := remoteCert.Certificate.Version()
|
||||
fingerprint := remoteCert.Fingerprint
|
||||
issuer := remoteCert.Certificate.Issuer()
|
||||
vpnNetworks := remoteCert.Certificate.Networks()
|
||||
|
||||
anyVpnAddrsInCommon := false
|
||||
vpnAddrs := make([]netip.Addr, len(vpnNetworks))
|
||||
for i, network := range vpnNetworks {
|
||||
if f.myVpnAddrsTable.Contains(network.Addr()) {
|
||||
f.l.WithField("vpnNetworks", vpnNetworks).WithField("from", via).
|
||||
for _, network := range remoteCert.Certificate.Networks() {
|
||||
vpnAddr := network.Addr()
|
||||
if f.myVpnAddrsTable.Contains(vpnAddr) {
|
||||
f.l.WithField("vpnAddr", vpnAddr).WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("fingerprint", fingerprint).
|
||||
@@ -217,24 +201,38 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).Error("Refusing to handshake with myself")
|
||||
return
|
||||
}
|
||||
vpnAddrs[i] = network.Addr()
|
||||
if f.myVpnNetworksTable.Contains(network.Addr()) {
|
||||
anyVpnAddrsInCommon = true
|
||||
|
||||
// vpnAddrs outside our vpn networks are of no use to us, filter them out
|
||||
if !f.myVpnNetworksTable.Contains(vpnAddr) {
|
||||
continue
|
||||
}
|
||||
|
||||
filteredNetworks = append(filteredNetworks, network)
|
||||
vpnAddrs = append(vpnAddrs, vpnAddr)
|
||||
}
|
||||
|
||||
if !via.IsRelayed {
|
||||
if len(vpnAddrs) == 0 {
|
||||
f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("fingerprint", fingerprint).
|
||||
WithField("issuer", issuer).
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).Error("No usable vpn addresses from host, refusing handshake")
|
||||
return
|
||||
}
|
||||
|
||||
if addr.IsValid() {
|
||||
// addr can be invalid when the tunnel is being relayed.
|
||||
// We only want to apply the remote allow list for direct tunnels here
|
||||
if !f.lightHouse.GetRemoteAllowList().AllowAll(vpnAddrs, via.UdpAddr.Addr()) {
|
||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||
Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||
if !f.lightHouse.GetRemoteAllowList().AllowAll(vpnAddrs, addr.Addr()) {
|
||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
myIndex, err := generateIndex(f.l)
|
||||
if err != nil {
|
||||
f.l.WithError(err).WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||
f.l.WithError(err).WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("fingerprint", fingerprint).
|
||||
@@ -257,30 +255,26 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
},
|
||||
}
|
||||
|
||||
msgRxL := f.l.WithFields(m{
|
||||
"vpnAddrs": vpnAddrs,
|
||||
"from": via,
|
||||
"certName": certName,
|
||||
"certVersion": certVersion,
|
||||
"fingerprint": fingerprint,
|
||||
"issuer": issuer,
|
||||
"initiatorIndex": hs.Details.InitiatorIndex,
|
||||
"responderIndex": hs.Details.ResponderIndex,
|
||||
"remoteIndex": h.RemoteIndex,
|
||||
"handshake": m{"stage": 1, "style": "ix_psk0"},
|
||||
})
|
||||
|
||||
if anyVpnAddrsInCommon {
|
||||
msgRxL.Info("Handshake message received")
|
||||
} else {
|
||||
//todo warn if not lighthouse or relay?
|
||||
msgRxL.Info("Handshake message received, but no vpnNetworks in common.")
|
||||
}
|
||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
||||
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": 1, "style": "ix_psk0"}).
|
||||
Info("Handshake message received")
|
||||
|
||||
hs.Details.ResponderIndex = myIndex
|
||||
hs.Details.Cert = cs.getHandshakeBytes(ci.myCert.Version())
|
||||
if hs.Details.Cert == nil {
|
||||
msgRxL.WithField("myCertVersion", ci.myCert.Version()).
|
||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
||||
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": 1, "style": "ix_psk0"}).
|
||||
WithField("certVersion", ci.myCert.Version()).
|
||||
Error("Unable to handshake with host because no certificate handshake bytes is available")
|
||||
return
|
||||
}
|
||||
@@ -291,7 +285,7 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
|
||||
hsBytes, err := hs.Marshal()
|
||||
if err != nil {
|
||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("fingerprint", fingerprint).
|
||||
@@ -303,7 +297,7 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
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)
|
||||
if err != nil {
|
||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("fingerprint", fingerprint).
|
||||
@@ -311,7 +305,7 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).Error("Failed to call noise.WriteMessage")
|
||||
return
|
||||
} else if dKey == nil || eKey == nil {
|
||||
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("fingerprint", fingerprint).
|
||||
@@ -337,17 +331,15 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
ci.eKey = NewNebulaCipherState(eKey)
|
||||
|
||||
hostinfo.remotes = f.lightHouse.QueryCache(vpnAddrs)
|
||||
if !via.IsRelayed {
|
||||
hostinfo.SetRemote(via.UdpAddr)
|
||||
}
|
||||
hostinfo.buildNetworks(f.myVpnNetworksTable, remoteCert.Certificate)
|
||||
hostinfo.SetRemote(addr)
|
||||
hostinfo.buildNetworks(filteredNetworks, remoteCert.Certificate.UnsafeNetworks())
|
||||
|
||||
existing, err := f.handshakeManager.CheckAndComplete(hostinfo, 0, f)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case ErrAlreadySeen:
|
||||
// Update remote if preferred
|
||||
if existing.SetRemoteIfPreferred(f.hostMap, via) {
|
||||
if existing.SetRemoteIfPreferred(f.hostMap, addr) {
|
||||
// Send a test packet to ensure the other side has also switched to
|
||||
// the preferred remote
|
||||
f.SendMessageToVpnAddr(header.Test, header.TestRequest, vpnAddrs[0], []byte(""), make([]byte, 12, 12), make([]byte, mtu))
|
||||
@@ -355,21 +347,21 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
|
||||
msg = existing.HandshakePacket[2]
|
||||
f.messageMetrics.Tx(header.Handshake, header.MessageSubType(msg[1]), 1)
|
||||
if !via.IsRelayed {
|
||||
err := f.outside.WriteTo(msg, via.UdpAddr)
|
||||
if addr.IsValid() {
|
||||
err := f.outside.WriteTo(msg, addr)
|
||||
if err != nil {
|
||||
f.l.WithField("vpnAddrs", existing.vpnAddrs).WithField("from", via).
|
||||
f.l.WithField("vpnAddrs", existing.vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("cached", true).
|
||||
WithError(err).Error("Failed to send handshake message")
|
||||
} else {
|
||||
f.l.WithField("vpnAddrs", existing.vpnAddrs).WithField("from", via).
|
||||
f.l.WithField("vpnAddrs", existing.vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("cached", true).
|
||||
Info("Handshake message sent")
|
||||
}
|
||||
return
|
||||
} else {
|
||||
if via.relay == nil {
|
||||
f.l.Error("Handshake send failed: both addr and via.relay are nil.")
|
||||
if via == nil {
|
||||
f.l.Error("Handshake send failed: both addr and via are nil.")
|
||||
return
|
||||
}
|
||||
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
|
||||
@@ -381,7 +373,7 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
}
|
||||
case ErrExistingHostInfo:
|
||||
// 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("from", via).
|
||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("oldHandshakeTime", existing.lastHandshakeTime).
|
||||
@@ -397,7 +389,7 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
return
|
||||
case ErrLocalIndexCollision:
|
||||
// This means we failed to insert because of collision on localIndexId. Just let the next handshake packet retry
|
||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("fingerprint", fingerprint).
|
||||
@@ -410,7 +402,7 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
default:
|
||||
// Shouldn't happen, but just in case someone adds a new error type to CheckAndComplete
|
||||
// And we forget to update it here
|
||||
f.l.WithError(err).WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||
f.l.WithError(err).WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("fingerprint", fingerprint).
|
||||
@@ -424,23 +416,30 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
|
||||
// Do the send
|
||||
f.messageMetrics.Tx(header.Handshake, header.MessageSubType(msg[1]), 1)
|
||||
if !via.IsRelayed {
|
||||
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 addr.IsValid() {
|
||||
err = f.outside.WriteTo(msg, addr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to send handshake")
|
||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
||||
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 {
|
||||
log.Info("Handshake message sent")
|
||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
||||
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 {
|
||||
if via.relay == nil {
|
||||
f.l.Error("Handshake send failed: both addr and via.relay are nil.")
|
||||
if via == nil {
|
||||
f.l.Error("Handshake send failed: both addr and via are nil.")
|
||||
return
|
||||
}
|
||||
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
|
||||
@@ -465,7 +464,7 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
||||
return
|
||||
}
|
||||
|
||||
func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packet []byte, h *header.H) bool {
|
||||
func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *HandshakeHostInfo, packet []byte, h *header.H) bool {
|
||||
if hh == nil {
|
||||
// Nothing here to tear down, got a bogus stage 2 packet
|
||||
return true
|
||||
@@ -475,10 +474,10 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
defer hh.Unlock()
|
||||
|
||||
hostinfo := hh.hostinfo
|
||||
if !via.IsRelayed {
|
||||
if addr.IsValid() {
|
||||
// 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, via.UdpAddr.Addr()) {
|
||||
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||
if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, addr.Addr()) {
|
||||
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -486,7 +485,7 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
ci := hostinfo.ConnectionState
|
||||
msg, eKey, dKey, err := ci.H.ReadMessage(nil, packet[header.Len:])
|
||||
if err != nil {
|
||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("header", h).
|
||||
Error("Failed to call noise.ReadMessage")
|
||||
|
||||
@@ -495,7 +494,7 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
// near future
|
||||
return false
|
||||
} else if dKey == nil || eKey == nil {
|
||||
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||
Error("Noise did not arrive at a key")
|
||||
|
||||
@@ -507,7 +506,7 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
hs := &NebulaHandshake{}
|
||||
err = hs.Unmarshal(msg)
|
||||
if err != nil || hs.Details == nil {
|
||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("udpAddr", addr).
|
||||
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
|
||||
@@ -516,7 +515,7 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
|
||||
rc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())
|
||||
if err != nil {
|
||||
f.l.WithError(err).WithField("from", via).
|
||||
f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||
Info("Handshake did not contain a certificate")
|
||||
@@ -530,7 +529,7 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
fp = "<error generating certificate fingerprint>"
|
||||
}
|
||||
|
||||
e := f.l.WithError(err).WithField("from", via).
|
||||
e := f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||
WithField("certFingerprint", fp).
|
||||
@@ -543,15 +542,9 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
e.Info("Invalid certificate from host")
|
||||
return true
|
||||
}
|
||||
if !bytes.Equal(remoteCert.Certificate.PublicKey(), ci.H.PeerStatic()) {
|
||||
f.l.WithField("from", via).
|
||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||
WithField("cert", remoteCert).Info("public key mismatch between certificate and handshake")
|
||||
return true
|
||||
}
|
||||
|
||||
if len(remoteCert.Certificate.Networks()) == 0 {
|
||||
f.l.WithError(err).WithField("from", via).
|
||||
f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||
WithField("cert", remoteCert).
|
||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||
@@ -574,30 +567,39 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
ci.eKey = NewNebulaCipherState(eKey)
|
||||
|
||||
// Make sure the current udpAddr being used is set for responding
|
||||
if !via.IsRelayed {
|
||||
hostinfo.SetRemote(via.UdpAddr)
|
||||
if addr.IsValid() {
|
||||
hostinfo.SetRemote(addr)
|
||||
} else {
|
||||
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
|
||||
}
|
||||
|
||||
correctHostResponded := false
|
||||
anyVpnAddrsInCommon := false
|
||||
vpnAddrs := make([]netip.Addr, len(vpnNetworks))
|
||||
for i, network := range vpnNetworks {
|
||||
vpnAddrs[i] = network.Addr()
|
||||
if f.myVpnNetworksTable.Contains(network.Addr()) {
|
||||
anyVpnAddrsInCommon = true
|
||||
}
|
||||
if hostinfo.vpnAddrs[0] == network.Addr() {
|
||||
// todo is it more correct to see if any of hostinfo.vpnAddrs are in the cert? it should have len==1, but one day it might not?
|
||||
correctHostResponded = true
|
||||
var vpnAddrs []netip.Addr
|
||||
var filteredNetworks []netip.Prefix
|
||||
for _, network := range vpnNetworks {
|
||||
// vpnAddrs outside our vpn networks are of no use to us, filter them out
|
||||
vpnAddr := network.Addr()
|
||||
if !f.myVpnNetworksTable.Contains(vpnAddr) {
|
||||
continue
|
||||
}
|
||||
|
||||
filteredNetworks = append(filteredNetworks, network)
|
||||
vpnAddrs = append(vpnAddrs, vpnAddr)
|
||||
}
|
||||
|
||||
if len(vpnAddrs) == 0 {
|
||||
f.l.WithError(err).WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("fingerprint", fingerprint).
|
||||
WithField("issuer", issuer).
|
||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).Error("No usable vpn addresses from host, refusing handshake")
|
||||
return true
|
||||
}
|
||||
|
||||
// Ensure the right host responded
|
||||
if !correctHostResponded {
|
||||
if !slices.Contains(vpnAddrs, hostinfo.vpnAddrs[0]) {
|
||||
f.l.WithField("intendedVpnAddrs", hostinfo.vpnAddrs).WithField("haveVpnNetworks", vpnNetworks).
|
||||
WithField("from", via).
|
||||
WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||
@@ -607,11 +609,10 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
f.handshakeManager.DeleteHostInfo(hostinfo)
|
||||
|
||||
// Create a new hostinfo/handshake for the intended vpn ip
|
||||
//TODO is hostinfo.vpnAddrs[0] always the address to use?
|
||||
f.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], func(newHH *HandshakeHostInfo) {
|
||||
// Block the current used address
|
||||
newHH.hostinfo.remotes = hostinfo.remotes
|
||||
newHH.hostinfo.remotes.BlockRemote(via)
|
||||
newHH.hostinfo.remotes.BlockRemote(addr)
|
||||
|
||||
f.l.WithField("blockedUdpAddrs", newHH.hostinfo.remotes.CopyBlockedRemotes()).
|
||||
WithField("vpnNetworks", vpnNetworks).
|
||||
@@ -634,7 +635,7 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
ci.window.Update(f.l, 2)
|
||||
|
||||
duration := time.Since(hh.startTime).Nanoseconds()
|
||||
msgRxL := f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||
f.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", addr).
|
||||
WithField("certName", certName).
|
||||
WithField("certVersion", certVersion).
|
||||
WithField("fingerprint", fingerprint).
|
||||
@@ -642,17 +643,12 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
||||
WithField("initiatorIndex", hs.Details.InitiatorIndex).WithField("responderIndex", hs.Details.ResponderIndex).
|
||||
WithField("remoteIndex", h.RemoteIndex).WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||
WithField("durationNs", duration).
|
||||
WithField("sentCachedPackets", len(hh.packetStore))
|
||||
if anyVpnAddrsInCommon {
|
||||
msgRxL.Info("Handshake message received")
|
||||
} else {
|
||||
//todo warn if not lighthouse or relay?
|
||||
msgRxL.Info("Handshake message received, but no vpnNetworks in common.")
|
||||
}
|
||||
WithField("sentCachedPackets", len(hh.packetStore)).
|
||||
Info("Handshake message received")
|
||||
|
||||
// Build up the radix for the firewall if we have subnets in the cert
|
||||
hostinfo.vpnAddrs = vpnAddrs
|
||||
hostinfo.buildNetworks(f.myVpnNetworksTable, remoteCert.Certificate)
|
||||
hostinfo.buildNetworks(filteredNetworks, remoteCert.Certificate.UnsafeNetworks())
|
||||
|
||||
// Complete our handshake and update metrics, this will replace any existing tunnels for the vpnAddrs here
|
||||
f.handshakeManager.Complete(hostinfo, f)
|
||||
|
||||
+13
-14
@@ -68,12 +68,11 @@ type HandshakeManager struct {
|
||||
type HandshakeHostInfo struct {
|
||||
sync.Mutex
|
||||
|
||||
startTime time.Time // Time that we first started trying with this handshake
|
||||
ready bool // Is the handshake ready
|
||||
initiatingVersionOverride cert.Version // Should we use a non-default cert version for this handshake?
|
||||
counter int64 // How many attempts have we made so far
|
||||
lastRemotes []netip.AddrPort // Remotes that we sent to during the previous attempt
|
||||
packetStore []*cachedPacket // A set of packets to be transmitted once the handshake completes
|
||||
startTime time.Time // Time that we first started trying with this handshake
|
||||
ready bool // Is the handshake ready
|
||||
counter int64 // How many attempts have we made so far
|
||||
lastRemotes []netip.AddrPort // Remotes that we sent to during the previous attempt
|
||||
packetStore []*cachedPacket // A set of packets to be transmitted once the handshake completes
|
||||
|
||||
hostinfo *HostInfo
|
||||
}
|
||||
@@ -136,11 +135,11 @@ func (hm *HandshakeManager) Run(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (hm *HandshakeManager) HandleIncoming(via ViaSender, packet []byte, h *header.H) {
|
||||
func (hm *HandshakeManager) HandleIncoming(addr netip.AddrPort, via *ViaSender, packet []byte, h *header.H) {
|
||||
// First remote allow list check before we know the vpnIp
|
||||
if !via.IsRelayed {
|
||||
if !hm.lightHouse.GetRemoteAllowList().AllowUnknownVpnAddr(via.UdpAddr.Addr()) {
|
||||
hm.l.WithField("from", via).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||
if addr.IsValid() {
|
||||
if !hm.lightHouse.GetRemoteAllowList().AllowUnknownVpnAddr(addr.Addr()) {
|
||||
hm.l.WithField("udpAddr", addr).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -149,11 +148,11 @@ func (hm *HandshakeManager) HandleIncoming(via ViaSender, packet []byte, h *head
|
||||
case header.HandshakeIXPSK0:
|
||||
switch h.MessageCounter {
|
||||
case 1:
|
||||
ixHandshakeStage1(hm.f, via, packet, h)
|
||||
ixHandshakeStage1(hm.f, addr, via, packet, h)
|
||||
|
||||
case 2:
|
||||
newHostinfo := hm.queryIndex(h.RemoteIndex)
|
||||
tearDown := ixHandshakeStage2(hm.f, via, newHostinfo, packet, h)
|
||||
tearDown := ixHandshakeStage2(hm.f, addr, via, newHostinfo, packet, h)
|
||||
if tearDown && newHostinfo != nil {
|
||||
hm.DeleteHostInfo(newHostinfo.hostinfo)
|
||||
}
|
||||
@@ -269,12 +268,12 @@ func (hm *HandshakeManager) handleOutbound(vpnIp netip.Addr, lighthouseTriggered
|
||||
hostinfo.logger(hm.l).WithField("relays", hostinfo.remotes.relays).Info("Attempt to relay through hosts")
|
||||
// Send a RelayRequest to all known Relay IP's
|
||||
for _, relay := range hostinfo.remotes.relays {
|
||||
// Don't relay through the host I'm trying to connect to
|
||||
// Don't relay to myself
|
||||
if relay == vpnIp {
|
||||
continue
|
||||
}
|
||||
|
||||
// Don't relay to myself
|
||||
// Don't relay through the host I'm trying to connect to
|
||||
if hm.f.myVpnAddrsTable.Contains(relay) {
|
||||
continue
|
||||
}
|
||||
|
||||
+17
-55
@@ -1,9 +1,7 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
@@ -214,18 +212,6 @@ func (rs *RelayState) InsertRelay(ip netip.Addr, idx uint32, r *Relay) {
|
||||
rs.relayForByIdx[idx] = r
|
||||
}
|
||||
|
||||
type NetworkType uint8
|
||||
|
||||
const (
|
||||
NetworkTypeUnknown NetworkType = iota
|
||||
// NetworkTypeVPN is a network that overlaps one or more of the vpnNetworks in our certificate
|
||||
NetworkTypeVPN
|
||||
// NetworkTypeVPNPeer is a network that does not overlap one of our networks
|
||||
NetworkTypeVPNPeer
|
||||
// NetworkTypeUnsafe is a network from Certificate.UnsafeNetworks()
|
||||
NetworkTypeUnsafe
|
||||
)
|
||||
|
||||
type HostInfo struct {
|
||||
remote netip.AddrPort
|
||||
remotes *RemoteList
|
||||
@@ -239,8 +225,8 @@ type HostInfo struct {
|
||||
// vpn networks but were removed because they are not usable
|
||||
vpnAddrs []netip.Addr
|
||||
|
||||
// networks is a combination of specific vpn addresses (not prefixes!) and full unsafe networks assigned to this host.
|
||||
networks *bart.Table[NetworkType]
|
||||
// networks are both all vpn and unsafe networks assigned to this host
|
||||
networks *bart.Lite
|
||||
relayState RelayState
|
||||
|
||||
// HandshakePacket records the packets used to create this hostinfo
|
||||
@@ -278,25 +264,9 @@ type HostInfo struct {
|
||||
}
|
||||
|
||||
type ViaSender struct {
|
||||
UdpAddr netip.AddrPort
|
||||
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
|
||||
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 {
|
||||
@@ -712,7 +682,6 @@ func (i *HostInfo) GetCert() *cert.CachedCertificate {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Maybe use ViaSender here?
|
||||
func (i *HostInfo) SetRemote(remote netip.AddrPort) {
|
||||
// We copy here because we likely got this remote from a source that reuses the object
|
||||
if i.remote != remote {
|
||||
@@ -723,14 +692,14 @@ func (i *HostInfo) SetRemote(remote netip.AddrPort) {
|
||||
|
||||
// SetRemoteIfPreferred returns true if the remote was changed. The lastRoam
|
||||
// time on the HostInfo will also be updated.
|
||||
func (i *HostInfo) SetRemoteIfPreferred(hm *HostMap, via ViaSender) bool {
|
||||
if via.IsRelayed {
|
||||
func (i *HostInfo) SetRemoteIfPreferred(hm *HostMap, newRemote netip.AddrPort) bool {
|
||||
if !newRemote.IsValid() {
|
||||
// relays have nil udp Addrs
|
||||
return false
|
||||
}
|
||||
|
||||
currentRemote := i.remote
|
||||
if !currentRemote.IsValid() {
|
||||
i.SetRemote(via.UdpAddr)
|
||||
i.SetRemote(newRemote)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -743,7 +712,7 @@ func (i *HostInfo) SetRemoteIfPreferred(hm *HostMap, via ViaSender) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if l.Contains(via.UdpAddr.Addr()) {
|
||||
if l.Contains(newRemote.Addr()) {
|
||||
newIsPreferred = true
|
||||
}
|
||||
}
|
||||
@@ -753,7 +722,7 @@ func (i *HostInfo) SetRemoteIfPreferred(hm *HostMap, via ViaSender) bool {
|
||||
i.lastRoam = time.Now()
|
||||
i.lastRoamRemote = currentRemote
|
||||
|
||||
i.SetRemote(via.UdpAddr)
|
||||
i.SetRemote(newRemote)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -761,26 +730,19 @@ func (i *HostInfo) SetRemoteIfPreferred(hm *HostMap, via ViaSender) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// buildNetworks fills in the networks field of HostInfo. It accepts a cert.Certificate so you never ever mix the network types up.
|
||||
func (i *HostInfo) buildNetworks(myVpnNetworksTable *bart.Lite, c cert.Certificate) {
|
||||
if len(c.Networks()) == 1 && len(c.UnsafeNetworks()) == 0 {
|
||||
if myVpnNetworksTable.Contains(c.Networks()[0].Addr()) {
|
||||
return // Simple case, no BART needed
|
||||
}
|
||||
func (i *HostInfo) buildNetworks(networks, unsafeNetworks []netip.Prefix) {
|
||||
if len(networks) == 1 && len(unsafeNetworks) == 0 {
|
||||
// Simple case, no CIDRTree needed
|
||||
return
|
||||
}
|
||||
|
||||
i.networks = new(bart.Table[NetworkType])
|
||||
for _, network := range c.Networks() {
|
||||
nprefix := netip.PrefixFrom(network.Addr(), network.Addr().BitLen())
|
||||
if myVpnNetworksTable.Contains(network.Addr()) {
|
||||
i.networks.Insert(nprefix, NetworkTypeVPN)
|
||||
} else {
|
||||
i.networks.Insert(nprefix, NetworkTypeVPNPeer)
|
||||
}
|
||||
i.networks = new(bart.Lite)
|
||||
for _, network := range networks {
|
||||
i.networks.Insert(network)
|
||||
}
|
||||
|
||||
for _, network := range c.UnsafeNetworks() {
|
||||
i.networks.Insert(network, NetworkTypeUnsafe)
|
||||
for _, network := range unsafeNetworks {
|
||||
i.networks.Insert(network)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,19 +11,19 @@ import (
|
||||
"github.com/slackhq/nebula/routing"
|
||||
)
|
||||
|
||||
func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet, nb, out []byte, q int, localCache firewall.ConntrackCache) {
|
||||
err := firewall.NewPacket(packet, false, fwPacket)
|
||||
func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet, nb, out []byte, queue func(netip.AddrPort, int), q int, localCache firewall.ConntrackCache) bool {
|
||||
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
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore local broadcast packets
|
||||
if f.dropLocalBroadcast {
|
||||
if f.myBroadcastAddrsTable.Contains(fwPacket.RemoteAddr) {
|
||||
return
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,12 +40,12 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
|
||||
}
|
||||
// Otherwise, drop. On linux, we should never see these packets - Linux
|
||||
// routes packets from the nebula addr to the nebula addr through the loopback device.
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore multicast packets
|
||||
if f.dropMulticast && fwPacket.RemoteAddr.IsMulticast() {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
hostinfo, ready := f.getOrHandshakeConsiderRouting(fwPacket, func(hh *HandshakeHostInfo) {
|
||||
@@ -59,26 +59,26 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
|
||||
WithField("fwPacket", fwPacket).
|
||||
Debugln("dropping outbound packet, vpnAddr not in our vpn networks or in unsafe networks")
|
||||
}
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if !ready {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
dropReason := f.firewall.Drop(*fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)
|
||||
if dropReason == nil {
|
||||
f.sendNoMetrics(header.Message, 0, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, packet, nb, out, q)
|
||||
|
||||
} else {
|
||||
f.rejectInside(packet, out, q)
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
hostinfo.logger(f.l).
|
||||
WithField("fwPacket", fwPacket).
|
||||
WithField("reason", dropReason).
|
||||
Debugln("dropping outbound packet")
|
||||
}
|
||||
return f.sendNoMetrics(header.Message, 0, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, packet, nb, out, queue, q)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *Interface) rejectInside(packet []byte, out []byte, q int) {
|
||||
@@ -117,13 +117,12 @@ func (f *Interface) rejectOutside(packet []byte, ci *ConnectionState, hostinfo *
|
||||
return
|
||||
}
|
||||
|
||||
f.sendNoMetrics(header.Message, 0, ci, hostinfo, netip.AddrPort{}, out, nb, packet, q)
|
||||
_ = f.sendNoMetrics(header.Message, 0, ci, hostinfo, netip.AddrPort{}, out, nb, packet, nil, q)
|
||||
}
|
||||
|
||||
// Handshake will attempt to initiate a tunnel with the provided vpn address. This is a no-op if the tunnel is already established or being established
|
||||
// it does not check if it is within our vpn networks!
|
||||
// Handshake will attempt to initiate a tunnel with the provided vpn address if it is within our vpn networks. This is a no-op if the tunnel is already established or being established
|
||||
func (f *Interface) Handshake(vpnAddr netip.Addr) {
|
||||
f.handshakeManager.GetOrHandshake(vpnAddr, nil)
|
||||
f.getOrHandshakeNoRouting(vpnAddr, nil)
|
||||
}
|
||||
|
||||
// getOrHandshakeNoRouting returns nil if the vpnAddr is not routable.
|
||||
@@ -139,6 +138,7 @@ func (f *Interface) getOrHandshakeNoRouting(vpnAddr netip.Addr, cacheCallback fu
|
||||
// getOrHandshakeConsiderRouting will try to find the HostInfo to handle this packet, starting a handshake if necessary.
|
||||
// If the 2nd return var is false then the hostinfo is not ready to be used in a tunnel.
|
||||
func (f *Interface) getOrHandshakeConsiderRouting(fwPacket *firewall.Packet, cacheCallback func(*HandshakeHostInfo)) (*HostInfo, bool) {
|
||||
|
||||
destinationAddr := fwPacket.RemoteAddr
|
||||
|
||||
hostinfo, ready := f.getOrHandshakeNoRouting(destinationAddr, cacheCallback)
|
||||
@@ -211,7 +211,7 @@ func (f *Interface) getOrHandshakeConsiderRouting(fwPacket *firewall.Packet, cac
|
||||
|
||||
func (f *Interface) sendMessageNow(t header.MessageType, st header.MessageSubType, hostinfo *HostInfo, p, nb, out []byte) {
|
||||
fp := &firewall.Packet{}
|
||||
err := firewall.NewPacket(p, false, fp)
|
||||
err := newPacket(p, false, fp)
|
||||
if err != nil {
|
||||
f.l.Warnf("error while parsing outgoing packet for firewall check; %v", err)
|
||||
return
|
||||
@@ -228,13 +228,12 @@ func (f *Interface) sendMessageNow(t header.MessageType, st header.MessageSubTyp
|
||||
return
|
||||
}
|
||||
|
||||
f.sendNoMetrics(header.Message, st, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, p, nb, out, 0)
|
||||
_ = f.sendNoMetrics(header.Message, st, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, p, nb, out, nil, 0)
|
||||
}
|
||||
|
||||
// SendMessageToVpnAddr handles real addr:port lookup and sends to the current best known address for vpnAddr.
|
||||
// This function ignores myVpnNetworksTable, and will always attempt to treat the address as a vpnAddr
|
||||
// SendMessageToVpnAddr handles real addr:port lookup and sends to the current best known address for vpnAddr
|
||||
func (f *Interface) SendMessageToVpnAddr(t header.MessageType, st header.MessageSubType, vpnAddr netip.Addr, p, nb, out []byte) {
|
||||
hostInfo, ready := f.handshakeManager.GetOrHandshake(vpnAddr, func(hh *HandshakeHostInfo) {
|
||||
hostInfo, ready := f.getOrHandshakeNoRouting(vpnAddr, func(hh *HandshakeHostInfo) {
|
||||
hh.cachePacket(f.l, t, st, p, f.SendMessageToHostInfo, f.cachedPacketMetrics)
|
||||
})
|
||||
|
||||
@@ -259,12 +258,12 @@ func (f *Interface) SendMessageToHostInfo(t header.MessageType, st header.Messag
|
||||
|
||||
func (f *Interface) send(t header.MessageType, st header.MessageSubType, ci *ConnectionState, hostinfo *HostInfo, p, nb, out []byte) {
|
||||
f.messageMetrics.Tx(t, st, 1)
|
||||
f.sendNoMetrics(t, st, ci, hostinfo, netip.AddrPort{}, p, nb, out, 0)
|
||||
_ = f.sendNoMetrics(t, st, ci, hostinfo, netip.AddrPort{}, p, nb, out, nil, 0)
|
||||
}
|
||||
|
||||
func (f *Interface) sendTo(t header.MessageType, st header.MessageSubType, ci *ConnectionState, hostinfo *HostInfo, remote netip.AddrPort, p, nb, out []byte) {
|
||||
f.messageMetrics.Tx(t, st, 1)
|
||||
f.sendNoMetrics(t, st, ci, hostinfo, remote, p, nb, out, 0)
|
||||
_ = f.sendNoMetrics(t, st, ci, hostinfo, remote, p, nb, out, nil, 0)
|
||||
}
|
||||
|
||||
// SendVia sends a payload through a Relay tunnel. No authentication or encryption is done
|
||||
@@ -332,9 +331,12 @@ func (f *Interface) SendVia(via *HostInfo,
|
||||
f.connectionManager.RelayUsed(relay.LocalIndex)
|
||||
}
|
||||
|
||||
func (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType, ci *ConnectionState, hostinfo *HostInfo, remote netip.AddrPort, p, nb, out []byte, q int) {
|
||||
// sendNoMetrics encrypts and writes/queues an outbound packet. It returns true
|
||||
// when the payload has been handed to a caller-provided queue (meaning the
|
||||
// caller is responsible for flushing it later).
|
||||
func (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType, ci *ConnectionState, hostinfo *HostInfo, remote netip.AddrPort, p, nb, out []byte, queue func(netip.AddrPort, int), q int) bool {
|
||||
if ci.eKey == nil {
|
||||
return
|
||||
return false
|
||||
}
|
||||
useRelay := !remote.IsValid() && !hostinfo.remote.IsValid()
|
||||
fullOut := out
|
||||
@@ -381,32 +383,39 @@ func (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType
|
||||
WithField("udpAddr", remote).WithField("counter", c).
|
||||
WithField("attemptedCounter", c).
|
||||
Error("Failed to encrypt outgoing packet")
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if remote.IsValid() {
|
||||
err = f.writers[q].WriteTo(out, remote)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).
|
||||
WithField("udpAddr", remote).Error("Failed to write outgoing packet")
|
||||
}
|
||||
} else if hostinfo.remote.IsValid() {
|
||||
err = f.writers[q].WriteTo(out, hostinfo.remote)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).
|
||||
WithField("udpAddr", remote).Error("Failed to write outgoing packet")
|
||||
}
|
||||
} else {
|
||||
// Try to send via a relay
|
||||
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("sendNoMetrics failed to find HostInfo")
|
||||
continue
|
||||
}
|
||||
f.SendVia(relayHostInfo, relay, out, nb, fullOut[:header.Len+len(out)], true)
|
||||
break
|
||||
}
|
||||
dest := remote
|
||||
if !dest.IsValid() {
|
||||
dest = hostinfo.remote
|
||||
}
|
||||
|
||||
if dest.IsValid() {
|
||||
if queue != nil {
|
||||
queue(dest, len(out))
|
||||
return true
|
||||
}
|
||||
|
||||
err = f.writers[q].WriteTo(out, dest)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).
|
||||
WithField("udpAddr", dest).Error("Failed to write outgoing packet")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Try to send via a relay
|
||||
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("sendNoMetrics failed to find HostInfo")
|
||||
continue
|
||||
}
|
||||
f.SendVia(relayHostInfo, relay, out, nb, fullOut[:header.Len+len(out)], true)
|
||||
break
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
+449
-97
@@ -6,8 +6,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -18,10 +18,22 @@ import (
|
||||
"github.com/slackhq/nebula/firewall"
|
||||
"github.com/slackhq/nebula/header"
|
||||
"github.com/slackhq/nebula/overlay"
|
||||
"github.com/slackhq/nebula/packet"
|
||||
"github.com/slackhq/nebula/udp"
|
||||
)
|
||||
|
||||
const mtu = 9001
|
||||
const (
|
||||
mtu = 9001
|
||||
|
||||
inboundBatchSizeDefault = 128
|
||||
outboundBatchSizeDefault = 64
|
||||
batchFlushIntervalDefault = 12 * time.Microsecond
|
||||
maxOutstandingBatchesDefault = 8
|
||||
sendBatchSizeDefault = 64
|
||||
maxPendingPacketsDefault = 32
|
||||
maxPendingBytesDefault = 64 * 1024
|
||||
maxSendBufPerRoutineDefault = 16
|
||||
)
|
||||
|
||||
type InterfaceConfig struct {
|
||||
HostMap *HostMap
|
||||
@@ -47,9 +59,20 @@ type InterfaceConfig struct {
|
||||
reQueryWait time.Duration
|
||||
|
||||
ConntrackCacheTimeout time.Duration
|
||||
BatchConfig BatchConfig
|
||||
l *logrus.Logger
|
||||
}
|
||||
|
||||
type BatchConfig struct {
|
||||
InboundBatchSize int
|
||||
OutboundBatchSize int
|
||||
FlushInterval time.Duration
|
||||
MaxOutstandingPerChan int
|
||||
MaxPendingPackets int
|
||||
MaxPendingBytes int
|
||||
MaxSendBuffersPerChan int
|
||||
}
|
||||
|
||||
type Interface struct {
|
||||
hostMap *HostMap
|
||||
outside udp.Conn
|
||||
@@ -77,8 +100,7 @@ type Interface struct {
|
||||
reQueryEvery atomic.Uint32
|
||||
reQueryWait atomic.Int64
|
||||
|
||||
sendRecvErrorConfig recvErrorConfig
|
||||
acceptRecvErrorConfig recvErrorConfig
|
||||
sendRecvErrorConfig sendRecvErrorConfig
|
||||
|
||||
// rebindCount is used to decide if an active tunnel should trigger a punch notification through a lighthouse
|
||||
rebindCount int8
|
||||
@@ -88,12 +110,165 @@ type Interface struct {
|
||||
|
||||
writers []udp.Conn
|
||||
readers []io.ReadWriteCloser
|
||||
wg sync.WaitGroup
|
||||
|
||||
metricHandshakes metrics.Histogram
|
||||
messageMetrics *MessageMetrics
|
||||
cachedPacketMetrics *cachedPacketMetrics
|
||||
|
||||
l *logrus.Logger
|
||||
|
||||
inPool sync.Pool
|
||||
inbound []chan *packetBatch
|
||||
|
||||
outPool sync.Pool
|
||||
outbound []chan *outboundBatch
|
||||
|
||||
packetBatchPool sync.Pool
|
||||
outboundBatchPool sync.Pool
|
||||
|
||||
sendPool sync.Pool
|
||||
sendBufCache [][]*[]byte
|
||||
sendBatchSize int
|
||||
|
||||
inboundBatchSize int
|
||||
outboundBatchSize int
|
||||
batchFlushInterval time.Duration
|
||||
maxOutstandingPerChan int
|
||||
maxPendingPackets int
|
||||
maxPendingBytes int
|
||||
maxSendBufPerRoutine int
|
||||
}
|
||||
|
||||
type outboundSend struct {
|
||||
buf *[]byte
|
||||
length int
|
||||
addr netip.AddrPort
|
||||
}
|
||||
|
||||
type packetBatch struct {
|
||||
packets []*packet.Packet
|
||||
}
|
||||
|
||||
func newPacketBatch(capacity int) *packetBatch {
|
||||
return &packetBatch{
|
||||
packets: make([]*packet.Packet, 0, capacity),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *packetBatch) add(p *packet.Packet) {
|
||||
b.packets = append(b.packets, p)
|
||||
}
|
||||
|
||||
func (b *packetBatch) reset() {
|
||||
for i := range b.packets {
|
||||
b.packets[i] = nil
|
||||
}
|
||||
b.packets = b.packets[:0]
|
||||
}
|
||||
|
||||
func (f *Interface) getPacketBatch() *packetBatch {
|
||||
if v := f.packetBatchPool.Get(); v != nil {
|
||||
b := v.(*packetBatch)
|
||||
b.reset()
|
||||
return b
|
||||
}
|
||||
return newPacketBatch(f.inboundBatchSize)
|
||||
}
|
||||
|
||||
func (f *Interface) releasePacketBatch(b *packetBatch) {
|
||||
b.reset()
|
||||
f.packetBatchPool.Put(b)
|
||||
}
|
||||
|
||||
type outboundBatch struct {
|
||||
payloads []*[]byte
|
||||
}
|
||||
|
||||
func newOutboundBatch(capacity int) *outboundBatch {
|
||||
return &outboundBatch{payloads: make([]*[]byte, 0, capacity)}
|
||||
}
|
||||
|
||||
func (b *outboundBatch) add(buf *[]byte) {
|
||||
b.payloads = append(b.payloads, buf)
|
||||
}
|
||||
|
||||
func (b *outboundBatch) reset() {
|
||||
for i := range b.payloads {
|
||||
b.payloads[i] = nil
|
||||
}
|
||||
b.payloads = b.payloads[:0]
|
||||
}
|
||||
|
||||
func (f *Interface) getOutboundBatch() *outboundBatch {
|
||||
if v := f.outboundBatchPool.Get(); v != nil {
|
||||
b := v.(*outboundBatch)
|
||||
b.reset()
|
||||
return b
|
||||
}
|
||||
return newOutboundBatch(f.outboundBatchSize)
|
||||
}
|
||||
|
||||
func (f *Interface) releaseOutboundBatch(b *outboundBatch) {
|
||||
b.reset()
|
||||
f.outboundBatchPool.Put(b)
|
||||
}
|
||||
|
||||
func (f *Interface) getSendBuffer(q int) *[]byte {
|
||||
cache := f.sendBufCache[q]
|
||||
if n := len(cache); n > 0 {
|
||||
buf := cache[n-1]
|
||||
f.sendBufCache[q] = cache[:n-1]
|
||||
*buf = (*buf)[:0]
|
||||
return buf
|
||||
}
|
||||
if v := f.sendPool.Get(); v != nil {
|
||||
buf := v.(*[]byte)
|
||||
*buf = (*buf)[:0]
|
||||
return buf
|
||||
}
|
||||
b := make([]byte, mtu)
|
||||
return &b
|
||||
}
|
||||
|
||||
func (f *Interface) releaseSendBuffer(q int, buf *[]byte) {
|
||||
if buf == nil {
|
||||
return
|
||||
}
|
||||
*buf = (*buf)[:0]
|
||||
cache := f.sendBufCache[q]
|
||||
if len(cache) < f.maxSendBufPerRoutine {
|
||||
f.sendBufCache[q] = append(cache, buf)
|
||||
return
|
||||
}
|
||||
f.sendPool.Put(buf)
|
||||
}
|
||||
|
||||
func (f *Interface) flushSendQueue(q int, pending *[]outboundSend, pendingBytes *int) {
|
||||
if len(*pending) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
batch := make([]udp.BatchPacket, len(*pending))
|
||||
for i, entry := range *pending {
|
||||
batch[i] = udp.BatchPacket{
|
||||
Payload: (*entry.buf)[:entry.length],
|
||||
Addr: entry.addr,
|
||||
}
|
||||
}
|
||||
|
||||
sent, err := f.writers[q].WriteBatch(batch)
|
||||
if err != nil {
|
||||
f.l.WithError(err).WithField("sent", sent).Error("Failed to batch send packets")
|
||||
}
|
||||
|
||||
for _, entry := range *pending {
|
||||
f.releaseSendBuffer(q, entry.buf)
|
||||
}
|
||||
*pending = (*pending)[:0]
|
||||
if pendingBytes != nil {
|
||||
*pendingBytes = 0
|
||||
}
|
||||
}
|
||||
|
||||
type EncWriter interface {
|
||||
@@ -111,34 +286,34 @@ type EncWriter interface {
|
||||
GetCertState() *CertState
|
||||
}
|
||||
|
||||
type recvErrorConfig uint8
|
||||
type sendRecvErrorConfig uint8
|
||||
|
||||
const (
|
||||
recvErrorAlways recvErrorConfig = iota
|
||||
recvErrorNever
|
||||
recvErrorPrivate
|
||||
sendRecvErrorAlways sendRecvErrorConfig = iota
|
||||
sendRecvErrorNever
|
||||
sendRecvErrorPrivate
|
||||
)
|
||||
|
||||
func (s recvErrorConfig) ShouldRecvError(endpoint netip.AddrPort) bool {
|
||||
func (s sendRecvErrorConfig) ShouldSendRecvError(endpoint netip.AddrPort) bool {
|
||||
switch s {
|
||||
case recvErrorPrivate:
|
||||
case sendRecvErrorPrivate:
|
||||
return endpoint.Addr().IsPrivate()
|
||||
case recvErrorAlways:
|
||||
case sendRecvErrorAlways:
|
||||
return true
|
||||
case recvErrorNever:
|
||||
case sendRecvErrorNever:
|
||||
return false
|
||||
default:
|
||||
panic(fmt.Errorf("invalid recvErrorConfig value: %d", s))
|
||||
panic(fmt.Errorf("invalid sendRecvErrorConfig value: %d", s))
|
||||
}
|
||||
}
|
||||
|
||||
func (s recvErrorConfig) String() string {
|
||||
func (s sendRecvErrorConfig) String() string {
|
||||
switch s {
|
||||
case recvErrorAlways:
|
||||
case sendRecvErrorAlways:
|
||||
return "always"
|
||||
case recvErrorNever:
|
||||
case sendRecvErrorNever:
|
||||
return "never"
|
||||
case recvErrorPrivate:
|
||||
case sendRecvErrorPrivate:
|
||||
return "private"
|
||||
default:
|
||||
return fmt.Sprintf("invalid(%d)", s)
|
||||
@@ -163,6 +338,29 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
|
||||
}
|
||||
|
||||
cs := c.pki.getCertState()
|
||||
|
||||
bc := c.BatchConfig
|
||||
if bc.InboundBatchSize <= 0 {
|
||||
bc.InboundBatchSize = inboundBatchSizeDefault
|
||||
}
|
||||
if bc.OutboundBatchSize <= 0 {
|
||||
bc.OutboundBatchSize = outboundBatchSizeDefault
|
||||
}
|
||||
if bc.FlushInterval <= 0 {
|
||||
bc.FlushInterval = batchFlushIntervalDefault
|
||||
}
|
||||
if bc.MaxOutstandingPerChan <= 0 {
|
||||
bc.MaxOutstandingPerChan = maxOutstandingBatchesDefault
|
||||
}
|
||||
if bc.MaxPendingPackets <= 0 {
|
||||
bc.MaxPendingPackets = maxPendingPacketsDefault
|
||||
}
|
||||
if bc.MaxPendingBytes <= 0 {
|
||||
bc.MaxPendingBytes = maxPendingBytesDefault
|
||||
}
|
||||
if bc.MaxSendBuffersPerChan <= 0 {
|
||||
bc.MaxSendBuffersPerChan = maxSendBufPerRoutineDefault
|
||||
}
|
||||
ifce := &Interface{
|
||||
pki: c.pki,
|
||||
hostMap: c.HostMap,
|
||||
@@ -195,9 +393,49 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
|
||||
dropped: metrics.GetOrRegisterCounter("hostinfo.cached_packets.dropped", nil),
|
||||
},
|
||||
|
||||
inbound: make([]chan *packetBatch, c.routines),
|
||||
outbound: make([]chan *outboundBatch, c.routines),
|
||||
|
||||
l: c.l,
|
||||
|
||||
inboundBatchSize: bc.InboundBatchSize,
|
||||
outboundBatchSize: bc.OutboundBatchSize,
|
||||
batchFlushInterval: bc.FlushInterval,
|
||||
maxOutstandingPerChan: bc.MaxOutstandingPerChan,
|
||||
maxPendingPackets: bc.MaxPendingPackets,
|
||||
maxPendingBytes: bc.MaxPendingBytes,
|
||||
maxSendBufPerRoutine: bc.MaxSendBuffersPerChan,
|
||||
sendBatchSize: bc.OutboundBatchSize,
|
||||
}
|
||||
|
||||
for i := 0; i < c.routines; i++ {
|
||||
ifce.inbound[i] = make(chan *packetBatch, ifce.maxOutstandingPerChan)
|
||||
ifce.outbound[i] = make(chan *outboundBatch, ifce.maxOutstandingPerChan)
|
||||
}
|
||||
|
||||
ifce.inPool = sync.Pool{New: func() any {
|
||||
return packet.New()
|
||||
}}
|
||||
|
||||
ifce.outPool = sync.Pool{New: func() any {
|
||||
t := make([]byte, mtu)
|
||||
return &t
|
||||
}}
|
||||
|
||||
ifce.packetBatchPool = sync.Pool{New: func() any {
|
||||
return newPacketBatch(ifce.inboundBatchSize)
|
||||
}}
|
||||
|
||||
ifce.outboundBatchPool = sync.Pool{New: func() any {
|
||||
return newOutboundBatch(ifce.outboundBatchSize)
|
||||
}}
|
||||
|
||||
ifce.sendPool = sync.Pool{New: func() any {
|
||||
buf := make([]byte, mtu)
|
||||
return &buf
|
||||
}}
|
||||
ifce.sendBufCache = make([][]*[]byte, c.routines)
|
||||
|
||||
ifce.tryPromoteEvery.Store(c.tryPromoteEvery)
|
||||
ifce.reQueryEvery.Store(c.reQueryEvery)
|
||||
ifce.reQueryWait.Store(int64(c.reQueryWait))
|
||||
@@ -210,7 +448,7 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
|
||||
// activate creates the interface on the host. After the interface is created, any
|
||||
// other services that want to bind listeners to its IP may do so successfully. However,
|
||||
// the interface isn't going to process anything until run() is called.
|
||||
func (f *Interface) activate() {
|
||||
func (f *Interface) activate() error {
|
||||
// actually turn on tun dev
|
||||
|
||||
addr, err := f.outside.LocalAddr()
|
||||
@@ -223,13 +461,6 @@ func (f *Interface) activate() {
|
||||
WithField("boringcrypto", boringEnabled()).
|
||||
Info("Nebula interface is active")
|
||||
|
||||
if f.routines > 1 {
|
||||
if !f.inside.SupportsMultiqueue() || !f.outside.SupportsMultipleReaders() {
|
||||
f.routines = 1
|
||||
f.l.Warn("routines is not supported on this platform, falling back to a single routine")
|
||||
}
|
||||
}
|
||||
|
||||
metrics.GetOrRegisterGauge("routines", nil).Update(int64(f.routines))
|
||||
|
||||
// Prepare n tun queues
|
||||
@@ -238,33 +469,44 @@ func (f *Interface) activate() {
|
||||
if i > 0 {
|
||||
reader, err = f.inside.NewMultiQueueReader()
|
||||
if err != nil {
|
||||
f.l.Fatal(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
f.readers[i] = reader
|
||||
}
|
||||
|
||||
if err := f.inside.Activate(); err != nil {
|
||||
if err = f.inside.Activate(); err != nil {
|
||||
f.inside.Close()
|
||||
f.l.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Interface) run() {
|
||||
// Launch n queues to read packets from udp
|
||||
func (f *Interface) run(c context.Context) (func(), error) {
|
||||
for i := 0; i < f.routines; i++ {
|
||||
// Launch n queues to read packets from udp
|
||||
f.wg.Add(1)
|
||||
go f.listenOut(i)
|
||||
|
||||
// Launch n queues to read packets from tun dev
|
||||
f.wg.Add(1)
|
||||
go f.listenIn(f.readers[i], i)
|
||||
|
||||
// Launch n queues to read packets from tun dev
|
||||
f.wg.Add(1)
|
||||
go f.workerIn(i, c)
|
||||
|
||||
// Launch n queues to read packets from tun dev
|
||||
f.wg.Add(1)
|
||||
go f.workerOut(i, c)
|
||||
}
|
||||
|
||||
// Launch n queues to read packets from tun dev
|
||||
for i := 0; i < f.routines; i++ {
|
||||
go f.listenIn(f.readers[i], i)
|
||||
}
|
||||
return f.wg.Wait, nil
|
||||
}
|
||||
|
||||
func (f *Interface) listenOut(i int) {
|
||||
runtime.LockOSThread()
|
||||
|
||||
var li udp.Conn
|
||||
if i > 0 {
|
||||
li = f.writers[i]
|
||||
@@ -272,48 +514,182 @@ func (f *Interface) listenOut(i int) {
|
||||
li = f.outside
|
||||
}
|
||||
|
||||
ctCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
|
||||
lhh := f.lightHouse.NewRequestHandler()
|
||||
plaintext := make([]byte, udp.MTU)
|
||||
h := &header.H{}
|
||||
fwPacket := &firewall.Packet{}
|
||||
nb := make([]byte, 12, 12)
|
||||
batch := f.getPacketBatch()
|
||||
lastFlush := time.Now()
|
||||
|
||||
li.ListenOut(func(fromUdpAddr netip.AddrPort, payload []byte) {
|
||||
f.readOutsidePackets(ViaSender{UdpAddr: fromUdpAddr}, plaintext[:0], payload, h, fwPacket, lhh, nb, i, ctCache.Get(f.l))
|
||||
flush := func(force bool) {
|
||||
if len(batch.packets) == 0 {
|
||||
if force {
|
||||
f.releasePacketBatch(batch)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
f.inbound[i] <- batch
|
||||
batch = f.getPacketBatch()
|
||||
lastFlush = time.Now()
|
||||
}
|
||||
|
||||
err := li.ListenOut(func(fromUdpAddr netip.AddrPort, payload []byte) {
|
||||
p := f.inPool.Get().(*packet.Packet)
|
||||
p.Payload = p.Payload[:mtu]
|
||||
copy(p.Payload, payload)
|
||||
p.Payload = p.Payload[:len(payload)]
|
||||
p.Addr = fromUdpAddr
|
||||
batch.add(p)
|
||||
|
||||
if len(batch.packets) >= f.inboundBatchSize || time.Since(lastFlush) >= f.batchFlushInterval {
|
||||
flush(false)
|
||||
}
|
||||
})
|
||||
|
||||
if len(batch.packets) > 0 {
|
||||
f.inbound[i] <- batch
|
||||
} else {
|
||||
f.releasePacketBatch(batch)
|
||||
}
|
||||
|
||||
if err != nil && !f.closed.Load() {
|
||||
f.l.WithError(err).Error("Error while reading packet inbound packet, closing")
|
||||
//TODO: Trigger Control to close
|
||||
}
|
||||
|
||||
f.l.Debugf("underlay reader %v is done", i)
|
||||
f.wg.Done()
|
||||
}
|
||||
|
||||
func (f *Interface) listenIn(reader io.ReadWriteCloser, i int) {
|
||||
runtime.LockOSThread()
|
||||
|
||||
packet := make([]byte, mtu)
|
||||
out := make([]byte, mtu)
|
||||
fwPacket := &firewall.Packet{}
|
||||
nb := make([]byte, 12, 12)
|
||||
batch := f.getOutboundBatch()
|
||||
lastFlush := time.Now()
|
||||
|
||||
conntrackCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
|
||||
|
||||
for {
|
||||
n, err := reader.Read(packet)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrClosed) && f.closed.Load() {
|
||||
return
|
||||
flush := func(force bool) {
|
||||
if len(batch.payloads) == 0 {
|
||||
if force {
|
||||
f.releaseOutboundBatch(batch)
|
||||
}
|
||||
|
||||
f.l.WithError(err).Error("Error while reading outbound packet")
|
||||
// This only seems to happen when something fatal happens to the fd, so exit.
|
||||
os.Exit(2)
|
||||
return
|
||||
}
|
||||
|
||||
f.consumeInsidePacket(packet[:n], fwPacket, nb, out, i, conntrackCache.Get(f.l))
|
||||
f.outbound[i] <- batch
|
||||
batch = f.getOutboundBatch()
|
||||
lastFlush = time.Now()
|
||||
}
|
||||
|
||||
for {
|
||||
p := f.outPool.Get().(*[]byte)
|
||||
*p = (*p)[:mtu]
|
||||
n, err := reader.Read(*p)
|
||||
if err != nil {
|
||||
if !f.closed.Load() {
|
||||
f.l.WithError(err).Error("Error while reading outbound packet, closing")
|
||||
//TODO: Trigger Control to close
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
*p = (*p)[:n]
|
||||
batch.add(p)
|
||||
|
||||
if len(batch.payloads) >= f.outboundBatchSize || time.Since(lastFlush) >= f.batchFlushInterval {
|
||||
flush(false)
|
||||
}
|
||||
}
|
||||
|
||||
if len(batch.payloads) > 0 {
|
||||
f.outbound[i] <- batch
|
||||
} else {
|
||||
f.releaseOutboundBatch(batch)
|
||||
}
|
||||
|
||||
f.l.Debugf("overlay reader %v is done", i)
|
||||
f.wg.Done()
|
||||
}
|
||||
|
||||
func (f *Interface) workerIn(i int, ctx context.Context) {
|
||||
lhh := f.lightHouse.NewRequestHandler()
|
||||
conntrackCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
|
||||
fwPacket2 := &firewall.Packet{}
|
||||
nb2 := make([]byte, 12, 12)
|
||||
result2 := make([]byte, mtu)
|
||||
h := &header.H{}
|
||||
|
||||
for {
|
||||
select {
|
||||
case batch := <-f.inbound[i]:
|
||||
for _, p := range batch.packets {
|
||||
f.readOutsidePackets(p.Addr, nil, result2[:0], p.Payload, h, fwPacket2, lhh, nb2, i, conntrackCache.Get(f.l))
|
||||
p.Payload = p.Payload[:mtu]
|
||||
f.inPool.Put(p)
|
||||
}
|
||||
f.releasePacketBatch(batch)
|
||||
case <-ctx.Done():
|
||||
f.wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Interface) workerOut(i int, ctx context.Context) {
|
||||
conntrackCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
|
||||
fwPacket1 := &firewall.Packet{}
|
||||
nb1 := make([]byte, 12, 12)
|
||||
pending := make([]outboundSend, 0, f.sendBatchSize)
|
||||
pendingBytes := 0
|
||||
maxPendingPackets := f.maxPendingPackets
|
||||
if maxPendingPackets <= 0 {
|
||||
maxPendingPackets = f.sendBatchSize
|
||||
}
|
||||
maxPendingBytes := f.maxPendingBytes
|
||||
if maxPendingBytes <= 0 {
|
||||
maxPendingBytes = f.sendBatchSize * mtu
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case batch := <-f.outbound[i]:
|
||||
for _, data := range batch.payloads {
|
||||
sendBuf := f.getSendBuffer(i)
|
||||
buf := (*sendBuf)[:0]
|
||||
queue := func(addr netip.AddrPort, length int) {
|
||||
if len(pending) >= maxPendingPackets || pendingBytes+length > maxPendingBytes {
|
||||
f.flushSendQueue(i, &pending, &pendingBytes)
|
||||
}
|
||||
pending = append(pending, outboundSend{
|
||||
buf: sendBuf,
|
||||
length: length,
|
||||
addr: addr,
|
||||
})
|
||||
pendingBytes += length
|
||||
if len(pending) >= f.sendBatchSize || pendingBytes >= maxPendingBytes {
|
||||
f.flushSendQueue(i, &pending, &pendingBytes)
|
||||
}
|
||||
}
|
||||
sent := f.consumeInsidePacket(*data, fwPacket1, nb1, buf, queue, i, conntrackCache.Get(f.l))
|
||||
if !sent {
|
||||
f.releaseSendBuffer(i, sendBuf)
|
||||
}
|
||||
*data = (*data)[:mtu]
|
||||
f.outPool.Put(data)
|
||||
}
|
||||
f.releaseOutboundBatch(batch)
|
||||
if len(pending) > 0 {
|
||||
f.flushSendQueue(i, &pending, &pendingBytes)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
if len(pending) > 0 {
|
||||
f.flushSendQueue(i, &pending, &pendingBytes)
|
||||
}
|
||||
f.wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Interface) RegisterConfigChangeCallbacks(c *config.C) {
|
||||
c.RegisterReloadCallback(f.reloadFirewall)
|
||||
c.RegisterReloadCallback(f.reloadSendRecvError)
|
||||
c.RegisterReloadCallback(f.reloadAcceptRecvError)
|
||||
c.RegisterReloadCallback(f.reloadDisconnectInvalid)
|
||||
c.RegisterReloadCallback(f.reloadMisc)
|
||||
|
||||
@@ -377,16 +753,16 @@ func (f *Interface) reloadSendRecvError(c *config.C) {
|
||||
|
||||
switch stringValue {
|
||||
case "always":
|
||||
f.sendRecvErrorConfig = recvErrorAlways
|
||||
f.sendRecvErrorConfig = sendRecvErrorAlways
|
||||
case "never":
|
||||
f.sendRecvErrorConfig = recvErrorNever
|
||||
f.sendRecvErrorConfig = sendRecvErrorNever
|
||||
case "private":
|
||||
f.sendRecvErrorConfig = recvErrorPrivate
|
||||
f.sendRecvErrorConfig = sendRecvErrorPrivate
|
||||
default:
|
||||
if c.GetBool("listen.send_recv_error", true) {
|
||||
f.sendRecvErrorConfig = recvErrorAlways
|
||||
f.sendRecvErrorConfig = sendRecvErrorAlways
|
||||
} else {
|
||||
f.sendRecvErrorConfig = recvErrorNever
|
||||
f.sendRecvErrorConfig = sendRecvErrorNever
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,30 +771,6 @@ func (f *Interface) reloadSendRecvError(c *config.C) {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Interface) reloadAcceptRecvError(c *config.C) {
|
||||
if c.InitialLoad() || c.HasChanged("listen.accept_recv_error") {
|
||||
stringValue := c.GetString("listen.accept_recv_error", "always")
|
||||
|
||||
switch stringValue {
|
||||
case "always":
|
||||
f.acceptRecvErrorConfig = recvErrorAlways
|
||||
case "never":
|
||||
f.acceptRecvErrorConfig = recvErrorNever
|
||||
case "private":
|
||||
f.acceptRecvErrorConfig = recvErrorPrivate
|
||||
default:
|
||||
if c.GetBool("listen.accept_recv_error", true) {
|
||||
f.acceptRecvErrorConfig = recvErrorAlways
|
||||
} else {
|
||||
f.acceptRecvErrorConfig = recvErrorNever
|
||||
}
|
||||
}
|
||||
|
||||
f.l.WithField("acceptRecvError", f.acceptRecvErrorConfig.String()).
|
||||
Info("Loaded accept_recv_error config")
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Interface) reloadMisc(c *config.C) {
|
||||
if c.HasChanged("counters.try_promote") {
|
||||
n := c.GetUint32("counters.try_promote", defaultPromoteEvery)
|
||||
@@ -484,21 +836,21 @@ func (f *Interface) GetCertState() *CertState {
|
||||
func (f *Interface) Close() error {
|
||||
f.closed.Store(true)
|
||||
|
||||
// Release the udp readers
|
||||
for _, u := range f.writers {
|
||||
err := u.Close()
|
||||
if err != nil {
|
||||
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 readers
|
||||
for _, u := range f.readers {
|
||||
err := u.Close()
|
||||
if err != nil {
|
||||
f.l.WithError(err).Error("Error while closing tun device")
|
||||
}
|
||||
}
|
||||
|
||||
// Release the tun device
|
||||
return f.inside.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
+4
-13
@@ -360,8 +360,7 @@ func (lh *LightHouse) parseLighthouses(c *config.C) ([]netip.Addr, error) {
|
||||
}
|
||||
|
||||
if !lh.myVpnNetworksTable.Contains(addr) {
|
||||
lh.l.WithFields(m{"vpnAddr": addr, "networks": lh.myVpnNetworks}).
|
||||
Warn("lighthouse host is not within our networks, lighthouse functionality will work but layer 3 network traffic to the lighthouse will not")
|
||||
return nil, util.NewContextualError("lighthouse host is not in our networks, invalid", m{"vpnAddr": addr, "networks": lh.myVpnNetworks}, nil)
|
||||
}
|
||||
out[i] = addr
|
||||
}
|
||||
@@ -432,8 +431,7 @@ func (lh *LightHouse) loadStaticMap(c *config.C, staticList map[netip.Addr]struc
|
||||
}
|
||||
|
||||
if !lh.myVpnNetworksTable.Contains(vpnAddr) {
|
||||
lh.l.WithFields(m{"vpnAddr": vpnAddr, "networks": lh.myVpnNetworks, "entry": i + 1}).
|
||||
Warn("static_host_map key is not within our networks, layer 3 network traffic to this host will not work")
|
||||
return util.NewContextualError("static_host_map key is not in our network, invalid", m{"vpnAddr": vpnAddr, "networks": lh.myVpnNetworks, "entry": i + 1}, nil)
|
||||
}
|
||||
|
||||
vals, ok := v.([]any)
|
||||
@@ -1339,19 +1337,12 @@ func (lhh *LightHouseHandler) handleHostPunchNotification(n *NebulaMeta, fromVpn
|
||||
}
|
||||
}
|
||||
|
||||
remoteAllowList := lhh.lh.GetRemoteAllowList()
|
||||
for _, a := range n.Details.V4AddrPorts {
|
||||
b := protoV4AddrPortToNetAddrPort(a)
|
||||
if remoteAllowList.Allow(detailsVpnAddr, b.Addr()) {
|
||||
punch(b, detailsVpnAddr)
|
||||
}
|
||||
punch(protoV4AddrPortToNetAddrPort(a), detailsVpnAddr)
|
||||
}
|
||||
|
||||
for _, a := range n.Details.V6AddrPorts {
|
||||
b := protoV6AddrPortToNetAddrPort(a)
|
||||
if remoteAllowList.Allow(detailsVpnAddr, b.Addr()) {
|
||||
punch(b, detailsVpnAddr)
|
||||
}
|
||||
punch(protoV6AddrPortToNetAddrPort(a), detailsVpnAddr)
|
||||
}
|
||||
|
||||
// This sends a nebula test packet to the host trying to contact us. In the case
|
||||
|
||||
+10
-9
@@ -1,6 +1,7 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
"github.com/slackhq/nebula/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestOldIPv4Only(t *testing.T) {
|
||||
@@ -41,14 +42,14 @@ func Test_lhStaticMapping(t *testing.T) {
|
||||
c := config.NewC(l)
|
||||
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1}}
|
||||
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
||||
_, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
_, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
lh2 := "10.128.0.3"
|
||||
c = config.NewC(l)
|
||||
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1, lh2}}
|
||||
c.Settings["static_host_map"] = map[string]any{lh1: []any{"100.1.1.1:4242"}}
|
||||
_, err = NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
_, err = NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
require.EqualError(t, err, "lighthouse 10.128.0.3 does not have a static_host_map entry")
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ func TestReloadLighthouseInterval(t *testing.T) {
|
||||
}
|
||||
|
||||
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
require.NoError(t, err)
|
||||
lh.ifce = &mockEncWriter{}
|
||||
|
||||
@@ -98,7 +99,7 @@ func BenchmarkLighthouseHandleRequest(b *testing.B) {
|
||||
}
|
||||
|
||||
c := config.NewC(l)
|
||||
lh, err := NewLightHouseFromConfig(b.Context(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
require.NoError(b, err)
|
||||
|
||||
hAddr := netip.MustParseAddrPort("4.5.6.7:12345")
|
||||
@@ -201,7 +202,7 @@ func TestLighthouse_Memory(t *testing.T) {
|
||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||
myVpnNetworksTable: nt,
|
||||
}
|
||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
lh.ifce = &mockEncWriter{}
|
||||
require.NoError(t, err)
|
||||
lhh := lh.NewRequestHandler()
|
||||
@@ -287,7 +288,7 @@ func TestLighthouse_reload(t *testing.T) {
|
||||
myVpnNetworksTable: nt,
|
||||
}
|
||||
|
||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
nc := map[string]any{
|
||||
@@ -522,7 +523,7 @@ func TestLighthouse_Dont_Delete_Static_Hosts(t *testing.T) {
|
||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||
myVpnNetworksTable: nt,
|
||||
}
|
||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
require.NoError(t, err)
|
||||
lh.ifce = &mockEncWriter{}
|
||||
|
||||
@@ -588,7 +589,7 @@ func TestLighthouse_DeletesWork(t *testing.T) {
|
||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||
myVpnNetworksTable: nt,
|
||||
}
|
||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||
require.NoError(t, err)
|
||||
lh.ifce = &mockEncWriter{}
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -15,7 +13,7 @@ import (
|
||||
"github.com/slackhq/nebula/sshd"
|
||||
"github.com/slackhq/nebula/udp"
|
||||
"github.com/slackhq/nebula/util"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type m = map[string]any
|
||||
@@ -29,10 +27,6 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
||||
}
|
||||
}()
|
||||
|
||||
if buildVersion == "" {
|
||||
buildVersion = moduleVersion()
|
||||
}
|
||||
|
||||
l := logger
|
||||
l.Formatter = &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
@@ -81,8 +75,7 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
||||
if c.GetBool("sshd.enabled", false) {
|
||||
sshStart, err = configSSH(l, ssh, c)
|
||||
if err != nil {
|
||||
l.WithError(err).Warn("Failed to configure sshd, ssh debugging will not be available")
|
||||
sshStart = nil
|
||||
return nil, util.ContextualizeIfNeeded("Error while configuring the sshd", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +164,7 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
||||
|
||||
for i := 0; i < routines; i++ {
|
||||
l.Infof("listening on %v", netip.AddrPortFrom(listenHost, uint16(port)))
|
||||
udpServer, err := udp.NewListener(l, listenHost, port, routines > 1, c.GetInt("listen.batch", 64))
|
||||
udpServer, err := udp.NewListener(l, listenHost, port, routines > 1, c.GetInt("listen.batch", 128))
|
||||
if err != nil {
|
||||
return nil, util.NewContextualError("Failed to open udp listener", m{"queue": i}, err)
|
||||
}
|
||||
@@ -228,6 +221,16 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
||||
}
|
||||
}
|
||||
|
||||
batchCfg := BatchConfig{
|
||||
InboundBatchSize: c.GetInt("batch.inbound_size", inboundBatchSizeDefault),
|
||||
OutboundBatchSize: c.GetInt("batch.outbound_size", outboundBatchSizeDefault),
|
||||
FlushInterval: c.GetDuration("batch.flush_interval", batchFlushIntervalDefault),
|
||||
MaxOutstandingPerChan: c.GetInt("batch.max_outstanding", maxOutstandingBatchesDefault),
|
||||
MaxPendingPackets: c.GetInt("batch.max_pending_packets", 0),
|
||||
MaxPendingBytes: c.GetInt("batch.max_pending_bytes", 0),
|
||||
MaxSendBuffersPerChan: c.GetInt("batch.max_send_buffers_per_routine", 0),
|
||||
}
|
||||
|
||||
ifConfig := &InterfaceConfig{
|
||||
HostMap: hostMap,
|
||||
Inside: tun,
|
||||
@@ -249,6 +252,7 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
||||
relayManager: NewRelayManager(ctx, l, hostMap, c),
|
||||
punchy: punchy,
|
||||
ConntrackCacheTimeout: conntrackCacheTimeout,
|
||||
BatchConfig: batchCfg,
|
||||
l: l,
|
||||
}
|
||||
|
||||
@@ -265,7 +269,6 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
||||
ifce.RegisterConfigChangeCallbacks(c)
|
||||
ifce.reloadDisconnectInvalid(c)
|
||||
ifce.reloadSendRecvError(c)
|
||||
ifce.reloadAcceptRecvError(c)
|
||||
|
||||
handshakeManager.f = ifce
|
||||
go handshakeManager.Run(ctx)
|
||||
@@ -292,29 +295,14 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
||||
}
|
||||
|
||||
return &Control{
|
||||
ifce,
|
||||
l,
|
||||
ctx,
|
||||
cancel,
|
||||
sshStart,
|
||||
statsStart,
|
||||
dnsStart,
|
||||
lightHouse.StartUpdateWorker,
|
||||
connManager.Start,
|
||||
f: ifce,
|
||||
l: l,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
sshStart: sshStart,
|
||||
statsStart: statsStart,
|
||||
dnsStart: dnsStart,
|
||||
lighthouseStart: lightHouse.StartUpdateWorker,
|
||||
connectionManagerStart: connManager.Start,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func moduleVersion() string {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, dep := range info.Deps {
|
||||
if dep.Path == "github.com/slackhq/nebula" {
|
||||
return strings.TrimPrefix(dep.Version, "v")
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ const EncryptLockNeeded = true
|
||||
// NewGCMTLS is no longer exposed in go1.19+, so we need to link it in
|
||||
// See: https://github.com/golang/go/issues/56326
|
||||
//
|
||||
// NewGCMTLS is the internal method used with boringcrypto that provides a
|
||||
// NewGCMTLS is the internal method used with boringcrypto that provices a
|
||||
// validated mode of AES-GCM which enforces the nonce is strictly
|
||||
// monotonically increasing. This is the TLS 1.2 specification for nonce
|
||||
// generation (which also matches the method used by the Noise Protocol)
|
||||
|
||||
+226
-52
@@ -1,30 +1,39 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/net/ipv6"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/firewall"
|
||||
"github.com/slackhq/nebula/header"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
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) {
|
||||
const (
|
||||
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) {
|
||||
err := h.Parse(packet)
|
||||
if err != nil {
|
||||
// Hole punch packets are 0 or 1 byte big, so lets ignore printing those errors
|
||||
if len(packet) > 1 {
|
||||
f.l.WithField("packet", packet).Infof("Error while parsing inbound packet from %s: %s", via, err)
|
||||
f.l.WithField("packet", packet).Infof("Error while parsing inbound packet from %s: %s", ip, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//l.Error("in packet ", header, packet[HeaderLen:])
|
||||
if !via.IsRelayed {
|
||||
if f.myVpnNetworksTable.Contains(via.UdpAddr.Addr()) {
|
||||
//f.l.Error("in packet ", h)
|
||||
if ip.IsValid() {
|
||||
if f.myVpnNetworksTable.Contains(ip.Addr()) {
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
f.l.WithField("from", via).Debug("Refusing to process double encrypted packet")
|
||||
f.l.WithField("udpAddr", ip).Debug("Refusing to process double encrypted packet")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -45,7 +54,8 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
||||
|
||||
switch h.Type {
|
||||
case header.Message:
|
||||
if !f.handleEncrypted(ci, via, h) {
|
||||
// TODO handleEncrypted sends directly to addr on error. Handle this in the tunneling case.
|
||||
if !f.handleEncrypted(ci, ip, h) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,7 +79,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
||||
// Successfully validated the thing. Get rid of the Relay header.
|
||||
signedPayload = signedPayload[header.Len:]
|
||||
// Pull the Roaming parts up here, and return in all call paths.
|
||||
f.handleHostRoaming(hostinfo, via)
|
||||
f.handleHostRoaming(hostinfo, ip)
|
||||
// Track usage of both the HostInfo and the Relay for the received & authenticated packet
|
||||
f.connectionManager.In(hostinfo)
|
||||
f.connectionManager.RelayUsed(h.RemoteIndex)
|
||||
@@ -86,14 +96,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
||||
case TerminalType:
|
||||
// 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.
|
||||
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)
|
||||
f.readOutsidePackets(netip.AddrPort{}, &ViaSender{relayHI: hostinfo, remoteIdx: relay.RemoteIndex, relay: relay}, out[:0], signedPayload, h, fwPacket, lhf, nb, q, localCache)
|
||||
return
|
||||
case ForwardingType:
|
||||
// Find the target HostInfo relay object
|
||||
@@ -123,32 +126,31 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
||||
|
||||
case header.LightHouse:
|
||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||
if !f.handleEncrypted(ci, via, h) {
|
||||
if !f.handleEncrypted(ci, ip, h) {
|
||||
return
|
||||
}
|
||||
|
||||
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).WithField("from", via).
|
||||
hostinfo.logger(f.l).WithError(err).WithField("udpAddr", ip).
|
||||
WithField("packet", packet).
|
||||
Error("Failed to decrypt lighthouse packet")
|
||||
return
|
||||
}
|
||||
|
||||
//TODO: assert via is not relayed
|
||||
lhf.HandleRequest(via.UdpAddr, hostinfo.vpnAddrs, d, f)
|
||||
lhf.HandleRequest(ip, hostinfo.vpnAddrs, d, f)
|
||||
|
||||
// Fallthrough to the bottom to record incoming traffic
|
||||
|
||||
case header.Test:
|
||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||
if !f.handleEncrypted(ci, via, h) {
|
||||
if !f.handleEncrypted(ci, ip, h) {
|
||||
return
|
||||
}
|
||||
|
||||
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).WithField("from", via).
|
||||
hostinfo.logger(f.l).WithError(err).WithField("udpAddr", ip).
|
||||
WithField("packet", packet).
|
||||
Error("Failed to decrypt test packet")
|
||||
return
|
||||
@@ -157,7 +159,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
||||
if h.Subtype == header.TestRequest {
|
||||
// This testRequest might be from TryPromoteBest, so we should roam
|
||||
// to the new IP address before responding
|
||||
f.handleHostRoaming(hostinfo, via)
|
||||
f.handleHostRoaming(hostinfo, ip)
|
||||
f.send(header.Test, header.TestReply, ci, hostinfo, d, nb, out)
|
||||
}
|
||||
|
||||
@@ -168,34 +170,34 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
||||
|
||||
case header.Handshake:
|
||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||
f.handshakeManager.HandleIncoming(via, packet, h)
|
||||
f.handshakeManager.HandleIncoming(ip, via, packet, h)
|
||||
return
|
||||
|
||||
case header.RecvError:
|
||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||
f.handleRecvError(via.UdpAddr, h)
|
||||
f.handleRecvError(ip, h)
|
||||
return
|
||||
|
||||
case header.CloseTunnel:
|
||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||
if !f.handleEncrypted(ci, via, h) {
|
||||
if !f.handleEncrypted(ci, ip, h) {
|
||||
return
|
||||
}
|
||||
|
||||
hostinfo.logger(f.l).WithField("from", via).
|
||||
hostinfo.logger(f.l).WithField("udpAddr", ip).
|
||||
Info("Close tunnel received, tearing down.")
|
||||
|
||||
f.closeTunnel(hostinfo)
|
||||
return
|
||||
|
||||
case header.Control:
|
||||
if !f.handleEncrypted(ci, via, h) {
|
||||
if !f.handleEncrypted(ci, ip, h) {
|
||||
return
|
||||
}
|
||||
|
||||
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).WithField("from", via).
|
||||
hostinfo.logger(f.l).WithError(err).WithField("udpAddr", ip).
|
||||
WithField("packet", packet).
|
||||
Error("Failed to decrypt Control packet")
|
||||
return
|
||||
@@ -205,11 +207,11 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
||||
|
||||
default:
|
||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||
hostinfo.logger(f.l).Debugf("Unexpected packet received from %s", via)
|
||||
hostinfo.logger(f.l).Debugf("Unexpected packet received from %s", ip)
|
||||
return
|
||||
}
|
||||
|
||||
f.handleHostRoaming(hostinfo, via)
|
||||
f.handleHostRoaming(hostinfo, ip)
|
||||
|
||||
f.connectionManager.In(hostinfo)
|
||||
}
|
||||
@@ -228,36 +230,37 @@ func (f *Interface) sendCloseTunnel(h *HostInfo) {
|
||||
f.send(header.CloseTunnel, 0, h.ConnectionState, h, []byte{}, make([]byte, 12, 12), make([]byte, mtu))
|
||||
}
|
||||
|
||||
func (f *Interface) handleHostRoaming(hostinfo *HostInfo, via ViaSender) {
|
||||
if !via.IsRelayed && hostinfo.remote != via.UdpAddr {
|
||||
if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, via.UdpAddr.Addr()) {
|
||||
hostinfo.logger(f.l).WithField("newAddr", via.UdpAddr).Debug("lighthouse.remote_allow_list denied roaming")
|
||||
func (f *Interface) handleHostRoaming(hostinfo *HostInfo, udpAddr netip.AddrPort) {
|
||||
if udpAddr.IsValid() && hostinfo.remote != udpAddr {
|
||||
if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, udpAddr.Addr()) {
|
||||
hostinfo.logger(f.l).WithField("newAddr", udpAddr).Debug("lighthouse.remote_allow_list denied roaming")
|
||||
return
|
||||
}
|
||||
|
||||
if !hostinfo.lastRoam.IsZero() && via.UdpAddr == hostinfo.lastRoamRemote && time.Since(hostinfo.lastRoam) < RoamingSuppressSeconds*time.Second {
|
||||
if !hostinfo.lastRoam.IsZero() && udpAddr == hostinfo.lastRoamRemote && time.Since(hostinfo.lastRoam) < RoamingSuppressSeconds*time.Second {
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
hostinfo.logger(f.l).WithField("udpAddr", hostinfo.remote).WithField("newAddr", via.UdpAddr).
|
||||
hostinfo.logger(f.l).WithField("udpAddr", hostinfo.remote).WithField("newAddr", udpAddr).
|
||||
Debugf("Suppressing roam back to previous remote for %d seconds", RoamingSuppressSeconds)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
hostinfo.logger(f.l).WithField("udpAddr", hostinfo.remote).WithField("newAddr", via.UdpAddr).
|
||||
//TODO: Seems we have a bunch of stuff racing here, since we don't have a lock on hostinfo anymore we announce roaming in bursts
|
||||
hostinfo.logger(f.l).WithField("udpAddr", hostinfo.remote).WithField("newAddr", udpAddr).
|
||||
Info("Host roamed to new udp ip/port.")
|
||||
hostinfo.lastRoam = time.Now()
|
||||
hostinfo.lastRoamRemote = hostinfo.remote
|
||||
hostinfo.SetRemote(via.UdpAddr)
|
||||
hostinfo.SetRemote(udpAddr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// handleEncrypted returns true if a packet should be processed, false otherwise
|
||||
func (f *Interface) handleEncrypted(ci *ConnectionState, via ViaSender, h *header.H) bool {
|
||||
func (f *Interface) handleEncrypted(ci *ConnectionState, addr netip.AddrPort, h *header.H) bool {
|
||||
// If connectionstate does not exist, send a recv error, if possible, to encourage a fast reconnect
|
||||
if ci == nil {
|
||||
if !via.IsRelayed {
|
||||
f.maybeSendRecvError(via.UdpAddr, h.RemoteIndex)
|
||||
if addr.IsValid() {
|
||||
f.maybeSendRecvError(addr, h.RemoteIndex)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -269,6 +272,184 @@ func (f *Interface) handleEncrypted(ci *ConnectionState, via ViaSender, h *heade
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
ErrPacketTooShort = errors.New("packet is too short")
|
||||
ErrUnknownIPVersion = errors.New("packet is an unknown ip version")
|
||||
ErrIPv4InvalidHeaderLength = errors.New("invalid ipv4 header length")
|
||||
ErrIPv4PacketTooShort = errors.New("ipv4 packet is too short")
|
||||
ErrIPv6PacketTooShort = errors.New("ipv6 packet is too short")
|
||||
ErrIPv6CouldNotFindPayload = errors.New("could not find payload in ipv6 packet")
|
||||
)
|
||||
|
||||
// newPacket validates and parses the interesting bits for the firewall out of the ip and sub protocol headers
|
||||
func newPacket(data []byte, incoming bool, fp *firewall.Packet) error {
|
||||
if len(data) < 1 {
|
||||
return ErrPacketTooShort
|
||||
}
|
||||
|
||||
version := int((data[0] >> 4) & 0x0f)
|
||||
switch version {
|
||||
case ipv4.Version:
|
||||
return parseV4(data, incoming, fp)
|
||||
case ipv6.Version:
|
||||
return parseV6(data, incoming, fp)
|
||||
}
|
||||
return ErrUnknownIPVersion
|
||||
}
|
||||
|
||||
func parseV6(data []byte, incoming bool, fp *firewall.Packet) error {
|
||||
dataLen := len(data)
|
||||
if dataLen < ipv6.HeaderLen {
|
||||
return ErrIPv6PacketTooShort
|
||||
}
|
||||
|
||||
if incoming {
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[8:24])
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[24:40])
|
||||
} else {
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[8:24])
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[24:40])
|
||||
}
|
||||
|
||||
protoAt := 6 // NextHeader is at 6 bytes into the ipv6 header
|
||||
offset := ipv6.HeaderLen // Start at the end of the ipv6 header
|
||||
next := 0
|
||||
for {
|
||||
if protoAt >= dataLen {
|
||||
break
|
||||
}
|
||||
proto := layers.IPProtocol(data[protoAt])
|
||||
|
||||
switch proto {
|
||||
case layers.IPProtocolICMPv6, layers.IPProtocolESP, layers.IPProtocolNoNextHeader:
|
||||
fp.Protocol = uint8(proto)
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
fp.Fragment = false
|
||||
return nil
|
||||
|
||||
case layers.IPProtocolTCP, layers.IPProtocolUDP:
|
||||
if dataLen < offset+4 {
|
||||
return ErrIPv6PacketTooShort
|
||||
}
|
||||
|
||||
fp.Protocol = uint8(proto)
|
||||
if incoming {
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[offset : offset+2])
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
|
||||
} else {
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[offset : offset+2])
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
|
||||
}
|
||||
|
||||
fp.Fragment = false
|
||||
return nil
|
||||
|
||||
case layers.IPProtocolIPv6Fragment:
|
||||
// Fragment header is 8 bytes, need at least offset+4 to read the offset field
|
||||
if dataLen < offset+8 {
|
||||
return ErrIPv6PacketTooShort
|
||||
}
|
||||
|
||||
// Check if this is the first fragment
|
||||
fragmentOffset := binary.BigEndian.Uint16(data[offset+2:offset+4]) &^ uint16(0x7) // Remove the reserved and M flag bits
|
||||
if fragmentOffset != 0 {
|
||||
// Non-first fragment, use what we have now and stop processing
|
||||
fp.Protocol = data[offset]
|
||||
fp.Fragment = true
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// The next loop should be the transport layer since we are the first fragment
|
||||
next = 8 // Fragment headers are always 8 bytes
|
||||
|
||||
case layers.IPProtocolAH:
|
||||
// Auth headers, used by IPSec, have a different meaning for header length
|
||||
if dataLen <= offset+1 {
|
||||
break
|
||||
}
|
||||
|
||||
next = int(data[offset+1]+2) << 2
|
||||
|
||||
default:
|
||||
// Normal ipv6 header length processing
|
||||
if dataLen <= offset+1 {
|
||||
break
|
||||
}
|
||||
|
||||
next = int(data[offset+1]+1) << 3
|
||||
}
|
||||
|
||||
if next <= 0 {
|
||||
// Safety check, each ipv6 header has to be at least 8 bytes
|
||||
next = 8
|
||||
}
|
||||
|
||||
protoAt = offset
|
||||
offset = offset + next
|
||||
}
|
||||
|
||||
return ErrIPv6CouldNotFindPayload
|
||||
}
|
||||
|
||||
func parseV4(data []byte, incoming bool, fp *firewall.Packet) error {
|
||||
// Do we at least have an ipv4 header worth of data?
|
||||
if len(data) < ipv4.HeaderLen {
|
||||
return ErrIPv4PacketTooShort
|
||||
}
|
||||
|
||||
// Adjust our start position based on the advertised ip header length
|
||||
ihl := int(data[0]&0x0f) << 2
|
||||
|
||||
// Well-formed ip header length?
|
||||
if ihl < ipv4.HeaderLen {
|
||||
return ErrIPv4InvalidHeaderLength
|
||||
}
|
||||
|
||||
// Check if this is the second or further fragment of a fragmented packet.
|
||||
flagsfrags := binary.BigEndian.Uint16(data[6:8])
|
||||
fp.Fragment = (flagsfrags & 0x1FFF) != 0
|
||||
|
||||
// Firewall handles protocol checks
|
||||
fp.Protocol = data[9]
|
||||
|
||||
// Accounting for a variable header length, do we have enough data for our src/dst tuples?
|
||||
minLen := ihl
|
||||
if !fp.Fragment && fp.Protocol != firewall.ProtoICMP {
|
||||
minLen += minFwPacketLen
|
||||
}
|
||||
if len(data) < minLen {
|
||||
return ErrIPv4InvalidHeaderLength
|
||||
}
|
||||
|
||||
// Firewall packets are locally oriented
|
||||
if incoming {
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||
if fp.Fragment || fp.Protocol == firewall.ProtoICMP {
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
} else {
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||
}
|
||||
} else {
|
||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||
if fp.Fragment || fp.Protocol == firewall.ProtoICMP {
|
||||
fp.RemotePort = 0
|
||||
fp.LocalPort = 0
|
||||
} else {
|
||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Interface) decrypt(hostinfo *HostInfo, mc uint64, out []byte, packet []byte, h *header.H, nb []byte) ([]byte, error) {
|
||||
var err error
|
||||
out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], mc, nb)
|
||||
@@ -290,11 +471,11 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
|
||||
|
||||
out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], messageCounter, nb)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).Error("Failed to decrypt packet")
|
||||
hostinfo.logger(f.l).WithError(err).WithField("fwPacket", fwPacket).Error("Failed to decrypt packet")
|
||||
return false
|
||||
}
|
||||
|
||||
err = firewall.NewPacket(out, true, fwPacket)
|
||||
err = newPacket(out, true, fwPacket)
|
||||
if err != nil {
|
||||
hostinfo.logger(f.l).WithError(err).WithField("packet", out).
|
||||
Warnf("Error while validating inbound packet")
|
||||
@@ -329,7 +510,7 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
|
||||
}
|
||||
|
||||
func (f *Interface) maybeSendRecvError(endpoint netip.AddrPort, index uint32) {
|
||||
if f.sendRecvErrorConfig.ShouldRecvError(endpoint) {
|
||||
if f.sendRecvErrorConfig.ShouldSendRecvError(endpoint) {
|
||||
f.sendRecvError(endpoint, index)
|
||||
}
|
||||
}
|
||||
@@ -347,13 +528,6 @@ func (f *Interface) sendRecvError(endpoint netip.AddrPort, index uint32) {
|
||||
}
|
||||
|
||||
func (f *Interface) handleRecvError(addr netip.AddrPort, h *header.H) {
|
||||
if !f.acceptRecvErrorConfig.ShouldRecvError(addr) {
|
||||
f.l.WithField("index", h.RemoteIndex).
|
||||
WithField("udpAddr", addr).
|
||||
Debug("Recv error received, ignoring")
|
||||
return
|
||||
}
|
||||
|
||||
if f.l.Level >= logrus.DebugLevel {
|
||||
f.l.WithField("index", h.RemoteIndex).
|
||||
WithField("udpAddr", addr).
|
||||
|
||||
+29
-29
@@ -20,13 +20,13 @@ func Test_newPacket(t *testing.T) {
|
||||
p := &firewall.Packet{}
|
||||
|
||||
// length fails
|
||||
err := firewall.NewPacket([]byte{}, true, p)
|
||||
err := newPacket([]byte{}, true, p)
|
||||
require.ErrorIs(t, err, ErrPacketTooShort)
|
||||
|
||||
err = firewall.NewPacket([]byte{0x40}, true, p)
|
||||
err = newPacket([]byte{0x40}, true, p)
|
||||
require.ErrorIs(t, err, ErrIPv4PacketTooShort)
|
||||
|
||||
err = firewall.NewPacket([]byte{0x60}, true, p)
|
||||
err = newPacket([]byte{0x60}, true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||
|
||||
// length fail with ip options
|
||||
@@ -39,15 +39,15 @@ func Test_newPacket(t *testing.T) {
|
||||
}
|
||||
|
||||
b, _ := h.Marshal()
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
err = newPacket(b, true, p)
|
||||
require.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)
|
||||
|
||||
// not an ipv4 packet
|
||||
err = firewall.NewPacket([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)
|
||||
err = newPacket([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)
|
||||
require.ErrorIs(t, err, ErrUnknownIPVersion)
|
||||
|
||||
// invalid ihl
|
||||
err = firewall.NewPacket([]byte{4<<4 | (8 >> 2 & 0x0f), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)
|
||||
err = newPacket([]byte{4<<4 | (8 >> 2 & 0x0f), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)
|
||||
require.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)
|
||||
|
||||
// account for variable ip header length - incoming
|
||||
@@ -62,7 +62,7 @@ func Test_newPacket(t *testing.T) {
|
||||
|
||||
b, _ = h.Marshal()
|
||||
b = append(b, []byte{0, 3, 0, 4}...)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
err = newPacket(b, true, p)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
||||
@@ -84,7 +84,7 @@ func Test_newPacket(t *testing.T) {
|
||||
|
||||
b, _ = h.Marshal()
|
||||
b = append(b, []byte{0, 5, 0, 6}...)
|
||||
err = firewall.NewPacket(b, false, p)
|
||||
err = newPacket(b, false, p)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(2), p.Protocol)
|
||||
@@ -114,7 +114,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
err := gopacket.SerializeLayers(buffer, opt, &ip)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = firewall.NewPacket(buffer.Bytes(), true, p)
|
||||
err = newPacket(buffer.Bytes(), true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||
|
||||
// A v6 packet with a hop-by-hop extension
|
||||
@@ -148,12 +148,12 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
|
||||
// A full IPv6 header and 1 byte in the first extension, but missing
|
||||
// the length byte.
|
||||
err = firewall.NewPacket(buffer.Bytes()[:41], true, p)
|
||||
err = newPacket(buffer.Bytes()[:41], true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||
|
||||
// A full IPv6 header plus 1 full extension, but only 1 byte of the
|
||||
// next layer, missing length byte
|
||||
err = firewall.NewPacket(buffer.Bytes()[:49], true, p)
|
||||
err = newPacket(buffer.Bytes()[:49], true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||
|
||||
// A good ICMP packet
|
||||
@@ -173,7 +173,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = firewall.NewPacket(buffer.Bytes(), true, p)
|
||||
err = newPacket(buffer.Bytes(), true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(layers.IPProtocolICMPv6), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -185,7 +185,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
// A good ESP packet
|
||||
b := buffer.Bytes()
|
||||
b[6] = byte(layers.IPProtocolESP)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
err = newPacket(b, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(layers.IPProtocolESP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -197,7 +197,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
// A good None packet
|
||||
b = buffer.Bytes()
|
||||
b[6] = byte(layers.IPProtocolNoNextHeader)
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
err = newPacket(b, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(layers.IPProtocolNoNextHeader), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -209,7 +209,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
// An unknown protocol packet
|
||||
b = buffer.Bytes()
|
||||
b[6] = 255 // 255 is a reserved protocol number
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
err = newPacket(b, true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||
|
||||
// A good UDP packet
|
||||
@@ -236,7 +236,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
b = buffer.Bytes()
|
||||
|
||||
// incoming
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
err = newPacket(b, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -246,7 +246,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// outgoing
|
||||
err = firewall.NewPacket(b, false, p)
|
||||
err = newPacket(b, false, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||
@@ -256,14 +256,14 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// Too short UDP packet
|
||||
err = firewall.NewPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
||||
err = newPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||
|
||||
// A good TCP packet
|
||||
b[6] = byte(layers.IPProtocolTCP)
|
||||
|
||||
// incoming
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
err = newPacket(b, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -273,7 +273,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// outgoing
|
||||
err = firewall.NewPacket(b, false, p)
|
||||
err = newPacket(b, false, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||
@@ -283,7 +283,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// Too short TCP packet
|
||||
err = firewall.NewPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
||||
err = newPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||
|
||||
// A good UDP packet with an AH header
|
||||
@@ -318,7 +318,7 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
b = append(b, ahb...)
|
||||
b = append(b, udpHeader...)
|
||||
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
err = newPacket(b, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
@@ -328,12 +328,12 @@ func Test_newPacket_v6(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// Ensure buffer bounds checking during processing
|
||||
err = firewall.NewPacket(b[:41], true, p)
|
||||
err = newPacket(b[:41], true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||
|
||||
// Invalid AH header
|
||||
b = buffer.Bytes()
|
||||
err = firewall.NewPacket(b, true, p)
|
||||
err = newPacket(b, true, p)
|
||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
||||
firstFrag = append(firstFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
|
||||
|
||||
// Test first fragment incoming
|
||||
err = firewall.NewPacket(firstFrag, true, p)
|
||||
err = newPacket(firstFrag, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
||||
@@ -391,7 +391,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
||||
assert.False(t, p.Fragment)
|
||||
|
||||
// Test first fragment outgoing
|
||||
err = firewall.NewPacket(firstFrag, false, p)
|
||||
err = newPacket(firstFrag, false, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
|
||||
@@ -420,7 +420,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
||||
secondFrag = append(secondFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
|
||||
|
||||
// Test second fragment incoming
|
||||
err = firewall.NewPacket(secondFrag, true, p)
|
||||
err = newPacket(secondFrag, true, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
||||
@@ -430,7 +430,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
||||
assert.True(t, p.Fragment)
|
||||
|
||||
// Test second fragment outgoing
|
||||
err = firewall.NewPacket(secondFrag, false, p)
|
||||
err = newPacket(secondFrag, false, p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
|
||||
@@ -440,7 +440,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
||||
assert.True(t, p.Fragment)
|
||||
|
||||
// Too short of a fragment packet
|
||||
err = firewall.NewPacket(secondFrag[:len(secondFrag)-10], false, p)
|
||||
err = newPacket(secondFrag[:len(secondFrag)-10], false, p)
|
||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,5 @@ type Device interface {
|
||||
Networks() []netip.Prefix
|
||||
Name() string
|
||||
RoutesFor(netip.Addr) routing.Gateways
|
||||
SupportsMultiqueue() bool
|
||||
NewMultiQueueReader() (io.ReadWriteCloser, error)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
@@ -12,15 +11,6 @@ import (
|
||||
|
||||
const DefaultMTU = 1300
|
||||
|
||||
type NameError struct {
|
||||
Name string
|
||||
Underlying error
|
||||
}
|
||||
|
||||
func (e *NameError) Error() string {
|
||||
return fmt.Sprintf("could not set tun device name: %s because %s", e.Name, e.Underlying)
|
||||
}
|
||||
|
||||
// TODO: We may be able to remove routines
|
||||
type DeviceFactory func(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error)
|
||||
|
||||
@@ -91,41 +81,3 @@ func prefixToMask(prefix netip.Prefix) netip.Addr {
|
||||
addr, _ := netip.AddrFromSlice(net.CIDRMask(prefix.Bits(), pLen))
|
||||
return addr
|
||||
}
|
||||
|
||||
func flipBytes(b []byte) []byte {
|
||||
for i := 0; i < len(b); i++ {
|
||||
b[i] ^= 0xFF
|
||||
}
|
||||
return b
|
||||
}
|
||||
func orBytes(a []byte, b []byte) []byte {
|
||||
ret := make([]byte, len(a))
|
||||
for i := 0; i < len(a); i++ {
|
||||
ret[i] = a[i] | b[i]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getBroadcast(cidr netip.Prefix) netip.Addr {
|
||||
broadcast, _ := netip.AddrFromSlice(
|
||||
orBytes(
|
||||
cidr.Addr().AsSlice(),
|
||||
flipBytes(prefixToMask(cidr).AsSlice()),
|
||||
),
|
||||
)
|
||||
return broadcast
|
||||
}
|
||||
|
||||
func selectGateway(dest netip.Prefix, gateways []netip.Prefix) (netip.Prefix, error) {
|
||||
for _, gateway := range gateways {
|
||||
if dest.Addr().Is4() && gateway.Addr().Is4() {
|
||||
return gateway, nil
|
||||
}
|
||||
|
||||
if dest.Addr().Is6() && gateway.Addr().Is6() {
|
||||
return gateway, nil
|
||||
}
|
||||
}
|
||||
|
||||
return netip.Prefix{}, fmt.Errorf("no gateway found for %v in the list of vpn networks", dest)
|
||||
}
|
||||
|
||||
@@ -95,10 +95,6 @@ func (t *tun) Name() string {
|
||||
return "android"
|
||||
}
|
||||
|
||||
func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for android")
|
||||
}
|
||||
|
||||
@@ -294,6 +294,7 @@ func (t *tun) activate6(network netip.Prefix) error {
|
||||
Vltime: 0xffffffff,
|
||||
Pltime: 0xffffffff,
|
||||
},
|
||||
//TODO: CERT-V2 should we disable DAD (duplicate address detection) and mark this as a secured address?
|
||||
Flags: _IN6_IFF_NODAD,
|
||||
}
|
||||
|
||||
@@ -549,10 +550,6 @@ func (t *tun) Name() string {
|
||||
return t.Device
|
||||
}
|
||||
|
||||
func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for darwin")
|
||||
}
|
||||
|
||||
+5
-34
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/rcrowley/go-metrics"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/firewall"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/routing"
|
||||
)
|
||||
@@ -88,52 +87,24 @@ func (t *disabledTun) handleICMPEchoRequest(b []byte) bool {
|
||||
default:
|
||||
t.l.Debugf("tun_disabled: dropped ICMP Echo Reply response")
|
||||
}
|
||||
if t.l.Level >= logrus.DebugLevel {
|
||||
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun responded to ICMP Echo Request")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *disabledTun) handleOtherPackets(b []byte) error {
|
||||
fp := &firewall.Packet{}
|
||||
err := firewall.NewPacket(b, true, fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out := make([]byte, len(b)) //todo do something!
|
||||
|
||||
// attempt to write it, but don't block
|
||||
select {
|
||||
case t.read <- out:
|
||||
default:
|
||||
t.l.Debugf("tun_disabled: dropped reply")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *disabledTun) Write(b []byte) (int, error) {
|
||||
t.rx.Inc(1)
|
||||
|
||||
// Check for ICMP Echo Request before spending time doing the full parsing
|
||||
if t.handleICMPEchoRequest(b) {
|
||||
return len(b), nil
|
||||
}
|
||||
if err := t.handleOtherPackets(b); err != nil {
|
||||
if t.l.Level >= logrus.DebugLevel {
|
||||
t.l.WithField("raw", prettyPacket(b)).WithError(err).Debugf("Disabled tun failed to respond")
|
||||
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun responded to ICMP Echo Request")
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
if t.l.Level >= logrus.DebugLevel {
|
||||
} else if t.l.Level >= logrus.DebugLevel {
|
||||
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun received unexpected payload")
|
||||
}
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (t *disabledTun) SupportsMultiqueue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *disabledTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
+25
-5
@@ -266,7 +266,7 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
|
||||
}
|
||||
|
||||
// Set the device name
|
||||
_ = ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
|
||||
ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
|
||||
}
|
||||
|
||||
t := &tun{
|
||||
@@ -450,10 +450,6 @@ func (t *tun) Name() string {
|
||||
return t.Device
|
||||
}
|
||||
|
||||
func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for freebsd")
|
||||
}
|
||||
@@ -505,6 +501,30 @@ func (t *tun) deviceBytes() (o [16]byte) {
|
||||
return
|
||||
}
|
||||
|
||||
func flipBytes(b []byte) []byte {
|
||||
for i := 0; i < len(b); i++ {
|
||||
b[i] ^= 0xFF
|
||||
}
|
||||
return b
|
||||
}
|
||||
func orBytes(a []byte, b []byte) []byte {
|
||||
ret := make([]byte, len(a))
|
||||
for i := 0; i < len(a); i++ {
|
||||
ret[i] = a[i] | b[i]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getBroadcast(cidr netip.Prefix) netip.Addr {
|
||||
broadcast, _ := netip.AddrFromSlice(
|
||||
orBytes(
|
||||
cidr.Addr().AsSlice(),
|
||||
flipBytes(prefixToMask(cidr).AsSlice()),
|
||||
),
|
||||
)
|
||||
return broadcast
|
||||
}
|
||||
|
||||
func addRoute(prefix netip.Prefix, gateway netroute.Addr) error {
|
||||
sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
|
||||
if err != nil {
|
||||
|
||||
@@ -151,10 +151,6 @@ func (t *tun) Name() string {
|
||||
return "iOS"
|
||||
}
|
||||
|
||||
func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for ios")
|
||||
}
|
||||
|
||||
+31
-69
@@ -10,7 +10,6 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
@@ -41,11 +40,6 @@ type tun struct {
|
||||
useSystemRoutes bool
|
||||
useSystemRoutesBufferSize int
|
||||
|
||||
// These are routes learned from `tun.use_system_route_table`
|
||||
// stored here to make it easier to restore them after a reload
|
||||
routesFromSystem map[netip.Prefix]routing.Gateways
|
||||
routesFromSystemLock sync.Mutex
|
||||
|
||||
l *logrus.Logger
|
||||
}
|
||||
|
||||
@@ -112,13 +106,9 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, multiqueu
|
||||
if multiqueue {
|
||||
req.Flags |= unix.IFF_MULTI_QUEUE
|
||||
}
|
||||
nameStr := c.GetString("tun.dev", "")
|
||||
copy(req.Name[:], nameStr)
|
||||
copy(req.Name[:], c.GetString("tun.dev", ""))
|
||||
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
||||
return nil, &NameError{
|
||||
Name: nameStr,
|
||||
Underlying: err,
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
name := strings.Trim(string(req.Name[:]), "\x00")
|
||||
|
||||
@@ -141,7 +131,6 @@ func newTunGeneric(c *config.C, l *logrus.Logger, file *os.File, vpnNetworks []n
|
||||
TXQueueLen: c.GetInt("tun.tx_queue", 500),
|
||||
useSystemRoutes: c.GetBool("tun.use_system_route_table", false),
|
||||
useSystemRoutesBufferSize: c.GetInt("tun.use_system_route_table_buffer_size", 0),
|
||||
routesFromSystem: map[netip.Prefix]routing.Gateways{},
|
||||
l: l,
|
||||
}
|
||||
|
||||
@@ -175,13 +164,6 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Bring along any routes learned from the system route table on reload
|
||||
t.routesFromSystemLock.Lock()
|
||||
for dst, gw := range t.routesFromSystem {
|
||||
routeTree.Insert(dst, gw)
|
||||
}
|
||||
t.routesFromSystemLock.Unlock()
|
||||
|
||||
oldDefaultMTU := t.DefaultMTU
|
||||
oldMaxMTU := t.MaxMTU
|
||||
newDefaultMTU := c.GetInt("tun.mtu", DefaultMTU)
|
||||
@@ -234,10 +216,6 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) SupportsMultiqueue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
@@ -604,42 +582,48 @@ func (t *tun) isGatewayInVpnNetworks(gwAddr netip.Addr) bool {
|
||||
}
|
||||
|
||||
func (t *tun) getGatewaysFromRoute(r *netlink.Route) routing.Gateways {
|
||||
|
||||
var gateways routing.Gateways
|
||||
|
||||
link, err := netlink.LinkByName(t.Device)
|
||||
if err != nil {
|
||||
t.l.WithField("deviceName", t.Device).Error("Ignoring route update: failed to get link by name")
|
||||
t.l.WithField("Devicename", t.Device).Error("Ignoring route update: failed to get link by name")
|
||||
return gateways
|
||||
}
|
||||
|
||||
// If this route is relevant to our interface and there is a gateway then add it
|
||||
if r.LinkIndex == link.Attrs().Index {
|
||||
gwAddr, ok := getGatewayAddr(r.Gw, r.Via)
|
||||
if ok {
|
||||
if t.isGatewayInVpnNetworks(gwAddr) {
|
||||
gateways = append(gateways, routing.NewGateway(gwAddr, 1))
|
||||
} else {
|
||||
// Gateway isn't in our overlay network, ignore
|
||||
t.l.WithField("route", r).Debug("Ignoring route update, gateway is not in our network")
|
||||
}
|
||||
if r.LinkIndex == link.Attrs().Index && len(r.Gw) > 0 {
|
||||
gwAddr, ok := netip.AddrFromSlice(r.Gw)
|
||||
if !ok {
|
||||
t.l.WithField("route", r).Debug("Ignoring route update, invalid gateway address")
|
||||
} else {
|
||||
t.l.WithField("route", r).Debug("Ignoring route update, invalid gateway or via address")
|
||||
gwAddr = gwAddr.Unmap()
|
||||
|
||||
if !t.isGatewayInVpnNetworks(gwAddr) {
|
||||
// Gateway isn't in our overlay network, ignore
|
||||
t.l.WithField("route", r).Debug("Ignoring route update, not in our network")
|
||||
} else {
|
||||
gateways = append(gateways, routing.NewGateway(gwAddr, 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range r.MultiPath {
|
||||
// If this route is relevant to our interface and there is a gateway then add it
|
||||
if p.LinkIndex == link.Attrs().Index {
|
||||
gwAddr, ok := getGatewayAddr(p.Gw, p.Via)
|
||||
if ok {
|
||||
if t.isGatewayInVpnNetworks(gwAddr) {
|
||||
gateways = append(gateways, routing.NewGateway(gwAddr, p.Hops+1))
|
||||
} else {
|
||||
// Gateway isn't in our overlay network, ignore
|
||||
t.l.WithField("route", r).Debug("Ignoring route update, gateway is not in our network")
|
||||
}
|
||||
if p.LinkIndex == link.Attrs().Index && len(p.Gw) > 0 {
|
||||
gwAddr, ok := netip.AddrFromSlice(p.Gw)
|
||||
if !ok {
|
||||
t.l.WithField("route", r).Debug("Ignoring multipath route update, invalid gateway address")
|
||||
} else {
|
||||
t.l.WithField("route", r).Debug("Ignoring route update, invalid gateway or via address")
|
||||
gwAddr = gwAddr.Unmap()
|
||||
|
||||
if !t.isGatewayInVpnNetworks(gwAddr) {
|
||||
// Gateway isn't in our overlay network, ignore
|
||||
t.l.WithField("route", r).Debug("Ignoring route update, not in our network")
|
||||
} else {
|
||||
// p.Hops+1 = weight of the route
|
||||
gateways = append(gateways, routing.NewGateway(gwAddr, p.Hops+1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -648,27 +632,10 @@ func (t *tun) getGatewaysFromRoute(r *netlink.Route) routing.Gateways {
|
||||
return gateways
|
||||
}
|
||||
|
||||
func getGatewayAddr(gw net.IP, via netlink.Destination) (netip.Addr, bool) {
|
||||
// Try to use the old RTA_GATEWAY first
|
||||
gwAddr, ok := netip.AddrFromSlice(gw)
|
||||
if !ok {
|
||||
// Fallback to the new RTA_VIA
|
||||
rVia, ok := via.(*netlink.Via)
|
||||
if ok {
|
||||
gwAddr, ok = netip.AddrFromSlice(rVia.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
if gwAddr.IsValid() {
|
||||
gwAddr = gwAddr.Unmap()
|
||||
return gwAddr, true
|
||||
}
|
||||
|
||||
return netip.Addr{}, false
|
||||
}
|
||||
|
||||
func (t *tun) updateRoutes(r netlink.RouteUpdate) {
|
||||
|
||||
gateways := t.getGatewaysFromRoute(&r.Route)
|
||||
|
||||
if len(gateways) == 0 {
|
||||
// No gateways relevant to our network, no routing changes required.
|
||||
t.l.WithField("route", r).Debug("Ignoring route update, no gateways")
|
||||
@@ -691,18 +658,14 @@ func (t *tun) updateRoutes(r netlink.RouteUpdate) {
|
||||
|
||||
newTree := t.routeTree.Load().Clone()
|
||||
|
||||
t.routesFromSystemLock.Lock()
|
||||
if r.Type == unix.RTM_NEWROUTE {
|
||||
t.l.WithField("destination", dst).WithField("via", gateways).Info("Adding route")
|
||||
t.routesFromSystem[dst] = gateways
|
||||
newTree.Insert(dst, gateways)
|
||||
|
||||
} else {
|
||||
t.l.WithField("destination", dst).WithField("via", gateways).Info("Removing route")
|
||||
delete(t.routesFromSystem, dst)
|
||||
newTree.Delete(dst)
|
||||
}
|
||||
t.routesFromSystemLock.Unlock()
|
||||
t.routeTree.Store(newTree)
|
||||
}
|
||||
|
||||
@@ -717,7 +680,6 @@ func (t *tun) Close() error {
|
||||
|
||||
if t.ioctlFd > 0 {
|
||||
_ = os.NewFile(t.ioctlFd, "ioctlFd").Close()
|
||||
t.ioctlFd = 0
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+66
-375
@@ -4,12 +4,13 @@
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
@@ -19,42 +20,11 @@ import (
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/routing"
|
||||
"github.com/slackhq/nebula/util"
|
||||
netroute "golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
SIOCAIFADDR_IN6 = 0x8080696b
|
||||
TUNSIFHEAD = 0x80047442
|
||||
TUNSIFMODE = 0x80047458
|
||||
)
|
||||
|
||||
type ifreqAlias4 struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Addr unix.RawSockaddrInet4
|
||||
DstAddr unix.RawSockaddrInet4
|
||||
MaskAddr unix.RawSockaddrInet4
|
||||
}
|
||||
|
||||
type ifreqAlias6 struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Addr unix.RawSockaddrInet6
|
||||
DstAddr unix.RawSockaddrInet6
|
||||
PrefixMask unix.RawSockaddrInet6
|
||||
Flags uint32
|
||||
Lifetime addrLifetime
|
||||
}
|
||||
|
||||
type ifreq struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
data int
|
||||
}
|
||||
|
||||
type addrLifetime struct {
|
||||
Expire uint64
|
||||
Preferred uint64
|
||||
Vltime uint32
|
||||
Pltime uint32
|
||||
type ifreqDestroy struct {
|
||||
Name [16]byte
|
||||
pad [16]byte
|
||||
}
|
||||
|
||||
type tun struct {
|
||||
@@ -64,18 +34,40 @@ type tun struct {
|
||||
Routes atomic.Pointer[[]Route]
|
||||
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
||||
l *logrus.Logger
|
||||
f *os.File
|
||||
fd int
|
||||
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
||||
func (t *tun) Close() error {
|
||||
if t.ReadWriteCloser != nil {
|
||||
if err := t.ReadWriteCloser.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(s)
|
||||
|
||||
ifreq := ifreqDestroy{Name: t.deviceBytes()}
|
||||
|
||||
err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq)))
|
||||
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {
|
||||
return nil, fmt.Errorf("newTunFromFd not supported in NetBSD")
|
||||
}
|
||||
|
||||
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
||||
|
||||
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
|
||||
// Try to open tun device
|
||||
var file *os.File
|
||||
var err error
|
||||
deviceName := c.GetString("tun.dev", "")
|
||||
if deviceName == "" {
|
||||
@@ -85,23 +77,17 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
|
||||
return nil, fmt.Errorf("a device name in the format of /dev/tunN must be specified")
|
||||
}
|
||||
|
||||
fd, err := unix.Open("/dev/"+deviceName, os.O_RDWR, 0)
|
||||
file, err = os.OpenFile("/dev/"+deviceName, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = unix.SetNonblock(fd, true)
|
||||
if err != nil {
|
||||
l.WithError(err).Warn("Failed to set the tun device as nonblocking")
|
||||
}
|
||||
|
||||
t := &tun{
|
||||
f: os.NewFile(uintptr(fd), ""),
|
||||
fd: fd,
|
||||
Device: deviceName,
|
||||
vpnNetworks: vpnNetworks,
|
||||
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
||||
l: l,
|
||||
ReadWriteCloser: file,
|
||||
Device: deviceName,
|
||||
vpnNetworks: vpnNetworks,
|
||||
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
||||
l: l,
|
||||
}
|
||||
|
||||
err = t.reload(c, true)
|
||||
@@ -119,225 +105,40 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *tun) Close() error {
|
||||
if t.f != nil {
|
||||
if err := t.f.Close(); err != nil {
|
||||
return fmt.Errorf("error closing tun file: %w", err)
|
||||
}
|
||||
|
||||
// t.f.Close should have handled it for us but let's be extra sure
|
||||
_ = unix.Close(t.fd)
|
||||
|
||||
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(s)
|
||||
|
||||
ifr := ifreq{Name: t.deviceBytes()}
|
||||
err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifr)))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) Read(to []byte) (int, error) {
|
||||
rc, err := t.f.SyscallConn()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get syscall conn for tun: %w", err)
|
||||
}
|
||||
|
||||
var errno syscall.Errno
|
||||
var n uintptr
|
||||
err = rc.Read(func(fd uintptr) bool {
|
||||
// first 4 bytes is protocol family, in network byte order
|
||||
head := [4]byte{}
|
||||
iovecs := []syscall.Iovec{
|
||||
{&head[0], 4},
|
||||
{&to[0], uint64(len(to))},
|
||||
}
|
||||
|
||||
n, _, errno = syscall.Syscall(syscall.SYS_READV, fd, uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))
|
||||
if errno.Temporary() {
|
||||
// We got an EAGAIN, EINTR, or EWOULDBLOCK, go again
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
if err == syscall.EBADF || err.Error() == "use of closed file" {
|
||||
// Go doesn't export poll.ErrFileClosing but happily reports it to us so here we are
|
||||
// https://github.com/golang/go/blob/master/src/internal/poll/fd_poll_runtime.go#L121
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
return 0, fmt.Errorf("failed to make read call for tun: %w", err)
|
||||
}
|
||||
|
||||
if errno != 0 {
|
||||
return 0, fmt.Errorf("failed to make inner read call for tun: %w", errno)
|
||||
}
|
||||
|
||||
// fix bytes read number to exclude header
|
||||
bytesRead := int(n)
|
||||
if bytesRead < 0 {
|
||||
return bytesRead, nil
|
||||
} else if bytesRead < 4 {
|
||||
return 0, nil
|
||||
} else {
|
||||
return bytesRead - 4, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Write is only valid for single threaded use
|
||||
func (t *tun) Write(from []byte) (int, error) {
|
||||
if len(from) <= 1 {
|
||||
return 0, syscall.EIO
|
||||
}
|
||||
|
||||
ipVer := from[0] >> 4
|
||||
var head [4]byte
|
||||
// first 4 bytes is protocol family, in network byte order
|
||||
if ipVer == 4 {
|
||||
head[3] = syscall.AF_INET
|
||||
} else if ipVer == 6 {
|
||||
head[3] = syscall.AF_INET6
|
||||
} else {
|
||||
return 0, fmt.Errorf("unable to determine IP version from packet")
|
||||
}
|
||||
|
||||
rc, err := t.f.SyscallConn()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var errno syscall.Errno
|
||||
var n uintptr
|
||||
err = rc.Write(func(fd uintptr) bool {
|
||||
iovecs := []syscall.Iovec{
|
||||
{&head[0], 4},
|
||||
{&from[0], uint64(len(from))},
|
||||
}
|
||||
|
||||
n, _, errno = syscall.Syscall(syscall.SYS_WRITEV, fd, uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))
|
||||
// According to NetBSD documentation for TUN, writes will only return errors in which
|
||||
// this packet will never be delivered so just go on living life.
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if errno != 0 {
|
||||
return 0, errno
|
||||
}
|
||||
|
||||
return int(n) - 4, err
|
||||
}
|
||||
|
||||
func (t *tun) addIp(cidr netip.Prefix) error {
|
||||
if cidr.Addr().Is4() {
|
||||
var req ifreqAlias4
|
||||
req.Name = t.deviceBytes()
|
||||
req.Addr = unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: cidr.Addr().As4(),
|
||||
}
|
||||
req.DstAddr = unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: cidr.Addr().As4(),
|
||||
}
|
||||
req.MaskAddr = unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: prefixToMask(cidr).As4(),
|
||||
}
|
||||
var err error
|
||||
|
||||
s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(s)
|
||||
|
||||
if err := ioctl(uintptr(s), unix.SIOCAIFADDR, uintptr(unsafe.Pointer(&req))); err != nil {
|
||||
return fmt.Errorf("failed to set tun address %s: %s", cidr.Addr(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
// TODO use syscalls instead of exec.Command
|
||||
cmd := exec.Command("/sbin/ifconfig", t.Device, cidr.String(), cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run 'ifconfig': %s", err)
|
||||
}
|
||||
|
||||
if cidr.Addr().Is6() {
|
||||
var req ifreqAlias6
|
||||
req.Name = t.deviceBytes()
|
||||
req.Addr = unix.RawSockaddrInet6{
|
||||
Len: unix.SizeofSockaddrInet6,
|
||||
Family: unix.AF_INET6,
|
||||
Addr: cidr.Addr().As16(),
|
||||
}
|
||||
req.PrefixMask = unix.RawSockaddrInet6{
|
||||
Len: unix.SizeofSockaddrInet6,
|
||||
Family: unix.AF_INET6,
|
||||
Addr: prefixToMask(cidr).As16(),
|
||||
}
|
||||
req.Lifetime = addrLifetime{
|
||||
Vltime: 0xffffffff,
|
||||
Pltime: 0xffffffff,
|
||||
}
|
||||
|
||||
s, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_IP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(s)
|
||||
|
||||
if err := ioctl(uintptr(s), SIOCAIFADDR_IN6, uintptr(unsafe.Pointer(&req))); err != nil {
|
||||
return fmt.Errorf("failed to set tun address %s: %s", cidr.Addr().String(), err)
|
||||
}
|
||||
return nil
|
||||
cmd = exec.Command("/sbin/route", "-n", "add", "-net", cidr.String(), cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run 'route add': %s", err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown address type %v", cidr)
|
||||
}
|
||||
|
||||
func (t *tun) Activate() error {
|
||||
mode := int32(unix.IFF_BROADCAST)
|
||||
err := ioctl(uintptr(t.fd), TUNSIFMODE, uintptr(unsafe.Pointer(&mode)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set tun device mode: %w", err)
|
||||
}
|
||||
|
||||
v := 1
|
||||
err = ioctl(uintptr(t.fd), TUNSIFHEAD, uintptr(unsafe.Pointer(&v)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set tun device head: %w", err)
|
||||
}
|
||||
|
||||
err = t.doIoctlByName(unix.SIOCSIFMTU, uint32(t.MTU))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set tun mtu: %w", err)
|
||||
}
|
||||
|
||||
for i := range t.vpnNetworks {
|
||||
err = t.addIp(t.vpnNetworks[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = exec.Command("/sbin/ifconfig", t.Device, "mtu", strconv.Itoa(t.MTU))
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run 'ifconfig': %s", err)
|
||||
}
|
||||
|
||||
// Unsafe path routes
|
||||
return t.addRoutes(false)
|
||||
}
|
||||
|
||||
func (t *tun) doIoctlByName(ctl uintptr, value uint32) error {
|
||||
s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
|
||||
if err != nil {
|
||||
return err
|
||||
func (t *tun) Activate() error {
|
||||
for i := range t.vpnNetworks {
|
||||
err := t.addIp(t.vpnNetworks[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer syscall.Close(s)
|
||||
|
||||
ir := ifreq{Name: t.deviceBytes(), data: int(value)}
|
||||
err = ioctl(uintptr(s), ctl, uintptr(unsafe.Pointer(&ir)))
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) reload(c *config.C, initial bool) error {
|
||||
@@ -390,33 +191,27 @@ func (t *tun) Name() string {
|
||||
return t.Device
|
||||
}
|
||||
|
||||
func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for netbsd")
|
||||
}
|
||||
|
||||
func (t *tun) addRoutes(logErrors bool) error {
|
||||
routes := *t.Routes.Load()
|
||||
|
||||
for _, r := range routes {
|
||||
if len(r.Via) == 0 || !r.Install {
|
||||
// We don't allow route MTUs so only install routes with a via
|
||||
continue
|
||||
}
|
||||
|
||||
err := addRoute(r.Cidr, t.vpnNetworks)
|
||||
if err != nil {
|
||||
retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
|
||||
cmd := exec.Command("/sbin/route", "-n", "add", "-net", r.Cidr.String(), t.vpnNetworks[0].Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err := cmd.Run(); err != nil {
|
||||
retErr := util.NewContextualError("failed to run 'route add' for unsafe_route", map[string]any{"route": r}, err)
|
||||
if logErrors {
|
||||
retErr.Log(t.l)
|
||||
} else {
|
||||
return retErr
|
||||
}
|
||||
} else {
|
||||
t.l.WithField("route", r).Info("Added route")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,8 +224,10 @@ func (t *tun) removeRoutes(routes []Route) error {
|
||||
continue
|
||||
}
|
||||
|
||||
err := delRoute(r.Cidr, t.vpnNetworks)
|
||||
if err != nil {
|
||||
//TODO: CERT-V2 is this right?
|
||||
cmd := exec.Command("/sbin/route", "-n", "delete", "-net", r.Cidr.String(), t.vpnNetworks[0].Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
||||
} else {
|
||||
t.l.WithField("route", r).Info("Removed route")
|
||||
@@ -445,109 +242,3 @@ func (t *tun) deviceBytes() (o [16]byte) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func addRoute(prefix netip.Prefix, gateways []netip.Prefix) error {
|
||||
sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create AF_ROUTE socket: %v", err)
|
||||
}
|
||||
defer unix.Close(sock)
|
||||
|
||||
route := &netroute.RouteMessage{
|
||||
Version: unix.RTM_VERSION,
|
||||
Type: unix.RTM_ADD,
|
||||
Flags: unix.RTF_UP | unix.RTF_GATEWAY,
|
||||
Seq: 1,
|
||||
}
|
||||
|
||||
if prefix.Addr().Is4() {
|
||||
gw, err := selectGateway(prefix, gateways)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route.Addrs = []netroute.Addr{
|
||||
unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},
|
||||
unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},
|
||||
unix.RTAX_GATEWAY: &netroute.Inet4Addr{IP: gw.Addr().As4()},
|
||||
}
|
||||
} else {
|
||||
gw, err := selectGateway(prefix, gateways)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route.Addrs = []netroute.Addr{
|
||||
unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},
|
||||
unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},
|
||||
unix.RTAX_GATEWAY: &netroute.Inet6Addr{IP: gw.Addr().As16()},
|
||||
}
|
||||
}
|
||||
|
||||
data, err := route.Marshal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create route.RouteMessage: %w", err)
|
||||
}
|
||||
|
||||
_, err = unix.Write(sock, data[:])
|
||||
if err != nil {
|
||||
if errors.Is(err, unix.EEXIST) {
|
||||
// Try to do a change
|
||||
route.Type = unix.RTM_CHANGE
|
||||
data, err = route.Marshal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create route.RouteMessage for change: %w", err)
|
||||
}
|
||||
_, err = unix.Write(sock, data[:])
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("failed to write route.RouteMessage to socket: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func delRoute(prefix netip.Prefix, gateways []netip.Prefix) error {
|
||||
sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create AF_ROUTE socket: %v", err)
|
||||
}
|
||||
defer unix.Close(sock)
|
||||
|
||||
route := netroute.RouteMessage{
|
||||
Version: unix.RTM_VERSION,
|
||||
Type: unix.RTM_DELETE,
|
||||
Seq: 1,
|
||||
}
|
||||
|
||||
if prefix.Addr().Is4() {
|
||||
gw, err := selectGateway(prefix, gateways)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route.Addrs = []netroute.Addr{
|
||||
unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},
|
||||
unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},
|
||||
unix.RTAX_GATEWAY: &netroute.Inet4Addr{IP: gw.Addr().As4()},
|
||||
}
|
||||
} else {
|
||||
gw, err := selectGateway(prefix, gateways)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route.Addrs = []netroute.Addr{
|
||||
unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},
|
||||
unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},
|
||||
unix.RTAX_GATEWAY: &netroute.Inet6Addr{IP: gw.Addr().As16()},
|
||||
}
|
||||
}
|
||||
|
||||
data, err := route.Marshal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create route.RouteMessage: %w", err)
|
||||
}
|
||||
_, err = unix.Write(sock, data[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write route.RouteMessage to socket: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+110
-328
@@ -4,50 +4,23 @@
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/routing"
|
||||
"github.com/slackhq/nebula/util"
|
||||
netroute "golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
SIOCAIFADDR_IN6 = 0x8080691a
|
||||
)
|
||||
|
||||
type ifreqAlias4 struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Addr unix.RawSockaddrInet4
|
||||
DstAddr unix.RawSockaddrInet4
|
||||
MaskAddr unix.RawSockaddrInet4
|
||||
}
|
||||
|
||||
type ifreqAlias6 struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Addr unix.RawSockaddrInet6
|
||||
DstAddr unix.RawSockaddrInet6
|
||||
PrefixMask unix.RawSockaddrInet6
|
||||
Flags uint32
|
||||
Lifetime [2]uint32
|
||||
}
|
||||
|
||||
type ifreq struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
data int
|
||||
}
|
||||
|
||||
type tun struct {
|
||||
Device string
|
||||
vpnNetworks []netip.Prefix
|
||||
@@ -55,46 +28,48 @@ type tun struct {
|
||||
Routes atomic.Pointer[[]Route]
|
||||
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
||||
l *logrus.Logger
|
||||
f *os.File
|
||||
fd int
|
||||
|
||||
io.ReadWriteCloser
|
||||
|
||||
// cache out buffer since we need to prepend 4 bytes for tun metadata
|
||||
out []byte
|
||||
}
|
||||
|
||||
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
||||
func (t *tun) Close() error {
|
||||
if t.ReadWriteCloser != nil {
|
||||
return t.ReadWriteCloser.Close()
|
||||
}
|
||||
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {
|
||||
return nil, fmt.Errorf("newTunFromFd not supported in openbsd")
|
||||
return nil
|
||||
}
|
||||
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {
|
||||
return nil, fmt.Errorf("newTunFromFd not supported in OpenBSD")
|
||||
}
|
||||
|
||||
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
||||
|
||||
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
|
||||
// Try to open tun device
|
||||
var err error
|
||||
deviceName := c.GetString("tun.dev", "")
|
||||
if deviceName == "" {
|
||||
return nil, fmt.Errorf("a device name in the format of /dev/tunN must be specified")
|
||||
}
|
||||
if !deviceNameRE.MatchString(deviceName) {
|
||||
return nil, fmt.Errorf("a device name in the format of /dev/tunN must be specified")
|
||||
return nil, fmt.Errorf("a device name in the format of tunN must be specified")
|
||||
}
|
||||
|
||||
fd, err := unix.Open("/dev/"+deviceName, os.O_RDWR, 0)
|
||||
if !deviceNameRE.MatchString(deviceName) {
|
||||
return nil, fmt.Errorf("a device name in the format of tunN must be specified")
|
||||
}
|
||||
|
||||
file, err := os.OpenFile("/dev/"+deviceName, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = unix.SetNonblock(fd, true)
|
||||
if err != nil {
|
||||
l.WithError(err).Warn("Failed to set the tun device as nonblocking")
|
||||
}
|
||||
|
||||
t := &tun{
|
||||
f: os.NewFile(uintptr(fd), ""),
|
||||
fd: fd,
|
||||
Device: deviceName,
|
||||
vpnNetworks: vpnNetworks,
|
||||
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
||||
l: l,
|
||||
ReadWriteCloser: file,
|
||||
Device: deviceName,
|
||||
vpnNetworks: vpnNetworks,
|
||||
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
||||
l: l,
|
||||
}
|
||||
|
||||
err = t.reload(c, true)
|
||||
@@ -112,154 +87,6 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *tun) Close() error {
|
||||
if t.f != nil {
|
||||
if err := t.f.Close(); err != nil {
|
||||
return fmt.Errorf("error closing tun file: %w", err)
|
||||
}
|
||||
|
||||
// t.f.Close should have handled it for us but let's be extra sure
|
||||
_ = unix.Close(t.fd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) Read(to []byte) (int, error) {
|
||||
buf := make([]byte, len(to)+4)
|
||||
|
||||
n, err := t.f.Read(buf)
|
||||
|
||||
copy(to, buf[4:])
|
||||
return n - 4, err
|
||||
}
|
||||
|
||||
// Write is only valid for single threaded use
|
||||
func (t *tun) Write(from []byte) (int, error) {
|
||||
buf := t.out
|
||||
if cap(buf) < len(from)+4 {
|
||||
buf = make([]byte, len(from)+4)
|
||||
t.out = buf
|
||||
}
|
||||
buf = buf[:len(from)+4]
|
||||
|
||||
if len(from) == 0 {
|
||||
return 0, syscall.EIO
|
||||
}
|
||||
|
||||
// Determine the IP Family for the NULL L2 Header
|
||||
ipVer := from[0] >> 4
|
||||
if ipVer == 4 {
|
||||
buf[3] = syscall.AF_INET
|
||||
} else if ipVer == 6 {
|
||||
buf[3] = syscall.AF_INET6
|
||||
} else {
|
||||
return 0, fmt.Errorf("unable to determine IP version from packet")
|
||||
}
|
||||
|
||||
copy(buf[4:], from)
|
||||
|
||||
n, err := t.f.Write(buf)
|
||||
return n - 4, err
|
||||
}
|
||||
|
||||
func (t *tun) addIp(cidr netip.Prefix) error {
|
||||
if cidr.Addr().Is4() {
|
||||
var req ifreqAlias4
|
||||
req.Name = t.deviceBytes()
|
||||
req.Addr = unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: cidr.Addr().As4(),
|
||||
}
|
||||
req.DstAddr = unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: cidr.Addr().As4(),
|
||||
}
|
||||
req.MaskAddr = unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: prefixToMask(cidr).As4(),
|
||||
}
|
||||
|
||||
s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(s)
|
||||
|
||||
if err := ioctl(uintptr(s), unix.SIOCAIFADDR, uintptr(unsafe.Pointer(&req))); err != nil {
|
||||
return fmt.Errorf("failed to set tun address %s: %s", cidr.Addr(), err)
|
||||
}
|
||||
|
||||
err = addRoute(cidr, t.vpnNetworks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set route for vpn network %v: %w", cidr, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if cidr.Addr().Is6() {
|
||||
var req ifreqAlias6
|
||||
req.Name = t.deviceBytes()
|
||||
req.Addr = unix.RawSockaddrInet6{
|
||||
Len: unix.SizeofSockaddrInet6,
|
||||
Family: unix.AF_INET6,
|
||||
Addr: cidr.Addr().As16(),
|
||||
}
|
||||
req.PrefixMask = unix.RawSockaddrInet6{
|
||||
Len: unix.SizeofSockaddrInet6,
|
||||
Family: unix.AF_INET6,
|
||||
Addr: prefixToMask(cidr).As16(),
|
||||
}
|
||||
req.Lifetime[0] = 0xffffffff
|
||||
req.Lifetime[1] = 0xffffffff
|
||||
|
||||
s, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_IP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(s)
|
||||
|
||||
if err := ioctl(uintptr(s), SIOCAIFADDR_IN6, uintptr(unsafe.Pointer(&req))); err != nil {
|
||||
return fmt.Errorf("failed to set tun address %s: %s", cidr.Addr().String(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown address type %v", cidr)
|
||||
}
|
||||
|
||||
func (t *tun) Activate() error {
|
||||
err := t.doIoctlByName(unix.SIOCSIFMTU, uint32(t.MTU))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set tun mtu: %w", err)
|
||||
}
|
||||
|
||||
for i := range t.vpnNetworks {
|
||||
err = t.addIp(t.vpnNetworks[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return t.addRoutes(false)
|
||||
}
|
||||
|
||||
func (t *tun) doIoctlByName(ctl uintptr, value uint32) error {
|
||||
s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(s)
|
||||
|
||||
ir := ifreq{Name: t.deviceBytes(), data: int(value)}
|
||||
err = ioctl(uintptr(s), ctl, uintptr(unsafe.Pointer(&ir)))
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *tun) reload(c *config.C, initial bool) error {
|
||||
change, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)
|
||||
if err != nil {
|
||||
@@ -297,46 +124,63 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) addIp(cidr netip.Prefix) error {
|
||||
var err error
|
||||
// TODO use syscalls instead of exec.Command
|
||||
cmd := exec.Command("/sbin/ifconfig", t.Device, cidr.String(), cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run 'ifconfig': %s", err)
|
||||
}
|
||||
|
||||
cmd = exec.Command("/sbin/ifconfig", t.Device, "mtu", strconv.Itoa(t.MTU))
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run 'ifconfig': %s", err)
|
||||
}
|
||||
|
||||
cmd = exec.Command("/sbin/route", "-n", "add", "-inet", cidr.String(), cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run 'route add': %s", err)
|
||||
}
|
||||
|
||||
// Unsafe path routes
|
||||
return t.addRoutes(false)
|
||||
}
|
||||
|
||||
func (t *tun) Activate() error {
|
||||
for i := range t.vpnNetworks {
|
||||
err := t.addIp(t.vpnNetworks[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
|
||||
r, _ := t.routeTree.Load().Lookup(ip)
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *tun) Networks() []netip.Prefix {
|
||||
return t.vpnNetworks
|
||||
}
|
||||
|
||||
func (t *tun) Name() string {
|
||||
return t.Device
|
||||
}
|
||||
|
||||
func (t *tun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for openbsd")
|
||||
}
|
||||
|
||||
func (t *tun) addRoutes(logErrors bool) error {
|
||||
routes := *t.Routes.Load()
|
||||
|
||||
for _, r := range routes {
|
||||
if len(r.Via) == 0 || !r.Install {
|
||||
// We don't allow route MTUs so only install routes with a via
|
||||
continue
|
||||
}
|
||||
|
||||
err := addRoute(r.Cidr, t.vpnNetworks)
|
||||
if err != nil {
|
||||
retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
|
||||
//TODO: CERT-V2 is this right?
|
||||
cmd := exec.Command("/sbin/route", "-n", "add", "-inet", r.Cidr.String(), t.vpnNetworks[0].Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err := cmd.Run(); err != nil {
|
||||
retErr := util.NewContextualError("failed to run 'route add' for unsafe_route", map[string]any{"route": r}, err)
|
||||
if logErrors {
|
||||
retErr.Log(t.l)
|
||||
} else {
|
||||
return retErr
|
||||
}
|
||||
} else {
|
||||
t.l.WithField("route", r).Info("Added route")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,9 +192,10 @@ func (t *tun) removeRoutes(routes []Route) error {
|
||||
if !r.Install {
|
||||
continue
|
||||
}
|
||||
|
||||
err := delRoute(r.Cidr, t.vpnNetworks)
|
||||
if err != nil {
|
||||
//TODO: CERT-V2 is this right?
|
||||
cmd := exec.Command("/sbin/route", "-n", "delete", "-inet", r.Cidr.String(), t.vpnNetworks[0].Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
||||
} else {
|
||||
t.l.WithField("route", r).Info("Removed route")
|
||||
@@ -359,115 +204,52 @@ func (t *tun) removeRoutes(routes []Route) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) deviceBytes() (o [16]byte) {
|
||||
for i, c := range t.Device {
|
||||
o[i] = byte(c)
|
||||
}
|
||||
return
|
||||
func (t *tun) Networks() []netip.Prefix {
|
||||
return t.vpnNetworks
|
||||
}
|
||||
|
||||
func addRoute(prefix netip.Prefix, gateways []netip.Prefix) error {
|
||||
sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create AF_ROUTE socket: %v", err)
|
||||
}
|
||||
defer unix.Close(sock)
|
||||
func (t *tun) Name() string {
|
||||
return t.Device
|
||||
}
|
||||
|
||||
route := &netroute.RouteMessage{
|
||||
Version: unix.RTM_VERSION,
|
||||
Type: unix.RTM_ADD,
|
||||
Flags: unix.RTF_UP | unix.RTF_GATEWAY,
|
||||
Seq: 1,
|
||||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for freebsd")
|
||||
}
|
||||
|
||||
func (t *tun) Read(to []byte) (int, error) {
|
||||
buf := make([]byte, len(to)+4)
|
||||
|
||||
n, err := t.ReadWriteCloser.Read(buf)
|
||||
|
||||
copy(to, buf[4:])
|
||||
return n - 4, err
|
||||
}
|
||||
|
||||
// Write is only valid for single threaded use
|
||||
func (t *tun) Write(from []byte) (int, error) {
|
||||
buf := t.out
|
||||
if cap(buf) < len(from)+4 {
|
||||
buf = make([]byte, len(from)+4)
|
||||
t.out = buf
|
||||
}
|
||||
buf = buf[:len(from)+4]
|
||||
|
||||
if len(from) == 0 {
|
||||
return 0, syscall.EIO
|
||||
}
|
||||
|
||||
if prefix.Addr().Is4() {
|
||||
gw, err := selectGateway(prefix, gateways)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route.Addrs = []netroute.Addr{
|
||||
unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},
|
||||
unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},
|
||||
unix.RTAX_GATEWAY: &netroute.Inet4Addr{IP: gw.Addr().As4()},
|
||||
}
|
||||
// Determine the IP Family for the NULL L2 Header
|
||||
ipVer := from[0] >> 4
|
||||
if ipVer == 4 {
|
||||
buf[3] = syscall.AF_INET
|
||||
} else if ipVer == 6 {
|
||||
buf[3] = syscall.AF_INET6
|
||||
} else {
|
||||
gw, err := selectGateway(prefix, gateways)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route.Addrs = []netroute.Addr{
|
||||
unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},
|
||||
unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},
|
||||
unix.RTAX_GATEWAY: &netroute.Inet6Addr{IP: gw.Addr().As16()},
|
||||
}
|
||||
return 0, fmt.Errorf("unable to determine IP version from packet")
|
||||
}
|
||||
|
||||
data, err := route.Marshal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create route.RouteMessage: %w", err)
|
||||
}
|
||||
copy(buf[4:], from)
|
||||
|
||||
_, err = unix.Write(sock, data[:])
|
||||
if err != nil {
|
||||
if errors.Is(err, unix.EEXIST) {
|
||||
// Try to do a change
|
||||
route.Type = unix.RTM_CHANGE
|
||||
data, err = route.Marshal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create route.RouteMessage for change: %w", err)
|
||||
}
|
||||
_, err = unix.Write(sock, data[:])
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("failed to write route.RouteMessage to socket: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func delRoute(prefix netip.Prefix, gateways []netip.Prefix) error {
|
||||
sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create AF_ROUTE socket: %v", err)
|
||||
}
|
||||
defer unix.Close(sock)
|
||||
|
||||
route := netroute.RouteMessage{
|
||||
Version: unix.RTM_VERSION,
|
||||
Type: unix.RTM_DELETE,
|
||||
Seq: 1,
|
||||
}
|
||||
|
||||
if prefix.Addr().Is4() {
|
||||
gw, err := selectGateway(prefix, gateways)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route.Addrs = []netroute.Addr{
|
||||
unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},
|
||||
unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},
|
||||
unix.RTAX_GATEWAY: &netroute.Inet4Addr{IP: gw.Addr().As4()},
|
||||
}
|
||||
} else {
|
||||
gw, err := selectGateway(prefix, gateways)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route.Addrs = []netroute.Addr{
|
||||
unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},
|
||||
unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},
|
||||
unix.RTAX_GATEWAY: &netroute.Inet6Addr{IP: gw.Addr().As16()},
|
||||
}
|
||||
}
|
||||
|
||||
data, err := route.Marshal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create route.RouteMessage: %w", err)
|
||||
}
|
||||
_, err = unix.Write(sock, data[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write route.RouteMessage to socket: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
n, err := t.ReadWriteCloser.Write(buf)
|
||||
return n - 4, err
|
||||
}
|
||||
|
||||
@@ -132,10 +132,6 @@ func (t *TestTun) Read(b []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (t *TestTun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *TestTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented")
|
||||
}
|
||||
|
||||
@@ -74,10 +74,7 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
|
||||
l.WithError(err).Debug("Failed to create wintun device, retrying")
|
||||
tunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)
|
||||
if err != nil {
|
||||
return nil, &NameError{
|
||||
Name: deviceName,
|
||||
Underlying: fmt.Errorf("create TUN device failed: %w", err),
|
||||
}
|
||||
return nil, fmt.Errorf("create TUN device failed: %w", err)
|
||||
}
|
||||
}
|
||||
t.tun = tunDevice.(*wintun.NativeTun)
|
||||
@@ -237,10 +234,6 @@ func (t *winTun) Write(b []byte) (int, error) {
|
||||
return t.tun.Write(b, 0)
|
||||
}
|
||||
|
||||
func (t *winTun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *winTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for windows")
|
||||
}
|
||||
|
||||
@@ -46,10 +46,6 @@ func (d *UserDevice) RoutesFor(ip netip.Addr) routing.Gateways {
|
||||
return routing.Gateways{routing.NewGateway(ip, 1)}
|
||||
}
|
||||
|
||||
func (d *UserDevice) SupportsMultiqueue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *UserDevice) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package packet
|
||||
|
||||
import "net/netip"
|
||||
|
||||
type Packet struct {
|
||||
Payload []byte
|
||||
Addr netip.AddrPort
|
||||
}
|
||||
|
||||
func New() *Packet {
|
||||
return &Packet{Payload: make([]byte, 9001)}
|
||||
}
|
||||
@@ -100,62 +100,55 @@ func (p *PKI) reloadCerts(c *config.C, initial bool) *util.ContextualError {
|
||||
currentState := p.cs.Load()
|
||||
if newState.v1Cert != nil {
|
||||
if currentState.v1Cert == nil {
|
||||
//adding certs is fine, actually. Networks-in-common confirmed in newCertState().
|
||||
} else {
|
||||
// did IP in cert change? if so, don't set
|
||||
if !slices.Equal(currentState.v1Cert.Networks(), newState.v1Cert.Networks()) {
|
||||
return util.NewContextualError(
|
||||
"Networks in new cert was different from old",
|
||||
m{"new_networks": newState.v1Cert.Networks(), "old_networks": currentState.v1Cert.Networks(), "cert_version": cert.Version1},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
if currentState.v1Cert.Curve() != newState.v1Cert.Curve() {
|
||||
return util.NewContextualError(
|
||||
"Curve in new v1 cert was different from old",
|
||||
m{"new_curve": newState.v1Cert.Curve(), "old_curve": currentState.v1Cert.Curve(), "cert_version": cert.Version1},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
return util.NewContextualError("v1 certificate was added, restart required", nil, err)
|
||||
}
|
||||
|
||||
// did IP in cert change? if so, don't set
|
||||
if !slices.Equal(currentState.v1Cert.Networks(), newState.v1Cert.Networks()) {
|
||||
return util.NewContextualError(
|
||||
"Networks in new cert was different from old",
|
||||
m{"new_networks": newState.v1Cert.Networks(), "old_networks": currentState.v1Cert.Networks()},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
if currentState.v1Cert.Curve() != newState.v1Cert.Curve() {
|
||||
return util.NewContextualError(
|
||||
"Curve in new cert was different from old",
|
||||
m{"new_curve": newState.v1Cert.Curve(), "old_curve": currentState.v1Cert.Curve()},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
} else if currentState.v1Cert != nil {
|
||||
//TODO: CERT-V2 we should be able to tear this down
|
||||
return util.NewContextualError("v1 certificate was removed, restart required", nil, err)
|
||||
}
|
||||
|
||||
if newState.v2Cert != nil {
|
||||
if currentState.v2Cert == nil {
|
||||
//adding certs is fine, actually
|
||||
} else {
|
||||
// did IP in cert change? if so, don't set
|
||||
if !slices.Equal(currentState.v2Cert.Networks(), newState.v2Cert.Networks()) {
|
||||
return util.NewContextualError(
|
||||
"Networks in new cert was different from old",
|
||||
m{"new_networks": newState.v2Cert.Networks(), "old_networks": currentState.v2Cert.Networks(), "cert_version": cert.Version2},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
if currentState.v2Cert.Curve() != newState.v2Cert.Curve() {
|
||||
return util.NewContextualError(
|
||||
"Curve in new cert was different from old",
|
||||
m{"new_curve": newState.v2Cert.Curve(), "old_curve": currentState.v2Cert.Curve(), "cert_version": cert.Version2},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
return util.NewContextualError("v2 certificate was added, restart required", nil, err)
|
||||
}
|
||||
|
||||
} else if currentState.v2Cert != nil {
|
||||
//newState.v1Cert is non-nil bc empty certstates aren't permitted
|
||||
if newState.v1Cert == nil {
|
||||
return util.NewContextualError("v1 and v2 certs are nil, this should be impossible", nil, err)
|
||||
}
|
||||
//if we're going to v1-only, we need to make sure we didn't orphan any v2-cert vpnaddrs
|
||||
if !slices.Equal(currentState.v2Cert.Networks(), newState.v1Cert.Networks()) {
|
||||
// did IP in cert change? if so, don't set
|
||||
if !slices.Equal(currentState.v2Cert.Networks(), newState.v2Cert.Networks()) {
|
||||
return util.NewContextualError(
|
||||
"Removing a V2 cert is not permitted unless it has identical networks to the new V1 cert",
|
||||
m{"new_v1_networks": newState.v1Cert.Networks(), "old_v2_networks": currentState.v2Cert.Networks()},
|
||||
"Networks in new cert was different from old",
|
||||
m{"new_networks": newState.v2Cert.Networks(), "old_networks": currentState.v2Cert.Networks()},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
if currentState.v2Cert.Curve() != newState.v2Cert.Curve() {
|
||||
return util.NewContextualError(
|
||||
"Curve in new cert was different from old",
|
||||
m{"new_curve": newState.v2Cert.Curve(), "old_curve": currentState.v2Cert.Curve()},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
} else if currentState.v2Cert != nil {
|
||||
return util.NewContextualError("v2 certificate was removed, restart required", nil, err)
|
||||
}
|
||||
|
||||
// Cipher cant be hot swapped so just leave it at what it was before
|
||||
@@ -523,13 +516,9 @@ func loadCAPoolFromConfig(l *logrus.Logger, c *config.C) (*cert.CAPool, error) {
|
||||
return nil, fmt.Errorf("error while adding CA certificate to CA trust store: %s", err)
|
||||
}
|
||||
|
||||
bl := c.GetStringSlice("pki.blocklist", []string{})
|
||||
if len(bl) > 0 {
|
||||
for _, fp := range bl {
|
||||
caPool.BlocklistFingerprint(fp)
|
||||
}
|
||||
|
||||
l.WithField("fingerprintCount", len(bl)).Info("Blocklisted certificates")
|
||||
for _, fp := range c.GetStringSlice("pki.blocklist", []string{}) {
|
||||
l.WithField("fingerprint", fp).Info("Blocklisting cert")
|
||||
caPool.BlocklistFingerprint(fp)
|
||||
}
|
||||
|
||||
return caPool, nil
|
||||
|
||||
+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
|
||||
func (r *RemoteList) BlockRemote(bad ViaSender) {
|
||||
if bad.IsRelayed {
|
||||
func (r *RemoteList) BlockRemote(bad netip.AddrPort) {
|
||||
if !bad.IsValid() {
|
||||
// relays can have nil udp Addrs
|
||||
return
|
||||
}
|
||||
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
// Check if we already blocked this addr
|
||||
if r.unlockedIsBad(bad.UdpAddr) {
|
||||
if r.unlockedIsBad(bad) {
|
||||
return
|
||||
}
|
||||
|
||||
// We copy here because we are taking something else's memory and we can't trust everything
|
||||
r.badRemotes = append(r.badRemotes, bad.UdpAddr)
|
||||
r.badRemotes = append(r.badRemotes, bad)
|
||||
|
||||
// Mark the next interaction must recollect/dedupe
|
||||
r.shouldRebuild = true
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// Sentinel value
|
||||
// Sentinal value
|
||||
BucketNotCalculated = -1
|
||||
)
|
||||
|
||||
|
||||
+10
-1
@@ -44,7 +44,10 @@ type Service struct {
|
||||
}
|
||||
|
||||
func New(control *nebula.Control) (*Service, error) {
|
||||
control.Start()
|
||||
wait, err := control.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx := control.Context()
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
@@ -141,6 +144,12 @@ func New(control *nebula.Control) (*Service, error) {
|
||||
}
|
||||
})
|
||||
|
||||
// Add the nebula wait function to the group
|
||||
eg.Go(func() error {
|
||||
wait()
|
||||
return nil
|
||||
})
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
"github.com/slackhq/nebula/cert_test"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/overlay"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type m = map[string]any
|
||||
|
||||
@@ -34,10 +34,6 @@ func (NoopTun) Write([]byte) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (NoopTun) SupportsMultiqueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (NoopTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return nil, errors.New("unsupported")
|
||||
}
|
||||
|
||||
+12
-7
@@ -16,13 +16,18 @@ type EncReader func(
|
||||
type Conn interface {
|
||||
Rebind() error
|
||||
LocalAddr() (netip.AddrPort, error)
|
||||
ListenOut(r EncReader)
|
||||
ListenOut(r EncReader) error
|
||||
WriteTo(b []byte, addr netip.AddrPort) error
|
||||
WriteBatch(pkts []BatchPacket) (int, error)
|
||||
ReloadConfig(c *config.C)
|
||||
SupportsMultipleReaders() bool
|
||||
Close() error
|
||||
}
|
||||
|
||||
type BatchPacket struct {
|
||||
Payload []byte
|
||||
Addr netip.AddrPort
|
||||
}
|
||||
|
||||
type NoopConn struct{}
|
||||
|
||||
func (NoopConn) Rebind() error {
|
||||
@@ -31,15 +36,15 @@ func (NoopConn) Rebind() error {
|
||||
func (NoopConn) LocalAddr() (netip.AddrPort, error) {
|
||||
return netip.AddrPort{}, nil
|
||||
}
|
||||
func (NoopConn) ListenOut(_ EncReader) {
|
||||
return
|
||||
}
|
||||
func (NoopConn) SupportsMultipleReaders() bool {
|
||||
return false
|
||||
func (NoopConn) ListenOut(_ EncReader) error {
|
||||
return nil
|
||||
}
|
||||
func (NoopConn) WriteTo(_ []byte, _ netip.AddrPort) error {
|
||||
return nil
|
||||
}
|
||||
func (NoopConn) WriteBatch(_ []BatchPacket) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (NoopConn) ReloadConfig(_ *config.C) {
|
||||
return
|
||||
}
|
||||
|
||||
+18
-8
@@ -98,9 +98,9 @@ func (u *StdConn) WriteTo(b []byte, ap netip.AddrPort) error {
|
||||
return ErrInvalidIPv6RemoteForSocket
|
||||
}
|
||||
|
||||
var rsa unix.RawSockaddrInet4
|
||||
rsa.Family = unix.AF_INET
|
||||
rsa.Addr = ap.Addr().As4()
|
||||
var rsa unix.RawSockaddrInet6
|
||||
rsa.Family = unix.AF_INET6
|
||||
rsa.Addr = ap.Addr().As16()
|
||||
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&rsa.Port))[:], ap.Port())
|
||||
sa = unsafe.Pointer(&rsa)
|
||||
addrLen = syscall.SizeofSockaddrInet4
|
||||
@@ -140,6 +140,17 @@ func (u *StdConn) WriteTo(b []byte, ap netip.AddrPort) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *StdConn) WriteBatch(pkts []BatchPacket) (int, error) {
|
||||
sent := 0
|
||||
for _, pkt := range pkts {
|
||||
if err := u.WriteTo(pkt.Payload, pkt.Addr); err != nil {
|
||||
return sent, err
|
||||
}
|
||||
sent++
|
||||
}
|
||||
return sent, nil
|
||||
}
|
||||
|
||||
func (u *StdConn) LocalAddr() (netip.AddrPort, error) {
|
||||
a := u.UDPConn.LocalAddr()
|
||||
|
||||
@@ -165,7 +176,7 @@ func NewUDPStatsEmitter(udpConns []Conn) func() {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
func (u *StdConn) ListenOut(r EncReader) {
|
||||
func (u *StdConn) ListenOut(r EncReader) error {
|
||||
buffer := make([]byte, MTU)
|
||||
|
||||
for {
|
||||
@@ -174,18 +185,17 @@ func (u *StdConn) ListenOut(r EncReader) {
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
u.l.WithError(err).Error("unexpected udp socket receive error")
|
||||
continue
|
||||
}
|
||||
|
||||
r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func (u *StdConn) SupportsMultipleReaders() bool {
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *StdConn) Rebind() error {
|
||||
|
||||
+13
-19
@@ -10,11 +10,9 @@ package udp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/config"
|
||||
@@ -44,6 +42,17 @@ func (u *GenericConn) WriteTo(b []byte, addr netip.AddrPort) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *GenericConn) WriteBatch(pkts []BatchPacket) (int, error) {
|
||||
sent := 0
|
||||
for _, pkt := range pkts {
|
||||
if err := u.WriteTo(pkt.Payload, pkt.Addr); err != nil {
|
||||
return sent, err
|
||||
}
|
||||
sent++
|
||||
}
|
||||
return sent, nil
|
||||
}
|
||||
|
||||
func (u *GenericConn) LocalAddr() (netip.AddrPort, error) {
|
||||
a := u.UDPConn.LocalAddr()
|
||||
|
||||
@@ -73,31 +82,16 @@ type rawMessage struct {
|
||||
Len uint32
|
||||
}
|
||||
|
||||
func (u *GenericConn) ListenOut(r EncReader) {
|
||||
func (u *GenericConn) ListenOut(r EncReader) error {
|
||||
buffer := make([]byte, MTU)
|
||||
|
||||
var lastRecvErr time.Time
|
||||
|
||||
for {
|
||||
// Just read one packet at a time
|
||||
n, rua, err := u.ReadFromUDPAddrPort(buffer)
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||
return
|
||||
}
|
||||
// Dampen unexpected message warns to once per minute
|
||||
if lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {
|
||||
lastRecvErr = time.Now()
|
||||
u.l.WithError(err).Warn("unexpected udp socket receive error")
|
||||
}
|
||||
continue
|
||||
return err
|
||||
}
|
||||
|
||||
r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func (u *GenericConn) SupportsMultipleReaders() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
+596
-19
@@ -5,10 +5,13 @@ package udp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/rcrowley/go-metrics"
|
||||
@@ -17,19 +20,40 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var readTimeout = unix.NsecToTimeval(int64(time.Millisecond * 500))
|
||||
|
||||
const (
|
||||
defaultGSOMaxSegments = 128
|
||||
defaultGSOFlushTimeout = 80 * time.Microsecond
|
||||
defaultGROReadBufferSize = MTU * defaultGSOMaxSegments
|
||||
maxGSOBatchBytes = 0xFFFF
|
||||
)
|
||||
|
||||
var (
|
||||
errGSOFallback = errors.New("udp gso fallback")
|
||||
errGSODisabled = errors.New("udp gso disabled")
|
||||
)
|
||||
|
||||
type StdConn struct {
|
||||
sysFd int
|
||||
isV4 bool
|
||||
l *logrus.Logger
|
||||
batch int
|
||||
}
|
||||
|
||||
func maybeIPV4(ip net.IP) (net.IP, bool) {
|
||||
ip4 := ip.To4()
|
||||
if ip4 != nil {
|
||||
return ip4, true
|
||||
}
|
||||
return ip, false
|
||||
enableGRO bool
|
||||
enableGSO bool
|
||||
|
||||
gsoMu sync.Mutex
|
||||
gsoBuf []byte
|
||||
gsoAddr netip.AddrPort
|
||||
gsoSegSize int
|
||||
gsoSegments int
|
||||
gsoMaxSegments int
|
||||
gsoMaxBytes int
|
||||
gsoFlushTimeout time.Duration
|
||||
gsoTimer *time.Timer
|
||||
|
||||
groBufSize int
|
||||
}
|
||||
|
||||
func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
||||
@@ -55,6 +79,11 @@ func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch in
|
||||
}
|
||||
}
|
||||
|
||||
// Set a read timeout
|
||||
if err = unix.SetsockoptTimeval(fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &readTimeout); err != nil {
|
||||
return nil, fmt.Errorf("unable to set SO_RCVTIMEO: %s", err)
|
||||
}
|
||||
|
||||
var sa unix.Sockaddr
|
||||
if ip.Is4() {
|
||||
sa4 := &unix.SockaddrInet4{Port: port}
|
||||
@@ -69,11 +98,16 @@ func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch in
|
||||
return nil, fmt.Errorf("unable to bind to socket: %s", err)
|
||||
}
|
||||
|
||||
return &StdConn{sysFd: fd, isV4: ip.Is4(), l: l, batch: batch}, err
|
||||
}
|
||||
|
||||
func (u *StdConn) SupportsMultipleReaders() bool {
|
||||
return true
|
||||
return &StdConn{
|
||||
sysFd: fd,
|
||||
isV4: ip.Is4(),
|
||||
l: l,
|
||||
batch: batch,
|
||||
gsoMaxSegments: defaultGSOMaxSegments,
|
||||
gsoMaxBytes: MTU * defaultGSOMaxSegments,
|
||||
gsoFlushTimeout: defaultGSOFlushTimeout,
|
||||
groBufSize: MTU,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (u *StdConn) Rebind() error {
|
||||
@@ -122,20 +156,46 @@ func (u *StdConn) LocalAddr() (netip.AddrPort, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *StdConn) ListenOut(r EncReader) {
|
||||
var ip netip.Addr
|
||||
func (u *StdConn) ListenOut(r EncReader) error {
|
||||
var (
|
||||
ip netip.Addr
|
||||
controls [][]byte
|
||||
)
|
||||
|
||||
msgs, buffers, names := u.PrepareRawMessages(u.batch)
|
||||
bufSize := u.readBufferSize()
|
||||
msgs, buffers, names := u.PrepareRawMessages(u.batch, bufSize)
|
||||
read := u.ReadMulti
|
||||
if u.batch == 1 {
|
||||
read = u.ReadSingle
|
||||
}
|
||||
|
||||
for {
|
||||
desired := u.readBufferSize()
|
||||
if len(buffers) == 0 || cap(buffers[0]) < desired {
|
||||
msgs, buffers, names = u.PrepareRawMessages(u.batch, desired)
|
||||
controls = nil
|
||||
}
|
||||
|
||||
if u.enableGRO {
|
||||
if controls == nil {
|
||||
controls = make([][]byte, len(msgs))
|
||||
for i := range controls {
|
||||
controls[i] = make([]byte, unix.CmsgSpace(4))
|
||||
}
|
||||
}
|
||||
for i := range msgs {
|
||||
setRawMessageControl(&msgs[i], controls[i])
|
||||
}
|
||||
} else if controls != nil {
|
||||
for i := range msgs {
|
||||
setRawMessageControl(&msgs[i], nil)
|
||||
}
|
||||
controls = nil
|
||||
}
|
||||
|
||||
n, err := read(msgs)
|
||||
if err != nil {
|
||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
@@ -145,11 +205,82 @@ func (u *StdConn) ListenOut(r EncReader) {
|
||||
} else {
|
||||
ip, _ = netip.AddrFromSlice(names[i][8:24])
|
||||
}
|
||||
r(netip.AddrPortFrom(ip.Unmap(), binary.BigEndian.Uint16(names[i][2:4])), buffers[i][:msgs[i].Len])
|
||||
addr := netip.AddrPortFrom(ip.Unmap(), binary.BigEndian.Uint16(names[i][2:4]))
|
||||
payload := buffers[i][:msgs[i].Len]
|
||||
|
||||
if u.enableGRO && u.l.IsLevelEnabled(logrus.DebugLevel) {
|
||||
ctrlLen := getRawMessageControlLen(&msgs[i])
|
||||
msgFlags := getRawMessageFlags(&msgs[i])
|
||||
u.l.WithFields(logrus.Fields{
|
||||
"tag": "gro-debug",
|
||||
"stage": "recv",
|
||||
"payload_len": len(payload),
|
||||
"ctrl_len": ctrlLen,
|
||||
"msg_flags": msgFlags,
|
||||
}).Debug("gro batch data")
|
||||
if controls != nil && ctrlLen > 0 {
|
||||
maxDump := ctrlLen
|
||||
if maxDump > 16 {
|
||||
maxDump = 16
|
||||
}
|
||||
u.l.WithFields(logrus.Fields{
|
||||
"tag": "gro-debug",
|
||||
"stage": "control-bytes",
|
||||
"control_hex": fmt.Sprintf("%x", controls[i][:maxDump]),
|
||||
"datalen": ctrlLen,
|
||||
}).Debug("gro control dump")
|
||||
}
|
||||
}
|
||||
|
||||
sawControl := false
|
||||
if controls != nil {
|
||||
if ctrlLen := getRawMessageControlLen(&msgs[i]); ctrlLen > 0 {
|
||||
if segSize, segCount := parseGROControl(controls[i][:ctrlLen]); segSize > 0 {
|
||||
sawControl = true
|
||||
if u.l.IsLevelEnabled(logrus.DebugLevel) {
|
||||
u.l.WithFields(logrus.Fields{
|
||||
"tag": "gro-debug",
|
||||
"stage": "control",
|
||||
"seg_size": segSize,
|
||||
"seg_count": segCount,
|
||||
"payloadLen": len(payload),
|
||||
}).Debug("gro control parsed")
|
||||
}
|
||||
segSize = normalizeGROSegSize(segSize, segCount, len(payload))
|
||||
if segSize > 0 && segSize < len(payload) {
|
||||
if u.emitGROSegments(r, addr, payload, segSize) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if u.enableGRO && len(payload) > MTU {
|
||||
if !sawControl && u.l.IsLevelEnabled(logrus.DebugLevel) {
|
||||
u.l.WithFields(logrus.Fields{
|
||||
"tag": "gro-debug",
|
||||
"stage": "fallback",
|
||||
"payload_len": len(payload),
|
||||
}).Debug("gro control missing; splitting payload by MTU")
|
||||
}
|
||||
if u.emitGROSegments(r, addr, payload, MTU) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
r(addr, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *StdConn) readBufferSize() int {
|
||||
if u.enableGRO && u.groBufSize > MTU {
|
||||
return u.groBufSize
|
||||
}
|
||||
return MTU
|
||||
}
|
||||
|
||||
func (u *StdConn) ReadSingle(msgs []rawMessage) (int, error) {
|
||||
for {
|
||||
n, _, err := unix.Syscall6(
|
||||
@@ -163,6 +294,9 @@ func (u *StdConn) ReadSingle(msgs []rawMessage) (int, error) {
|
||||
)
|
||||
|
||||
if err != 0 {
|
||||
if err == unix.EAGAIN || err == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
return 0, &net.OpError{Op: "recvmsg", Err: err}
|
||||
}
|
||||
|
||||
@@ -184,6 +318,9 @@ func (u *StdConn) ReadMulti(msgs []rawMessage) (int, error) {
|
||||
)
|
||||
|
||||
if err != 0 {
|
||||
if err == unix.EAGAIN || err == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
return 0, &net.OpError{Op: "recvmmsg", Err: err}
|
||||
}
|
||||
|
||||
@@ -192,12 +329,132 @@ func (u *StdConn) ReadMulti(msgs []rawMessage) (int, error) {
|
||||
}
|
||||
|
||||
func (u *StdConn) WriteTo(b []byte, ip netip.AddrPort) error {
|
||||
if u.enableGSO && ip.IsValid() {
|
||||
if err := u.queueGSOPacket(b, ip); err == nil {
|
||||
return nil
|
||||
} else if !errors.Is(err, errGSOFallback) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if u.isV4 {
|
||||
return u.writeTo4(b, ip)
|
||||
}
|
||||
return u.writeTo6(b, ip)
|
||||
}
|
||||
|
||||
func (u *StdConn) WriteBatch(pkts []BatchPacket) (int, error) {
|
||||
if len(pkts) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
msgs := make([]rawMessage, 0, len(pkts))
|
||||
iovs := make([]iovec, 0, len(pkts))
|
||||
names := make([][unix.SizeofSockaddrInet6]byte, 0, len(pkts))
|
||||
|
||||
sent := 0
|
||||
|
||||
for _, pkt := range pkts {
|
||||
if len(pkt.Payload) == 0 {
|
||||
sent++
|
||||
continue
|
||||
}
|
||||
|
||||
if u.enableGSO && pkt.Addr.IsValid() {
|
||||
if err := u.queueGSOPacket(pkt.Payload, pkt.Addr); err == nil {
|
||||
sent++
|
||||
continue
|
||||
} else if !errors.Is(err, errGSOFallback) {
|
||||
return sent, err
|
||||
}
|
||||
}
|
||||
|
||||
if !pkt.Addr.IsValid() {
|
||||
if err := u.WriteTo(pkt.Payload, pkt.Addr); err != nil {
|
||||
return sent, err
|
||||
}
|
||||
sent++
|
||||
continue
|
||||
}
|
||||
|
||||
msgs = append(msgs, rawMessage{})
|
||||
iovs = append(iovs, iovec{})
|
||||
names = append(names, [unix.SizeofSockaddrInet6]byte{})
|
||||
|
||||
idx := len(msgs) - 1
|
||||
msg := &msgs[idx]
|
||||
iov := &iovs[idx]
|
||||
name := &names[idx]
|
||||
|
||||
setIovecSlice(iov, pkt.Payload)
|
||||
msg.Hdr.Iov = iov
|
||||
msg.Hdr.Iovlen = 1
|
||||
setRawMessageControl(msg, nil)
|
||||
msg.Hdr.Flags = 0
|
||||
|
||||
nameLen, err := u.encodeSockaddr(name[:], pkt.Addr)
|
||||
if err != nil {
|
||||
return sent, err
|
||||
}
|
||||
msg.Hdr.Name = &name[0]
|
||||
msg.Hdr.Namelen = nameLen
|
||||
}
|
||||
|
||||
if len(msgs) == 0 {
|
||||
return sent, nil
|
||||
}
|
||||
|
||||
offset := 0
|
||||
for offset < len(msgs) {
|
||||
n, _, errno := unix.Syscall6(
|
||||
unix.SYS_SENDMMSG,
|
||||
uintptr(u.sysFd),
|
||||
uintptr(unsafe.Pointer(&msgs[offset])),
|
||||
uintptr(len(msgs)-offset),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
|
||||
if errno != 0 {
|
||||
if errno == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
return sent + offset, &net.OpError{Op: "sendmmsg", Err: errno}
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
offset += int(n)
|
||||
}
|
||||
|
||||
return sent + len(msgs), nil
|
||||
}
|
||||
|
||||
func (u *StdConn) encodeSockaddr(dst []byte, addr netip.AddrPort) (uint32, error) {
|
||||
if u.isV4 {
|
||||
if !addr.Addr().Is4() {
|
||||
return 0, fmt.Errorf("Listener is IPv4, but writing to IPv6 remote")
|
||||
}
|
||||
var sa unix.RawSockaddrInet4
|
||||
sa.Family = unix.AF_INET
|
||||
sa.Addr = addr.Addr().As4()
|
||||
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], addr.Port())
|
||||
size := unix.SizeofSockaddrInet4
|
||||
copy(dst[:size], (*(*[unix.SizeofSockaddrInet4]byte)(unsafe.Pointer(&sa)))[:])
|
||||
return uint32(size), nil
|
||||
}
|
||||
|
||||
var sa unix.RawSockaddrInet6
|
||||
sa.Family = unix.AF_INET6
|
||||
sa.Addr = addr.Addr().As16()
|
||||
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], addr.Port())
|
||||
size := unix.SizeofSockaddrInet6
|
||||
copy(dst[:size], (*(*[unix.SizeofSockaddrInet6]byte)(unsafe.Pointer(&sa)))[:])
|
||||
return uint32(size), nil
|
||||
}
|
||||
|
||||
func (u *StdConn) writeTo6(b []byte, ip netip.AddrPort) error {
|
||||
var rsa unix.RawSockaddrInet6
|
||||
rsa.Family = unix.AF_INET6
|
||||
@@ -225,7 +482,7 @@ func (u *StdConn) writeTo6(b []byte, ip netip.AddrPort) error {
|
||||
|
||||
func (u *StdConn) writeTo4(b []byte, ip netip.AddrPort) error {
|
||||
if !ip.Addr().Is4() {
|
||||
return ErrInvalidIPv6RemoteForSocket
|
||||
return fmt.Errorf("Listener is IPv4, but writing to IPv6 remote")
|
||||
}
|
||||
|
||||
var rsa unix.RawSockaddrInet4
|
||||
@@ -298,6 +555,94 @@ func (u *StdConn) ReloadConfig(c *config.C) {
|
||||
u.l.WithError(err).Error("Failed to set listen.so_mark")
|
||||
}
|
||||
}
|
||||
|
||||
u.configureGRO(c)
|
||||
u.configureGSO(c)
|
||||
}
|
||||
|
||||
func (u *StdConn) configureGRO(c *config.C) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
enable := c.GetBool("listen.enable_gro", true)
|
||||
if enable == u.enableGRO {
|
||||
if enable {
|
||||
if size := c.GetInt("listen.gro_read_buffer", 0); size > 0 {
|
||||
u.setGROBufferSize(size)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if enable {
|
||||
if err := unix.SetsockoptInt(u.sysFd, unix.SOL_UDP, unix.UDP_GRO, 1); err != nil {
|
||||
u.l.WithError(err).Warn("Failed to enable UDP GRO")
|
||||
return
|
||||
}
|
||||
u.enableGRO = true
|
||||
u.setGROBufferSize(c.GetInt("listen.gro_read_buffer", defaultGROReadBufferSize))
|
||||
u.l.WithField("buffer_size", u.groBufSize).Info("UDP GRO enabled")
|
||||
return
|
||||
}
|
||||
|
||||
if err := unix.SetsockoptInt(u.sysFd, unix.SOL_UDP, unix.UDP_GRO, 0); err != nil && err != unix.ENOPROTOOPT {
|
||||
u.l.WithError(err).Warn("Failed to disable UDP GRO")
|
||||
}
|
||||
u.enableGRO = false
|
||||
u.groBufSize = MTU
|
||||
}
|
||||
|
||||
func (u *StdConn) configureGSO(c *config.C) {
|
||||
enable := c.GetBool("listen.enable_gso", true)
|
||||
if !enable {
|
||||
u.disableGSO()
|
||||
} else {
|
||||
u.enableGSO = true
|
||||
}
|
||||
|
||||
segments := c.GetInt("listen.gso_max_segments", defaultGSOMaxSegments)
|
||||
if segments < 1 {
|
||||
segments = 1
|
||||
}
|
||||
u.gsoMaxSegments = segments
|
||||
|
||||
maxBytes := c.GetInt("listen.gso_max_bytes", 0)
|
||||
if maxBytes <= 0 {
|
||||
maxBytes = MTU * segments
|
||||
}
|
||||
if maxBytes > maxGSOBatchBytes {
|
||||
u.l.WithField("requested", maxBytes).Warn("listen.gso_max_bytes larger than UDP limit; clamping")
|
||||
maxBytes = maxGSOBatchBytes
|
||||
}
|
||||
u.gsoMaxBytes = maxBytes
|
||||
|
||||
timeout := c.GetDuration("listen.gso_flush_timeout", defaultGSOFlushTimeout)
|
||||
if timeout < 0 {
|
||||
timeout = 0
|
||||
}
|
||||
u.gsoFlushTimeout = timeout
|
||||
}
|
||||
|
||||
func (u *StdConn) setGROBufferSize(size int) {
|
||||
if size < MTU {
|
||||
size = defaultGROReadBufferSize
|
||||
}
|
||||
if size > maxGSOBatchBytes {
|
||||
size = maxGSOBatchBytes
|
||||
}
|
||||
u.groBufSize = size
|
||||
}
|
||||
|
||||
func (u *StdConn) disableGSO() {
|
||||
u.gsoMu.Lock()
|
||||
defer u.gsoMu.Unlock()
|
||||
u.enableGSO = false
|
||||
_ = u.flushGSOlocked()
|
||||
u.gsoBuf = nil
|
||||
u.gsoSegments = 0
|
||||
u.gsoSegSize = 0
|
||||
u.stopGSOTimerLocked()
|
||||
}
|
||||
|
||||
func (u *StdConn) getMemInfo(meminfo *[unix.SK_MEMINFO_VARS]uint32) error {
|
||||
@@ -309,7 +654,239 @@ func (u *StdConn) getMemInfo(meminfo *[unix.SK_MEMINFO_VARS]uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *StdConn) queueGSOPacket(b []byte, addr netip.AddrPort) error {
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
u.gsoMu.Lock()
|
||||
defer u.gsoMu.Unlock()
|
||||
|
||||
if !u.enableGSO || !addr.IsValid() || len(b) > u.gsoMaxBytes {
|
||||
if err := u.flushGSOlocked(); err != nil {
|
||||
return err
|
||||
}
|
||||
return errGSOFallback
|
||||
}
|
||||
|
||||
if u.gsoSegments == 0 {
|
||||
if cap(u.gsoBuf) < u.gsoMaxBytes {
|
||||
u.gsoBuf = make([]byte, 0, u.gsoMaxBytes)
|
||||
}
|
||||
u.gsoAddr = addr
|
||||
u.gsoSegSize = len(b)
|
||||
} else if addr != u.gsoAddr || len(b) != u.gsoSegSize {
|
||||
if err := u.flushGSOlocked(); err != nil {
|
||||
return err
|
||||
}
|
||||
if cap(u.gsoBuf) < u.gsoMaxBytes {
|
||||
u.gsoBuf = make([]byte, 0, u.gsoMaxBytes)
|
||||
}
|
||||
u.gsoAddr = addr
|
||||
u.gsoSegSize = len(b)
|
||||
}
|
||||
|
||||
if len(u.gsoBuf)+len(b) > u.gsoMaxBytes {
|
||||
if err := u.flushGSOlocked(); err != nil {
|
||||
return err
|
||||
}
|
||||
if cap(u.gsoBuf) < u.gsoMaxBytes {
|
||||
u.gsoBuf = make([]byte, 0, u.gsoMaxBytes)
|
||||
}
|
||||
u.gsoAddr = addr
|
||||
u.gsoSegSize = len(b)
|
||||
}
|
||||
|
||||
u.gsoBuf = append(u.gsoBuf, b...)
|
||||
u.gsoSegments++
|
||||
|
||||
if u.gsoSegments >= u.gsoMaxSegments || u.gsoFlushTimeout <= 0 {
|
||||
return u.flushGSOlocked()
|
||||
}
|
||||
|
||||
u.scheduleGSOFlushLocked()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *StdConn) flushGSOlocked() error {
|
||||
if u.gsoSegments == 0 {
|
||||
u.stopGSOTimerLocked()
|
||||
return nil
|
||||
}
|
||||
|
||||
payload := append([]byte(nil), u.gsoBuf...)
|
||||
addr := u.gsoAddr
|
||||
segSize := u.gsoSegSize
|
||||
|
||||
u.gsoBuf = u.gsoBuf[:0]
|
||||
u.gsoSegments = 0
|
||||
u.gsoSegSize = 0
|
||||
u.stopGSOTimerLocked()
|
||||
|
||||
if segSize <= 0 {
|
||||
return errGSOFallback
|
||||
}
|
||||
|
||||
err := u.sendSegmented(payload, addr, segSize)
|
||||
if errors.Is(err, errGSODisabled) {
|
||||
u.l.WithField("addr", addr).Warn("UDP GSO disabled by kernel, falling back to sendto")
|
||||
u.enableGSO = false
|
||||
return u.sendSegmentsIndividually(payload, addr, segSize)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *StdConn) sendSegmented(payload []byte, addr netip.AddrPort, segSize int) error {
|
||||
if len(payload) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
control := make([]byte, unix.CmsgSpace(2))
|
||||
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||
hdr.Level = unix.SOL_UDP
|
||||
hdr.Type = unix.UDP_SEGMENT
|
||||
setCmsgLen(hdr, unix.CmsgLen(2))
|
||||
binary.NativeEndian.PutUint16(control[unix.CmsgLen(0):unix.CmsgLen(0)+2], uint16(segSize))
|
||||
|
||||
var sa unix.Sockaddr
|
||||
if addr.Addr().Is4() {
|
||||
var sa4 unix.SockaddrInet4
|
||||
sa4.Port = int(addr.Port())
|
||||
sa4.Addr = addr.Addr().As4()
|
||||
sa = &sa4
|
||||
} else {
|
||||
var sa6 unix.SockaddrInet6
|
||||
sa6.Port = int(addr.Port())
|
||||
sa6.Addr = addr.Addr().As16()
|
||||
sa = &sa6
|
||||
}
|
||||
|
||||
if _, err := unix.SendmsgN(u.sysFd, payload, control, sa, 0); err != nil {
|
||||
if errno, ok := err.(syscall.Errno); ok && (errno == unix.EINVAL || errno == unix.ENOTSUP || errno == unix.EOPNOTSUPP) {
|
||||
return errGSODisabled
|
||||
}
|
||||
return &net.OpError{Op: "sendmsg", Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *StdConn) sendSegmentsIndividually(buf []byte, addr netip.AddrPort, segSize int) error {
|
||||
if segSize <= 0 {
|
||||
return errGSOFallback
|
||||
}
|
||||
|
||||
for offset := 0; offset < len(buf); offset += segSize {
|
||||
end := offset + segSize
|
||||
if end > len(buf) {
|
||||
end = len(buf)
|
||||
}
|
||||
var err error
|
||||
if u.isV4 {
|
||||
err = u.writeTo4(buf[offset:end], addr)
|
||||
} else {
|
||||
err = u.writeTo6(buf[offset:end], addr)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *StdConn) scheduleGSOFlushLocked() {
|
||||
if u.gsoTimer == nil {
|
||||
u.gsoTimer = time.AfterFunc(u.gsoFlushTimeout, u.gsoFlushTimer)
|
||||
return
|
||||
}
|
||||
u.gsoTimer.Reset(u.gsoFlushTimeout)
|
||||
}
|
||||
|
||||
func (u *StdConn) stopGSOTimerLocked() {
|
||||
if u.gsoTimer != nil {
|
||||
u.gsoTimer.Stop()
|
||||
u.gsoTimer = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *StdConn) gsoFlushTimer() {
|
||||
u.gsoMu.Lock()
|
||||
defer u.gsoMu.Unlock()
|
||||
_ = u.flushGSOlocked()
|
||||
}
|
||||
|
||||
func parseGROControl(control []byte) (int, int) {
|
||||
if len(control) == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
cmsgs, err := unix.ParseSocketControlMessage(control)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
for _, c := range cmsgs {
|
||||
if c.Header.Level == unix.SOL_UDP && c.Header.Type == unix.UDP_GRO && len(c.Data) >= 2 {
|
||||
segSize := int(binary.NativeEndian.Uint16(c.Data[:2]))
|
||||
segCount := 0
|
||||
if len(c.Data) >= 4 {
|
||||
segCount = int(binary.NativeEndian.Uint16(c.Data[2:4]))
|
||||
}
|
||||
return segSize, segCount
|
||||
}
|
||||
}
|
||||
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (u *StdConn) emitGROSegments(r EncReader, addr netip.AddrPort, payload []byte, segSize int) bool {
|
||||
if segSize <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for offset := 0; offset < len(payload); offset += segSize {
|
||||
end := offset + segSize
|
||||
if end > len(payload) {
|
||||
end = len(payload)
|
||||
}
|
||||
segment := make([]byte, end-offset)
|
||||
copy(segment, payload[offset:end])
|
||||
r(addr, segment)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func normalizeGROSegSize(segSize, segCount, total int) int {
|
||||
if segSize <= 0 || total <= 0 {
|
||||
return segSize
|
||||
}
|
||||
|
||||
if segSize > total && segCount > 0 {
|
||||
segSize = total / segCount
|
||||
if segSize == 0 {
|
||||
segSize = total
|
||||
}
|
||||
}
|
||||
|
||||
if segCount <= 1 && segSize > 0 && total > segSize {
|
||||
calculated := total / segSize
|
||||
if calculated <= 1 {
|
||||
calculated = (total + segSize - 1) / segSize
|
||||
}
|
||||
if calculated > 1 {
|
||||
segCount = calculated
|
||||
}
|
||||
}
|
||||
|
||||
if segSize > MTU {
|
||||
return MTU
|
||||
}
|
||||
|
||||
return segSize
|
||||
}
|
||||
|
||||
func (u *StdConn) Close() error {
|
||||
u.disableGSO()
|
||||
return syscall.Close(u.sysFd)
|
||||
}
|
||||
|
||||
|
||||
+37
-2
@@ -30,13 +30,16 @@ type rawMessage struct {
|
||||
Len uint32
|
||||
}
|
||||
|
||||
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
|
||||
func (u *StdConn) PrepareRawMessages(n int, bufSize int) ([]rawMessage, [][]byte, [][]byte) {
|
||||
if bufSize <= 0 {
|
||||
bufSize = MTU
|
||||
}
|
||||
msgs := make([]rawMessage, n)
|
||||
buffers := make([][]byte, n)
|
||||
names := make([][]byte, n)
|
||||
|
||||
for i := range msgs {
|
||||
buffers[i] = make([]byte, MTU)
|
||||
buffers[i] = make([]byte, bufSize)
|
||||
names[i] = make([]byte, unix.SizeofSockaddrInet6)
|
||||
|
||||
vs := []iovec{
|
||||
@@ -52,3 +55,35 @@ func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
|
||||
|
||||
return msgs, buffers, names
|
||||
}
|
||||
|
||||
func setRawMessageControl(msg *rawMessage, buf []byte) {
|
||||
if len(buf) == 0 {
|
||||
msg.Hdr.Control = nil
|
||||
msg.Hdr.Controllen = 0
|
||||
return
|
||||
}
|
||||
msg.Hdr.Control = &buf[0]
|
||||
msg.Hdr.Controllen = uint32(len(buf))
|
||||
}
|
||||
|
||||
func getRawMessageControlLen(msg *rawMessage) int {
|
||||
return int(msg.Hdr.Controllen)
|
||||
}
|
||||
|
||||
func getRawMessageFlags(msg *rawMessage) int {
|
||||
return int(msg.Hdr.Flags)
|
||||
}
|
||||
|
||||
func setCmsgLen(h *unix.Cmsghdr, l int) {
|
||||
h.Len = uint32(l)
|
||||
}
|
||||
|
||||
func setIovecSlice(iov *iovec, b []byte) {
|
||||
if len(b) == 0 {
|
||||
iov.Base = nil
|
||||
iov.Len = 0
|
||||
return
|
||||
}
|
||||
iov.Base = &b[0]
|
||||
iov.Len = uint32(len(b))
|
||||
}
|
||||
|
||||
+37
-2
@@ -33,13 +33,16 @@ type rawMessage struct {
|
||||
Pad0 [4]byte
|
||||
}
|
||||
|
||||
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
|
||||
func (u *StdConn) PrepareRawMessages(n int, bufSize int) ([]rawMessage, [][]byte, [][]byte) {
|
||||
if bufSize <= 0 {
|
||||
bufSize = MTU
|
||||
}
|
||||
msgs := make([]rawMessage, n)
|
||||
buffers := make([][]byte, n)
|
||||
names := make([][]byte, n)
|
||||
|
||||
for i := range msgs {
|
||||
buffers[i] = make([]byte, MTU)
|
||||
buffers[i] = make([]byte, bufSize)
|
||||
names[i] = make([]byte, unix.SizeofSockaddrInet6)
|
||||
|
||||
vs := []iovec{
|
||||
@@ -55,3 +58,35 @@ func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
|
||||
|
||||
return msgs, buffers, names
|
||||
}
|
||||
|
||||
func setRawMessageControl(msg *rawMessage, buf []byte) {
|
||||
if len(buf) == 0 {
|
||||
msg.Hdr.Control = nil
|
||||
msg.Hdr.Controllen = 0
|
||||
return
|
||||
}
|
||||
msg.Hdr.Control = &buf[0]
|
||||
msg.Hdr.Controllen = uint64(len(buf))
|
||||
}
|
||||
|
||||
func getRawMessageControlLen(msg *rawMessage) int {
|
||||
return int(msg.Hdr.Controllen)
|
||||
}
|
||||
|
||||
func getRawMessageFlags(msg *rawMessage) int {
|
||||
return int(msg.Hdr.Flags)
|
||||
}
|
||||
|
||||
func setCmsgLen(h *unix.Cmsghdr, l int) {
|
||||
h.Len = uint64(l)
|
||||
}
|
||||
|
||||
func setIovecSlice(iov *iovec, b []byte) {
|
||||
if len(b) == 0 {
|
||||
iov.Base = nil
|
||||
iov.Len = 0
|
||||
return
|
||||
}
|
||||
iov.Base = &b[0]
|
||||
iov.Len = uint64(len(b))
|
||||
}
|
||||
|
||||
+22
-28
@@ -14,7 +14,6 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -67,7 +66,7 @@ func NewRIOListener(l *logrus.Logger, addr netip.Addr, port int) (*RIOConn, erro
|
||||
|
||||
u := &RIOConn{l: l}
|
||||
|
||||
err := u.bind(l, &windows.SockaddrInet6{Addr: addr.As16(), Port: port})
|
||||
err := u.bind(&windows.SockaddrInet6{Addr: addr.As16(), Port: port})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bind: %w", err)
|
||||
}
|
||||
@@ -83,11 +82,11 @@ func NewRIOListener(l *logrus.Logger, addr netip.Addr, port int) (*RIOConn, erro
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (u *RIOConn) bind(l *logrus.Logger, sa windows.Sockaddr) error {
|
||||
func (u *RIOConn) bind(sa windows.Sockaddr) error {
|
||||
var err error
|
||||
u.sock, err = winrio.Socket(windows.AF_INET6, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("winrio.Socket error: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Enable v4 for this socket
|
||||
@@ -101,64 +100,52 @@ func (u *RIOConn) bind(l *logrus.Logger, sa windows.Sockaddr) error {
|
||||
size := uint32(unsafe.Sizeof(flag))
|
||||
err = syscall.WSAIoctl(syscall.Handle(u.sock), syscall.SIO_UDP_CONNRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0)
|
||||
if err != nil {
|
||||
// This is a best-effort to prevent errors from being returned by the udp recv operation.
|
||||
// Quietly log a failure and continue.
|
||||
l.WithError(err).Debug("failed to set UDP_CONNRESET ioctl")
|
||||
return err
|
||||
}
|
||||
|
||||
ret = 0
|
||||
flag = 0
|
||||
size = uint32(unsafe.Sizeof(flag))
|
||||
SIO_UDP_NETRESET := uint32(syscall.IOC_IN | syscall.IOC_VENDOR | 15)
|
||||
err = syscall.WSAIoctl(syscall.Handle(u.sock), SIO_UDP_NETRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0)
|
||||
if err != nil {
|
||||
// This is a best-effort to prevent errors from being returned by the udp recv operation.
|
||||
// Quietly log a failure and continue.
|
||||
l.WithError(err).Debug("failed to set UDP_NETRESET ioctl")
|
||||
return err
|
||||
}
|
||||
|
||||
err = u.rx.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error rx.Open(): %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = u.tx.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error tx.Open(): %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
u.rq, err = winrio.CreateRequestQueue(u.sock, packetsPerRing, 1, packetsPerRing, 1, u.rx.cq, u.tx.cq, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error CreateRequestQueue: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = windows.Bind(u.sock, sa)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error windows.Bind(): %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *RIOConn) ListenOut(r EncReader) {
|
||||
func (u *RIOConn) ListenOut(r EncReader) error {
|
||||
buffer := make([]byte, MTU)
|
||||
|
||||
var lastRecvErr time.Time
|
||||
|
||||
for {
|
||||
// Just read one packet at a time
|
||||
n, rua, err := u.receive(buffer)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||
return
|
||||
}
|
||||
// Dampen unexpected message warns to once per minute
|
||||
if lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {
|
||||
lastRecvErr = time.Now()
|
||||
u.l.WithError(err).Warn("unexpected udp socket receive error")
|
||||
}
|
||||
u.l.WithError(err).Error("unexpected udp socket receive error")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -317,6 +304,17 @@ func (u *RIOConn) WriteTo(buf []byte, ip netip.AddrPort) error {
|
||||
return winrio.SendEx(u.rq, dataBuffer, 1, nil, addressBuffer, nil, nil, 0, 0)
|
||||
}
|
||||
|
||||
func (u *RIOConn) WriteBatch(pkts []BatchPacket) (int, error) {
|
||||
sent := 0
|
||||
for _, pkt := range pkts {
|
||||
if err := u.WriteTo(pkt.Payload, pkt.Addr); err != nil {
|
||||
return sent, err
|
||||
}
|
||||
sent++
|
||||
}
|
||||
return sent, nil
|
||||
}
|
||||
|
||||
func (u *RIOConn) LocalAddr() (netip.AddrPort, error) {
|
||||
sa, err := windows.Getsockname(u.sock)
|
||||
if err != nil {
|
||||
@@ -328,10 +326,6 @@ func (u *RIOConn) LocalAddr() (netip.AddrPort, error) {
|
||||
|
||||
}
|
||||
|
||||
func (u *RIOConn) SupportsMultipleReaders() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *RIOConn) Rebind() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
+11
-4
@@ -106,6 +106,17 @@ func (u *TesterConn) WriteTo(b []byte, addr netip.AddrPort) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *TesterConn) WriteBatch(pkts []BatchPacket) (int, error) {
|
||||
sent := 0
|
||||
for _, pkt := range pkts {
|
||||
if err := u.WriteTo(pkt.Payload, pkt.Addr); err != nil {
|
||||
return sent, err
|
||||
}
|
||||
sent++
|
||||
}
|
||||
return sent, nil
|
||||
}
|
||||
|
||||
func (u *TesterConn) ListenOut(r EncReader) {
|
||||
for {
|
||||
p, ok := <-u.RxPackets
|
||||
@@ -127,10 +138,6 @@ func (u *TesterConn) LocalAddr() (netip.AddrPort, error) {
|
||||
return u.Addr, nil
|
||||
}
|
||||
|
||||
func (u *TesterConn) SupportsMultipleReaders() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *TesterConn) Rebind() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user