#include #include #include "networkdiscovery.h" #include "../common/fbgui.h" /** * constructor */ NetworkDiscovery::NetworkDiscovery(QObject *parent) { _tag = "[nd:NetworkDiscovery]"; _server = new QLocalServer(this); } /** * destructor */ NetworkDiscovery::~NetworkDiscovery() { } /** * initialize all important class members and start the main work. * * @param serverIp * the ip of the server with which we are testing the connectivity. * * @param userChoice * true if the user wishes to have a user choice. true: the chooseInterfaceDialog will be showed. * * @param autoUp * true if we want to "auto Up" all down interfaces. * * @param pathToLogFile * the path to the log file. * * @param serverPath * the path to the server socket (default value: DEFAULT_QTSOCKETADDRESS "/var/tmp/qt_c_socket_default") * * @param pathToExe * the path to the customdhcpcd exe. (default value: #define DEFAULT_QTSOCKETADDRESS "/var/tmp/qt_c_socket_default") * * @param args * additional arguments for the customdhcpcd client. (default value: NULL) */ void NetworkDiscovery::initAndRun(QString serverIp, bool userChoice, bool autoUp, QString pathToLogFile, QString serverPath, QString pathToExe, QStringList* args) { _serverIp = serverIp; _userChoice = userChoice; _autoUp = autoUp; _pathToLogFile = pathToLogFile; _pathToDhcpcdExe = pathToExe; _blocked = false; _numberOfProcesses = 0; _ifUpCountdown = 10; if (serverPath != DEFAULT_QTSOCKETADDRESS) { _dhcpcdArguments.append("-q"); _dhcpcdArguments.append(serverPath); } /* 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(serverPath)) { QFile::remove(serverPath); } if (!_server->listen(serverPath)) { // emit signal to the gui that a critial error occoured qDebug() << _tag << "Unable to start server: " << _server->errorString(); emit abortBoot("Unable to start server: " + _server->errorString()); return; } // check if the path to the customdhcpcd file is correct QFileInfo fInfo(_pathToDhcpcdExe); if (!fInfo.exists()) { qDebug() << _tag << " 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())); connect(this, SIGNAL(readyForRun()), this, SLOT(slotReadyForRun())); if (args != NULL && !args->isEmpty()) { qDebug() << _tag << "added additional args"; _dhcpcdArguments.append(*args); } mainWork(); } /** * 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::slotReadyForRun() { if (_ifUpList.size() > 0) { foreach(QString i, _ifUpList) { emit addInterface(i); } _numberOfProcesses = _ifUpList.size(); qDebug() << _tag << "number of processes:" << _numberOfProcesses ; runDHCPCD( _ifUpList); } else { qDebug() << _tag << "list is empty. Have not found usable interface."; emit abortBoot("Have not found usable interface"); return; } } /** * 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; QList copyOfIfDownList(_ifDownList); 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(); emit readyForRun(); } } /** * replace the default route. sets af automatically to AF_INET * * @param ifName * interface name * * @param gateway * gateway address * * @param mss * mss value (i think this is the metric. in most cases this value is 0) */ int NetworkDiscovery::ip4_replaceDefaultRoute(QString ifName, QString gateway, int mss) { return _networkManager.replaceDefaultRoute(ifName, gateway, mss, AF_INET); } /** * 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); qDebug() << _tag << "set man conf. and check connectivity"; if (!checkConnectivityViaTcp(_serverIp)) { qDebug() << _tag << "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; } emit continueBoot(result["ifname"].toString(), 0); return 0; } /** * returns the gateway address, written into the dhcp config file. * * @param ifName * name of the interface. * * @return * gateway address as string. */ QString NetworkDiscovery::getGatewayForInterface(QString ifName) { interfaceconfiguration * ifConf = _ifcMap.value(ifName); return ifConf->getGateway(); } /** * reads the log file. * * @return the log file as one string. */ QString NetworkDiscovery::readLogFile() { // path to log file is in _pathToLogFile. initialized in initAndRun(). QString retval("the log file"); QFile logFile(_pathToLogFile); if (logFile.exists()) { if (logFile.open(QIODevice::ReadOnly | QIODevice::Text)) { while (!logFile.atEnd()) { retval.append(logFile.readLine()); } return retval; } } } /* *TODO: to be bug fixed *TODO: do it with kill and not QProcess("killall cdhcpcd") *TODO: still some bugs. if you press tryAgain it can happen that the app stops with the mainscreen. *TODO: reproducible: start normal with user choice. plug out the cable. press continue. abort screen should appear. *TODO: press tryAgain. */ void NetworkDiscovery::tryAgain() { // kill all cdhcpcd processes qDebug() << " kill cdhcpcd processes"; QProcess * p = new QProcess(this); p->start("killall cdhcpcd"); p->waitForFinished(); qDebug() << _tag << "[tryAgain]" << p->errorString(); /* foreach(Q_PID pid , _pidsList) { if (kill(pid,SIGKILL) <= -1) qDebug() << _tag << " error: trying to kill process: " << pid << " error: " << strerror(errno); } */ // reset everything _clients.clear(); _clientProcessToIfNameMap.clear(); _ifNameToClient.clear(); _numberOfProcesses = 0; _blocked = false; _ifUpCountdown = 10; _ifUpList.clear(); _ifDownList.clear(); _pidsList.clear(); _ifcMap.clear(); // start again mainWork(); //SIGK //kill(); } /**/ QVariantMap NetworkDiscovery::getInterfaceConfig(QString ifName) { QList dns; interfaceconfiguration * ifc = _ifcMap.value(ifName, NULL); if (ifc != NULL) { dns.clear(); dns = ifc->getDnsservers().trimmed().split(" "); //ifc->getIpAddress(), ifc->getNetmask(), ifc->getBroadcast(), //ifc->getGateway(), 0, AF_INET, "/etc/", dns); } } /** * ================================================================================ ********************************* Private Methods ******************************** * ================================================================================ **/ /**/ void NetworkDiscovery::mainWork() { if (_autoUp) { getListOfNetworkInterfacesWithAutoUp(); _timer = new QTimer(this); connect(_timer, SIGNAL(timeout()), this, SLOT(checkForIsRunning())); _timer->start(1000); } else { getListOfNetworkInterfaces(); emit readyForRun(); } } /** * searches for usable interfaces and puts them into a list. * if the interface is down, put it in the _ifDownList, try to bring it up. * else put it in the _ifUpList. * usable interfaces are: can Broadcast, no loopback, no point to point, name is not in the BlackList, */ void NetworkDiscovery::getListOfNetworkInterfacesWithAutoUp() { QList nIList = QNetworkInterface::allInterfaces(); if (nIList.size() > 0) { foreach(QNetworkInterface nI, nIList) { if (((!(nI.flags() & QNetworkInterface::CanBroadcast) || nI.flags() & QNetworkInterface::IsLoopBack) || nI.flags() & QNetworkInterface::IsPointToPoint) || checkBlackList(nI.humanReadableName())) { continue; } if ((nI.flags() & QNetworkInterface::IsRunning)) { _ifUpList.append(nI.humanReadableName()); } else if (!(nI.flags() & QNetworkInterface::IsUp)) { _networkManager.bringInterfaceUP(nI.humanReadableName()); qDebug() << _tag << " bring up .."; _ifDownList.append(nI.humanReadableName()); } else if (!(nI.flags() & QNetworkInterface::IsRunning)) { _ifDownList.append(nI.humanReadableName()); } } } else { qDebug() << _tag << "no interfaces found!"; } } /** * 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(); if (nIList.size() > 0) { foreach(QNetworkInterface nI, nIList) { if (((!(nI.flags() & QNetworkInterface::CanBroadcast) || nI.flags() & QNetworkInterface::IsLoopBack) || nI.flags() & QNetworkInterface::IsPointToPoint) || !(nI.flags() & QNetworkInterface::IsUp) || checkBlackList(nI.humanReadableName())) { continue; } if (!(nI.flags() & QNetworkInterface::IsRunning)) { _ifDownList.append(nI.humanReadableName()); } else { _ifUpList.append(nI.humanReadableName()); } } } else { qDebug() << _tag << "no interfaces found!"; } } /** * not used so far. checks the carrier state using the sysfs library. * if carrier = 1 ==> the interface is running. * interfaces have to be up in order to get right results. * * @param interface * name of the interface */ bool NetworkDiscovery::checkCarrierState(QString interface) { qDebug() << _tag << "check carrier state for interface " << interface; QByteArray ba = interface.toAscii(); const char * iface = ba.data(); struct sysfs_class_device *class_device = sysfs_open_class_device("net", iface); struct dlist *attrlist = sysfs_get_classdev_attributes(class_device); if (attrlist != NULL) { struct sysfs_attribute *attr = NULL; dlist_for_each_data(attrlist, attr, struct sysfs_attribute) { if (strcmp("carrier", attr->name) == 0) { QString value(attr->value); bool ok = false; bool * pok = &ok; int v = value.toInt(pok); if (*pok) { if (v == 1) { qDebug() << "carrier is 1. Cable is plugged. return true"; return true; } else { qDebug() << "carrier is 0. Cable is unplugged. return false"; return false; } } else { qDebug() << _tag << "conversion error"; } } } } else { qDebug() << _tag << "attrlist is Null"; } sysfs_close_class_device(class_device); return true; } /** * 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 updateStatusLabel(interface, "start DHCP"); _dhcpcdArguments.append(interface); QProcess * p = new QProcess(this); qDebug() << _tag << _dhcpcdArguments; _clientProcessToIfNameMap.insert(p, interface); p->start(_pathToDhcpcdExe, _dhcpcdArguments); connect(p, SIGNAL(started()), this, SLOT(handleProcessStarted())); connect(p, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(handleProcessFinished(int, QProcess::ExitStatus))); _dhcpcdArguments.removeLast(); } /** * 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 = new interfaceconfiguration(); ifConf->readConfigOutOfFile(pathToGatewayFile); _ifcMap.insert(ifName, ifConf); // replace default route qDebug() << _tag << "replace default route"; _networkManager.replaceDefaultRoute(ifName, ifConf->getGateway(), mss, AF_INET); if (checkConnectivityViaTcp(_serverIp)) { qDebug() << _tag << "internet: check passed! for interface" << ifName; emit updateStatusLabel(ifName, "connection possible"); if (!_userChoice) { // blockiere jeden weiteren check // emite continueBoot _blocked = true; emit continueBoot(ifName, 0); } else { emit connectionEstablished(ifName); } return true; } else { qDebug() << _tag << "no internet connection with interface" << ifName; emit updateStatusLabel(ifName, "connection not possible"); return false; } } /**/ bool NetworkDiscovery::checkConnectivityViaTcp() { return checkConnectivityViaTcp(_serverIp); } /** * 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 QTcpSocket *tcpSocket = new QTcpSocket(this); tcpSocket->connectToHost(server, 80); if (!tcpSocket->waitForConnected(500)) { qDebug() << _tag << tcpSocket->errorString(); return false; } else { return true; } } /** * connected to the new client arrived signal. * connects the client readyRead signal with the handleNewInput slot. */ void NetworkDiscovery::handleNewConnection() { qDebug() << _tag << "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); qDebug() << _tag << "disconnect client"; handleNewInput(client); client->deleteLater(); } /** * same function as handleNewInput() but with a client as parameter. * * @param cleint * a client */ void NetworkDiscovery::handleNewInput(QLocalSocket * client) { qDebug() << _tag << "last read before exit"; while (client->canReadLine()) { QString data(client->readLine()); data = data.trimmed(); qDebug() << _tag << 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)); //client->write("ACK", ACK_SIZE); //client->waitForBytesWritten(); data = data.trimmed(); //qDebug() << _tag << data; 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 data) { QString logMsg(data); 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(); //qDebug() << _tag << logMsg; if (_ifNameToClient.size() < _numberOfProcesses && !_ifNameToClient.contains( interface)) { _ifNameToClient.insert(interface, client); } switch (st) { case LOG_INFO: switch (sst) { case DHCP_DISCOVER: emit changeProgressBarValue(interface, 10); break; case DHCP_OFFER: emit changeProgressBarValue(interface, 20); break; case DHCP_REQUEST: emit changeProgressBarValue(interface, 30); break; case DHCP_ACK: emit changeProgressBarValue(interface, 40); break; case DHCP_NAK: emit changeProgressBarValue(interface, 40); break; case DHCPCD_ARP_TEST: emit changeProgressBarValue(interface, 50); break; case DHCP_DECLINE: emit changeProgressBarValue(interface, 60); break; case DHCP_RELEASE: break; case DHCP_INFORM: break; case DHCPCD_CONFIGURE: emit changeProgressBarValue(interface, 70); break; case DHCPCD_WRITE: emit changeProgressBarValue(interface, 80); break; case DHCPCD_EXIT: emit changeProgressBarValue(interface, 100); break; case DHCPCD_LOG: default: qDebug() << _tag << "default" << msg; break; } break; case LOG_ERR: qDebug() << _tag << "received error:" << msg; break; default: //qDebug() << _tag << logMsg; break; } } /** * 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 * TODO:: the reason for the unexpected exit should be presented in the logfile. * * @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) { qDebug() << _tag << "--- \t [NetworkDiscovery::handleProcessFinished] haven't found process!"; } else { qDebug() << _tag << "process for interface" << ifName << "finished" << exitCode << exitStatus; if (exitCode > 0) { qDebug() << _tag << "process exited unexpected"; emit updateStatusLabel(ifName, "process exited unexpected"); } else { qDebug() << _tag << "process normal exit"; emit changeProgressBarValue(ifName, 100); emit updateStatusLabel(ifName, "check connectivity"); checkConnectivity(ifName); } } 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 { qDebug() << _tag << "already blocked"; emit updateStatusLabel(ifName, "finished DHCP"); } } /** * 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"); qDebug() << _tag << "process started for interface:" << ifName << "with pid:" << p->pid(); _pidsList.append(p->pid()); } /** * 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) { if (i.startsWith("v", Qt::CaseInsensitive)) { return true; } else if (i.startsWith("d", Qt::CaseInsensitive)) { return true; } else { return false; } }