@@ -14,7 +14,6 @@ import (
1414 "net"
1515 "os"
1616 "sync"
17- "sync/atomic"
1817 "syscall"
1918 "unsafe"
2019
@@ -50,14 +49,14 @@ func getServer(sd C.int) (*server, error) {
5049
5150// listeners tracks all the tsnet_listener objects allocated via tsnet_listen.
5251var listeners struct {
53- mu sync.Mutex
54- next C.int
55- m map [C.int ]* listener
52+ mu sync.Mutex
53+ m map [C.int ]* listener
5654}
5755
5856type listener struct {
5957 s * server
6058 ln net.Listener
59+ fd int // go side fd of socketpair sent to C
6160}
6261
6362// conns tracks all the pipe(2)s allocated via tsnet_dial.
@@ -180,46 +179,85 @@ func TsnetListen(sd C.int, network, addr *C.char, listenerOut *C.int) C.int {
180179 return s .recErr (err )
181180 }
182181
183- listeners .mu .Lock ()
184- if listeners .next == 0 {
185- // Arbitrary magic number that will hopefully help someone
186- // debug some type confusion one day.
187- listeners .next = 37 << 16 + 1
182+ // The tailscale_listener we return to C is one side of a socketpair(2).
183+ // We do this so we can proactively call ln.Accept in a goroutine and
184+ // feed an fd for the connection through the listener. This lets C use
185+ // epoll on the tailscale_listener to know if it should call
186+ // tailscale_accept, which avoids a blocking call on the far side.
187+ fds , err := syscall .Socketpair (syscall .AF_LOCAL , syscall .SOCK_STREAM , 0 )
188+ if err != nil {
189+ return s .recErr (err )
188190 }
191+ sp := fds [1 ]
192+ fdC := C .int (fds [0 ])
193+
194+ listeners .mu .Lock ()
189195 if listeners .m == nil {
190196 listeners .m = map [C.int ]* listener {}
191197 }
192- ld := listeners .next
193- listeners .next ++
194- listeners .m [ld ] = & listener {s : s , ln : ln }
198+ listeners .m [fdC ] = & listener {s : s , ln : ln , fd : sp }
195199 listeners .mu .Unlock ()
196200
197- * listenerOut = ld
198- return 0
199- }
200-
201- //export TsnetListenerClose
202- func TsnetListenerClose (ld C.int ) C.int {
203- listeners .mu .Lock ()
204- defer listeners .mu .Unlock ()
201+ cleanup := func () {
202+ // If fdC is closed on the C side, then we end up calling
203+ // into cleanup twice. Be careful to avoid syscall.Close
204+ // twice as the FD may have been reallocated.
205+ listeners .mu .Lock ()
206+ if tsLn , ok := listeners .m [fdC ]; ok && tsLn .ln == ln {
207+ delete (listeners .m , fdC )
208+ syscall .Close (sp )
209+ }
210+ listeners .mu .Unlock ()
205211
206- l := listeners .m [ld ]
207- if l == nil {
208- return C .EBADF
212+ ln .Close ()
209213 }
210- err := l .ln .Close ()
211- delete (listeners .m , ld )
214+ go func () {
215+ // fdC is never written to, so trying to read from sp blocks
216+ // until fdC is closed. We use this as a signal that C is
217+ // done with the listener, and we can tear it down.
218+ //
219+ // TODO: would using os.NewFile avoid a locked up thread?
220+ var buf [256 ]byte
221+ syscall .Read (sp , buf [:])
222+ cleanup ()
223+ }()
224+ go func () {
225+ defer cleanup ()
226+ for {
227+ netConn , err := ln .Accept ()
228+ if err != nil {
229+ return
230+ }
231+ var connFd C.int
232+ if err := newConn (s , netConn , & connFd ); err != nil {
233+ if s .s .Logf != nil {
234+ s .s .Logf ("libtailscale.accept: newConn: %v" , err )
235+ }
236+ netConn .Close ()
237+ continue
238+ }
239+ rights := syscall .UnixRights (int (connFd ))
240+ err = syscall .Sendmsg (sp , nil , rights , nil , 0 )
241+ if err != nil {
242+ // We handle sp being closed in the read goroutine above.
243+ if s .s .Logf != nil {
244+ s .s .Logf ("libtailscale.accept: sendmsg failed: %v" , err )
245+ }
246+ netConn .Close ()
247+ // fallthrough to close connFd, then continue Accept()ing
248+ }
249+ syscall .Close (int (connFd )) // now owned by recvmsg
250+ }
251+ }()
212252
213- if err != nil {
214- return l .s .recErr (err )
215- }
253+ * listenerOut = fdC
216254 return 0
217255}
218256
219- func newConn (s * server , netConn net.Conn , connOut * C.int ) C. int {
257+ func newConn (s * server , netConn net.Conn , connOut * C.int ) error {
220258 fds , err := syscall .Socketpair (syscall .AF_LOCAL , syscall .SOCK_STREAM , 0 )
221259 if err != nil {
222- return s . recErr ( err )
260+ return err
223261 }
224262 r := os .NewFile (uintptr (fds [1 ]), "socketpair-r" )
225263 c := & conn {s : s .s , c : netConn , r : r }
@@ -232,17 +270,21 @@ func newConn(s *server, netConn net.Conn, connOut *C.int) C.int {
232270 conns .m [fdC ] = c
233271 conns .mu .Unlock ()
234272
235- var doneOnce atomic.Bool
236273 connCleanup := func () {
237- if ! doneOnce .Swap (true ) {
274+ var inCleanup bool
275+ conns .mu .Lock ()
276+ if tsConn , ok := conns .m [fdC ]; ok && tsConn .c == netConn {
277+ delete (conns .m , fdC )
278+ inCleanup = true
279+ }
280+ conns .mu .Unlock ()
281+
282+ if ! inCleanup {
238283 return
239284 }
285+
240286 r .Close ()
241287 netConn .Close ()
242-
243- conns .mu .Lock ()
244- delete (conns .m , fdC )
245- conns .mu .Unlock ()
246288 }
247289 go func () {
248290 defer connCleanup ()
@@ -264,24 +306,7 @@ func newConn(s *server, netConn net.Conn, connOut *C.int) C.int {
264306 }()
265307
266308 * connOut = fdC
267- return 0
268- }
269-
270- //export TsnetAccept
271- func TsnetAccept (ld C.int , connOut * C.int ) C.int {
272- listeners .mu .Lock ()
273- l := listeners .m [ld ]
274- listeners .mu .Unlock ()
275-
276- if l == nil {
277- return C .EBADF
278- }
279-
280- netConn , err := l .ln .Accept ()
281- if err != nil {
282- return l .s .recErr (err )
283- }
284- return newConn (l .s , netConn , connOut )
309+ return nil
285310}
286311
287312//export TsnetDial
@@ -294,7 +319,10 @@ func TsnetDial(sd C.int, network, addr *C.char, connOut *C.int) C.int {
294319 if err != nil {
295320 return s .recErr (err )
296321 }
297- return newConn (s , netConn , connOut )
322+ if newConn (s , netConn , connOut ); err != nil {
323+ return s .recErr (err )
324+ }
325+ return 0
298326}
299327
300328//export TsnetSetDir
0 commit comments