Foundation-Devices/tor

Convert to a Flutter FFI plugin

icota opened this issue ยท 31 comments

icota commented

As per https://codelabs.developers.google.com/codelabs/flutter-ffigen#2

Shoehorning cargo into a cmake/cocoapods build might be a challenge

icota commented

@jeandudey I tested it but I found it an overkill and it doesn't even work for Android. I think for the Linux build it's enough to do vanilla cmake like:

add_custom_target(
        tor_rust
        COMMAND cargo build --manifest-path ../../src/tor-ffi/Cargo.toml
)

add_dependencies(tor tor_rust)

Android should work with: https://github.com/mozilla/rust-android-gradle
iOS: ?
macOS: ? (but probably same as iOS)
Windows: ?????

The CypherStack team managed to get a cargo-built rust plugin working in flutter_libepiccash, so I am referencing that repo while following your first link to try and get this building as a Flutter FFI plugin.

It should work on iOS, Android, linux and mac desktop, and I think I even got it working on Windows (Monero was a blocker for Windows so the plugin mentioned before didn't undergo extensive testing and use on that platform)

I can only test Android and linux and Windows desktop platforms

icota commented

Thanks @sneurlax! We can test on iOS/macOS

OK I have got it building but not tested working on android and linux in https://github.com/sneurlax/flutter_libtor/tree/flutter-plugin

I just renamed the repo to flutter_libtor to match other Stack Wallet plugins for our use because I'll be testing the tor-ffi plugin as part of that, but after getting it working I'll make a branch with the changes necessary so y'all don't have to rename anything on your end and can look over the changes without them being attached with a repo name change

You can follow README.md and run the scripts in eg scripts/android or scripts/linux to test, maybe it will be easier for you to test if either works than it will be for me to get the example app working?

icota commented

Thank you @sneurlax. So with this approach you still need to manually run the script?

I'd prefer for this to build automatically (as long as user has cargo and other dependencies) by triggering cargo (or the scripts themselves) from cmake/cocoapods like in the Flutter FFI template.

Imagine if we publish this on pub.dev and user does an flutter pub add tor. How does one find the script then?

Thank you @sneurlax. So with this approach you still need to manually run the script?

I'd prefer for this to build automatically (as long as user has cargo and other dependencies) by triggering cargo (or the scripts themselves) from cmake/cocoapods like in the Flutter FFI template.

Imagine if we publish this on pub.dev and user does an flutter pub add tor. How does one find the script then?

Yes, you have to manually run the script. That's how CypherStack's plugins are set up. That's a very good point and that makes sense, I'll try to do that now but I'm not familiar with it. If the Flutter FFI template does it, though, I should be able to follow that example and get it building automatically. Not sure if I can get to that today or tomorrow, have something else needing addressing

@jeandudey I tested it but I found it an overkill and it doesn't even work for Android. I think for the Linux build it's enough to do vanilla cmake like:

add_custom_target(
        tor_rust
        COMMAND cargo build --manifest-path ../../src/tor-ffi/Cargo.toml
)

add_dependencies(tor tor_rust)

Android should work with: https://github.com/mozilla/rust-android-gradle iOS: ? macOS: ? (but probably same as iOS) Windows: ?????

I was working along these lines and got linux working, but while addressing android I found https://github.com/fzyzcjy/flutter_rust_bridge and hope it offers a better approach overall

icota commented

Their approach is outlined in https://cjycode.com/flutter_rust_bridge/library/platform_setup.html#how-it-works:

We have a series of build scripts (/scripts/build-*.sh) that build all of our platform specific binaries into /platform-build and package them up appropriately, based on the target platform. Example: on iOS/macOS, this bundle is an XCFramework, on Windows/Linux, it is a .tar.gz.

These binaries are uploaded to somewhere online; as mentioned previously, we will use GitHub releases in this guide (which is automated in ci).

When the Dart tooling builds our library (such as when an application consuming our library is built), it invokes the platform specific build process. We hijack this build process by downloading a copy of the binaries for the needed platform, if not already present on the filesystem. This last part is the key; it allows us to run integration tests locally and in CI by providing our own copy of the binaries instead of forcing our build process to always fetch the binaries from GitHub releases.

After the binaries are stored locally (either by being copied to the proper folder(s) or by fetching them from online), we extract them and place them in the needed locations.

Basically they suggest building the binaries on the CI and uploading somewhere. Flutter plugin build then fetches the binaries from the internet.

I'm not sure if this approach works for us (Foundation Devices) since repoducible builds is one of our aims. With this apporach if the user wanted to verify the build he'd have to build the rust libs himself and then change the code to point to whereever he put those binaries. Seems pretty involved and I'm not even sure if it would work.

@sneurlax give me like a week to retry the trigger-the-script-from-cmake-gradle-cocoa-whatever approach again and I'll report back here.

Executing a custom command in the build process, how hard can it be? (famous last words)

With this apporach if the user wanted to verify the build he'd have to build the rust libs himself and then change the code to point to whereever he put those binaries. Seems pretty involved and I'm not even sure if it would work.

How we are approaching this is that there are two scripts, build and download, basically. We will still default to building it from source, but the download script downloads tagged binaries from github releases. If your builds are reproducible and will produce the same binary as a tagged release, I only see upsides to this approach: you don't have to build, but if you do, you can verify that the output is identical. Reproducibility is still an outstanding issue for us, so not all local builds will match tagged releases, but if yours are reproducible, then downloading and verifying should be just as safe as building locally, right? We're missing that feature but offering downloading binaries as an option for the convenience of higher-level developers that don't want to bother with sometimes-lengthy builds (we build Monero from source... twice, ha!)

As to

wherever he put those binaries

you have the build and download script put their outputs in the same place: wherever they'd go to get built or bundled into your binary, package, etc.

I'm not even sure if it would work

We have been wanting to follow the example of isar, which wraps rust as a dart ffi plugin and is distributed as tagged binaries per-platform similar to the approach described by flutter_rust_bridge... I'm not sure if they've achieved reproducibility, but the number of contributors they have communicates to me that their system for modifying the source and testing the binaries is coherent, at least

Let me know how I can help. For the time being I'm going to test and see how flutter_rust_bridge works if all downloading is disabled and we just build from source every time (that's gotta be possible... right?)

