laforge
/
openbts-osmo
Archived
1
0
Fork 0

A big patch implementing remote CLI.

This patch adds ability to connect to OpenBTS CLI with TCP socket and UNIX socket. This is configurable in the config file. To connect to the CLI use apps/OpenBTScli executable, which reads configuration from the same config file.
This commit is contained in:
Alexander Chemeris 2010-11-16 21:15:32 +03:00 committed by Thomas Tsou
parent 1418461410
commit 5ababfad65
26 changed files with 2763 additions and 906 deletions

View File

@ -22,781 +22,88 @@
*/
#include <iostream>
#include <iomanip>
#include <fstream>
#include <iterator>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <config.h>
#include "CLI.h"
#include <Logger.h>
#include <Globals.h>
#include <GSMConfig.h>
#include <GSMLogicalChannel.h>
#include <ControlCommon.h>
#include <TRXManager.h>
#include <PowerManager.h>
#ifdef HAVE_LIBREADLINE // [
//# include <stdio.h>
# include <readline/readline.h>
# include <readline/history.h>
#endif // HAVE_LIBREADLINE ]
using namespace std;
using namespace CommandLine;
#define SUCCESS 0
#define BAD_NUM_ARGS 1
#define BAD_VALUE 2
#define NOT_FOUND 3
#define TOO_MANY_ARGS 4
#define FAILURE 5
extern TransceiverManager gTRX;
/** Standard responses in the CLI, much mach erorrCode enum. */
static const char* standardResponses[] = {
"success", // 0
"wrong number of arguments", // 1
"bad argument(s)", // 2
"command not found", // 3
"too many arguments for parser", // 4
"command failed", // 5
};
int Parser::execute(char* line, ostream& os, istream& is) const
void CommandLine::runCLI(ParserBase *processor)
{
// escape to the shell?
if (line[0]=='!') {
os << endl;
int retVal = system(line+1);
os << endl << "External call returned " << retVal << endl;
return SUCCESS;
}
// tokenize
char *argv[mMaxArgs];
int argc = 0;
char **ap;
// This is (almost) straight from the man page for strsep.
for (ap=argv; (*ap=strsep(&line," ")) != NULL; ) {
if (**ap != '\0') {
if (++ap >= &argv[mMaxArgs]) break;
else argc++;
}
}
// Blank line?
if (!argc) return SUCCESS;
// Find the command.
ParseTable::const_iterator cfp = mParseTable.find(argv[0]);
if (cfp == mParseTable.end()) {
return NOT_FOUND;
}
int (*func)(int,char**,ostream&,istream&);
func = cfp->second;
// Do it.
int retVal = (*func)(argc,argv,os,is);
// Give hint on bad # args.
if (retVal==BAD_NUM_ARGS) os << help(argv[0]) << endl;
return retVal;
}
#ifdef HAVE_LIBREADLINE // [
// start console
using_history();
static const char * const history_file_name = "/.openbts_history";
char *history_name = 0;
char *home_dir = getenv("HOME");
int Parser::process(const char* line, ostream& os, istream& is) const
{
char *newLine = strdup(line);
int retVal = execute(newLine,os,is);
free(newLine);
if (retVal>0) os << standardResponses[retVal] << endl;
return retVal;
}
const char * Parser::help(const string& cmd) const
{
HelpTable::const_iterator hp = mHelpTable.find(cmd);
if (hp==mHelpTable.end()) return "no help available";
return hp->second.c_str();
}
/**@name Commands for the CLI. */
//@{
// forward refs
int printStats(int argc, char** argv, ostream& os, istream& is);
/*
A CLI command takes the argument in an array.
It returns 0 on success.
*/
/** Set the logging file. */
int setlogfile(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=2) return BAD_NUM_ARGS;
if (gSetLogFile(argv[1])) {
gConfig.set("LogFile",argv[1]);
return SUCCESS;
}
os << "cannot open " << argv[1] << " for logging" << endl;
return FAILURE;
}
/** Display system uptime and current GSM frame number. */
int uptime(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=1) return BAD_NUM_ARGS;
os.precision(2);
os << "Unix time " << time(NULL) << endl;
int seconds = gBTS.uptime();
if (seconds<120) {
os << "uptime " << seconds << " seconds, frame " << gBTS.time() << endl;
return SUCCESS;
}
float minutes = seconds / 60.0F;
if (minutes<120) {
os << "uptime " << minutes << " minutes, frame " << gBTS.time() << endl;
return SUCCESS;
}
float hours = minutes / 60.0F;
if (hours<48) {
os << "uptime " << hours << " hours, frame " << gBTS.time() << endl;
return SUCCESS;
}
float days = hours / 24.0F;
os << "uptime " << days << " days, frame " << gBTS.time() << endl;
return SUCCESS;
}
/** Give a list of available commands or describe a specific command. */
int showHelp(int argc, char** argv, ostream& os, istream& is)
{
if (argc==2) {
os << argv[1] << " " << gParser.help(argv[1]) << endl;
return SUCCESS;
}
if (argc!=1) return BAD_NUM_ARGS;
ParseTable::const_iterator cp = gParser.begin();
os << endl << "Type \"help\" followed by the command name for help on that command." << endl << endl;
int c=0;
const int cols = 3;
while (cp != gParser.end()) {
const string& wd = cp->first;
os << wd << '\t';
if (wd.size()<8) os << '\t';
++cp;
c++;
if (c%cols==0) os << endl;
}
if (c%cols!=0) os << endl;
os << endl << "Lines starting with '!' are escaped to the shell." << endl;
os << endl << "Use <cntrl-A>, <D> to detach from \"screen\", *not* <cntrl-C>." << endl << endl;
return SUCCESS;
}
/** A function to return -1, the exit code for the caller. */
int exit_function(int argc, char** argv, ostream& os, istream& is)
{
unsigned wait =0;
if (argc>2) return BAD_NUM_ARGS;
if (argc==2) wait = atoi(argv[1]);
if (wait!=0)
os << "waiting up to " << wait << " seconds for clearing of "
<< gBTS.TCHActive() << " active calls" << endl;
// Block creation of new channels.
gBTS.hold(true);
// Wait up to the timeout for active channels to release.
time_t finish = time(NULL) + wait;
while (time(NULL)<finish) {
unsigned load = gBTS.SDCCHActive() + gBTS.TCHActive();
if (load==0) break;
sleep(1);
}
bool loads = false;
if (gBTS.SDCCHActive()>0) {
LOG(WARN) << "dropping " << gBTS.SDCCHActive() << " control transactions on exit";
loads = true;
}
if (gBTS.TCHActive()>0) {
LOG(WARN) << "dropping " << gBTS.TCHActive() << " calls on exit";
loads = true;
}
if (loads) {
os << endl << "exiting with loads:" << endl;
printStats(1,NULL,os,is);
}
if (gConfig.defines("Control.TMSITable.SavePath")) {
gTMSITable.save(gConfig.getStr("Control.TMSITable.SavePath"));
}
os << endl << "exiting..." << endl;
return -1;
}
// Forward ref.
int tmsis(int argc, char** argv, ostream& os, istream& is);
/** Dump TMSI table to a text file. */
int dumpTMSIs(const char* filename, istream& is)
{
ofstream fileout;
fileout.open(filename, ios::out); // erases existing!
// FIXME -- Check that the file really opened.
// Fake an argument list to call printTMSIs.
char* subargv[] = {"tmsis", NULL};
int subargc = 1;
return tmsis(subargc, subargv, fileout, is);
}
/** Print or clear the TMSI table. */
int tmsis(int argc, char** argv, ostream& os, istream& is)
{
if (argc>=2) {
// Clear?
if (strcmp(argv[1],"clear")==0) {
if (argc!=2) return BAD_NUM_ARGS;
os << "clearing TMSI table" << endl;
gTMSITable.clear();
return SUCCESS;
}
// Dump?
if (strcmp(argv[1],"dump")==0) {
if (argc!=3) return BAD_NUM_ARGS;
os << "dumping TMSI table to " << argv[2] << endl;
return dumpTMSIs(argv[2],is);
}
return BAD_VALUE;
}
if (argc!=1) return BAD_NUM_ARGS;
os << "TMSI IMSI IMEI age used" << endl;
gTMSITable.dump(os);
os << endl << gTMSITable.size() << " TMSIs in table" << endl;
return SUCCESS;
}
/** Print the transactions table. */
int trans(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=1) return BAD_NUM_ARGS;
// os << "TMSI IMSI IMEI age used" << endl;
gTransactionTable.dump(os);
os << endl << gTransactionTable.size() << " transactions in table" << endl;
return SUCCESS;
}
int findimsi(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=2) {
os << "usage: findimsi <imsiprefix>\n";
return BAD_VALUE;
}
// FIXME -- THis should be moved into the TMSITable object to make it thread-safe.
Control::TMSIMap::const_iterator tp = gTMSITable.begin();
char buf[50]; // max size in decimal digits plus 1 plus RandomPositive
while (tp != gTMSITable.end()) {
std::string target;
sprintf(buf, "%d", tp->first);
std::string imsi = buf;
target.assign(imsi, 0, strlen(argv[1]));
if (target == argv[1])
os << tp->second << " 0x" << std::hex << tp->first << std::dec << endl;
++tp;
}
return SUCCESS;
}
/** Submit an SMS for delivery to an IMSI. */
int sendsms(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=3) return BAD_NUM_ARGS;
os << "enter text to send: ";
char txtBuf[161];
cin.getline(txtBuf,160,'\n');
char *IMSI = argv[1];
char *srcAddr = argv[2];
Control::TransactionEntry transaction(
GSM::L3MobileIdentity(IMSI),
GSM::L3CMServiceType::MobileTerminatedShortMessage,
GSM::L3CallingPartyBCDNumber(srcAddr),
txtBuf);
transaction.Q931State(Control::TransactionEntry::Paging);
Control::initiateMTTransaction(transaction,GSM::SDCCHType,30000);
os << "message submitted for delivery" << endl;
return SUCCESS;
}
/** DEBUGGING: Sends a special sms that triggers a RRLP message to an IMSI. */
int sendrrlp(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=3) return BAD_NUM_ARGS;
char *IMSI = argv[1];
UDPSocket sock(0,"127.0.0.1",gConfig.getNum("SIP.Port"));
unsigned port = sock.port();
unsigned callID = random();
// Just fake out a SIP message.
const char form[] = "MESSAGE sip:IMSI%s@localhost SIP/2.0\nVia: SIP/2.0/TCP localhost;branch=z9hG4bK776sgdkse\nMax-Forwards: 2\nFrom: RRLP@localhost:%d;tag=49583\nTo: sip:IMSI%s@localhost\nCall-ID: %d@127.0.0.1:5063\nCSeq: 1 MESSAGE\nContent-Type: text/plain\nContent-Length: %lu\n\n%s\n";
char txtBuf[161];
sprintf(txtBuf,"RRLP%s",argv[2]);
char outbuf[2048];
sprintf(outbuf,form,IMSI,port,IMSI,callID,strlen(txtBuf),txtBuf);
sock.write(outbuf);
sleep(2);
sock.write(outbuf);
sock.close();
os << "RRLP Triggering message submitted for delivery" << endl;
return SUCCESS;
}
/** Print current usage loads. */
int printStats(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=1) return BAD_NUM_ARGS;
os << "SDCCH load: " << gBTS.SDCCHActive() << '/' << gBTS.SDCCHTotal() << endl;
os << "TCH/F load: " << gBTS.TCHActive() << '/' << gBTS.TCHTotal() << endl;
os << "AGCH/PCH load: " << gBTS.AGCHLoad() << ',' << gBTS.PCHLoad() << endl;
// paging table size
os << "Paging table size: " << gBTS.pager().pagingEntryListSize() << endl;
os << "Transactions/TMSIs: " << gTransactionTable.size() << ',' << gTMSITable.size() << endl;
// 3122 timer current value (the number of seconds an MS should hold off the next RACH)
os << "T3122: " << gBTS.T3122() << " ms" << endl;
return SUCCESS;
}
/** Get/Set MCC, MNC, LAC, CI. */
int cellID(int argc, char** argv, ostream& os, istream& is)
{
if (argc==1) {
os << "MCC=" << gConfig.getStr("GSM.MCC")
<< " MNC=" << gConfig.getStr("GSM.MNC")
<< " LAC=" << gConfig.getNum("GSM.LAC")
<< " CI=" << gConfig.getNum("GSM.CI")
<< endl;
return SUCCESS;
}
if (argc!=5) return BAD_NUM_ARGS;
// Safety check the args!!
char* MCC = argv[1];
char* MNC = argv[2];
if (strlen(MCC)!=3) {
os << "MCC must be three digits" << endl;
return BAD_VALUE;
}
int MNCLen = strlen(MNC);
if ((MNCLen<2)||(MNCLen>3)) {
os << "MNC must be two or three digits" << endl;
return BAD_VALUE;
}
gTMSITable.clear();
gConfig.set("GSM.MCC",MCC);
gConfig.set("GSM.MNC",MNC);
gConfig.set("GSM.LAC",argv[3]);
gConfig.set("GSM.CI",argv[4]);
gBTS.regenerateBeacon();
return SUCCESS;
}
/** Print table of current transactions. */
int calls(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=1) return BAD_NUM_ARGS;
Control::TransactionMap::const_iterator trans = gTransactionTable.begin();
int count = 0;
gTransactionTable.clearDeadEntries();
while (trans != gTransactionTable.end()) {
os << trans->second << endl;
++trans;
count++;
}
os << endl << count << " transactions in table" << endl;
return SUCCESS;
}
/** Print or modify the global configuration table. */
int config(int argc, char** argv, ostream& os, istream& is)
{
// no args, just print
if (argc==1) {
gConfig.dump(os);
return SUCCESS;
}
// one arg, pattern match and print
bool anything = false;
if (argc==2) {
StringMap::const_iterator p = gConfig.begin();
while (p != gConfig.end()) {
if (strstr(p->first.c_str(),argv[1])) {
os << p->first << ": " << p->second << endl;
anything = true;
if(home_dir) {
size_t home_dir_len = strlen(home_dir);
size_t history_file_len = strlen(history_file_name);
size_t history_len = home_dir_len + history_file_len + 1;
if(history_len > home_dir_len) {
if(!(history_name = (char *)malloc(history_len))) {
LOG(ERROR) << "malloc failed: " << strerror(errno);
exit(-1);
}
++p;
memcpy(history_name, home_dir, home_dir_len);
memcpy(history_name + home_dir_len, history_file_name,
history_file_len + 1);
read_history(history_name);
}
if (!anything)
os << "nothing matching \"" << argv[1] << "\"" << endl;
return SUCCESS;
}
#endif // HAVE_LIBREADLINE ]
COUT("\n\nWelcome to OpenBTS. Type \"help\" to see available commands.");
// FIXME: We want to catch control-d (emacs keybinding for exit())
// The logging parts were removed from this loop.
// If we want them back, they will need to go into their own thread.
while (1) {
#ifdef HAVE_LIBREADLINE // [
char *inbuf = readline(gConfig.getStr("CLI.Prompt"));
if (!inbuf) break;
if (*inbuf) {
add_history(inbuf);
// The parser returns -1 on exit.
if (processor->process(inbuf, cout)<0) {
free(inbuf);
break;
}
}
free(inbuf);
#else // HAVE_LIBREADLINE ][
cout << endl << gConfig.getStr("CLI.Prompt");
cout.flush();
std::string inbuf;
getline(cin, inbuf, '\n');
// The parser returns -1 on exit.
if (processor->process(inbuf,cout)<0) break;
#endif // !HAVE_LIBREADLINE ]
}
// >1 args: set new value
string val;
for (int i=2; i<argc; i++) {
val.append(argv[i]);
if (i!=(argc-1)) val.append(" ");
#ifdef HAVE_LIBREADLINE // [
if(history_name) {
int e = write_history(history_name);
if(e) {
fprintf(stderr, "error: history: %s\n", strerror(e));
}
free(history_name);
history_name = 0;
}
bool existing = gConfig.defines(argv[1]);
if (!gConfig.set(argv[1],val)) {
os << argv[1] << " is static and connot be altered after initialization" << endl;
return BAD_VALUE;
}
if (!existing) {
os << "defined new config " << argv[1] << " as \"" << val << "\"" << endl;
// Anything created by the CLI is optional.
gConfig.makeOptional(argv[1]);
} else {
os << "changed " << argv[1] << " to \"" << val << "\"" << endl;
}
gBTS.regenerateBeacon();
return SUCCESS;
#endif // HAVE_LIBREADLINE ]
}
/** Remove a configiuration value. */
int unconfig(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=2) return BAD_NUM_ARGS;
if (gConfig.unset(argv[1])) {
os << "\"" << argv[1] << "\" removed from the configuration table" << endl;
gBTS.regenerateBeacon();
return SUCCESS;
}
if (gConfig.defines(argv[1])) {
os << "\"" << argv[1] << "\" could not be removed" << endl;
} else {
os << "\"" << argv[1] << "\" was not in the table" << endl;
}
return BAD_VALUE;
}
/** Dump current configuration to a file. */
int configsave(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=2) return BAD_NUM_ARGS;
fstream f;
f.open(argv[1],fstream::out);
if (f.fail()) {
os << "cannot open " << argv[1] << " for writing" << endl;
return FAILURE;
}
f << "# OpenBTS configuration file" << endl;
time_t now = time(NULL);
f << "# " << ctime(&now) << endl;
gConfig.write(f);
f.close();
return SUCCESS;
}
/** Change the registration timers. */
int regperiod(int argc, char** argv, ostream& os, istream& is)
{
if (argc==1) {
os << "T3212 is " << gConfig.getNum("GSM.T3212") << " minutes" << endl;
os << "SIP registration period is " << gConfig.getNum("SIP.RegistrationPeriod")/60 << " minutes" << endl;
return SUCCESS;
}
if (argc>3) return BAD_NUM_ARGS;
unsigned newT3212 = strtol(argv[1],NULL,10);
if ((newT3212<6)||(newT3212>1530)) {
os << "valid T3212 range is 6..1530 minutes" << endl;
return BAD_VALUE;
}
// By defuault, make SIP registration period 1.5x the GSM registration period.
unsigned SIPRegPeriod = newT3212*90;
if (argc==3) {
SIPRegPeriod = 60*strtol(argv[2],NULL,10);
}
// Set the values in the table and on the GSM beacon.
gConfig.set("SIP.RegistrationPeriod",SIPRegPeriod);
gConfig.set("GSM.T3212",newT3212);
gBTS.regenerateBeacon();
// Done.
return SUCCESS;
}
/** Print the list of alarms kept by the logger, i.e. the last LOG(ALARM) << <text> */
int alarms(int argc, char** argv, ostream& os, istream& is)
{
std::ostream_iterator<std::string> output( os, "\n" );
std::list<std::string> alarms = gGetLoggerAlarms();
std::copy( alarms.begin(), alarms.end(), output );
return SUCCESS;
}
/** Version string. */
int version(int argc, char **argv, ostream& os, istream& is)
{
if (argc!=1) return BAD_NUM_ARGS;
os << "release " VERSION " built " __DATE__ << endl;
return SUCCESS;
}
/** Show start-up notices. */
int notices(int argc, char **argv, ostream& os, istream& is)
{
if (argc!=1) return BAD_NUM_ARGS;
os << endl << gOpenBTSWelcome << endl;
return SUCCESS;
}
int page(int argc, char **argv, ostream& os, istream& is)
{
if (argc==1) {
gBTS.pager().dump(os);
return SUCCESS;
}
if (argc!=3) return BAD_NUM_ARGS;
char *IMSI = argv[1];
if (strlen(IMSI)>15) {
os << IMSI << " is not a valid IMSI" << endl;
return BAD_VALUE;
}
Control::TransactionEntry dummy;
gBTS.pager().addID(GSM::L3MobileIdentity(IMSI),GSM::SDCCHType,dummy,1000*atoi(argv[2]));
return SUCCESS;
}
int testcall(int argc, char **argv, ostream& os, istream& is)
{
if (argc!=3) return BAD_NUM_ARGS;
char *IMSI = argv[1];
if (strlen(IMSI)!=15) {
os << IMSI << " is not a valid IMSI" << endl;
return BAD_VALUE;
}
Control::TransactionEntry transaction(
GSM::L3MobileIdentity(IMSI),
GSM::L3CMServiceType::TestCall,
GSM::L3CallingPartyBCDNumber("0"));
transaction.Q931State(Control::TransactionEntry::Paging);
Control::initiateMTTransaction(transaction,GSM::TCHFType,1000*atoi(argv[2]));
return SUCCESS;
}
int endcall(int argc, char **argv, ostream& os, istream& is)
{
// FIXME -- This doesn't really work.
if (argc!=2) return BAD_NUM_ARGS;
unsigned transID = atoi(argv[1]);
Control::TransactionEntry target;
if (!gTransactionTable.find(transID,target)) {
os << transID << " not found in table";
return BAD_VALUE;
}
target.Q931State(Control::TransactionEntry::ReleaseRequest);
gTransactionTable.update(target);
return SUCCESS;
}
void printChanInfo(const GSM::LogicalChannel* chan, ostream& os)
{
os << setw(2) << chan->TN();
os << " " << setw(9) << chan->typeAndOffset();
char buffer[1024];
sprintf(buffer,"%10d %5.2f %4d %5d %4d",
chan->transactionID(),
100.0*chan->FER(), (int)round(chan->RSSI()),
chan->actualMSPower(), chan->actualMSTiming());
os << " " << buffer;
const GSM::L3MeasurementResults& meas = chan->SACCH()->measurementResults();
if (!meas.MEAS_VALID()) {
sprintf(buffer,"%5d %5.2f",
meas.RXLEV_FULL_SERVING_CELL_dBm(),
100.0*meas.RXQUAL_FULL_SERVING_CELL_BER());
os << " " << buffer;
} else {
os << " ----- ------";
}
os << endl;
}
int chans(int argc, char **argv, ostream& os, istream& is)
{
if (argc!=1) return BAD_NUM_ARGS;
os << "TN chan transaction UPFER RSSI TXPWR TXTA DNLEV DNBER" << endl;
os << "TN type id pct dB dBm sym dBm pct" << endl;
// SDCCHs
GSM::SDCCHList::const_iterator sChanItr = gBTS.SDCCHPool().begin();
while (sChanItr != gBTS.SDCCHPool().end()) {
const GSM::SDCCHLogicalChannel* sChan = *sChanItr;
if (sChan->active()) printChanInfo(sChan,os);
++sChanItr;
}
// TCHs
GSM::TCHList::const_iterator tChanItr = gBTS.TCHPool().begin();
while (tChanItr != gBTS.TCHPool().end()) {
const GSM::TCHFACCHLogicalChannel* tChan = *tChanItr;
if (tChan->active()) printChanInfo(tChan,os);
++tChanItr;
}
os << endl;
return SUCCESS;
}
int power(int argc, char **argv, ostream& os, istream& is)
{
os << "current downlink power " << gBTS.powerManager().power() << " dB wrt full scale" << endl;
os << "current attenuation bounds "
<< gConfig.getNum("GSM.PowerManager.MinAttenDB")
<< " to "
<< gConfig.getNum("GSM.PowerManager.MaxAttenDB")
<< " dB" << endl;
if (argc==1) return SUCCESS;
if (argc!=3) return BAD_NUM_ARGS;
int min = atoi(argv[1]);
int max = atoi(argv[2]);
if (min>max) return BAD_VALUE;
gConfig.set("GSM.PowerManager.MinAttenDB",argv[1]);
gConfig.set("GSM.PowerManager.MaxAttenDB",argv[2]);
os << "new attenuation bounds "
<< gConfig.getNum("GSM.PowerManager.MinAttenDB")
<< " to "
<< gConfig.getNum("GSM.PowerManager.MaxAttenDB")
<< " dB" << endl;
return SUCCESS;
}
int rxgain(int argc, char** argv, ostream& os, istream& is)
{
os << "current RX gain is " << gConfig.getNum("GSM.RxGain") << " dB" << endl;
if (argc==1) return SUCCESS;
if (argc!=2) return BAD_NUM_ARGS;
int newGain = gTRX.ARFCN(0)->setRxGain(atoi(argv[1]));
os << "new RX gain is " << newGain << " dB" << endl;
gConfig.set("GSM.RxGain",newGain);
return SUCCESS;
}
int noise(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=1) return BAD_NUM_ARGS;
int noise = gTRX.ARFCN(0)->getNoiseLevel();
os << "noise RSSI is -" << noise << " dB wrt full scale" << endl;
os << "MS RSSI target is " << gConfig.getNum("GSM.RSSITarget") << " dB wrt full scale" << endl;
return SUCCESS;
}
//@} // CLI commands
Parser::Parser()
{
// The constructor adds the commands.
addCommand("setlogfile", setlogfile, "<path> -- set the logging file to <path>.");
addCommand("uptime", uptime, "-- show BTS uptime and BTS frame number.");
addCommand("help", showHelp, "[command] -- list available commands or gets help on a specific command.");
addCommand("exit", exit_function, "[wait] -- exit the application, either immediately, or waiting for existing calls to clear with a timeout in seconds");
addCommand("tmsis", tmsis, "[\"clear\"] or [\"dump\" filename] -- print/clear the TMSI table or dump it to a file.");
addCommand("trans", trans, "-- print the transactions table.");
addCommand("findimsi", findimsi, "[IMSIPrefix] -- prints all imsi's that are prefixed by IMSIPrefix");
addCommand("sendsms", sendsms, "<IMSI> <src> -- send SMS to <IMSI>, addressed from <src>, after prompting.");
addCommand("sendrrlp", sendrrlp, "<IMSI> <hexstring> -- send RRLP message <hexstring> to <IMSI>.");
addCommand("load", printStats, "-- print the current activity loads.");
addCommand("cellid", cellID, "[MCC MNC LAC CI] -- get/set location area identity (MCC, MNC, LAC) and cell ID (CI)");
addCommand("calls", calls, "-- print the transaction table");
addCommand("config", config, "[] OR [patt] OR [key val(s)] -- print the current configuration, print configuration values matching a pattern, or set/change a configuration value");
addCommand("configsave", configsave, "<path> -- write the current configuration to a file");
addCommand("regperiod", regperiod, "[GSM] [SIP] -- get/set the registration period (GSM T3212), in MINUTES");
addCommand("alarms", alarms, "-- show latest alarms");
addCommand("version", version,"-- print the version string");
addCommand("page", page, "[IMSI time] -- dump the paging table or page the given IMSI for the given period");
addCommand("testcall", testcall, "IMSI time -- initiate a test call to a given IMSI with a given paging time");
addCommand("chans", chans, "-- report PHY status for active channels");
addCommand("power", power, "[minAtten maxAtten] -- report current attentuation or set min/max bounds");
addCommand("rxgain", rxgain, "[newRxgain] -- get/set the RX gain in dB");
addCommand("noise", noise, "-- report receive noise level in RSSI dB");
addCommand("unconfig", unconfig, "key -- remove a config value");
addCommand("notices", notices, "-- show startup copyright and legal notices");
// HACK -- Comment out these until they are fixed.
// addCommand("endcall", endcall,"trans# -- terminate the given transaction");
}
// vim: ts=4 sw=4

View File

@ -1,5 +1,5 @@
/*
* Copyright 2009 Free Software Foundation, Inc.
* Copyright 2009, 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
@ -29,53 +29,12 @@
#include <string>
#include <map>
#include <iostream>
#include <CLIParserBase.h>
namespace CommandLine {
/** A table for matching strings to actions. */
typedef std::map<std::string,int (*)(int,char**,std::ostream&,std::istream&)> ParseTable;
/** The help table. */
typedef std::map<std::string,std::string> HelpTable;
class Parser {
private:
ParseTable mParseTable;
HelpTable mHelpTable;
static const int mMaxArgs = 10;
public:
Parser();
/**
Process a command line.
@return 0 on sucess, -1 on exit request, error codes otherwise
*/
int process(const char* line, std::ostream& os, std::istream& is) const;
/** Add a command to the parsing table. */
void addCommand(const char* name, int (*func)(int,char**,std::ostream&,std::istream&), const char* helpString)
{ mParseTable[name] = func; mHelpTable[name]=helpString; }
ParseTable::const_iterator begin() const { return mParseTable.begin(); }
ParseTable::const_iterator end() const { return mParseTable.end(); }
/** Return a help string. */
const char *help(const std::string& cmd) const;
private:
/** Parse and execute a command string. */
int execute(char* line, std::ostream& os, std::istream& is) const;
};
void runCLI(ParserBase *processor);
} // CLI

