Skip to content

Commit

Permalink
Server listens on TCP and TLS, allowing both plain and TLS clients
Browse files Browse the repository at this point in the history
.. if TLS is enabled, otherwise only TCP
  • Loading branch information
kasemir committed Aug 9, 2023
1 parent 7dc08d4 commit 37c4d12
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public void encodeRequest(final byte version, final ByteBuffer buffer) throws Ex
};

/** Pool for sender and receiver threads */
// Default keeps idle threads for one minute
private static final ExecutorService thread_pool = Executors.newCachedThreadPool(runnable ->
{
final Thread thread = new Thread(runnable);
Expand Down
12 changes: 7 additions & 5 deletions core/pva/src/main/java/org/epics/pva/server/PVAServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,12 @@ public PVAServer(final SearchHandler search_handler) throws Exception
tcp = new ServerTCPListener(this);
}

/** @return TCP address and port where server is accepting clients */
public InetSocketAddress getTCPAddress()
/** @param tls Request TLS or plain TCP address?
* @return TCP address and port where server is accepting clients
*/
public InetSocketAddress getTCPAddress(final boolean tls)
{
return tcp.getResponseAddress();
return tcp.getResponseAddress(tls);
}

/** Create a read-only PV which serves data to clients
Expand Down Expand Up @@ -196,7 +198,7 @@ boolean handleSearchRequest(final int seq, final int cid, final String name,
if (tcp_connection != null)
tcp_connection.submitSearchReply(guid, seq, -1, USE_THIS_TCP_CONNECTION, tls);
else
POOL.execute(() -> udp.sendSearchReply(guid, 0, -1, getTCPAddress(), tls, client));
POOL.execute(() -> udp.sendSearchReply(guid, 0, -1, getTCPAddress(tls), tls, client));
return true;
}
else
Expand All @@ -213,7 +215,7 @@ boolean handleSearchRequest(final int seq, final int cid, final String name,
if (tcp_connection != null)
send_search_reply.accept(USE_THIS_TCP_CONNECTION);
else
send_search_reply.accept(getTCPAddress());
send_search_reply.accept(getTCPAddress(tls));
return true;
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public ServerTCPHandler(final PVAServer server, final Socket client) throws Exce
// TODO ServerAuthentication
PVAString.encodeString("ca", buffer);
PVAString.encodeString("anonymous", buffer);
// TODO "x509"
});
}

Expand Down
90 changes: 71 additions & 19 deletions core/pva/src/main/java/org/epics/pva/server/ServerTCPListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -41,11 +42,11 @@ class ServerTCPListener

private final PVAServer server;

/** TCP channel on which we listen for connections */
private final ServerSocket server_socket;
/** Open TCP socket on which we listen for clients */
private final ServerSocket tcp_server_socket;

/** Server's TCP address on which clients can connect */
private final InetSocketAddress local_address;
/** Secure TCP socket on which we listen for clients */
private final ServerSocket tls_server_socket;

private volatile boolean running = true;
private volatile Thread listen_thread;
Expand All @@ -57,25 +58,37 @@ public ServerTCPListener(final PVAServer server) throws Exception
// Is TLS configured?
final boolean tls = !PVASettings.EPICS_PVAS_TLS_KEYCHAIN.isBlank();

// TODO Support both plain and tls, not either/or
if (! tls)
server_socket = createSocket(PVASettings.EPICS_PVA_SERVER_PORT, false);
else
server_socket = createSocket(PVASettings.EPICS_PVAS_TLS_PORT, true);

local_address = (InetSocketAddress) server_socket.getLocalSocketAddress();
// Support open TCP, maybe also TLS
tcp_server_socket = createSocket(PVASettings.EPICS_PVA_SERVER_PORT, false);
InetSocketAddress local_address = (InetSocketAddress) tcp_server_socket.getLocalSocketAddress();
logger.log(Level.CONFIG, "Listening on TCP " + local_address);
String name = "TCP-listener " + local_address.getAddress() + ":" + local_address.getPort();

if (tls)
{
tls_server_socket = createSocket(PVASettings.EPICS_PVAS_TLS_PORT, true);
local_address = (InetSocketAddress) tls_server_socket.getLocalSocketAddress();
logger.log(Level.CONFIG, "Listening on TLS " + local_address);
name += ", TLS:" + local_address.getPort();
}
else
tls_server_socket = null;

// Start accepting connections
listen_thread = new Thread(this::listen, "TCP-listener " + local_address.getAddress() + ":" + local_address.getPort());
listen_thread = new Thread(this::listen, name);
listen_thread.setDaemon(true);
listen_thread.start();
}

