/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2014, TeleStax Inc. and individual contributors * by the @authors tag. * * This program is free software: you can redistribute it and/or modify * under the terms of the GNU Affero General Public License as * published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> */ package org.jdiameter.server.impl.io.sctp; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import org.jdiameter.api.AvpDataException; import org.jdiameter.client.api.io.NotInitializedException; import org.mobicents.protocols.api.Association; import org.mobicents.protocols.api.AssociationListener; import org.mobicents.protocols.api.IpChannelType; import org.mobicents.protocols.api.Management; import org.mobicents.protocols.api.PayloadData; import org.mobicents.protocols.api.Server; import org.mobicents.protocols.api.ServerListener; import org.mobicents.protocols.sctp.ManagementImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a> * @author <a href="mailto:baranowb@gmail.com"> Bartosz Baranowski </a> */ public class SCTPTransportServer { private Management management = null; private Association serverAssociation = null; private Association remoteClientAssociation = null; private SCTPServerConnection parentConnection; private String serverAssociationName; private String remoteClientAssociationName; private String serverName; protected InetSocketAddress destAddress; protected InetSocketAddress origAddress; private Server server = null; private static final Logger logger = LoggerFactory.getLogger(SCTPTransportServer.class); private int payloadProtocolId = 0; private int streamNumber = 0; public SCTPTransportServer() { } /** * Default constructor * * @param parentConnectionConnection * connection created this transport */ SCTPTransportServer(SCTPServerConnection parentConnectionConnection) { this.parentConnection = parentConnectionConnection; } public SCTPServerConnection getParent() { return parentConnection; } public Management getManagement() { return management; } public void setManagement(Management management) { this.management = management; } public void startNewRemoteConnection(Server server, Association association, String peerAddress, int peerPort) { logger.debug("Initializing new Remote Connection '{}' -> '{}' ---> '{}:{}'", new Object[]{this.origAddress, this.destAddress, peerAddress, peerPort}); remoteClientAssociationName = peerAddress + ":" + peerPort; serverName = server.getName(); try { logger.debug("Adding new server association for [{}:{}]", peerAddress, peerPort); //remoteClientAssociation = management.addServerAssociation(peerAddress, peerPort, serverName, remoteClientAssociationName, // IpChannelType.SCTP); //logger.debug("Setting new Association Listener"); remoteClientAssociation = association; remoteClientAssociation.acceptAnonymousAssociation(new ServerAssociationListener()); //remoteClientAssociation.setAssociationListener(new ServerAssociationListener()); //logger.debug("Starting Association: {}", remoteClientAssociationName); //management.startAssociation(remoteClientAssociationName); // ammendonca: this is managed, no need to do it manually now. // logger.debug("Setting association socket channel"); // remoteClientAssociation.setSocketChannel(socketChannel); // Accept the connection and make it non-blocking // socketChannel.configureBlocking(false); // Register the new SocketChannel with our Selector, // indicating we'd like to be notified when there's data // waiting to be read // logger.debug("registering socketchannel"); // SelectionKey key1 = socketChannel.register(selector, SelectionKey.OP_READ); // logger.debug("Attaching server association to key1"); // key1.attach(((Association) remoteClientAssociation)); logger.info(String.format("Connected to {}", remoteClientAssociation)); } catch (Exception e) { // ammendonca: this is managed, no need to do it manually now. // try { // socketChannel.close(); // } // catch (IOException ex) { // logger.error("Error closing channel: " + ex.getMessage()); // } logger.error("Failed to initialize new remote connection.", e); } } public void startServer() throws NotInitializedException { logger.debug("Initializing SCTP server"); try { if (this.management == null) { this.management = new ManagementImpl("server-management-" + origAddress.getAddress().getHostAddress() + "." + origAddress.getPort()); this.management.setSingleThread(true); this.management.start(); // Clear any saved connections, we will get them from jdiameter-config.xml this.management.removeAllResourses(); } logger.debug("Orig Address: '{}:{}'", origAddress.getAddress().getHostAddress(), origAddress.getPort()); logger.debug("Dest Address: '{}'", this.destAddress); serverAssociationName = origAddress.getHostName() + ":" + origAddress.getPort(); serverName = serverAssociationName; // Let's check if we already have the server configured for (Server s : management.getServers()) { if (s.getName().equals(serverName)) { server = s; break; } } // We don't have any, let's create it if (server == null) { server = this.management.addServer(serverName, origAddress.getAddress().getHostAddress(), origAddress.getPort(), IpChannelType.SCTP, true, 10, null); } for (String assocName : server.getAssociations()) { Association a = management.getAssociation(assocName); if (a.getName().equals(serverAssociationName)) { serverAssociation = a; break; } } if (serverAssociation == null) { serverAssociation = this.management.addServerAssociation(origAddress.getAddress().getHostAddress(), origAddress.getPort(), serverName, serverAssociationName, IpChannelType.SCTP); } this.management.setServerListener(new ServerEventListener()); serverAssociation.setAssociationListener(new ServerAssociationListener()); this.management.startAssociation(serverAssociationName); if (!server.isStarted()) { logger.debug("Starting server"); this.management.startServer(serverName); } } catch (Exception e) { logger.error("Failed to initialize client ", e); } if (getParent() == null) { throw new NotInitializedException("No parent connection is set is set"); } logger.debug("Successfuly initialized SCTP Server Host[{}:{}] Peer[{}:{}]", new Object[] { serverAssociation.getHostAddress(), serverAssociation.getHostPort(), serverAssociation.getPeerAddress(), serverAssociation.getPeerPort() }); logger.debug("Server Association Status: Started[{}] Connected[{}] Up[{}] ", new Object[]{serverAssociation.isStarted(), serverAssociation.isConnected(), serverAssociation.isUp()}); logger.trace("Server Association [{}]", serverAssociation); } private class ServerAssociationListener implements AssociationListener { private final Logger logger = LoggerFactory.getLogger(ServerAssociationListener.class); /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#onCommunicationUp(org.mobicents.protocols.api.Association, int, int) */ @Override public void onCommunicationUp(Association association, int maxInboundStreams, int maxOutboundStreams) { logger.debug("onCommunicationUp called for [{}]", this); getParent().onConnected(); } /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#onCommunicationShutdown(org.mobicents.protocols.api.Association) */ @Override public void onCommunicationShutdown(Association association) { logger.debug("onCommunicationShutdown called for [{}]", this); try { getParent().onDisconnect(); if (remoteClientAssociation != null) { management.stopAssociation(remoteClientAssociationName); management.removeAssociation(remoteClientAssociationName); remoteClientAssociation = null; } } catch (Exception e) { logger.debug("Error", e); } } /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#onCommunicationLost(org.mobicents.protocols.api.Association) */ @Override public void onCommunicationLost(Association association) { logger.debug("onCommunicationLost called for [{}]", this); } /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#onCommunicationRestart(org.mobicents.protocols.api.Association) */ @Override public void onCommunicationRestart(Association association) { logger.debug("onCommunicationRestart called for [{}]", this); } /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#onPayload(org.mobicents.protocols.api.Association, * org.mobicents.protocols.api.PayloadData) */ @Override public void onPayload(Association association, PayloadData payloadData) { // set payload and stream number values; payloadProtocolId = payloadData.getPayloadProtocolId(); streamNumber = payloadData.getStreamNumber(); byte[] data = new byte[payloadData.getDataLength()]; System.arraycopy(payloadData.getData(), 0, data, 0, payloadData.getDataLength()); logger.debug("SCTP Server received a message of length: [{}] ", data.length); try { // make a message out of data and process it getParent().onMessageReceived(ByteBuffer.wrap(data)); } catch (AvpDataException e) { logger.debug("Garbage was received. Discarding."); // storage.clear(); getParent().onAvpDataException(e); } } /* * (non-Javadoc) * * @see org.mobicents.protocols.api.AssociationListener#inValidStreamId(org.mobicents.protocols.api.PayloadData) */ @Override public void inValidStreamId(PayloadData payloadData) { // NOP ? } } private class ServerEventListener implements ServerListener { private final Logger logger = LoggerFactory.getLogger(ServerEventListener.class); @Override public void onNewRemoteConnection(Server server, Association association) { logger.debug("Received notfification of a new remote connection!"); try { // notify network guard that new remote connection is done! getParent().onNewRemoteConnection(server, association); } catch (Exception e) { try { // ammendonca: changed. is it right ? // socketChannel.close(); association.stopAnonymousAssociation(); } catch (Exception ex) { logger.error("Error closing channel: " + ex.getMessage()); } } } } public void destroy() throws Exception { // Stop the SCTP logger.debug("Destroying SCTP Server"); if (remoteClientAssociation != null) { this.management.stopAssociation(remoteClientAssociationName); this.management.removeAssociation(remoteClientAssociationName); remoteClientAssociation = null; } if (serverAssociation != null) { this.management.stopAssociation(serverAssociationName); this.management.removeAssociation(serverAssociationName); this.management.stopServer(serverName); this.management.removeServer(serverName); this.management.stop(); serverAssociation = null; } } public void stop() throws Exception { logger.debug("Stopping SCTP Server"); // Note we never stop the server association as it is always listening - we only stop the remote client association if (remoteClientAssociation != null) { this.management.stopAssociation(remoteClientAssociationName); } } public void release() throws Exception { logger.debug("Releasing SCTP Server"); // Note we never release the server association as it is always listening - we only stop the remote client association this.stop(); if (remoteClientAssociation != null) { this.management.removeAssociation(remoteClientAssociationName); remoteClientAssociation = null; } // destAddress = null; } public InetSocketAddress getDestAddress() { return this.destAddress; } public void setDestAddress(InetSocketAddress address) { this.destAddress = address; if (logger.isDebugEnabled()) { logger.debug("Destination address is set to [{}:{}]", destAddress.getHostName(), destAddress.getPort()); } } public void setOrigAddress(InetSocketAddress address) { this.origAddress = address; if (logger.isDebugEnabled()) { logger.debug("Origin address is set to [{}:{}]", origAddress.getHostName(), origAddress.getPort()); } } public InetSocketAddress getOrigAddress() { return this.origAddress; } public void sendMessage(ByteBuffer bytes) throws IOException { if (logger.isDebugEnabled()) { logger.debug("About to send a byte buffer of size [{}] over the SCTP", bytes.array().length); } PayloadData payloadData = new PayloadData(bytes.array().length, bytes.array(), true, false, payloadProtocolId, streamNumber); try { this.remoteClientAssociation.send(payloadData); } catch (Exception e) { logger.error("Failed sending byte buffer over SCTP", e); } if (logger.isDebugEnabled()) { logger.debug("Sent a byte buffer of size [{}] over SCTP", bytes.array().length); } } boolean isConnected() { if (remoteClientAssociation == null) { return false; } return this.remoteClientAssociation.isConnected(); } }