/** * @class NetworkDiscovery * * @brief the logic behind the NetworkDiscovery. * * This class holds all the logic of the NetworkDiscovery. It's main task is to search for usable * interfaces, check if they are wired (in Running state) and start a cdhcpcd process for each interface. * It also sends signals to the ndgui class for presenting notifications to the user. * */ #include #include #include #include "networkdiscovery.h" #include #include "qlog4cxx.h" using namespace log4cxx; using namespace log4cxx::helpers; LoggerPtr ndcLogger(Logger::getLogger("fbgui.nd.core")); /** * constructor */ NetworkDiscovery::NetworkDiscovery(QObject *parent) { } /** * destructor */ NetworkDiscovery::~NetworkDiscovery() { delete _networkManager; delete _server; foreach(QProcess* p, _clientProcessToIfNameMap.keys()) { delete p; } foreach(InterfaceConfiguration* i, _ifcMap.values()) { delete i; } } /** * initialize all important class members and start the main work. * * @param userChoice * true if the user wishes to have a user choice. true: the chooseInterfaceDialog will be showed. * * @param args * additional arguments for the customdhcpcd client. (default value: NULL) */ void NetworkDiscovery::initAndRun(bool userChoice, QStringList* args) { _userChoice = userChoice; _blocked = false; _numberOfProcesses = 0; _ifUpCountdown = 10; _errorStr = ""; _networkManager = new NetworkManager(); _clientProcessToIfNameMap.clear(); _clients.clear(); _dhcpcdArguments.clear(); _ifDownList.clear(); _ifNameToClient.clear(); _ifUpList.clear(); _ifcMap.clear(); _server = new QLocalServer(); if (gSocketServerPath != DEFAULT_QTSOCKETADDRESS) { _dhcpcdArguments.append("-q"); _dhcpcdArguments.append(gSocketServerPath); } /* delete the file at serverPath. this is necessary since in case the application crashes, the file still * exists which leads to an error. */ if (QFile::exists(gSocketServerPath)) { QFile::remove(gSocketServerPath); } emit updateStatus("try to create server socket"); if (!_server->listen(gSocketServerPath)) { // emit signal to the gui that a critial error occoured LOG4CXX_DEBUG(ndcLogger, "Unable to start server: " << _server->errorString()); emit abortBoot("Unable to start server: " + _server->errorString()); return; } killDHCPCD(); // check if the path to the customdhcpcd file is correct emit updateStatus("check if cdhcpcd is available"); QFileInfo fInfo(gPathToDhcpExe); if (!fInfo.exists()) { LOG4CXX_DEBUG( ndcLogger, "could not find customdhcpcd exe. Please check the path to this file."); emit abortBoot( "could not find customdhcpcd exe. Please check the path to this file."); return; } connect(_server, SIGNAL(newConnection()), this, SLOT(handleNewConnection())); if (args != NULL && !args->isEmpty()) { LOG4CXX_DEBUG(ndcLogger, "added additional args"); _dhcpcdArguments.append(*args); } emit updateStatus("start mainwork"); mainWork(); } //------------------------------------------------------------------------- // Main Network Discovery Flow //------------------------------------------------------------------------- /** * @brief the main work. Here we start with searching for usable interfaces and check the IsRunning state. * * the main work. Here we start with searching for usable interfaces and check the IsRunning state. * check every second the IsRunning state. Do this as long the counter (@see _ifUpCountdown) is greater than 0. * Default: _ifUpCountdown = 10. */ void NetworkDiscovery::mainWork() { gAutoUp ? emit updateStatus("search for usable interfaces (with auto Up)") : emit updateStatus("search for usable interfaces"); getListOfNetworkInterfaces(); emit updateStatus("check if interfaces are in running state"); _timer = new QTimer(this); connect(_timer, SIGNAL(timeout()), this, SLOT(checkForIsRunning())); _timer->start(1000); } /** * searches for usable interfaces which are up and running and put them into a list. * usable interfaces are: can Broadcast, no loopback, no point to point, name is not in the BlackList, */ void NetworkDiscovery::getListOfNetworkInterfaces() { QList nIList = QNetworkInterface::allInterfaces(); _ifUpList.clear(); _ifDownList.clear(); if (nIList.size() > 0) { foreach(QNetworkInterface nI, nIList) { LOG4CXX_DEBUG(ndcLogger, "found Interface:" << nI.humanReadableName()); if ((nI.flags() & QNetworkInterface::CanBroadcast)) { LOG4CXX_DEBUG(ndcLogger, "flags: can broadcast "); } if ((nI.flags() & QNetworkInterface::IsLoopBack)) { LOG4CXX_DEBUG(ndcLogger, "flags: is LoopBack "); } if ((nI.flags() & QNetworkInterface::IsPointToPoint)) { LOG4CXX_DEBUG(ndcLogger, "flags: is Point to Point "); } if ((nI.flags() & QNetworkInterface::IsRunning)) { LOG4CXX_DEBUG(ndcLogger, "flags: is Running "); } if ((nI.flags() & QNetworkInterface::IsUp)) { LOG4CXX_DEBUG(ndcLogger, "flags: is Up "); } if (((!(nI.flags() & QNetworkInterface::CanBroadcast) || nI.flags() & QNetworkInterface::IsLoopBack) || nI.flags() & QNetworkInterface::IsPointToPoint) || (gAutoUp && !(nI.flags() & QNetworkInterface::IsUp)) || checkBlackList(nI.humanReadableName())) { continue; } if ((nI.flags() & QNetworkInterface::IsRunning)) { _ifUpList.append(nI.humanReadableName()); } else if (gAutoUp && !(nI.flags() & QNetworkInterface::IsUp)) { _networkManager->bringInterfaceUP(nI.humanReadableName()); LOG4CXX_DEBUG( ndcLogger, "interface is down, try to bring up: " << nI.humanReadableName()); _ifDownList.append(nI.humanReadableName()); } else if (!(nI.flags() & QNetworkInterface::IsRunning)) { _ifDownList.append(nI.humanReadableName()); } } } else { LOG4CXX_DEBUG(ndcLogger, "no interfaces found! "); } } /** * This Method implements a blacklist. * * This Method implements a blacklist. We check the fist character * of the interface name. if this letter is in the list, we return true. * True means, that this interface won't be put into the result list of * getListOfNetworkInterfaces(). * * @param i * is a interface name. * * @return bool * returns true: if the interface name i starts with a letter in the blacklist. * * returns false: else * * @see NetworkDiscovery::getListOfNetworkInterfaces() */ bool NetworkDiscovery::checkBlackList(QString i) { return (i.startsWith("v", Qt::CaseInsensitive) || i.startsWith("d", Qt::CaseInsensitive)); } /** * only called if autoUp == true. * check the IsRunning flag of each interface in the _ifDownList. * connected to the timeout signal of the timer. */ void NetworkDiscovery::checkForIsRunning() { bool isRunning = false; foreach(QString i, _ifDownList) { QNetworkInterface networkInterface = QNetworkInterface::interfaceFromName( i); isRunning = (networkInterface.flags() & QNetworkInterface::IsRunning); if (isRunning) { _ifUpList.append(i); _ifDownList.removeAt(_ifDownList.indexOf(i)); } } _ifUpCountdown--; if ((_ifUpCountdown <= 0) || _ifDownList.isEmpty()) { // shut down timer _timer->stop(); delete _timer; readyForRun(); } } /** * emits the addInterface signal for each interface name in _ifUpList * and calls the runDHCPCD method. * if the _ifUpList is empty, this method emits an abortBoot signal. * connected to the readyForRun signal. */ void NetworkDiscovery::readyForRun() { if (_ifUpList.size() > 0) { foreach(QString i, _ifUpList) { LOG4CXX_DEBUG(ndcLogger, "emit addInterface : " << i); emit addInterface(i); } _numberOfProcesses = _ifUpList.size(); emit updateStatus("start dhcp client for each interface"); runDHCPCD(_ifUpList); } else { LOG4CXX_DEBUG(ndcLogger, "list is empty. Have not found usable interface. "); emit foreach(QString i, _ifDownList) { LOG4CXX_DEBUG(ndcLogger, "" << i << " is not in running state. (check cable)"); } abortBoot( "All interfaces are not usable. (e.g. please check if all network cables are plugged in. Read the log for more informations.)"); return; } } //------------------------------------------------------------------------- // DHCPCD Control //------------------------------------------------------------------------- /** * call for every interface in the list the runDHCPCD method. * * @param interfaces * list of interface names. */ void NetworkDiscovery::runDHCPCD(QList &interfaces) { foreach(QString nI, interfaces) { runDHCPCD(nI); } } /** * start a cdhcpcd process with the interface name as last argument. * * @param interface * name of an interface. */ void NetworkDiscovery::runDHCPCD(QString interface) { emit updateIfStatus(interface, "start DHCP"); _dhcpcdArguments.append(interface); QProcess * p = new QProcess(this); LOG4CXX_DEBUG(ndcLogger, "start cdhcpcd with arguments: " << _dhcpcdArguments.join(", ")); _clientProcessToIfNameMap.insert(p, interface); p->start(gPathToDhcpExe, _dhcpcdArguments); connect(p, SIGNAL(started()), this, SLOT(handleProcessStarted())); connect(p, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(handleProcessFinished(int, QProcess::ExitStatus))); _dhcpcdArguments.removeLast(); } /** * This Method is called when a process is started. * * This Method is called when a process is started. * It prints the message: "process started for interface: ". */ void NetworkDiscovery::handleProcessStarted() { QProcess* p = qobject_cast(QObject::sender()); QString ifName = _clientProcessToIfNameMap.value(p, "ifName"); LOG4CXX_DEBUG(ndcLogger, "process started for interface: " << ifName); } /** * This Method is called when a process is finished. * * This Method is called when a process is finished. This slot is connected * with the signal finished() of the QProcess class. * If the process finishes, it will be checked if the process exited normal * or if an unexpected error occurred. For this, we determine the sender (which is a * QProcess), get the corresponding interface (which is stored in a map), and check * the exitCode. Further actions are taken according to the exitCode check. * Normal exit: * emit changeProgressBar() to 100% * emit updateIfStatus() to check connection * checkConnectivity() @see NetworkDiscovery::checkConnectivity() * Unexpected exit: * emit updateIfStatus() to process exited unexpected * * @param exitCode * * @param exitStatus * * @return bool * returns true: if the interface name i starts with a letter in the blacklist. * * returns false: else * * @see NetworkDiscovery::getListOfNetworkInterfaces() */ void NetworkDiscovery::handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { QProcess* p = qobject_cast(QObject::sender()); QString ifName = _clientProcessToIfNameMap.value(p, "ifName"); _numberOfProcesses = _numberOfProcesses - 1; if (!_blocked) { //_blocked becomes true, if _userChoice is false and we already found a usable interface if (ifName.compare("ifName") == 0) { LOG4CXX_DEBUG(ndcLogger, "haven't found process!"); } else { LOG4CXX_DEBUG( ndcLogger, "process for interface " << ifName << " finished " << " exit code: " << exitCode << " exit status " << exitStatus); if (exitCode > 0) { LOG4CXX_DEBUG(ndcLogger, "process exited unexpected: " << p->errorString()); emit updateIfStatus(ifName, "process exited unexpected" + p->errorString()); } else { LOG4CXX_DEBUG(ndcLogger, "process normal exit "); emit changeProgressBarValue(ifName, 100); emit updateIfStatus(ifName, "check connectivity"); if (checkConnectivity(ifName)) { emit connectionEstablished(ifName); if (!_userChoice) { // blockiere jeden weiteren check _blocked = true; emit allProcessesFinished(); } } } } if (!_blocked) { //_blocked becomes true, if _userChoice is false and we found a usable interface QLocalSocket *client = _ifNameToClient.value(ifName, 0); if (client != 0) { handleNewInput(client); } //_numberOfProcesses = _numberOfProcesses - 1; && _userChoice if (_numberOfProcesses <= 0) { emit allProcessesFinished(); } } } else { LOG4CXX_DEBUG(ndcLogger, "already blocked"); emit updateIfStatus(ifName, "finished DHCP"); } } /**/ void NetworkDiscovery::killDHCPCD() { LOG4CXX_DEBUG(ndcLogger, "kill cdhcpcd processes"); QProcess * p = new QProcess(this); p->start("killall cdhcpcd"); p->waitForFinished(); if (p->exitCode() > 0) LOG4CXX_DEBUG(ndcLogger, "[tryAgain] " << p->errorString()); delete p; } //------------------------------------------------------------------------- // Connectivity Testing //------------------------------------------------------------------------- /** * checks the connectivity. tries to open a TCP connection to the * server (see _serverIp). For this it adjusts the routing table. * (sets the gateway of the interface as default gateway) * Gateway is written into the dhcpcd config file of this interface. * (see DEFAULT_INTERFACE_CONF_LOCATION "/var/tmp/conf_") * * @param ifName * name of a interface. * * @return * true: connection is possible * false: connection not possible */ bool NetworkDiscovery::checkConnectivity(QString ifName) { int mss = 0; // get gateway address QString pathToGatewayFile(DEFAULT_INTERFACE_CONF_LOCATION); pathToGatewayFile += ifName; InterfaceConfiguration* ifConf; if (!_ifcMap.contains(ifName)) { ifConf = new InterfaceConfiguration(); _ifcMap.insert(ifName, ifConf); } else { ifConf = _ifcMap.value(ifName); } ifConf->readConfigOutOfFile(pathToGatewayFile); // replace default route LOG4CXX_DEBUG(ndcLogger, "replace default route"); _networkManager->replaceDefaultRoute(ifName, ifConf->getGateway(), mss, AF_INET); if (checkConnectivityViaTcp(gServerIp)) { LOG4CXX_DEBUG(ndcLogger, "passed connectivity check! for interface " << ifName); emit updateIfStatus(ifName, "connection possible"); return true; } else { LOG4CXX_DEBUG(ndcLogger, "failed connectivity check! for interface " << ifName); emit updateIfStatus(ifName, "connection not possible"); return false; } } /**/ bool NetworkDiscovery::checkConnectivityViaTcp() { return checkConnectivityViaTcp(gServerIp); } /** * try to open a tcp connection to the server * * @param server * a ip address. * * @return * true: connection is possible * false: connection not possible */ bool NetworkDiscovery::checkConnectivityViaTcp(QString server) { // check connectivity via tcp connection LOG4CXX_DEBUG(ndcLogger, "check connectivity to server: " << server); // do host lookup QTcpSocket *tcpSocket = new QTcpSocket(this); //LOG4CXX_DEBUG(ndcLogger, "hostInfo first address: " << hostInfo.addresses().first().toString()); //hostInfo.addresses().first() tcpSocket->connectToHost(server, 80); if (!tcpSocket->waitForConnected(500)) { LOG4CXX_DEBUG(ndcLogger, tcpSocket->errorString()); _errorStr += tcpSocket->errorString(); return false; } else { return true; } delete tcpSocket; } //------------------------------------------------------------------------- // Manual Configuration //------------------------------------------------------------------------- /** * replace the dhcp configuration with the manual config, entered by the user. * if we can not establish a connection with the entered values, reset to the old * dhcp values. * * @param result * a json object formated string. * * @return * 0 if everything ok */ int NetworkDiscovery::ip4_setManualConfiguration(QVariantMap result) { QList dns; dns.append(result["dns"].toString()); _networkManager->ip4_setManualConfiguration(result["ifname"].toString(), result["ipaddr"].toString(), result["netmask"].toString(), result["broadcast"].toString(), result["gateway"].toString(), 0, AF_INET, "/etc/", dns); LOG4CXX_DEBUG(ndcLogger, "set man conf. and check connectivity"); if (!checkConnectivityViaTcp(gServerIp)) { LOG4CXX_DEBUG(ndcLogger, "no connectivity. reset conf."); InterfaceConfiguration * ifc = _ifcMap.value(result["ifname"].toString(), NULL); if (ifc != NULL) { dns.clear(); dns = ifc->getDnsservers().trimmed().split(" "); _networkManager->ip4_setManualConfiguration( result["ifname"].toString(), ifc->getIpAddress(), ifc->getNetmask(), ifc->getBroadcast(), ifc->getGateway(), 0, AF_INET, "/etc/", dns); } return 0; }LOG4CXX_DEBUG( ndcLogger, "emit signal continueBootWithoutCheck(" << result["ifname"].toString() << ")"); emit continueBootWithoutCheck(result["ifname"].toString()); return 0; } //------------------------------------------------------------------------- // Socket Connection Handling //------------------------------------------------------------------------- /** * connected to the new client arrived signal. * connects the client readyRead signal with the handleNewInput slot. */ void NetworkDiscovery::handleNewConnection() { LOG4CXX_DEBUG(ndcLogger, "New Connection arrived"); /*QLocalSocket **/ _client = _server->nextPendingConnection(); _clients.insert(_client, _client); connect(_client, SIGNAL(disconnected()), this, SLOT(handleClientDisconnect())); connect(_client, SIGNAL(readyRead()), this, SLOT(handleNewInput())); } /** * called when a client disconnects. */ void NetworkDiscovery::handleClientDisconnect() { QLocalSocket* socket = qobject_cast(QObject::sender()); QLocalSocket * client = _clients.value(socket); LOG4CXX_DEBUG(ndcLogger, "disconnect client"); handleNewInput(client); client->deleteLater(); } /** * same function as handleNewInput() but with a client as parameter. * * @param client * a client */ void NetworkDiscovery::handleNewInput(QLocalSocket * client) { LOG4CXX_DEBUG(ndcLogger, "last read before exit"); while (client->canReadLine()) { QString data(client->readLine()); data = data.trimmed(); if (!data.isEmpty()) LOG4CXX_DEBUG(ndcLogger, data); QStringList lines = data.split("\n"); for (int i = 0; i < lines.length(); i++) { handleNewInputLine(client, lines.at(i)); } } } /** * This method is connected to the readyRead Signal of the QLocalSocket * client. * send an ACK to the client with every received message. */ void NetworkDiscovery::handleNewInput() { QLocalSocket* socket = qobject_cast(QObject::sender()); QLocalSocket * client = _clients.value(socket); QString data(client->read(DHCP_MESSAGE_SIZE)); data = data.trimmed(); QStringList lines = data.split("\n"); for (int i = 0; i < lines.length(); i++) { handleNewInputLine(client, lines.at(i)); } } /** * This Method processes the send messages. * * This Method processes the send messages. It splits the line * into several components. Those components are: * interface: interface name ==> indicates the process who send the message * s_state: is the number representation of syslog.h LOG levels * s_subState: is the number representation of the dhcp.c DHCP states (1 - 8) plus * the status. h states (9 - ..) * msg: is a message which can contain additional informations * * According to the s_state and s_subState we emit the changeProgressBarValue() signal * with different values. * * @param client * the client who send the message * * @param data * the message. (format ;;; ) */ void NetworkDiscovery::handleNewInputLine(QLocalSocket * client, QString logMsg) { if (logMsg.trimmed().size() < 1) return; QString interface = logMsg.section(";", 0, 0); QString s_state = logMsg.section(";", 1, 1); QString s_subState = logMsg.section(";", 2, 2); QString msg = logMsg.section(";", 3, 3); int st = s_state.trimmed().toInt(); int sst = s_subState.trimmed().toInt(); //LOG4CXX_DEBUG(ndcLogger, logMsg); if (_ifNameToClient.size() < _numberOfProcesses && !_ifNameToClient.contains(interface)) { _ifNameToClient.insert(interface, client); } // st states // #define LOG_EMERG 0 /* system is unusable */ // #define LOG_ALERT 1 /* action must be taken immediately */ // #define LOG_CRIT 2 /* critical conditions */ // #define LOG_ERR 3 /* error conditions */ // #define LOG_WARNING 4 /* warning conditions */ // #define LOG_NOTICE 5 /* normal but significant condition */ // #define LOG_INFO 6 /* informational */ // #define LOG_DEBUG 7 /* debug-level messages */ QString out; QTextStream outStream(&out); switch (sst) { case DHCP_DISCOVER: emit changeProgressBarValue(interface, 10); outStream << interface << " send discover"; break; case DHCP_OFFER: emit changeProgressBarValue(interface, 20); outStream << interface << " got offer"; break; case DHCP_REQUEST: emit changeProgressBarValue(interface, 30); outStream << interface << " send request"; break; case DHCP_ACK: emit changeProgressBarValue(interface, 40); outStream << interface << " ack"; break; case DHCP_NAK: emit changeProgressBarValue(interface, 40); outStream << interface << " nak"; break; case DHCP_RELEASE: outStream << interface << " release"; break; case DHCP_INFORM: break; case DHCPCD_ARP_TEST: emit changeProgressBarValue(interface, 50); outStream << interface << " do arp test"; break; case DHCP_DECLINE: emit changeProgressBarValue(interface, 60); break; case DHCPCD_CONFIGURE: emit changeProgressBarValue(interface, 70); outStream << interface << " do configure interface"; break; case DHCPCD_WRITE: emit changeProgressBarValue(interface, 80); outStream << interface << " write conf file"; break; case DHCPCD_EXIT: emit changeProgressBarValue(interface, 100); outStream << interface << " exiting"; break; case DHCPCD_LOG: outStream << "received dhcpcd log: " << msg; break; default: outStream << "received unknown substatus: " << msg; break; } switch (st) { case LOG_INFO: LOG4CXX_INFO(ndcLogger, out); break; case LOG_NOTICE: LOG4CXX_WARN(ndcLogger, out); break; case LOG_WARNING: LOG4CXX_WARN(ndcLogger, out); break; case LOG_DEBUG: LOG4CXX_DEBUG(ndcLogger, out); break; case LOG_ERR: LOG4CXX_ERROR(ndcLogger, out); break; default: LOG4CXX_DEBUG(ndcLogger, out); break; } } //------------------------------------------------------------------------- // Public member access //------------------------------------------------------------------------- /** * @brief Return the InterfaceConfiguration for the given interface. * * @param Interface name as QString * * @return InterfaceConfiguration* */ InterfaceConfiguration* NetworkDiscovery::getInterfaceConfig(QString ifName) { return _ifcMap.value(ifName, NULL); } /** * @brief Returns the list of interfaces that are up. * * @return The QList member. */ QList NetworkDiscovery::getIfUpList() { return _ifUpList; } /**/ QString NetworkDiscovery::GetErrorStr() { return _errorStr; } //------------------------------------------------------------------------- // Bugged Methods.... //------------------------------------------------------------------------- /* *TODO: to be bug fixed *TODO: do it with kill and not QProcess("killall cdhcpcd") */ void NetworkDiscovery::tryAgain() { prepareTryAgain(); initAndRun(_userChoice); } /**/ void NetworkDiscovery::prepareTryAgain() { // kill all cdhcpcd processes killDHCPCD(); /* foreach(Q_PID pid , _pidsList) { if (kill(pid,SIGKILL) <= -1) qDebug() << " error: trying to kill process: " << pid << " error: " << strerror(errno); } */ // reset everything //delete _networkManager; //delete _server; foreach(QProcess* p, _clientProcessToIfNameMap.keys()) { delete p; } foreach(InterfaceConfiguration* i, _ifcMap.values()) { delete i; } }