From b041f306cb2ee8177dfcb9ed8e7f31e6e7fe6564 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 10:25:27 -0400 Subject: [PATCH 01/15] Bump the golang-x-dependencies group with 3 updates (#1742) Bumps the golang-x-dependencies group with 3 updates: [golang.org/x/crypto](https://github.com/golang/crypto), [golang.org/x/net](https://github.com/golang/net) and [golang.org/x/sys](https://github.com/golang/sys). Updates `golang.org/x/crypto` from 0.51.0 to 0.52.0 - [Commits](https://github.com/golang/crypto/compare/v0.51.0...v0.52.0) Updates `golang.org/x/net` from 0.54.0 to 0.55.0 - [Commits](https://github.com/golang/net/compare/v0.54.0...v0.55.0) Updates `golang.org/x/sys` from 0.44.0 to 0.45.0 - [Commits](https://github.com/golang/sys/compare/v0.44.0...v0.45.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.52.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: golang-x-dependencies - dependency-name: golang.org/x/net dependency-version: 0.55.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: golang-x-dependencies - dependency-name: golang.org/x/sys dependency-version: 0.45.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: golang-x-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index bd1c0c57..27ba90ea 100644 --- a/go.mod +++ b/go.mod @@ -24,11 +24,11 @@ require ( github.com/vishvananda/netlink v1.3.1 go.uber.org/goleak v1.3.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.51.0 + golang.org/x/crypto v0.52.0 golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 - golang.org/x/net v0.54.0 + golang.org/x/net v0.55.0 golang.org/x/sync v0.20.0 - golang.org/x/sys v0.44.0 + golang.org/x/sys v0.45.0 golang.org/x/term v0.43.0 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b diff --git a/go.sum b/go.sum index 8ab36d34..b6f886bd 100644 --- a/go.sum +++ b/go.sum @@ -162,8 +162,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= 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= @@ -182,8 +182,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= 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= @@ -208,8 +208,8 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/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.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= From e6032f81aabdf70ecc73659cb94004c9f99c9c9f Mon Sep 17 00:00:00 2001 From: Jack Doan Date: Tue, 9 Jun 2026 16:18:59 -0500 Subject: [PATCH 02/15] correctly record window counters for relayed packets in a tunnel (#1751) --- e2e/tunnels_test.go | 95 +++++++++++++++++++++++++++++++++++++++++++++ outside.go | 7 ++++ 2 files changed, 102 insertions(+) diff --git a/e2e/tunnels_test.go b/e2e/tunnels_test.go index 697f25af..18c69a3f 100644 --- a/e2e/tunnels_test.go +++ b/e2e/tunnels_test.go @@ -15,6 +15,7 @@ import ( "github.com/slackhq/nebula/header" "github.com/slackhq/nebula/udp" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" ) @@ -373,6 +374,100 @@ func TestCrossStackRelaysWork(t *testing.T) { //relayControl.Stop() } +// TestRelayReplayProtection asserts that a relay (forwarding-type) node rejects +// replayed relay frames. A captured relay frame, re-injected with the same +// message counter, must be dropped by the replay window rather than re-forwarded +// to the relay target. Before the fix, handleOutsideRelayPacket authenticated the +// frame but never advanced the replay window, so every replay was re-forwarded. +func TestRelayReplayProtection(t *testing.T) { + t.Parallel() + 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}}) + + myVpnV6 := myVpnIpNet[1] + relayVpnV4 := relayVpnIpNet[0] + relayVpnV6 := relayVpnIpNet[1] + theirVpnV6 := theirVpnIpNet[0] + + // Teach me how to reach the relay and that them is reachable 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) + + r := router.NewR(t, myControl, relayControl, theirControl) + defer r.RenderFlow() + + myControl.Start() + relayControl.Start() + theirControl.Start() + + // Establish the relayed tunnel in both directions so all handshakes complete. + t.Log("Establish the relayed tunnel") + myControl.InjectTunPacket(BuildTunUDPPacket(theirVpnV6.Addr(), 80, myVpnV6.Addr(), 80, []byte("Hi from me"))) + p := r.RouteForAllUntilTxTun(theirControl) + assertUdpPacket(t, []byte("Hi from me"), p, myVpnV6.Addr(), theirVpnV6.Addr(), 80, 80) + theirControl.InjectTunPacket(BuildTunUDPPacket(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) + + // Drain anything still queued on me's UDP tx so the next packet we pull is the + // relay frame we are about to generate. + for myControl.GetFromUDP(false) != nil { + } + + // Capture a single legitimate relay frame that me transmits toward the relay. + t.Log("Capture a relay frame from me -> relay") + myControl.InjectTunPacket(BuildTunUDPPacket(theirVpnV6.Addr(), 80, myVpnV6.Addr(), 80, []byte("replay me"))) + relayFrame := myControl.GetFromUDP(true) + require.Equal(t, relayUdpAddr, relayFrame.To, "captured frame should be addressed to the relay") + var fh header.H + require.NoError(t, fh.Parse(relayFrame.Data)) + require.Equal(t, header.Message, fh.Type) + require.Equal(t, header.MessageRelay, fh.Subtype) + + // drainForwards counts relay frames the relay forwards toward them within the + // settle window. We match on destination + (Message, MessageRelay) so the + // relay's own direct traffic to them can't be miscounted. + drainForwards := func(settle time.Duration) int { + ch := relayControl.GetUDPTxChan() + count := 0 + for { + select { + case pkt := <-ch: + var ph header.H + if pkt.To == theirUdpAddr && ph.Parse(pkt.Data) == nil && + ph.Type == header.Message && ph.Subtype == header.MessageRelay { + count++ + } + pkt.Release() + case <-time.After(settle): + return count + } + } + } + + // First delivery of the captured frame: the relay should forward it once. + t.Log("Deliver the captured frame once; relay forwards it to them") + relayControl.InjectUDPPacket(relayFrame) + require.Equal(t, 1, drainForwards(200*time.Millisecond), "relay should forward the first, legitimate copy") + + // Replay the exact same frame several times. A correct replay window rejects + // these duplicates so the relay forwards none of them. + t.Log("Replay the captured frame; relay must drop the duplicates") + const replays = 3 + for i := 0; i < replays; i++ { + relayControl.InjectUDPPacket(relayFrame) + } + forwarded := drainForwards(200 * time.Millisecond) + assert.Equal(t, 0, forwarded, "relay re-forwarded %d/%d replayed relay frames; replay protection is ineffective on relay tunnels", forwarded, replays) + + r.RenderHostmaps("Final hostmaps", myControl, relayControl, theirControl) +} + func TestCloseTunnelAuthenticated(t *testing.T) { t.Parallel() ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{}) diff --git a/outside.go b/outside.go index 4c0c935e..7ebd4b5e 100644 --- a/outside.go +++ b/outside.go @@ -181,6 +181,13 @@ func (f *Interface) handleOutsideRelayPacket(hostinfo *HostInfo, via ViaSender, if err != nil { return } + // Advance the replay window now that the frame is authenticated + if !hostinfo.ConnectionState.window.Update(f.l, h.MessageCounter) { + if f.l.Enabled(context.Background(), slog.LevelDebug) { + hostinfo.logger(f.l).Debug("dropping out of window relay packet", "header", h) + } + return + } // 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. From eaad4896c1249d947c5f9c6bd99e0cb5b0074697 Mon Sep 17 00:00:00 2001 From: Jack Doan Date: Wed, 10 Jun 2026 09:26:31 -0500 Subject: [PATCH 03/15] udp_darwin: don't call the EncReader on a UDP error (#1755) --- udp/udp_darwin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/udp/udp_darwin.go b/udp/udp_darwin.go index 8a4f5b18..3d6b39a5 100644 --- a/udp/udp_darwin.go +++ b/udp/udp_darwin.go @@ -175,8 +175,8 @@ func (u *StdConn) ListenOut(r EncReader) error { if errors.Is(err, net.ErrClosed) { return err } - u.l.Error("unexpected udp socket receive error", "error", err) + continue } r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n]) From 3db406b8ac351b9784e0625cc28096073e12f1ed Mon Sep 17 00:00:00 2001 From: Jack Doan Date: Wed, 10 Jun 2026 09:27:15 -0500 Subject: [PATCH 04/15] fix a race in RelayState.CopyRelayIps (#1753) --- hostmap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hostmap.go b/hostmap.go index 08acd1be..bff586c7 100644 --- a/hostmap.go +++ b/hostmap.go @@ -138,9 +138,9 @@ func (rs *RelayState) InsertRelayTo(ip netip.Addr) { } func (rs *RelayState) CopyRelayIps() []netip.Addr { - ret := make([]netip.Addr, len(rs.relays)) rs.RLock() defer rs.RUnlock() + ret := make([]netip.Addr, len(rs.relays)) copy(ret, rs.relays) return ret } From e028e6bf1a6e42c8d058a73c1f0de4941774b86a Mon Sep 17 00:00:00 2001 From: Jack Doan Date: Wed, 10 Jun 2026 09:27:40 -0500 Subject: [PATCH 05/15] disallow negative stats intervals (#1754) --- stats.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stats.go b/stats.go index 97ce7cf5..98c2baf2 100644 --- a/stats.go +++ b/stats.go @@ -331,7 +331,7 @@ func loadStatsConfig(c *config.C) (statsConfig, error) { } cfg.interval = c.GetDuration("stats.interval", 0) - if cfg.interval == 0 { + if cfg.interval <= 0 { return cfg, fmt.Errorf("stats.interval was an invalid duration: %s", c.GetString("stats.interval", "")) } From a690c904ba3baa0f0321fbb8f02a31302d6483a0 Mon Sep 17 00:00:00 2001 From: Jack Doan Date: Wed, 10 Jun 2026 09:28:07 -0500 Subject: [PATCH 06/15] improve rejection of malformed handshakes (#1756) --- handshake/errors.go | 1 + handshake/machine.go | 12 ++++++++++-- handshake/machine_test.go | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/handshake/errors.go b/handshake/errors.go index bb8a5893..3bdcc947 100644 --- a/handshake/errors.go +++ b/handshake/errors.go @@ -13,6 +13,7 @@ var ( ErrUnknownSubtype = errors.New("unknown handshake subtype") ErrMissingContent = errors.New("expected handshake content but message was empty") ErrUnexpectedContent = errors.New("received unexpected handshake content") + ErrInvalidRemoteIndex = errors.New("peer sent an invalid index in handshake payload") ErrIndexAllocation = errors.New("failed to allocate local index") ErrNoCredential = errors.New("no handshake credential available for cert version") ErrAsymmetricCipherKeys = errors.New("noise produced only one cipher key") diff --git a/handshake/machine.go b/handshake/machine.go index 737358dc..baf61589 100644 --- a/handshake/machine.go +++ b/handshake/machine.go @@ -312,11 +312,19 @@ func (m *Machine) processPayload(msg []byte, flags msgFlags) error { // Process payload if flags.expectsPayload { + var remoteIndex uint32 if m.result.Initiator { - m.result.RemoteIndex = payload.ResponderIndex + remoteIndex = payload.ResponderIndex } else { - m.result.RemoteIndex = payload.InitiatorIndex + remoteIndex = payload.InitiatorIndex } + // The payload presence check above can be satisfied by Time alone, so a payload + // could still carry a zero index here. We need to reject it. + if remoteIndex == 0 { + m.failed = true + return ErrInvalidRemoteIndex + } + m.result.RemoteIndex = remoteIndex m.result.HandshakeTime = payload.Time m.payloadSet = true } diff --git a/handshake/machine_test.go b/handshake/machine_test.go index 722a39e1..01c968ed 100644 --- a/handshake/machine_test.go +++ b/handshake/machine_test.go @@ -229,6 +229,24 @@ func TestMachineProcessPayload(t *testing.T) { require.ErrorIs(t, err, ErrUnexpectedContent) assert.True(t, m.Failed()) }) + + t.Run("zero initiator index on responder is fatal", func(t *testing.T) { + m := newTestMachine(t, cs, v, false, 100) + bytes := MarshalPayload(nil, Payload{InitiatorIndex: 0, Time: 1}) + err := m.processPayload(bytes, msgFlags{expectsPayload: true}) + require.ErrorIs(t, err, ErrInvalidRemoteIndex) + assert.True(t, m.Failed()) + assert.Zero(t, m.result.RemoteIndex) + }) + + t.Run("zero responder index on initiator is fatal", func(t *testing.T) { + m := newTestMachine(t, cs, v, true, 100) + bytes := MarshalPayload(nil, Payload{InitiatorIndex: 100, ResponderIndex: 0, Time: 1}) + err := m.processPayload(bytes, msgFlags{expectsPayload: true}) + require.ErrorIs(t, err, ErrInvalidRemoteIndex) + assert.True(t, m.Failed()) + assert.Zero(t, m.result.RemoteIndex) + }) } // TestMachineRequireComplete checks the fail-on-incomplete-handshake path From 2e9117da5bc0fc471303028af2b481ac6ce31ef1 Mon Sep 17 00:00:00 2001 From: Jack Doan Date: Wed, 10 Jun 2026 11:03:23 -0500 Subject: [PATCH 07/15] fix tunnels that could permanently escape connection-manager monitoring (#1752) --- connection_manager.go | 8 -------- handshake_manager.go | 2 -- hostmap.go | 5 +++++ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/connection_manager.go b/connection_manager.go index ee6d1eaf..65fdab2a 100644 --- a/connection_manager.go +++ b/connection_manager.go @@ -136,14 +136,6 @@ func (cm *connectionManager) getAndResetTrafficCheck(h *HostInfo, now time.Time) return in, out } -// AddTrafficWatch must be called for every new HostInfo. -// We will continue to monitor the HostInfo until the tunnel is dropped. -func (cm *connectionManager) AddTrafficWatch(h *HostInfo) { - if h.out.Swap(true) == false { - cm.trafficTimer.Add(h.localIndexId, cm.checkInterval) - } -} - func (cm *connectionManager) Start(ctx context.Context) { clockSource := time.NewTicker(cm.trafficTimer.t.tickDuration) defer clockSource.Stop() diff --git a/handshake_manager.go b/handshake_manager.go index e04886b5..0d25305f 100644 --- a/handshake_manager.go +++ b/handshake_manager.go @@ -796,7 +796,6 @@ func (hm *HandshakeManager) beginHandshake(via ViaSender, packet []byte, h *head } hm.sendHandshakeResponse(via, response, hostinfo, false) - f.connectionManager.AddTrafficWatch(hostinfo) hostinfo.remotes.RefreshFromHandshake(vpnAddrs) // Don't wait for UpdateWorker @@ -963,7 +962,6 @@ func (hm *HandshakeManager) continueHandshake(via ViaSender, hh *HandshakeHostIn hostinfo.buildNetworks(f.myVpnNetworksTable, remoteCert.Certificate) hm.Complete(hostinfo, f) - f.connectionManager.AddTrafficWatch(hostinfo) if len(hh.packetStore) > 0 { if f.l.Enabled(context.Background(), slog.LevelDebug) { diff --git a/hostmap.go b/hostmap.go index bff586c7..957894b6 100644 --- a/hostmap.go +++ b/hostmap.go @@ -623,6 +623,11 @@ func (hm *HostMap) unlockedAddHostInfo(hostinfo *HostInfo, f *Interface) { hm.Indexes[hostinfo.localIndexId] = hostinfo hm.RemoteIndexes[hostinfo.remoteIndexId] = hostinfo + hostinfo.out.Store(true) + if f.connectionManager != nil { // f.connectionManager is only nil in some unit tests + f.connectionManager.trafficTimer.Add(hostinfo.localIndexId, f.connectionManager.checkInterval) + } + if hm.l.Enabled(context.Background(), slog.LevelDebug) { hm.l.Debug("Hostmap vpnIp added", "hostMap", m{"vpnAddrs": hostinfo.vpnAddrs, "mapTotalSize": len(hm.Hosts), From 36b38396afdef530c65e48b7baaacad2b702ca78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:04:22 -0400 Subject: [PATCH 08/15] Bump the golang-x-dependencies group with 4 updates (#1750) Bumps the golang-x-dependencies group with 4 updates: [golang.org/x/crypto](https://github.com/golang/crypto), [golang.org/x/sync](https://github.com/golang/sync), [golang.org/x/sys](https://github.com/golang/sys) and [golang.org/x/term](https://github.com/golang/term). Updates `golang.org/x/crypto` from 0.52.0 to 0.53.0 - [Commits](https://github.com/golang/crypto/compare/v0.52.0...v0.53.0) Updates `golang.org/x/sync` from 0.20.0 to 0.21.0 - [Commits](https://github.com/golang/sync/compare/v0.20.0...v0.21.0) Updates `golang.org/x/sys` from 0.45.0 to 0.46.0 - [Commits](https://github.com/golang/sys/compare/v0.45.0...v0.46.0) Updates `golang.org/x/term` from 0.43.0 to 0.44.0 - [Commits](https://github.com/golang/term/compare/v0.43.0...v0.44.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.53.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: golang-x-dependencies - dependency-name: golang.org/x/sync dependency-version: 0.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: golang-x-dependencies - dependency-name: golang.org/x/sys dependency-version: 0.46.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: golang-x-dependencies - dependency-name: golang.org/x/term dependency-version: 0.44.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: golang-x-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 27ba90ea..f0f567f5 100644 --- a/go.mod +++ b/go.mod @@ -24,12 +24,12 @@ require ( github.com/vishvananda/netlink v1.3.1 go.uber.org/goleak v1.3.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.52.0 + golang.org/x/crypto v0.53.0 golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 golang.org/x/net v0.55.0 - golang.org/x/sync v0.20.0 - golang.org/x/sys v0.45.0 - golang.org/x/term v0.43.0 + golang.org/x/sync v0.21.0 + golang.org/x/sys v0.46.0 + golang.org/x/term v0.44.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.6.1 diff --git a/go.sum b/go.sum index b6f886bd..d7667f23 100644 --- a/go.sum +++ b/go.sum @@ -162,8 +162,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= 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= @@ -191,8 +191,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 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= @@ -208,11 +208,11 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/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.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= 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= From ef95b25fa3ddddaa7fc995e9ad0d69ea0e18884e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:09:39 -0400 Subject: [PATCH 09/15] Bump github.com/gaissmai/bart from 0.27.1 to 0.28.0 (#1743) Bumps [github.com/gaissmai/bart](https://github.com/gaissmai/bart) from 0.27.1 to 0.28.0. - [Release notes](https://github.com/gaissmai/bart/releases) - [Commits](https://github.com/gaissmai/bart/compare/v0.27.1...v0.28.0) --- updated-dependencies: - dependency-name: github.com/gaissmai/bart dependency-version: 0.28.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f0f567f5..6e0ffbb1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( 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.27.1 + github.com/gaissmai/bart v0.28.0 github.com/gogo/protobuf v1.3.2 github.com/google/gopacket v1.1.19 github.com/kardianos/service v1.2.4 diff --git a/go.sum b/go.sum index d7667f23..91686e3e 100644 --- a/go.sum +++ b/go.sum @@ -26,8 +26,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.27.1 h1:FysPzqETMJa8q9rNkLW5peT1hq25nLOz8ksHbSVoiAk= -github.com/gaissmai/bart v0.27.1/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= +github.com/gaissmai/bart v0.28.0 h1:89yZLo8NmyqD0RYgJ3QO9HhqqGGw+oWhf90cZm69Lko= +github.com/gaissmai/bart v0.28.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= 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= From b7d83b050078ade629c6f6348726667191696ae0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:59:19 -0400 Subject: [PATCH 10/15] Bump golang.org/x/net in the golang-x-dependencies group (#1763) Bumps the golang-x-dependencies group with 1 update: [golang.org/x/net](https://github.com/golang/net). Updates `golang.org/x/net` from 0.55.0 to 0.56.0 - [Commits](https://github.com/golang/net/compare/v0.55.0...v0.56.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.56.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: golang-x-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6e0ffbb1..40260040 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.53.0 golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 - golang.org/x/net v0.55.0 + golang.org/x/net v0.56.0 golang.org/x/sync v0.21.0 golang.org/x/sys v0.46.0 golang.org/x/term v0.44.0 diff --git a/go.sum b/go.sum index 91686e3e..0555a9db 100644 --- a/go.sum +++ b/go.sum @@ -182,8 +182,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= 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= From ab539f8a3fb78b8b8f199570ee2f8f3f02346243 Mon Sep 17 00:00:00 2001 From: Wade Simmons Date: Tue, 16 Jun 2026 12:13:05 -0400 Subject: [PATCH 11/15] Add smoke test for ipv6 (#1764) * Add smoke test for ipv6 * fix ncat listen for ipv6 --- .github/workflows/smoke.yml | 8 +++++ .github/workflows/smoke/build.sh | 27 +++++++++++---- .github/workflows/smoke/smoke.sh | 59 +++++++++++++++++++------------- Makefile | 3 ++ 4 files changed, 67 insertions(+), 30 deletions(-) diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 66913326..b3c5f2d9 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -36,6 +36,14 @@ jobs: working-directory: ./.github/workflows/smoke run: ./smoke.sh + - name: setup docker image ipv6 + working-directory: ./.github/workflows/smoke + run: SMOKE_OVERLAY_IPV6=1 ./build.sh + + - name: run smoke ipv6 + working-directory: ./.github/workflows/smoke + run: SMOKE_OVERLAY_IPV6=1 ./smoke.sh + - name: setup relay docker image working-directory: ./.github/workflows/smoke run: ./build-relay.sh diff --git a/.github/workflows/smoke/build.sh b/.github/workflows/smoke/build.sh index b23516ee..fef76098 100755 --- a/.github/workflows/smoke/build.sh +++ b/.github/workflows/smoke/build.sh @@ -5,6 +5,19 @@ set -e -x rm -rf ./build mkdir ./build +if [ "$SMOKE_OVERLAY_IPV6" ] +then + LIGHTHOUSE_NIP="fd00:4242:0:0:0:ffff:c0a8:6401" + HOST2_NIP="fd00:4242:0:0:0:ffff:c0a8:6402" + HOST3_NIP="fd00:4242:0:0:0:ffff:c0a8:6403" + HOST4_NIP="fd00:4242:0:0:0:ffff:c0a8:6404" +else + LIGHTHOUSE_NIP="192.168.100.1" + HOST2_NIP="192.168.100.2" + HOST3_NIP="192.168.100.3" + HOST4_NIP="192.168.100.4" +fi + # Smoke containers run on a dedicated docker network whose subnet is allocated # at smoke time, not known at build time. Configs are written with TEST-NET-3 # placeholder IPs (RFC 5737) and smoke.sh / smoke-vagrant.sh / smoke-relay.sh @@ -31,24 +44,24 @@ LIGHTHOUSE_IP="203.0.113.2" ../genconfig.sh >lighthouse1.yml HOST="host2" \ - LIGHTHOUSES="192.168.100.1 $LIGHTHOUSE_IP:4242" \ + LIGHTHOUSES="$LIGHTHOUSE_NIP $LIGHTHOUSE_IP:4242" \ ../genconfig.sh >host2.yml HOST="host3" \ - LIGHTHOUSES="192.168.100.1 $LIGHTHOUSE_IP:4242" \ + LIGHTHOUSES="$LIGHTHOUSE_NIP $LIGHTHOUSE_IP:4242" \ INBOUND='[{"port": "any", "proto": "icmp", "group": "lighthouse"}]' \ ../genconfig.sh >host3.yml HOST="host4" \ - LIGHTHOUSES="192.168.100.1 $LIGHTHOUSE_IP:4242" \ + LIGHTHOUSES="$LIGHTHOUSE_NIP $LIGHTHOUSE_IP:4242" \ OUTBOUND='[{"port": "any", "proto": "icmp", "group": "lighthouse"}]' \ ../genconfig.sh >host4.yml ../../../../nebula-cert ca -curve "${CURVE:-25519}" -name "Smoke Test" - ../../../../nebula-cert sign -name "lighthouse1" -groups "lighthouse,lighthouse1" -ip "192.168.100.1/24" - ../../../../nebula-cert sign -name "host2" -groups "host,host2" -ip "192.168.100.2/24" - ../../../../nebula-cert sign -name "host3" -groups "host,host3" -ip "192.168.100.3/24" - ../../../../nebula-cert sign -name "host4" -groups "host,host4" -ip "192.168.100.4/24" + ../../../../nebula-cert sign -name "lighthouse1" -groups "lighthouse,lighthouse1" -ip "$LIGHTHOUSE_NIP/24" + ../../../../nebula-cert sign -name "host2" -groups "host,host2" -ip "$HOST2_NIP/24" + ../../../../nebula-cert sign -name "host3" -groups "host,host3" -ip "$HOST3_NIP/24" + ../../../../nebula-cert sign -name "host4" -groups "host,host4" -ip "$HOST4_NIP/24" ) docker build -t "nebula:${NAME:-smoke}" . diff --git a/.github/workflows/smoke/smoke.sh b/.github/workflows/smoke/smoke.sh index cad9dde7..f13ed380 100755 --- a/.github/workflows/smoke/smoke.sh +++ b/.github/workflows/smoke/smoke.sh @@ -47,6 +47,19 @@ HOST2_IP="$PREFIX.3" HOST3_IP="$PREFIX.4" HOST4_IP="$PREFIX.5" +if [ "$SMOKE_OVERLAY_IPV6" ] +then + LIGHTHOUSE_NIP="fd00:4242:0:0:0:ffff:c0a8:6401" + HOST2_NIP="fd00:4242:0:0:0:ffff:c0a8:6402" + HOST3_NIP="fd00:4242:0:0:0:ffff:c0a8:6403" + HOST4_NIP="fd00:4242:0:0:0:ffff:c0a8:6404" +else + LIGHTHOUSE_NIP="192.168.100.1" + HOST2_NIP="192.168.100.2" + HOST3_NIP="192.168.100.3" + HOST4_NIP="192.168.100.4" +fi + # Sed the placeholder TEST-NET-3 IPs in the host configs to the real ones. # build/lighthouse1.yml has no IPs to rewrite so it's skipped. for f in build/host2.yml build/host3.yml build/host4.yml; do @@ -80,28 +93,28 @@ docker exec host3 tcpdump -i eth0 -q -w - -U 2>logs/host3.outside.log >logs/host docker exec host4 tcpdump -i tun0 -q -w - -U 2>logs/host4.inside.log >logs/host4.inside.pcap & docker exec host4 tcpdump -i eth0 -q -w - -U 2>logs/host4.outside.log >logs/host4.outside.pcap & -docker exec host2 ncat -nklv 0.0.0.0 2000 & -docker exec host3 ncat -nklv 0.0.0.0 2000 & -docker exec host4 ncat -e '/usr/bin/echo helloagainfromhost4' -nkluv 0.0.0.0 4000 & -docker exec host2 ncat -e '/usr/bin/echo host2' -nkluv 0.0.0.0 3000 & -docker exec host3 ncat -e '/usr/bin/echo host3' -nkluv 0.0.0.0 3000 & +docker exec host2 ncat -nklv 2000 & +docker exec host3 ncat -nklv 2000 & +docker exec host4 ncat -e '/usr/bin/echo helloagainfromhost4' -nkluv 4000 & +docker exec host2 ncat -e '/usr/bin/echo host2' -nkluv 3000 & +docker exec host3 ncat -e '/usr/bin/echo host3' -nkluv 3000 & set +x echo echo " *** Testing ping from lighthouse1" echo set -x -docker exec lighthouse1 ping -c1 192.168.100.2 -docker exec lighthouse1 ping -c1 192.168.100.3 +docker exec lighthouse1 ping -c1 $HOST2_NIP +docker exec lighthouse1 ping -c1 $HOST3_NIP set +x echo echo " *** Testing ping from host2" echo set -x -docker exec host2 ping -c1 192.168.100.1 +docker exec host2 ping -c1 $LIGHTHOUSE_NIP # Should fail because not allowed by host3 inbound firewall -! docker exec host2 ping -c1 192.168.100.3 -w5 || exit 1 +! docker exec host2 ping -c1 $HOST3_NIP -w5 || exit 1 set +x echo @@ -109,34 +122,34 @@ echo " *** Testing ncat from host2" echo set -x # Should fail because not allowed by host3 inbound firewall -! docker exec host2 ncat -nzv -w5 192.168.100.3 2000 || exit 1 -! docker exec host2 ncat -nzuv -w5 192.168.100.3 3000 | grep -q host3 || exit 1 +! docker exec host2 ncat -nzv -w5 $HOST3_NIP 2000 || exit 1 +! docker exec host2 ncat -nzuv -w5 $HOST3_NIP 3000 | grep -q host3 || exit 1 set +x echo echo " *** Testing ping from host3" echo set -x -docker exec host3 ping -c1 192.168.100.1 -docker exec host3 ping -c1 192.168.100.2 +docker exec host3 ping -c1 $LIGHTHOUSE_NIP +docker exec host3 ping -c1 $HOST2_NIP set +x echo echo " *** Testing ncat from host3" echo set -x -docker exec host3 ncat -nzv -w5 192.168.100.2 2000 -docker exec host3 ncat -nzuv -w5 192.168.100.2 3000 | grep -q host2 +docker exec host3 ncat -nzv -w5 $HOST2_NIP 2000 +docker exec host3 ncat -nzuv -w5 $HOST2_NIP 3000 | grep -q host2 set +x echo echo " *** Testing ping from host4" echo set -x -docker exec host4 ping -c1 192.168.100.1 +docker exec host4 ping -c1 $LIGHTHOUSE_NIP # Should fail because not allowed by host4 outbound firewall -! docker exec host4 ping -c1 192.168.100.2 -w5 || exit 1 -! docker exec host4 ping -c1 192.168.100.3 -w5 || exit 1 +! docker exec host4 ping -c1 $HOST2_NIP -w5 || exit 1 +! docker exec host4 ping -c1 $HOST3_NIP -w5 || exit 1 set +x echo @@ -144,10 +157,10 @@ echo " *** Testing ncat from host4" echo set -x # Should fail because not allowed by host4 outbound firewall -! docker exec host4 ncat -nzv -w5 192.168.100.2 2000 || exit 1 -! docker exec host4 ncat -nzv -w5 192.168.100.3 2000 || exit 1 -! docker exec host4 ncat -nzuv -w5 192.168.100.2 3000 | grep -q host2 || exit 1 -! docker exec host4 ncat -nzuv -w5 192.168.100.3 3000 | grep -q host3 || exit 1 +! docker exec host4 ncat -nzv -w5 $HOST2_NIP 2000 || exit 1 +! docker exec host4 ncat -nzv -w5 $HOST3_NIP 2000 || exit 1 +! docker exec host4 ncat -nzuv -w5 $HOST2_NIP 3000 | grep -q host2 || exit 1 +! docker exec host4 ncat -nzuv -w5 $HOST3_NIP 3000 | grep -q host3 || exit 1 set +x echo @@ -159,7 +172,7 @@ set -x # cannot initiate UDP to host2. Once host2 initiates a flow to host4:4000, # conntrack must let host4's listener reply on that flow. If it doesn't, # the echo back from host4 never reaches host2. -docker exec host2 sh -c "(/usr/bin/echo host2; sleep 2) | ncat -nuv 192.168.100.4 4000" | grep -q helloagainfromhost4 +docker exec host2 sh -c "(/usr/bin/echo host2; sleep 2) | ncat -nuv $HOST4_NIP 4000" | grep -q helloagainfromhost4 docker exec host4 sh -c 'kill 1' docker exec host3 sh -c 'kill 1' diff --git a/Makefile b/Makefile index 892c8eb0..24c71459 100644 --- a/Makefile +++ b/Makefile @@ -268,6 +268,9 @@ smoke-relay-docker: bin-docker cd .github/workflows/smoke/ && ./build-relay.sh cd .github/workflows/smoke/ && ./smoke-relay.sh +smoke-docker-ipv6: export SMOKE_OVERLAY_IPV6 = 1 +smoke-docker-ipv6: smoke-docker + smoke-docker-race: BUILD_ARGS = -race smoke-docker-race: CGO_ENABLED = 1 smoke-docker-race: smoke-docker From 16b302c11dd81d236bbb74302a15e92843de0c6b Mon Sep 17 00:00:00 2001 From: Jack Doan Date: Tue, 16 Jun 2026 11:38:34 -0500 Subject: [PATCH 12/15] Relay log fix (#1765) * ensure CreateRelayRequest gets logged correctly * ensure CreateRelayResponse gets logged correctly --- connection_manager.go | 4 ++-- relay_manager.go | 41 ++++++++++++++++++++--------------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/connection_manager.go b/connection_manager.go index 65fdab2a..88f31321 100644 --- a/connection_manager.go +++ b/connection_manager.go @@ -298,8 +298,8 @@ func (cm *connectionManager) migrateRelayUsed(oldhostinfo, newhostinfo *HostInfo } else { cm.intf.SendMessageToHostInfo(header.Control, 0, newhostinfo, msg, make([]byte, 12), make([]byte, mtu)) cm.l.Info("send CreateRelayRequest", - "relayFrom", req.RelayFromAddr, - "relayTo", req.RelayToAddr, + "relayFrom", relayFrom, + "relayTo", relayTo, "initiatorRelayIndex", req.InitiatorRelayIndex, "responderRelayIndex", req.ResponderRelayIndex, "vpnAddrs", newhostinfo.vpnAddrs, diff --git a/relay_manager.go b/relay_manager.go index 1fd98963..985225f4 100644 --- a/relay_manager.go +++ b/relay_manager.go @@ -318,17 +318,16 @@ func (rm *relayManager) HandleControlMsg(h *HostInfo, d []byte, f *Interface) { } func (rm *relayManager) handleCreateRelayResponse(v cert.Version, h *HostInfo, f *Interface, m *NebulaControl) { + relayFrom := protoAddrToNetAddr(m.RelayFromAddr) + relayTo := protoAddrToNetAddr(m.RelayToAddr) rm.l.Info("handleCreateRelayResponse", - "relayFrom", protoAddrToNetAddr(m.RelayFromAddr), - "relayTo", protoAddrToNetAddr(m.RelayToAddr), + "relayFrom", relayFrom, + "relayTo", relayTo, "initiatorRelayIndex", m.InitiatorRelayIndex, "responderRelayIndex", m.ResponderRelayIndex, "vpnAddrs", h.vpnAddrs, ) - target := m.RelayToAddr - targetAddr := protoAddrToNetAddr(target) - relay, err := rm.EstablishRelay(h, m) if err != nil { rm.l.Error("Failed to update relay for relayTo", "error", err) @@ -344,7 +343,7 @@ func (rm *relayManager) handleCreateRelayResponse(v cert.Version, h *HostInfo, f rm.l.Error("Can't find a HostInfo for peer", "relayTo", relay.PeerAddr) return } - peerRelay, ok := peerHostInfo.relayState.QueryRelayForByIp(targetAddr) + peerRelay, ok := peerHostInfo.relayState.QueryRelayForByIp(relayTo) if !ok { rm.l.Error("peerRelay does not have Relay state for relayTo", "relayTo", peerHostInfo.vpnAddrs[0]) return @@ -354,19 +353,19 @@ func (rm *relayManager) handleCreateRelayResponse(v cert.Version, h *HostInfo, f // I initiated the request to this peer, but haven't heard back from the peer yet. I must wait for this peer // to respond to complete the connection. case PeerRequested, Disestablished, Established: - peerHostInfo.relayState.UpdateRelayForByIpState(targetAddr, Established) + peerHostInfo.relayState.UpdateRelayForByIpState(relayTo, Established) resp := NebulaControl{ Type: NebulaControl_CreateRelayResponse, ResponderRelayIndex: peerRelay.LocalIndex, InitiatorRelayIndex: peerRelay.RemoteIndex, } + peer := peerHostInfo.vpnAddrs[0] if v == cert.Version1 { - peer := peerHostInfo.vpnAddrs[0] if !peer.Is4() { rm.l.Error("Refusing to CreateRelayResponse for a v1 relay with an ipv6 address", "relayFrom", peer, - "relayTo", target, + "relayTo", relayTo, "initiatorRelayIndex", resp.InitiatorRelayIndex, "responderRelayIndex", resp.ResponderRelayIndex, "vpnAddrs", peerHostInfo.vpnAddrs, @@ -376,26 +375,26 @@ func (rm *relayManager) handleCreateRelayResponse(v cert.Version, h *HostInfo, f b := peer.As4() resp.OldRelayFromAddr = binary.BigEndian.Uint32(b[:]) - b = targetAddr.As4() + b = relayTo.As4() resp.OldRelayToAddr = binary.BigEndian.Uint32(b[:]) } else { - resp.RelayFromAddr = netAddrToProtoAddr(peerHostInfo.vpnAddrs[0]) - resp.RelayToAddr = target + resp.RelayFromAddr = netAddrToProtoAddr(peer) + resp.RelayToAddr = m.RelayToAddr } msg, err := resp.Marshal() if err != nil { rm.l.Error("relayManager Failed to marshal Control CreateRelayResponse message to create relay", "error", err) - } else { - f.SendMessageToHostInfo(header.Control, 0, peerHostInfo, msg, make([]byte, 12), make([]byte, mtu)) - rm.l.Info("send CreateRelayResponse", - "relayFrom", resp.RelayFromAddr, - "relayTo", resp.RelayToAddr, - "initiatorRelayIndex", resp.InitiatorRelayIndex, - "responderRelayIndex", resp.ResponderRelayIndex, - "vpnAddrs", peerHostInfo.vpnAddrs, - ) + return } + f.SendMessageToHostInfo(header.Control, 0, peerHostInfo, msg, make([]byte, 12), make([]byte, mtu)) + rm.l.Info("send CreateRelayResponse", + "relayFrom", peer, + "relayTo", relayTo, + "initiatorRelayIndex", resp.InitiatorRelayIndex, + "responderRelayIndex", resp.ResponderRelayIndex, + "vpnAddrs", peerHostInfo.vpnAddrs, + ) } } From e4cc80aaca1767a669cad9d822c49e007ef7be65 Mon Sep 17 00:00:00 2001 From: Wade Simmons Date: Tue, 16 Jun 2026 13:04:21 -0400 Subject: [PATCH 13/15] add IPv6 reject packet generation (#1766) * add IPv6 reject packet generation (ICMPv6 Destination Unreachable and TCP RST) * use ICMPv6 code 1 (administratively prohibited) and cap body at 1000 bytes * cleanup, use ICMP error code 13 for ipv4 * better docs * cleanup --- examples/config.yml | 2 +- iputil/packet.go | 265 +++++++++++++++++++++++++++++++++++++++--- iputil/packet_test.go | 170 ++++++++++++++++++++++++++- 3 files changed, 416 insertions(+), 21 deletions(-) diff --git a/examples/config.yml b/examples/config.yml index 6c7fb489..4f7fd1e7 100644 --- a/examples/config.yml +++ b/examples/config.yml @@ -397,7 +397,7 @@ firewall: # `drop` (default): silently drop the packet. # `reject`: send a reject reply. # - For TCP, this will be a RST "Connection Reset" packet. - # - For other protocols, this will be an ICMP port unreachable packet. + # - For other protocols, this will be an ICMP "Destination unreachable: Communication administratively prohibited" packet. outbound_action: drop inbound_action: drop diff --git a/iputil/packet.go b/iputil/packet.go index b18e5244..e5b9f70f 100644 --- a/iputil/packet.go +++ b/iputil/packet.go @@ -4,26 +4,50 @@ import ( "encoding/binary" "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" ) const ( - // Need 96 bytes for the largest reject packet: + // MaxIPv4RejectPacketSize is the largest IPv4 reject packet: // - 20 byte ipv4 header // - 8 byte icmpv4 header // - 68 byte body (60 byte max orig ipv4 header + 8 byte orig icmpv4 header) - MaxRejectPacketSize = ipv4.HeaderLen + 8 + 60 + 8 + maxIPv4RejectPacketSize = ipv4.HeaderLen + 8 + 60 + 8 + + // MaxRejectPacketSize is sized for the largest possible reject packet (IPv6): + // - 40 byte ipv6 header + // - 8 byte icmpv6 header + // - up to 1000 byte body (original packet, possibly truncated. We want to stay + // under the MTU with Nebula overhead included) + maxIPv6RejectPacketSize = ipv6.HeaderLen + 8 + 1000 + + MaxRejectPacketSize = maxIPv6RejectPacketSize ) func CreateRejectPacket(packet []byte, out []byte) []byte { - if len(packet) < ipv4.HeaderLen || int(packet[0]>>4) != ipv4.Version { + if len(packet) < 1 { return nil } - switch packet[9] { - case 6: // tcp - return ipv4CreateRejectTCPPacket(packet, out) + version := int(packet[0] >> 4) + switch version { + case ipv4.Version: + if len(packet) < ipv4.HeaderLen { + return nil + } + switch packet[9] { + case 6: // tcp + return ipv4CreateRejectTCPPacket(packet, out) + default: + return ipv4CreateRejectICMPPacket(packet, out) + } + case ipv6.Version: + if len(packet) < ipv6.HeaderLen { + return nil + } + return ipv6CreateRejectPacket(packet, out) default: - return ipv4CreateRejectICMPPacket(packet, out) + return nil } } @@ -36,10 +60,7 @@ func ipv4CreateRejectICMPPacket(packet []byte, out []byte) []byte { } // ICMP reply includes original header and first 8 bytes of the packet - packetLen := len(packet) - if packetLen > ihl+8 { - packetLen = ihl + 8 - } + packetLen := min(len(packet), ihl+8) outLen := ipv4.HeaderLen + 8 + packetLen if outLen > cap(out) { @@ -71,14 +92,14 @@ func ipv4CreateRejectICMPPacket(packet []byte, out []byte) []byte { // ICMP Destination Unreachable icmpOut := out[ipv4.HeaderLen:] - icmpOut[0] = 3 // type (Destination unreachable) - icmpOut[1] = 3 // code (Port unreachable error) - icmpOut[2] = 0 // checksum - icmpOut[3] = 0 // . - icmpOut[4] = 0 // unused - icmpOut[5] = 0 // . - icmpOut[6] = 0 // . - icmpOut[7] = 0 // . + icmpOut[0] = 3 // type (Destination unreachable) + icmpOut[1] = 13 // code (Communication administratively prohibited) + icmpOut[2] = 0 // checksum + icmpOut[3] = 0 // . + icmpOut[4] = 0 // unused + icmpOut[5] = 0 // . + icmpOut[6] = 0 // . + icmpOut[7] = 0 // . // Copy original IP header and first 8 bytes as body copy(icmpOut[8:], packet[:packetLen]) @@ -165,6 +186,197 @@ func ipv4CreateRejectTCPPacket(packet []byte, out []byte) []byte { return out } +func ipv6CreateRejectPacket(packet []byte, out []byte) []byte { + proto := ipv6FindUpperProtocol(packet) + switch proto { + case 6: // tcp + return ipv6CreateRejectTCPPacket(packet, out) + default: + return ipv6CreateRejectICMPPacket(packet, out) + } +} + +func ipv6FindUpperProtocol(packet []byte) uint8 { + nextHeader := packet[6] + offset := ipv6.HeaderLen + + for { + switch nextHeader { + case 0, 43, 60: // Hop-by-Hop, Routing, Destination + if len(packet) < offset+2 { + return nextHeader + } + nextHeader = packet[offset] + offset += int(packet[offset+1]+1) << 3 + + case 44: // Fragment + if len(packet) < offset+8 { + return nextHeader + } + nextHeader = packet[offset] + offset += 8 + + case 51: // AH + if len(packet) < offset+2 { + return nextHeader + } + nextHeader = packet[offset] + offset += int(packet[offset+1]+2) << 2 + + default: + return nextHeader + } + } +} + +func ipv6CreateRejectICMPPacket(packet []byte, out []byte) []byte { + // Include as much of the original packet as possible, up to 1000 bytes, + // so the response fits comfortably within any tunnel MTU. + packetLen := min(len(packet), 1000) + + outLen := ipv6.HeaderLen + 8 + packetLen + if outLen > cap(out) { + return nil + } + + out = out[:outLen] + + // IPv6 header + ipHdr := out[0:ipv6.HeaderLen] + ipHdr[0] = ipv6.Version << 4 // version, traffic class (high bits) + ipHdr[1] = 0 // traffic class (low bits), flow label (high bits) + ipHdr[2] = 0 // flow label + ipHdr[3] = 0 // flow label + + payloadLen := uint16(outLen - ipv6.HeaderLen) + binary.BigEndian.PutUint16(ipHdr[4:], payloadLen) // payload length + ipHdr[6] = 58 // next header (ICMPv6) + ipHdr[7] = 64 // hop limit + + // Swap dest / src IPs (each 16 bytes, src at 8, dst at 24) + copy(ipHdr[8:24], packet[24:40]) + copy(ipHdr[24:40], packet[8:24]) + + // ICMPv6 Destination Unreachable + icmpOut := out[ipv6.HeaderLen:] + icmpOut[0] = 1 // type (Destination Unreachable) + icmpOut[1] = 1 // code (Communication with destination administratively prohibited) + icmpOut[2] = 0 // checksum + icmpOut[3] = 0 // . + icmpOut[4] = 0 // unused + icmpOut[5] = 0 // . + icmpOut[6] = 0 // . + icmpOut[7] = 0 // . + + copy(icmpOut[8:], packet[:packetLen]) + + // ICMPv6 checksum uses a pseudo-header + csum := ipv6PseudoheaderChecksum(ipHdr[8:24], ipHdr[24:40], 58, uint32(payloadLen)) + binary.BigEndian.PutUint16(icmpOut[2:], tcpipChecksum(icmpOut, csum)) + + return out +} + +func ipv6CreateRejectTCPPacket(packet []byte, out []byte) []byte { + const tcpLen = 20 + + offset := ipv6FindUpperProtocolOffset(packet) + if len(packet) < offset+tcpLen { + return nil + } + + outLen := ipv6.HeaderLen + tcpLen + if outLen > cap(out) { + return nil + } + + out = out[:outLen] + + // IPv6 header + ipHdr := out[0:ipv6.HeaderLen] + ipHdr[0] = ipv6.Version << 4 // version, traffic class (high bits) + ipHdr[1] = 0 // traffic class (low bits), flow label (high bits) + ipHdr[2] = 0 // flow label + ipHdr[3] = 0 // flow label + + binary.BigEndian.PutUint16(ipHdr[4:], tcpLen) // payload length + ipHdr[6] = 6 // next header (TCP) + ipHdr[7] = 64 // hop limit + + // Swap dest / src IPs + copy(ipHdr[8:24], packet[24:40]) + copy(ipHdr[24:40], packet[8:24]) + + // TCP RST + tcpIn := packet[offset:] + var ackSeq, seq uint32 + outFlags := byte(0b00000100) // RST + + inAck := tcpIn[13]&0b00010000 != 0 + if inAck { + seq = binary.BigEndian.Uint32(tcpIn[8:]) + } else { + inSyn := uint32((tcpIn[13] & 0b00000010) >> 1) + inFin := uint32(tcpIn[13] & 0b00000001) + ackSeq = binary.BigEndian.Uint32(tcpIn[4:]) + inSyn + inFin + uint32(len(tcpIn)) - uint32(tcpIn[12]>>4)<<2 + outFlags |= 0b00010000 // ACK + } + + tcpOut := out[ipv6.HeaderLen:] + // Swap dest / src ports + copy(tcpOut[0:2], tcpIn[2:4]) + copy(tcpOut[2:4], tcpIn[0:2]) + binary.BigEndian.PutUint32(tcpOut[4:], seq) + binary.BigEndian.PutUint32(tcpOut[8:], ackSeq) + tcpOut[12] = (tcpLen >> 2) << 4 // data offset, reserved, NS + tcpOut[13] = outFlags // CWR, ECE, URG, ACK, PSH, RST, SYN, FIN + tcpOut[14] = 0 // window size + tcpOut[15] = 0 // . + tcpOut[16] = 0 // checksum + tcpOut[17] = 0 // . + tcpOut[18] = 0 // URG Pointer + tcpOut[19] = 0 // . + + // Calculate checksum with IPv6 pseudo-header + csum := ipv6PseudoheaderChecksum(ipHdr[8:24], ipHdr[24:40], 6, tcpLen) + binary.BigEndian.PutUint16(tcpOut[16:], tcpipChecksum(tcpOut, csum)) + + return out +} + +func ipv6FindUpperProtocolOffset(packet []byte) int { + nextHeader := packet[6] + offset := ipv6.HeaderLen + + for { + switch nextHeader { + case 0, 43, 60: // Hop-by-Hop, Routing, Destination + if len(packet) < offset+2 { + return offset + } + nextHeader = packet[offset] + offset += int(packet[offset+1]+1) << 3 + + case 44: // Fragment + if len(packet) < offset+8 { + return offset + } + nextHeader = packet[offset] + offset += 8 + + case 51: // AH + if len(packet) < offset+2 { + return offset + } + nextHeader = packet[offset] + offset += int(packet[offset+1]+2) << 2 + + default: + return offset + } + } +} + func CreateICMPEchoResponse(packet, out []byte) []byte { // Return early if this is not a simple ICMP Echo Request //TODO: make constants out of these @@ -236,3 +448,18 @@ func ipv4PseudoheaderChecksum(src, dst []byte, proto, length uint32) (csum uint3 csum += length >> 16 return csum } + +// based on: +// - https://github.com/google/gopacket/blob/v1.1.19/layers/tcpip.go#L37-L48 +func ipv6PseudoheaderChecksum(src, dst []byte, proto, length uint32) (csum uint32) { + for i := 0; i < 16; i += 2 { + csum += uint32(src[i]) << 8 + csum += uint32(src[i+1]) + csum += uint32(dst[i]) << 8 + csum += uint32(dst[i+1]) + } + csum += proto + csum += length & 0xffff + csum += length >> 16 + return csum +} diff --git a/iputil/packet_test.go b/iputil/packet_test.go index e1d0d95d..81644c5e 100644 --- a/iputil/packet_test.go +++ b/iputil/packet_test.go @@ -1,11 +1,13 @@ package iputil import ( + "encoding/binary" "net" "testing" "github.com/stretchr/testify/assert" "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" ) func Test_CreateRejectPacket(t *testing.T) { @@ -43,7 +45,7 @@ func Test_CreateRejectPacket(t *testing.T) { } b = append(b, []byte{0, 3, 0, 4, 0, 0, 0, 0}...) - expectedLen = MaxRejectPacketSize + expectedLen = maxIPv4RejectPacketSize out = make([]byte, MaxRejectPacketSize) rejectPacket = CreateRejectPacket(b, out) assert.NotNil(t, rejectPacket) @@ -71,3 +73,169 @@ func Test_CreateRejectPacket(t *testing.T) { assert.NotNil(t, rejectPacket) assert.Len(t, rejectPacket, expectedLen) } + +func makeIPv6Packet(src, dst net.IP, nextHeader uint8, payload []byte) []byte { + b := make([]byte, ipv6.HeaderLen+len(payload)) + b[0] = ipv6.Version << 4 + binary.BigEndian.PutUint16(b[4:], uint16(len(payload))) + b[6] = nextHeader + b[7] = 64 + copy(b[8:24], src.To16()) + copy(b[24:40], dst.To16()) + copy(b[ipv6.HeaderLen:], payload) + return b +} + +func Test_CreateRejectPacketIPv6_ICMP(t *testing.T) { + src := net.ParseIP("fd00::1") + dst := net.ParseIP("fd00::2") + + // Small UDP packet: entire original included in body + udpPayload := make([]byte, 20) + udpPayload[0] = 0x00 // src port high + udpPayload[1] = 0x50 // src port low (80) + udpPayload[2] = 0x01 // dst port high + udpPayload[3] = 0xBB // dst port low (443) + packet := makeIPv6Packet(src, dst, 17, udpPayload) + + out := make([]byte, MaxRejectPacketSize) + rejectPacket := CreateRejectPacket(packet, out) + assert.NotNil(t, rejectPacket) + + // Small packet fits entirely: 40 (ipv6 hdr) + 8 (icmpv6 hdr) + 60 (original) + expectedLen := ipv6.HeaderLen + 8 + len(packet) + assert.Len(t, rejectPacket, expectedLen) + + // Verify version + assert.Equal(t, byte(ipv6.Version<<4), rejectPacket[0]&0xf0) + // Verify next header is ICMPv6 (58) + assert.Equal(t, byte(58), rejectPacket[6]) + // Verify src/dst are swapped + assert.Equal(t, dst.To16(), net.IP(rejectPacket[8:24])) + assert.Equal(t, src.To16(), net.IP(rejectPacket[24:40])) + // Verify ICMPv6 type=1 (Dest Unreachable), code=1 (Administratively prohibited) + assert.Equal(t, byte(1), rejectPacket[ipv6.HeaderLen]) + assert.Equal(t, byte(1), rejectPacket[ipv6.HeaderLen+1]) + // Verify entire original packet is included in body + assert.Equal(t, packet, rejectPacket[ipv6.HeaderLen+8:]) + + // Large packet: body is truncated to 1000 bytes + largePkt := makeIPv6Packet(src, dst, 17, make([]byte, 1200)) + rejectPacket = CreateRejectPacket(largePkt, out) + assert.NotNil(t, rejectPacket) + assert.Len(t, rejectPacket, ipv6.HeaderLen+8+1000) + assert.Equal(t, largePkt[:1000], rejectPacket[ipv6.HeaderLen+8:]) +} + +func Test_CreateRejectPacketIPv6_TCP(t *testing.T) { + src := net.ParseIP("fd00::1") + dst := net.ParseIP("fd00::2") + + // TCP SYN packet (next header 6) + tcpPayload := make([]byte, 20) + tcpPayload[0] = 0x00 // src port high + tcpPayload[1] = 0x50 // src port low (80) + tcpPayload[2] = 0x01 // dst port high + tcpPayload[3] = 0xBB // dst port low (443) + binary.BigEndian.PutUint32(tcpPayload[4:], 1000) // seq + binary.BigEndian.PutUint32(tcpPayload[8:], 0) // ack seq + tcpPayload[12] = (20 >> 2) << 4 // data offset + tcpPayload[13] = 0b00000010 // SYN flag + + packet := makeIPv6Packet(src, dst, 6, tcpPayload) + + out := make([]byte, MaxRejectPacketSize) + rejectPacket := CreateRejectPacket(packet, out) + assert.NotNil(t, rejectPacket) + + // Expected: 40 (ipv6 hdr) + 20 (tcp RST) + expectedLen := ipv6.HeaderLen + 20 + assert.Len(t, rejectPacket, expectedLen) + + // Verify version + assert.Equal(t, byte(ipv6.Version<<4), rejectPacket[0]&0xf0) + // Verify next header is TCP (6) + assert.Equal(t, byte(6), rejectPacket[6]) + // Verify src/dst are swapped + assert.Equal(t, dst.To16(), net.IP(rejectPacket[8:24])) + assert.Equal(t, src.To16(), net.IP(rejectPacket[24:40])) + // Verify ports are swapped + tcpOut := rejectPacket[ipv6.HeaderLen:] + assert.Equal(t, uint16(443), binary.BigEndian.Uint16(tcpOut[0:2])) + assert.Equal(t, uint16(80), binary.BigEndian.Uint16(tcpOut[2:4])) + // RST+ACK flags (since input was SYN without ACK) + assert.Equal(t, byte(0b00010100), tcpOut[13]) + // ack_seq = original seq (1000) + SYN (1) + FIN (0) + segment data (0) + assert.Equal(t, uint32(1001), binary.BigEndian.Uint32(tcpOut[8:])) +} + +func Test_CreateRejectPacketIPv6_TCPWithACK(t *testing.T) { + src := net.ParseIP("fd00::1") + dst := net.ParseIP("fd00::2") + + // TCP packet with ACK set + tcpPayload := make([]byte, 20) + tcpPayload[0] = 0x00 + tcpPayload[1] = 0x50 + tcpPayload[2] = 0x01 + tcpPayload[3] = 0xBB + binary.BigEndian.PutUint32(tcpPayload[4:], 1000) // seq + binary.BigEndian.PutUint32(tcpPayload[8:], 2000) // ack seq + tcpPayload[12] = (20 >> 2) << 4 // data offset + tcpPayload[13] = 0b00010000 // ACK flag + + packet := makeIPv6Packet(src, dst, 6, tcpPayload) + + out := make([]byte, MaxRejectPacketSize) + rejectPacket := CreateRejectPacket(packet, out) + assert.NotNil(t, rejectPacket) + + tcpOut := rejectPacket[ipv6.HeaderLen:] + // RST only (no ACK) since input had ACK + assert.Equal(t, byte(0b00000100), tcpOut[13]) + // seq = original ack_seq + assert.Equal(t, uint32(2000), binary.BigEndian.Uint32(tcpOut[4:])) +} + +func Test_CreateRejectPacketIPv6_TooShort(t *testing.T) { + // Packet too short to be valid IPv6 + out := make([]byte, MaxRejectPacketSize) + assert.Nil(t, CreateRejectPacket([]byte{0x60}, out)) + assert.Nil(t, CreateRejectPacket(make([]byte, 39), out)) +} + +func Test_CreateRejectPacketIPv6_ExtensionHeaders(t *testing.T) { + src := net.ParseIP("fd00::1") + dst := net.ParseIP("fd00::2") + + // IPv6 + Hop-by-Hop extension header + TCP + hopByHop := []byte{ + 6, // next header: TCP + 0, // length (8 bytes total) + 0, 0, // padding + 0, 0, 0, 0, + } + tcpPayload := make([]byte, 20) + tcpPayload[0] = 0x00 + tcpPayload[1] = 0x50 + tcpPayload[2] = 0x01 + tcpPayload[3] = 0xBB + binary.BigEndian.PutUint32(tcpPayload[4:], 1000) + binary.BigEndian.PutUint32(tcpPayload[8:], 2000) + tcpPayload[12] = (20 >> 2) << 4 + tcpPayload[13] = 0b00010000 // ACK + + payload := append(hopByHop, tcpPayload...) + packet := makeIPv6Packet(src, dst, 0, payload) // next header 0 = Hop-by-Hop + + out := make([]byte, MaxRejectPacketSize) + rejectPacket := CreateRejectPacket(packet, out) + assert.NotNil(t, rejectPacket) + + // Should produce TCP RST + expectedLen := ipv6.HeaderLen + 20 + assert.Len(t, rejectPacket, expectedLen) + assert.Equal(t, byte(6), rejectPacket[6]) // next header is TCP + tcpOut := rejectPacket[ipv6.HeaderLen:] + assert.Equal(t, byte(0b00000100), tcpOut[13]) // RST only +} From fe1c5682f07893e8916f7e2f33c4a9ff74f6320c Mon Sep 17 00:00:00 2001 From: Wade Simmons Date: Tue, 16 Jun 2026 13:16:47 -0400 Subject: [PATCH 14/15] add IPv6 support to CreateICMPEchoResponse (#1767) The function previously only handled IPv4 ICMP Echo Request packets. This adds handling for IPv6 ICMPv6 Echo Request (type 128) by generating a proper Echo Reply (type 129) with correct pseudo-header checksum. --- iputil/packet.go | 52 +++++++++++++++++++++ iputil/packet_test.go | 103 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/iputil/packet.go b/iputil/packet.go index e5b9f70f..1cf895ce 100644 --- a/iputil/packet.go +++ b/iputil/packet.go @@ -378,6 +378,21 @@ func ipv6FindUpperProtocolOffset(packet []byte) int { } func CreateICMPEchoResponse(packet, out []byte) []byte { + if len(packet) < 1 { + return nil + } + + switch packet[0] >> 4 { + case 4: + return createICMPv4EchoResponse(packet, out) + case 6: + return createICMPv6EchoResponse(packet, out) + default: + return nil + } +} + +func createICMPv4EchoResponse(packet, out []byte) []byte { // Return early if this is not a simple ICMP Echo Request //TODO: make constants out of these if !(len(packet) >= 28 && len(packet) <= 9001 && packet[0] == 0x45 && packet[9] == 0x01 && packet[20] == 0x08) { @@ -411,6 +426,43 @@ func CreateICMPEchoResponse(packet, out []byte) []byte { return out } +func createICMPv6EchoResponse(packet, out []byte) []byte { + // IPv6 header (40 bytes) + ICMPv6 header (8 bytes minimum) + if len(packet) < ipv6.HeaderLen+8 || len(packet) > 9001 { + return nil + } + + // Next Header must be ICMPv6 (58) + if packet[6] != 58 { + return nil + } + + // ICMPv6 type must be Echo Request (128) + if packet[ipv6.HeaderLen] != 128 { + return nil + } + + out = out[:len(packet)] + copy(out, packet) + + // Swap src/dst addresses (bytes 8-23 and 24-39) + copy(out[8:24], packet[24:40]) + copy(out[24:40], packet[8:24]) + + // Change ICMPv6 type to Echo Reply (129) + icmp := out[ipv6.HeaderLen:] + icmp[0] = 129 + icmp[2] = 0 + icmp[3] = 0 + + // ICMPv6 checksum uses a pseudo-header with src, dst, length, and next header + payloadLen := uint32(len(icmp)) + csum := ipv6PseudoheaderChecksum(out[8:24], out[24:40], 58, payloadLen) + binary.BigEndian.PutUint16(icmp[2:], tcpipChecksum(icmp, csum)) + + return out +} + // calculates the TCP/IP checksum defined in rfc1071. The passed-in // csum is any initial checksum data that's already been computed. // diff --git a/iputil/packet_test.go b/iputil/packet_test.go index 81644c5e..346d4dd3 100644 --- a/iputil/packet_test.go +++ b/iputil/packet_test.go @@ -239,3 +239,106 @@ func Test_CreateRejectPacketIPv6_ExtensionHeaders(t *testing.T) { tcpOut := rejectPacket[ipv6.HeaderLen:] assert.Equal(t, byte(0b00000100), tcpOut[13]) // RST only } + +func TestCreateICMPEchoResponse_IPv4(t *testing.T) { + // Build a simple IPv4 ICMP Echo Request + packet := make([]byte, 28) + packet[0] = 0x45 // version 4, IHL 5 + binary.BigEndian.PutUint16(packet[2:], uint16(28)) // total length + packet[8] = 64 // TTL + packet[9] = 1 // protocol ICMP + copy(packet[12:16], net.IPv4(10, 0, 0, 1).To4()) // src + copy(packet[16:20], net.IPv4(10, 0, 0, 2).To4()) // dst + packet[20] = 8 // ICMP Echo Request + + out := make([]byte, len(packet)) + result := CreateICMPEchoResponse(packet, out) + assert.NotNil(t, result) + assert.Equal(t, byte(0x45), result[0]) + // src/dst swapped + assert.Equal(t, net.IPv4(10, 0, 0, 2).To4(), net.IP(result[12:16])) + assert.Equal(t, net.IPv4(10, 0, 0, 1).To4(), net.IP(result[16:20])) + // ICMP Echo Reply + assert.Equal(t, byte(0), result[20]) +} + +func TestCreateICMPEchoResponse_IPv6(t *testing.T) { + src := net.ParseIP("fd00::1").To16() + dst := net.ParseIP("fd00::2").To16() + + // Build an IPv6 ICMPv6 Echo Request packet + // IPv6 header (40 bytes) + ICMPv6 (8 bytes) + packet := make([]byte, 48) + packet[0] = 0x60 // version 6 + payloadLen := uint16(8) // ICMPv6 header only + binary.BigEndian.PutUint16(packet[4:], payloadLen) + packet[6] = 58 // Next Header: ICMPv6 + packet[7] = 64 // Hop Limit + copy(packet[8:24], src) // src address + copy(packet[24:40], dst) // dst address + + // ICMPv6 Echo Request + icmp := packet[40:] + icmp[0] = 128 // type: Echo Request + icmp[1] = 0 // code + binary.BigEndian.PutUint16(icmp[4:], 1) // identifier + binary.BigEndian.PutUint16(icmp[6:], 1) // sequence number + + // Compute correct checksum for the request + csum := ipv6PseudoheaderChecksum(src, dst, 58, uint32(payloadLen)) + binary.BigEndian.PutUint16(icmp[2:], tcpipChecksum(icmp, csum)) + + out := make([]byte, len(packet)) + result := CreateICMPEchoResponse(packet, out) + assert.NotNil(t, result) + + // Version should still be 6 + assert.Equal(t, byte(6), result[0]>>4) + // src/dst swapped + assert.Equal(t, dst, net.IP(result[8:24])) + assert.Equal(t, src, net.IP(result[24:40])) + // ICMPv6 Echo Reply type + assert.Equal(t, byte(129), result[40]) + + // Verify checksum is valid (tcpipChecksum returns 0 when data+checksum is correct) + respIcmp := result[40:] + verifyCsum := ipv6PseudoheaderChecksum(result[8:24], result[24:40], 58, uint32(payloadLen)) + assert.Equal(t, uint16(0), tcpipChecksum(respIcmp, verifyCsum)) +} + +func TestCreateICMPEchoResponse_IPv6_NotEchoRequest(t *testing.T) { + src := net.ParseIP("fd00::1").To16() + dst := net.ParseIP("fd00::2").To16() + + packet := make([]byte, 48) + packet[0] = 0x60 + binary.BigEndian.PutUint16(packet[4:], 8) + packet[6] = 58 + packet[7] = 64 + copy(packet[8:24], src) + copy(packet[24:40], dst) + + // ICMPv6 type 1 (Destination Unreachable) - not Echo Request + packet[40] = 1 + + out := make([]byte, len(packet)) + result := CreateICMPEchoResponse(packet, out) + assert.Nil(t, result) +} + +func TestCreateICMPEchoResponse_IPv6_NotICMPv6(t *testing.T) { + src := net.ParseIP("fd00::1").To16() + dst := net.ParseIP("fd00::2").To16() + + packet := make([]byte, 48) + packet[0] = 0x60 + binary.BigEndian.PutUint16(packet[4:], 8) + packet[6] = 6 // TCP, not ICMPv6 + packet[7] = 64 + copy(packet[8:24], src) + copy(packet[24:40], dst) + + out := make([]byte, len(packet)) + result := CreateICMPEchoResponse(packet, out) + assert.Nil(t, result) +} From 7d3166a19df7d1f308d9021d41ce18b4fe9db82b Mon Sep 17 00:00:00 2001 From: Wade Simmons Date: Tue, 16 Jun 2026 16:51:14 -0400 Subject: [PATCH 15/15] cleanup ipv6 iputil helpers / skip reject for ICMP error packets and fragments (#1768) * cleanup ipv6 iputil helpers With my refactoring in this PR I accidentally had some duplicate logic, this PR cleans it up: - https://github.com/slackhq/nebula/pull/1766 * skip ICMP reject for ICMP error packets and fragments Per RFC 1122, ICMP error messages must not be generated in response to other ICMP error messages to prevent infinite error loops. This applies to both IPv4 (types 3, 4, 5, 11, 12) and IPv6 (types 1-4). Do not generate reject packets for IPv4 or IPv6 fragments. For IPv4, check MF flag and fragment offset. For IPv6, add isFragment return to ipv6FindUpperProtocol so a single traversal handles both protocol lookup and fragment detection. * do send rejects for the initial fragment RFC says "non-initial fragment"s * fix fragment checks --- iputil/packet.go | 78 +++++++++++-------------- iputil/packet_test.go | 132 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 43 deletions(-) diff --git a/iputil/packet.go b/iputil/packet.go index 1cf895ce..99893822 100644 --- a/iputil/packet.go +++ b/iputil/packet.go @@ -35,6 +35,10 @@ func CreateRejectPacket(packet []byte, out []byte) []byte { if len(packet) < ipv4.HeaderLen { return nil } + // Do not send reject packets for non-first fragments + if packet[6]&0x1f != 0 || packet[7] != 0 { + return nil + } switch packet[9] { case 6: // tcp return ipv4CreateRejectTCPPacket(packet, out) @@ -59,6 +63,14 @@ func ipv4CreateRejectICMPPacket(packet []byte, out []byte) []byte { return nil } + // Do not generate ICMP errors in response to ICMP error packets + if packet[9] == 1 && len(packet) > ihl { + icmpType := packet[ihl] + if icmpType == 3 || icmpType == 4 || icmpType == 5 || icmpType == 11 || icmpType == 12 { + return nil + } + } + // ICMP reply includes original header and first 8 bytes of the packet packetLen := min(len(packet), ihl+8) @@ -187,49 +199,27 @@ func ipv4CreateRejectTCPPacket(packet []byte, out []byte) []byte { } func ipv6CreateRejectPacket(packet []byte, out []byte) []byte { - proto := ipv6FindUpperProtocol(packet) + proto, offset, isFragment := ipv6FindUpperProtocol(packet) + if isFragment { + return nil + } switch proto { case 6: // tcp - return ipv6CreateRejectTCPPacket(packet, out) + return ipv6CreateRejectTCPPacket(packet, out, offset) default: - return ipv6CreateRejectICMPPacket(packet, out) + return ipv6CreateRejectICMPPacket(packet, out, proto, offset) } } -func ipv6FindUpperProtocol(packet []byte) uint8 { - nextHeader := packet[6] - offset := ipv6.HeaderLen - - for { - switch nextHeader { - case 0, 43, 60: // Hop-by-Hop, Routing, Destination - if len(packet) < offset+2 { - return nextHeader - } - nextHeader = packet[offset] - offset += int(packet[offset+1]+1) << 3 - - case 44: // Fragment - if len(packet) < offset+8 { - return nextHeader - } - nextHeader = packet[offset] - offset += 8 - - case 51: // AH - if len(packet) < offset+2 { - return nextHeader - } - nextHeader = packet[offset] - offset += int(packet[offset+1]+2) << 2 - - default: - return nextHeader +func ipv6CreateRejectICMPPacket(packet []byte, out []byte, proto uint8, offset int) []byte { + // Do not generate ICMPv6 errors in response to ICMPv6 error packets + if proto == 58 && len(packet) > offset { + icmpType := packet[offset] + if icmpType >= 1 && icmpType <= 4 { + return nil } } -} -func ipv6CreateRejectICMPPacket(packet []byte, out []byte) []byte { // Include as much of the original packet as possible, up to 1000 bytes, // so the response fits comfortably within any tunnel MTU. packetLen := min(len(packet), 1000) @@ -277,10 +267,9 @@ func ipv6CreateRejectICMPPacket(packet []byte, out []byte) []byte { return out } -func ipv6CreateRejectTCPPacket(packet []byte, out []byte) []byte { +func ipv6CreateRejectTCPPacket(packet []byte, out []byte, offset int) []byte { const tcpLen = 20 - offset := ipv6FindUpperProtocolOffset(packet) if len(packet) < offset+tcpLen { return nil } @@ -344,35 +333,38 @@ func ipv6CreateRejectTCPPacket(packet []byte, out []byte) []byte { return out } -func ipv6FindUpperProtocolOffset(packet []byte) int { - nextHeader := packet[6] - offset := ipv6.HeaderLen +func ipv6FindUpperProtocol(packet []byte) (nextHeader uint8, offset int, isFragment bool) { + nextHeader = packet[6] + offset = ipv6.HeaderLen for { switch nextHeader { case 0, 43, 60: // Hop-by-Hop, Routing, Destination if len(packet) < offset+2 { - return offset + return nextHeader, offset, isFragment } nextHeader = packet[offset] offset += int(packet[offset+1]+1) << 3 case 44: // Fragment if len(packet) < offset+8 { - return offset + return nextHeader, offset, isFragment + } + if packet[offset+2] != 0 || packet[offset+3]&0xf8 != 0 { + isFragment = true } nextHeader = packet[offset] offset += 8 case 51: // AH if len(packet) < offset+2 { - return offset + return nextHeader, offset, isFragment } nextHeader = packet[offset] offset += int(packet[offset+1]+2) << 2 default: - return offset + return nextHeader, offset, isFragment } } } diff --git a/iputil/packet_test.go b/iputil/packet_test.go index 346d4dd3..6d567d51 100644 --- a/iputil/packet_test.go +++ b/iputil/packet_test.go @@ -74,6 +74,111 @@ func Test_CreateRejectPacket(t *testing.T) { assert.Len(t, rejectPacket, expectedLen) } +func Test_CreateRejectPacket_NoFragment(t *testing.T) { + out := make([]byte, MaxRejectPacketSize) + + // IPv4: non-zero fragment offset should not generate reject packet + h := ipv4.Header{ + Len: 20, + Src: net.IPv4(10, 0, 0, 1), + Dst: net.IPv4(10, 0, 0, 2), + Protocol: 17, // UDP + } + b, err := h.Marshal() + if err != nil { + t.Fatalf("h.Marshal: %v", err) + } + b = append(b, make([]byte, 8)...) + // Set fragment offset to non-zero (byte 6-7, offset in 8-byte units) + b[6] = 0x00 + b[7] = 0x01 + assert.Nil(t, CreateRejectPacket(b, out)) + + // MF flag with zero offset (first fragment) should still generate reject + b[6] = 0x20 // MF flag set + b[7] = 0x00 + assert.NotNil(t, CreateRejectPacket(b, out)) + + // Non-fragment should still generate reject packet + b[6] = 0x00 + b[7] = 0x00 + assert.NotNil(t, CreateRejectPacket(b, out)) + + // DF flag only (not a fragment) should still generate reject packet + b[6] = 0x40 + b[7] = 0x00 + assert.NotNil(t, CreateRejectPacket(b, out)) +} + +func Test_CreateRejectPacketIPv6_NoFragment(t *testing.T) { + src := net.ParseIP("fd00::1") + dst := net.ParseIP("fd00::2") + out := make([]byte, MaxRejectPacketSize) + + // IPv6 with Fragment header and non-zero offset should not generate reject + fragHeader := []byte{ + 17, // next header: UDP + 0, // reserved + 0, 9, // fragment offset=1 (shifted left 3), M=1 + 0, 0, 0, 1, // identification + } + udpPayload := make([]byte, 8) + payload := append(fragHeader, udpPayload...) + packet := makeIPv6Packet(src, dst, 44, payload) // next header 44 = Fragment + assert.Nil(t, CreateRejectPacket(packet, out)) + + // Fragment header with zero offset (first fragment) should still generate reject + fragHeader[2] = 0 + fragHeader[3] = 1 // offset=0, M=1 + payload = append(fragHeader, udpPayload...) + packet = makeIPv6Packet(src, dst, 44, payload) + assert.NotNil(t, CreateRejectPacket(packet, out)) +} + +func Test_CreateRejectPacket_NoICMPError(t *testing.T) { + out := make([]byte, MaxRejectPacketSize) + + // ICMP error types should not generate reject packets + icmpErrorTypes := []byte{3, 4, 5, 11, 12} + for _, icmpType := range icmpErrorTypes { + h := ipv4.Header{ + Len: 20, + Src: net.IPv4(10, 0, 0, 1), + Dst: net.IPv4(10, 0, 0, 2), + Protocol: 1, // ICMP + } + + b, err := h.Marshal() + if err != nil { + t.Fatalf("h.Marshal: %v", err) + } + b = append(b, icmpType, 0, 0, 0, 0, 0, 0, 0) + + rejectPacket := CreateRejectPacket(b, out) + assert.Nil(t, rejectPacket, "ICMP type %d should not generate a reject packet", icmpType) + } + + // ICMP non-error types should still generate reject packets + icmpNonErrorTypes := []byte{0, 8, 13, 14} + for _, icmpType := range icmpNonErrorTypes { + h := ipv4.Header{ + Len: 20, + Src: net.IPv4(10, 0, 0, 1), + Dst: net.IPv4(10, 0, 0, 2), + Protocol: 1, // ICMP + } + + b, err := h.Marshal() + if err != nil { + t.Fatalf("h.Marshal: %v", err) + } + b = append(b, icmpType, 0, 0, 0, 0, 0, 0, 0) + + rejectPacket := CreateRejectPacket(b, out) + assert.NotNil(t, rejectPacket, "ICMP type %d should generate a reject packet", icmpType) + } +} + func makeIPv6Packet(src, dst net.IP, nextHeader uint8, payload []byte) []byte { b := make([]byte, ipv6.HeaderLen+len(payload)) b[0] = ipv6.Version << 4 @@ -197,6 +302,33 @@ func Test_CreateRejectPacketIPv6_TCPWithACK(t *testing.T) { assert.Equal(t, uint32(2000), binary.BigEndian.Uint32(tcpOut[4:])) } +func Test_CreateRejectPacketIPv6_NoICMPError(t *testing.T) { + src := net.ParseIP("fd00::1") + dst := net.ParseIP("fd00::2") + out := make([]byte, MaxRejectPacketSize) + + // ICMPv6 error types (1-4) should not generate reject packets + for icmpType := byte(1); icmpType <= 4; icmpType++ { + payload := make([]byte, 8) + payload[0] = icmpType + packet := makeIPv6Packet(src, dst, 58, payload) + + rejectPacket := CreateRejectPacket(packet, out) + assert.Nil(t, rejectPacket, "ICMPv6 type %d should not generate a reject packet", icmpType) + } + + // ICMPv6 non-error types should still generate reject packets + nonErrorTypes := []byte{128, 129, 133, 134} + for _, icmpType := range nonErrorTypes { + payload := make([]byte, 8) + payload[0] = icmpType + packet := makeIPv6Packet(src, dst, 58, payload) + + rejectPacket := CreateRejectPacket(packet, out) + assert.NotNil(t, rejectPacket, "ICMPv6 type %d should generate a reject packet", icmpType) + } +} + func Test_CreateRejectPacketIPv6_TooShort(t *testing.T) { // Packet too short to be valid IPv6 out := make([]byte, MaxRejectPacketSize)