Skip to content
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

TLSCallbacks => TLSWrap, better TLS inception #840

Closed
wants to merge 41 commits into from

Conversation

indutny
Copy link
Member

@indutny indutny commented Feb 13, 2015

Ohai @iojs/crypto @iojs/streams .

This is very important thing for both of you! Please take a look at it, even if you don't think that you should actually review it.

Since introduction of TLSWrap concept and _tls_wrap.js/_tls_legacy.js files, there was a big concept gap that needed to be filled. The TLSSocket itself is parasiting (consuming) the original TCP socket, making the reads from the raw TCP socket go directly into OpenSSL, thus improving the performance significantly.

However, there was a major drawbacks:

  • the original socket is unusable after creation of TLSSocket
  • the original socket is replaced by TLSSocket thing
  • no way to wrap non-TCP/non-Pipe stream with TLSSocket
  • and so no way to wrap TLSSocket in TLSSocket (see test/parallel/test-tls-inception.js)
  • no way to start TLSSocket on plain javascript stream (this is why I've cc'ed @iojs/streams )

This PR should solve first 4 issues, and a follow-up will solve the last one. The main idea of a fix is to separate concept of StreamWrap C++ class from the HandleWrap class. I.e. make C++ streams independent of internal libuv socket/pipe. Next step that it does is a TLSWrap C++ stream that consumes the input of the input stream and proxies writes to itself to the underlying TCP stream (performing encryption and TLS protocol on this way).

4th step will be fixed by introducing JSStreamWrap C++ class, which will emulate C++ input events from JS-land.

Please take a look and review!

Thank you.

@indutny
Copy link
Member Author

indutny commented Feb 13, 2015

@indutny
Copy link
Member Author

indutny commented Feb 13, 2015

@bnoordhuis : would be cool to land ASAP, as it'll be impossible to rebase in case of major conflicts :)

@indutny
Copy link
Member Author

indutny commented Feb 13, 2015

The interesting question is what semver tag should it have. Technically, there are some differences regarding the this.ssl => this._handle move (though, it is undocumented and internal), and could be some possible behavior changes. But I think we could address it as a bugfixes.

What are your thoughts, semver-patch/semver-minor/semver-major?

@indutny
Copy link
Member Author

indutny commented Feb 13, 2015

One more thing! There are some C++ lint issues, but I think they are mostly a bugs of cpplint itself.

@wraithan
Copy link

I was using this.ssl when hunting the memory leak in 0.10 tls. But it wasn't for anything that I'd actually release just for debugging. I after reading through all of the tls code I don't imagine anyone else is using it and it makes this interface more consistent with others.

@indutny
Copy link
Member Author

indutny commented Feb 13, 2015

@wraithan I'm going to create a legacy property for this purpose right now. Thanks!

@indutny
Copy link
Member Author

indutny commented Feb 13, 2015

