@@ -23,17 +23,14 @@ import (
2323)
2424
2525type tun struct {
26- io. ReadWriteCloser
26+ f * os. File
2727 Device string
2828 vpnNetworks []netip.Prefix
2929 DefaultMTU int
3030 Routes atomic.Pointer [[]Route ]
3131 routeTree atomic.Pointer [bart.Table [routing.Gateways ]]
3232 linkAddr * netroute.LinkAddr
3333 l * slog.Logger
34-
35- // cache out buffer since we need to prepend 4 bytes for tun metadata
36- out []byte
3734}
3835
3936type ifReq struct {
@@ -124,11 +121,11 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*t
124121 }
125122
126123 t := & tun {
127- ReadWriteCloser : os .NewFile (uintptr (fd ), "" ),
128- Device : name ,
129- vpnNetworks : vpnNetworks ,
130- DefaultMTU : c .GetInt ("tun.mtu" , DefaultMTU ),
131- l : l ,
124+ f : os .NewFile (uintptr (fd ), "" ),
125+ Device : name ,
126+ vpnNetworks : vpnNetworks ,
127+ DefaultMTU : c .GetInt ("tun.mtu" , DefaultMTU ),
128+ l : l ,
132129 }
133130
134131 err = t .reload (c , true )
@@ -158,8 +155,8 @@ func newTunFromFd(_ *config.C, _ *slog.Logger, _ int, _ []netip.Prefix) (*tun, e
158155}
159156
160157func (t * tun ) Close () error {
161- if t .ReadWriteCloser != nil {
162- return t .ReadWriteCloser .Close ()
158+ if t .f != nil {
159+ return t .f .Close ()
163160 }
164161 return nil
165162}
@@ -502,42 +499,103 @@ func delRoute(prefix netip.Prefix, gateway netroute.Addr) error {
502499 return nil
503500}
504501
502+ // tunWritev and tunReadv are linkname'd to x/sys/unix's libc-routed writev/readv stubs so the
503+ // calls go through libSystem's pinned trampoline. A raw syscall.Syscall(SYS_WRITEV/SYS_READV, ...)
504+ // on darwin/arm64 emits an SVC #0x80 trap (see $GOROOT/src/syscall/asm_darwin_arm64.s), the path
505+ // Apple keeps warning they will eventually disallow. We pull the low-level stubs instead of calling
506+ // unix.Writev/unix.Readv because those take [][]byte and rebuild the []Iovec every call, which
507+ // heap-allocates the header; linkname'ing the stubs lets us hand them our own stack-allocated
508+ // iovecs. See golang/go#78049.
509+
510+ //go:linkname tunWritev golang.org/x/sys/unix.writev
511+ //go:noescape
512+ func tunWritev (fd int , iovecs []unix.Iovec ) (n int , err error )
513+
514+ //go:linkname tunReadv golang.org/x/sys/unix.readv
515+ //go:noescape
516+ func tunReadv (fd int , iovecs []unix.Iovec ) (n int , err error )
517+
518+ // Read pulls one IP packet off the utun device, scattering the 4 byte protocol header away from
519+ // the packet so the payload lands directly in to.
505520func (t * tun ) Read (to []byte ) (int , error ) {
506- buf := make ([ ]byte , len ( to ) + 4 )
521+ var head [ 4 ]byte
507522
508- n , err := t .ReadWriteCloser .Read (buf )
523+ rc , err := t .f .SyscallConn ()
524+ if err != nil {
525+ return 0 , err
526+ }
509527
510- copy (to , buf [4 :])
511- return n - 4 , err
528+ var n int
529+ var callErr error
530+ err = rc .Read (func (fd uintptr ) bool {
531+ iovecs := []unix.Iovec {
532+ {Base : & head [0 ], Len : 4 },
533+ {Base : & to [0 ], Len : uint64 (len (to ))},
534+ }
535+ n , callErr = tunReadv (int (fd ), iovecs )
536+ if errno , ok := callErr .(syscall.Errno ); ok && errno .Temporary () {
537+ return false
538+ }
539+ return true
540+ })
541+ if err != nil {
542+ return 0 , err
543+ }
544+ if callErr != nil {
545+ return 0 , callErr
546+ }
547+ if n < 4 {
548+ return 0 , nil
549+ }
550+ return n - 4 , nil
512551}
513552
514- // Write is only valid for single threaded use
553+ // Write pushes one IP packet onto the utun device.
515554func (t * tun ) Write (from []byte ) (int , error ) {
516- buf := t .out
517- if cap (buf ) < len (from )+ 4 {
518- buf = make ([]byte , len (from )+ 4 )
519- t .out = buf
520- }
521- buf = buf [:len (from )+ 4 ]
522-
523555 if len (from ) == 0 {
524556 return 0 , syscall .EIO
525557 }
526558
527- // Determine the IP Family for the NULL L2 Header
528559 ipVer := from [0 ] >> 4
529- if ipVer == 4 {
530- buf [3 ] = syscall .AF_INET
531- } else if ipVer == 6 {
532- buf [3 ] = syscall .AF_INET6
533- } else {
560+ var head [4 ]byte
561+ switch ipVer {
562+ case 4 :
563+ head [3 ] = syscall .AF_INET
564+ case 6 :
565+ head [3 ] = syscall .AF_INET6
566+ default :
534567 return 0 , fmt .Errorf ("unable to determine IP version from packet" )
535568 }
536569
537- copy (buf [4 :], from )
570+ // Grab rc as a local so the compiler can devirtualize the call and keep the closure on the stack.
571+ rc , err := t .f .SyscallConn ()
572+ if err != nil {
573+ return 0 , err
574+ }
575+
576+ var n int
577+ var callErr error
578+ err = rc .Write (func (fd uintptr ) bool {
579+ iovecs := []unix.Iovec {
580+ {Base : & head [0 ], Len : 4 },
581+ {Base : & from [0 ], Len : uint64 (len (from ))},
582+ }
583+ n , callErr = tunWritev (int (fd ), iovecs )
584+ // Type-assert to syscall.Errno so the EAGAIN/EWOULDBLOCK/EINTR check doesn't box the errno
585+ // constants into error interfaces on every call.
586+ if errno , ok := callErr .(syscall.Errno ); ok && errno .Temporary () {
587+ return false
588+ }
589+ return true
590+ })
591+ if err != nil {
592+ return 0 , err
593+ }
594+ if callErr != nil {
595+ return 0 , callErr
596+ }
538597
539- n , err := t .ReadWriteCloser .Write (buf )
540- return n - 4 , err
598+ return n - 4 , nil
541599}
542600
543601func (t * tun ) Networks () []netip.Prefix {
0 commit comments