mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-05 09:57:14 +00:00
58671533bb
New internal/amneziawgnet package: builds a real amneziawg-go Device over a gVisor netstack from an existing amneziawg.Instance, with a TCP/UDP forwarder that recovers each tunnel connection's real destination and a peer-identity index keyed by AllowedIPs. This is the foundation for migrating AmneziaWG off the kernel-module+TPROXY path (see the AmneziaWG-go vs kernel-module decision) -- nothing wires into live traffic yet, that's Phase 2 (relay into Xray's own SOCKS5 inbound). Covered by three real end-to-end tests: a genuine handshake + TCP forwarder + identity resolution, the same for UDP (including a reply routed back through the tunnel), and the manager's reconfigure-in-place vs. rebuild lifecycle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
101 lines
3.8 KiB
Go
101 lines
3.8 KiB
Go
package amneziawgnet
|
|
|
|
import (
|
|
"fmt"
|
|
"net/netip"
|
|
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
|
)
|
|
|
|
// UDPHandler is called for every UDP packet a tunnel client sends, with its
|
|
// source (the peer's tunnel-internal address) and its real,
|
|
// dynamically-arbitrary destination -- recovered the same way the TCP
|
|
// forwarder recovers its destination, from the packet's own transport
|
|
// endpoint ID, never from a preconfigured table. The handler owns all flow
|
|
// tracking and reply delivery (via WriteUDPReply): gVisor has no
|
|
// udp.NewForwarder the way it does for TCP, so unlike AttachTCPForwarder
|
|
// this can't just hand back a ready net.Conn.
|
|
type UDPHandler func(src, dst netip.AddrPort, payload []byte)
|
|
|
|
// AttachUDPHandler attaches a raw UDP handler to gstack, independently
|
|
// enabling the same promiscuous+spoofing mode AttachTCPForwarder needs --
|
|
// safe and idempotent to call regardless of whether AttachTCPForwarder was
|
|
// attached to the same stack first, or at all. Adapted from xtls/xray-core's
|
|
// proxy/wireguard/tun.go UDP path (MIT), which hand-tracks flows for the
|
|
// identical reason: gVisor doesn't provide a UDP forwarder.
|
|
func AttachUDPHandler(gstack *stack.Stack, handler UDPHandler) {
|
|
enablePromiscuousRouting(gstack)
|
|
|
|
gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
|
data := pkt.Clone().Data().AsRange().ToSlice()
|
|
src := netip.AddrPortFrom(addrFromTcpip(id.RemoteAddress), id.RemotePort)
|
|
dst := netip.AddrPortFrom(addrFromTcpip(id.LocalAddress), id.LocalPort)
|
|
handler(src, dst, data)
|
|
return true
|
|
})
|
|
}
|
|
|
|
// WriteUDPReply injects a UDP packet into gstack as if it arrived from
|
|
// `from` addressed to `to` -- i.e. a reply travelling back into the tunnel
|
|
// toward the client -- constructed by hand since gVisor exposes no
|
|
// connected-socket-style Write for an address the stack doesn't itself own.
|
|
func WriteUDPReply(gstack *stack.Stack, from, to netip.AddrPort, payload []byte) error {
|
|
udpLen := header.UDPMinimumSize + len(payload)
|
|
srcIP := tcpip.AddrFromSlice(from.Addr().AsSlice())
|
|
dstIP := tcpip.AddrFromSlice(to.Addr().AsSlice())
|
|
|
|
isIPv4 := from.Addr().Is4()
|
|
ipHdrSize := header.IPv6MinimumSize
|
|
ipProtocol := header.IPv6ProtocolNumber
|
|
if isIPv4 {
|
|
ipHdrSize = header.IPv4MinimumSize
|
|
ipProtocol = header.IPv4ProtocolNumber
|
|
}
|
|
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
ReserveHeaderBytes: ipHdrSize + header.UDPMinimumSize,
|
|
Payload: buffer.MakeWithData(payload),
|
|
})
|
|
defer pkt.DecRef()
|
|
|
|
udpHdr := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
|
|
udpHdr.Encode(&header.UDPFields{
|
|
SrcPort: from.Port(),
|
|
DstPort: to.Port(),
|
|
Length: uint16(udpLen),
|
|
})
|
|
xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, srcIP, dstIP, uint16(udpLen))
|
|
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum)))
|
|
|
|
if isIPv4 {
|
|
ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
|
|
ipHdr.Encode(&header.IPv4Fields{
|
|
TotalLength: uint16(header.IPv4MinimumSize + udpLen),
|
|
TTL: 64,
|
|
Protocol: uint8(header.UDPProtocolNumber),
|
|
SrcAddr: srcIP,
|
|
DstAddr: dstIP,
|
|
})
|
|
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
|
} else {
|
|
ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
|
|
ipHdr.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(udpLen),
|
|
TransportProtocol: header.UDPProtocolNumber,
|
|
HopLimit: 64,
|
|
SrcAddr: srcIP,
|
|
DstAddr: dstIP,
|
|
})
|
|
}
|
|
|
|
if tcpipErr := gstack.WriteRawPacket(1, ipProtocol, buffer.MakeWithView(pkt.ToView())); tcpipErr != nil {
|
|
return fmt.Errorf("amneziawgnet: WriteRawPacket: %s", tcpipErr)
|
|
}
|
|
return nil
|
|
}
|