icota commented

After some thinking I'd say you are right.

It's totally okay to have the default be to download - as long as the "power-user" can still build from source locally. As the CI would probably just be GH actions it would be obvious what source the binaries are built from (as long as you trust GitHub - but we all implicitly do here).

Since the approach in your branch is somewhat similar to what flutter_rust_bridge is doing for the build (keeping the native build scripts in a separate folder) I think it makes sense to move in that direction. I won't have the time to tackle this myself in the coming weeks but would be more than happy to assist.

I have closed #3 but maybe it can be used later on, in case we decide to do some cool way of triggering the local build (maybe with build flavours?).

PS I forked this and pushed changes on top of your #ffi-plugin branch pushing things forward a bit. I generated C headers and dart bindings and I think this is ready to be used, but I had trouble testing. I'll have to revisit it. I wanted to see if libtor (vs libtor-sys) would be any better, but it wasn't much better...

Here's another example of a rust plugin done well: https://github.com/LtbLightning/bdk-flutter

I am just trying to use the generated dart bindings now ๐Ÿคž

OK! I got it building and apparently working on a fork of your ffi-plugin branch in the tor branch of stack_wallet. when I use start I see:

flutter: Instance of Tor created!
May 17 17:26:32.655 [notice] Tor 0.4.7.13 running on Linux with Libevent 2.1.12-stable, OpenSSL 1.1.1t, Zlib 1.2.13, Liblzma N/A, Libzstd N/A and Glibc 2.31 as libc.
May 17 17:26:32.655 [notice] Tor can't help you if you use it wrong! Learn how to be safe at https://support.torproject.org/faq/staying-anonymous/
May 17 17:26:32.655 [notice] Read configuration file "/home/user/Documents/tor/torrc".
May 17 17:26:32.656 [notice] Opening Socks listener on 127.0.0.1:64179
May 17 17:26:32.656 [notice] Opened Socks listener connection (ready) on 127.0.0.1:64179
May 17 17:26:32.656 [notice] Opening Control listener on 127.0.0.1:8795
May 17 17:26:32.656 [notice] Opened Control listener connection (ready) on 127.0.0.1:8795

so it seems to be working, but I need to test it better. The scripts need cleaning and they don't run automatically yet. I'd like to polish this package up a bit and maybe get the example app running if that'd be helpful--what would you say are the next steps here?

