-
Notifications
You must be signed in to change notification settings - Fork 894
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
Project: Support Cronet Go binding #282
Comments
Chromium already has cronet for calling from platforms like android, but it's basically https. I'm not familiar with C/C++, but if you're done, I'll try it. |
Cronet does support bidirectional "sockets". Several years ago when I checked, it was being used from gRPC with odd support status (ios only, or whatever, https://source.chromium.org/chromium/chromium/src/+/main:components/grpc_support/README.md: If you use Cronet's ExperimentalBidirectionalStream:
The more ambitious goal of providing a general Go connection API with better TLS parroting seems better achieved with building a Go FFI with Cronet native API (if it works and is without major restrictions from the original Chromium stack). This requires more work, some design review, and more testing. The less ambitious goal is to provide a specific Go API for running the logic of NaiveProxy inside Go address space without socks5 round trips, as a proxy backend along side other backends. This requires less work and is easier. Recap of NaiveProxyThe changes made to Chromium
Apparently much of Naiveproxy itself can be implemented with less and cleaner Go code. Key changes and uses of the Chromium net stack, summary
|
Cronet's BidirectionalStream:
So the use case BidirectionalStream can support is something like WebSockets, where the origin is represented in other fields. In NaiveProxy both the client intermediary and server intermediary assume HTTP/2 CONNECT tunneling. A proxy system using Cronet's BidirectionalStream won't work with Caddy's forwardproxy (without abusing HTTP headers). TBC |
I managed to find a not very messy way to smuggle the authority in: Pass a Architecturally Cronet and Naiveproxy both use the Chromium net core, but Cronet uses In terms of the API as a product, Cronet is a better option because its API is better maintained. So unless more major restrictions are discovered (e.g. parrot detectors), it's better to use Cronet as basis to build proxy apps. With that said, it's still better to use my fork of Chromium to build Cronet, because the build is minimized and supports OpenWrt and other niceties.
@nekohasekai The following is an example of using Cronet bidirectional stream: naiveproxy-cronet-test-linux-x64.tar.xz from https://github.com/klzgrad/naiveproxy/releases/tag/cronet-test # Note this does not use Chromium's build system
g++ bidi_example.cc libcronet.100.0.4896.60.so
# SSLKEYLOGFILE is saved at /tmp/keys so Wireshark can use it.
LD_LIBRARY_PATH=$PWD ./a.out https://my-caddy-forwardproxy.com "Basic $(printf user:pass | base64)" Remaining questions: |
I have just tried https://gist.github.com/nekohasekai/dda9ba499332433ba7a94f5f3d690a53
Seems to work, but looks like it requires a lot of wrappers to use in go |
This comment was marked as resolved.
This comment was marked as resolved.
I don't know how many is a lot. 100-200 lines of code seem reasonable. There can be something to do on the C side to make things nicer. I can think of this golang/go@918396b |
Unless we have a code generator for that idl, I guess Maybe https://chromium.googlesource.com/chromium/src.git/+/HEAD/mojo/README.md |
Chromium's Mojo does not support Go bindings. I kind of get what you're trying to do. cronet.idl exports 100~200 functions. An automatic binding generator wouldn't produce code as nice as that of manually written, which is not a small undertaking but also achievable with a few days of work. And once written, it's also a complete and general Go binding on its own, for the official Cronet library. I would suggest starting with the important part of the API first so to prove it works, in terms of replacing libnaive.so with native use of Cronet. Even though it can be fun to engage in completionism, as I can imagine, PublicKeyPins is not needed for proving the above concept. |
This comment was marked as resolved.
This comment was marked as resolved.
I changed it to |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Everything works now |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Only bidirectional_stream.go needs LDFLAGS with libcronet. Others don't need it, or they are just adding duplicate ldflags to linker invocation. Static linking:
This produces working but less optimal binaries than libcronet.so (optimized with LTO and other stuff). The optimal ldflags are complicated and platform-dependent. But this produces a single binary compared to two binaries with libcronet.so ( |
Would not recommend binary packing like UPX. It's reinventing something at least, in the linker infra, and may have problems with security measures against self modifying binaries. It could work, but I doubt the aesthetic is worth the effort. Static linking of libcronet but not the rest of the common libraries. E.g. I don't think you can not link with libandroid.so on Android. But as long as the common libraries have no version and availability issues, they can be depended upon with dynamic linking. |
As long as the dynamic linking to cronet is successful, only need to package libcronet.so into apk/lib/arch.
As well gomobile has some problems, it's old and the android part doesn't seem to be maintained anymore. |
The version of aarch64-linux-android21-clang in NDK is 12.0.8 and I don't know how to do static linking |
/* Returns true if the |stream| was successfully started and is now done
* (succeeded, canceled, or failed).
* Returns false if the |stream| stream is not yet started or is in progress.
*/
GRPC_SUPPORT_EXPORT
bool bidirectional_stream_is_done(bidirectional_stream* stream); ld reports bidirectional_stream_is_done not found |
bidirectional_stream_is_done was never implemented chromium/chromium@06eab3e#diff-9bb8e6ea79473fee571b5eb74e0959307330d19e9eb9f5a36cf05a6736e5291bR197 It was derived from CronetBidirectionalStream.isDone() is only used in test cases, so not super useful. I think it can be ignored. |
I don't know how it's used to build Android apps. If the end product is something like libgojni.so in an apk, then using libcronet.so is quite appropriate. Static linking producing single binaries is for releases like this https://github.com/v2fly/v2ray-core/releases. |
Generating a release like v2ray requires disabling CGO, which is not possible with cronet. Maybe need another get-clang.sh |
I don't know much about v2ray build, but I don't see an inherent reason or difficulty why CGO is required to be disabled. According to this guy, v2ray/discussion#754 (comment) CGO makes cross compile difficult. I don't see any specifics of where it is difficult, but there is definitely something. Then it has to showcase a Go app where cross compile works with Cronet CGO. |
Ok, how about this: Use Cronet to recreate naiveproxy in pure Go (except libcronet.so) and see how many platforms Go can build for. As I summarized in "Recap of NaiveProxy" above, I suspect the actual logic of naiveproxy can be expressed in 100-200 lines of code in Go (besides protocol libraries). This should happen in cronet-go repo to see what would happen if a third-party developer tries to use cronet-go for cross compile. |
The answer is only host arch can build, we need cross compiler for each target (and sysroot) |
How to set insecure-concurrency from cronet? |
Drive by comment on "the specifics": CGO has to be used together with Go's own build system, which in turn calls $CC and $LD to build C codes. It also does some insane things (parsing gcc stderr to do compile-time introspection on C In this case, since libcronet has to be built with another build system, as long as we ship one prebuilt .a per arch, most of the above does not directly apply (it should be possible to convince cgo to never call $CC in our package, and lld is mostly a hassle free cross-linker), HOWEVER:
We could try to bundle required dynsym stubs along with prebuilt .a-s, similarly to what Zig does, but it is definitely not trivial. |
What does this mean? There are some struct definitions in bidirectional_stream_c.h, which cronet-go imports. CC would be needed to get its layout, no?
I see, the problem is not creating a perfect cross compile tool chain for building a binary, but instead creating a cross tool chain that can be transitively included with other packages and not conflict with other CGO packages with possibly their own cross tool chain?! It seems cross toolchains don't really coexist with each other, and there has to be one single universal tool chain that handles everything?
This part is a solved problem in naiveproxy. The solution has three parts: a cross compiler (a single clang binary), sysroots creator, the flags to build it. There is some work to put the sysroots in better packaging so easier to use, and extract build flags from chromium for external use. |
Yes. I mean it is possible if we come up with a struct-free FFI. I agree it would be horrible.
The problem is Go developers prefer CGO_ENABLED=0 over installing and configuring a cross toolchain. Even if you make a perfect toolchain, you still need to convince those developers that "v2ray has to be built with CGO_ENABLED=0" is a myth and if they do A then B then C it could work. Remember that Go is its own build system, in this build system, cross toolchains are configured by setting environment variables before invoking And yes, the one toolchain has to be able to build all CGO dependencies of a project. Actually, in 2022 we do have something close to "the perfect cross toolchain" (with a few rough edges), and people do try to use it with cgo: https://dev.to/kristoff/zig-makes-go-cross-compilation-just-work-29ho. Maybe we could promote that. |
I'm just talking about the fact that CGO-enabled projects can't be directly cross-compiled successfully to that many targets, but no myth. There are already projects like xgo that create cross-compilation environments with gcc to help with compilation, but not perfect.
https://ziglang.org/learn/overview/#zig-ships-with-libc It only comes with musl for linux, glibc for linux and windows, apparently not working out of the box for openwrt/darwin/bsd/... By the way, currently static linking libcronet depends on llvm-15 which has not been stably released yet |
Issues related to cross compiling cronet-go should continue in SagerNet/cronet-go#1. This issue is used for tracking the C part of libcronet. Cronet-go is the Go part. In short it's doable because it's only adapting existing cross tool chain in naiveproxy to cgo. There will be a showcase showing how it's done. |
The existing api only provides switches for http2 and quic, while http1 is always enabled. I think we need a mandatory setting, otherwise quic naive client will not work as expected. |
It's not? Bidirectional stream doesn't allow http endpoint. Didn't understand your issue. Please rephrase. |
Sorry, this came up on url request, it doesn't appear to be the problem. (quic seems to almost never be used when using url requests.) |
https://github.com/klzgrad/naiveproxy/releases/tag/cronet-test6 I admit, CGO on Windows is hard. But it's done. The hardest part is this: Chromium uses clang-cl.exe, but CGO officially only supports GCC/MinGW on Windows. See golang/go#17014.
Tested build mode: dynamic executable linking with dynamic cronet library, dynamic executable linking with static cronet library. Not tested build mode: dynamic library linking with static cronet library, static executable linking with static cronet library. Known deficiencies: PIE executables on x86 Ubuntu and OpenWrt (except Android) segfault, so PIE is disabled on those platforms. I still haven't found out why. |
Any interest to support Rust binding in similar way? I'm glad to help or implement them! |
It's a C API. If Rust has C FFI, it can use Cronet. I know nothing about Rust's build system, so you'll have to hook it up with the Cronet build artifacts here yourself. I can help from the C side, including the C/C++ build system. My main concern about a Rust binding is: A Go binding of Cronet allows many Go projects like v2ray-core to use this high fidelity TLS stack with relatively low development cost, so the work has more impact. I don't know if there are many projects that can make use of a Rust binding. |
Actually, at the very first, I'm curious about could we implement an user space congestion algorithm (eg: BBR) for QUIC protocol to Credit: Inspired by tuic |
Fun is a enough reason. Feel free to go ahead, and report issues here if any. Cronet extensions are doable. Post it if you need any. |
Considered done. |
Any Go developers who are interested in using the core of naiveproxy in Go natively, please see my proposal and comment below:
The core of naiveproxy is namely Chromium's net stack. It's possible to expose its interface in the style of BSD socket in a library libnaive.so or libnaive.a (or more accurately libchromiumsocket.a), which then could be hooked up with a Go program via FFI. The benefit of doing this: no socks5 round trip overhead for using Chromium's net stack, and more versatile to link directly with Go's net stacks at socket/connection level. The impact of doing this: a better TLS fingerprint parrot than utls with similar Go development friendliness.
To achieve this goal:
Do you @nekohasekai think it's better to use Cronet from Java or Go?I'll take it as a yes.I'm considering finalizing it toCurrently it's decided as-connect-authority-override
in the next version. Really hard to name this one.-connect-authority
, asconnect
sufficiently defines the scope of this header, the leading-
denotes its internal nature, and the suffix-override
doesn't seem absolutely necessary and the lack of it doesn't introduce confusion.-network-isolation-key
header to smuggle it in and configure stream isolation.The text was updated successfully, but these errors were encountered: