Marcs tech blog

Welcome to the blog of Marc aka Switch Case AB

Nature

CALL GO CODE FROM JAVA THROUGH JNI

November 16, 2018

Everyday I spend almost 2 hours commuting with the train to my customers. So to make something constructive with the time spending on the train I usually educate myself in different subjects.

A long time ago I needed to invoke native functions written in C through Java Native Interface from a Java app I was developing. Nothing crucial with that except, besides all the Java coding I do everyday, I have also been up with developing a lot of Golang in the past for a client.

So to make something constructive this evening on the train I will write a post of how to connect Java 11 with Golang through JNI.

So what is something constructive? For this, I would say that something constructive is to get Java doing something it cannot, like creating raw sockets. But Go can! So we will write a small program that makes it possible to send ICMP packets (ping) to another host.

The first thing we have to do is to write the JNI class defining the JNI methods.

Create the Java class defining the ping method.

package se.switchcase;
public class GoJava {
  public static native long ping(String address);
}

The next thing is to create the corresponding C header file for this class which is done using javac:

javac -h headers -d target GoJava.java

This will result in a C header file called se_switchcase_GoJava.h. The file will contain our C definition of the function with the following signature:

JNIEXPORT void JNICALL Java_se_switchcase_GoJava_ping (JNIEnv *, jclass, jstring);

So what we will do next is to create our implementation of that function and we will do it in Go and not in C!

We create a file called GoJava.go that will act as the entry to our JNI implementations.
The implementation of our ping function declared in the C header file will look like this in Go:

//export Java_se_switchcase_GoJava_ping
func Java_se_switchcase_GoJava_ping(env *C.JNIEnv, clazz C.jclass, host C.jstring) {
  // s := C.cw_GetStringUTFChars(env, str, (*C.jboolean)(nil))
  // defer C.cw_ReleaseStringUTFChars(env, str, s)
  // Add ping function call later
}

The //export above the function signature is used bo CGO to indicate that this method shall be accessable from the C code (that is the JVM)But there is an issue with the above code, and that is that Go cannot access function pointers and most of the JNI functions are all function pointers. So we have to write wrapper functions in C for every call to a JNI function. We will wrap two JNI functions, GetStringUTFChars and ReleaseStringUTFChars.
The first one takes a jstring and returns a char pointer that we can make a CString of. The second clears the pointer and must be called after calling the first function.

So create a C header file and call it something useful, like cw.h (cw for "C Wrapper"):

#include "jni.h"
const char* cw_GetStringUTFChars(JNIEnv *, jstring, jboolean *);
void cw_ReleaseStringUTFChars(JNIEnv *, jstring, const char *);

And then add the implementation in a cw.c file:

#include "cw.h"
const char* cw_GetStringUTFChars(JNIEnv * env, jstring str, jboolean * isCopy) {
  return (*env)->GetStringUTFChars(env, str, isCopy);
}
void cw_ReleaseStringUTFChars(JNIEnv *env, jstring str, const char *utf) {
  (*env)->ReleaseStringUTFChars(env, str, utf);
}

Then we have to build a shared object that later will be linked into the library we will build later in this tutorial.

gcc -shared -fPIC -I/usr/java/jdk-11.0.1/include/ -I/usr/java/jdk-11.0.1/include/linux c/cw.c -o target/libcw.so

This will result in a library object that can be linked into other builds.

Now we can implement our ping function. We will make use of the packages golang.org/x/net/icmp" and "golang.org/x/net/ipv4". We create a jping.go file and add the following:

func ping(host string) {
  c, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
  defer c.Close()

  if err != nil {
    log.Fatalf("listen err, %s", err)
  }

  wm := icmp.Message{
    Type: ipv4.ICMPTypeEcho, Code: 0,
    Body: &icmp.Echo{
      ID: os.Getpid() & 0xffff, Seq: 1,
      Data: []byte("Hello from Switch Case!"),
    },
  }
  wb, err := wm.Marshal(nil)
  if err != nil {
    log.Fatal(err)
  }
  if _, err := c.WriteTo(wb, &net.IPAddr{IP: net.ParseIP(host)}); err != nil {
    log.Fatalf("WriteTo err, %s", err)
  }
}

Now we can finnish the implementation of the golang function declared above in the file called gojava.go:

//export Java_se_switchcase_GoJava_ping
func Java_se_switchcase_GoJava_ping(env *C.JNIEnv, clazz C.jclass, host C.jstring) {
  s := C.cw_GetStringUTFChars(env, str, (*C.jboolean)(nil))
  defer C.cw_ReleaseStringUTFChars(env, str, s)
  ping(C.GoString(hostString))
}

There is one more thing we have to do! We must link the shared library we built into the Go code. This is done by setting these lines as comments in the top of gojava.go

package main
#cgo CFLAGS: -I/usr/java/jdk-11.0.1/include/
#cgo CFLAGS: -I/usr/java/jdk-11.0.1/include/linux
#cgo LDFLAGS: -L${SRCDIR}/../target -lcw

#include "../c/cw.h"

*/
import "C"
...
// Rest of the code
...

Now it is time to build a shared library of our Go code the JVM will load. We will name the lib libgojava.so.
${SRCDIR}/../target is where I have the other shared library we built some steps above containing the ping implementatiom.

go build -buildmode=c-shared -o target/libgojava.so go/gojava.go go/jping.go

If you get any error saying it could not link the other lib, make sure you have set LD_LIBRARY_PATH variable to point at the path where your library built in the previous step is (the lib called libcw.so).

So when we have our shared library containing the implementation in Go, then it´s time to test it.
So we create a new Java file, called Main.java and add this:

public class Main {
  static {
    System.loadLibrary("gojava");
  }

  public static void main(String[] args) {
    GoJava.ping("8.8.8.8");
  }
}

Compile it and run it as root as creating raw sockets require root privilegies. Look with a packet scanner, like tcpdump and you will se your packet go away to the destination.

Responsive image


About me

A system engineer consultant loving to code and explore new technologies and of course barbeque!


Tags

JNI Go Java 11 C