zapret-kvn/runtime/amnezia/engine_test.go
loop-uh 38e951c129
Some checks failed
Windows project source guards / test (push) Failing after 1m11s
fix: preserve AWG relay TCP half-close and verify sustained traffic
2026-09-06 17:25:30 +03:00

180 lines
5.8 KiB
Go

package main
import (
"bytes"
"context"
"crypto/ecdh"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"net"
"net/netip"
"strconv"
"strings"
"testing"
"time"
"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 enginePair(t *testing.T, extensions string) (*netstack.Net, *netstack.Net, *device.Device) {
t.Helper()
a, err := ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
b, err := ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
makeCore := func(ip string, key, public []byte, endpoint string) (*device.Device, *netstack.Net) {
tun, stack, err := netstack.CreateNetTUN([]netip.Addr{netip.MustParseAddr(ip)}, nil, 1280)
if err != nil {
t.Fatal(err)
}
core := device.NewDevice(tun, conn.NewStdNetBind(), &device.Logger{Verbosef: func(string, ...any) {}, Errorf: func(string, ...any) {}})
t.Cleanup(core.Close)
ipc := "private_key=" + hex.EncodeToString(key) + "\n" + extensions + "public_key=" + hex.EncodeToString(public) + "\nallowed_ip=10.77.0.0/24\n"
if endpoint != "" {
ipc += "endpoint=" + endpoint + "\n"
}
if err := core.IpcSet(ipc); err != nil {
t.Fatal(err)
}
if err := core.Up(); err != nil {
t.Fatal(err)
}
return core, stack
}
serverCore, serverStack := makeCore("10.77.0.1", a.Bytes(), b.PublicKey().Bytes(), "")
stats, err := serverCore.IpcGet()
if err != nil {
t.Fatal(err)
}
var port string
for _, line := range strings.Split(stats, "\n") {
if value, ok := strings.CutPrefix(line, "listen_port="); ok {
port = value
}
}
if n, err := strconv.Atoi(port); err != nil || n == 0 {
t.Fatal("server did not bind UDP")
}
clientCore, clientStack := makeCore("10.77.0.2", b.Bytes(), a.PublicKey().Bytes(), net.JoinHostPort("127.0.0.1", port))
return serverStack, clientStack, clientCore
}
// All outer packets remain on loopback. Inner traffic crosses the real official
// Noise/UDP engine and userspace netstack, never an OS VPN or a user's server.
func TestOfficialEngineEncryptedTCPUDP(t *testing.T) {
for _, tc := range []struct{ name, extensions string }{
{"wireguard", ""},
{"awg2", "jc=2\njmin=4\njmax=8\ns1=27\ns2=21\ns3=27\ns4=15\nh1=100-110\nh2=200-210\nh3=300-310\nh4=400-410\ni1=<r 4>\n"},
{"awg31", "jc=2\njmin=4\njmax=8\ns1=27\ns2=21\ns3=27\ns4=15\nh1=100-110\nh2=200-210\nh3=300-310\nh4=400-410\ni1=<r 4>\ni2=<r 4>\ni3=<r 4>\ni4=<r 4>\ni5=<r 4>\nheader_protection_key=" + strings.Repeat("ab", 32) + "\ncontent_padding_addition=0-124\nrandom_trailers=true\ndisable_cookies=true\n"},
} {
t.Run(tc.name, func(t *testing.T) {
server, client, core := enginePair(t, tc.extensions)
tcpDst := netip.MustParseAddrPort("10.77.0.1:8443")
tcp, err := server.ListenTCPAddrPort(tcpDst)
if err != nil {
t.Fatal(err)
}
defer tcp.Close()
go func() {
connection, err := tcp.Accept()
if err == nil {
defer connection.Close()
io.Copy(connection, connection)
}
}()
udpDst := netip.MustParseAddrPort("10.77.0.1:5353")
udp, err := server.ListenUDPAddrPort(udpDst)
if err != nil {
t.Fatal(err)
}
defer udp.Close()
go func() {
data := make([]byte, 65535)
for {
n, addr, err := udp.ReadFrom(data)
if err != nil {
return
}
udp.WriteTo(data[:n], addr)
}
}()
c := testConfig()
s := relay{username: c.Username, password: c.Password, journal: newJournal(c),
dialTCP: func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
return client.DialContextTCPAddrPort(ctx, dst)
},
dialUDP: func(dst netip.AddrPort) (net.Conn, error) { return client.DialUDPAddrPort(netip.AddrPort{}, dst) },
}
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
done := make(chan error, 1)
go func() { done <- s.serve(ctx, l) }()
defer func() {
cancel()
l.Close()
select {
case <-done:
case <-time.After(3 * time.Second):
t.Error("relay did not stop")
}
}()
connection := login(t, l.Addr().String(), c)
defer connection.Close()
request(t, connection, 1, tcpDst)
body := bytes.Repeat([]byte("encrypted"), 16384)
writeDone := make(chan error, 1)
go func() { _, err := connection.Write(body); writeDone <- err }()
response := make([]byte, len(body))
if _, err := io.ReadFull(connection, response); err != nil {
t.Fatal("TCP", err)
}
if err := <-writeDone; err != nil || !bytes.Equal(body, response) {
t.Fatal("TCP mismatch", err)
}
halfCloseDst := netip.MustParseAddrPort("10.77.0.1:8444")
halfCloseServer, err := server.ListenTCPAddrPort(halfCloseDst)
if err != nil {
t.Fatal(err)
}
defer halfCloseServer.Close()
assertHalfCloseResponse(t, l.Addr().String(), c, halfCloseServer, halfCloseDst)
control := login(t, l.Addr().String(), c)
defer control.Close()
local, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
if err != nil {
t.Fatal(err)
}
defer local.Close()
relayAddr := request(t, control, 3, local.LocalAddr().(*net.UDPAddr).AddrPort())
for _, size := range []int{48, 1200, 20000} {
packet := append([]byte{0, 0, 0}, addressBytes(udpDst)...)
packet = append(packet, bytes.Repeat([]byte{77}, size)...)
local.SetDeadline(time.Now().Add(3 * time.Second))
local.WriteToUDPAddrPort(packet, relayAddr)
buf := make([]byte, 65535)
n, _, err := local.ReadFromUDPAddrPort(buf)
if err != nil || !bytes.Equal(buf[:n], packet) {
t.Fatal(fmt.Sprintf("UDP %d", size), err)
}
}
stats, err := core.IpcGet()
if err != nil {
t.Fatal(err)
}
if strings.Contains(stats, "last_handshake_time_sec=0\n") {
t.Fatal("no authenticated handshake")
}
})
}
}