Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions device/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
peer.handshake.lastSentHandshake = time.Now()
peer.handshake.mutex.Unlock()

return peer.sendHandshakeInitiation()
}

func (peer *Peer) sendHandshakeInitiation() error {
peer.device.log.Verbosef("%v - Sending handshake initiation", peer)

msg, err := peer.device.CreateMessageInitiation(peer)
Expand All @@ -139,6 +143,25 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
return err
}

// needsHandshake reports whether the peer lacks a usable session, i.e. a new
// handshake is required before traffic can flow.
func (peer *Peer) needsHandshake() bool {
keypair := peer.keypairs.Current()
return keypair == nil ||
keypair.sendNonce.Load() >= RejectAfterMessages ||
time.Since(keypair.created) >= RejectAfterTime
}

func (peer *Peer) SendHandshakeInitiationOnEndpointChange() error {
peer.timers.handshakeAttempts.Store(0)

peer.handshake.mutex.Lock()
peer.handshake.lastSentHandshake = time.Now()
peer.handshake.mutex.Unlock()

return peer.sendHandshakeInitiation()
}

func (peer *Peer) SendHandshakeResponse() error {
peer.handshake.mutex.Lock()
peer.handshake.lastSentHandshake = time.Now()
Expand Down
16 changes: 12 additions & 4 deletions device/uapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,11 @@ func (device *Device) handleDeviceLine(key, value string) error {

// An ipcSetPeer is the current state of an IPC set operation on a peer.
type ipcSetPeer struct {
*Peer // Peer is the current peer being operated on
dummy bool // dummy reports whether this peer is a temporary, placeholder peer
created bool // new reports whether this is a newly created peer
pkaOn bool // pkaOn reports whether the peer had the persistent keepalive turn on
*Peer // Peer is the current peer being operated on
dummy bool // dummy reports whether this peer is a temporary, placeholder peer
created bool // new reports whether this is a newly created peer
pkaOn bool // pkaOn reports whether the peer had the persistent keepalive turn on
endpointChanged bool // the endpoint address changed
}

func (peer *ipcSetPeer) handlePostConfig() {
Expand All @@ -267,6 +268,9 @@ func (peer *ipcSetPeer) handlePostConfig() {
if peer.pkaOn {
peer.SendKeepalive()
}
if peer.endpointChanged && peer.needsHandshake() {
peer.SendHandshakeInitiationOnEndpointChange()
}
peer.SendStagedPackets()
}
}
Expand Down Expand Up @@ -345,6 +349,10 @@ func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error
}
peer.endpoint.Lock()
defer peer.endpoint.Unlock()
// Only an actual change of an existing endpoint triggers a handshake.
if peer.endpoint.val != nil && peer.endpoint.val.DstToString() != endpoint.DstToString() {
peer.endpointChanged = true
}
peer.endpoint.val = endpoint

case "persistent_keepalive_interval":
Expand Down
88 changes: 88 additions & 0 deletions device/uapi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
*/

package device

import (
"encoding/hex"
"math/rand"
"testing"

"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/tun/tuntest"
)

// TestEndpointChangeDetection checks that only replacing an already-set
// endpoint with a different address counts as a change: a first-time
// assignment or a no-op reassignment must not, so a live session is never
// rekeyed unnecessarily.
func TestEndpointChangeDetection(t *testing.T) {
dev, peer := newTestPeer(t)

if changed := setEndpoint(t, dev, peer, "127.0.0.1:51820"); changed {
t.Error("first-time endpoint assignment reported as a change")
}
if changed := setEndpoint(t, dev, peer, "127.0.0.1:51820"); changed {
t.Error("reassigning the same endpoint reported as a change")
}
if changed := setEndpoint(t, dev, peer, "127.0.0.1:51821"); !changed {
t.Error("changing the endpoint address was not reported as a change")
}
}

func TestNeedsHandshake(t *testing.T) {
_, peer := newTestPeer(t)

if !peer.needsHandshake() {
t.Error("peer without a keypair should need a handshake")
}
}

func newTestPeer(t *testing.T) (*Device, *Peer) {
t.Helper()

var key NoisePrivateKey
if _, err := rand.Read(key[:]); err != nil {
t.Fatalf("generate private key: %v", err)
}
var peerKey NoisePrivateKey
if _, err := rand.Read(peerKey[:]); err != nil {
t.Fatalf("generate peer key: %v", err)
}
peerPub := peerKey.publicKey()

tun := tuntest.NewChannelTUN()
dev := NewDevice(tun.TUN(), conn.NewDefaultBind(), NewLogger(LogLevelError, "test: "))
t.Cleanup(dev.Close)

cfg := uapiCfg(
"private_key", hex.EncodeToString(key[:]),
"listen_port", "0",
"replace_peers", "true",
"public_key", hex.EncodeToString(peerPub[:]),
"protocol_version", "1",
"replace_allowed_ips", "true",
"allowed_ip", "1.0.0.2/32",
)
if err := dev.IpcSet(cfg); err != nil {
t.Fatalf("configure device: %v", err)
}

peer := dev.LookupPeer(peerPub)
if peer == nil {
t.Fatal("configured peer not found")
}
return dev, peer
}

func setEndpoint(t *testing.T, dev *Device, peer *Peer, value string) bool {
t.Helper()

setPeer := &ipcSetPeer{Peer: peer}
if err := dev.handlePeerLine(setPeer, "endpoint", value); err != nil {
t.Fatalf("set endpoint %q: %v", value, err)
}
return setPeer.endpointChanged
}