/** * layer3.cpp * This file is part of the YATE Project http://YATE.null.ro * * Yet Another Signalling Stack - implements the support for SS7, ISDN and PSTN * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2014 Null Team * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. * * 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. */ #include "yatesig.h" #include #include using namespace TelEngine; static const TokenDict s_dict_control[] = { { "show", SS7MTP3::Status }, { "pause", SS7MTP3::Pause }, { "resume", SS7MTP3::Resume }, { "restart", SS7MTP3::Restart }, { 0, 0 } }; typedef GenPointer L2Pointer; void SS7L3User::notify(SS7Layer3* network, int sls) { Debug(this,DebugStub,"Please implement SS7L3User::notify(%p,%d) [%p]",network,sls,this); } ObjList* SS7L3User::getNetRoutes(SS7Layer3* network, SS7PointCode::Type type) { return network ? network->getRoutes(type) : (ObjList*)0; } const ObjList* SS7L3User::getNetRoutes(const SS7Layer3* network, SS7PointCode::Type type) { return network ? network->getRoutes(type) : (const ObjList*)0; } // Constructor SS7Layer3::SS7Layer3(SS7PointCode::Type type) : SignallingComponent("SS7Layer3"), m_routeMutex(true,"SS7Layer3::route"), m_l3userMutex(true,"SS7Layer3::l3user"), m_l3user(0), m_defNI(SS7MSU::National) { for (unsigned int i = 0; i < YSS7_PCTYPE_COUNT; i++) m_local[i] = 0; setType(type); } // Initialize the Layer 3 component bool SS7Layer3::initialize(const NamedList* config) { if (config) setNI(SS7MSU::getNetIndicator(config->getValue(YSTRING("netindicator")),SS7MSU::National)); if (engine() && !user()) { NamedList params("ss7router"); if (!resolveConfig(YSTRING("router"),params,config)) params.addParam("local-config","true"); if (params.toBoolean(true)) SS7Layer3::attach(YOBJECT(SS7Router,engine()->build("SS7Router",params,true,false))); } return true; } // Attach a Layer 3 user component to this network void SS7Layer3::attach(SS7L3User* l3user) { Lock lock(m_l3userMutex); if (m_l3user == l3user) return; SS7L3User* tmp = m_l3user; m_l3user = l3user; lock.drop(); if (tmp) { const char* name = 0; if (engine() && engine()->find(tmp)) { name = tmp->toString().safe(); if (tmp->getObject(YSTRING("SS7Router"))) (static_cast(tmp))->detach(this); else tmp->attach(0); } Debug(this,DebugAll,"Detached L3 user (%p,'%s') [%p]",tmp,name,this); } if (!l3user) return; Debug(this,DebugAll,"Attached L3 user (%p,'%s') [%p]",l3user,l3user->toString().safe(),this); insert(l3user); if (l3user->getObject(YSTRING("SS7Router"))) (static_cast(l3user))->attach(this); else l3user->attach(this); } SS7PointCode::Type SS7Layer3::type(unsigned char netType) const { if (netType & 0xc0) netType >>= 6; return m_cpType[netType & 0x03]; } void SS7Layer3::setType(SS7PointCode::Type type, unsigned char netType) { if (netType & 0xc0) netType >>= 6; m_cpType[netType & 0x03] = type; } void SS7Layer3::setType(SS7PointCode::Type type) { m_cpType[3] = m_cpType[2] = m_cpType[1] = m_cpType[0] = type; } unsigned char SS7Layer3::getNI(SS7PointCode::Type pcType, unsigned char defNI) const { if ((defNI & 0xc0) == 0) defNI <<= 6; if (SS7PointCode::Other == pcType || type(defNI) == pcType) return defNI; if (pcType == m_cpType[2]) return SS7MSU::National; if (pcType == m_cpType[3]) return SS7MSU::ReservedNational; if (pcType == m_cpType[0]) return SS7MSU::International; if (pcType == m_cpType[1]) return SS7MSU::SpareInternational; return defNI; } void SS7Layer3::setNI(unsigned char defNI) { if ((defNI & 0xc0) == 0) defNI <<= 6; m_defNI = defNI & 0xc0; } bool SS7Layer3::hasType(SS7PointCode::Type pcType) const { if (SS7PointCode::Other == pcType) return false; for (int i = 0; i < 4; i++) if (pcType == m_cpType[i]) return true; return false; } // Build the list of destination point codes and set the routing priority bool SS7Layer3::buildRoutes(const NamedList& params) { Lock lock(m_routeMutex); for (unsigned int i = 0; i < YSS7_PCTYPE_COUNT; i++) { m_route[i].clear(); m_local[i] = 0; } unsigned int n = params.length(); bool added = false; for (unsigned int i= 0; i < n; i++) { NamedString* ns = params.getParam(i); if (!ns) continue; unsigned int prio = 0; unsigned int shift = 0; unsigned int maxLength = MAX_TDM_MSU_SIZE; bool local = false; if (ns->name() == YSTRING("local")) local = true; else if (ns->name() == YSTRING("route")) prio = 100; else if (ns->name() != YSTRING("adjacent")) continue; // Get & check the route ObjList* route = ns->split(',',true); ObjList* obj = route->skipNull(); SS7PointCode pc; SS7PointCode::Type type = SS7PointCode::Other; do { if (!obj) break; type = SS7PointCode::lookup(obj->get()->toString()); obj = obj->skipNext(); if (!(obj && pc.assign(obj->get()->toString(),type))) break; if (prio) { if (!(obj = obj->skipNext())) break; prio = obj->get()->toString().toInteger(prio); obj = obj->skipNext(); if (obj) shift = obj->get()->toString().toInteger(0); } if (!(obj = obj->skipNext()) || local) break; maxLength = obj->get()->toString().toInteger(maxLength); if (maxLength < MAX_TDM_MSU_SIZE) { Debug(this,DebugNote,"MaxDataLength is too small %d. Setting it to %d", maxLength,MAX_TDM_MSU_SIZE); maxLength = MAX_TDM_MSU_SIZE; } } while (false); TelEngine::destruct(route); unsigned int packed = pc.pack(type); if ((unsigned int)type > YSS7_PCTYPE_COUNT || !packed) { Debug(this,DebugNote,"Invalid %s='%s' (invalid point code%s) [%p]", ns->name().c_str(),ns->safe(),type == SS7PointCode::Other ? " type" : "",this); continue; } if (local) { m_local[type - 1] = packed; continue; } if (findRoute(type,packed)) { Debug(this,DebugWarn,"Duplicate route found %s!!",ns->c_str()); continue; } added = true; m_route[(unsigned int)type - 1].append(new SS7Route(packed,type,prio,shift,maxLength)); DDebug(this,DebugAll,"Added route '%s'",ns->c_str()); } if (!added) Debug(this,DebugMild,"No outgoing routes [%p]",this); else printRoutes(); return added; } // Get the maximum data length that this route can transport unsigned int SS7Layer3::getRouteMaxLength(SS7PointCode::Type type, unsigned int packedPC) { if (type == SS7PointCode::Other || (unsigned int)type > YSS7_PCTYPE_COUNT || !packedPC) return MAX_TDM_MSU_SIZE; Lock lock(m_routeMutex); SS7Route* route = findRoute(type,packedPC); if (route) return route->m_maxDataLength; return MAX_TDM_MSU_SIZE; } // Get the priority of a route. unsigned int SS7Layer3::getRoutePriority(SS7PointCode::Type type, unsigned int packedPC) { if (type == SS7PointCode::Other || (unsigned int)type > YSS7_PCTYPE_COUNT || !packedPC) return (unsigned int)-1; Lock lock(m_routeMutex); SS7Route* route = findRoute(type,packedPC); if (route) return route->m_priority; return (unsigned int)-1; } // Get the state of a route. SS7Route::State SS7Layer3::getRouteState(SS7PointCode::Type type, unsigned int packedPC, bool checkAdjacent) { if (type == SS7PointCode::Other || (unsigned int)type > YSS7_PCTYPE_COUNT || !packedPC) return SS7Route::Unknown; Lock lock(m_routeMutex); for (ObjList* o = m_route[type-1].skipNull(); o; o = o->skipNext()) { SS7Route* route = static_cast(o->get()); if (route->packed() == packedPC) return route->state(); if (checkAdjacent && !route->priority() && !(route->state() & SS7Route::NotProhibited)) return route->state(); } return SS7Route::Unknown; } bool SS7Layer3::maintenance(const SS7MSU& msu, const SS7Label& label, int sls) { if (msu.getSIF() != SS7MSU::MTN && msu.getSIF() != SS7MSU::MTNS) return false; // Q.707 says test pattern length should be 1-15 but we accept 0 as well const unsigned char* s = msu.getData(label.length()+1,2); if (!s) return false; String addr; addr << SS7PointCode::lookup(label.type()) << "," << label; if (debugAt(DebugAll)) addr << " (" << label.opc().pack(label.type()) << ":" << label.dpc().pack(label.type()) << ":" << label.sls() << ")"; unsigned int local = getLocal(label.type()); if (local && label.dpc().pack(label.type()) != local) { Debug(this,DebugMild,"Received MTN %s type %02X length %u %s [%p]", addr.c_str(),s[0],msu.length(), (label.opc().pack(label.type()) == local ? "looped back!" : "with invalid DPC"), this); return false; } bool badLink = label.sls() != sls; if (!badLink) { unsigned int local = getLocal(label.type()); // maintenance messages must be addressed to us if (local && label.dpc().pack(label.type()) != local) badLink = true; // and come from an adjacent node else if (getRoutePriority(label.type(),label.opc())) badLink = true; } int level = DebugAll; if (getNI(type(msu.getNI())) != msu.getNI()) { addr << " wrong " << msu.getIndicatorName() << " NI"; level = DebugMild; } if (badLink) { addr << " on " << sls; level = DebugWarn; } unsigned char len = s[1] >> 4; // get a pointer to the test pattern const unsigned char* t = msu.getData(label.length()+3,len); if (!t) { Debug(this,DebugMild,"Received MTN %s type %02X length %u with invalid pattern length %u [%p]", addr.c_str(),s[0],msu.length(),len,this); return false; } switch (s[0]) { case SS7MsgMTN::SLTM: Debug(this,level,"Received SLTM %s with %u bytes",addr.c_str(),len); if (badLink) return false; if (responder()) { SS7Label lbl(label,label.sls(),0); SS7MSU answer(msu.getSIO(),lbl,0,len+2); unsigned char* d = answer.getData(lbl.length()+1,len+2); if (!d) return false; linkChecked(sls,true); addr.clear(); addr << SS7PointCode::lookup(lbl.type()) << "," << lbl; if (debugAt(DebugAll)) addr << " (" << lbl.opc().pack(lbl.type()) << ":" << lbl.dpc().pack(lbl.type()) << ":" << lbl.sls() << ")"; Debug(this,level,"Sending SLTA %s with %u bytes",addr.c_str(),len); *d++ = SS7MsgMTN::SLTA; *d++ = len << 4; while (len--) *d++ = *t++; return transmitMSU(answer,lbl,sls) >= 0; } return true; case SS7MsgMTN::SLTA: Debug(this,level,"Received SLTA %s with %u bytes",addr.c_str(),len); if (badLink) return false; if (len != 4) return false; unsigned char patt = sls; patt = (patt << 4) | (patt & 0x0f); while (len--) if (*t++ != patt++) return false; linkChecked(sls,false); return true; } Debug(this,DebugMild,"Received MTN %s type %02X, length %u [%p]", addr.c_str(),s[0],msu.length(),this); return false; } bool SS7Layer3::management(const SS7MSU& msu, const SS7Label& label, int sls) { if (msu.getSIF() != SS7MSU::SNM) return false; Debug(this,DebugStub,"Please implement SS7Layer3::management(%p,%p,%d) [%p]", &msu,&label,sls,this); // according to Q.704 there should be at least the heading codes (8 bit) const unsigned char* s = msu.getData(label.length()+1,1); if (!s) return false; // TODO: implement return false; } bool SS7Layer3::unavailable(const SS7MSU& msu, const SS7Label& label, int sls, unsigned char cause) { DDebug(this,DebugInfo,"SS7Layer3::unavailable(%p,%p,%d,%d) [%p]", &msu,&label,sls,cause,this); #ifdef DEBUG String s; s.hexify(msu.data(),msu.length(),' '); Debug(this,DebugMild,"Unhandled MSU len=%u Serv: %s, Prio: %s, Net: %s, Data: %s", msu.length(),msu.getServiceName(),msu.getPriorityName(), msu.getIndicatorName(),s.c_str()); #endif if (msu.getSIF() == SS7MSU::SNM) return false; // send a SNM UPU (User Part Unavailable, Q.704 15.17.2) unsigned char llen = SS7PointCode::length(label.type()); SS7Label lbl(label,label.sls(),0); unsigned int local = getLocal(label.type()); if (local) lbl.opc().unpack(label.type(),local); SS7MSU answer(SS7MSU::SNM,msu.getSSF(),lbl,0,llen+2); unsigned char* d = answer.getData(lbl.length()+1,llen+2); if (!d) return false; d[0] = SS7MsgSNM::UPU; label.dpc().store(label.type(),d+1); d[llen+1] = msu.getSIF() | ((cause & 0x0f) << 4); return transmitMSU(answer,lbl,sls) >= 0; } bool SS7Layer3::prohibited(unsigned char ssf, const SS7Label& label, int sls) { DDebug(this,DebugInfo,"SS7Layer3::prohibited(%u,%p,%d) [%p]", ssf,&label,sls,this); // send a SNM TFP (Transfer Prohibited, Q.704 13.2) unsigned char llen = SS7PointCode::length(label.type()); SS7Label lbl(label,label.sls(),0); unsigned int local = getLocal(label.type()); if (local) lbl.opc().unpack(label.type(),local); SS7MSU answer(SS7MSU::SNM,ssf,lbl,0,llen+1); unsigned char* d = answer.getData(lbl.length()+1,llen+1); if (!d) return false; d[0] = SS7MsgSNM::TFP; label.dpc().store(label.type(),d+1); return transmitMSU(answer,lbl,sls) >= 0; } // Find a route having the specified point code type and packed point code SS7Route* SS7Layer3::findRoute(SS7PointCode::Type type, unsigned int packed) { if ((unsigned int)type == 0 || !packed) return 0; unsigned int index = (unsigned int)type - 1; if (index >= YSS7_PCTYPE_COUNT) return 0; Lock lock(m_routeMutex); for (ObjList* o = m_route[index].skipNull(); o; o = o->skipNext()) { SS7Route* route = static_cast(o->get()); if (route->packed() == packed) return route; } return 0; } void SS7Layer3::printRoutes() { String s; bool router = getObject(YSTRING("SS7Router")) != 0; for (unsigned int i = 0; i < YSS7_PCTYPE_COUNT; i++) { ObjList* o = m_route[i].skipNull(); if (!o) continue; SS7PointCode::Type type = (SS7PointCode::Type)(i + 1); String tmp; String sType = SS7PointCode::lookup(type); sType << String(' ',(unsigned int)(8 - sType.length())); if (m_local[i]) sType << SS7PointCode(type,m_local[i]) << " > "; for (; o; o = o->skipNext()) { SS7Route* route = static_cast(o->get()); tmp << sType << SS7PointCode(type,route->m_packed); if (!router) { tmp << " " << route->m_priority << " (" << route->stateName() << ")"; if (route->shift()) tmp << " >> " << route->shift(); tmp << "\r\n"; continue; } tmp << " (" << route->stateName() << ")"; for (ObjList* oo = route->m_networks.skipNull(); oo; oo = oo->skipNext()) { GenPointer* d = static_cast*>(oo->get()); if (*d) tmp << " " << (*d)->toString() << "," << (*d)->getRoutePriority(type,route->m_packed) << "," << SS7Route::stateName((*d)->getRouteState(type,route->m_packed)); } if (route->shift()) tmp << " >> " << route->shift(); tmp << "\r\n"; } s << tmp; } if (s) { s = s.substr(0,s.length() - 2); Output("%s of '%s': [%p]\r\n%s",router?"Routing table":"Destinations",debugName(),this,s.c_str()); } else Output("No %s in '%s' [%p]",router?"routes":"destinations",debugName(),this); } SS7MTP3::SS7MTP3(const NamedList& params) : SignallingComponent(params.safe("SS7MTP3"),¶ms,"ss7-mtp3"), SignallingDumpable(SignallingDumper::Mtp3), Mutex(true,"SS7MTP3"), m_total(0), m_active(0), m_slcShift(false), m_inhibit(false), m_warnDown(true), m_checklinks(true), m_forcealign(true), m_checkT1(0), m_checkT2(0) { #ifdef DEBUG if (debugAt(DebugAll)) { String tmp; params.dump(tmp,"\r\n ",'\'',true); Debug(this,DebugAll,"SS7MTP3::SS7MTP3(%p) [%p]%s", ¶ms,this,tmp.c_str()); } #endif for (unsigned int i = 0; i < YSS7_PCTYPE_COUNT; i++) m_allowed[i] = 0; // Set point code type for each network indicator static const unsigned char ni[4] = { SS7MSU::International, SS7MSU::SpareInternational, SS7MSU::National, SS7MSU::ReservedNational }; String stype = params.getValue(YSTRING("netind2pctype")); int level = DebugAll; if (stype.find(',') >= 0) { ObjList* obj = stype.split(',',false); ObjList* o = obj->skipNull(); for (unsigned int i = 0; i < 4; i++) { String* s = 0; if (o) { s = static_cast(o->get()); o = o->skipNext(); } SS7PointCode::Type type = SS7PointCode::lookup(s?s->c_str():0); if (type == SS7PointCode::Other) level = DebugNote; setType(type,ni[i]); } TelEngine::destruct(obj); } else { SS7PointCode::Type type = SS7PointCode::lookup(stype.c_str()); if (type == SS7PointCode::Other) level = DebugNote; for (unsigned int i = 0; i < 4; i++) setType(type,ni[i]); } Debug(this,level,"Point code types are '%s' [%p]",stype.safe(),this); m_slcShift = params.getBoolValue(YSTRING("slcshift"),false); m_inhibit = !params.getBoolValue(YSTRING("autostart"),true); m_checklinks = params.getBoolValue(YSTRING("checklinks"),m_checklinks); m_forcealign = params.getBoolValue(YSTRING("forcealign"),m_forcealign); int check = params.getIntValue(YSTRING("checkfails"),5000); if (check > 0) { if (check < 4000) check = 4000; else if (check > 12000) check = 12000; m_checkT1 = 1000 * check; } check = params.getIntValue(YSTRING("maintenance"),60000); if (check > 0) { if (check < 30000) check = 30000; else if (check > 300000) check = 300000; m_checkT2 = 1000 * check; } buildRoutes(params); unsigned int n = params.length(); for (unsigned int p = 0; p < n; p++) { NamedString* ns = params.getParam(p); if (!ns || (ns->name() != "allowed")) continue; ObjList* l = ns->split(',',false); ObjList* o = l->skipNull(); if (o) { SS7PointCode::Type type = SS7PointCode::lookup(o->get()->toString()); o = o->skipNext(); if (o && (SS7PointCode::Other != type)) { unsigned int a = o->count(); delete[] m_allowed[type-1]; m_allowed[type-1] = new unsigned int[a+1]; for (a = 0; o; o = o->skipNext()) m_allowed[type-1][a++] = o->get()->toString().toInteger(-1); m_allowed[type-1][a] = 0; } } TelEngine::destruct(l); } setDumper(params.getValue(YSTRING("layer3dump"))); } SS7MTP3::~SS7MTP3() { setDumper(); for (unsigned int i = 0; i < YSS7_PCTYPE_COUNT; i++) delete[] m_allowed[i]; } unsigned int SS7MTP3::countLinks() { unsigned int total = 0; unsigned int checked = 0; unsigned int active = 0; ObjList* l = &m_links; for (; l; l = l->next()) { L2Pointer* p = static_cast(l->get()); if (!(p && *p)) continue; total++; if ((*p)->operational()) { if (!((*p)->inhibited(SS7Layer2::Unchecked))) { checked++; if (!((*p)->inhibited())) active++; } } } m_total = total; m_checked = checked; m_active = active; return active; } bool SS7MTP3::operational(int sls) const { if (m_inhibit) return false; if (sls < 0) return (m_active != 0); const ObjList* l = &m_links; for (; l; l = l->next()) { L2Pointer* p = static_cast(l->get()); if (!(p && *p)) continue; if ((*p)->sls() == sls) return (*p)->operational(); } return false; } int SS7MTP3::inhibited(int sls) const { if (sls < 0) return m_inhibit ? SS7Layer2::Inactive : 0; const ObjList* l = &m_links; for (; l; l = l->next()) { L2Pointer* p = static_cast(l->get()); if (!(p && *p)) continue; if ((*p)->sls() == sls) return (*p)->inhibited(); } return SS7Layer2::Inactive; } bool SS7MTP3::inhibit(int sls, int setFlags, int clrFlags) { if (sls < 0) return false; const ObjList* l = &m_links; for (; l; l = l->next()) { L2Pointer* p = static_cast(l->get()); if (!(p && *p)) continue; if ((*p)->sls() == sls) { DDebug(this,DebugAll,"Setting inhibition +0x%02X -0x%02X on %d '%s' [%p]", setFlags,clrFlags,sls,(*p)->toString().c_str(),this); return (*p)->inhibit(setFlags,clrFlags); } } return false; } unsigned int SS7MTP3::congestion(int sls) { unsigned int level = 0; const ObjList* l = &m_links; for (; l; l = l->next()) { L2Pointer* p = static_cast(l->get()); if (!(p && *p)) continue; if ((*p)->sls() == sls) return (*p)->congestion(); else if (sls >= 0) { unsigned int cong = (*p)->congestion();; if (level < cong) level = cong; } } return level; } int SS7MTP3::getSequence(int sls) const { if (sls < 0) return -1; const ObjList* l = &m_links; for (; l; l = l->next()) { L2Pointer* p = static_cast(l->get()); if (!(p && *p)) continue; if ((*p)->sls() == sls) return (*p)->getSequence(); } return false; } void SS7MTP3::recoverMSU(int sls, int sequence) { if (sls < 0) return; const ObjList* l = &m_links; for (; l; l = l->next()) { L2Pointer* p = static_cast(l->get()); if (!(p && *p)) continue; if ((*p)->sls() == sls) { (*p)->recoverMSU(sequence); break; } } } // Attach a link in the first free SLS void SS7MTP3::attach(SS7Layer2* link) { if (!link) return; SignallingComponent::insert(link); Lock lock(this); // Check if already attached for (ObjList* o = m_links.skipNull(); o; o = o->skipNext()) { L2Pointer* p = static_cast(o->get()); if (*p == link) { link->attach(this); return; } } ObjList* before = 0; int sls = link->sls(); if (sls >= 0) { before = m_links.skipNull(); for (; before; before = before->skipNext()) { L2Pointer* p = static_cast(before->get()); if (!*p) continue; if (sls < (*p)->sls()) break; if (sls == (*p)->sls()) { sls = -1; break; } } } if (sls < 0) { // Attach in the first free SLS sls = 0; before = m_links.skipNull(); for (; before; before = before->skipNext()) { L2Pointer* p = static_cast(before->get()); if (!*p) continue; if (sls < (*p)->sls()) break; sls++; } link->sls(sls); } link->ref(); if (!before) m_links.append(new L2Pointer(link)); else before->insert(new L2Pointer(link)); Debug(this,DebugAll,"Attached link (%p,'%s') with SLS=%d [%p]", link,link->toString().safe(),link->sls(),this); countLinks(); link->attach(this); } // Detach a link. Remove its L2 user void SS7MTP3::detach(SS7Layer2* link) { if (!link) return; Lock lock(this); for (ObjList* o = m_links.skipNull(); o; o = o->skipNext()) { L2Pointer* p = static_cast(o->get()); if (*p != link) continue; m_links.remove(p); Debug(this,DebugAll,"Detached link (%p,'%s') with SLS=%d [%p]", link,link->toString().safe(),link->sls(),this); link->attach(0); TelEngine::destruct(link); countLinks(); return; } } bool SS7MTP3::allowedTo(SS7PointCode::Type type, unsigned int packedPC) const { if (type >= SS7PointCode::DefinedTypes) return false; if (!m_allowed[type-1]) return true; for (int i = 0; m_allowed[type-1][i]; i++) { if (packedPC == m_allowed[type-1][i]) return true; } return false; } bool SS7MTP3::control(Operation oper, NamedList* params) { bool ok = operational(); if (params) { // cannot change SLS to SLC shift while active if (m_active == 0) m_slcShift = params->getBoolValue(YSTRING("slcshift"),m_slcShift); m_checklinks = params->getBoolValue(YSTRING("checklinks"),m_checklinks); m_forcealign = params->getBoolValue(YSTRING("forcealign"),m_forcealign); const String& inh = (*params)[YSTRING("inhibit")]; if (inh) { // inhibit=slc,[inh_flags][,uninh_flags] ObjList* l = inh.split(',',true); if (l && (l->length() == 2 || l->length() == 3)) { int slc = l->at(0)->toString().toInteger(-1); if (slc >= 0) { int inh = l->at(1)->toString().toInteger(0); int unh = l->at(2) ? l->at(2)->toString().toInteger(0) : 0; inhibit(slc,inh,unh); } } TelEngine::destruct(l); } } switch (oper) { case Pause: if (!m_inhibit) { m_inhibit = true; if (ok) SS7Layer3::notify(-1); } return TelEngine::controlReturn(params,true); case Restart: if (ok) { ok = false; m_inhibit = true; SS7Layer3::notify(-1); } // fall through case Resume: if (m_inhibit) { m_inhibit = false; if (ok != operational()) SS7Layer3::notify(-1); } m_warnDown = true; if (params && params->getBoolValue(YSTRING("emergency"))) { unsigned int cnt = 0; const ObjList* l = &m_links; for (; l; l = l->next()) { L2Pointer* p = static_cast(l->get()); if (!(p && *p)) continue; cnt++; (*p)->control(SS7Layer2::Resume,params); } Debug(this,DebugNote,"Emergency resume attempt on %u links [%p]",cnt,this); } return TelEngine::controlReturn(params,true); case Status: printRoutes(); return TelEngine::controlReturn(params,ok);; } return TelEngine::controlReturn(params,false); } bool SS7MTP3::control(NamedList& params) { String* ret = params.getParam(YSTRING("completion")); const String* oper = params.getParam(YSTRING("operation")); const char* cmp = params.getValue(YSTRING("component")); int cmd = oper ? oper->toInteger(s_dict_control,-1) : -1; if (ret) { if (oper && (cmd < 0)) return false; String part = params.getValue(YSTRING("partword")); if (cmp) { if (toString() != cmp) return false; for (const TokenDict* d = s_dict_control; d->token; d++) Module::itemComplete(*ret,d->token,part); return true; } return Module::itemComplete(*ret,toString(),part); } if (!(cmp && toString() == cmp)) return false; if (cmd >= 0) return control((Operation)cmd,¶ms); return SignallingDumpable::control(params,this); } // Configure and initialize MTP3 and its links bool SS7MTP3::initialize(const NamedList* config) { #ifdef DEBUG String tmp; if (config && debugAt(DebugAll)) config->dump(tmp,"\r\n ",'\'',true); Debug(this,DebugInfo,"SS7MTP3::initialize(%p) [%p]%s",config,this,tmp.c_str()); #endif if (config) debugLevel(config->getIntValue(YSTRING("debuglevel_mtp3"), config->getIntValue(YSTRING("debuglevel"),-1))); countLinks(); m_warnDown = true; if (config && (0 == m_total)) { m_slcShift = config->getBoolValue(YSTRING("slcshift"),m_slcShift); m_checklinks = config->getBoolValue(YSTRING("checklinks"),m_checklinks); m_forcealign = config->getBoolValue(YSTRING("forcealign"),m_forcealign); unsigned int n = config->length(); for (unsigned int i = 0; i < n; i++) { NamedString* param = config->getParam(i); if (!(param && param->name() == YSTRING("link"))) continue; NamedPointer* ptr = YOBJECT(NamedPointer,param); NamedList* linkConfig = ptr ? YOBJECT(NamedList,ptr->userData()) : 0; String linkName(*param); int linkSls = -1; int sep = linkName.find(','); if (sep >= 0) { linkSls = linkName.substr(sep + 1).toInteger(-1); linkName = linkName.substr(0,sep); } NamedList params(linkName); params.addParam("basename",linkName); if (linkConfig) params.copyParams(*linkConfig); else { if (config->hasSubParams(params + ".")) params.copySubParams(*config,params + "."); else params.addParam("local-config","true"); linkConfig = ¶ms; } SS7Layer2* link = YSIGCREATE(SS7Layer2,¶ms); if (!link) continue; if (linkSls >= 0) link->sls(linkSls); if (m_checklinks) link->inhibit(SS7Layer2::Unchecked|SS7Layer2::Inactive); attach(link); if (!link->initialize(linkConfig)) detach(link); TelEngine::destruct(link); } m_inhibit = !config->getBoolValue(YSTRING("autostart"),true); } SS7Layer3::initialize(config); return 0 != m_total; } // Detach all links and user. Destroys the object, disposes the memory void SS7MTP3::destroyed() { lock(); ListIterator iter(m_links); for (GenObject* o = 0; 0 != (o = iter.get());) { L2Pointer* p = static_cast(o); detach(*p); } SS7Layer3::attach(0); unlock(); SS7Layer3::destroyed(); } int SS7MTP3::transmitMSU(const SS7MSU& msu, const SS7Label& label, int sls) { bool maint = (msu.getSIF() == SS7MSU::MTN) || (msu.getSIF() == SS7MSU::MTNS); bool mgmt = (msu.getSIF() == SS7MSU::SNM); bool regular = !maint && !mgmt; Lock lock(this); if (!(maint || m_active || (mgmt && m_checked))) { if (m_warnDown) { m_warnDown = false; Debug(this,m_total ? DebugInfo : DebugMild,"Could not transmit %s MSU, %s", msu.getServiceName(), m_total ? "all links are down" : "no data links attached"); } return -1; } // TODO: support ranges with holes if (regular && sls >= 0) { if (m_slcShift) sls = sls >> 1; sls = sls % m_total; } // Try to find a link with the given SLS ObjList* l = (sls >= 0) ? &m_links : 0; for (; l; l = l->next()) { L2Pointer* p = static_cast(l->get()); if (!(p && *p)) continue; SS7Layer2* link = *p; if (link->sls() == sls) { XDebug(this,DebugAll,"Found link %p for SLS=%d [%p]",link,sls,this); if (link->operational() && (maint || !link->inhibited())) { if (link->transmitMSU(msu)) { DDebug(this,DebugAll,"Sent MSU over link '%s' %p with SLS=%d%s [%p]", link->toString().c_str(),link,sls, (m_inhibit ? " while inhibited" : ""),this); dump(msu,true,sls); m_warnDown = true; return (regular && m_slcShift) ? (sls << 1) : sls; } return -1; } if (maint) { Debug(this,DebugNote,"Dropping maintenance MSU for SLS=%d, link is down",sls); return -1; } // found link but is down - reroute Debug(this,DebugAll,"Rerouting %s MSU for SLS=%d, link is down", msu.getServiceName(),sls); break; } } if (maint) return -1; // Link not found or not operational: choose another one for (l = m_links.skipNull(); l; l = l->skipNext()) { L2Pointer* p = static_cast(l->get()); if (!*p) continue; SS7Layer2* link = *p; // if we are desperate use even inhibited (but checked) links bool inh = (mgmt && (sls == -2)) ? link->inhibited(SS7Layer2::Unchecked) : (link->inhibited() != 0); if (link->operational() && !inh && link->transmitMSU(msu)) { sls = link->sls(); DDebug(this,DebugAll,"Sent MSU over link '%s' %p with SLS=%d%s [%p]", link->toString().c_str(),link,sls, (m_inhibit ? " while inhibited" : ""),this); dump(msu,true,sls); m_warnDown = true; return (regular && m_slcShift) ? (sls << 1) : sls; } } Debug(this,((sls == -2) ? DebugWarn : DebugInfo), "Could not find any link to send %s MSU",msu.getServiceName()); return -1; } bool SS7MTP3::receivedMSU(const SS7MSU& msu, SS7Layer2* link, int sls) { dump(msu,false,sls); int netType = msu.getNI(); SS7PointCode::Type cpType = type(netType); unsigned int llen = SS7Label::length(cpType); if (!llen) { Debug(toString(),DebugWarn,"Received %s MSU, point code type unknown [%p]", msu.getIndicatorName(),this); return false; } // check MSU length against SIO + label length if (msu.length() <= llen) { Debug(this,DebugMild,"Received on %d short MSU of length %u [%p]", sls,msu.length(),this); return false; } SS7Label label(cpType,msu); #ifdef DEBUG if (debugAt(DebugInfo)) { String tmp; tmp << label << " (" << label.opc().pack(cpType) << ":" << label.dpc().pack(cpType) << ":" << label.sls() << ")"; Debug(this,DebugAll,"Received MSU from link %d '%s' %p. Address: %s", sls,link->toString().c_str(),link,tmp.c_str()); } #endif bool maint = (msu.getSIF() == SS7MSU::MTN) || (msu.getSIF() == SS7MSU::MTNS); if (link && !maint) { int inhibited = link->inhibited() & (SS7Layer2::Unchecked|SS7Layer2::Inactive|SS7Layer2::Local); if (inhibited & SS7Layer2::Unchecked) return false; if (inhibited && msu.getSIF() != SS7MSU::SNM) { if (inhibited == SS7Layer2::Inactive) { Debug(this,DebugNote,"Activating inactive link %d '%s' on %s MSU receive", sls,link->toString().c_str(),msu.getServiceName()); link->inhibit(0,SS7Layer2::Inactive); } else { Debug(this,DebugMild,"Received MSU on inhibited 0x%02X link %d '%s'", link->inhibited(),sls,link->toString().c_str()); return false; } } } // first try to call the user part HandledMSU handled = SS7Layer3::receivedMSU(msu,label,sls); XDebug(this,DebugAll,"MSU handling result: %u [%p]",(unsigned int)handled,this); switch (handled) { case HandledMSU::Accepted: case HandledMSU::Failure: case HandledMSU::NoCircuit: return true; default: break; } // then try to minimally process MTN and SNM MSUs if (maintenance(msu,label,sls) || management(msu,label,sls)) return true; // either maintenance type cannot be processed more if (maint) return false; switch (handled) { case HandledMSU::NoAddress: while (SS7Router* router = YOBJECT(SS7Router,user())) { RefPointer mngmt = router->getManagement(); if (!mngmt) break; NamedList* ctl = mngmt->controlCreate("prohibit"); if (!ctl) break; unsigned int local = getLocal(cpType); if (!local) local = label.dpc().pack(cpType); String addr; addr << SS7PointCode::lookup(cpType) << ","; addr << SS7PointCode(cpType,local) << "," << label.opc(); String dest; dest << label.dpc(); ctl->addParam("address",addr); ctl->addParam("destination",dest); ctl->setParam("automatic",String::boolText(true)); return mngmt->controlExecute(ctl); } return prohibited(msu.getSSF(),label,sls); default: // if nothing worked, report the unavailable regular user part return (msu.getSIF() != SS7MSU::SNM) && unavailable(msu,label,sls,handled.upu()); } } bool SS7MTP3::recoveredMSU(const SS7MSU& msu, SS7Layer2* link, int sls) { int netType = msu.getNI(); SS7PointCode::Type cpType = type(netType); unsigned int llen = SS7Label::length(cpType); if (!llen) { Debug(toString(),DebugWarn,"Recovered MSU but point code type is unconfigured [%p]",this); return false; } // check MSU length against SIO + label length if (msu.length() <= llen) { Debug(this,DebugWarn,"Recovered short MSU of length %u [%p]", msu.length(),this); return false; } SS7Label label(cpType,msu); #ifdef DEBUG if (debugAt(DebugInfo)) { String tmp; tmp << label << " (" << label.opc().pack(cpType) << ":" << label.dpc().pack(cpType) << ":" << label.sls() << ")"; Debug(this,DebugAll,"Recovered MSU from link %d '%s' %p. Address: %s", sls,link->toString().c_str(),link,tmp.c_str()); } #endif // first try to send on another active link in the linkset if (transmitMSU(msu,label,sls % m_total) >= 0) return true; return SS7Layer3::recoveredMSU(msu,label,sls); } void SS7MTP3::notify(SS7Layer2* link) { Lock mylock(this); unsigned int chk = m_checked; unsigned int act = m_active; if (link) { if (link->operational()) { if (link->inhibited(SS7Layer2::Unchecked)) { // initiate a slightly delayed SLTM check u_int64_t t = Time::now() + 100000 + (Random::random() % 200000); if ((link->m_checkTime > t) || (t - 2000000 > link->m_checkTime)) link->m_checkTime = t; } } else { if (m_checklinks) link->inhibit(SS7Layer2::Unchecked,0); else link->inhibit(0,SS7Layer2::Unchecked); } } countLinks(); String text; text << "Linkset has " << m_active << " active, "; text << m_checked << " checked of " << m_total << " links"; #ifdef DEBUG String tmp; if (link) tmp << "Link '" << link->toString() << "' is " << (link->operational()?"":"not ") << "operational. "; Debug(this,DebugInfo,"%s%s [%p]",tmp.safe(),text.c_str(),this); #endif // if operational status of a link changed notify upper layer if (act != m_active || chk != m_checked) { Debug(this,DebugNote,"Linkset is%s operational [%p]", (operational() ? "" : " not"),this); // if we became inaccessible try to uninhibit or resume all other links const ObjList* l = 0; // if a link became inactive or unchecked start emergency procedures if (!m_active && (act || (m_checked < chk))) l = &m_links; unsigned int cnt = 0; for (; l && !(m_active || m_inhibit); l = l->next()) { L2Pointer* p = static_cast(l->get()); if (!p) continue; SS7Layer2* l2 = *p; if ((l2 == link) || !l2) continue; cnt++; if (l2->operational() && l2->inhibited(SS7Layer2::Local|SS7Layer2::Remote) && !l2->inhibited(SS7Layer2::Unchecked|SS7Layer2::Inactive)) { SS7Router* router = YOBJECT(SS7Router,user()); if (!router) { Debug(this,DebugMild,"No router, uninhibiting link %d '%s' [%p]", l2->sls(),l2->toString().c_str(),this); l2->inhibit(0,SS7Layer2::Local|SS7Layer2::Remote); continue; } if (l2->inhibited(SS7Layer2::Local)) router->uninhibit(this,l2->sls(),false); if (l2->inhibited(SS7Layer2::Remote)) router->uninhibit(this,l2->sls(),true); } else l2->control(SS7Layer2::Resume); } if (cnt) Debug(this,DebugNote,"Attempted to uninhibit/resume %u links [%p]",cnt,this); int sls = link ? link->sls() : -1; NamedList notif(""); notif.addParam("from", toString()); notif.addParam("type","ss7-mtp3"); notif.addParam("operational",String::boolText(operational())); notif.addParam("active",String(m_active)); notif.addParam("total",String(m_total)); notif.addParam("link", link ? link->toString() : ""); notif.addParam("linkup", link ? String::boolText(link->operational()) : ""); notif.addParam("text", text); mylock.drop(); SS7Layer3::notify(sls); engine()->notify(this,notif); } } void SS7MTP3::timerTick(const Time& when) { Lock mylock(this,SignallingEngine::maxLockWait()); if (!mylock.locked()) return; for (ObjList* o = m_links.skipNull(); o; o = o->skipNext()) { L2Pointer* p = static_cast(o->get()); if (!p) continue; SS7Layer2* l2 = *p; if (l2 && l2->m_checkTime && (l2->m_checkTime < when) && l2->operational()) { l2->m_checkTime = 0; int level = DebugAll; u_int64_t check = m_checkT2; if (l2->m_checkFail > 1) { bool takeOOS = !l2->inhibited(SS7Layer2::Unchecked); if (takeOOS || m_forcealign) { if (takeOOS) Debug(this,DebugWarn,"Taking link %d '%s' out of service [%p]", l2->sls(),l2->toString().c_str(),this); else if (m_forcealign) Debug(this,DebugNote,"Cycling not in service link %d '%s' [%p]", l2->sls(),l2->toString().c_str(),this); if (m_checkT1) check = m_checkT1; int cycle = 0; if (m_forcealign) { cycle = SS7Layer2::Inactive; l2->m_checkFail = 0; } l2->inhibit(SS7Layer2::Unchecked | cycle,cycle); } } else if (m_checkT1) { if (l2->m_checkFail++) level = DebugInfo; check = m_checkT1; } // if some action set a new timer bail out, we'll get back to it if (l2->m_checkTime || !l2->operational()) continue; l2->m_checkTime = check ? when + check : 0; for (unsigned int i = 0; i < YSS7_PCTYPE_COUNT; i++) { SS7PointCode::Type type = (SS7PointCode::Type)(i + 1); unsigned int local = getLocal(type); if (!local) continue; ObjList* o = getRoutes(type); if (!o) continue; unsigned char sio = getNI(type) | SS7MSU::MTN; for (o = o->skipNull(); o; o = o->skipNext()) { const SS7Route* r = static_cast(o->get()); if (r->priority()) continue; // build and send a SLTM to the adjacent node unsigned int len = 4; int sls = l2->sls(); SS7Label lbl(type,r->packed(),local,sls); SS7MSU sltm(sio,lbl,0,len+2); unsigned char* d = sltm.getData(lbl.length()+1,len+2); if (!d) continue; String addr; addr << SS7PointCode::lookup(type) << "," << lbl; if (debugAt(DebugAll)) addr << " (" << lbl.opc().pack(type) << ":" << lbl.dpc().pack(type) << ":" << sls << ")"; Debug(this,level,"Sending SLTM %s with %u bytes",addr.c_str(),len); *d++ = SS7MsgMTN::SLTM; *d++ = len << 4; unsigned char patt = sls; patt = (patt << 4) | (patt & 0x0f); while (len--) *d++ = patt++; if (l2->transmitMSU(sltm)) dump(sltm,true,sls); } } } } } void SS7MTP3::linkChecked(int sls, bool remote) { if (sls < 0) return; const ObjList* l = &m_links; for (; l; l = l->next()) { L2Pointer* p = static_cast(l->get()); if (!p) continue; SS7Layer2* l2 = *p; if (!l2 || (l2->sls() != sls)) continue; if (remote) { if (l2->inhibited(SS7Layer2::Unchecked)) { // trigger a slightly delayed SLTM check u_int64_t t = Time::now() + 100000; if ((l2->m_checkTime > t + m_checkT1) || (t - 4000000 > l2->m_checkTime)) l2->m_checkTime = t; } } else { l2->m_checkFail = 0; l2->m_checkTime = m_checkT2 ? Time::now() + m_checkT2 : 0; if (l2->inhibited(SS7Layer2::Unchecked)) { Debug(this,DebugNote,"Placing link %d '%s' in service [%p]", sls,l2->toString().c_str(),this); l2->inhibit(0,SS7Layer2::Unchecked); } } break; } } /* vi: set ts=8 sw=4 sts=4 noet: */