All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dorkbox.network.connection.RegistrationWrapper Maven / Gradle / Ivy

/*
 * 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