View File

@ -0,0 +1,72 @@
/*
* Copyright 2009, 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string>
#include <config.h>
#include "CLIClient.h"
#include <Globals.h>
#include <Logger.h>
#include <CLI.h>
using namespace std;
using namespace CommandLine;
int CLIClientProcessor::process(const std::string &line, std::ostream& os)
{
// Step 1 -- Send command
if (!mConnection.sendBlock(line))
return -1;
// Step 2 -- Receive response
std::string recvBlock;
if (!mConnection.receiveBlock(recvBlock))
return -1;
// Step 3 -- Print out response
os << recvBlock;
os << endl;
return 0;
}
void CommandLine::runCLIClient(ConnectionSocket *sock)
{
// Connect to a server
int res = sock->connect();
if (res < 0) {
int errsv = errno;
LOG(WARN) << "sock.connect() failed with errno="
<< errsv << " (0x" << hex << errsv << dec << "): "
<< strerror(errsv);
sock->close();
return;
}
// Start the main loop
CLIClientProcessor proc(sock);
runCLI(&proc);
}
// vim: ts=4 sw=4

View File

@ -0,0 +1,65 @@
/*
* Copyright 2009, 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPENBTSCLICLIENT_H
#define OPENBTSCLICLIENT_H
#include <string>
#include <iostream>
#include <CLIConnection.h>
#include <CLIParserBase.h>
namespace CommandLine {
class CLIClientProcessor : public ParserBase {
public:
CLIClientProcessor(ConnectionSocket *pSock)
: mConnection(pSock)
{};
~CLIClientProcessor() {};
/**
Process a command line.
@return 0 on success, -1 on exit request, error codes otherwise
*/
virtual int process(const std::string &line, std::ostream& os);
protected:
CLIConnection mConnection; ///< Network connection to a server
};
// Run CLI client
void runCLIClient(ConnectionSocket *sock);
} // CLI
#endif
// vim: ts=4 sw=4

