Skip to content

feat(socket): TCPSocket/TCPServer + OpenSSL::SSL::SSLSocket transport (net + crypto/tls) - #173

Merged
tannevaled merged 2 commits into
mainfrom
feat/socket-tcp-tls
Jul 6, 2026
Merged

tannevaled merged 2 commits into
mainfrom
feat/socket-tcp-tls

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

What

rbgo's foundational network layer: a real stream socket backed by Go's net, plus client-side TLS over crypto/tls. rbgo already binds many network-capable clients (grpc/mysql/mongodb/redis/pg) but exposed no general Ruby socket — redis/pg had to take an injected IO-like duck. This lands the transport those clients (and Net::HTTP) actually sit on.

Module surface

require "socket":

  • TCPSocket (net.Dial) — read([n]) / gets([sep]) / readpartial(n) / write(*a) / print / puts / << / flush / sync / close / closed? / eof? / setsockopt (no-op) / peeraddr / local_address / addr. MRI-faithful EOF semantics (read(n)→nil at EOF, readpartial→EOFError).
  • TCPServer (net.Listen) — new([host,] port) / accept→connected TCPSocket / close / addr / local_address / listen / closed?.
  • Ancestry TCPServer < TCPSocket < IPSocket < BasicSocket, plus Socket with the common address-family/type constants (AF_INET, SOCK_STREAM, …) and SocketError. BasicSocket is rooted at Object rather than IO on purpose — IO's instance methods assume an *IOObj receiver and would panic on a socket value; the socket surface re-implements the IO methods that matter.

require "openssl" (upgrades the former not-implemented shell):

  • OpenSSL::SSL::SSLSocket over crypto/tls — new(io[, ctx]) wraps a TCPSocket; connect runs the client handshake; shares the read/write surface with TCPSocket; io/to_io/context/hostname=/peer_cert.
  • OpenSSL::SSL::SSLContext gains verify_mode / cert / key / ca_file / … accessors. verify_mode drives crypto/tls verification and defaults to VERIFY_NONE (nil), matching a bare MRI SSLContext.

Proof (hermetic, no external network)

Every assertion runs a Ruby script through the rbgo engine against an in-process loopback server started inside the test:

  • raw TCPSocket HTTP GET vs net/http/httptest — asserts the body;
  • SSLSocket HTTPS GET vs httptest.NewTLSServer (self-signed, default VERIFY_NONE ctx) — asserts the body;
  • TCPServer + Thread accept loop;
  • plus TestSocketThroughRbgoBinary, which builds and runs the actual cmd/rbgo binary against an httptest server and asserts its stdout.

GOWORK=off go build ./... && go vet ./... && go test ./... all green; 100% line coverage on the new binding code (loopback servers + error-injection for the raising arms).

Deferred follow-ups

Raw Socket.new / UDPSocket / UNIXSocket, server-side TLS, client-certificate auth, and the full cert/verify surface (peer_cert etc.).

Next step this unblocks

The Net::HTTP binding (a go-ruby-net-http codec over this transport) — net/http is currently a load-time shell whose networking methods raise NotImplementedError; it now has a real socket to sit on.

🤖 Generated with Claude Code

… (net + crypto/tls)

Add rbgo's foundational network layer: a real stream socket backed by Go's
net package plus client-side TLS over crypto/tls. rbgo bound many
network-capable clients (grpc/mysql/redis) but exposed no general Ruby
socket, so redis/pg took an injected IO-like seam; this lands the transport
those clients (and Net::HTTP) actually sit on.

- require "socket": TCPSocket (net.Dial) with read/gets/readpartial/write/
  print/puts/<</flush/sync/close/closed?/eof?/setsockopt/peeraddr/
  local_address/addr, and TCPServer (net.Listen) with accept/close/addr/
  listen/closed?. BasicSocket < ... < TCPSocket ancestry (rooted at Object,
  not IO, so IO's *IOObj-typed methods can't panic on a socket value), plus
  the Socket address-family/type constants and SocketError.
- OpenSSL::SSL::SSLSocket over crypto/tls wraps a TCPSocket: new(io[, ctx]) +
  connect (client handshake) + the shared read/write surface; SSLContext
  gains verify_mode/cert/key/ca_file/... accessors (verify_mode drives
  InsecureSkipVerify, defaulting to VERIFY_NONE like MRI). Replaces the old
  not-implemented SSLSocket shell in openssl.go.
- Proven end-to-end through the rbgo engine against in-process loopback
  servers: a raw TCPSocket HTTP GET vs net/http/httptest, an SSLSocket HTTPS
  GET vs httptest.NewTLSServer, and a TCPServer+Thread accept loop — plus a
  test that builds and runs the actual cmd/rbgo binary against an httptest
  server. 100% coverage on the new binding code (loopback servers +
  error-injection).

