mirror of
https://github.com/amnezia-vpn/amneziawg-go.git
synced 2026-10-12 22:24:18 +03:00
Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
513511c162 | ||
|
|
b6393b6f2d | ||
|
|
dffb93aa29 | ||
|
|
b5928efb6c | ||
|
|
da11c9fadc | ||
|
|
1b86b2ae0e | ||
|
|
08271d00b3 | ||
|
|
75ea550a64 | ||
|
|
1f50ad736e | ||
|
|
08d68cdae2 | ||
|
|
cf9d2dd202 | ||
|
|
7860d60a52 | ||
|
|
ce7cf1034d | ||
|
|
d57d98d55b | ||
|
|
0527dfa476 | ||
|
|
9f5d948bc7 | ||
|
|
457d920a1a | ||
|
|
c1e9bb3758 | ||
|
|
1cc94272ca | ||
|
|
3610f21b75 | ||
|
|
f4f4c99926 | ||
|
|
12a012205e | ||
|
|
e7ef4339e7 |
No files matched your search
+14
-8
@@ -1,18 +1,24 @@
|
||||
FROM golang:1.24.4 as awg
|
||||
FROM golang:1.25.12 as awg
|
||||
COPY . /awg
|
||||
WORKDIR /awg
|
||||
RUN go mod download && \
|
||||
go mod verify && \
|
||||
go build -ldflags '-linkmode external -extldflags "-fno-PIC -static"' -v -o /usr/bin
|
||||
|
||||
FROM alpine:3.19
|
||||
ARG AWGTOOLS_RELEASE="1.0.20250901"
|
||||
FROM alpine:3.19 as tools
|
||||
ARG AWGTOOLS_COMMIT="v3.1.20260812"
|
||||
|
||||
RUN apk --no-cache add iproute2 iptables bash && \
|
||||
cd /usr/bin/ && \
|
||||
wget https://github.com/amnezia-vpn/amneziawg-tools/releases/download/v${AWGTOOLS_RELEASE}/alpine-3.19-amneziawg-tools.zip && \
|
||||
unzip -j alpine-3.19-amneziawg-tools.zip && \
|
||||
chmod +x /usr/bin/awg /usr/bin/awg-quick && \
|
||||
RUN apk add --no-cache git build-base linux-headers && \
|
||||
git clone https://github.com/amnezia-vpn/amneziawg-tools.git /amneziawg-tools && \
|
||||
cd /amneziawg-tools && git checkout ${AWGTOOLS_COMMIT} && \
|
||||
cd src && make
|
||||
|
||||
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-quick /usr/bin/wg-quick
|
||||
COPY --from=awg /usr/bin/amneziawg-go /usr/bin/amneziawg-go
|
||||
@@ -53,9 +53,59 @@ $ make
|
||||
|
||||
## 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]
|
||||
> 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
- `H2: string` - header range of handshake initial message
|
||||
- `H2: string` - header range of handshake response message
|
||||
- `H3: string` - header range of handshake cookie message
|
||||
- `H4: string` - header range of transport message
|
||||
|
||||
@@ -89,12 +139,6 @@ Values could be specified as:
|
||||
- range: `x-y`, x <= y; e.g. `123-456`
|
||||
- single value `1234`
|
||||
|
||||
> [!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
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If the final size of any packet exceeds system MTU, it would be fractured into fragments, which looks suspicious
|
||||
|
||||
### Custom signature packets
|
||||
|
||||
These packets are being send prior to every handshake, in the same way as Junk packets do. The sending order is `I1`, `I2`, `I3`, `I4`, `I5`. If there is no value specified, the packet is skipped.
|
||||
@@ -105,187 +149,15 @@ These packets are being send prior to every handshake, in the same way as Junk p
|
||||
- `I4: string`
|
||||
- `I5: string`
|
||||
|
||||
Value as a tag-sequence, as described in Tag format paragraph
|
||||
Value is a sequence of tags specified below:
|
||||
- `<b 0x[seq]>` - static bytes tag. Dumps `[seq]` as-is to the packet. `[seq]` is hex-encoded sequence which represents bytes sequence (2 hex numbers per byte) and is always even-sized
|
||||
- `<r [size]>` - random bytes tag. Dumps `[size]` amount of randomly-generated bytes 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
|
||||
- `<t>` - timestamp tag. Dumps 4-bytes long current system time in UNIX format
|
||||
|
||||
### Network
|
||||
|
||||
**`Network: "udp" | "tcp"`**
|
||||
- A network which device would use to connect to peers
|
||||
- In case of `tcp`, the connection is always initiated by the peer which has `Endpoint` specified
|
||||
- In case of `tcp`, the device would not listen if `ListenPort` is omitted
|
||||
|
||||
> [!NOTE]
|
||||
> Although it is not required, `Network = tcp` would highly-likely require `FormatIn` and `FormatOut` parameters since stream connection does not have fixed-sized messages
|
||||
|
||||
### Message format
|
||||
|
||||
**`FormatIn: string of tags`**
|
||||
- A format of packet which AWG device would expect on the input side instead of the original WG packet
|
||||
- The packet could be a part of the stream if `<dz>` is specified
|
||||
|
||||
**`FormatOut: string of tags`**
|
||||
- A format of packet which AWG device would generate on the output side instead of the original WG packet. The whole packet would be generated as a single piece and would be sent to the system as-is
|
||||
|
||||
- **Example**
|
||||
```
|
||||
FormatIn = <b 0x4567><dz be 4><r 12><d><t>
|
||||
FormatOut = <b 0x1234><dz be 4><d>
|
||||
```
|
||||
> [!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
|
||||
|
||||
> [!IMPORTANT]
|
||||
> For TCP network it is really neccessary to specify `<dz>` tag somewhere because of the stream nature - the original packet could be coalecsed with the next one or splitted into the fragments
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Always put `<d>` tag in the format.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Peer's `FormatIn` must be the same as device's `FormatOut` for ***machines*** to understand each other formats
|
||||
|
||||
## Tags format
|
||||
|
||||
### Static bytes tag
|
||||
|
||||
**Description**
|
||||
- Static bytes, sends and receives specified bytes as-is with predictable size
|
||||
- Immutable, does not change after configuration
|
||||
|
||||
**Format**
|
||||
```
|
||||
<b 0x[seq]>
|
||||
```
|
||||
|
||||
**Params**
|
||||
- `[seq]` - *always even-sized* hex-encoded sequence of bytes (2 hex numbers per byte)
|
||||
|
||||
**Sample usage**
|
||||
```
|
||||
<b 0xd34db3ef>
|
||||
<b 0x1234><dz be 2><d>
|
||||
```
|
||||
|
||||
### Random bytes tag
|
||||
|
||||
**Description**
|
||||
- Randomly-generated bytes of specified size
|
||||
- Uses cryptographically secure generator
|
||||
|
||||
**Format**
|
||||
```
|
||||
<r [size]>
|
||||
```
|
||||
|
||||
**Params**
|
||||
- `[size]` - amount of random bytes
|
||||
|
||||
**Sample usage**
|
||||
```
|
||||
<r 21>
|
||||
<r 21><dz be 2><r 1><d>
|
||||
```
|
||||
|
||||
### Random digits tag
|
||||
|
||||
**Description**
|
||||
- Randomly-generated digits from `[0-9]` set
|
||||
- Uses cryptographically secure generator
|
||||
|
||||
**Format**
|
||||
```
|
||||
<rd [size]>
|
||||
```
|
||||
|
||||
**Params**
|
||||
- `[size]` - amount of digits
|
||||
|
||||
**Sample usage**
|
||||
```
|
||||
<rd 21>
|
||||
<b 0x6e756d3a><rd 21><b 0x0d0a><dz hex 0x0d><b 0x0a><rd 1><d>
|
||||
```
|
||||
|
||||
### Random characters tag
|
||||
|
||||
**Description**
|
||||
- Randomly-generated digits from `[a-zA-Z]` set
|
||||
- Uses cryptographically secure generator
|
||||
|
||||
**Format**
|
||||
```
|
||||
<rc [size]>
|
||||
```
|
||||
|
||||
**Params**
|
||||
- `[size]` - amount of chars
|
||||
|
||||
**Example**
|
||||
```
|
||||
<rc 21>
|
||||
<b 0x6e616d653a><rc 21><b 0x0d0a><dz ascii 0x0d><b 0x0a><rc 1><d>
|
||||
```
|
||||
|
||||
### Timestamp tag
|
||||
|
||||
**Description**
|
||||
- Current UNIX time, int32
|
||||
- Provided in big-endian
|
||||
|
||||
**Format**
|
||||
```
|
||||
<t>
|
||||
```
|
||||
|
||||
**Sample usage**
|
||||
```
|
||||
<t>
|
||||
<b 0x1234><t><dz be 2><d>
|
||||
```
|
||||
|
||||
### Data tag
|
||||
|
||||
**Description**
|
||||
- The original WireGuard packet
|
||||
- Has `H1-H4` and `S1-S4` params applied
|
||||
|
||||
**Format**
|
||||
```
|
||||
<d>
|
||||
```
|
||||
|
||||
**Sample usage**
|
||||
```
|
||||
<d>
|
||||
<dz hex 0x0d><b 0x0a><d>
|
||||
```
|
||||
|
||||
### Data size tag
|
||||
|
||||
**Description**
|
||||
- Size of the remaining pard of original WireGuard packet
|
||||
- `hex` and `ascii` formats are dynamic-sized and no leading zeroes
|
||||
- sender appends `[end]` to the end in case of `ascii` or `hex`
|
||||
|
||||
**Format**
|
||||
```
|
||||
<dz [fmt] [size|end]>
|
||||
```
|
||||
|
||||
**Params**
|
||||
- `[fmt]` - format of the field
|
||||
- `be` - fixed-size big-endian
|
||||
- `le` - fixed-size little-endian
|
||||
- `ascii` - decimal ascii representation
|
||||
- `hex` - hexadecimal ASCII representation
|
||||
- `[size]` - amount of bytes of the field
|
||||
- only for `be` and `le`
|
||||
- `[end]` - the symbol following the data size
|
||||
- only for `ascii` and `hex`
|
||||
|
||||
**Sample usage**
|
||||
```
|
||||
<dz ascii 0x0d> e.g. `10 -> "10\r"`
|
||||
<dz hex 0x0a> e.g. `10 -> "A\n"`
|
||||
<dz be 4> e.g. `10 -> 0x00 0x00 0x00 0x0A`
|
||||
<dz le 2> e.g. `10 -> 0x0A 0x00`
|
||||
|
||||
<dz 0x0d><b 0x0a><d><b 0x0d0a>
|
||||
```
|
||||
> If the final size of any packet exceeds system MTU, it would be fractured into fragments, which looks suspicious
|
||||
@@ -1,581 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
const (
|
||||
benchmarkMaxPacketSize = 65535
|
||||
benchmarkBatchPoolSize = 128
|
||||
benchmarkUDPListenPort = 51820
|
||||
benchmarkFixtureRingSize = 256
|
||||
)
|
||||
|
||||
var (
|
||||
benchmarkFramedOpts = FramedOpts{
|
||||
H1: benchmarkMustHeader("777"),
|
||||
H2: benchmarkMustHeader("778"),
|
||||
H3: benchmarkMustHeader("779"),
|
||||
H4: benchmarkMustHeader("780"),
|
||||
S1: 8,
|
||||
S2: 8,
|
||||
S3: 8,
|
||||
S4: 16,
|
||||
}
|
||||
benchmarkFramedCompatOpts = func() FramedOpts {
|
||||
opts := benchmarkFramedOpts
|
||||
opts.HeaderCompat = true
|
||||
return opts
|
||||
}()
|
||||
benchmarkMasqueradeRules = benchmarkMustRules("<dz be 2><d>")
|
||||
benchmarkPreludeOneRule = PreludeOpts{
|
||||
RulesArr: [5]Rules{
|
||||
benchmarkMustRules("<b 0xaabb>"),
|
||||
},
|
||||
}
|
||||
benchmarkPreludeFiveRules = PreludeOpts{
|
||||
RulesArr: [5]Rules{
|
||||
benchmarkMustRules("<b 0xa1>"),
|
||||
benchmarkMustRules("<b 0xa2a3>"),
|
||||
benchmarkMustRules("<b 0xa4a5a6>"),
|
||||
benchmarkMustRules("<b 0xa7a8a9aa>"),
|
||||
benchmarkMustRules("<b 0xabacadaeaf>"),
|
||||
},
|
||||
}
|
||||
benchmarkPreludeRulesPlusJunk = PreludeOpts{
|
||||
Jc: 1,
|
||||
Jmin: 3,
|
||||
Jmax: 3,
|
||||
RulesArr: [5]Rules{
|
||||
benchmarkMustRules("<b 0xaabb>"),
|
||||
},
|
||||
}
|
||||
benchmarkPayloads = benchmarkBuildPayloadProfiles()
|
||||
benchmarkUDPAddr = &net.UDPAddr{
|
||||
IP: net.IPv4(127, 0, 0, 1),
|
||||
Port: benchmarkUDPListenPort,
|
||||
}
|
||||
|
||||
benchmarkRandMu sync.Mutex
|
||||
)
|
||||
|
||||
type benchmarkPayloadProfiles struct {
|
||||
initiation []byte
|
||||
response []byte
|
||||
cookie []byte
|
||||
transportKeepalive []byte
|
||||
transportSmall []byte
|
||||
transportMTU []byte
|
||||
|
||||
compatInitiation []byte
|
||||
compatResponse []byte
|
||||
compatCookie []byte
|
||||
compatTransportKeepalive []byte
|
||||
compatTransportSmall []byte
|
||||
compatTransportMTU []byte
|
||||
}
|
||||
|
||||
func benchmarkBuildPayloadProfiles() benchmarkPayloadProfiles {
|
||||
return benchmarkPayloadProfiles{
|
||||
initiation: benchmarkMakePayload(WireguardMsgInitiationSize, WireguardMsgInitiationType),
|
||||
response: benchmarkMakePayload(WireguardMsgResponseSize, WireguardMsgResponseType),
|
||||
cookie: benchmarkMakePayload(WireguardMsgCookieReplySize, WireguardMsgCookieReplyType),
|
||||
transportKeepalive: benchmarkMakePayload(WireguardMsgTransportMinSize, WireguardMsgTransportType),
|
||||
transportSmall: benchmarkMakePayload(256, WireguardMsgTransportType),
|
||||
transportMTU: benchmarkMakePayload(1280, WireguardMsgTransportType),
|
||||
compatInitiation: benchmarkMakePayload(WireguardMsgInitiationSize, benchmarkFramedCompatOpts.H1.start),
|
||||
compatResponse: benchmarkMakePayload(WireguardMsgResponseSize, benchmarkFramedCompatOpts.H2.start),
|
||||
compatCookie: benchmarkMakePayload(WireguardMsgCookieReplySize, benchmarkFramedCompatOpts.H3.start),
|
||||
compatTransportKeepalive: benchmarkMakePayload(WireguardMsgTransportMinSize, benchmarkFramedCompatOpts.H4.start),
|
||||
compatTransportSmall: benchmarkMakePayload(256, benchmarkFramedCompatOpts.H4.start),
|
||||
compatTransportMTU: benchmarkMakePayload(1280, benchmarkFramedCompatOpts.H4.start),
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMakePayload(size int, header uint32) []byte {
|
||||
payload := make([]byte, size)
|
||||
binary.LittleEndian.PutUint32(payload[:4], header)
|
||||
for i := 4; i < len(payload); i++ {
|
||||
payload[i] = byte(i)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func benchmarkMustRules(spec string) Rules {
|
||||
rules, err := ParseRules(spec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
func benchmarkMustHeader(spec string) *RangedHeader {
|
||||
header, err := NewRangedHeader(spec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
func benchmarkNewBufferPool() *sync.Pool {
|
||||
return &sync.Pool{
|
||||
New: func() any {
|
||||
return make([]byte, benchmarkMaxPacketSize)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkNewMsgsPool() *sync.Pool {
|
||||
return &sync.Pool{
|
||||
New: func() any {
|
||||
msgs := make([]ipv4.Message, benchmarkBatchPoolSize)
|
||||
for i := range msgs {
|
||||
msgs[i].Buffers = make([][]byte, 1)
|
||||
msgs[i].OOB = make([]byte, 0)
|
||||
}
|
||||
return &msgs
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMaxFramePadding(opts FramedOpts) int {
|
||||
maxPadding := opts.S1
|
||||
if opts.S2 > maxPadding {
|
||||
maxPadding = opts.S2
|
||||
}
|
||||
if opts.S3 > maxPadding {
|
||||
maxPadding = opts.S3
|
||||
}
|
||||
if opts.S4 > maxPadding {
|
||||
maxPadding = opts.S4
|
||||
}
|
||||
return maxPadding
|
||||
}
|
||||
|
||||
func benchmarkEncodeFramedRecord(opts FramedOpts, payload []byte) []byte {
|
||||
enc, ok := newFrameEncoding(opts)
|
||||
if !ok {
|
||||
panic("framed benchmark encoding unavailable")
|
||||
}
|
||||
buf := make([]byte, len(payload)+benchmarkMaxFramePadding(opts))
|
||||
n := enc.Encode(buf, payload)
|
||||
if n == 0 {
|
||||
panic("framed benchmark encoding failed")
|
||||
}
|
||||
return append([]byte(nil), buf[:n]...)
|
||||
}
|
||||
|
||||
func benchmarkEncodeMasqueradeRecord(rules Rules, payload []byte) []byte {
|
||||
pool := benchmarkNewBufferPool()
|
||||
ctx := &writeContext{
|
||||
FlexBuffer: WrapFlexBuffer(payload),
|
||||
BufferPool: WrapBufferPool(pool),
|
||||
}
|
||||
tmp := pool.Get().([]byte)
|
||||
defer pool.Put(tmp)
|
||||
|
||||
w := bytes.NewBuffer(tmp[:0])
|
||||
if err := rules.Write(w, ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return append([]byte(nil), w.Bytes()...)
|
||||
}
|
||||
|
||||
func benchmarkEncodeStreamRecords(rules Rules, records ...[]byte) []byte {
|
||||
var out bytes.Buffer
|
||||
for _, record := range records {
|
||||
out.Write(benchmarkEncodeMasqueradeRecord(rules, record))
|
||||
}
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
func benchmarkRepeatPayload(payload []byte, count int) [][]byte {
|
||||
payloads := make([][]byte, count)
|
||||
for i := range payloads {
|
||||
payloads[i] = payload
|
||||
}
|
||||
return payloads
|
||||
}
|
||||
|
||||
func benchmarkInitiationBatch(batchSize int) [][]byte {
|
||||
payloads := make([][]byte, batchSize)
|
||||
if batchSize == 0 {
|
||||
return payloads
|
||||
}
|
||||
payloads[0] = benchmarkPayloads.initiation
|
||||
for i := 1; i < len(payloads); i++ {
|
||||
payloads[i] = benchmarkPayloads.transportSmall
|
||||
}
|
||||
return payloads
|
||||
}
|
||||
|
||||
func benchmarkTotalBytes(payloads [][]byte) int {
|
||||
total := 0
|
||||
for _, payload := range payloads {
|
||||
total += len(payload)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func benchmarkAverageBytes(payloads ...[]byte) int {
|
||||
if len(payloads) == 0 {
|
||||
return 0
|
||||
}
|
||||
total := 0
|
||||
for _, payload := range payloads {
|
||||
total += len(payload)
|
||||
}
|
||||
return total / len(payloads)
|
||||
}
|
||||
|
||||
func benchmarkAverageInts(values ...int) int {
|
||||
if len(values) == 0 {
|
||||
return 0
|
||||
}
|
||||
total := 0
|
||||
for _, value := range values {
|
||||
total += value
|
||||
}
|
||||
return total / len(values)
|
||||
}
|
||||
|
||||
func benchmarkFullMatrixEnabled() bool {
|
||||
return os.Getenv("AMNEZWG_BENCH_FULL") != ""
|
||||
}
|
||||
|
||||
func benchmarkRunLoop(b *testing.B, bytesPerOp int, reset func(), op func() error) {
|
||||
b.Helper()
|
||||
b.ReportAllocs()
|
||||
if bytesPerOp > 0 {
|
||||
b.SetBytes(int64(bytesPerOp))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i > 0 && reset != nil {
|
||||
b.StopTimer()
|
||||
reset()
|
||||
b.StartTimer()
|
||||
}
|
||||
if err := op(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkRunLoopWithFixtureRing[T any](b *testing.B, bytesPerOp int, fixtures []T, reset func(T), op func(T) error) {
|
||||
b.Helper()
|
||||
if len(fixtures) == 0 {
|
||||
b.Fatal("benchmark fixture ring must not be empty")
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
if bytesPerOp > 0 {
|
||||
b.SetBytes(int64(bytesPerOp))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i > 0 && i%len(fixtures) == 0 && reset != nil {
|
||||
b.StopTimer()
|
||||
for _, fixture := range fixtures {
|
||||
reset(fixture)
|
||||
}
|
||||
b.StartTimer()
|
||||
}
|
||||
if err := op(fixtures[i%len(fixtures)]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkUseDeterministicRand(b *testing.B) {
|
||||
b.Helper()
|
||||
benchmarkRandMu.Lock()
|
||||
oldReader := crand.Reader
|
||||
crand.Reader = &benchmarkDeterministicReader{state: 1}
|
||||
b.Cleanup(func() {
|
||||
crand.Reader = oldReader
|
||||
benchmarkRandMu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
type benchmarkDeterministicReader struct {
|
||||
state uint64
|
||||
}
|
||||
|
||||
func (r *benchmarkDeterministicReader) Read(p []byte) (int, error) {
|
||||
x := r.state
|
||||
if x == 0 {
|
||||
x = 1
|
||||
}
|
||||
for i := range p {
|
||||
x ^= x << 13
|
||||
x ^= x >> 7
|
||||
x ^= x << 17
|
||||
p[i] = byte(x)
|
||||
}
|
||||
r.state = x
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
type benchmarkStreamConn struct {
|
||||
readBuf []byte
|
||||
readOff int
|
||||
readChunks [][]byte
|
||||
readChunkAt int
|
||||
readChunkOff int
|
||||
}
|
||||
|
||||
func newBenchmarkStreamConn(readBuf []byte) *benchmarkStreamConn {
|
||||
return &benchmarkStreamConn{readBuf: readBuf}
|
||||
}
|
||||
|
||||
func newBenchmarkStreamChunksConn(readChunks [][]byte) *benchmarkStreamConn {
|
||||
return &benchmarkStreamConn{readChunks: readChunks}
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) ResetRead() {
|
||||
c.readOff = 0
|
||||
c.readChunkAt = 0
|
||||
c.readChunkOff = 0
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) Read(p []byte) (int, error) {
|
||||
if len(c.readChunks) > 0 {
|
||||
chunk := c.readChunks[c.readChunkAt]
|
||||
if c.readChunkOff >= len(chunk) {
|
||||
c.readChunkAt++
|
||||
if c.readChunkAt == len(c.readChunks) {
|
||||
c.readChunkAt = 0
|
||||
}
|
||||
c.readChunkOff = 0
|
||||
chunk = c.readChunks[c.readChunkAt]
|
||||
}
|
||||
n := copy(p, chunk[c.readChunkOff:])
|
||||
c.readChunkOff += n
|
||||
if c.readChunkOff == len(chunk) {
|
||||
c.readChunkAt++
|
||||
if c.readChunkAt == len(c.readChunks) {
|
||||
c.readChunkAt = 0
|
||||
}
|
||||
c.readChunkOff = 0
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
if len(c.readBuf) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if c.readOff >= len(c.readBuf) {
|
||||
c.readOff = 0
|
||||
}
|
||||
n := copy(p, c.readBuf[c.readOff:])
|
||||
c.readOff += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) LocalAddr() net.Addr {
|
||||
return benchmarkUDPAddr
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) RemoteAddr() net.Addr {
|
||||
return benchmarkUDPAddr
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) SetDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) SetReadDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type benchmarkUDPConn struct {
|
||||
readPayloads [][]byte
|
||||
readIndex int
|
||||
}
|
||||
|
||||
func newBenchmarkUDPConn(readPayloads [][]byte) *benchmarkUDPConn {
|
||||
return &benchmarkUDPConn{readPayloads: readPayloads}
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) ResetRead() {
|
||||
c.readIndex = 0
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
n, _, _, udpAddr, err := c.ReadMsgUDP(p, nil)
|
||||
return n, udpAddr, err
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
udpAddr, _ := addr.(*net.UDPAddr)
|
||||
n, _, err = c.WriteMsgUDP(p, nil, udpAddr)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) LocalAddr() net.Addr {
|
||||
return benchmarkUDPAddr
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) SetDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) SetReadDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) SyscallConn() (syscall.RawConn, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
|
||||
if len(c.readPayloads) == 0 {
|
||||
return 0, 0, 0, nil, io.EOF
|
||||
}
|
||||
payload := c.readPayloads[c.readIndex]
|
||||
c.readIndex++
|
||||
if c.readIndex == len(c.readPayloads) {
|
||||
c.readIndex = 0
|
||||
}
|
||||
n = copy(b, payload)
|
||||
return n, 0, 0, benchmarkUDPAddr, nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) {
|
||||
return len(b), len(oob), nil
|
||||
}
|
||||
|
||||
type benchmarkBatchPacket struct {
|
||||
payload []byte
|
||||
oob []byte
|
||||
addr *net.UDPAddr
|
||||
}
|
||||
|
||||
type benchmarkBatchConn struct {
|
||||
readBatches [][]benchmarkBatchPacket
|
||||
readIndex int
|
||||
}
|
||||
|
||||
func newBenchmarkBatchConn(readBatches [][]benchmarkBatchPacket) *benchmarkBatchConn {
|
||||
return &benchmarkBatchConn{readBatches: readBatches}
|
||||
}
|
||||
|
||||
func (c *benchmarkBatchConn) ResetRead() {
|
||||
c.readIndex = 0
|
||||
}
|
||||
|
||||
func (c *benchmarkBatchConn) ReadBatch(ms []ipv4.Message, flags int) (int, error) {
|
||||
if len(c.readBatches) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
batch := c.readBatches[c.readIndex]
|
||||
c.readIndex++
|
||||
if c.readIndex == len(c.readBatches) {
|
||||
c.readIndex = 0
|
||||
}
|
||||
for i := range batch {
|
||||
packet := batch[i]
|
||||
msg := &ms[i]
|
||||
msg.N = copy(msg.Buffers[0], packet.payload)
|
||||
if cap(msg.OOB) < len(packet.oob) {
|
||||
panic("benchmark batch OOB buffer too small")
|
||||
}
|
||||
msg.OOB = msg.OOB[:len(packet.oob)]
|
||||
copy(msg.OOB, packet.oob)
|
||||
msg.NN = len(packet.oob)
|
||||
msg.Addr = packet.addr
|
||||
}
|
||||
for i := len(batch); i < len(ms); i++ {
|
||||
ms[i].N = 0
|
||||
ms[i].NN = 0
|
||||
ms[i].Addr = nil
|
||||
ms[i].OOB = ms[i].OOB[:0]
|
||||
}
|
||||
return len(batch), nil
|
||||
}
|
||||
|
||||
func (c *benchmarkBatchConn) WriteBatch(ms []ipv4.Message, flags int) (int, error) {
|
||||
return len(ms), nil
|
||||
}
|
||||
|
||||
func benchmarkBatchPackets(payloads [][]byte) []benchmarkBatchPacket {
|
||||
packets := make([]benchmarkBatchPacket, len(payloads))
|
||||
for i, payload := range payloads {
|
||||
packets[i] = benchmarkBatchPacket{
|
||||
payload: payload,
|
||||
addr: benchmarkUDPAddr,
|
||||
}
|
||||
}
|
||||
return packets
|
||||
}
|
||||
|
||||
func benchmarkNewBatchReadMessages(count int, oobCap int) []ipv4.Message {
|
||||
msgs := make([]ipv4.Message, count)
|
||||
for i := range msgs {
|
||||
msgs[i].Buffers = make([][]byte, 1)
|
||||
msgs[i].Buffers[0] = make([]byte, benchmarkMaxPacketSize)
|
||||
msgs[i].OOB = make([]byte, 0, oobCap)
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
type benchmarkBatchWriteFixture struct {
|
||||
msgs []ipv4.Message
|
||||
payloads [][]byte
|
||||
addr *net.UDPAddr
|
||||
}
|
||||
|
||||
func newBenchmarkBatchWriteFixture(payloads [][]byte, oobCap int) *benchmarkBatchWriteFixture {
|
||||
fixture := &benchmarkBatchWriteFixture{
|
||||
msgs: make([]ipv4.Message, len(payloads)),
|
||||
payloads: payloads,
|
||||
addr: benchmarkUDPAddr,
|
||||
}
|
||||
for i := range fixture.msgs {
|
||||
fixture.msgs[i].Buffers = make([][]byte, 1)
|
||||
fixture.msgs[i].OOB = make([]byte, 0, oobCap)
|
||||
}
|
||||
fixture.Reset()
|
||||
return fixture
|
||||
}
|
||||
|
||||
func (f *benchmarkBatchWriteFixture) Reset() {
|
||||
for i, payload := range f.payloads {
|
||||
f.msgs[i].Buffers[0] = payload
|
||||
f.msgs[i].N = len(payload)
|
||||
f.msgs[i].NN = 0
|
||||
f.msgs[i].Addr = f.addr
|
||||
f.msgs[i].OOB = f.msgs[i].OOB[:0]
|
||||
}
|
||||
}
|
||||
@@ -1,901 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkTCPRawConn(b *testing.B) {
|
||||
benchmarkUseDeterministicRand(b)
|
||||
if benchmarkFullMatrixEnabled() {
|
||||
benchmarkTCPRawConnFull(b)
|
||||
return
|
||||
}
|
||||
|
||||
readPayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.transportSmall,
|
||||
}
|
||||
b.Run("Read/mixed", func(b *testing.B) {
|
||||
conn := newBenchmarkStreamChunksConn(readPayloads)
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(readPayloads...), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
writePayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.transportSmall,
|
||||
benchmarkPayloads.transportMTU,
|
||||
}
|
||||
b.Run("Write/mixed", func(b *testing.B) {
|
||||
conn := newBenchmarkStreamConn(nil)
|
||||
next := 0
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(writePayloads...), nil, func() error {
|
||||
payload := writePayloads[next]
|
||||
next++
|
||||
if next == len(writePayloads) {
|
||||
next = 0
|
||||
}
|
||||
_, err := conn.Write(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkTCPRawConnFull(b *testing.B) {
|
||||
readCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Read/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "Read/transport_small", payload: benchmarkPayloads.transportSmall},
|
||||
}
|
||||
for _, tc := range readCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
conn := newBenchmarkStreamConn(tc.payload)
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
writeCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Write/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "Write/transport_small", payload: benchmarkPayloads.transportSmall},
|
||||
{name: "Write/transport_mtu", payload: benchmarkPayloads.transportMTU},
|
||||
}
|
||||
for _, tc := range writeCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
conn := newBenchmarkStreamConn(nil)
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Write(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTCPRecordMasquerade(b *testing.B) {
|
||||
benchmarkUseDeterministicRand(b)
|
||||
if benchmarkFullMatrixEnabled() {
|
||||
benchmarkTCPRecordMasqueradeFull(b)
|
||||
return
|
||||
}
|
||||
|
||||
readPayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.transportSmall,
|
||||
}
|
||||
readEncoded := make([][]byte, len(readPayloads))
|
||||
for i, payload := range readPayloads {
|
||||
readEncoded[i] = benchmarkEncodeMasqueradeRecord(benchmarkMasqueradeRules, payload)
|
||||
}
|
||||
b.Run("ReadRecord/mixed", func(b *testing.B) {
|
||||
source := newBenchmarkStreamChunksConn(readEncoded)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected stream masquerade benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(readPayloads...), nil, func() error {
|
||||
_, err := conn.ReadRecord(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
writePayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.transportSmall,
|
||||
benchmarkPayloads.transportMTU,
|
||||
}
|
||||
b.Run("WriteRecord/mixed", func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected stream masquerade benchmark conn")
|
||||
}
|
||||
next := 0
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(writePayloads...), nil, func() error {
|
||||
payload := writePayloads[next]
|
||||
next++
|
||||
if next == len(writePayloads) {
|
||||
next = 0
|
||||
}
|
||||
_, err := conn.WriteRecord(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkTCPRecordMasqueradeFull(b *testing.B) {
|
||||
readCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "ReadRecord/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "ReadRecord/transport_small", payload: benchmarkPayloads.transportSmall},
|
||||
}
|
||||
for _, tc := range readCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(benchmarkMasqueradeRules, tc.payload))
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected stream masquerade benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.ReadRecord(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
writeCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "WriteRecord/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "WriteRecord/transport_small", payload: benchmarkPayloads.transportSmall},
|
||||
{name: "WriteRecord/transport_mtu", payload: benchmarkPayloads.transportMTU},
|
||||
}
|
||||
for _, tc := range writeCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected stream masquerade benchmark conn")
|
||||
}
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.WriteRecord(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTCPFramed(b *testing.B) {
|
||||
benchmarkUseDeterministicRand(b)
|
||||
if benchmarkFullMatrixEnabled() {
|
||||
benchmarkTCPFramedFull(b)
|
||||
return
|
||||
}
|
||||
|
||||
compatOffPayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.response,
|
||||
benchmarkPayloads.cookie,
|
||||
benchmarkPayloads.transportKeepalive,
|
||||
benchmarkPayloads.transportMTU,
|
||||
}
|
||||
compatOffEncoded := make([][]byte, len(compatOffPayloads))
|
||||
for i, payload := range compatOffPayloads {
|
||||
compatOffEncoded[i] = benchmarkEncodeFramedRecord(benchmarkFramedOpts, payload)
|
||||
}
|
||||
b.Run("Read/compat_off/mixed", func(b *testing.B) {
|
||||
source := newBenchmarkStreamChunksConn(compatOffEncoded)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(source, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected framed benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(compatOffPayloads...), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
b.Run("Write/compat_off/mixed", func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(sink, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected framed benchmark conn")
|
||||
}
|
||||
next := 0
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(compatOffPayloads...), nil, func() error {
|
||||
payload := compatOffPayloads[next]
|
||||
next++
|
||||
if next == len(compatOffPayloads) {
|
||||
next = 0
|
||||
}
|
||||
_, err := conn.Write(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
compatOnPayloads := [][]byte{
|
||||
benchmarkPayloads.compatInitiation,
|
||||
benchmarkPayloads.compatTransportMTU,
|
||||
}
|
||||
compatOnEncoded := make([][]byte, len(compatOnPayloads))
|
||||
for i, payload := range compatOnPayloads {
|
||||
compatOnEncoded[i] = benchmarkEncodeFramedRecord(benchmarkFramedCompatOpts, payload)
|
||||
}
|
||||
b.Run("Read/compat_on/mixed", func(b *testing.B) {
|
||||
source := newBenchmarkStreamChunksConn(compatOnEncoded)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(source, pool, benchmarkFramedCompatOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected compat framed benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(compatOnPayloads...), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
b.Run("Write/compat_on/mixed", func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(sink, pool, benchmarkFramedCompatOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected compat framed benchmark conn")
|
||||
}
|
||||
next := 0
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(compatOnPayloads...), nil, func() error {
|
||||
payload := compatOnPayloads[next]
|
||||
next++
|
||||
if next == len(compatOnPayloads) {
|
||||
next = 0
|
||||
}
|
||||
_, err := conn.Write(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkTCPFramedFull(b *testing.B) {
|
||||
readCompatOff := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Read/compat_off/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "Read/compat_off/response", payload: benchmarkPayloads.response},
|
||||
{name: "Read/compat_off/cookie", payload: benchmarkPayloads.cookie},
|
||||
{name: "Read/compat_off/transport_keepalive", payload: benchmarkPayloads.transportKeepalive},
|
||||
{name: "Read/compat_off/transport_mtu", payload: benchmarkPayloads.transportMTU},
|
||||
}
|
||||
for _, tc := range readCompatOff {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeFramedRecord(benchmarkFramedOpts, tc.payload))
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(source, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected framed benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
writeCompatOff := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Write/compat_off/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "Write/compat_off/response", payload: benchmarkPayloads.response},
|
||||
{name: "Write/compat_off/cookie", payload: benchmarkPayloads.cookie},
|
||||
{name: "Write/compat_off/transport_keepalive", payload: benchmarkPayloads.transportKeepalive},
|
||||
{name: "Write/compat_off/transport_mtu", payload: benchmarkPayloads.transportMTU},
|
||||
}
|
||||
for _, tc := range writeCompatOff {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(sink, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected framed benchmark conn")
|
||||
}
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Write(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
readCompatOn := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Read/compat_on/initiation", payload: benchmarkPayloads.compatInitiation},
|
||||
{name: "Read/compat_on/transport_mtu", payload: benchmarkPayloads.compatTransportMTU},
|
||||
}
|
||||
for _, tc := range readCompatOn {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeFramedRecord(benchmarkFramedCompatOpts, tc.payload))
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(source, pool, benchmarkFramedCompatOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected compat framed benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
writeCompatOn := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Write/compat_on/initiation", payload: benchmarkPayloads.compatInitiation},
|
||||
{name: "Write/compat_on/transport_mtu", payload: benchmarkPayloads.compatTransportMTU},
|
||||
}
|
||||
for _, tc := range writeCompatOn {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(sink, pool, benchmarkFramedCompatOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected compat framed benchmark conn")
|
||||
}
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Write(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTCPPrelude(b *testing.B) {
|
||||
benchmarkUseDeterministicRand(b)
|
||||
if benchmarkFullMatrixEnabled() {
|
||||
benchmarkTCPPreludeFull(b)
|
||||
return
|
||||
}
|
||||
|
||||
b.Run("ReadCold/mixed_decoys_then_initiation", func(b *testing.B) {
|
||||
fixtures := make([]*benchmarkTCPPreludeReadFixture, benchmarkFixtureRingSize)
|
||||
for i := range fixtures {
|
||||
decoys := [][]byte{{0xaa}}
|
||||
if i%2 == 1 {
|
||||
decoys = [][]byte{{0xaa}, {0xab}, {0xac}, {0xad}, {0xae}}
|
||||
}
|
||||
fixtures[i] = newBenchmarkTCPPreludeReadFixture(decoys)
|
||||
}
|
||||
benchmarkRunLoopWithFixtureRing(b, len(benchmarkPayloads.initiation), fixtures, func(f *benchmarkTCPPreludeReadFixture) {
|
||||
f.Reset()
|
||||
}, func(f *benchmarkTCPPreludeReadFixture) error {
|
||||
return f.Read()
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("ReadHot/transport_small", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
prelude.seenValid = true
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.transportSmall), nil, func() error {
|
||||
_, err := prelude.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Write/initiation_mixed", func(b *testing.B) {
|
||||
fixtures := make([]*benchmarkTCPPreludeWriteFixture, benchmarkFixtureRingSize)
|
||||
for i := range fixtures {
|
||||
opts := benchmarkPreludeOneRule
|
||||
if i%2 == 1 {
|
||||
opts = benchmarkPreludeFiveRules
|
||||
}
|
||||
fixtures[i] = newBenchmarkTCPPreludeWriteFixture(
|
||||
opts,
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
)
|
||||
}
|
||||
benchmarkRunLoopWithFixtureRing(b, len(benchmarkPayloads.initiation), fixtures, nil, func(f *benchmarkTCPPreludeWriteFixture) error {
|
||||
return f.Write()
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Write/transport_small/passthrough", func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
recordConn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
payload := benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall)
|
||||
benchmarkRunLoop(b, len(payload), nil, func() error {
|
||||
_, err := prelude.Write(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkTCPPreludeFull(b *testing.B) {
|
||||
b.Run("ReadCold/decoy1_then_initiation", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
[]byte{0xaa},
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
reset := func() {
|
||||
source.ResetRead()
|
||||
prelude.seenValid = false
|
||||
}
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.initiation), reset, func() error {
|
||||
_, err := prelude.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("ReadCold/decoy5_then_initiation", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
[]byte{0xaa},
|
||||
[]byte{0xab},
|
||||
[]byte{0xac},
|
||||
[]byte{0xad},
|
||||
[]byte{0xae},
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
reset := func() {
|
||||
source.ResetRead()
|
||||
prelude.seenValid = false
|
||||
}
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.initiation), reset, func() error {
|
||||
_, err := prelude.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("ReadHot/transport_small", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
prelude.seenValid = true
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.transportSmall), nil, func() error {
|
||||
_, err := prelude.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
writeCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
opts PreludeOpts
|
||||
}{
|
||||
{
|
||||
name: "Write/initiation/1_rule",
|
||||
payload: benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
opts: benchmarkPreludeOneRule,
|
||||
},
|
||||
{
|
||||
name: "Write/initiation/5_rules",
|
||||
payload: benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
opts: benchmarkPreludeFiveRules,
|
||||
},
|
||||
{
|
||||
name: "Write/transport_small/passthrough",
|
||||
payload: benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall),
|
||||
opts: benchmarkPreludeOneRule,
|
||||
},
|
||||
}
|
||||
for _, tc := range writeCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
recordConn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, tc.opts)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := prelude.Write(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTCPPipeline(b *testing.B) {
|
||||
benchmarkUseDeterministicRand(b)
|
||||
if benchmarkFullMatrixEnabled() {
|
||||
benchmarkTCPPipelineFull(b)
|
||||
return
|
||||
}
|
||||
|
||||
b.Run("Read/full_pipeline/cold_initiation", func(b *testing.B) {
|
||||
fixtures := make([]*benchmarkTCPPipelineReadFixture, benchmarkFixtureRingSize)
|
||||
for i := range fixtures {
|
||||
fixtures[i] = newBenchmarkTCPPipelineReadFixture([][]byte{{0xaa, 0xbb}})
|
||||
}
|
||||
benchmarkRunLoopWithFixtureRing(b, len(benchmarkPayloads.initiation), fixtures, func(f *benchmarkTCPPipelineReadFixture) {
|
||||
f.Reset()
|
||||
}, func(f *benchmarkTCPPipelineReadFixture) error {
|
||||
return f.Read()
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Read/full_pipeline/hot_transport_small", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
prelude.seenValid = true
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected pipeline benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.transportSmall), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
writePayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.transportSmall,
|
||||
benchmarkPayloads.transportMTU,
|
||||
}
|
||||
b.Run("Write/full_pipeline/mixed", func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
recordConn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected pipeline benchmark conn")
|
||||
}
|
||||
next := 0
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(writePayloads...), nil, func() error {
|
||||
payload := writePayloads[next]
|
||||
next++
|
||||
if next == len(writePayloads) {
|
||||
next = 0
|
||||
}
|
||||
_, err := conn.Write(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkTCPPipelineFull(b *testing.B) {
|
||||
b.Run("Read/full_pipeline/cold_initiation", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
[]byte{0xaa, 0xbb},
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected pipeline benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
reset := func() {
|
||||
source.ResetRead()
|
||||
prelude.seenValid = false
|
||||
}
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.initiation), reset, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Read/full_pipeline/hot_transport_small", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
prelude.seenValid = true
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected pipeline benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.transportSmall), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
writeCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Write/full_pipeline/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "Write/full_pipeline/transport_small", payload: benchmarkPayloads.transportSmall},
|
||||
{name: "Write/full_pipeline/transport_mtu", payload: benchmarkPayloads.transportMTU},
|
||||
}
|
||||
for _, tc := range writeCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
recordConn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected pipeline benchmark conn")
|
||||
}
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Write(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type benchmarkTCPPreludeReadFixture struct {
|
||||
source *benchmarkStreamConn
|
||||
prelude *PreludeConn
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newBenchmarkTCPPreludeReadFixture(decoys [][]byte) *benchmarkTCPPreludeReadFixture {
|
||||
pool := benchmarkNewBufferPool()
|
||||
records := make([][]byte, 0, len(decoys)+1)
|
||||
records = append(records, decoys...)
|
||||
records = append(records, benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation))
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(benchmarkMasqueradeRules, records...))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
panic("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
panic("expected prelude benchmark conn")
|
||||
}
|
||||
return &benchmarkTCPPreludeReadFixture{
|
||||
source: source,
|
||||
prelude: prelude,
|
||||
buf: make([]byte, benchmarkMaxPacketSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *benchmarkTCPPreludeReadFixture) Reset() {
|
||||
f.source.ResetRead()
|
||||
f.prelude.seenValid = false
|
||||
}
|
||||
|
||||
func (f *benchmarkTCPPreludeReadFixture) Read() error {
|
||||
_, err := f.prelude.Read(f.buf)
|
||||
return err
|
||||
}
|
||||
|
||||
type benchmarkTCPPreludeWriteFixture struct {
|
||||
prelude *PreludeConn
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func newBenchmarkTCPPreludeWriteFixture(opts PreludeOpts, payload []byte) *benchmarkTCPPreludeWriteFixture {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
recordConn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
panic("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, opts)
|
||||
if !ok {
|
||||
panic("expected prelude benchmark conn")
|
||||
}
|
||||
return &benchmarkTCPPreludeWriteFixture{
|
||||
prelude: prelude,
|
||||
payload: payload,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *benchmarkTCPPreludeWriteFixture) Write() error {
|
||||
_, err := f.prelude.Write(f.payload)
|
||||
return err
|
||||
}
|
||||
|
||||
type benchmarkTCPPipelineReadFixture struct {
|
||||
source *benchmarkStreamConn
|
||||
prelude *PreludeConn
|
||||
conn *FramedConn
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newBenchmarkTCPPipelineReadFixture(decoys [][]byte) *benchmarkTCPPipelineReadFixture {
|
||||
pool := benchmarkNewBufferPool()
|
||||
records := make([][]byte, 0, len(decoys)+1)
|
||||
records = append(records, decoys...)
|
||||
records = append(records, benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation))
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(benchmarkMasqueradeRules, records...))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
panic("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
panic("expected prelude benchmark conn")
|
||||
}
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
panic("expected pipeline benchmark conn")
|
||||
}
|
||||
return &benchmarkTCPPipelineReadFixture{
|
||||
source: source,
|
||||
prelude: prelude,
|
||||
conn: conn,
|
||||
buf: make([]byte, benchmarkMaxPacketSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *benchmarkTCPPipelineReadFixture) Reset() {
|
||||
f.source.ResetRead()
|
||||
f.prelude.seenValid = false
|
||||
}
|
||||
|
||||
func (f *benchmarkTCPPipelineReadFixture) Read() error {
|
||||
_, err := f.conn.Read(f.buf)
|
||||
return err
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,79 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import "sync"
|
||||
|
||||
func WrapFlexBuffer(buf []byte) *FlexBuffer {
|
||||
return &FlexBuffer{
|
||||
buf: buf,
|
||||
}
|
||||
}
|
||||
|
||||
type FlexBuffer struct {
|
||||
buf []byte
|
||||
offset int
|
||||
len int
|
||||
}
|
||||
|
||||
func (b *FlexBuffer) PushTail(size int) []byte {
|
||||
newLen := b.len + size
|
||||
if b.offset+newLen > len(b.buf) {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldLen := b.len
|
||||
b.len = newLen
|
||||
return b.buf[b.offset+oldLen : b.offset+newLen]
|
||||
}
|
||||
|
||||
func (b *FlexBuffer) PullTail(size int) []byte {
|
||||
newLen := b.len - size
|
||||
if newLen < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldLen := b.len
|
||||
b.len = newLen
|
||||
return b.buf[b.offset+newLen : b.offset+oldLen]
|
||||
}
|
||||
|
||||
func (b *FlexBuffer) PullHead(size int) []byte {
|
||||
if size == -1 {
|
||||
size = len(b.buf)
|
||||
}
|
||||
|
||||
newOffset := b.offset + size
|
||||
if newOffset+b.len > len(b.buf) {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldOffset := b.offset
|
||||
b.offset = newOffset
|
||||
|
||||
return b.buf[oldOffset+b.len : newOffset+b.len]
|
||||
}
|
||||
|
||||
func (b *FlexBuffer) Cap() int {
|
||||
return len(b.buf)
|
||||
}
|
||||
|
||||
func (b *FlexBuffer) Len() int {
|
||||
return b.len
|
||||
}
|
||||
|
||||
func WrapBufferPool(pool *sync.Pool) *BufferPool {
|
||||
return &BufferPool{
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
type BufferPool struct {
|
||||
pool *sync.Pool
|
||||
}
|
||||
|
||||
func (p *BufferPool) Get() []byte {
|
||||
return p.pool.Get().([]byte)
|
||||
}
|
||||
|
||||
func (p *BufferPool) Put(b []byte) {
|
||||
p.pool.Put(b)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
type UDPConn interface {
|
||||
net.PacketConn
|
||||
SyscallConn() (syscall.RawConn, error)
|
||||
ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error)
|
||||
WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error)
|
||||
}
|
||||
|
||||
var _ ipv4.Message = ipv6.Message{}
|
||||
|
||||
type BatchConn interface {
|
||||
ReadBatch(ms []ipv4.Message, flags int) (n int, err error)
|
||||
WriteBatch(ms []ipv4.Message, flags int) (n int, err error)
|
||||
}
|
||||
@@ -1,391 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
type FramedOpts struct {
|
||||
H1 *RangedHeader
|
||||
H2 *RangedHeader
|
||||
H3 *RangedHeader
|
||||
H4 *RangedHeader
|
||||
S1 int
|
||||
S2 int
|
||||
S3 int
|
||||
S4 int
|
||||
HeaderCompat bool
|
||||
}
|
||||
|
||||
func (o *FramedOpts) HasIntersections() bool {
|
||||
headers := []*RangedHeader{o.H1, o.H2, o.H3, o.H4}
|
||||
|
||||
for i := range len(headers) {
|
||||
left := headers[i]
|
||||
if left == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for j := i + 1; j < len(headers); j++ {
|
||||
right := headers[j]
|
||||
if right == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if left.start <= right.end && right.start <= left.end {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func newFrameEncoding(opts FramedOpts) (e frameEncoding, ok bool) {
|
||||
e = frameEncoding{}
|
||||
|
||||
if opts.H1 != nil {
|
||||
e.header.initial = *opts.H1
|
||||
ok = true
|
||||
} else {
|
||||
e.header.initial = RangedHeader{WireguardMsgInitiationType, WireguardMsgInitiationType}
|
||||
}
|
||||
|
||||
if opts.H2 != nil {
|
||||
e.header.response = *opts.H2
|
||||
ok = true
|
||||
} else {
|
||||
e.header.response = RangedHeader{WireguardMsgResponseType, WireguardMsgResponseType}
|
||||
}
|
||||
|
||||
if opts.H3 != nil {
|
||||
e.header.cookie = *opts.H3
|
||||
ok = true
|
||||
} else {
|
||||
e.header.cookie = RangedHeader{WireguardMsgCookieReplyType, WireguardMsgCookieReplyType}
|
||||
}
|
||||
|
||||
if opts.H4 != nil {
|
||||
e.header.transport = *opts.H4
|
||||
ok = true
|
||||
} else {
|
||||
e.header.transport = RangedHeader{WireguardMsgTransportType, WireguardMsgTransportType}
|
||||
}
|
||||
|
||||
if opts.S1 != 0 {
|
||||
e.padding.initial = opts.S1
|
||||
ok = true
|
||||
}
|
||||
|
||||
if opts.S2 != 0 {
|
||||
e.padding.response = opts.S2
|
||||
ok = true
|
||||
}
|
||||
|
||||
if opts.S3 != 0 {
|
||||
e.padding.cookie = opts.S3
|
||||
ok = true
|
||||
}
|
||||
|
||||
if opts.S4 != 0 {
|
||||
e.padding.transport = opts.S4
|
||||
ok = true
|
||||
}
|
||||
|
||||
e.compat = opts.HeaderCompat
|
||||
return e, ok
|
||||
}
|
||||
|
||||
type frameEncoding struct {
|
||||
header struct {
|
||||
initial RangedHeader
|
||||
response RangedHeader
|
||||
cookie RangedHeader
|
||||
transport RangedHeader
|
||||
}
|
||||
padding struct {
|
||||
initial int
|
||||
response int
|
||||
cookie int
|
||||
transport int
|
||||
}
|
||||
compat bool
|
||||
}
|
||||
|
||||
type frameRecordKind uint8
|
||||
|
||||
const (
|
||||
frameRecordInvalid frameRecordKind = iota
|
||||
frameRecordInitiation
|
||||
frameRecordResponse
|
||||
frameRecordCookie
|
||||
frameRecordTransport
|
||||
)
|
||||
|
||||
func encodeOne(dst, src []byte, header RangedHeader, padding int) int {
|
||||
rand.Read(dst[:padding])
|
||||
dst = dst[padding:]
|
||||
|
||||
binary.LittleEndian.PutUint32(dst[:4], header.Generate())
|
||||
dst = dst[4:]
|
||||
|
||||
n := copy(dst, src[4:])
|
||||
return padding + 4 + n
|
||||
}
|
||||
|
||||
func encodeOneCompat(dst, src []byte, padding int) int {
|
||||
rand.Read(dst[:padding])
|
||||
n := copy(dst[padding:], src)
|
||||
return padding + n
|
||||
}
|
||||
|
||||
func (e *frameEncoding) Encode(dst, src []byte) int {
|
||||
if len(src) < 4 {
|
||||
return 0
|
||||
}
|
||||
|
||||
header := binary.LittleEndian.Uint32(src[:4])
|
||||
|
||||
if e.compat {
|
||||
if e.header.initial.Validate(header) {
|
||||
return encodeOneCompat(dst, src, e.padding.initial)
|
||||
} else if e.header.response.Validate(header) {
|
||||
return encodeOneCompat(dst, src, e.padding.response)
|
||||
} else if e.header.cookie.Validate(header) {
|
||||
return encodeOneCompat(dst, src, e.padding.cookie)
|
||||
} else if e.header.transport.Validate(header) {
|
||||
return encodeOneCompat(dst, src, e.padding.transport)
|
||||
}
|
||||
} else {
|
||||
switch src[0] {
|
||||
case WireguardMsgInitiationType:
|
||||
return encodeOne(dst, src, e.header.initial, e.padding.initial)
|
||||
case WireguardMsgResponseType:
|
||||
return encodeOne(dst, src, e.header.response, e.padding.response)
|
||||
case WireguardMsgCookieReplyType:
|
||||
return encodeOne(dst, src, e.header.cookie, e.padding.cookie)
|
||||
case WireguardMsgTransportType:
|
||||
return encodeOne(dst, src, e.header.transport, e.padding.transport)
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func decodeOneCompat(b []byte, header RangedHeader, padding int) int {
|
||||
bb := b[padding:]
|
||||
if !header.Validate(binary.LittleEndian.Uint32(bb[:4])) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return copy(b, bb)
|
||||
}
|
||||
|
||||
func decodeOne(b []byte, header RangedHeader, padding int, originalHeader uint32) int {
|
||||
bb := b[padding:]
|
||||
if !header.Validate(binary.LittleEndian.Uint32(bb[:4])) {
|
||||
return 0
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(b[:4], originalHeader)
|
||||
n := copy(b[4:], bb[4:])
|
||||
|
||||
return 4 + n
|
||||
}
|
||||
|
||||
func (e *frameEncoding) matchesRecord(b []byte, header RangedHeader, padding, size int) bool {
|
||||
if len(b) != size {
|
||||
return false
|
||||
}
|
||||
if len(b) < padding+4 {
|
||||
return false
|
||||
}
|
||||
return header.Validate(binary.LittleEndian.Uint32(b[padding : padding+4]))
|
||||
}
|
||||
|
||||
func (e *frameEncoding) matchesTransportRecord(b []byte) bool {
|
||||
if len(b) < WireguardMsgTransportMinSize+e.padding.transport {
|
||||
return false
|
||||
}
|
||||
if len(b) < e.padding.transport+4 {
|
||||
return false
|
||||
}
|
||||
return e.header.transport.Validate(binary.LittleEndian.Uint32(b[e.padding.transport : e.padding.transport+4]))
|
||||
}
|
||||
|
||||
func (e *frameEncoding) recordKind(b []byte) frameRecordKind {
|
||||
if e.matchesRecord(b, e.header.initial, e.padding.initial, WireguardMsgInitiationSize+e.padding.initial) {
|
||||
return frameRecordInitiation
|
||||
}
|
||||
if e.matchesRecord(b, e.header.response, e.padding.response, WireguardMsgResponseSize+e.padding.response) {
|
||||
return frameRecordResponse
|
||||
}
|
||||
if e.matchesRecord(b, e.header.cookie, e.padding.cookie, WireguardMsgCookieReplySize+e.padding.cookie) {
|
||||
return frameRecordCookie
|
||||
}
|
||||
if e.matchesTransportRecord(b) {
|
||||
return frameRecordTransport
|
||||
}
|
||||
return frameRecordInvalid
|
||||
}
|
||||
|
||||
func (e *frameEncoding) IsValidRecord(b []byte) bool {
|
||||
return e.recordKind(b) != frameRecordInvalid
|
||||
}
|
||||
|
||||
func (e *frameEncoding) IsInitiationRecord(b []byte) bool {
|
||||
return e.recordKind(b) == frameRecordInitiation
|
||||
}
|
||||
|
||||
func (e *frameEncoding) Decode(b []byte) int {
|
||||
switch e.recordKind(b) {
|
||||
case frameRecordInitiation:
|
||||
if e.compat {
|
||||
return decodeOneCompat(b, e.header.initial, e.padding.initial)
|
||||
}
|
||||
return decodeOne(b, e.header.initial, e.padding.initial, WireguardMsgInitiationType)
|
||||
case frameRecordResponse:
|
||||
if e.compat {
|
||||
return decodeOneCompat(b, e.header.response, e.padding.response)
|
||||
}
|
||||
return decodeOne(b, e.header.response, e.padding.response, WireguardMsgResponseType)
|
||||
case frameRecordCookie:
|
||||
if e.compat {
|
||||
return decodeOneCompat(b, e.header.cookie, e.padding.cookie)
|
||||
}
|
||||
return decodeOne(b, e.header.cookie, e.padding.cookie, WireguardMsgCookieReplyType)
|
||||
case frameRecordTransport:
|
||||
if e.compat {
|
||||
return decodeOneCompat(b, e.header.transport, e.padding.transport)
|
||||
}
|
||||
return decodeOne(b, e.header.transport, e.padding.transport, WireguardMsgTransportType)
|
||||
default:
|
||||
return len(b)
|
||||
}
|
||||
}
|
||||
|
||||
func NewFramedConn(conn net.Conn, pool *sync.Pool, opts FramedOpts) (c *FramedConn, ok bool) {
|
||||
enc, ok := newFrameEncoding(opts)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &FramedConn{
|
||||
Conn: conn,
|
||||
pool: WrapBufferPool(pool),
|
||||
enc: enc,
|
||||
}, true
|
||||
}
|
||||
|
||||
type FramedConn struct {
|
||||
net.Conn
|
||||
pool *BufferPool
|
||||
enc frameEncoding
|
||||
}
|
||||
|
||||
func (c *FramedConn) Read(b []byte) (n int, err error) {
|
||||
n, err = c.Conn.Read(b)
|
||||
n = c.enc.Decode(b[:n])
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *FramedConn) Write(b []byte) (n int, err error) {
|
||||
t := c.pool.Get()
|
||||
defer c.pool.Put(t)
|
||||
|
||||
n = c.enc.Encode(t, b)
|
||||
diff := n - len(b)
|
||||
n, err = c.Conn.Write(t[:n])
|
||||
|
||||
return max(n-diff, 0), err
|
||||
}
|
||||
|
||||
func NewFramedUDPConn(conn UDPConn, pool *sync.Pool, opts FramedOpts) (c UDPConn, ok bool) {
|
||||
enc, ok := newFrameEncoding(opts)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &FramedUDPConn{
|
||||
UDPConn: conn,
|
||||
pool: WrapBufferPool(pool),
|
||||
enc: enc,
|
||||
}, true
|
||||
}
|
||||
|
||||
type FramedUDPConn struct {
|
||||
UDPConn
|
||||
pool *BufferPool
|
||||
enc frameEncoding
|
||||
}
|
||||
|
||||
func (c *FramedUDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
|
||||
n, oobn, flags, addr, err = c.UDPConn.ReadMsgUDP(b, oob)
|
||||
if err != nil {
|
||||
return 0, 0, 0, nil, err
|
||||
}
|
||||
n = c.enc.Decode(b[:n])
|
||||
return n, oobn, flags, addr, err
|
||||
}
|
||||
|
||||
func (c *FramedUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) {
|
||||
t := c.pool.Get()
|
||||
defer c.pool.Put(t)
|
||||
|
||||
n = c.enc.Encode(t, b)
|
||||
diff := n - len(b)
|
||||
n, oobn, err = c.UDPConn.WriteMsgUDP(t[:n], oob, addr)
|
||||
|
||||
return max(n-diff, 0), oobn, err
|
||||
}
|
||||
|
||||
func NewFramedBatchConn(conn BatchConn, pool *sync.Pool, opts FramedOpts) (c BatchConn, ok bool) {
|
||||
enc, ok := newFrameEncoding(opts)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &FramedBatchConn{
|
||||
BatchConn: conn,
|
||||
pool: WrapBufferPool(pool),
|
||||
enc: enc,
|
||||
}, true
|
||||
}
|
||||
|
||||
type FramedBatchConn struct {
|
||||
BatchConn
|
||||
pool *BufferPool
|
||||
enc frameEncoding
|
||||
}
|
||||
|
||||
func (c *FramedBatchConn) ReadBatch(ms []ipv4.Message, flags int) (n int, err error) {
|
||||
n, err = c.BatchConn.ReadBatch(ms, flags)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for i := range ms[:n] {
|
||||
b := ms[i].Buffers[0][:ms[i].N]
|
||||
ms[i].N = c.enc.Decode(b)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *FramedBatchConn) WriteBatch(ms []ipv4.Message, flags int) (n int, err error) {
|
||||
for i := range ms {
|
||||
t := c.pool.Get()
|
||||
defer c.pool.Put(t)
|
||||
|
||||
n = c.enc.Encode(t, ms[i].Buffers[0])
|
||||
ms[i].Buffers[0] = t[:n]
|
||||
}
|
||||
|
||||
// ms[i].N has incorrect N because the original data was modifier above
|
||||
// however, WG does not check this field, so this is fine
|
||||
return c.BatchConn.WriteBatch(ms, flags)
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
type MasqueradeOpts struct {
|
||||
RulesIn Rules
|
||||
RulesOut Rules
|
||||
}
|
||||
|
||||
func NewMasqueradeConn(conn net.Conn, pool *sync.Pool, opts MasqueradeOpts) (c *MasqueradeConn, ok bool) {
|
||||
if opts.RulesIn == nil && opts.RulesOut == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &MasqueradeConn{
|
||||
Conn: conn,
|
||||
rulesIn: opts.RulesIn,
|
||||
rulesOut: opts.RulesOut,
|
||||
pool: WrapBufferPool(pool),
|
||||
}, true
|
||||
}
|
||||
|
||||
type MasqueradeConn struct {
|
||||
net.Conn
|
||||
rulesIn Rules
|
||||
rulesOut Rules
|
||||
pool *BufferPool
|
||||
}
|
||||
|
||||
var _ StreamRecordConn = (*MasqueradeConn)(nil)
|
||||
|
||||
func (c *MasqueradeConn) CanReadRecord() bool {
|
||||
return c.rulesIn != nil
|
||||
}
|
||||
|
||||
func (c *MasqueradeConn) CanWriteRecord() bool {
|
||||
return c.rulesOut != nil
|
||||
}
|
||||
|
||||
func (c *MasqueradeConn) ReadRecord(b []byte) (n int, err error) {
|
||||
if !c.CanReadRecord() {
|
||||
return 0, ErrNoReadRecord
|
||||
}
|
||||
|
||||
ctx := &readContext{
|
||||
FlexBuffer: WrapFlexBuffer(b),
|
||||
BufferPool: c.pool,
|
||||
}
|
||||
|
||||
if err := c.rulesIn.Read(c.Conn, ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ctx.Len(), nil
|
||||
}
|
||||
|
||||
func (c *MasqueradeConn) WriteRecord(b []byte) (n int, err error) {
|
||||
if !c.CanWriteRecord() {
|
||||
return 0, ErrNoWriteRecord
|
||||
}
|
||||
|
||||
ctx := &writeContext{
|
||||
FlexBuffer: WrapFlexBuffer(b),
|
||||
BufferPool: c.pool,
|
||||
}
|
||||
|
||||
t := c.pool.Get()
|
||||
defer c.pool.Put(t)
|
||||
|
||||
w := bytes.NewBuffer(t[:0])
|
||||
|
||||
if err := c.rulesOut.Write(w, ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if _, err := c.Conn.Write(w.Bytes()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ctx.Len(), nil
|
||||
}
|
||||
|
||||
func (c *MasqueradeConn) Read(b []byte) (n int, err error) {
|
||||
if !c.CanReadRecord() {
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
return c.ReadRecord(b)
|
||||
}
|
||||
|
||||
func (c *MasqueradeConn) Write(b []byte) (n int, err error) {
|
||||
if !c.CanWriteRecord() {
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
return c.WriteRecord(b)
|
||||
}
|
||||
|
||||
func NewMasqueradeUDPConn(conn UDPConn, pool *sync.Pool, opts MasqueradeOpts) (c *MasqueradeUDPConn, ok bool) {
|
||||
if opts.RulesIn == nil && opts.RulesOut == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &MasqueradeUDPConn{
|
||||
UDPConn: conn,
|
||||
rulesIn: opts.RulesIn,
|
||||
rulesOut: opts.RulesOut,
|
||||
pool: WrapBufferPool(pool),
|
||||
}, true
|
||||
}
|
||||
|
||||
type MasqueradeUDPConn struct {
|
||||
UDPConn
|
||||
rulesIn Rules
|
||||
rulesOut Rules
|
||||
pool *BufferPool
|
||||
}
|
||||
|
||||
func (c *MasqueradeUDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
|
||||
n, oobn, flags, addr, err = c.UDPConn.ReadMsgUDP(b, oob)
|
||||
if err != nil {
|
||||
return n, oobn, flags, addr, err
|
||||
}
|
||||
|
||||
r := bytes.NewBuffer(b[:n])
|
||||
ctx := &readContext{
|
||||
FlexBuffer: WrapFlexBuffer(b),
|
||||
BufferPool: c.pool,
|
||||
}
|
||||
|
||||
if err = c.rulesIn.Read(r, ctx); err != nil {
|
||||
return 0, oobn, flags, addr, err
|
||||
}
|
||||
|
||||
return ctx.Len(), oobn, flags, addr, err
|
||||
}
|
||||
|
||||
func (c *MasqueradeUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) {
|
||||
t := c.pool.Get()
|
||||
defer c.pool.Put(t)
|
||||
|
||||
w := bytes.NewBuffer(t[:0])
|
||||
ctx := &writeContext{
|
||||
FlexBuffer: WrapFlexBuffer(b),
|
||||
BufferPool: c.pool,
|
||||
}
|
||||
|
||||
if err = c.rulesOut.Write(w, ctx); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return c.UDPConn.WriteMsgUDP(t[:ctx.Len()], oob, addr)
|
||||
}
|
||||
|
||||
func NewMasqueradeBatchConn(conn BatchConn, bp *sync.Pool, opts MasqueradeOpts) (c *MasqueradeBatchConn, ok bool) {
|
||||
if opts.RulesIn == nil && opts.RulesOut == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &MasqueradeBatchConn{
|
||||
BatchConn: conn,
|
||||
rulesIn: opts.RulesIn,
|
||||
rulesOut: opts.RulesOut,
|
||||
pool: WrapBufferPool(bp),
|
||||
}, true
|
||||
}
|
||||
|
||||
type MasqueradeBatchConn struct {
|
||||
BatchConn
|
||||
rulesIn Rules
|
||||
rulesOut Rules
|
||||
pool *BufferPool
|
||||
}
|
||||
|
||||
func (c *MasqueradeBatchConn) ReadBatch(ms []ipv4.Message, flags int) (n int, err error) {
|
||||
n, err = c.BatchConn.ReadBatch(ms, flags)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for i := range n {
|
||||
r := bytes.NewBuffer(ms[i].Buffers[0][:ms[i].N])
|
||||
ctx := &readContext{
|
||||
FlexBuffer: WrapFlexBuffer(ms[i].Buffers[0]),
|
||||
BufferPool: c.pool,
|
||||
}
|
||||
|
||||
if err = c.rulesIn.Read(r, ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ms[i].N = ctx.Len()
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *MasqueradeBatchConn) WriteBatch(ms []ipv4.Message, flags int) (n int, err error) {
|
||||
for i := range ms {
|
||||
t := c.pool.Get()
|
||||
defer c.pool.Put(t)
|
||||
|
||||
w := bytes.NewBuffer(t[:0])
|
||||
ctx := &writeContext{
|
||||
FlexBuffer: WrapFlexBuffer(ms[i].Buffers[0]),
|
||||
BufferPool: c.pool,
|
||||
}
|
||||
|
||||
if err = c.rulesOut.Write(w, ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ms[i].Buffers[0] = w.Bytes()
|
||||
}
|
||||
|
||||
return c.BatchConn.WriteBatch(ms, flags)
|
||||
}
|
||||
@@ -1,334 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
type PreludeOpts struct {
|
||||
Jc int
|
||||
Jmin int
|
||||
Jmax int
|
||||
RulesArr [5]Rules
|
||||
}
|
||||
|
||||
func (o PreludeOpts) HasDecoyRules() bool {
|
||||
for _, rules := range o.RulesArr {
|
||||
if rules != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o PreludeOpts) IsEmpty() bool {
|
||||
if o.HasDecoyRules() {
|
||||
return false
|
||||
}
|
||||
return o.Jc == 0
|
||||
}
|
||||
|
||||
func newJunkGenerator(min, max int) *junkGenerator {
|
||||
return &junkGenerator{
|
||||
rang: big.NewInt(int64(max - min + 1)),
|
||||
offset: min,
|
||||
}
|
||||
}
|
||||
|
||||
type junkGenerator struct {
|
||||
rang *big.Int
|
||||
offset int
|
||||
}
|
||||
|
||||
func (p *junkGenerator) generate(b []byte) []byte {
|
||||
rndBig, _ := rand.Int(rand.Reader, p.rang)
|
||||
n := int(rndBig.Int64()) + p.offset
|
||||
|
||||
junk := b[:n]
|
||||
rand.Read(junk)
|
||||
return junk
|
||||
}
|
||||
|
||||
func NewPreludeUDPConn(
|
||||
conn UDPConn,
|
||||
origin UDPConn,
|
||||
pool *sync.Pool,
|
||||
header *RangedHeader,
|
||||
opts PreludeOpts,
|
||||
) (c *PreludeUDPConn, ok bool) {
|
||||
if opts.IsEmpty() {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if opts.Jmin > opts.Jmax {
|
||||
opts.Jmin, opts.Jmax = opts.Jmax, opts.Jmin
|
||||
}
|
||||
|
||||
return &PreludeUDPConn{
|
||||
UDPConn: conn,
|
||||
origin: origin,
|
||||
pool: WrapBufferPool(pool),
|
||||
rulesArr: opts.RulesArr,
|
||||
junkCount: opts.Jc,
|
||||
junkGen: newJunkGenerator(opts.Jmin, opts.Jmax),
|
||||
header: header,
|
||||
}, true
|
||||
}
|
||||
|
||||
type PreludeUDPConn struct {
|
||||
UDPConn
|
||||
origin UDPConn
|
||||
pool *BufferPool
|
||||
rulesArr [5]Rules
|
||||
junkCount int
|
||||
junkGen *junkGenerator
|
||||
header *RangedHeader
|
||||
}
|
||||
|
||||
func (c *PreludeUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) {
|
||||
var isInit bool
|
||||
if len(b) >= 4 {
|
||||
typ := binary.LittleEndian.Uint32(b[:4])
|
||||
if c.header != nil {
|
||||
isInit = c.header.Validate(typ)
|
||||
} else {
|
||||
isInit = typ == WireguardMsgInitiationType
|
||||
}
|
||||
}
|
||||
|
||||
if isInit {
|
||||
b := c.pool.Get()
|
||||
defer c.pool.Put(b)
|
||||
|
||||
ctx := &writeContext{
|
||||
FlexBuffer: WrapFlexBuffer(nil),
|
||||
BufferPool: c.pool,
|
||||
}
|
||||
|
||||
for _, rules := range c.rulesArr {
|
||||
if rules == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
w := bytes.NewBuffer(b[:0])
|
||||
if err = rules.Write(w, ctx); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if _, _, err = c.origin.WriteMsgUDP(w.Bytes(), oob, addr); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
for range c.junkCount {
|
||||
junk := c.junkGen.generate(b)
|
||||
if _, _, err = c.origin.WriteMsgUDP(junk, oob, addr); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c.UDPConn.WriteMsgUDP(b, oob, addr)
|
||||
}
|
||||
|
||||
func NewPreludeConn(
|
||||
conn StreamRecordConn,
|
||||
pool *sync.Pool,
|
||||
framedOpts FramedOpts,
|
||||
opts PreludeOpts,
|
||||
) (c *PreludeConn, ok bool) {
|
||||
if !opts.HasDecoyRules() || !conn.CanReadRecord() || !conn.CanWriteRecord() {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
enc, _ := newFrameEncoding(framedOpts)
|
||||
|
||||
return &PreludeConn{
|
||||
StreamRecordConn: conn,
|
||||
pool: WrapBufferPool(pool),
|
||||
rulesArr: opts.RulesArr,
|
||||
recordEncoding: enc,
|
||||
}, true
|
||||
}
|
||||
|
||||
type PreludeConn struct {
|
||||
StreamRecordConn
|
||||
pool *BufferPool
|
||||
rulesArr [5]Rules
|
||||
recordEncoding frameEncoding
|
||||
seenValid bool
|
||||
}
|
||||
|
||||
func (c *PreludeConn) Read(b []byte) (n int, err error) {
|
||||
if c.seenValid {
|
||||
return c.StreamRecordConn.ReadRecord(b)
|
||||
}
|
||||
|
||||
for {
|
||||
n, err = c.StreamRecordConn.ReadRecord(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if c.recordEncoding.IsValidRecord(b[:n]) {
|
||||
c.seenValid = true
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PreludeConn) Write(b []byte) (n int, err error) {
|
||||
if c.recordEncoding.IsInitiationRecord(b) {
|
||||
if err := c.writePreludeRecords(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return c.StreamRecordConn.WriteRecord(b)
|
||||
}
|
||||
|
||||
func (c *PreludeConn) writePreludeRecords() (err error) {
|
||||
buf := c.pool.Get()
|
||||
defer c.pool.Put(buf)
|
||||
|
||||
ctx := &writeContext{
|
||||
FlexBuffer: WrapFlexBuffer(nil),
|
||||
BufferPool: c.pool,
|
||||
}
|
||||
|
||||
for _, rules := range c.rulesArr {
|
||||
if rules == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
w := bytes.NewBuffer(buf[:0])
|
||||
if err = rules.Write(w, ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = c.StreamRecordConn.WriteRecord(w.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPreludeBatchConn(
|
||||
conn BatchConn,
|
||||
origin BatchConn,
|
||||
bufPool *sync.Pool,
|
||||
msgsPool *sync.Pool,
|
||||
header *RangedHeader,
|
||||
opts PreludeOpts,
|
||||
) (c *PreludeBatchConn, ok bool) {
|
||||
if opts.IsEmpty() {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if opts.Jmin > opts.Jmax {
|
||||
opts.Jmin, opts.Jmax = opts.Jmax, opts.Jmin
|
||||
}
|
||||
|
||||
return &PreludeBatchConn{
|
||||
BatchConn: conn,
|
||||
origin: origin,
|
||||
bufPool: WrapBufferPool(bufPool),
|
||||
msgsPool: msgsPool,
|
||||
rulesArr: opts.RulesArr,
|
||||
junkCount: opts.Jc,
|
||||
junkGen: newJunkGenerator(opts.Jmin, opts.Jmax),
|
||||
header: header,
|
||||
}, true
|
||||
}
|
||||
|
||||
type PreludeBatchConn struct {
|
||||
BatchConn
|
||||
origin BatchConn
|
||||
bufPool *BufferPool
|
||||
msgsPool *sync.Pool
|
||||
rulesArr [5]Rules
|
||||
junkCount int
|
||||
junkGen *junkGenerator
|
||||
header *RangedHeader
|
||||
}
|
||||
|
||||
func (c *PreludeBatchConn) WriteBatch(ms []ipv4.Message, flags int) (n int, err error) {
|
||||
var initMsg *ipv4.Message
|
||||
for i := range ms {
|
||||
b := ms[i].Buffers[0]
|
||||
|
||||
var isInit bool
|
||||
if len(b) >= 4 {
|
||||
typ := binary.LittleEndian.Uint32(b[:4])
|
||||
if c.header != nil {
|
||||
isInit = c.header.Validate(typ)
|
||||
} else {
|
||||
isInit = typ == WireguardMsgInitiationType
|
||||
}
|
||||
}
|
||||
if isInit {
|
||||
initMsg = &ms[i]
|
||||
}
|
||||
}
|
||||
|
||||
if initMsg != nil {
|
||||
ctx := &writeContext{
|
||||
FlexBuffer: WrapFlexBuffer(nil),
|
||||
BufferPool: c.bufPool,
|
||||
}
|
||||
|
||||
msgs := c.msgsPool.Get().(*[]ipv4.Message)
|
||||
defer c.msgsPool.Put(msgs)
|
||||
i := 0
|
||||
|
||||
for _, rules := range c.rulesArr {
|
||||
if rules == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
buf := c.bufPool.Get()
|
||||
defer c.bufPool.Put(buf)
|
||||
|
||||
w := bytes.NewBuffer(buf[:0])
|
||||
if err = rules.Write(w, ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
(*msgs)[i].Buffers[0] = w.Bytes()
|
||||
(*msgs)[i].OOB = initMsg.OOB
|
||||
(*msgs)[i].Addr = initMsg.Addr
|
||||
i++
|
||||
}
|
||||
|
||||
for range c.junkCount {
|
||||
buf := c.bufPool.Get()
|
||||
defer c.bufPool.Put(buf)
|
||||
|
||||
(*msgs)[i].Buffers[0] = c.junkGen.generate(buf)
|
||||
(*msgs)[i].OOB = initMsg.OOB
|
||||
(*msgs)[i].Addr = initMsg.Addr
|
||||
i++
|
||||
}
|
||||
|
||||
var start int
|
||||
for {
|
||||
m := (*msgs)[start:i]
|
||||
n, err = c.origin.WriteBatch(m, flags)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n == len(m) {
|
||||
break
|
||||
}
|
||||
start += n
|
||||
}
|
||||
}
|
||||
|
||||
return c.BatchConn.WriteBatch(ms, flags)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package conceal
|
||||
|
||||
const (
|
||||
WireguardMsgInitiationType = 1
|
||||
WireguardMsgResponseType = 2
|
||||
WireguardMsgCookieReplyType = 3
|
||||
WireguardMsgTransportType = 4
|
||||
|
||||
WireguardMsgInitiationSize = 148
|
||||
WireguardMsgResponseSize = 92
|
||||
WireguardMsgCookieReplySize = 64
|
||||
WireguardMsgTransportMinSize = 32
|
||||
)
|
||||
@@ -1,45 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func buildNumFormat(str string) (NumFormat, error) {
|
||||
str = strings.ToLower(str)
|
||||
|
||||
switch str {
|
||||
case "be":
|
||||
return NumFormatBE, nil
|
||||
case "le":
|
||||
return NumFormatLE, nil
|
||||
case "ascii":
|
||||
return NumFormatAscii, nil
|
||||
case "hex":
|
||||
return NumFormatHex, nil
|
||||
}
|
||||
return NumFormatBE, errors.New("wrong format")
|
||||
}
|
||||
|
||||
type NumFormat int
|
||||
|
||||
const (
|
||||
NumFormatBE NumFormat = iota
|
||||
NumFormatLE
|
||||
NumFormatAscii
|
||||
NumFormatHex
|
||||
)
|
||||
|
||||
func (f NumFormat) Spec() string {
|
||||
switch f {
|
||||
case NumFormatBE:
|
||||
return "be"
|
||||
case NumFormatLE:
|
||||
return "le"
|
||||
case NumFormatAscii:
|
||||
return "ascii"
|
||||
case NumFormatHex:
|
||||
return "hex"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RangedHeader struct {
|
||||
start uint32
|
||||
end uint32
|
||||
}
|
||||
|
||||
func NewRangedHeader(spec string) (*RangedHeader, 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 &RangedHeader{
|
||||
start: uint32(start),
|
||||
end: uint32(end),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *RangedHeader) GenSpec() string {
|
||||
if h.start == h.end {
|
||||
return fmt.Sprintf("%d", h.start)
|
||||
}
|
||||
return fmt.Sprintf("%d-%d", h.start, h.end)
|
||||
}
|
||||
|
||||
func (h *RangedHeader) Validate(val uint32) bool {
|
||||
return h.start <= val && val <= h.end
|
||||
}
|
||||
|
||||
func (h *RangedHeader) Generate() uint32 {
|
||||
high := int64(h.end - h.start + 1)
|
||||
r, _ := rand.Int(rand.Reader, big.NewInt(high))
|
||||
return h.start + uint32(r.Int64())
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ruleBuilder func(val string) (Rule, error)
|
||||
|
||||
var ruleBuilders = map[string]ruleBuilder{
|
||||
"b": buildBytesRule,
|
||||
"r": buildRandRule,
|
||||
"rd": buildRandDigitsRule,
|
||||
"rc": buildRandCharRule,
|
||||
"t": buildTimestampRule,
|
||||
"dz": buildDataSizeRule,
|
||||
"d": buildDataRule,
|
||||
}
|
||||
|
||||
func ParseRules(spec string) (Rules, error) {
|
||||
var (
|
||||
rules Rules
|
||||
errs []error
|
||||
)
|
||||
|
||||
for i := 0; i < len(spec); {
|
||||
start := strings.IndexByte(spec[i:], '<')
|
||||
if start == -1 {
|
||||
break
|
||||
}
|
||||
start += i
|
||||
|
||||
end := strings.IndexByte(spec[start:], '>')
|
||||
if end == -1 {
|
||||
return nil, errors.New("missing enclosing >")
|
||||
}
|
||||
end += start
|
||||
|
||||
key, val, _ := strings.Cut(spec[start+1:end], " ")
|
||||
i = end + 1
|
||||
|
||||
builder, ok := ruleBuilders[key]
|
||||
if !ok {
|
||||
errs = append(errs, fmt.Errorf("unknown tag <%s>", key))
|
||||
continue
|
||||
}
|
||||
|
||||
rule, err := builder(val)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to build <%s>: %w", key, err))
|
||||
continue
|
||||
}
|
||||
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import "io"
|
||||
|
||||
func ReadUntil(r io.Reader, b []byte, delim byte) (n int, err error) {
|
||||
for n < len(b) {
|
||||
if _, err = r.Read(b[n : n+1]); err != nil {
|
||||
return n, err
|
||||
}
|
||||
if b[n] == delim {
|
||||
return n, nil
|
||||
}
|
||||
n++
|
||||
}
|
||||
return n, io.ErrShortBuffer
|
||||
}
|
||||
-470
@@ -1,470 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidData = errors.New("invalid data")
|
||||
)
|
||||
|
||||
type readContext struct {
|
||||
*FlexBuffer
|
||||
*BufferPool
|
||||
nextDataSize int
|
||||
}
|
||||
|
||||
type writeContext struct {
|
||||
*FlexBuffer
|
||||
*BufferPool
|
||||
}
|
||||
|
||||
type Rule interface {
|
||||
Spec() string
|
||||
Write(w io.Writer, ctx *writeContext) error
|
||||
Read(r io.Reader, ctx *readContext) error
|
||||
}
|
||||
|
||||
type Rules []Rule
|
||||
|
||||
func (r Rules) Spec() string {
|
||||
var builder strings.Builder
|
||||
for _, rule := range r {
|
||||
builder.WriteString(rule.Spec())
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func (r Rules) Write(w io.Writer, ctx *writeContext) error {
|
||||
for _, rule := range r {
|
||||
if err := rule.Write(w, ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Rules) Read(rd io.Reader, ctx *readContext) error {
|
||||
for _, rule := range r {
|
||||
if err := rule.Read(rd, ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildBytesRule(val string) (Rule, error) {
|
||||
val = strings.TrimPrefix(val, "0x")
|
||||
|
||||
if len(val) == 0 {
|
||||
return nil, errors.New("empty argument")
|
||||
}
|
||||
|
||||
if len(val)%2 != 0 {
|
||||
return nil, errors.New("odd amount of symbols")
|
||||
}
|
||||
|
||||
bytes, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &bytesRule{data: bytes}, nil
|
||||
}
|
||||
|
||||
type bytesRule struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (r *bytesRule) Spec() string {
|
||||
return fmt.Sprintf("<b 0x%x>", r.data)
|
||||
}
|
||||
|
||||
func (r *bytesRule) Write(w io.Writer, ctx *writeContext) error {
|
||||
_, err := w.Write(r.data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *bytesRule) Read(rd io.Reader, ctx *readContext) error {
|
||||
tmp := ctx.Get()
|
||||
defer ctx.Put(tmp)
|
||||
|
||||
buf := tmp[:len(r.data)]
|
||||
if _, err := io.ReadFull(rd, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(buf, r.data) {
|
||||
return errInvalidData
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildRandRule(val string) (Rule, error) {
|
||||
length, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &randRule{length: length}, nil
|
||||
}
|
||||
|
||||
type randRule struct {
|
||||
length int
|
||||
}
|
||||
|
||||
func (r *randRule) Spec() string {
|
||||
return fmt.Sprintf("<r %d>", r.length)
|
||||
}
|
||||
|
||||
func (r *randRule) Write(w io.Writer, ctx *writeContext) error {
|
||||
tmp := ctx.Get()
|
||||
defer ctx.Put(tmp)
|
||||
|
||||
buf := tmp[:r.length]
|
||||
rand.Read(buf)
|
||||
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *randRule) Read(rd io.Reader, ctx *readContext) error {
|
||||
tmp := ctx.Get()
|
||||
defer ctx.Put(tmp)
|
||||
|
||||
buf := tmp[:r.length]
|
||||
if _, err := io.ReadFull(rd, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// I guess, there is no way to validate randomness
|
||||
// so just return nil here like everything is fine
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildRandDigitsRule(val string) (Rule, error) {
|
||||
length, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &randDigitRule{length: length}, nil
|
||||
}
|
||||
|
||||
type randDigitRule struct {
|
||||
length int
|
||||
}
|
||||
|
||||
func (r *randDigitRule) Spec() string {
|
||||
return fmt.Sprintf("<rd %d>", r.length)
|
||||
}
|
||||
|
||||
const digits10 = "0123456789"
|
||||
|
||||
func (r *randDigitRule) Write(w io.Writer, ctx *writeContext) error {
|
||||
tmp := ctx.Get()
|
||||
defer ctx.Put(tmp)
|
||||
|
||||
buf := tmp[:r.length]
|
||||
rand.Read(buf)
|
||||
for i := range buf {
|
||||
buf[i] = digits10[buf[i]%10]
|
||||
}
|
||||
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *randDigitRule) Read(rd io.Reader, ctx *readContext) error {
|
||||
tmp := ctx.Get()
|
||||
defer ctx.Put(tmp)
|
||||
|
||||
buf := tmp[:r.length]
|
||||
if _, err := io.ReadFull(rd, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, b := range buf {
|
||||
if !unicode.IsDigit(rune(b)) {
|
||||
return errInvalidData
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildRandCharRule(val string) (Rule, error) {
|
||||
length, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &randCharRule{length: length}, nil
|
||||
}
|
||||
|
||||
type randCharRule struct {
|
||||
length int
|
||||
}
|
||||
|
||||
func (r *randCharRule) Spec() string {
|
||||
return fmt.Sprintf("<rc %d>", r.length)
|
||||
}
|
||||
|
||||
const chars52 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
func (r *randCharRule) Write(w io.Writer, ctx *writeContext) error {
|
||||
tmp := ctx.Get()
|
||||
defer ctx.Put(tmp)
|
||||
|
||||
buf := tmp[:r.length]
|
||||
rand.Read(buf)
|
||||
for i := range buf {
|
||||
buf[i] = chars52[buf[i]%52]
|
||||
}
|
||||
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *randCharRule) Read(rd io.Reader, ctx *readContext) error {
|
||||
tmp := ctx.Get()
|
||||
defer ctx.Put(tmp)
|
||||
|
||||
buf := tmp[:r.length]
|
||||
if _, err := io.ReadFull(rd, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, b := range buf {
|
||||
if !unicode.IsLetter(rune(b)) {
|
||||
return errInvalidData
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildTimestampRule(_ string) (Rule, error) {
|
||||
return ×tampRule{}, nil
|
||||
}
|
||||
|
||||
type timestampRule struct{}
|
||||
|
||||
func (r *timestampRule) Spec() string {
|
||||
return "<t>"
|
||||
}
|
||||
|
||||
func (r *timestampRule) Write(w io.Writer, ctx *writeContext) error {
|
||||
timestamp := uint32(time.Now().Unix())
|
||||
return binary.Write(w, binary.BigEndian, timestamp)
|
||||
}
|
||||
|
||||
func (r *timestampRule) Read(rd io.Reader, ctx *readContext) error {
|
||||
var timestamp uint32
|
||||
if err := binary.Read(rd, binary.BigEndian, ×tamp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: check timestamp?
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildDataSizeRule(val string) (Rule, error) {
|
||||
var (
|
||||
length int = 2
|
||||
format NumFormat = NumFormatBE
|
||||
end byte = 0
|
||||
err error
|
||||
)
|
||||
|
||||
parts := strings.Fields(val)
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.New("wrong amount of arguments")
|
||||
}
|
||||
|
||||
if format, err = buildNumFormat(parts[0]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch format {
|
||||
case NumFormatAscii, NumFormatHex:
|
||||
parts[1] = strings.TrimPrefix(parts[1], "0x")
|
||||
|
||||
var bytes []byte
|
||||
bytes, err = hex.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(bytes) != 1 {
|
||||
return nil, errors.New("too many bytes")
|
||||
}
|
||||
|
||||
end = bytes[0]
|
||||
default:
|
||||
if length, err = strconv.Atoi(parts[1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &dataSizeRule{
|
||||
length: length,
|
||||
format: format,
|
||||
end: end,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type dataSizeRule struct {
|
||||
format NumFormat
|
||||
length int
|
||||
end byte
|
||||
}
|
||||
|
||||
func (r *dataSizeRule) Spec() string {
|
||||
switch r.format {
|
||||
case NumFormatAscii, NumFormatHex:
|
||||
return fmt.Sprintf("<dz %s 0x%02x>", r.format.Spec(), r.end)
|
||||
}
|
||||
return fmt.Sprintf("<dz %s %d>", r.format.Spec(), r.length)
|
||||
}
|
||||
|
||||
func (r *dataSizeRule) Write(w io.Writer, ctx *writeContext) error {
|
||||
tmp := ctx.Get()
|
||||
defer ctx.Put(tmp)
|
||||
|
||||
size := int32(ctx.Cap())
|
||||
|
||||
switch r.format {
|
||||
case NumFormatBE:
|
||||
for i := r.length - 1; i >= 0; i-- {
|
||||
tmp[i] = byte(size & 0xFF)
|
||||
size >>= 8
|
||||
}
|
||||
if _, err := w.Write(tmp[:r.length]); err != nil {
|
||||
return err
|
||||
}
|
||||
case NumFormatLE:
|
||||
for i := range r.length {
|
||||
tmp[i] = byte(size & 0xFF)
|
||||
size >>= 8
|
||||
}
|
||||
if _, err := w.Write(tmp[:r.length]); err != nil {
|
||||
return err
|
||||
}
|
||||
case NumFormatAscii:
|
||||
b := strconv.AppendInt(tmp[:0], int64(size), 10)
|
||||
b = append(b, r.end)
|
||||
|
||||
if _, err := w.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
case NumFormatHex:
|
||||
b := strconv.AppendInt(tmp[:0], int64(size), 16)
|
||||
b = append(b, r.end)
|
||||
|
||||
if _, err := w.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *dataSizeRule) Read(rd io.Reader, ctx *readContext) error {
|
||||
tmp := ctx.Get()
|
||||
defer ctx.Put(tmp)
|
||||
|
||||
switch r.format {
|
||||
case NumFormatBE:
|
||||
buf := tmp[:r.length]
|
||||
if _, err := io.ReadFull(rd, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
var size int
|
||||
for i := range buf {
|
||||
size <<= 8
|
||||
size |= int(buf[i])
|
||||
}
|
||||
ctx.nextDataSize = size
|
||||
|
||||
case NumFormatLE:
|
||||
buf := tmp[:r.length]
|
||||
if _, err := io.ReadFull(rd, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
var size int
|
||||
for i := len(buf) - 1; i >= 0; i-- {
|
||||
size <<= 8
|
||||
size |= int(buf[i])
|
||||
}
|
||||
ctx.nextDataSize = size
|
||||
|
||||
case NumFormatAscii:
|
||||
n, err := ReadUntil(rd, tmp, r.end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size64, err := strconv.ParseInt(string(tmp[:n]), 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.nextDataSize = int(size64)
|
||||
|
||||
case NumFormatHex:
|
||||
n, err := ReadUntil(rd, tmp, r.end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size64, err := strconv.ParseInt(string(tmp[:n]), 16, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.nextDataSize = int(size64)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildDataRule(val string) (Rule, error) {
|
||||
return &dataRule{}, nil
|
||||
}
|
||||
|
||||
type dataRule struct{}
|
||||
|
||||
func (r *dataRule) Spec() string {
|
||||
return "<d>"
|
||||
}
|
||||
|
||||
func (r *dataRule) Write(w io.Writer, ctx *writeContext) error {
|
||||
buf := ctx.PullHead(-1)
|
||||
if buf == nil {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *dataRule) Read(rd io.Reader, ctx *readContext) error {
|
||||
buf := ctx.PushTail(ctx.nextDataSize)
|
||||
if buf == nil {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
|
||||
_, err := io.ReadFull(rd, buf)
|
||||
return err
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package conceal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoReadRecord = errors.New("stream record reader unavailable")
|
||||
ErrNoWriteRecord = errors.New("stream record writer unavailable")
|
||||
)
|
||||
|
||||
type StreamRecordConn interface {
|
||||
net.Conn
|
||||
CanReadRecord() bool
|
||||
CanWriteRecord() bool
|
||||
ReadRecord(b []byte) (int, error)
|
||||
WriteRecord(b []byte) (int, error)
|
||||
}
|
||||
@@ -1,622 +0,0 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
const benchmarkConnMaxPacketSize = 65535
|
||||
|
||||
func BenchmarkConnEndpointDstToBytes(b *testing.B) {
|
||||
cases := []struct {
|
||||
name string
|
||||
ep Endpoint
|
||||
}{
|
||||
{
|
||||
name: "StdNetEndpoint/IPv4",
|
||||
ep: &StdNetEndpoint{
|
||||
AddrPort: netip.MustParseAddrPort("127.0.0.1:51820"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "StdNetEndpoint/IPv6",
|
||||
ep: &StdNetEndpoint{
|
||||
AddrPort: netip.MustParseAddrPort("[2001:db8::1]:51820"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "streamEndpoint/IPv4",
|
||||
ep: &streamEndpoint{
|
||||
dst: netip.MustParseAddrPort("127.0.0.1:51820"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "streamEndpoint/IPv6",
|
||||
ep: &streamEndpoint{
|
||||
dst: netip.MustParseAddrPort("[2001:db8::1]:51820"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = tc.ep.DstToBytes()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConnCoalesceMessages(b *testing.B) {
|
||||
addr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 51820}
|
||||
ep := &StdNetEndpoint{
|
||||
AddrPort: addr.AddrPort(),
|
||||
src: make([]byte, stickyControlSize),
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
bufs [][]byte
|
||||
}{
|
||||
{
|
||||
name: "batch8/equal_transport",
|
||||
bufs: benchmarkConnEqualPayloads(8, 256),
|
||||
},
|
||||
{
|
||||
name: "batch8/mixed_transport",
|
||||
bufs: benchmarkConnMixedPayloads(8),
|
||||
},
|
||||
{
|
||||
name: "batch64/equal_transport",
|
||||
bufs: benchmarkConnEqualPayloads(64, 256),
|
||||
},
|
||||
{
|
||||
name: "batch64/mixed_transport",
|
||||
bufs: benchmarkConnMixedPayloads(64),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
msgs := benchmarkConnMessages(len(tc.bufs), 2)
|
||||
benchmarkConnRunLoop(b, benchmarkConnTotalBytes(tc.bufs), nil, func() error {
|
||||
_ = coalesceMessages(addr, ep, tc.bufs, msgs, benchmarkConnSetGSOSize)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConnSplitCoalescedMessages(b *testing.B) {
|
||||
cases := []struct {
|
||||
name string
|
||||
build func() []ipv6.Message
|
||||
firstMsgAt int
|
||||
bytesPerOp int
|
||||
reset func([]ipv6.Message)
|
||||
}{
|
||||
{
|
||||
name: "gso0",
|
||||
build: func() []ipv6.Message { return benchmarkConnSplitMessages(1, 256, 0, 0) },
|
||||
firstMsgAt: 0,
|
||||
bytesPerOp: 256,
|
||||
},
|
||||
{
|
||||
name: "gso16_8msgs",
|
||||
build: func() []ipv6.Message { return benchmarkConnSplitMessages(8, 16, 16, 7) },
|
||||
firstMsgAt: 7,
|
||||
bytesPerOp: 8 * 16,
|
||||
reset: func(msgs []ipv6.Message) {
|
||||
for i := 0; i < len(msgs)-1; i++ {
|
||||
msgs[i].N = 0
|
||||
msgs[i].NN = 0
|
||||
}
|
||||
msgs[7].N = 8 * 16
|
||||
msgs[7].NN = 2
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gso128_64msgs",
|
||||
build: func() []ipv6.Message { return benchmarkConnSplitMessages(64, 128, 128, 63) },
|
||||
firstMsgAt: 63,
|
||||
bytesPerOp: 64 * 128,
|
||||
reset: func(msgs []ipv6.Message) {
|
||||
for i := 0; i < len(msgs)-1; i++ {
|
||||
msgs[i].N = 0
|
||||
msgs[i].NN = 0
|
||||
}
|
||||
msgs[63].N = 64 * 128
|
||||
msgs[63].NN = 2
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
if tc.reset == nil {
|
||||
msgs := tc.build()
|
||||
benchmarkConnRunLoop(b, tc.bytesPerOp, nil, func() error {
|
||||
_, err := splitCoalescedMessages(msgs, tc.firstMsgAt, benchmarkConnGetGSOSize)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const fixtureRingSize = 256
|
||||
fixtures := make([][]ipv6.Message, fixtureRingSize)
|
||||
for i := range fixtures {
|
||||
fixtures[i] = tc.build()
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
if tc.bytesPerOp > 0 {
|
||||
b.SetBytes(int64(tc.bytesPerOp))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i > 0 && i%fixtureRingSize == 0 {
|
||||
b.StopTimer()
|
||||
for j := range fixtures {
|
||||
tc.reset(fixtures[j])
|
||||
}
|
||||
b.StartTimer()
|
||||
}
|
||||
|
||||
_, err := splitCoalescedMessages(fixtures[i%fixtureRingSize], tc.firstMsgAt, benchmarkConnGetGSOSize)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConnStdNetBindSend(b *testing.B) {
|
||||
cases := []struct {
|
||||
name string
|
||||
batchLen int
|
||||
offload bool
|
||||
}{
|
||||
{name: "gso_off/batch1", batchLen: 1, offload: false},
|
||||
{name: "gso_off/batch8", batchLen: 8, offload: false},
|
||||
{name: "gso_on/batch8", batchLen: 8, offload: true},
|
||||
{name: "gso_on/batch64", batchLen: 64, offload: true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
bind := NewStdNetBind().(*StdNetBind)
|
||||
bind.ipv4 = &benchmarkConnUDPConn{}
|
||||
bind.ipv4PC = &benchmarkConnLinuxPacketConn{}
|
||||
bind.ipv4TxOffload = tc.offload
|
||||
|
||||
endpoint := &StdNetEndpoint{
|
||||
AddrPort: netip.MustParseAddrPort("127.0.0.1:51820"),
|
||||
src: make([]byte, stickyControlSize),
|
||||
}
|
||||
|
||||
if !tc.offload {
|
||||
bufs := benchmarkConnSendPayloads(tc.batchLen, 256, false)
|
||||
benchmarkConnRunLoop(b, benchmarkConnTotalBytes(bufs), nil, func() error {
|
||||
return bind.Send(bufs, endpoint)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
fixtures := make([]benchmarkConnSendFixture, benchmarkConnFixtureRingSize)
|
||||
for i := range fixtures {
|
||||
fixtures[i] = newBenchmarkConnSendFixture(tc.batchLen, 256, true)
|
||||
}
|
||||
benchmarkConnRunLoopWithFixtureRing(b, benchmarkConnTotalBytes(fixtures[0].bufs), fixtures, func(f benchmarkConnSendFixture) {
|
||||
benchmarkConnResetPayloadLens(f.bufs, f.lens)
|
||||
}, func(f benchmarkConnSendFixture) error {
|
||||
return bind.Send(f.bufs, endpoint)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConnStdNetBindReceive(b *testing.B) {
|
||||
cases := []struct {
|
||||
name string
|
||||
batchSize int
|
||||
rxOffload bool
|
||||
reader *benchmarkConnLinuxPacketConn
|
||||
conn *benchmarkConnUDPConn
|
||||
}{
|
||||
{
|
||||
name: "single",
|
||||
batchSize: 1,
|
||||
rxOffload: false,
|
||||
reader: &benchmarkConnLinuxPacketConn{
|
||||
readBatches: [][]benchmarkConnPacket{benchmarkConnBatchPackets(1, 256)},
|
||||
},
|
||||
conn: &benchmarkConnUDPConn{
|
||||
readPayloads: [][]byte{benchmarkConnPayload(256)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "batch",
|
||||
batchSize: 8,
|
||||
rxOffload: false,
|
||||
reader: &benchmarkConnLinuxPacketConn{
|
||||
readBatches: [][]benchmarkConnPacket{benchmarkConnBatchPackets(8, 256)},
|
||||
},
|
||||
conn: &benchmarkConnUDPConn{
|
||||
readPayloads: [][]byte{benchmarkConnPayload(256)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "batch_rx_offload",
|
||||
batchSize: 64,
|
||||
rxOffload: true,
|
||||
reader: &benchmarkConnLinuxPacketConn{
|
||||
readBatches: [][]benchmarkConnPacket{benchmarkConnCoalescedBatch(64, 256)},
|
||||
},
|
||||
conn: &benchmarkConnUDPConn{
|
||||
readPayloads: [][]byte{benchmarkConnPayload(256)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
bind := NewStdNetBind().(*StdNetBind)
|
||||
bufs := make([][]byte, tc.batchSize)
|
||||
for i := range bufs {
|
||||
bufs[i] = make([]byte, benchmarkConnMaxPacketSize)
|
||||
}
|
||||
sizes := make([]int, tc.batchSize)
|
||||
eps := make([]Endpoint, tc.batchSize)
|
||||
|
||||
benchmarkConnRunLoop(b, tc.batchSize*256, nil, func() error {
|
||||
_, err := bind.receiveIP(tc.reader, tc.conn, tc.rxOffload, bufs, sizes, eps)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkConnRunLoop(b *testing.B, bytesPerOp int, reset func(), op func() error) {
|
||||
b.Helper()
|
||||
b.ReportAllocs()
|
||||
if bytesPerOp > 0 {
|
||||
b.SetBytes(int64(bytesPerOp))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i > 0 && reset != nil {
|
||||
b.StopTimer()
|
||||
reset()
|
||||
b.StartTimer()
|
||||
}
|
||||
if err := op(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkConnRunLoopWithFixtureRing[T any](b *testing.B, bytesPerOp int, fixtures []T, reset func(T), op func(T) error) {
|
||||
b.Helper()
|
||||
if len(fixtures) == 0 {
|
||||
b.Fatal("benchmark fixture ring must not be empty")
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
if bytesPerOp > 0 {
|
||||
b.SetBytes(int64(bytesPerOp))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i > 0 && i%len(fixtures) == 0 && reset != nil {
|
||||
b.StopTimer()
|
||||
for _, fixture := range fixtures {
|
||||
reset(fixture)
|
||||
}
|
||||
b.StartTimer()
|
||||
}
|
||||
if err := op(fixtures[i%len(fixtures)]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkConnPayload(size int) []byte {
|
||||
payload := make([]byte, size)
|
||||
for i := range payload {
|
||||
payload[i] = byte(i)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func benchmarkConnEqualPayloads(count, size int) [][]byte {
|
||||
bufs := make([][]byte, count)
|
||||
for i := range bufs {
|
||||
capacity := size
|
||||
if i == 0 {
|
||||
capacity = size * count
|
||||
}
|
||||
buf := make([]byte, size, capacity)
|
||||
for j := range buf {
|
||||
buf[j] = byte(j)
|
||||
}
|
||||
bufs[i] = buf
|
||||
}
|
||||
return bufs
|
||||
}
|
||||
|
||||
func benchmarkConnMixedPayloads(count int) [][]byte {
|
||||
bufs := make([][]byte, count)
|
||||
for i := range bufs {
|
||||
size := 256
|
||||
if i%2 == 1 {
|
||||
size = 1280
|
||||
}
|
||||
capacity := size
|
||||
if i == 0 {
|
||||
capacity = size * count
|
||||
}
|
||||
buf := make([]byte, size, capacity)
|
||||
for j := range buf {
|
||||
buf[j] = byte(j)
|
||||
}
|
||||
bufs[i] = buf
|
||||
}
|
||||
return bufs
|
||||
}
|
||||
|
||||
func benchmarkConnSendPayloads(count, size int, offload bool) [][]byte {
|
||||
bufs := make([][]byte, count)
|
||||
for i := range bufs {
|
||||
capacity := size
|
||||
if offload && i == 0 {
|
||||
capacity = size * count
|
||||
}
|
||||
buf := make([]byte, size, capacity)
|
||||
for j := range buf {
|
||||
buf[j] = byte(j)
|
||||
}
|
||||
bufs[i] = buf
|
||||
}
|
||||
return bufs
|
||||
}
|
||||
|
||||
func benchmarkConnLengths(bufs [][]byte) []int {
|
||||
lens := make([]int, len(bufs))
|
||||
for i := range bufs {
|
||||
lens[i] = len(bufs[i])
|
||||
}
|
||||
return lens
|
||||
}
|
||||
|
||||
func benchmarkConnResetPayloadLens(bufs [][]byte, lens []int) {
|
||||
for i := range bufs {
|
||||
bufs[i] = bufs[i][:lens[i]]
|
||||
}
|
||||
}
|
||||
|
||||
const benchmarkConnFixtureRingSize = 256
|
||||
|
||||
type benchmarkConnSendFixture struct {
|
||||
bufs [][]byte
|
||||
lens []int
|
||||
}
|
||||
|
||||
func newBenchmarkConnSendFixture(count, size int, offload bool) benchmarkConnSendFixture {
|
||||
bufs := benchmarkConnSendPayloads(count, size, offload)
|
||||
return benchmarkConnSendFixture{
|
||||
bufs: bufs,
|
||||
lens: benchmarkConnLengths(bufs),
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkConnTotalBytes(bufs [][]byte) int {
|
||||
total := 0
|
||||
for _, buf := range bufs {
|
||||
total += len(buf)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func benchmarkConnMessages(count int, oobCap int) []ipv6.Message {
|
||||
msgs := make([]ipv6.Message, count)
|
||||
for i := range msgs {
|
||||
msgs[i].Buffers = make([][]byte, 1)
|
||||
msgs[i].OOB = make([]byte, 0, oobCap)
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
func benchmarkConnResetMessages(msgs []ipv6.Message) {
|
||||
for i := range msgs {
|
||||
msgs[i].Addr = nil
|
||||
msgs[i].N = 0
|
||||
msgs[i].NN = 0
|
||||
msgs[i].OOB = msgs[i].OOB[:0]
|
||||
msgs[i].Buffers[0] = nil
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkConnSetGSOSize(control *[]byte, gsoSize uint16) {
|
||||
*control = (*control)[:cap(*control)]
|
||||
binary.LittleEndian.PutUint16(*control, gsoSize)
|
||||
}
|
||||
|
||||
func benchmarkConnGetGSOSize(control []byte) (int, error) {
|
||||
if len(control) < 2 {
|
||||
return 0, nil
|
||||
}
|
||||
return int(binary.LittleEndian.Uint16(control)), nil
|
||||
}
|
||||
|
||||
func benchmarkConnSplitMessages(numMsgs, segmentSize, gsoSize, sourceIndex int) []ipv6.Message {
|
||||
msgs := make([]ipv6.Message, numMsgs)
|
||||
for i := range msgs {
|
||||
bufSize := segmentSize
|
||||
if i == sourceIndex {
|
||||
bufSize = numMsgs * segmentSize
|
||||
}
|
||||
msgs[i].Buffers = [][]byte{make([]byte, bufSize)}
|
||||
msgs[i].OOB = make([]byte, 0, 2)
|
||||
}
|
||||
for i := 0; i < numMsgs*segmentSize; i++ {
|
||||
msgs[sourceIndex].Buffers[0][i] = byte(i)
|
||||
}
|
||||
msgs[sourceIndex].N = numMsgs * segmentSize
|
||||
if gsoSize > 0 {
|
||||
msgs[sourceIndex].OOB = msgs[sourceIndex].OOB[:2]
|
||||
binary.LittleEndian.PutUint16(msgs[sourceIndex].OOB, uint16(gsoSize))
|
||||
msgs[sourceIndex].NN = 2
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
type benchmarkConnPacket struct {
|
||||
payload []byte
|
||||
oob []byte
|
||||
}
|
||||
|
||||
func benchmarkConnBatchPackets(count, size int) []benchmarkConnPacket {
|
||||
packets := make([]benchmarkConnPacket, count)
|
||||
for i := range packets {
|
||||
packets[i].payload = benchmarkConnPayload(size)
|
||||
}
|
||||
return packets
|
||||
}
|
||||
|
||||
func benchmarkConnCoalescedBatch(count, segmentSize int) []benchmarkConnPacket {
|
||||
payload := make([]byte, count*segmentSize)
|
||||
for i := range payload {
|
||||
payload[i] = byte(i)
|
||||
}
|
||||
oob := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(oob, uint16(segmentSize))
|
||||
return []benchmarkConnPacket{
|
||||
{
|
||||
payload: payload,
|
||||
oob: oob,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type benchmarkConnLinuxPacketConn struct {
|
||||
readBatches [][]benchmarkConnPacket
|
||||
readIndex int
|
||||
}
|
||||
|
||||
func (c *benchmarkConnLinuxPacketConn) ResetRead() {
|
||||
c.readIndex = 0
|
||||
}
|
||||
|
||||
func (c *benchmarkConnLinuxPacketConn) ReadBatch(ms []ipv4.Message, flags int) (int, error) {
|
||||
if len(c.readBatches) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
batch := c.readBatches[c.readIndex]
|
||||
c.readIndex++
|
||||
if c.readIndex == len(c.readBatches) {
|
||||
c.readIndex = 0
|
||||
}
|
||||
for i := range batch {
|
||||
msg := &ms[i]
|
||||
packet := batch[i]
|
||||
msg.N = copy(msg.Buffers[0], packet.payload)
|
||||
if cap(msg.OOB) < len(packet.oob) {
|
||||
msg.OOB = make([]byte, len(packet.oob))
|
||||
}
|
||||
msg.OOB = msg.OOB[:len(packet.oob)]
|
||||
copy(msg.OOB, packet.oob)
|
||||
msg.NN = len(packet.oob)
|
||||
msg.Addr = &net.UDPAddr{
|
||||
IP: net.IPv4(127, 0, 0, 1),
|
||||
Port: 51820,
|
||||
}
|
||||
}
|
||||
for i := len(batch); i < len(ms); i++ {
|
||||
ms[i].N = 0
|
||||
ms[i].NN = 0
|
||||
ms[i].Addr = nil
|
||||
ms[i].OOB = ms[i].OOB[:0]
|
||||
}
|
||||
return len(batch), nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnLinuxPacketConn) WriteBatch(ms []ipv4.Message, flags int) (int, error) {
|
||||
return len(ms), nil
|
||||
}
|
||||
|
||||
type benchmarkConnUDPConn struct {
|
||||
readPayloads [][]byte
|
||||
readIndex int
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) ResetRead() {
|
||||
c.readIndex = 0
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) ReadFrom(p []byte) (int, net.Addr, error) {
|
||||
n, _, _, addr, err := c.ReadMsgUDP(p, nil)
|
||||
return n, addr, err
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) WriteTo(p []byte, addr net.Addr) (int, error) {
|
||||
udpAddr, _ := addr.(*net.UDPAddr)
|
||||
n, _, err := c.WriteMsgUDP(p, nil, udpAddr)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) LocalAddr() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
IP: net.IPv4(127, 0, 0, 1),
|
||||
Port: 51820,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) SetDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) SetReadDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) SyscallConn() (syscall.RawConn, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
|
||||
if len(c.readPayloads) == 0 {
|
||||
return 0, 0, 0, nil, io.EOF
|
||||
}
|
||||
payload := c.readPayloads[c.readIndex]
|
||||
c.readIndex++
|
||||
if c.readIndex == len(c.readPayloads) {
|
||||
c.readIndex = 0
|
||||
}
|
||||
n = copy(b, payload)
|
||||
return n, 0, 0, &net.UDPAddr{
|
||||
IP: net.IPv4(127, 0, 0, 1),
|
||||
Port: 51820,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (int, int, error) {
|
||||
return len(b), len(oob), nil
|
||||
}
|
||||
+12
-31
@@ -16,16 +16,12 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conceal"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Bind = (*StdNetBind)(nil)
|
||||
_ Framable = (*StdNetBind)(nil)
|
||||
_ Preludable = (*StdNetBind)(nil)
|
||||
_ Masqueradable = (*StdNetBind)(nil)
|
||||
_ Bind = (*StdNetBind)(nil)
|
||||
)
|
||||
|
||||
// StdNetBind implements Bind for all platforms. While Windows has its own Bind
|
||||
@@ -35,10 +31,10 @@ var (
|
||||
// proposal in https://github.com/golang/go/issues/45886#issuecomment-1218301564.
|
||||
type StdNetBind struct {
|
||||
mu sync.Mutex // protects all fields except as specified
|
||||
ipv4 UDPConn
|
||||
ipv6 UDPConn
|
||||
ipv4PC LinuxPacketConn // will be nil on non-Linux
|
||||
ipv6PC LinuxPacketConn // will be nil on non-Linux
|
||||
ipv4 *net.UDPConn
|
||||
ipv6 *net.UDPConn
|
||||
ipv4PC *ipv4.PacketConn // will be nil on non-Linux
|
||||
ipv6PC *ipv6.PacketConn // will be nil on non-Linux
|
||||
ipv4TxOffload bool
|
||||
ipv4RxOffload bool
|
||||
ipv6TxOffload bool
|
||||
@@ -47,14 +43,9 @@ type StdNetBind struct {
|
||||
// these two fields are not guarded by mu
|
||||
udpAddrPool sync.Pool
|
||||
msgsPool sync.Pool
|
||||
bufPool sync.Pool
|
||||
|
||||
blackhole4 bool
|
||||
blackhole6 bool
|
||||
|
||||
framedOpts conceal.FramedOpts
|
||||
preludeOpts conceal.PreludeOpts
|
||||
masqueradeOpts conceal.MasqueradeOpts
|
||||
}
|
||||
|
||||
func NewStdNetBind() Bind {
|
||||
@@ -79,12 +70,6 @@ func NewStdNetBind() Bind {
|
||||
return &msgs
|
||||
},
|
||||
},
|
||||
|
||||
bufPool: sync.Pool{
|
||||
New: func() any {
|
||||
return make([]byte, 65535)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,9 +152,9 @@ func (s *StdNetBind) Open(uport uint16) ([]ReceiveFunc, uint16, error) {
|
||||
// If uport is 0, we can retry on failure.
|
||||
again:
|
||||
port := int(uport)
|
||||
var v4conn, v6conn UDPConn
|
||||
var v4pc LinuxPacketConn
|
||||
var v6pc LinuxPacketConn
|
||||
var v4conn, v6conn *net.UDPConn
|
||||
var v4pc *ipv4.PacketConn
|
||||
var v6pc *ipv6.PacketConn
|
||||
|
||||
v4conn, port, err = listenNet("udp4", port)
|
||||
if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||
@@ -192,10 +177,8 @@ again:
|
||||
s.ipv4TxOffload, s.ipv4RxOffload = supportsUDPOffload(v4conn)
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||
v4pc = ipv4.NewPacketConn(v4conn)
|
||||
v4pc = s.upgradePacketConn(v4pc)
|
||||
s.ipv4PC = v4pc
|
||||
}
|
||||
v4conn = s.upgradeUDPConn(v4conn)
|
||||
fns = append(fns, s.makeReceiveIPv4(v4pc, v4conn, s.ipv4RxOffload))
|
||||
s.ipv4 = v4conn
|
||||
}
|
||||
@@ -203,10 +186,8 @@ again:
|
||||
s.ipv6TxOffload, s.ipv6RxOffload = supportsUDPOffload(v6conn)
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||
v6pc = ipv6.NewPacketConn(v6conn)
|
||||
v6pc = s.upgradePacketConn(v6pc)
|
||||
s.ipv6PC = v6pc
|
||||
}
|
||||
v6conn = s.upgradeUDPConn(v6conn)
|
||||
fns = append(fns, s.makeReceiveIPv6(v6pc, v6conn, s.ipv6RxOffload))
|
||||
s.ipv6 = v6conn
|
||||
}
|
||||
@@ -244,7 +225,7 @@ type batchWriter interface {
|
||||
|
||||
func (s *StdNetBind) receiveIP(
|
||||
br batchReader,
|
||||
conn UDPConn,
|
||||
conn *net.UDPConn,
|
||||
rxOffload bool,
|
||||
bufs [][]byte,
|
||||
sizes []int,
|
||||
@@ -296,13 +277,13 @@ func (s *StdNetBind) receiveIP(
|
||||
return numMsgs, nil
|
||||
}
|
||||
|
||||
func (s *StdNetBind) makeReceiveIPv4(pc LinuxPacketConn, conn UDPConn, rxOffload bool) ReceiveFunc {
|
||||
func (s *StdNetBind) makeReceiveIPv4(pc *ipv4.PacketConn, conn *net.UDPConn, rxOffload bool) ReceiveFunc {
|
||||
return func(bufs [][]byte, sizes []int, eps []Endpoint) (n int, err error) {
|
||||
return s.receiveIP(pc, conn, rxOffload, bufs, sizes, eps)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StdNetBind) makeReceiveIPv6(pc LinuxPacketConn, conn UDPConn, rxOffload bool) ReceiveFunc {
|
||||
func (s *StdNetBind) makeReceiveIPv6(pc *ipv6.PacketConn, conn *net.UDPConn, rxOffload bool) ReceiveFunc {
|
||||
return func(bufs [][]byte, sizes []int, eps []Endpoint) (n int, err error) {
|
||||
return s.receiveIP(pc, conn, rxOffload, bufs, sizes, eps)
|
||||
}
|
||||
@@ -428,7 +409,7 @@ retry:
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StdNetBind) send(conn UDPConn, pc batchWriter, msgs []ipv6.Message) error {
|
||||
func (s *StdNetBind) send(conn *net.UDPConn, pc batchWriter, msgs []ipv6.Message) error {
|
||||
var (
|
||||
n int
|
||||
err error
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conceal"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Bind = (*BindStream)(nil)
|
||||
_ Framable = (*BindStream)(nil)
|
||||
_ Preludable = (*BindStream)(nil)
|
||||
_ Masqueradable = (*BindStream)(nil)
|
||||
)
|
||||
|
||||
type streamPacketQueue struct {
|
||||
ep *streamEndpoint
|
||||
buf [65535]byte
|
||||
n int
|
||||
}
|
||||
|
||||
func NewBindStream() *BindStream {
|
||||
return &BindStream{
|
||||
streamPacketPool: sync.Pool{
|
||||
New: func() any {
|
||||
return new(streamPacketQueue)
|
||||
},
|
||||
},
|
||||
bufferPool: sync.Pool{
|
||||
New: func() any {
|
||||
return make([]byte, 65535)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type BindStream struct {
|
||||
queue chan *streamPacketQueue
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
streamPacketPool sync.Pool
|
||||
bufferPool sync.Pool
|
||||
dialer net.Dialer
|
||||
listenConfig net.ListenConfig
|
||||
port uint16
|
||||
|
||||
framedOpts conceal.FramedOpts
|
||||
preludeOpts conceal.PreludeOpts
|
||||
masqueradeOpts conceal.MasqueradeOpts
|
||||
}
|
||||
|
||||
func (b *BindStream) readFaucet() ReceiveFunc {
|
||||
return func(packets [][]byte, sizes []int, eps []Endpoint) (n int, err error) {
|
||||
streamPacket, ok := <-b.queue
|
||||
if !ok {
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
|
||||
packet := streamPacket.buf[:streamPacket.n]
|
||||
|
||||
copy(packets[0], packet)
|
||||
sizes[0] = streamPacket.n
|
||||
eps[0] = streamPacket.ep
|
||||
|
||||
b.streamPacketPool.Put(streamPacket)
|
||||
return 1, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BindStream) readStream(ep *streamEndpoint) {
|
||||
defer b.wg.Done()
|
||||
|
||||
for {
|
||||
sp := b.streamPacketPool.Get().(*streamPacketQueue)
|
||||
n, err := ep.conn.Read(sp.buf[:])
|
||||
if err != nil {
|
||||
ep.Close()
|
||||
return
|
||||
}
|
||||
|
||||
sp.ep, sp.n = ep, n
|
||||
b.queue <- sp
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BindStream) accept(listener net.Listener) {
|
||||
defer b.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-b.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
// log this error somewhere
|
||||
break
|
||||
}
|
||||
conn = b.upgradeConn(conn)
|
||||
|
||||
b.wg.Add(1)
|
||||
go b.handleAccepted(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BindStream) handleAccepted(conn net.Conn) {
|
||||
defer b.wg.Done()
|
||||
|
||||
ap, err := netip.ParseAddrPort(conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
// add logs
|
||||
}
|
||||
|
||||
ep := &streamEndpoint{
|
||||
conn: conn,
|
||||
dst: ap,
|
||||
}
|
||||
|
||||
b.wg.Add(1)
|
||||
go b.readStream(ep)
|
||||
|
||||
<-b.ctx.Done()
|
||||
ep.Close()
|
||||
}
|
||||
|
||||
func (b *BindStream) dial(ep *streamEndpoint) error {
|
||||
ep.mutex.Lock()
|
||||
defer ep.mutex.Unlock()
|
||||
|
||||
if ep.conn != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := b.dialer.DialContext(b.ctx, "tcp", ep.DstToString())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to dial context: %v", err)
|
||||
}
|
||||
|
||||
conn = b.upgradeConn(conn)
|
||||
ep.conn = conn
|
||||
|
||||
b.wg.Add(1)
|
||||
go func() {
|
||||
defer b.wg.Done()
|
||||
<-b.ctx.Done()
|
||||
ep.Close()
|
||||
}()
|
||||
|
||||
b.wg.Add(1)
|
||||
go b.readStream(ep)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BindStream) openConnections() error {
|
||||
b.ctx, b.cancel = context.WithCancel(context.Background())
|
||||
|
||||
if b.port != 0 {
|
||||
listener, err := b.listenConfig.Listen(b.ctx, "tcp", ":"+strconv.Itoa(int(b.port)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if addr, ok := listener.Addr().(*net.TCPAddr); ok {
|
||||
b.port = uint16(addr.Port)
|
||||
}
|
||||
|
||||
b.wg.Add(1)
|
||||
go func() {
|
||||
defer b.wg.Done()
|
||||
<-b.ctx.Done()
|
||||
listener.Close()
|
||||
}()
|
||||
|
||||
b.wg.Add(1)
|
||||
go b.accept(listener)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BindStream) Open(port uint16) (fns []ReceiveFunc, actualPort uint16, err error) {
|
||||
b.queue = make(chan *streamPacketQueue, 1024)
|
||||
b.port = port
|
||||
|
||||
err = b.openConnections()
|
||||
return []ReceiveFunc{b.readFaucet()}, b.port, err
|
||||
}
|
||||
|
||||
func (b *BindStream) Send(bufs [][]byte, ep Endpoint) error {
|
||||
streamEp, ok := ep.(*streamEndpoint)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-b.ctx.Done():
|
||||
return io.ErrClosedPipe
|
||||
default:
|
||||
}
|
||||
|
||||
if err := b.dial(streamEp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, buf := range bufs {
|
||||
if _, err := streamEp.conn.Write(buf); err != nil {
|
||||
streamEp.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BindStream) closeConnections() {
|
||||
if b.cancel != nil {
|
||||
b.cancel()
|
||||
}
|
||||
b.wg.Wait()
|
||||
}
|
||||
|
||||
func (b *BindStream) Close() error {
|
||||
b.closeConnections()
|
||||
|
||||
if b.queue != nil {
|
||||
close(b.queue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BindStream) ParseEndpoint(s string) (Endpoint, error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve addr: %v", err)
|
||||
}
|
||||
|
||||
return &streamEndpoint{
|
||||
dst: tcpAddr.AddrPort(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *BindStream) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
var _ Endpoint = (*streamEndpoint)(nil)
|
||||
|
||||
type streamEndpoint struct {
|
||||
conn net.Conn
|
||||
|
||||
dst netip.AddrPort
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (e *streamEndpoint) Close() {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
if e.conn != nil {
|
||||
e.conn.Close()
|
||||
e.conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *streamEndpoint) DstToString() string {
|
||||
return e.dst.String()
|
||||
}
|
||||
|
||||
func (e *streamEndpoint) DstToBytes() []byte {
|
||||
b, _ := e.dst.MarshalBinary()
|
||||
return b
|
||||
}
|
||||
|
||||
func (e *streamEndpoint) DstIP() netip.Addr {
|
||||
return e.dst.Addr()
|
||||
}
|
||||
|
||||
func (e *streamEndpoint) ClearSrc() {
|
||||
}
|
||||
|
||||
func (e *streamEndpoint) SrcToString() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *streamEndpoint) SrcIP() netip.Addr {
|
||||
return netip.Addr{}
|
||||
}
|
||||
+21
-1
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn/winrio"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn/winrio"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -77,6 +77,8 @@ type WinRingBind struct {
|
||||
isOpen atomic.Uint32 // 0, 1, or 2
|
||||
}
|
||||
|
||||
func NewDefaultBind() Bind { return NewWinRingBind() }
|
||||
|
||||
func NewWinRingBind() Bind {
|
||||
if !winrio.Initialize() {
|
||||
return NewStdNetBind()
|
||||
@@ -580,6 +582,24 @@ func (bind *WinRingBind) BindSocketToInterface6(interfaceIndex uint32, blackhole
|
||||
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 {
|
||||
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. */
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
)
|
||||
|
||||
type ChannelBind struct {
|
||||
|
||||
File renamed without changes.
-180
@@ -1,180 +0,0 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conceal"
|
||||
)
|
||||
|
||||
type Framable interface {
|
||||
SetFramedOpts(opts conceal.FramedOpts)
|
||||
}
|
||||
|
||||
type Preludable interface {
|
||||
SetPreludeOpts(opts conceal.PreludeOpts)
|
||||
}
|
||||
|
||||
type Masqueradable interface {
|
||||
SetMasqueradeOpts(opts conceal.MasqueradeOpts)
|
||||
}
|
||||
|
||||
type concealStage string
|
||||
|
||||
const (
|
||||
concealStageMasquerade concealStage = "masquerade"
|
||||
concealStageRecord concealStage = "record"
|
||||
concealStageFramed concealStage = "framed"
|
||||
concealStagePrelude concealStage = "prelude"
|
||||
)
|
||||
|
||||
type concealPipeline struct {
|
||||
stages []concealStage
|
||||
}
|
||||
|
||||
func (p concealPipeline) names() []string {
|
||||
names := make([]string, 0, len(p.stages))
|
||||
for _, stage := range p.stages {
|
||||
names = append(names, string(stage))
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func hasFramed(opts conceal.FramedOpts) bool {
|
||||
return opts.H1 != nil || opts.H2 != nil || opts.H3 != nil || opts.H4 != nil ||
|
||||
opts.S1 != 0 || opts.S2 != 0 || opts.S3 != 0 || opts.S4 != 0
|
||||
}
|
||||
|
||||
func hasMasquerade(opts conceal.MasqueradeOpts) bool {
|
||||
return opts.RulesIn != nil || opts.RulesOut != nil
|
||||
}
|
||||
|
||||
func hasBidirectionalStreamRecords(opts conceal.MasqueradeOpts) bool {
|
||||
return opts.RulesIn != nil && opts.RulesOut != nil
|
||||
}
|
||||
|
||||
func udpPreludeHeader(opts conceal.FramedOpts) *conceal.RangedHeader {
|
||||
if opts.HeaderCompat {
|
||||
return opts.H1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *StdNetBind) udpConcealPipeline() concealPipeline {
|
||||
stages := make([]concealStage, 0, 3)
|
||||
if hasMasquerade(b.masqueradeOpts) {
|
||||
stages = append(stages, concealStageMasquerade)
|
||||
}
|
||||
if hasFramed(b.framedOpts) {
|
||||
stages = append(stages, concealStageFramed)
|
||||
}
|
||||
if !b.preludeOpts.IsEmpty() {
|
||||
stages = append(stages, concealStagePrelude)
|
||||
}
|
||||
return concealPipeline{stages: stages}
|
||||
}
|
||||
|
||||
func (b *StdNetBind) batchConcealPipeline() concealPipeline {
|
||||
return b.udpConcealPipeline()
|
||||
}
|
||||
|
||||
func (b *BindStream) streamConcealPipeline() concealPipeline {
|
||||
stages := make([]concealStage, 0, 3)
|
||||
if hasMasquerade(b.masqueradeOpts) {
|
||||
stages = append(stages, concealStageRecord)
|
||||
}
|
||||
if b.preludeOpts.HasDecoyRules() && hasBidirectionalStreamRecords(b.masqueradeOpts) {
|
||||
stages = append(stages, concealStagePrelude)
|
||||
}
|
||||
if hasFramed(b.framedOpts) {
|
||||
stages = append(stages, concealStageFramed)
|
||||
}
|
||||
return concealPipeline{stages: stages}
|
||||
}
|
||||
|
||||
func (b *StdNetBind) upgradeUDPConn(conn UDPConn) UDPConn {
|
||||
origin := conn
|
||||
for _, stage := range b.udpConcealPipeline().stages {
|
||||
switch stage {
|
||||
case concealStageMasquerade:
|
||||
if masquerade, ok := conceal.NewMasqueradeUDPConn(conn, &b.bufPool, b.masqueradeOpts); ok {
|
||||
conn = masquerade
|
||||
}
|
||||
case concealStageFramed:
|
||||
if framed, ok := conceal.NewFramedUDPConn(conn, &b.bufPool, b.framedOpts); ok {
|
||||
conn = framed
|
||||
}
|
||||
case concealStagePrelude:
|
||||
if prelude, ok := conceal.NewPreludeUDPConn(conn, origin, &b.bufPool, udpPreludeHeader(b.framedOpts), b.preludeOpts); ok {
|
||||
conn = prelude
|
||||
}
|
||||
}
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func (b *StdNetBind) upgradePacketConn(conn LinuxPacketConn) LinuxPacketConn {
|
||||
origin := conn
|
||||
for _, stage := range b.batchConcealPipeline().stages {
|
||||
switch stage {
|
||||
case concealStageMasquerade:
|
||||
if masquerade, ok := conceal.NewMasqueradeBatchConn(conn, &b.bufPool, b.masqueradeOpts); ok {
|
||||
conn = masquerade
|
||||
}
|
||||
case concealStageFramed:
|
||||
if framed, ok := conceal.NewFramedBatchConn(conn, &b.bufPool, b.framedOpts); ok {
|
||||
conn = framed
|
||||
}
|
||||
case concealStagePrelude:
|
||||
if prelude, ok := conceal.NewPreludeBatchConn(conn, origin, &b.bufPool, &b.msgsPool, udpPreludeHeader(b.framedOpts), b.preludeOpts); ok {
|
||||
conn = prelude
|
||||
}
|
||||
}
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func (b *StdNetBind) SetFramedOpts(opts conceal.FramedOpts) {
|
||||
b.framedOpts = opts
|
||||
}
|
||||
|
||||
func (b *StdNetBind) SetPreludeOpts(opts conceal.PreludeOpts) {
|
||||
b.preludeOpts = opts
|
||||
}
|
||||
|
||||
func (b *StdNetBind) SetMasqueradeOpts(opts conceal.MasqueradeOpts) {
|
||||
b.masqueradeOpts = opts
|
||||
}
|
||||
|
||||
func (b *BindStream) upgradeConn(conn net.Conn) net.Conn {
|
||||
var recordConn conceal.StreamRecordConn
|
||||
for _, stage := range b.streamConcealPipeline().stages {
|
||||
switch stage {
|
||||
case concealStageRecord:
|
||||
if masquerade, ok := conceal.NewMasqueradeConn(conn, &b.bufferPool, b.masqueradeOpts); ok {
|
||||
recordConn = masquerade
|
||||
conn = masquerade
|
||||
}
|
||||
case concealStagePrelude:
|
||||
if prelude, ok := conceal.NewPreludeConn(recordConn, &b.bufferPool, b.framedOpts, b.preludeOpts); ok {
|
||||
conn = prelude
|
||||
}
|
||||
case concealStageFramed:
|
||||
if framed, ok := conceal.NewFramedConn(conn, &b.bufferPool, b.framedOpts); ok {
|
||||
conn = framed
|
||||
}
|
||||
}
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func (b *BindStream) SetFramedOpts(opts conceal.FramedOpts) {
|
||||
b.framedOpts = opts
|
||||
}
|
||||
|
||||
func (b *BindStream) SetPreludeOpts(opts conceal.PreludeOpts) {
|
||||
b.preludeOpts = opts
|
||||
}
|
||||
|
||||
func (b *BindStream) SetMasqueradeOpts(opts conceal.MasqueradeOpts) {
|
||||
b.masqueradeOpts = opts
|
||||
}
|
||||
@@ -1,455 +0,0 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"slices"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conceal"
|
||||
)
|
||||
|
||||
func TestStdNetBindUDPPipelineOrder(t *testing.T) {
|
||||
bind := NewStdNetBind().(*StdNetBind)
|
||||
bind.SetMasqueradeOpts(conceal.MasqueradeOpts{
|
||||
RulesIn: mustParseRules(t, "<dz be 2><d>"),
|
||||
RulesOut: mustParseRules(t, "<dz be 2><d>"),
|
||||
})
|
||||
bind.SetFramedOpts(conceal.FramedOpts{H1: mustHeader(t, "777")})
|
||||
bind.SetPreludeOpts(conceal.PreludeOpts{
|
||||
RulesArr: [5]conceal.Rules{mustParseRules(t, "<b 0xaabb>")},
|
||||
})
|
||||
|
||||
got := bind.udpConcealPipeline().names()
|
||||
want := []string{"masquerade", "framed", "prelude"}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("udp pipeline = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStdNetBindUDPPreludeStillEmitsJunk(t *testing.T) {
|
||||
bind := NewStdNetBind().(*StdNetBind)
|
||||
bind.SetPreludeOpts(conceal.PreludeOpts{
|
||||
Jc: 1,
|
||||
Jmin: 3,
|
||||
Jmax: 3,
|
||||
RulesArr: [5]conceal.Rules{
|
||||
mustParseRules(t, "<b 0xaabb>"),
|
||||
},
|
||||
})
|
||||
|
||||
conn := &recordingUDPConn{}
|
||||
upgraded := bind.upgradeUDPConn(conn)
|
||||
initiation := makeInitiationPacket()
|
||||
|
||||
if _, _, err := upgraded.WriteMsgUDP(initiation, nil, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 51820}); err != nil {
|
||||
t.Fatalf("udp write failed: %v", err)
|
||||
}
|
||||
|
||||
if len(conn.writes) != 3 {
|
||||
t.Fatalf("udp write count = %d, want 3", len(conn.writes))
|
||||
}
|
||||
if !bytes.Equal(conn.writes[0], []byte{0xaa, 0xbb}) {
|
||||
t.Fatalf("udp decoy record = %x, want aabb", conn.writes[0])
|
||||
}
|
||||
if len(conn.writes[1]) != 3 {
|
||||
t.Fatalf("udp junk length = %d, want 3", len(conn.writes[1]))
|
||||
}
|
||||
if !bytes.Equal(conn.writes[2], initiation) {
|
||||
t.Fatalf("udp initiation payload changed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindStreamPipelineUsesRecordLayer(t *testing.T) {
|
||||
bind := newRecordAwareBind(t)
|
||||
|
||||
got := bind.streamConcealPipeline().names()
|
||||
want := []string{"record", "prelude", "framed"}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("stream pipeline = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindStreamPipelineOmitsPreludeWithoutBidirectionalRecords(t *testing.T) {
|
||||
bind := NewBindStream()
|
||||
bind.SetMasqueradeOpts(conceal.MasqueradeOpts{
|
||||
RulesOut: mustParseRules(t, "<dz be 2><d>"),
|
||||
})
|
||||
bind.SetFramedOpts(conceal.FramedOpts{H1: mustHeader(t, "777")})
|
||||
bind.SetPreludeOpts(conceal.PreludeOpts{
|
||||
RulesArr: [5]conceal.Rules{mustParseRules(t, "<b 0xaabb>")},
|
||||
})
|
||||
|
||||
got := bind.streamConcealPipeline().names()
|
||||
want := []string{"record", "framed"}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("stream pipeline without bidirectional records = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindStreamPipelineOmitsPreludeForTCPJunkOnlyConfig(t *testing.T) {
|
||||
bind := NewBindStream()
|
||||
bind.SetMasqueradeOpts(conceal.MasqueradeOpts{
|
||||
RulesIn: mustParseRules(t, "<dz be 2><d>"),
|
||||
RulesOut: mustParseRules(t, "<dz be 2><d>"),
|
||||
})
|
||||
bind.SetFramedOpts(conceal.FramedOpts{H1: mustHeader(t, "777")})
|
||||
bind.SetPreludeOpts(conceal.PreludeOpts{
|
||||
Jc: 1,
|
||||
Jmin: 2,
|
||||
Jmax: 2,
|
||||
})
|
||||
|
||||
got := bind.streamConcealPipeline().names()
|
||||
want := []string{"record", "framed"}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("stream pipeline with tcp junk only = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindStreamTCPPreludeInjectsBeforeEveryInitiation(t *testing.T) {
|
||||
senderRaw, receiverRaw := net.Pipe()
|
||||
defer senderRaw.Close()
|
||||
defer receiverRaw.Close()
|
||||
|
||||
bind := newRecordAwareBind(t)
|
||||
sender := bind.upgradeConn(senderRaw)
|
||||
receiver := mustRecordConn(t, receiverRaw, &bind.bufferPool, conceal.MasqueradeOpts{
|
||||
RulesIn: mustParseRules(t, "<dz be 2><d>"),
|
||||
})
|
||||
|
||||
initiation := makeInitiationPacket()
|
||||
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := sender.Write(initiation)
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
prelude := readRecord(t, receiver, 16)
|
||||
if !bytes.Equal(prelude, []byte{0xaa, 0xbb}) {
|
||||
t.Fatalf("prelude record = %x, want aabb", prelude)
|
||||
}
|
||||
|
||||
firstInit := readRecord(t, receiver, len(initiation))
|
||||
if gotHeader := binary.LittleEndian.Uint32(firstInit[:4]); gotHeader != 777 {
|
||||
t.Fatalf("first initiation header = %d, want 777", gotHeader)
|
||||
}
|
||||
|
||||
if err := <-writeErr; err != nil {
|
||||
t.Fatalf("first write failed: %v", err)
|
||||
}
|
||||
|
||||
writeErr = make(chan error, 1)
|
||||
go func() {
|
||||
_, err := sender.Write(initiation)
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
secondPrelude := readRecord(t, receiver, 16)
|
||||
if !bytes.Equal(secondPrelude, []byte{0xaa, 0xbb}) {
|
||||
t.Fatalf("second prelude record = %x, want aabb", secondPrelude)
|
||||
}
|
||||
|
||||
secondInit := readRecord(t, receiver, len(initiation))
|
||||
if gotHeader := binary.LittleEndian.Uint32(secondInit[:4]); gotHeader != 777 {
|
||||
t.Fatalf("second initiation header = %d, want 777", gotHeader)
|
||||
}
|
||||
|
||||
if err := <-writeErr; err != nil {
|
||||
t.Fatalf("second write failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindStreamTCPPreludeSkipsNonInitiationRecords(t *testing.T) {
|
||||
senderRaw, receiverRaw := net.Pipe()
|
||||
defer senderRaw.Close()
|
||||
defer receiverRaw.Close()
|
||||
|
||||
bind := newRecordAwareBind(t)
|
||||
sender := bind.upgradeConn(senderRaw)
|
||||
receiver := mustRecordConn(t, receiverRaw, &bind.bufferPool, conceal.MasqueradeOpts{
|
||||
RulesIn: mustParseRules(t, "<dz be 2><d>"),
|
||||
})
|
||||
|
||||
transport := makeTransportPacket()
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := sender.Write(transport)
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
gotTransport := readRecord(t, receiver, len(transport))
|
||||
if gotHeader := binary.LittleEndian.Uint32(gotTransport[:4]); gotHeader != 779 {
|
||||
t.Fatalf("transport header = %d, want 779", gotHeader)
|
||||
}
|
||||
|
||||
if err := <-writeErr; err != nil {
|
||||
t.Fatalf("transport write failed: %v", err)
|
||||
}
|
||||
|
||||
if err := receiver.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
|
||||
t.Fatalf("set deadline: %v", err)
|
||||
}
|
||||
|
||||
buf := make([]byte, len(transport))
|
||||
if _, err := receiver.ReadRecord(buf); err == nil {
|
||||
t.Fatal("expected no extra prelude records for transport write")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindStreamTCPPreludeDropsInjectedDecoysOnRead(t *testing.T) {
|
||||
senderRaw, receiverRaw := net.Pipe()
|
||||
defer senderRaw.Close()
|
||||
defer receiverRaw.Close()
|
||||
|
||||
bind := newRecordAwareBind(t)
|
||||
sender := bind.upgradeConn(senderRaw)
|
||||
receiver := bind.upgradeConn(receiverRaw)
|
||||
|
||||
initiation := makeInitiationPacket()
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := sender.Write(initiation)
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
got := readPacket(t, receiver, len(initiation))
|
||||
if !bytes.Equal(got, initiation) {
|
||||
t.Fatalf("initiation payload changed after dropping decoys")
|
||||
}
|
||||
|
||||
if err := <-writeErr; err != nil {
|
||||
t.Fatalf("write failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindStreamTCPPreludeDropsLeadingInvalidRecords(t *testing.T) {
|
||||
senderRaw, receiverRaw := net.Pipe()
|
||||
defer senderRaw.Close()
|
||||
defer receiverRaw.Close()
|
||||
|
||||
bind := newRecordAwareBind(t)
|
||||
receiver := bind.upgradeConn(receiverRaw)
|
||||
|
||||
recordWriter := mustRecordConn(t, senderRaw, &bind.bufferPool, conceal.MasqueradeOpts{
|
||||
RulesOut: mustParseRules(t, "<dz be 2><d>"),
|
||||
})
|
||||
framedWriter, ok := conceal.NewFramedConn(recordWriter, &bind.bufferPool, bind.framedOpts)
|
||||
if !ok {
|
||||
t.Fatal("expected framed writer")
|
||||
}
|
||||
|
||||
initiation := makeInitiationPacket()
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
if _, err := recordWriter.WriteRecord([]byte{0xde, 0xad}); err != nil {
|
||||
writeErr <- err
|
||||
return
|
||||
}
|
||||
if _, err := recordWriter.WriteRecord([]byte{0xbe, 0xef, 0x01}); err != nil {
|
||||
writeErr <- err
|
||||
return
|
||||
}
|
||||
_, err := framedWriter.Write(initiation)
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
got := readPacket(t, receiver, len(initiation))
|
||||
if !bytes.Equal(got, initiation) {
|
||||
t.Fatalf("read-side did not recover after invalid leading records")
|
||||
}
|
||||
|
||||
if err := <-writeErr; err != nil {
|
||||
t.Fatalf("write failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindStreamTCPPreludePassesResponseAndTransportRecords(t *testing.T) {
|
||||
senderRaw, receiverRaw := net.Pipe()
|
||||
defer senderRaw.Close()
|
||||
defer receiverRaw.Close()
|
||||
|
||||
bind := newRecordAwareBind(t)
|
||||
sender := bind.upgradeConn(senderRaw)
|
||||
receiver := bind.upgradeConn(receiverRaw)
|
||||
|
||||
response := makeResponsePacket()
|
||||
sendAndAssertRoundTrip(t, sender, receiver, response)
|
||||
|
||||
transport := makeTransportPacket()
|
||||
sendAndAssertRoundTrip(t, sender, receiver, transport)
|
||||
}
|
||||
|
||||
func newRecordAwareBind(t *testing.T) *BindStream {
|
||||
t.Helper()
|
||||
|
||||
rules := mustParseRules(t, "<dz be 2><d>")
|
||||
bind := NewBindStream()
|
||||
bind.SetMasqueradeOpts(conceal.MasqueradeOpts{
|
||||
RulesIn: rules,
|
||||
RulesOut: rules,
|
||||
})
|
||||
bind.SetFramedOpts(conceal.FramedOpts{
|
||||
H1: mustHeader(t, "777"),
|
||||
H2: mustHeader(t, "778"),
|
||||
H4: mustHeader(t, "779"),
|
||||
})
|
||||
bind.SetPreludeOpts(conceal.PreludeOpts{
|
||||
Jc: 1,
|
||||
Jmin: 2,
|
||||
Jmax: 2,
|
||||
RulesArr: [5]conceal.Rules{
|
||||
mustParseRules(t, "<b 0xaabb>"),
|
||||
},
|
||||
})
|
||||
return bind
|
||||
}
|
||||
|
||||
func sendAndAssertRoundTrip(t *testing.T, sender, receiver net.Conn, packet []byte) {
|
||||
t.Helper()
|
||||
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := sender.Write(packet)
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
got := readPacket(t, receiver, len(packet))
|
||||
if !bytes.Equal(got, packet) {
|
||||
t.Fatalf("round-trip payload mismatch")
|
||||
}
|
||||
|
||||
if err := <-writeErr; err != nil {
|
||||
t.Fatalf("write failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustRecordConn(t *testing.T, conn net.Conn, pool *sync.Pool, opts conceal.MasqueradeOpts) *conceal.MasqueradeConn {
|
||||
t.Helper()
|
||||
|
||||
recordConn, ok := conceal.NewMasqueradeConn(conn, pool, opts)
|
||||
if !ok {
|
||||
t.Fatal("expected record connection")
|
||||
}
|
||||
return recordConn
|
||||
}
|
||||
|
||||
func mustParseRules(t *testing.T, spec string) conceal.Rules {
|
||||
t.Helper()
|
||||
|
||||
rules, err := conceal.ParseRules(spec)
|
||||
if err != nil {
|
||||
t.Fatalf("parse rules %q: %v", spec, err)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
func mustHeader(t *testing.T, spec string) *conceal.RangedHeader {
|
||||
t.Helper()
|
||||
|
||||
header, err := conceal.NewRangedHeader(spec)
|
||||
if err != nil {
|
||||
t.Fatalf("parse header %q: %v", spec, err)
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
func makeInitiationPacket() []byte {
|
||||
packet := make([]byte, conceal.WireguardMsgInitiationSize)
|
||||
binary.LittleEndian.PutUint32(packet[:4], conceal.WireguardMsgInitiationType)
|
||||
for i := 4; i < len(packet); i++ {
|
||||
packet[i] = byte(i)
|
||||
}
|
||||
return packet
|
||||
}
|
||||
|
||||
func makeResponsePacket() []byte {
|
||||
packet := make([]byte, conceal.WireguardMsgResponseSize)
|
||||
binary.LittleEndian.PutUint32(packet[:4], conceal.WireguardMsgResponseType)
|
||||
for i := 4; i < len(packet); i++ {
|
||||
packet[i] = byte(i)
|
||||
}
|
||||
return packet
|
||||
}
|
||||
|
||||
func makeTransportPacket() []byte {
|
||||
packet := make([]byte, conceal.WireguardMsgTransportMinSize)
|
||||
binary.LittleEndian.PutUint32(packet[:4], conceal.WireguardMsgTransportType)
|
||||
for i := 4; i < len(packet); i++ {
|
||||
packet[i] = byte(i)
|
||||
}
|
||||
return packet
|
||||
}
|
||||
|
||||
func readPacket(t *testing.T, conn net.Conn, size int) []byte {
|
||||
t.Helper()
|
||||
|
||||
buf := make([]byte, size)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
}
|
||||
return slices.Clone(buf[:n])
|
||||
}
|
||||
|
||||
func readRecord(t *testing.T, conn interface{ ReadRecord([]byte) (int, error) }, size int) []byte {
|
||||
t.Helper()
|
||||
|
||||
buf := make([]byte, size)
|
||||
n, err := conn.ReadRecord(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("read record: %v", err)
|
||||
}
|
||||
return slices.Clone(buf[:n])
|
||||
}
|
||||
|
||||
type recordingUDPConn struct {
|
||||
writes [][]byte
|
||||
}
|
||||
|
||||
func (c *recordingUDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
return 0, nil, net.ErrClosed
|
||||
}
|
||||
|
||||
func (c *recordingUDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
c.writes = append(c.writes, bytes.Clone(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *recordingUDPConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *recordingUDPConn) LocalAddr() net.Addr {
|
||||
return &net.UDPAddr{}
|
||||
}
|
||||
|
||||
func (c *recordingUDPConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *recordingUDPConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *recordingUDPConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *recordingUDPConn) SyscallConn() (syscall.RawConn, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *recordingUDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
|
||||
return 0, 0, 0, nil, net.ErrClosed
|
||||
}
|
||||
|
||||
func (c *recordingUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) {
|
||||
c.writes = append(c.writes, bytes.Clone(b))
|
||||
return len(b), len(oob), nil
|
||||
}
|
||||
@@ -9,14 +9,10 @@ package conn
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -88,18 +84,6 @@ type Endpoint interface {
|
||||
SrcIP() netip.Addr
|
||||
}
|
||||
|
||||
type UDPConn interface {
|
||||
net.PacketConn
|
||||
SyscallConn() (syscall.RawConn, error)
|
||||
ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error)
|
||||
WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error)
|
||||
}
|
||||
|
||||
type LinuxPacketConn interface {
|
||||
ReadBatch([]ipv4.Message, int) (int, error)
|
||||
WriteBatch(ms []ipv4.Message, flags int) (n int, err error)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrBindAlreadyOpen = errors.New("bind is already open")
|
||||
ErrWrongEndpointType = errors.New("endpoint type does not correspond with bind type")
|
||||
|
||||
+1
-3
@@ -7,6 +7,4 @@
|
||||
|
||||
package conn
|
||||
|
||||
func NewDefaultBind() Bind {
|
||||
return NewMultibind(NewStdNetBind(), NewBindStream())
|
||||
}
|
||||
func NewDefaultBind() Bind { return NewStdNetBind() }
|
||||
@@ -1,7 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package conn
|
||||
|
||||
func NewDefaultBind() Bind {
|
||||
return NewMultibind(NewWinRingBind(), NewBindStream())
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package conn
|
||||
|
||||
func supportsUDPOffload(_ UDPConn) (txOffload, rxOffload bool) {
|
||||
import "net"
|
||||
|
||||
func supportsUDPOffload(_ *net.UDPConn) (txOffload, rxOffload bool) {
|
||||
return
|
||||
}
|
||||
@@ -6,10 +6,12 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func supportsUDPOffload(conn UDPConn) (txOffload, rxOffload bool) {
|
||||
func supportsUDPOffload(conn *net.UDPConn) (txOffload, rxOffload bool) {
|
||||
rc, err := conn.SyscallConn()
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -10,7 +10,3 @@ package conn
|
||||
func (s *StdNetBind) SetMark(mark uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BindStream) SetMark(mark uint32) error {
|
||||
return nil
|
||||
}
|
||||
@@ -9,7 +9,6 @@ package conn
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
@@ -64,37 +63,3 @@ func (s *StdNetBind) SetMark(mark uint32) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BindStream) SetMark(mark uint32) error {
|
||||
b.closeConnections()
|
||||
|
||||
ctrl := b.dialer.Control
|
||||
b.dialer.Control = func(network, address string, c syscall.RawConn) error {
|
||||
if ctrl != nil {
|
||||
if err := ctrl(network, address, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := c.Control(func(fd uintptr) {
|
||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, fwmarkIoctl, int(mark))
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
ctrl = b.listenConfig.Control
|
||||
b.listenConfig.Control = func(network, address string, c syscall.RawConn) error {
|
||||
if ctrl != nil {
|
||||
if err := ctrl(network, address, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := c.Control(func(fd uintptr) {
|
||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, fwmarkIoctl, int(mark))
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
return b.openConnections()
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var _ Bind = (*Multibind)(nil)
|
||||
|
||||
func NewMultibind(udp Bind, tcp Bind) *Multibind {
|
||||
return &Multibind{
|
||||
udp: udp,
|
||||
tcp: tcp,
|
||||
Bind: udp,
|
||||
}
|
||||
}
|
||||
|
||||
type Multibind struct {
|
||||
Bind
|
||||
udp Bind
|
||||
tcp Bind
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (mb *Multibind) SelectNetwork(proto string) error {
|
||||
mb.mutex.Lock()
|
||||
defer mb.mutex.Unlock()
|
||||
|
||||
if proto == "udp" {
|
||||
mb.Bind = mb.udp
|
||||
return nil
|
||||
}
|
||||
|
||||
if proto == "tcp" {
|
||||
mb.Bind = mb.tcp
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("unknown network")
|
||||
}
|
||||
|
||||
func (mb *Multibind) Proto() string {
|
||||
switch mb.Bind {
|
||||
case mb.tcp:
|
||||
return "tcp"
|
||||
case mb.udp:
|
||||
return "udp"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
+1
-1
@@ -8,7 +8,7 @@ package device
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
)
|
||||
|
||||
type DummyDatagram struct {
|
||||
|
||||
@@ -24,6 +24,7 @@ const (
|
||||
CookieRefreshTime = time.Second * 120
|
||||
HandshakeInitationRate = time.Second / 50
|
||||
PaddingMultiple = 16
|
||||
DefaultUdpWindow = 500
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+2
-2
@@ -118,7 +118,7 @@ func (st *CookieChecker) CreateReply(
|
||||
msg []byte,
|
||||
recv uint32,
|
||||
src []byte,
|
||||
typ uint32,
|
||||
msgType uint32,
|
||||
) (*MessageCookieReply, error) {
|
||||
st.RLock()
|
||||
|
||||
@@ -154,7 +154,7 @@ func (st *CookieChecker) CreateReply(
|
||||
smac1 := smac2 - blake2s.Size128
|
||||
|
||||
reply := new(MessageCookieReply)
|
||||
reply.Type = typ
|
||||
reply.Type = msgType
|
||||
reply.Receiver = recv
|
||||
|
||||
_, err := rand.Read(reply.Nonce[:])
|
||||
|
||||
+63
-38
@@ -11,11 +11,10 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conceal"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ratelimiter"
|
||||
"github.com/amnezia-vpn/amneziawg-go/rwcancel"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/ratelimiter"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/rwcancel"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun"
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
@@ -41,15 +40,11 @@ type Device struct {
|
||||
net struct {
|
||||
stopping sync.WaitGroup
|
||||
sync.RWMutex
|
||||
bind conn.Bind // bind interface
|
||||
netlinkCancel *rwcancel.RWCancel
|
||||
port uint16 // listening port
|
||||
fwmark uint32 // mark value (0 = disabled)
|
||||
brokenRoaming bool
|
||||
network string
|
||||
framedOpts conceal.FramedOpts
|
||||
preludeOpts conceal.PreludeOpts
|
||||
masqueradeOpts conceal.MasqueradeOpts
|
||||
bind conn.Bind // bind interface
|
||||
netlinkCancel *rwcancel.RWCancel
|
||||
port uint16 // listening port
|
||||
fwmark uint32 // mark value (0 = disabled)
|
||||
brokenRoaming bool
|
||||
}
|
||||
|
||||
staticIdentity struct {
|
||||
@@ -94,6 +89,46 @@ type Device struct {
|
||||
ipcMutex sync.RWMutex
|
||||
closed chan struct{}
|
||||
log *Logger
|
||||
|
||||
junk struct {
|
||||
min atomic.Uint32
|
||||
max atomic.Uint32
|
||||
count atomic.Uint32
|
||||
}
|
||||
|
||||
headers struct {
|
||||
init AtomicUintRange
|
||||
cookie AtomicUintRange
|
||||
response AtomicUintRange
|
||||
transport AtomicUintRange
|
||||
}
|
||||
|
||||
paddings struct {
|
||||
init atomic.Uint32
|
||||
response atomic.Uint32
|
||||
cookie atomic.Uint32
|
||||
transport atomic.Uint32
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -188,7 +223,7 @@ func (device *Device) upLocked() error {
|
||||
device.peers.RLock()
|
||||
for _, peer := range device.peers.keyMap {
|
||||
peer.Start()
|
||||
if peer.persistentKeepaliveInterval.Load() > 0 {
|
||||
if !peer.persistentKeepaliveInterval.Load().IsZero() {
|
||||
peer.SendKeepalive()
|
||||
}
|
||||
}
|
||||
@@ -288,6 +323,8 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
|
||||
}
|
||||
|
||||
func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device {
|
||||
var rang UintRange
|
||||
|
||||
device := new(Device)
|
||||
device.state.state.Store(uint32(deviceStateDown))
|
||||
device.closed = make(chan struct{})
|
||||
@@ -304,8 +341,14 @@ func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device {
|
||||
device.rate.limiter.Init()
|
||||
device.indexTable.Init()
|
||||
|
||||
device.net.network = "udp"
|
||||
device.net.framedOpts.HeaderCompat = true
|
||||
rang.FromUint32(MessageInitiationType, MessageInitiationType)
|
||||
device.headers.init.Store(rang)
|
||||
rang.FromUint32(MessageResponseType, MessageResponseType)
|
||||
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()
|
||||
|
||||
@@ -417,10 +460,12 @@ func (device *Device) SendKeepalivesToPeersWithCurrentKeypair() {
|
||||
return
|
||||
}
|
||||
|
||||
timeout := device.keychainExpireTime()
|
||||
|
||||
device.peers.RLock()
|
||||
for _, peer := range device.peers.keyMap {
|
||||
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()
|
||||
if sendKeepalive {
|
||||
peer.SendKeepalive()
|
||||
@@ -496,26 +541,6 @@ func (device *Device) BindUpdate() error {
|
||||
var recvFns []conn.ReceiveFunc
|
||||
netc := &device.net
|
||||
|
||||
underlying := netc.bind
|
||||
if multi, ok := netc.bind.(*conn.Multibind); ok {
|
||||
if err := multi.SelectNetwork(netc.network); err != nil {
|
||||
return err
|
||||
}
|
||||
underlying = multi.Bind
|
||||
}
|
||||
|
||||
if framable, ok := underlying.(conn.Framable); ok {
|
||||
framable.SetFramedOpts(netc.framedOpts)
|
||||
}
|
||||
|
||||
if preludable, ok := underlying.(conn.Preludable); ok {
|
||||
preludable.SetPreludeOpts(netc.preludeOpts)
|
||||
}
|
||||
|
||||
if masqueradable, ok := underlying.(conn.Masqueradable); ok {
|
||||
masqueradable.SetMasqueradeOpts(netc.masqueradeOpts)
|
||||
}
|
||||
|
||||
recvFns, netc.port, err = netc.bind.Open(netc.port)
|
||||
if err != nil {
|
||||
netc.port = 0
|
||||
|
||||
+4
-136
@@ -12,23 +12,21 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn/bindtest"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/tuntest"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn/bindtest"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun/tuntest"
|
||||
)
|
||||
|
||||
// uapiCfg returns a string that contains cfg formatted use with IpcSet.
|
||||
@@ -103,54 +101,6 @@ func genConfigs(tb testing.TB, cfg ...string) (cfgs, endpointCfgs [2]string) {
|
||||
return
|
||||
}
|
||||
|
||||
func genTCPConfigs(tb testing.TB, ports [2]int, cfg ...string) (cfgs, endpointCfgs [2]string) {
|
||||
var key1, key2 NoisePrivateKey
|
||||
_, err := rand.Read(key1[:])
|
||||
if err != nil {
|
||||
tb.Errorf("unable to generate private key random bytes: %v", err)
|
||||
}
|
||||
_, err = rand.Read(key2[:])
|
||||
if err != nil {
|
||||
tb.Errorf("unable to generate private key random bytes: %v", err)
|
||||
}
|
||||
pub1, pub2 := key1.publicKey(), key2.publicKey()
|
||||
|
||||
args0 := append([]string(nil), cfg...)
|
||||
args0 = append(args0, []string{
|
||||
"private_key", hex.EncodeToString(key1[:]),
|
||||
"listen_port", strconv.Itoa(ports[0]),
|
||||
"replace_peers", "true",
|
||||
"public_key", hex.EncodeToString(pub2[:]),
|
||||
"protocol_version", "1",
|
||||
"replace_allowed_ips", "true",
|
||||
"allowed_ip", "1.0.0.2/32",
|
||||
}...)
|
||||
cfgs[0] = uapiCfg(args0...)
|
||||
|
||||
endpointCfgs[0] = uapiCfg(
|
||||
"public_key", hex.EncodeToString(pub2[:]),
|
||||
"endpoint", fmt.Sprintf("127.0.0.1:%d", ports[1]),
|
||||
)
|
||||
|
||||
args1 := append([]string(nil), cfg...)
|
||||
args1 = append(args1, []string{
|
||||
"private_key", hex.EncodeToString(key2[:]),
|
||||
"listen_port", strconv.Itoa(ports[1]),
|
||||
"replace_peers", "true",
|
||||
"public_key", hex.EncodeToString(pub1[:]),
|
||||
"protocol_version", "1",
|
||||
"replace_allowed_ips", "true",
|
||||
"allowed_ip", "1.0.0.1/32",
|
||||
}...)
|
||||
|
||||
cfgs[1] = uapiCfg(args1...)
|
||||
endpointCfgs[1] = uapiCfg(
|
||||
"public_key", hex.EncodeToString(pub1[:]),
|
||||
"endpoint", fmt.Sprintf("127.0.0.1:%d", ports[0]),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// A testPair is a pair of testPeers.
|
||||
type testPair [2]testPeer
|
||||
|
||||
@@ -263,64 +213,6 @@ func genTestPair(
|
||||
return
|
||||
}
|
||||
|
||||
func getFreeTCPPort(tb testing.TB) int {
|
||||
tb.Helper()
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
tb.Fatalf("failed to allocate tcp port: %v", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
addr, ok := listener.Addr().(*net.TCPAddr)
|
||||
if !ok {
|
||||
tb.Fatalf("unexpected tcp addr type: %T", listener.Addr())
|
||||
}
|
||||
|
||||
return addr.Port
|
||||
}
|
||||
|
||||
func genTestPairTCP(tb testing.TB, extraCfg ...string) (pair testPair) {
|
||||
ports := [2]int{getFreeTCPPort(tb), getFreeTCPPort(tb)}
|
||||
cfg, endpointCfg := genTCPConfigs(tb, ports, extraCfg...)
|
||||
|
||||
var binds [2]conn.Bind
|
||||
binds[0], binds[1] = conn.NewDefaultBind(), conn.NewDefaultBind()
|
||||
|
||||
for i := range pair {
|
||||
p := &pair[i]
|
||||
p.tun = tuntest.NewChannelTUN()
|
||||
p.ip = netip.AddrFrom4([4]byte{1, 0, 0, byte(i + 1)})
|
||||
level := LogLevelVerbose
|
||||
if _, ok := tb.(*testing.B); ok && !testing.Verbose() {
|
||||
level = LogLevelError
|
||||
}
|
||||
p.dev = NewDevice(p.tun.TUN(), binds[i], NewLogger(level, fmt.Sprintf("dev%d: ", i)))
|
||||
if err := p.dev.IpcSet(cfg[i]); err != nil {
|
||||
tb.Errorf("failed to configure tcp device %d: %v", i, err)
|
||||
p.dev.Close()
|
||||
continue
|
||||
}
|
||||
if err := p.dev.Up(); err != nil {
|
||||
tb.Errorf("failed to bring up tcp device %d: %v", i, err)
|
||||
p.dev.Close()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for i := range pair {
|
||||
p := &pair[i]
|
||||
if err := p.dev.IpcSet(endpointCfg[i]); err != nil {
|
||||
tb.Errorf("failed to configure tcp endpoint %d: %v", i, err)
|
||||
p.dev.Close()
|
||||
continue
|
||||
}
|
||||
tb.Cleanup(p.dev.Close)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TestTwoDevicePing(t *testing.T) {
|
||||
goroutineLeakCheck(t)
|
||||
pair := genTestPair(t, true)
|
||||
@@ -357,30 +249,6 @@ func TestAWGDevicePing(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAWGDevicePingTCPConceal(t *testing.T) {
|
||||
goroutineLeakCheck(t)
|
||||
|
||||
pair := genTestPairTCP(t,
|
||||
"network", "tcp",
|
||||
"format_in", "<b 0xfeed><dz be 2><d>",
|
||||
"format_out", "<b 0xfeed><dz be 2><d>",
|
||||
"i1", "<b 0xaabb>",
|
||||
"i2", "<b 0xccdd>",
|
||||
"s1", "15",
|
||||
"s2", "18",
|
||||
"s4", "25",
|
||||
"h1", "123456-123500",
|
||||
"h2", "67543-67550",
|
||||
"h4", "32345-32350",
|
||||
)
|
||||
t.Run("ping 1.0.0.1", func(t *testing.T) {
|
||||
pair.Send(t, Ping, nil)
|
||||
})
|
||||
t.Run("ping 1.0.0.2", func(t *testing.T) {
|
||||
pair.Send(t, Pong, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Needs to be stopped with Ctrl-C
|
||||
func TestAWGHandshakeDevicePing(t *testing.T) {
|
||||
t.Skip("This test is intended to be run manually, not as part of the test suite.")
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
||||
"sync/atomic"
|
||||
"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
|
||||
|
||||
+41
-40
@@ -6,16 +6,18 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/blake2s"
|
||||
"golang.org/x/crypto/chacha20"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/poly1305"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/tai64n"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tai64n"
|
||||
)
|
||||
|
||||
type handshakeState int
|
||||
@@ -53,10 +55,11 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
MessageInitiationType = 1
|
||||
MessageResponseType = 2
|
||||
MessageCookieReplyType = 3
|
||||
MessageTransportType = 4
|
||||
MessageUnknownType uint32 = 0
|
||||
MessageInitiationType uint32 = 1
|
||||
MessageResponseType uint32 = 2
|
||||
MessageCookieReplyType uint32 = 3
|
||||
MessageTransportType uint32 = 4
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -193,16 +196,10 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
|
||||
|
||||
handshake.mixHash(handshake.remoteStatic[:])
|
||||
|
||||
opts := &device.net.framedOpts
|
||||
var typ uint32
|
||||
if opts.HeaderCompat && opts.H1 != nil {
|
||||
typ = opts.H1.Generate()
|
||||
} else {
|
||||
typ = MessageInitiationType
|
||||
}
|
||||
msgType := device.headers.init.Load().PickOne()
|
||||
|
||||
msg := MessageInitiation{
|
||||
Type: typ,
|
||||
Type: msgType,
|
||||
Ephemeral: handshake.localEphemeral.publicKey(),
|
||||
}
|
||||
|
||||
@@ -258,15 +255,8 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
||||
chainKey [blake2s.Size]byte
|
||||
)
|
||||
|
||||
opts := &device.net.framedOpts
|
||||
if opts.HeaderCompat && opts.H1 != nil {
|
||||
if !opts.H1.Validate(msg.Type) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if msg.Type != MessageInitiationType {
|
||||
return nil
|
||||
}
|
||||
if msg.Type != MessageInitiationType {
|
||||
return nil
|
||||
}
|
||||
|
||||
device.staticIdentity.RLock()
|
||||
@@ -381,16 +371,8 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := &device.net.framedOpts
|
||||
var typ uint32
|
||||
if opts.HeaderCompat && opts.H2 != nil {
|
||||
typ = opts.H2.Generate()
|
||||
} else {
|
||||
typ = MessageResponseType
|
||||
}
|
||||
|
||||
var msg MessageResponse
|
||||
msg.Type = typ
|
||||
msg.Type = device.headers.response.Load().PickOne()
|
||||
msg.Sender = handshake.localIndex
|
||||
msg.Receiver = handshake.remoteIndex
|
||||
|
||||
@@ -440,15 +422,8 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error
|
||||
}
|
||||
|
||||
func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer {
|
||||
opts := &device.net.framedOpts
|
||||
if opts.HeaderCompat && opts.H2 != nil {
|
||||
if !opts.H2.Validate(msg.Type) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if msg.Type != MessageResponseType {
|
||||
return nil
|
||||
}
|
||||
if msg.Type != MessageResponseType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookup handshake by receiver
|
||||
@@ -653,3 +628,29 @@ func (peer *Peer) ReceivedWithKeypair(receivedKeypair *Keypair) bool {
|
||||
keypairs.next.Store(nil)
|
||||
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)
|
||||
}
|
||||
@@ -9,12 +9,18 @@ import (
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
NoisePublicKeySize = 32
|
||||
NoisePrivateKeySize = 32
|
||||
NoisePresharedKeySize = 32
|
||||
HeaderCipherKeySize = 32
|
||||
HeaderCipherNonceSize = 12
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -22,6 +28,7 @@ type (
|
||||
NoisePrivateKey [NoisePrivateKeySize]byte
|
||||
NoisePresharedKey [NoisePresharedKeySize]byte
|
||||
NoiseNonce uint64 // padded to 12-bytes
|
||||
HeaderCipherKey [HeaderCipherKeySize]byte
|
||||
)
|
||||
|
||||
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 {
|
||||
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)))
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/tuntest"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun/tuntest"
|
||||
)
|
||||
|
||||
func TestCurveWrappers(t *testing.T) {
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type obfBuilder func(val string) (obf, error)
|
||||
|
||||
var obfBuilders = map[string]obfBuilder{
|
||||
"b": newBytesObf,
|
||||
"t": newTimestampObf,
|
||||
"r": newRandObf,
|
||||
"rc": newRandCharObf,
|
||||
"rd": newRandDigitsObf,
|
||||
"d": newDataObf,
|
||||
"ds": newDataStringObf,
|
||||
"dz": newDataSizeObf,
|
||||
}
|
||||
|
||||
type obf interface {
|
||||
Obfuscate(dst, src []byte)
|
||||
Deobfuscate(dst, src []byte) bool
|
||||
ObfuscatedLen(srcLen int) int
|
||||
DeobfuscatedLen(srcLen int) int
|
||||
}
|
||||
|
||||
type obfChain struct {
|
||||
Spec string
|
||||
obfs []obf
|
||||
}
|
||||
|
||||
func newObfChain(spec string) (*obfChain, error) {
|
||||
var (
|
||||
obfs []obf
|
||||
errs []error
|
||||
)
|
||||
|
||||
remaining := spec[:]
|
||||
for {
|
||||
start := strings.IndexByte(remaining, '<')
|
||||
if start == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
end := strings.IndexByte(remaining[start:], '>')
|
||||
if end == -1 {
|
||||
return nil, errors.New("missing enclosing >")
|
||||
}
|
||||
end += start
|
||||
|
||||
tag := remaining[start+1 : end]
|
||||
parts := strings.Fields(tag)
|
||||
if len(parts) == 0 {
|
||||
errs = append(errs, errors.New("empty tag"))
|
||||
remaining = remaining[end+1:]
|
||||
continue
|
||||
}
|
||||
|
||||
key := parts[0]
|
||||
builder, ok := obfBuilders[key]
|
||||
if !ok {
|
||||
errs = append(errs, fmt.Errorf("unknown tag <%s>", key))
|
||||
remaining = remaining[end+1:]
|
||||
continue
|
||||
}
|
||||
|
||||
val := ""
|
||||
if len(parts) > 1 {
|
||||
val = parts[1]
|
||||
}
|
||||
|
||||
o, err := builder(val)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to build <%s>: %w", key, err))
|
||||
remaining = remaining[end+1:]
|
||||
continue
|
||||
}
|
||||
|
||||
obfs = append(obfs, o)
|
||||
remaining = remaining[end+1:]
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
if len(obfs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &obfChain{
|
||||
Spec: spec,
|
||||
obfs: obfs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *obfChain) Obfuscate(dst, src []byte) {
|
||||
written := 0
|
||||
for _, o := range c.obfs {
|
||||
obfLen := o.ObfuscatedLen(len(src))
|
||||
o.Obfuscate(dst[written:written+obfLen], src)
|
||||
written += obfLen
|
||||
}
|
||||
}
|
||||
|
||||
func (c *obfChain) Deobfuscate(dst, src []byte) bool {
|
||||
dynamicLen := len(src) - c.ObfuscatedLen(0)
|
||||
|
||||
written, read := 0, 0
|
||||
|
||||
for _, o := range c.obfs {
|
||||
deobfLen := o.DeobfuscatedLen(dynamicLen)
|
||||
obfLen := o.ObfuscatedLen(deobfLen)
|
||||
|
||||
if !o.Deobfuscate(dst[written:written+deobfLen], src[read:read+obfLen]) {
|
||||
return false
|
||||
}
|
||||
|
||||
written += deobfLen
|
||||
read += obfLen
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *obfChain) ObfuscatedLen(n int) int {
|
||||
total := 0
|
||||
for _, o := range c.obfs {
|
||||
total += o.ObfuscatedLen(n)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func (c *obfChain) DeobfuscatedLen(n int) int {
|
||||
dynamicLen := n - c.ObfuscatedLen(0)
|
||||
|
||||
total := 0
|
||||
for _, o := range c.obfs {
|
||||
total += o.DeobfuscatedLen(dynamicLen)
|
||||
}
|
||||
return total
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func newBytesObf(val string) (obf, error) {
|
||||
val = strings.TrimPrefix(val, "0x")
|
||||
|
||||
if len(val) == 0 {
|
||||
return nil, errors.New("empty argument")
|
||||
}
|
||||
|
||||
if len(val)%2 != 0 {
|
||||
return nil, errors.New("odd amount of symbols")
|
||||
}
|
||||
|
||||
bytes, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &bytesObf{data: bytes}, nil
|
||||
}
|
||||
|
||||
type bytesObf struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (o *bytesObf) Obfuscate(dst, src []byte) {
|
||||
copy(dst, o.data)
|
||||
}
|
||||
|
||||
func (o *bytesObf) Deobfuscate(dst, src []byte) bool {
|
||||
return bytes.Equal(o.data, src[:o.ObfuscatedLen(0)])
|
||||
}
|
||||
|
||||
func (o *bytesObf) ObfuscatedLen(srcLen int) int {
|
||||
return len(o.data)
|
||||
}
|
||||
|
||||
func (o *bytesObf) DeobfuscatedLen(srcLen int) int {
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package device
|
||||
|
||||
func newDataObf(val string) (obf, error) {
|
||||
return &dataObf{}, nil
|
||||
}
|
||||
|
||||
type dataObf struct {
|
||||
}
|
||||
|
||||
func (obf *dataObf) Obfuscate(dst, src []byte) {
|
||||
copy(dst, src)
|
||||
}
|
||||
|
||||
func (obf *dataObf) Deobfuscate(dst, src []byte) bool {
|
||||
copy(dst, src)
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *dataObf) ObfuscatedLen(n int) int {
|
||||
return n
|
||||
}
|
||||
|
||||
func (o *dataObf) DeobfuscatedLen(n int) int {
|
||||
return n
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package device
|
||||
|
||||
import "strconv"
|
||||
|
||||
func newDataSizeObf(val string) (obf, error) {
|
||||
length, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dataSizeObf{
|
||||
length: length,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type dataSizeObf struct {
|
||||
length int
|
||||
}
|
||||
|
||||
func (o *dataSizeObf) Obfuscate(dst, src []byte) {
|
||||
srcLen := len(src)
|
||||
for i := o.length - 1; i >= 0; i-- {
|
||||
dst[i] = byte(srcLen & 0xFF)
|
||||
srcLen >>= 8
|
||||
}
|
||||
}
|
||||
|
||||
func (o *dataSizeObf) Deobfuscate(dst, src []byte) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *dataSizeObf) ObfuscatedLen(srcLen int) int {
|
||||
return o.length
|
||||
}
|
||||
|
||||
func (o *dataSizeObf) DeobfuscatedLen(srcLen int) int {
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
func newDataStringObf(val string) (obf, error) {
|
||||
return &dataStringObf{}, nil
|
||||
}
|
||||
|
||||
type dataStringObf struct {
|
||||
}
|
||||
|
||||
func (o *dataStringObf) Obfuscate(dst, src []byte) {
|
||||
base64.RawStdEncoding.Encode(dst, src)
|
||||
}
|
||||
|
||||
func (o *dataStringObf) Deobfuscate(dst, src []byte) bool {
|
||||
base64.RawStdEncoding.Decode(dst, src)
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *dataStringObf) ObfuscatedLen(n int) int {
|
||||
return base64.RawStdEncoding.EncodedLen(n)
|
||||
}
|
||||
|
||||
func (o *dataStringObf) DeobfuscatedLen(n int) int {
|
||||
return base64.RawStdEncoding.DecodedLen(n)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func newRandObf(val string) (obf, error) {
|
||||
length, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &randObf{
|
||||
length: length,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type randObf struct {
|
||||
length int
|
||||
}
|
||||
|
||||
func (o *randObf) Obfuscate(dst, src []byte) {
|
||||
rand.Read(dst[:o.length])
|
||||
}
|
||||
|
||||
func (o *randObf) Deobfuscate(dst, src []byte) bool {
|
||||
// there is no way to validate randomness :)
|
||||
// assume that it is always true
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *randObf) ObfuscatedLen(n int) int {
|
||||
return o.length
|
||||
}
|
||||
|
||||
func (o *randObf) DeobfuscatedLen(n int) int {
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"strconv"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const chars52 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
func newRandCharObf(val string) (obf, error) {
|
||||
length, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &randCharObf{
|
||||
length: length,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type randCharObf struct {
|
||||
length int
|
||||
}
|
||||
|
||||
func (o *randCharObf) Obfuscate(dst, src []byte) {
|
||||
rand.Read(dst[:o.length])
|
||||
for i := range dst[:o.length] {
|
||||
dst[i] = chars52[dst[i]%52]
|
||||
}
|
||||
}
|
||||
|
||||
func (o *randCharObf) Deobfuscate(dst, src []byte) bool {
|
||||
for _, b := range src[:o.length] {
|
||||
if !unicode.IsLetter(rune(b)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *randCharObf) ObfuscatedLen(n int) int {
|
||||
return o.length
|
||||
}
|
||||
|
||||
func (o *randCharObf) DeobfuscatedLen(n int) int {
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"strconv"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const digits10 = "0123456789"
|
||||
|
||||
func newRandDigitsObf(val string) (obf, error) {
|
||||
length, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &randDigitObf{
|
||||
length: length,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type randDigitObf struct {
|
||||
length int
|
||||
}
|
||||
|
||||
func (o *randDigitObf) Obfuscate(dst, src []byte) {
|
||||
rand.Read(dst[:o.length])
|
||||
for i := range dst[:o.length] {
|
||||
dst[i] = digits10[dst[i]%10]
|
||||
}
|
||||
}
|
||||
|
||||
func (o *randDigitObf) Deobfuscate(dst, src []byte) bool {
|
||||
for _, b := range src[:o.length] {
|
||||
if !unicode.IsDigit(rune(b)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *randDigitObf) ObfuscatedLen(n int) int {
|
||||
return o.length
|
||||
}
|
||||
|
||||
func (o *randDigitObf) DeobfuscatedLen(n int) int {
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newTimestampObf(_ string) (obf, error) {
|
||||
return ×tampObf{}, nil
|
||||
}
|
||||
|
||||
type timestampObf struct{}
|
||||
|
||||
func (o *timestampObf) Obfuscate(dst, src []byte) {
|
||||
t := uint32(time.Now().Unix())
|
||||
binary.BigEndian.PutUint32(dst, t)
|
||||
}
|
||||
|
||||
func (o *timestampObf) Deobfuscate(dst, src []byte) bool {
|
||||
// replay attack check?
|
||||
// requires time to be always synchronized
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *timestampObf) ObfuscatedLen(n int) int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (o *timestampObf) DeobfuscatedLen(n int) int {
|
||||
return 0
|
||||
}
|
||||
+11
-4
@@ -12,7 +12,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
)
|
||||
|
||||
type Peer struct {
|
||||
@@ -39,6 +39,7 @@ type Peer struct {
|
||||
zeroKeyMaterial *Timer
|
||||
persistentKeepalive *Timer
|
||||
handshakeAttempts atomic.Uint32
|
||||
maxHandshakeAttempts atomic.Uint32
|
||||
needAnotherKeepalive atomic.Bool
|
||||
sentLastMinuteHandshake atomic.Bool
|
||||
}
|
||||
@@ -55,7 +56,8 @@ type Peer struct {
|
||||
|
||||
cookieGenerator CookieGenerator
|
||||
trieEntries list.List
|
||||
persistentKeepaliveInterval atomic.Uint32
|
||||
persistentKeepaliveInterval AtomicUintRange
|
||||
udpWindow atomic.Uint32
|
||||
}
|
||||
|
||||
func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) {
|
||||
@@ -78,6 +80,8 @@ func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) {
|
||||
// create peer
|
||||
peer := new(Peer)
|
||||
|
||||
peer.udpWindow.Store(DefaultUdpWindow)
|
||||
|
||||
peer.cookieGenerator.Init(pk)
|
||||
peer.device = device
|
||||
peer.queue.outbound = newAutodrainingOutboundQueue(device)
|
||||
@@ -192,7 +196,7 @@ func (peer *Peer) Start() {
|
||||
peer.stopping.Add(2)
|
||||
|
||||
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.device.queue.encryption.wg.Add(1) // keep encryption queue open for our writes
|
||||
@@ -242,7 +246,7 @@ func (peer *Peer) ExpireCurrentKeypairs() {
|
||||
handshake.mutex.Lock()
|
||||
peer.device.indexTable.Delete(handshake.localIndex)
|
||||
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()
|
||||
|
||||
keypairs := &peer.keypairs
|
||||
@@ -282,6 +286,9 @@ func (peer *Peer) SetEndpointFromPacket(endpoint conn.Endpoint) {
|
||||
if peer.endpoint.disableRoaming {
|
||||
return
|
||||
}
|
||||
if peer.endpoint.val != endpoint {
|
||||
peer.udpWindow.Store(DefaultUdpWindow)
|
||||
}
|
||||
peer.endpoint.clearSrcOnTx = false
|
||||
peer.endpoint.val = endpoint
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
package device
|
||||
|
||||
import "github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
import "github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
|
||||
/* Reduce memory consumption for Android */
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
package device
|
||||
|
||||
import "github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
import "github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
|
||||
const (
|
||||
QueueStagedSize = conn.IdealBatchSize
|
||||
|
||||
+135
-25
@@ -13,7 +13,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
@@ -32,6 +32,7 @@ type QueueInboundElement struct {
|
||||
counter uint64
|
||||
keypair *Keypair
|
||||
endpoint conn.Endpoint
|
||||
padding uint32
|
||||
}
|
||||
|
||||
type QueueInboundElementsContainer struct {
|
||||
@@ -59,7 +60,8 @@ func (peer *Peer) keepKeyFreshReceiving() {
|
||||
return
|
||||
}
|
||||
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.SendHandshakeInitiation(false)
|
||||
}
|
||||
@@ -70,7 +72,10 @@ func (peer *Peer) keepKeyFreshReceiving() {
|
||||
* Every time the bind is updated a new routine is started for
|
||||
* IPv4 and IPv6 (separately)
|
||||
*/
|
||||
func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.ReceiveFunc) {
|
||||
func (device *Device) RoutineReceiveIncoming(
|
||||
maxBatchSize int,
|
||||
recv conn.ReceiveFunc,
|
||||
) {
|
||||
recvName := recv.PrettyName()
|
||||
defer func() {
|
||||
device.log.Verbosef("Routine: receive incoming %s - stopped", recvName)
|
||||
@@ -92,15 +97,16 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive
|
||||
endpoints = make([]conn.Endpoint, maxBatchSize)
|
||||
deathSpiral int
|
||||
elemsByPeer = make(map[*Peer]*QueueInboundElementsContainer, maxBatchSize)
|
||||
typeHashBuf [4]byte
|
||||
)
|
||||
|
||||
for i := range bufsArrs {
|
||||
for i := range maxBatchSize {
|
||||
bufsArrs[i] = device.GetMessageBuffer()
|
||||
bufs[i] = bufsArrs[i][:]
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for i := 0; i < maxBatchSize; i++ {
|
||||
for i := range maxBatchSize {
|
||||
if bufsArrs[i] != nil {
|
||||
device.PutMessageBuffer(bufsArrs[i])
|
||||
}
|
||||
@@ -133,21 +139,30 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive
|
||||
}
|
||||
|
||||
// check size of packet
|
||||
|
||||
packet := bufsArrs[i][:size]
|
||||
msgType := binary.LittleEndian.Uint32(packet[:4])
|
||||
|
||||
opts := &device.net.framedOpts
|
||||
if opts.HeaderCompat {
|
||||
if opts.H1 != nil && opts.H1.Validate(msgType) {
|
||||
msgType = MessageInitiationType
|
||||
} else if opts.H2 != nil && opts.H2.Validate(msgType) {
|
||||
msgType = MessageResponseType
|
||||
} else if opts.H3 != nil && opts.H3.Validate(msgType) {
|
||||
msgType = MessageCookieReplyType
|
||||
} else if opts.H4 != nil && opts.H4.Validate(msgType) {
|
||||
msgType = MessageTransportType
|
||||
}
|
||||
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
|
||||
msgSize, msgType, padding := device.DeterminePacketTypeAndPadding(packet, typeHash)
|
||||
|
||||
packet = packet[padding:]
|
||||
if msgType != MessageTransportType {
|
||||
packet = packet[:msgSize]
|
||||
}
|
||||
|
||||
if cip != nil {
|
||||
applyHash(packet[:4], packet[:4], typeHash)
|
||||
}
|
||||
|
||||
switch msgType {
|
||||
@@ -161,6 +176,9 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive
|
||||
if len(packet) < MessageTransportSize {
|
||||
continue
|
||||
}
|
||||
if cip != nil {
|
||||
cip.XORKeyStream(packet[4:MessageTransportHeaderSize], packet[4:MessageTransportHeaderSize])
|
||||
}
|
||||
|
||||
// lookup key pair
|
||||
|
||||
@@ -175,7 +193,7 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive
|
||||
|
||||
// check keypair expiry
|
||||
|
||||
if keypair.created.Add(RejectAfterTime).Before(time.Now()) {
|
||||
if keypair.created.Add(device.keychainExpireTime()).Before(time.Now()) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -187,6 +205,7 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive
|
||||
elem.keypair = keypair
|
||||
elem.endpoint = endpoints[i]
|
||||
elem.counter = 0
|
||||
elem.padding = padding
|
||||
|
||||
elemsForPeer, ok := elemsByPeer[peer]
|
||||
if !ok {
|
||||
@@ -205,16 +224,25 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive
|
||||
if len(packet) != MessageInitiationSize {
|
||||
continue
|
||||
}
|
||||
if cip != nil {
|
||||
cip.XORKeyStream(packet[4:MessageInitiationSize], packet[4:MessageInitiationSize])
|
||||
}
|
||||
|
||||
case MessageResponseType:
|
||||
if len(packet) != MessageResponseSize {
|
||||
continue
|
||||
}
|
||||
if cip != nil {
|
||||
cip.XORKeyStream(packet[4:MessageResponseSize], packet[4:MessageResponseSize])
|
||||
}
|
||||
|
||||
case MessageCookieReplyType:
|
||||
if len(packet) != MessageCookieReplySize {
|
||||
continue
|
||||
}
|
||||
if cip != nil {
|
||||
cip.XORKeyStream(packet[4:MessageCookieReplySize], packet[4:MessageCookieReplySize])
|
||||
}
|
||||
|
||||
default:
|
||||
device.log.Verbosef("Received message with unknown type")
|
||||
@@ -317,9 +345,14 @@ func (device *Device) RoutineHandshake(id int) {
|
||||
// consume reply
|
||||
|
||||
if peer := entry.peer; peer.isRunning.Load() {
|
||||
device.log.Verbosef("Receiving cookie response from %s", elem.endpoint.DstToString())
|
||||
device.log.Verbosef(
|
||||
"Receiving cookie response from %s",
|
||||
elem.endpoint.DstToString(),
|
||||
)
|
||||
if !peer.cookieGenerator.ConsumeReply(&reply) {
|
||||
device.log.Verbosef("Could not decrypt invalid cookie response")
|
||||
device.log.Verbosef(
|
||||
"Could not decrypt invalid cookie response",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +369,8 @@ func (device *Device) RoutineHandshake(id int) {
|
||||
|
||||
// endpoints destination address is the source of the datagram
|
||||
|
||||
if device.IsUnderLoad() {
|
||||
disableCookies := device.disableCookies.Load()
|
||||
if !disableCookies && device.IsUnderLoad() {
|
||||
|
||||
// verify MAC2 field
|
||||
|
||||
@@ -370,6 +404,9 @@ func (device *Device) RoutineHandshake(id int) {
|
||||
goto skip
|
||||
}
|
||||
|
||||
// have to reassign msgType for ranged msgType to work
|
||||
msg.Type = elem.msgType
|
||||
|
||||
// consume initiation
|
||||
peer := device.ConsumeMessageInitiation(&msg)
|
||||
if peer == nil {
|
||||
@@ -402,6 +439,9 @@ func (device *Device) RoutineHandshake(id int) {
|
||||
goto skip
|
||||
}
|
||||
|
||||
// have to reassign msgType for ranged msgType to work
|
||||
msg.Type = elem.msgType
|
||||
|
||||
// consume response
|
||||
|
||||
peer := device.ConsumeMessageResponse(&msg)
|
||||
@@ -475,7 +515,12 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
|
||||
}
|
||||
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)
|
||||
continue
|
||||
}
|
||||
@@ -516,11 +561,14 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
|
||||
}
|
||||
|
||||
default:
|
||||
device.log.Verbosef("Packet with invalid IP version from %v", peer)
|
||||
device.log.Verbosef(
|
||||
"Packet with invalid IP version from %v",
|
||||
peer,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
bufs = append(bufs, elem.buffer[:MessageTransportOffsetContent+len(elem.packet)])
|
||||
bufs = append(bufs, elem.buffer[int(elem.padding):int(elem.padding)+MessageTransportHeaderSize+len(elem.packet)])
|
||||
}
|
||||
|
||||
peer.rxBytes.Add(rxBytesLen)
|
||||
@@ -547,3 +595,65 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
|
||||
device.PutInboundElementsContainer(elemsContainer)
|
||||
}
|
||||
}
|
||||
|
||||
func applyHash(dst, src, hash []byte) {
|
||||
for i := range len(dst) {
|
||||
dst[i] = src[i] ^ hash[i]
|
||||
}
|
||||
}
|
||||
|
||||
func (device *Device) DeterminePacketTypeAndPadding(packet []byte, typeHash []byte) (int, uint32, uint32) {
|
||||
var headerBytes [4]byte
|
||||
var padding uint32
|
||||
var header UintRange
|
||||
var expectedSize int
|
||||
|
||||
size := len(packet)
|
||||
randomTrailers := device.randomTrailers.Load()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
padding = device.paddings.cookie.Load()
|
||||
header = device.headers.cookie.Load()
|
||||
expectedSize = int(padding) + MessageCookieReplySize
|
||||
|
||||
if size == expectedSize || randomTrailers && size > expectedSize {
|
||||
applyHash(headerBytes[:], packet[padding:padding+4], typeHash)
|
||||
if header.Contains(binary.LittleEndian.Uint32(headerBytes[:])) {
|
||||
return MessageCookieReplySize, MessageCookieReplyType, padding
|
||||
}
|
||||
}
|
||||
|
||||
padding = device.paddings.transport.Load()
|
||||
header = device.headers.transport.Load()
|
||||
expectedSize = int(padding) + MessageTransportSize
|
||||
|
||||
if size >= expectedSize {
|
||||
applyHash(headerBytes[:], packet[padding:padding+4], typeHash)
|
||||
if header.Contains(binary.LittleEndian.Uint32(headerBytes[:])) {
|
||||
return MessageTransportSize, MessageTransportType, padding
|
||||
}
|
||||
}
|
||||
|
||||
return 0, MessageUnknownType, 0
|
||||
}
|
||||
+193
-46
@@ -7,15 +7,17 @@ package device
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
@@ -46,11 +48,13 @@ import (
|
||||
*/
|
||||
|
||||
type QueueOutboundElement struct {
|
||||
buffer *[MaxMessageSize]byte // slice holding the packet data
|
||||
packet []byte // slice of "buffer" (always!)
|
||||
nonce uint64 // nonce for encryption
|
||||
keypair *Keypair // keypair for encryption
|
||||
peer *Peer // related peer
|
||||
buffer *[MaxMessageSize]byte // slice holding the packet data
|
||||
packet []byte // slice of "buffer" (always!)
|
||||
nonce uint64 // nonce for encryption
|
||||
keypair *Keypair // keypair for encryption
|
||||
peer *Peer // related peer
|
||||
padding uint32
|
||||
isKeepalive bool
|
||||
}
|
||||
|
||||
type QueueOutboundElementsContainer struct {
|
||||
@@ -62,6 +66,8 @@ func (device *Device) NewOutboundElement() *QueueOutboundElement {
|
||||
elem := device.GetOutboundElement()
|
||||
elem.buffer = device.GetMessageBuffer()
|
||||
elem.nonce = 0
|
||||
elem.padding = device.paddings.transport.Load()
|
||||
elem.isKeepalive = false
|
||||
// keypair and peer were cleared (if necessary) by clearPointers.
|
||||
return elem
|
||||
}
|
||||
@@ -82,6 +88,7 @@ func (elem *QueueOutboundElement) clearPointers() {
|
||||
func (peer *Peer) SendKeepalive() {
|
||||
if len(peer.queue.staged) == 0 && peer.isRunning.Load() {
|
||||
elem := peer.device.NewOutboundElement()
|
||||
elem.isKeepalive = true
|
||||
elemsContainer := peer.device.GetOutboundElementsContainer()
|
||||
elemsContainer.elems = append(elemsContainer.elems, elem)
|
||||
select {
|
||||
@@ -99,17 +106,20 @@ func (peer *Peer) SendKeepalive() {
|
||||
func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
||||
if !isRetry {
|
||||
peer.timers.handshakeAttempts.Store(0)
|
||||
peer.timers.maxHandshakeAttempts.Store(peer.device.maxHandshakeAttemps())
|
||||
}
|
||||
|
||||
timeout := peer.device.rekeyMinTimeout()
|
||||
|
||||
peer.handshake.mutex.RLock()
|
||||
if time.Since(peer.handshake.lastSentHandshake) < RekeyTimeout {
|
||||
if time.Since(peer.handshake.lastSentHandshake) < timeout {
|
||||
peer.handshake.mutex.RUnlock()
|
||||
return nil
|
||||
}
|
||||
peer.handshake.mutex.RUnlock()
|
||||
|
||||
peer.handshake.mutex.Lock()
|
||||
if time.Since(peer.handshake.lastSentHandshake) < RekeyTimeout {
|
||||
if time.Since(peer.handshake.lastSentHandshake) < timeout {
|
||||
peer.handshake.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -124,8 +134,27 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf [MessageInitiationSize]byte
|
||||
writer := bytes.NewBuffer(buf[:0])
|
||||
var sendBuffer [][]byte
|
||||
|
||||
for _, ipacket := range peer.device.ipackets {
|
||||
if ipacket != nil {
|
||||
buf := make([]byte, ipacket.ObfuscatedLen(0))
|
||||
ipacket.Obfuscate(buf, nil)
|
||||
sendBuffer = append(sendBuffer, buf)
|
||||
}
|
||||
}
|
||||
|
||||
sendBuffer = append(sendBuffer, peer.device.JunkPackets()...)
|
||||
|
||||
padding := int(peer.device.paddings.init.Load())
|
||||
trailerLen := max(peer.randomTrailer(padding+MessageInitiationSize), 0)
|
||||
|
||||
buf := make([]byte, padding+MessageInitiationSize+trailerLen)
|
||||
|
||||
crypt := buf[:padding]
|
||||
rand.Read(crypt)
|
||||
|
||||
writer := bytes.NewBuffer(buf[padding:padding])
|
||||
binary.Write(writer, binary.LittleEndian, msg)
|
||||
packet := writer.Bytes()
|
||||
peer.cookieGenerator.AddMacs(packet)
|
||||
@@ -133,7 +162,19 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
||||
peer.timersAnyAuthenticatedPacketTraversal()
|
||||
peer.timersAnyAuthenticatedPacketSent()
|
||||
|
||||
err = peer.SendBuffers([][]byte{packet})
|
||||
cip, err := peer.device.HeaderProtectionCipher(crypt[:HeaderCipherNonceSize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cip != nil {
|
||||
cip.XORKeyStream(packet, packet)
|
||||
}
|
||||
|
||||
trailer := buf[padding+MessageInitiationSize:]
|
||||
rand.Read(trailer)
|
||||
|
||||
sendBuffer = append(sendBuffer, buf)
|
||||
err = peer.SendBuffers(sendBuffer)
|
||||
if err != nil {
|
||||
peer.device.log.Errorf("%v - Failed to send handshake initiation: %v", peer, err)
|
||||
}
|
||||
@@ -155,9 +196,15 @@ func (peer *Peer) SendHandshakeResponse() error {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf [MessageResponseSize]byte
|
||||
writer := bytes.NewBuffer(buf[:0])
|
||||
padding := int(peer.device.paddings.response.Load())
|
||||
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)
|
||||
packet := writer.Bytes()
|
||||
peer.cookieGenerator.AddMacs(packet)
|
||||
@@ -172,8 +219,19 @@ func (peer *Peer) SendHandshakeResponse() error {
|
||||
peer.timersAnyAuthenticatedPacketTraversal()
|
||||
peer.timersAnyAuthenticatedPacketSent()
|
||||
|
||||
cip, err := peer.device.HeaderProtectionCipher(crypt[:HeaderCipherNonceSize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cip != nil {
|
||||
cip.XORKeyStream(packet, packet)
|
||||
}
|
||||
|
||||
trailer := buf[padding+MessageResponseSize:]
|
||||
rand.Read(trailer)
|
||||
|
||||
// TODO: allocation could be avoided
|
||||
err = peer.SendBuffers([][]byte{packet})
|
||||
err = peer.SendBuffers([][]byte{buf})
|
||||
if err != nil {
|
||||
peer.device.log.Errorf("%v - Failed to send handshake response: %v", peer, err)
|
||||
}
|
||||
@@ -183,28 +241,45 @@ func (peer *Peer) SendHandshakeResponse() error {
|
||||
func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement) error {
|
||||
device.log.Verbosef("Sending cookie response for denied handshake message for %v", initiatingElem.endpoint.DstToString())
|
||||
|
||||
opts := &device.net.framedOpts
|
||||
var typ uint32
|
||||
if opts.HeaderCompat && opts.H3 != nil {
|
||||
typ = opts.H3.Generate()
|
||||
} else {
|
||||
typ = MessageCookieReplyType
|
||||
}
|
||||
|
||||
sender := binary.LittleEndian.Uint32(initiatingElem.packet[4:8])
|
||||
reply, err := device.cookieChecker.CreateReply(initiatingElem.packet, sender, initiatingElem.endpoint.DstToBytes(), typ)
|
||||
msgType := device.headers.cookie.Load().PickOne()
|
||||
|
||||
reply, err := device.cookieChecker.CreateReply(
|
||||
initiatingElem.packet,
|
||||
sender,
|
||||
initiatingElem.endpoint.DstToBytes(),
|
||||
msgType,
|
||||
)
|
||||
if err != nil {
|
||||
device.log.Errorf("Failed to create cookie reply: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var buf [MessageCookieReplySize]byte
|
||||
writer := bytes.NewBuffer(buf[:0])
|
||||
padding := int(device.paddings.cookie.Load())
|
||||
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)
|
||||
packet := writer.Bytes()
|
||||
|
||||
cip, err := device.HeaderProtectionCipher(crypt[:HeaderCipherNonceSize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cip != nil {
|
||||
cip.XORKeyStream(packet, packet)
|
||||
}
|
||||
|
||||
trailer := buf[padding+MessageCookieReplySize:]
|
||||
rand.Read(trailer)
|
||||
|
||||
// TODO: allocation could be avoided
|
||||
device.net.bind.Send([][]byte{packet}, initiatingElem.endpoint)
|
||||
device.net.bind.Send([][]byte{buf}, initiatingElem.endpoint)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -214,7 +289,7 @@ func (peer *Peer) keepKeyFreshSending() {
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -236,7 +311,6 @@ func (device *Device) RoutineReadFromTUN() {
|
||||
elemsByPeer = make(map[*Peer]*QueueOutboundElementsContainer, batchSize)
|
||||
count = 0
|
||||
sizes = make([]int, batchSize)
|
||||
offset = MessageTransportHeaderSize
|
||||
)
|
||||
|
||||
for i := range elems {
|
||||
@@ -254,6 +328,9 @@ func (device *Device) RoutineReadFromTUN() {
|
||||
}()
|
||||
|
||||
for {
|
||||
padding := device.paddings.transport.Load()
|
||||
offset := MessageTransportHeaderSize + int(padding)
|
||||
|
||||
// read packets
|
||||
count, readErr = device.tun.device.Read(bufs, sizes, offset)
|
||||
for i := 0; i < count; i++ {
|
||||
@@ -263,6 +340,7 @@ func (device *Device) RoutineReadFromTUN() {
|
||||
|
||||
elem := elems[i]
|
||||
elem.packet = bufs[i][offset : offset+sizes[i]]
|
||||
elem.padding = padding
|
||||
|
||||
// lookup peer
|
||||
var peer *Peer
|
||||
@@ -357,7 +435,7 @@ top:
|
||||
}
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
@@ -447,13 +525,55 @@ func calculatePaddingSize(packetSize, mtu int) int {
|
||||
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
|
||||
* and marks them for sequential consumption (by releasing the mutex)
|
||||
*
|
||||
* Obs. One instance per core
|
||||
*/
|
||||
func (device *Device) RoutineEncryption(id int) {
|
||||
var paddingZeros [PaddingMultiple]byte
|
||||
var nonce [chacha20poly1305.NonceSize]byte
|
||||
|
||||
defer device.log.Verbosef("Routine: encryption worker %d - stopped", id)
|
||||
@@ -461,38 +581,63 @@ func (device *Device) RoutineEncryption(id int) {
|
||||
|
||||
for elemsContainer := range device.queue.encryption.c {
|
||||
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
|
||||
header := elem.buffer[:MessageTransportHeaderSize]
|
||||
header := elem.buffer[elem.padding : elem.padding+MessageTransportHeaderSize]
|
||||
|
||||
fieldType := header[0:4]
|
||||
fieldReceiver := header[4:8]
|
||||
fieldNonce := header[8:16]
|
||||
|
||||
opts := &device.net.framedOpts
|
||||
var typ uint32
|
||||
if opts.HeaderCompat && opts.H4 != nil {
|
||||
typ = opts.H4.Generate()
|
||||
} else {
|
||||
typ = MessageTransportType
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(fieldType, typ)
|
||||
binary.LittleEndian.PutUint32(fieldType, device.headers.transport.Load().PickOne())
|
||||
binary.LittleEndian.PutUint32(fieldReceiver, elem.keypair.remoteIndex)
|
||||
binary.LittleEndian.PutUint64(fieldNonce, elem.nonce)
|
||||
|
||||
// pad content to multiple of 16
|
||||
paddingSize := calculatePaddingSize(len(elem.packet), int(device.tun.mtu.Load()))
|
||||
elem.packet = append(elem.packet, paddingZeros[:paddingSize]...)
|
||||
packetSize := len(elem.packet) + MinMessageSize + int(elem.padding)
|
||||
mtu := int(device.tun.mtu.Load())
|
||||
|
||||
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
|
||||
|
||||
binary.LittleEndian.PutUint64(nonce[4:], elem.nonce)
|
||||
elem.packet = elem.keypair.send.Seal(
|
||||
header,
|
||||
elem.buffer[:elem.padding+MessageTransportHeaderSize],
|
||||
nonce[:],
|
||||
elem.packet,
|
||||
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()
|
||||
}
|
||||
@@ -531,9 +676,10 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
|
||||
dataSent := false
|
||||
elemsContainer.Lock()
|
||||
for _, elem := range elemsContainer.elems {
|
||||
if len(elem.packet) != MessageKeepaliveSize {
|
||||
if !elem.isKeepalive {
|
||||
dataSent = true
|
||||
}
|
||||
|
||||
bufs = append(bufs, elem.packet)
|
||||
}
|
||||
|
||||
@@ -544,6 +690,7 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
|
||||
if dataSent {
|
||||
peer.timersDataSent()
|
||||
}
|
||||
|
||||
for _, elem := range elemsContainer.elems {
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
device.PutOutboundElement(elem)
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/rwcancel"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/rwcancel"
|
||||
)
|
||||
|
||||
func (device *Device) startRouteListener(_ conn.Bind) (*rwcancel.RWCancel, error) {
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/rwcancel"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/rwcancel"
|
||||
)
|
||||
|
||||
func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, error) {
|
||||
|
||||
+124
-27
@@ -22,24 +22,25 @@ type Timer struct {
|
||||
*time.Timer
|
||||
modifyingLock sync.RWMutex
|
||||
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 = time.AfterFunc(time.Hour, func() {
|
||||
timer.runningLock.Lock()
|
||||
defer timer.runningLock.Unlock()
|
||||
|
||||
timer.modifyingLock.Lock()
|
||||
if !timer.isPending {
|
||||
if timer.duration == 0 {
|
||||
timer.modifyingLock.Unlock()
|
||||
return
|
||||
}
|
||||
timer.isPending = false
|
||||
duration := timer.duration
|
||||
timer.modifyingLock.Unlock()
|
||||
timer.duration = 0
|
||||
|
||||
expirationFunction(peer)
|
||||
expirationFunction(peer, duration)
|
||||
})
|
||||
timer.Stop()
|
||||
return timer
|
||||
@@ -47,14 +48,14 @@ func (peer *Peer) NewTimer(expirationFunction func(*Peer)) *Timer {
|
||||
|
||||
func (timer *Timer) Mod(d time.Duration) {
|
||||
timer.modifyingLock.Lock()
|
||||
timer.isPending = true
|
||||
timer.duration = d
|
||||
timer.Reset(d)
|
||||
timer.modifyingLock.Unlock()
|
||||
}
|
||||
|
||||
func (timer *Timer) Del() {
|
||||
timer.modifyingLock.Lock()
|
||||
timer.isPending = false
|
||||
timer.duration = 0
|
||||
timer.Stop()
|
||||
timer.modifyingLock.Unlock()
|
||||
}
|
||||
@@ -69,16 +70,18 @@ func (timer *Timer) DelSync() {
|
||||
func (timer *Timer) IsPending() bool {
|
||||
timer.modifyingLock.RLock()
|
||||
defer timer.modifyingLock.RUnlock()
|
||||
return timer.isPending
|
||||
return timer.duration > 0
|
||||
}
|
||||
|
||||
func (peer *Peer) timersActive() bool {
|
||||
return peer.isRunning.Load() && peer.device != nil && peer.device.isUp()
|
||||
}
|
||||
|
||||
func expiredRetransmitHandshake(peer *Peer) {
|
||||
if peer.timers.handshakeAttempts.Load() > MaxTimerHandshakes {
|
||||
peer.device.log.Verbosef("%s - Handshake did not complete after %d attempts, giving up", peer, MaxTimerHandshakes+2)
|
||||
func expiredRetransmitHandshake(peer *Peer, d time.Duration) {
|
||||
maxAttempts := peer.timers.maxHandshakeAttempts.Load()
|
||||
|
||||
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() {
|
||||
peer.timers.sendKeepalive.Del()
|
||||
@@ -93,11 +96,11 @@ func expiredRetransmitHandshake(peer *Peer) {
|
||||
* of a partial exchange.
|
||||
*/
|
||||
if peer.timersActive() && !peer.timers.zeroKeyMaterial.IsPending() {
|
||||
peer.timers.zeroKeyMaterial.Mod(RejectAfterTime * 3)
|
||||
peer.timers.zeroKeyMaterial.Mod(peer.device.keychainExpireTime() * 3)
|
||||
}
|
||||
} else {
|
||||
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. */
|
||||
peer.markEndpointSrcForClearing()
|
||||
@@ -106,30 +109,30 @@ func expiredRetransmitHandshake(peer *Peer) {
|
||||
}
|
||||
}
|
||||
|
||||
func expiredSendKeepalive(peer *Peer) {
|
||||
func expiredSendKeepalive(peer *Peer, d time.Duration) {
|
||||
peer.SendKeepalive()
|
||||
if peer.timers.needAnotherKeepalive.Load() {
|
||||
peer.timers.needAnotherKeepalive.Store(false)
|
||||
if peer.timersActive() {
|
||||
peer.timers.sendKeepalive.Mod(KeepaliveTimeout)
|
||||
peer.timers.sendKeepalive.Mod(peer.sendKeepaliveTimeout())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func expiredNewHandshake(peer *Peer) {
|
||||
peer.device.log.Verbosef("%s - Retrying handshake because we stopped hearing back after %d seconds", peer, int((KeepaliveTimeout + RekeyTimeout).Seconds()))
|
||||
func expiredNewHandshake(peer *Peer, d time.Duration) {
|
||||
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. */
|
||||
peer.markEndpointSrcForClearing()
|
||||
peer.SendHandshakeInitiation(false)
|
||||
}
|
||||
|
||||
func expiredZeroKeyMaterial(peer *Peer) {
|
||||
peer.device.log.Verbosef("%s - Removing all keys, since we haven't received a new one in %d seconds", peer, int((RejectAfterTime * 3).Seconds()))
|
||||
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(d.Seconds()))
|
||||
peer.ZeroAndFlushAll()
|
||||
}
|
||||
|
||||
func expiredPersistentKeepalive(peer *Peer) {
|
||||
if peer.persistentKeepaliveInterval.Load() > 0 {
|
||||
func expiredPersistentKeepalive(peer *Peer, d time.Duration) {
|
||||
if !peer.persistentKeepaliveInterval.Load().IsZero() {
|
||||
peer.SendKeepalive()
|
||||
}
|
||||
}
|
||||
@@ -137,7 +140,7 @@ func expiredPersistentKeepalive(peer *Peer) {
|
||||
/* Should be called after an authenticated data packet is sent. */
|
||||
func (peer *Peer) timersDataSent() {
|
||||
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() {
|
||||
if peer.timersActive() {
|
||||
if !peer.timers.sendKeepalive.IsPending() {
|
||||
peer.timers.sendKeepalive.Mod(KeepaliveTimeout)
|
||||
peer.timers.sendKeepalive.Mod(peer.sendKeepaliveTimeout())
|
||||
} else {
|
||||
peer.timers.needAnotherKeepalive.Store(true)
|
||||
}
|
||||
@@ -169,7 +172,7 @@ func (peer *Peer) timersAnyAuthenticatedPacketReceived() {
|
||||
/* Should be called after a handshake initiation message is sent. */
|
||||
func (peer *Peer) timersHandshakeInitiated() {
|
||||
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.handshakeAttempts.Store(0)
|
||||
peer.timers.maxHandshakeAttempts.Store(peer.device.maxHandshakeAttemps())
|
||||
peer.timers.sentLastMinuteHandshake.Store(false)
|
||||
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. */
|
||||
func (peer *Peer) timersSessionDerived() {
|
||||
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. */
|
||||
func (peer *Peer) timersAnyAuthenticatedPacketTraversal() {
|
||||
keepalive := peer.persistentKeepaliveInterval.Load()
|
||||
if keepalive > 0 && peer.timersActive() {
|
||||
peer.timers.persistentKeepalive.Mod(time.Duration(keepalive) * time.Second)
|
||||
if !keepalive.IsZero() && peer.timersActive() {
|
||||
peer.timers.persistentKeepalive.Mod(time.Duration(keepalive.PickOne()) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +212,7 @@ func (peer *Peer) timersInit() {
|
||||
|
||||
func (peer *Peer) timersStart() {
|
||||
peer.timers.handshakeAttempts.Store(0)
|
||||
peer.timers.maxHandshakeAttempts.Store(peer.device.maxHandshakeAttemps())
|
||||
peer.timers.sentLastMinuteHandshake.Store(false)
|
||||
peer.timers.needAnotherKeepalive.Store(false)
|
||||
}
|
||||
@@ -219,3 +224,95 @@ func (peer *Peer) timersStop() {
|
||||
peer.timers.zeroKeyMaterial.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
@@ -8,7 +8,7 @@ package device
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun"
|
||||
)
|
||||
|
||||
const DefaultMTU = 1420
|
||||
|
||||
+273
-260
@@ -18,8 +18,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conceal"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/ipc"
|
||||
)
|
||||
|
||||
type IPCError struct {
|
||||
@@ -71,6 +70,18 @@ func (device *Device) IpcGetOperation(w io.Writer) error {
|
||||
}
|
||||
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() {
|
||||
// lock required resources
|
||||
@@ -84,6 +95,9 @@ func (device *Device) IpcGetOperation(w io.Writer) error {
|
||||
device.peers.RLock()
|
||||
defer device.peers.RUnlock()
|
||||
|
||||
device.headerProtection.RLock()
|
||||
defer device.headerProtection.RUnlock()
|
||||
|
||||
// serialize device related values
|
||||
|
||||
if !device.staticIdentity.privateKey.IsZero() {
|
||||
@@ -98,69 +112,81 @@ func (device *Device) IpcGetOperation(w io.Writer) error {
|
||||
sendf("fwmark=%d", device.net.fwmark)
|
||||
}
|
||||
|
||||
if device.net.preludeOpts.Jc != 0 {
|
||||
sendf("jc=%d", device.net.preludeOpts.Jc)
|
||||
if count := device.junk.count.Load(); count != 0 {
|
||||
sendf("jc=%d", count)
|
||||
}
|
||||
|
||||
if device.net.preludeOpts.Jmin != 0 {
|
||||
sendf("jmin=%d", device.net.preludeOpts.Jmin)
|
||||
if min := device.junk.min.Load(); min != 0 {
|
||||
sendf("jmin=%d", min)
|
||||
}
|
||||
|
||||
if device.net.preludeOpts.Jmax != 0 {
|
||||
sendf("jmax=%d", device.net.preludeOpts.Jmax)
|
||||
if max := device.junk.max.Load(); max != 0 {
|
||||
sendf("jmax=%d", max)
|
||||
}
|
||||
|
||||
if device.net.framedOpts.S1 != 0 {
|
||||
sendf("s1=%d", device.net.framedOpts.S1)
|
||||
if padding := device.paddings.init.Load(); padding != 0 {
|
||||
sendf("s1=%d", padding)
|
||||
}
|
||||
|
||||
if device.net.framedOpts.S2 != 0 {
|
||||
sendf("s2=%d", device.net.framedOpts.S2)
|
||||
if padding := device.paddings.response.Load(); padding != 0 {
|
||||
sendf("s2=%d", padding)
|
||||
}
|
||||
|
||||
if device.net.framedOpts.S3 != 0 {
|
||||
sendf("s3=%d", device.net.framedOpts.S3)
|
||||
if padding := device.paddings.cookie.Load(); padding != 0 {
|
||||
sendf("s3=%d", padding)
|
||||
}
|
||||
|
||||
if device.net.framedOpts.S4 != 0 {
|
||||
sendf("s4=%d", device.net.framedOpts.S4)
|
||||
if padding := device.paddings.transport.Load(); padding != 0 {
|
||||
sendf("s4=%d", padding)
|
||||
}
|
||||
|
||||
if device.net.framedOpts.H1 != nil {
|
||||
sendf("h1=%s", device.net.framedOpts.H1.GenSpec())
|
||||
if header := device.headers.init.Load(); !header.IsZero() {
|
||||
sendf("h1=%s", header.ToString())
|
||||
}
|
||||
|
||||
if device.net.framedOpts.H2 != nil {
|
||||
sendf("h2=%s", device.net.framedOpts.H2.GenSpec())
|
||||
if header := device.headers.response.Load(); !header.IsZero() {
|
||||
sendf("h2=%s", header.ToString())
|
||||
}
|
||||
|
||||
if device.net.framedOpts.H3 != nil {
|
||||
sendf("h3=%s", device.net.framedOpts.H3.GenSpec())
|
||||
if header := device.headers.cookie.Load(); !header.IsZero() {
|
||||
sendf("h3=%s", header.ToString())
|
||||
}
|
||||
|
||||
if device.net.framedOpts.H4 != nil {
|
||||
sendf("h4=%s", device.net.framedOpts.H4.GenSpec())
|
||||
if header := device.headers.transport.Load(); !header.IsZero() {
|
||||
sendf("h4=%s", header.ToString())
|
||||
}
|
||||
|
||||
for i, rules := range device.net.preludeOpts.RulesArr {
|
||||
if rules != nil {
|
||||
sendf("i%d=%s", i+1, rules.Spec())
|
||||
for i, ipacket := range device.ipackets {
|
||||
if ipacket != nil {
|
||||
sendf("i%d=%s", i+1, ipacket.Spec)
|
||||
}
|
||||
}
|
||||
|
||||
if len(device.net.network) > 0 {
|
||||
sendf("network=%s", device.net.network)
|
||||
if !device.headerProtection.key.IsZero() {
|
||||
keyf("header_protection_key", (*[32]byte)(&device.headerProtection.key))
|
||||
}
|
||||
|
||||
if device.net.masqueradeOpts.RulesIn != nil {
|
||||
sendf("format_in=%s", device.net.masqueradeOpts.RulesIn.Spec())
|
||||
if addition := device.contentPaddingAddition.Load(); !addition.IsZero() {
|
||||
sendf("content_padding_addition=%s", addition.ToString())
|
||||
}
|
||||
|
||||
if device.net.masqueradeOpts.RulesOut != nil {
|
||||
sendf("format_out=%s", device.net.masqueradeOpts.RulesOut.Spec())
|
||||
if timing := device.timings.rekeyAfterTimeSec.Load(); !timing.IsZero() {
|
||||
sendf("rekey_after_time=%s", timing.ToString())
|
||||
}
|
||||
|
||||
sendf("header_compat=%s", strconv.FormatBool(device.net.framedOpts.HeaderCompat))
|
||||
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 {
|
||||
// Serialize peer state.
|
||||
@@ -183,7 +209,10 @@ func (device *Device) IpcGetOperation(w io.Writer) error {
|
||||
sendf("last_handshake_time_nsec=%d", nano)
|
||||
sendf("tx_bytes=%d", peer.txBytes.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 {
|
||||
sendf("allowed_ip=%s", prefix.String())
|
||||
@@ -212,6 +241,8 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
ipcDev := new(ipcSetDevice)
|
||||
ipcDev.fromDevice(device)
|
||||
peer := new(ipcSetPeer)
|
||||
deviceConfig := true
|
||||
|
||||
@@ -220,6 +251,10 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||
line := scanner.Text()
|
||||
if line == "" {
|
||||
// Blank line means terminate operation.
|
||||
err := ipcDev.mergeWithDevice(device)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to merge with device: %w", err)
|
||||
}
|
||||
peer.handlePostConfig()
|
||||
return nil
|
||||
}
|
||||
@@ -247,7 +282,7 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||
|
||||
var err error
|
||||
if deviceConfig {
|
||||
err = device.handleDeviceLine(key, value)
|
||||
err = device.handleDeviceLine(ipcDev, key, value)
|
||||
} else {
|
||||
err = device.handlePeerLine(peer, key, value)
|
||||
}
|
||||
@@ -255,6 +290,10 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = ipcDev.mergeWithDevice(device)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to merge with device: %w", err)
|
||||
}
|
||||
peer.handlePostConfig()
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
@@ -263,7 +302,7 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (device *Device) handleDeviceLine(key, value string) error {
|
||||
func (device *Device) handleDeviceLine(ipcDev *ipcSetDevice, key, value string) error {
|
||||
switch key {
|
||||
case "private_key":
|
||||
var sk NoisePrivateKey
|
||||
@@ -314,305 +353,195 @@ func (device *Device) handleDeviceLine(key, value string) error {
|
||||
device.RemoveAllPeers()
|
||||
|
||||
case "jc":
|
||||
jc, err := strconv.Atoi(value)
|
||||
jc, err := strconv.ParseUint(value, 10, 32)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jc: %w", err)
|
||||
}
|
||||
if jc <= 0 {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "jc must be a positive value")
|
||||
}
|
||||
device.log.Verbosef("UAPI: Updating junk count")
|
||||
device.net.preludeOpts.Jc = jc
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set junk count: %w", err)
|
||||
}
|
||||
device.log.Verbosef("UAPI: Updating junk count")
|
||||
device.junk.count.Store(uint32(jc))
|
||||
|
||||
case "jmin":
|
||||
jmin, err := strconv.Atoi(value)
|
||||
jmin, err := strconv.ParseUint(value, 10, 32)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jmin: %w", err)
|
||||
}
|
||||
if jmin <= 0 {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "jmin must be a positive value")
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating junk min")
|
||||
device.net.preludeOpts.Jmin = jmin
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set junk min: %w", err)
|
||||
}
|
||||
device.junk.min.Store(uint32(jmin))
|
||||
|
||||
case "jmax":
|
||||
jmax, err := strconv.Atoi(value)
|
||||
jmax, err := strconv.ParseUint(value, 10, 32)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jmax: %w", err)
|
||||
}
|
||||
if jmax <= 0 {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "jmax must be a positive value")
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating junk max")
|
||||
device.net.preludeOpts.Jmax = jmax
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set junk max: %w", err)
|
||||
}
|
||||
device.junk.max.Store(uint32(jmax))
|
||||
|
||||
case "s1":
|
||||
padding, err := strconv.Atoi(value)
|
||||
padding, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s1: %w", err)
|
||||
}
|
||||
if padding < 0 {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "s1 must be non-negative")
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating s1 padding")
|
||||
device.net.framedOpts.S1 = padding
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set s1: %w", err)
|
||||
}
|
||||
ipcDev.paddings.init = uint32(padding)
|
||||
|
||||
case "s2":
|
||||
padding, err := strconv.Atoi(value)
|
||||
padding, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s2: %w", err)
|
||||
}
|
||||
if padding < 0 {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "s2 must be non-negative")
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating s2 padding")
|
||||
device.net.framedOpts.S2 = padding
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set s2: %w", err)
|
||||
}
|
||||
ipcDev.paddings.response = uint32(padding)
|
||||
|
||||
case "s3":
|
||||
padding, err := strconv.Atoi(value)
|
||||
padding, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s3: %w", err)
|
||||
}
|
||||
if padding < 0 {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "s3 must be non-negative")
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating s3 padding")
|
||||
device.net.framedOpts.S3 = padding
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set s3: %w", err)
|
||||
}
|
||||
ipcDev.paddings.cookie = uint32(padding)
|
||||
|
||||
case "s4":
|
||||
padding, err := strconv.Atoi(value)
|
||||
padding, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s4: %w", err)
|
||||
}
|
||||
if padding < 0 {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "s4 must be non-negative")
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating s4 padding")
|
||||
device.net.framedOpts.S4 = padding
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set s4: %w", err)
|
||||
}
|
||||
ipcDev.paddings.transport = uint32(padding)
|
||||
|
||||
case "h1":
|
||||
header, err := conceal.NewRangedHeader(value)
|
||||
if err != nil {
|
||||
var rang UintRange
|
||||
if err := rang.FromString(value); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H1: %w", err)
|
||||
}
|
||||
|
||||
opts := device.net.framedOpts
|
||||
opts.H1 = header
|
||||
if opts.HasIntersections() {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "headers must not overlap")
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating h1 header")
|
||||
device.net.framedOpts.H1 = header
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set h1: %w", err)
|
||||
}
|
||||
ipcDev.headers.init = rang
|
||||
|
||||
case "h2":
|
||||
header, err := conceal.NewRangedHeader(value)
|
||||
if err != nil {
|
||||
var rang UintRange
|
||||
if err := rang.FromString(value); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H2: %w", err)
|
||||
}
|
||||
|
||||
opts := device.net.framedOpts
|
||||
opts.H2 = header
|
||||
if opts.HasIntersections() {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "headers must not overlap")
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating h2 header")
|
||||
device.net.framedOpts.H2 = header
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set h2: %w", err)
|
||||
}
|
||||
ipcDev.headers.response = rang
|
||||
|
||||
case "h3":
|
||||
header, err := conceal.NewRangedHeader(value)
|
||||
if err != nil {
|
||||
var rang UintRange
|
||||
if err := rang.FromString(value); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H3: %w", err)
|
||||
}
|
||||
|
||||
opts := device.net.framedOpts
|
||||
opts.H3 = header
|
||||
if opts.HasIntersections() {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "headers must not overlap")
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating h3 header")
|
||||
device.net.framedOpts.H3 = header
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set h3: %w", err)
|
||||
}
|
||||
ipcDev.headers.cookie = rang
|
||||
|
||||
case "h4":
|
||||
header, err := conceal.NewRangedHeader(value)
|
||||
if err != nil {
|
||||
var rang UintRange
|
||||
if err := rang.FromString(value); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H4: %w", err)
|
||||
}
|
||||
|
||||
opts := device.net.framedOpts
|
||||
opts.H4 = header
|
||||
if opts.HasIntersections() {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "headers must not overlap")
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating h4 header")
|
||||
device.net.framedOpts.H4 = header
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set h4: %w", err)
|
||||
}
|
||||
ipcDev.headers.transport = rang
|
||||
|
||||
case "i1":
|
||||
rules, err := conceal.ParseRules(value)
|
||||
chain, err := newObfChain(value)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse i1: %w", err)
|
||||
}
|
||||
|
||||
device.net.preludeOpts.RulesArr[0] = rules
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set i1: %w", err)
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I1: %w", err)
|
||||
}
|
||||
device.ipackets[0] = chain
|
||||
|
||||
case "i2":
|
||||
rules, err := conceal.ParseRules(value)
|
||||
chain, err := newObfChain(value)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse i2: %w", err)
|
||||
}
|
||||
|
||||
device.net.preludeOpts.RulesArr[1] = rules
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set i2: %w", err)
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I2: %w", err)
|
||||
}
|
||||
device.ipackets[1] = chain
|
||||
|
||||
case "i3":
|
||||
rules, err := conceal.ParseRules(value)
|
||||
chain, err := newObfChain(value)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse i3: %w", err)
|
||||
}
|
||||
|
||||
device.net.preludeOpts.RulesArr[2] = rules
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set i3: %w", err)
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I3: %w", err)
|
||||
}
|
||||
device.ipackets[2] = chain
|
||||
|
||||
case "i4":
|
||||
rules, err := conceal.ParseRules(value)
|
||||
chain, err := newObfChain(value)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse i4: %w", err)
|
||||
}
|
||||
|
||||
device.net.preludeOpts.RulesArr[3] = rules
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set i4: %w", err)
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I4: %w", err)
|
||||
}
|
||||
device.ipackets[3] = chain
|
||||
|
||||
case "i5":
|
||||
rules, err := conceal.ParseRules(value)
|
||||
chain, err := newObfChain(value)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse i5: %w", err)
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I5: %w", err)
|
||||
}
|
||||
device.ipackets[4] = chain
|
||||
|
||||
device.net.preludeOpts.RulesArr[4] = rules
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set i5: %w", err)
|
||||
}
|
||||
|
||||
case "network":
|
||||
device.net.Lock()
|
||||
device.net.network = value
|
||||
device.net.Unlock()
|
||||
|
||||
device.log.Verbosef("UAPI: Updating network")
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
// TODO: change IpcErrorPortInUse to something reasonable
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set network: %w", err)
|
||||
}
|
||||
|
||||
case "format_in":
|
||||
rules, err := conceal.ParseRules(value)
|
||||
case "header_protection_key":
|
||||
var key HeaderCipherKey
|
||||
err := key.FromHex(value)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse rules: %w", err)
|
||||
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 format_in")
|
||||
device.net.masqueradeOpts.RulesIn = rules
|
||||
device.log.Verbosef("UAPI: Updating content padding addition")
|
||||
device.contentPaddingAddition.Store(rang)
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
// TODO: change IpcErrorPortInUse to something reasonable
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set format_in: %w", err)
|
||||
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 "format_out":
|
||||
rules, err := conceal.ParseRules(value)
|
||||
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 rules: %w", err)
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse random trailers: %w", err)
|
||||
}
|
||||
device.log.Verbosef("UAPI: Updating random trailers")
|
||||
device.randomTrailers.Store(val)
|
||||
|
||||
device.log.Verbosef("UAPI: Updating format_out")
|
||||
device.net.masqueradeOpts.RulesOut = rules
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
// TODO: change IpcErrorPortInUse to something reasonable
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set format_out: %w", err)
|
||||
}
|
||||
|
||||
case "header_compat":
|
||||
compat, err := strconv.ParseBool(value)
|
||||
case "disable_cookies":
|
||||
val, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse header_compat: %w", err)
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating header_compat")
|
||||
device.net.framedOpts.HeaderCompat = compat
|
||||
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
// TODO: change IpcErrorPortInUse to something reasonable
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set header_compat: %w", err)
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse disable cookies: %w", err)
|
||||
}
|
||||
device.log.Verbosef("UAPI: Updating disable cookies")
|
||||
device.disableCookies.Store(val)
|
||||
|
||||
default:
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI device key: %v", key)
|
||||
@@ -734,19 +663,15 @@ func (device *Device) handlePeerLine(
|
||||
case "persistent_keepalive_interval":
|
||||
device.log.Verbosef("%v - UAPI: Updating persistent keepalive interval", peer.Peer)
|
||||
|
||||
secs, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil {
|
||||
return ipcErrorf(
|
||||
ipc.IpcErrorInvalid,
|
||||
"failed to set persistent keepalive interval: %w",
|
||||
err,
|
||||
)
|
||||
var rang UintRange
|
||||
if err := rang.FromString(value); err != nil {
|
||||
return ipcErrorf(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.
|
||||
peer.pkaOn = old == 0 && secs != 0
|
||||
peer.pkaOn = old.IsZero() && !rang.IsZero()
|
||||
|
||||
case "replace_allowed_ips":
|
||||
device.log.Verbosef("%v - UAPI: Removing all allowedips", peer.Peer)
|
||||
@@ -862,3 +787,91 @@ func (device *Device) IpcHandle(socket net.Conn) {
|
||||
buffered.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
type ipcSetDevice struct {
|
||||
headers struct {
|
||||
init UintRange
|
||||
response UintRange
|
||||
cookie UintRange
|
||||
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 {
|
||||
device.headerProtection.Lock()
|
||||
defer device.headerProtection.Unlock()
|
||||
|
||||
headers := []UintRange{d.headers.init, d.headers.response, d.headers.cookie, d.headers.transport}
|
||||
for i := 0; i < len(headers); i++ {
|
||||
for j := i + 1; j < len(headers); j++ {
|
||||
left := headers[i]
|
||||
right := headers[j]
|
||||
|
||||
if left.Overlap(right) {
|
||||
return errors.New("headers must not overlap")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
device.log.Verbosef("UAPI: Updating h1 padding")
|
||||
device.headers.init.Store(d.headers.init)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
module github.com/amnezia-vpn/amneziawg-go
|
||||
module github.com/amnezia-vpn/amneziawg-go/v3
|
||||
|
||||
go 1.24.4
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/Jigsaw-Code/outline-sdk v0.0.20
|
||||
github.com/Jigsaw-Code/outline-sdk/x v0.0.8
|
||||
github.com/goccy/go-yaml v1.17.1
|
||||
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/net v0.44.0
|
||||
golang.org/x/sys v0.36.0
|
||||
@@ -15,14 +15,21 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // 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/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/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/mod v0.28.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/tools v0.37.0 // indirect
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
|
||||
)
|
||||
@@ -1,35 +1,62 @@
|
||||
github.com/Jigsaw-Code/outline-sdk v0.0.20 h1:4ep7MK9lFmcyPIRIbn4xrP1VKdJNsqR6+iJEOHDKnNg=
|
||||
github.com/Jigsaw-Code/outline-sdk v0.0.20/go.mod h1:CFDKyGZA4zatKE4vMLe8TyQpZCyINOeRFbMAmYHxodw=
|
||||
github.com/Jigsaw-Code/outline-sdk/x v0.0.8 h1:fFHFXW7CKhRiegyNSdP25S/WIiVrRnMKysHDoO/N2Xg=
|
||||
github.com/Jigsaw-Code/outline-sdk/x v0.0.8/go.mod h1:zqSH7yEYIQ0pYOhrr4QnodATVb5X/eZXV4AjUp9zhvs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
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/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/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/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/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/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/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28=
|
||||
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/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
|
||||
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/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-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
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/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/mobile v0.0.0-20240520174638-fa72addaaa1b h1:WX7nnnLfCEXg+FmdYZPai2XuP3VqCP1HZVMST0n9DF0=
|
||||
golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b/go.mod h1:EiXZlVfUTaAyySFVJb9rsODuiO+WXu8HrUuySb7nYFw=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
@@ -41,10 +68,13 @@ golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
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/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/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
@@ -55,6 +85,10 @@ golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTF
|
||||
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/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
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/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc/namedpipe"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/ipc/namedpipe"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/rwcancel"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/rwcancel"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ package ipc
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc/namedpipe"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/ipc/namedpipe"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/ipc"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
|
||||
+4
-4
@@ -12,11 +12,11 @@ import (
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/ipc"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+4
-4
@@ -6,10 +6,10 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/Jigsaw-Code/outline-sdk/transport"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/netstack"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun/netstack"
|
||||
"golang.getoutline.org/sdk/transport"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
)
|
||||
|
||||
|
||||
+69
-24
@@ -9,34 +9,43 @@ import (
|
||||
"strconv"
|
||||
"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"
|
||||
"golang.getoutline.org/sdk/transport"
|
||||
"golang.getoutline.org/sdk/x/mobileproxy"
|
||||
"golang.getoutline.org/sdk/x/smart"
|
||||
)
|
||||
|
||||
type DeviceConfig struct {
|
||||
PrivateKey string `yaml:"private_key"`
|
||||
Address []string `yaml:"address"`
|
||||
Dns []string `yaml:"dns"`
|
||||
Mtu int `yaml:"mtu,omitempty"`
|
||||
Jc int `yaml:"jc,omitempty"`
|
||||
Jmin int `yaml:"jmin,omitempty"`
|
||||
Jmax int `yaml:"jmax,omitempty"`
|
||||
S1 int `yaml:"s1,omitempty"`
|
||||
S2 int `yaml:"s2,omitempty"`
|
||||
S3 int `yaml:"s3,omitempty"`
|
||||
S4 int `yaml:"s4,omitempty"`
|
||||
H1 string `yaml:"h1,omitempty"`
|
||||
H2 string `yaml:"h2,omitempty"`
|
||||
H3 string `yaml:"h3,omitempty"`
|
||||
H4 string `yaml:"h4,omitempty"`
|
||||
I1 string `yaml:"i1,omitempty"`
|
||||
I2 string `yaml:"i2,omitempty"`
|
||||
I3 string `yaml:"i3,omitempty"`
|
||||
I4 string `yaml:"i4,omitempty"`
|
||||
I5 string `yaml:"i5,omitempty"`
|
||||
Peers []PeerConfig `yaml:"peers,omitempty"`
|
||||
PrivateKey string `yaml:"private_key"`
|
||||
Address []string `yaml:"address"`
|
||||
Dns []string `yaml:"dns"`
|
||||
Mtu int `yaml:"mtu,omitempty"`
|
||||
Jc int `yaml:"jc,omitempty"`
|
||||
Jmin int `yaml:"jmin,omitempty"`
|
||||
Jmax int `yaml:"jmax,omitempty"`
|
||||
S1 int `yaml:"s1,omitempty"`
|
||||
S2 int `yaml:"s2,omitempty"`
|
||||
S3 int `yaml:"s3,omitempty"`
|
||||
S4 int `yaml:"s4,omitempty"`
|
||||
H1 string `yaml:"h1,omitempty"`
|
||||
H2 string `yaml:"h2,omitempty"`
|
||||
H3 string `yaml:"h3,omitempty"`
|
||||
H4 string `yaml:"h4,omitempty"`
|
||||
I1 string `yaml:"i1,omitempty"`
|
||||
I2 string `yaml:"i2,omitempty"`
|
||||
I3 string `yaml:"i3,omitempty"`
|
||||
I4 string `yaml:"i4,omitempty"`
|
||||
I5 string `yaml:"i5,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 {
|
||||
@@ -136,6 +145,42 @@ func genIpcString(cfg *DeviceConfig) (string, error) {
|
||||
b.WriteString("\ni5=")
|
||||
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 {
|
||||
publicKeyBytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
|
||||
|
||||
@@ -3,8 +3,8 @@ package outline_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Jigsaw-Code/outline-sdk/x/mobileproxy"
|
||||
awg "github.com/amnezia-vpn/amneziawg-go/outline"
|
||||
awg "github.com/amnezia-vpn/amneziawg-go/v3/outline"
|
||||
"golang.getoutline.org/sdk/x/mobileproxy"
|
||||
)
|
||||
|
||||
const cfg = `
|
||||
@@ -46,7 +46,7 @@ func Test_outlineIntegration(t *testing.T) {
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,370 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# This script exercises TCP outer transport with all active TCP conceal layers.
|
||||
# It is Linux-only. AWG-specific options are configured through raw UAPI, so the
|
||||
# local `wg` binary is only needed for key generation helpers.
|
||||
#
|
||||
# Usage:
|
||||
# ./netns_tcp_masquerade.sh <path to amneziawg-go> [shared-interface-fragment]
|
||||
#
|
||||
# The optional fragment file should contain only shared interface keys, e.g.
|
||||
# Network / FormatIn / FormatOut / I1 / I2 / H1-H4 / S1-S4.
|
||||
#
|
||||
# The script verifies:
|
||||
# - outer TCP stream contains the configured masquerade marker and decoys
|
||||
# - an inner TCP payload sent over the tunnel is decrypted correctly
|
||||
#
|
||||
# Optional environment overrides:
|
||||
# OUTER_PORT1=10000 OUTER_PORT2=20000
|
||||
# FORMAT_MAGIC_HEX=feed
|
||||
# I1_HEX=aabb I2_HEX=ccdd
|
||||
# INNER_PORT=1111
|
||||
|
||||
exec 3>&1
|
||||
|
||||
program="${1:?usage: $0 <path to amneziawg-go>}"
|
||||
shared_fragment="${2:-}"
|
||||
|
||||
: "${OUTER_PORT1:=10000}"
|
||||
: "${OUTER_PORT2:=20000}"
|
||||
: "${INNER_PORT:=1111}"
|
||||
: "${FORMAT_MAGIC_HEX:=feed}"
|
||||
: "${FORMAT_IN:=<b 0x${FORMAT_MAGIC_HEX}><dz be 2><d>}"
|
||||
: "${FORMAT_OUT:=<b 0x${FORMAT_MAGIC_HEX}><dz be 2><d>}"
|
||||
: "${I1_HEX:=aabb}"
|
||||
: "${I2_HEX:=ccdd}"
|
||||
: "${I1_RULES:=<b 0x${I1_HEX}>}"
|
||||
: "${I2_RULES:=<b 0x${I2_HEX}>}"
|
||||
: "${HEADER_COMPAT:=true}"
|
||||
: "${S1:=15}"
|
||||
: "${S2:=18}"
|
||||
: "${S3:=20}"
|
||||
: "${S4:=25}"
|
||||
: "${H1:=123456-123500}"
|
||||
: "${H2:=67543-67550}"
|
||||
: "${H3:=223344-223350}"
|
||||
: "${H4:=32345-32350}"
|
||||
|
||||
netns0="awg-tcp-test-$$-0"
|
||||
netns1="awg-tcp-test-$$-1"
|
||||
netns2="awg-tcp-test-$$-2"
|
||||
tmpdir="$(mktemp -d /tmp/awg-tcp-test.XXXXXX)"
|
||||
capture_file="$tmpdir/outer-tcpdump.txt"
|
||||
recv_file="$tmpdir/inner-tcp.recv"
|
||||
tcpdump_pid=""
|
||||
server_pid=""
|
||||
|
||||
pretty() { echo -e "\x1b[32m\x1b[1m[+] ${1:+NS$1: }${2}\x1b[0m" >&3; }
|
||||
pp() { pretty "" "$*"; "$@"; }
|
||||
maybe_exec() { if [[ $BASHPID -eq $$ ]]; then "$@"; else exec "$@"; fi; }
|
||||
n0() { pretty 0 "$*"; maybe_exec ip netns exec "$netns0" "$@"; }
|
||||
n1() { pretty 1 "$*"; maybe_exec ip netns exec "$netns1" "$@"; }
|
||||
n2() { pretty 2 "$*"; maybe_exec ip netns exec "$netns2" "$@"; }
|
||||
ip0() { pretty 0 "ip $*"; ip -n "$netns0" "$@"; }
|
||||
ip1() { pretty 1 "ip $*"; ip -n "$netns1" "$@"; }
|
||||
ip2() { pretty 2 "ip $*"; ip -n "$netns2" "$@"; }
|
||||
|
||||
require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
echo "missing required command: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
b64_to_hex() {
|
||||
printf '%s' "$1" | base64 -d | xxd -p -c 256
|
||||
}
|
||||
|
||||
wait_file() {
|
||||
local ns=$1
|
||||
local path=$2
|
||||
local label=$3
|
||||
pretty "${ns//*-}" "wait for $label"
|
||||
while ! ip netns exec "$ns" test -S "$path"; do
|
||||
sleep 0.1
|
||||
done
|
||||
}
|
||||
|
||||
wait_listener() {
|
||||
local ns=$1
|
||||
local port=$2
|
||||
local label=$3
|
||||
pretty "${ns//*-}" "wait for $label:$port"
|
||||
while ! ss -N "$ns" -tlH "sport = $port" | grep -q LISTEN; do
|
||||
sleep 0.1
|
||||
done
|
||||
}
|
||||
|
||||
render_shared_interface() {
|
||||
if [[ -n $shared_fragment ]]; then
|
||||
cat "$shared_fragment"
|
||||
return
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
Network = tcp
|
||||
HeaderCompat = ${HEADER_COMPAT}
|
||||
FormatIn = ${FORMAT_IN}
|
||||
FormatOut = ${FORMAT_OUT}
|
||||
I1 = ${I1_RULES}
|
||||
I2 = ${I2_RULES}
|
||||
S1 = ${S1}
|
||||
S2 = ${S2}
|
||||
S3 = ${S3}
|
||||
S4 = ${S4}
|
||||
H1 = ${H1}
|
||||
H2 = ${H2}
|
||||
H3 = ${H3}
|
||||
H4 = ${H4}
|
||||
EOF
|
||||
}
|
||||
|
||||
render_shared_uapi() {
|
||||
if [[ -z $shared_fragment ]]; then
|
||||
cat <<EOF
|
||||
network=tcp
|
||||
header_compat=${HEADER_COMPAT}
|
||||
format_in=${FORMAT_IN}
|
||||
format_out=${FORMAT_OUT}
|
||||
i1=${I1_RULES}
|
||||
i2=${I2_RULES}
|
||||
s1=${S1}
|
||||
s2=${S2}
|
||||
s3=${S3}
|
||||
s4=${S4}
|
||||
h1=${H1}
|
||||
h2=${H2}
|
||||
h3=${H3}
|
||||
h4=${H4}
|
||||
EOF
|
||||
return
|
||||
fi
|
||||
|
||||
awk '
|
||||
function trim(s) {
|
||||
gsub(/^[ \t]+|[ \t]+$/, "", s)
|
||||
return s
|
||||
}
|
||||
BEGIN {
|
||||
map["Network"] = "network"
|
||||
map["FormatIn"] = "format_in"
|
||||
map["FormatOut"] = "format_out"
|
||||
map["HeaderCompat"] = "header_compat"
|
||||
map["I1"] = "i1"
|
||||
map["I2"] = "i2"
|
||||
map["I3"] = "i3"
|
||||
map["I4"] = "i4"
|
||||
map["I5"] = "i5"
|
||||
map["Jc"] = "jc"
|
||||
map["Jmin"] = "jmin"
|
||||
map["Jmax"] = "jmax"
|
||||
map["S1"] = "s1"
|
||||
map["S2"] = "s2"
|
||||
map["S3"] = "s3"
|
||||
map["S4"] = "s4"
|
||||
map["H1"] = "h1"
|
||||
map["H2"] = "h2"
|
||||
map["H3"] = "h3"
|
||||
map["H4"] = "h4"
|
||||
}
|
||||
/^[ \t]*(#|;|$)/ { next }
|
||||
/^[ \t]*\[/ { next }
|
||||
{
|
||||
line = $0
|
||||
sub(/\r$/, "", line)
|
||||
split(line, parts, "=")
|
||||
if (length(parts) < 2) {
|
||||
next
|
||||
}
|
||||
key = trim(parts[1])
|
||||
sub(/^[^=]*=/, "", line)
|
||||
value = trim(line)
|
||||
if (key in map) {
|
||||
printf "%s=%s\n", map[key], value
|
||||
}
|
||||
}
|
||||
' "$shared_fragment"
|
||||
}
|
||||
|
||||
uapi_set() {
|
||||
local iface=$1
|
||||
local payload=$2
|
||||
local output
|
||||
output="$(
|
||||
printf 'set=1\n%s\n' "$payload" |
|
||||
ip netns exec "$netns0" socat - "UNIX-CONNECT:/var/run/amneziawg/${iface}.sock"
|
||||
)"
|
||||
if [[ "$output" != *"errno=0"* ]]; then
|
||||
echo "uapi set failed for $iface" >&2
|
||||
echo "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
render_base_uapi() {
|
||||
local private_key=$1
|
||||
local listen_port=$2
|
||||
local peer_pub=$3
|
||||
local psk=$4
|
||||
local allowed_ip=$5
|
||||
|
||||
cat <<EOF
|
||||
private_key=${private_key}
|
||||
listen_port=${listen_port}
|
||||
$(render_shared_uapi)
|
||||
replace_peers=true
|
||||
public_key=${peer_pub}
|
||||
preshared_key=${psk}
|
||||
protocol_version=1
|
||||
replace_allowed_ips=true
|
||||
allowed_ip=${allowed_ip}
|
||||
EOF
|
||||
}
|
||||
|
||||
render_endpoint_uapi() {
|
||||
local peer_pub=$1
|
||||
local endpoint=$2
|
||||
|
||||
cat <<EOF
|
||||
public_key=${peer_pub}
|
||||
endpoint=${endpoint}
|
||||
EOF
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
set +e
|
||||
exec 2>/dev/null
|
||||
|
||||
if [[ -n $tcpdump_pid ]]; then
|
||||
kill -INT "$tcpdump_pid"
|
||||
wait "$tcpdump_pid"
|
||||
fi
|
||||
|
||||
if [[ -n $server_pid ]]; then
|
||||
kill "$server_pid"
|
||||
wait "$server_pid"
|
||||
fi
|
||||
|
||||
ip1 link del dev wg1
|
||||
ip2 link del dev wg2
|
||||
|
||||
local to_kill
|
||||
to_kill="$(ip netns pids "$netns0") $(ip netns pids "$netns1") $(ip netns pids "$netns2")"
|
||||
[[ -n $to_kill ]] && kill $to_kill
|
||||
|
||||
pp ip netns del "$netns1"
|
||||
pp ip netns del "$netns2"
|
||||
pp ip netns del "$netns0"
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
require_cmd ip
|
||||
require_cmd ss
|
||||
require_cmd wg
|
||||
require_cmd base64
|
||||
require_cmd xxd
|
||||
require_cmd socat
|
||||
require_cmd tcpdump
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n $shared_fragment && ! -f $shared_fragment ]]; then
|
||||
echo "shared interface fragment not found: $shared_fragment" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ip netns del "$netns0" 2>/dev/null || true
|
||||
ip netns del "$netns1" 2>/dev/null || true
|
||||
ip netns del "$netns2" 2>/dev/null || true
|
||||
|
||||
pp ip netns add "$netns0"
|
||||
pp ip netns add "$netns1"
|
||||
pp ip netns add "$netns2"
|
||||
|
||||
ip0 link set up dev lo
|
||||
ip1 link set up dev lo
|
||||
ip2 link set up dev lo
|
||||
|
||||
n0 "$program" wg1
|
||||
wait_file "$netns0" /var/run/amneziawg/wg1.sock "UAPI socket wg1"
|
||||
ip0 link set wg1 netns "$netns1"
|
||||
|
||||
n0 "$program" wg2
|
||||
wait_file "$netns0" /var/run/amneziawg/wg2.sock "UAPI socket wg2"
|
||||
ip0 link set wg2 netns "$netns2"
|
||||
|
||||
key1_b64="$(pp wg genkey)"
|
||||
key2_b64="$(pp wg genkey)"
|
||||
pub1_b64="$(pp wg pubkey <<<"$key1_b64")"
|
||||
pub2_b64="$(pp wg pubkey <<<"$key2_b64")"
|
||||
psk_b64="$(pp wg genpsk)"
|
||||
|
||||
key1="$(b64_to_hex "$key1_b64")"
|
||||
key2="$(b64_to_hex "$key2_b64")"
|
||||
pub1="$(b64_to_hex "$pub1_b64")"
|
||||
pub2="$(b64_to_hex "$pub2_b64")"
|
||||
psk="$(b64_to_hex "$psk_b64")"
|
||||
|
||||
ip1 addr add 192.168.241.1/24 dev wg1
|
||||
ip2 addr add 192.168.241.2/24 dev wg2
|
||||
|
||||
cfg1="$tmpdir/wg1.conf"
|
||||
cfg2="$tmpdir/wg2.conf"
|
||||
cat >"$cfg1" <<EOF
|
||||
$(render_base_uapi "$key1" "$OUTER_PORT1" "$pub2" "$psk" "192.168.241.2/32")
|
||||
EOF
|
||||
cat >"$cfg2" <<EOF
|
||||
$(render_base_uapi "$key2" "$OUTER_PORT2" "$pub1" "$psk" "192.168.241.1/32")
|
||||
EOF
|
||||
|
||||
uapi_set wg1 "$(cat "$cfg1")"
|
||||
uapi_set wg2 "$(cat "$cfg2")"
|
||||
|
||||
ip1 link set up dev wg1
|
||||
ip2 link set up dev wg2
|
||||
|
||||
wait_listener "$netns0" "$OUTER_PORT1" "outer tcp listen"
|
||||
wait_listener "$netns0" "$OUTER_PORT2" "outer tcp listen"
|
||||
|
||||
# Endpoint parsing must happen after the interface is up, otherwise it is still
|
||||
# interpreted by the default UDP bind.
|
||||
uapi_set wg1 "$(render_endpoint_uapi "$pub2" "127.0.0.1:${OUTER_PORT2}")"
|
||||
uapi_set wg2 "$(render_endpoint_uapi "$pub1" "127.0.0.1:${OUTER_PORT1}")"
|
||||
|
||||
pretty 0 "capture outer tcp stream"
|
||||
ip netns exec "$netns0" tcpdump -i lo -nn -s 0 -l -XX "tcp port ${OUTER_PORT1} or tcp port ${OUTER_PORT2}" >"$capture_file" 2>&1 &
|
||||
tcpdump_pid=$!
|
||||
sleep 1
|
||||
|
||||
pretty 2 "start inner tcp receiver on 192.168.241.2:${INNER_PORT}"
|
||||
ip netns exec "$netns2" socat -T 10 -u "TCP-LISTEN:${INNER_PORT},bind=192.168.241.2,reuseaddr" - >"$recv_file" &
|
||||
server_pid=$!
|
||||
wait_listener "$netns2" "$INNER_PORT" "inner tcp listen"
|
||||
|
||||
payload="masked tcp over awg conceal $(date +%s)"
|
||||
pretty 1 "send inner tcp payload to 192.168.241.2:${INNER_PORT}"
|
||||
printf '%s' "$payload" | ip netns exec "$netns1" socat -T 10 -u - "TCP:192.168.241.2:${INNER_PORT},connect-timeout=10"
|
||||
wait "$server_pid"
|
||||
server_pid=""
|
||||
|
||||
recv_payload="$(cat "$recv_file")"
|
||||
[[ "$recv_payload" == "$payload" ]]
|
||||
pretty "" "inner tcp payload decrypted correctly"
|
||||
|
||||
sleep 1
|
||||
kill -INT "$tcpdump_pid"
|
||||
wait "$tcpdump_pid" || true
|
||||
tcpdump_pid=""
|
||||
|
||||
capture_lc="$(tr '[:upper:]' '[:lower:]' <"$capture_file")"
|
||||
[[ "$capture_lc" == *"$FORMAT_MAGIC_HEX"* ]]
|
||||
[[ "$capture_lc" == *"$I1_HEX"* ]]
|
||||
[[ "$capture_lc" == *"$I2_HEX"* ]]
|
||||
|
||||
pretty "" "outer tcp capture contains format magic ${FORMAT_MAGIC_HEX} and prelude decoys ${I1_HEX}/${I2_HEX}"
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/netstack"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun/netstack"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/netstack"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun/netstack"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -17,9 +17,9 @@ import (
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/netstack"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun/netstack"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun"
|
||||
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"io"
|
||||
"unsafe"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/rwcancel"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/rwcancel"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/v3/tun"
|
||||
)
|
||||
|
||||
func Ping(dst, src netip.Addr) []byte {
|
||||
|
||||
Reference in new issue
Block a user