/* * 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.multiplexer; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.log4j.Logger; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.util.internal.ConcurrentHashMap; import org.mobicents.tools.sip.balancer.BalancerRunner; import org.mobicents.tools.smpp.balancer.api.ServerConnection; import org.mobicents.tools.smpp.balancer.timers.CustomerTimerConnection; import org.mobicents.tools.smpp.balancer.timers.CustomerTimerConnectionCheck; import org.mobicents.tools.smpp.balancer.timers.CustomerTimerEnquire; import org.mobicents.tools.smpp.balancer.timers.CustomerTimerResponse; 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.pdu.BaseBind; import com.cloudhopper.smpp.pdu.EnquireLink; import com.cloudhopper.smpp.pdu.EnquireLinkResp; import com.cloudhopper.smpp.pdu.GenericNack; 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.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 MServerConnectionImpl implements ServerConnection { private static final Logger logger = Logger.getLogger(MServerConnectionImpl.class); private ServerState serverState = ServerState.OPEN; private MBalancerDispatcher lbServerListener; private Long sessionId; private SmppSessionConfiguration config = new SmppSessionConfiguration(); private Channel channel; private final PduTranscoder transcoder; private Map<Integer, TimerData> packetMap = new ConcurrentHashMap <Integer, TimerData>(); private Map<Integer, CustomerPacket> sequenceMap = new ConcurrentHashMap <Integer, CustomerPacket>(); private UserSpace userSpace; private BaseBind<?> bindRequest; public BaseBind<?> getBindRequest() { return bindRequest; } private ScheduledFuture<?> connectionTimer; private CustomerTimerConnection connectionRunnable; private ScheduledFuture<?> enquireTimer; private CustomerTimerEnquire enquireRunnable; private ScheduledFuture<?> connectionCheckTimer; private CustomerTimerConnectionCheck connectionCheckRunnable; private long timeoutResponse; private long timeoutConnection; private long timeoutEnquire; private long timeoutConnectionCheckClientSide; private long timeoutConnectionCheckServerSide; private ScheduledExecutorService monitorExecutor; private AtomicInteger lastSequenceNumberSent = new AtomicInteger(1); private long lastTimeSMPPLinkUpdated = System.currentTimeMillis(); // When Kos wrote this, only God and him understood what he was doing // Now, God only knows // private boolean isClientSideOk; // private boolean isServerSideOk; public MServerConnectionImpl(Long sessionId, Channel channel, MBalancerDispatcher lbServerListener, BalancerRunner balancerRunner, ScheduledExecutorService monitorExecutor, boolean useSsl) { this.lbServerListener = lbServerListener; this.channel = channel; this.sessionId = sessionId; this.config.setUseSsl(useSsl); this.transcoder = new DefaultPduTranscoder(new DefaultPduTranscoderContext()); this.timeoutResponse = balancerRunner.balancerContext.lbConfig.getSmppConfiguration().getTimeoutResponse(); this.timeoutConnection = balancerRunner.balancerContext.lbConfig.getSmppConfiguration().getTimeoutConnection(); this.timeoutEnquire = balancerRunner.balancerContext.lbConfig.getSmppConfiguration().getTimeoutEnquire(); this.timeoutConnectionCheckClientSide = balancerRunner.balancerContext.lbConfig.getSmppConfiguration().getTimeoutConnectionCheckClientSide(); this.timeoutConnectionCheckServerSide = balancerRunner.balancerContext.lbConfig.getSmppConfiguration().getTimeoutConnectionCheckServerSide(); this.monitorExecutor = monitorExecutor; this.connectionRunnable = new CustomerTimerConnection(this, sessionId); this.connectionTimer = monitorExecutor.schedule(connectionRunnable,timeoutConnection,TimeUnit.MILLISECONDS); this.connectionCheckRunnable=new CustomerTimerConnectionCheck(this, sessionId); this.connectionCheckTimer = monitorExecutor.scheduleWithFixedDelay(connectionCheckRunnable, timeoutConnection, timeoutConnection, TimeUnit.MILLISECONDS); this.enquireRunnable=new CustomerTimerEnquire(this); this.enquireTimer = monitorExecutor.scheduleAtFixedRate(enquireRunnable,timeoutEnquire,timeoutEnquire,TimeUnit.MILLISECONDS); if(logger.isDebugEnabled()) { logger.debug("timeoutConnectionCheckClientSide " + timeoutConnectionCheckClientSide); logger.debug("timeoutConnectionCheckServerSide " + timeoutConnectionCheckServerSide); logger.debug("timeoutConnection " + timeoutConnection); logger.debug("timeoutEnquire " + timeoutEnquire); logger.debug("channel " + channel.getRemoteAddress().toString()); } } public Long getSessionId() { return sessionId; } public SmppSessionConfiguration getConfig() { return config; } public enum ServerState { OPEN, BINDING, BOUND, REBINDING, UNBINDING, CLOSED } @Override public void packetReceived(Pdu packet) { switch (serverState) { case OPEN: Boolean correctPacket = false; switch (packet.getCommandId()) { case SmppConstants.CMD_ID_BIND_RECEIVER: correctPacket = true; config.setType(SmppBindType.RECEIVER); break; case SmppConstants.CMD_ID_BIND_TRANSCEIVER: correctPacket = true; config.setType(SmppBindType.TRANSCEIVER); break; case SmppConstants.CMD_ID_BIND_TRANSMITTER: correctPacket = true; config.setType(SmppBindType.TRANSMITTER); break; } if (!correctPacket) { logger.error("Unable to convert a BaseBind request of server " + channel.getRemoteAddress().toString() + ". session ID: " + sessionId); sendGenericNack(packet); channel.close(); serverState = ServerState.CLOSED; } else { if(logger.isDebugEnabled()) logger.debug("LB received bind request (" + packet + ") from server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); serverState = ServerState.BINDING; // TODO Do it on the response instead as if we can't bind, we will still enquire // enquireRunnable=new CustomerTimerEnquire(this); // enquireTimer = monitorExecutor.scheduleAtFixedRate(enquireRunnable,timeoutEnquire,timeoutEnquire,TimeUnit.MILLISECONDS); if(connectionTimer!=null) { connectionRunnable.cancel(); connectionTimer.cancel(false); } this.bindRequest = (BaseBind<?>) packet; //config.setName("LoadBalancerSession." + this.bindRequest.getSystemId() + "." + this.bindRequest.getSystemType()); config.setSystemId(this.bindRequest.getSystemId()); config.setPassword(this.bindRequest.getPassword()); config.setSystemType(this.bindRequest.getSystemType()); config.setAddressRange(this.bindRequest.getAddressRange()); config.setInterfaceVersion(this.bindRequest.getInterfaceVersion()); CustomerTimerResponse responseTimer=new CustomerTimerResponse(this ,packet); packetMap.put(packet.getSequenceNumber(), new TimerData(packet, monitorExecutor.schedule(responseTimer,timeoutResponse,TimeUnit.MILLISECONDS),responseTimer)); userSpace = lbServerListener.bindRequested(sessionId, this, this.bindRequest); userSpace.bind(this, packet); } break; case BINDING: logger.error("LB received packet in incorrect state (BINDING). session ID : " + sessionId + " .packet : " + packet); break; case BOUND: correctPacket = false; switch (packet.getCommandId()) { case SmppConstants.CMD_ID_UNBIND: if(logger.isDebugEnabled()) logger.debug("LB received unbind request from server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); correctPacket = true; enquireRunnable.cancel(); enquireTimer.cancel(false); CustomerTimerResponse responseTimer=new CustomerTimerResponse(this ,packet); packetMap.put(packet.getSequenceNumber(), new TimerData(packet, monitorExecutor.schedule(responseTimer,timeoutResponse,TimeUnit.MILLISECONDS),responseTimer)); userSpace.unbind(sessionId, (Unbind)packet); serverState = ServerState.UNBINDING; break; case SmppConstants.CMD_ID_CANCEL_SM: case SmppConstants.CMD_ID_DATA_SM: case SmppConstants.CMD_ID_QUERY_SM: case SmppConstants.CMD_ID_REPLACE_SM: case SmppConstants.CMD_ID_SUBMIT_SM: case SmppConstants.CMD_ID_SUBMIT_MULTI: //GENERIC_NACK doesn't have responses so we don't have to start response timer responseTimer=new CustomerTimerResponse(this ,packet); packetMap.put(packet.getSequenceNumber(), new TimerData(packet, monitorExecutor.schedule(responseTimer,timeoutResponse,TimeUnit.MILLISECONDS),responseTimer)); case SmppConstants.CMD_ID_GENERIC_NACK: if(logger.isDebugEnabled()) logger.debug("LB received SMPP request (" + packet + ") from server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); correctPacket = true; userSpace.sendRequestToServer(sessionId, packet); break; case SmppConstants.CMD_ID_ENQUIRE_LINK: if(logger.isDebugEnabled()) logger.debug("LB received enquire_link request from server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); updateLastTimeSMPPLinkUpdated(); correctPacket = true; EnquireLinkResp resp=new EnquireLinkResp(); resp.setSequenceNumber(packet.getSequenceNumber()); sendResponse(resp); break; case SmppConstants.CMD_ID_DATA_SM_RESP: case SmppConstants.CMD_ID_DELIVER_SM_RESP: if(logger.isDebugEnabled()) logger.debug("LB received SMPP response (" + packet + ") from server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); CustomerPacket originalSequence=sequenceMap.remove(packet.getSequenceNumber()); if(originalSequence!=null) { packet.setSequenceNumber(originalSequence.getSequence()); correctPacket = true; userSpace.sendResponseToServer(sessionId,packet,originalSequence.getSessionId()); } break; case SmppConstants.CMD_ID_ENQUIRE_LINK_RESP: if(logger.isDebugEnabled()) logger.debug("LB received enquire_link response from server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); correctPacket = true; updateLastTimeSMPPLinkUpdated(); break; } if (!correctPacket) { sendGenericNack(packet); } break; case REBINDING: if(logger.isDebugEnabled()) logger.debug("LB received packet (" + packet + ") in REBINDING state from server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId+". LB sent SYSERR responses!" ); if(packet instanceof PduRequest<?>) { PduResponse pduResponse = ((PduRequest<?>) packet).createResponse(); pduResponse.setCommandStatus(SmppConstants.STATUS_SYSERR); sendResponse(pduResponse); } else { correctPacket = true; updateLastTimeSMPPLinkUpdated(); } break; case UNBINDING: correctPacket = false; if (packet.getCommandId() == SmppConstants.CMD_ID_UNBIND_RESP) correctPacket = true; if (!correctPacket) logger.error("LB received invalid packet in unbinding state from serevr " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId +". packet : " + packet); else { if(logger.isDebugEnabled()) logger.debug("LB received unbind response from server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); enquireRunnable.cancel(); enquireTimer.cancel(false); packetMap.clear(); sequenceMap.clear(); channel.close(); serverState = ServerState.CLOSED; } break; case CLOSED: logger.error("LB received packet in incorrect state (CLOSED) from serevr " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId +". packet : " + packet); break; } } @Override public void sendBindResponse(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 response (" + packet + ") to server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); serverState = ServerState.BOUND; channel.write(buffer); } @Override public void sendUnbindResponse(Pdu packet){ enquireRunnable.cancel(); enquireTimer.cancel(false); 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); } serverState = ServerState.CLOSED; packetMap.clear(); sequenceMap.clear(); if(logger.isDebugEnabled()) logger.debug("LB sent unbind response ("+ packet +") to server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); channel.write(buffer); } @Override public void sendResponse(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 server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); channel.write(buffer); } @Override public void sendUnbindRequest(Pdu packet) { Integer currSequence=lastSequenceNumberSent.incrementAndGet(); sequenceMap.put(currSequence, new CustomerPacket(null,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); } serverState = ServerState.UNBINDING; if(logger.isDebugEnabled()) logger.debug("LB sent unbind request ("+ packet +") to server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); channel.write(buffer); } /** *Send generic_nack to client if unable to convert request *from client *@param packet PDU packet */ private void sendGenericNack(Pdu packet){ GenericNack genericNack = new GenericNack(); genericNack.setSequenceNumber(packet.getSequenceNumber()); genericNack.setCommandStatus(SmppConstants.STATUS_INVCMDID); ChannelBuffer buffer = null; try { buffer = transcoder.encode(genericNack); } catch (UnrecoverablePduException e) { logger.error("Encode error: ", e); }catch(RecoverablePduException e){ logger.error("Encode error: ", e); } if(logger.isDebugEnabled()) logger.debug("LB sent generic_nack response for packet ("+ packet +") to server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); channel.write(buffer); } @Override public void sendRequest(Long serverSessionID,Pdu packet) { Integer currSequence=lastSequenceNumberSent.incrementAndGet(); sequenceMap.put(currSequence, new CustomerPacket(serverSessionID,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 server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); channel.write(buffer); } @Override public void reconnectState(boolean isReconnect) { if (isReconnect){ serverState = ServerState.REBINDING; enquireRunnable.cancel(); enquireTimer.cancel(false); } else { if(enquireTimer!=null) { enquireRunnable.cancel(); enquireTimer.cancel(false); } enquireRunnable=new CustomerTimerEnquire(this); enquireTimer = monitorExecutor.scheduleAtFixedRate(enquireRunnable,timeoutEnquire,timeoutEnquire,TimeUnit.MILLISECONDS); serverState = ServerState.BOUND; } } @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() + ". session ID : " + sessionId); } else { if(logger.isDebugEnabled()) logger.info("<<requestTimeout>> LB did NOT receive SMPP response from client in time for server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); lbServerListener.getNotRespondedPackets().incrementAndGet(); packetMap.remove(packet.getSequenceNumber()); PduResponse pduResponse = ((PduRequest<?>) packet).createResponse(); pduResponse.setCommandStatus(SmppConstants.STATUS_SYSERR); sendResponse(pduResponse); } } @Override public void connectionTimeout(Long sessionId) { if(logger.isDebugEnabled()) logger.debug("<<connectionTimeout>> Session initialization failed and will be closed " + channel.getRemoteAddress().toString() + ". session ID: " + sessionId); lbServerListener.getNotBindClients().incrementAndGet(); channel.close(); } @Override public void enquireLinkTimerCheck() { long currentTime = System.currentTimeMillis(); long timeDiff = currentTime - lastTimeSMPPLinkUpdated; if(logger.isDebugEnabled()) logger.debug("<<enquireTimeout>> LB should check connection to serevr " + channel.getRemoteAddress().toString() + ". session ID : "+ sessionId + ". LB must generate enquire_link."); if(logger.isDebugEnabled()) logger.debug("Current time " + currentTime + " lastTimeSMPPLinkUpdated " + lastTimeSMPPLinkUpdated + " diff: " + timeDiff + " ms"); // isServerSideOk = false; // isClientSideOk = false; if(timeDiff > timeoutConnectionCheckServerSide) // generates enquire link to server only if we didn't receive any as a last attempt. generateEnquireLink(); } @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); } if(logger.isDebugEnabled()) logger.debug("LB sent enquire_link request ("+ packet +") to server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId); channel.write(buffer); } @Override public void connectionCheck(Long sessionId) { long currentTime = System.currentTimeMillis(); long timeDiff = currentTime - lastTimeSMPPLinkUpdated; if(logger.isDebugEnabled()) logger.debug("Current time " + currentTime + " lastTimeSMPPLinkUpdated " + lastTimeSMPPLinkUpdated + " diff: " + timeDiff + " ms, timeoutConnectionCheck " + timeoutConnectionCheckServerSide ); if(timeDiff < timeoutConnectionCheckServerSide) { if(logger.isDebugEnabled()) logger.debug("Connection to server " + channel.getRemoteAddress().toString() + " is OK. session ID : " + sessionId); } else { if(timeDiff < (timeoutConnectionCheckServerSide * 3)) { if(logger.isDebugEnabled()) logger.debug("Current time " + currentTime + " lastTimeSMPPLinkUpdated " + lastTimeSMPPLinkUpdated + " diff: " + timeDiff + " ms, timeoutConnectionCheck * 3 = " + timeoutConnectionCheckServerSide *3 ); generateEnquireLink(); } else { if(logger.isDebugEnabled()) logger.debug("Connection to server " + channel.getRemoteAddress().toString() + " will be closed. session ID " + sessionId + " . LB did not receive enquire response from client or servers"); //remove client form userspase and close connection to client closeChannel(); } } } /* * (non-Javadoc) * @see org.mobicents.tools.smpp.balancer.api.ServerConnection#updateLastEnquireLinkTimeReceived() */ @Override public void updateLastTimeSMPPLinkUpdated() { lastTimeSMPPLinkUpdated = System.currentTimeMillis(); if(logger.isDebugEnabled()) logger.debug("Updated Last Server " + channel.getRemoteAddress().toString() + " Enquire Link time update " + lastTimeSMPPLinkUpdated); } public void closeChannel() { if(channel.getPipeline().getLast()!=null) channel.getPipeline().removeLast(); channel.close(); enquireRunnable.cancel(); enquireTimer.cancel(false); connectionCheckRunnable.cancel(); connectionCheckTimer.cancel(false); userSpace.getCustomers().remove(sessionId); logger.info("Connection to server " + channel.getRemoteAddress().toString() + ". session ID : " + sessionId + " closed"); if(userSpace.getCustomers().isEmpty()) { logger.info("We did not have connection to Node so we should disconnect from SMPP provider"); userSpace.disconnectFromServer(); } } @Override public void sendRequest(Pdu packet) { } public void startEnquireTime() { enquireRunnable=new CustomerTimerEnquire(this); enquireTimer = monitorExecutor.scheduleAtFixedRate(enquireRunnable,timeoutEnquire,timeoutEnquire,TimeUnit.MILLISECONDS); } }