-
-
Notifications
You must be signed in to change notification settings - Fork 561
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
Add smoltcp TCP transport #2213
Add smoltcp TCP transport #2213
Conversation
This looks great so far, please don't hesitate to ask if you have any questions! |
757e886
to
a717755
Compare
@conectado thank you for continuing to work on this. |
Thanks 😄 For now things are going quite smoothly, I will start working on making it work with |
@mrinalwadhwa @thomcc I'm in the middle of changing the implementation to support If there's not I think there are a couple of options on how to advance with this:
For now I'll try going with the third option(at least to understand the problem better and decide later) but any kind of tip or insight is appreciated as I don't have any experience with this topic 🙏 |
@antoinevg might have some insight onto the 2nd option, but in general the third option sounds best for now, if possible, since I believe we'd like not to only be able to use this in embedded (even if that is the primary use case). |
I think we could use smoltcp's |
Ah, that would work. |
Everyone will tend to have their own way of handling system time for their particular project so I'd probably try to steer away from making that decision for them. For myself, I usually handle this by defining an atomic integer and then setting a See, for example: I'd be perfectly happy if you just passed smoltcp's timestamp requirement on to the developer. i.e. requiring developers to pass it in via your |
Since I spawn the task to poll the interface myself I could ask for the developer a struct when starting the type MIlliseconds = i64;
trait Clock {
type Instant: Into<Milliseconds>
fn now(&self) -> Self::Instant;
} Or do you think it is better if I let starting the task for polling to the developer too? |
It may also be useful to give the developer the option to do even the polling themselves. e.g. On embedded it's best to poll In the longer run maybe we should even be looking at something like: https://crates.io/crates/embedded-nal All that said, there are so many open questions around async/embedded that I think almost any approach that results in running code is valuable right now because it helps us all understand the space better! |
Alright! Then, I will advance with my current approach, I think adding the possibility for the developer to |
80d574a
to
123f0a9
Compare
d41504c
to
a7afe28
Compare
d35610c
to
b449ea2
Compare
c6b1cd9
to
f2d28c6
Compare
1942f29
to
6e99517
Compare
b36a3ec
to
a7804f1
Compare
f36d0f0
to
6d17559
Compare
7948d14
to
8517611
Compare
8517611
to
4d51a34
Compare
* Changes to support using smoltcp as transport protocol in crate `ockam_transport_smoltcp`. * Includes async interface. * Ability to manually poll. * Necessary implementation of interfaces to run in std using tun/tap interface. * Extract common behavior from smoltcp and std tcp into `ockam_transport_core` in `tcp` module. * Necessary unix socket behavior extracted into traits. * Worker and Route behavior extracted into common modules. * No support for portals and inlet yet.
4d51a34
to
4f089cd
Compare
@conectado awesome work! Thank you ❤️ |
Cargo.lock
Outdated
@@ -204,9 +244,9 @@ dependencies = [ | |||
|
|||
[[package]] | |||
name = "autocfg" | |||
version = "1.1.0" | |||
version = "1.0.1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may need to update the Cargo.lock before merging since this diff bumps several dependency versions down from what is in develop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed here!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey!
Thank you for all the effort you already put into this PR. I left you some comments with minor things I'd like to see changed in the API.
I haven't read all the code yet (it's quite a lot 😅) but this looks very good so far 🙂
let tcp = SmolTcpTransport::<ThreadLocalPortProvider>::create(&ctx, configuration, Some(StdClock)) | ||
.await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any way we can hide that generic parameter here? It's good to have the option to use a different port provider mechanism but most users are not going to care and be confused by what exactly this is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could either create a type alias:
type TcpTransport = SmolTcpTransport<ThreadLocalPortProvider>
So most users would use TcpTransport
and those who care about the port provider use SmolTcpTransport<P>
.
Or we could add a default generic parameter:
struct SmolTcpTransport<P = ThreadLocalPortProvider> { ... }
But that has the problem that users that wanted to use the default would need to do <SmolTcpTransport>::create(...)
.
However, I think that most users that will use this crate would do it in a no_std
context, meaning they will need to provide the port provider. I think that using this in a std
context would be mostly done for testing and examples.
/// TCP address type constant | ||
pub const TCP: u8 = 1; | ||
|
||
pub(crate) const CLUSTER_NAME: &str = "_internals.transport.tcp"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor note: maybe we want to rename this cluster to smoltcp
so that shutdowns between the two tcp stacks (if running in parallel) are independent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done here!
peer_addr: Address, | ||
tcp_stream: R, | ||
cluster_name: &'static str, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to store the cluster_name
again here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was a vestigial change when I took the cluster_name
as a parameter from outside the crate and I needed to store it to use it in the worker's initialize
. But since I changed it back to taking the cluster_name
from outside I need to keep it.
I'll finish reviewing later today, but the length of the lines that have comments is a bit much. I think we try to keep that to around 100 characters. If you use vscode, I use https://marketplace.visualstudio.com/items?itemName=stkb.rewrap to help with this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. This is a stunning PR, really good work.
I can't wait to try this out on embedded!
[license-image]: https://img.shields.io/badge/License-Apache%202.0-green.svg | ||
[license-link]: https://github.com/ockam-network/ockam/blob/HEAD/LICENSE | ||
|
||
[discuss-image]: https://img.shields.io/badge/Discuss-Github%20Discussions-ff70b4.svg | ||
[discuss-link]: https://github.com/ockam-network/ockam/discussions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Please restore the formatting here to keep it consistent with the other crates.
std = [ | ||
"ockam_core/std", | ||
"ockam_node/std", | ||
"tokio", | ||
"tokio/io-util", | ||
"tokio/net", | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree 100% with you that we should try to reduce code duplication as much as possible but, in this particular case, I'd argue against moving the TCP router/workers etc. into ockam_transport_core
.
My reasoning is basically this:
- We've now created a dependency in
ockam_transport_core
onockam_node
andtokio
. [1] This could be problematic as we already have an alternate async executor implementation forno_std
targets (ockam_executor
) and we are also likely to have alternateockam_node
implementations in the near future. - There's likely to be other weirdnesses that come up when we start using
ockam_transport_smoltcp
in anger on bare-metal embedded devices that would require some changes to router/worker that are exclusive toockam_transport_smoltcp
- It's not a lot of code duplication and it's nice to have it living close to the code that will be using it. We now have multiple transports and I don't think we've yet had a case where we had to make big changes to one of their router/worker implementations that needed to be propagated to the others.
[1] Aside: Check out the (admittedly hackish) way we currently wrap tokio
in ockam_node
for no_std
:
And:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I think this is not a problem since
tokio
is an optional dependency that's only included withstd
enabled. I only usetokio
here for the implementations of theAsyncRead
andAsyncWrite
traits onstd
. I could completely remove the dependency by using the newtype pattern to do the implementation inockam_transport_tcp
if that's preferable. - I figured that we can start extracting the behavior as the weirdness appears, that way we maximize the shared behavior.
- Although not particularly a big change while working on this the
heartbeat
was added and by solving conflicts it was automatically enjoyed by the smoltcp crate.
All in all I thought it was a good idea as the behavior they share don't seem coincidental and maybe in the future more protocols could use this worker/router implementation.
Anyways, I understand the points and also the generic implementation along with all the trait bounds make the code much harder to parse so if you still think is better to have each crate with their own worker/router implementation I will go back to that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Valid points all but I do feel like the end result is a bigger increase in overall code complexity than what we're gaining through de-duplication here.
Thoughts @thomcc @SanjoDeundiak @spacekookie ?
// We need alloc here for `async_trait` it's very easy to prevent this by making this clone | ||
// directly inside of `bind` but since `processors` already needs to be an async_trait and | ||
// ockam_core still needs alloc I'll leave this here. | ||
use ockam_core::compat::boxed::Box; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -0,0 +1,222 @@ | |||
//! Traits based in the `futures::io` crate to support `no_std` compilation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for these!
I don't think we're likely to get official ones any time soon: rust-lang/wg-async#23
I'm wondering if these shouldn't live in ockam_core::compat
? @SanjoDeundiak @thomcc @spacekookie ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See nrc/portable-interoperable#5 (and https://www.ncameron.org/blog/async-read-and-write-traits/) for discussion on "official" equivalents.
But in the mean time, these should be in ockam_core::compat, yeah.
[main-ockam-crate-link]: https://crates.io/crates/ockam | ||
[crate-image]: https://img.shields.io/crates/v/ockam_transport_tcp.svg | ||
[crate-link]: https://crates.io/crates/ockam_transport_tcp | ||
[docs-image]: https://docs.rs/ockam_transport_tcp/badge.svg | ||
[docs-link]: https://docs.rs/ockam_transport_tcp | ||
[license-image]: https://img.shields.io/badge/License-Apache%202.0-green.svg | ||
[license-link]: https://github.com/ockam-network/ockam/blob/HEAD/LICENSE | ||
[discuss-image]: https://img.shields.io/badge/Discuss-Github%20Discussions-ff70b4.svg | ||
[discuss-link]: https://github.com/ockam-network/ockam/discussions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: See previous nit on consistency between crates :-)
@@ -0,0 +1,77 @@ | |||
// This is copied verbatim(Some stuff we don't need removed) from [embassy](https://github.com/embassy-rs/embassy/blob/7561fa19348530ce85e2645e0be8801b9b2bbe13/embassy-net/src/packet_pool.rs) | |||
// One advantage of using atomic_pool instead of heapless it doesn't seem to have the same kind of ABA problem. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think we should consider using atomic_pool
to replace heapless
in ockam_core::compat
where we can?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main reason to use atomic_pool
was because embassy
was doing that.
I'm not sure the advantages of heapless
's Treiber stack over atomic_pool
's method of keeping track of available pool using a bitset.
While the advantage of atomic_pool
's method is clear, it is Sync
for all architectures, I'm not sure if it's because atomic_pool
didn't take the same considerations and edge cases into account or the implementation is sound.
Also, atomic_pool
doesn't mention the ABA soundness in x64 problem and by superficially reviewing it I came to the conclusion that it doesn't seem to have that problem since that seems to arise from the Treiber stack using a linked list but I can't confirm it %100.
I will read more on this and get back to you! (Atomics are really complicated 😵💫)
Hi @conectado, do you plan to resume your work at some stage on this PR? Otherwise, since it has derived quite a bit from the current |
Since this branch has diverged we are going to close this PR for now. If anyone wants to resume the work on the smoltcp transport, the branch is available in the ockam repository at |
@conectado Thank you for all the time you put into this 🙏 |
Implementing feature discussed in #1804
Description
This PR includes:
smoltcp
.smoltcp
andstd
implementation intoockam_transport_core
.This PR does NOT include:
smoltcp
.no_std
implementation of theockam_transport_smoltcp
traits needed to run an example in ano_std
device. (Although it does compile inno_std
).Implementation notes
ockam_transport_core::tcp
includes the extracted behavior fromockam_transport_tcp
.worker
are the same workers as they were in the oldockam_transport_tcp
crate but using the new traits to work inno_std
with smoltcp.router
includes the old definition ofrouter
andhandle
as in the oldockam_transport_tcp
, save for the methods used by portals and inlets/outlets, those were extended by a newtype pattern in theockam_transport_tcp
crate.traits
Includes the definition of the behavior needed from tokio's socket to implement the workers(andendpoint_resolver
to resolve endpoints with therouter
). Additionally:io
Includes a defintion of the asyncio
traits, copied fromfutures
, that is compatible withno_std
(plus the necessary extensions), this is needed because neithertokio
norfutures
have ano_std
compatibleAsyncRead
andAsyncWrite
due to their use ofstd::io::Error
.ockam_transport_smoltcp
includes the implementation of the protocol using smoltcp. The surface structure is basically the same asockam_transport_tcp
using aSmolTcpTransport
instead ofTcpTransport
with some differences needed by additional configuration for smoltcp and the new methodget_stack
to obtain the stack for polling manually.port_provider
is needed to abstract the behavior of obtaining a port that can be pretty different in each implementation(For now I decided to make thePortProvider
trait static to be able to hide it inside the smoltcp'sEndpointResolver
and then an implementation can use an static global if it needs to keep track of some state, this way thePortProvider
doesn't exist for the normal tcp implementation).net
here is most of the interesting things for this crate, providing a wrapper forsmoltcp
related things. (A lot of the implementation was based on embassy)stack
provides a tcp stack.StackFacade
provides type safety to make sure the stack is initialized before being used. (Note: I only managed to panic to prevent double initialization any idea to give type safety to prevent this is welcomed!)tcp
Provides an interface similar to tcp's sockets.timer
provide a monotonic clock trait that is needed by the stack(More discussion about this in this PR comments!).device
Provides aDevice
wrapper around smoltcp'sDevice
to handle explicitly the wakers for async. (This module also includes the tuntap implementation)unsafe
unless absolutely needed(only for the tuntap interface), even if it meant having mutexes wrapping other mutexes, so that those can be added in a separate PR.alloc
in the future except when the implementation would be much harder or would requireunsafe
.(I figured it'd be okay requiringalloc
since the whole project requiresalloc
anyways) I tried to leave atodo
comment over each allocation.Device
. Leaving this for a future PR.Test the PR
ockam_transport_smoltcp
compiles inno_std
, even if there is no example doing this:cd implementaitons/rust/ockam/ockam_transport_smoltcp && cargo +nightly check --target thumbv7em-none-eabihf --no-default-features --features="no_std, alloc, pool-32"
Next steps
no_std
implementation of:clock
Device
. Leaving this for a future PR.PortProvider
embedded_nal
as suggested here, this probably would let us replace some of thetraits
inockam_tcp_core
.ockam_tcp_core
and add an implementation for smoltcp.unsafe
sections to prevent wrapping mutexes within mutexes and allocations.Checks