Compare commits

...
Author SHA1 Message Date
FrogRocky 513511c162 outline: pass header protection key directly to UAPI 2026-09-20 10:43:13 +02:00
FrogRocky b6393b6f2d outline: simplify AWG configuration 2026-09-20 10:31:02 +02:00
FrogRocky dffb93aa29 outline: support AWG 3.1 parameters 2026-09-18 22:20:23 +02:00
Yaroslav Gurov b5928efb6c fix: disable the whole underload if DisableCookies is on
Thanks to @darkk for spotting and suggesting the fix
2026-08-28 17:48:27 +02:00
Yaroslav Gurov da11c9fadc fix: use udp window for RandomPaddingAddition 2026-08-28 17:48:05 +02:00
Yaroslav Gurov 1b86b2ae0e fix: wrong trailerLen for HandshakeCookie message
* AWG3.1-related changes indroduced a new bug with HandshakeCookie
  buffer size. Instead of adding trailerLen to the final size, it is
getting provided as a capacity param leading to the wrong behavior and
runtime panic while RandomTrailers is used
* Must be the typo :_)
2026-08-13 21:36:22 +02:00
Yaroslav Gurov 08271d00b3 feat: make PeekLookAtSocketFd available across all platforms 2026-08-13 15:55:40 +02:00
Yaroslav Gurov 75ea550a64 chore: update awg-tools 2026-08-13 13:39:15 +02:00
Yaroslav Gurov 1f50ad736e feat: awg 3.1 features
* add RandomTrailers feature which appends random amount of bytes to the
end of each packet
* add DisableCookie feature which prohibits interface to send any cookie
replies
2026-08-13 00:25:03 +02:00
Yaroslav Gurov 08d68cdae2 fix: keepalives are ignored 2026-08-05 13:59:10 +02:00
itfsdev cf9d2dd202 fix: pin Dockerfile's amneziawg-tools build to the tagged v3.0.20260730 release 2026-07-31 20:16:15 +08:00
Yaroslav Gurov 7860d60a52 fix: change HeaderCipherNonceSize to be reported in wrong S1-S4 2026-07-31 08:53:19 +02:00
Yaroslav Gurov ce7cf1034d docs: change 8 requirement to 12 in README 2026-07-31 08:53:19 +02:00
itfsdev d57d98d55b feat: update Dockerfile to point to the special commit from feat/awg3 from amneziawg-tools 2026-07-28 22:40:25 +08:00
itfsdev 0527dfa476 fix: bump Docker builder image to golang:1.25.12 2026-07-28 18:33:16 +08:00
Yaroslav Gurov 9f5d948bc7 fix: use v3 versioning 2026-07-24 15:06:35 +02:00
Yaroslav Gurov 457d920a1a feat: amneziawg 3.0
* feat: add header protection mechanism, random transport payload trailing size, and handshake timings randomization

* fix: use uint range instead of the int one

* fix: uapi typo

* fix: simultaneous padding access

* feat: prohibit Sx < 8 if headerProtection is set

* fix: do not count bytes on crypt

* fix: trailing size calculation

* fix: get rid of buffer copying on receival

* chore: rename random padding multiple

* fix: atomize junk packets

* fix: use padding from device by default

* chore: readme changes

* chore: add readme annotation about client-side params

* chore: add both side recommendation for content padding

* feat: change ContentPaddingMultiple to ContentPaddingAddition

* feat: bring chacha20 in there

* chore: use UintRange instead of magicHeader

* feat: make all timers parameters

* fix: reissue max_handshake_amount on successful handshake

* chore: add readme info about new timers

* fix: use ParseUint for UintRange

* feat: make persistent keepalive a range

* feat: use atomics everywhere where possible

* chore: readme changes

* fix: typo

* fix: minor timer fixes
2026-07-24 14:18:33 +02:00
Vinicius Fortuna c1e9bb3758 Update Outline SDK module path 2026-07-09 04:22:33 +02:00
Yaroslav Gurov 1cc94272ca fix: handle empty I1-I5 2026-06-18 00:38:58 +02:00
stereomonk 3610f21b75 doc: fix typo README.md 2026-06-17 22:22:36 +02:00
admin f4f4c99926 fix: apply S4 transport padding to keepalive packets
Keepalive packets were excluded from S4 padding because the padding
logic was nested inside the dataSent guard. The receiving side
(DeterminePacketTypeAndPadding) expects S4 padding on all transport
packets, so unpadded keepalives fail H4 header validation and are
silently dropped.

This prevents the responder from completing key confirmation —
lastHandshakeNano stays 0 until real data flows through the tunnel.
2026-05-13 11:11:21 +02:00
Yaroslav Gurov 12a012205e readme: actualize type for H1-H4 2026-03-31 17:56:17 +02:00
Yaroslav Gurov e7ef4339e7 readme: remove <c> tag from tag reference 2026-03-23 12:10:15 +01:00
43 changed files with 1092 additions and 460 deletions

No files matched your search

