| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 |
- 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
- }
|