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 }