+14 -8
View File
@@ -1,18 +1,24 @@
FROM golang:1.24.4 as awg FROM golang:1.25.12 as awg
COPY . /awg COPY . /awg
WORKDIR /awg WORKDIR /awg
RUN go mod download && \ RUN go mod download && \
go mod verify && \ go mod verify && \
go build -ldflags '-linkmode external -extldflags "-fno-PIC -static"' -v -o /usr/bin go build -ldflags '-linkmode external -extldflags "-fno-PIC -static"' -v -o /usr/bin
FROM alpine:3.19 FROM alpine:3.19 as tools
ARG AWGTOOLS_RELEASE="1.0.20250901" ARG AWGTOOLS_COMMIT="v3.1.20260812"
RUN apk --no-cache add iproute2 iptables bash && \ RUN apk add --no-cache git build-base linux-headers && \
cd /usr/bin/ && \ git clone https://github.com/amnezia-vpn/amneziawg-tools.git /amneziawg-tools && \
wget https://github.com/amnezia-vpn/amneziawg-tools/releases/download/v${AWGTOOLS_RELEASE}/alpine-3.19-amneziawg-tools.zip && \ cd /amneziawg-tools && git checkout ${AWGTOOLS_COMMIT} && \
unzip -j alpine-3.19-amneziawg-tools.zip && \ cd src && make
chmod +x /usr/bin/awg /usr/bin/awg-quick && \
FROM alpine:3.19
RUN apk --no-cache add iproute2 iptables bash
COPY --from=tools /amneziawg-tools/src/wg /usr/bin/awg
COPY --from=tools /amneziawg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick
RUN chmod +x /usr/bin/awg /usr/bin/awg-quick && \
ln -s /usr/bin/awg /usr/bin/wg && \ ln -s /usr/bin/awg /usr/bin/wg && \
ln -s /usr/bin/awg-quick /usr/bin/wg-quick ln -s /usr/bin/awg-quick /usr/bin/wg-quick
COPY --from=awg /usr/bin/amneziawg-go /usr/bin/amneziawg-go COPY --from=awg /usr/bin/amneziawg-go /usr/bin/amneziawg-go
+53 -4
View File
@@ -53,9 +53,59 @@ $ make
## Configuration ## Configuration
### Data types and definitions
`client-side` means the param is not required to be the same on both server and client, while
`server-side` means this is mandatory the values are the same on server and on client
`range,x`, where x is the underlying type of left and right
* Format: "a-b", or "a", or "(off)"
* Example: PersistentKeepalive = 22-30
> [!NOTE] > [!NOTE]
> If there is no value specified (for any param), AWG treats it as 0 > If there is no value specified (for any param), AWG treats it as 0
### Header protection [AWG 3+]
Header protection is the mechanism of protecting low-entropy values of packets' headers. The idea is to apply fast encryption to the specific fields which WireGuard does use for authentication and its own encryption. The cipher uses `S1-S4` crypto padding as nonce for each incoming packet.
```
[Device]
+ HeaderProtectionKey: key,string - server-side # the key to be used in header protection
```
> [!TIP]
> Use `awg genkey` to generate header protection key
> [!IMPORTANT]
> Header protection requires `S1-S4` value to be 12 at least
### Content padding [AWG 3+]
```
[Device]
+ ContentPaddingAddition: uint32,range - client-side # the range to be used as a custom padding
```
> [!TIP]
> It's important to specify content padding on both sides. However, this is not strictly required and could be omitted.
### Timings [AWG 3+]
This param could be used to customize default Wireguard's timings
```
[Device]
+ RekeyAfterTime = "uint32,range - client-side - seconds" # time, after which client tries to handshake
+ RekeyTimeout = "uint32,range - client-side - seconds" # timeout, after which handshake is repeated
+ RejectAfterTime = "uint32,range - client-side - seconds" # time, after which client forces handshake, and declines all incoming data
+ KeepaliveTimeout = "uint32,range - client-side - seconds" # time from last data sending, after which keepalive is sent
+ MaxHandshakeAttempts = "uint32,range - client-side - amount" # maximum attempts of handshake repetition
[Peer]
M PersistentKeepalive = "uint32,range - client-side - seconds" # interval of persistent keepalive
```
### Junk packets ### Junk packets
The amount of junk packets specified in `Jc` with a random size between `Jmin` and `Jmax` would be generated and sent prior every handshake The amount of junk packets specified in `Jc` with a random size between `Jmin` and `Jmax` would be generated and sent prior every handshake
@@ -78,10 +128,10 @@ The amount of junk packets specified in `Jc` with a random size between `Jmin` a
### Message headers ### Message headers
Every message in wireguard has `int32` type at the beginning of the packet. This field could be controlled by specifying the params below: Every message in wireguard has `uint32` type at the beginning of the packet. This field could be controlled by specifying the params below:
- `H1: string` - header range of handshake initial message - `H1: string` - header range of handshake initial message
- `H2: string` - header range of handshake initial message - `H2: string` - header range of handshake response message
- `H3: string` - header range of handshake cookie message - `H3: string` - header range of handshake cookie message
- `H4: string` - header range of transport message - `H4: string` - header range of transport message
@@ -105,10 +155,9 @@ Value is a sequence of tags specified below:
- `<rd [size]>` - random digits tag. Dumps `[size]` amount of randomly-generated bytes from `[0-9]` set to the packet - `<rd [size]>` - random digits tag. Dumps `[size]` amount of randomly-generated bytes from `[0-9]` set to the packet
- `<rc [size]>` - random chars tag. Dumps `[size]` amount of randomly-generated bytes from `[a-zA-Z] set to the packet - `<rc [size]>` - random chars tag. Dumps `[size]` amount of randomly-generated bytes from `[a-zA-Z] set to the packet
- `<t>` - timestamp tag. Dumps 4-bytes long current system time in UNIX format - `<t>` - timestamp tag. Dumps 4-bytes long current system time in UNIX format
- `<c>` - packet counter tag. Dumps 4-bytes long amount of packets sent by AWG
> [!TIP] > [!TIP]
> Custom signature packets does not carry any actual data, so there is no need to specify it on both sides. General recommendation is to use it on the client side only > Custom signature packets does not carry any actual data, so there is no need to specify it on both sides. General recommendation is to use it on the client side only
> [!IMPORTANT] > [!IMPORTANT]
> If the final size of any packet exceeds system MTU, it would be fractured into fragments, which looks suspicious > If the final size of any packet exceeds system MTU, it would be fractured into fragments, which looks suspicious
+19 -1
View File
@@ -17,7 +17,7 @@ import (
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"github.com/amnezia-vpn/amneziawg-go/conn/winrio" "github.com/amnezia-vpn/amneziawg-go/v3/conn/winrio"
) )
const ( const (
@@ -582,6 +582,24 @@ func (bind *WinRingBind) BindSocketToInterface6(interfaceIndex uint32, blackhole
return nil return nil
} }
func (bind *WinRingBind) PeekLookAtSocketFd4() (fd int, err error) {
bind.mu.RLock()
defer bind.mu.RUnlock()
if bind.isOpen.Load() != 1 {
return -1, net.ErrClosed
}
return int(bind.v4.sock), nil
}
func (bind *WinRingBind) PeekLookAtSocketFd6() (fd int, err error) {
bind.mu.RLock()
defer bind.mu.RUnlock()
if bind.isOpen.Load() != 1 {
return -1, net.ErrClosed
}
return int(bind.v6.sock), nil
}
func bindSocketToInterface4(handle windows.Handle, interfaceIndex uint32) error { func bindSocketToInterface4(handle windows.Handle, interfaceIndex uint32) error {
const IP_UNICAST_IF = 31 const IP_UNICAST_IF = 31
/* MSDN says for IPv4 this needs to be in net byte order, so that it's like an IP address with leading zeros. */ /* MSDN says for IPv4 this needs to be in net byte order, so that it's like an IP address with leading zeros. */
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"net/netip" "net/netip"
"os" "os"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
) )
type ChannelBind struct { type ChannelBind struct {
File renamed without changes.
+1 -1
View File
@@ -8,7 +8,7 @@ package device
import ( import (
"errors" "errors"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
) )
type DummyDatagram struct { type DummyDatagram struct {
+1
View File
@@ -24,6 +24,7 @@ const (
CookieRefreshTime = time.Second * 120 CookieRefreshTime = time.Second * 120
HandshakeInitationRate = time.Second / 50 HandshakeInitationRate = time.Second / 50
PaddingMultiple = 16 PaddingMultiple = 16
DefaultUdpWindow = 500
) )
const ( const (
+47 -21
View File
@@ -11,10 +11,10 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/ratelimiter" "github.com/amnezia-vpn/amneziawg-go/v3/ratelimiter"
"github.com/amnezia-vpn/amneziawg-go/rwcancel" "github.com/amnezia-vpn/amneziawg-go/v3/rwcancel"
"github.com/amnezia-vpn/amneziawg-go/tun" "github.com/amnezia-vpn/amneziawg-go/v3/tun"
) )
type Device struct { type Device struct {
@@ -91,26 +91,44 @@ type Device struct {
log *Logger log *Logger
junk struct { junk struct {
min int min atomic.Uint32
max int max atomic.Uint32
count int count atomic.Uint32
} }
headers struct { headers struct {
init *magicHeader init AtomicUintRange
cookie *magicHeader cookie AtomicUintRange
response *magicHeader response AtomicUintRange
transport *magicHeader transport AtomicUintRange
} }
paddings struct { paddings struct {
init int init atomic.Uint32
response int response atomic.Uint32
cookie int cookie atomic.Uint32
transport int transport atomic.Uint32
} }
ipackets [5]*obfChain ipackets [5]*obfChain
headerProtection struct {
sync.RWMutex
key HeaderCipherKey
}
contentPaddingAddition AtomicUintRange
timings struct {
rekeyAfterTimeSec AtomicUintRange
rekeyTimeoutSec AtomicUintRange
rejectAfterTimeSec AtomicUintRange
keepaliveTimeoutSec AtomicUintRange
maxHandshakeAttemps AtomicUintRange
}
randomTrailers atomic.Bool
disableCookies atomic.Bool
} }
// deviceState represents the state of a Device. // deviceState represents the state of a Device.
@@ -205,7 +223,7 @@ func (device *Device) upLocked() error {
device.peers.RLock() device.peers.RLock()
for _, peer := range device.peers.keyMap { for _, peer := range device.peers.keyMap {
peer.Start() peer.Start()
if peer.persistentKeepaliveInterval.Load() > 0 { if !peer.persistentKeepaliveInterval.Load().IsZero() {
peer.SendKeepalive() peer.SendKeepalive()
} }
} }
@@ -305,6 +323,8 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
} }
func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device { func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device {
var rang UintRange
device := new(Device) device := new(Device)
device.state.state.Store(uint32(deviceStateDown)) device.state.state.Store(uint32(deviceStateDown))
device.closed = make(chan struct{}) device.closed = make(chan struct{})
@@ -321,10 +341,14 @@ func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device {
device.rate.limiter.Init() device.rate.limiter.Init()
device.indexTable.Init() device.indexTable.Init()
device.headers.init = &magicHeader{start: MessageInitiationType, end: MessageInitiationType} rang.FromUint32(MessageInitiationType, MessageInitiationType)
device.headers.response = &magicHeader{start: MessageResponseType, end: MessageResponseType} device.headers.init.Store(rang)
device.headers.cookie = &magicHeader{start: MessageCookieReplyType, end: MessageCookieReplyType} rang.FromUint32(MessageResponseType, MessageResponseType)
device.headers.transport = &magicHeader{start: MessageTransportType, end: MessageTransportType} device.headers.response.Store(rang)
rang.FromUint32(MessageCookieReplyType, MessageCookieReplyType)
device.headers.cookie.Store(rang)
rang.FromUint32(MessageTransportType, MessageTransportType)
device.headers.transport.Store(rang)
device.PopulatePools() device.PopulatePools()
@@ -436,10 +460,12 @@ func (device *Device) SendKeepalivesToPeersWithCurrentKeypair() {
return return
} }
timeout := device.keychainExpireTime()
device.peers.RLock() device.peers.RLock()
for _, peer := range device.peers.keyMap { for _, peer := range device.peers.keyMap {
peer.keypairs.RLock() peer.keypairs.RLock()
sendKeepalive := peer.keypairs.current != nil && !peer.keypairs.current.created.Add(RejectAfterTime).Before(time.Now()) sendKeepalive := peer.keypairs.current != nil && !peer.keypairs.current.created.Add(timeout).Before(time.Now())
peer.keypairs.RUnlock() peer.keypairs.RUnlock()
if sendKeepalive { if sendKeepalive {
peer.SendKeepalive() peer.SendKeepalive()
+4 -4
View File
@@ -23,10 +23,10 @@ import (
"go.uber.org/atomic" "go.uber.org/atomic"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/conn/bindtest" "github.com/amnezia-vpn/amneziawg-go/v3/conn/bindtest"
"github.com/amnezia-vpn/amneziawg-go/tun" "github.com/amnezia-vpn/amneziawg-go/v3/tun"
"github.com/amnezia-vpn/amneziawg-go/tun/tuntest" "github.com/amnezia-vpn/amneziawg-go/v3/tun/tuntest"
) )
// uapiCfg returns a string that contains cfg formatted use with IpcSet. // uapiCfg returns a string that contains cfg formatted use with IpcSet.
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/amnezia-vpn/amneziawg-go/replay" "github.com/amnezia-vpn/amneziawg-go/v3/replay"
) )
/* Due to limitations in Go and /x/crypto there is currently /* Due to limitations in Go and /x/crypto there is currently
-63
View File
@@ -1,63 +0,0 @@
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())
}
+31 -3
View File
@@ -6,16 +6,18 @@
package device package device
import ( import (
"crypto/rand"
"errors" "errors"
"fmt" "fmt"
"sync" "sync"
"time" "time"
"golang.org/x/crypto/blake2s" "golang.org/x/crypto/blake2s"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/poly1305" "golang.org/x/crypto/poly1305"
"github.com/amnezia-vpn/amneziawg-go/tai64n" "github.com/amnezia-vpn/amneziawg-go/v3/tai64n"
) )
type handshakeState int type handshakeState int
@@ -194,7 +196,7 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
handshake.mixHash(handshake.remoteStatic[:]) handshake.mixHash(handshake.remoteStatic[:])
msgType := device.headers.init.Generate() msgType := device.headers.init.Load().PickOne()
msg := MessageInitiation{ msg := MessageInitiation{
Type: msgType, Type: msgType,
@@ -370,7 +372,7 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error
} }
var msg MessageResponse var msg MessageResponse
msg.Type = device.headers.response.Generate() msg.Type = device.headers.response.Load().PickOne()
msg.Sender = handshake.localIndex msg.Sender = handshake.localIndex
msg.Receiver = handshake.remoteIndex msg.Receiver = handshake.remoteIndex
@@ -626,3 +628,29 @@ func (peer *Peer) ReceivedWithKeypair(receivedKeypair *Keypair) bool {
keypairs.next.Store(nil) keypairs.next.Store(nil)
return true return true
} }
func (device *Device) JunkPackets() [][]byte {
var bufs [][]byte
min := device.junk.min.Load()
max := device.junk.max.Load()
for range device.junk.count.Load() {
buf := make([]byte, min+fastrandn(max-min))
rand.Read(buf)
bufs = append(bufs, buf)
}
return bufs
}
func (device *Device) HeaderProtectionCipher(salt []byte) (*chacha20.Cipher, error) {
device.headerProtection.RLock()
defer device.headerProtection.RUnlock()
if device.headerProtection.key.IsZero() {
return nil, nil
}
return chacha20.NewUnauthenticatedCipher(device.headerProtection.key[:], salt)
}
+108
View File
@@ -9,12 +9,18 @@ import (
"crypto/subtle" "crypto/subtle"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"strconv"
"strings"
"sync/atomic"
) )
const ( const (
NoisePublicKeySize = 32 NoisePublicKeySize = 32
NoisePrivateKeySize = 32 NoisePrivateKeySize = 32
NoisePresharedKeySize = 32 NoisePresharedKeySize = 32
HeaderCipherKeySize = 32
HeaderCipherNonceSize = 12
) )
type ( type (
@@ -22,6 +28,7 @@ type (
NoisePrivateKey [NoisePrivateKeySize]byte NoisePrivateKey [NoisePrivateKeySize]byte
NoisePresharedKey [NoisePresharedKeySize]byte NoisePresharedKey [NoisePresharedKeySize]byte
NoiseNonce uint64 // padded to 12-bytes NoiseNonce uint64 // padded to 12-bytes
HeaderCipherKey [HeaderCipherKeySize]byte
) )
func loadExactHex(dst []byte, src string) error { func loadExactHex(dst []byte, src string) error {
@@ -76,3 +83,104 @@ func (key NoisePublicKey) Equals(tar NoisePublicKey) bool {
func (key *NoisePresharedKey) FromHex(src string) error { func (key *NoisePresharedKey) FromHex(src string) error {
return loadExactHex(key[:], src) return loadExactHex(key[:], src)
} }
func (key HeaderCipherKey) IsZero() bool {
var zero HeaderCipherKey
return key.Equals(zero)
}
func (key HeaderCipherKey) Equals(tar HeaderCipherKey) bool {
return subtle.ConstantTimeCompare(key[:], tar[:]) == 1
}
func (key *HeaderCipherKey) FromHex(src string) error {
return loadExactHex(key[:], src)
}
type UintRange uint64
func (r *UintRange) FromUint32(lo, hi uint32) {
*r = UintRange(uint64(hi)<<32 | uint64(lo))
}
func (r *UintRange) FromString(str string) error {
parts := strings.Split(str, "-")
if len(parts) < 1 || len(parts) > 2 {
return errors.New("wrong format")
}
lo, err := strconv.ParseUint(parts[0], 10, 32)
if err != nil {
return err
}
hi := lo
if len(parts) > 1 {
hi, err = strconv.ParseUint(parts[1], 10, 32)
if err != nil {
return err
}
}
if hi < lo {
return errors.New("wrong range specified")
}
r.FromUint32(uint32(lo), uint32(hi))
return nil
}
func (r UintRange) Contains(num uint32) bool {
lo, hi := uint32(r), uint32(r>>32)
return lo <= num && num <= hi
}
func (r UintRange) IsZero() bool {
return r == 0
}
func (r UintRange) PickOne() uint32 {
lo, hi := uint32(r), uint32(r>>32)
return lo + fastrandn(hi-lo+1)
}
func (r UintRange) ToString() string {
lo, hi := uint32(r), uint32(r>>32)
if lo == hi {
return fmt.Sprintf("%d", lo)
} else {
return fmt.Sprintf("%d-%d", lo, hi)
}
}
func (r UintRange) Overlap(right UintRange) bool {
l_lo, l_hi := uint32(r), uint32(r>>32)
r_lo, r_hi := uint32(right), uint32(right>>32)
return l_lo <= r_hi && r_lo <= l_hi
}
func (r UintRange) Lo() uint32 {
return uint32(r)
}
func (r UintRange) Hi() uint32 {
return uint32(r >> 32)
}
type AtomicUintRange struct {
v atomic.Uint64
}
func (a *AtomicUintRange) Load() UintRange {
return UintRange(a.v.Load())
}
func (a *AtomicUintRange) Store(r UintRange) {
a.v.Store(uint64(r))
}
func (a *AtomicUintRange) Swap(r UintRange) UintRange {
return UintRange(a.v.Swap(uint64(r)))
}
+2 -2
View File
@@ -10,8 +10,8 @@ import (
"encoding/binary" "encoding/binary"
"testing" "testing"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/tun/tuntest" "github.com/amnezia-vpn/amneziawg-go/v3/tun/tuntest"
) )
func TestCurveWrappers(t *testing.T) { func TestCurveWrappers(t *testing.T) {
+3
View File
@@ -85,6 +85,9 @@ func newObfChain(spec string) (*obfChain, error) {
if len(errs) > 0 { if len(errs) > 0 {
return nil, errors.Join(errs...) return nil, errors.Join(errs...)
} }
if len(obfs) == 0 {
return nil, nil
}
return &obfChain{ return &obfChain{
Spec: spec, Spec: spec,
+11 -4
View File
@@ -12,7 +12,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
) )
type Peer struct { type Peer struct {
@@ -39,6 +39,7 @@ type Peer struct {
zeroKeyMaterial *Timer zeroKeyMaterial *Timer
persistentKeepalive *Timer persistentKeepalive *Timer
handshakeAttempts atomic.Uint32 handshakeAttempts atomic.Uint32
maxHandshakeAttempts atomic.Uint32
needAnotherKeepalive atomic.Bool needAnotherKeepalive atomic.Bool
sentLastMinuteHandshake atomic.Bool sentLastMinuteHandshake atomic.Bool
} }
@@ -55,7 +56,8 @@ type Peer struct {
cookieGenerator CookieGenerator cookieGenerator CookieGenerator
trieEntries list.List trieEntries list.List
persistentKeepaliveInterval atomic.Uint32 persistentKeepaliveInterval AtomicUintRange
udpWindow atomic.Uint32
} }
func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) { func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) {
@@ -78,6 +80,8 @@ func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) {
// create peer // create peer
peer := new(Peer) peer := new(Peer)
peer.udpWindow.Store(DefaultUdpWindow)
peer.cookieGenerator.Init(pk) peer.cookieGenerator.Init(pk)
peer.device = device peer.device = device
peer.queue.outbound = newAutodrainingOutboundQueue(device) peer.queue.outbound = newAutodrainingOutboundQueue(device)
@@ -192,7 +196,7 @@ func (peer *Peer) Start() {
peer.stopping.Add(2) peer.stopping.Add(2)
peer.handshake.mutex.Lock() peer.handshake.mutex.Lock()
peer.handshake.lastSentHandshake = time.Now().Add(-(RekeyTimeout + time.Second)) peer.handshake.lastSentHandshake = time.Now().Add(-(peer.device.rekeyMinTimeout() + time.Second))
peer.handshake.mutex.Unlock() peer.handshake.mutex.Unlock()
peer.device.queue.encryption.wg.Add(1) // keep encryption queue open for our writes peer.device.queue.encryption.wg.Add(1) // keep encryption queue open for our writes
@@ -242,7 +246,7 @@ func (peer *Peer) ExpireCurrentKeypairs() {
handshake.mutex.Lock() handshake.mutex.Lock()
peer.device.indexTable.Delete(handshake.localIndex) peer.device.indexTable.Delete(handshake.localIndex)
handshake.Clear() handshake.Clear()
peer.handshake.lastSentHandshake = time.Now().Add(-(RekeyTimeout + time.Second)) peer.handshake.lastSentHandshake = time.Now().Add(-(peer.device.rekeyMinTimeout() + time.Second))
handshake.mutex.Unlock() handshake.mutex.Unlock()
keypairs := &peer.keypairs keypairs := &peer.keypairs
@@ -282,6 +286,9 @@ func (peer *Peer) SetEndpointFromPacket(endpoint conn.Endpoint) {
if peer.endpoint.disableRoaming { if peer.endpoint.disableRoaming {
return return
} }
if peer.endpoint.val != endpoint {
peer.udpWindow.Store(DefaultUdpWindow)
}
peer.endpoint.clearSrcOnTx = false peer.endpoint.clearSrcOnTx = false
peer.endpoint.val = endpoint peer.endpoint.val = endpoint
} }
+1 -1
View File
@@ -5,7 +5,7 @@
package device package device
import "github.com/amnezia-vpn/amneziawg-go/conn" import "github.com/amnezia-vpn/amneziawg-go/v3/conn"
/* Reduce memory consumption for Android */ /* Reduce memory consumption for Android */
+1 -1
View File
@@ -7,7 +7,7 @@
package device package device
import "github.com/amnezia-vpn/amneziawg-go/conn" import "github.com/amnezia-vpn/amneziawg-go/v3/conn"
const ( const (
QueueStagedSize = conn.IdealBatchSize QueueStagedSize = conn.IdealBatchSize
+95 -51
View File
@@ -13,7 +13,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"golang.org/x/net/ipv6" "golang.org/x/net/ipv6"
@@ -32,6 +32,7 @@ type QueueInboundElement struct {
counter uint64 counter uint64
keypair *Keypair keypair *Keypair
endpoint conn.Endpoint endpoint conn.Endpoint
padding uint32
} }
type QueueInboundElementsContainer struct { type QueueInboundElementsContainer struct {
@@ -59,7 +60,8 @@ func (peer *Peer) keepKeyFreshReceiving() {
return return
} }
keypair := peer.keypairs.Current() keypair := peer.keypairs.Current()
if keypair != nil && keypair.isInitiator && time.Since(keypair.created) > (RejectAfterTime-KeepaliveTimeout-RekeyTimeout) {
if keypair != nil && keypair.isInitiator && time.Since(keypair.created) > peer.device.keyRefreshTimeoutReceiving() {
peer.timers.sentLastMinuteHandshake.Store(true) peer.timers.sentLastMinuteHandshake.Store(true)
peer.SendHandshakeInitiation(false) peer.SendHandshakeInitiation(false)
} }
@@ -95,6 +97,7 @@ func (device *Device) RoutineReceiveIncoming(
endpoints = make([]conn.Endpoint, maxBatchSize) endpoints = make([]conn.Endpoint, maxBatchSize)
deathSpiral int deathSpiral int
elemsByPeer = make(map[*Peer]*QueueInboundElementsContainer, maxBatchSize) elemsByPeer = make(map[*Peer]*QueueInboundElementsContainer, maxBatchSize)
typeHashBuf [4]byte
) )
for i := range maxBatchSize { for i := range maxBatchSize {
@@ -138,11 +141,28 @@ func (device *Device) RoutineReceiveIncoming(
// check size of packet // check size of packet
packet := bufsArrs[i][:size] packet := bufsArrs[i][:size]
cip, err := device.HeaderProtectionCipher(packet[:HeaderCipherNonceSize])
if err != nil {
device.log.Errorf("Failed to initialize header cipher")
continue
}
typeHash := typeHashBuf[:]
clear(typeHash)
if cip != nil {
cip.XORKeyStream(typeHash, typeHash)
}
// get message padding and type based on information from S1-S4 and H1-H4 // get message padding and type based on information from S1-S4 and H1-H4
msgType, padding := device.DeterminePacketTypeAndPadding(packet, MessageUnknownType) msgSize, msgType, padding := device.DeterminePacketTypeAndPadding(packet, typeHash)
if padding > 0 {
copy(packet, packet[padding:]) packet = packet[padding:]
packet = packet[:len(packet)-padding] if msgType != MessageTransportType {
packet = packet[:msgSize]
}
if cip != nil {
applyHash(packet[:4], packet[:4], typeHash)
} }
switch msgType { switch msgType {
@@ -156,6 +176,9 @@ func (device *Device) RoutineReceiveIncoming(
if len(packet) < MessageTransportSize { if len(packet) < MessageTransportSize {
continue continue
} }
if cip != nil {
cip.XORKeyStream(packet[4:MessageTransportHeaderSize], packet[4:MessageTransportHeaderSize])
}
// lookup key pair // lookup key pair
@@ -170,7 +193,7 @@ func (device *Device) RoutineReceiveIncoming(
// check keypair expiry // check keypair expiry
if keypair.created.Add(RejectAfterTime).Before(time.Now()) { if keypair.created.Add(device.keychainExpireTime()).Before(time.Now()) {
continue continue
} }
@@ -182,6 +205,7 @@ func (device *Device) RoutineReceiveIncoming(
elem.keypair = keypair elem.keypair = keypair
elem.endpoint = endpoints[i] elem.endpoint = endpoints[i]
elem.counter = 0 elem.counter = 0
elem.padding = padding
elemsForPeer, ok := elemsByPeer[peer] elemsForPeer, ok := elemsByPeer[peer]
if !ok { if !ok {
@@ -200,16 +224,25 @@ func (device *Device) RoutineReceiveIncoming(
if len(packet) != MessageInitiationSize { if len(packet) != MessageInitiationSize {
continue continue
} }
if cip != nil {
cip.XORKeyStream(packet[4:MessageInitiationSize], packet[4:MessageInitiationSize])
}
case MessageResponseType: case MessageResponseType:
if len(packet) != MessageResponseSize { if len(packet) != MessageResponseSize {
continue continue
} }
if cip != nil {
cip.XORKeyStream(packet[4:MessageResponseSize], packet[4:MessageResponseSize])
}
case MessageCookieReplyType: case MessageCookieReplyType:
if len(packet) != MessageCookieReplySize { if len(packet) != MessageCookieReplySize {
continue continue
} }
if cip != nil {
cip.XORKeyStream(packet[4:MessageCookieReplySize], packet[4:MessageCookieReplySize])
}
default: default:
device.log.Verbosef("Received message with unknown type") device.log.Verbosef("Received message with unknown type")
@@ -336,7 +369,8 @@ func (device *Device) RoutineHandshake(id int) {
// endpoints destination address is the source of the datagram // endpoints destination address is the source of the datagram
if device.IsUnderLoad() { disableCookies := device.disableCookies.Load()
if !disableCookies && device.IsUnderLoad() {
// verify MAC2 field // verify MAC2 field
@@ -481,7 +515,12 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
} }
rxBytesLen += uint64(len(elem.packet) + MinMessageSize) rxBytesLen += uint64(len(elem.packet) + MinMessageSize)
if len(elem.packet) == 0 { udpWindow := elem.padding + MessageTransportHeaderSize + uint32(len(elem.packet))
if peer.udpWindow.Load() < udpWindow {
peer.udpWindow.Store(udpWindow)
}
if len(elem.packet) == 0 || elem.packet[0] == 0 {
device.log.Verbosef("%v - Receiving keepalive packet", peer) device.log.Verbosef("%v - Receiving keepalive packet", peer)
continue continue
} }
@@ -529,10 +568,7 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
continue continue
} }
bufs = append( bufs = append(bufs, elem.buffer[int(elem.padding):int(elem.padding)+MessageTransportHeaderSize+len(elem.packet)])
bufs,
elem.buffer[:MessageTransportOffsetContent+len(elem.packet)],
)
} }
peer.rxBytes.Add(rxBytesLen) peer.rxBytes.Add(rxBytesLen)
@@ -560,56 +596,64 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
} }
} }
func (device *Device) DeterminePacketTypeAndPadding(packet []byte, expectedType uint32) (uint32, int) { func applyHash(dst, src, hash []byte) {
size := len(packet) for i := range len(dst) {
dst[i] = src[i] ^ hash[i]
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 { func (device *Device) DeterminePacketTypeAndPadding(packet []byte, typeHash []byte) (int, uint32, uint32) {
padding := device.paddings.response var headerBytes [4]byte
header := device.headers.response var padding uint32
var header UintRange
var expectedSize int
if size == padding+MessageResponseSize { size := len(packet)
data := packet[padding:] randomTrailers := device.randomTrailers.Load()
if header.Validate(binary.LittleEndian.Uint32(data)) {
return MessageResponseType, padding padding = device.paddings.init.Load()
} header = device.headers.init.Load()
expectedSize = int(padding) + MessageInitiationSize
if size == expectedSize || randomTrailers && size > expectedSize {
applyHash(headerBytes[:], packet[padding:padding+4], typeHash)
if header.Contains(binary.LittleEndian.Uint32(headerBytes[:])) {
return MessageInitiationSize, MessageInitiationType, padding
}
}
padding = device.paddings.response.Load()
header = device.headers.response.Load()
expectedSize = int(padding) + MessageResponseSize
if size == expectedSize || randomTrailers && size > expectedSize {
applyHash(headerBytes[:], packet[padding:padding+4], typeHash)
if header.Contains(binary.LittleEndian.Uint32(headerBytes[:])) {
return MessageResponseSize, MessageResponseType, padding
} }
} }
if expectedType == MessageUnknownType || expectedType == MessageCookieReplyType { padding = device.paddings.cookie.Load()
padding := device.paddings.cookie header = device.headers.cookie.Load()
header := device.headers.cookie expectedSize = int(padding) + MessageCookieReplySize
if size == padding+MessageCookieReplySize { if size == expectedSize || randomTrailers && size > expectedSize {
data := packet[padding:] applyHash(headerBytes[:], packet[padding:padding+4], typeHash)
if header.Validate(binary.LittleEndian.Uint32(data)) { if header.Contains(binary.LittleEndian.Uint32(headerBytes[:])) {
return MessageCookieReplyType, padding return MessageCookieReplySize, MessageCookieReplyType, padding
}
} }
} }
if expectedType == MessageUnknownType || expectedType == MessageTransportType { padding = device.paddings.transport.Load()
padding := device.paddings.transport header = device.headers.transport.Load()
header := device.headers.transport expectedSize = int(padding) + MessageTransportSize
if size >= padding+MessageTransportHeaderSize { if size >= expectedSize {
data := packet[padding:] applyHash(headerBytes[:], packet[padding:padding+4], typeHash)
if header.Validate(binary.LittleEndian.Uint32(data)) { if header.Contains(binary.LittleEndian.Uint32(headerBytes[:])) {
return MessageTransportType, padding return MessageTransportSize, MessageTransportType, padding
}
} }
} }
return MessageUnknownType, 0 return 0, MessageUnknownType, 0
} }
+163 -68
View File
@@ -10,14 +10,14 @@ import (
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"errors" "errors"
"math/big"
"net" "net"
"os" "os"
"slices"
"sync" "sync"
"time" "time"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/tun" "github.com/amnezia-vpn/amneziawg-go/v3/tun"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"golang.org/x/net/ipv6" "golang.org/x/net/ipv6"
@@ -48,11 +48,13 @@ import (
*/ */
type QueueOutboundElement struct { type QueueOutboundElement struct {
buffer *[MaxMessageSize]byte // slice holding the packet data buffer *[MaxMessageSize]byte // slice holding the packet data
packet []byte // slice of "buffer" (always!) packet []byte // slice of "buffer" (always!)
nonce uint64 // nonce for encryption nonce uint64 // nonce for encryption
keypair *Keypair // keypair for encryption keypair *Keypair // keypair for encryption
peer *Peer // related peer peer *Peer // related peer
padding uint32
isKeepalive bool
} }
type QueueOutboundElementsContainer struct { type QueueOutboundElementsContainer struct {
@@ -64,6 +66,8 @@ func (device *Device) NewOutboundElement() *QueueOutboundElement {
elem := device.GetOutboundElement() elem := device.GetOutboundElement()
elem.buffer = device.GetMessageBuffer() elem.buffer = device.GetMessageBuffer()
elem.nonce = 0 elem.nonce = 0
elem.padding = device.paddings.transport.Load()
elem.isKeepalive = false
// keypair and peer were cleared (if necessary) by clearPointers. // keypair and peer were cleared (if necessary) by clearPointers.
return elem return elem
} }
@@ -84,6 +88,7 @@ func (elem *QueueOutboundElement) clearPointers() {
func (peer *Peer) SendKeepalive() { func (peer *Peer) SendKeepalive() {
if len(peer.queue.staged) == 0 && peer.isRunning.Load() { if len(peer.queue.staged) == 0 && peer.isRunning.Load() {
elem := peer.device.NewOutboundElement() elem := peer.device.NewOutboundElement()
elem.isKeepalive = true
elemsContainer := peer.device.GetOutboundElementsContainer() elemsContainer := peer.device.GetOutboundElementsContainer()
elemsContainer.elems = append(elemsContainer.elems, elem) elemsContainer.elems = append(elemsContainer.elems, elem)
select { select {
@@ -101,17 +106,20 @@ func (peer *Peer) SendKeepalive() {
func (peer *Peer) SendHandshakeInitiation(isRetry bool) error { func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
if !isRetry { if !isRetry {
peer.timers.handshakeAttempts.Store(0) peer.timers.handshakeAttempts.Store(0)
peer.timers.maxHandshakeAttempts.Store(peer.device.maxHandshakeAttemps())
} }
timeout := peer.device.rekeyMinTimeout()
peer.handshake.mutex.RLock() peer.handshake.mutex.RLock()
if time.Since(peer.handshake.lastSentHandshake) < RekeyTimeout { if time.Since(peer.handshake.lastSentHandshake) < timeout {
peer.handshake.mutex.RUnlock() peer.handshake.mutex.RUnlock()
return nil return nil
} }
peer.handshake.mutex.RUnlock() peer.handshake.mutex.RUnlock()
peer.handshake.mutex.Lock() peer.handshake.mutex.Lock()
if time.Since(peer.handshake.lastSentHandshake) < RekeyTimeout { if time.Since(peer.handshake.lastSentHandshake) < timeout {
peer.handshake.mutex.Unlock() peer.handshake.mutex.Unlock()
return nil return nil
} }
@@ -136,21 +144,17 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
} }
} }
jc := peer.device.junk.count sendBuffer = append(sendBuffer, peer.device.JunkPackets()...)
jmin := peer.device.junk.min
jmax := peer.device.junk.max
for i := 0; i < jc; i++ { padding := int(peer.device.paddings.init.Load())
nBig, _ := rand.Int(rand.Reader, big.NewInt(int64(jmax-jmin+1))) trailerLen := max(peer.randomTrailer(padding+MessageInitiationSize), 0)
n := int(nBig.Int64()) + jmin
buf := make([]byte, n) buf := make([]byte, padding+MessageInitiationSize+trailerLen)
rand.Read(buf)
sendBuffer = append(sendBuffer, buf)
}
var buf [MessageInitiationSize]byte crypt := buf[:padding]
writer := bytes.NewBuffer(buf[:0]) rand.Read(crypt)
writer := bytes.NewBuffer(buf[padding:padding])
binary.Write(writer, binary.LittleEndian, msg) binary.Write(writer, binary.LittleEndian, msg)
packet := writer.Bytes() packet := writer.Bytes()
peer.cookieGenerator.AddMacs(packet) peer.cookieGenerator.AddMacs(packet)
@@ -158,15 +162,18 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
peer.timersAnyAuthenticatedPacketTraversal() peer.timersAnyAuthenticatedPacketTraversal()
peer.timersAnyAuthenticatedPacketSent() peer.timersAnyAuthenticatedPacketSent()
if padding := peer.device.paddings.init; padding > 0 { cip, err := peer.device.HeaderProtectionCipher(crypt[:HeaderCipherNonceSize])
buf := make([]byte, padding+len(packet)) if err != nil {
rand.Read(buf[:padding]) return err
copy(buf[padding:], packet) }
packet = buf if cip != nil {
cip.XORKeyStream(packet, packet)
} }
sendBuffer = append(sendBuffer, packet) trailer := buf[padding+MessageInitiationSize:]
rand.Read(trailer)
sendBuffer = append(sendBuffer, buf)
err = peer.SendBuffers(sendBuffer) 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)
@@ -189,9 +196,15 @@ func (peer *Peer) SendHandshakeResponse() error {
return err return err
} }
var buf [MessageResponseSize]byte padding := int(peer.device.paddings.response.Load())
writer := bytes.NewBuffer(buf[:0]) trailerLen := max(peer.randomTrailer(padding+MessageResponseSize), 0)
buf := make([]byte, padding+MessageResponseSize+trailerLen)
crypt := buf[:padding]
rand.Read(crypt)
writer := bytes.NewBuffer(buf[padding:padding])
binary.Write(writer, binary.LittleEndian, response) binary.Write(writer, binary.LittleEndian, response)
packet := writer.Bytes() packet := writer.Bytes()
peer.cookieGenerator.AddMacs(packet) peer.cookieGenerator.AddMacs(packet)
@@ -206,15 +219,19 @@ func (peer *Peer) SendHandshakeResponse() error {
peer.timersAnyAuthenticatedPacketTraversal() peer.timersAnyAuthenticatedPacketTraversal()
peer.timersAnyAuthenticatedPacketSent() peer.timersAnyAuthenticatedPacketSent()
if padding := peer.device.paddings.response; padding > 0 { cip, err := peer.device.HeaderProtectionCipher(crypt[:HeaderCipherNonceSize])
buf := make([]byte, padding+len(packet)) if err != nil {
rand.Read(buf[:padding]) return err
copy(buf[padding:], packet) }
packet = buf if cip != nil {
cip.XORKeyStream(packet, packet)
} }
trailer := buf[padding+MessageResponseSize:]
rand.Read(trailer)
// TODO: allocation could be avoided // TODO: allocation could be avoided
err = peer.SendBuffers([][]byte{packet}) err = peer.SendBuffers([][]byte{buf})
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)
} }
@@ -225,7 +242,7 @@ 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])
msgType := device.headers.cookie.Generate() msgType := device.headers.cookie.Load().PickOne()
reply, err := device.cookieChecker.CreateReply( reply, err := device.cookieChecker.CreateReply(
initiatingElem.packet, initiatingElem.packet,
@@ -238,20 +255,31 @@ func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement)
return err return err
} }
var buf [MessageCookieReplySize]byte padding := int(device.paddings.cookie.Load())
writer := bytes.NewBuffer(buf[:0]) trailerLen := max(device.randomTrailer(padding+MessageCookieReplySize), 0)
buf := make([]byte, padding+MessageCookieReplySize+trailerLen)
crypt := buf[:padding]
rand.Read(crypt)
writer := bytes.NewBuffer(buf[padding:padding])
binary.Write(writer, binary.LittleEndian, reply) binary.Write(writer, binary.LittleEndian, reply)
packet := writer.Bytes() packet := writer.Bytes()
if padding := device.paddings.cookie; padding > 0 { cip, err := device.HeaderProtectionCipher(crypt[:HeaderCipherNonceSize])
buf := make([]byte, padding+len(packet)) if err != nil {
rand.Read(buf[:padding]) return err
copy(buf[padding:], packet) }
packet = buf if cip != nil {
cip.XORKeyStream(packet, packet)
} }
trailer := buf[padding+MessageCookieReplySize:]
rand.Read(trailer)
// TODO: allocation could be avoided // TODO: allocation could be avoided
device.net.bind.Send([][]byte{packet}, initiatingElem.endpoint) device.net.bind.Send([][]byte{buf}, initiatingElem.endpoint)
return nil return nil
} }
@@ -261,7 +289,7 @@ func (peer *Peer) keepKeyFreshSending() {
return return
} }
nonce := keypair.sendNonce.Load() nonce := keypair.sendNonce.Load()
if nonce > RekeyAfterMessages || (keypair.isInitiator && time.Since(keypair.created) > RekeyAfterTime) { if nonce > RekeyAfterMessages || (keypair.isInitiator && time.Since(keypair.created) > peer.device.keyRefreshTimeoutSending()) {
peer.SendHandshakeInitiation(false) peer.SendHandshakeInitiation(false)
} }
} }
@@ -283,7 +311,6 @@ func (device *Device) RoutineReadFromTUN() {
elemsByPeer = make(map[*Peer]*QueueOutboundElementsContainer, batchSize) elemsByPeer = make(map[*Peer]*QueueOutboundElementsContainer, batchSize)
count = 0 count = 0
sizes = make([]int, batchSize) sizes = make([]int, batchSize)
offset = MessageTransportHeaderSize
) )
for i := range elems { for i := range elems {
@@ -301,6 +328,9 @@ func (device *Device) RoutineReadFromTUN() {
}() }()
for { for {
padding := device.paddings.transport.Load()
offset := MessageTransportHeaderSize + int(padding)
// read packets // read packets
count, readErr = device.tun.device.Read(bufs, sizes, offset) count, readErr = device.tun.device.Read(bufs, sizes, offset)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
@@ -310,6 +340,7 @@ func (device *Device) RoutineReadFromTUN() {
elem := elems[i] elem := elems[i]
elem.packet = bufs[i][offset : offset+sizes[i]] elem.packet = bufs[i][offset : offset+sizes[i]]
elem.padding = padding
// lookup peer // lookup peer
var peer *Peer var peer *Peer
@@ -404,7 +435,7 @@ top:
} }
keypair := peer.keypairs.Current() keypair := peer.keypairs.Current()
if keypair == nil || keypair.sendNonce.Load() >= RejectAfterMessages || time.Since(keypair.created) >= RejectAfterTime { if keypair == nil || keypair.sendNonce.Load() >= RejectAfterMessages || time.Since(keypair.created) >= peer.device.keychainExpireTime() {
peer.SendHandshakeInitiation(false) peer.SendHandshakeInitiation(false)
return return
} }
@@ -494,13 +525,55 @@ func calculatePaddingSize(packetSize, mtu int) int {
return paddedSize - lastUnit return paddedSize - lastUnit
} }
func (peer *Peer) randomPaddingAddition(packetSize int) int {
addition := peer.device.contentPaddingAddition.Load()
if addition.IsZero() {
return -1
}
udpWindow := int(peer.udpWindow.Load())
if udpWindow < packetSize {
return 0
}
add := int(addition.PickOne())
space := udpWindow - packetSize
if add > space {
add = space
}
return add
}
func (device *Device) randomTrailer(packetSize int) int {
if !device.randomTrailers.Load() {
return -1
}
if DefaultUdpWindow < packetSize {
return 0
}
return int(fastrandn(uint32(DefaultUdpWindow - packetSize)))
}
func (peer *Peer) randomTrailer(packetSize int) int {
if !peer.device.randomTrailers.Load() {
return -1
}
udpWindow := int(peer.udpWindow.Load())
if udpWindow < packetSize {
return 0
}
return int(fastrandn(uint32(udpWindow - packetSize)))
}
/* Encrypts the elements in the queue /* Encrypts the elements in the queue
* and marks them for sequential consumption (by releasing the mutex) * and marks them for sequential consumption (by releasing the mutex)
* *
* Obs. One instance per core * Obs. One instance per core
*/ */
func (device *Device) RoutineEncryption(id int) { func (device *Device) RoutineEncryption(id int) {
var paddingZeros [PaddingMultiple]byte
var nonce [chacha20poly1305.NonceSize]byte var nonce [chacha20poly1305.NonceSize]byte
defer device.log.Verbosef("Routine: encryption worker %d - stopped", id) defer device.log.Verbosef("Routine: encryption worker %d - stopped", id)
@@ -508,32 +581,63 @@ 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 {
udpWindow := elem.padding + MinMessageSize + uint32(len(elem.packet))
if elem.peer.udpWindow.Load() < udpWindow {
elem.peer.udpWindow.Store(udpWindow)
}
// fill crypto padding
crypt := elem.buffer[:elem.padding]
rand.Read(crypt)
// populate header fields // populate header fields
header := elem.buffer[:MessageTransportHeaderSize] header := elem.buffer[elem.padding : elem.padding+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]
msgType := device.headers.transport.Generate() binary.LittleEndian.PutUint32(fieldType, device.headers.transport.Load().PickOne())
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)
// pad content to multiple of 16 packetSize := len(elem.packet) + MinMessageSize + int(elem.padding)
paddingSize := calculatePaddingSize(len(elem.packet), int(device.tun.mtu.Load())) mtu := int(device.tun.mtu.Load())
elem.packet = append(elem.packet, paddingZeros[:paddingSize]...)
paddingSize := elem.peer.randomPaddingAddition(packetSize)
if paddingSize < 0 {
paddingSize = elem.peer.randomTrailer(packetSize)
}
if paddingSize < 0 {
// pad content to multiple of 16
paddingSize = calculatePaddingSize(len(elem.packet), mtu)
}
// append trailing zeroes
oldLen := len(elem.packet)
elem.packet = slices.Grow(elem.packet, paddingSize)
elem.packet = elem.packet[:oldLen+paddingSize]
clear(elem.packet[oldLen:])
// encrypt content and release to consumer // encrypt content and release to consumer
binary.LittleEndian.PutUint64(nonce[4:], elem.nonce) binary.LittleEndian.PutUint64(nonce[4:], elem.nonce)
elem.packet = elem.keypair.send.Seal( elem.packet = elem.keypair.send.Seal(
header, elem.buffer[:elem.padding+MessageTransportHeaderSize],
nonce[:], nonce[:],
elem.packet, elem.packet,
nil, nil,
) )
cip, err := device.HeaderProtectionCipher(crypt[:HeaderCipherNonceSize])
if err != nil {
device.log.Errorf("Routing: header obfuscation failed - packet dropped")
elem.packet = nil
continue
}
if cip != nil {
cip.XORKeyStream(header, header)
}
} }
elemsContainer.Unlock() elemsContainer.Unlock()
} }
@@ -572,19 +676,10 @@ 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) != MessageKeepaliveSize { if !elem.isKeepalive {
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)
} }
+2 -2
View File
@@ -3,8 +3,8 @@
package device package device
import ( import (
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/rwcancel" "github.com/amnezia-vpn/amneziawg-go/v3/rwcancel"
) )
func (device *Device) startRouteListener(_ conn.Bind) (*rwcancel.RWCancel, error) { func (device *Device) startRouteListener(_ conn.Bind) (*rwcancel.RWCancel, error) {
+2 -2
View File
@@ -20,8 +20,8 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/rwcancel" "github.com/amnezia-vpn/amneziawg-go/v3/rwcancel"
) )
func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, error) { func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, error) {
+124 -27
View File
@@ -22,24 +22,25 @@ type Timer struct {
*time.Timer *time.Timer
modifyingLock sync.RWMutex modifyingLock sync.RWMutex
runningLock sync.Mutex runningLock sync.Mutex
isPending bool duration time.Duration
} }
func (peer *Peer) NewTimer(expirationFunction func(*Peer)) *Timer { func (peer *Peer) NewTimer(expirationFunction func(*Peer, time.Duration)) *Timer {
timer := &Timer{} timer := &Timer{}
timer.Timer = time.AfterFunc(time.Hour, func() { timer.Timer = time.AfterFunc(time.Hour, func() {
timer.runningLock.Lock() timer.runningLock.Lock()
defer timer.runningLock.Unlock() defer timer.runningLock.Unlock()
timer.modifyingLock.Lock() timer.modifyingLock.Lock()
if !timer.isPending { if timer.duration == 0 {
timer.modifyingLock.Unlock() timer.modifyingLock.Unlock()
return return
} }
timer.isPending = false duration := timer.duration
timer.modifyingLock.Unlock() timer.modifyingLock.Unlock()
timer.duration = 0
expirationFunction(peer) expirationFunction(peer, duration)
}) })
timer.Stop() timer.Stop()
return timer return timer
@@ -47,14 +48,14 @@ func (peer *Peer) NewTimer(expirationFunction func(*Peer)) *Timer {
func (timer *Timer) Mod(d time.Duration) { func (timer *Timer) Mod(d time.Duration) {
timer.modifyingLock.Lock() timer.modifyingLock.Lock()
timer.isPending = true timer.duration = d
timer.Reset(d) timer.Reset(d)
timer.modifyingLock.Unlock() timer.modifyingLock.Unlock()
} }
func (timer *Timer) Del() { func (timer *Timer) Del() {
timer.modifyingLock.Lock() timer.modifyingLock.Lock()
timer.isPending = false timer.duration = 0
timer.Stop() timer.Stop()
timer.modifyingLock.Unlock() timer.modifyingLock.Unlock()
} }
@@ -69,16 +70,18 @@ func (timer *Timer) DelSync() {
func (timer *Timer) IsPending() bool { func (timer *Timer) IsPending() bool {
timer.modifyingLock.RLock() timer.modifyingLock.RLock()
defer timer.modifyingLock.RUnlock() defer timer.modifyingLock.RUnlock()
return timer.isPending return timer.duration > 0
} }
func (peer *Peer) timersActive() bool { func (peer *Peer) timersActive() bool {
return peer.isRunning.Load() && peer.device != nil && peer.device.isUp() return peer.isRunning.Load() && peer.device != nil && peer.device.isUp()
} }
func expiredRetransmitHandshake(peer *Peer) { func expiredRetransmitHandshake(peer *Peer, d time.Duration) {
if peer.timers.handshakeAttempts.Load() > MaxTimerHandshakes { maxAttempts := peer.timers.maxHandshakeAttempts.Load()
peer.device.log.Verbosef("%s - Handshake did not complete after %d attempts, giving up", peer, MaxTimerHandshakes+2)
if peer.timers.handshakeAttempts.Load() > maxAttempts {
peer.device.log.Verbosef("%s - Handshake did not complete after %d attempts, giving up", peer, maxAttempts+2)
if peer.timersActive() { if peer.timersActive() {
peer.timers.sendKeepalive.Del() peer.timers.sendKeepalive.Del()
@@ -93,11 +96,11 @@ func expiredRetransmitHandshake(peer *Peer) {
* of a partial exchange. * of a partial exchange.
*/ */
if peer.timersActive() && !peer.timers.zeroKeyMaterial.IsPending() { if peer.timersActive() && !peer.timers.zeroKeyMaterial.IsPending() {
peer.timers.zeroKeyMaterial.Mod(RejectAfterTime * 3) peer.timers.zeroKeyMaterial.Mod(peer.device.keychainExpireTime() * 3)
} }
} else { } else {
peer.timers.handshakeAttempts.Add(1) peer.timers.handshakeAttempts.Add(1)
peer.device.log.Verbosef("%s - Handshake did not complete after %d seconds, retrying (try %d)", peer, int(RekeyTimeout.Seconds()), peer.timers.handshakeAttempts.Load()+1) peer.device.log.Verbosef("%s - Handshake did not complete after %d seconds, retrying (try %d)", peer, int(d.Seconds()), peer.timers.handshakeAttempts.Load()+1)
/* We clear the endpoint address src address, in case this is the cause of trouble. */ /* We clear the endpoint address src address, in case this is the cause of trouble. */
peer.markEndpointSrcForClearing() peer.markEndpointSrcForClearing()
@@ -106,30 +109,30 @@ func expiredRetransmitHandshake(peer *Peer) {
} }
} }
func expiredSendKeepalive(peer *Peer) { func expiredSendKeepalive(peer *Peer, d time.Duration) {
peer.SendKeepalive() peer.SendKeepalive()
if peer.timers.needAnotherKeepalive.Load() { if peer.timers.needAnotherKeepalive.Load() {
peer.timers.needAnotherKeepalive.Store(false) peer.timers.needAnotherKeepalive.Store(false)
if peer.timersActive() { if peer.timersActive() {
peer.timers.sendKeepalive.Mod(KeepaliveTimeout) peer.timers.sendKeepalive.Mod(peer.sendKeepaliveTimeout())
} }
} }
} }
func expiredNewHandshake(peer *Peer) { func expiredNewHandshake(peer *Peer, d time.Duration) {
peer.device.log.Verbosef("%s - Retrying handshake because we stopped hearing back after %d seconds", peer, int((KeepaliveTimeout + RekeyTimeout).Seconds())) peer.device.log.Verbosef("%s - Retrying handshake because we stopped hearing back after %d seconds", peer, int(d.Seconds()))
/* We clear the endpoint address src address, in case this is the cause of trouble. */ /* We clear the endpoint address src address, in case this is the cause of trouble. */
peer.markEndpointSrcForClearing() peer.markEndpointSrcForClearing()
peer.SendHandshakeInitiation(false) peer.SendHandshakeInitiation(false)
} }
func expiredZeroKeyMaterial(peer *Peer) { func expiredZeroKeyMaterial(peer *Peer, d time.Duration) {
peer.device.log.Verbosef("%s - Removing all keys, since we haven't received a new one in %d seconds", peer, int((RejectAfterTime * 3).Seconds())) peer.device.log.Verbosef("%s - Removing all keys, since we haven't received a new one in %d seconds", peer, int(d.Seconds()))
peer.ZeroAndFlushAll() peer.ZeroAndFlushAll()
} }
func expiredPersistentKeepalive(peer *Peer) { func expiredPersistentKeepalive(peer *Peer, d time.Duration) {
if peer.persistentKeepaliveInterval.Load() > 0 { if !peer.persistentKeepaliveInterval.Load().IsZero() {
peer.SendKeepalive() peer.SendKeepalive()
} }
} }
@@ -137,7 +140,7 @@ func expiredPersistentKeepalive(peer *Peer) {
/* Should be called after an authenticated data packet is sent. */ /* Should be called after an authenticated data packet is sent. */
func (peer *Peer) timersDataSent() { func (peer *Peer) timersDataSent() {
if peer.timersActive() && !peer.timers.newHandshake.IsPending() { if peer.timersActive() && !peer.timers.newHandshake.IsPending() {
peer.timers.newHandshake.Mod(KeepaliveTimeout + RekeyTimeout + time.Millisecond*time.Duration(fastrandn(RekeyTimeoutJitterMaxMs))) peer.timers.newHandshake.Mod(peer.newHandshakeTimeout() + time.Millisecond*time.Duration(fastrandn(RekeyTimeoutJitterMaxMs)))
} }
} }
@@ -145,7 +148,7 @@ func (peer *Peer) timersDataSent() {
func (peer *Peer) timersDataReceived() { func (peer *Peer) timersDataReceived() {
if peer.timersActive() { if peer.timersActive() {
if !peer.timers.sendKeepalive.IsPending() { if !peer.timers.sendKeepalive.IsPending() {
peer.timers.sendKeepalive.Mod(KeepaliveTimeout) peer.timers.sendKeepalive.Mod(peer.sendKeepaliveTimeout())
} else { } else {
peer.timers.needAnotherKeepalive.Store(true) peer.timers.needAnotherKeepalive.Store(true)
} }
@@ -169,7 +172,7 @@ func (peer *Peer) timersAnyAuthenticatedPacketReceived() {
/* Should be called after a handshake initiation message is sent. */ /* Should be called after a handshake initiation message is sent. */
func (peer *Peer) timersHandshakeInitiated() { func (peer *Peer) timersHandshakeInitiated() {
if peer.timersActive() { if peer.timersActive() {
peer.timers.retransmitHandshake.Mod(RekeyTimeout + time.Millisecond*time.Duration(fastrandn(RekeyTimeoutJitterMaxMs))) peer.timers.retransmitHandshake.Mod(peer.retransmitHandshakeTimeout() + time.Millisecond*time.Duration(fastrandn(RekeyTimeoutJitterMaxMs)))
} }
} }
@@ -179,6 +182,7 @@ func (peer *Peer) timersHandshakeComplete() {
peer.timers.retransmitHandshake.Del() peer.timers.retransmitHandshake.Del()
} }
peer.timers.handshakeAttempts.Store(0) peer.timers.handshakeAttempts.Store(0)
peer.timers.maxHandshakeAttempts.Store(peer.device.maxHandshakeAttemps())
peer.timers.sentLastMinuteHandshake.Store(false) peer.timers.sentLastMinuteHandshake.Store(false)
peer.lastHandshakeNano.Store(time.Now().UnixNano()) peer.lastHandshakeNano.Store(time.Now().UnixNano())
} }
@@ -186,15 +190,15 @@ func (peer *Peer) timersHandshakeComplete() {
/* Should be called after an ephemeral key is created, which is before sending a handshake response or after receiving a handshake response. */ /* Should be called after an ephemeral key is created, which is before sending a handshake response or after receiving a handshake response. */
func (peer *Peer) timersSessionDerived() { func (peer *Peer) timersSessionDerived() {
if peer.timersActive() { if peer.timersActive() {
peer.timers.zeroKeyMaterial.Mod(RejectAfterTime * 3) peer.timers.zeroKeyMaterial.Mod(peer.device.keychainExpireTime() * 3)
} }
} }
/* Should be called before a packet with authentication -- keepalive, data, or handshake -- is sent, or after one is received. */ /* Should be called before a packet with authentication -- keepalive, data, or handshake -- is sent, or after one is received. */
func (peer *Peer) timersAnyAuthenticatedPacketTraversal() { func (peer *Peer) timersAnyAuthenticatedPacketTraversal() {
keepalive := peer.persistentKeepaliveInterval.Load() keepalive := peer.persistentKeepaliveInterval.Load()
if keepalive > 0 && peer.timersActive() { if !keepalive.IsZero() && peer.timersActive() {
peer.timers.persistentKeepalive.Mod(time.Duration(keepalive) * time.Second) peer.timers.persistentKeepalive.Mod(time.Duration(keepalive.PickOne()) * time.Second)
} }
} }
@@ -208,6 +212,7 @@ func (peer *Peer) timersInit() {
func (peer *Peer) timersStart() { func (peer *Peer) timersStart() {
peer.timers.handshakeAttempts.Store(0) peer.timers.handshakeAttempts.Store(0)
peer.timers.maxHandshakeAttempts.Store(peer.device.maxHandshakeAttemps())
peer.timers.sentLastMinuteHandshake.Store(false) peer.timers.sentLastMinuteHandshake.Store(false)
peer.timers.needAnotherKeepalive.Store(false) peer.timers.needAnotherKeepalive.Store(false)
} }
@@ -219,3 +224,95 @@ func (peer *Peer) timersStop() {
peer.timers.zeroKeyMaterial.DelSync() peer.timers.zeroKeyMaterial.DelSync()
peer.timers.persistentKeepalive.DelSync() peer.timers.persistentKeepalive.DelSync()
} }
func (peer *Peer) retransmitHandshakeTimeout() time.Duration {
timeout := RekeyTimeout
if t := peer.device.timings.rekeyTimeoutSec.Load(); !t.IsZero() {
timeout = time.Duration(t.PickOne()) * time.Second
}
return timeout
}
func (peer *Peer) sendKeepaliveTimeout() time.Duration {
timeout := KeepaliveTimeout
if t := peer.device.timings.keepaliveTimeoutSec.Load(); !t.IsZero() {
timeout = time.Duration(t.PickOne()) * time.Second
}
return timeout
}
func (peer *Peer) newHandshakeTimeout() time.Duration {
keepaliveTimeout := KeepaliveTimeout
rekeyTimeout := RekeyTimeout
if t := peer.device.timings.keepaliveTimeoutSec.Load(); !t.IsZero() {
keepaliveTimeout = time.Duration(t.Hi()) * time.Second
}
if t := peer.device.timings.rekeyTimeoutSec.Load(); !t.IsZero() {
rekeyTimeout = time.Duration(t.PickOne()) * time.Second
}
return keepaliveTimeout + rekeyTimeout
}
func (device *Device) keyRefreshTimeoutSending() time.Duration {
rekeyAfterTime := RekeyAfterTime
if t := device.timings.rekeyAfterTimeSec.Load(); !t.IsZero() {
rekeyAfterTime = time.Duration(t.PickOne()) * time.Second
}
return rekeyAfterTime
}
func (device *Device) keyRefreshTimeoutReceiving() time.Duration {
rejectAfterTime := RejectAfterTime
keepaliveTimeout := KeepaliveTimeout
rekeyTimeout := RekeyTimeout
if t := device.timings.rejectAfterTimeSec.Load(); !t.IsZero() {
rejectAfterTime = time.Duration(t.PickOne()) * time.Second
}
if t := device.timings.keepaliveTimeoutSec.Load(); !t.IsZero() {
keepaliveTimeout = time.Duration(t.Lo()) * time.Second
}
if t := device.timings.rekeyTimeoutSec.Load(); !t.IsZero() {
rekeyTimeout = time.Duration(t.Lo()) * time.Second
}
return max(0, rejectAfterTime-keepaliveTimeout-rekeyTimeout)
}
func (device *Device) keychainExpireTime() time.Duration {
rejectAfterTime := RejectAfterTime
if t := device.timings.rejectAfterTimeSec.Load(); !t.IsZero() {
rejectAfterTime = time.Duration(t.Hi()) * time.Second
}
return rejectAfterTime
}
func (device *Device) rekeyMinTimeout() time.Duration {
rekeyTimeout := RekeyTimeout
if t := device.timings.rekeyTimeoutSec.Load(); !t.IsZero() {
rekeyTimeout = time.Duration(t.Lo()) * time.Second
}
return rekeyTimeout
}
func (device *Device) maxHandshakeAttemps() uint32 {
res := uint32(MaxTimerHandshakes)
if t := device.timings.maxHandshakeAttemps.Load(); !t.IsZero() {
res = t.PickOne()
}
return res
}
+1 -1
View File
@@ -8,7 +8,7 @@ package device
import ( import (
"fmt" "fmt"
"github.com/amnezia-vpn/amneziawg-go/tun" "github.com/amnezia-vpn/amneziawg-go/v3/tun"
) )
const DefaultMTU = 1420 const DefaultMTU = 1420
+245 -111
View File
@@ -18,7 +18,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/amnezia-vpn/amneziawg-go/ipc" "github.com/amnezia-vpn/amneziawg-go/v3/ipc"
) )
type IPCError struct { type IPCError struct {
@@ -70,6 +70,18 @@ func (device *Device) IpcGetOperation(w io.Writer) error {
} }
buf.WriteByte('\n') buf.WriteByte('\n')
} }
boolf := func(prefix string, val bool) {
buf.Grow(3 + len(prefix))
buf.WriteString(prefix)
buf.WriteByte('=')
if val {
buf.WriteByte('1')
} else {
buf.WriteByte('0')
}
buf.WriteByte('\n')
}
func() { func() {
// lock required resources // lock required resources
@@ -83,6 +95,9 @@ func (device *Device) IpcGetOperation(w io.Writer) error {
device.peers.RLock() device.peers.RLock()
defer device.peers.RUnlock() defer device.peers.RUnlock()
device.headerProtection.RLock()
defer device.headerProtection.RUnlock()
// serialize device related values // serialize device related values
if !device.staticIdentity.privateKey.IsZero() { if !device.staticIdentity.privateKey.IsZero() {
@@ -97,48 +112,48 @@ 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 { if count := device.junk.count.Load(); count != 0 {
sendf("jc=%d", device.junk.count) sendf("jc=%d", count)
} }
if device.junk.min != 0 { if min := device.junk.min.Load(); min != 0 {
sendf("jmin=%d", device.junk.min) sendf("jmin=%d", min)
} }
if device.junk.max != 0 { if max := device.junk.max.Load(); max != 0 {
sendf("jmax=%d", device.junk.max) sendf("jmax=%d", max)
} }
if device.paddings.init != 0 { if padding := device.paddings.init.Load(); padding != 0 {
sendf("s1=%d", device.paddings.init) sendf("s1=%d", padding)
} }
if device.paddings.response != 0 { if padding := device.paddings.response.Load(); padding != 0 {
sendf("s2=%d", device.paddings.response) sendf("s2=%d", padding)
} }
if device.paddings.cookie != 0 { if padding := device.paddings.cookie.Load(); padding != 0 {
sendf("s3=%d", device.paddings.cookie) sendf("s3=%d", padding)
} }
if device.paddings.transport != 0 { if padding := device.paddings.transport.Load(); padding != 0 {
sendf("s4=%d", device.paddings.transport) sendf("s4=%d", padding)
} }
if device.headers.init != nil { if header := device.headers.init.Load(); !header.IsZero() {
sendf("h1=%s", device.headers.init.GenSpec()) sendf("h1=%s", header.ToString())
} }
if device.headers.response != nil { if header := device.headers.response.Load(); !header.IsZero() {
sendf("h2=%s", device.headers.response.GenSpec()) sendf("h2=%s", header.ToString())
} }
if device.headers.cookie != nil { if header := device.headers.cookie.Load(); !header.IsZero() {
sendf("h3=%s", device.headers.cookie.GenSpec()) sendf("h3=%s", header.ToString())
} }
if device.headers.transport != nil { if header := device.headers.transport.Load(); !header.IsZero() {
sendf("h4=%s", device.headers.transport.GenSpec()) sendf("h4=%s", header.ToString())
} }
for i, ipacket := range device.ipackets { for i, ipacket := range device.ipackets {
@@ -147,6 +162,32 @@ func (device *Device) IpcGetOperation(w io.Writer) error {
} }
} }
if !device.headerProtection.key.IsZero() {
keyf("header_protection_key", (*[32]byte)(&device.headerProtection.key))
}
if addition := device.contentPaddingAddition.Load(); !addition.IsZero() {
sendf("content_padding_addition=%s", addition.ToString())
}
if timing := device.timings.rekeyAfterTimeSec.Load(); !timing.IsZero() {
sendf("rekey_after_time=%s", timing.ToString())
}
if timing := device.timings.rekeyTimeoutSec.Load(); !timing.IsZero() {
sendf("rekey_timeout=%s", timing.ToString())
}
if timing := device.timings.rejectAfterTimeSec.Load(); !timing.IsZero() {
sendf("reject_after_time=%s", timing.ToString())
}
if timing := device.timings.keepaliveTimeoutSec.Load(); !timing.IsZero() {
sendf("keepalive_timeout=%s", timing.ToString())
}
if rang := device.timings.maxHandshakeAttemps.Load(); !rang.IsZero() {
sendf("max_handshake_attempts=%s", rang.ToString())
}
boolf("random_trailers", device.randomTrailers.Load())
boolf("disable_cookies", device.disableCookies.Load())
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()
@@ -168,7 +209,10 @@ func (device *Device) IpcGetOperation(w io.Writer) error {
sendf("last_handshake_time_nsec=%d", nano) sendf("last_handshake_time_nsec=%d", nano)
sendf("tx_bytes=%d", peer.txBytes.Load()) sendf("tx_bytes=%d", peer.txBytes.Load())
sendf("rx_bytes=%d", peer.rxBytes.Load()) sendf("rx_bytes=%d", peer.rxBytes.Load())
sendf("persistent_keepalive_interval=%d", peer.persistentKeepaliveInterval.Load())
if keepalive := peer.persistentKeepaliveInterval.Load(); !keepalive.IsZero() {
sendf("persistent_keepalive_interval=%s", keepalive.ToString())
}
device.allowedips.EntriesForPeer(peer, func(prefix netip.Prefix) bool { device.allowedips.EntriesForPeer(peer, func(prefix netip.Prefix) bool {
sendf("allowed_ip=%s", prefix.String()) sendf("allowed_ip=%s", prefix.String())
@@ -198,6 +242,7 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
}() }()
ipcDev := new(ipcSetDevice) ipcDev := new(ipcSetDevice)
ipcDev.fromDevice(device)
peer := new(ipcSetPeer) peer := new(ipcSetPeer)
deviceConfig := true deviceConfig := true
@@ -237,7 +282,7 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
var err error var err error
if deviceConfig { if deviceConfig {
err = device.handleDeviceLine(key, value) err = device.handleDeviceLine(ipcDev, key, value)
} else { } else {
err = device.handlePeerLine(peer, key, value) err = device.handlePeerLine(peer, key, value)
} }
@@ -257,7 +302,7 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
return nil return nil
} }
func (device *Device) handleDeviceLine(key, value string) error { func (device *Device) handleDeviceLine(ipcDev *ipcSetDevice, key, value string) error {
switch key { switch key {
case "private_key": case "private_key":
var sk NoisePrivateKey var sk NoisePrivateKey
@@ -308,109 +353,87 @@ func (device *Device) handleDeviceLine(key, value string) error {
device.RemoveAllPeers() device.RemoveAllPeers()
case "jc": case "jc":
jc, err := strconv.Atoi(value) jc, err := strconv.ParseUint(value, 10, 32)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jc: %w", err) 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.log.Verbosef("UAPI: Updating junk count")
device.junk.count = jc device.junk.count.Store(uint32(jc))
case "jmin": case "jmin":
jmin, err := strconv.Atoi(value) jmin, err := strconv.ParseUint(value, 10, 32)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jmin: %w", err) 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.log.Verbosef("UAPI: Updating junk min")
device.junk.min = jmin device.junk.min.Store(uint32(jmin))
case "jmax": case "jmax":
jmax, err := strconv.Atoi(value) jmax, err := strconv.ParseUint(value, 10, 32)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jmax: %w", err) 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.log.Verbosef("UAPI: Updating junk max")
device.junk.max = jmax device.junk.max.Store(uint32(jmax))
case "s1": case "s1":
padding, err := strconv.Atoi(value) padding, err := strconv.ParseUint(value, 10, 16)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s1: %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s1: %w", err)
} }
if padding < 0 { ipcDev.paddings.init = uint32(padding)
return ipcErrorf(ipc.IpcErrorInvalid, "s1 must be non-negative")
}
device.log.Verbosef("UAPI: Updating s1 padding")
device.paddings.init = padding
case "s2": case "s2":
padding, err := strconv.Atoi(value) padding, err := strconv.ParseUint(value, 10, 16)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s2: %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s2: %w", err)
} }
if padding < 0 { ipcDev.paddings.response = uint32(padding)
return ipcErrorf(ipc.IpcErrorInvalid, "s2 must be non-negative")
}
device.log.Verbosef("UAPI: Updating s2 padding")
device.paddings.response = padding
case "s3": case "s3":
padding, err := strconv.Atoi(value) padding, err := strconv.ParseUint(value, 10, 16)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s3: %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s3: %w", err)
} }
if padding < 0 { ipcDev.paddings.cookie = uint32(padding)
return ipcErrorf(ipc.IpcErrorInvalid, "s3 must be non-negative")
}
device.log.Verbosef("UAPI: Updating s3 padding")
device.paddings.cookie = padding
case "s4": case "s4":
padding, err := strconv.Atoi(value) padding, err := strconv.ParseUint(value, 10, 16)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s4: %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s4: %w", err)
} }
if padding < 0 { ipcDev.paddings.transport = uint32(padding)
return ipcErrorf(ipc.IpcErrorInvalid, "s4 must be non-negative")
}
device.log.Verbosef("UAPI: Updating s4 padding")
device.paddings.transport = padding
case "h1": case "h1":
header, err := newMagicHeader(value) var rang UintRange
if err != nil { if err := rang.FromString(value); err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H1: %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H1: %w", err)
} }
device.headers.init = header ipcDev.headers.init = rang
case "h2": case "h2":
header, err := newMagicHeader(value) var rang UintRange
if err != nil { if err := rang.FromString(value); err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H2: %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H2: %w", err)
} }
device.headers.response = header ipcDev.headers.response = rang
case "h3": case "h3":
header, err := newMagicHeader(value) var rang UintRange
if err != nil { if err := rang.FromString(value); err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H3: %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H3: %w", err)
} }
device.headers.cookie = header ipcDev.headers.cookie = rang
case "h4": case "h4":
header, err := newMagicHeader(value) var rang UintRange
if err != nil { if err := rang.FromString(value); err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H4: %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H4: %w", err)
} }
device.headers.transport = header ipcDev.headers.transport = rang
case "i1": case "i1":
chain, err := newObfChain(value) chain, err := newObfChain(value)
@@ -447,6 +470,79 @@ func (device *Device) handleDeviceLine(key, value string) error {
} }
device.ipackets[4] = chain device.ipackets[4] = chain
case "header_protection_key":
var key HeaderCipherKey
err := key.FromHex(value)
if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set header_protection_key: %w", err)
}
ipcDev.headerProtectionKey = key
case "content_padding_addition":
var rang UintRange
if err := rang.FromString(value); err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse content_padding_addition: %w", err)
}
device.log.Verbosef("UAPI: Updating content padding addition")
device.contentPaddingAddition.Store(rang)
case "rekey_after_time":
var rang UintRange
if err := rang.FromString(value); err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse rekey after time: %w", err)
}
device.log.Verbosef("UAPI: Updating rekey after time")
device.timings.rekeyAfterTimeSec.Store(rang)
case "rekey_timeout":
var rang UintRange
if err := rang.FromString(value); err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse rekey timeout: %w", err)
}
device.log.Verbosef("UAPI: Updating rekey timeout")
device.timings.rekeyTimeoutSec.Store(rang)
case "reject_after_time":
var rang UintRange
if err := rang.FromString(value); err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse reject after time: %w", err)
}
device.log.Verbosef("UAPI: Updating reject after time")
device.timings.rejectAfterTimeSec.Store(rang)
case "keepalive_timeout":
var rang UintRange
if err := rang.FromString(value); err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse keepalive timeout: %w", err)
}
device.log.Verbosef("UAPI: Updating keepalive timeout")
device.timings.keepaliveTimeoutSec.Store(rang)
case "max_handshake_attempts":
var rang UintRange
if err := rang.FromString(value); err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse max handshake attempts: %w", err)
}
device.log.Verbosef("UAPI: Updating max handshake attempts")
device.timings.maxHandshakeAttemps.Store(rang)
case "random_trailers":
val, err := strconv.ParseBool(value)
if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse random trailers: %w", err)
}
device.log.Verbosef("UAPI: Updating random trailers")
device.randomTrailers.Store(val)
case "disable_cookies":
val, err := strconv.ParseBool(value)
if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse disable cookies: %w", err)
}
device.log.Verbosef("UAPI: Updating disable cookies")
device.disableCookies.Store(val)
default: default:
return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI device key: %v", key) return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI device key: %v", key)
} }
@@ -567,19 +663,15 @@ func (device *Device) handlePeerLine(
case "persistent_keepalive_interval": case "persistent_keepalive_interval":
device.log.Verbosef("%v - UAPI: Updating persistent keepalive interval", peer.Peer) device.log.Verbosef("%v - UAPI: Updating persistent keepalive interval", peer.Peer)
secs, err := strconv.ParseUint(value, 10, 16) var rang UintRange
if err != nil { if err := rang.FromString(value); err != nil {
return ipcErrorf( return ipcErrorf(ipc.IpcErrorInvalid, "failed to set persistent keepalive interval: %w", err)
ipc.IpcErrorInvalid,
"failed to set persistent keepalive interval: %w",
err,
)
} }
old := peer.persistentKeepaliveInterval.Swap(uint32(secs)) old := peer.persistentKeepaliveInterval.Swap(rang)
// Send immediate keepalive if we're turning it on and before it wasn't on. // Send immediate keepalive if we're turning it on and before it wasn't on.
peer.pkaOn = old == 0 && secs != 0 peer.pkaOn = old.IsZero() && !rang.IsZero()
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)
@@ -698,46 +790,88 @@ func (device *Device) IpcHandle(socket net.Conn) {
type ipcSetDevice struct { type ipcSetDevice struct {
headers struct { headers struct {
init *magicHeader init UintRange
response *magicHeader response UintRange
cookie *magicHeader cookie UintRange
transport *magicHeader transport UintRange
} }
paddings struct {
init uint32
response uint32
cookie uint32
transport uint32
}
headerProtectionKey HeaderCipherKey
}
func (d *ipcSetDevice) fromDevice(device *Device) {
device.headerProtection.RLock()
defer device.headerProtection.RUnlock()
d.headers.init = device.headers.init.Load()
d.headers.response = device.headers.response.Load()
d.headers.cookie = device.headers.cookie.Load()
d.headers.transport = device.headers.transport.Load()
d.paddings.init = device.paddings.init.Load()
d.paddings.response = device.paddings.response.Load()
d.paddings.cookie = device.paddings.cookie.Load()
d.paddings.transport = device.paddings.transport.Load()
d.headerProtectionKey = device.headerProtection.key
} }
func (d *ipcSetDevice) mergeWithDevice(device *Device) error { func (d *ipcSetDevice) mergeWithDevice(device *Device) error {
if d.headers.init == nil { device.headerProtection.Lock()
d.headers.init = device.headers.init defer device.headerProtection.Unlock()
}
if d.headers.response == nil { headers := []UintRange{d.headers.init, d.headers.response, d.headers.cookie, d.headers.transport}
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 i := 0; i < len(headers); i++ {
for j := i + 1; j < len(headers); j++ { for j := i + 1; j < len(headers); j++ {
left := headers[i] left := headers[i]
right := headers[j] right := headers[j]
if left.start <= right.end && right.start <= left.end { if left.Overlap(right) {
return errors.New("headers must not overlap") return errors.New("headers must not overlap")
} }
} }
} }
device.headers.init = d.headers.init device.log.Verbosef("UAPI: Updating h1 padding")
device.headers.response = d.headers.response device.headers.init.Store(d.headers.init)
device.headers.cookie = d.headers.cookie
device.headers.transport = d.headers.transport device.log.Verbosef("UAPI: Updating h2 padding")
device.headers.response.Store(d.headers.response)
device.log.Verbosef("UAPI: Updating h3 padding")
device.headers.cookie.Store(d.headers.cookie)
device.log.Verbosef("UAPI: Updating h4 padding")
device.headers.transport.Store(d.headers.transport)
if !d.headerProtectionKey.IsZero() {
paddings := []uint32{d.paddings.init, d.paddings.response, d.paddings.cookie, d.paddings.transport}
for i, padding := range paddings {
if padding < HeaderCipherNonceSize {
return fmt.Errorf("S%d must be more then %d to use headerProtection", i, HeaderCipherNonceSize)
}
}
}
device.log.Verbosef("UAPI: Updating s1 padding")
device.paddings.init.Store(d.paddings.init)
device.log.Verbosef("UAPI: Updating s2 padding")
device.paddings.response.Store(d.paddings.response)
device.log.Verbosef("UAPI: Updating s3 padding")
device.paddings.cookie.Store(d.paddings.cookie)
device.log.Verbosef("UAPI: Updating s4 padding")
device.paddings.transport.Store(d.paddings.transport)
device.log.Verbosef("UAPI: Updating header protection key")
device.headerProtection.key = d.headerProtectionKey
return nil return nil
} }
+14 -11
View File
@@ -1,13 +1,13 @@
module github.com/amnezia-vpn/amneziawg-go module github.com/amnezia-vpn/amneziawg-go/v3
go 1.24.4 go 1.25.0
require ( require (
github.com/stretchr/testify v1.10.0 github.com/goccy/go-yaml v1.17.1
github.com/tevino/abool v1.2.0
go.uber.org/atomic v1.11.0 go.uber.org/atomic v1.11.0
golang.getoutline.org/sdk v0.0.23
golang.getoutline.org/sdk/x v0.2.0
golang.org/x/crypto v0.42.0 golang.org/x/crypto v0.42.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.44.0 golang.org/x/net v0.44.0
golang.org/x/sys v0.36.0 golang.org/x/sys v0.36.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
@@ -15,18 +15,21 @@ require (
) )
require ( require (
github.com/Jigsaw-Code/outline-sdk v0.0.20 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/Jigsaw-Code/outline-sdk/x v0.0.8 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/goccy/go-yaml v1.17.1 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/onsi/ginkgo/v2 v2.12.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.48.1 // indirect
github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect
github.com/stretchr/testify v1.10.0 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b // indirect golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b // indirect
golang.org/x/mod v0.28.0 // indirect golang.org/x/mod v0.28.0 // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/sync v0.17.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.9.0 // indirect golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.37.0 // indirect golang.org/x/tools v0.37.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+46 -15
View File
@@ -1,34 +1,60 @@
github.com/Jigsaw-Code/outline-sdk v0.0.20 h1:4ep7MK9lFmcyPIRIbn4xrP1VKdJNsqR6+iJEOHDKnNg= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/Jigsaw-Code/outline-sdk v0.0.20/go.mod h1:CFDKyGZA4zatKE4vMLe8TyQpZCyINOeRFbMAmYHxodw= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/Jigsaw-Code/outline-sdk/x v0.0.8 h1:fFHFXW7CKhRiegyNSdP25S/WIiVrRnMKysHDoO/N2Xg= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/Jigsaw-Code/outline-sdk/x v0.0.8/go.mod h1:zqSH7yEYIQ0pYOhrr4QnodATVb5X/eZXV4AjUp9zhvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY= github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA=
github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28= github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28=
github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM= github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.getoutline.org/sdk v0.0.23 h1:UKoKCrRH3Ed6Jpg8ODYwOo6O1B1lvfIHPreMrTkSTcc=
golang.getoutline.org/sdk v0.0.23/go.mod h1:nKZlO//e/sRFk+rp8gm8EJ5RasDSyY+fGDNSd3I2iaA=
golang.getoutline.org/sdk/x v0.2.0 h1:4kuT2SgkPXktwPwT6CXuF+Jwe+COAGqiBDZbgJkS5fM=
golang.getoutline.org/sdk/x v0.2.0/go.mod h1:vyWoHW0PUsnRHdLXiDGv1Z8X48Asp2ALgwIQfJfAaNs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b h1:WX7nnnLfCEXg+FmdYZPai2XuP3VqCP1HZVMST0n9DF0= golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b h1:WX7nnnLfCEXg+FmdYZPai2XuP3VqCP1HZVMST0n9DF0=
@@ -36,28 +62,33 @@ golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b/go.mod h1:EiXZlVfUTaAyySF
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE= gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/amnezia-vpn/amneziawg-go/ipc/namedpipe" "github.com/amnezia-vpn/amneziawg-go/v3/ipc/namedpipe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"net" "net"
"os" "os"
"github.com/amnezia-vpn/amneziawg-go/rwcancel" "github.com/amnezia-vpn/amneziawg-go/v3/rwcancel"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
+1 -1
View File
@@ -8,7 +8,7 @@ package ipc
import ( import (
"net" "net"
"github.com/amnezia-vpn/amneziawg-go/ipc/namedpipe" "github.com/amnezia-vpn/amneziawg-go/v3/ipc/namedpipe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
+4 -4
View File
@@ -14,10 +14,10 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/device" "github.com/amnezia-vpn/amneziawg-go/v3/device"
"github.com/amnezia-vpn/amneziawg-go/ipc" "github.com/amnezia-vpn/amneziawg-go/v3/ipc"
"github.com/amnezia-vpn/amneziawg-go/tun" "github.com/amnezia-vpn/amneziawg-go/v3/tun"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
+4 -4
View File
@@ -12,11 +12,11 @@ import (
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/device" "github.com/amnezia-vpn/amneziawg-go/v3/device"
"github.com/amnezia-vpn/amneziawg-go/ipc" "github.com/amnezia-vpn/amneziawg-go/v3/ipc"
"github.com/amnezia-vpn/amneziawg-go/tun" "github.com/amnezia-vpn/amneziawg-go/v3/tun"
) )
const ( const (
+4 -4
View File
@@ -6,10 +6,10 @@ import (
"net" "net"
"net/netip" "net/netip"
"github.com/Jigsaw-Code/outline-sdk/transport" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/device"
"github.com/amnezia-vpn/amneziawg-go/device" "github.com/amnezia-vpn/amneziawg-go/v3/tun/netstack"
"github.com/amnezia-vpn/amneziawg-go/tun/netstack" "golang.getoutline.org/sdk/transport"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
) )
+69 -24
View File
@@ -9,34 +9,43 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/Jigsaw-Code/outline-sdk/transport"
"github.com/Jigsaw-Code/outline-sdk/x/mobileproxy"
"github.com/Jigsaw-Code/outline-sdk/x/smart"
"github.com/goccy/go-yaml" "github.com/goccy/go-yaml"
"golang.getoutline.org/sdk/transport"
"golang.getoutline.org/sdk/x/mobileproxy"
"golang.getoutline.org/sdk/x/smart"
) )
type DeviceConfig struct { type DeviceConfig struct {
PrivateKey string `yaml:"private_key"` PrivateKey string `yaml:"private_key"`
Address []string `yaml:"address"` Address []string `yaml:"address"`
Dns []string `yaml:"dns"` Dns []string `yaml:"dns"`
Mtu int `yaml:"mtu,omitempty"` Mtu int `yaml:"mtu,omitempty"`
Jc int `yaml:"jc,omitempty"` Jc int `yaml:"jc,omitempty"`
Jmin int `yaml:"jmin,omitempty"` Jmin int `yaml:"jmin,omitempty"`
Jmax int `yaml:"jmax,omitempty"` Jmax int `yaml:"jmax,omitempty"`
S1 int `yaml:"s1,omitempty"` S1 int `yaml:"s1,omitempty"`
S2 int `yaml:"s2,omitempty"` S2 int `yaml:"s2,omitempty"`
S3 int `yaml:"s3,omitempty"` S3 int `yaml:"s3,omitempty"`
S4 int `yaml:"s4,omitempty"` S4 int `yaml:"s4,omitempty"`
H1 string `yaml:"h1,omitempty"` H1 string `yaml:"h1,omitempty"`
H2 string `yaml:"h2,omitempty"` H2 string `yaml:"h2,omitempty"`
H3 string `yaml:"h3,omitempty"` H3 string `yaml:"h3,omitempty"`
H4 string `yaml:"h4,omitempty"` H4 string `yaml:"h4,omitempty"`
I1 string `yaml:"i1,omitempty"` I1 string `yaml:"i1,omitempty"`
I2 string `yaml:"i2,omitempty"` I2 string `yaml:"i2,omitempty"`
I3 string `yaml:"i3,omitempty"` I3 string `yaml:"i3,omitempty"`
I4 string `yaml:"i4,omitempty"` I4 string `yaml:"i4,omitempty"`
I5 string `yaml:"i5,omitempty"` I5 string `yaml:"i5,omitempty"`
Peers []PeerConfig `yaml:"peers,omitempty"` HeaderProtectionKey string `yaml:"header_protection_key,omitempty"`
ContentPaddingAddition string `yaml:"content_padding_addition,omitempty"`
RekeyAfterTime string `yaml:"rekey_after_time,omitempty"`
RekeyTimeout string `yaml:"rekey_timeout,omitempty"`
RejectAfterTime string `yaml:"reject_after_time,omitempty"`
KeepaliveTimeout string `yaml:"keepalive_timeout,omitempty"`
MaxHandshakeAttempts string `yaml:"max_handshake_attempts,omitempty"`
RandomTrailers string `yaml:"random_trailers,omitempty"`
DisableCookies string `yaml:"disable_cookies,omitempty"`
Peers []PeerConfig `yaml:"peers,omitempty"`
} }
type PeerConfig struct { type PeerConfig struct {
@@ -136,6 +145,42 @@ func genIpcString(cfg *DeviceConfig) (string, error) {
b.WriteString("\ni5=") b.WriteString("\ni5=")
b.WriteString(cfg.I5) b.WriteString(cfg.I5)
} }
if cfg.HeaderProtectionKey != "" {
b.WriteString("\nheader_protection_key=")
b.WriteString(cfg.HeaderProtectionKey)
}
if cfg.ContentPaddingAddition != "" {
b.WriteString("\ncontent_padding_addition=")
b.WriteString(cfg.ContentPaddingAddition)
}
if cfg.RekeyAfterTime != "" {
b.WriteString("\nrekey_after_time=")
b.WriteString(cfg.RekeyAfterTime)
}
if cfg.RekeyTimeout != "" {
b.WriteString("\nrekey_timeout=")
b.WriteString(cfg.RekeyTimeout)
}
if cfg.RejectAfterTime != "" {
b.WriteString("\nreject_after_time=")
b.WriteString(cfg.RejectAfterTime)
}
if cfg.KeepaliveTimeout != "" {
b.WriteString("\nkeepalive_timeout=")
b.WriteString(cfg.KeepaliveTimeout)
}
if cfg.MaxHandshakeAttempts != "" {
b.WriteString("\nmax_handshake_attempts=")
b.WriteString(cfg.MaxHandshakeAttempts)
}
if cfg.RandomTrailers != "" {
b.WriteString("\nrandom_trailers=")
b.WriteString(cfg.RandomTrailers)
}
if cfg.DisableCookies != "" {
b.WriteString("\ndisable_cookies=")
b.WriteString(cfg.DisableCookies)
}
for _, peer := range cfg.Peers { for _, peer := range cfg.Peers {
publicKeyBytes, err := base64.StdEncoding.DecodeString(peer.PublicKey) publicKeyBytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
+3 -3
View File
@@ -3,8 +3,8 @@ package outline_test
import ( import (
"testing" "testing"
"github.com/Jigsaw-Code/outline-sdk/x/mobileproxy" awg "github.com/amnezia-vpn/amneziawg-go/v3/outline"
awg "github.com/amnezia-vpn/amneziawg-go/outline" "golang.getoutline.org/sdk/x/mobileproxy"
) )
const cfg = ` const cfg = `
@@ -46,7 +46,7 @@ func Test_outlineIntegration(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err = mobileproxy.RunProxy("", dialer); err != nil { if _, err = mobileproxy.RunProxy("127.0.0.1:0", dialer); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
+3 -3
View File
@@ -13,9 +13,9 @@ import (
"net/http" "net/http"
"net/netip" "net/netip"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/device" "github.com/amnezia-vpn/amneziawg-go/v3/device"
"github.com/amnezia-vpn/amneziawg-go/tun/netstack" "github.com/amnezia-vpn/amneziawg-go/v3/tun/netstack"
) )
func main() { func main() {
+3 -3
View File
@@ -14,9 +14,9 @@ import (
"net/http" "net/http"
"net/netip" "net/netip"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/device" "github.com/amnezia-vpn/amneziawg-go/v3/device"
"github.com/amnezia-vpn/amneziawg-go/tun/netstack" "github.com/amnezia-vpn/amneziawg-go/v3/tun/netstack"
) )
func main() { func main() {
+3 -3
View File
@@ -17,9 +17,9 @@ import (
"golang.org/x/net/icmp" "golang.org/x/net/icmp"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/device" "github.com/amnezia-vpn/amneziawg-go/v3/device"
"github.com/amnezia-vpn/amneziawg-go/tun/netstack" "github.com/amnezia-vpn/amneziawg-go/v3/tun/netstack"
) )
func main() { func main() {
+1 -1
View File
@@ -22,7 +22,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/amnezia-vpn/amneziawg-go/tun" "github.com/amnezia-vpn/amneziawg-go/v3/tun"
"golang.org/x/net/dns/dnsmessage" "golang.org/x/net/dns/dnsmessage"
"gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/buffer"
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"io" "io"
"unsafe" "unsafe"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"net/netip" "net/netip"
"testing" "testing"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/header"
+2 -2
View File
@@ -17,8 +17,8 @@ import (
"time" "time"
"unsafe" "unsafe"
"github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/v3/conn"
"github.com/amnezia-vpn/amneziawg-go/rwcancel" "github.com/amnezia-vpn/amneziawg-go/v3/rwcancel"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"net/netip" "net/netip"
"os" "os"
"github.com/amnezia-vpn/amneziawg-go/tun" "github.com/amnezia-vpn/amneziawg-go/v3/tun"
) )
func Ping(dst, src netip.Addr) []byte { func Ping(dst, src netip.Addr) []byte {