From ddc444bb22c70e13666c3378773ebf3652b2510e Mon Sep 17 00:00:00 2001 From: James Tucker Date: Wed, 16 Aug 2023 14:13:35 -0700 Subject: [PATCH] .,ruby: enable accept to be called as TsnetAccept & fix gem This fixes calling accept on the updates listen socket strategy by embedding the accept behavior into the Go exported library, avoiding the mandatory requirement for building a separate object from the C library. --- ruby/lib/tailscale.rb | 14 ++++++----- ruby/test/tailscale/test_tailscale.rb | 26 ++++++++++++++++++- tailscale.c | 23 ++--------------- tailscale.go | 36 +++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/ruby/lib/tailscale.rb b/ruby/lib/tailscale.rb index eab3aa1..4921b2b 100644 --- a/ruby/lib/tailscale.rb +++ b/ruby/lib/tailscale.rb @@ -39,8 +39,7 @@ module Libtailscale attach_function :TsnetSetLogFD, [:int, :int], :int attach_function :TsnetDial, [:int, :string, :string, :pointer], :int, blocking: true attach_function :TsnetListen, [:int, :string, :string, :pointer], :int - attach_function :close, [:int], :int - attach_function :tailscale_accept, [:int, :pointer], :int, blocking: true + attach_function :TsnetAccept, [:int, :pointer], :int, blocking: true attach_function :TsnetErrmsg, [:int, :pointer, :size_t], :int attach_function :TsnetLoopback, [:int, :pointer, :size_t, :pointer, :pointer], :int end @@ -86,15 +85,19 @@ def initialize(ts, listener) # write. def accept @ts.assert_open + lio = IO.for_fd(@listener) + until IO.select([lio]).first.any? + @ts.assert_open + end conn = FFI::MemoryPointer.new(:int) - Error.check(@ts, Libtailscale::tailscale_accept(@listener, conn)) + Error.check(@ts, Libtailscale::TsnetAccept(@listener, conn)) IO::new(conn.read_int) end # Close the listener. def close @ts.assert_open - Error.check(@ts, Libtailscale::close(@listener)) + IO.for_fd(@listener).close end end @@ -229,8 +232,7 @@ def set_log_fd(log_fd) end # Dial a network address. +network+ is one of "tcp" or "udp". +addr+ is the - # remote address to connect to. This method blocks until the connection is - # established. + # remote address to connect to. This method blocks until the connection is established. def dial(network, addr) assert_open conn = FFI::MemoryPointer.new(:int) diff --git a/ruby/test/tailscale/test_tailscale.rb b/ruby/test/tailscale/test_tailscale.rb index 4109b72..94ac5a1 100644 --- a/ruby/test/tailscale/test_tailscale.rb +++ b/ruby/test/tailscale/test_tailscale.rb @@ -23,6 +23,7 @@ def test_that_it_has_a_version_number def test_listen_sorta_works ts = newts ts.up + wait_status_running ts s = ts.listen("tcp", ":1999") s.close ts.close @@ -31,18 +32,41 @@ def test_listen_sorta_works def test_dial_sorta_works ts = newts ts.up + wait_status_running ts c = ts.dial("udp", "100.100.100.100:53") c.close ts.close end + def test_listen_accept_dial_close + ts = newts + ts.up + wait_status_running ts + hn = ts.local_api.status["Self"]["TailscaleIPs"][0] + s = ts.listen "tcp", "#{hn}:1999" + c = ts.dial "tcp", "#{hn}:1999" + ss = s.accept + c.write "hello" + assert_equal "hello", ss.read(5) + ss.write "world" + assert_equal "world", c.read(5) + ss.close + c.close + ts.close + end + + def wait_status_running ts + while ts.local_api.status["BackendState"] != "Running" + sleep 0.01 + end + end + def newts t = Tailscale::new unless ENV["VERBOSE"] logfd = IO.sysopen("/dev/null", "w+") t.set_log_fd(logfd) end - t.set_ephemeral(1) t.set_dir(@tmpdir) t.set_control_url($testcontrol_url) diff --git a/tailscale.c b/tailscale.c index 8cbbb22..be18085 100644 --- a/tailscale.c +++ b/tailscale.c @@ -22,6 +22,7 @@ extern int TsnetSetLogFD(int sd, int fd); extern int TsnetGetIps(int sd, char *buf, size_t buflen); extern int TsnetGetRemoteAddr(int listener, int conn, char *buf, size_t buflen); extern int TsnetListen(int sd, char* net, char* addr, int* listenerOut); +extern int TsnetAccept(int ld, int* connOut); extern int TsnetLoopback(int sd, char* addrOut, size_t addrLen, char* proxyOut, char* localOut); extern int TsnetEnableFunnelToLocalhostPlaintextHttp1(int sd, int localhostPort); @@ -50,27 +51,7 @@ int tailscale_listen(tailscale sd, const char* network, const char* addr, tailsc } int tailscale_accept(tailscale_listener ld, tailscale_conn* conn_out) { - struct msghdr msg = {0}; - - char mbuf[256]; - struct iovec io = { .iov_base = mbuf, .iov_len = sizeof(mbuf) }; - msg.msg_iov = &io; - msg.msg_iovlen = 1; - - char cbuf[256]; - msg.msg_control = cbuf; - msg.msg_controllen = sizeof(cbuf); - - if (recvmsg(ld, &msg, 0) == -1) { - return -1; - } - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - unsigned char* data = CMSG_DATA(cmsg); - - int fd = *(int*)data; - *conn_out = fd; - return 0; + return TsnetAccept(ld, (int*)conn_out); } int tailscale_getremoteaddr(tailscale_listener l, tailscale_conn conn, char* buf, size_t buflen) { diff --git a/tailscale.go b/tailscale.go index 605506d..d276fa3 100644 --- a/tailscale.go +++ b/tailscale.go @@ -20,6 +20,7 @@ import ( "syscall" "unsafe" + "golang.org/x/sys/unix" "tailscale.com/hostinfo" "tailscale.com/ipn" "tailscale.com/tsnet" @@ -292,6 +293,41 @@ func TsnetListen(sd C.int, network, addr *C.char, listenerOut *C.int) C.int { return 0 } +//export TsnetAccept +func TsnetAccept(listenerFd C.int, connOut *C.int) C.int { + listeners.mu.Lock() + ln := listeners.m[listenerFd] + listeners.mu.Unlock() + + if ln == nil { + return C.EBADF + } + + buf := make([]byte, unix.CmsgLen(int(unsafe.Sizeof((C.int)(0))))) + _, oobn, _, _, err := syscall.Recvmsg(int(listenerFd), nil, buf, 0) + if err != nil { + return ln.s.recErr(err) + } + + scms, err := syscall.ParseSocketControlMessage(buf[:oobn]) + if err != nil { + return ln.s.recErr(err) + } + if len(scms) != 1 { + return ln.s.recErr(fmt.Errorf("libtailscale: got %d control messages, want 1", len(scms))) + } + fds, err := syscall.ParseUnixRights(&scms[0]) + if err != nil { + return ln.s.recErr(err) + } + if len(fds) != 1 { + return ln.s.recErr(fmt.Errorf("libtailscale: got %d FDs, want 1", len(fds))) + } + *connOut = (C.int)(fds[0]) + + return 0 +} + func newConn(s *server, netConn net.Conn, connOut *C.int) error { fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0) if err != nil {