From 717305cf77e90a852a2945ecc5dae9d114f3deb6 Mon Sep 17 00:00:00 2001 From: Mikael Magnusson Date: Sat, 20 Nov 2021 20:03:12 +0100 Subject: tunnel: add gRPC over unix domain socket to the go backend With gRPC it will be easier to extend the go backend API. In this commit the Version function is reimplemented in gRPC. Gitignore generated protobuf files. --- gradle/libs.versions.toml | 10 +++ tunnel/build.gradle.kts | 58 +++++++++++++++++ .../com/wireguard/android/backend/GoBackend.java | 28 +++++++- tunnel/src/main/proto/libwg.proto | 27 ++++++++ tunnel/tools/libwg-go/.gitignore | 3 +- tunnel/tools/libwg-go/Makefile | 27 +++++++- tunnel/tools/libwg-go/api-android.go | 29 +++++++-- tunnel/tools/libwg-go/jni.c | 13 ++++ tunnel/tools/libwg-go/service.go | 76 ++++++++++++++++++++++ 9 files changed, 264 insertions(+), 7 deletions(-) create mode 100644 tunnel/src/main/proto/libwg.proto create mode 100644 tunnel/tools/libwg-go/service.go diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d8527046..a6ca2b9d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,10 @@ [versions] agp = "8.2.0-beta06" +grpc = "1.55.1" kotlin = "1.9.0" +protobuf = "0.9.3" +protoc = "3.22.4" +protocgengrpc = '1.55.1' [libraries] androidx-activity-ktx = "androidx.activity:activity-ktx:1.8.0" @@ -17,6 +21,11 @@ androidx-lifecycle-runtime-ktx = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2 androidx-preference-ktx = "androidx.preference:preference-ktx:1.2.1" desugarJdkLibs = "com.android.tools:desugar_jdk_libs:2.0.3" google-material = "com.google.android.material:material:1.10.0" +grpc-android = "io.grpc:grpc-android:1.55.1" +grpc-okhttp = "io.grpc:grpc-okhttp:1.55.1" +grpc-protobuf-lite = "io.grpc:grpc-protobuf-lite:1.55.1" +grpc-stub = "io.grpc:grpc-stub:1.55.1" +javax-annotation-api = "javax.annotation:javax.annotation-api:1.3.2" jsr305 = "com.google.code.findbugs:jsr305:3.0.2" junit = "junit:junit:4.13.2" kotlinx-coroutines-android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0" @@ -25,5 +34,6 @@ zxing-android-embedded = "com.journeyapps:zxing-android-embedded:4.3.0" [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } +google-protobuf = { id = "com.google.protobuf", version.ref = "protobuf" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } diff --git a/tunnel/build.gradle.kts b/tunnel/build.gradle.kts index 589d72da..6793c3ce 100644 --- a/tunnel/build.gradle.kts +++ b/tunnel/build.gradle.kts @@ -1,5 +1,6 @@ @file:Suppress("UnstableApiUsage") +import com.google.protobuf.gradle.* import org.gradle.api.tasks.testing.logging.TestLogEvent val pkg: String = providers.gradleProperty("wireguardPackageName").get() @@ -7,6 +8,7 @@ val pkg: String = providers.gradleProperty("wireguardPackageName").get() plugins { alias(libs.plugins.android.library) `maven-publish` + alias(libs.plugins.google.protobuf) signing } @@ -67,10 +69,66 @@ android { dependencies { implementation(libs.androidx.annotation) implementation(libs.androidx.collection) + implementation(libs.grpc.android) + implementation(libs.grpc.okhttp) + implementation(libs.grpc.protobuf.lite) + implementation(libs.grpc.stub) + compileOnly(libs.javax.annotation.api) compileOnly(libs.jsr305) testImplementation(libs.junit) } +protobuf { + protoc { + // The artifact spec for the Protobuf Compiler + artifact = "com.google.protobuf:protoc:${libs.versions.protoc.get()}" + } + plugins { + // Optional: an artifact spec for a protoc plugin, with "grpc" as + // the identifier, which can be referred to in the "plugins" + // container of the "generateProtoTasks" closure. + id("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:${libs.versions.protocgengrpc.get()}" + } + id("java") { + } + } + generateProtoTasks { + all().forEach { task -> + task.plugins{ + id("grpc") { + option("lite") + } + id("java") { + option("lite") + } + } + } + } +} + +afterEvaluate({ -> + // All custom configurations created by the protobuf plugin, + // are only available at this point. + var protoc = configurations.named("protobufToolsLocator_protoc") + var protocCache = "${gradle.gradleUserHomeDir}/caches/protoc-bin-${libs.versions.protoc.get()}" + + tasks.register("copyProtoc", Copy::class) { + // Used by tunnel/tools/libwg-go/Makefile run in tools/CMakeLists.txt + from(protoc) + into(protocCache) + rename("protoc-.*", "protoc") + setFileMode(7 * 64 + 7 * 8 + 5) // 0775 + } + + tasks.named("preBuild").get().dependsOn("copyProtoc") + + // Extract duration.proto used by external library in libwg.proto + tasks.named("preDebugBuild").get().dependsOn(tasks.named("extractIncludeDebugProto").get()) + tasks.named("preReleaseBuild").get().dependsOn(tasks.named("extractIncludeReleaseProto").get()) + println("done afterEvaluate") +}) + publishing { publications { register("release") { diff --git a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java index 6af9eb7e..79c362b4 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -9,6 +9,7 @@ import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.LinkProperties; +import android.net.LocalSocketAddress; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; @@ -18,8 +19,15 @@ import android.os.ParcelFileDescriptor; import android.system.OsConstants; import android.util.Log; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Empty; + import com.wireguard.android.backend.BackendException.Reason; import com.wireguard.android.backend.Tunnel.State; +import com.wireguard.android.backend.gen.LibwgGrpc; +import com.wireguard.android.backend.gen.VersionRequest; +import com.wireguard.android.backend.gen.VersionResponse; import com.wireguard.android.util.SharedLibraryLoader; import com.wireguard.config.Config; import com.wireguard.config.InetEndpoint; @@ -30,6 +38,12 @@ import com.wireguard.crypto.KeyFormatException; import com.wireguard.util.NonNullForAll; import com.wireguard.util.Resolver; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.android.UdsChannelBuilder; +import io.grpc.okhttp.OkHttpChannelBuilder; + +import java.io.File; import java.net.InetAddress; import java.net.URL; import java.time.Instant; @@ -41,6 +55,8 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.net.SocketFactory; + import androidx.annotation.Nullable; import androidx.collection.ArraySet; @@ -61,6 +77,7 @@ public final class GoBackend implements Backend { private ConnectivityManager connectivityManager; private ConnectivityManager.NetworkCallback myNetworkCallback = new MyNetworkCallback(); @Nullable private Network activeNetwork; + private ManagedChannel channel; /** * Public constructor for GoBackend. @@ -71,6 +88,10 @@ public final class GoBackend implements Backend { SharedLibraryLoader.loadSharedLibrary(context, "wg-go"); this.context = context; connectivityManager = context.getSystemService(ConnectivityManager.class); + File socketFile = new File(context.getCacheDir(), "libwg.sock"); + String socketName = socketFile.getAbsolutePath(); + Log.i(TAG, "wgStartGrpc: " + wgStartGrpc(socketName)); + channel = UdsChannelBuilder.forPath(socketName, LocalSocketAddress.Namespace.FILESYSTEM).build(); } /** @@ -97,6 +118,8 @@ public final class GoBackend implements Backend { private static native String wgVersion(); + private static native int wgStartGrpc(String sockName); + /** * Method to get the names of running tunnels. * @@ -199,7 +222,10 @@ public final class GoBackend implements Backend { */ @Override public String getVersion() { - return wgVersion(); + LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel); + VersionRequest request = VersionRequest.newBuilder().build(); + VersionResponse resp = stub.version(request); + return resp.getVersion(); } /** diff --git a/tunnel/src/main/proto/libwg.proto b/tunnel/src/main/proto/libwg.proto new file mode 100644 index 00000000..2d964897 --- /dev/null +++ b/tunnel/src/main/proto/libwg.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = 'com.wireguard.android.backend.gen'; +option java_outer_classname = "LibwgProto"; +option java_generic_services = true; +option go_package = 'golang.zx2c4.com/wireguard/android/gen'; + +package api; + +service Libwg { + rpc StopGrpc(StopGrpcRequest) returns (StopGrpcResponse); + rpc Version(VersionRequest) returns (VersionResponse); +} + +message StopGrpcRequest { +} + +message StopGrpcResponse { +} + +message VersionRequest { +} + +message VersionResponse { + string version = 1; +} diff --git a/tunnel/tools/libwg-go/.gitignore b/tunnel/tools/libwg-go/.gitignore index d1638636..d69ee823 100644 --- a/tunnel/tools/libwg-go/.gitignore +++ b/tunnel/tools/libwg-go/.gitignore @@ -1 +1,2 @@ -build/ \ No newline at end of file +build/ +/gen/ diff --git a/tunnel/tools/libwg-go/Makefile b/tunnel/tools/libwg-go/Makefile index 427bfdc3..a4610d23 100644 --- a/tunnel/tools/libwg-go/Makefile +++ b/tunnel/tools/libwg-go/Makefile @@ -19,6 +19,7 @@ export CGO_LDFLAGS := $(CLANG_FLAGS) $(patsubst -Wl$(comma)--build-id=%,-Wl$(com export GOARCH := $(NDK_GO_ARCH_MAP_$(ANDROID_ARCH_NAME)) export GOOS := android export CGO_ENABLED := 1 +export GOPATH ?= $(HOME)/go GO_VERSION := 1.21.3 GO_PLATFORM := $(shell uname -s | tr '[:upper:]' '[:lower:]')-$(NDK_GO_ARCH_MAP_$(shell uname -m)) @@ -27,6 +28,13 @@ GO_HASH_darwin-amd64 := 27014fc69e301d7588a169ca239b3cc609f0aa1abf38528bf0d20d3b GO_HASH_darwin-arm64 := 65302a7a9f7a4834932b3a7a14cb8be51beddda757b567a2f9e0cbd0d7b5a6ab GO_HASH_linux-amd64 := 1241381b2843fae5a9707eec1f8fb2ef94d827990582c7c7c32f5bdfbfd420c8 +PROTOC_VERSION := 3.22.4 +PROTOC_GEN_GO := $(GOPATH)/bin/protoc-gen-go +PROTOC := $(GRADLE_USER_HOME)/caches/protoc-bin-$(PROTOC_VERSION)/protoc +PROTODIR = $(CURDIR)/../../src/main/proto +PROTO_INCLUDEDIR = $(CURDIR)/../../build/extracted-include-protos/debug +PBDIR = $(GOPATH)/pkg/mod + default: $(DESTDIR)/libwg-go.so $(GRADLE_USER_HOME)/caches/golang/$(GO_TARBALL): @@ -45,8 +53,25 @@ $(BUILDDIR)/go-$(GO_VERSION)/.prepared: $(GRADLE_USER_HOME)/caches/golang/$(GO_T patch -p1 -f -N -r- -d "$(dir $@)" < goruntime-boottime-over-monotonic.diff && \ touch "$@"' +$(PROTOC_GEN_GO): export GOARCH := +$(PROTOC_GEN_GO): export GOOS := +$(PROTOC_GEN_GO): export PATH := $(BUILDDIR)/go-$(GO_VERSION)/bin/:$(PATH) +$(PROTOC_GEN_GO): $(BUILDDIR)/go-$(GO_VERSION)/.prepared Makefile + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 + +gen/%.pb.go: export PATH := $(GOPATH)/bin:$(PATH) +gen/%.pb.go: $(PROTODIR)/%.proto $(BUILDDIR)/go-$(GO_VERSION)/.prepared $(PROTOC_GEN_GO) + test -d gen || mkdir gen + $(PROTOC) -I $(PROTODIR) -I $(PROTO_INCLUDEDIR) --go_out=paths=source_relative:./gen $< + +gen/%_grpc.pb.go: export PATH := $(GOPATH)/bin:$(PATH) +gen/%_grpc.pb.go: $(PROTODIR)/%.proto $(BUILDDIR)/go-$(GO_VERSION)/.prepared $(PROTOC_GEN_GO) + test -d gen || mkdir gen + $(PROTOC) -I $(PROTODIR) -I $(PROTO_INCLUDEDIR) --go-grpc_out=./gen --go-grpc_opt=paths=source_relative $< + $(DESTDIR)/libwg-go.so: export PATH := $(BUILDDIR)/go-$(GO_VERSION)/bin/:$(PATH) -$(DESTDIR)/libwg-go.so: $(BUILDDIR)/go-$(GO_VERSION)/.prepared go.mod +$(DESTDIR)/libwg-go.so: $(BUILDDIR)/go-$(GO_VERSION)/.prepared go.mod api-android.go service.go gen/libwg.pb.go gen/libwg_grpc.pb.go jni.c go build -tags linux -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard -buildid=" -v -trimpath -buildvcs=false -o "$@" -buildmode c-shared .DELETE_ON_ERROR: diff --git a/tunnel/tools/libwg-go/api-android.go b/tunnel/tools/libwg-go/api-android.go index d4e36480..484a89b0 100644 --- a/tunnel/tools/libwg-go/api-android.go +++ b/tunnel/tools/libwg-go/api-android.go @@ -48,6 +48,7 @@ func (l AndroidLogger) Printf(format string, args ...interface{}) { type TunnelHandle struct { device *device.Device uapi net.Listener + logger *device.Logger } var tunnelHandles map[int32]TunnelHandle @@ -208,20 +209,40 @@ func wgGetConfig(tunnelHandle int32) *C.char { //export wgVersion func wgVersion() *C.char { + return C.CString(Version()) +} + +func Version() string { info, ok := debug.ReadBuildInfo() if !ok { - return C.CString("unknown") + return "unknown" } for _, dep := range info.Deps { if dep.Path == "golang.zx2c4.com/wireguard" { parts := strings.Split(dep.Version, "-") if len(parts) == 3 && len(parts[2]) == 12 { - return C.CString(parts[2][:7]) + return parts[2][:7] } - return C.CString(dep.Version) + return dep.Version } } - return C.CString("unknown") + return "unknown" +} + +//export wgStartGrpc +func wgStartGrpc(sock_path string) C.int{ + tag := cstring("WireGuard/GoBackend/gRPC") + logger := &device.Logger{ + Verbosef: AndroidLogger{level: C.ANDROID_LOG_DEBUG, tag: tag}.Printf, + Errorf: AndroidLogger{level: C.ANDROID_LOG_ERROR, tag: tag}.Printf, + } + + res, errmsg := StartGrpc(sock_path, logger) + if res < 0 { + logger.Verbosef("wgStartGrpc: %v (%v)", errmsg, res) + } + + return C.int(res) } //export wgSetConfig diff --git a/tunnel/tools/libwg-go/jni.c b/tunnel/tools/libwg-go/jni.c index e47b0be5..b386579c 100644 --- a/tunnel/tools/libwg-go/jni.c +++ b/tunnel/tools/libwg-go/jni.c @@ -15,6 +15,7 @@ extern int wgGetSocketV6(int handle); extern char *wgGetConfig(int handle); extern char *wgVersion(); extern int wgSetConfig(int handle, struct go_string settings); +extern int wgStartGrpc(); JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOn(JNIEnv *env, jclass c, jstring ifname, jint tun_fd, jstring settings) { @@ -82,3 +83,15 @@ JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgSetConfig( (*env)->ReleaseStringUTFChars(env, settings, settings_str); return ret; } + +JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgStartGrpc(JNIEnv *env, jclass c, jstring sockname) +{ + const char *sockname_str = (*env)->GetStringUTFChars(env, sockname, 0); + size_t sockname_len = (*env)->GetStringUTFLength(env, sockname); + jint res = wgStartGrpc((struct go_string){ + .str = sockname_str, + .n = sockname_len + }); + (*env)->ReleaseStringUTFChars(env, sockname, sockname_str); + return res; +} diff --git a/tunnel/tools/libwg-go/service.go b/tunnel/tools/libwg-go/service.go new file mode 100644 index 00000000..2a0dfb2d --- /dev/null +++ b/tunnel/tools/libwg-go/service.go @@ -0,0 +1,76 @@ +package main + +import ( + "context" + "fmt" + "net" + "os" + + "google.golang.org/grpc" + + gen "golang.zx2c4.com/wireguard/android/gen" + "golang.zx2c4.com/wireguard/device" +) + +type LibwgServiceImpl struct { + gen.UnimplementedLibwgServer + logger *device.Logger +} + +var _ gen.LibwgServer = (*LibwgServiceImpl)(nil) +var server *grpc.Server + +func NewLibwgService(logger *device.Logger) gen.LibwgServer { + return &LibwgServiceImpl{logger: logger} +} + +func StartGrpc(sock_path string, logger *device.Logger) (int, string) { + if server != nil { + return -1, "Already started" + } + + if _, err := os.Stat(sock_path); err == nil { + if err := os.RemoveAll(sock_path); err != nil { + return -1, fmt.Sprintf("Cleanup failed: %v %v", sock_path, err) + } + } + + listener, err := net.Listen("unix", sock_path) + if err != nil { + return -1, fmt.Sprintf("Listen failed: %v %v", sock_path, err) + } + + server = grpc.NewServer() + service := NewLibwgService(logger) + + gen.RegisterLibwgServer(server, service) + + go func() { + server.Serve(listener) + }() + + logger.Verbosef("gRPC started") + + return 0, "" +} + +func (e *LibwgServiceImpl) Version(ctx context.Context, req *gen.VersionRequest) (*gen.VersionResponse, error) { + + r := &gen.VersionResponse{ + Version: Version(), + } + + return r, nil +} + +func (e *LibwgServiceImpl) StopGrpc(ctx context.Context, req *gen.StopGrpcRequest) (*gen.StopGrpcResponse, error) { + if server != nil { + server.Stop() + server = nil + } + + r := &gen.StopGrpcResponse{ + } + + return r, nil +} -- cgit v1.2.3