Skip to content
This repository has been archived by the owner on Feb 26, 2021. It is now read-only.

refactor: async await #14

Merged
merged 25 commits into from
Mar 6, 2020
Merged

refactor: async await #14

merged 25 commits into from
Mar 6, 2020

Conversation

vasco-santos
Copy link
Member

@vasco-santos vasco-santos commented Jan 8, 2020

This PR refactors libp2p-stardust and its server to have an async await API, which is compliant with the new interface-transport, interface-connection and interface-peer-discovery.

In addition, the stardust server was also completely refactor to work with the new transport.

There is some stuff that I would like to be different in this module, such as what I described in the next comment. However, I think that we should move that into a new issue to be considered later if relevant.

Needs:

  • Update docs
  • Connection encryption redundancy

Follow up tasks (create issues):

  • interface-transports tests should be refactored to be used in this module (as well as for circuit relay)
  • new protobuf definition?

@vasco-santos vasco-santos force-pushed the refactor/async-await branch 9 times, most recently from 129da5e to cc3366d Compare January 9, 2020 13:09
alanshaw pushed a commit to ipfs/js-ipfs that referenced this pull request Feb 7, 2020
[`libp2p-websocket-star`](libp2p/js-libp2p-websocket-star#57) has a security vulnerability and was not converted in the refactor. This PR removes the `libp2p-websocket-star` information until libp2p/js-libp2p-stardust#14 lands and we can recommend using it instead.

refs #2600
alanshaw pushed a commit to ipfs/js-ipfs that referenced this pull request Feb 7, 2020
[`libp2p-websocket-star`](libp2p/js-libp2p-websocket-star#57) has a security vulnerability and was not converted in the refactor. This PR removes the `libp2p-websocket-star` information until libp2p/js-libp2p-stardust#14 lands and we can recommend using it instead.

refs #2600
alanshaw pushed a commit to ipfs/js-ipfs that referenced this pull request Feb 7, 2020
[`libp2p-websocket-star`](libp2p/js-libp2p-websocket-star#57) has a security vulnerability and was not converted in the refactor. This PR removes the `libp2p-websocket-star` information until libp2p/js-libp2p-stardust#14 lands and we can recommend using it instead.

refs #2600
@vasco-santos
Copy link
Member Author

@mkg20001 @jacobheun

I currently have the register and discovery flows working with the new js-libp2p dialer + new transport/discovery interfaces. I am currently working on the dial flow.

Since starting to tackle this, I was thinking on suggesting that we change the naming of the messages in a more meaningful way. However, I wanted to discuss this with both of you after getting an initial version working.

Taking into account the current state for the dial flow, I got to a point when I really want to change the protobuf definition.

  1. Have a single top level messages instead of a bunch of different ones. Considering for example libp2p-circuit, or libp2p-daemon. This way, we should have a Stardust message which includes all the other messages inside, like the daemon example. The main advantage of this consists on being easy to identify the type of the message from the start, which is particularly nice when the server needs to understand if a message from a new stream should go to the register or dial flows, without needing to decode and inspect the content of the message to infer its type.

  2. Simplify the messages naming and parameters:

JoinInit {random128, peerId} => Register { data, peerId }
JoinChallenge { error, saltEncrypted } => Challenge { data } / Error { msg }
JoinChallengeSolution { solution } => Solution { data }
JoinVerify { error } => Ok { } / Error { msg }

Discovery { ids } => Discovery { ids }
DiscoveryAck { } => Ok { }

DIalRequest { target } => Dial { }
DialResponse { } => Ok { }

I will start working on changing the first part while I wait for your feedback, as it will ease a lot the implementation of the dial flow. Anyway, I would like feedback from you on both this points before getting this ready to review.

@vasco-santos
Copy link
Member Author

FYI, I did not do the protobuf changes above, and I think that I will pass them into an issue to be discussed, instead of being included in this PR.

@mkg20001
Copy link
Member

mkg20001 commented Feb 11, 2020

My reasoning for not wanting to use a single message is that it doesn't matter if you put in a valid but wrongly typed message, it's still invalid. Except there's more stuff to check for the fields existing. And more code to be executed per parsed message since it needs to parse all the other non-existing messages aswell.
And more code is more bugs. So unless there would be any advantage for the actual code itself, let's just not add complexity for no reason.

Edit: Also the type is explicitly specified by the order, so no checking required.

Copy link
Member

@mkg20001 mkg20001 left a comment

Choose a reason for hiding this comment

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

Not sold on using an extra SECIO layer, due to e2e-secio and wss being assumed

README.md Show resolved Hide resolved
src/index.js Outdated Show resolved Hide resolved
* @param {Array<multiaddr>} options.addresses
* @param {Array<Transport>} options.transports
* @param {Array<Multiplexer>} options.muxers
* @param {Array<Encryption>} options.encryption
Copy link
Member

Choose a reason for hiding this comment

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

The idea was to use stardust over wss:// protocol, making it encrypted already, and dropping the extra secio layer.
Connections between peers are always encrypted end-to-end encrypted and authed by secio, so even plaintext stardust isn't posing a security risk (aside from allowing praying eyes to view metadata)

Since it was planned to be used over wss:// the SECIO layer will only slow things down.

Copy link
Member Author

Choose a reason for hiding this comment

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

@mkg20001 yeah, I know that not using the extra encryption layer was one the ideas here. I focused on getting this to work with the new libp2p api first. Now that things are getting shaped, I will understand if we will be able to not use the SECIO layer

Copy link
Member

Choose a reason for hiding this comment

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

Ok

Possibly it could just use a switch with plaintext, otherwise making a new micro-switch would also be possible should the libp2p switch be either too complicated to run without a swarm or cause significant performance impacts

We should also look out to prevent the user from having to enter an id, even if it's just a placeholder for plaintext-crypto. If not possible to remove from the switch a fake id could always be appended by the listener in that case.

Copy link
Member Author

Choose a reason for hiding this comment

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

We should make the libp2p dialer working for all types of use cases, so I think that we should not go on creating a new "switch" to get this and understand what can be done without compromising security. In this way, we cannot use the Plaintext crypto protocol as peers would have to add it to use Stardust 🙅‍♂

We can potentially have an upgrader here, that would basically allow us to upgrade the connection manually instead of using the provided upgrader (this would an improvement over the micro-switch idea).

I was discussing this with @jacobheun earlier today and he pointed out an important aspect. Our websocket transport is relying on the underlying ws module to verify if the data is encrypted and it is not considering wss / ws. This creates a risk for this.

We should probably think a bit about the alternatives that we have and understand what will be the best decision.

Copy link
Member

Choose a reason for hiding this comment

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

We can potentially have an upgrader here,

Sounds good

ws module to verify if the data is encrypted and it is not considering wss / ws. This creates a risk for this.

As I already mentioned earlier the actual p2p connections are encrypted and authed by SECIO

What is possible with using a non-encrypted stardust connection is just DOSing the client with false discovery with a MITM information (this would only work if a MITM would happen and additionally would create at most a minor inconvenience) and the leakage of some metadata (who talks to whom) which would be leaked with regular TCP anyways as the SECIO-handshake is unencrypted.

Stardust is also mainly intended for websites that can't directly dial to peers so the website is most likely using HTTPS which means that we're not able to use unencrypted ws

So unless I'm missing something it appears to be a wrong assumption that this creates a problem.

Though checking if the underlying connection is ws or wss isn't hard as the transport is the one that's dialing so it should be enough to match it the multiaddr in question uses ws or wss

Copy link
Member

Choose a reason for hiding this comment

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

So if we insist on encryption we could do:

  • if ws: custom upgrader with SECIO (or user-specified) as only crypto
  • if wss: custom upgrader with plaintext as only crypto

The server would use both plaintext and SECIO then

Copy link
Contributor

Choose a reason for hiding this comment

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

One thing we need to keep in mind is that stardust allows for any reliable address, which includes tcp, webrtc, http, etc. We are also not even checking that, just that the stardust multiaddr codec exists in the address. If we remove the potential additional encryption layer, we should be more strict about the addresses we allow. As @mkg20001 mentioned, the general use case is websockets in browser, which will mandate wss. However, I don't see anything in the dialer/listener preventing me from connecting to an insecure tcp stardust server if I discover the address, but as already mentioned it's a low threat attack vector.

We could enforce wss and use plaintext, but the fact that stardust potentially supports any reliable multiaddr is pretty nice. If we want to support broader use cases like tcp > stardust > websockets broadly using secio to reduce the complexity might be worth the throughput hit. If we just care about wss > stardust > websockets, I think we can enforce that and reasonably use a plaintext upgrader.

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 suggest .that we take this out of the scope of the refactor PR and discuss this on an issue. I think we should be able to provide options to the transport/server that would allow the users to operate with both of those cases. What do you think? Can you create an issue @mkg20001 ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be reasonable to take the double encryption hit for now and do a followup release for performance tuning for transports with built in encryption. It's not ideal but mitigates any risk/complexity of multiple reliable multiaddrs and gets the transport usable for folks.

src/server/index.js Outdated Show resolved Hide resolved
src/server/index.js Outdated Show resolved Hide resolved
Copy link
Contributor

@jacobheun jacobheun left a comment

Choose a reason for hiding this comment

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

Just two minor things. I think once we merged this it would be good to do an RC and test it out in js-ipfs before doing a final release.

src/listener.js Outdated

// close stream and connection with the server
const wrappedStream = this.wrappedStream.unwrap()
wrappedStream.sink([])
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be unnecessary. The call below to this.serverConnection.close() is terminating the entire connection, so this.wrappedStream, which is a stream of that connection should automatically terminate.

Also, just a note, wrappedStream.sink([]) ONLY closes the write side of the stream, the read would still be open. source and sink must both be closed. We should document this better in the libp2p and connection interface docs, with examples of how to properly close streams to help people avoid halfopen streams.

Copy link
Member Author

Choose a reason for hiding this comment

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

We should document this better in the libp2p and connection interface docs, with examples of how to properly close streams to help people avoid halfopen streams.

Yeah, it makes sense

}, conn => pipe(conn, conn)))
await Promise.all(listeners.map(listener => listener.listen(SERVER_URL)))

