Initial support for RFC2385 10/5510/3
authorMiroslav Miklus <mmiklus@cisco.com>
Thu, 6 Mar 2014 12:54:34 +0000 (13:54 +0100)
committerMiroslav Miklus <mmiklus@cisco.com>
Tue, 11 Mar 2014 12:23:35 +0000 (13:23 +0100)
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 <robert.varga@pantheon.sk>
Signed-off-by: Miroslav Miklus <mmiklus@cisco.com>
15 files changed:
commons/parent/pom.xml
pom.xml
tcp-md5/.project [new file with mode: 0644]
tcp-md5/core/.project [new file with mode: 0644]
tcp-md5/core/pom.xml [new file with mode: 0644]
tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5ChannelOptions.java [new file with mode: 0644]
tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5ServerSocketChannel.java [new file with mode: 0644]
tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5SocketChannel.java [new file with mode: 0644]
tcp-md5/core/src/main/java/org/opendaylight/bgpcep/tcpmd5/MD5SocketOptions.java [new file with mode: 0644]
tcp-md5/jni/.project [new file with mode: 0644]
tcp-md5/jni/pom.xml [new file with mode: 0644]
tcp-md5/jni/src/main/c/tcpmd5_jni.c [new file with mode: 0644]
tcp-md5/jni/src/main/java/org/opendaylight/bgpcep/tcpmd5/jni/KeyAccess.java [new file with mode: 0644]
tcp-md5/jni/src/main/java/org/opendaylight/bgpcep/tcpmd5/jni/NativeKeyAccess.java [new file with mode: 0644]
tcp-md5/pom.xml [new file with mode: 0644]

index 9a0cc392fcaede9efc2d0374b57459fb09198055..cafd7a17ff265e1739fdbdd38af5301a4509e8ca 100644 (file)
                 <!-- Let eclipse know about the generated sources -->
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>build-helper-maven-plugin</artifactId>
-                <version>1.8</version>
                 <executions>
                     <execution>
                         <phase>generate-sources</phase>
                     <artifactId>maven-release-plugin</artifactId>
                     <version>${maven.release.version}</version>
                 </plugin>
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>build-helper-maven-plugin</artifactId>
+                    <version>1.8</version>
+                </plugin>
 
                 <!--This plugin's configuration is used to store Eclipse m2e settings
                     only. It has no influence on the Maven build itself. -->
diff --git a/pom.xml b/pom.xml
index f9d566846b7e035a9f0e8f6717cea309354640fb..d5668e7a1c25e33a6280e1be4b1b77f6d32a82bc 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -40,6 +40,7 @@
         <module>programming</module>
         <module>rsvp</module>
         <module>topology</module>
+        <module>tcp-md5</module>
 
         <!-- Integration tests -->
         <module>integration-tests</module>