View File

@ -0,0 +1,101 @@
/*
* Copyright 2009, 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include "CLIConnection.h"
#include <Logger.h>
//#include <Globals.h>
using namespace std;
using namespace CommandLine;
bool CLIConnection::sendBlock(const std::string &block)
{
// Step 1 -- Prepare block
std::string::size_type blockLen = block.length();
uint32_t sendBlockLen = htonl(blockLen);
int bytesSent;
LOG(DEEPDEBUG) << "Sending: (" << blockLen << "/0x" << hex << blockLen << dec
<< ") \"" << block << "\"";
// Step 2 -- Send block length
bytesSent = mSocket->write((const char *)&sendBlockLen, 4);
if (bytesSent < 4) {
onError(errno, "mSocket.write()");
return false;
}
// Step 3 -- Send block data
bytesSent = mSocket->write(block.data(), blockLen);
if (bytesSent < blockLen) {
onError(errno, "mSocket.write()");
return false;
}
return true;
}
bool CLIConnection::receiveBlock(std::string &block)
{
uint32_t recvDataLen;
int bytesReceived;
// Step 1 -- Read length of the block
bytesReceived = mSocket->read((char*)&recvDataLen, 4);
if (bytesReceived < 4) {
onError(errno, "mSocket.read()");
return false;
}
recvDataLen = ntohl(recvDataLen);
// Step 2 -- prepare buffer
block.resize(recvDataLen);
// Step 3 -- Receive actual data
bytesReceived = mSocket->read((char*)block.data(), recvDataLen);
if (bytesReceived < recvDataLen) {
onError(errno, "mSocket.read()");
return false;
}
// readBuf[bytesReceived>=sizeof(readBuf)?sizeof(readBuf)-1:bytesReceived]= '\0';
LOG(DEEPDEBUG) << "Received: (" << recvDataLen << "/0x" << hex << recvDataLen << dec
<< ") \"" << block << "\"";
return true;
}
void CLIConnection::onError(int errnum, const char *oper)
{
if (errnum == 0) {
LOG(INFO) << "Connection has been closed by remote party for socket " << *mSocket;
} else {
LOG(WARN) << "Socket=" << *mSocket << " operation " << oper
<< " failed with errno=" << errnum << " (0x"
<< hex << errnum << dec << "): "
<< strerror(errnum);
}
mSocket->close();
}
// vim: ts=4 sw=4

View File

@ -0,0 +1,74 @@
/*
* Copyright 2009, 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPENBTSCLICONNECTION_H
#define OPENBTSCLICONNECTION_H
#include <string>
#include <Sockets.h>
namespace CommandLine {
class CLIConnection {
public:
/** Constructor */
CLIConnection(ConnectionSocket *pSocket)
: mSocket(pSocket)
{
assert(mSocket);
}
/** Return a reference to the actual socket. */
ConnectionSocket *socket() { return mSocket; }
/**
Send a block of data
@return true if sent successfully, false otherwise.
*/
bool sendBlock(const std::string &block);
/**
Receive a block of data
@return true if received successfully, false otherwise.
*/
bool receiveBlock(std::string &block);
protected:
ConnectionSocket *mSocket; ///< Actual socket for the connection
/** Logs the error in a readable form and closes socket. */
void onError(int errnum, const char *oper);
};
} // CLI
#endif
// vim: ts=4 sw=4

View File

