lx: graft AmneziaWG 2.0 obfuscation onto sagernet/wireguard-go
3-way merge of amnezia-vpn/amneziawg-go (AWG2, I1-I5 CPS + S4 keepalive) into sagernet/wireguard-go@506b763185 (the base sing-box uses). Keeps sagernet's sing-box-compat additions (Send offset contract, InputPacket, conn reserved/ control) and neutralizes the encapsulating headroom (MessageEncapsulatingTransportSize=0) so obfuscation composes cleanly. Obfuscation lives in device/ (obf*.go, magic-header.go new files + send/receive/device/uapi grafts); conn/tun/ipc kept pure sagernet. LIVE-VALIDATED: handshake + keepalive + traffic against a real AmneziaWG 2.0 server.
This commit is contained in:
commit
27290b6de3
60 changed files with 1215 additions and 167 deletions
41
.github/workflows/build-if-tag.yml
vendored
Normal file
41
.github/workflows/build-if-tag.yml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
name: build-if-tag
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
|
||||||
|
env:
|
||||||
|
APP: amneziawg-go
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: build
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Setup metadata
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
id: metadata
|
||||||
|
with:
|
||||||
|
images: amneziavpn/${{ env.APP }}
|
||||||
|
tags: type=semver,pattern={{version}}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.metadata.outputs.tags }}
|
||||||
18
Dockerfile
Normal file
18
Dockerfile
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
FROM golang:1.24.4 as awg
|
||||||
|
COPY . /awg
|
||||||
|
WORKDIR /awg
|
||||||
|
RUN go mod download && \
|
||||||
|
go mod verify && \
|
||||||
|
go build -ldflags '-linkmode external -extldflags "-fno-PIC -static"' -v -o /usr/bin
|
||||||
|
|
||||||
|
FROM alpine:3.19
|
||||||
|
ARG AWGTOOLS_RELEASE="1.0.20250901"
|
||||||
|
|
||||||
|
RUN apk --no-cache add iproute2 iptables bash && \
|
||||||
|
cd /usr/bin/ && \
|
||||||
|
wget https://github.com/amnezia-vpn/amneziawg-tools/releases/download/v${AWGTOOLS_RELEASE}/alpine-3.19-amneziawg-tools.zip && \
|
||||||
|
unzip -j alpine-3.19-amneziawg-tools.zip && \
|
||||||
|
chmod +x /usr/bin/awg /usr/bin/awg-quick && \
|
||||||
|
ln -s /usr/bin/awg /usr/bin/wg && \
|
||||||
|
ln -s /usr/bin/awg-quick /usr/bin/wg-quick
|
||||||
|
COPY --from=awg /usr/bin/amneziawg-go /usr/bin/amneziawg-go
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,3 +1,5 @@
|
||||||
|
Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
the Software without restriction, including without limitation the rights to
|
the Software without restriction, including without limitation the rights to
|
||||||
|
|
|
||||||
12
Makefile
12
Makefile
|
|
@ -9,23 +9,23 @@ MAKEFLAGS += --no-print-directory
|
||||||
|
|
||||||
generate-version-and-build:
|
generate-version-and-build:
|
||||||
@export GIT_CEILING_DIRECTORIES="$(realpath $(CURDIR)/..)" && \
|
@export GIT_CEILING_DIRECTORIES="$(realpath $(CURDIR)/..)" && \
|
||||||
tag="$$(git describe --dirty 2>/dev/null)" && \
|
tag="$$(git describe --tags --dirty 2>/dev/null)" && \
|
||||||
ver="$$(printf 'package main\n\nconst Version = "%s"\n' "$$tag")" && \
|
ver="$$(printf 'package main\n\nconst Version = "%s"\n' "$$tag")" && \
|
||||||
[ "$$(cat version.go 2>/dev/null)" != "$$ver" ] && \
|
[ "$$(cat version.go 2>/dev/null)" != "$$ver" ] && \
|
||||||
echo "$$ver" > version.go && \
|
echo "$$ver" > version.go && \
|
||||||
git update-index --assume-unchanged version.go || true
|
git update-index --assume-unchanged version.go || true
|
||||||
@$(MAKE) wireguard-go
|
@$(MAKE) amneziawg-go
|
||||||
|
|
||||||
wireguard-go: $(wildcard *.go) $(wildcard */*.go)
|
amneziawg-go: $(wildcard *.go) $(wildcard */*.go)
|
||||||
go build -v -o "$@"
|
go build -v -o "$@"
|
||||||
|
|
||||||
install: wireguard-go
|
install: amneziawg-go
|
||||||
@install -v -d "$(DESTDIR)$(BINDIR)" && install -v -m 0755 "$<" "$(DESTDIR)$(BINDIR)/wireguard-go"
|
@install -v -d "$(DESTDIR)$(BINDIR)" && install -v -m 0755 "$<" "$(DESTDIR)$(BINDIR)/amneziawg-go"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f wireguard-go
|
rm -f amneziawg-go
|
||||||
|
|
||||||
.PHONY: all clean test install generate-version-and-build
|
.PHONY: all clean test install generate-version-and-build
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package conn implements WireGuard's network connections.
|
// Package conn implements WireGuard's network connections.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package winrio
|
package winrio
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
@ -223,19 +223,11 @@ func (table *AllowedIPs) EntriesForPeer(peer *Peer, cb func(prefix netip.Prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (table *AllowedIPs) RemoveByPeer(peer *Peer) {
|
func (node *trieEntry) remove() {
|
||||||
table.mutex.Lock()
|
|
||||||
defer table.mutex.Unlock()
|
|
||||||
|
|
||||||
var next *list.Element
|
|
||||||
for elem := peer.trieEntries.Front(); elem != nil; elem = next {
|
|
||||||
next = elem.Next()
|
|
||||||
node := elem.Value.(*trieEntry)
|
|
||||||
|
|
||||||
node.removeFromPeerEntries()
|
node.removeFromPeerEntries()
|
||||||
node.peer = nil
|
node.peer = nil
|
||||||
if node.child[0] != nil && node.child[1] != nil {
|
if node.child[0] != nil && node.child[1] != nil {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
bit := 0
|
bit := 0
|
||||||
if node.child[0] == nil {
|
if node.child[0] == nil {
|
||||||
|
|
@ -248,12 +240,12 @@ func (table *AllowedIPs) RemoveByPeer(peer *Peer) {
|
||||||
*node.parent.parentBit = child
|
*node.parent.parentBit = child
|
||||||
if node.child[0] != nil || node.child[1] != nil || node.parent.parentBitType > 1 {
|
if node.child[0] != nil || node.child[1] != nil || node.parent.parentBitType > 1 {
|
||||||
node.zeroizePointers()
|
node.zeroizePointers()
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
parent := (*trieEntry)(unsafe.Pointer(uintptr(unsafe.Pointer(node.parent.parentBit)) - unsafe.Offsetof(node.child) - unsafe.Sizeof(node.child[0])*uintptr(node.parent.parentBitType)))
|
parent := (*trieEntry)(unsafe.Pointer(uintptr(unsafe.Pointer(node.parent.parentBit)) - unsafe.Offsetof(node.child) - unsafe.Sizeof(node.child[0])*uintptr(node.parent.parentBitType)))
|
||||||
if parent.peer != nil {
|
if parent.peer != nil {
|
||||||
node.zeroizePointers()
|
node.zeroizePointers()
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
child = parent.child[node.parent.parentBitType^1]
|
child = parent.child[node.parent.parentBitType^1]
|
||||||
if child != nil {
|
if child != nil {
|
||||||
|
|
@ -263,6 +255,37 @@ func (table *AllowedIPs) RemoveByPeer(peer *Peer) {
|
||||||
node.zeroizePointers()
|
node.zeroizePointers()
|
||||||
parent.zeroizePointers()
|
parent.zeroizePointers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (table *AllowedIPs) Remove(prefix netip.Prefix, peer *Peer) {
|
||||||
|
table.mutex.Lock()
|
||||||
|
defer table.mutex.Unlock()
|
||||||
|
var node *trieEntry
|
||||||
|
var exact bool
|
||||||
|
|
||||||
|
if prefix.Addr().Is6() {
|
||||||
|
ip := prefix.Addr().As16()
|
||||||
|
node, exact = table.IPv6.nodePlacement(ip[:], uint8(prefix.Bits()))
|
||||||
|
} else if prefix.Addr().Is4() {
|
||||||
|
ip := prefix.Addr().As4()
|
||||||
|
node, exact = table.IPv4.nodePlacement(ip[:], uint8(prefix.Bits()))
|
||||||
|
} else {
|
||||||
|
panic(errors.New("removing unknown address type"))
|
||||||
|
}
|
||||||
|
if !exact || node == nil || peer != node.peer {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (table *AllowedIPs) RemoveByPeer(peer *Peer) {
|
||||||
|
table.mutex.Lock()
|
||||||
|
defer table.mutex.Unlock()
|
||||||
|
|
||||||
|
var next *list.Element
|
||||||
|
for elem := peer.trieEntries.Front(); elem != nil; elem = next {
|
||||||
|
next = elem.Next()
|
||||||
|
elem.Value.(*trieEntry).remove()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (table *AllowedIPs) Insert(prefix netip.Prefix, peer *Peer) {
|
func (table *AllowedIPs) Insert(prefix netip.Prefix, peer *Peer) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
@ -118,6 +118,7 @@ func (st *CookieChecker) CreateReply(
|
||||||
msg []byte,
|
msg []byte,
|
||||||
recv uint32,
|
recv uint32,
|
||||||
src []byte,
|
src []byte,
|
||||||
|
msgType uint32,
|
||||||
) (*MessageCookieReply, error) {
|
) (*MessageCookieReply, error) {
|
||||||
st.RLock()
|
st.RLock()
|
||||||
|
|
||||||
|
|
@ -153,7 +154,7 @@ func (st *CookieChecker) CreateReply(
|
||||||
smac1 := smac2 - blake2s.Size128
|
smac1 := smac2 - blake2s.Size128
|
||||||
|
|
||||||
reply := new(MessageCookieReply)
|
reply := new(MessageCookieReply)
|
||||||
reply.Type = MessageCookieReplyType
|
reply.Type = msgType
|
||||||
reply.Receiver = recv
|
reply.Receiver = recv
|
||||||
|
|
||||||
_, err := rand.Read(reply.Nonce[:])
|
_, err := rand.Read(reply.Nonce[:])
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
@ -93,6 +93,29 @@ type Device struct {
|
||||||
closed chan struct{}
|
closed chan struct{}
|
||||||
log *Logger
|
log *Logger
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
|
|
||||||
|
// lx: AmneziaWG obfuscation state (grafted from amneziawg-go).
|
||||||
|
junk struct {
|
||||||
|
min int
|
||||||
|
max int
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
headers struct {
|
||||||
|
init *magicHeader
|
||||||
|
cookie *magicHeader
|
||||||
|
response *magicHeader
|
||||||
|
transport *magicHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
paddings struct {
|
||||||
|
init int
|
||||||
|
response int
|
||||||
|
cookie int
|
||||||
|
transport int
|
||||||
|
}
|
||||||
|
|
||||||
|
ipackets [5]*obfChain
|
||||||
}
|
}
|
||||||
|
|
||||||
// deviceState represents the state of a Device.
|
// deviceState represents the state of a Device.
|
||||||
|
|
@ -166,7 +189,8 @@ func (device *Device) changeState(want deviceState) (err error) {
|
||||||
err = errDown
|
err = errDown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
device.log.Verbosef("Interface state was %s, requested %s, now %s", old, want, device.deviceState())
|
device.log.Verbosef(
|
||||||
|
"Interface state was %s, requested %s, now %s", old, want, device.deviceState())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,6 +327,11 @@ func NewDevice(ctx context.Context, tunDevice tun.Device, bind conn.Bind, logger
|
||||||
device.rate.limiter.Init()
|
device.rate.limiter.Init()
|
||||||
device.indexTable.Init()
|
device.indexTable.Init()
|
||||||
|
|
||||||
|
device.headers.init = &magicHeader{start: MessageInitiationType, end: MessageInitiationType}
|
||||||
|
device.headers.response = &magicHeader{start: MessageResponseType, end: MessageResponseType}
|
||||||
|
device.headers.cookie = &magicHeader{start: MessageCookieReplyType, end: MessageCookieReplyType}
|
||||||
|
device.headers.transport = &magicHeader{start: MessageTransportType, end: MessageTransportType}
|
||||||
|
|
||||||
device.PopulatePools()
|
device.PopulatePools()
|
||||||
|
|
||||||
// create queues
|
// create queues
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
63
device/magic-header.go
Normal file
63
device/magic-header.go
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type magicHeader struct {
|
||||||
|
start uint32
|
||||||
|
end uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMagicHeader(spec string) (*magicHeader, error) {
|
||||||
|
parts := strings.Split(spec, "-")
|
||||||
|
if len(parts) < 1 || len(parts) > 2 {
|
||||||
|
return nil, errors.New("bad format")
|
||||||
|
}
|
||||||
|
|
||||||
|
start, err := strconv.ParseUint(parts[0], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s: %w", parts[0], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var end uint64
|
||||||
|
if len(parts) > 1 {
|
||||||
|
end, err = strconv.ParseUint(parts[1], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s: %w", parts[1], err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
end = start
|
||||||
|
}
|
||||||
|
|
||||||
|
if end < start {
|
||||||
|
return nil, errors.New("wrong range specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &magicHeader{
|
||||||
|
start: uint32(start),
|
||||||
|
end: uint32(end),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *magicHeader) GenSpec() string {
|
||||||
|
if h.start == h.end {
|
||||||
|
return fmt.Sprintf("%d", h.start)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d-%d", h.start, h.end)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *magicHeader) Validate(val uint32) bool {
|
||||||
|
return h.start <= val && val <= h.end
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *magicHeader) Generate() uint32 {
|
||||||
|
high := int64(h.end - h.start + 1)
|
||||||
|
r, _ := rand.Int(rand.Reader, big.NewInt(high))
|
||||||
|
return h.start + uint32(r.Int64())
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
@ -54,10 +54,11 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MessageInitiationType = 1
|
MessageUnknownType uint32 = 0
|
||||||
MessageResponseType = 2
|
MessageInitiationType uint32 = 1
|
||||||
MessageCookieReplyType = 3
|
MessageResponseType uint32 = 2
|
||||||
MessageTransportType = 4
|
MessageCookieReplyType uint32 = 3
|
||||||
|
MessageTransportType uint32 = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -65,7 +66,7 @@ const (
|
||||||
MessageResponseSize = 92 // size of response message
|
MessageResponseSize = 92 // size of response message
|
||||||
MessageCookieReplySize = 64 // size of cookie reply message
|
MessageCookieReplySize = 64 // size of cookie reply message
|
||||||
MessageTransportHeaderSize = 16 // size of data preceding content in transport message
|
MessageTransportHeaderSize = 16 // size of data preceding content in transport message
|
||||||
MessageEncapsulatingTransportSize = 8 // size of optional, free (for use by conn.Bind.Send()) space preceding the transport header
|
MessageEncapsulatingTransportSize = 0 // lx: zeroed so AmneziaWG obfuscation composes without sagernet headroom (AWG path doesn't use the Bind.Send prepend)
|
||||||
MessageTransportSize = MessageTransportHeaderSize + poly1305.TagSize // size of empty transport
|
MessageTransportSize = MessageTransportHeaderSize + poly1305.TagSize // size of empty transport
|
||||||
MessageKeepaliveSize = MessageTransportSize // size of keepalive
|
MessageKeepaliveSize = MessageTransportSize // size of keepalive
|
||||||
MessageHandshakeSize = MessageInitiationSize // size of largest handshake related message
|
MessageHandshakeSize = MessageInitiationSize // size of largest handshake related message
|
||||||
|
|
@ -287,8 +288,10 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
|
||||||
|
|
||||||
handshake.mixHash(handshake.remoteStatic[:])
|
handshake.mixHash(handshake.remoteStatic[:])
|
||||||
|
|
||||||
|
msgType := device.headers.init.Generate()
|
||||||
|
|
||||||
msg := MessageInitiation{
|
msg := MessageInitiation{
|
||||||
Type: MessageInitiationType,
|
Type: msgType,
|
||||||
Ephemeral: handshake.localEphemeral.publicKey(),
|
Ephemeral: handshake.localEphemeral.publicKey(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,7 +469,7 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg MessageResponse
|
var msg MessageResponse
|
||||||
msg.Type = MessageResponseType
|
msg.Type = device.headers.response.Generate()
|
||||||
msg.Sender = handshake.localIndex
|
msg.Sender = handshake.localIndex
|
||||||
msg.Receiver = handshake.remoteIndex
|
msg.Receiver = handshake.remoteIndex
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
140
device/obf.go
Normal file
140
device/obf.go
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type obfBuilder func(val string) (obf, error)
|
||||||
|
|
||||||
|
var obfBuilders = map[string]obfBuilder{
|
||||||
|
"b": newBytesObf,
|
||||||
|
"t": newTimestampObf,
|
||||||
|
"r": newRandObf,
|
||||||
|
"rc": newRandCharObf,
|
||||||
|
"rd": newRandDigitsObf,
|
||||||
|
"d": newDataObf,
|
||||||
|
"ds": newDataStringObf,
|
||||||
|
"dz": newDataSizeObf,
|
||||||
|
}
|
||||||
|
|
||||||
|
type obf interface {
|
||||||
|
Obfuscate(dst, src []byte)
|
||||||
|
Deobfuscate(dst, src []byte) bool
|
||||||
|
ObfuscatedLen(srcLen int) int
|
||||||
|
DeobfuscatedLen(srcLen int) int
|
||||||
|
}
|
||||||
|
|
||||||
|
type obfChain struct {
|
||||||
|
Spec string
|
||||||
|
obfs []obf
|
||||||
|
}
|
||||||
|
|
||||||
|
func newObfChain(spec string) (*obfChain, error) {
|
||||||
|
var (
|
||||||
|
obfs []obf
|
||||||
|
errs []error
|
||||||
|
)
|
||||||
|
|
||||||
|
remaining := spec[:]
|
||||||
|
for {
|
||||||
|
start := strings.IndexByte(remaining, '<')
|
||||||
|
if start == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
end := strings.IndexByte(remaining[start:], '>')
|
||||||
|
if end == -1 {
|
||||||
|
return nil, errors.New("missing enclosing >")
|
||||||
|
}
|
||||||
|
end += start
|
||||||
|
|
||||||
|
tag := remaining[start+1 : end]
|
||||||
|
parts := strings.Fields(tag)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
errs = append(errs, errors.New("empty tag"))
|
||||||
|
remaining = remaining[end+1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := parts[0]
|
||||||
|
builder, ok := obfBuilders[key]
|
||||||
|
if !ok {
|
||||||
|
errs = append(errs, fmt.Errorf("unknown tag <%s>", key))
|
||||||
|
remaining = remaining[end+1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val := ""
|
||||||
|
if len(parts) > 1 {
|
||||||
|
val = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
o, err := builder(val)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("failed to build <%s>: %w", key, err))
|
||||||
|
remaining = remaining[end+1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
obfs = append(obfs, o)
|
||||||
|
remaining = remaining[end+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return nil, errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &obfChain{
|
||||||
|
Spec: spec,
|
||||||
|
obfs: obfs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfChain) Obfuscate(dst, src []byte) {
|
||||||
|
written := 0
|
||||||
|
for _, o := range c.obfs {
|
||||||
|
obfLen := o.ObfuscatedLen(len(src))
|
||||||
|
o.Obfuscate(dst[written:written+obfLen], src)
|
||||||
|
written += obfLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfChain) Deobfuscate(dst, src []byte) bool {
|
||||||
|
dynamicLen := len(src) - c.ObfuscatedLen(0)
|
||||||
|
|
||||||
|
written, read := 0, 0
|
||||||
|
|
||||||
|
for _, o := range c.obfs {
|
||||||
|
deobfLen := o.DeobfuscatedLen(dynamicLen)
|
||||||
|
obfLen := o.ObfuscatedLen(deobfLen)
|
||||||
|
|
||||||
|
if !o.Deobfuscate(dst[written:written+deobfLen], src[read:read+obfLen]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
written += deobfLen
|
||||||
|
read += obfLen
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfChain) ObfuscatedLen(n int) int {
|
||||||
|
total := 0
|
||||||
|
for _, o := range c.obfs {
|
||||||
|
total += o.ObfuscatedLen(n)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfChain) DeobfuscatedLen(n int) int {
|
||||||
|
dynamicLen := n - c.ObfuscatedLen(0)
|
||||||
|
|
||||||
|
total := 0
|
||||||
|
for _, o := range c.obfs {
|
||||||
|
total += o.DeobfuscatedLen(dynamicLen)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
47
device/obf_bytes.go
Normal file
47
device/obf_bytes.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newBytesObf(val string) (obf, error) {
|
||||||
|
val = strings.TrimPrefix(val, "0x")
|
||||||
|
|
||||||
|
if len(val) == 0 {
|
||||||
|
return nil, errors.New("empty argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(val)%2 != 0 {
|
||||||
|
return nil, errors.New("odd amount of symbols")
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := hex.DecodeString(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bytesObf{data: bytes}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type bytesObf struct {
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bytesObf) Obfuscate(dst, src []byte) {
|
||||||
|
copy(dst, o.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bytesObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
return bytes.Equal(o.data, src[:o.ObfuscatedLen(0)])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bytesObf) ObfuscatedLen(srcLen int) int {
|
||||||
|
return len(o.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bytesObf) DeobfuscatedLen(srcLen int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
25
device/obf_data.go
Normal file
25
device/obf_data.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
func newDataObf(val string) (obf, error) {
|
||||||
|
return &dataObf{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dataObf struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obf *dataObf) Obfuscate(dst, src []byte) {
|
||||||
|
copy(dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obf *dataObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
copy(dst, src)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataObf) ObfuscatedLen(n int) int {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataObf) DeobfuscatedLen(n int) int {
|
||||||
|
return n
|
||||||
|
}
|
||||||
38
device/obf_datasize.go
Normal file
38
device/obf_datasize.go
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func newDataSizeObf(val string) (obf, error) {
|
||||||
|
length, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dataSizeObf{
|
||||||
|
length: length,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dataSizeObf struct {
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataSizeObf) Obfuscate(dst, src []byte) {
|
||||||
|
srcLen := len(src)
|
||||||
|
for i := o.length - 1; i >= 0; i-- {
|
||||||
|
dst[i] = byte(srcLen & 0xFF)
|
||||||
|
srcLen >>= 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataSizeObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataSizeObf) ObfuscatedLen(srcLen int) int {
|
||||||
|
return o.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataSizeObf) DeobfuscatedLen(srcLen int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
29
device/obf_datastring.go
Normal file
29
device/obf_datastring.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDataStringObf(val string) (obf, error) {
|
||||||
|
return &dataStringObf{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dataStringObf struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataStringObf) Obfuscate(dst, src []byte) {
|
||||||
|
base64.RawStdEncoding.Encode(dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataStringObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
base64.RawStdEncoding.Decode(dst, src)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataStringObf) ObfuscatedLen(n int) int {
|
||||||
|
return base64.RawStdEncoding.EncodedLen(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataStringObf) DeobfuscatedLen(n int) int {
|
||||||
|
return base64.RawStdEncoding.DecodedLen(n)
|
||||||
|
}
|
||||||
39
device/obf_rand.go
Normal file
39
device/obf_rand.go
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRandObf(val string) (obf, error) {
|
||||||
|
length, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &randObf{
|
||||||
|
length: length,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type randObf struct {
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randObf) Obfuscate(dst, src []byte) {
|
||||||
|
rand.Read(dst[:o.length])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
// there is no way to validate randomness :)
|
||||||
|
// assume that it is always true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randObf) ObfuscatedLen(n int) int {
|
||||||
|
return o.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randObf) DeobfuscatedLen(n int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
48
device/obf_randchars.go
Normal file
48
device/obf_randchars.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"strconv"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const chars52 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
|
func newRandCharObf(val string) (obf, error) {
|
||||||
|
length, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &randCharObf{
|
||||||
|
length: length,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type randCharObf struct {
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randCharObf) Obfuscate(dst, src []byte) {
|
||||||
|
rand.Read(dst[:o.length])
|
||||||
|
for i := range dst[:o.length] {
|
||||||
|
dst[i] = chars52[dst[i]%52]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randCharObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
for _, b := range src[:o.length] {
|
||||||
|
if !unicode.IsLetter(rune(b)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randCharObf) ObfuscatedLen(n int) int {
|
||||||
|
return o.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randCharObf) DeobfuscatedLen(n int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
48
device/obf_randdigits.go
Normal file
48
device/obf_randdigits.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"strconv"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const digits10 = "0123456789"
|
||||||
|
|
||||||
|
func newRandDigitsObf(val string) (obf, error) {
|
||||||
|
length, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &randDigitObf{
|
||||||
|
length: length,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type randDigitObf struct {
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randDigitObf) Obfuscate(dst, src []byte) {
|
||||||
|
rand.Read(dst[:o.length])
|
||||||
|
for i := range dst[:o.length] {
|
||||||
|
dst[i] = digits10[dst[i]%10]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randDigitObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
for _, b := range src[:o.length] {
|
||||||
|
if !unicode.IsDigit(rune(b)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randDigitObf) ObfuscatedLen(n int) int {
|
||||||
|
return o.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randDigitObf) DeobfuscatedLen(n int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
31
device/obf_timestamp.go
Normal file
31
device/obf_timestamp.go
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTimestampObf(_ string) (obf, error) {
|
||||||
|
return ×tampObf{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type timestampObf struct{}
|
||||||
|
|
||||||
|
func (o *timestampObf) Obfuscate(dst, src []byte) {
|
||||||
|
t := uint32(time.Now().Unix())
|
||||||
|
binary.BigEndian.PutUint32(dst, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *timestampObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
// replay attack check?
|
||||||
|
// requires time to be always synchronized
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *timestampObf) ObfuscatedLen(n int) int {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *timestampObf) DeobfuscatedLen(n int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
@ -69,7 +69,10 @@ func (peer *Peer) keepKeyFreshReceiving() {
|
||||||
* Every time the bind is updated a new routine is started for
|
* Every time the bind is updated a new routine is started for
|
||||||
* IPv4 and IPv6 (separately)
|
* IPv4 and IPv6 (separately)
|
||||||
*/
|
*/
|
||||||
func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.ReceiveFunc) {
|
func (device *Device) RoutineReceiveIncoming(
|
||||||
|
maxBatchSize int,
|
||||||
|
recv conn.ReceiveFunc,
|
||||||
|
) {
|
||||||
recvName := recv.PrettyName()
|
recvName := recv.PrettyName()
|
||||||
defer func() {
|
defer func() {
|
||||||
device.log.Verbosef("Routine: receive incoming %s - stopped", recvName)
|
device.log.Verbosef("Routine: receive incoming %s - stopped", recvName)
|
||||||
|
|
@ -93,13 +96,13 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive
|
||||||
elemsByPeer = make(map[*Peer]*QueueInboundElementsContainer, maxBatchSize)
|
elemsByPeer = make(map[*Peer]*QueueInboundElementsContainer, maxBatchSize)
|
||||||
)
|
)
|
||||||
|
|
||||||
for i := range bufsArrs {
|
for i := range maxBatchSize {
|
||||||
bufsArrs[i] = device.GetMessageBuffer()
|
bufsArrs[i] = device.GetMessageBuffer()
|
||||||
bufs[i] = bufsArrs[i][:]
|
bufs[i] = bufsArrs[i][:]
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
for i := 0; i < maxBatchSize; i++ {
|
for i := range maxBatchSize {
|
||||||
if bufsArrs[i] != nil {
|
if bufsArrs[i] != nil {
|
||||||
device.PutMessageBuffer(bufsArrs[i])
|
device.PutMessageBuffer(bufsArrs[i])
|
||||||
}
|
}
|
||||||
|
|
@ -132,9 +135,14 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive
|
||||||
}
|
}
|
||||||
|
|
||||||
// check size of packet
|
// check size of packet
|
||||||
|
|
||||||
packet := bufsArrs[i][:size]
|
packet := bufsArrs[i][:size]
|
||||||
msgType := binary.LittleEndian.Uint32(packet[:4])
|
|
||||||
|
// get message padding and type based on information from S1-S4 and H1-H4
|
||||||
|
msgType, padding := device.DeterminePacketTypeAndPadding(packet, MessageUnknownType)
|
||||||
|
if padding > 0 {
|
||||||
|
copy(packet, packet[padding:])
|
||||||
|
packet = packet[:len(packet)-padding]
|
||||||
|
}
|
||||||
|
|
||||||
switch msgType {
|
switch msgType {
|
||||||
|
|
||||||
|
|
@ -276,7 +284,6 @@ func (device *Device) RoutineHandshake(id int) {
|
||||||
device.log.Verbosef("Routine: handshake worker %d - started", id)
|
device.log.Verbosef("Routine: handshake worker %d - started", id)
|
||||||
|
|
||||||
for elem := range device.queue.handshake.c {
|
for elem := range device.queue.handshake.c {
|
||||||
|
|
||||||
// handle cookie fields and ratelimiting
|
// handle cookie fields and ratelimiting
|
||||||
|
|
||||||
switch elem.msgType {
|
switch elem.msgType {
|
||||||
|
|
@ -303,9 +310,14 @@ func (device *Device) RoutineHandshake(id int) {
|
||||||
// consume reply
|
// consume reply
|
||||||
|
|
||||||
if peer := entry.peer; peer.isRunning.Load() {
|
if peer := entry.peer; peer.isRunning.Load() {
|
||||||
device.log.Verbosef("Receiving cookie response from %s", elem.endpoint.DstToString())
|
device.log.Verbosef(
|
||||||
|
"Receiving cookie response from %s",
|
||||||
|
elem.endpoint.DstToString(),
|
||||||
|
)
|
||||||
if !peer.cookieGenerator.ConsumeReply(&reply) {
|
if !peer.cookieGenerator.ConsumeReply(&reply) {
|
||||||
device.log.Verbosef("Could not decrypt invalid cookie response")
|
device.log.Verbosef(
|
||||||
|
"Could not decrypt invalid cookie response",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,9 +359,7 @@ func (device *Device) RoutineHandshake(id int) {
|
||||||
|
|
||||||
switch elem.msgType {
|
switch elem.msgType {
|
||||||
case MessageInitiationType:
|
case MessageInitiationType:
|
||||||
|
|
||||||
// unmarshal
|
// unmarshal
|
||||||
|
|
||||||
var msg MessageInitiation
|
var msg MessageInitiation
|
||||||
err := msg.unmarshal(elem.packet)
|
err := msg.unmarshal(elem.packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -357,7 +367,8 @@ func (device *Device) RoutineHandshake(id int) {
|
||||||
goto skip
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// consume initiation
|
// have to reassign msgType for ranged msgType to work
|
||||||
|
msg.Type = elem.msgType
|
||||||
|
|
||||||
peer := device.ConsumeMessageInitiation(&msg, elem.endpoint)
|
peer := device.ConsumeMessageInitiation(&msg, elem.endpoint)
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
|
|
@ -389,6 +400,9 @@ func (device *Device) RoutineHandshake(id int) {
|
||||||
goto skip
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// have to reassign msgType for ranged msgType to work
|
||||||
|
msg.Type = elem.msgType
|
||||||
|
|
||||||
// consume response
|
// consume response
|
||||||
|
|
||||||
peer := device.ConsumeMessageResponse(&msg)
|
peer := device.ConsumeMessageResponse(&msg)
|
||||||
|
|
@ -505,11 +519,28 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
device.log.Verbosef("Packet with invalid IP version from %v", peer)
|
device.log.Verbosef(
|
||||||
|
"Packet with invalid IP version from %v",
|
||||||
|
peer,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
bufs = append(bufs, elem.buffer[:MessageTransportOffsetContent+len(elem.packet)])
|
bufs = append(
|
||||||
|
bufs,
|
||||||
|
elem.buffer[:MessageTransportOffsetContent+len(elem.packet)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.rxBytes.Add(rxBytesLen)
|
||||||
|
if validTailPacket >= 0 {
|
||||||
|
peer.SetEndpointFromPacket(elemsContainer.elems[validTailPacket].endpoint)
|
||||||
|
peer.keepKeyFreshReceiving()
|
||||||
|
peer.timersAnyAuthenticatedPacketTraversal()
|
||||||
|
peer.timersAnyAuthenticatedPacketReceived()
|
||||||
|
}
|
||||||
|
if dataPacketReceived {
|
||||||
|
peer.timersDataReceived()
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.rxBytes.Add(rxBytesLen)
|
peer.rxBytes.Add(rxBytesLen)
|
||||||
|
|
@ -536,3 +567,57 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
|
||||||
device.PutInboundElementsContainer(elemsContainer)
|
device.PutInboundElementsContainer(elemsContainer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (device *Device) DeterminePacketTypeAndPadding(packet []byte, expectedType uint32) (uint32, int) {
|
||||||
|
size := len(packet)
|
||||||
|
|
||||||
|
if expectedType == MessageUnknownType || expectedType == MessageInitiationType {
|
||||||
|
padding := device.paddings.init
|
||||||
|
header := device.headers.init
|
||||||
|
|
||||||
|
if size == padding+MessageInitiationSize {
|
||||||
|
data := packet[padding:]
|
||||||
|
if header.Validate(binary.LittleEndian.Uint32(data)) {
|
||||||
|
return MessageInitiationType, padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedType == MessageUnknownType || expectedType == MessageResponseType {
|
||||||
|
padding := device.paddings.response
|
||||||
|
header := device.headers.response
|
||||||
|
|
||||||
|
if size == padding+MessageResponseSize {
|
||||||
|
data := packet[padding:]
|
||||||
|
if header.Validate(binary.LittleEndian.Uint32(data)) {
|
||||||
|
return MessageResponseType, padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedType == MessageUnknownType || expectedType == MessageCookieReplyType {
|
||||||
|
padding := device.paddings.cookie
|
||||||
|
header := device.headers.cookie
|
||||||
|
|
||||||
|
if size == padding+MessageCookieReplySize {
|
||||||
|
data := packet[padding:]
|
||||||
|
if header.Validate(binary.LittleEndian.Uint32(data)) {
|
||||||
|
return MessageCookieReplyType, padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedType == MessageUnknownType || expectedType == MessageTransportType {
|
||||||
|
padding := device.paddings.transport
|
||||||
|
header := device.headers.transport
|
||||||
|
|
||||||
|
if size >= padding+MessageTransportHeaderSize {
|
||||||
|
data := packet[padding:]
|
||||||
|
if header.Validate(binary.LittleEndian.Uint32(data)) {
|
||||||
|
return MessageTransportType, padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageUnknownType, 0
|
||||||
|
}
|
||||||
|
|
|
||||||
111
device/send.go
111
device/send.go
|
|
@ -1,13 +1,16 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -127,15 +130,48 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, MessageEncapsulatingTransportSize+MessageInitiationSize)
|
var sendBuffer [][]byte
|
||||||
packet := buf[MessageEncapsulatingTransportSize:]
|
|
||||||
_ = msg.marshal(packet)
|
for _, ipacket := range peer.device.ipackets {
|
||||||
|
if ipacket != nil {
|
||||||
|
buf := make([]byte, ipacket.ObfuscatedLen(0))
|
||||||
|
ipacket.Obfuscate(buf, nil)
|
||||||
|
sendBuffer = append(sendBuffer, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jc := peer.device.junk.count
|
||||||
|
jmin := peer.device.junk.min
|
||||||
|
jmax := peer.device.junk.max
|
||||||
|
|
||||||
|
for i := 0; i < jc; i++ {
|
||||||
|
nBig, _ := rand.Int(rand.Reader, big.NewInt(int64(jmax-jmin+1)))
|
||||||
|
n := int(nBig.Int64()) + jmin
|
||||||
|
|
||||||
|
buf := make([]byte, n)
|
||||||
|
rand.Read(buf)
|
||||||
|
sendBuffer = append(sendBuffer, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf [MessageInitiationSize]byte
|
||||||
|
writer := bytes.NewBuffer(buf[:0])
|
||||||
|
binary.Write(writer, binary.LittleEndian, msg)
|
||||||
|
packet := writer.Bytes()
|
||||||
peer.cookieGenerator.AddMacs(packet)
|
peer.cookieGenerator.AddMacs(packet)
|
||||||
|
|
||||||
peer.timersAnyAuthenticatedPacketTraversal()
|
peer.timersAnyAuthenticatedPacketTraversal()
|
||||||
peer.timersAnyAuthenticatedPacketSent()
|
peer.timersAnyAuthenticatedPacketSent()
|
||||||
|
|
||||||
err = peer.SendBuffers([][]byte{buf})
|
if padding := peer.device.paddings.init; padding > 0 {
|
||||||
|
buf := make([]byte, padding+len(packet))
|
||||||
|
rand.Read(buf[:padding])
|
||||||
|
copy(buf[padding:], packet)
|
||||||
|
packet = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
sendBuffer = append(sendBuffer, packet)
|
||||||
|
|
||||||
|
err = peer.SendBuffers(sendBuffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
peer.device.log.Errorf("%v - Failed to send handshake initiation: %v", peer, err)
|
peer.device.log.Errorf("%v - Failed to send handshake initiation: %v", peer, err)
|
||||||
}
|
}
|
||||||
|
|
@ -157,9 +193,11 @@ func (peer *Peer) SendHandshakeResponse() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, MessageEncapsulatingTransportSize+MessageResponseSize)
|
var buf [MessageResponseSize]byte
|
||||||
packet := buf[MessageEncapsulatingTransportSize:]
|
writer := bytes.NewBuffer(buf[:0])
|
||||||
_ = response.marshal(packet)
|
|
||||||
|
binary.Write(writer, binary.LittleEndian, response)
|
||||||
|
packet := writer.Bytes()
|
||||||
peer.cookieGenerator.AddMacs(packet)
|
peer.cookieGenerator.AddMacs(packet)
|
||||||
|
|
||||||
err = peer.BeginSymmetricSession()
|
err = peer.BeginSymmetricSession()
|
||||||
|
|
@ -172,8 +210,15 @@ func (peer *Peer) SendHandshakeResponse() error {
|
||||||
peer.timersAnyAuthenticatedPacketTraversal()
|
peer.timersAnyAuthenticatedPacketTraversal()
|
||||||
peer.timersAnyAuthenticatedPacketSent()
|
peer.timersAnyAuthenticatedPacketSent()
|
||||||
|
|
||||||
|
if padding := peer.device.paddings.response; padding > 0 {
|
||||||
|
buf := make([]byte, padding+len(packet))
|
||||||
|
rand.Read(buf[:padding])
|
||||||
|
copy(buf[padding:], packet)
|
||||||
|
packet = buf
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: allocation could be avoided
|
// TODO: allocation could be avoided
|
||||||
err = peer.SendBuffers([][]byte{buf})
|
err = peer.SendBuffers([][]byte{packet})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
peer.device.log.Errorf("%v - Failed to send handshake response: %v", peer, err)
|
peer.device.log.Errorf("%v - Failed to send handshake response: %v", peer, err)
|
||||||
}
|
}
|
||||||
|
|
@ -184,18 +229,33 @@ func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement)
|
||||||
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.endpoint.DstToString())
|
||||||
|
|
||||||
sender := binary.LittleEndian.Uint32(initiatingElem.packet[4:8])
|
sender := binary.LittleEndian.Uint32(initiatingElem.packet[4:8])
|
||||||
reply, err := device.cookieChecker.CreateReply(initiatingElem.packet, sender, initiatingElem.endpoint.DstToBytes())
|
msgType := device.headers.cookie.Generate()
|
||||||
|
|
||||||
|
reply, err := device.cookieChecker.CreateReply(
|
||||||
|
initiatingElem.packet,
|
||||||
|
sender,
|
||||||
|
initiatingElem.endpoint.DstToBytes(),
|
||||||
|
msgType,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
device.log.Errorf("Failed to create cookie reply: %v", err)
|
device.log.Errorf("Failed to create cookie reply: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, MessageEncapsulatingTransportSize+MessageCookieReplySize)
|
var buf [MessageCookieReplySize]byte
|
||||||
packet := buf[MessageEncapsulatingTransportSize:]
|
writer := bytes.NewBuffer(buf[:0])
|
||||||
_ = reply.marshal(packet)
|
binary.Write(writer, binary.LittleEndian, reply)
|
||||||
// TODO: allocation could be avoided
|
packet := writer.Bytes()
|
||||||
device.net.bind.Send([][]byte{buf}, initiatingElem.endpoint, MessageEncapsulatingTransportSize)
|
|
||||||
|
|
||||||
|
if padding := device.paddings.cookie; padding > 0 {
|
||||||
|
buf := make([]byte, padding+len(packet))
|
||||||
|
rand.Read(buf[:padding])
|
||||||
|
copy(buf[padding:], packet)
|
||||||
|
packet = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: allocation could be avoided
|
||||||
|
device.net.bind.Send([][]byte{packet}, initiatingElem.endpoint, 0)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -477,13 +537,15 @@ func (device *Device) RoutineEncryption(id int) {
|
||||||
for elemsContainer := range device.queue.encryption.c {
|
for elemsContainer := range device.queue.encryption.c {
|
||||||
for _, elem := range elemsContainer.elems {
|
for _, elem := range elemsContainer.elems {
|
||||||
// populate header fields
|
// populate header fields
|
||||||
header := elem.buffer[MessageEncapsulatingTransportSize : MessageEncapsulatingTransportSize+MessageTransportHeaderSize]
|
header := elem.buffer[:MessageTransportHeaderSize]
|
||||||
|
|
||||||
fieldType := header[0:4]
|
fieldType := header[0:4]
|
||||||
fieldReceiver := header[4:8]
|
fieldReceiver := header[4:8]
|
||||||
fieldNonce := header[8:16]
|
fieldNonce := header[8:16]
|
||||||
|
|
||||||
binary.LittleEndian.PutUint32(fieldType, MessageTransportType)
|
msgType := device.headers.transport.Generate()
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(fieldType, msgType)
|
||||||
binary.LittleEndian.PutUint32(fieldReceiver, elem.keypair.remoteIndex)
|
binary.LittleEndian.PutUint32(fieldReceiver, elem.keypair.remoteIndex)
|
||||||
binary.LittleEndian.PutUint64(fieldNonce, elem.nonce)
|
binary.LittleEndian.PutUint64(fieldNonce, elem.nonce)
|
||||||
|
|
||||||
|
|
@ -500,9 +562,6 @@ func (device *Device) RoutineEncryption(id int) {
|
||||||
elem.packet,
|
elem.packet,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// re-slice packet to include encapsulating transport space
|
|
||||||
elem.packet = elem.buffer[:MessageEncapsulatingTransportSize+len(elem.packet)]
|
|
||||||
}
|
}
|
||||||
elemsContainer.Unlock()
|
elemsContainer.Unlock()
|
||||||
}
|
}
|
||||||
|
|
@ -541,9 +600,18 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
|
||||||
dataSent := false
|
dataSent := false
|
||||||
elemsContainer.Lock()
|
elemsContainer.Lock()
|
||||||
for _, elem := range elemsContainer.elems {
|
for _, elem := range elemsContainer.elems {
|
||||||
if len(elem.packet[MessageEncapsulatingTransportSize:]) != MessageKeepaliveSize {
|
if len(elem.packet) != MessageKeepaliveSize {
|
||||||
dataSent = true
|
dataSent = true
|
||||||
}
|
}
|
||||||
|
if padding := device.paddings.transport; padding > 0 {
|
||||||
|
// elem.packet is stored at the start of elem.buffer
|
||||||
|
// with zero padding
|
||||||
|
for i := len(elem.packet) - 1; i >= 0; i-- {
|
||||||
|
elem.buffer[i+padding] = elem.buffer[i]
|
||||||
|
}
|
||||||
|
rand.Read(elem.buffer[:padding])
|
||||||
|
elem.packet = elem.buffer[:padding+len(elem.packet)]
|
||||||
|
}
|
||||||
bufs = append(bufs, elem.packet)
|
bufs = append(bufs, elem.packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -554,6 +622,7 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
|
||||||
if dataSent {
|
if dataSent {
|
||||||
peer.timersDataSent()
|
peer.timersDataSent()
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, elem := range elemsContainer.elems {
|
for _, elem := range elemsContainer.elems {
|
||||||
device.PutMessageBuffer(elem.buffer)
|
device.PutMessageBuffer(elem.buffer)
|
||||||
device.PutOutboundElement(elem)
|
device.PutOutboundElement(elem)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* This is based heavily on timers.c from the kernel implementation.
|
* This is based heavily on timers.c from the kernel implementation.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
306
device/uapi.go
306
device/uapi.go
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
@ -97,6 +97,56 @@ func (device *Device) IpcGetOperation(w io.Writer) error {
|
||||||
sendf("fwmark=%d", device.net.fwmark)
|
sendf("fwmark=%d", device.net.fwmark)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if device.junk.count != 0 {
|
||||||
|
sendf("jc=%d", device.junk.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.junk.min != 0 {
|
||||||
|
sendf("jmin=%d", device.junk.min)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.junk.max != 0 {
|
||||||
|
sendf("jmax=%d", device.junk.max)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.paddings.init != 0 {
|
||||||
|
sendf("s1=%d", device.paddings.init)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.paddings.response != 0 {
|
||||||
|
sendf("s2=%d", device.paddings.response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.paddings.cookie != 0 {
|
||||||
|
sendf("s3=%d", device.paddings.cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.paddings.transport != 0 {
|
||||||
|
sendf("s4=%d", device.paddings.transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.headers.init != nil {
|
||||||
|
sendf("h1=%s", device.headers.init.GenSpec())
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.headers.response != nil {
|
||||||
|
sendf("h2=%s", device.headers.response.GenSpec())
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.headers.cookie != nil {
|
||||||
|
sendf("h3=%s", device.headers.cookie.GenSpec())
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.headers.transport != nil {
|
||||||
|
sendf("h4=%s", device.headers.transport.GenSpec())
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, ipacket := range device.ipackets {
|
||||||
|
if ipacket != nil {
|
||||||
|
sendf("i%d=%s", i+1, ipacket.Spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, peer := range device.peers.keyMap {
|
for _, peer := range device.peers.keyMap {
|
||||||
// Serialize peer state.
|
// Serialize peer state.
|
||||||
peer.handshake.mutex.RLock()
|
peer.handshake.mutex.RLock()
|
||||||
|
|
@ -147,6 +197,7 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
ipcDev := new(ipcSetDevice)
|
||||||
peer := new(ipcSetPeer)
|
peer := new(ipcSetPeer)
|
||||||
deviceConfig := true
|
deviceConfig := true
|
||||||
|
|
||||||
|
|
@ -155,12 +206,20 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if line == "" {
|
if line == "" {
|
||||||
// Blank line means terminate operation.
|
// Blank line means terminate operation.
|
||||||
|
err := ipcDev.mergeWithDevice(device)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to merge with device: %w", err)
|
||||||
|
}
|
||||||
peer.handlePostConfig()
|
peer.handlePostConfig()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
key, value, ok := strings.Cut(line, "=")
|
key, value, ok := strings.Cut(line, "=")
|
||||||
if !ok {
|
if !ok {
|
||||||
return ipcErrorf(ipc.IpcErrorProtocol, "failed to parse line %q", line)
|
return ipcErrorf(
|
||||||
|
ipc.IpcErrorProtocol,
|
||||||
|
"failed to parse line %q",
|
||||||
|
line,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if key == "public_key" {
|
if key == "public_key" {
|
||||||
|
|
@ -186,6 +245,10 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
err = ipcDev.mergeWithDevice(device)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to merge with device: %w", err)
|
||||||
|
}
|
||||||
peer.handlePostConfig()
|
peer.handlePostConfig()
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
|
|
@ -235,11 +298,155 @@ func (device *Device) handleDeviceLine(key, value string) error {
|
||||||
|
|
||||||
case "replace_peers":
|
case "replace_peers":
|
||||||
if value != "true" {
|
if value != "true" {
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set replace_peers, invalid value: %v", value)
|
return ipcErrorf(
|
||||||
|
ipc.IpcErrorInvalid,
|
||||||
|
"failed to set replace_peers, invalid value: %v",
|
||||||
|
value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
device.log.Verbosef("UAPI: Removing all peers")
|
device.log.Verbosef("UAPI: Removing all peers")
|
||||||
device.RemoveAllPeers()
|
device.RemoveAllPeers()
|
||||||
|
|
||||||
|
case "jc":
|
||||||
|
jc, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jc: %w", err)
|
||||||
|
}
|
||||||
|
if jc <= 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "jc must be a positive value")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating junk count")
|
||||||
|
device.junk.count = jc
|
||||||
|
|
||||||
|
case "jmin":
|
||||||
|
jmin, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jmin: %w", err)
|
||||||
|
}
|
||||||
|
if jmin <= 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "jmin must be a positive value")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating junk min")
|
||||||
|
device.junk.min = jmin
|
||||||
|
|
||||||
|
case "jmax":
|
||||||
|
jmax, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jmax: %w", err)
|
||||||
|
}
|
||||||
|
if jmax <= 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "jmax must be a positive value")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating junk max")
|
||||||
|
device.junk.max = jmax
|
||||||
|
|
||||||
|
case "s1":
|
||||||
|
padding, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s1: %w", err)
|
||||||
|
}
|
||||||
|
if padding < 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "s1 must be non-negative")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating s1 padding")
|
||||||
|
device.paddings.init = padding
|
||||||
|
|
||||||
|
case "s2":
|
||||||
|
padding, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s2: %w", err)
|
||||||
|
}
|
||||||
|
if padding < 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "s2 must be non-negative")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating s2 padding")
|
||||||
|
device.paddings.response = padding
|
||||||
|
|
||||||
|
case "s3":
|
||||||
|
padding, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s3: %w", err)
|
||||||
|
}
|
||||||
|
if padding < 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "s3 must be non-negative")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating s3 padding")
|
||||||
|
device.paddings.cookie = padding
|
||||||
|
|
||||||
|
case "s4":
|
||||||
|
padding, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s4: %w", err)
|
||||||
|
}
|
||||||
|
if padding < 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "s4 must be non-negative")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating s4 padding")
|
||||||
|
device.paddings.transport = padding
|
||||||
|
|
||||||
|
case "h1":
|
||||||
|
header, err := newMagicHeader(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H1: %w", err)
|
||||||
|
}
|
||||||
|
device.headers.init = header
|
||||||
|
|
||||||
|
case "h2":
|
||||||
|
header, err := newMagicHeader(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H2: %w", err)
|
||||||
|
}
|
||||||
|
device.headers.response = header
|
||||||
|
|
||||||
|
case "h3":
|
||||||
|
header, err := newMagicHeader(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H3: %w", err)
|
||||||
|
}
|
||||||
|
device.headers.cookie = header
|
||||||
|
|
||||||
|
case "h4":
|
||||||
|
header, err := newMagicHeader(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H4: %w", err)
|
||||||
|
}
|
||||||
|
device.headers.transport = header
|
||||||
|
|
||||||
|
case "i1":
|
||||||
|
chain, err := newObfChain(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I1: %w", err)
|
||||||
|
}
|
||||||
|
device.ipackets[0] = chain
|
||||||
|
|
||||||
|
case "i2":
|
||||||
|
chain, err := newObfChain(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I2: %w", err)
|
||||||
|
}
|
||||||
|
device.ipackets[1] = chain
|
||||||
|
|
||||||
|
case "i3":
|
||||||
|
chain, err := newObfChain(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I3: %w", err)
|
||||||
|
}
|
||||||
|
device.ipackets[2] = chain
|
||||||
|
|
||||||
|
case "i4":
|
||||||
|
chain, err := newObfChain(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I4: %w", err)
|
||||||
|
}
|
||||||
|
device.ipackets[3] = chain
|
||||||
|
|
||||||
|
case "i5":
|
||||||
|
chain, err := newObfChain(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I5: %w", err)
|
||||||
|
}
|
||||||
|
device.ipackets[4] = chain
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI device key: %v", key)
|
return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI device key: %v", key)
|
||||||
}
|
}
|
||||||
|
|
@ -271,7 +478,10 @@ func (peer *ipcSetPeer) handlePostConfig() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) handlePublicKeyLine(peer *ipcSetPeer, value string) error {
|
func (device *Device) handlePublicKeyLine(
|
||||||
|
peer *ipcSetPeer,
|
||||||
|
value string,
|
||||||
|
) error {
|
||||||
// Load/create the peer we are configuring.
|
// Load/create the peer we are configuring.
|
||||||
var publicKey NoisePublicKey
|
var publicKey NoisePublicKey
|
||||||
err := publicKey.FromHex(value)
|
err := publicKey.FromHex(value)
|
||||||
|
|
@ -301,12 +511,19 @@ func (device *Device) handlePublicKeyLine(peer *ipcSetPeer, value string) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error {
|
func (device *Device) handlePeerLine(
|
||||||
|
peer *ipcSetPeer,
|
||||||
|
key, value string,
|
||||||
|
) error {
|
||||||
switch key {
|
switch key {
|
||||||
case "update_only":
|
case "update_only":
|
||||||
// allow disabling of creation
|
// allow disabling of creation
|
||||||
if value != "true" {
|
if value != "true" {
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set update only, invalid value: %v", value)
|
return ipcErrorf(
|
||||||
|
ipc.IpcErrorInvalid,
|
||||||
|
"failed to set update only, invalid value: %v",
|
||||||
|
value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if peer.created && !peer.dummy {
|
if peer.created && !peer.dummy {
|
||||||
device.RemovePeer(peer.handshake.remoteStatic)
|
device.RemovePeer(peer.handshake.remoteStatic)
|
||||||
|
|
@ -352,7 +569,11 @@ func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error
|
||||||
|
|
||||||
secs, err := strconv.ParseUint(value, 10, 16)
|
secs, err := strconv.ParseUint(value, 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set persistent keepalive interval: %w", err)
|
return ipcErrorf(
|
||||||
|
ipc.IpcErrorInvalid,
|
||||||
|
"failed to set persistent keepalive interval: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
old := peer.persistentKeepaliveInterval.Swap(uint32(secs))
|
old := peer.persistentKeepaliveInterval.Swap(uint32(secs))
|
||||||
|
|
@ -363,7 +584,11 @@ func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error
|
||||||
case "replace_allowed_ips":
|
case "replace_allowed_ips":
|
||||||
device.log.Verbosef("%v - UAPI: Removing all allowedips", peer.Peer)
|
device.log.Verbosef("%v - UAPI: Removing all allowedips", peer.Peer)
|
||||||
if value != "true" {
|
if value != "true" {
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to replace allowedips, invalid value: %v", value)
|
return ipcErrorf(
|
||||||
|
ipc.IpcErrorInvalid,
|
||||||
|
"failed to replace allowedips, invalid value: %v",
|
||||||
|
value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if peer.dummy {
|
if peer.dummy {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -371,7 +596,14 @@ func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error
|
||||||
device.allowedips.RemoveByPeer(peer.Peer)
|
device.allowedips.RemoveByPeer(peer.Peer)
|
||||||
|
|
||||||
case "allowed_ip":
|
case "allowed_ip":
|
||||||
device.log.Verbosef("%v - UAPI: Adding allowedip", peer.Peer)
|
add := true
|
||||||
|
verb := "Adding"
|
||||||
|
if len(value) > 0 && value[0] == '-' {
|
||||||
|
add = false
|
||||||
|
verb = "Removing"
|
||||||
|
value = value[1:]
|
||||||
|
}
|
||||||
|
device.log.Verbosef("%v - UAPI: %s allowedip", peer.Peer, verb)
|
||||||
prefix, err := netip.ParsePrefix(value)
|
prefix, err := netip.ParsePrefix(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set allowed ip: %w", err)
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set allowed ip: %w", err)
|
||||||
|
|
@ -379,7 +611,11 @@ func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error
|
||||||
if peer.dummy {
|
if peer.dummy {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if add {
|
||||||
device.allowedips.Insert(prefix, peer.Peer)
|
device.allowedips.Insert(prefix, peer.Peer)
|
||||||
|
} else {
|
||||||
|
device.allowedips.Remove(prefix, peer.Peer)
|
||||||
|
}
|
||||||
|
|
||||||
case "protocol_version":
|
case "protocol_version":
|
||||||
if value != "1" {
|
if value != "1" {
|
||||||
|
|
@ -431,7 +667,11 @@ func (device *Device) IpcHandle(socket net.Conn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if nextByte != '\n' {
|
if nextByte != '\n' {
|
||||||
err = ipcErrorf(ipc.IpcErrorInvalid, "trailing character in UAPI get: %q", nextByte)
|
err = ipcErrorf(
|
||||||
|
ipc.IpcErrorInvalid,
|
||||||
|
"trailing character in UAPI get: %q",
|
||||||
|
nextByte,
|
||||||
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
err = device.IpcGetOperation(buffered.Writer)
|
err = device.IpcGetOperation(buffered.Writer)
|
||||||
|
|
@ -455,3 +695,49 @@ func (device *Device) IpcHandle(socket net.Conn) {
|
||||||
buffered.Flush()
|
buffered.Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ipcSetDevice struct {
|
||||||
|
headers struct {
|
||||||
|
init *magicHeader
|
||||||
|
response *magicHeader
|
||||||
|
cookie *magicHeader
|
||||||
|
transport *magicHeader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ipcSetDevice) mergeWithDevice(device *Device) error {
|
||||||
|
if d.headers.init == nil {
|
||||||
|
d.headers.init = device.headers.init
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.headers.response == nil {
|
||||||
|
d.headers.response = device.headers.response
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.headers.cookie == nil {
|
||||||
|
d.headers.cookie = device.headers.cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.headers.transport == nil {
|
||||||
|
d.headers.transport = device.headers.transport
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []*magicHeader{d.headers.init, d.headers.response, d.headers.cookie, d.headers.transport}
|
||||||
|
for i := 0; i < len(headers); i++ {
|
||||||
|
for j := i + 1; j < len(headers); j++ {
|
||||||
|
left := headers[i]
|
||||||
|
right := headers[j]
|
||||||
|
|
||||||
|
if left.start <= right.end && right.start <= left.end {
|
||||||
|
return errors.New("headers must not overlap")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
device.headers.init = d.headers.init
|
||||||
|
device.headers.response = d.headers.response
|
||||||
|
device.headers.cookie = d.headers.cookie
|
||||||
|
device.headers.transport = d.headers.transport
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -1,6 +1,6 @@
|
||||||
module github.com/sagernet/wireguard-go
|
module github.com/sagernet/wireguard-go
|
||||||
|
|
||||||
go 1.20
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/sagernet/sing v0.7.10
|
github.com/sagernet/sing v0.7.10
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ipc
|
package ipc
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ipc
|
package ipc
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ipc
|
package ipc
|
||||||
|
|
@ -26,7 +26,7 @@ const (
|
||||||
|
|
||||||
// socketDirectory is variable because it is modified by a linker
|
// socketDirectory is variable because it is modified by a linker
|
||||||
// flag in wireguard-android.
|
// flag in wireguard-android.
|
||||||
var socketDirectory = "/var/run/wireguard"
|
var socketDirectory = "/var/run/amneziawg"
|
||||||
|
|
||||||
func sockPath(iface string) string {
|
func sockPath(iface string) string {
|
||||||
return fmt.Sprintf("%s/%s.sock", socketDirectory, iface)
|
return fmt.Sprintf("%s/%s.sock", socketDirectory, iface)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ratelimiter
|
package ratelimiter
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package replay implements an efficient anti-replay algorithm as specified in RFC 6479.
|
// Package replay implements an efficient anti-replay algorithm as specified in RFC 6479.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package rwcancel implements cancelable read/write operations on
|
// Package rwcancel implements cancelable read/write operations on
|
||||||
|
|
@ -64,7 +64,7 @@ func (rw *RWCancel) ReadyRead() bool {
|
||||||
|
|
||||||
func (rw *RWCancel) ReadyWrite() bool {
|
func (rw *RWCancel) ReadyWrite() bool {
|
||||||
closeFd := int32(rw.closingReader.Fd())
|
closeFd := int32(rw.closingReader.Fd())
|
||||||
pollFds := []unix.PollFd{{Fd: int32(rw.fd), Events: unix.POLLOUT}, {Fd: closeFd, Events: unix.POLLOUT}}
|
pollFds := []unix.PollFd{{Fd: int32(rw.fd), Events: unix.POLLOUT}, {Fd: closeFd, Events: unix.POLLIN}}
|
||||||
var err error
|
var err error
|
||||||
for {
|
for {
|
||||||
_, err = unix.Poll(pollFds, -1)
|
_, err = unix.Poll(pollFds, -1)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tai64n
|
package tai64n
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
@ -30,18 +28,6 @@ type NativeTun struct {
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func retryInterfaceByIndex(index int) (iface *net.Interface, err error) {
|
|
||||||
for i := 0; i < 20; i++ {
|
|
||||||
iface, err = net.InterfaceByIndex(index)
|
|
||||||
if err != nil && errors.Is(err, unix.ENOMEM) {
|
|
||||||
time.Sleep(time.Duration(i) * time.Second / 3)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return iface, err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
||||||
var (
|
var (
|
||||||
statusUp bool
|
statusUp bool
|
||||||
|
|
@ -62,26 +48,22 @@ func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if n < 14 {
|
if n < 28 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if data[3 /* type */] != unix.RTM_IFINFO {
|
if data[3 /* ifm_type */] != unix.RTM_IFINFO {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ifindex := int(*(*uint16)(unsafe.Pointer(&data[12 /* ifindex */])))
|
ifindex := int(*(*uint16)(unsafe.Pointer(&data[12 /* ifm_index */])))
|
||||||
if ifindex != tunIfindex {
|
if ifindex != tunIfindex {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
iface, err := retryInterfaceByIndex(ifindex)
|
flags := int(*(*uint32)(unsafe.Pointer(&data[8 /* ifm_flags */])))
|
||||||
if err != nil {
|
|
||||||
tun.errors <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Up / Down event
|
// Up / Down event
|
||||||
up := (iface.Flags & net.FlagUp) != 0
|
up := (flags & syscall.IFF_UP) != 0
|
||||||
if up != statusUp && up {
|
if up != statusUp && up {
|
||||||
tun.events <- EventUp
|
tun.events <- EventUp
|
||||||
}
|
}
|
||||||
|
|
@ -90,11 +72,13 @@ func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
||||||
}
|
}
|
||||||
statusUp = up
|
statusUp = up
|
||||||
|
|
||||||
|
mtu := int(*(*uint32)(unsafe.Pointer(&data[24 /* ifm_data.ifi_mtu */])))
|
||||||
|
|
||||||
// MTU changes
|
// MTU changes
|
||||||
if iface.MTU != statusMTU {
|
if mtu != statusMTU {
|
||||||
tun.events <- EventMTUUpdate
|
tun.events <- EventMTUUpdate
|
||||||
}
|
}
|
||||||
statusMTU = iface.MTU
|
statusMTU = mtu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
@ -160,11 +160,10 @@ retry:
|
||||||
packet, err := tun.session.ReceivePacket()
|
packet, err := tun.session.ReceivePacket()
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
packetSize := len(packet)
|
n := copy(bufs[0][offset:], packet)
|
||||||
copy(bufs[0][offset:], packet)
|
sizes[0] = n
|
||||||
sizes[0] = packetSize
|
|
||||||
tun.session.ReleaseReceivePacket(packet)
|
tun.session.ReleaseReceivePacket(packet)
|
||||||
tun.rate.update(uint64(packetSize))
|
tun.rate.update(uint64(n))
|
||||||
return 1, nil
|
return 1, nil
|
||||||
case windows.ERROR_NO_MORE_ITEMS:
|
case windows.ERROR_NO_MORE_ITEMS:
|
||||||
if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {
|
if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
const Version = "0.0.20230223"
|
const Version = "0.0.20250522"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue