This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+297 -149
View File
@@ -8,7 +8,9 @@ package device
import (
"encoding/binary"
"errors"
"fmt"
"net"
"net/netip"
"os"
"sync"
"time"
@@ -45,28 +47,26 @@ import (
*/
type QueueOutboundElement struct {
buffer *[MaxMessageSize]byte // slice holding the packet data
// packet is always a slice of "buffer". The starting offset in buffer
// is either:
// a) MessageEncapsulatingTransportSize+MessageTransportHeaderSize (plaintext)
// b) 0 (post-encryption)
packet []byte
nonce uint64 // nonce for encryption
keypair *Keypair // keypair for encryption
peer *Peer // related peer
buffer *packetBuf // shared packet buffer that contains packet
packet []byte // packet is always a slice of buffer.slab
// plaintextOffset describes the pre-encryption offset of packet within
// buffer.slab. The encryption process grows packet on both ends to make
// room for any potential encapsulating transport, WireGuard header, and
// tail padding.
plaintextOffset int
nonce uint64 // nonce for encryption
keypair *Keypair // keypair for encryption
peer *Peer // related peer
}
type QueueOutboundElementsContainer struct {
sync.Mutex
elems []*QueueOutboundElement
}
func (device *Device) NewOutboundElement() *QueueOutboundElement {
elem := device.GetOutboundElement()
elem.buffer = device.GetMessageBuffer()
elem.nonce = 0
// keypair and peer were cleared (if necessary) by clearPointers.
return elem
// filling is a one-shot barrier signaling encryption→send handoff.
// SendStagedPackets calls Add(1) before sending the container down
// the encryption and outbound queues; RoutineEncryption calls Done
// after encrypting; RoutineSequentialSender calls Wait before
// reading the encrypted packets.
filling sync.WaitGroup
elems []*QueueOutboundElement
}
// clearPointers clears elem fields that contain pointers.
@@ -80,18 +80,24 @@ func (elem *QueueOutboundElement) clearPointers() {
elem.peer = nil
}
/* Queues a keepalive if no packets are queued for peer
*/
// SendKeepalive queues a keepalive if no packets are queued for
// peer.
func (peer *Peer) SendKeepalive() {
if len(peer.queue.staged) == 0 && peer.isRunning.Load() {
elem := peer.device.NewOutboundElement()
if len(peer.queue.staged) == 0 {
elem := peer.device.GetOutboundElement()
elem.buffer = peer.device.getSmallPacketBuf()
elem.plaintextOffset = MessageEncapsulatingTransportSize + MessageTransportHeaderSize
elemsContainer := peer.device.GetOutboundElementsContainer()
elemsContainer.elems = append(elemsContainer.elems, elem)
select {
case peer.queue.staged <- elemsContainer:
peer.device.log.Verbosef("%v - Sending keepalive packet", peer)
default:
peer.device.PutMessageBuffer(elem.buffer)
if queued := peer.doIfRunning(func() {
select {
case peer.queue.staged <- elemsContainer:
peer.device.log.Verbosef("%v - Sending keepalive packet", peer)
default:
peer.device.PutOutboundElement(elem)
peer.device.PutOutboundElementsContainer(elemsContainer)
}
}); !queued {
peer.device.PutOutboundElement(elem)
peer.device.PutOutboundElementsContainer(elemsContainer)
}
@@ -99,6 +105,62 @@ func (peer *Peer) SendKeepalive() {
peer.SendStagedPackets()
}
// SendPriorityMessage invokes the [PeerPriorityMessageFunc] callback if one is
// set, and queues the returned message for encryption and transmission if the
// current keypair is valid.
func (peer *Peer) SendPriorityMessage() {
f := peer.device.priorityMsgFn.Load()
if f == nil {
return
}
keypair := peer.keypairs.Current()
if keypair == nil || keypair.sendNonce.Load() >= RejectAfterMessages || time.Since(keypair.created) >= RejectAfterTime {
// SendStagedPackets initializes a handshake when the keypair is invalid,
// but we explicitly avoid that here. A priority message is only intended
// to flow around symmetric session establishment, but it should never
// trigger a new session. Reaching this branch due to nonce exhaustion
// or keypair expiration is highly unlikely considering where
// SendPriorityMessage is called (at current keypair establishment).
return
}
// get plaintext message to send
msg := (*f)(peer.handshake.remoteStatic)
if len(msg) == 0 {
return
}
if len(msg) > MaxPriorityMessageContentSize {
peer.device.log.Verbosef("%v - Failed to queue priority message due to size", peer)
return
}
// get pooled elements
elem := peer.device.GetOutboundElement()
elemsContainer := peer.device.GetOutboundElementsContainer()
elemsContainer.elems = append(elemsContainer.elems, elem)
buf := peer.device.getSmallPacketBuf()
// initialize outbound element
const offset = MessageEncapsulatingTransportSize + MessageTransportHeaderSize
n := copy(buf.slab[offset:], msg)
elem.buffer = buf
elem.packet = buf.slab[offset : offset+n]
elem.plaintextOffset = offset
elem.peer = peer
elem.nonce = keypair.sendNonce.Add(1) - 1
if elem.nonce >= RejectAfterMessages {
keypair.sendNonce.Store(RejectAfterMessages)
peer.device.PutOutboundElement(elem)
peer.device.PutOutboundElementsContainer(elemsContainer)
return
}
elem.keypair = keypair
// add to parallel and sequential queue
peer.queueOutboundIfRunning(elemsContainer)
}
func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
if !isRetry {
peer.timers.handshakeAttempts.Store(0)
@@ -116,6 +178,10 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
peer.handshake.mutex.Unlock()
return nil
}
// handshakeOnUserSend is flipped false after lastSentHandshake checks,
// enabling eventual transmission at a future call of this method, while
// still honoring [RekeyTimeout].
peer.handshakeOnUserSend.Store(false)
peer.handshake.lastSentHandshake = time.Now()
peer.handshake.mutex.Unlock()
@@ -135,6 +201,11 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
peer.timersAnyAuthenticatedPacketTraversal()
peer.timersAnyAuthenticatedPacketSent()
if isRetry {
peer.device.config.metrics.MessageInitiationTXAttemptRetry.Add(1)
} else {
peer.device.config.metrics.MessageInitiationTXAttemptInitial.Add(1)
}
err = peer.SendBuffers([][]byte{buf})
if err != nil {
peer.device.log.Errorf("%v - Failed to send handshake initiation: %v", peer, err)
@@ -172,6 +243,7 @@ func (peer *Peer) SendHandshakeResponse() error {
peer.timersAnyAuthenticatedPacketTraversal()
peer.timersAnyAuthenticatedPacketSent()
peer.device.config.metrics.MessageResponseTXAttempt.Add(1)
// TODO: allocation could be avoided
err = peer.SendBuffers([][]byte{buf})
if err != nil {
@@ -181,10 +253,10 @@ func (peer *Peer) SendHandshakeResponse() error {
}
func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement) error {
device.log.Verbosef("Sending cookie response for denied handshake message for %v", initiatingElem.endpoint.DstToString())
device.log.Verbosef("Sending cookie response for denied handshake message for %v", initiatingElem.packetMeta.Endpoint.DstToString())
sender := binary.LittleEndian.Uint32(initiatingElem.packet[4:8])
reply, err := device.cookieChecker.CreateReply(initiatingElem.packet, sender, initiatingElem.endpoint.DstToBytes())
reply, err := device.cookieChecker.CreateReply(initiatingElem.packet, sender, initiatingElem.packetMeta.Endpoint.DstToBytes())
if err != nil {
device.log.Errorf("Failed to create cookie reply: %v", err)
return err
@@ -193,8 +265,9 @@ func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement)
buf := make([]byte, MessageEncapsulatingTransportSize+MessageCookieReplySize)
packet := buf[MessageEncapsulatingTransportSize:]
_ = reply.marshal(packet)
device.config.metrics.MessageCookieReplyTXAttempt.Add(1)
// TODO: allocation could be avoided
device.net.bind.Send([][]byte{buf}, initiatingElem.endpoint, MessageEncapsulatingTransportSize)
device.net.bind.Send([][]byte{buf}, initiatingElem.packetMeta.Endpoint, MessageEncapsulatingTransportSize)
return nil
}
@@ -204,73 +277,132 @@ func (peer *Peer) keepKeyFreshSending() {
if keypair == nil {
return
}
txHandshake := peer.handshakeOnUserSend.Load()
nonce := keypair.sendNonce.Load()
if nonce > RekeyAfterMessages || (keypair.isInitiator && time.Since(keypair.created) > RekeyAfterTime) {
if txHandshake || nonce > RekeyAfterMessages || (keypair.isInitiator && time.Since(keypair.created) > RekeyAfterTime) {
peer.SendHandshakeInitiation(false)
}
}
func (device *Device) RoutineReadFromTUN() {
// copyPacketBufIfFits copies the prefix of src.slab ending at the final packet
// into dst.slab if that prefix+[outboundPlaintextTailroom] fits. It reports
// whether the copy occurred. Packet descriptors must be in offset ascending order.
func copyPacketBufIfFits(dst, src *packetBuf, packets []tun.ReadPacket) bool {
if dst == nil || len(packets) < 1 {
return false
}
// validate tail descriptor as it relates to src
tailPkt := packets[len(packets)-1]
if tailPkt.Offset < 0 || tailPkt.Size < 1 {
return false
}
end := tailPkt.Offset + tailPkt.Size
if end < tailPkt.Offset || end > len(src.slab) ||
outboundPlaintextTailroom > len(src.slab)-end {
return false
}
// validate src fits in dst
if end+outboundPlaintextTailroom > len(dst.slab) {
return false
}
copy(dst.slab, src.slab[:end])
return true
}
func (device *Device) RoutineReadFromTUN(id int, queue tun.Reader) {
defer func() {
device.log.Verbosef("Routine: TUN reader - stopped")
device.log.Verbosef("Routine: TUN reader %d - stopped", id)
device.state.stopping.Done()
device.queue.encryption.wg.Done()
}()
device.log.Verbosef("Routine: TUN reader - started")
device.log.Verbosef("Routine: TUN reader %d - started", id)
var (
batchSize = device.BatchSize()
packets = make([]tun.ReadPacket, batchSize)
fullSizeBuf = device.getPacketBuf()
smallBuf *packetBuf
readErr error
elems = make([]*QueueOutboundElement, batchSize)
bufs = make([][]byte, batchSize)
elemsByPeer = make(map[*Peer]*QueueOutboundElementsContainer, batchSize)
count = 0
sizes = make([]int, batchSize)
offset = MessageEncapsulatingTransportSize + MessageTransportHeaderSize
)
if device.hasDistinctSmallPacketBufPool() {
smallBuf = device.getSmallPacketBuf()
}
defer func() {
fullSizeBuf.decRef()
if smallBuf != nil {
smallBuf.decRef()
}
}()
for i := range elems {
elems[i] = device.NewOutboundElement()
bufs[i] = elems[i].buffer[:]
elems[i] = device.GetOutboundElement()
}
defer func() {
for _, elem := range elems {
if elem != nil {
device.PutMessageBuffer(elem.buffer)
device.PutOutboundElement(elem)
}
}
}()
var (
buf *packetBuf // either fullSizeBuf or smallBuf
bufHandedOff bool
)
for {
if bufHandedOff {
if buf == smallBuf {
smallBuf.decRef()
smallBuf = device.getSmallPacketBuf()
} else {
fullSizeBuf.decRef()
fullSizeBuf = device.getPacketBuf()
}
}
buf = fullSizeBuf
bufHandedOff = false
// read packets
count, readErr = device.tun.device.Read(bufs, sizes, offset)
for i := 0; i < count; i++ {
if sizes[i] < 1 {
count, readErr = queue.Read(fullSizeBuf.slab, packets)
if copyPacketBufIfFits(smallBuf, fullSizeBuf, packets[:count]) {
buf = smallBuf
}
for i, meta := range packets[:count] {
if meta.Size < 1 || meta.Size > MaxContentSize {
continue
}
elem := elems[i]
elem.packet = bufs[i][offset : offset+sizes[i]]
// explicitly pin slice capacity, so downstream expansion of length
// never leak into an adjacent packet
packet := buf.slab[meta.Offset : meta.Offset+meta.Size : meta.Offset+meta.Size+outboundPlaintextTailroom]
// lookup peer
var peer *Peer
switch elem.packet[0] >> 4 {
switch packet[0] >> 4 {
case 4:
if len(elem.packet) < ipv4.HeaderLen {
if len(packet) < ipv4.HeaderLen {
continue
}
dst := elem.packet[IPv4offsetDst : IPv4offsetDst+net.IPv4len]
peer = device.allowedips.Lookup(dst)
src := netip.AddrFrom4([4]byte(packet[IPv4offsetSrc : IPv4offsetSrc+net.IPv4len]))
dst := netip.AddrFrom4([4]byte(packet[IPv4offsetDst : IPv4offsetDst+net.IPv4len]))
peer = device.allowedips.LookupFromPacket(src, dst, packet)
case 6:
if len(elem.packet) < ipv6.HeaderLen {
if len(packet) < ipv6.HeaderLen {
continue
}
dst := elem.packet[IPv6offsetDst : IPv6offsetDst+net.IPv6len]
peer = device.allowedips.Lookup(dst)
src := netip.AddrFrom16([16]byte(packet[IPv6offsetSrc : IPv6offsetSrc+net.IPv6len]))
dst := netip.AddrFrom16([16]byte(packet[IPv6offsetDst : IPv6offsetDst+net.IPv6len]))
peer = device.allowedips.LookupFromPacket(src, dst, packet)
default:
device.log.Verbosef("Received packet with unknown IP version")
@@ -279,27 +411,26 @@ func (device *Device) RoutineReadFromTUN() {
if peer == nil {
continue
}
elem := elems[i]
buf.incRef()
bufHandedOff = true
elem.buffer = buf
elem.packet = packet
elem.plaintextOffset = meta.Offset
elemsForPeer, ok := elemsByPeer[peer]
if !ok {
elemsForPeer = device.GetOutboundElementsContainer()
elemsByPeer[peer] = elemsForPeer
}
elemsForPeer.elems = append(elemsForPeer.elems, elem)
elems[i] = device.NewOutboundElement()
bufs[i] = elems[i].buffer[:]
elems[i] = device.GetOutboundElement()
}
for peer, elemsForPeer := range elemsByPeer {
if peer.isRunning.Load() {
peer.StagePackets(elemsForPeer)
peer.SendStagedPackets()
} else {
for _, elem := range elemsForPeer.elems {
device.PutMessageBuffer(elem.buffer)
device.PutOutboundElement(elem)
}
device.PutOutboundElementsContainer(elemsForPeer)
}
peer.StagePackets(elemsForPeer)
peer.SendStagedPackets()
delete(elemsByPeer, peer)
}
@@ -323,24 +454,31 @@ func (device *Device) RoutineReadFromTUN() {
}
func (peer *Peer) StagePackets(elems *QueueOutboundElementsContainer) {
for {
select {
case peer.queue.staged <- elems:
return
default:
}
select {
case tooOld := <-peer.queue.staged:
for _, elem := range tooOld.elems {
peer.device.PutMessageBuffer(elem.buffer)
peer.device.PutOutboundElement(elem)
if running := peer.doIfRunning(func() {
for {
select {
case peer.queue.staged <- elems:
return
default:
}
select {
case tooOld := <-peer.queue.staged:
for _, elem := range tooOld.elems {
peer.device.PutOutboundElement(elem)
}
peer.device.PutOutboundElementsContainer(tooOld)
default:
}
peer.device.PutOutboundElementsContainer(tooOld)
default:
}
}); !running {
for _, elem := range elems.elems {
peer.device.PutOutboundElement(elem)
}
peer.device.PutOutboundElementsContainer(elems)
}
}
// SendStagedPackets sends any staged packets to Peer.
func (peer *Peer) SendStagedPackets() {
top:
if len(peer.queue.staged) == 0 || !peer.device.isUp() {
@@ -375,7 +513,6 @@ top:
elem.keypair = keypair
}
elemsContainer.Lock()
elemsContainer.elems = elemsContainer.elems[:i]
if elemsContainerOOO != nil {
@@ -387,17 +524,7 @@ top:
goto top
}
// add to parallel and sequential queue
if peer.isRunning.Load() {
peer.queue.outbound.c <- elemsContainer
peer.device.queue.encryption.c <- elemsContainer
} else {
for _, elem := range elemsContainer.elems {
peer.device.PutMessageBuffer(elem.buffer)
peer.device.PutOutboundElement(elem)
}
peer.device.PutOutboundElementsContainer(elemsContainer)
}
peer.queueOutboundIfRunning(elemsContainer)
if elemsContainerOOO != nil {
goto top
@@ -413,7 +540,6 @@ func (peer *Peer) FlushStagedPackets() {
select {
case elemsContainer := <-peer.queue.staged:
for _, elem := range elemsContainer.elems {
peer.device.PutMessageBuffer(elem.buffer)
peer.device.PutOutboundElement(elem)
}
peer.device.PutOutboundElementsContainer(elemsContainer)
@@ -453,7 +579,7 @@ func (device *Device) RoutineEncryption(id int) {
for elemsContainer := range device.queue.encryption.c {
for _, elem := range elemsContainer.elems {
// populate header fields
header := elem.buffer[MessageEncapsulatingTransportSize : MessageEncapsulatingTransportSize+MessageTransportHeaderSize]
header := elem.buffer.slab[elem.plaintextOffset-MessageTransportHeaderSize : elem.plaintextOffset]
fieldType := header[0:4]
fieldReceiver := header[4:8]
@@ -465,6 +591,7 @@ func (device *Device) RoutineEncryption(id int) {
// pad content to multiple of 16
paddingSize := calculatePaddingSize(len(elem.packet), int(device.tun.mtu.Load()))
elem.packet = append(elem.packet, paddingZeros[:paddingSize]...)
// encrypt content and release to consumer
@@ -478,9 +605,11 @@ func (device *Device) RoutineEncryption(id int) {
)
// re-slice packet to include encapsulating transport space
elem.packet = elem.buffer[:MessageEncapsulatingTransportSize+len(elem.packet)]
start := elem.plaintextOffset - MessageTransportHeaderSize - MessageEncapsulatingTransportSize
end := elem.plaintextOffset - MessageTransportHeaderSize + len(elem.packet)
elem.packet = elem.buffer.slab[start:end]
}
elemsContainer.Unlock()
elemsContainer.filling.Done()
}
}
@@ -488,65 +617,84 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
device := peer.device
defer func() {
defer device.log.Verbosef("%v - Routine: sequential sender - stopped", peer)
peer.stopping.Done()
peer.runningState.queueReaders.Done()
}()
device.log.Verbosef("%v - Routine: sequential sender - started", peer)
bufs := make([][]byte, 0, maxBatchSize)
for elemsContainer := range peer.queue.outbound.c {
bufs = bufs[:0]
if elemsContainer == nil {
return
}
if !peer.isRunning.Load() {
// peer has been stopped; return re-usable elems to the shared pool.
// This is an optimization only. It is possible for the peer to be stopped
// immediately after this check, in which case, elem will get processed.
// The timers and SendBuffers code are resilient to a few stragglers.
// TODO: rework peer shutdown order to ensure
// that we never accidentally keep timers alive longer than necessary.
elemsContainer.Lock()
for _, elem := range elemsContainer.elems {
device.PutMessageBuffer(elem.buffer)
device.PutOutboundElement(elem)
}
device.PutOutboundElementsContainer(elemsContainer)
continue
}
dataSent := false
elemsContainer.Lock()
for _, elem := range elemsContainer.elems {
if len(elem.packet[MessageEncapsulatingTransportSize:]) != MessageKeepaliveSize {
dataSent = true
}
bufs = append(bufs, elem.packet)
}
peer.timersAnyAuthenticatedPacketTraversal()
peer.timersAnyAuthenticatedPacketSent()
err := peer.SendBuffers(bufs)
if dataSent {
peer.timersDataSent()
}
for _, elem := range elemsContainer.elems {
device.PutMessageBuffer(elem.buffer)
device.PutOutboundElement(elem)
}
device.PutOutboundElementsContainer(elemsContainer)
if err != nil {
var errGSO conn.ErrUDPGSODisabled
if errors.As(err, &errGSO) {
device.log.Verbosef(err.Error())
err = errGSO.RetryErr
}
}
if err != nil {
device.log.Errorf("%v - Failed to send data packets: %v", peer, err)
continue
}
peer.keepKeyFreshSending()
for elemsContainer := range peer.queue.outbound {
peer.processOutboundContainer(elemsContainer, bufs[:0])
}
}
// processOutboundContainer waits for the encryption routine to finish
// filling elemsContainer, then sends the batch (or drops it, if the peer
// has been stopped) and returns the container to the pool.
//
// scratch is a length-0 slice used to assemble the per-packet buffers
// passed to SendBuffers; its backing array is reused across calls.
func (peer *Peer) processOutboundContainer(elemsContainer *QueueOutboundElementsContainer, scratch [][]byte) {
// Invariants from RoutineSequentialSender; all should be unreachable.
if len(scratch) != 0 || cap(scratch) == 0 {
panic(fmt.Sprintf("processOutboundContainer: scratch must be empty with non-zero cap; got len=%d cap=%d",
len(scratch), cap(scratch)))
}
if cap(scratch) < len(elemsContainer.elems) {
panic(fmt.Sprintf("processOutboundContainer: scratch cap %d < elems %d",
cap(scratch), len(elemsContainer.elems)))
}
device := peer.device
defer device.PutOutboundElementsContainer(elemsContainer)
// Wait for RoutineEncryption to finish filling the container. After
// Wait returns we have happens-before with that goroutine and are the
// sole owner of the container until Put hands it back to the pool.
elemsContainer.filling.Wait()
if !peer.runningState.isRunning.Load() {
// peer has been stopped; return re-usable elems to the shared pool.
// This is an optimization only. It is possible for the peer to be stopped
// immediately after this check, in which case, elem will get processed.
// The timers and SendBuffers code are resilient to a few stragglers.
// TODO: rework peer shutdown order to ensure
// that we never accidentally keep timers alive longer than necessary.
for _, elem := range elemsContainer.elems {
device.PutOutboundElement(elem)
}
return
}
dataSent := false
for _, elem := range elemsContainer.elems {
if len(elem.packet[MessageEncapsulatingTransportSize:]) != MessageKeepaliveSize {
dataSent = true
}
scratch = append(scratch, elem.packet)
}
peer.timersAnyAuthenticatedPacketTraversal()
peer.timersAnyAuthenticatedPacketSent()
err := peer.SendBuffers(scratch)
if dataSent {
peer.timersDataSent()
}
for _, elem := range elemsContainer.elems {
device.PutOutboundElement(elem)
}
if err != nil {
var errGSO conn.ErrUDPGSODisabled
if errors.As(err, &errGSO) {
device.log.Verbosef(err.Error())
err = errGSO.RetryErr
}
}
if err != nil {
device.log.Errorf("%v - Failed to send data packets: %v", peer, err)
return
}
peer.keepKeyFreshSending()
}