github.com/chwjbn/xclash@v0.2.0/listener/tproxy/udp_linux.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package tproxy
     5  
     6  import (
     7  	"encoding/binary"
     8  	"errors"
     9  	"fmt"
    10  	"net"
    11  	"os"
    12  	"strconv"
    13  	"syscall"
    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 *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, 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  	if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
    40  		syscall.Close(fd)
    41  		return nil, err
    42  	}
    43  
    44  	if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
    45  		syscall.Close(fd)
    46  		return nil, err
    47  	}
    48  
    49  	if err = syscall.Bind(fd, lSockAddr); err != nil {
    50  		syscall.Close(fd)
    51  		return nil, err
    52  	}
    53  
    54  	if err = syscall.Connect(fd, rSockAddr); err != nil {
    55  		syscall.Close(fd)
    56  		return nil, err
    57  	}
    58  
    59  	fdFile := os.NewFile(uintptr(fd), fmt.Sprintf("net-udp-dial-%s", rAddr.String()))
    60  	defer fdFile.Close()
    61  
    62  	c, err := net.FileConn(fdFile)
    63  	if err != nil {
    64  		syscall.Close(fd)
    65  		return nil, err
    66  	}
    67  
    68  	return c.(*net.UDPConn), nil
    69  }
    70  
    71  func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) {
    72  	switch {
    73  	case addr.IP.To4() != nil:
    74  		ip := [4]byte{}
    75  		copy(ip[:], addr.IP.To4())
    76  
    77  		return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil
    78  
    79  	default:
    80  		ip := [16]byte{}
    81  		copy(ip[:], addr.IP.To16())
    82  
    83  		zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
    84  		if err != nil {
    85  			zoneID = 0
    86  		}
    87  
    88  		return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil
    89  	}
    90  }
    91  
    92  func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int {
    93  	switch net[len(net)-1] {
    94  	case '4':
    95  		return syscall.AF_INET
    96  	case '6':
    97  		return syscall.AF_INET6
    98  	}
    99  
   100  	if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) {
   101  		return syscall.AF_INET
   102  	}
   103  	return syscall.AF_INET6
   104  }
   105  
   106  func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
   107  	msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	for _, msg := range msgs {
   113  		if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
   114  			ip := net.IP(msg.Data[4:8])
   115  			port := binary.BigEndian.Uint16(msg.Data[2:4])
   116  			return &net.UDPAddr{IP: ip, Port: int(port)}, nil
   117  		} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
   118  			ip := net.IP(msg.Data[8:24])
   119  			port := binary.BigEndian.Uint16(msg.Data[2:4])
   120  			return &net.UDPAddr{IP: ip, Port: int(port)}, nil
   121  		}
   122  	}
   123  
   124  	return nil, errors.New("cannot find origDst")
   125  }