@ -0,0 +1,837 @@
/*
* Copyright 2009, 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <iomanip>
#include <fstream>
#include <iterator>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <config.h>
#include "CLIParser.h"
#include "Tokenizer.h"
#include <Logger.h>
#include <Globals.h>
#include <GSMConfig.h>
#include <GSMLogicalChannel.h>
#include <ControlCommon.h>
#include <TRXManager.h>
#include <PowerManager.h>
using namespace std;
using namespace CommandLine;
enum erorrCode {
SUCCESS=0,
BAD_NUM_ARGS=1,
BAD_VALUE=2,
NOT_FOUND=3,
TOO_MANY_ARGS=4,
FAILURE=5
};
/** Standard responses in the CLI, must match erorrCode enum. */
static const char* errorCodeText[] = {
"success", // 0
"wrong number of arguments", // 1
"bad argument(s)", // 2
"command not found", // 3
"too many arguments for parser", // 4
"command failed", // 5
};
extern TransceiverManager gTRX;
CommandLine::Parser CommandLine::gParser;
/**@name Commands for the CLI. */
//@{
// forward refs
int printStats(int argc, char** argv, ostream& os);
/*
A CLI command takes the argument in an array.
It returns 0 on success.
*/
/** Set the logging file. */
int setlogfile(int argc, char** argv, ostream& os)
{
if (argc!=2) return BAD_NUM_ARGS;
if (gSetLogFile(argv[1])) {
gConfig.set("LogFile",argv[1]);
return SUCCESS;
}
os << "cannot open " << argv[1] << " for logging" << endl;
return FAILURE;
}
/** Display system uptime and current GSM frame number. */
int uptime(int argc, char** argv, ostream& os)
{
if (argc!=1) return BAD_NUM_ARGS;
os.precision(2);
os << "Unix time " << time(NULL) << endl;
int seconds = gBTS.uptime();
if (seconds<120) {
os << "uptime " << seconds << " seconds, frame " << gBTS.time() << endl;
return SUCCESS;
}
float minutes = seconds / 60.0F;
if (minutes<120) {
os << "uptime " << minutes << " minutes, frame " << gBTS.time() << endl;
return SUCCESS;
}
float hours = minutes / 60.0F;
if (hours<48) {
os << "uptime " << hours << " hours, frame " << gBTS.time() << endl;
return SUCCESS;
}
float days = hours / 24.0F;
os << "uptime " << days << " days, frame " << gBTS.time() << endl;
return SUCCESS;
}
/** Give a list of available commands or describe a specific command. */
int showHelp(int argc, char** argv, ostream& os)
{
if (argc==2) {
os << argv[1] << " " << gParser.help(argv[1]) << endl;
return SUCCESS;
}
if (argc!=1) return BAD_NUM_ARGS;
os << endl << "Type \"help\" followed by the command name for help on that command." << endl << endl;
gParser.printCommands(os, 3);
os << endl;
os << "Lines starting with '!' are escaped to the shell." << endl;
os << endl;
os << "To pass string with spaces to a command, use quotes. E.g.:" << endl;
os << " sendsms IMSI255025502132582 100 \"multi-word sms with spaces\"" << endl;
os << "Escaping is not yet available, so you can't pass \" symbol itself to a command." << endl;
os << endl;
if ( strcasecmp(gConfig.getStr("CLI.Type"),"TCP") == 0
|| strcasecmp(gConfig.getStr("CLI.Type"),"UNIX") == 0) {
os << "Use <Ctrl-D> or <Ctrl-C> to exit from the CLI. \"exit\" will shutdown BTS!" << endl;
} else {
os << "Use <cntrl-A>, <D> to detach from \"screen\", *not* <cntrl-C>." << endl << endl;
}
return SUCCESS;
}
/** A function to return -1, the exit code for the caller. */
int exit_function(int argc, char** argv, ostream& os)
{
unsigned wait =0;
if (argc>2) return BAD_NUM_ARGS;
if (argc==2) wait = atoi(argv[1]);
if (wait!=0)
os << "waiting up to " << wait << " seconds for clearing of "
<< gBTS.TCHActive() << " active calls" << endl;
// Block creation of new channels.
gBTS.hold(true);
// Wait up to the timeout for active channels to release.
time_t finish = time(NULL) + wait;
while (time(NULL)<finish) {
unsigned load = gBTS.SDCCHActive() + gBTS.TCHActive();
if (load==0) break;
sleep(1);
}
bool loads = false;
if (gBTS.SDCCHActive()>0) {
LOG(WARN) << "dropping " << gBTS.SDCCHActive() << " control transactions on exit";
loads = true;
}
if (gBTS.TCHActive()>0) {
LOG(WARN) << "dropping " << gBTS.TCHActive() << " calls on exit";
loads = true;
}
if (loads) {
os << endl << "exiting with loads:" << endl;
printStats(1,NULL,os);
}
if (gConfig.defines("Control.TMSITable.SavePath")) {
gTMSITable.save(gConfig.getStr("Control.TMSITable.SavePath"));
}
os << endl << "exiting..." << endl;
return -1;
}
// Forward ref.
int tmsis(int argc, char** argv, ostream& os);
/** Dump TMSI table to a text file. */
int dumpTMSIs(const char* filename)
{
ofstream fileout;
fileout.open(filename, ios::out); // erases existing!
// FIXME -- Check that the file really opened.
// Fake an argument list to call printTMSIs.
char* subargv[] = {"tmsis", NULL};
int subargc = 1;
return tmsis(subargc, subargv, fileout);
}
/** Print or clear the TMSI table. */
int tmsis(int argc, char** argv, ostream& os)
{
if (argc>=2) {
// Clear?
if (strcmp(argv[1],"clear")==0) {
if (argc!=2) return BAD_NUM_ARGS;
os << "clearing TMSI table" << endl;
gTMSITable.clear();
return SUCCESS;
}
// Dump?
if (strcmp(argv[1],"dump")==0) {
if (argc!=3) return BAD_NUM_ARGS;
os << "dumping TMSI table to " << argv[2] << endl;
return dumpTMSIs(argv[2]);
}
return BAD_VALUE;
}
if (argc!=1) return BAD_NUM_ARGS;
os << "TMSI IMSI IMEI age used" << endl;
gTMSITable.dump(os);
os << endl << gTMSITable.size() << " TMSIs in table" << endl;
return SUCCESS;
}
/** Print the transactions table. */
int trans(int argc, char** argv, ostream& os)
{
if (argc!=1) return BAD_NUM_ARGS;
// os << "TMSI IMSI IMEI age used" << endl;
gTransactionTable.dump(os);
os << endl << gTransactionTable.size() << " transactions in table" << endl;
return SUCCESS;
}
int findimsi(int argc, char** argv, ostream& os)
{
if (argc!=2) {
os << "usage: findimsi <imsiprefix>\n";
return BAD_VALUE;
}
// FIXME -- THis should be moved into the TMSITable object to make it thread-safe.
Control::TMSIMap::const_iterator tp = gTMSITable.begin();
char buf[50]; // max size in decimal digits plus 1 plus RandomPositive
while (tp != gTMSITable.end()) {
std::string target;
sprintf(buf, "%d", tp->first);
std::string imsi = buf;
target.assign(imsi, 0, strlen(argv[1]));
if (target == argv[1])
os << tp->second << " 0x" << std::hex << tp->first << std::dec << endl;
++tp;
}
return SUCCESS;
}
/** Submit an SMS for delivery to an IMSI. */
int sendsms(int argc, char** argv, ostream& os)
{
if (argc!=4) return BAD_NUM_ARGS;
// os << "enter text to send: ";
// char txtBuf[161];
// cin.getline(txtBuf,160,'\n');
char *IMSI = argv[1];
char *srcAddr = argv[2];
char *txtBuf = argv[3];
Control::TransactionEntry transaction(
GSM::L3MobileIdentity(IMSI),
GSM::L3CMServiceType::MobileTerminatedShortMessage,
GSM::L3CallingPartyBCDNumber(srcAddr),
txtBuf);
transaction.Q931State(Control::TransactionEntry::Paging);
Control::initiateMTTransaction(transaction,GSM::SDCCHType,30000);
os << "message submitted for delivery" << endl;
return SUCCESS;
}
/** DEBUGGING: Sends a special sms that triggers a RRLP message to an IMSI. */
int sendrrlp(int argc, char** argv, ostream& os)
{
if (argc!=3) return BAD_NUM_ARGS;
char *IMSI = argv[1];
UDPSocket sock(0,"127.0.0.1",gConfig.getNum("SIP.Port"));
unsigned port = sock.port();
unsigned callID = random();
// Just fake out a SIP message.
const char form[] = "MESSAGE sip:IMSI%s@localhost SIP/2.0\nVia: SIP/2.0/TCP localhost;branch=z9hG4bK776sgdkse\nMax-Forwards: 2\nFrom: RRLP@localhost:%d;tag=49583\nTo: sip:IMSI%s@localhost\nCall-ID: %d@127.0.0.1:5063\nCSeq: 1 MESSAGE\nContent-Type: text/plain\nContent-Length: %lu\n\n%s\n";
char txtBuf[161];
sprintf(txtBuf,"RRLP%s",argv[2]);
char outbuf[2048];
sprintf(outbuf,form,IMSI,port,IMSI,callID,strlen(txtBuf),txtBuf);
sock.write(outbuf);
sleep(2);
sock.write(outbuf);
sock.close();
os << "RRLP Triggering message submitted for delivery" << endl;
return SUCCESS;
}
/** Print current usage loads. */
int printStats(int argc, char** argv, ostream& os)
{
if (argc!=1) return BAD_NUM_ARGS;
os << "SDCCH load: " << gBTS.SDCCHActive() << '/' << gBTS.SDCCHTotal() << endl;
os << "TCH/F load: " << gBTS.TCHActive() << '/' << gBTS.TCHTotal() << endl;
os << "AGCH/PCH load: " << gBTS.AGCHLoad() << ',' << gBTS.PCHLoad() << endl;
// paging table size
os << "Paging table size: " << gBTS.pager().pagingEntryListSize() << endl;
os << "Transactions/TMSIs: " << gTransactionTable.size() << ',' << gTMSITable.size() << endl;
// 3122 timer current value (the number of seconds an MS should hold off the next RACH)
os << "T3122: " << gBTS.T3122() << " ms" << endl;
return SUCCESS;
}
/** Get/Set MCC, MNC, LAC, CI. */
int cellID(int argc, char** argv, ostream& os)
{
if (argc==1) {
os << "MCC=" << gConfig.getStr("GSM.MCC")
<< " MNC=" << gConfig.getStr("GSM.MNC")
<< " LAC=" << gConfig.getNum("GSM.LAC")
<< " CI=" << gConfig.getNum("GSM.CI")
<< endl;
return SUCCESS;
}
if (argc!=5) return BAD_NUM_ARGS;
// Safety check the args!!
char* MCC = argv[1];
char* MNC = argv[2];
if (strlen(MCC)!=3) {
os << "MCC must be three digits" << endl;
return BAD_VALUE;
}
int MNCLen = strlen(MNC);
if ((MNCLen<2)||(MNCLen>3)) {
os << "MNC must be two or three digits" << endl;
return BAD_VALUE;
}
gTMSITable.clear();
gConfig.set("GSM.MCC",MCC);
gConfig.set("GSM.MNC",MNC);
gConfig.set("GSM.LAC",argv[3]);
gConfig.set("GSM.CI",argv[4]);
gBTS.regenerateBeacon();
return SUCCESS;
}
/** Print table of current transactions. */
int calls(int argc, char** argv, ostream& os)
{
if (argc!=1) return BAD_NUM_ARGS;
Control::TransactionMap::const_iterator trans = gTransactionTable.begin();
int count = 0;
gTransactionTable.clearDeadEntries();
while (trans != gTransactionTable.end()) {
os << trans->second << endl;
++trans;
count++;
}
os << endl << count << " transactions in table" << endl;
return SUCCESS;
}
/** Print or modify the global configuration table. */
int config(int argc, char** argv, ostream& os)
{
// no args, just print
if (argc==1) {
gConfig.dump(os);
return SUCCESS;
}
// one arg, pattern match and print
bool anything = false;
if (argc==2) {
StringMap::const_iterator p = gConfig.begin();
while (p != gConfig.end()) {
if (strstr(p->first.c_str(),argv[1])) {
os << p->first << ": " << p->second << endl;
anything = true;
}
++p;
}
if (!anything)
os << "nothing matching \"" << argv[1] << "\"" << endl;
return SUCCESS;
}
// >1 args: set new value
string val;
for (int i=2; i<argc; i++) {
val.append(argv[i]);
if (i!=(argc-1)) val.append(" ");
}
bool existing = gConfig.defines(argv[1]);
if (!gConfig.set(argv[1],val)) {
os << argv[1] << " is static and connot be altered after initialization" << endl;
return BAD_VALUE;
}
if (!existing) {
os << "defined new config " << argv[1] << " as \"" << val << "\"" << endl;
// Anything created by the CLI is optional.
gConfig.makeOptional(argv[1]);
} else {
os << "changed " << argv[1] << " to \"" << val << "\"" << endl;
}
gBTS.regenerateBeacon();
return SUCCESS;
}
/** Remove a configiuration value. */
int unconfig(int argc, char** argv, ostream& os)
{
if (argc!=2) return BAD_NUM_ARGS;
if (gConfig.unset(argv[1])) {
os << "\"" << argv[1] << "\" removed from the configuration table" << endl;
gBTS.regenerateBeacon();
return SUCCESS;
}
if (gConfig.defines(argv[1])) {
os << "\"" << argv[1] << "\" could not be removed" << endl;
} else {
os << "\"" << argv[1] << "\" was not in the table" << endl;
}
return BAD_VALUE;
}
/** Dump current configuration to a file. */
int configsave(int argc, char** argv, ostream& os)
{
if (argc!=2) return BAD_NUM_ARGS;
fstream f;
f.open(argv[1],fstream::out);
if (f.fail()) {
os << "cannot open " << argv[1] << " for writing" << endl;
return FAILURE;
}
f << "# OpenBTS configuration file" << endl;
time_t now = time(NULL);
f << "# " << ctime(&now) << endl;
gConfig.write(f);
f.close();
return SUCCESS;
}
/** Change the registration timers. */
int regperiod(int argc, char** argv, ostream& os)
{
if (argc==1) {
os << "T3212 is " << gConfig.getNum("GSM.T3212") << " minutes" << endl;
os << "SIP registration period is " << gConfig.getNum("SIP.RegistrationPeriod")/60 << " minutes" << endl;
return SUCCESS;
}
if (argc>3) return BAD_NUM_ARGS;
unsigned newT3212 = strtol(argv[1],NULL,10);
if ((newT3212<6)||(newT3212>1530)) {
os << "valid T3212 range is 6..1530 minutes" << endl;
return BAD_VALUE;
}
// By defuault, make SIP registration period 1.5x the GSM registration period.
unsigned SIPRegPeriod = newT3212*90;
if (argc==3) {
SIPRegPeriod = 60*strtol(argv[2],NULL,10);
}
// Set the values in the table and on the GSM beacon.
gConfig.set("SIP.RegistrationPeriod",SIPRegPeriod);
gConfig.set("GSM.T3212",newT3212);
gBTS.regenerateBeacon();
// Done.
return SUCCESS;
}
/** Print the list of alarms kept by the logger, i.e. the last LOG(ALARM) << <text> */
int alarms(int argc, char** argv, ostream& os)
{
std::ostream_iterator<std::string> output( os, "\n" );
std::list<std::string> alarms = gGetLoggerAlarms();
std::copy( alarms.begin(), alarms.end(), output );
return SUCCESS;
}
/** Version string. */
int version(int argc, char **argv, ostream& os)
{
if (argc!=1) return BAD_NUM_ARGS;
os << "release " VERSION " built " __DATE__ << endl;
return SUCCESS;
}
/** Show start-up notices. */
int notices(int argc, char **argv, ostream& os)
{
if (argc!=1) return BAD_NUM_ARGS;
os << endl << gOpenBTSWelcome << endl;
return SUCCESS;
}
int page(int argc, char **argv, ostream& os)
{
if (argc==1) {
gBTS.pager().dump(os);
return SUCCESS;
}
if (argc!=3) return BAD_NUM_ARGS;
char *IMSI = argv[1];
if (strlen(IMSI)>15) {
os << IMSI << " is not a valid IMSI" << endl;
return BAD_VALUE;
}
Control::TransactionEntry dummy;
gBTS.pager().addID(GSM::L3MobileIdentity(IMSI),GSM::SDCCHType,dummy,1000*atoi(argv[2]));
return SUCCESS;
}
int testcall(int argc, char **argv, ostream& os)
{
if (argc!=3) return BAD_NUM_ARGS;
char *IMSI = argv[1];
if (strlen(IMSI)!=15) {
os << IMSI << " is not a valid IMSI" << endl;
return BAD_VALUE;
}
Control::TransactionEntry transaction(
GSM::L3MobileIdentity(IMSI),
GSM::L3CMServiceType::TestCall,
GSM::L3CallingPartyBCDNumber("0"));
transaction.Q931State(Control::TransactionEntry::Paging);
Control::initiateMTTransaction(transaction,GSM::TCHFType,1000*atoi(argv[2]));
return SUCCESS;
}
int endcall(int argc, char **argv, ostream& os)
{
// FIXME -- This doesn't really work.
if (argc!=2) return BAD_NUM_ARGS;
unsigned transID = atoi(argv[1]);
Control::TransactionEntry target;
if (!gTransactionTable.find(transID,target)) {
os << transID << " not found in table";
return BAD_VALUE;
}
target.Q931State(Control::TransactionEntry::ReleaseRequest);
gTransactionTable.update(target);
return SUCCESS;
}
void printChanInfo(const GSM::LogicalChannel* chan, ostream& os)
{
os << setw(2) << chan->TN();
os << " " << setw(9) << chan->typeAndOffset();
char buffer[1024];
sprintf(buffer,"%10d %5.2f %4d %5d %4d",
chan->transactionID(),
100.0*chan->FER(), (int)round(chan->RSSI()),
chan->actualMSPower(), chan->actualMSTiming());
os << " " << buffer;
const GSM::L3MeasurementResults& meas = chan->SACCH()->measurementResults();
if (!meas.MEAS_VALID()) {
sprintf(buffer,"%5d %5.2f",
meas.RXLEV_FULL_SERVING_CELL_dBm(),
100.0*meas.RXQUAL_FULL_SERVING_CELL_BER());
os << " " << buffer;
} else {
os << " ----- ------";
}
os << endl;
}
int chans(int argc, char **argv, ostream& os)
{
if (argc!=1) return BAD_NUM_ARGS;
os << "TN chan transaction UPFER RSSI TXPWR TXTA DNLEV DNBER" << endl;
os << "TN type id pct dB dBm sym dBm pct" << endl;
// SDCCHs
GSM::SDCCHList::const_iterator sChanItr = gBTS.SDCCHPool().begin();
while (sChanItr != gBTS.SDCCHPool().end()) {
const GSM::SDCCHLogicalChannel* sChan = *sChanItr;
if (sChan->active()) printChanInfo(sChan,os);
++sChanItr;
}
// TCHs
GSM::TCHList::const_iterator tChanItr = gBTS.TCHPool().begin();
while (tChanItr != gBTS.TCHPool().end()) {
const GSM::TCHFACCHLogicalChannel* tChan = *tChanItr;
if (tChan->active()) printChanInfo(tChan,os);
++tChanItr;
}
os << endl;
return SUCCESS;
}
int power(int argc, char **argv, ostream& os)
{
os << "current downlink power " << gBTS.powerManager().power() << " dB wrt full scale" << endl;
os << "current attenuation bounds "
<< gConfig.getNum("GSM.PowerManager.MinAttenDB")
<< " to "
<< gConfig.getNum("GSM.PowerManager.MaxAttenDB")
<< " dB" << endl;
if (argc==1) return SUCCESS;
if (argc!=3) return BAD_NUM_ARGS;
int min = atoi(argv[1]);
int max = atoi(argv[2]);
if (min>max) return BAD_VALUE;
gConfig.set("GSM.PowerManager.MinAttenDB",argv[1]);
gConfig.set("GSM.PowerManager.MaxAttenDB",argv[2]);
os << "new attenuation bounds "
<< gConfig.getNum("GSM.PowerManager.MinAttenDB")
<< " to "
<< gConfig.getNum("GSM.PowerManager.MaxAttenDB")
<< " dB" << endl;
return SUCCESS;
}
int rxgain(int argc, char** argv, ostream& os)
{
os << "current RX gain is " << gConfig.getNum("GSM.RxGain") << " dB" << endl;
if (argc==1) return SUCCESS;
if (argc!=2) return BAD_NUM_ARGS;
int newGain = gTRX.ARFCN(0)->setRxGain(atoi(argv[1]));
os << "new RX gain is " << newGain << " dB" << endl;
gConfig.set("GSM.RxGain",newGain);
return SUCCESS;
}
int noise(int argc, char** argv, ostream& os)
{
if (argc!=1) return BAD_NUM_ARGS;
int noise = gTRX.ARFCN(0)->getNoiseLevel();
os << "noise RSSI is -" << noise << " dB wrt full scale" << endl;
os << "MS RSSI target is " << gConfig.getNum("GSM.RSSITarget") << " dB wrt full scale" << endl;
return SUCCESS;
}
int echofirst(int argc, char** argv, ostream& os)
{
if (argc!=2) return BAD_NUM_ARGS;
os << argv[1];
return SUCCESS;
}
int shellexec(const char *line, ostream& os)
{
os << endl;
FILE *pout = popen(line, "r");
char buf[1024];
while (fgets(buf, sizeof(buf), pout) != NULL) {
os << buf;
}
int retVal = pclose(pout);
os << endl << "External call returned " << retVal << endl;
return SUCCESS;
}
//@} // CLI commands
int Parser::execute(std::string &line, ostream& os) const
{
// escape to the shell?
if (line[0]=='!') {
return shellexec(line.data()+1, os);
}
// Tokenize
char *argv[mMaxArgs];
int argc = tokenize((char*)line.data(), argv, mMaxArgs);
// Blank line?
if (!argc) return SUCCESS;
// Find the command.
mMutex.lock();
ParseTable::const_iterator cfp = mParseTable.find(argv[0]);
if (cfp == mParseTable.end()) {
mMutex.unlock();
return NOT_FOUND;
}
mMutex.unlock();
CLICommandFunc func;
func = cfp->second;
// Do it.
int retVal = (*func)(argc,argv,os);
// Give hint on bad # args.
if (retVal==BAD_NUM_ARGS) os << help(argv[0]) << endl;
return retVal;
}
int Parser::process(const std::string &line, ostream& os)
{
std::string newLine = line;
int retVal = execute(newLine,os);
if (retVal>0) os << errorCodeText[retVal] << endl;
return retVal;
}
void Parser::printCommands(std::ostream &os, int numCols) const
{
mMutex.lock();
ParseTable::const_iterator cp = mParseTable.begin();
int c=0;
while (cp != mParseTable.end()) {
const string& wd = cp->first;
os << wd << '\t';
if (wd.size()<8) os << '\t';
++cp;
c++;
if (c%numCols==0) os << endl;
}
if (c%numCols!=0) os << endl;
mMutex.unlock();
}
const char * Parser::help(const string& cmd) const
{
HelpTable::const_iterator hp = mHelpTable.find(cmd);
if (hp==mHelpTable.end()) return "no help available";
return hp->second.c_str();
}
Parser::Parser()
{
// The constructor adds the commands.
addCommand("setlogfile", setlogfile, "<path> -- set the logging file to <path>.");
addCommand("uptime", uptime, "-- show BTS uptime and BTS frame number.");
addCommand("help", showHelp, "[command] -- list available commands or gets help on a specific command.");
addCommand("exit", exit_function, "[wait] -- exit the application, either immediately, or waiting for existing calls to clear with a timeout in seconds");
addCommand("tmsis", tmsis, "[\"clear\"] or [\"dump\" filename] -- print/clear the TMSI table or dump it to a file.");
addCommand("trans", trans, "-- print the transactions table.");
addCommand("findimsi", findimsi, "[IMSIPrefix] -- prints all imsi's that are prefixed by IMSIPrefix");
addCommand("sendsms", sendsms, "<IMSI> <src> <text> -- send SMS to <IMSI>, addressed from <src>.");
addCommand("sendrrlp", sendrrlp, "<IMSI> <hexstring> -- send RRLP message <hexstring> to <IMSI>.");
addCommand("load", printStats, "-- print the current activity loads.");
addCommand("cellid", cellID, "[MCC MNC LAC CI] -- get/set location area identity (MCC, MNC, LAC) and cell ID (CI)");
addCommand("calls", calls, "-- print the transaction table");
addCommand("config", config, "[] OR [patt] OR [key val(s)] -- print the current configuration, print configuration values matching a pattern, or set/change a configuration value");
addCommand("configsave", configsave, "<path> -- write the current configuration to a file");
addCommand("regperiod", regperiod, "[GSM] [SIP] -- get/set the registration period (GSM T3212), in MINUTES");
addCommand("alarms", alarms, "-- show latest alarms");
addCommand("version", version,"-- print the version string");
addCommand("page", page, "[IMSI time] -- dump the paging table or page the given IMSI for the given period");
addCommand("testcall", testcall, "IMSI time -- initiate a test call to a given IMSI with a given paging time");
addCommand("chans", chans, "-- report PHY status for active channels");
addCommand("power", power, "[minAtten maxAtten] -- report current attentuation or set min/max bounds");
addCommand("rxgain", rxgain, "[newRxgain] -- get/set the RX gain in dB");
addCommand("noise", noise, "-- report receive noise level in RSSI dB");
addCommand("unconfig", unconfig, "key -- remove a config value");
addCommand("notices", notices, "-- show startup copyright and legal notices");
addCommand("echo", echofirst, "<string> -- print <string> to the screen");
// HACK -- Comment out these until they are fixed.
// addCommand("endcall", endcall,"trans# -- terminate the given transaction");
}
// vim: ts=4 sw=4