await pWaitFor(() => discovered.length >= 3)
Copy link
Contributor

Choose a reason for hiding this comment

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

We should validate the discovered peer list after the wait for. Also, it might be good to make discovered a key:peerId Map to avoid discovered being larger than 3. We should not find more than 3 peers, the excess is likely due to "rediscovering" peers we already know.

@vasco-santos vasco-santos requested review from jacobheun, mkalinin and mkg20001 and removed request for mkalinin and mkg20001 March 5, 2020 14:08
Copy link
Member

@mkg20001 mkg20001 left a comment

Choose a reason for hiding this comment

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

Nitpicks mostly, otherwise good to go

README.md Outdated Show resolved Hide resolved
src/index.js Show resolved Hide resolved
src/listener.js Outdated Show resolved Hide resolved
src/server/bin.js Show resolved Hide resolved
src/server/index.js Outdated Show resolved Hide resolved
@vasco-santos
Copy link
Member Author

@mkg20001 and @jacobheun made a few changes to this. Listen should take into account multiaddr like /ip4/0.0.0.0/tcp/5892/ws/p2p-stardust/p2p/${stardustServerId}/p2p/${ownId}.

Also tested this with browser examples of js-libp2p and js-ipfs

Copy link
Member

@mkg20001 mkg20001 left a comment

