From: Miroslav Miklus Date: Thu, 6 Mar 2014 12:54:34 +0000 (+0100) Subject: Initial support for RFC2385 X-Git-Tag: release/helium~367^2 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=7c713a81010775845206cfbfc411ea6332d2d1e3;p=bgpcep.git Initial support for RFC2385 This brings the ability to manipulate TCP MD5 key attached to a channel if the underlying operating system supports it. In order to talk to the underlying OS, we rely on Java Native Interface (JNI), which contains OS-specific system calls. Currently only Linux is supported. Since the JRE libraries insulate Java code from the underlying details, we also need to muck around implementation-private information within Channel classes. Currently only openjdk and Oracle JRE is supported. Change-Id: I664106a96da20ff47ecb71d7b541fe6eb0e056b1 Signed-off-by: Robert Varga Signed-off-by: Miroslav Miklus --- diff --git a/commons/parent/pom.xml b/commons/parent/pom.xml index 9a0cc392fc..cafd7a17ff 100644 --- a/commons/parent/pom.xml +++ b/commons/parent/pom.xml @@ -365,7 +365,6 @@ org.codehaus.mojo build-helper-maven-plugin - 1.8 generate-sources @@ -399,6 +398,11 @@ maven-release-plugin ${maven.release.version} + + org.codehaus.mojo + build-helper-maven-plugin + 1.8 + diff --git a/pom.xml b/pom.xml index f9d566846b..d5668e7a1c 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ programming rsvp topology + tcp-md5 integration-tests diff --git a/tcp-md5/.project b/tcp-md5/.project new file mode 100644 index 0000000000..8e91711041 --- /dev/null +++ b/tcp-md5/.project @@ -0,0 +1,24 @@ + + + + tcpmd5-parent + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/tcp-md5/core/.project b/tcp-md5/core/.project new file mode 100644 index 0000000000..cba4063704 --- /dev/null +++ b/tcp-md5/core/.project @@ -0,0 +1,31 @@ + + + + tcpmd5-core + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/tcp-md5/core/pom.xml b/tcp-md5/core/pom.xml new file mode 100644 index 0000000000..d7553d0aa9 --- /dev/null +++ b/tcp-md5/core/pom.xml @@ -0,0 +1,83 @@ + + + + + + + org.opendaylight.bgpcep + tcpmd5-parent + 0.3.1-SNAPSHOT + + + 4.0.0 + tcpmd5-core + RFC2385-enable sockets + bundle + ${project.artifactId} + + 3.0.4 + + + + + ${project.groupId} + tcpmd5-jni + + + + com.google.guava + guava + + + + + junit + junit + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + ${project.groupId}.${project.artifactId} + + org.opendaylight.bgpcep.tcpmd5 + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + package + + test-jar + + + + + + + + + + ${project.artifactId} + TCPMD5-CORE Module site + ${basedir}/target/site/${project.artifactId} + + + diff --git a/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5ChannelOptions.java b/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5ChannelOptions.java new file mode 100644 index 0000000000..d3fc18b2f4 --- /dev/null +++ b/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5ChannelOptions.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013 Robert Varga. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.bgpcep.tcpmd5; + +import java.io.IOException; +import java.net.SocketOption; +import java.nio.channels.NetworkChannel; +import java.util.Set; + +import org.opendaylight.bgpcep.tcpmd5.jni.KeyAccess; +import org.opendaylight.bgpcep.tcpmd5.jni.NativeKeyAccess; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; + +/** + * Channel option wrapper which unifies the underlying NetworkChannel with + * the TCP MD5 Signature option if the channel is supported by the JNI library. + */ +final class MD5ChannelOptions { + private static final Set> MD5_OPTIONS = ImmutableSet.>of(MD5SocketOptions.TCP_MD5SIG); + private final NetworkChannel ch; + private final KeyAccess access; + + private MD5ChannelOptions(final NetworkChannel ch, final KeyAccess access) { + this.ch = Preconditions.checkNotNull(ch); + this.access = access; + } + + static MD5ChannelOptions create(final NetworkChannel ch) { + final KeyAccess access = NativeKeyAccess.create(Preconditions.checkNotNull(ch)); + return new MD5ChannelOptions(ch, access); + } + + public T getOption(final SocketOption name) throws IOException { + if (access != null && name.equals(MD5SocketOptions.TCP_MD5SIG)) { + @SuppressWarnings("unchecked") + final T key = (T)access.getKey(); + return key; + } + + return ch.getOption(name); + } + + public void setOption(final SocketOption name, final T value) throws IOException { + if (access != null && name.equals(MD5SocketOptions.TCP_MD5SIG)) { + access.setKey((byte[])value); + } else { + ch.setOption(name, value); + } + } + + public Set> supportedOptions() { + if (access != null) { + return Sets.union(MD5_OPTIONS, ch.supportedOptions()); + } else { + return ch.supportedOptions(); + } + } +} diff --git a/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5ServerSocketChannel.java b/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5ServerSocketChannel.java new file mode 100644 index 0000000000..6788b6fab3 --- /dev/null +++ b/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5ServerSocketChannel.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2013 Robert Varga. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.bgpcep.tcpmd5; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.SocketAddress; +import java.net.SocketOption; +import java.nio.channels.ServerSocketChannel; +import java.util.Set; + +import com.google.common.base.Preconditions; + +/** + * {@link ServerSocketChannel} augmented with support for TCP MD5 Signature + * option. + */ +public final class MD5ServerSocketChannel extends ServerSocketChannel { + private final ServerSocketChannel inner; + private final MD5ChannelOptions options; + + public MD5ServerSocketChannel(final ServerSocketChannel inner) { + super(inner.provider()); + this.inner = Preconditions.checkNotNull(inner); + this.options = MD5ChannelOptions.create(inner); + } + + public static MD5ServerSocketChannel open() throws IOException { + return new MD5ServerSocketChannel(ServerSocketChannel.open()); + } + + @Override + public SocketAddress getLocalAddress() throws IOException { + return inner.getLocalAddress(); + } + + @Override + public T getOption(final SocketOption name) throws IOException { + return options.getOption(name); + } + + @Override + public Set> supportedOptions() { + return options.supportedOptions(); + } + + @Override + public ServerSocketChannel bind(final SocketAddress local, final int backlog) throws IOException { + inner.bind(local, backlog); + return this; + } + + @Override + public ServerSocketChannel setOption(final SocketOption name, final T value) throws IOException { + options.setOption(name, value); + return this; + } + + @Override + public ServerSocket socket() { + // FIXME: provider a wrapper + return inner.socket(); + } + + @Override + public MD5SocketChannel accept() throws IOException { + return new MD5SocketChannel(inner.accept()); + } + + @Override + protected void implCloseSelectableChannel() throws IOException { + inner.close(); + } + + @Override + protected void implConfigureBlocking(final boolean block) throws IOException { + inner.configureBlocking(block); + } +} diff --git a/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5SocketChannel.java b/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5SocketChannel.java new file mode 100644 index 0000000000..803ed14ea3 --- /dev/null +++ b/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5SocketChannel.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2013 Robert Varga. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.bgpcep.tcpmd5; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketOption; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.Set; + +/** + * {@link SocketChannel} augmented with support for TCP MD5 Signature option. + */ +public final class MD5SocketChannel extends SocketChannel { + private final MD5ChannelOptions options; + private final SocketChannel inner; + + public MD5SocketChannel(final SocketChannel inner) { + super(inner.provider()); + options = MD5ChannelOptions.create(inner); + this.inner = inner; + } + + @Override + public SocketAddress getLocalAddress() throws IOException { + return inner.getLocalAddress(); + } + + @Override + public T getOption(final SocketOption name) throws IOException { + return options.getOption(name); + } + + @Override + public Set> supportedOptions() { + return options.supportedOptions(); + } + + @Override + public SocketChannel bind(final SocketAddress local) throws IOException { + inner.bind(local); + return this; + } + + @Override + public SocketChannel setOption(final SocketOption name, final T value) throws IOException { + options.setOption(name, value); + return this; + } + + @Override + public SocketChannel shutdownInput() throws IOException { + inner.shutdownInput(); + return this; + } + + @Override + public SocketChannel shutdownOutput() throws IOException { + inner.shutdownOutput(); + return this; + } + + @Override + public Socket socket() { + // FIXME: provide a wrapper + return inner.socket(); + } + + @Override + public boolean isConnected() { + return inner.isConnected(); + } + + @Override + public boolean isConnectionPending() { + return inner.isConnectionPending(); + } + + @Override + public boolean connect(final SocketAddress remote) throws IOException { + return inner.connect(remote); + } + + @Override + public boolean finishConnect() throws IOException { + return inner.finishConnect(); + } + + @Override + public SocketAddress getRemoteAddress() throws IOException { + return inner.getRemoteAddress(); + } + + @Override + public int read(final ByteBuffer dst) throws IOException { + return inner.read(dst); + } + + @Override + public long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException { + return inner.read(dsts, offset, length); + } + + @Override + public int write(final ByteBuffer src) throws IOException { + return inner.write(src); + } + + @Override + public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { + return inner.write(srcs, offset, length); + } + + @Override + protected void implCloseSelectableChannel() throws IOException { + inner.close(); + } + + @Override + protected void implConfigureBlocking(final boolean block) throws IOException { + inner.configureBlocking(block); + } +} diff --git a/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5SocketOptions.java b/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5SocketOptions.java new file mode 100644 index 0000000000..710b7c599b --- /dev/null +++ b/tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5SocketOptions.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 Robert Varga. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.bgpcep.tcpmd5; + +import java.net.SocketOption; + +/** + * Utility class holding the singleton socket options used by the TCP-MD5 support + * library. + */ +public final class MD5SocketOptions { + /** + * TCP MD5 Signature option, as defined in RFC 2385. + */ + public static final SocketOption TCP_MD5SIG = new SocketOption() { + @Override + public String name() { + return "TCP_MD5SIG"; + } + + @Override + public Class type() { + return byte[].class; + } + }; + + private MD5SocketOptions() { + throw new UnsupportedOperationException("Utility class cannot be instatiated"); + } +} diff --git a/tcp-md5/jni/.project b/tcp-md5/jni/.project new file mode 100644 index 0000000000..31485997d9 --- /dev/null +++ b/tcp-md5/jni/.project @@ -0,0 +1,30 @@ + + + + tcpmd5-jni + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/tcp-md5/jni/pom.xml b/tcp-md5/jni/pom.xml new file mode 100644 index 0000000000..d48b4fb74e --- /dev/null +++ b/tcp-md5/jni/pom.xml @@ -0,0 +1,177 @@ + + + + + + + org.opendaylight.bgpcep + tcpmd5-parent + 0.3.1-SNAPSHOT + + + 4.0.0 + tcpmd5-jni + Native support for RFC2385-enabled TCP sockets + jar + ${project.artifactId} + + + 3.0.4 + + + + com.github.maven-nar + 3.0.0 + + + + + com.google.guava + guava + + + org.slf4j + slf4j-api + + + + + junit + junit + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + ${project.groupId}.${project.artifactId} + + org.opendaylight.bgpcep.jni + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + target/nar/nar-generated + + + + + + + ${nar.groupId} + nar-maven-plugin + ${nar.version} + true + + + + + jni + org.opendaylight.bgpcep.tcpmd5.jni + false + + + + + + + nar-validate + nar-download + nar-unpack + nar-assembly + nar-system-generate + nar-resources + nar-javah + nar-compile + nar-package + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + ${nar.groupId} + nar-maven-plugin + ${nar.version} + + nar-assembly + nar-compile + nar-download + nar-gnu-configure + nar-gnu-make + nar-gnu-process + nar-gnu-resources + nar-javah + nar-process-libraries + nar-resources + nar-system-generate + nar-testCompile + nar-testDownload + nar-testUnpack + nar-unpack + nar-validate + nar-vcproj + + + + + + + + + + + + + + + + + imagej.thirdparty + http://maven.imagej.net/content/repositories/thirdparty + + + + + + ${project.artifactId} + TCPMD5-JNI Module site + ${basedir}/target/site/${project.artifactId} + + + diff --git a/tcp-md5/jni/src/main/c/tcpmd5_jni.c b/tcp-md5/jni/src/main/c/tcpmd5_jni.c new file mode 100644 index 0000000000..6c4de7449b --- /dev/null +++ b/tcp-md5/jni/src/main/c/tcpmd5_jni.c @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2013 Robert Varga. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct handler { + jclass clazz; + jfieldID field; +}; + +static struct handler *handlers = NULL; +static unsigned handler_count = 0; +static jclass illegal_argument = NULL; +static jclass illegal_state = NULL; +static jclass io = NULL; + +static void __attribute__ ((format (printf, 3, 4))) jni_exception(JNIEnv *env, jclass clazz, const char *fmt, ...) +{ + char buf[1024] = ""; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + (*env)->ThrowNew(env, clazz, buf); +} + +#define ILLEGAL_ARGUMENT(...) jni_exception(env, illegal_argument, __VA_ARGS__) +#define ILLEGAL_STATE(...) jni_exception(env, illegal_state, __VA_ARGS__) +#define IO(...) jni_exception(env, io, __VA_ARGS__) + +static void __attribute__ ((format (printf, 3, 4))) log_message(JNIEnv *env, jobject logger, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +#define ERROR(...) log_message(env, logger, __VA_ARGS__) +#define WARN(...) log_message(env, logger, __VA_ARGS__) + +static const struct handler *add_handler(JNIEnv *env, jobject logger, const char *name, const char *field, const char *sig) +{ + // Find the class + const jclass clazz = (*env)->FindClass(env, name); + if (clazz == NULL) { + goto out; + } + + // Find the field + const jfieldID fid = (*env)->GetFieldID(env, clazz, field, sig); + if (fid == NULL) { + goto out_clazz; + } + + // Reallocate the array + struct handler *r = realloc(handlers, (handler_count + 1) * sizeof(struct handler)); + if (r == NULL) { + ERROR("Failed to allocate handler: %s", strerror(errno)); + goto out_clazz; + } + handlers = r; + + // Fill the array + struct handler *ret = handlers + handler_count; + ret->clazz = (*env)->NewGlobalRef(env, clazz); + ret->field = fid; + + if (ret->clazz == NULL) { + goto out_clazz; + } + + (*env)->DeleteLocalRef(env, clazz); + return ret; + +out_clazz: + (*env)->DeleteLocalRef(env, clazz); +out: + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return NULL; +} + +static const struct handler *find_handler(JNIEnv *env, jclass clazz) +{ + if (clazz != NULL) { + unsigned i; + + for (i = 0; i < handler_count; ++i) { + const struct handler *h = handlers + i; + if ((*env)->IsAssignableFrom(env, clazz, h->clazz) == JNI_TRUE) { + return h; + } + } + } + + return NULL; +} + +static jclass resolve_class(JNIEnv *env, const char *what, jclass *where) +{ + if (*where != NULL) { + jclass clazz = (*env)->FindClass(env, what); + if (clazz == NULL) { + return NULL; + } + + jclass ref = (*env)->NewGlobalRef(env, clazz); + (*env)->DeleteLocalRef(env, clazz); + if (ref == NULL) { + return NULL; + } + + *where = ref; + return ref; + } else { + return *where; + } +} + +static void native_error(JNIEnv *env, int err) +{ + char buf[256] = ""; + strerror_r(err, buf, sizeof(buf)); + IO("Native operation failed: %s", buf); +} + +static int resolve_exceptions(JNIEnv *env) +{ + // First resolve Exception classes, these are mandatory + if (resolve_class(env, "java/lang/IllegalStateException", &illegal_state) == NULL) { + return 1; + } + if (resolve_class(env, "java/lang/IllegalArgumentException", &illegal_argument) == NULL) { + return 1; + } + if (resolve_class(env, "java/io/IOException", &io) == NULL) { + return 1; + } + + return 0; +} + +static jobject resolve_logging(JNIEnv *env, jclass clazz) +{ + jclass lf = (*env)->FindClass(env, "org/slf4j/LoggerFactory"); + if (lf != NULL) { + jmethodID mid = (*env)->GetStaticMethodID(env, lf, "getLogger", "(Ljava/lang/Class;)Lorg/slf4j/Logger"); + if (mid != NULL) { + return (*env)->CallStaticObjectMethod(env, lf, mid, clazz); + } + } + + return NULL; +} + +static jint sanity_check(JNIEnv *env, jobject logger) +{ + struct tcp_md5sig md5sig; + + // Sanity-check maximum key size against the structure + if (TCP_MD5SIG_MAXKEYLEN > sizeof(md5sig.tcpm_key)) { + ERROR("Structure key size %zu is less than %d", sizeof(md5sig.tcpm_key), TCP_MD5SIG_MAXKEYLEN); + return 0; + } + + // Now run a quick check to see if we can really the getsockopt/setsockopt calls are + // supported. + const int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + ERROR("Failed to open a socket for sanity tests: %s", strerror(errno)); + return 0; + } + + int ret = -1; + socklen_t len = sizeof(md5sig); + memset(&md5sig, 0, sizeof(md5sig)); + if (getsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &md5sig, &len) != 0) { + WARN("Platform has no support of TCP_MD5SIG: %s", strerror(errno)); + goto out_close; + } + + // Sanity check: freshly-created sockets should not have a key + if (md5sig.tcpm_keylen != 0) { + ERROR("Platform advertizes a fresh socket with key length %u", md5sig.tcpm_keylen); + goto out_close; + } + + // Attempt to set a new key with maximum size + md5sig.tcpm_keylen = TCP_MD5SIG_MAXKEYLEN; + if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &md5sig, sizeof(md5sig)) != 0) { + ERROR("Failed to set TCP_MD5SIG option: %s", strerror(errno)); + goto out_close; + } + + // Attempt to remove the key + md5sig.tcpm_keylen = 0; + if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &md5sig, sizeof(md5sig)) != 0) { + ERROR("Failed to clear TCP_MD5SIG option: %s", strerror(errno)); + goto out_close; + } + + ret = 0; + +out_close: + close(fd); + return ret; +} + +/* + * Class: org_opendaylight_bgpcep_tcpmd5_jni_NarSystem + * Method: runUnitTestsNative + * Signature: ()I + */ +jint Java_org_opendaylight_bgpcep_tcpmd5_jni_NarSystem_runUnitTestsNative(JNIEnv *env, jobject obj) +{ + if (resolve_exceptions(env)) { + return 0; + } + + jobject logger = resolve_logging(env, (*env)->GetObjectClass(env, obj)); + if (sanity_check(env, logger)) { + return 0; + } + + int ret = 0; + if (add_handler(env, logger, "sun/nio/ch/ServerSocketChannelImpl", "fdVal", "I")) { + ++ret; + } + if (add_handler(env, logger, "sun/nio/ch/SocketChannelImpl", "fdVal", "I")) { + ++ret; + } + + return ret; +} + +/* + * Class: org_opendaylight_bgpcep_tcpmd5_jni_NativeKeyAccess + * Method: isClassSupported0 + * Signature: (Ljava/lang/Class;)Z + */ +jboolean Java_org_opendaylight_bgpcep_tcpmd5_jni_NativeKeyAccess_isClassSupported0(JNIEnv *env, jclass clazz, jclass arg) +{ + const struct handler *h = find_handler(env, arg); + return h == NULL ? JNI_FALSE : JNI_TRUE; +} + +/* + * Class: org_opendaylight_bgpcep_tcpmd5_jni_NativeKeyAccess + * Method: getChannelKey0 + * Signature: (Ljava/nio/channels/Channel;)[B + */ +jbyteArray Java_org_opendaylight_bgpcep_tcpmd5_jni_NativeKeyAccess_getChannelKey0(JNIEnv *env, jclass clazz, jobject channel) +{ + const struct handler *h = find_handler(env, (*env)->GetObjectClass(env, channel)); + if (h == NULL) { + ILLEGAL_STATE("Failed to find handler"); + return NULL; + } + + const jint fd = (*env)->GetIntField(env, channel, h->field); + if ((*env)->ExceptionCheck(env) == JNI_TRUE) { + return NULL; + } + + struct tcp_md5sig md5sig; + memset(&md5sig, 0, sizeof(md5sig)); + socklen_t len = sizeof(md5sig); + if (getsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &md5sig, &len) != 0) { + native_error(env, errno); + return NULL; + } + + jbyteArray ret = (*env)->NewByteArray(env, md5sig.tcpm_keylen); + if (ret != NULL) { + (*env)->SetByteArrayRegion(env, ret, 0, md5sig.tcpm_keylen, (jbyte *)md5sig.tcpm_key); + } + + return ret; +} + +/* + * Class: org_opendaylight_bgpcep_tcpmd5_jni_NativeKeyAccess + * Method: setChannelKey0 + * Signature: (Ljava/nio/channels/Channel;[B)V + */ +void Java_org_opendaylight_bgpcep_tcpmd5_jni_NativeKeyAccess_setChannelKey0(JNIEnv *env, jclass clazz, jobject channel, jbyteArray key) +{ + const jsize keylen = (*env)->GetArrayLength(env, key); + if (keylen < 0) { + ILLEGAL_ARGUMENT("Negative array length %d encountered", keylen); + return; + } + if (keylen > TCP_MD5SIG_MAXKEYLEN) { + ILLEGAL_ARGUMENT("Key length %d exceeds platform limit %d", keylen, TCP_MD5SIG_MAXKEYLEN); + return; + } + + struct tcp_md5sig md5sig; + memset(&md5sig, 0, sizeof(md5sig)); + md5sig.tcpm_keylen = (uint16_t) keylen; + + /* + * TCP_MD5SIG_MAXKEYLEN may not be an accurate check of key field + * length. Check the field size before accessing it. + */ + if (keylen > sizeof(md5sig.tcpm_key)) { + ILLEGAL_ARGUMENT("Key length %d exceeds native buffer limit %zu", keylen, sizeof(md5sig.tcpm_key)); + return; + } + + (*env)->GetByteArrayRegion(env, key, 0, keylen, (void *) &md5sig.tcpm_key); + if ((*env)->ExceptionCheck(env) == JNI_TRUE) { + return; + } + + const struct handler *h = find_handler(env, (*env)->GetObjectClass(env, channel)); + if (h == NULL) { + ILLEGAL_STATE("Failed to find handler"); + return; + } + + const jint fd = (*env)->GetIntField(env, channel, h->field); + if ((*env)->ExceptionCheck(env) == JNI_TRUE) { + return; + } + + const int ret = setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &md5sig, sizeof(md5sig)); + if (ret != 0) { + native_error(env, errno); + } +} + diff --git a/tcp-md5/jni/src/main/java/org/opendaylight/bgpcep/tcpmd5/jni/KeyAccess.java b/tcp-md5/jni/src/main/java/org/opendaylight/bgpcep/tcpmd5/jni/KeyAccess.java new file mode 100644 index 0000000000..8d5bff6954 --- /dev/null +++ b/tcp-md5/jni/src/main/java/org/opendaylight/bgpcep/tcpmd5/jni/KeyAccess.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013 Robert Varga. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.bgpcep.tcpmd5.jni; + +import java.io.IOException; + +/** + * Interface for accessing key information attached to an object. + */ +public interface KeyAccess { + /** + * Retrieve the key. + * + * @return The key currently attached, null if there is no key attached. + * @throws IOException when the retrieve operation fails. + */ + byte[] getKey() throws IOException; + + /** + * Set the key. + * + * @param key The key to be attached, null indicates removal. + * @throws IOException when the set operation fails. + */ + void setKey(byte[] key) throws IOException; +} diff --git a/tcp-md5/jni/src/main/java/org/opendaylight/bgpcep/tcpmd5/jni/NativeKeyAccess.java b/tcp-md5/jni/src/main/java/org/opendaylight/bgpcep/tcpmd5/jni/NativeKeyAccess.java new file mode 100644 index 0000000000..e0fa715a68 --- /dev/null +++ b/tcp-md5/jni/src/main/java/org/opendaylight/bgpcep/tcpmd5/jni/NativeKeyAccess.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2013 Robert Varga. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.bgpcep.tcpmd5.jni; + +import java.io.IOException; +import java.nio.channels.Channel; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +/** + * Implementation of KeyAccess using Java Native Interface plugin to talk + * directly to the underlying operating system. + */ +public final class NativeKeyAccess implements KeyAccess { + private static final Logger LOG = LoggerFactory.getLogger(NativeKeyAccess.class); + + static { + NarSystem.loadLibrary(); + + int rt = NarSystem.runUnitTests(); + if (rt == 0) { + LOG.warn("Run-time initialization failed"); + } else { + LOG.debug("Run-time found {} supported channel classes", rt); + } + } + + private static native boolean isClassSupported0(Class channel); + private static native byte[] getChannelKey0(Channel channel) throws IOException; + private static native void setChannelKey0(Channel channel, byte[] key) throws IOException; + + private final Channel channel; + + private NativeKeyAccess(final Channel channel) { + this.channel = Preconditions.checkNotNull(channel); + } + + public static KeyAccess create(final Channel channel) { + if (isClassSupported0(channel.getClass())) { + return new NativeKeyAccess(channel); + } else { + return null; + } + } + + public static boolean isAvailableForClass(final Class clazz) { + return isClassSupported0(clazz); + } + + @Override + public byte[] getKey() throws IOException { + synchronized (channel) { + return getChannelKey0(channel); + } + } + + @Override + public void setKey(final byte[] key) throws IOException { + synchronized (channel) { + setChannelKey0(channel, Preconditions.checkNotNull(key)); + } + } +} diff --git a/tcp-md5/pom.xml b/tcp-md5/pom.xml new file mode 100644 index 0000000000..e04688c42b --- /dev/null +++ b/tcp-md5/pom.xml @@ -0,0 +1,61 @@ + + + + + + 4.0.0 + + scm:git:ssh://git.opendaylight.org:29418/bgpcep.git + scm:git:ssh://git.opendaylight.org:29418/bgpcep.git + https://wiki.opendaylight.org/view/BGP_LS_PCEP:Main + HEAD + + + org.opendaylight.bgpcep + commons.parent + 0.3.1-SNAPSHOT + ../commons/parent + + + 3.0.4 + + + + tcpmd5-parent + RFC2385-enabled sockets components + pom + ${project.artifactId} + + + core + jni + + + + + + ${project.groupId} + tcpmd5-core + ${project.version} + + + ${project.groupId} + tcpmd5-jni + ${project.version} + + + ${project.groupId} + tcpmd5-jni + ${project.version} + nar + + + + +