View File

@ -0,0 +1,86 @@
/*
* Copyright 2009, 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPENBTSCLIPARSER_H
#define OPENBTSCLIPARSER_H
#include <string>
#include <map>
#include <iostream>
#include <CLIParserBase.h>
#include <Threads.h>
namespace CommandLine {
typedef int (*CLICommandFunc)(int,char**,std::ostream&);
/** A table for matching strings to actions. */
typedef std::map<std::string,CLICommandFunc> ParseTable;
/** The help table. */
typedef std::map<std::string,std::string> HelpTable;
class Parser : public ParserBase {
public:
Parser();
/**
Process a command line.
@return 0 on success, -1 on exit request, error codes otherwise
*/
virtual int process(const std::string &line, std::ostream& os);
/** Add a command to the parsing table. */
void addCommand(const char* name, CLICommandFunc func, const char* helpString)
{ mMutex.lock(); mParseTable[name] = func; mHelpTable[name]=helpString; mMutex.unlock(); }
/** Print command list to ostream */
void printCommands(std::ostream &os, int numCols=1) const;
/** Return a help string. */
const char *help(const std::string& cmd) const;
protected:
mutable Mutex mMutex; ///< Mutex to protect mParseTable and mHelpTable.
ParseTable mParseTable;
HelpTable mHelpTable;
static const int mMaxArgs = 10;
/** Parse and execute a command string. */
int execute(std::string &line, std::ostream& os) const;
};
extern CommandLine::Parser gParser;
} // CLI
#endif
// vim: ts=4 sw=4

View File

@ -0,0 +1,47 @@
/*
* Copyright 2009, 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPENBTSCLIBASE_H
#define OPENBTSCLIBASE_H
namespace CommandLine {
class ParserBase {
public:
/**
Process a command line.
@return 0 on success, -1 on exit request, error codes otherwise
*/
virtual int process(const std::string &line, std::ostream& os) =0;
};
} // CLI
#endif
// vim: ts=4 sw=4

View File

@ -0,0 +1,172 @@
/*
* Copyright 2009, 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <vector>
#include <list>
#include <config.h>
#include <CLIServer.h>
#include <CLIConnection.h>
#include <CLIParser.h>
#include <Globals.h>
#include <Logger.h>
using namespace std;
using namespace CommandLine;
namespace CommandLine {
typedef std::list<CLIServerConnectionThread *> ServerThreadsList;
/** Free all finished threads from the given list */
void clearFinishedThreads(ServerThreadsList &threadsList)
{
ServerThreadsList::iterator it;
for (it = threadsList.begin(); it != threadsList.end(); )
{
CLIServerConnectionThread *pThread = *it;
if (pThread->state() == CLIServerConnectionThread::FINISHING) {
pThread->join();
delete pThread;
// This moves iterator forward, so we don't need to increment it.
it = threadsList.erase(it);
} else {
it++;
}
}
}
/** Stop all threads in the list */
void stopAllThreads(ServerThreadsList &threadsList)
{
while (threadsList.begin() != threadsList.end())
{
// Pop an element from the list.
CLIServerConnectionThread *pThread = threadsList.front();
threadsList.pop_front();
switch (pThread->state()) {
case CLIServerConnectionThread::IDLE:
delete pThread;
break;
case CLIServerConnectionThread::STARTING:
case CLIServerConnectionThread::STARTED:
pThread->stop();
// no break;
case CLIServerConnectionThread::FINISHING:
pThread->join();
delete pThread;
break;
}
}
}
};
void CommandLine::runCLIServer(ConnectionServerSocket *serverSock)
{
ServerThreadsList serverThreads;
// Step 1 -- Start listening at the given interface/port
if (!serverSock->bind()) {
int errsv = errno;
LOG(ERROR) << "serverSock->bind() failed with errno="
<< errsv << " (0x" << hex << errsv << dec << ")";
return;
}
// Step 2 -- Serve incoming connections
while (1) {
// Step 2.1 -- Wait for incoming connection
ConnectionSocket *newConn = serverSock->accept();
if (newConn == NULL)
{
int errsv = errno;
if (errsv == EINVAL) {
LOG(INFO) << "serverSock has been closed.";
} else {
LOG(INFO) << "serverSock->accept() failed with errno="
<< errsv << " (0x" << hex << errsv << dec << ")";
}
break;
}
LOG(INFO) << "New incoming connection: " << *newConn;
// Step 2.2 -- Add thread to our threads list and start it.
CLIServerConnectionThread *ipServerThread
= new CLIServerConnectionThread(newConn, serverSock);
LOG(DEEPDEBUG) << "New server thread: " << hex << ipServerThread << dec;
serverThreads.push_back(ipServerThread);
ipServerThread->start();
// Step 2.3 -- Clean up finished threads
clearFinishedThreads(serverThreads);
}
// Step 3 -- Cleanup
serverSock->close();
stopAllThreads(serverThreads);
}
void *CLIServerConnectionThread::startFunc(void *data)
{
CLIServerConnectionThread *threadObj = (CLIServerConnectionThread*)data;
// Simply forward the call to member function
threadObj->state(STARTED);
threadObj->run();
threadObj->state(FINISHING);
return NULL;
}
void CLIServerConnectionThread::run()
{
assert(mConnSock != NULL);
CLIConnection conn(mConnSock);
while (1) {
// Receive a command
std::string recvBlock;
if (!conn.receiveBlock(recvBlock))
break;
// Process command
ostringstream sendBlock;
int res = gParser.process(recvBlock, sendBlock);
if (res < 0) {
// Shutdown CLI server if process() returns -1
mServerSock->close();
break;
}
// Send result back to the client
if (!conn.sendBlock(sendBlock.str()))
break;
}
mConnSock->close();
}
// vim: ts=4 sw=4

