/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2015, 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.mobicents.tools.smpp.balancer.impl; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLEngine; import org.apache.log4j.Logger; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; import org.jboss.netty.handler.ssl.SslHandler; import org.jboss.netty.util.internal.ConcurrentHashMap; import org.mobicents.tools.heartbeat.api.Node; import org.mobicents.tools.sip.balancer.BalancerRunner; import org.mobicents.tools.sip.balancer.InvocationContext; import org.mobicents.tools.sip.balancer.KeySmpp; import org.mobicents.tools.smpp.balancer.api.ClientConnection; import org.mobicents.tools.smpp.balancer.core.BalancerDispatcher; import org.mobicents.tools.smpp.balancer.timers.ServerTimerResponse; import org.mobicents.tools.smpp.balancer.timers.ServerTimerConnectionCheck; import org.mobicents.tools.smpp.balancer.timers.TimerData; import com.cloudhopper.smpp.SmppBindType; import com.cloudhopper.smpp.SmppConstants; import com.cloudhopper.smpp.SmppSessionConfiguration; import com.cloudhopper.smpp.channel.SmppChannelConstants; import com.cloudhopper.smpp.channel.SmppSessionPduDecoder; import com.cloudhopper.smpp.pdu.BaseBind; import com.cloudhopper.smpp.pdu.BindReceiver; import com.cloudhopper.smpp.pdu.BindTransceiver; import com.cloudhopper.smpp.pdu.BindTransmitter; import com.cloudhopper.smpp.pdu.EnquireLink; import com.cloudhopper.smpp.pdu.EnquireLinkResp; import com.cloudhopper.smpp.pdu.Pdu; import com.cloudhopper.smpp.pdu.PduRequest; import com.cloudhopper.smpp.pdu.PduResponse; import com.cloudhopper.smpp.pdu.Unbind; import com.cloudhopper.smpp.ssl.SslConfiguration; import com.cloudhopper.smpp.ssl.SslContextFactory; import com.cloudhopper.smpp.transcoder.DefaultPduTranscoder; import com.cloudhopper.smpp.transcoder.DefaultPduTranscoderContext; import com.cloudhopper.smpp.transcoder.PduTranscoder; import com.cloudhopper.smpp.type.RecoverablePduException; import com.cloudhopper.smpp.type.UnrecoverablePduException; /** * @author Konstantin Nosach (kostyantyn.nosach@telestax.com) */ public class ClientConnectionImpl implements ClientConnection{ private static final Logger logger = Logger.getLogger(ClientConnectionImpl.class); private Channel channel; private ClientBootstrap clientBootstrap; private ClientConnectionHandlerImpl clientConnectionHandler; private SmppSessionConfiguration config; private Pdu bindPacket; private final PduTranscoder transcoder; private ClientState clientState=ClientState.INITIAL; private AtomicInteger lastSequenceNumberSent = new AtomicInteger(0); private BalancerDispatcher lbClientListener; private Long sessionId; private Map<Integer, TimerData> packetMap = new ConcurrentHashMap <Integer, TimerData>(); private Map<Integer, Integer> sequenceMap = new ConcurrentHashMap <Integer, Integer>(); private ScheduledExecutorService monitorExecutor; private Node node; private long timeoutResponse; private boolean isEnquireLinkSent; private InvocationContext invocationContext; //private ScheduledFuture<?> connectionCheckServerSideTimer; private ServerTimerConnectionCheck connectionCheck; public boolean isEnquireLinkSent() { return isEnquireLinkSent; } public SmppSessionConfiguration getConfig() { return config; } public ClientState getClientState() { return clientState; } public void setClientState(ClientState clientState) { this.clientState = clientState; } public Long getSessionId() { return sessionId; } public enum ClientState { INITIAL, OPEN, BINDING, BOUND, REBINDING, UNBINDING, CLOSED } public ClientConnectionImpl(Long sessionId,SmppSessionConfiguration config, BalancerDispatcher clientListener, ScheduledExecutorService monitorExecutor, BalancerRunner balancerRunner, Pdu bindPacket, Node node) { this.node = node; this.bindPacket = bindPacket; this.timeoutResponse = balancerRunner.balancerContext.lbConfig.getSmppConfiguration().getTimeoutResponse(); this.monitorExecutor = monitorExecutor; this.sessionId = sessionId; this.config = config; this.transcoder = new DefaultPduTranscoder(new DefaultPduTranscoderContext()); this.lbClientListener=clientListener; this.clientConnectionHandler = new ClientConnectionHandlerImpl(this); this.clientBootstrap = new ClientBootstrap(new NioClientSocketChannelFactory()); this.clientBootstrap.getPipeline().addLast(SmppChannelConstants.PIPELINE_SESSION_PDU_DECODER_NAME, new SmppSessionPduDecoder(transcoder)); this.clientBootstrap.getPipeline().addLast(SmppChannelConstants.PIPELINE_CLIENT_CONNECTOR_NAME, this.clientConnectionHandler); this.invocationContext = balancerRunner.getLatestInvocationContext(); } @Override public Boolean connect() { ChannelFuture channelFuture = null; try { if(logger.isDebugEnabled()) logger.debug("LB trying to connect to server " + config.getHost() + " " + config.getPort()); channelFuture = clientBootstrap.connect(new InetSocketAddress(config.getHost(), config.getPort())).sync(); channel = channelFuture.getChannel(); if (config.isUseSsl()) { SslConfiguration sslConfig = config.getSslConfiguration(); if (sslConfig == null) throw new IllegalStateException("sslConfiguration must be set"); try { SslContextFactory factory = new SslContextFactory(sslConfig); SSLEngine sslEngine = factory.newSslEngine(); sslEngine.setUseClientMode(true); channel.getPipeline().addFirst(SmppChannelConstants.PIPELINE_SESSION_SSL_NAME, new SslHandler(sslEngine)); } catch (Exception e) { logger.error("Unable to create SSL session: " + e.getMessage(), e); } } } catch (Exception ex) { return false; } if(clientState!=ClientState.REBINDING) clientState = ClientState.OPEN; return true; } @SuppressWarnings("rawtypes") @Override public void bind() { BaseBind packet = null; if (config.getType() == SmppBindType.TRANSCEIVER) packet = new BindTransceiver(); else if (config.getType() == SmppBindType.RECEIVER) packet = new BindReceiver(); else if (config.getType() == SmppBindType.TRANSMITTER) packet = new BindTransmitter(); packet.setSystemId(config.getSystemId()); packet.setPassword(config.getPassword()); packet.setSystemType(config.getSystemType()); packet.setInterfaceVersion(config.getInterfaceVersion()); packet.setAddressRange(config.getAddressRange()); packet.setSequenceNumber(lastSequenceNumberSent.incrementAndGet()); ChannelBuffer buffer = null; try { buffer = transcoder.encode(packet); } catch (UnrecoverablePduException e) { logger.error("Encode error: ", e); } catch(RecoverablePduException e) { logger.error("Encode error: ", e); } if(clientState!=ClientState.REBINDING) clientState=ClientState.BINDING; if(logger.isDebugEnabled()) logger.debug("LB trying to bind to server " + config.getHost() + " " + config.getPort() + ": client state " + clientState); channel.write(buffer); } @Override public void packetReceived(Pdu packet) { switch (clientState) { case INITIAL: case OPEN: logger.error("LB received packet ("+packet+") in initial or open state from " + channel.getRemoteAddress().toString()+" sessionId : " +sessionId); break; case BINDING: Boolean correctPacket = false; switch (config.getType()) { case TRANSCEIVER: if (packet.getCommandId() == SmppConstants.CMD_ID_BIND_TRANSCEIVER_RESP) correctPacket = true; break; case RECEIVER: if (packet.getCommandId() == SmppConstants.CMD_ID_BIND_RECEIVER_RESP) correctPacket = true; break; case TRANSMITTER: if (packet.getCommandId() == SmppConstants.CMD_ID_BIND_TRANSMITTER_RESP) correctPacket = true; break; } if (!correctPacket) logger.error("Received invalid packet in binding state, packet type: " + packet.getName()); else { if (packet.getCommandStatus() == SmppConstants.STATUS_OK) { if(logger.isDebugEnabled()) logger.debug("LB received bind response (" + packet + ") from " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); clientState = ClientState.BOUND; lbClientListener.bindSuccesfull(sessionId, packet); } else { logger.error("Binding to " + channel.getRemoteAddress().toString() + " is unsuccesful. sessionId" + sessionId + " , error code: " + packet.getCommandStatus()); lbClientListener.bindFailed(sessionId, packet); closeChannel(); clientState = ClientState.CLOSED; } } break; case BOUND: correctPacket = false; switch (packet.getCommandId()) { case SmppConstants.CMD_ID_CANCEL_SM_RESP: case SmppConstants.CMD_ID_DATA_SM_RESP: case SmppConstants.CMD_ID_QUERY_SM_RESP: case SmppConstants.CMD_ID_REPLACE_SM_RESP: case SmppConstants.CMD_ID_SUBMIT_SM_RESP: case SmppConstants.CMD_ID_SUBMIT_MULTI_RESP: if(logger.isDebugEnabled()) logger.debug("LB received SMPP response (" + packet + ") from " + channel.getRemoteAddress().toString() + ". sessionId: " + sessionId); Integer originalSequence=sequenceMap.remove(packet.getSequenceNumber()); if(originalSequence!=null) { packet.setSequenceNumber(originalSequence); correctPacket = true; this.lbClientListener.smppEntityResponse(sessionId, packet); } break; case SmppConstants.CMD_ID_GENERIC_NACK: if(logger.isDebugEnabled()) logger.debug("LB received generic nack (" + packet + ") from " + channel.getRemoteAddress().toString() +". sessionId : " + sessionId); correctPacket = true; this.lbClientListener.smppEntityResponse(sessionId, packet); break; case SmppConstants.CMD_ID_ENQUIRE_LINK_RESP: if(logger.isDebugEnabled()) logger.debug("LB received enquire_link response from " + channel.getRemoteAddress().toString() + ". session ID : "+ sessionId); correctPacket = true; isEnquireLinkSent = false; connectionCheck.cancel(); //connectionCheckServerSideTimer.cancel(false); this.lbClientListener.enquireLinkReceivedFromServer(sessionId); break; case SmppConstants.CMD_ID_DATA_SM: case SmppConstants.CMD_ID_DELIVER_SM: if(logger.isDebugEnabled()) logger.debug("LB received SMPP request (" + packet + ") from " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); correctPacket = true; ServerTimerResponse response=new ServerTimerResponse(this ,packet); packetMap.put(packet.getSequenceNumber(), new TimerData(packet, monitorExecutor.schedule(response,timeoutResponse,TimeUnit.MILLISECONDS),response)); this.lbClientListener.smppEntityRequestFromServer(sessionId, packet); break; case SmppConstants.CMD_ID_ENQUIRE_LINK: if(logger.isDebugEnabled()) logger.debug("LB received enquire_link request from " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); correctPacket = true; EnquireLinkResp resp=new EnquireLinkResp(); resp.setSequenceNumber(packet.getSequenceNumber()); sendSmppResponse(resp); break; case SmppConstants.CMD_ID_UNBIND: if(logger.isDebugEnabled()) logger.debug("LB received unbind request from " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); correctPacket = true; response=new ServerTimerResponse(this ,packet); packetMap.put(packet.getSequenceNumber(), new TimerData(packet, monitorExecutor.schedule(response,timeoutResponse,TimeUnit.MILLISECONDS),response)); lbClientListener.unbindRequestedFromServer(sessionId, packet); clientState = ClientState.UNBINDING; break; } if (!correctPacket) { logger.error("LB received invalid packet in bound state. sessionId : " + sessionId + ". packet : " + packet); } break; case REBINDING: switch (packet.getCommandId()) { case SmppConstants.CMD_ID_BIND_RECEIVER_RESP: case SmppConstants.CMD_ID_BIND_TRANSCEIVER_RESP: case SmppConstants.CMD_ID_BIND_TRANSMITTER_RESP: if (packet.getCommandStatus() == SmppConstants.STATUS_OK) { if(logger.isDebugEnabled()) logger.debug("Connection reconnected to " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); this.lbClientListener.reconnectSuccesful(sessionId); clientState = ClientState.BOUND; if(invocationContext.activeNodeKey!=null) invocationContext.activeNodeKey = new KeySmpp((Node) new ArrayList(invocationContext.smppNodeMap.values()).get(0)); }else { logger.debug("Reconnection to client unsuccessful. client session ID : " + sessionId + ". LB will close session!"); this.lbClientListener.unbindRequestedFromServer(sessionId, new Unbind()); } } break; case UNBINDING: correctPacket = false; if (packet.getCommandId() == SmppConstants.CMD_ID_UNBIND_RESP) correctPacket = true; if (!correctPacket) logger.error("LB received invalid packet (" + packet + ") form "+channel.getRemoteAddress().toString()+" in unbinding state. sessionId : " + sessionId); else { if(logger.isDebugEnabled()) logger.debug("LB received unbind response form " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); Integer originalSequence=sequenceMap.remove(packet.getSequenceNumber()); if(originalSequence!=null) { packet.setSequenceNumber(originalSequence); this.lbClientListener.unbindSuccesfull(sessionId, packet); } sequenceMap.clear(); packetMap.clear(); closeChannel(); clientState = ClientState.CLOSED; } break; case CLOSED: logger.error("LB received packet ("+packet+") in closed state from " + channel.getRemoteAddress().toString()+" sessionId : " +sessionId); break; } } @Override public void sendUnbindRequest(Pdu packet) { Integer currSequence=lastSequenceNumberSent.incrementAndGet(); sequenceMap.put(currSequence, packet.getSequenceNumber()); packet.setSequenceNumber(currSequence); ChannelBuffer buffer = null; try { buffer = transcoder.encode(packet); } catch (UnrecoverablePduException e ) { logger.error("Encode error: ", e); } catch (RecoverablePduException e){ logger.error("Encode error: ", e); } clientState = ClientState.UNBINDING; if(logger.isDebugEnabled()) logger.debug("LB sent unbind request (" + packet + ") to " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); channel.write(buffer); } @Override public void sendSmppRequest(Pdu packet) { Integer currSequence=lastSequenceNumberSent.incrementAndGet(); sequenceMap.put(currSequence, packet.getSequenceNumber()); packet.setSequenceNumber(currSequence); ChannelBuffer buffer = null; try { buffer = transcoder.encode(packet); } catch (UnrecoverablePduException e) { logger.error("Encode error: ", e); } catch(RecoverablePduException e){ logger.error("Encode error: ", e); } if(logger.isDebugEnabled()) logger.debug("LB sent SMPP request (" + packet + ") to " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); channel.write(buffer); } @Override public void sendSmppResponse(Pdu packet) { if(packetMap.containsKey(packet.getSequenceNumber())) { TimerData data=packetMap.remove(packet.getSequenceNumber()); if(data!=null) { data.getRunnable().cancel(); data.getScheduledFuture().cancel(false); } } ChannelBuffer buffer = null; try { buffer = transcoder.encode(packet); } catch (UnrecoverablePduException e) { logger.error("Encode error: ", e); } catch(RecoverablePduException e){ logger.error("Encode error: ", e); } if(logger.isDebugEnabled()) logger.debug("LB sent SMPP response (" + packet + ") to " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); channel.write(buffer); } @Override public void sendUnbindResponse(Pdu packet) { if(packetMap.containsKey(packet.getSequenceNumber())) { TimerData data=packetMap.remove(packet.getSequenceNumber()); if(data!=null) { data.getRunnable().cancel(); data.getScheduledFuture().cancel(false); } } ChannelBuffer buffer = null; try { buffer = transcoder.encode(packet); } catch (UnrecoverablePduException e) { logger.error("Encode error: ", e); } catch(RecoverablePduException e){ logger.error("Encode error: ", e); } clientState = ClientState.CLOSED; sequenceMap.clear(); packetMap.clear(); if(logger.isDebugEnabled()) logger.debug("LB sent unbind response (" + packet + ") to " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); channel.write(buffer); closeChannel(); } @Override public void rebind() { if(logger.isDebugEnabled()) logger.debug("LB tried to rebind to client " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); if(invocationContext.activeNodeKey!=null) invocationContext.activeNodeKey = new KeySmpp((Node) new ArrayList(invocationContext.smppNodeMap.values()).get(0)); clientState = ClientState.REBINDING; this.lbClientListener.connectionLost(sessionId, bindPacket, node); } @Override public void requestTimeout(Pdu packet) { if (!packetMap.containsKey(packet.getSequenceNumber())) if(logger.isDebugEnabled()) logger.debug("<<requestTimeout>> LB received SMPP response from client in time for server " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); else { if(logger.isDebugEnabled()) logger.debug("<<requestTimeout>> We did NOT take SMPP response in time from client with sessionId : " + sessionId); lbClientListener.getNotRespondedPackets().incrementAndGet(); packetMap.remove(packet.getSequenceNumber()); PduResponse pduResponse = ((PduRequest<?>) packet).createResponse(); pduResponse.setCommandStatus(SmppConstants.STATUS_SYSERR); sendSmppResponse(pduResponse); } } @Override public void generateEnquireLink() { Pdu packet = new EnquireLink(); packet.setSequenceNumber(lastSequenceNumberSent.incrementAndGet()); ChannelBuffer buffer = null; try { buffer = transcoder.encode(packet); } catch (UnrecoverablePduException e) { logger.error("Encode error: ", e); } catch(RecoverablePduException e){ logger.error("Encode error: ", e); } isEnquireLinkSent = true; connectionCheck=new ServerTimerConnectionCheck(this); //connectionCheckServerSideTimer = monitorExecutor.schedule(connectionCheck,timeoutConnectionCheckServerSide,TimeUnit.MILLISECONDS); if(logger.isDebugEnabled()) logger.debug("LB sent enquire_link to " + channel.getRemoteAddress().toString() + ". sessionId : " + sessionId); channel.write(buffer); } @Override public void closeChannel() { if(channel.getPipeline().getLast()!=null) channel.getPipeline().removeLast(); channel.close(); } @Override public void connectionCheckClientSide() { rebind(); } @Override public void sendSmppRequest(Long sessionId, Pdu packet) { // TODO Auto-generated method stub } @Override public void enquireLinkTimerCheck() { // TODO Auto-generated method stub } }