github.com/apernet/sing-tun@v0.2.6-0.20240323130332-b9f6511036ad/tun_darwin.go (about) 1 package tun 2 3 import ( 4 "fmt" 5 "net" 6 "net/netip" 7 "os" 8 "syscall" 9 "unsafe" 10 11 "github.com/sagernet/sing/common" 12 "github.com/sagernet/sing/common/buf" 13 "github.com/sagernet/sing/common/bufio" 14 E "github.com/sagernet/sing/common/exceptions" 15 N "github.com/sagernet/sing/common/network" 16 "github.com/sagernet/sing/common/shell" 17 18 "golang.org/x/net/route" 19 "golang.org/x/sys/unix" 20 ) 21 22 const PacketOffset = 4 23 24 type NativeTun struct { 25 tunFile *os.File 26 tunWriter N.VectorisedWriter 27 mtu uint32 28 inet4Address [4]byte 29 inet6Address [16]byte 30 } 31 32 func New(options Options) (Tun, error) { 33 var tunFd int 34 if options.FileDescriptor == 0 { 35 ifIndex := -1 36 _, err := fmt.Sscanf(options.Name, "utun%d", &ifIndex) 37 if err != nil { 38 return nil, E.New("bad tun name: ", options.Name) 39 } 40 41 tunFd, err = unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2) 42 if err != nil { 43 return nil, err 44 } 45 46 err = configure(tunFd, ifIndex, options.Name, options) 47 if err != nil { 48 unix.Close(tunFd) 49 return nil, err 50 } 51 } else { 52 tunFd = options.FileDescriptor 53 } 54 55 nativeTun := &NativeTun{ 56 tunFile: os.NewFile(uintptr(tunFd), "utun"), 57 mtu: options.MTU, 58 } 59 if len(options.Inet4Address) > 0 { 60 nativeTun.inet4Address = options.Inet4Address[0].Addr().As4() 61 } 62 if len(options.Inet6Address) > 0 { 63 nativeTun.inet6Address = options.Inet6Address[0].Addr().As16() 64 } 65 var ok bool 66 nativeTun.tunWriter, ok = bufio.CreateVectorisedWriter(nativeTun.tunFile) 67 if !ok { 68 panic("create vectorised writer") 69 } 70 return nativeTun, nil 71 } 72 73 func (t *NativeTun) Read(p []byte) (n int, err error) { 74 return t.tunFile.Read(p) 75 } 76 77 func (t *NativeTun) Write(p []byte) (n int, err error) { 78 return t.tunFile.Write(p) 79 } 80 81 var ( 82 packetHeader4 = [4]byte{0x00, 0x00, 0x00, unix.AF_INET} 83 packetHeader6 = [4]byte{0x00, 0x00, 0x00, unix.AF_INET6} 84 ) 85 86 func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error { 87 var packetHeader []byte 88 if buffers[0].Byte(0)>>4 == 4 { 89 packetHeader = packetHeader4[:] 90 } else { 91 packetHeader = packetHeader6[:] 92 } 93 return t.tunWriter.WriteVectorised(append([]*buf.Buffer{buf.As(packetHeader)}, buffers...)) 94 } 95 96 func (t *NativeTun) Close() error { 97 flushDNSCache() 98 return t.tunFile.Close() 99 } 100 101 const utunControlName = "com.apple.net.utun_control" 102 103 const ( 104 SIOCAIFADDR_IN6 = 2155899162 // netinet6/in6_var.h 105 IN6_IFF_NODAD = 0x0020 // netinet6/in6_var.h 106 IN6_IFF_SECURED = 0x0400 // netinet6/in6_var.h 107 ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h 108 ) 109 110 type ifAliasReq struct { 111 Name [unix.IFNAMSIZ]byte 112 Addr unix.RawSockaddrInet4 113 Dstaddr unix.RawSockaddrInet4 114 Mask unix.RawSockaddrInet4 115 } 116 117 type ifAliasReq6 struct { 118 Name [16]byte 119 Addr unix.RawSockaddrInet6 120 Dstaddr unix.RawSockaddrInet6 121 Mask unix.RawSockaddrInet6 122 Flags uint32 123 Lifetime addrLifetime6 124 } 125 126 type addrLifetime6 struct { 127 Expire float64 128 Preferred float64 129 Vltime uint32 130 Pltime uint32 131 } 132 133 func configure(tunFd int, ifIndex int, name string, options Options) error { 134 ctlInfo := &unix.CtlInfo{} 135 copy(ctlInfo.Name[:], utunControlName) 136 err := unix.IoctlCtlInfo(tunFd, ctlInfo) 137 if err != nil { 138 return os.NewSyscallError("IoctlCtlInfo", err) 139 } 140 141 err = unix.Connect(tunFd, &unix.SockaddrCtl{ 142 ID: ctlInfo.Id, 143 Unit: uint32(ifIndex) + 1, 144 }) 145 if err != nil { 146 return os.NewSyscallError("Connect", err) 147 } 148 149 err = unix.SetNonblock(tunFd, true) 150 if err != nil { 151 return os.NewSyscallError("SetNonblock", err) 152 } 153 154 err = useSocket(unix.AF_INET, unix.SOCK_DGRAM, 0, func(socketFd int) error { 155 var ifr unix.IfreqMTU 156 copy(ifr.Name[:], name) 157 ifr.MTU = int32(options.MTU) 158 return unix.IoctlSetIfreqMTU(socketFd, &ifr) 159 }) 160 if err != nil { 161 return os.NewSyscallError("IoctlSetIfreqMTU", err) 162 } 163 if len(options.Inet4Address) > 0 { 164 for _, address := range options.Inet4Address { 165 ifReq := ifAliasReq{ 166 Addr: unix.RawSockaddrInet4{ 167 Len: unix.SizeofSockaddrInet4, 168 Family: unix.AF_INET, 169 Addr: address.Addr().As4(), 170 }, 171 Dstaddr: unix.RawSockaddrInet4{ 172 Len: unix.SizeofSockaddrInet4, 173 Family: unix.AF_INET, 174 Addr: address.Addr().As4(), 175 }, 176 Mask: unix.RawSockaddrInet4{ 177 Len: unix.SizeofSockaddrInet4, 178 Family: unix.AF_INET, 179 Addr: netip.MustParseAddr(net.IP(net.CIDRMask(address.Bits(), 32)).String()).As4(), 180 }, 181 } 182 copy(ifReq.Name[:], name) 183 err = useSocket(unix.AF_INET, unix.SOCK_DGRAM, 0, func(socketFd int) error { 184 if _, _, errno := unix.Syscall( 185 syscall.SYS_IOCTL, 186 uintptr(socketFd), 187 uintptr(unix.SIOCAIFADDR), 188 uintptr(unsafe.Pointer(&ifReq)), 189 ); errno != 0 { 190 return os.NewSyscallError("SIOCAIFADDR", errno) 191 } 192 return nil 193 }) 194 if err != nil { 195 return err 196 } 197 } 198 } 199 if len(options.Inet6Address) > 0 { 200 for _, address := range options.Inet6Address { 201 ifReq6 := ifAliasReq6{ 202 Addr: unix.RawSockaddrInet6{ 203 Len: unix.SizeofSockaddrInet6, 204 Family: unix.AF_INET6, 205 Addr: address.Addr().As16(), 206 }, 207 Mask: unix.RawSockaddrInet6{ 208 Len: unix.SizeofSockaddrInet6, 209 Family: unix.AF_INET6, 210 Addr: netip.MustParseAddr(net.IP(net.CIDRMask(address.Bits(), 128)).String()).As16(), 211 }, 212 Flags: IN6_IFF_NODAD | IN6_IFF_SECURED, 213 Lifetime: addrLifetime6{ 214 Vltime: ND6_INFINITE_LIFETIME, 215 Pltime: ND6_INFINITE_LIFETIME, 216 }, 217 } 218 if address.Bits() == 128 { 219 ifReq6.Dstaddr = unix.RawSockaddrInet6{ 220 Len: unix.SizeofSockaddrInet6, 221 Family: unix.AF_INET6, 222 Addr: address.Addr().Next().As16(), 223 } 224 } 225 copy(ifReq6.Name[:], name) 226 err = useSocket(unix.AF_INET6, unix.SOCK_DGRAM, 0, func(socketFd int) error { 227 if _, _, errno := unix.Syscall( 228 syscall.SYS_IOCTL, 229 uintptr(socketFd), 230 uintptr(SIOCAIFADDR_IN6), 231 uintptr(unsafe.Pointer(&ifReq6)), 232 ); errno != 0 { 233 return os.NewSyscallError("SIOCAIFADDR_IN6", errno) 234 } 235 return nil 236 }) 237 if err != nil { 238 return err 239 } 240 } 241 } 242 if options.AutoRoute { 243 var routeRanges []netip.Prefix 244 routeRanges, err = options.BuildAutoRouteRanges(false) 245 for _, routeRange := range routeRanges { 246 if routeRange.Addr().Is4() { 247 err = addRoute(routeRange, options.Inet4Address[0].Addr()) 248 } else { 249 err = addRoute(routeRange, options.Inet6Address[0].Addr()) 250 } 251 if err != nil { 252 return E.Cause(err, "add route: ", routeRange) 253 } 254 } 255 flushDNSCache() 256 } 257 return nil 258 } 259 260 func useSocket(domain, typ, proto int, block func(socketFd int) error) error { 261 socketFd, err := unix.Socket(domain, typ, proto) 262 if err != nil { 263 return err 264 } 265 defer unix.Close(socketFd) 266 return block(socketFd) 267 } 268 269 func addRoute(destination netip.Prefix, gateway netip.Addr) error { 270 routeMessage := route.RouteMessage{ 271 Type: unix.RTM_ADD, 272 Flags: unix.RTF_UP | unix.RTF_STATIC | unix.RTF_GATEWAY, 273 Version: unix.RTM_VERSION, 274 Seq: 1, 275 } 276 if gateway.Is4() { 277 routeMessage.Addrs = []route.Addr{ 278 syscall.RTAX_DST: &route.Inet4Addr{IP: destination.Addr().As4()}, 279 syscall.RTAX_NETMASK: &route.Inet4Addr{IP: netip.MustParseAddr(net.IP(net.CIDRMask(destination.Bits(), 32)).String()).As4()}, 280 syscall.RTAX_GATEWAY: &route.Inet4Addr{IP: gateway.As4()}, 281 } 282 } else { 283 routeMessage.Addrs = []route.Addr{ 284 syscall.RTAX_DST: &route.Inet6Addr{IP: destination.Addr().As16()}, 285 syscall.RTAX_NETMASK: &route.Inet6Addr{IP: netip.MustParseAddr(net.IP(net.CIDRMask(destination.Bits(), 128)).String()).As16()}, 286 syscall.RTAX_GATEWAY: &route.Inet6Addr{IP: gateway.As16()}, 287 } 288 } 289 request, err := routeMessage.Marshal() 290 if err != nil { 291 return err 292 } 293 return useSocket(unix.AF_ROUTE, unix.SOCK_RAW, 0, func(socketFd int) error { 294 return common.Error(unix.Write(socketFd, request)) 295 }) 296 } 297 298 func flushDNSCache() { 299 shell.Exec("dscacheutil", "-flushcache").Start() 300 }