Repository navigation
feat(socket): TCPSocket/TCPServer + OpenSSL::SSL::SSLSocket transport (net + crypto/tls) - #173
Merged
Merged
Conversation
… (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
force-pushed
the
feat/socket-tcp-tls
branch
from
July 6, 2026 15:30
3d975e8 to
135ee63
Compare
go build -o writes rbgo.exe on Windows; exec the OS-correct binary name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 6, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
rbgo's foundational network layer: a real stream socket backed by Go's
net, plus client-side TLS overcrypto/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 (andNet::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)→nilat EOF,readpartial→EOFError).TCPServer(net.Listen) —new([host,] port)/accept→connectedTCPSocket/close/addr/local_address/listen/closed?.TCPServer < TCPSocket < IPSocket < BasicSocket, plusSocketwith the common address-family/type constants (AF_INET,SOCK_STREAM, …) andSocketError. BasicSocket is rooted atObjectrather thanIOon purpose — IO's instance methods assume an*IOObjreceiver 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::SSLSocketovercrypto/tls—new(io[, ctx])wraps aTCPSocket;connectruns the client handshake; shares the read/write surface withTCPSocket;io/to_io/context/hostname=/peer_cert.OpenSSL::SSL::SSLContextgainsverify_mode/cert/key/ca_file/ … accessors.verify_modedrivescrypto/tlsverification and defaults toVERIFY_NONE(nil), matching a bare MRISSLContext.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:
TCPSocketHTTPGETvsnet/http/httptest— asserts the body;SSLSocketHTTPSGETvshttptest.NewTLSServer(self-signed, default VERIFY_NONE ctx) — asserts the body;TCPServer+Threadaccept loop;TestSocketThroughRbgoBinary, which builds and runs the actualcmd/rbgobinary 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_certetc.).Next step this unblocks
The
Net::HTTPbinding (ago-ruby-net-httpcodec over this transport) —net/httpis currently a load-time shell whose networking methods raiseNotImplementedError; it now has a real socket to sit on.🤖 Generated with Claude Code