dorkbox.network.connection.RegistrationWrapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Network Show documentation
Show all versions of Network Show documentation
Encrypted, high-performance, and event-driven/reactive network stack for Java 11+
/*
* Copyright 2010 dorkbox, llc
*
* 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
*
* http://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 dorkbox.network.connection;
import com.esotericsoftware.kryo.util.ObjectMap;
import dorkbox.network.connection.registration.MetaChannel;
import dorkbox.network.connection.registration.remote.RegistrationRemoteHandler;
import dorkbox.network.pipeline.KryoEncoder;
import dorkbox.network.pipeline.KryoEncoderCrypto;
import dorkbox.util.MathUtil;
import dorkbox.util.collections.IntMap;
import dorkbox.util.crypto.CryptoECC;
import dorkbox.util.exceptions.SecurityException;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.slf4j.Logger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.ReentrantLock;
import static dorkbox.network.connection.registration.remote.RegistrationRemoteHandler.checkEqual;
/**
* Just wraps common/needed methods of the client/server endpoint by the registration stage/handshake.
*
* This is in the connection package, so it can access the endpoint methods that it needs to (without having to publicly expose them)
*/
public
class RegistrationWrapper implements UdpServer {
private final org.slf4j.Logger logger;
private final KryoEncoder kryoEncoder;
private final KryoEncoderCrypto kryoEncoderCrypto;
private final EndPoint endPoint;
// keeps track of connections (TCP/UDT/UDP-client)
private final ReentrantLock channelMapLock = new ReentrantLock();
private final IntMap channelMap = new IntMap();
// keeps track of connections (UDP-server)
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private volatile ObjectMap udpRemoteMap;
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
// use-case 99% of the time)
private final Object singleWriterLock1 = new Object();
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
private static final AtomicReferenceFieldUpdater udpRemoteMapREF =
AtomicReferenceFieldUpdater.newUpdater(RegistrationWrapper.class,
ObjectMap.class,
"udpRemoteMap");
public
RegistrationWrapper(final EndPoint endPoint,
final Logger logger,
final KryoEncoder kryoEncoder,
final KryoEncoderCrypto kryoEncoderCrypto) {
this.endPoint = endPoint;
this.logger = logger;
this.kryoEncoder = kryoEncoder;
this.kryoEncoderCrypto = kryoEncoderCrypto;
if (endPoint instanceof EndPointServer) {
this.udpRemoteMap = new ObjectMap(32, ConnectionManager.LOAD_FACTOR);
}
else {
this.udpRemoteMap = null;
}
}
/**
* @return true if RMI is enabled
*/
public
boolean rmiEnabled() {
return endPoint.globalRmiBridge != null;
}
public
KryoEncoder getKryoEncoder() {
return this.kryoEncoder;
}
public
KryoEncoderCrypto getKryoEncoderCrypto() {
return this.kryoEncoderCrypto;
}
/**
* Locks, and then returns the channelMap used by the registration process.
*
* Make SURE to use this in a try/finally block with releaseChannelMap in the finally block!
*/
private
IntMap getAndLockChannelMap() {
// try to lock access, also guarantees that the contents of this map are visible across threads
this.channelMapLock.lock();
return this.channelMap;
}
private
void releaseChannelMap() {
// try to unlock access
this.channelMapLock.unlock();
}
/**
* The amount of milli-seconds that must elapse with no read or write before {@link Listener:idle()} will be triggered
*/
public
int getIdleTimeout() {
return this.endPoint.getIdleTimeout();
}
/**
* Internal call by the pipeline to notify the client to continue registering the different session protocols. The server does not use
* this.
*
* @return true if we are done registering bootstraps
*/
public
boolean registerNextProtocol0() {
return this.endPoint.registerNextProtocol0();
}
/**
* Internal call by the pipeline to notify the "Connection" object that it has "connected", meaning that modifications to the pipeline
* are finished.
*/
public
void connectionConnected0(ConnectionImpl networkConnection) {
this.endPoint.connectionConnected0(networkConnection);
}
/**
* Internal call by the pipeline when: - creating a new network connection - when determining the baseClass for generics
*
* @param metaChannel
* can be NULL (when getting the baseClass)
*/
public
Connection connection0(MetaChannel metaChannel) {
return this.endPoint.connection0(metaChannel);
}
public
SecureRandom getSecureRandom() {
return this.endPoint.secureRandom;
}
public
ECPublicKeyParameters getPublicKey() {
return this.endPoint.publicKey;
}
public
CipherParameters getPrivateKey() {
return this.endPoint.privateKey;
}
/**
* @return true if the remote address public key matches the one saved
*
* @throws SecurityException
*/
public
boolean validateRemoteAddress(final InetSocketAddress tcpRemoteServer, final ECPublicKeyParameters publicKey)
throws SecurityException {
InetAddress address = tcpRemoteServer.getAddress();
byte[] hostAddress = address.getAddress();
ECPublicKeyParameters savedPublicKey = this.endPoint.propertyStore.getRegisteredServerKey(hostAddress);
Logger logger2 = this.logger;
if (savedPublicKey == null) {
if (logger2.isDebugEnabled()) {
logger2.debug("Adding new remote IP address key for {}", address.getHostAddress());
}
this.endPoint.propertyStore.addRegisteredServerKey(hostAddress, publicKey);
}
else {
// COMPARE!
if (!CryptoECC.compare(publicKey, savedPublicKey)) {
String byAddress;
try {
byAddress = InetAddress.getByAddress(hostAddress)
.getHostAddress();
} catch (UnknownHostException e) {
byAddress = "Unknown Address";
}
if (this.endPoint.disableRemoteKeyValidation) {
logger2.warn("Invalid or non-matching public key from remote server. Their public key has changed. To fix, remove entry for: {}", byAddress);
return true;
}
else {
// keys do not match, abort!
logger2.error("Invalid or non-matching public key from remote server. Their public key has changed. To fix, remove entry for: {}", byAddress);
return false;
}
}
}
return true;
}
@SuppressWarnings("AutoBoxing")
public
void removeRegisteredServerKey(final byte[] hostAddress) throws SecurityException {
ECPublicKeyParameters savedPublicKey = this.endPoint.propertyStore.getRegisteredServerKey(hostAddress);
if (savedPublicKey != null) {
Logger logger2 = this.logger;
if (logger2.isDebugEnabled()) {
logger2.debug("Deleting remote IP address key {}.{}.{}.{}",
hostAddress[0],
hostAddress[1],
hostAddress[2],
hostAddress[3]);
}
this.endPoint.propertyStore.removeRegisteredServerKey(hostAddress);
}
}
/**
* ONLY SERVER SIDE CALLS THIS Called when creating a connection. Only called if we have a UDP channel
*/
@Override
public final
void registerServerUDP(final MetaChannel metaChannel) {
if (metaChannel != null && metaChannel.udpRemoteAddress != null) {
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
// use-case 99% of the time)
synchronized (singleWriterLock1) {
// access a snapshot of the connections (single-writer-principle)
final ObjectMap udpRemoteMap = udpRemoteMapREF.get(this);
udpRemoteMap.put(metaChannel.udpRemoteAddress, metaChannel.connection);
// save this snapshot back to the original (single writer principle)
udpRemoteMapREF.lazySet(this, udpRemoteMap);
}
this.logger.info("Connected to remote UDP connection. [{} <== {}]",
metaChannel.udpChannel.localAddress(),
metaChannel.udpRemoteAddress);
}
}
/**
* ONLY SERVER SIDE CALLS THIS Called when closing a connection.
*/
@Override
public final
void unRegisterServerUDP(final InetSocketAddress udpRemoteAddress) {
if (udpRemoteAddress != null) {
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
// use-case 99% of the time)
synchronized (singleWriterLock1) {
// access a snapshot of the connections (single-writer-principle)
final ObjectMap udpRemoteMap = udpRemoteMapREF.get(this);
udpRemoteMap.remove(udpRemoteAddress);
// save this snapshot back to the original (single writer principle)
udpRemoteMapREF.lazySet(this, udpRemoteMap);
}
logger.info("Closed remote UDP connection: {}", udpRemoteAddress);
}
}
/**
* ONLY SERVER SIDE CALLS THIS
*/
@Override
public
ConnectionImpl getServerUDP(final InetSocketAddress udpRemoteAddress) {
if (udpRemoteAddress != null) {
// access a snapshot of the connections (single-writer-principle)
final ObjectMap udpRemoteMap = udpRemoteMapREF.get(this);
return udpRemoteMap.get(udpRemoteAddress);
}
else {
return null;
}
}
public
void abortRegistrationIfClient() {
if (this.endPoint instanceof EndPointClient) {
((EndPointClient) this.endPoint).abortRegistration();
}
}
public
void addChannel(final int channelHashCodeOrId, final MetaChannel metaChannel) {
try {
IntMap channelMap = this.getAndLockChannelMap();
channelMap.put(channelHashCodeOrId, metaChannel);
} finally {
this.releaseChannelMap();
}
}
public
MetaChannel removeChannel(final int channelHashCodeOrId) {
try {
IntMap channelMap = getAndLockChannelMap();
return channelMap.remove(channelHashCodeOrId);
} finally {
releaseChannelMap();
}
}
public
MetaChannel getChannel(final int channelHashCodeOrId) {
try {
IntMap channelMap = getAndLockChannelMap();
return channelMap.get(channelHashCodeOrId);
} finally {
releaseChannelMap();
}
}
/**
* Closes all connections ONLY (keeps the server/client running).
*
* @param maxShutdownWaitTimeInMilliSeconds
* The amount of time in milli-seconds to wait for this endpoint to close all {@link Channel}s and shutdown gracefully.
*/
public
void closeChannels(final long maxShutdownWaitTimeInMilliSeconds) {
try {
IntMap channelMap = getAndLockChannelMap();
IntMap.Entries entries = channelMap.entries();
while (entries.hasNext()) {
MetaChannel metaChannel = entries.next().value;
metaChannel.close(maxShutdownWaitTimeInMilliSeconds);
Thread.yield();
}
channelMap.clear();
} finally {
releaseChannelMap();
}
}
/**
* Closes the specified connections ONLY (keeps the server/client running).
*
* @param maxShutdownWaitTimeInMilliSeconds
* The amount of time in milli-seconds to wait for this endpoint to close all {@link Channel}s and shutdown gracefully.
*/
public
MetaChannel closeChannel(final Channel channel, final long maxShutdownWaitTimeInMilliSeconds) {
try {
IntMap channelMap = getAndLockChannelMap();
IntMap.Entries entries = channelMap.entries();
while (entries.hasNext()) {
MetaChannel metaChannel = entries.next().value;
if (metaChannel.localChannel == channel ||
metaChannel.tcpChannel == channel ||
metaChannel.udpChannel == channel ||
metaChannel.udtChannel == channel) {
entries.remove();
metaChannel.close(maxShutdownWaitTimeInMilliSeconds);
return metaChannel;
}
}
} finally {
releaseChannelMap();
}
return null;
}
/**
* now that we are CONNECTED, we want to remove ourselves (and channel ID's) from the map.
* they will be ADDED in another map, in the followup handler!!
*/
public
boolean setupChannels(final RegistrationRemoteHandler handler, final MetaChannel metaChannel) {
boolean registerServer = false;
try {
IntMap channelMap = getAndLockChannelMap();
channelMap.remove(metaChannel.tcpChannel.hashCode());
channelMap.remove(metaChannel.connectionID);
ChannelPipeline pipeline = metaChannel.tcpChannel.pipeline();
// The TCP channel is what calls this method, so we can use "this" for TCP, and the others are handled during the registration process
pipeline.remove(handler);
if (metaChannel.udpChannel != null) {
// the setup is different between CLIENT / SERVER
if (metaChannel.udpRemoteAddress == null) {
// CLIENT RUNS THIS
// don't want to muck with the SERVER udp pipeline, as it NEVER CHANGES.
// More specifically, the UDP SERVER doesn't use a channelMap, it uses the udpRemoteMap
// to keep track of UDP connections. This is very different than how the client works
// only the client will have the udp remote address
channelMap.remove(metaChannel.udpChannel.hashCode());
}
else {
// SERVER RUNS THIS
// don't ALWAYS have UDP on SERVER...
registerServer = true;
}
}
} finally {
releaseChannelMap();
}
return registerServer;
}
public
Integer initializeChannel(final MetaChannel metaChannel) {
Integer connectionID = MathUtil.randomInt();
try {
IntMap channelMap = getAndLockChannelMap();
while (channelMap.containsKey(connectionID)) {
connectionID = MathUtil.randomInt();
}
metaChannel.connectionID = connectionID;
channelMap.put(connectionID, metaChannel);
} finally {
releaseChannelMap();
}
return connectionID;
}
public
boolean associateChannels(final Channel channel, final InetAddress remoteAddress, final boolean isUdt) {
boolean success = false;
try {
IntMap channelMap = getAndLockChannelMap();
IntMap.Entries entries = channelMap.entries();
while (entries.hasNext()) {
MetaChannel metaChannel = entries.next().value;
// associate TCP and UDP!
final InetSocketAddress inetSocketAddress = (InetSocketAddress) metaChannel.tcpChannel.remoteAddress();
InetAddress tcpRemoteServer = inetSocketAddress.getAddress();
if (checkEqual(tcpRemoteServer, remoteAddress)) {
channelMap.put(channel.hashCode(), metaChannel);
if (isUdt) {
metaChannel.udtChannel = channel;
}
else {
metaChannel.udpChannel = channel;
}
success = true;
// only allow one server per registration!
break;
}
}
} finally {
releaseChannelMap();
}
return success;
}
public
MetaChannel getAssociatedChannel_UDT(final InetAddress remoteAddress) {
try {
MetaChannel metaChannel;
IntMap channelMap = getAndLockChannelMap();
IntMap.Entries entries = channelMap.entries();
while (entries.hasNext()) {
metaChannel = entries.next().value;
// only look at connections that do not have UDP already setup.
if (metaChannel.udtChannel == null) {
InetSocketAddress tcpRemote = (InetSocketAddress) metaChannel.tcpChannel.remoteAddress();
InetAddress tcpRemoteAddress = tcpRemote.getAddress();
if (RegistrationRemoteHandler.checkEqual(tcpRemoteAddress, remoteAddress)) {
return metaChannel;
}
else {
return null;
}
}
}
} finally {
releaseChannelMap();
}
return null;
}
public
MetaChannel getAssociatedChannel_UDP(final InetAddress remoteAddress) {
try {
MetaChannel metaChannel;
IntMap channelMap = getAndLockChannelMap();
IntMap.Entries entries = channelMap.entries();
while (entries.hasNext()) {
metaChannel = entries.next().value;
// only look at connections that do not have UDP already setup.
if (metaChannel.udpChannel == null) {
InetSocketAddress tcpRemote = (InetSocketAddress) metaChannel.tcpChannel.remoteAddress();
InetAddress tcpRemoteAddress = tcpRemote.getAddress();
if (RegistrationRemoteHandler.checkEqual(tcpRemoteAddress, remoteAddress)) {
return metaChannel;
}
else {
return null;
}
}
}
} finally {
releaseChannelMap();
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy