github.com/yaling888/clash@v1.53.0/listener/tproxy/udp_linux.go (about)

     1  //go:build linux
     2  
     3  package tproxy
     4  
     5  import (
     6  	"fmt"
     7  	"net"
     8  	"net/netip"
     9  	"os"
    10  	"strconv"
    11  	"syscall"
    12  
    13  	"golang.org/x/sys/unix"
    14  )
    15  
    16  const (
    17  	IPV6_TRANSPARENT     = 0x4b
    18  	IPV6_RECVORIGDSTADDR = 0x4a
    19  )
    20  
    21  // dialUDP acts like net.DialUDP for transparent proxy.
    22  // It binds to a non-local address(`lAddr`).
    23  func dialUDP(network string, lAddr, rAddr netip.AddrPort) (uc *net.UDPConn, err error) {
    24  	rSockAddr, err := udpAddrToSockAddr(rAddr)
    25  	if err != nil {
    26  		return nil, err
    27  	}
    28  
    29  	lSockAddr, err := udpAddrToSockAddr(lAddr)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	fd, err := syscall.Socket(udpAddrFamily(network, lAddr, rAddr), syscall.SOCK_DGRAM, 0)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	defer func() {
    40  		if err != nil {
    41  			syscall.Close(fd)
    42  		}
    43  	}()
    44  
    45  	if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	if err = syscall.Bind(fd, lSockAddr); err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	if err = syscall.Connect(fd, rSockAddr); err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	fdFile := os.NewFile(uintptr(fd), fmt.Sprintf("net-udp-dial-%s", rAddr.String()))
    62  	defer fdFile.Close()
    63  
    64  	c, err := net.FileConn(fdFile)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	return c.(*net.UDPConn), nil
    70  }
    71  
    72  func udpAddrToSockAddr(addr netip.AddrPort) (syscall.Sockaddr, error) {
    73  	if addr.Addr().Is4() {
    74  		return &syscall.SockaddrInet4{Addr: addr.Addr().As4(), Port: int(addr.Port())}, nil
    75  	}
    76  
    77  	zoneID, err := strconv.ParseUint(addr.Addr().Zone(), 10, 32)
    78  	if err != nil {
    79  		zoneID = 0
    80  	}
    81  
    82  	return &syscall.SockaddrInet6{Addr: addr.Addr().As16(), Port: int(addr.Port()), ZoneId: uint32(zoneID)}, nil
    83  }
    84  
    85  func udpAddrFamily(net string, lAddr, rAddr netip.AddrPort) int {
    86  	switch net[len(net)-1] {
    87  	case '4':
    88  		return syscall.AF_INET
    89  	case '6':
    90  		return syscall.AF_INET6
    91  	}
    92  
    93  	if lAddr.Addr().Is4() && rAddr.Addr().Is4() {
    94  		return syscall.AF_INET
    95  	}
    96  	return syscall.AF_INET6
    97  }
    98  
    99  func getOrigDst(oob []byte) (netip.AddrPort, error) {
   100  	// oob contains socket control messages which we need to parse.
   101  	scms, err := unix.ParseSocketControlMessage(oob)
   102  	if err != nil {
   103  		return netip.AddrPort{}, fmt.Errorf("parse control message: %w", err)
   104  	}
   105  
   106  	// retrieve the destination address from the SCM.
   107  	sa, err := unix.ParseOrigDstAddr(&scms[0])
   108  	if err != nil {
   109  		return netip.AddrPort{}, fmt.Errorf("retrieve destination: %w", err)
   110  	}
   111  
   112  	// encode the destination address into a cmsg.
   113  	var rAddr netip.AddrPort
   114  	switch v := sa.(type) {
   115  	case *unix.SockaddrInet4:
   116  		rAddr = netip.AddrPortFrom(netip.AddrFrom4(v.Addr), uint16(v.Port))
   117  	case *unix.SockaddrInet6:
   118  		rAddr = netip.AddrPortFrom(netip.AddrFrom16(v.Addr), uint16(v.Port))
   119  	default:
   120  		return netip.AddrPort{}, fmt.Errorf("unsupported address type: %T", v)
   121  	}
   122  
   123  	return rAddr, nil
   124  }