Skip to content

.,ruby: enable accept to be called as TsnetAccept & fix gem#24

Open
raggi wants to merge 1 commit intomainfrom
raggi/accept
Open

.,ruby: enable accept to be called as TsnetAccept & fix gem#24
raggi wants to merge 1 commit intomainfrom
raggi/accept

Conversation

@raggi
Copy link
Member

@raggi raggi commented Aug 16, 2023

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.

@raggi raggi requested a review from crawshaw August 16, 2023 21:16
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.
Copilot AI review requested due to automatic review settings February 7, 2026 03:38
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR moves the listener accept FD-receive logic from the Ruby/C side into the Go-exported library API (TsnetAccept), so language bindings can call accept directly without relying on a separate C-side recvmsg implementation.

Changes:

  • Added Go-exported TsnetAccept to receive an accepted connection FD via recvmsg/SCM_RIGHTS.
  • Simplified the C wrapper tailscale_accept to delegate to TsnetAccept.
  • Updated the Ruby FFI binding and tests to use the new accept path and added a basic listen/accept/dial roundtrip test.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
tailscale.go Adds exported TsnetAccept and imports x/sys/unix for control-message sizing.
tailscale.c Replaces C-side recvmsg accept logic with a call into TsnetAccept.
ruby/lib/tailscale.rb Switches Ruby binding to TsnetAccept and changes listener accept/close behavior.
ruby/test/tailscale/test_tailscale.rb Adds a new accept/dial roundtrip test and a helper to wait for backend readiness.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +306 to +319
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])
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The control-message buffer is sized with unix.CmsgLen, which is the length value for a cmsghdr, not the space needed for receiving ancillary data. This can lead to truncated SCM_RIGHTS data and failed accepts on some platforms. Allocate the OOB buffer with unix.CmsgSpace(sizeof(int)) (or syscall.CmsgSpace) and check recvmsg flags for MSG_CTRUNC/MSG_TRUNC before parsing.

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +90
lio = IO.for_fd(@listener)
until IO.select([lio]).first.any?
@ts.assert_open
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IO.for_fd defaults to autoclose=true; the temporary lio created here can be garbage-collected after accept returns, which would close the underlying listener FD unexpectedly. Use IO.for_fd(@Listener, autoclose: false) (or store a persistent IO wrapper on the Listener instance and close it in #close) so GC can’t close the listener.

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +90
until IO.select([lio]).first.any?
@ts.assert_open
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This until IO.select([lio]).first.any? loop is effectively a single blocking call because IO.select has no timeout; the body won’t re-run to re-check assert_open. Either pass a small timeout to IO.select and loop, or remove the loop and just do a single select (or go back to a fully blocking accept call) to avoid misleading control flow.

Suggested change
until IO.select([lio]).first.any?
@ts.assert_open
loop do
# Wait briefly for the listener to become readable so we can
# periodically re-check that the server is still open.
readable, = IO.select([lio], nil, nil, 0.1)
@ts.assert_open
break if readable && !readable.empty?

Copilot uses AI. Check for mistakes.
end

def wait_status_running ts
while ts.local_api.status["BackendState"] != "Running"
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait_status_running can loop forever if the backend never reaches "Running", which can hang the test suite. Add a timeout/deadline (and possibly a short backoff) and fail the test with a helpful message when exceeded.

Suggested change
while ts.local_api.status["BackendState"] != "Running"
timeout_seconds = 30
deadline = Time.now + timeout_seconds
loop do
status = ts.local_api.status
return if status["BackendState"] == "Running"
if Time.now > deadline
flunk "BackendState did not reach 'Running' within #{timeout_seconds} seconds. Last status: #{status.inspect}"
end

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +56
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
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation in this new test method is inconsistent with the rest of the file (2-space indentation). Reformat the body to match the surrounding style to keep the test file readable.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant