mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 19:25:05 +03:00
5e6ffebdf2
The embedded interface's TCP forwarder admits 65535 connections in flight for all peers together, and a forwarded request holds its slot until the handshake ends or gVisor gives up on it. One client sending bare SYNs from fresh source ports therefore took every slot and left the other clients' connections unanswered. The UDP relay likewise opened a SOCKS5 association and a host UDP socket for every fresh tunnel source port with no per-peer bound. The forwarder now names the peer on the SYN, refuses a source no peer owns and caps one peer at 4096 connections; the UDP relay caps one peer at 1024 sessions and drops the datagrams that would need more.
163 lines
5.3 KiB
Go
163 lines
5.3 KiB
Go
package amneziawgnet
|
|
|
|
import (
|
|
"net"
|
|
"net/netip"
|
|
"testing"
|
|
"time"
|
|
|
|
awgtun "github.com/amnezia-vpn/amneziawg-go/v3/tun"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
|
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
)
|
|
|
|
func rawTCPSegment(src, dst netip.AddrPort, seq, ack uint32, flags header.TCPFlags) []byte {
|
|
srcIP, dstIP := tcpip.AddrFromSlice(src.Addr().AsSlice()), tcpip.AddrFromSlice(dst.Addr().AsSlice())
|
|
packet := make([]byte, header.IPv4MinimumSize+header.TCPMinimumSize)
|
|
ip := header.IPv4(packet)
|
|
ip.Encode(&header.IPv4Fields{
|
|
TotalLength: uint16(len(packet)),
|
|
TTL: 64,
|
|
Protocol: uint8(header.TCPProtocolNumber),
|
|
SrcAddr: srcIP,
|
|
DstAddr: dstIP,
|
|
})
|
|
ip.SetChecksum(^ip.CalculateChecksum())
|
|
tcp := header.TCP(packet[header.IPv4MinimumSize:])
|
|
tcp.Encode(&header.TCPFields{
|
|
SrcPort: src.Port(),
|
|
DstPort: dst.Port(),
|
|
SeqNum: seq,
|
|
AckNum: ack,
|
|
DataOffset: header.TCPMinimumSize,
|
|
Flags: flags,
|
|
WindowSize: 65535,
|
|
})
|
|
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, srcIP, dstIP, header.TCPMinimumSize)
|
|
tcp.SetChecksum(^tcp.CalculateChecksum(checksum.Checksum(nil, xsum)))
|
|
return packet
|
|
}
|
|
|
|
func injectFromPeer(t *testing.T, tun awgtun.Device, packet []byte) {
|
|
t.Helper()
|
|
if _, err := tun.Write([][]byte{packet}, 0); err != nil {
|
|
t.Fatalf("inject packet: %v", err)
|
|
}
|
|
}
|
|
|
|
// A peer's RST can beat the handler, and a reset endpoint has no RemoteAddr:
|
|
// deriving the source from it panicked the panel on a goroutine with no recover.
|
|
func TestTCPForwarderHandsOverSourceOfAnAlreadyResetConnection(t *testing.T) {
|
|
tun, gstack, err := createNetTUNWithStack([]netip.Addr{netip.MustParseAddr("10.9.0.1")}, 1420)
|
|
if err != nil {
|
|
t.Fatalf("createNetTUNWithStack: %v", err)
|
|
}
|
|
defer tun.Close()
|
|
|
|
peer := netip.MustParseAddrPort("10.9.0.2:40000")
|
|
target := netip.MustParseAddrPort("203.0.113.7:443")
|
|
|
|
type handedOver struct {
|
|
src, dest netip.AddrPort
|
|
remoteAddr net.Addr
|
|
}
|
|
accepted, resetSent := make(chan struct{}), make(chan struct{})
|
|
got := make(chan handedOver, 1)
|
|
AttachTCPForwarder(gstack, ownerBySource, func(conn *gonet.TCPConn, src, dest netip.AddrPort) {
|
|
defer conn.Close()
|
|
close(accepted)
|
|
<-resetSent
|
|
// Returns only once the stack has processed the RST.
|
|
_, _ = conn.Read(make([]byte, 1))
|
|
got <- handedOver{src: src, dest: dest, remoteAddr: conn.RemoteAddr()}
|
|
})
|
|
|
|
const peerISN = 1000
|
|
injectFromPeer(t, tun, rawTCPSegment(peer, target, peerISN, 0, header.TCPFlagSyn))
|
|
bufs, sizes := [][]byte{make([]byte, 2048)}, make([]int, 1)
|
|
if _, err := tun.Read(bufs, sizes, 0); err != nil {
|
|
t.Fatalf("read SYN-ACK: %v", err)
|
|
}
|
|
synAck := header.TCP(bufs[0][header.IPv4MinimumSize:sizes[0]])
|
|
if synAck.Flags() != header.TCPFlagSyn|header.TCPFlagAck {
|
|
t.Fatalf("first reply has flags %s, want SYN-ACK", synAck.Flags())
|
|
}
|
|
injectFromPeer(t, tun, rawTCPSegment(peer, target, peerISN+1, synAck.SequenceNumber()+1, header.TCPFlagAck))
|
|
select {
|
|
case <-accepted:
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("handler never ran")
|
|
}
|
|
injectFromPeer(t, tun, rawTCPSegment(peer, target, peerISN+1, 0, header.TCPFlagRst))
|
|
close(resetSent)
|
|
|
|
select {
|
|
case h := <-got:
|
|
if h.remoteAddr != nil {
|
|
t.Fatalf("RemoteAddr() = %v after the RST, want nil: the test no longer reaches the reset state", h.remoteAddr)
|
|
}
|
|
if h.src != peer {
|
|
t.Errorf("src = %s, want %s", h.src, peer)
|
|
}
|
|
if h.dest != target {
|
|
t.Errorf("dest = %s, want %s", h.dest, target)
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("handler never saw the reset")
|
|
}
|
|
}
|
|
|
|
// ownerBySource counts every tunnel source address as a peer of its own.
|
|
func ownerBySource(src netip.Addr) (string, bool) {
|
|
return src.String(), true
|
|
}
|
|
|
|
// The forwarder's in-flight limit is shared by the interface, so one peer's bare SYNs used
|
|
// to take every slot and leave the other clients' connections unanswered.
|
|
func TestTCPForwarderPeerCannotStarveOthers(t *testing.T) {
|
|
tun, gstack, err := createNetTUNWithStack([]netip.Addr{netip.MustParseAddr("10.8.1.1")}, 1420)
|
|
if err != nil {
|
|
t.Fatalf("createNetTUNWithStack: %v", err)
|
|
}
|
|
defer tun.Close()
|
|
|
|
flooderRange := netip.MustParsePrefix("10.8.2.0/24")
|
|
AttachTCPForwarder(gstack, func(src netip.Addr) (string, bool) {
|
|
if flooderRange.Contains(src) {
|
|
return "flooder", true
|
|
}
|
|
return "other", true
|
|
}, func(conn *gonet.TCPConn, _, _ netip.AddrPort) { conn.Close() })
|
|
|
|
other := netip.MustParseAddrPort("10.8.1.3:40000")
|
|
answered := make(chan struct{})
|
|
go func() {
|
|
bufs, sizes := [][]byte{make([]byte, 2048)}, []int{0}
|
|
for {
|
|
if _, err := tun.Read(bufs, sizes, 0); err != nil {
|
|
return
|
|
}
|
|
reply := header.IPv4(bufs[0][:sizes[0]])
|
|
seg := header.TCP(reply.Payload())
|
|
if reply.DestinationAddress() == tcpip.AddrFrom4(other.Addr().As4()) && seg.Flags() == header.TCPFlagSyn|header.TCPFlagAck {
|
|
close(answered)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
dst := netip.MustParseAddrPort("10.8.1.1:80")
|
|
for n := range 70000 {
|
|
src := netip.AddrPortFrom(netip.AddrFrom4([4]byte{10, 8, 2, byte(n)}), uint16(1024+n/256))
|
|
injectFromPeer(t, tun, rawTCPSegment(src, dst, 1000, 0, header.TCPFlagSyn))
|
|
}
|
|
injectFromPeer(t, tun, rawTCPSegment(other, dst, 1000, 0, header.TCPFlagSyn))
|
|
select {
|
|
case <-answered:
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatalf("no SYN-ACK for %s; dropped at the in-flight limit: %d", other, gstack.Stats().TCP.ForwardMaxInFlightDrop.Value())
|
|
}
|
|
}
|