View File

@ -0,0 +1,116 @@
/*
* Copyright 2009, 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPENBTSCLISERVER_H
#define OPENBTSCLISERVER_H
#include <string>
#include <map>
#include <iostream>
#include <Threads.h>
#include "CLIConnection.h"
class ConnectionServerSocket;
namespace CommandLine {
void runCLIServer(ConnectionServerSocket *serverSock);
class CLIServerConnectionThread {
public:
enum State {
IDLE, ///< Thread is not started
STARTING, ///< Thread start has been just requested
STARTED, ///< Thread has entered its main processing loop
FINISHING ///< Thread has exited from its main processing loop
};
/** Constructor */
CLIServerConnectionThread(ConnectionSocket *connSock,
ConnectionServerSocket *serverSock)
: mConnSock(connSock)
, mServerSock(serverSock)
, mState(IDLE)
{}
/** Destructor */
~CLIServerConnectionThread()
{
assert(state() == IDLE);
delete mConnSock;
}
/** Request thread to start. */
void start() { assert(state() == IDLE); state(STARTING); mThread.start(startFunc, this); }
/** Request thread to stop. */
void stop() { mConnSock->close(); }
/** Wait for the thread to finish. */
void join() { mThread.join(); state(IDLE); }
/** Get internal state of the thread */
State state() const
{
mStateMutex.lock();
State retval = mState;
mStateMutex.unlock();
return retval;
}
protected:
Thread mThread; ///< Actual thread instance. We're proxying requests to it.
ConnectionSocket * const mConnSock; ///< Connection to a client socket.
///< It's thread-safe to access it as long as it's const.
ConnectionServerSocket * const mServerSock; ///< Listening server socket.
///< It's thread-safe to access it as long as it's const.
mutable Mutex mStateMutex; ///< A mutex to protect mState variable.
State mState; ///< State of the thread - is it running or what.
/** Static function to pass to Thread::start() */
static void *startFunc(void *data);
/** Main entry for the thread */
void run();
/** Change internal state of the thread. */
void state(State newState)
{
mStateMutex.lock();
mState = newState;
mStateMutex.unlock();
}
};
} // CLI
#endif
// vim: ts=4 sw=4

View File

@ -26,10 +26,31 @@ EXTRA_DIST = \
AM_CXXFLAGS = -Wall
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
noinst_LTLIBRARIES = libcli.la
noinst_LTLIBRARIES = libcli.la libcli_commands.la
noinst_PROGRAMS = \
TokenizerTest
libcli_la_SOURCES = \
CLI.cpp
CLI.cpp \
CLIServer.cpp \
CLIClient.cpp \
CLIConnection.cpp \
Tokenizer.cpp
libcli_commands_la_SOURCES = \
CLIParser.cpp
TokenizerTest_SOURCES = \
TokenizerTest.cpp
TokenizerTest_LDADD = \
$(CLI_LA)
noinst_HEADERS = \
CLI.h
CLI.h \
CLIServer.h \
CLIClient.h \
CLIConnection.h \
CLIParser.h \
CLIParserBase.h \
Tokenizer.h

View File

@ -0,0 +1,78 @@
/*
* Copyright 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "Tokenizer.h"
#include <stdio.h> // For debug only
using namespace CommandLine;
int CommandLine::tokenize(char *line, char **argv, int maxArgs)
{
char *lineData = line;
int argc;
const char *tokenEnd = " ";
// Find the beginning of the first token
lineData += strspn(lineData, " ");
// Check if this is a start of quoting
if (*lineData == '"') {
tokenEnd = "\"";
lineData++;
} else {
tokenEnd = " ";
}
for (argc = 0; argc < maxArgs && *(argv[argc] = lineData) != '\0'; argc++) {
// Find the end of the token
lineData = strpbrk(lineData, tokenEnd);
if (lineData == NULL) {
argc++;
break;
}
*lineData = '\0';
// Find the beginning of the next token
lineData++;
lineData += strspn(lineData, " ");
if (*lineData == '\0') {
argc++;
break;
}
// Check if this is a start of quoting
if (*lineData == '"') {
tokenEnd = "\"";
lineData++;
} else {
tokenEnd = " ";
}
}
return argc;
}
// vim: ts=4 sw=4

View File

@ -0,0 +1,41 @@
/*
* Copyright 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPENBTSTOKENIZER_H
#define OPENBTSTOKENIZER_H
namespace CommandLine {
/**
Split given string into tokens.
WARNING: String will be changed during processing and argv will point
to parts of this string.
*/
int tokenize(char *line, char **argv, int maxArgs);
}
#endif
// vim: ts=4 sw=4

View File

@ -0,0 +1,64 @@
/*
* Copyright 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Tokenizer.h"
#include <iostream>
using namespace std;
using namespace CommandLine;
const int gMaxArgs = 5;
void test(const char *line)
{
char *argv[gMaxArgs];
std::string lineStr = line;
cout << "Tokenizing string: |" << line << "|" << endl;
int argc = tokenize((char*)lineStr.data(), argv, gMaxArgs);
cout << "Found " << argc << " arguments" << endl;
for (int i=0; i<argc; i++) {
cout << "|" << argv[i] << "|" << endl;
}
}
int main()
{
test("");
test(" ");
test("This software is distributed ");
test("This software is distributed under the terms ");
test(" This software is distributed under the terms ");
test(" This software \"is distributed\" under the terms ");
test(" This software \"is distributed \" under \"the terms\"");
test(" This software \"is distributed \"under \"the terms\"");
test(" This software \"is distributed \"under \"the terms");
test("software \"is distributed \" \"the terms");
test("\"software is\" distributed \" \"the terms");
test("\" ");
test("\"\" ");
test("\" \"");
}

View File

@ -0,0 +1,324 @@
/*
* Copyright 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Sockets.h"
#include "Threads.h"
#include "Logger.h"
#include "Configuration.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>
using namespace std;
ConfigurationTable gConfig;
static const int gNumToSend = 3;
static const int gIpBindPort = 22239;
static const char *gIpBindHost = "127.0.0.1";
//static const char *gIpBindHost = "0.0.0.0";
static const char *gUnixSocketPath = "/tmp/testUnixSock";
void reportError(const std::string &what, int errsv)
{
LOG(ERROR) << what << " failed with errno="
<< errsv << " (0x" << hex << errsv << dec << "): "
<< strerror(errsv);
}
void *ipServerClose(void *data)
{
ConnectionServerSocket *pServer = (ConnectionServerSocket*)data;
sleep(1);
pServer->close();
return NULL;
}
bool clientConnect(ConnectionSocket *conn)
{
int res = conn->connect();
if (res < 0) {
reportError("conn->connect()", errno);
conn->close();
return false;
}
return true;
}
bool clientWrite(ConnectionSocket *conn, const char *text)
{
std::string sendBuf = text;
LOG(INFO) << "Client sending: \"" << sendBuf << "\"";
uint32_t sendBufLength = htonl(sendBuf.length());
int bytesSent;
bytesSent = conn->write((const char *)&sendBufLength, 4);
if (bytesSent < 4) {
reportError("conn->write()", errno);
conn->close();
return false;
}
bytesSent = conn->write(sendBuf.data(), sendBuf.length());
if (bytesSent < sendBuf.length()) {
reportError("conn->write()", errno);
conn->close();
return false;
}
return true;
}
bool clientRead(ConnectionSocket *conn)
{
uint32_t recvDataLen;
char readBuf[1024];
int bytesReceived;
bytesReceived = conn->read(readBuf, 4);
if (bytesReceived < 4) {
reportError("conn->read()", errno);
conn->close();
return false;
}
recvDataLen = ntohl(*(uint32_t*)readBuf);
if (recvDataLen > 1024) {
recvDataLen = 1024;
}
bytesReceived = conn->read(readBuf, recvDataLen);
if (bytesReceived < recvDataLen) {
reportError("conn->read()", errno);
conn->close();
return false;
}
readBuf[bytesReceived>=sizeof(readBuf)?sizeof(readBuf)-1:bytesReceived]= '\0';
LOG(INFO) << "Client received: \"" << readBuf << "\"";
return true;
}
void *ipClient(void *)
{
for (int i=0; i<gNumToSend; i++)
{
ConnectionSocketTCP conn(gIpBindPort, gIpBindHost);
if (!clientConnect(&conn)) continue;
if (!clientWrite(&conn, "ping")) continue;
if (!clientRead(&conn)) continue;
conn.close();
usleep(500*1000);
}
// Shutdown server
ConnectionSocketTCP conn(gIpBindPort, gIpBindHost);
if (!clientConnect(&conn)) return NULL;
if (!clientWrite(&conn, "exit")) return NULL;
conn.close();
return NULL;
}
void *unixClient(void *)
{
for (int i=0; i<gNumToSend; i++)
{
ConnectionSocketUnix conn(gUnixSocketPath);
if (!clientConnect(&conn)) continue;
if (!clientWrite(&conn, "ping")) continue;
if (!clientRead(&conn)) continue;
conn.close();
usleep(500*1000);
}
// Shutdown server
ConnectionSocketUnix conn(gUnixSocketPath);
if (!clientConnect(&conn)) return NULL;
if (!clientWrite(&conn, "exit")) return NULL;
conn.close();
return NULL;
}
struct ConnectionPair {
ConnectionPair(ConnectionServerSocket *serv, ConnectionSocket *conn)
: serverSocket(serv)
, connectionSocket(conn)
{};
ConnectionServerSocket *serverSocket;
ConnectionSocket *connectionSocket;
};
void *serverConnThread(void *data)
{
ConnectionPair *pConnPair = (ConnectionPair*)data;
ConnectionSocket *pConn = pConnPair->connectionSocket;
ConnectionServerSocket *pServerSock = pConnPair->serverSocket;
delete pConnPair;
uint32_t recvDataLen;
char readBuf[1024];
int bytesReceived;
bytesReceived = pConn->read(readBuf, 4);
if (bytesReceived < 4) {
reportError("pConn->read()", errno);
pConn->close();
return NULL;
}
recvDataLen = ntohl(*(uint32_t*)readBuf);
if (recvDataLen > 1024) {
recvDataLen = 1024;
}
bytesReceived = pConn->read(readBuf, recvDataLen);
if (bytesReceived < recvDataLen) {
reportError("pConn->read()", errno);
pConn->close();
return NULL;
}
readBuf[bytesReceived>=sizeof(readBuf)?sizeof(readBuf)-1:bytesReceived]= '\0';
LOG(INFO) << "Server received: \"" << readBuf << "\"";
// Shutdown server is requested so.
if (strcmp(readBuf, "exit")==0) {
pConn->close();
pServerSock->close();
return NULL;
}
std::string sendBuf = "pong";
LOG(INFO) << "Server sending: \"" << sendBuf << "\"";
uint32_t sendBufLength = htonl(sendBuf.length());
int bytesSent;
bytesSent = pConn->write((const char *)&sendBufLength, 4);
if (bytesSent < 4) {
reportError("pConn->write()", errno);
pConn->close();
return NULL;
}
bytesSent = pConn->write(sendBuf.data(), sendBuf.length());
if (bytesSent < sendBuf.length()) {
reportError("pConn->write()", errno);
pConn->close();
return NULL;
}
// Wait for remote side to close connection to avoid TIME_WAIT on server side.
// Refer to http://hea-www.harvard.edu/~fine/Tech/addrinuse.html
bytesReceived = pConn->read(readBuf, 1);
pConn->close();
// This should be done in thread's destructor
delete pConn;
return NULL;
}
void serverBind(ConnectionServerSocket *serverSock)
{
if (!serverSock->bind()) {
reportError("serverSock->bind()", errno);
return;
}
}
void serverRun(ConnectionServerSocket *serverSock)
{
std::vector<Thread *> serverThreads;
while (1) {
ConnectionSocket *newConn = serverSock->accept();
if (newConn == NULL)
{
int errsv = errno;
if (errsv == EINVAL) {
LOG(INFO) << "serverSock has been closed.";
} else {
reportError("serverSock->accept()", errsv);
}
break;
}
LOG(INFO) << "New incoming connection " << newConn;
Thread *ipServerThread = new Thread();
LOG(INFO) << "New server thread: " << hex << ipServerThread << dec;
serverThreads.push_back(ipServerThread);
ipServerThread->start(serverConnThread, new ConnectionPair(serverSock, newConn));
}
// Stop server
serverSock->close();
// Clean up
Thread *ipServerThread;
while (serverThreads.size()) {
ipServerThread = serverThreads.back();
LOG(INFO) << "Removing server thread: " << hex << ipServerThread << dec;
ipServerThread->join();
delete ipServerThread;
serverThreads.pop_back();
}
}
int main(int argc, char * argv[] )
{
gLogInit("DEBUG");
LOG(INFO) << "Testing TCP sockets";
{
ConnectionServerSocketTCP serverSock(gIpBindPort, gIpBindHost);
Thread readerThreadIP;
serverBind(&serverSock);
readerThreadIP.start(ipClient, NULL);
serverRun(&serverSock);
readerThreadIP.join();
}
LOG(INFO) << "Testing UNIX sockets";
{
ConnectionServerSocketUnix serverSock(gUnixSocketPath);
Thread readerThreadIP;
serverBind(&serverSock);
readerThreadIP.start(unixClient, NULL);
serverRun(&serverSock);
readerThreadIP.join();
}
}
// vim: ts=4 sw=4

View File

@ -41,6 +41,7 @@ libcommon_la_SOURCES = \
noinst_PROGRAMS = \
BitVectorTest \
InterthreadTest \
ConnectionSocketsTest \
SocketsTest \
TimevalTest \
RegexpTest \
@ -73,6 +74,10 @@ SocketsTest_SOURCES = SocketsTest.cpp
SocketsTest_LDADD = libcommon.la
SocketsTest_LDFLAGS = -lpthread
ConnectionSocketsTest_SOURCES = ConnectionSocketsTest.cpp
ConnectionSocketsTest_LDADD = libcommon.la
ConnectionSocketsTest_LDFLAGS = -lpthread
TimevalTest_SOURCES = TimevalTest.cpp
TimevalTest_LDADD = libcommon.la

View File

@ -303,6 +303,226 @@ void UDDSocket::destination(const char* remotePath)
}
ConnectionSocket::ConnectionSocket(int af, int type, int protocol)
: mSocketFD(-1)
{
// Create the socket
mSocketFD = ::socket(af, type, protocol);
if (mSocketFD < 0) {
// int errsv = errno;
// LOG(ERROR) << "socket() failed with errno=" << errsv;
mSocketFD = -1;
}
}
ConnectionSocket::~ConnectionSocket()
{
close();
}
int ConnectionSocket::write( const char * buffer, int length)
{
// MSG_NOSIGNAL does not allow send() to emit signals on error, it will
// just return result.
ssize_t bytesSent = ::send(mSocketFD, buffer, length, MSG_NOSIGNAL);
if (bytesSent != length) {
// int errsv = errno;
// LOG(ERROR) << "send() has sent " << bytesSent << "bytes instead of "
// << length << " bytes, errno=" << errsv;
}
return bytesSent;
}
int ConnectionSocket::read(char* buffer, size_t length)
{
// MSG_NOSIGNAL does not allow recv() to emit signals on error, it will
// just return result.
int bytesRead = ::recv(mSocketFD, buffer, length, MSG_NOSIGNAL);
return bytesRead;
}
void ConnectionSocket::nonblocking()
{
fcntl(mSocketFD,F_SETFL,0);
}
void ConnectionSocket::blocking()
{
fcntl(mSocketFD,F_SETFL,0);
}
void ConnectionSocket::close()
{
// shutdown() forces all selects which are blocked on this socket to return
::shutdown(mSocketFD, SHUT_RDWR);
::close(mSocketFD);
}
std::ostream& operator<<(std::ostream& os, const ConnectionSocket& conn)
{
os << "(socket=" << conn.mSocketFD << ")";
// TODO:: Add host/port information to output.
return os;
}
ConnectionSocketTCP::ConnectionSocketTCP(int port, const char* address)
: ConnectionSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
{
if (!resolveAddress(&mRemote, address, port)) {
mRemote.sin_family = AF_UNSPEC;
}
}
int ConnectionSocketTCP::connect()
{
return ::connect(mSocketFD, (sockaddr*)&mRemote, sizeof(mRemote));
}
int ConnectionSocketUnix::connect()
{
return ::connect(mSocketFD, (sockaddr*)&mRemote, sizeof(mRemote));
}
ConnectionServerSocket::ConnectionServerSocket(int af, int type, int protocol)
: mSocketFD(-1)
{
// Create the socket
mSocketFD = ::socket(af, type, protocol);
if (mSocketFD < 0) {
// int errsv = errno;
// LOG(ERROR) << "socket() failed with errno=" << errsv;
mSocketFD = -1;
}
}
bool ConnectionServerSocket::bindInternal(const sockaddr *addr, int addrlen,
int connectionQueueSize)
{
int error;
error = ::bind(mSocketFD, addr, addrlen);
if (error < 0) {
// int errsv = errno;
// LOG(ERROR) << "bind() to port " << bindPort<< " failed with errnp=" << errsv;
close();
return false;
}
// Setup the queue for connection requests
error = ::listen(mSocketFD, connectionQueueSize);
if (error < 0) {
// int errsv = errno;
// LOG(ERROR) << "listen() failed with errno=" << errsv;
close();
return false;
}
return true;
}
int ConnectionServerSocket::acceptInternal(sockaddr *addr, socklen_t &addrlen)
{
// Wait for a client to connect.
int newSocketFD = ::accept(mSocketFD, addr, &addrlen);
if (newSocketFD < 0) {
// int errsv = errno;
// LOG(INFO) << "accept() failed with errno=" << errsv;
mSocketFD = -1;
}
return newSocketFD;
}
void ConnectionServerSocket::close()
{
// Under Linux we should call shutdown first to unblock blocking accept().
::shutdown(mSocketFD, SHUT_RDWR);
::close(mSocketFD);
}
ConnectionServerSocketTCP::ConnectionServerSocketTCP(int bindPort,
const char* bindAddress)
: ConnectionServerSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
{
const int one = 1;
if (setsockopt(mSocketFD, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one))) {
// int errsv = errno;
// LOG(ERROR) << "socket() failed with errno=" << errsv;
}
#ifdef __APPLE__
// We use SO_NOSIGPIPE here because MSG_NOSIGNAL is not supported
// for the write() call under MacOs X.
if (::setsockopt(mSocketFD, SOL_SOCKET, SO_NOSIGPIPE, (char *)&one, sizeof(one))) {
// int errsv = errno;
// LOG(ERROR) << "setsockopt() failed with errno=" << errsv;
close();
return false;
}
#endif
if (!resolveAddress(&mLocal, bindAddress, bindPort)) {
mLocal.sin_family = AF_UNSPEC;
}
}
bool ConnectionServerSocketTCP::bind(int connectionQueueSize)
{
return bindInternal((sockaddr*)&mLocal, sizeof(mLocal), connectionQueueSize);
}
ConnectionSocket* ConnectionServerSocketTCP::accept()
{
sockaddr_in newConnectionAddr;
socklen_t newConnectionAddrLen = sizeof(newConnectionAddr);
int newSocketFD = acceptInternal((sockaddr*)&newConnectionAddr, newConnectionAddrLen);
if (newSocketFD < 0) {
// int errsv = errno;
// LOG(INFO) << "accept() failed with errno=" << errsv;
return NULL;
}
return new ConnectionSocketTCP(newSocketFD, newConnectionAddr);
}
ConnectionServerSocketUnix::ConnectionServerSocketUnix(const std::string &path)
: ConnectionServerSocket(AF_UNIX, SOCK_STREAM, 0)
, mBound(false)
{
mLocal.sun_family = AF_UNIX;
strncpy(mLocal.sun_path, path.data(), 108);
mLocal.sun_path[108-1] = '\0';
}
bool ConnectionServerSocketUnix::bind(int connectionQueueSize)
{
ConnectionSocketUnix testSocket(mLocal.sun_path);
if (testSocket.connect() == 0) {
// Socket is alive. Return error - we can't bind.
return false;
} else {
// Socket is not alive. Remove its file entry to avoid EINVAL.
::unlink(mLocal.sun_path);
}
mBound = bindInternal((sockaddr*)&mLocal, sizeof(mLocal), connectionQueueSize);
return mBound;
}
ConnectionSocket* ConnectionServerSocketUnix::accept()
{
sockaddr_un newConnectionAddr;
socklen_t newConnectionAddrLen = sizeof(newConnectionAddr);
int newSocketFD = acceptInternal((sockaddr*)&newConnectionAddr, newConnectionAddrLen);
if (newSocketFD < 0) {
// int errsv = errno;
// LOG(INFO) << "accept() failed with errno=" << errsv;
return NULL;
}
return new ConnectionSocketUnix(newSocketFD, newConnectionAddr);
}
// vim: ts=4 sw=4

View File

@ -33,10 +33,9 @@
#include <sys/un.h>
#include <errno.h>
#include <list>
#include <ostream>
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
@ -182,6 +181,233 @@ public:
};
/** A base class for connection-oriented sockets. */
class ConnectionSocket {
public:
/** An almost-does-nothing constructor. */
ConnectionSocket(int af, int type, int protocol);
protected:
/** Constructor for the use with ConnectionServerSocket. */
ConnectionSocket(int socketFD) : mSocketFD(socketFD) {}
public:
virtual ~ConnectionSocket();
/**
Connect to the specified destination.
@return Same as system connect() function - 0 on success, -1 on error.
*/
virtual int connect() =0;
/**
Send data.
@param buffer The data bytes to send.
@param length Number of bytes to send.
@return number of bytes written, or -1 on error.
*/
int write( const char * buffer, int length);
/**
Receive data.
@param buffer A buffer to read data to.
@param length Length of the buffer.
@return The number of bytes received or -1 on non-blocking pass.
*/
int read(char* buffer, size_t length);
/** Make the socket non-blocking. */
void nonblocking();
/** Make the socket blocking (the default). */
void blocking();
/** Close the socket. */
void close();
friend std::ostream& operator<<(std::ostream& os, const ConnectionSocket& conn);
friend class ConnectionServerSocket;
protected:
int mSocketFD; ///< underlying file descriptor
};
class ConnectionSocketTCP : public ConnectionSocket {
friend class ConnectionServerSocketTCP;
public:
/** Constructor */
ConnectionSocketTCP(const struct sockaddr_in &remote)
: ConnectionSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
, mRemote(remote)
{}
/** Constructor */
ConnectionSocketTCP(int port, const char* address);
protected:
/** Constructor for the use with ConnectionServerSocket. */
ConnectionSocketTCP(int socketFD, const sockaddr_in &peerAddr)
: ConnectionSocket(socketFD), mRemote(peerAddr) {}
public:
/**
Connect to the specified destination.
@return Same as system connect() function - 0 on success, -1 on error.
*/
virtual int connect();
protected:
sockaddr_in mRemote;
};
class ConnectionSocketUnix : public ConnectionSocket {
friend class ConnectionServerSocketUnix;
public:
/** Constructor */
ConnectionSocketUnix(const struct sockaddr_un &remote)
: ConnectionSocket(AF_UNIX, SOCK_STREAM, 0)
, mRemote(remote)
{}
/** Constructor */
ConnectionSocketUnix(const std::string &path)
: ConnectionSocket(AF_UNIX, SOCK_STREAM, 0)
{
mRemote.sun_family = AF_UNIX;
strncpy(mRemote.sun_path, path.data(), 108);
mRemote.sun_path[108-1] = '\0';
}
protected:
/** Constructor for the use with ConnectionServerSocket. */
ConnectionSocketUnix(int socketFD, const sockaddr_un &peerAddr)
: ConnectionSocket(socketFD), mRemote(peerAddr) {}
public:
/**
Connect to the specified destination.
@return Same as system connect() function - 0 on success, -1 on error.
*/
virtual int connect();
protected:
sockaddr_un mRemote;
};
/** Abstract class for a server side of connection-oriented sockets. */
class ConnectionServerSocket {
public:
/** Constructor - creates a socket. */
ConnectionServerSocket(int af, int type, int protocol);
virtual ~ConnectionServerSocket() {};
/**
Perform bind.
@return True on success, false otherwise.
*/
virtual bool bind(int connectionQueueSize = 16) =0;
/**
Wait for the next incoming connection request.
@return Incoming connection or NULL on error.
*/
virtual ConnectionSocket* accept() =0;
/** Close the socket. */
void close();
protected:
int mSocketFD; ///< underlying file descriptor
/**
Bind to given address.
@return True on success, false otherwise.
*/
bool bindInternal(const sockaddr *addr, int addrlen, int connectionQueueSize = 16);
/**
Wait for the next incoming connection request.
@return Same as system accept() - descriptor on success, -1 on error.
*/
int acceptInternal(sockaddr *addr, socklen_t &addrlen);
};
class ConnectionServerSocketTCP : public ConnectionServerSocket {
public:
enum {
DEFAULT_PORT = 0 ///< Use this value for bindPort to let OS select port for you.
};
/** Constructor */
ConnectionServerSocketTCP(int bindPort, const char* bindAddress);
/** Destructor */
virtual ~ConnectionServerSocketTCP() {close();}
/**
Perform bind.
@return True on success, false otherwise.
*/
virtual bool bind(int connectionQueueSize = 16);
/**
Wait for the next incoming connection request.
@return Incoming connection or NULL on error.
*/
virtual ConnectionSocket* accept();
protected:
sockaddr_in mLocal;
};
class ConnectionServerSocketUnix : public ConnectionServerSocket {
public:
/** Constructor */
ConnectionServerSocketUnix(const std::string &path);
/** Destructor */
virtual ~ConnectionServerSocketUnix() {
close();
if (mBound) ::unlink(mLocal.sun_path);
}
/**
Perform bind.
@return True on success, false otherwise.
*/
virtual bool bind(int connectionQueueSize = 16);
/**
Wait for the next incoming connection request.
@return Incoming connection or NULL on error.
*/
virtual ConnectionSocket* accept();
protected:
sockaddr_un mLocal;
bool mBound; ///< Set to true if we successfully bound the socket.
};
#endif

