/** * ciscosm.cpp * This file is part of the YATE Project http://YATE.null.ro * * Yet Another Signalling Stack - implements the support for SS7 * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2009 Null Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #define MAX_BUF_SIZE 48500 using namespace TelEngine; namespace { // anonymous class RudpSocket; class SessionManager; class SessionUser; class SLT; class DataSequence; class RudpThread; class SessionManagerModule; class Modulo256; // The list of sessions static ObjList s_sessions; Mutex s_sessionMutex(false,"CiscoSM"); class Modulo256 { public: // Increment a value. Set to 0 if greater the 255 static inline void inc(unsigned int& value) { if (value < 255) value++; else value = 0; } static inline bool between(int value, int low, int high) { if (low == high) return value == low; if (low < high) return value >= low && value <= high; // low > high: counter wrapped around return value >= low || value <= high; } }; // Class used to keep the data and the sequence number class DataSequence : public DataBlock { public: DataSequence(DataBlock& data, u_int8_t seq); ~DataSequence(); inline void addSeq(unsigned int seq) {m_seq = seq;} inline u_int8_t sequence() {return m_seq;} inline void inc() {m_retransmitted++;} inline u_int8_t retransCounter() { return m_retransmitted; } // Method called to change ack number in case the data need to be retransmissed bool refreshAck(u_int8_t ack); private: u_int8_t m_seq; // The data sequence number u_int8_t m_retransmitted; // Counter increased every time when the data is retransmitted }; class RudpThread : public Thread { friend class RudpSocket; public: RudpThread(RudpSocket* rudp,Priority prio); virtual ~RudpThread(); virtual void run(); private: RudpSocket* m_rudp; // The rudp socket that holds this thread bool m_stop; }; class RudpSocket : public GenObject, public Mutex { friend class DataSequence; public: enum RudpState { RudpUp, RudpDown, RudpWait, RudpDead, }; RudpSocket(SessionManager* sm); ~RudpSocket(); //Method called by Session Manager to initialize the socket bool initialize(const NamedList& params); // Socket initialization bool initSocket(const NamedList& params); // Check if we have any new data in queue bool checkData(bool force); // Retransmit the data between the retransmission timer sequence number and actual sequence number void retransData(); void checkAck(const DataBlock& data); bool checkSeq(const DataBlock& data); void checkTimeouts(u_int64_t time); void sendAck(); void buildAck(DataBlock& data); void sendNull(); void sendSyn(bool recvSyn); void keepData(DataBlock& data, int seq); void sendMSG(const DataBlock& msg); bool readData(); void recvMsg(DataBlock& packet); bool sendData(const DataBlock& msg); bool handleSyn(const DataBlock& data, bool ack); void removeData(u_int8_t ack); void handleEack(const DataBlock& data); void handleData(DataBlock& data); void reset(); bool checkChecksum(DataBlock& data); //thread functions bool running(); void kill(); void removeOneData(u_int8_t ack); bool startThread(Thread::Priority prio = Thread::Normal); void stopThread(); u_int16_t checksum(u_int16_t len,const u_int8_t* buff); void appendChecksum(DataBlock& data); inline void setThread() { m_thread = 0;} inline bool haveSyn(u_int8_t flags) { return 0 != (flags & 0x80); } inline bool haveAck(u_int8_t flags) { return 0 != (flags & 0x40); } inline bool haveNul(u_int8_t flags) { return 0 != (flags & 0x8); } inline bool haveChecksum(u_int8_t flags) { return 0 != (flags & 0x4); } inline bool haveEack(u_int8_t flags) { return 0 != (flags & 0x20); } inline bool haveReset(u_int8_t flags) { return 0 != (flags & 0x10); } inline bool haveTcs(u_int8_t flags) { return 0 != (flags & 0x2); } inline unsigned int getAckNum() { return m_ackNum; } static inline const char* stateName(RudpState s) { return lookup((int)s,s_RudpStates); } void changeState(RudpState newState); inline RudpState State() { return m_state; } private: SessionManager* m_sm; // The session manager that owns this socket RudpThread* m_thread; // The socket thread Socket *m_socket; // The socket ObjList m_msgList; // The messages list // Sequence numbers unsigned int m_sequence; // The sequence number unsigned int m_ackNum; // The sequence number of the last in sequence message received unsigned int m_lastAck; // The last acknowledged message unsigned int m_lastSend; // The last message sequence number that was send unsigned int m_retTStartSeq; // The sequence number when was started retransmission timer unsigned int m_syn; // The sequence number of the syn segment sent by us when // we receive the confirmation for it we inform the session manager // Timers SignallingTimer m_cumAckTimer; // Cumulative Acknowledgment timer SignallingTimer m_nullTimer; // Null timer SignallingTimer m_retransTimer; // Retransmission timer SignallingTimer m_synTimer; // Syn retransmission timer // Flags int m_version; // RUDP version, negative to autodetect bool m_haveChecksum; // Flag that indicate if we have checksum or not bool m_sendSyn; // Flag that indicate if we should send syn segment or wait for it // Connection u_int32_t m_connId; // Connection identifier // Counters u_int8_t m_retransCounter; // The maximum number of retransmissions u_int8_t m_maxCumAck; // The maximum number of segments received without being confirmed u_int8_t m_queueCount; // The last number of messages from list known by rudp thread unsigned int m_wrongChecksum; // Counter how keeps the number of packets // received with wrong checksum static const TokenDict s_RudpStates[]; RudpState m_state; // Rudp state }; class SessionManager : public RefObject , public DebugEnabler, public Mutex { public: enum State { Operational, Nonoperational, StandBy, }; enum Type { Start = 0, // Start Message Stop = 1, // Stop Message Active = 2, // Active Message used with redundant MGC configuration Standby = 3, // Standby Message used with redundant MGC configuration Q_HOLD_I = 4, // Q_HOLD Invoke Message used with redundant MGC configuration Q_HOLD_R = 5, // Q_HOLD Response Message used with redundant MGC configuration Q_RESUME_I = 6, // Q_RESUME Invoke Message used with redundant MGC configuration Q_RESUME_R = 7, // Q_RESUME Response Message used with redundant MGC configuration Q_RESET_I = 8, // Q_RESET Invoke Message used with redundant MGC configuration Q_RESET_R = 9, // Q_RESET Response Message used with redundant MGC configuration PDU = 0x8000, // PDU - Non Session Manager message from application }; SessionManager(const String& name,const NamedList& param); ~SessionManager(); // Initialize this layer and rudp also bool initialize(const NamedList& params); virtual const String& toString() const { return m_name; } // Receiving notifications from rudp void notify(bool up); // Handle the data received from Rudp void handleData(DataBlock& data); // Send start stop messages void initSession(); bool sendData(DataBlock& data, bool connectR = false); // Find a specific user bool insert(SessionUser* user); void remove(SessionUser* user); void userNotice(bool up); static SessionManager* get(const String& name, const NamedList* params); // Send a PDU message if state is operational else return false bool sendPdu(const DataBlock& data); void handlePDU(DataBlock& data); void handleSmMessage(u_int32_t smMessageType); // Inform all users about session manager status change void informUser(bool up); static inline const char* stateName(State s) { return lookup((int)s,s_smStates); } void changeState(State newState); static inline const char* typeName(Type type) { return lookup(type,s_types,"Unknown Message Type"); } inline RudpSocket* socket() { return m_socket;} virtual void destroyed(); private: ObjList m_users; // List of users RudpSocket* m_socket; // The Rudp Layer State m_state; // Session Manager state String m_name; // The name of this session unsigned int m_upUsers; // The number of up users static const TokenDict s_smStates[]; static const TokenDict s_types[]; }; class SessionUser { public: SessionUser(u_int16_t protType); virtual ~SessionUser(); virtual void notify(bool up) = 0; virtual bool checkMessage(DataBlock& data) = 0; inline u_int16_t protocol() {return m_protType;} protected: u_int16_t m_protType; // Protocol type RefPointer m_session; }; class SLT : public SS7Layer2, public SessionUser { YCLASS(SLT,SS7Layer2) public: enum Messages { Connect_R = 0x06, // Connect Request message Connect_C = 0x07, // Connect confirmation Disconnect_R = 0x0a, // Disconnect request Disconnect_C = 0x0b, // Disconnect confirmation Disconnect_I = 0x0c, // Disconnect indication Data_Req = 0x10, // Data request, send message out link Data_Ind = 0x11, // Data indication, receive message from link Data_Retrieval_R = 0x12, // Data retrieval request Data_Retrieval_C = 0x13, // Data retrieval confirmation Data_Retrieval_I = 0x14, // Data retrieval indication Data_Retrieval_M = 0x15, // Data retrieval message Link_State_Controller_R = 0x20, // Link state controller request Link_State_Controller_C = 0x21, // Link state controller confirmation Link_State_Controller_I = 0x22, // Link state controller indication // Management messages Configuration_R = 0x40, // Configuration request Configuration_C = 0x41, // Configuration confirmation Status_R = 0x42, // Status request Status_C = 0x43, // Status confirmation Statistic_R = 0x44, // Statistic request Statistic_C = 0x45, // Statistic confirmation Control_R = 0x46, // Control request Control_C = 0x47, // Control confirmation Flow_Control_R = 0x50, // Flow control response from mtp3 Flow_Control_I = 0x51, // Flow control indication to mtp3 }; enum connectM { Emergency = 0x03, // Emergency alignment Normal = 0x04, // Normal alignment Power = 0x05, // Power on mtp2 Start = 0x06, // Start mtp2 }; enum errors { Unknown = 0x00, // Unknown error T2_expired = 0x14, // T2 expired waiting for sio // these are the only error code known at this time }; enum State { Configured, // SLT layer has sent the configuration pdu and received the // confirmation Waiting, // SLT layer has sent the configuration pdu but hasn't received // the confirmation Unconfigured, // SLT didn't send the configuration message }; enum dataRetR { Return = 0x01, // Return the BSN Retrieve = 0x02, // Retrieve messages from BSN Drop = 0x03, // Drop messages }; enum linkStateCR { LPDR = 0x00, // Local processor down LPUR = 0x01, // Local processor up Emergency_ = 0x02, Emergency_c = 0x03, // Emergency ceases FlushB = 0x04, // Flush buffers FlushTB = 0x05, // Flush transmit buffers FlushRT = 0x06, // Flush retransmit buffers FlushRecvB = 0x07, // Flush receive buffers Continue = 0x08, }; enum linkStateCI { LPD = 0x00, // Local processor down LPU = 0x01, // Local processor up LEC = 0x02, // Link entered congestion PLU = 0x03, // Physical layer up PLD = 0x04, // Physical layer down PE = 0x06, // Protocol error WHAL = 0x07, // We have aligned the link WHLA = 0x08, // We have lost alignment RTBF = 0x09, // RTB full RTBNF = 0x0a, // RTB no longer full NA = 0x0b, // Negative acknowledgment RECS = 0x0c, // Remote entered a congested state RCO = 0x0d, // Remote congestion is over REPO = 0x0e, // Remote entered processor outage RPOR = 0x0f, // Remote processor outage has recovered }; enum linkCongestion { UnknownC = 0x00, ManagementI = 0x01, CongestionE = 0x03, }; enum protocolError { UnknownE = 0x00, AbnormalBSN = 0x02, AbnormalFIB = 0x03, CongestionD = 0x04, }; SLT(const String& name, const NamedList& param); virtual ~SLT(); virtual unsigned int status() const; void setStatus(unsigned int status); void setRemoteStatus(unsigned int status); void setReqStatus(unsigned int status); virtual void notify(bool up); virtual bool control(Operation oper, NamedList* params = 0); virtual bool aligned() const; virtual void destroyed(); virtual void timerTick(const Time& when); u_int16_t get16Message(u_int8_t* msg); u_int32_t get32Message(u_int8_t* msg); virtual bool checkMessage(DataBlock& data); void processManagement(u_int16_t msgType, DataBlock& data); void processSltMessage(u_int16_t msgType, DataBlock& data); void buildHeader(DataBlock& data, bool management = false); void sendConnect(unsigned int status); void sendControllerR(unsigned int linkState); void sendManagement(unsigned int message); void sendDisconnect(); void processCIndication(DataBlock& data); virtual bool transmitMSU(const SS7MSU& msu); void getStringMessage(String& tmp, DataBlock& data); virtual bool operational() const; virtual void configure(bool start); static inline const char* stateName(State s) { return lookup((int)s,s_states); } inline u_int16_t channel() { return m_channelId; } void changeState(State newState); static inline const char* messageType(Messages m) { return lookup((int)m,s_messages); } static inline const char* connectType(connectM m) { return lookup((int)m,s_connectM); } static inline const char* slinkStateCI(linkStateCI state) {return lookup((int)state, s_linkStateCI);} static inline const char* showError(errors er) {return lookup((int)er, s_errors, "Not Handled");} static inline const char* showState(State s) { return lookup((int)s,s_states); } static inline const char* showDataRet(dataRetR d) { return lookup((int)d,s_dataRetR); } static inline const char* slinkStateCR(linkStateCR state) {return lookup((int)state, s_linkStateCR);} static inline const char* slinkCongestion(linkCongestion state) {return lookup((int)state, s_linkCongestion);} static inline const char* sprotocolError(protocolError state) {return lookup((int)state, s_protocolError);} static SignallingComponent* create(const String& type,const NamedList& params); protected: unsigned int m_status; // Layer status unsigned int m_rStatus; // Remote layer status unsigned int m_reqStatus; // We keep the requested status and we change it when // receive the confirmation u_int16_t m_messageId; // Message ID u_int16_t m_channelId; // Channel ID u_int16_t m_bearerId; // Bearer ID NOTE it is set to 0 SignallingTimer m_confReqTimer; // The configuration request timer bool m_printMsg; // Flag used to see if we print this layer messages static const TokenDict s_messages[]; static const TokenDict s_connectM[]; static const TokenDict s_errors[]; static const TokenDict s_states[]; static const TokenDict s_dataRetR[]; static const TokenDict s_linkStateCR[]; static const TokenDict s_linkStateCI[]; static const TokenDict s_linkCongestion[]; static const TokenDict s_protocolError[]; }; class CiscoSMModule : public Module { public: CiscoSMModule(); ~CiscoSMModule(); virtual void initialize(); private: bool m_init; }; static CiscoSMModule plugin; typedef GenPointer UserPointer; YSIGFACTORY2(SLT); /** Class SltThread */ RudpThread::RudpThread(RudpSocket* rudp, Priority prio) : Thread("RUDP Runner",prio), m_rudp(rudp), m_stop(false) { } RudpThread::~RudpThread() { DDebug("RudpThread",DebugAll,"RudpThread::~RudpThread() [%p]",this); m_rudp->setThread(); } void RudpThread::run() { if (!m_rudp) return; while (true) { if (m_rudp->readData()) Thread::check(true); else Thread::idle(true); } } /** class DataSequence */ DataSequence::DataSequence(DataBlock& data, u_int8_t seq) : DataBlock(data), m_seq(seq), m_retransmitted(0) { } DataSequence::~DataSequence() { } bool DataSequence::refreshAck(u_int8_t acn) { u_int8_t* ack = data(3); if (acn == *ack) return false; *ack = acn; return true; } /** class Rudp */ const TokenDict RudpSocket::s_RudpStates[] = { { "RudpUp", RudpUp }, { "RudpDown", RudpDown }, { "RudpWait", RudpWait }, { "RudpDead", RudpDead }, { 0, 0 } }; RudpSocket::RudpSocket(SessionManager* sm) : Mutex(true,"RudpSocket"), m_sm(sm), m_thread(0), m_socket(0), m_sequence(0), m_ackNum(0), m_lastAck(0), m_lastSend(0), m_retTStartSeq(0), m_syn(1000), m_cumAckTimer(0), m_nullTimer(0), m_retransTimer(0), m_synTimer(0), m_version(-1), m_haveChecksum(false), m_sendSyn(false), m_connId(0x208000), m_retransCounter(0), m_maxCumAck(0), m_queueCount(0), m_wrongChecksum(0), m_state(RudpDown) { } RudpSocket::~RudpSocket() { DDebug(&plugin,DebugAll,"RudpSocket::~RudpSocket() [%p]",this); Lock mylock(this); m_msgList.clear(); delete m_socket; } void RudpSocket::kill() { stopThread(); m_socket->terminate(); m_sm = 0; } void RudpSocket::changeState(RudpState newState) { if (m_state == newState) return; Debug(m_sm,DebugNote,"Socket state changed: %s -> %s", stateName(m_state),stateName(newState)); m_state = newState; } // Initialize Parameters ,initialize socket and start thread bool RudpSocket::initialize(const NamedList& params) { m_sequence = params.getIntValue("rudp_sequence",0xff & ::random()); if (!Modulo256::between(m_sequence,0,255)) { Debug(m_sm,DebugNote,"Rudp Sequence value out of bounds set to 0"); m_sequence = 0; } m_cumAckTimer.interval(params,"rudp_cumulative",100,300,false); m_nullTimer.interval(params,"rudp_nulltimer",1500,2000,false); m_retransTimer.interval(params,"rudp_retransmission",400,600,false); m_synTimer.interval(params,"rudp_syntimer",900,1000,false); m_retransCounter = params.getIntValue("rudp_maxretrans",2); m_maxCumAck = params.getIntValue("rudp_maxcumulative",3); m_version = params.getIntValue("rudp_version",-1); m_haveChecksum = params.getBoolValue("rudp_checksum",false); m_sendSyn = params.getBoolValue("rudp_sendsyn",false); if (!initSocket(params)) { DDebug(m_sm,DebugMild,"Failed to initialize the socket"); return false; } startThread(); if (m_sendSyn) sendSyn(false); return true; } // Socket initialization bool RudpSocket::initSocket(const NamedList& params) { m_socket = new Socket(AF_INET,SOCK_DGRAM); String rhost = params.getValue("remote_host"); unsigned int rport = params.getIntValue("remote_port",8060); if (!rhost || !rport) { Debug(m_sm,DebugStub, "Unable to initialize socket, remote%s%s%s is missing", rhost ? "" : " host", (rhost || rport) ? "" : " and", rport ? "" : " port"); return false; } String host = params.getValue("local_host","0.0.0.0"); bool randHost = false; unsigned int port = params.getIntValue("local_port",rport); if (host == "0.0.0.0") randHost = true; SocketAddr addr(AF_INET); addr.host(host); addr.port(port); if (!m_socket->bind(addr)) { Debug(m_sm,DebugNote,"Unable to bind to %s:%u : %s",addr.host().c_str(),addr.port(),strerror(m_socket->error())); return false; } if (randHost && !m_socket->getSockName(addr)) { Debug(m_sm,DebugNote,"Error getting address: %s",strerror(m_socket->error())); return false; } Debug(m_sm,DebugAll,"Socket bound to: %s:%u",addr.host().c_str(),addr.port()); addr.host(rhost); addr.port(rport); if (!m_socket->connect(addr)) { Debug(m_sm,DebugNote,"Unable to connect to %s:%u : %s", addr.host().c_str(),addr.port(),strerror(m_socket->error())); return false; } Debug(m_sm,DebugAll,"Socket connected to %s:%u",addr.host().c_str(),addr.port()); return true; } // Call check data and verify if any timer has expired is called from readData() void RudpSocket::checkTimeouts(u_int64_t time) { if (m_state != RudpUp) { if (m_synTimer.timeout(time)) { m_synTimer.stop(); sendSyn(false); } return; } checkData(false); if (m_retransTimer.timeout(time)) { m_retransTimer.stop(); retransData(); } if (m_cumAckTimer.timeout(time)) { if (!checkData(true)) sendAck(); } if (m_nullTimer.timeout(time)) { if (!checkData(true)) sendNull(); } } // Verify if exist any new data in the queue and transmit all data // with sequence number > last sent and <= actual sequence number bool RudpSocket::checkData(bool force) { Lock mylock(this); if (!force && (m_queueCount >= m_msgList.count())) return false; bool sent = false; ObjList* obj = m_msgList.skipNull(); for (; obj; obj = obj->skipNext()) { DataSequence* data = static_cast(obj->get()); if (data && data->sequence() != m_lastSend && Modulo256::between(data->sequence(),m_lastSend,m_sequence)) { sent = true; if (data->refreshAck(m_ackNum) && m_haveChecksum) appendChecksum(static_cast(*data)); sendData(static_cast(*data)); m_lastSend = data->sequence(); } } m_queueCount = m_msgList.count(); if (sent) { // Stop cumulative acknowledge timer because we send acknowledge segment with data m_cumAckTimer.stop(); // Restart null timer because we had data trafic m_nullTimer.stop(); m_nullTimer.start(); if (!m_retransTimer.started()) { // start retrasnsmission timer and refresh retransmission sequence number m_retransTimer.start(); m_retTStartSeq = m_lastSend; } } return sent; } // Method called from checkTimeouts // Retransmits all data with sequence number between last data confirmed and current sequence number // increment retransmission counter and if reach the maximum retransmission counter notify the // session manager and initiate reset void RudpSocket::retransData() { if (m_state != RudpUp) return; Lock mylock(this); ObjList* obj = m_msgList.skipNull(); for (; obj; obj = obj->skipNext()) { DataSequence* data = static_cast(obj->get()); if (data && Modulo256::between(data->sequence(),m_lastAck,m_sequence)) { if (data->retransCounter() <= m_retransCounter) { XDebug(m_sm,DebugInfo,"Retransmission %u of data with seq %u", data->retransCounter(),data->sequence()); if (data->refreshAck(m_ackNum) && m_haveChecksum) appendChecksum(static_cast(*data)); sendData(static_cast(*data)); data->inc(); if (!m_retransTimer.started()) m_retransTimer.start(); } else { Debug(m_sm,DebugNote,"RUDP Layer down, retransmission exceeded for seq %u",data->sequence()); #ifdef DEBUG String aux; aux.hexify(data->data(),data->length(),' '); Debug(m_sm,DebugInfo,"Retransmission exceeded for data: %s ",aux.c_str()); #endif m_sm->notify(true); changeState(RudpDown); mylock.drop(); reset(); } } } } void RudpSocket::reset() { m_sequence = m_ackNum = m_lastAck = m_lastSend = m_retTStartSeq = 0; m_retransTimer.stop(); m_nullTimer.stop(); // remove all data removeData(255); if (m_sendSyn) sendSyn(false); } void RudpSocket::buildAck(DataBlock& data) { u_int8_t buf[8]; for (int i = 0;i < 8;i ++) buf[i] = 0; buf[0] = 0x40; if (m_haveChecksum) buf[1] = 8; else buf[1] = 4; buf[2] = m_sequence; buf[3] = m_ackNum; data.assign((void*)buf,buf[1]); } // Build and return or send an ack segment void RudpSocket::sendAck() { m_cumAckTimer.stop(); if (m_state != RudpUp) return; DataBlock data; buildAck(data); if (m_haveChecksum) appendChecksum(data); String dat; dat.hexify(data.data(),data.length(),' '); sendData(data); } // Build a null segment and enqueue it void RudpSocket::sendNull() { DataBlock data; buildAck(data); u_int8_t* buf = data.data(0,2); buf[0] = 0x48; if (m_haveChecksum) appendChecksum(data); keepData(data,m_sequence); m_nullTimer.stop(); m_nullTimer.start(); } // helper function to store a 16 bit in big endian format static void store16(u_int8_t* dest, u_int16_t val) { dest[0] = (u_int8_t)(0xff & (val >> 8)); dest[1] = (u_int8_t)(0xff & val); } // helper function to store a 32 bit in big endian format static void store32(u_int8_t* dest, u_int32_t val) { store16(dest,val >> 16); store16(dest+2,val & 0xffff); } void RudpSocket::sendSyn(bool recvSyn) { if (m_version < 0) return; u_int8_t buf[30]; for (unsigned int i = 0; i < sizeof(buf); i++) buf[i] = 0; if (!recvSyn) { m_synTimer.start(); buf[0] = 0x80; buf[3] = 0; } else { if (m_synTimer.started()) m_synTimer.stop(); buf[0] = 0xc0; buf[3] = m_ackNum; } buf[2] = m_sequence; m_syn = m_sequence; switch (m_version) { case 0: buf[1] = m_haveChecksum ? 12 : 8; store32(buf+4,m_connId); break; case 1: buf[1] = 30; store16(buf+8,0xe447); // ??? store16(buf+10,0xce0c); // ??? store32(buf+12,m_connId); store16(buf+16,0x0180); // MSS? store16(buf+18,(u_int16_t)m_retransTimer.interval()); store16(buf+20,(u_int16_t)m_cumAckTimer.interval()); store16(buf+22,(u_int16_t)m_nullTimer.interval()); store16(buf+24,2000); // Transf. state timeout? buf[26] = m_retransCounter; buf[27] = m_maxCumAck; buf[28] = 0x03; // Max out of seq? buf[29] = 0x05; // Max auto reset? break; default: Debug(m_sm,DebugWarn,"Unhandled RUDP version %d",m_version); m_version = -1; return; } DataBlock data; data.assign((void*)buf,buf[1]); if (m_haveChecksum) appendChecksum(data); if (sendData(data)) changeState(RudpWait); } // Method used to assign a sequence number for data and append it to message list to be send void RudpSocket::keepData(DataBlock& data, int seq) { DataSequence* seqdata = new DataSequence(data,seq); Lock mylock(this); m_msgList.append(seqdata); Modulo256::inc(m_sequence); } bool RudpSocket::readData() { checkTimeouts(Time::msecNow()); if (!m_socket) return false; if (!m_socket->valid() && State() == RudpSocket::RudpUp) { Debug(m_sm,DebugWarn,"RUDP socket is dead, check the network connection!"); changeState(RudpDead); reset(); return false; } bool readOk = false,error = false; if (!m_socket->select(&readOk,0,&error,1000)) return false; if (error) Debug(m_sm,DebugInfo,"Reading data error: %s (%d)", strerror(m_socket->error()),m_socket->error()); if (!readOk || error) return false; unsigned char buffer [MAX_BUF_SIZE]; SocketAddr addr; int r = m_socket->recv(buffer,MAX_BUF_SIZE); if (r <= 0) return false; DataBlock packet(buffer,r); #ifdef XDEBUG String seen; seen.hexify(packet.data(),packet.length(),' '); Debug(m_sm,DebugInfo,"Reading data: %s length returned = %d",seen.c_str(), r); #endif // Sanity checks for packet and header length if (packet.length() < 4) return false; if ((unsigned int)packet.at(1) > packet.length()) return false; if (m_state == RudpDown && !haveSyn((u_int8_t)packet.at(0))) return false; if (m_haveChecksum && !checkChecksum(packet)) { DDebug(m_sm,DebugMild,"Wrong checksum received"); return false; } recvMsg(packet); return true; } bool RudpSocket::sendData(const DataBlock& msg) { bool sendOk = false, error = false; if (!m_socket) return false; if (m_socket->select(0,&sendOk,&error,1000)) { if (error) return false; if (!sendOk) return false; int msgLen = msg.length(); int len = m_socket->send(msg.data(),msgLen); if (len != msgLen) { Debug(m_sm,DebugAll,"Error sending data, message not sent: %s ",strerror(m_socket->error())); return false; } else { #ifdef XDEBUG String seen; seen.hexify(msg.data(),msg.length(),' '); XDebug(m_sm,DebugInfo,"Sending data: %s length returned = %d",seen.c_str(), msg.length()); #endif return true; } } return false; } // Enqueue data received from session manager void RudpSocket::sendMSG(const DataBlock& data) { if (m_state != RudpUp) return; DataBlock auxdata; buildAck(auxdata); auxdata += data; if (m_haveChecksum) appendChecksum(auxdata); keepData(auxdata,m_sequence); } // Check received packet and send it for processing void RudpSocket::recvMsg(DataBlock& packet) { u_int8_t flag = packet.at(0); // SYN needs to be handled first if (haveSyn(flag)) { if (haveAck(flag)) { handleSyn(packet,true); m_nullTimer.start(); return; } handleSyn(packet,false); return; } bool hasData = false; // Then acknowledge pending packets - ACK and EACK if (haveAck(flag)) { hasData = true; checkAck(packet); } if (haveEack(flag)) handleEack(packet); // Check for special packets if (haveNul(flag)) { hasData = false; if (!haveAck(flag)) Debug(m_sm,DebugWarn,"Received NULL segment without ACK flag set"); checkSeq(packet); m_cumAckTimer.fire(); } if (haveReset(flag)) { Debug(m_sm,DebugMild,"Received RESET segment, ignored"); hasData = false; checkSeq(packet); } if (haveTcs(flag)) { Debug(m_sm,DebugMild,"Received TCS segment, ignored"); hasData = false; } // If we had ACK or EACK only also check for data after header if (hasData) handleData(packet); } bool RudpSocket::handleSyn(const DataBlock& data, bool ack) { DDebug(m_sm,DebugInfo,"Handling SYN%s with length %u", (ack ? "-ACK" : ""),data.length()); if (m_version < 0) { switch (data.length()) { case 12: m_version = 0; m_haveChecksum = true; break; case 8: m_version = 0; m_haveChecksum = false; break; case 30: m_version = 1; m_haveChecksum = true; break; default: Debug(m_sm,DebugWarn,"Cannot guess RUDP version from SYN length %u", data.length()); return false; } Debug(m_sm,DebugNote,"Guessed RUDP version %d%s from SYN length %u", m_version,(m_haveChecksum ? " (CKSUM)" : ""),data.length()); } m_ackNum = data.at(2); if ((m_version == 1) && (data.length() >= 30)) { m_connId = (data.at(12) << 24) | (data.at(13) << 16) | (data.at(14) << 8) | data.at(15); m_retransTimer.interval((data.at(18) << 8) | data.at(19)); m_cumAckTimer.interval((data.at(20) << 8) | data.at(21)); m_nullTimer.interval((data.at(22) << 8) | data.at(23)); m_retransCounter = data.at(26); m_maxCumAck = data.at(27); } if (ack) { checkAck(data); sendAck(); } else sendSyn(true); return true; } // Check acknowledged sequence number void RudpSocket::checkAck(const DataBlock& data) { u_int8_t ack = data.at(3); if (ack == m_syn) { m_nullTimer.stop(); m_nullTimer.start(); changeState(RudpUp); m_sm->notify(false); m_syn = 1000; } removeData(ack); } // Check incoming sequence number, increment if matched bool RudpSocket::checkSeq(const DataBlock& data) { u_int8_t seq = data.at(2); unsigned int exp = m_ackNum; Modulo256::inc(exp); if (seq == exp) { m_ackNum = seq; if (!m_cumAckTimer.started()) m_cumAckTimer.start(); return true; } // Received packet is not next in sequence - ignore it if (seq != m_ackNum) Debug(m_sm,DebugMild,"Packet out of sequence, expecting %u or %u but got %u", m_ackNum,exp,seq); else Debug(m_sm,DebugNote,"Received duplicate packet %u",seq); return false; } // Remove ack data void RudpSocket::removeData(u_int8_t ack) { if (Modulo256::between(m_retTStartSeq,m_lastAck,m_sequence)) m_retransTimer.stop(); Lock mylock(this); XDebug(m_sm,DebugInfo,"Removing packets in range %u - %u",m_lastAck,ack); ListIterator iter(m_msgList); while (DataSequence* data = static_cast(iter.get())) { if (Modulo256::between(data->sequence(),m_lastAck,ack)) { XDebug(m_sm,DebugAll,"Removed packet with seq %u",data->sequence()); m_msgList.remove(data,true); if (m_queueCount > 0) m_queueCount -- ; else m_queueCount = 0; } } m_lastAck = ack; // If queue count is bigger than 0 means that we still have packets in queue waiting to be confirmed if (m_queueCount > 0) { // set sequence timer to next in queue to ack m_retTStartSeq = ack; Modulo256::inc(m_retTStartSeq); // Restart Retransmission Timer m_retransTimer.start(); } } void RudpSocket::handleEack(const DataBlock& data) { u_int8_t pack = data.at(1) - (m_haveChecksum ? 8 : 4); DDebug(m_sm,DebugNote,"Received EACK for %u packets, last Ack %u",pack,m_lastAck); for (int i = 4; i < pack + 4; i++) removeOneData(data.at(i)); if (!m_cumAckTimer.started()) m_cumAckTimer.start(); } void RudpSocket::handleData(DataBlock& data) { int hdr = data.at(1); if (data.length() <= (unsigned int)hdr) return; if (!checkSeq(data)) return; data.cut(-hdr); m_sm->handleData(data); } void RudpSocket::removeOneData(u_int8_t ack) { Lock mylock(this); ObjList* obj = m_msgList.skipNull(); for (; obj; obj = obj->skipNext()) { DataSequence* data = static_cast(obj->get()); if (data && data->sequence() == ack) { XDebug(m_sm,DebugAll,"Removed one packet with seq %u",ack); m_msgList.remove(data,true); if (m_queueCount > 0) m_queueCount --; else m_queueCount = 0; return; } } DDebug(m_sm,DebugInfo,"Not found packet with seq %u",ack); } bool RudpSocket::running() { return m_thread && m_thread->running(); } bool RudpSocket::startThread(Thread::Priority prio) { if (!m_thread) { m_thread = new RudpThread(this,prio); Debug(m_sm,DebugAll,"Creating %s",m_thread->name()); } if (m_thread->running()) { Debug(m_sm,DebugAll,"%s is already running",m_thread->name()); return true; } if (m_thread->startup()) { Debug(m_sm,DebugAll,"Starting up %s",m_thread->name()); return true; } Debug(m_sm,DebugWarn,"%s failed to start",m_thread->name()); m_thread->cancel(true); m_thread = 0; return false; } void RudpSocket::stopThread() { if (!m_thread) return; m_thread->cancel(); while (m_thread) Thread::yield(); } u_int16_t RudpSocket::checksum(u_int16_t len, const u_int8_t* buff) { u_int32_t sum = 0; for (u_int16_t i = 0; i < len; i+=2) sum += (((u_int16_t)buff[i]) << 8) + ((i+1 < len) ? buff[i+1] : 0); while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); return ~sum; } bool RudpSocket::checkChecksum(DataBlock& data) { u_int8_t* buf = data.data(0,data.length()); if (!buf) return false; if (!haveEack(buf[0]) && buf[1] == 4) return true; return checksum((haveChecksum(buf[0]) ? data.length() : buf[1]),buf) == 0; } void RudpSocket::appendChecksum(DataBlock& data) { int dataLen = data.length(); int rudpLen = 0; u_int8_t* buf = data.data(0,dataLen); if (!buf) return; rudpLen = buf[1]; u_int8_t* cks = buf + (rudpLen - 4); if (haveSyn(buf[0]) && (m_version == 1)) cks = buf+4; cks[0] = cks[1] = 0; u_int16_t sum = checksum((haveChecksum(buf[0]) ? dataLen : rudpLen),buf); cks[0] = (u_int8_t)(sum >> 8); cks[1] = (u_int8_t)(sum & 0xff); } /** class SessionManager */ // Find a session by name and reference it, if it doesn't exist create a new one SessionManager* SessionManager::get(const String& name, const NamedList* params) { if (name.null()) return 0; Lock lock(s_sessionMutex); ObjList* l = s_sessions.find(name); SessionManager* session = l ? static_cast(l->get()) : 0; lock.drop(); if (session && !session->ref()) session = 0; if (params && !session) session = new SessionManager(name,*params); return session; } const TokenDict SessionManager::s_smStates[] = { { "Operational", Operational }, { "Nonoperational", Nonoperational }, { "Standby", StandBy }, { 0, 0 } }; // Session Manager messages types NOTE we only handle PDU's const TokenDict SessionManager::s_types[] = { { "Start", Start }, { "Stop", Stop }, { "Active", Active }, { "Standby", Standby }, { "Q_HOLD_I", Q_HOLD_I }, { "Q_HOLD_R", Q_HOLD_R }, { "Q_RESUME_I", Q_RESUME_I }, { "Q_RESUME_R", Q_RESUME_R }, { "Q_RESET_I", Q_RESET_I }, { "Q_RESET_R", Q_RESET_R }, { "PDU", PDU }, { 0, 0 } }; SessionManager::SessionManager(const String& name, const NamedList& param) : Mutex(true,"SessionManager"), m_socket(0), m_state(Nonoperational), m_name(name), m_upUsers(0) { debugName(0); debugName(m_name.c_str()); DDebug(this,DebugNote,"Creating new session"); Lock mylock(s_sessionMutex); s_sessions.append(this); initialize(param); } void SessionManager::destroyed() { m_socket->kill(); TelEngine::destruct(m_socket); Lock mylock(s_sessionMutex); s_sessions.remove(this,false); } SessionManager::~SessionManager() { DDebug(this,DebugAll,"SessionManager::~SessionManager() [%p]",this); Lock mylock(s_sessionMutex); s_sessions.remove(this); } bool SessionManager::initialize(const NamedList& params) { m_socket = new RudpSocket(this); return m_socket->initialize(params); } void SessionManager::notify(bool down) { if (down) { changeState(Nonoperational); informUser(false); } else { changeState(Operational); initSession(); informUser(true); } } void SessionManager::handleData(DataBlock& data) { if (data.length() < 4) return; u_int8_t* buf = data.data(0,4); u_int32_t smMessageType; smMessageType = (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]; if (smMessageType == PDU) handlePDU(data); else { #ifdef DEBUG String aux; aux.hexify(data.data(),data.length(),' '); Debug(this,DebugInfo,"Session data dump: %s",aux.c_str()); #endif handleSmMessage(smMessageType); } } // Session Initialization send standby and active messages void SessionManager::initSession() { if (!m_socket) return; u_int8_t buf[4]; // send standby message buf[0] = buf[1] = buf[2] = 0; buf[3] = 3; DataBlock data((void*)buf,4,false); DDebug(this,DebugInfo,"Session manager sending: Standby"); m_socket->sendMSG(data); // send active message buf[3] = 2; DDebug(this,DebugInfo,"Session manager sending: Active"); m_socket->sendMSG(data); data.clear(false); } bool SessionManager::insert(SessionUser* user) { if (!user) return false; Lock mylock(this); m_users.append(new UserPointer(user)); return true; } void SessionManager::remove(SessionUser* user) { if (!user) return; Lock mylock(this); for (ObjList* obj = m_users.skipNull(); obj; obj = obj->skipNext()) { UserPointer* u = static_cast(obj->get()); if (static_cast(*u) == user) { obj->remove(); return; } } } // Send a PDU message if state is operational else return false // We reinitialize the session if the message is a connect request and we don't have any user up bool SessionManager::sendData(DataBlock& data, bool connectR) { Lock mylock(this); if (!m_socket || m_state != Operational) return false; // Send Standby and Active messages if we have an connect request message and all users are down if (connectR && m_upUsers == 0) { initSession(); DDebug(this,DebugAll,"Sending init delayed PDU data: %u bytes",data.length()); } m_socket->sendMSG(data); return true; } // Inform all users about session status change void SessionManager::informUser(bool up) { Lock mylock(this); ObjList* obj = m_users.skipNull(); for (; obj; obj = obj->skipNext()) { UserPointer* user = static_cast(obj->get()); if (user) (*user)->notify(up); } } // Method that handle the user up counter to know how many do we have up void SessionManager::userNotice(bool up) { if (up) m_upUsers++; else if (m_upUsers >= 1) m_upUsers--; else m_upUsers = 0; } // Method that looks for an user to process the message // When the user is found we stop looking void SessionManager::handlePDU(DataBlock& data) { u_int8_t* buf = data.data(4,2); u_int16_t protType = 0; protType = (buf[0] << 8) + buf[1]; lock(); ListIterator iter(m_users); while (UserPointer* user = static_cast(iter.get())) { if ((*user)->protocol() != protType) continue; unlock(); if ((*user)->checkMessage(data)) return; lock(); } unlock(); } void SessionManager::changeState(State newState) { if (m_state == newState) return; Debug(this,DebugNote,"Session state changed: %s -> %s", stateName(m_state),stateName(newState)); m_state = newState; } // As far as we know these messages should be sent by us void SessionManager::handleSmMessage(u_int32_t smMessageType) { switch (smMessageType) { case Start: case Stop: case Active: case Standby: case Q_HOLD_I: case Q_HOLD_R: case Q_RESUME_I: case Q_RESUME_R: case Q_RESET_I: case Q_RESET_R: Debug(DebugMild,"Received unexpected SM message %s",typeName((Type)smMessageType)); break; default: Debug(this,DebugNote,"Unknown message type = 0x%08X",smMessageType); } } /** class SessionUser */ SessionUser::SessionUser(u_int16_t protType) : m_protType(protType) { } SessionUser::~SessionUser() { DDebug(&plugin,DebugAll,"SessionUser::~SessionUser() [%p]",this); } /** class SLT */ const TokenDict SLT::s_messages[] = { { "Connect Request", Connect_R }, { "Connect Confirmation", Connect_C }, { "Disconnect Request", Disconnect_R }, { "Disconnect confirmation", Disconnect_C }, { "Disconnect indication", Disconnect_I }, { "Data Request", Data_Req }, { "Data Indication", Data_Ind }, { "Data retrieval request", Data_Retrieval_R }, { "Data retrieval confirmation", Data_Retrieval_C }, { "Data retrieval indication", Data_Retrieval_I }, { "Data retrieval message", Data_Retrieval_M }, { "Link state controller request", Link_State_Controller_R }, { "Link state controller confirmation", Link_State_Controller_C }, { "Link state controller indication", Link_State_Controller_I }, { "Configuration request", Configuration_R }, { "Configuration confirmation", Configuration_C }, { "Status request", Status_R }, { "Status confirmation", Status_C }, { "Statistic request", Statistic_R }, { "Statistic confirmation", Statistic_C }, { "Control request", Control_R }, { "Control confirmation", Control_C }, { "Flow control response", Flow_Control_R }, { "Flow control indication", Flow_Control_I }, { 0, 0 } }; const TokenDict SLT::s_connectM[] = { { "Emergency alignment", Emergency }, { "Normal alignment", Normal }, { "Power on mtp2", Power }, { "Start mtp2", Start }, { 0, 0 } }; const TokenDict SLT::s_errors[] = { { "No error", Unknown }, { "T2 expired", T2_expired }, // this are the only error code known at this time { 0, 0 } }; const TokenDict SLT::s_states[] = { { "Configured", Configured }, { "Waiting", Waiting }, { "Unconfigured", Unconfigured }, { 0, 0 } }; const TokenDict SLT::s_dataRetR[] = { { "Return the BSN", Return }, { "Retrieve messages from BSN", Retrieve }, { "Drop messages", Drop }, { 0, 0 } }; const TokenDict SLT::s_linkStateCR[] = { { "Local processor down", LPD }, { "Local processor up", LPU }, { "Emergency", Emergency }, { "Emergency ceases", Emergency_c }, { "Flush buffers", FlushB }, { "Flush transmit buffers", FlushTB }, { "Flush retransmit buffers", FlushRT }, { "Flush receive buffers", FlushRecvB }, { "Continue", Continue }, { 0, 0 } }; const TokenDict SLT::s_linkStateCI[] = { { "Local processor down", LPD }, { "Local processor up", LPU }, { "Link entered congestion", LEC }, { "Physical layer up", PLU }, { "Physical layer down", PLD }, { "Protocol error", PE }, { "We have aligned the link", WHAL }, { "We have lost alignment", WHLA }, { "RTB full", RTBF }, { "RTB no longer full", RTBNF }, { "Negative acknowledgment", NA }, { "Remote entered a congested state", RECS }, { "Remote congestion is over", RCO }, { "Remote entered processor outage", REPO }, { "Remote recovered from outage", RPOR }, { 0, 0 } }; const TokenDict SLT::s_linkCongestion[] = { { "Unknown", UnknownC }, { "Management initiated", ManagementI }, { "Congestion ended", CongestionE }, { 0, 0 } }; const TokenDict SLT::s_protocolError[] = { { "Unknown", UnknownE }, { "Abnormal BSN received", AbnormalBSN }, { "Abnormal FIB received", AbnormalFIB }, { "Congestion discard", CongestionD }, { 0, 0 } }; SLT::SLT(const String& name, const NamedList& param) : SignallingComponent(param.safe("CiscoSLT")), SessionUser(1), m_status(Unconfigured), m_rStatus(OutOfService), m_reqStatus(OutOfService), m_messageId(1), m_channelId(0), m_bearerId(0), m_confReqTimer(0), m_printMsg(false) { #ifdef DEBUG String tmp; if (debugAt(DebugAll)) param.dump(tmp,"\r\n ",'\'',true); Debug(this,DebugInfo,"SLT::SLT('%s',%p) [%p]%s", name.c_str(),¶m,this,tmp.c_str()); #endif setCompType("cisco-slt"); m_channelId = param.getIntValue("channel",0); String sessionName = param.getValue("session","session"); setName(name); m_session = SessionManager::get(sessionName,¶m); if (m_session) { m_session->insert(this); m_session->deref(); } m_confReqTimer.interval(param,"configuration",250,5000,true); m_printMsg = param.getBoolValue("printslt",false); m_autoEmergency = param.getBoolValue("autoemergency",true); if (param.getBoolValue("autostart",true)) m_reqStatus = NormalAlignment; } SLT::~SLT() { DDebug(&plugin,DebugAll,"SLT::~SLT() [%p]",this); } void SLT::destroyed() { DDebug(&plugin,DebugAll,"SLT::destroyed() [%p]",this); m_session->remove(this); RefPointer tmp = m_session; m_session = 0; SignallingComponent::destroyed(); } unsigned int SLT::status() const { if (Configured != m_status || OutOfService == m_reqStatus) return OutOfService; return m_rStatus; } void SLT::setStatus(unsigned int status) { if (status == m_status) return; DDebug(this,DebugNote,"SLT status change: %s -> %s [%p]", showState((State)m_status),showState((State)status),this); m_status = status; } void SLT::setRemoteStatus(unsigned int status) { if (status == m_rStatus) return; DDebug(this,DebugNote,"Remote status change: %s -> %s [%p]", statusName(m_rStatus,true),statusName(status,true),this); bool old = aligned(); m_rStatus = status; if (aligned() != old) SS7Layer2::notify(); } void SLT::setReqStatus(unsigned int status) { if (status == m_reqStatus) return; DDebug(this,DebugNote,"Request status change: %s -> %s [%p]", statusName(m_reqStatus,true),statusName(status,true),this); bool old = aligned(); m_reqStatus = status; if (aligned() != old) SS7Layer2::notify(); } // Process notification received from session manager void SLT::notify(bool up) { if (!up) setStatus(Unconfigured); else configure(true); } bool SLT::control(Operation oper, NamedList* params) { if (params) { m_autoEmergency = params->getBoolValue("autoemergency",m_autoEmergency); m_printMsg = params->getBoolValue("printslt",m_printMsg); } switch (oper) { case Pause: setReqStatus(OutOfService); sendManagement(Disconnect_R); return true; case Resume: if (aligned()) return true; case Align: { bool emg = getEmergency(params); setReqStatus(emg ? EmergencyAlignment : NormalAlignment); switch (m_status) { case Configured: sendConnect(emg ? Emergency : Normal); break; case Waiting: break; default: configure(true); } } return true; case Status: return aligned() && m_status == Configured; default: return false;//SignallingReceiver::control((SignallingInterface::Operation)oper,params); } } bool SLT::aligned() const { return ((m_reqStatus == NormalAlignment) || (m_reqStatus == EmergencyAlignment)) && ((m_rStatus == NormalAlignment) || (m_rStatus == EmergencyAlignment)); } // We have to wait for an configuration confirmation message // if we don't receive it we resend the configuration request void SLT::timerTick(const Time& when) { if (m_confReqTimer.timeout()) { sendManagement(Configuration_R); m_confReqTimer.stop(); m_confReqTimer.start(); } } void SLT::getStringMessage(String& tmp, DataBlock& data) { String aux; const char* tab = " "; tmp << "PDU message: " << messageType((SLT::Messages)get16Message(data.data(8,2))) << "\r\n"; aux.hexify(data.data(),data.length(),' '); tmp << tab << "Data dump: " << aux << "\r\n"; tmp << tab << "Protocol Type: " << get16Message(data.data(4,2)) << "\r\n"; tmp << tab << "Message ID: " << get16Message(data.data(6,2)) << "\r\n"; tmp << tab << "Channel ID: " << m_channelId << "\r\n"; switch ((SLT::Messages)get16Message(data.data(8,2))) { case Connect_R: case Connect_C: if (data.length() >= 20) tmp << tab << "Message Description: " << connectType((connectM)get32Message(data.data(16,4))) << "\r\n"; break; case Link_State_Controller_R: if (data.length() >= 20) tmp << tab << "Message Description: " << slinkStateCR((linkStateCR)get32Message(data.data(16,4))) << "\r\n"; break; case Link_State_Controller_I: if (data.length() >= 20) tmp << tab << "Message Description: " << slinkStateCI((linkStateCI)get32Message(data.data(16,4))) << "\r\n"; if (data.length() >= 24) switch (get32Message(data.data(16,4))) { case LEC: case PLD: tmp << tab << "Details: " << slinkCongestion((linkCongestion)get32Message(data.data(20,4))); break; case PE: tmp << tab << "Details: " << sprotocolError((protocolError)get32Message(data.data(20,4))); break; default: tmp << tab << "Error: " << showError((errors)get32Message(data.data(20,4))); } break; case Disconnect_C: case Disconnect_I: if (data.length() >= 20) tmp << tab << "Error: " << showError((errors)get32Message(data.data(16,4))); default: break; } tmp << "\r\n" << " "; } u_int16_t SLT::get16Message(u_int8_t* msj) { if (!msj) return 0; return ((msj[0] << 8) + msj[1]); } u_int32_t SLT::get32Message(u_int8_t* msj) { if (!msj) return 0; return ((msj[0] << 24) + (msj[1] << 16) + (msj[2] << 8) + msj[3]); } // Check if this message is for this SLT and if is it is send for processing bool SLT::checkMessage(DataBlock& data) { if (m_status == Unconfigured) return false; u_int16_t channelId = get16Message(data.data(10,2)); if (m_channelId != channelId) return false; if (m_printMsg) { String tmp; getStringMessage(tmp,data); Debug(this,DebugInfo,"Received %s",tmp.c_str()); } u_int16_t msgType = get16Message(data.data(8,2)); if (msgType == Data_Req || msgType == Data_Ind) { if (get16Message(data.data(14,2)) < 1) { DDebug(this,DebugWarn,"Received data message with no data"); return true; } if (aligned()) { data.cut(-16); SS7MSU msu(data); return receivedMSU(msu); } else DDebug(this,DebugWarn,"Received data message while not aligned, local status = %s, remote status = %s", statusName(m_reqStatus,false),statusName(m_rStatus,false)); } else if (msgType & 0x40) { data.cut(-16); // Management message processManagement(msgType,data); } else { data.cut(-16); processSltMessage(msgType, data); } return true; } // Process an management message // NOTE we only handle configuration confirmation void SLT::processManagement(u_int16_t msgType, DataBlock& data) { switch ((Messages)msgType) { case Configuration_C: configure(false); break; case Status_C: case Statistic_C: case Control_C: case Flow_Control_R: case Flow_Control_I: DDebug(this,DebugInfo,"Unhandled management message: %s",messageType((SLT::Messages)msgType)); break; default: DDebug(this,DebugInfo,"Unknown management message 0x%04X",msgType); } } void SLT::processSltMessage(u_int16_t msgType, DataBlock& data) { u_int32_t mes = get32Message(data.data(0,4)); switch ((Messages)msgType) { case Connect_C: if (m_reqStatus == NormalAlignment && mes == Emergency) sendConnect(Normal); else if (m_reqStatus == EmergencyAlignment && mes == Normal) sendConnect(Emergency); else if (m_reqStatus != EmergencyAlignment && m_reqStatus != NormalAlignment) sendDisconnect(); else { setRemoteStatus(mes == Normal ? NormalAlignment : EmergencyAlignment); if (aligned()) m_session->userNotice(true); } break; case Disconnect_C: setRemoteStatus(OutOfService); if (m_reqStatus == EmergencyAlignment || m_reqStatus == NormalAlignment) sendConnect(m_reqStatus == NormalAlignment ? Normal : Emergency); break; case Disconnect_I: setRemoteStatus(OutOfService); if (m_reqStatus == EmergencyAlignment || m_reqStatus == NormalAlignment) sendConnect(m_reqStatus == NormalAlignment ? Normal : Emergency); break; case Link_State_Controller_C: setRemoteStatus(m_reqStatus); if (aligned()) m_session->userNotice(false); break; case Link_State_Controller_I: processCIndication(data); break; default: const char* mes = messageType((Messages)msgType); if (mes) DDebug(this,DebugWarn,"Received unhandled SLT message: %s",mes); else DDebug(this,DebugWarn,"Received unknown SLT message: 0x%04X",msgType); } } // Method called to build an PDU message void SLT::buildHeader(DataBlock& data,bool management) { u_int8_t head[16]; for (int i = 0;i < 16;i ++) head[i] = 0; head[2] = 0x80; head[5] = 1; if (!management) head[7] = 1; head[10] = (u_int8_t)(m_channelId >> 8); head[11] = (u_int8_t)(m_channelId & 0xff); data.append((void*)head,16); } void SLT::sendConnect(unsigned int status) { if (m_status != Configured) return; DataBlock data; buildHeader(data); u_int8_t* header = data.data(0,16); header[9] = 0x6; header[15] = 4; u_int8_t det[4]; det[0] = det[1] = det[2] = 0; det[3] = status; data.append((void*)det,4); if (m_printMsg) { String tmp; getStringMessage(tmp,data); Debug(this,DebugInfo,"Sending %s",tmp.c_str()); } m_session->sendData(data,true); } void SLT::sendControllerR(unsigned int linkState) { DataBlock data; buildHeader(data); u_int8_t* header = data.data(0,16); header[9] = 0x20; header[15] = 4; u_int8_t det[4]; for (int i = 0;i < 4;i ++) det[i] = 0; det[3] |= linkState; data.append((void*)det,4); if (m_printMsg) { String tmp; getStringMessage(tmp,data); Debug(this,DebugInfo,"Sending %s",tmp.c_str()); } m_session->sendData(data); } void SLT::sendManagement(unsigned int message) { DataBlock data; buildHeader(data,true); u_int8_t* header = data.data(0,16); header[9] |= message; if (m_printMsg) { String tmp; getStringMessage(tmp,data); Debug(this,DebugInfo,"Sending %s",tmp.c_str()); } m_session->sendData(data); } void SLT::sendDisconnect() { DataBlock data; buildHeader(data); u_int8_t* header = data.data(0,16); header[9] = 0x0a; if (m_printMsg) { String tmp; getStringMessage(tmp,data); Debug(this,DebugInfo,"Sending %s",tmp.c_str()); } m_session->sendData(data); } void SLT::processCIndication(DataBlock& data) { u_int32_t message = get32Message(data.data(0,4)); switch ((linkStateCI)message) { case LPU: case PLU: case WHAL: case RCO: case RPOR: case RTBNF: break; case LEC: case RECS: case RTBF: if (aligned()) m_session->userNotice(false); setRemoteStatus(Busy); break; case REPO: if (aligned()) m_session->userNotice(false); setRemoteStatus(ProcessorOutage); break; case WHLA: if (aligned()) m_session->userNotice(false); setRemoteStatus(OutOfAlignment); break; case LPD: case PLD: case PE: case NA: if (aligned()) m_session->userNotice(false); setRemoteStatus(OutOfService); break; } } bool SLT::transmitMSU(const SS7MSU& msu) { if (!m_session) return false; if (!aligned()) { Debug(this,DebugNote,"Requested to send data while not operational"); return false; } DataBlock data; buildHeader(data); u_int8_t* header = data.data(0,16); header[9] = 0x10; header[15] = msu.length(); data += msu; if (m_printMsg) { String tmp; getStringMessage(tmp,data); Debug(this,DebugInfo,"Sending %s",tmp.c_str()); } return m_session->sendData(data); } bool SLT::operational() const { return aligned(); } void SLT::configure(bool start) { if (start && m_confReqTimer.interval()) { sendManagement(Configuration_R); m_confReqTimer.start(); setStatus(Waiting); return; } m_confReqTimer.stop(); setStatus(Configured); SS7Layer2::notify(); DDebug(this,DebugInfo,"requested status = %s",statusName(m_reqStatus,false)); if (m_reqStatus == NormalAlignment || m_reqStatus == EmergencyAlignment) sendConnect(m_reqStatus == NormalAlignment ? Normal : Emergency); } SignallingComponent* SLT::create(const String& type, const NamedList& name) { if (type != "SS7Layer2") return 0; const String* module = name.getParam("module"); if (module && *module != "ciscosm") return 0; Configuration cfg(Engine::configFile("ciscosm")); const char* sectName = name.getValue("link",name); NamedList* layer = cfg.getSection(sectName); if (module) { if (layer) layer->clearParams(); else { cfg.createSection(sectName); layer = cfg.getSection(sectName); } layer->copyParams(name); cfg.save(); } if (!layer) { return 0; } String confSec = layer->getValue("session","session"); NamedList recvSec(confSec); recvSec.copySubParams(*layer,confSec + "."); NamedList* section = cfg.getSection(confSec); if (recvSec.count()) { if (section) section->clearParams(); else { cfg.createSection(confSec); section = cfg.getSection(confSec); } section->copyParams(recvSec); cfg.save(); } if (!section) { Debug(&plugin,DebugConf,"Session '%s' does not exist in configuration",confSec.c_str()); return 0; } layer->copyParams(*section); layer->copyParams(name); return new SLT(name,*layer); } /** class CiscoSMModule */ CiscoSMModule::CiscoSMModule() : Module("ciscosm","misc",true), m_init(false) { Output("Loaded module Cisco SM"); } CiscoSMModule::~CiscoSMModule() { Output("Unloading module Cisco SM"); s_sessions.clear(); } void CiscoSMModule::initialize() { Output("Initializing module Cisco SM"); Configuration cfg(Engine::configFile("ciscosm")); if (!m_init) { m_init = true; setup(); } Lock lock(s_sessionMutex); ObjList* obj = s_sessions.skipNull(); for (; obj; obj = obj->skipNext()) { SessionManager* ses = static_cast(obj->get()); if (ses->socket()->State() == RudpSocket::RudpDead) ses->socket()->initSocket(*(cfg.getSection(ses->toString()))); } } UNLOAD_PLUGIN(unloadNow) { if (unloadNow) { if (!s_sessionMutex.lock(500000)) return false; bool ok = (s_sessions.count() == 0); s_sessionMutex.unlock(); return ok; } return true; } }; // anonymous namespace /* vi: set ts=8 sw=4 sts=4 noet: */