/** @return Server's TCP address on which clients can connect */
public InetSocketAddress getResponseAddress()
/** @param tls Request TLS or plain TCP address?
* @return Server's TCP address on which clients can connect
*/
public InetSocketAddress getResponseAddress(final boolean tls)
{
return local_address;
if (tls && tls_server_socket != null)
return (InetSocketAddress) tls_server_socket.getLocalSocketAddress();

return (InetSocketAddress) tcp_server_socket.getLocalSocketAddress();
}

// How to check if the desired TCP server port is already in use?
Expand Down Expand Up @@ -165,11 +178,47 @@ private void listen()
try
{
logger.log(Level.FINER, Thread.currentThread().getName() + " started");

// Assume that open TCP, secure TLS, or both are configured.
// Need to accept clients on any.
// ServerSocketChannel allows using the Selector,
// but there is no SSLServerSocketChannel.
// SSLContext only creates (SSL)ServerSocket, and no easy way to
// turn (SSL)ServerSocket into (SSL)ServerSocketChannel
// https://stackoverflow.com/questions/37763038/is-there-any-way-to-use-sslcontext-with-serversocketchannel
//
// As a workaround we configure a (short) timeout on the sockets
// and then take turns 'accept'ing from them
if (tcp_server_socket != null)
tcp_server_socket.setSoTimeout(10);
if (tls_server_socket != null)
tls_server_socket.setSoTimeout(10);
while (running)
{
final Socket client = server_socket.accept();
logger.log(Level.FINE, () -> Thread.currentThread().getName() + " accepted client " + client.getRemoteSocketAddress());
new ServerTCPHandler(server, client);
if (tcp_server_socket != null)
{
try
{ // Check TCP
final Socket client = tcp_server_socket.accept();
logger.log(Level.FINE, () -> Thread.currentThread().getName() + " accepted TCP client " + client.getRemoteSocketAddress());
new ServerTCPHandler(server, client);
}
catch (SocketTimeoutException timeout)
{ // Ignore
}
}
if (tls_server_socket != null)
{
try
{ // Check TLS
final Socket client = tls_server_socket.accept();
logger.log(Level.FINE, () -> Thread.currentThread().getName() + " accepted TLS client " + client.getRemoteSocketAddress());
new ServerTCPHandler(server, client);
}
catch (SocketTimeoutException timeout)
{ // Ignore
}
}
}
}
catch (Exception ex)
Expand All @@ -186,7 +235,10 @@ public void close()
// Close sockets, wait a little for threads to exit
try
{
server_socket.close();
if (tcp_server_socket != null)
tcp_server_socket.close();
if (tls_server_socket != null)
tls_server_socket.close();

if (listen_thread != null)
listen_thread.join(5000);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2020-2022 Oak Ridge National Laboratory.
* Copyright (c) 2020-2023 Oak Ridge National Laboratory.
* 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
Expand Down Expand Up @@ -116,7 +116,7 @@ public static void main(String[] args) throws Exception
{
System.out.println("For UDP search, run 'pvget' or 'pvxget' with");
System.out.println("EPICS_PVA_BROADCAST_PORT=" + PVASettings.EPICS_PVAS_BROADCAST_PORT);
System.out.println("For TCP search, set EPICS_PVA_NAME_SERVERS = " + server.getTCPAddress());
System.out.println("For TCP search, set EPICS_PVA_NAME_SERVERS = " + server.getTCPAddress(false));
System.out.println("or other IP address of this host and same port.");
System.out.println("Run 'pvget QUIT' to stop");
done.await();
Expand Down

0 comments on commit 37c4d12

Please sign in to comment.