Skip to content

Commit ddc444b

Browse files
committed
.,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.
1 parent 1012969 commit ddc444b

File tree

4 files changed

+71
-28
lines changed

4 files changed

+71
-28
lines changed

ruby/lib/tailscale.rb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ module Libtailscale
3939
attach_function :TsnetSetLogFD, [:int, :int], :int
4040
attach_function :TsnetDial, [:int, :string, :string, :pointer], :int, blocking: true
4141
attach_function :TsnetListen, [:int, :string, :string, :pointer], :int
42-
attach_function :close, [:int], :int
43-
attach_function :tailscale_accept, [:int, :pointer], :int, blocking: true
42+
attach_function :TsnetAccept, [:int, :pointer], :int, blocking: true
4443
attach_function :TsnetErrmsg, [:int, :pointer, :size_t], :int
4544
attach_function :TsnetLoopback, [:int, :pointer, :size_t, :pointer, :pointer], :int
4645
end
@@ -86,15 +85,19 @@ def initialize(ts, listener)
8685
# write.
8786
def accept
8887
@ts.assert_open
88+
lio = IO.for_fd(@listener)
89+
until IO.select([lio]).first.any?
90+
@ts.assert_open
91+
end
8992
conn = FFI::MemoryPointer.new(:int)
90-
Error.check(@ts, Libtailscale::tailscale_accept(@listener, conn))
93+
Error.check(@ts, Libtailscale::TsnetAccept(@listener, conn))
9194
IO::new(conn.read_int)
9295
end
9396

9497
# Close the listener.
9598
def close
9699
@ts.assert_open
97-
Error.check(@ts, Libtailscale::close(@listener))
100+
IO.for_fd(@listener).close
98101
end
99102
end
100103

@@ -229,8 +232,7 @@ def set_log_fd(log_fd)
229232
end
230233

231234
# Dial a network address. +network+ is one of "tcp" or "udp". +addr+ is the
232-
# remote address to connect to. This method blocks until the connection is
233-
# established.
235+
# remote address to connect to. This method blocks until the connection is established.
234236
def dial(network, addr)
235237
assert_open
236238
conn = FFI::MemoryPointer.new(:int)

ruby/test/tailscale/test_tailscale.rb

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def test_that_it_has_a_version_number
2323
def test_listen_sorta_works
2424
ts = newts
2525
ts.up
26+
wait_status_running ts
2627
s = ts.listen("tcp", ":1999")
2728
s.close
2829
ts.close
@@ -31,18 +32,41 @@ def test_listen_sorta_works
3132
def test_dial_sorta_works
3233
ts = newts
3334
ts.up
35+
wait_status_running ts
3436
c = ts.dial("udp", "100.100.100.100:53")
3537
c.close
3638
ts.close
3739
end
3840

41+
def test_listen_accept_dial_close
42+
ts = newts
43+
ts.up
44+
wait_status_running ts
45+
hn = ts.local_api.status["Self"]["TailscaleIPs"][0]
46+
s = ts.listen "tcp", "#{hn}:1999"
47+
c = ts.dial "tcp", "#{hn}:1999"
48+
ss = s.accept
49+
c.write "hello"
50+
assert_equal "hello", ss.read(5)
51+
ss.write "world"
52+
assert_equal "world", c.read(5)
53+
ss.close
54+
c.close
55+
ts.close
56+
end
57+
58+
def wait_status_running ts
59+
while ts.local_api.status["BackendState"] != "Running"
60+
sleep 0.01
61+
end
62+
end
63+
3964
def newts
4065
t = Tailscale::new
4166
unless ENV["VERBOSE"]
4267
logfd = IO.sysopen("/dev/null", "w+")
4368
t.set_log_fd(logfd)
4469
end
45-
4670
t.set_ephemeral(1)
4771
t.set_dir(@tmpdir)
4872
t.set_control_url($testcontrol_url)

tailscale.c

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extern int TsnetSetLogFD(int sd, int fd);
2222
extern int TsnetGetIps(int sd, char *buf, size_t buflen);
2323
extern int TsnetGetRemoteAddr(int listener, int conn, char *buf, size_t buflen);
2424
extern int TsnetListen(int sd, char* net, char* addr, int* listenerOut);
25+
extern int TsnetAccept(int ld, int* connOut);
2526
extern int TsnetLoopback(int sd, char* addrOut, size_t addrLen, char* proxyOut, char* localOut);
2627
extern int TsnetEnableFunnelToLocalhostPlaintextHttp1(int sd, int localhostPort);
2728

@@ -50,27 +51,7 @@ int tailscale_listen(tailscale sd, const char* network, const char* addr, tailsc
5051
}
5152

5253
int tailscale_accept(tailscale_listener ld, tailscale_conn* conn_out) {
53-
struct msghdr msg = {0};
54-
55-
char mbuf[256];
56-
struct iovec io = { .iov_base = mbuf, .iov_len = sizeof(mbuf) };
57-
msg.msg_iov = &io;
58-
msg.msg_iovlen = 1;
59-
60-
char cbuf[256];
61-
msg.msg_control = cbuf;
62-
msg.msg_controllen = sizeof(cbuf);
63-
64-
if (recvmsg(ld, &msg, 0) == -1) {
65-
return -1;
66-
}
67-
68-
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
69-
unsigned char* data = CMSG_DATA(cmsg);
70-
71-
int fd = *(int*)data;
72-
*conn_out = fd;
73-
return 0;
54+
return TsnetAccept(ld, (int*)conn_out);
7455
}
7556

7657
int tailscale_getremoteaddr(tailscale_listener l, tailscale_conn conn, char* buf, size_t buflen) {

tailscale.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"syscall"
2121
"unsafe"
2222

23+
"golang.org/x/sys/unix"
2324
"tailscale.com/hostinfo"
2425
"tailscale.com/ipn"
2526
"tailscale.com/tsnet"
@@ -292,6 +293,41 @@ func TsnetListen(sd C.int, network, addr *C.char, listenerOut *C.int) C.int {
292293
return 0
293294
}
294295

296+
//export TsnetAccept
297+
func TsnetAccept(listenerFd C.int, connOut *C.int) C.int {
298+
listeners.mu.Lock()
299+
ln := listeners.m[listenerFd]
300+
listeners.mu.Unlock()
301+
302+
if ln == nil {
303+
return C.EBADF
304+
}
305+
306+
buf := make([]byte, unix.CmsgLen(int(unsafe.Sizeof((C.int)(0)))))
307+
_, oobn, _, _, err := syscall.Recvmsg(int(listenerFd), nil, buf, 0)
308+
if err != nil {
309+
return ln.s.recErr(err)
310+
}
311+
312+
scms, err := syscall.ParseSocketControlMessage(buf[:oobn])
313+
if err != nil {
314+
return ln.s.recErr(err)
315+
}
316+
if len(scms) != 1 {
317+
return ln.s.recErr(fmt.Errorf("libtailscale: got %d control messages, want 1", len(scms)))
318+
}
319+
fds, err := syscall.ParseUnixRights(&scms[0])
320+
if err != nil {
321+
return ln.s.recErr(err)
322+
}
323+
if len(fds) != 1 {
324+
return ln.s.recErr(fmt.Errorf("libtailscale: got %d FDs, want 1", len(fds)))
325+
}
326+
*connOut = (C.int)(fds[0])
327+
328+
return 0
329+
}
330+
295331
func newConn(s *server, netConn net.Conn, connOut *C.int) error {
296332
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)
297333
if err != nil {

0 commit comments

Comments
 (0)