Choose a reason for hiding this comment

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

Last batch of nitpicks to apply, after that LGTM

src/server/index.js Outdated Show resolved Hide resolved
src/utils.js Outdated Show resolved Hide resolved
Co-Authored-By: Maciej Krüger <mkg20001@gmail.com>
@vasco-santos
Copy link
Member Author

As @jacobheun suggested, we should probably release this as a rc while the examples are reviewed. What do you think @mkg20001 ?

Copy link
Contributor

@jacobheun jacobheun left a comment

Choose a reason for hiding this comment

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

LGTM 🚀

@mkg20001
Copy link
Member

mkg20001 commented Mar 6, 2020

@vasco-santos ok

Since I'm lead I should merge this now? (Haven't done a merge in eons)

@vasco-santos
Copy link
Member Author

Since I'm lead I should merge this now? (Haven't done a merge in eons)

Yeah, go for it 🚢

For releasing, you can have a look at ipfs/aegir/#releasing. There is an example for the pre release! 🙂

@mkg20001 mkg20001 merged commit f7393db into master Mar 6, 2020
@mkg20001 mkg20001 deleted the refactor/async-await branch March 6, 2020 18:23
@vasco-santos
Copy link
Member Author

You should just need to run:

npm run release -- --type prerelease --preid rc --dist-tag next

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants