Files
openbts/GPRS/BSSG.cpp
2014-07-16 23:57:22 +02:00

375 lines
12 KiB
C++

/*
* Copyright 2011, 2014 Range Networks, Inc.
*
* 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.
*/
#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging
#include "Defines.h"
#include "GPRSInternal.h" // For GPRSLOG()
#include "GSMConfig.h"
#include "Threads.h"
#include "BSSGMessages.h"
#include "BSSG.h"
#include "Utils.h"
#include "errno.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#if INTERNAL_SGSN == 0
namespace BSSG {
BSSGMain gBSSG;
const unsigned rbufSize = 3000; // Much bigger than any PDU message.
#if _UNUSED_
static int BSTLVParse(ByteType *data, int &rp,
IEIType::type expected_ieitype, int expected_length)
{
int received_ieitype = data[rp++];
int received_length = data[rp++];
if (received_ieitype != expected_ieitype || received_length != expected_length) {
int bstype = data[NSMsg::UnitDataHeaderLen];
LOG(ERR) << "Error in BSSG msg type="<<bstype
<<LOGVAR(expected_ieitype) <<LOGVAR(expected_length)
<<LOGVAR(received_ieitype) <<LOGVAR(received_length);
return 0;
}
int result;
switch (received_length) {
case 1: result = data[rp++]; break;
case 2: result = getntohs(&data[rp]); rp+=2; break;
case 4: result = getntohl(&data[rp]); rp+=4; break;
default: assert(0);
}
return result;
}
#endif
static void BsRecvSignallingMsg(ByteType *data, int nsize)
{
int rp = NSMsg::UnitDataHeaderLen;
int bstype = data[rp++];
switch (bstype) {
// PDUs between NM SAPs:
// We will not use the flow control stuff, block, unblock, etc.
// However, we will generate the appropriate ACKs to keep the link happy.
case BSSG::BSPDUType::BVC_BLOCK:
BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_BLOCK_ACK,0));
break;
case BSSG::BSPDUType::BVC_BLOCK_ACK:
break;
case BSSG::BSPDUType::BVC_RESET: // network->BSS request reset everything.
// Dont know what we should do about reset.
BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_RESET_ACK,0));
break;
case BSSG::BSPDUType::BVC_RESET_ACK: // BSS->network and network->BSS?
break;
case BSSG::BSPDUType::BVC_UNBLOCK:
BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_UNBLOCK_ACK,0));
break;
case BSSG::BSPDUType::BVC_UNBLOCK_ACK:
break;
// We ignore all these:
case BSSG::BSPDUType::SUSPEND_ACK: // network->MS ACK
case BSSG::BSPDUType::SUSPEND_NACK: // network->MS NACK
case BSSG::BSPDUType::RESUME_ACK: // network->MS ACK
case BSSG::BSPDUType::RESUME_NACK: // network->MS NACK
case BSSG::BSPDUType::FLUSH_LL: // newtork->BSS forget this MS (it moved to another cell.)
case BSSG::BSPDUType::SGSN_INVOKE_TRACE: // network->BSS request trace an MS
LOG(WARNING) << "Unimplemented BSSG message:" << BSPDUType::name(bstype);
return;
case BSSG::BSPDUType::SUSPEND: // MS->network request to suspend GPRS service.
case BSSG::BSPDUType::RESUME: // MS->network request to resume GPRS service.
case BSSG::BSPDUType::FLUSH_LL_ACK: // BSS->network
case BSSG::BSPDUType::LLC_DISCARDED: // BSS->network notification of lost PDUs (probably expired)
LOG(ERR) << "Invalid BSSG message:" << BSPDUType::name(bstype);
return;
}
}
void NsRecvMsg(unsigned char *data, int nsize)
{
NSPDUType::type nstype = (NSPDUType::type) data[0];
// We dont need to see all the keep alive messages.
if (nstype != NSPDUType::NS_UNITDATA) { GPRSLOG(4) << "BSSG NsRecvMsg "<<nstype<<LOGVAR(nsize); }
switch (nstype) {
case NSPDUType::NS_UNITDATA: {
int bvci = getntohs(&data[2]);
if (bvci == BVCI::SIGNALLING) {
GPRSLOG(4) << "BSSG <=== signalling "<<nstype<<LOGVAR(nsize) <<timestr();
BsRecvSignallingMsg(data, nsize);
} else if (bvci == BVCI::PTM) {
GPRSLOG(4) << "BSSG <=== "<<nstype<<LOGVAR(nsize)<<" ignored" <<timestr();
// Not implemented
} else {
// Send data to the MAC
// We left the NS header intact.
BSSGDownlinkMsg *dlmsg = new BSSGDownlinkMsg(data,nsize);
//GPRSLOG(1) << "BSSG <=== queued "<<dlmsg->str() <<timestr();
GPRSLOG(1) << "BSSG <=== queued size="<<dlmsg->size() <<timestr();
gBSSG.mbsRxQ.write(dlmsg);
}
break;
}
case NSPDUType::NS_RESET:
gBSSG.mbsResetReceived = true;
BSSGWriteLowSide(NsFactory(NSPDUType::NS_RESET_ACK));
break;
case NSPDUType::NS_RESET_ACK:
gBSSG.mbsResetAckReceived = true;
break;
case NSPDUType::NS_BLOCK:
gBSSG.mbsBlocked = true;
BSSGWriteLowSide(NsFactory(NSPDUType::NS_BLOCK_ACK));
break;
case NSPDUType::NS_BLOCK_ACK:
// ignored.
break;
case NSPDUType::NS_UNBLOCK:
gBSSG.mbsBlocked = false;
BSSGWriteLowSide(NsFactory(NSPDUType::NS_UNBLOCK_ACK));
break;
case NSPDUType::NS_UNBLOCK_ACK:
gBSSG.mbsBlocked = false;
// ignored.
break;
case NSPDUType::NS_STATUS:
// This happens when the sgsn is stopped and restarted.
// It probably happens for other reasons too, but lets just
// assume that is what happened and reset the BSSG link.
gBSSG.BSSGReset();
break;
case NSPDUType::NS_ALIVE:
gBSSG.mbsAliveReceived = true;
BSSGWriteLowSide(NsFactory(NSPDUType::NS_ALIVE_ACK));
break;
case NSPDUType::NS_ALIVE_ACK:
gBSSG.mbsAliveAckReceived = true;
break;
default:
LOG(INFO) << "unrecognized NS message received, type "<<nstype;
break;
}
}
// Pull messages out of the BSSG socket and put them in the BSSG queue.
void *recvServiceLoop(void *arg)
{
BSSGMain *bssgp = (BSSGMain*)arg;
static unsigned char *buf = NULL;
if (buf == NULL) { buf = (unsigned char*) malloc(rbufSize); }
bssgp->mbsIsOpen = true;
int failures = 0;
while (bssgp->mbsIsOpen && ++failures < 10) {
ssize_t rsize = recv(bssgp->mbsSGSockfd,buf,rbufSize,0);
if (rsize > 0) {
failures = 0;
NsRecvMsg(buf,rsize);
} else if (rsize == -1) {
LOG(ERR) << "Received -1 from BSSG recv(), error:"<<strerror(errno);
LOG(ERR) << "Above error may mean SGSN is not responding";
}
}
// Oops. Close the BSSG and kill the other thread.
LOG(ERR) << "BSSGP aborting; too many failures in a row";
bssgp->mbsIsOpen = false;
// Send a message to the sendServiceLoop so that it will notice
// we have died and die also.
BSSGWriteLowSide(NsFactory(NSPDUType::NS_BLOCK));
return NULL;
}
// The send probably does not need to be in a separate thread.
// We could also have used a select or poll system call.
// But it was easier to use two threads.
// OLD: Send this loop an NS_BLOCK message to kill this thread off;
// and we dont normally use that NS message.
// There is a BSSG-level BVC_BLOCK message that we would use to do a temporary data block.
void *sendServiceLoop(void *arg)
{
BSSGMain *bssgp = (BSSGMain*)arg;
NSPDUType::type nstype = NSPDUType::NS_RESET; // init to anything.
do {
NSMsg *ulmsg = bssgp->mbsTxQ.read();
// It is already wrapped up in an NS protocol.
int msgsize = ulmsg->size();
ssize_t result = send(bssgp->mbsSGSockfd,ulmsg->begin(),msgsize,0);
nstype = ulmsg->getNSPDUType();
int debug_level = 1; //(nstype == NSPDUType::NS_UNITDATA) ? 1 : 4;
if (GPRS::GPRSDebug & debug_level) {
GPRSLOG(debug_level) << "BSSG ===> sendServiceLoop sent "
<<nstype<<LOGVAR(msgsize)<<ulmsg->str()<<timestr();
}
if (result != msgsize) {
LOG(ERR) << "BSSGP invalid send result" << LOGVAR(result) << LOGVAR(msgsize);
}
delete ulmsg;
} while (bssgp->mbsIsOpen /*&& nstype != NSPDUType::NS_BLOCK*/);
return NULL;
}
static int opensock(uint32_t sgsnIp, int sgsnPort /*,int bssgPort*/ )
{
//int fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
int sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) {
LOG(ERR) << "Could not create socket for BSSGP";
return -1;
}
/******
{ // We dont want to bind here. connect will pick a port for us.
int32_t bssgIp = INADDR_ANY;
struct sockaddr_in myAddr;
memset(&myAddr,0,sizeof(myAddr)); // be safe.
myAddr.sin_family = AF_INET;
myAddr.sin_addr.s_addr = htonl(bssgIp);
myAddr.sin_port = htons(bssgPort);
if (0 != bind(sockfd,(sockaddr*)&myAddr,sizeof(myAddr))) {
LOG(ERR) << "Could not bind NS socket to"
<< LOGVAR(bssgIp) << LOGVAR(bssgPort) << LOGVAR(errno);
close(sockfd);
return -1;
}
}
****/
struct sockaddr_in sgsnAddr;
memset(&sgsnAddr,0,sizeof(sgsnAddr));
sgsnAddr.sin_family = AF_INET;
sgsnAddr.sin_addr.s_addr = sgsnIp; // This is already in network order.
sgsnAddr.sin_port = htons(sgsnPort);
if (0 != connect(sockfd,(sockaddr*)&sgsnAddr,sizeof(sgsnAddr))) {
LOG(ERR) << "Could not connect NS socket to"
<< LOGVAR(sgsnIp) << LOGVAR(sgsnPort) << LOGVAR(errno);
close(sockfd);
return -1;
} else {
GPRSLOG(1) << "connected to SGSN at "<< inet_ntoa(sgsnAddr.sin_addr) <<" port "<<sgsnPort;
}
return sockfd;
}
// NOTES: The sgsn uses UDP.
// It does not accept connections using listen() and accept().
// Rather it waits for a packet containing an NS_RESET message,
// then it remembers the IP&port from the IP header, (via recvfrom)
// for future communication with the BTS.
// BEGINCONFIG
// 'GPRS.SGSN.Host','127.0.0.1',0,0, 'IP address of the SGSN required for GPRS service. The default value is the localhost, ie, SGSN runs on same machine as OpenBTS.'
// 'GPRS.SGSN.port',1920,0,0,'Port number of the SGSN required for GPRS service. This must match the port specified in the SGSN config file, currently osmo_sgsn.cfg'
// ENDCONFIG
bool BSSGMain::BSSGOpen()
{
if (mbsIsOpen) return true;
// TODO: Look up the proper default sgsn port. Maybe 3386. See pat.txt
// Originally I specified the BSSG port, but now I let the O.S. pick
// a free port via connect() call.
//int bssgPort = gConfig.getNum("GPRS.BSSG.port",1921);
// Default BVCI to the first available value.
// The user may want to specify this some day far away.
mbsBVCI = BVCI::PTP;
int sgsnPort = gConfig.getNum("GPRS.SGSN.port");
std::string sgsnHost = gConfig.getStr("GPRS.SGSN.Host"); // Default: localhost
uint32_t sgsnIp = inet_addr(sgsnHost.c_str());
if (sgsnIp == INADDR_NONE) {
LOG(ERR) << "Config GPRS.SGSN.Host value is not a valid IP address: " << sgsnHost << "\n";
}
//mbsSGSockfd = openudp(sgsnIp,sgsnPort,bssgPort);
if (mbsSGSockfd < 0) {
mbsSGSockfd = opensock(sgsnIp,sgsnPort);
}
if (mbsSGSockfd < 0) {
LOG(ERR) << "Could not init BSSGP due to socket failure";
return false;
}
mbsBlocked = true;
mbsRecvThread.start(recvServiceLoop,this);
mbsSendThread.start(sendServiceLoop,this);
return BSSGReset();
}
bool BSSGMain::BSSGReset()
{
// BSSG starts out blocked until it receives a reset.
mbsBlocked = true;
mbsResetReceived = false;
mbsResetAckReceived = false;
// Start communication with SGSN.
// Initiate NS Reset procedure: GSM 08.16 7.3
// We are supposed to send NS_STATUS, but it doesnt matter with our sgsn.
BSSGWriteLowSide(NsFactory(NSPDUType::NS_RESET));
BSSGWriteLowSide(NsFactory(NSPDUType::NS_UNBLOCK));
// Wait a bit for the sgsn to respond.
// Note: The first time we talk to the SGSN it sends us an NS_RESET,
// but if OpenBTS crashes, the second time it inits it doesnt send NS_RESET,
// which may be a bug in the SGSN, but in any event we dont want
// to wait for a RESET msg.
for (int i = 0; 1; i++) {
if (mbsResetAckReceived && !mbsBlocked) { break; }
Utils::sleepf(0.1);
if (i >= 40) { // wait 4 seconds
GPRSLOG(1) << LOGVAR(mbsResetReceived)
<<LOGVAR(mbsResetAckReceived) <<LOGVAR(mbsBlocked);
LOG(INFO) << "SGSN failed to respond\n";
return false;
}
}
// GSM 08.18 8.4: Reset the BVC. You must do this after verifying NS layer is working.
// Must reset each BVCI separately.
// I'm not going to bother to check for acks - if the NS protocol inited,
// the sgsn is fine and this will init ok too.
BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_RESET,BVCI::SIGNALLING));
BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_RESET,gBSSG.mbsBVCI));
return true;
}
BSSGDownlinkMsg *BSSGMain::BSSGReadLowSide()
{
return mbsRxQ.readNoBlock();
}
void BSSGWriteLowSide(NSMsg *ulmsg)
{
if (gBSSG.mbsTestQ) {
// For testing, deliver messages to this queue instead:
gBSSG.mbsTestQ->write(ulmsg);
} else {
GPRSLOG(1) << "BSSG ===> writelowside " <<ulmsg->str()<<timestr();
gBSSG.mbsTxQ.write(ulmsg); // normal mode; block is headed for the SGSN.
}
}
};
#endif