Add mac address database and build tool
This commit is contained in:
parent
08aae83a00
commit
60de7e5c45
3
.gitignore
vendored
3
.gitignore
vendored
@ -9,6 +9,9 @@ snap/prime/
|
|||||||
snap/stage/
|
snap/stage/
|
||||||
doc/interfacelist.qdoc
|
doc/interfacelist.qdoc
|
||||||
|
|
||||||
|
# Never add downloaded json data
|
||||||
|
data/mac-database/macaddress.io-db.json
|
||||||
|
|
||||||
.crossbuilder/
|
.crossbuilder/
|
||||||
debs*.tar
|
debs*.tar
|
||||||
|
|
||||||
|
|||||||
16
data/mac-database/README.md
Normal file
16
data/mac-database/README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# MAC address dabase
|
||||||
|
|
||||||
|
The MAC address database has been created using the dowloaded JSON data from [https://macaddress.io](https://macaddress.io).
|
||||||
|
|
||||||
|
## Build database
|
||||||
|
|
||||||
|
Before you can start the `build-database.py` script, please make sure you downloaded the the online database in JSON format.
|
||||||
|
|
||||||
|
Once the `macaddress.io-db.json` file has been downloaded and placed into this folder, the python tool can be started in order to generate a read performance optimized and minimal database for searching mac address OUIs (Organizationally Unique Identifiers).
|
||||||
|
|
||||||
|
$ python3 build-database.py
|
||||||
|
|
||||||
|
The final database will be named `mac-addresses.db`.
|
||||||
|
|
||||||
|
In nymea the `MacAddressDatabase` will search for this database file in `/usr/share/nymea/mac-addresses.db` and provides an asynch mechanism to provide the manufacturer name for a given mac address.
|
||||||
|
|
||||||
88
data/mac-database/build-database.py
Normal file
88
data/mac-database/build-database.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
#
|
||||||
|
# Copyright 2013 - 2021, nymea GmbH
|
||||||
|
# Contact: contact@nymea.io
|
||||||
|
#
|
||||||
|
# This file is part of nymea.
|
||||||
|
# This project including source code and documentation is protected by
|
||||||
|
# copyright law, and remains the property of nymea GmbH. All rights, including
|
||||||
|
# reproduction, publication, editing and translation, are reserved. The use of
|
||||||
|
# this project is subject to the terms of a license agreement to be concluded
|
||||||
|
# with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||||
|
# under https://nymea.io/license
|
||||||
|
#
|
||||||
|
# GNU Lesser General Public License Usage
|
||||||
|
# Alternatively, this project may be redistributed and/or modified under the
|
||||||
|
# terms of the GNU Lesser General Public License as published by the Free
|
||||||
|
# Software Foundation; version 3. This project 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# For any further details and any questions please contact us under
|
||||||
|
# contact@nymea.io or see our FAQ/Licensing Information on
|
||||||
|
# https://nymea.io/license/faq
|
||||||
|
#
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
jsonDataFileName = 'macaddress.io-db.json'
|
||||||
|
print('Loading JSON data from', jsonDataFileName)
|
||||||
|
jsonDataFile = open(jsonDataFileName, 'r')
|
||||||
|
lines = jsonDataFile.readlines()
|
||||||
|
|
||||||
|
vendorInfoHash = {}
|
||||||
|
for line in lines:
|
||||||
|
vendorMap = json.loads(line)
|
||||||
|
vendorInfoHash[vendorMap['oui']] = vendorMap['companyName']
|
||||||
|
|
||||||
|
jsonDataFile.close()
|
||||||
|
|
||||||
|
# Sort by oui for good binary search in the db
|
||||||
|
sortedOuiHash = sorted(vendorInfoHash.items(), key=lambda x: x[0], reverse=False)
|
||||||
|
databaseFileName = 'mac-addresses.db'
|
||||||
|
if os.path.exists(databaseFileName):
|
||||||
|
print('Delete old db file and create a new one', databaseFileName)
|
||||||
|
os.remove(databaseFileName)
|
||||||
|
|
||||||
|
connection = sqlite3.connect(databaseFileName)
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute('CREATE TABLE companyNames (companyName TEXT PRIMARY KEY, UNIQUE(companyName));')
|
||||||
|
cursor.execute('CREATE TABLE oui (oui TEXT PRIMARY KEY, companyNameIndex INTEGER, UNIQUE(oui)) WITHOUT ROWID;')
|
||||||
|
#cursor.execute('CREATE UNIQUE INDEX ouiIndex ON `oui` (`oui`);')
|
||||||
|
|
||||||
|
# Insert all vendor names alphabetically
|
||||||
|
sortedVendorHash = sorted(vendorInfoHash.items(), key=lambda x: x[1], reverse=False)
|
||||||
|
vendorCount = 0
|
||||||
|
for vendorInfo in sortedVendorHash:
|
||||||
|
insertQuery = 'INSERT OR IGNORE INTO companyNames (companyName) VALUES(?);'
|
||||||
|
cursor.execute(insertQuery, [vendorInfo[1]])
|
||||||
|
cursor.execute('SELECT COUNT(companyName) FROM companyNames');
|
||||||
|
countResult = cursor.fetchall()
|
||||||
|
vendorCount = countResult[0][0]
|
||||||
|
|
||||||
|
connection.commit()
|
||||||
|
|
||||||
|
# Insert all oui with reference to company name
|
||||||
|
ouiCount = 0
|
||||||
|
for vendorInfo in sortedOuiHash:
|
||||||
|
insertQuery = 'INSERT OR IGNORE INTO oui (oui, companyNameIndex) VALUES(?, (SELECT rowid FROM companyNames WHERE companyName = ?));'
|
||||||
|
cursor.execute(insertQuery, [vendorInfo[0].replace(':', ''), vendorInfo[1]])
|
||||||
|
cursor.execute('SELECT COUNT(oui) FROM oui');
|
||||||
|
countResult = cursor.fetchall()
|
||||||
|
ouiCount = countResult[0][0]
|
||||||
|
|
||||||
|
connection.commit()
|
||||||
|
connection.close()
|
||||||
|
print('Loaded', ouiCount, 'OUI values from', vendorCount, 'manufacturers into', databaseFileName)
|
||||||
|
|
||||||
|
|
||||||
BIN
data/mac-database/mac-addresses.db
Normal file
BIN
data/mac-database/mac-addresses.db
Normal file
Binary file not shown.
@ -3,8 +3,9 @@ include(../nymea.pri)
|
|||||||
TARGET = nymea
|
TARGET = nymea
|
||||||
TEMPLATE = lib
|
TEMPLATE = lib
|
||||||
|
|
||||||
QT += network bluetooth dbus serialport
|
QT += network bluetooth dbus serialport sql
|
||||||
QT -= gui
|
QT -= gui
|
||||||
|
|
||||||
DEFINES += LIBNYMEA_LIBRARY
|
DEFINES += LIBNYMEA_LIBRARY
|
||||||
|
|
||||||
CONFIG += link_pkgconfig
|
CONFIG += link_pkgconfig
|
||||||
@ -43,6 +44,7 @@ HEADERS += \
|
|||||||
network/apikeys/apikeysprovider.h \
|
network/apikeys/apikeysprovider.h \
|
||||||
network/apikeys/apikeystorage.h \
|
network/apikeys/apikeystorage.h \
|
||||||
network/arpsocket.h \
|
network/arpsocket.h \
|
||||||
|
network/macaddressdatabase.h \
|
||||||
network/networkdevice.h \
|
network/networkdevice.h \
|
||||||
network/networkdevicediscovery.h \
|
network/networkdevicediscovery.h \
|
||||||
network/networkdevicediscoveryreply.h \
|
network/networkdevicediscoveryreply.h \
|
||||||
@ -149,6 +151,7 @@ SOURCES += \
|
|||||||
network/apikeys/apikeysprovider.cpp \
|
network/apikeys/apikeysprovider.cpp \
|
||||||
network/apikeys/apikeystorage.cpp \
|
network/apikeys/apikeystorage.cpp \
|
||||||
network/arpsocket.cpp \
|
network/arpsocket.cpp \
|
||||||
|
network/macaddressdatabase.cpp \
|
||||||
network/networkdevice.cpp \
|
network/networkdevice.cpp \
|
||||||
network/networkdevicediscovery.cpp \
|
network/networkdevicediscovery.cpp \
|
||||||
network/networkdevicediscoveryreply.cpp \
|
network/networkdevicediscoveryreply.cpp \
|
||||||
|
|||||||
200
libnymea/network/macaddressdatabase.cpp
Normal file
200
libnymea/network/macaddressdatabase.cpp
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* Copyright 2013 - 2021, nymea GmbH
|
||||||
|
* Contact: contact@nymea.io
|
||||||
|
*
|
||||||
|
* This file is part of nymea.
|
||||||
|
* This project including source code and documentation is protected by
|
||||||
|
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||||
|
* reproduction, publication, editing and translation, are reserved. The use of
|
||||||
|
* this project is subject to the terms of a license agreement to be concluded
|
||||||
|
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||||
|
* under https://nymea.io/license
|
||||||
|
*
|
||||||
|
* GNU Lesser General Public License Usage
|
||||||
|
* Alternatively, this project may be redistributed and/or modified under the
|
||||||
|
* terms of the GNU Lesser General Public License as published by the Free
|
||||||
|
* Software Foundation; version 3. This project 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
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* For any further details and any questions please contact us under
|
||||||
|
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||||
|
* https://nymea.io/license/faq
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "macaddressdatabase.h"
|
||||||
|
#include "loggingcategories.h"
|
||||||
|
|
||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QSqlError>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QtConcurrent/QtConcurrent>
|
||||||
|
|
||||||
|
NYMEA_LOGGING_CATEGORY(dcMacAddressDatabase, "MacAddressDatabase")
|
||||||
|
|
||||||
|
MacAddressDatabase::MacAddressDatabase(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
m_available = initDatabase();
|
||||||
|
if (m_available) {
|
||||||
|
m_futureWatcher = new QFutureWatcher<QString>(this);
|
||||||
|
connect(m_futureWatcher, &QFutureWatcher<QString>::finished, this, &MacAddressDatabase::onLookupFinished);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MacAddressDatabase::MacAddressDatabase(const QString &databaseName, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_databaseName(databaseName)
|
||||||
|
{
|
||||||
|
m_available = initDatabase();
|
||||||
|
if (m_available) {
|
||||||
|
m_futureWatcher = new QFutureWatcher<QString>(this);
|
||||||
|
connect(m_futureWatcher, &QFutureWatcher<QString>::finished, this, &MacAddressDatabase::onLookupFinished);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MacAddressDatabase::~MacAddressDatabase()
|
||||||
|
{
|
||||||
|
m_db.close();
|
||||||
|
m_db = QSqlDatabase();
|
||||||
|
QSqlDatabase::removeDatabase(m_connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacAddressDatabase::available() const
|
||||||
|
{
|
||||||
|
return m_available;
|
||||||
|
}
|
||||||
|
|
||||||
|
MacAddressDatabaseReply *MacAddressDatabase::lookupMacAddress(const QString &macAddress)
|
||||||
|
{
|
||||||
|
MacAddressDatabaseReply *reply = new MacAddressDatabaseReply(this);
|
||||||
|
connect(reply, &MacAddressDatabaseReply::finished, reply, &MacAddressDatabaseReply::deleteLater);
|
||||||
|
reply->m_macAddress = macAddress;
|
||||||
|
|
||||||
|
if (!m_available) {
|
||||||
|
QTimer::singleShot(0, this, [=](){ emit reply->finished(); });
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pendingReplies.enqueue(reply);
|
||||||
|
runNextLookup();
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacAddressDatabase::initDatabase()
|
||||||
|
{
|
||||||
|
m_connectionName = QFileInfo(m_databaseName).baseName();
|
||||||
|
m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);
|
||||||
|
m_db.setDatabaseName(m_databaseName);
|
||||||
|
|
||||||
|
if (!m_db.isValid()) {
|
||||||
|
qCWarning(dcMacAddressDatabase()) << "The network database is not valid" << m_db.databaseName();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_db.close();
|
||||||
|
if (!m_db.open()) {
|
||||||
|
qCWarning(dcMacAddressDatabase()) << "Could not open database" << m_db.databaseName() << "Initialization failed.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the tables we need exist
|
||||||
|
qCDebug(dcMacAddressDatabase()) << "Tables" << m_db.tables();
|
||||||
|
if (!m_db.tables().contains("oui")) {
|
||||||
|
qCWarning(dcMacAddressDatabase()) << "Invalid database. Could not find \"oui\" table in" << m_db.databaseName();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_db.tables().contains("companyNames")) {
|
||||||
|
qCWarning(dcMacAddressDatabase()) << "Invalid database. Could not find \"companyNames\" table in" << m_db.databaseName();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacAddressDatabase::runNextLookup()
|
||||||
|
{
|
||||||
|
if (m_pendingReplies.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_futureWatcher->isRunning() || m_currentReply)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_currentReply = m_pendingReplies.dequeue();
|
||||||
|
m_currentReply->m_startTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||||
|
QFuture<QString> future = QtConcurrent::run(this, &MacAddressDatabase::lookupMacAddressVendorInternal, m_currentReply->macAddress());
|
||||||
|
m_futureWatcher->setFuture(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacAddressDatabase::onLookupFinished()
|
||||||
|
{
|
||||||
|
if (m_currentReply) {
|
||||||
|
QString manufacturer = m_futureWatcher->future().result();
|
||||||
|
qCDebug(dcMacAddressDatabase()) << "Manufacturer lookup for" << m_currentReply->macAddress() << "finished:" << manufacturer << QDateTime::currentMSecsSinceEpoch() - m_currentReply->m_startTimestamp << "ms";
|
||||||
|
m_currentReply->m_manufacturer = manufacturer;
|
||||||
|
emit m_currentReply->finished();
|
||||||
|
m_currentReply = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
runNextLookup();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MacAddressDatabase::lookupMacAddressVendorInternal(const QString &macAddress)
|
||||||
|
{
|
||||||
|
qCDebug(dcMacAddressDatabase()) << "Start looking up vendor for" << macAddress;
|
||||||
|
// Convert the mac address string to upper like in the database and remove : since they have been removed for size reasons
|
||||||
|
QString fullMacAddressString = QString(macAddress).toUpper().remove(":");
|
||||||
|
|
||||||
|
QString manufacturer;
|
||||||
|
int length = 6;
|
||||||
|
while (true) {
|
||||||
|
QString searchString = fullMacAddressString.left(length);
|
||||||
|
QString queryString = QString("SELECT COUNT(oui) FROM oui WHERE oui LIKE \'%1%\';").arg(searchString);
|
||||||
|
qCDebug(dcMacAddressDatabase()) << "Query:" << queryString;
|
||||||
|
QSqlQuery countQuery = m_db.exec(queryString);
|
||||||
|
if (countQuery.lastError().isValid()) {
|
||||||
|
qCWarning(dcMacAddressDatabase()) << "Query finished with error" << countQuery.lastError().text();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!countQuery.next())
|
||||||
|
break;
|
||||||
|
|
||||||
|
int rowCount = countQuery.value(0).toInt();
|
||||||
|
qCDebug(dcMacAddressDatabase()) << "Found" << rowCount << "with" << searchString;
|
||||||
|
// If we have found the one...
|
||||||
|
if (rowCount == 1) {
|
||||||
|
// Query the name
|
||||||
|
queryString = QString("SELECT companyName from companyNames WHERE rowid IS (SELECT companyNameIndex FROM oui WHERE oui=\'%1\');").arg(searchString);
|
||||||
|
qCDebug(dcMacAddressDatabase()) << "Query:" << queryString;
|
||||||
|
countQuery = m_db.exec(queryString);
|
||||||
|
if (!countQuery.next())
|
||||||
|
break;
|
||||||
|
|
||||||
|
manufacturer = countQuery.value(0).toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing found
|
||||||
|
if (rowCount == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Found to many results, lets add a value until we find the matching vendor
|
||||||
|
length += 1;
|
||||||
|
if (length > fullMacAddressString)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Search with one addition digit
|
||||||
|
}
|
||||||
|
|
||||||
|
return manufacturer;
|
||||||
|
}
|
||||||
|
|
||||||
93
libnymea/network/macaddressdatabase.h
Normal file
93
libnymea/network/macaddressdatabase.h
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* Copyright 2013 - 2021, nymea GmbH
|
||||||
|
* Contact: contact@nymea.io
|
||||||
|
*
|
||||||
|
* This file is part of nymea.
|
||||||
|
* This project including source code and documentation is protected by
|
||||||
|
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||||
|
* reproduction, publication, editing and translation, are reserved. The use of
|
||||||
|
* this project is subject to the terms of a license agreement to be concluded
|
||||||
|
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||||
|
* under https://nymea.io/license
|
||||||
|
*
|
||||||
|
* GNU Lesser General Public License Usage
|
||||||
|
* Alternatively, this project may be redistributed and/or modified under the
|
||||||
|
* terms of the GNU Lesser General Public License as published by the Free
|
||||||
|
* Software Foundation; version 3. This project 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
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* For any further details and any questions please contact us under
|
||||||
|
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||||
|
* https://nymea.io/license/faq
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef MACADDRESSDATABASE_H
|
||||||
|
#define MACADDRESSDATABASE_H
|
||||||
|
|
||||||
|
#include <QQueue>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
|
||||||
|
#include "libnymea.h"
|
||||||
|
|
||||||
|
class LIBNYMEA_EXPORT MacAddressDatabaseReply : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
friend class MacAddressDatabase;
|
||||||
|
|
||||||
|
public:
|
||||||
|
QString macAddress() const { return m_macAddress; };
|
||||||
|
QString manufacturer() const { return m_manufacturer; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit MacAddressDatabaseReply(QObject *parent = nullptr) : QObject(parent) { };
|
||||||
|
QString m_macAddress;
|
||||||
|
QString m_manufacturer;
|
||||||
|
qint64 m_startTimestamp;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void finished();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class LIBNYMEA_EXPORT MacAddressDatabase : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit MacAddressDatabase(QObject *parent = nullptr);
|
||||||
|
MacAddressDatabase(const QString &databaseName, QObject *parent = nullptr);
|
||||||
|
~MacAddressDatabase();
|
||||||
|
|
||||||
|
bool available() const;
|
||||||
|
|
||||||
|
MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSqlDatabase m_db;
|
||||||
|
bool m_available = false;
|
||||||
|
QString m_connectionName;
|
||||||
|
QString m_databaseName = "/usr/share/nymea/mac-addresses.db";
|
||||||
|
|
||||||
|
MacAddressDatabaseReply *m_currentReply = nullptr;
|
||||||
|
QFutureWatcher<QString> *m_futureWatcher = nullptr;
|
||||||
|
QQueue<MacAddressDatabaseReply *> m_pendingReplies;
|
||||||
|
|
||||||
|
bool initDatabase();
|
||||||
|
void runNextLookup();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onLookupFinished();
|
||||||
|
QString lookupMacAddressVendorInternal(const QString &macAddress);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MACADDRESSDATABASE_H
|
||||||
@ -31,6 +31,9 @@
|
|||||||
#include "networkdevicediscovery.h"
|
#include "networkdevicediscovery.h"
|
||||||
#include "loggingcategories.h"
|
#include "loggingcategories.h"
|
||||||
#include "networkutils.h"
|
#include "networkutils.h"
|
||||||
|
#include "macaddressdatabase.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
NYMEA_LOGGING_CATEGORY(dcNetworkDeviceDiscovery, "NetworkDeviceDiscovery")
|
NYMEA_LOGGING_CATEGORY(dcNetworkDeviceDiscovery, "NetworkDeviceDiscovery")
|
||||||
|
|
||||||
@ -49,6 +52,10 @@ NetworkDeviceDiscovery::NetworkDeviceDiscovery(QObject *parent) :
|
|||||||
if (!m_ping->available())
|
if (!m_ping->available())
|
||||||
qCWarning(dcNetworkDeviceDiscovery()) << "Failed to create ping tool" << m_ping->error();
|
qCWarning(dcNetworkDeviceDiscovery()) << "Failed to create ping tool" << m_ping->error();
|
||||||
|
|
||||||
|
m_macAddressDatabase = new MacAddressDatabase(this);
|
||||||
|
if (!m_macAddressDatabase->available())
|
||||||
|
qCWarning(dcNetworkDeviceDiscovery()) << "The mac address database is not available. Network discovery will not lookup mac address manufacturer";
|
||||||
|
|
||||||
m_discoveryTimer = new QTimer(this);
|
m_discoveryTimer = new QTimer(this);
|
||||||
m_discoveryTimer->setInterval(5000);
|
m_discoveryTimer->setInterval(5000);
|
||||||
m_discoveryTimer->setSingleShot(true);
|
m_discoveryTimer->setSingleShot(true);
|
||||||
@ -72,6 +79,7 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscovery::discover()
|
|||||||
|
|
||||||
NetworkDeviceDiscoveryReply *reply = new NetworkDeviceDiscoveryReply(this);
|
NetworkDeviceDiscoveryReply *reply = new NetworkDeviceDiscoveryReply(this);
|
||||||
m_currentReply = reply;
|
m_currentReply = reply;
|
||||||
|
m_currentReply->m_startTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||||
|
|
||||||
if (m_ping->available()) {
|
if (m_ping->available()) {
|
||||||
pingAllNetworkDevices();
|
pingAllNetworkDevices();
|
||||||
@ -179,13 +187,53 @@ void NetworkDeviceDiscovery::finishDiscovery()
|
|||||||
m_running = false;
|
m_running = false;
|
||||||
emit runningChanged(m_running);
|
emit runningChanged(m_running);
|
||||||
|
|
||||||
qCDebug(dcNetworkDeviceDiscovery()) << "Discovery finished. Found" << m_currentReply->networkDevices().count() << "network devices.";
|
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_currentReply->m_startTimestamp;
|
||||||
|
qCDebug(dcNetworkDeviceDiscovery()) << "Discovery finished. Found" << m_currentReply->networkDevices().count() << "network devices in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
|
||||||
m_arpSocket->closeSocket();
|
m_arpSocket->closeSocket();
|
||||||
emit m_currentReply->finished();
|
emit m_currentReply->finished();
|
||||||
m_currentReply->deleteLater();
|
m_currentReply->deleteLater();
|
||||||
m_currentReply = nullptr;
|
m_currentReply = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetworkDeviceDiscovery::updateOrAddNetworkDeviceArp(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress, const QString &manufacturer)
|
||||||
|
{
|
||||||
|
int index = m_currentReply->networkDevices().indexFromHostAddress(address);
|
||||||
|
if (index >= 0) {
|
||||||
|
// Update the network device
|
||||||
|
m_currentReply->networkDevices()[index].setMacAddress(macAddress);
|
||||||
|
if (!manufacturer.isEmpty())
|
||||||
|
m_currentReply->networkDevices()[index].setMacAddressManufacturer(manufacturer);
|
||||||
|
|
||||||
|
if (interface.isValid()) {
|
||||||
|
m_currentReply->networkDevices()[index].setNetworkInterface(interface);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
index = m_currentReply->networkDevices().indexFromMacAddress(macAddress);
|
||||||
|
if (index >= 0) {
|
||||||
|
// Update the network device
|
||||||
|
m_currentReply->networkDevices()[index].setAddress(address);
|
||||||
|
if (!manufacturer.isEmpty())
|
||||||
|
m_currentReply->networkDevices()[index].setMacAddressManufacturer(manufacturer);
|
||||||
|
|
||||||
|
if (interface.isValid()) {
|
||||||
|
m_currentReply->networkDevices()[index].setNetworkInterface(interface);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Add the network device
|
||||||
|
NetworkDevice networkDevice;
|
||||||
|
networkDevice.setAddress(address);
|
||||||
|
networkDevice.setMacAddress(macAddress);
|
||||||
|
if (!manufacturer.isEmpty())
|
||||||
|
networkDevice.setMacAddressManufacturer(manufacturer);
|
||||||
|
|
||||||
|
if (interface.isValid())
|
||||||
|
networkDevice.setNetworkInterface(interface);
|
||||||
|
|
||||||
|
m_currentReply->networkDevices().append(networkDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkDeviceDiscovery::onArpResponseRceived(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress)
|
void NetworkDeviceDiscovery::onArpResponseRceived(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress)
|
||||||
{
|
{
|
||||||
if (!m_currentReply) {
|
if (!m_currentReply) {
|
||||||
@ -194,31 +242,15 @@ void NetworkDeviceDiscovery::onArpResponseRceived(const QNetworkInterface &inter
|
|||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(dcNetworkDeviceDiscovery()) << "ARP reply received" << address.toString() << macAddress << interface.name();
|
qCDebug(dcNetworkDeviceDiscovery()) << "ARP reply received" << address.toString() << macAddress << interface.name();
|
||||||
// Update or add network device
|
|
||||||
int index = m_currentReply->networkDevices().indexFromHostAddress(address);
|
|
||||||
if (index >= 0) {
|
|
||||||
// Update the network device
|
|
||||||
m_currentReply->networkDevices()[index].setMacAddress(macAddress);
|
|
||||||
if (interface.isValid()) {
|
|
||||||
m_currentReply->networkDevices()[index].setNetworkInterface(interface);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
index = m_currentReply->networkDevices().indexFromMacAddress(macAddress);
|
|
||||||
if (index >= 0) {
|
|
||||||
// Update the network device
|
|
||||||
m_currentReply->networkDevices()[index].setAddress(address);
|
|
||||||
if (interface.isValid()) {
|
|
||||||
m_currentReply->networkDevices()[index].setNetworkInterface(interface);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Add the network device
|
|
||||||
NetworkDevice networkDevice;
|
|
||||||
networkDevice.setAddress(address);
|
|
||||||
networkDevice.setMacAddress(macAddress);
|
|
||||||
if (interface.isValid())
|
|
||||||
networkDevice.setNetworkInterface(interface);
|
|
||||||
|
|
||||||
m_currentReply->networkDevices().append(networkDevice);
|
// Lookup the mac address vendor if possible
|
||||||
}
|
if (m_macAddressDatabase->available()) {
|
||||||
|
MacAddressDatabaseReply *reply = m_macAddressDatabase->lookupMacAddress(macAddress);
|
||||||
|
connect(reply, &MacAddressDatabaseReply::finished, this, [=](){
|
||||||
|
qCDebug(dcNetworkDeviceDiscovery()) << "MAC manufacturer lookup finished for" << macAddress << ":" << reply->manufacturer();
|
||||||
|
updateOrAddNetworkDeviceArp(interface, address, macAddress, reply->manufacturer());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
updateOrAddNetworkDeviceArp(interface, address, macAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,8 @@
|
|||||||
#include "arpsocket.h"
|
#include "arpsocket.h"
|
||||||
#include "networkdevicediscoveryreply.h"
|
#include "networkdevicediscoveryreply.h"
|
||||||
|
|
||||||
|
class MacAddressDatabase;
|
||||||
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(dcNetworkDeviceDiscovery)
|
Q_DECLARE_LOGGING_CATEGORY(dcNetworkDeviceDiscovery)
|
||||||
|
|
||||||
class LIBNYMEA_EXPORT NetworkDeviceDiscovery : public QObject
|
class LIBNYMEA_EXPORT NetworkDeviceDiscovery : public QObject
|
||||||
@ -59,6 +61,7 @@ signals:
|
|||||||
void runningChanged(bool running);
|
void runningChanged(bool running);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
MacAddressDatabase *m_macAddressDatabase = nullptr;
|
||||||
ArpSocket *m_arpSocket = nullptr;
|
ArpSocket *m_arpSocket = nullptr;
|
||||||
Ping *m_ping = nullptr;
|
Ping *m_ping = nullptr;
|
||||||
bool m_running = false;
|
bool m_running = false;
|
||||||
@ -70,6 +73,8 @@ private:
|
|||||||
void pingAllNetworkDevices();
|
void pingAllNetworkDevices();
|
||||||
void finishDiscovery();
|
void finishDiscovery();
|
||||||
|
|
||||||
|
void updateOrAddNetworkDeviceArp(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress, const QString &manufacturer = QString());
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onArpResponseRceived(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress);
|
void onArpResponseRceived(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress);
|
||||||
|
|
||||||
|
|||||||
@ -43,15 +43,15 @@ class LIBNYMEA_EXPORT NetworkDeviceDiscoveryReply : public QObject
|
|||||||
friend class NetworkDeviceDiscovery;
|
friend class NetworkDeviceDiscovery;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit NetworkDeviceDiscoveryReply(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
NetworkDevices &networkDevices();
|
NetworkDevices &networkDevices();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void finished();
|
void finished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
explicit NetworkDeviceDiscoveryReply(QObject *parent = nullptr);
|
||||||
NetworkDevices m_networkDevices;
|
NetworkDevices m_networkDevices;
|
||||||
|
qint64 m_startTimestamp;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -200,6 +200,10 @@ void Ping::performPing(PingReply *reply)
|
|||||||
|
|
||||||
// Start reply timer and handle timeout
|
// Start reply timer and handle timeout
|
||||||
m_pendingReplies.insert(reply->requestId(), reply);
|
m_pendingReplies.insert(reply->requestId(), reply);
|
||||||
|
reply->m_timer->start(8000);
|
||||||
|
connect(reply, &PingReply::timeout, this, [=](){
|
||||||
|
finishReply(reply, PingReply::ErrorTimeout);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ping::verifyErrno(int error)
|
void Ping::verifyErrno(int error)
|
||||||
@ -336,7 +340,7 @@ void Ping::onSocketReadyRead(int socketDescriptor)
|
|||||||
// Verify sequence number
|
// Verify sequence number
|
||||||
if (responsePacket->icmp_seq != reply->sequenceNumber()) {
|
if (responsePacket->icmp_seq != reply->sequenceNumber()) {
|
||||||
qCWarning(dcPing()) << "Received echo reply with different sequence number" << htons(responsePacket->icmp_seq);
|
qCWarning(dcPing()) << "Received echo reply with different sequence number" << htons(responsePacket->icmp_seq);
|
||||||
finishReply(reply, PingReply::ErrorInvalidSequenceNumberResponse);
|
finishReply(reply, PingReply::ErrorInvalidResponse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,10 @@
|
|||||||
|
|
||||||
PingReply::PingReply(QObject *parent) : QObject(parent)
|
PingReply::PingReply(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
|
m_timer = new QTimer(this);
|
||||||
|
m_timer->setSingleShot(true);
|
||||||
|
connect(m_timer, &QTimer::timeout, this, &PingReply::timeout);
|
||||||
|
connect(this, &PingReply::finished, m_timer, &QTimer::stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
QHostAddress PingReply::targetHostAddress() const
|
QHostAddress PingReply::targetHostAddress() const
|
||||||
|
|||||||
@ -51,11 +51,12 @@ class LIBNYMEA_EXPORT PingReply : public QObject
|
|||||||
public:
|
public:
|
||||||
enum Error {
|
enum Error {
|
||||||
ErrorNoError,
|
ErrorNoError,
|
||||||
ErrorInvalidSequenceNumberResponse,
|
ErrorInvalidResponse,
|
||||||
ErrorNetworkDown,
|
ErrorNetworkDown,
|
||||||
ErrorNetworkUnreachable,
|
ErrorNetworkUnreachable,
|
||||||
ErrorPermissionDenied,
|
ErrorPermissionDenied,
|
||||||
ErrorSocketError,
|
ErrorSocketError,
|
||||||
|
ErrorTimeout,
|
||||||
ErrorHostUnreachable
|
ErrorHostUnreachable
|
||||||
};
|
};
|
||||||
Q_ENUM(Error)
|
Q_ENUM(Error)
|
||||||
@ -74,8 +75,10 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void finished();
|
void finished();
|
||||||
|
void timeout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QTimer *m_timer = nullptr;
|
||||||
QHostAddress m_targetHostAddress;
|
QHostAddress m_targetHostAddress;
|
||||||
quint16 m_sequenceNumber = 0;
|
quint16 m_sequenceNumber = 0;
|
||||||
quint16 m_requestId = 0;
|
quint16 m_requestId = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user