Deferred follow-ups: raw Socket.new / UDPSocket / UNIXSocket, server-side
TLS, client-cert auth, and the full cert/verify surface (peer_cert etc.).
This unblocks the Net::HTTP binding (go-ruby-net-http codec over this
transport) as the natural next step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tannevaled
tannevaled force-pushed the feat/socket-tcp-tls branch from 3d975e8 to 135ee63 Compare July 6, 2026 15:30
go build -o writes rbgo.exe on Windows; exec the OS-correct binary name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit d286970 into main Jul 6, 2026
9 checks passed
@tannevaled
tannevaled deleted the feat/socket-tcp-tls branch July 6, 2026 16:59
tannevaled added a commit that referenced this pull request Jul 6, 2026
Extend rbgo's socket transport (require "socket" / "openssl") with the
families and TLS features the initial PR (#173) deferred, all backed by
Go's net + crypto/tls:

- UDPSocket (socket_udp.go): datagram socket over *net.UDPConn —
  new([af]) / bind / connect / send(msg,flags[,host,port]) / recvfrom ->
  [msg, sender] / recv / addr / close, with MRI datagram semantics for the
  common cases (ephemeral bind on new, connect records a default peer).

- UNIXSocket / UNIXServer (socket_unix.go): AF_UNIX stream sockets sharing
  the connected-stream IO surface (read/gets/write/recv/send/...) with
  TCPSocket. AF_UNIX is unsupported on Windows, so the whole transport is
  build-tagged !windows; the Windows build (socket_unix_windows.go) installs
  stub classes whose constructors raise a clean, rescuable
  NotImplementedError. The UNIX loopback tests skip on Windows.

- Server-side TLS (socket_bind.go): OpenSSL::SSL::SSLSocket#accept (server
  handshake via tls.Server) and OpenSSL::SSL::SSLServer wrapping a TCPServer
  + SSLContext so #accept returns handshaked server SSLSockets. Both
  handshakes release the GVL so an in-VM client and server Thread run
  concurrently.

- Cert / verify surface: SSLContext#cert / #key / #ca_file now feed real
  crypto/tls material — server certificate, client RootCAs, mutual-TLS
  client certificate. VERIFY_PEER performs Go's chain + hostname
  verification; #peer_cert returns the handshaked peer's certificate.
  OpenSSL::X509::Certificate is upgraded from a shell to a real PEM-carrying
  cert (parse on new, #to_pem / #subject).

Proven end-to-end through the engine against in-test loopback servers:
UDP echo, UNIX loopback (skipped on Windows), in-VM server-TLS round trip
with a self-signed cert generated in-test, mutual-TLS, and ca_file
verification (success + hostname-mismatch failure). 100% coverage on the
new code (loopback + injected error seams).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
tannevaled added a commit that referenced this pull request Jul 6, 2026
)

Extend rbgo's socket transport (require "socket") with the name-resolution
and address utilities the connected/datagram socket work (#173/#192)
deferred, all backed by Go's net package (no cgo):

- Socket.getaddrinfo(host, port[, family, socktype, protocol, flags]) ->
  MRI's [afamily, port, host, addr, pfamily, socktype, protocol] tuples,
  resolved through net.Resolver with no reverse lookup (hermetic).
- Addrinfo value class: .tcp / .udp / .ip / .getaddrinfo / .new (packed
  sockaddr String or [afamily, port, host, addr] array), plus
  ip_address / ip_port / afamily / pfamily / socktype / protocol /
  ipv4? / ipv6? / to_sockaddr / inspect.
- sockaddr pack/unpack: Socket.pack_sockaddr_in / sockaddr_in /
  unpack_sockaddr_in (IPv4 + IPv6) and pack_sockaddr_un / sockaddr_un /
  unpack_sockaddr_un (AF_UNIX, non-Windows; NotImplementedError stubs on
  Windows, mirroring the UNIXSocket transport).

The sockaddr layout is the Linux one (no BSD sa_len byte): the family
field is host native byte order (a C sa_family_t) while port and address
are network byte order, so the [port, host] round-trip is byte-exact on
every arch. Verified on big-endian s390x under emulation.

Tests exercise the whole surface through the rbgo engine against
loopback-resolvable inputs (127.0.0.1 / ::1 / localhost); the resolver and
service-name port lookup are driven through injectable seams (resolveIPs /
lookupPort) so the suite never touches live DNS. 100% coverage of the new
code, including every error and platform arm.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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