mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-15 10:25:00 +03:00
f55938ba0a
* fix(amneziawg): respect tunnel MTU when writing UDP replies Send replies through gVisor FindRoute/Route.WritePacket instead of WriteRawPacket so the network endpoints apply the tunnel MTU and fragment oversized IPv4/IPv6 datagrams. Preserve UDP checksums including the zero-complement IPv6 case and validate lengths before narrowing to uint16. Regression tests inspect and reassemble actual channel-backed TUN output at and above the MTU, verify checksums and exercise concurrent sources. * docs(review): put the tally before Design and name who can store a trigger
100 lines
3.9 KiB
Go
100 lines
3.9 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 {
|
|
// ToSlice already returns an owned copy, so cloning pkt here would only
|
|
// strand a pooled packet buffer and its chunks on every datagram.
|
|
data := pkt.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 sends replies through gVisor's network route,
|
|
// which applies the NIC MTU and fragments locally generated IP datagrams.
|
|
func WriteUDPReply(gstack *stack.Stack, from, to netip.AddrPort, payload []byte) error {
|
|
if !from.Addr().IsValid() || !to.Addr().IsValid() || from.Addr().Zone() != "" || to.Addr().Zone() != "" || from.Addr().Is4() != to.Addr().Is4() {
|
|
return fmt.Errorf("amneziawgnet: invalid UDP reply addresses: %s -> %s", from, to)
|
|
}
|
|
udpLen := header.UDPMinimumSize + len(payload)
|
|
if udpLen > 65535 {
|
|
return fmt.Errorf("amneziawgnet: UDP reply too large: %d bytes", udpLen)
|
|
}
|
|
srcIP := tcpip.AddrFromSlice(from.Addr().AsSlice())
|
|
dstIP := tcpip.AddrFromSlice(to.Addr().AsSlice())
|
|
|
|
ipHdrSize := header.IPv6MinimumSize
|
|
ipProtocol := header.IPv6ProtocolNumber
|
|
if from.Addr().Is4() {
|
|
ipHdrSize = header.IPv4MinimumSize
|
|
ipProtocol = header.IPv4ProtocolNumber
|
|
if ipHdrSize+udpLen > 65535 {
|
|
return fmt.Errorf("amneziawgnet: IPv4 reply too large: %d bytes", ipHdrSize+udpLen)
|
|
}
|
|
}
|
|
route, tcpipErr := gstack.FindRoute(1, srcIP, dstIP, ipProtocol, false)
|
|
if tcpipErr != nil {
|
|
return fmt.Errorf("amneziawgnet: FindRoute: %s", tcpipErr)
|
|
}
|
|
defer route.Release()
|
|
|
|
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))
|
|
udpChecksum := ^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum))
|
|
if udpChecksum == 0 {
|
|
udpChecksum = 0xffff
|
|
}
|
|
udpHdr.SetChecksum(udpChecksum)
|
|
|
|
if tcpipErr := route.WritePacket(stack.NetworkHeaderParams{
|
|
Protocol: header.UDPProtocolNumber,
|
|
TTL: 64,
|
|
}, pkt); tcpipErr != nil {
|
|
return fmt.Errorf("amneziawgnet: WritePacket: %s", tcpipErr)
|
|
}
|
|
return nil
|
|
}
|