Skip to content

Commit 337eb55

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 7c1e4de commit 337eb55

File tree

5 files changed

+65
-28
lines changed

5 files changed

+65
-28
lines changed

ruby/Gemfile.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ GEM
1414
rake
1515

1616
PLATFORMS
17+
arm64-darwin-22
1718
x86_64-linux-gnu
1819

1920
DEPENDENCIES

ruby/lib/tailscale.rb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
require 'tailscale/version'
66
require 'ffi'
77
require 'rbconfig'
8+
require 'base64'
9+
require 'net/http'
10+
require 'json'
811

912
# Tailscale provides an embedded tailscale network interface for ruby programs.
1013
class Tailscale
@@ -36,8 +39,7 @@ module Libtailscale
3639
attach_function :TsnetSetLogFD, [:int, :int], :int
3740
attach_function :TsnetDial, [:int, :string, :string, :pointer], :int, blocking: true
3841
attach_function :TsnetListen, [:int, :string, :string, :pointer], :int
39-
attach_function :close, [:int], :int
40-
attach_function :tailscale_accept, [:int, :pointer], :int, blocking: true
42+
attach_function :TsnetAccept, [:int, :pointer], :int, blocking: true
4143
attach_function :TsnetErrmsg, [:int, :pointer, :size_t], :int
4244
attach_function :TsnetLoopback, [:int, :pointer, :size_t, :pointer, :pointer], :int
4345
end
@@ -82,6 +84,7 @@ def initialize(ts, listener)
8284
# write.
8385
def accept
8486
@ts.assert_open
87+
IO.select [IO.for_fd(@listener)]
8588
conn = FFI::MemoryPointer.new(:int)
8689
Error.check @ts, Libtailscale::TsnetAccept(@listener, conn)
8790
IO::new conn.read_int
@@ -90,7 +93,7 @@ def accept
9093
# Close the listener.
9194
def close
9295
@ts.assert_open
93-
Error.check @ts, Libtailscale::close(@listener)
96+
IO.for_fd(@listener).close
9497
end
9598
end
9699

@@ -225,9 +228,8 @@ def set_log_fd(log_fd)
225228
end
226229

227230
# Dial a network address. +network+ is one of "tcp" or "udp". +addr+ is the
228-
# remote address to connect to, and +local_addr+ is the local address to
229-
# bind to. This method blocks until the connection is established.
230-
def dial(network, addr, local_addr)
231+
# remote address to connect to. This method blocks until the connection is established.
232+
def dial(network, addr)
231233
assert_open
232234
conn = FFI::MemoryPointer.new(:int)
233235
Error.check self, Libtailscale::TsnetDial(@t, network, addr, conn)

ruby/test/tailscale/test_tailscale.rb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,28 @@ def test_dial_sorta_works
2222
# TODO: make a more useful test when we can make a server to connect to.
2323
ts = newts
2424
ts.start
25-
c = ts.dial "udp", "100.100.100.100:53", ""
25+
c = ts.dial "udp", "100.100.100.100:53"
2626
c.close
2727
ts.close
2828
end
2929

30+
# Requires a solution to be logged in:
31+
# def test_listen_accept_dial_close
32+
# ts = newts
33+
# ts.up
34+
# hn = ts.local_api.status["Self"]["HostName"]
35+
# s = ts.listen "tcp", ":1999"
36+
# c = ts.dial "tcp", "#{hn}:1999"
37+
# ss = s.accept
38+
# c.write "hello"
39+
# assert_equal "hello", ss.read(5)
40+
# ss.write "world"
41+
# assert_equal "world", c.read(5)
42+
# ss.close
43+
# c.close
44+
# ts.close
45+
# end
46+
3047
def newts
3148
t = Tailscale::new
3249
unless ENV['VERBOSE']

tailscale.c

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ extern int TsnetSetControlURL(int sd, char* str);
2020
extern int TsnetSetEphemeral(int sd, int ephemeral);
2121
extern int TsnetSetLogFD(int sd, int fd);
2222
extern int TsnetListen(int sd, char* net, char* addr, int* listenerOut);
23+
extern int TsnetAccept(int ld, int* connOut);
2324
extern int TsnetLoopback(int sd, char* addrOut, size_t addrLen, char* proxyOut, char* localOut);
2425

2526
tailscale tailscale_new() {
@@ -47,27 +48,7 @@ int tailscale_listen(tailscale sd, const char* network, const char* addr, tailsc
4748
}
4849

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

7354
int tailscale_set_dir(tailscale sd, const char* dir) {

tailscale.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"syscall"
1818
"unsafe"
1919

20+
"golang.org/x/sys/unix"
2021
"tailscale.com/hostinfo"
2122
"tailscale.com/tsnet"
2223
"tailscale.com/types/logger"
@@ -254,6 +255,41 @@ func TsnetListen(sd C.int, network, addr *C.char, listenerOut *C.int) C.int {
254255
return 0
255256
}
256257

258+
//export TsnetAccept
259+
func TsnetAccept(listenerFd C.int, connOut *C.int) C.int {
260+
listeners.mu.Lock()
261+
ln := listeners.m[listenerFd]
262+
listeners.mu.Unlock()
263+
264+
if ln == nil {
265+
return C.EBADF
266+
}
267+
268+
buf := make([]byte, unix.CmsgLen(int(unsafe.Sizeof((C.int)(0)))))
269+
_, oobn, _, _, err := syscall.Recvmsg(int(listenerFd), nil, buf, 0)
270+
if err != nil {
271+
return ln.s.recErr(err)
272+
}
273+
274+
scms, err := syscall.ParseSocketControlMessage(buf[:oobn])
275+
if err != nil {
276+
return ln.s.recErr(err)
277+
}
278+
if len(scms) != 1 {
279+
return ln.s.recErr(fmt.Errorf("libtailscale: got %d control messages, want 1", len(scms)))
280+
}
281+
fds, err := syscall.ParseUnixRights(&scms[0])
282+
if err != nil {
283+
return ln.s.recErr(err)
284+
}
285+
if len(fds) != 1 {
286+
return ln.s.recErr(fmt.Errorf("libtailscale: got %d FDs, want 1", len(fds)))
287+
}
288+
*connOut = (C.int)(fds[0])
289+
290+
return 0
291+
}
292+
257293
func newConn(s *server, netConn net.Conn, connOut *C.int) error {
258294
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)
259295
if err != nil {

0 commit comments

Comments
 (0)