diff --git a/tcp-md5/.project b/tcp-md5/.project
new file mode 100644 (file)
index 0000000..8e91711
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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
+-->
+<projectDescription>
+       <name>tcpmd5-parent</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.m2e.core.maven2Builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.m2e.core.maven2Nature</nature>
+       </natures>
+</projectDescription>
diff --git a/tcp-md5/core/.project b/tcp-md5/core/.project
new file mode 100644 (file)
index 0000000..cba4063
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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
+-->
+<projectDescription>
+       <name>tcpmd5-core</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.m2e.core.maven2Builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+               <nature>org.eclipse.m2e.core.maven2Nature</nature>
+       </natures>
+</projectDescription>
diff --git a/tcp-md5/core/pom.xml b/tcp-md5/core/pom.xml
new file mode 100644 (file)
index 0000000..d7553d0
--- /dev/null
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+      <parent>
+          <groupId>org.opendaylight.bgpcep</groupId>
+          <artifactId>tcpmd5-parent</artifactId>
+          <version>0.3.1-SNAPSHOT</version>
+      </parent>
+
+      <modelVersion>4.0.0</modelVersion>
+      <artifactId>tcpmd5-core</artifactId>
+      <description>RFC2385-enable sockets</description>
+      <packaging>bundle</packaging>
+      <name>${project.artifactId}</name>
+      <prerequisites>
+          <maven>3.0.4</maven>
+      </prerequisites>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>tcpmd5-jni</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+
+        <!-- Testing dependencies -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
+                        <Export-Package>
+                            org.opendaylight.bgpcep.tcpmd5
+                        </Export-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <distributionManagement>
+        <site>
+            <id>${project.artifactId}</id>
+            <name>TCPMD5-CORE Module site</name>
+            <url>${basedir}/target/site/${project.artifactId}</url>
+        </site>
+    </distributionManagement>
+</project>
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 (file)
index 0000000..d3fc18b
--- /dev/null
@@ -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<SocketOption<?>> MD5_OPTIONS = ImmutableSet.<SocketOption<?>>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> T getOption(final SocketOption<T> 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 <T> void setOption(final SocketOption<T> 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<SocketOption<?>> 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 (file)
index 0000000..6788b6f
--- /dev/null
@@ -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> T getOption(final SocketOption<T> name) throws IOException {
+               return options.getOption(name);
+       }
+
+       @Override
+       public Set<SocketOption<?>> supportedOptions() {
+               return options.supportedOptions();
+       }
+
+       @Override
+       public ServerSocketChannel bind(final SocketAddress local, final int backlog) throws IOException {
+               inner.bind(local, backlog);
+               return this;
+       }
+
+       @Override
+       public <T> ServerSocketChannel setOption(final SocketOption<T> 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 (file)
index 0000000..803ed14
--- /dev/null
@@ -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> T getOption(final SocketOption<T> name) throws IOException {
+               return options.getOption(name);
+       }
+
+       @Override
+       public Set<SocketOption<?>> supportedOptions() {
+               return options.supportedOptions();
+       }
+
+       @Override
+       public SocketChannel bind(final SocketAddress local) throws IOException {
+               inner.bind(local);
+               return this;
+       }
+
+       @Override
+       public <T> SocketChannel setOption(final SocketOption<T> 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 (file)
index 0000000..710b7c5
--- /dev/null
@@ -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<byte[]> TCP_MD5SIG = new SocketOption<byte[]>() {
+               @Override
+               public String name() {
+                       return "TCP_MD5SIG";
+               }
+
+               @Override
+               public Class<byte[]> 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 (file)
index 0000000..3148599
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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
+-->
+<projectDescription>
+       <name>tcpmd5-jni</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.m2e.core.maven2Builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+               <nature>org.eclipse.m2e.core.maven2Nature</nature>
+       </natures>
+</projectDescription>
diff --git a/tcp-md5/jni/pom.xml b/tcp-md5/jni/pom.xml
new file mode 100644 (file)
index 0000000..d48b4fb
--- /dev/null
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <parent>
+        <groupId>org.opendaylight.bgpcep</groupId>
+        <artifactId>tcpmd5-parent</artifactId>
+        <version>0.3.1-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>tcpmd5-jni</artifactId>
+    <description>Native support for RFC2385-enabled TCP sockets</description>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <prerequisites>
+        <maven>3.0.4</maven>
+    </prerequisites>
+
+    <properties>
+        <nar.groupId>com.github.maven-nar</nar.groupId>
+        <nar.version>3.0.0</nar.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <!-- Testing dependencies -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
+                        <Export-Package>
+                            org.opendaylight.bgpcep.jni
+                        </Export-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <!-- Let eclipse know about the generated sources -->
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>add-source</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>target/nar/nar-generated</source>
+                            </sources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>${nar.groupId}</groupId>
+                <artifactId>nar-maven-plugin</artifactId>
+                <version>${nar.version}</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <c/>
+                    <libraries>
+                        <library>
+                            <type>jni</type>
+                            <narSystemPackage>org.opendaylight.bgpcep.tcpmd5.jni</narSystemPackage>
+                            <linkCPP>false</linkCPP>
+                        </library>
+                    </libraries>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>nar-validate</goal>
+                            <goal>nar-download</goal>
+                            <goal>nar-unpack</goal>
+                            <goal>nar-assembly</goal>
+                            <goal>nar-system-generate</goal>
+                            <goal>nar-resources</goal>
+                            <goal>nar-javah</goal>
+                            <goal>nar-compile</goal>
+                            <goal>nar-package</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.eclipse.m2e</groupId>
+                    <artifactId>lifecycle-mapping</artifactId>
+                    <version>1.0.0</version>
+                    <configuration>
+                        <lifecycleMappingMetadata>
+                            <pluginExecutions>
+                                <pluginExecution>
+                                    <pluginExecutionFilter>
+                                        <groupId>${nar.groupId}</groupId>
+                                        <artifactId>nar-maven-plugin</artifactId>
+                                        <versionRange>${nar.version}</versionRange>
+                                        <goals>
+                                            <goal>nar-assembly</goal>
+                                            <goal>nar-compile</goal>
+                                            <goal>nar-download</goal>
+                                            <goal>nar-gnu-configure</goal>
+                                            <goal>nar-gnu-make</goal>
+                                            <goal>nar-gnu-process</goal>
+                                            <goal>nar-gnu-resources</goal>
+                                            <goal>nar-javah</goal>
+                                            <goal>nar-process-libraries</goal>
+                                            <goal>nar-resources</goal>
+                                            <goal>nar-system-generate</goal>
+                                            <goal>nar-testCompile</goal>
+                                            <goal>nar-testDownload</goal>
+                                            <goal>nar-testUnpack</goal>
+                                            <goal>nar-unpack</goal>
+                                            <goal>nar-validate</goal>
+                                            <goal>nar-vcproj</goal>
+                                        </goals>
+                                    </pluginExecutionFilter>
+                                    <action>
+                                        <ignore/>
+                                    </action>
+                                </pluginExecution>
+                            </pluginExecutions>
+                        </lifecycleMappingMetadata>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>imagej.thirdparty</id>
+            <url>http://maven.imagej.net/content/repositories/thirdparty</url>
+        </pluginRepository>
+    </pluginRepositories>
+
+    <distributionManagement>
+        <site>
+            <id>${project.artifactId}</id>
+            <name>TCPMD5-JNI Module site</name>
+            <url>${basedir}/target/site/${project.artifactId}</url>
+        </site>
+    </distributionManagement>
+</project>
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 (file)
index 0000000..6c4de74
--- /dev/null
@@ -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 <org_opendaylight_bgpcep_tcpmd5_jni_NarSystem.h>
+#include <org_opendaylight_bgpcep_tcpmd5_jni_NativeKeyAccess.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <linux/tcp.h>
+
+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 (file)
index 0000000..8d5bff6
--- /dev/null
@@ -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 (file)
index 0000000..e0fa715
--- /dev/null
@@ -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 (file)
index 0000000..e04688c
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <scm>
+        <connection>scm:git:ssh://git.opendaylight.org:29418/bgpcep.git</connection>
+        <developerConnection>scm:git:ssh://git.opendaylight.org:29418/bgpcep.git</developerConnection>
+        <url>https://wiki.opendaylight.org/view/BGP_LS_PCEP:Main</url>
+        <tag>HEAD</tag>
+    </scm>
+    <parent>
+        <groupId>org.opendaylight.bgpcep</groupId>
+        <artifactId>commons.parent</artifactId>
+        <version>0.3.1-SNAPSHOT</version>
+        <relativePath>../commons/parent</relativePath>
+    </parent>
+    <prerequisites>
+        <maven>3.0.4</maven>
+    </prerequisites>
+
+
+       <artifactId>tcpmd5-parent</artifactId>
+       <description>RFC2385-enabled sockets components</description>
+       <packaging>pom</packaging>
+       <name>${project.artifactId}</name>
+
+       <modules>
+        <module>core</module>
+        <module>jni</module>
+    </modules>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>tcpmd5-core</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>tcpmd5-jni</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>tcpmd5-jni</artifactId>
+                <version>${project.version}</version>
+                <type>nar</type>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+</project>