Skip to content

fix tcp socket stream destruction #126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 25, 2022
Merged

fix tcp socket stream destruction #126

merged 4 commits into from
Jul 25, 2022

Conversation

markkuhn
Copy link
Contributor

@markkuhn markkuhn commented Jul 21, 2022

Issue #, if available: #120

Description of changes:

Changes made to how the socket initially connects to its endpoint.

As per Nodejs documentation, "[socket.connect()] should only be used for reconnecting a socket after 'close' has been emitted or otherwise it may lead to undefined behavior".
The previous version was at times calling socket.connect() without the close event being emitted. This would sometimes try to connect to a socket that is already connected (EISCONN) and would destroy the stream. Additionally, the writable property in net.Socket() was removed since it was causing socket streams on node >=v15.3.0 to start as readOnly and with an additional connect() attempt would lead to undefined behaviour.

To avoid the same issues pre 0034ce1, an async init() method within the constructor was added to initialize the connection once a TCP Socket is created.

Additionally, fixed critical, high and moderate severity dependency related security alerts: #124, CVE-2021-43138, CVE-2021-3807, CVE-2021-3918, CVE-2021-44906, CVE-2021-3777, CVE-2021-23343, CVE-2020-28469

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@markkuhn
Copy link
Contributor Author

Initial build failed due to outdated CodeBuild environment image.

@markkuhn markkuhn requested a review from Himtanaya July 22, 2022 20:54

public async init(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.socket.connect(this.endpoint.port, this.endpoint.host, (err?: Error) => {

Choose a reason for hiding this comment

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

So, if I understand this correctly, by doing an early connect attempt here we cover the case where the socket was already connected. We just throw so that the next connect can work? How exactly will this guarantee that close is emitted before we try to connect next?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to get rid of the writable: false socket property which was causing the socket to be in readOnly mode for newer node versions, but doing that brings back this issue. To avoid that we need to connect to the socket after creation as recommended by the node docs.

There are 5 states the socket can be in: open, opening, readOnly, writeOnly and closed.

  • The open and opening states are already being handled by the code.
  • The readOnly and writeOnly states won't happen since we aren't setting the socket to readable or writable anywhere.
  • The closed state calls connect() or on error calls disconnect() and then tries to reconnect.

.setEncoding('utf8')
.setKeepAlive(true)
.setTimeout(5000) // idle timeout
.on('timeout', () => this.disconnect('idle timeout'))
.on('end', () => this.disconnect('end'))
.on('data', data => LOG('TcpClient received data.', data));
this.init.apply(this);

Choose a reason for hiding this comment

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

Having init() & warmup() adds unclarity on what these methods are doing.

It seems a bit surprising that the socket would start making calls in the constructor, before the object is even fully constructed and returned.
Would making these changes inside warmup() be enough to catch this edge case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree that it might be confusing to have those two functions there, but putting the initial connection in warmup() won't be possible since getSocketClient() which is calling warmup() is a synchronous function that cannot be changed to an asynchronous one (called in AgentSink constructor).

I could rename init() to firstConnect(), initialConnect(), createConnection() or something similar for more clarity.

Copy link

@giancarlokc giancarlokc Jul 25, 2022

Choose a reason for hiding this comment

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

As discussed, doing in the constructor is not optimal but it's an stablished pattern in this repo. So not a big issue.
firstConnect() or initialConnect() is better, as long as there's a comment explaining why it's needed.

@markkuhn markkuhn self-assigned this Jul 25, 2022
@markkuhn markkuhn merged commit 698b515 into awslabs:master Jul 25, 2022
@markkuhn markkuhn linked an issue Sep 18, 2022 that may be closed by this pull request
@markkuhn markkuhn added the bug Something isn't working label Sep 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

"Cannot call write after a stream was destroyed" on ECS
3 participants