View File

@ -59,6 +59,3 @@ const char* gOpenBTSWelcome =
"All users of this software are expected to comply with applicable\n"
"regulations and laws.\n"
;
CommandLine::Parser gParser;

View File

@ -31,7 +31,6 @@
#define GLOBALS_H
#include <Configuration.h>
#include <CLI.h>
/**
Just about everything goes into the configuration table.
@ -42,7 +41,4 @@ extern ConfigurationTable gConfig;
/** The OpenBTS welcome message. */
extern const char* gOpenBTSWelcome;
/** The central parser. */
extern CommandLine::Parser gParser;
#endif

View File

@ -49,6 +49,7 @@ TRX_LA = $(top_builddir)/TRXManager/libtrxmanager.la
CONTROL_LA = $(top_builddir)/Control/libcontrol.la
GLOBALS_LA = $(top_builddir)/Globals/libglobals.la
CLI_LA = $(top_builddir)/CLI/libcli.la
CLI_COMMANDS_LA = $(top_builddir)/CLI/libcli_commands.la
HLR_LA = $(top_builddir)/HLR/libHLR.la
#SQL_LA = $(top_builddir)/SQL/libSQL.la

View File

@ -24,7 +24,8 @@ AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
AM_CXXFLAGS = -Wall -g -pthread
noinst_PROGRAMS = \
OpenBTS
OpenBTS \
OpenBTScli
OpenBTS_SOURCES = OpenBTS.cpp
OpenBTS_LDADD = \
@ -36,10 +37,17 @@ OpenBTS_LDADD = \
$(TRX_LA) \
$(CONTROL_LA) \
$(SMS_LA) \
$(CLI_COMMANDS_LA) \
$(CLI_LA) \
$(OSIP_LIBS) \
$(ORTP_LIBS)
OpenBTScli_SOURCES = OpenBTScli.cpp
OpenBTScli_LDADD = \
$(GLOBALS_LA) \
$(COMMON_LA) \
$(CLI_LA)
EXTRA_DIST = \
OpenBTS.config.example \

View File

@ -514,8 +514,33 @@ GSM.MaxSpeechLatency 2
# CLI paramters
#
# A string, used as a CLI prompt
CLI.Prompt OpenBTS>
# CLI type has three possible values:
# Local - command line is provided by the same process.
# Useful when you don't daemonize OpenBTS process.
# Unix - command line is provided over a UNIX-socket.
# Useful for local access to command line when you run
# OpenBTS as a daemon.
# TCP - command line is provided by a TCP connection.
# Useful when you want to control your OpenBTS remotely.
#
# !!! WARNING !!!
# Current implementation of remote CLI doesn't support authentication
# and encryption. Use remote CLI with care!
CLI.Type Local
# Parameters for TCP remote CLI.
# Interface to bind to:
CLI.TCP.IP 127.0.0.1
$static CLI.TCP.IP
# Port to bind to:
CLI.TCP.Port 22239
$static CLI.TCP.Port
# Parameters for UNIX remote CLI.
# Path to the socket
CLI.Unix.Path /tmp/OpenBTScli.socket
$static CLI.Unix.Path

View File

@ -37,6 +37,8 @@
#include <Logger.h>
#include <CLI.h>
#include <CLIServer.h>
#include <CLIParser.h>
#include <PowerManager.h>
#include <RRLPQueryController.h>
#include <Configuration.h>
@ -46,14 +48,9 @@
#include <string.h>
#include <signal.h>
#ifdef HAVE_LIBREADLINE // [
//# include <stdio.h>
# include <readline/readline.h>
# include <readline/history.h>
#endif // HAVE_LIBREADLINE ]
using namespace std;
using namespace GSM;
using namespace CommandLine;
// Load configuration from a file.
ConfigurationTable gConfig("OpenBTS.config");
@ -107,21 +104,10 @@ void restartTransceiver()
}
int main(int argc, char *argv[])
void startBTS()
{
srandom(time(NULL));
COUT("\n\n" << gOpenBTSWelcome << "\n");
COUT("\nStarting the system...");
if (gConfig.defines("Log.FileName")) {
gSetLogFile(gConfig.getStr("Log.FileName"));
}
if (gConfig.defines("Control.TMSITable.SavePath")) {
gTMSITable.load(gConfig.getStr("Control.TMSITable.SavePath"));
}
@ -196,9 +182,9 @@ int main(int argc, char *argv[])
// C-V C0T0 SDCCHs
SDCCHLogicalChannel C0T0SDCCH[4] = {
SDCCHLogicalChannel(0,gSDCCH_4_0),
SDCCHLogicalChannel(0,gSDCCH_4_1),
SDCCHLogicalChannel(0,gSDCCH_4_2),
SDCCHLogicalChannel(0,gSDCCH_4_3),
SDCCHLogicalChannel(0,gSDCCH_4_1),
SDCCHLogicalChannel(0,gSDCCH_4_2),
SDCCHLogicalChannel(0,gSDCCH_4_3),
};
Thread C0T0SDCCHControlThread[4];
for (int i=0; i<4; i++) {
@ -212,22 +198,22 @@ int main(int argc, char *argv[])
unsigned sCount = 1;
bool halfDuplex = gConfig.defines("GSM.HalfDuplex");
if (halfDuplex) LOG(NOTICE) << "Configuring for half-duplex operation.";
else LOG(NOTICE) << "Configuring for full-duplex operation.";
if (halfDuplex) { LOG(NOTICE) << "Configuring for half-duplex operation." ; }
else { LOG(NOTICE) << "Configuring for full-duplex operation."; }
if (halfDuplex) sCount++;
if (halfDuplex) sCount++;
// Create C-VII slots.
for (int i=0; i<gConfig.getNum("GSM.NumC7s"); i++) {
gBTS.createCombinationVII(gTRX,sCount/8,sCount);
if (halfDuplex) sCount++;
if (halfDuplex) sCount++;
sCount++;
}
// Create C-I slots.
for (int i=0; i<gConfig.getNum("GSM.NumC1s"); i++) {
gBTS.createCombinationI(gTRX,sCount/8,sCount);
if (halfDuplex) sCount++;
if (halfDuplex) sCount++;
sCount++;
}
@ -235,7 +221,7 @@ int main(int argc, char *argv[])
// Set up idle filling on C0 as needed.
while (sCount<8) {
gBTS.createCombination0(gTRX,sCount/8,sCount);
if (halfDuplex) sCount++;
if (halfDuplex) sCount++;
sCount++;
}
@ -260,74 +246,11 @@ int main(int argc, char *argv[])
// OK, now it is safe to start the BTS.
gBTS.start();
#ifdef HAVE_LIBREADLINE // [
// start console
using_history();
static const char * const history_file_name = "/.openbts_history";
char *history_name = 0;
char *home_dir = getenv("HOME");
if(home_dir) {
size_t home_dir_len = strlen(home_dir);
size_t history_file_len = strlen(history_file_name);
size_t history_len = home_dir_len + history_file_len + 1;
if(history_len > home_dir_len) {
if(!(history_name = (char *)malloc(history_len))) {
LOG(ERROR) << "malloc failed: " << strerror(errno);
exit(-1);
}
memcpy(history_name, home_dir, home_dir_len);
memcpy(history_name + home_dir_len, history_file_name,
history_file_len + 1);
read_history(history_name);
}
}
#endif // HAVE_LIBREADLINE ]
LOG(INFO) << "system ready";
COUT("\n\nWelcome to OpenBTS. Type \"help\" to see available commands.");
// FIXME: We want to catch control-d (emacs keybinding for exit())
// The logging parts were removed from this loop.
// If we want them back, they will need to go into their own thread.
while (1) {
#ifdef HAVE_LIBREADLINE // [
char *inbuf = readline(gConfig.getStr("CLI.Prompt"));
if (!inbuf) break;
if (*inbuf) {
add_history(inbuf);
// The parser returns -1 on exit.
if (gParser.process(inbuf, cout, cin)<0) {
free(inbuf);
break;
}
}
free(inbuf);
#else // HAVE_LIBREADLINE ][
cout << endl << gConfig.getStr("CLI.Prompt");
cout.flush();
char inbuf[1024];
cin.getline(inbuf,1024,'\n');
// The parser returns -1 on exit.
if (gParser.process(inbuf,cout,cin)<0) break;
#endif // !HAVE_LIBREADLINE ]
}
#ifdef HAVE_LIBREADLINE // [
if(history_name) {
int e = write_history(history_name);
if(e) {
fprintf(stderr, "error: history: %s\n", strerror(e));
}
free(history_name);
history_name = 0;
}
#endif // HAVE_LIBREADLINE ]
}
void stopBTS()
{
if (gConfig.defines("Control.TMSISavePath")) {
gTMSITable.save(gConfig.getStr("Control.TMSISavePath"));
}
@ -335,4 +258,32 @@ int main(int argc, char *argv[])
if (gTransceiverPid) kill(gTransceiverPid, SIGKILL);
}
int main(int argc, char *argv[])
{
srandom(time(NULL));
COUT("\n\n" << gOpenBTSWelcome << "\n");
if (gConfig.defines("Log.FileName")) {
gSetLogFile(gConfig.getStr("Log.FileName"));
}
// startBTS();
if (strcasecmp(gConfig.getStr("CLI.Type"),"TCP") == 0) {
ConnectionServerSocketTCP serverSock(gConfig.getNum("CLI.TCP.Port"),
gConfig.getStr("CLI.TCP.IP"));
runCLIServer(&serverSock);
} else if (strcasecmp(gConfig.getStr("CLI.Type"),"Unix") == 0) {
ConnectionServerSocketUnix serverSock(gConfig.getStr("CLI.Unix.Path"));
runCLIServer(&serverSock);
} else {
runCLI(&gParser);
}
// stopBTS();
}
// vim: ts=4 sw=4

View File

@ -0,0 +1,64 @@
/*
* Copyright 2010 Free Software Foundation, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <Globals.h>
#include <Sockets.h>
#include <Threads.h>
#include <Logger.h>
#include <CLIClient.h>
using namespace std;
using namespace CommandLine;
// Load configuration from a file.
ConfigurationTable gConfig("OpenBTS.config");
int main(int argc, char * argv[])
{
srandom(time(NULL));
COUT(endl << endl << gOpenBTSWelcome << endl);
gLogInit("INFO");
// if (gConfig.defines("Log.FileName")) {
// gSetLogFile(gConfig.getStr("Log.FileName"));
// }
if (strcasecmp(gConfig.getStr("CLI.Type"),"TCP") == 0) {
ConnectionSocketTCP sock(gConfig.getNum("CLI.TCP.Port"),
gConfig.getStr("CLI.TCP.IP"));
runCLIClient(&sock);
} else if (strcasecmp(gConfig.getStr("CLI.Type"),"Unix") == 0) {
ConnectionSocketUnix sock(gConfig.getStr("CLI.Unix.Path"));
runCLIClient(&sock);
} else {
COUT(endl << "ERROR -- OpenBTS not configured to use remote CLI" << endl);
}
}
// vim: ts=4 sw=4