One thing I notice is that I'm going to need to configure the directory used for tor files, as right now they're put into ~/Documents by default

icota commented

One thing I notice is that I'm going to need to configure the directory used for tor files, as right now they're put into ~/Documents by default

By all means! As long as AppDocumentsDir or whatever we are using right now can stay the default. I don't think most users (especially on mobile) care where Tor files are.

icota commented

what would you say are the next steps here?

Like you said if we could get a simple start/stop Tor example app running that would be grand.

I got the start/stop Tor example up, but am wondering how to actually use it :P

I'll get the example app working vs how I'm demoing use now: cypherstack#2

I'll post here again later when I get the standalone example working

I updated the ffi-plugin branch's flutter example app to show start/stop: https://github.com/sneurlax/tor/blob/ffi-plugin/example/lib/main.dart

I'm looking into usage beyond start/stop, got any tips? will try connecting to daemon as socks5 proxy tomorrow

I still need to make/fix and demonstrate builds for android

also note that I got another Rust-to-Dart example working well in https://github.com/sneurlax/libxmr after having gone through this process

a key takeway is to generate C bindings from Rust, not C++

I connected to it as a normal, authenticated socks5 proxy and successfully showed a Tor exit node ip, so at this point I'm going to work on adding a configuration model

I am having issues with host lookups for .onion services

icota commented

We connect to .onion services through BDK and that "just works" by passing the socks proxy parameters. What client are you using?

I had to make a custom client in order to support sockets over the socks5 proxy: https://github.com/cypherstack/tor/blob/main/example/lib/socks_socket.dart but this doesn't necessarily address .onion lookups; I'll have to look at it again. Right now we're focusing on trying to connect to clearnet nodes through the proxy vs .onion-hosted nodes


@julian-CStack made significant progress removing the requirement for manual or scripted building using cargokit, but an open issue prevents some platforms from working. That code is here: https://github.com/cypherstack/tor/tree/no_scripts_updated_cargokit

but when it works, it's really cool to just flutter run it and go. The author of cargokit has already filed an issue and a fix, so we should eventually be able to remove all the manual setup instructions and scripts alike


Now we need a way to stop the daemon. Got any suggestions for approaching that @icota ? I haven't even really looked into Arti's rust or API since the change, but I'll dive in--just wanted to report progress on removing the manual build steps and replacing scripted setups and see if you had an easy stop method handy

@sneurlax one way to stop the "daemon" would be to set_dormant on TorClient and we could also kill the proxy it is connected to for good measure (they are separate).

Let's investigate and compare notes

Just a small heads-up, Cargokit now has workaround for the pub bug so the symlink is resolved beforehand. I also added github action that builds example project for all possible platforms / configurations to detect regressions like this earlier.

Thank you @knopp!

@icota we have Linux, iOS, and Android working now thanks to @julian-CStack, but macOS uses a precompiled binary right now. I haven't tested Windows yet, but it should work and here's that PR

I'll be testing Windows next but like I said, it should work if you want to give it a try :)

I haven't worked on adding a stop or status function, stopping could save lots of battery so that will probably be next. I'm tracking it in issues over on our fork

@knopp and @julian-CStack helpfully resolved the macOS build issue, so we will make another PR for that once it's polished. I've yet to test Windows, still working on fixing certain aspects of our usage of this plugin

macOS just uses a recompiled binary currently, otherwise this issue could be closed or it could be closed by #17 (which currently has issues to resolve of its own)

#20 solved the macOS precompiled binary issue. So this should be good or close to publishing, right?

The only issue might be the SOCKSSocket class, which I did find has an issue: cypherstack#24

I had to write it custom because many other packages I tried wouldn't work for our use case (raw socket use to connect to ElectrumX). It could and maybe should be spun out as a separate package. We moved it into the main lib dir and exported it so that we wouldn't have to manually sync fixes between repos, rather just using one source for the class, but it probably ought to get published as a standalone package because--as far as I tested--none of the top 6 or so major SOCKS5 Dart packages support HTTP, HTTPS, and socket connections (at least insofar as we needed).

It isn't even needed except for its use in the example app. I had to put a lot of work into that to make sure our use case was covered tho. You could also just remove it and not use it in the example even