mirror of
https://github.com/slackhq/nebula.git
synced 2026-09-30 12:46:37 +02:00
Compare commits
3
Commits
fix-addrmap
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89178f45ba | ||
|
|
6a72e1c304 | ||
|
|
e50f8128f4 |
@@ -51,15 +51,19 @@ wsl -d $Distro -- bash -c "rm -rf $WslDir && mkdir -p $WslDir" | Out-Null
|
|||||||
$DevName = 'nebula-smoke'
|
$DevName = 'nebula-smoke'
|
||||||
$Ip1 = '192.168.241.1'
|
$Ip1 = '192.168.241.1'
|
||||||
$Ip2 = '192.168.241.2'
|
$Ip2 = '192.168.241.2'
|
||||||
|
# Dual stack on purpose: a v4-only overlay never exercises the v6 side of tun.mtu.
|
||||||
|
$Ip6_1 = 'fd42:4242:241::1'
|
||||||
|
$Ip6_2 = 'fd42:4242:241::2'
|
||||||
|
$Mtu = 1300
|
||||||
$Port = 4242
|
$Port = 4242
|
||||||
|
|
||||||
& $NebulaCert ca -name 'smoke-ca' -out-crt "$WorkDir\ca.crt" -out-key "$WorkDir\ca.key"
|
& $NebulaCert ca -name 'smoke-ca' -out-crt "$WorkDir\ca.crt" -out-key "$WorkDir\ca.key"
|
||||||
if ($LASTEXITCODE -ne 0) { throw "nebula-cert ca failed (exit $LASTEXITCODE)" }
|
if ($LASTEXITCODE -ne 0) { throw "nebula-cert ca failed (exit $LASTEXITCODE)" }
|
||||||
|
|
||||||
& $NebulaCert sign -name 'lighthouse' -networks "$Ip1/24" -ca-crt "$WorkDir\ca.crt" -ca-key "$WorkDir\ca.key" -out-crt "$WorkDir\lighthouse.crt" -out-key "$WorkDir\lighthouse.key"
|
& $NebulaCert sign -name 'lighthouse' -networks "$Ip1/24,$Ip6_1/64" -ca-crt "$WorkDir\ca.crt" -ca-key "$WorkDir\ca.key" -out-crt "$WorkDir\lighthouse.crt" -out-key "$WorkDir\lighthouse.key"
|
||||||
if ($LASTEXITCODE -ne 0) { throw "nebula-cert sign lighthouse failed (exit $LASTEXITCODE)" }
|
if ($LASTEXITCODE -ne 0) { throw "nebula-cert sign lighthouse failed (exit $LASTEXITCODE)" }
|
||||||
|
|
||||||
& $NebulaCert sign -name 'peer' -networks "$Ip2/24" -ca-crt "$WorkDir\ca.crt" -ca-key "$WorkDir\ca.key" -out-crt "$WorkDir\peer.crt" -out-key "$WorkDir\peer.key"
|
& $NebulaCert sign -name 'peer' -networks "$Ip2/24,$Ip6_2/64" -ca-crt "$WorkDir\ca.crt" -ca-key "$WorkDir\ca.key" -out-crt "$WorkDir\peer.crt" -out-key "$WorkDir\peer.key"
|
||||||
if ($LASTEXITCODE -ne 0) { throw "nebula-cert sign peer failed (exit $LASTEXITCODE)" }
|
if ($LASTEXITCODE -ne 0) { throw "nebula-cert sign peer failed (exit $LASTEXITCODE)" }
|
||||||
|
|
||||||
# Windows lighthouse config.
|
# Windows lighthouse config.
|
||||||
@@ -82,7 +86,7 @@ tun:
|
|||||||
drop_local_broadcast: false
|
drop_local_broadcast: false
|
||||||
drop_multicast: false
|
drop_multicast: false
|
||||||
tx_queue: 500
|
tx_queue: 500
|
||||||
mtu: 1300
|
mtu: $Mtu
|
||||||
network_category: private
|
network_category: private
|
||||||
logging:
|
logging:
|
||||||
level: info
|
level: info
|
||||||
@@ -126,7 +130,7 @@ tun:
|
|||||||
drop_local_broadcast: false
|
drop_local_broadcast: false
|
||||||
drop_multicast: false
|
drop_multicast: false
|
||||||
tx_queue: 500
|
tx_queue: 500
|
||||||
mtu: 1300
|
mtu: $Mtu
|
||||||
logging:
|
logging:
|
||||||
level: info
|
level: info
|
||||||
format: text
|
format: text
|
||||||
@@ -169,7 +173,7 @@ Write-Host '=== WSL diagnostic ==='
|
|||||||
wsl --version 2>&1 | Out-Host
|
wsl --version 2>&1 | Out-Host
|
||||||
wsl --list --verbose 2>&1 | Out-Host
|
wsl --list --verbose 2>&1 | Out-Host
|
||||||
wsl -d $Distro -u root -- uname -a | Out-Host
|
wsl -d $Distro -u root -- uname -a | Out-Host
|
||||||
wsl -d $Distro -u root -- bash -c "modprobe tun 2>&1 || true; mkdir -p /dev/net; [ -c /dev/net/tun ] || mknod /dev/net/tun c 10 200; chmod 600 /dev/net/tun; ls -l /dev/net/tun"
|
wsl -d $Distro -u root -- bash -c "modprobe tun 2>&1 || true; mkdir -p /dev/net; [ -c /dev/net/tun ] || mknod /dev/net/tun c 10 200; chmod 600 /dev/net/tun; { echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6; echo 0 > /proc/sys/net/ipv6/conf/default/disable_ipv6; } 2>/dev/null || true; ls -l /dev/net/tun"
|
||||||
if ($LASTEXITCODE -ne 0) { throw "failed to prepare /dev/net/tun in WSL (TUN support missing?)" }
|
if ($LASTEXITCODE -ne 0) { throw "failed to prepare /dev/net/tun in WSL (TUN support missing?)" }
|
||||||
|
|
||||||
# Deliberately no New-NetFirewallRule calls here -- nebula's windows_bypass_wdf
|
# Deliberately no New-NetFirewallRule calls here -- nebula's windows_bypass_wdf
|
||||||
@@ -214,6 +218,16 @@ try {
|
|||||||
}
|
}
|
||||||
Write-Host "OK: $DevName NetworkCategory=Private"
|
Write-Host "OK: $DevName NetworkCategory=Private"
|
||||||
|
|
||||||
|
# v6 silently kept the adapter default of 65535 while v4 was correct.
|
||||||
|
foreach ($family in @('IPv4', 'IPv6')) {
|
||||||
|
Wait-Until -TimeoutSec 30 -What "$DevName $family NlMtu=$Mtu" -Predicate {
|
||||||
|
if ($lhProc.HasExited) { throw "lighthouse exited (code $($lhProc.ExitCode)) before $family mtu was set" }
|
||||||
|
$rows = @(Get-NetIPInterface -InterfaceAlias $DevName -AddressFamily $family -ErrorAction SilentlyContinue)
|
||||||
|
$rows.Count -gt 0 -and -not ($rows | Where-Object { $_.NlMtu -ne $Mtu })
|
||||||
|
}
|
||||||
|
Write-Host "OK: $DevName $family NlMtu=$Mtu"
|
||||||
|
}
|
||||||
|
|
||||||
Wait-Until -TimeoutSec 30 -What "WSL nebula1 with $Ip2" -Predicate {
|
Wait-Until -TimeoutSec 30 -What "WSL nebula1 with $Ip2" -Predicate {
|
||||||
if ($peerProc.HasExited) { throw "peer exited (code $($peerProc.ExitCode)) before tun was ready" }
|
if ($peerProc.HasExited) { throw "peer exited (code $($peerProc.ExitCode)) before tun was ready" }
|
||||||
$r = wsl -d $Distro -u root -- bash -c "ip -o addr show nebula1 2>/dev/null | grep -q 'inet $Ip2' && echo yes"
|
$r = wsl -d $Distro -u root -- bash -c "ip -o addr show nebula1 2>/dev/null | grep -q 'inet $Ip2' && echo yes"
|
||||||
@@ -221,6 +235,13 @@ try {
|
|||||||
}
|
}
|
||||||
Write-Host "OK: WSL nebula1 has $Ip2"
|
Write-Host "OK: WSL nebula1 has $Ip2"
|
||||||
|
|
||||||
|
Wait-Until -TimeoutSec 30 -What "WSL nebula1 with $Ip6_2" -Predicate {
|
||||||
|
if ($peerProc.HasExited) { throw "peer exited (code $($peerProc.ExitCode)) before the v6 address was up" }
|
||||||
|
$r = wsl -d $Distro -u root -- bash -c "ip -o addr show nebula1 2>/dev/null | grep -q 'inet6 $Ip6_2' && echo yes"
|
||||||
|
("$r").Trim() -eq 'yes'
|
||||||
|
}
|
||||||
|
Write-Host "OK: WSL nebula1 has $Ip6_2"
|
||||||
|
|
||||||
Wait-Until -TimeoutSec 30 -What "ping from WSL peer to windows lighthouse ($Ip1)" -Predicate {
|
Wait-Until -TimeoutSec 30 -What "ping from WSL peer to windows lighthouse ($Ip1)" -Predicate {
|
||||||
if ($peerProc.HasExited) { throw "peer exited (code $($peerProc.ExitCode)) before ping succeeded" }
|
if ($peerProc.HasExited) { throw "peer exited (code $($peerProc.ExitCode)) before ping succeeded" }
|
||||||
$r = wsl -d $Distro -u root -- bash -c "ping -c1 -W1 $Ip1 >/dev/null 2>&1 && echo OK"
|
$r = wsl -d $Distro -u root -- bash -c "ping -c1 -W1 $Ip1 >/dev/null 2>&1 && echo OK"
|
||||||
@@ -234,6 +255,14 @@ try {
|
|||||||
}
|
}
|
||||||
Write-Host "OK: windows lighthouse -> WSL peer"
|
Write-Host "OK: windows lighthouse -> WSL peer"
|
||||||
|
|
||||||
|
# Otherwise the v6 networks only prove the interface exists, not that it forwards.
|
||||||
|
Wait-Until -TimeoutSec 30 -What "v6 ping from WSL peer to windows lighthouse ($Ip6_1)" -Predicate {
|
||||||
|
if ($peerProc.HasExited) { throw "peer exited (code $($peerProc.ExitCode)) before the v6 ping succeeded" }
|
||||||
|
$r = wsl -d $Distro -u root -- bash -c "ping -6 -c1 -W1 $Ip6_1 >/dev/null 2>&1 && echo OK"
|
||||||
|
("$r").Trim() -eq 'OK'
|
||||||
|
}
|
||||||
|
Write-Host "OK: WSL peer -> windows lighthouse over v6"
|
||||||
|
|
||||||
Write-Host ''
|
Write-Host ''
|
||||||
Write-Host 'All smoke checks passed.'
|
Write-Host 'All smoke checks passed.'
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-1
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.11.1] - 2026-08-21
|
||||||
|
|
||||||
|
See the [v1.11.1](https://github.com/slackhq/nebula/milestone/30?closed=1) milestone for a complete list of changes.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- IPv6 packets whose next header is a protocol Nebula does not parse (SCTP, GRE, IP-in-IP, etc.) are now
|
- IPv6 packets whose next header is a protocol Nebula does not parse (SCTP, GRE, IP-in-IP, etc.) are now
|
||||||
@@ -15,11 +19,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
their true protocol, so only a `proto: any` rule allows them. If you carry one of these protocols over the
|
their true protocol, so only a `proto: any` rule allows them. If you carry one of these protocols over the
|
||||||
overlay, confirm a `proto: any` rule covers it before upgrading, it may have been passing only through this
|
overlay, confirm a `proto: any` rule covers it before upgrading, it may have been passing only through this
|
||||||
bypass. (#1840)
|
bypass. (#1840)
|
||||||
|
- Drop the dependency on `github.com/cyberdelia/go-metrics-graphite`, which has been unmaintained for over ten
|
||||||
|
years, by inlining the small amount of code Nebula used. (#1832)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- The ICMPv6 type was read from the wrong byte when classifying IPv6 packets, so the echo identifier used
|
- The ICMPv6 type was read from the wrong byte when classifying IPv6 packets, so the echo identifier used
|
||||||
for conntrack was never picked up. (#1840)
|
for conntrack was never picked up. (#1840)
|
||||||
|
- Enforce outbound message counter limits so a tunnel is rehandshaked before the counter can wrap, preventing
|
||||||
|
nonce reuse. This is unreachable in practice, but is enforced as a defense-in-depth measure. (#1841)
|
||||||
|
- Prevent `nebula-cert ca` from running out of memory on 32bit systems when generating encrypted private keys. (#1834)
|
||||||
|
- Tolerate `ErrDumpInterrupted` when listing tun addresses on Linux, so a transient interrupted netlink dump
|
||||||
|
no longer aborts startup. (#1835)
|
||||||
|
|
||||||
## [1.11.0] - 2026-07-23
|
## [1.11.0] - 2026-07-23
|
||||||
|
|
||||||
@@ -884,7 +895,9 @@ created.)
|
|||||||
|
|
||||||
- Initial public release.
|
- Initial public release.
|
||||||
|
|
||||||
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.3...HEAD
|
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.11.1...HEAD
|
||||||
|
[1.11.1]: https://github.com/slackhq/nebula/releases/tag/v1.11.1
|
||||||
|
[1.11.0]: https://github.com/slackhq/nebula/releases/tag/v1.11.0
|
||||||
[1.10.3]: https://github.com/slackhq/nebula/releases/tag/v1.10.3
|
[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.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.1]: https://github.com/slackhq/nebula/releases/tag/v1.10.1
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ type LightHouse struct {
|
|||||||
|
|
||||||
myVpnNetworks []netip.Prefix
|
myVpnNetworks []netip.Prefix
|
||||||
myVpnNetworksTable *bart.Lite
|
myVpnNetworksTable *bart.Lite
|
||||||
|
// myVpnAddrsTable contains our overlay host addrs, as opposed to the overlay networks
|
||||||
|
myVpnAddrsTable *bart.Lite
|
||||||
punchy *Punchy
|
punchy *Punchy
|
||||||
|
|
||||||
// localAddrsFn enumerates the underlay addresses we advertise. It is a field so tests can supply simulated
|
// localAddrsFn enumerates the underlay addresses we advertise. It is a field so tests can supply simulated
|
||||||
@@ -104,6 +106,7 @@ func NewLightHouseFromConfig(ctx context.Context, l *slog.Logger, c *config.C, c
|
|||||||
amLighthouse: amLighthouse,
|
amLighthouse: amLighthouse,
|
||||||
myVpnNetworks: cs.myVpnNetworks,
|
myVpnNetworks: cs.myVpnNetworks,
|
||||||
myVpnNetworksTable: cs.myVpnNetworksTable,
|
myVpnNetworksTable: cs.myVpnNetworksTable,
|
||||||
|
myVpnAddrsTable: cs.myVpnAddrsTable,
|
||||||
addrMap: make(map[netip.Addr]*RemoteList),
|
addrMap: make(map[netip.Addr]*RemoteList),
|
||||||
nebulaPort: nebulaPort,
|
nebulaPort: nebulaPort,
|
||||||
punchy: p,
|
punchy: p,
|
||||||
@@ -1158,6 +1161,17 @@ func (lhh *LightHouseHandler) handleHostQuery(n *NebulaMeta, fromVpnAddrs []neti
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't respond to requests for us.
|
||||||
|
if lhh.lh.myVpnAddrsTable.Contains(queryVpnAddr) {
|
||||||
|
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
||||||
|
lhh.l.Debug("Ignoring HostQuery for one of my own addresses",
|
||||||
|
"fromVpnAddrs", fromVpnAddrs,
|
||||||
|
"queryVpnAddr", queryVpnAddr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
found, ln, err := lhh.lh.queryAndPrepMessage(queryVpnAddr, func(c *cache) (int, error) {
|
found, ln, err := lhh.lh.queryAndPrepMessage(queryVpnAddr, func(c *cache) (int, error) {
|
||||||
n = lhh.resetMeta()
|
n = lhh.resetMeta()
|
||||||
n.Type = NebulaMeta_HostQueryReply
|
n.Type = NebulaMeta_HostQueryReply
|
||||||
|
|||||||
+80
-54
@@ -27,15 +27,27 @@ func TestOldIPv4Only(t *testing.T) {
|
|||||||
assert.Equal(t, binary.BigEndian.Uint32(bp[:]), m.GetAddr())
|
assert.Equal(t, binary.BigEndian.Uint32(bp[:]), m.GetAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCertState(networks ...netip.Prefix) *CertState {
|
||||||
|
cs := &CertState{
|
||||||
|
myVpnNetworks: networks,
|
||||||
|
myVpnNetworksTable: new(bart.Lite),
|
||||||
|
myVpnAddrs: make([]netip.Addr, 0, len(networks)),
|
||||||
|
myVpnAddrsTable: new(bart.Lite),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range networks {
|
||||||
|
cs.myVpnNetworksTable.Insert(n)
|
||||||
|
cs.myVpnAddrs = append(cs.myVpnAddrs, n.Addr())
|
||||||
|
cs.myVpnAddrsTable.Insert(netip.PrefixFrom(n.Addr(), n.Addr().BitLen()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cs
|
||||||
|
}
|
||||||
|
|
||||||
func Test_lhStaticMapping(t *testing.T) {
|
func Test_lhStaticMapping(t *testing.T) {
|
||||||
l := test.NewLogger()
|
l := test.NewLogger()
|
||||||
myVpnNet := netip.MustParsePrefix("10.128.0.1/16")
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/16")
|
||||||
nt := new(bart.Lite)
|
cs := testCertState(myVpnNet)
|
||||||
nt.Insert(myVpnNet)
|
|
||||||
cs := &CertState{
|
|
||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
|
||||||
myVpnNetworksTable: nt,
|
|
||||||
}
|
|
||||||
lh1 := "10.128.0.2"
|
lh1 := "10.128.0.2"
|
||||||
|
|
||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
@@ -55,12 +67,7 @@ func Test_lhStaticMapping(t *testing.T) {
|
|||||||
func TestReloadLighthouseInterval(t *testing.T) {
|
func TestReloadLighthouseInterval(t *testing.T) {
|
||||||
l := test.NewLogger()
|
l := test.NewLogger()
|
||||||
myVpnNet := netip.MustParsePrefix("10.128.0.1/16")
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/16")
|
||||||
nt := new(bart.Lite)
|
cs := testCertState(myVpnNet)
|
||||||
nt.Insert(myVpnNet)
|
|
||||||
cs := &CertState{
|
|
||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
|
||||||
myVpnNetworksTable: nt,
|
|
||||||
}
|
|
||||||
lh1 := "10.128.0.2"
|
lh1 := "10.128.0.2"
|
||||||
|
|
||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
@@ -90,12 +97,7 @@ func TestReloadLighthouseInterval(t *testing.T) {
|
|||||||
func BenchmarkLighthouseHandleRequest(b *testing.B) {
|
func BenchmarkLighthouseHandleRequest(b *testing.B) {
|
||||||
l := test.NewLogger()
|
l := test.NewLogger()
|
||||||
myVpnNet := netip.MustParsePrefix("10.128.0.1/0")
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/0")
|
||||||
nt := new(bart.Lite)
|
cs := testCertState(myVpnNet)
|
||||||
nt.Insert(myVpnNet)
|
|
||||||
cs := &CertState{
|
|
||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
|
||||||
myVpnNetworksTable: nt,
|
|
||||||
}
|
|
||||||
|
|
||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
lh, err := NewLightHouseFromConfig(b.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(b.Context(), l, c, cs, nil, nil)
|
||||||
@@ -195,12 +197,7 @@ func TestLighthouse_Memory(t *testing.T) {
|
|||||||
c.Settings["listen"] = map[string]any{"port": 4242}
|
c.Settings["listen"] = map[string]any{"port": 4242}
|
||||||
|
|
||||||
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
||||||
nt := new(bart.Lite)
|
cs := testCertState(myVpnNet)
|
||||||
nt.Insert(myVpnNet)
|
|
||||||
cs := &CertState{
|
|
||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
|
||||||
myVpnNetworksTable: nt,
|
|
||||||
}
|
|
||||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
lh.ifce = &mockEncWriter{}
|
lh.ifce = &mockEncWriter{}
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -280,12 +277,7 @@ func TestLighthouse_reload(t *testing.T) {
|
|||||||
c.Settings["listen"] = map[string]any{"port": 4242}
|
c.Settings["listen"] = map[string]any{"port": 4242}
|
||||||
|
|
||||||
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
||||||
nt := new(bart.Lite)
|
cs := testCertState(myVpnNet)
|
||||||
nt.Insert(myVpnNet)
|
|
||||||
cs := &CertState{
|
|
||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
|
||||||
myVpnNetworksTable: nt,
|
|
||||||
}
|
|
||||||
|
|
||||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -315,12 +307,7 @@ func TestLighthouse_reloadStaticHostMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
||||||
nt := new(bart.Lite)
|
cs := testCertState(myVpnNet)
|
||||||
nt.Insert(myVpnNet)
|
|
||||||
cs := &CertState{
|
|
||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
|
||||||
myVpnNetworksTable: nt,
|
|
||||||
}
|
|
||||||
|
|
||||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -429,7 +416,9 @@ func TestLighthouse_reloadStaticHostMap(t *testing.T) {
|
|||||||
assert.Equal(t, []netip.AddrPort{netip.MustParseAddrPort("3.3.3.3:4242")}, rl.CopyAddrs([]netip.Prefix{}))
|
assert.Equal(t, []netip.AddrPort{netip.MustParseAddrPort("3.3.3.3:4242")}, rl.CopyAddrs([]netip.Prefix{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLHHostRequest(fromAddr netip.AddrPort, myVpnIp, queryVpnIp netip.Addr, lhh *LightHouseHandler) testLhReply {
|
// sendLHHostRequest delivers a HostQuery to lhh and hands back the writer that
|
||||||
|
// captured what it emitted. Pass a nil filter to see every message.
|
||||||
|
func sendLHHostRequest(fromAddr netip.AddrPort, myVpnIp, queryVpnIp netip.Addr, lhh *LightHouseHandler, filter *NebulaMeta_MessageType) *testEncWriter {
|
||||||
req := &NebulaMeta{
|
req := &NebulaMeta{
|
||||||
Type: NebulaMeta_HostQuery,
|
Type: NebulaMeta_HostQuery,
|
||||||
Details: &NebulaMetaDetails{},
|
Details: &NebulaMetaDetails{},
|
||||||
@@ -447,12 +436,59 @@ func newLHHostRequest(fromAddr netip.AddrPort, myVpnIp, queryVpnIp netip.Addr, l
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := NebulaMeta_HostQueryReply
|
w := &testEncWriter{metaFilter: filter}
|
||||||
w := &testEncWriter{
|
|
||||||
metaFilter: &filter,
|
|
||||||
}
|
|
||||||
lhh.HandleRequest(fromAddr, []netip.Addr{myVpnIp}, b, w)
|
lhh.HandleRequest(fromAddr, []netip.Addr{myVpnIp}, b, w)
|
||||||
return w.lastReply
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLHHostRequest(fromAddr netip.AddrPort, myVpnIp, queryVpnIp netip.Addr, lhh *LightHouseHandler) testLhReply {
|
||||||
|
filter := NebulaMeta_HostQueryReply
|
||||||
|
return sendLHHostRequest(fromAddr, myVpnIp, queryVpnIp, lhh, &filter).lastReply
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLighthouse_IgnoresHostQueryForItself(t *testing.T) {
|
||||||
|
// Validate that we don't answer host queries for our own address.
|
||||||
|
l := test.NewLogger()
|
||||||
|
|
||||||
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
||||||
|
myVpnIp := myVpnNet.Addr()
|
||||||
|
|
||||||
|
c := config.NewC(l)
|
||||||
|
c.Settings["lighthouse"] = map[string]any{"am_lighthouse": true}
|
||||||
|
c.Settings["listen"] = map[string]any{"port": 4242}
|
||||||
|
// Add a static_host_map entry for ourselves, so our address
|
||||||
|
// is in the addrMap.
|
||||||
|
c.Settings["static_host_map"] = map[string]any{
|
||||||
|
myVpnIp.String(): []any{"192.168.100.1:4242"},
|
||||||
|
}
|
||||||
|
|
||||||
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, testCertState(myVpnNet), nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
lh.ifce = &mockEncWriter{}
|
||||||
|
lhh := lh.NewRequestHandler()
|
||||||
|
|
||||||
|
peerVpnIp := netip.MustParseAddr("10.128.0.2")
|
||||||
|
peerUdpAddr := netip.MustParseAddrPort("10.0.0.2:4242")
|
||||||
|
otherVpnIp := netip.MustParseAddr("10.128.0.3")
|
||||||
|
otherUdpAddr := netip.MustParseAddrPort("10.0.0.3:4242")
|
||||||
|
|
||||||
|
newLHHostUpdate(peerUdpAddr, peerVpnIp, []netip.AddrPort{peerUdpAddr}, lhh)
|
||||||
|
newLHHostUpdate(otherUdpAddr, otherVpnIp, []netip.AddrPort{otherUdpAddr}, lhh)
|
||||||
|
|
||||||
|
// Control: a query about a real peer is still answered, and still ends with
|
||||||
|
// the punch notification aimed at the host that was asked about.
|
||||||
|
w := sendLHHostRequest(peerUdpAddr, peerVpnIp, otherVpnIp, lhh, nil)
|
||||||
|
require.NotNil(t, w.lastReply.msg)
|
||||||
|
assert.Equal(t, NebulaMeta_HostPunchNotification, w.lastReply.msg.Type)
|
||||||
|
assert.Equal(t, otherVpnIp, w.lastReply.vpnIp)
|
||||||
|
|
||||||
|
// Now validate that we don't send to ourselves.
|
||||||
|
found, _, err := lh.queryAndPrepMessage(myVpnIp, func(*cache) (int, error) { return 0, nil })
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, found, "the lighthouse should hold a cache entry for its own address")
|
||||||
|
|
||||||
|
w = sendLHHostRequest(peerUdpAddr, peerVpnIp, myVpnIp, lhh, nil)
|
||||||
|
assert.Nil(t, w.lastReply.msg, "a query about our own address must produce no reply and no punch notification")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLHHostUpdate(fromAddr netip.AddrPort, vpnIp netip.Addr, addrs []netip.AddrPort, lhh *LightHouseHandler) {
|
func newLHHostUpdate(fromAddr netip.AddrPort, vpnIp netip.Addr, addrs []netip.AddrPort, lhh *LightHouseHandler) {
|
||||||
@@ -642,12 +678,7 @@ func TestLighthouse_Dont_Delete_Static_Hosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
||||||
nt := new(bart.Lite)
|
cs := testCertState(myVpnNet)
|
||||||
nt.Insert(myVpnNet)
|
|
||||||
cs := &CertState{
|
|
||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
|
||||||
myVpnNetworksTable: nt,
|
|
||||||
}
|
|
||||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
lh.ifce = &mockEncWriter{}
|
lh.ifce = &mockEncWriter{}
|
||||||
@@ -708,12 +739,7 @@ func TestLighthouse_DeletesWork(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
||||||
nt := new(bart.Lite)
|
cs := testCertState(myVpnNet)
|
||||||
nt.Insert(myVpnNet)
|
|
||||||
cs := &CertState{
|
|
||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
|
||||||
myVpnNetworksTable: nt,
|
|
||||||
}
|
|
||||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
lh.ifce = &mockEncWriter{}
|
lh.ifce = &mockEncWriter{}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -182,6 +183,7 @@ func (t *winTun) addRoutes(logErrors bool) error {
|
|||||||
luid := winipcfg.LUID(t.tun.LUID())
|
luid := winipcfg.LUID(t.tun.LUID())
|
||||||
routes := *t.Routes.Load()
|
routes := *t.Routes.Load()
|
||||||
foundDefault4 := false
|
foundDefault4 := false
|
||||||
|
carriesV6 := slices.ContainsFunc(t.vpnNetworks, func(p netip.Prefix) bool { return p.Addr().Is6() })
|
||||||
|
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
if len(r.Via) == 0 || !r.Install {
|
if len(r.Via) == 0 || !r.Install {
|
||||||
@@ -189,6 +191,9 @@ func (t *winTun) addRoutes(logErrors bool) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A v6 unsafe_route is legal under a v4-only cert; uninstalled ones put nothing on the adapter.
|
||||||
|
carriesV6 = carriesV6 || r.Cidr.Addr().Is6()
|
||||||
|
|
||||||
// Add our unsafe route as an on-link route to the nebula tun device.
|
// Add our unsafe route as an on-link route to the nebula tun device.
|
||||||
err := luid.AddRoute(r.Cidr, unspecifiedNextHop(r.Cidr), uint32(r.Metric))
|
err := luid.AddRoute(r.Cidr, unspecifiedNextHop(r.Cidr), uint32(r.Metric))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -210,6 +215,11 @@ func (t *winTun) addRoutes(logErrors bool) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return t.setMTU(luid, foundDefault4, carriesV6)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setMTU applies tun.mtu per address family. The default route metric rides along on the v4 handle.
|
||||||
|
func (t *winTun) setMTU(luid winipcfg.LUID, foundDefault4, carriesV6 bool) error {
|
||||||
ipif, err := luid.IPInterface(windows.AF_INET)
|
ipif, err := luid.IPInterface(windows.AF_INET)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get ip interface: %w", err)
|
return fmt.Errorf("failed to get ip interface: %w", err)
|
||||||
@@ -224,6 +234,25 @@ func (t *winTun) addRoutes(logErrors bool) error {
|
|||||||
if err := ipif.Set(); err != nil {
|
if err := ipif.Set(); err != nil {
|
||||||
return fmt.Errorf("failed to set ip interface: %w", err)
|
return fmt.Errorf("failed to set ip interface: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Windows tracks NLMTU per family and wintun sets neither, so v6 keeps the adapter default of 65535.
|
||||||
|
// Gated so a v4-only overlay under 1280 boots; a v6 one deliberately does not, as linux also refuses.
|
||||||
|
if !carriesV6 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ipif6, err := luid.IPInterface(windows.AF_INET6)
|
||||||
|
if err != nil {
|
||||||
|
// No v6 on the adapter means there is no NLMTU to get wrong. A failed Set below is not the same thing.
|
||||||
|
t.l.Info("Skipping ipv6 MTU, no ipv6 interface on this adapter", "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ipif6.NLMTU = uint32(t.MTU)
|
||||||
|
if err := ipif6.Set(); err != nil {
|
||||||
|
return fmt.Errorf("failed to set ipv6 interface: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-5
@@ -31,7 +31,8 @@ func procyield(cycles uint32)
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
packetsPerRing = 1024
|
packetsPerRing = 1024
|
||||||
bytesPerPacket = 2048 - 32
|
// Caps tun.mtu at MTU-32 direct, MTU-64 relayed, unenforced anywhere else. 17.6MB page locked per socket.
|
||||||
|
bytesPerPacket = MTU
|
||||||
receiveSpins = 15
|
receiveSpins = 15
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,12 +70,14 @@ func NewRIOListener(l *slog.Logger, addr netip.Addr, port int) (*RIOConn, error)
|
|||||||
|
|
||||||
err := u.bind(l, &windows.SockaddrInet6{Addr: addr.As16(), Port: port})
|
err := u.bind(l, &windows.SockaddrInet6{Addr: addr.As16(), Port: port})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
u.close()
|
||||||
return nil, fmt.Errorf("bind: %w", err)
|
return nil, fmt.Errorf("bind: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < packetsPerRing; i++ {
|
for i := 0; i < packetsPerRing; i++ {
|
||||||
err = u.insertReceiveRequest()
|
err = u.insertReceiveRequest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
u.close()
|
||||||
return nil, fmt.Errorf("init rx ring: %w", err)
|
return nil, fmt.Errorf("init rx ring: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,15 +359,25 @@ func (u *RIOConn) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u.close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also unwinds a partial build from NewRIOListener, where isOpen is false and Close would no-op.
|
||||||
|
// Socket first, unlike wireguard-go: receive() re-arms every slot, so freeing the rings under a live socket
|
||||||
|
// hands the kernel freed pages for all packetsPerRing outstanding receives.
|
||||||
|
func (u *RIOConn) close() {
|
||||||
|
// WSASocket reports failure as InvalidHandle, not zero.
|
||||||
|
if u.sock != 0 && u.sock != windows.InvalidHandle {
|
||||||
|
windows.CloseHandle(u.sock)
|
||||||
|
}
|
||||||
|
u.sock = 0
|
||||||
|
|
||||||
windows.PostQueuedCompletionStatus(u.rx.iocp, 0, 0, nil)
|
windows.PostQueuedCompletionStatus(u.rx.iocp, 0, 0, nil)
|
||||||
windows.PostQueuedCompletionStatus(u.tx.iocp, 0, 0, nil)
|
windows.PostQueuedCompletionStatus(u.tx.iocp, 0, 0, nil)
|
||||||
|
|
||||||
u.rx.CloseAndZero()
|
u.rx.CloseAndZero()
|
||||||
u.tx.CloseAndZero()
|
u.tx.CloseAndZero()
|
||||||
if u.sock != 0 {
|
|
||||||
windows.CloseHandle(u.sock)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ring *ringBuffer) Push() *ringPacket {
|
func (ring *ringBuffer) Push() *ringPacket {
|
||||||
|
|||||||
Reference in New Issue
Block a user