@wraithan - done

}
socket.once('connect', onHandle);
}
socket = new TLSSocket(options.socket, {

Choose a reason for hiding this comment

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

there a reason this isn't just var socket = instead of forward declaring it 2 lines above? Same for var result which could be collapsed

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed!

@Fishrock123 Fishrock123 added https Issues or PRs related to the https subsystem. stream Issues and PRs related to the stream subsystem. crypto Issues and PRs related to the crypto subsystem. labels Feb 13, 2015
@@ -437,49 +478,56 @@ TLSSocket.prototype._finishInit = function() {
};

TLSSocket.prototype._start = function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible that the once below could set multiple listeners for connect, causing _start to be called >1?

Copy link
Member Author

Choose a reason for hiding this comment

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

Start is called only once, otherwise assertion will happen in C++.

@indutny
Copy link
Member Author

indutny commented Feb 13, 2015

@trevnorris mind helping with benchmark it against current v1.x?

// Update handle if it was wrapped
handle = self._handle;
// TODO(indutny): assert that the handle is actually an ancestor of old one
// assert(handle === self._handle, 'handle != self._handle');
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the following diagram encapsulate the behavior after wrapping?

         A             B             C
Readable -> net.Socket -> TLSWrap    -> TCPWrap
TCPWrap  -> TLSWrap    -> net.Socket -> Writable
         D             E             F

A: cleartext bytes
B: cleartext bytes
C: encrypted bytes
D: encrypted bytes
E: cleartext bytes
F: cleartext bytes

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the Readble/Writable should be swapped with this direction of arrows, but generally this is correct.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm using Readable as "userland readable stream" – so bytes written to the socket will always hit TLSWrap on egress.

@rvagg
Copy link
Member

rvagg commented Feb 13, 2015

any numbers on performance for this?

// Determine storage size first
size_t storage_size = 0;
for (size_t i = 0; i < count; i++) {
Handle<Value> chunk = chunks->Get(i * 2);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why use chunks->Length() >> 1 above but i * 2 here (instead of i << 1)?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a question for a separate issue, I was only migrating old code base here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Cool, just wanted to make sure I wasn't missing something.

@indutny
Copy link
Member Author

indutny commented Feb 13, 2015

@rvagg: @trevnorris is much better at this than I am, hopefully he will help us :)

@chrisdickinson
Copy link
Contributor

I read through it – it looks good to me, though I'd like to get a more experienced C++ reviewer's eyes on it. I confess I'm having a bit of a hard time conceptualizing the entire route from "write to TLSSocket in JS" to "write bytes to the network", and vice versa.

If this works like I think it does, would it be possible to, for example, make the zlib transform streams nested in a similar fashion (by doing the compressing/decompressing work in the uv threadpool)? Or for a perhaps sillier point, to pass a duplex stream representing a FIFO into TLSSocket from JS?

@indutny
Copy link
Member Author

indutny commented Feb 14, 2015

Yes, though, you won't be able to do zlib compress in the same uv_work_t call. It will be two separate thread pool requests.

@indutny
Copy link
Member Author

indutny commented Feb 14, 2015

@chrisdickinson thanks for taking a look!

@indutny
Copy link
Member Author

indutny commented Feb 14, 2015

@rvagg: benchmarks appears to be unaffected, which is great!

~/Code/indutny/node $ ./iojs benchmark/net/net-s2c.js
net/net-s2c.js len=102400 type=utf dur=5: 6.26616
net/net-s2c.js len=102400 type=asc dur=5: 10.68418
net/net-s2c.js len=102400 type=buf dur=5: 12.35287
net/net-s2c.js len=16777216 type=utf dur=5: 5.78789
net/net-s2c.js len=16777216 type=asc dur=5: 9.41214
net/net-s2c.js len=16777216 type=buf dur=5: 13.59318
~/Code/indutny/node $ iojs benchmark/net/net-s2c.js
net/net-s2c.js len=102400 type=utf dur=5: 6.33293
net/net-s2c.js len=102400 type=asc dur=5: 11.07404
net/net-s2c.js len=102400 type=buf dur=5: 12.34970
net/net-s2c.js len=16777216 type=utf dur=5: 5.66070
net/net-s2c.js len=16777216 type=asc dur=5: 9.64388
net/net-s2c.js len=16777216 type=buf dur=5: 13.89736

@indutny
Copy link
Member Author

indutny commented Feb 15, 2015

Another idea. What about exposing this API and adding a "standard" way to wrap stream?

res[name] = function methodProxy() {
return handle[name].apply(handle, arguments);
};
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Not in love with this solution, but too lazy right now to suggest a better one.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can't say that I like it either. But the thing is that somehow HandleWrap, StreamWrap methods should be called. This definitely better be abstracted in JS.

Copy link

Choose a reason for hiding this comment

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

Will this not leak arguments, making optimization impossible?

Copy link
Member Author

Choose a reason for hiding this comment

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

This usually applies to close and connect methods. Nothing performance critical.

@indutny
Copy link
Member Author

indutny commented Feb 21, 2015

@trevnorris thank you!

@bnoordhuis going to push it in a couple of days. I have already rebased it twice with merge conflicts :(

@indutny
Copy link
Member Author

indutny commented Feb 22, 2015

Guess it is time to land it :)

indutny added a commit that referenced this pull request Feb 22, 2015
StreamBase is an improved way to write C++ streams. The class itself is
for separting `StreamWrap` (with the methods like `.writeAsciiString`,
`.writeBuffer`, `.writev`, etc) from the `HandleWrap` class, making
possible to write abstract C++ streams that are not bound to any uv
socket.

The following methods are important part of the abstraction (which
mimics libuv's stream API):

* Events:
  * `OnAlloc(size_t size, uv_buf_t*)`
  * `OnRead(ssize_t nread, const uv_buf_t*, uv_handle_type pending)`
  * `OnAfterWrite(WriteWrap*)`
* Wrappers:
  * `DoShutdown(ShutdownWrap*)`
  * `DoTryWrite(uv_buf_t** bufs, size_t* count)`
  * `DoWrite(WriteWrap*, uv_buf_t*, size_t count, uv_stream_t* handle)`
  * `Error()`
  * `ClearError()`

The implementation should provide all of these methods, thus providing
the access to the underlying resource (be it uv handle, TLS socket, or
anything else).

A C++ stream may consume the input of another stream by replacing the
event callbacks and proxying the writes. This kind of API is actually
used now for the TLSWrap implementation, making it possible to wrap TLS
stream into another TLS stream. Thus legacy API calls are no longer
required in `_tls_wrap.js`.

PR-URL: #840
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
@indutny
Copy link
Member Author

indutny commented Feb 22, 2015

Landed in b968623, thank you everyone! @bnoordhuis if you have any comments - feel free to open issue for them, I'll fix everything or we'll figure out how to do it best.

@indutny indutny closed this Feb 22, 2015
@indutny indutny deleted the feature/stream-resource branch February 22, 2015 20:16
This was referenced Feb 23, 2015
@rvagg rvagg mentioned this pull request Feb 28, 2015
rvagg added a commit that referenced this pull request Feb 28, 2015
Notable changes:

* tls: A typo introduced in the TLSWrap changes in
  #840 only encountered as a bug on
  Windows was not caught by the io.js CI system due to problems with the
  Windows build script and the Windows CI slave configuration, see
  Fixed in #994 &
  #1004 (Fedor Indutny)
* npm: Upgrade npm to 2.6.1. See
  https://github.com/npm/npm/blob/master/CHANGELOG.md#v260-2015-02-12
  for details.
* Add new collaborators:
  - Robert Kowalski (@robertkowalski)
  - Christian Vaagland Tellnes (@tellnes)
  - Brian White (@mscdex)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
crypto Issues and PRs related to the crypto subsystem. https Issues or PRs related to the https subsystem. stream Issues and PRs related to the stream subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants