Implement built-in node:net impl
Implement a subset of the node:net and node:tls modules supporting net.Socket, tls.TLSSocket, net.BlockList, and net.SocketAddress. The net.Server and tls.TLSServer types are explicitly unsupported and won't be implemented.
Update: The key next step on this is implementing tests... Folks should feel free to start performing code review on the main piece. If folks would like a walkthrough to help with the code review, let me know
Very rough todo list:
- [x] Get it basically working ... ;-) ... basic implementation of the core functions. Difficult to delineate all the specific tasks involved here.
- [x]
net.connect(...)creates the Socket, normalizes the arguments, and forwards the args tosocket.connect(...) - [x]
socket.connect(...)validates the input arguments, initiates the underlying socket connection - [x]
new Socket([options])works- [x]
options.allowHalfOpenworks and passes through to the underlyingSocket - [x]
options.fdthrows an error if set - [x]
options.readableis ignored sinceoptions.fdis unsupported - [x]
options.writableis ignored sinceoptions.fdin unsupported - [x]
options.signalsets anAbortSignalthat destroys thenet.Socketwhen triggered - [x] ~~Setting
options.handleto an existing standardSocketinstance should wrap that socket~~ Will implement this separately as it's not critical for immediate compat in the short term
- [x]
- [x] Initialize and establish the connection
- [x]
connectandreadyevents are emitted when the connection is opened - [x]
closeevent is emitted when destroy is called - [x]
closeeventhadErrorargument is true when destroy is called with an error - [x]
errorevent is emitted when destroy is called with an error - [x] destroy is called with an error when attempt to establish a connection fails
- [x]
connectionAttemptevent is emitted when initializing a connection - [x]
connectionAttemptFailedevent is emitted when initializing a connection fails - [x]
connectionAttemptTimeoutevent will not be emitted since the family autoselection feature is not implemented - [x] The
addressTypeargument in theconnectAttemptandconnectionAttmptFailedevents is the correct type - [x] Writes while connecting are buffered until connection established, then buffered writes are flushed
- [x] Writev works
- [x] Writes can be corked/uncorked any time
- [x] Writes can be encoded strings or TypedArrays, other types throw
- [x] Individual write callbacks are called. Error argument is set appropriately when write fails
- [x] Calling
end()closes the connection - [x] The
drainevent is emitted when the write buffer is empty - [x] When
end()is called, buffered data in the readable side is still avaialble - [x] If
options.lookupis provided and destination address is not an IP address, theoptions.lookupfunction should be called to "resolve" the address. (optional... we could just as easily choose not to supportoptions.lookup) - [x] The
lookupevent is emitted after theoptions.lookupis called and returns a result - [x] Underlying reader on the socket is a BYOB reader reusing the user provided buffer or our auto allocated buffer
- [x] An error while reading from the underlying socket causes the
net.Socketto be destroyed with an error - [x] The
net.Socketcan have areadevent attached putting the stream in flowing mode - [x] If the
readevent is attached while the connection is still pending, thereadwill automatically start when the connection is established. - [x] The stream can be paused/resumed to stop/restart the flow of data
- [x] Attempting to connect to a pipe path fails
- [x] The
dataevent is emitted when a chunk of data has been read - [x] The
endevent is emitted when the underlying stream readable side is done - [x] Reading honors backpressure signaling by pausing
- [x] Receiving and EOS from the remote should end the readable side of the stream. If
allowHalfOpenis false, this should close and destroy the socket ONLY once the write buffer is drained. IfallowHalfOpenis true, it should not. - [x]
socket.setTimeout(...)sets an activity timer on the socket. - [x] The socket timeout is reset on writes, reads, and when the connection is established.
- [x] The
timeoutevent is emitted when the activity timeout expires. - [x]
socket.address()returns the local address if the socket is connected, undefined otherwise... we hard code this to0.0.0.0:0since we have no notion of a local bound address in workers - [x]
socket.autoSelectFamilyAttemptedAddressesis always an array, when connect is called, it always just contains the one address we're connecting to. This is generally non-op since we do not implement family autoselection - [x]
socket.bufferSizereturns the number of bytes buffered - [x]
socket.bytesReadreturns the number of bytes read - [x]
socket.bytesWrittenreturns the number of bytes written - [x]
socket.connect(options[, connectListener])works- [x] Passing an ipv6 address works
- [x]
options.autoSelectFamilyonly accepts a falsy value. truthy values throw - [x]
options.autoSelectFamilyAttemptTimeoutis validated to be a number but is otherwise ignored - [x]
options.familyis verified to match thehostspecified - [x]
options.hintsis ignored unlessoptions.lookupis given, then it is passed to that function when called - [x]
options.hostis the address to connect to. Defaults tolocalhost. - [x]
options.keepAliveonly falsy values are accepted. Truthy values throw - [x]
options.keepAliveInitialDelayis validated to be a number but is otherwise ignored - [x]
options.localAddressis ignored - [x]
options.localPortis ignored - [x]
options.lookupis used if thehostis not a valid IP address - [x]
options.noDelayfalsy values are accepted. Truthy values throw - [x]
options.portis validated to be a proper port - [x]
options.paththrows - [x]
options.onreadis used if specified
- [x]
socket.connect(path[, connectListener])throws - [x]
socket.connect(port[, host[, connectListener])works - [x]
socket.connectingis true while the connection is being established. Is false otherwise - [x]
socket.destroy(error)immediately destroys the stream and closes the connection- [x] All underlying resources are freed
- [x] ~
socket.connect(...)can be called again immediately aftersocket.destroy(...)~ Currently not planning to implement this for now. We can do this in a separate PR. - [x]
socket.destroyedis true after callingsocket.destroy() - [x]
socket.destroySoon()destroys the socket after all data is written. If thefinishevent already emitted, the socket is destroyed immediately. If the socket is still writable,end()is called. - [x]
socket.end(data[, encoding[, callback]])writes the given chun and ends the writable size and half-closes the socket - [x]
socket.localAddressalways returns0.0.0.0 - [x]
socket.localPortalways returns0 - [x]
socket.localFamilyalways returns0 - [x]
socket.pause()pauses reading of data on the stream. Pauses the underlying read loop - [x]
socket.pendingistrueif the socket is not connected yet at all - [x]
socket.ref()andsocket.unref()are non-op - [x]
socket.remoteAddressis the remote host - [x]
socket.remoteFamilyis the remote host IP type - [x]
socket.remotePortis the remote port - [x]
socket.resetAndDestroy()closes the connection and destroys the stream. - [x]
socket.resume()resumes reading after a call to pause - [x]
socket.setEncoding(...)sets the text encoding for the readable side of the socket - [x]
socket.setKeepAlive()accepts falsy values, throws on truthy values - [x]
socket.setNoDelay()accepts falsy values, throws on truthy values - [x]
socket.readyStateaccurately reflects the ready status of the connection - [x] The timeout timer is reset after each read from the underlying socket
- [x]
- [x] Add the compat flag / date that will signal the availability of the net API`
- [x] Tests test and more tests ... The plan is to port as many of the Node.js tests as possible but testing of the
SocketAPI within workerd is fairly limited currently - [ ] Documentation
Future work items (to come in separate follow-up PRs
tls.TLSSocketis implementednet.BlockListis implementednet.SocketAddressis implemented- Socket supports diagnostics channel
- AsyncLocalStorage context is correctly propagated such that it matches Node.js' behavior (will needs tests to verify this)
This is not 100% complete but it's far enough along that we ought to be able to get the initial bits landed. Follow on PRs will add tests and missing pieces.
Rebased the PR to build on top of the tcp-ingress PR so I can start working on tests
Tests from Node.js to port ... not all of these will be relevant. Will check them off as they are ported or determined not to be relevant:
- [x] test/parallel/test-net-access-byteswritten.js
- [x] test/parallel/test-net-after-close.js
- [x] test/parallel/test-net-allow-half-open.js
- [x] test/parallel/test-net-better-error-messages-port-hostname.js
- [x] test/parallel/test-net-binary.js
- [x] test/parallel/test-net-buffersize.js
- [x] test/parallel/test-net-bytes-read.js
- [x] test/parallel/test-net-bytes-stats.js
- [x] test/parallel/test-net-bytes-written-large.js
- [x] test/parallel/test-net-can-reset-timeout.js
- [x] test/parallel/test-net-connect-abort-controller.js
- [x] test/parallel/test-net-connect-after-destroy.js
- [x] test/parallel/test-net-connect-buffer.js
- [x] test/parallel/test-net-connect-destroy.js
- [x] test/parallel/test-net-connect-immediate-destroy.js
- [x] test/parallel/test-net-connect-immediate-finish.js
- [x] test/parallel/test-net-connect-keepalive.js
- [x] test/parallel/test-net-connect-memleak.js
- [x] test/parallel/test-net-connect-no-arg.js
- [x] test/parallel/test-net-connect-nodelay.js
- [x] test/parallel/test-net-connect-options-allowhalfopen.js
- [x] test/parallel/test-net-connect-options-fd.js
- [x] test/parallel/test-net-connect-options-invalid.js
- [x] test/parallel/test-net-connect-options-ipv6.js
- [x] test/parallel/test-net-connect-options-path.js
- [x] test/parallel/test-net-connect-options-port.js
- [x] test/parallel/test-net-connect-paused-connection.js
- [x] test/parallel/test-net-dns-custom-lookup.js
- [x] test/parallel/test-net-dns-error.js
- [x] test/parallel/test-net-dns-lookup-skip.js
- [x] test/parallel/test-net-dns-lookup.js
- [x] test/parallel/test-net-during-close.js
- [x] test/parallel/test-net-end-close.js
- [x] test/parallel/test-net-end-destroyed.js
- [x] test/parallel/test-net-end-without-connect.js
- [x] test/parallel/test-net-isip.js
- [x] test/parallel/test-net-isipv4.js
- [x] test/parallel/test-net-isipv6.js
- [x] test/parallel/test-net-keepalive.js
- [x] test/parallel/test-net-large-string.js
- [x] test/parallel/test-net-local-address-port.js
- [x] test/parallel/test-net-localerror.js
- [x] test/parallel/test-net-normalize-args.js
- [x] test/parallel/test-net-onread-static-buffer.js
- [x] test/parallel/test-net-options-lookup.js
- [x] test/parallel/test-net-pause-resume-connecting.js
- [x] test/parallel/test-net-perf_hooks.js
- [x] test/parallel/test-net-persistent-keepalive.js
- [x] test/parallel/test-net-persistent-nodelay.js
- [x] test/parallel/test-net-persistent-ref-unref.js
- [x] test/parallel/test-net-pingpong.js
- [x] test/parallel/test-net-pipe-connect-errors.js
- [x] test/parallel/test-net-pipe-with-long-path.js
- [x] test/parallel/test-net-reconnect.js
- [x] test/parallel/test-net-remote-address-port.js
- [x] test/parallel/test-net-remote-address.js
- [x] test/parallel/test-net-settimeout.js
- [x] test/parallel/test-net-socket-byteswritten.js
- [x] test/parallel/test-net-socket-close-after-end.js
- [x] test/parallel/test-net-socket-connect-invalid-autoselectfamily.js
- [x] test/parallel/test-net-socket-connect-invalid-autoselectfamilyattempttimeout.js
- [x] test/parallel/test-net-socket-connect-without-cb.js
- [x] test/parallel/test-net-socket-connecting.js
- [x] test/parallel/test-net-socket-constructor.js
- [x] test/parallel/test-net-socket-destroy-send.js
- [x] test/parallel/test-net-socket-destroy-twice.js
- [x] test/parallel/test-net-socket-end-before-connect.js
- [x] test/parallel/test-net-socket-end-callback.js
- [x] test/parallel/test-net-socket-local-address.js
- [x] test/parallel/test-net-socket-no-halfopen-enforcer.js
- [x] test/parallel/test-net-socket-ready-without-cb.js
- [x] test/parallel/test-net-socket-reset-send.js
- [x] test/parallel/test-net-socket-reset-twice.js
- [x] test/parallel/test-net-socket-setnodelay.js
- [x] test/parallel/test-net-socket-timeout-unref.js
- [x] test/parallel/test-net-socket-timeout.js
- [x] test/parallel/test-net-socket-write-after-close.js
- [x] test/parallel/test-net-socket-write-error.js
- [x] test/parallel/test-net-stream.js
- [x] test/parallel/test-net-sync-cork.js
- [x] test/parallel/test-net-throttle.js
- [x] test/parallel/test-net-timeout-no-handle.js
- [x] test/parallel/test-net-writable.js
- [x] test/parallel/test-net-write-after-close.js
- [x] test/parallel/test-net-write-after-end-nt.js
- [x] test/parallel/test-net-write-arguments.js
- [x] test/parallel/test-net-write-cb-on-destroy-before-connect.js
- [x] test/parallel/test-net-write-connect-write.js
- [x] test/parallel/test-net-write-fully-async-buffer.js
- [x] test/parallel/test-net-write-fully-async-hex-string.js
- [x] test/parallel/test-net-write-slow.js
- [x] test/parallel/test-net-connect-reset-after-destroy.js
- [x] test/parallel/test-net-connect-reset-before-connected.js
- [x] test/parallel/test-net-connect-reset-until-connected.js
- [x] test/parallel/test-net-connect-reset.js
test-net-* tests we won't be implementing now:
- The autoselectfamily tests are a happy eyeballs implementation in Node.js that we are not implementing directly
- test/parallel/test-net-autoselectfamily-attempt-timeout-cli-option.js
- test/parallel/test-net-autoselectfamily-attempt-timeout-default-value.js
- test/parallel/test-net-autoselectfamily-commandline-option.js
- test/parallel/test-net-autoselectfamily-default.js
- test/parallel/test-net-autoselectfamily-ipv4first.js
- test/parallel/test-net-autoselectfamily.js
- test/parallel/test-net-better-error-messages-listen-path.js
- test/parallel/test-net-better-error-messages-listen.js
- test/parallel/test-net-better-error-messages-path.js
- test/parallel/test-net-bind-twice.js
- test/parallel/test-net-child-process-connect-reset.js
- test/parallel/test-net-client-bind-twice.js
- test/parallel/test-net-listen-after-destroying-stdin.js
- test/parallel/test-net-listen-close-server-callback-is-not-function.js
- test/parallel/test-net-listen-close-server.js
- test/parallel/test-net-listen-error.js
- test/parallel/test-net-listen-exclusive-random-ports.js
- test/parallel/test-net-listen-fd0.js
- test/parallel/test-net-listen-handle-in-cluster-1.js
- test/parallel/test-net-listen-handle-in-cluster-2.js
- test/parallel/test-net-listen-invalid-port.js
- test/parallel/test-net-listen-ipv6only.js
- test/parallel/test-net-listen-twice.js
- test/parallel/test-net-listening.js
- test/parallel/test-net-connect-buffer2.js
- test/parallel/test-net-connect-call-socket-connect.js
- test/parallel/test-net-server-async-dispose.mjs
- test/parallel/test-net-server-call-listen-multiple-times.js
- test/parallel/test-net-server-capture-rejection.js
- test/parallel/test-net-server-close-before-calling-lookup-callback.js
- test/parallel/test-net-server-close-before-ipc-response.js
- test/parallel/test-net-server-close.js
- test/parallel/test-net-server-drop-connections.js
- test/parallel/test-net-server-keepalive.js
- test/parallel/test-net-server-listen-handle.js
- test/parallel/test-net-server-listen-options-signal.js
- test/parallel/test-net-server-listen-options.js
- test/parallel/test-net-server-listen-path.js
- test/parallel/test-net-server-listen-remove-callback.js
- test/parallel/test-net-server-max-connections-close-makes-more-available.js
- test/parallel/test-net-server-max-connections.js
- test/parallel/test-net-server-nodelay.js
- test/parallel/test-net-server-options.js
- test/parallel/test-net-server-pause-on-connect.js
- test/parallel/test-net-server-reset.js
- test/parallel/test-net-server-simultaneous-accepts-produce-warning-once.js
- test/parallel/test-net-server-try-ports.js
- test/parallel/test-net-server-unref-persistent.js
- test/parallel/test-net-server-unref.js
- test/parallel/test-net-deprecated-setsimultaneousaccepts.js
- test/parallel/test-net-eaddrinuse.js
- test/parallel/test-net-error-twice.js
This PR can be reviewed. Landing is blocked on the pending code review of https://github.com/cloudflare/workerd/pull/1429
Note that this PR is still blocked pending review of https://github.com/cloudflare/workerd/pull/1429