Skip to content

Commit

Permalink
Merge branch 'master' into blacklist_ui
Browse files Browse the repository at this point in the history
  • Loading branch information
rowleya committed Sep 12, 2023
2 parents 43215b0 + 6c0dd1d commit 9842d6a
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 4 deletions.
44 changes: 44 additions & 0 deletions SpiNNaker-proxy/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!--
Copyright (c) 2023 The University of Manchester
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>uk.ac.manchester.spinnaker</groupId>
<artifactId>SpiNNaker</artifactId>
<version>7.1.0-SNAPSHOT</version>
</parent>
<artifactId>SpiNNaker-proxy</artifactId>

<description>A simple persistent proxy server.</description>
<name>SpiNNaker Proxy Server</name>

<build>
<finalName>spinnaker-proxy</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>uk.ac.manchester.spinnaker.proxy.TCPProxy</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
/*
* Copyright (c) 2023 The University of Manchester
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.ac.manchester.spinnaker.proxy;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
* A TCP Proxy server that can re-connect if the target goes down.
*/
public class TCPProxy {

/** The maximum size of a read from a TCP socket. */
private static final int BUFFER_SIZE = 4096;

private Socket client;

private final Remote remote;

TCPProxy(Socket client, String remoteHost, int remotePort) {
System.err.println("New connection from "
+ client.getRemoteSocketAddress());
this.client = client;
this.remote = new Remote(remoteHost, remotePort);

Thread clientToRemote = new Thread(this::clientToRemote);
Thread remoteToClient = new Thread(this::remoteToClient);
clientToRemote.start();
remoteToClient.start();
}

/**
* Read the client and send to the remote. If remote write fails,
* reconnect to remote and resend. If client read fails, stop.
*/
private void clientToRemote() {
try {
remote.connect();
byte[] buffer = new byte[BUFFER_SIZE];
try (var input = client.getInputStream()) {
while (client.isConnected()) {
int bytesRead = input.read(buffer);
if (bytesRead == -1) {
throw new IOException();
}
remote.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
// This is likely caused by the client disconnecting.
}
} catch (InterruptedException e) {
// Exiting
}
forceCloseClient();
remote.close();
}

/**
* Read from the remote and send to the client. If the remote read fails,
* retry until working. If the client write fails, stop.
*/
private void remoteToClient() {
try {
remote.connect();
byte[] buffer = new byte[BUFFER_SIZE];
try (var output = client.getOutputStream()) {
while (client.isConnected()) {
int bytesRead = remote.read(buffer);
if (bytesRead == -1) {
throw new IOException();
}
output.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
// This is likely caused by the client disconnecting.
}
} catch (InterruptedException e) {
// Exiting
}
forceCloseClient();
remote.close();
}

/**
* Close the client and ignore any errors.
*/
private synchronized void forceCloseClient() {
if (client == null) {
return;
}
System.err.println(
"Client " + client.getRemoteSocketAddress() + " left");
try {
client.close();
} catch (IOException e) {
// Ignore
}
client = null;
}

/**
* The main method.
* @param args args[0]: The local port to listen on
* args[1]: The remote host to proxy
* args[2]: The remote port to proxy
* @throws IOException If we can't start the server.
*/
public static void main(String[] args) throws IOException {
int localPort = Integer.parseInt(args[0]);
String remoteHost = args[1];
int remotePort = Integer.parseInt(args[2]);

try (var server = new ServerSocket(localPort)) {
while (true) {
var client = server.accept();
new TCPProxy(client, remoteHost, remotePort);
}
}
}
}

/**
* A persistent remote connection.
*/
final class Remote {

/** How long to wait between retries of the connection. */
private static final int RETRY_MS = 5000;

/** How long to wait between writes on a connection. */
private static final int WRITE_RETRY_MS = 1000;

private final String remoteHost;

private final int remotePort;

private Socket remote = null;

private boolean closed = false;

/**
* Create a remote.
*
* @param remoteHost The host to connect to.
* @param remotePort The port to connect to.
*/
Remote(String remoteHost, int remotePort) {
this.remoteHost = remoteHost;
this.remotePort = remotePort;
}

/**
* Connect to the remote site and don't stop until connected.
*
* @throws InterruptedException If interrupted.
*/
synchronized void connect() throws InterruptedException {
if (remote != null || closed) {
return;
}
System.err.println("Connecting to " + remoteHost + ":" + remotePort);
while (true) {
try {
remote = new Socket(remoteHost, remotePort);
System.err.println("Connected to "
+ remote.getRemoteSocketAddress());
return;
} catch (IOException e) {
Thread.sleep(RETRY_MS);
}
}
}

/**
* Write to the remote. Retry until successful, or interrupted.
*
* @param buffer The buffer to write.
* @param offset The offset of the buffer to start from.
* @param length The length of the buffer to write, starting at the offset.
* @throws InterruptedException If interrupted.
*/
void write(byte[] buffer, int offset, int length)
throws InterruptedException {
while (true) {
while (remote == null) {
Thread.sleep(WRITE_RETRY_MS);
}
try {
var remoteOutput = remote.getOutputStream();
remoteOutput.write(buffer, 0, length);
return;
} catch (IOException e) {
if (!closed) {
closeConnections();
connect();
}
}
}
}

/**
* Read from the remote. Retry until successful, or interrupted.
*
* @param buffer The buffer to read into.
* @return The bytes written.
* @throws InterruptedException If interrupted.
*/
int read(byte[] buffer) throws InterruptedException {
while (true) {
if (remote == null) {
return -1;
}
try {
var remoteInput = remote.getInputStream();
int bytesRead = remoteInput.read(buffer);
if (bytesRead == -1) {
throw new IOException();
}
return bytesRead;
} catch (IOException e) {
if (!closed) {
closeConnections();
connect();
}
}
}
}

/**
* Close the connection never to be reopened.
*/
void close() {
closed = true;
closeConnections();
}

/**
* Close the connections.
*/
synchronized void closeConnections() {
if (remote == null) {
return;
}
forceClose(remote);
remote = null;
}

/**
* Force closure and ignore any exceptions.
* @param closable The thing to close.
*/
private void forceClose(AutoCloseable closable) {
if (closable == null) {
return;
}
try {
closable.close();
} catch (Exception e) {
// Ignore the exception
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2022 The University of Manchester
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A simple persistent proxy server, that allows the service to be restarted
* without losing client connections.
*/
package uk.ac.manchester.spinnaker.proxy;
9 changes: 5 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ limitations under the License.
<jackson.version>2.15.2</jackson.version>
<jackson.doc.version>2.14</jackson.doc.version>
<sqlite.version>3.43.0.0</sqlite.version>
<keycloak.version>22.0.1</keycloak.version>
<swagger.version>5.4.2</swagger.version>
<keycloak.version>22.0.2</keycloak.version>
<swagger.version>5.6.1</swagger.version>
<jsontaglib.version>1.0.5</jsontaglib.version>
<jython.version>2.7.3</jython.version>
<error-prone.version>2.18.0</error-prone.version>
Expand Down Expand Up @@ -410,7 +410,7 @@ limitations under the License.
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>6.6.1.202309021850-r</version>
<version>6.7.0.202309050840-r</version>
<exclusions>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
Expand Down Expand Up @@ -683,7 +683,7 @@ limitations under the License.
<plugin>
<groupId>com.github.blutorange</groupId>
<artifactId>closure-compiler-maven-plugin</artifactId>
<version>2.26.0</version>
<version>2.27.0</version>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -872,6 +872,7 @@ limitations under the License.
<module>SpiNNaker-nmpiserv</module>
<module>SpiNNaker-nmpimodel</module>
<module>SpiNNaker-nmpiexec</module>
<module>SpiNNaker-proxy</module>
</modules>
<name>SpiNNaker Java Host</name>
<description>Implementation of the host software for SpiNNaker in Java.</description>
Expand Down

0 comments on commit 9842d6a

Please sign in to comment.