This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
Simon Stürz c59185672f Add mac address class and unit tests
Finish updated network discovery
Improve ARP and monitor handling
Introduce network device info cache housekeeping
2022-04-13 11:08:29 +02:00

490 lines
20 KiB
C++

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "arpsocket.h"
#include "loggingcategories.h"
#include "networkutils.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <net/if_arp.h>
#include <net/ethernet.h>
#include <netinet/if_ether.h>
#include <QHostInfo>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QDataStream>
NYMEA_LOGGING_CATEGORY(dcArpSocket, "ArpSocket")
NYMEA_LOGGING_CATEGORY(dcArpSocketTraffic, "ArpSocketTraffic")
#define ETHER_PROTOCOL_LEN 4 // Length of the IPv4 address
#define ETHER_HEADER_LEN sizeof(struct ether_header)
#define ETHER_ARP_LEN sizeof(struct ether_arp)
#define ETHER_ARP_PACKET_LEN ETHER_HEADER_LEN + ETHER_ARP_LEN
ArpSocket::ArpSocket(QObject *parent) : QObject(parent)
{
}
bool ArpSocket::sendRequest()
{
if (!m_isOpen)
return false;
// Initially load the arp cache before we start to broadcast
//loadArpCache();
// Send the ARP request trough each network interface
qCDebug(dcArpSocket()) << "Sending ARP request to all local network interfaces...";
foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
sendRequest(networkInterface);
}
return true;
}
bool ArpSocket::sendRequest(const QString &interfaceName)
{
if (!m_isOpen)
return false;
// Get the interface
qCDebug(dcArpSocket()) << "Sending ARP request to all network interfaces" << interfaceName << "...";
QNetworkInterface networkInterface = QNetworkInterface::interfaceFromName(interfaceName);
if (!networkInterface.isValid()) {
qCWarning(dcArpSocket()) << "Failed to send the ARP request to network interface" << interfaceName << "because the interface is not valid.";
return false;
}
return sendRequest(networkInterface);
}
bool ArpSocket::sendRequest(const QNetworkInterface &networkInterface)
{
if (!m_isOpen)
return false;
// Skip local host
if (networkInterface.flags().testFlag(QNetworkInterface::IsLoopBack))
return false;
// If have no interface indes, we cannot use this network
if (networkInterface.index() == 0) {
qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because the system interface index is unknown.";
return false;
}
// Check if the interface is up and running
if (!networkInterface.flags().testFlag(QNetworkInterface::IsUp)) {
qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because it is not up.";
return false;
}
if (!networkInterface.flags().testFlag(QNetworkInterface::IsRunning)) {
qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because it is not running.";
return false;
}
// Verify we have a hardware address (virtual network interfaces like tunnels)
if (networkInterface.hardwareAddress().isEmpty()) {
qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because there is no hardware address which is required for ARP.";
return false;
}
qCDebug(dcArpSocket()) << "Verifying network interface" << networkInterface.name() << networkInterface.hardwareAddress() << "...";
foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) {
// Only IPv4
if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol)
continue;
qCDebug(dcArpSocket()) << " Host address:" << entry.ip().toString();
qCDebug(dcArpSocket()) << " Broadcast address:" << entry.broadcast().toString();
qCDebug(dcArpSocket()) << " Netmask:" << entry.netmask().toString();
quint32 addressRangeStart = entry.ip().toIPv4Address() & entry.netmask().toIPv4Address();
quint32 addressRangeStop = entry.broadcast().toIPv4Address() | addressRangeStart;
quint32 range = addressRangeStop - addressRangeStart;
qCDebug(dcArpSocket()) << " Address range" << range << " | from" << QHostAddress(addressRangeStart).toString() << "-->" << QHostAddress(addressRangeStop).toString();
if (range > 255) {
qCWarning(dcArpSocket()) << "Not sending ARP requests to the network" << networkInterface.name() << "because it has a to wide range for ARP broadcast pinging.";
return false;
}
qCDebug(dcArpSocket()) << "Start sending ARP requests to each host within the range...";
// Send ARP request to each address within the range
for (quint32 i = 0; i < range; i++) {
quint32 address = addressRangeStart + i;
QHostAddress targetAddress(address);
if (targetAddress == entry.ip())
continue;
sendRequestInternally(networkInterface.index(), MacAddress(networkInterface.hardwareAddress()), entry.ip(), MacAddress::broadcast(), targetAddress);
}
}
return true;
}
bool ArpSocket::sendRequest(const QHostAddress &targetAddress)
{
if (!m_isOpen)
return false;
if (targetAddress.protocol() != QAbstractSocket::IPv4Protocol) {
qCWarning(dcArpSocket()) << "Not sending ARP request to host" << targetAddress << "because only IPv4 is supported.";
return false;
}
qCDebug(dcArpSocket()) << "Sending ARP request to host" << targetAddress.toString() << "...";
foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) {
// Only IPv4
if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol)
continue;
qCDebug(dcArpSocket()) << "Check subnet for" << networkInterface.name() << entry.ip() << entry.netmask();
if (targetAddress.isInSubnet(entry.ip(), entry.prefixLength())) {
return sendRequestInternally(networkInterface.index(), MacAddress(networkInterface.hardwareAddress()), entry.ip(), MacAddress::broadcast(), targetAddress);
} else {
qCDebug(dcArpSocket()) << targetAddress << "is not part of subnet" << entry.ip() << "netmask" << entry.netmask() << "netmask int" << entry.netmask().toIPv4Address();
}
}
}
qCWarning(dcArpSocket()) << "Failed to send ARP request to" << targetAddress.toString() << "because no valid network interface could be found.";
return false;
}
bool ArpSocket::isOpen() const
{
return m_isOpen;
}
bool ArpSocket::openSocket()
{
qCDebug(dcArpSocket()) << "Open ARP socket...";
if (m_isOpen) {
qCWarning(dcArpSocket()) << "Failed to enable ARP scanner because the scanner is already running.";
return false;
}
// Build socket descriptor
m_socketDescriptor = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
if (m_socketDescriptor < 0) {
qCWarning(dcArpSocket()) << "Failed to create the ARP capture socket for" << "." << strerror(errno);
return false;
}
// Configure non blocking
if (fcntl(m_socketDescriptor, F_SETFL, fcntl(m_socketDescriptor, F_GETFL, 0) | O_NONBLOCK) != 0) {
qCWarning(dcArpSocket()) << "Failed to set the ARP socket function control to non-blocking" << strerror(errno);
close(m_socketDescriptor);
return false;
}
m_socketNotifier = new QSocketNotifier(m_socketDescriptor, QSocketNotifier::Read, this);
m_socketNotifier->setEnabled(false);
connect(m_socketNotifier, &QSocketNotifier::activated, this, [=](int socket){
if (socket != m_socketDescriptor)
return;
// Make sure to read all data from the socket...
int bytesReceived = 0;
while (bytesReceived >= 0) {
unsigned char receiveBuffer[ETHER_ARP_PACKET_LEN];
memset(&receiveBuffer, 0, sizeof(receiveBuffer));
// Read the buffer
bytesReceived = recv(m_socketDescriptor, receiveBuffer, ETHER_ARP_PACKET_LEN, 0);
if (bytesReceived < 0)
continue;
processDataBuffer(receiveBuffer, bytesReceived);
}
});
m_socketNotifier->setEnabled(true);
m_isOpen = true;
qCDebug(dcArpSocket()) << "ARP enabled successfully";
// Send broadcast request
//sendRequest();
return true;
}
void ArpSocket::closeSocket()
{
m_isOpen = false;
if (m_socketNotifier) {
m_socketNotifier->setEnabled(false);
delete m_socketNotifier;
m_socketNotifier = nullptr;
}
if (m_socketDescriptor >= 0) {
close(m_socketDescriptor);
m_socketDescriptor = -1;
}
qCDebug(dcArpSocket()) << "ARP disabled successfully";
}
bool ArpSocket::sendRequestInternally(int networkInterfaceIndex, const MacAddress &senderMacAddress, const QHostAddress &senderHostAddress, const MacAddress &targetMacAddress, const QHostAddress &targetHostAddress)
{
// Set up data structures
unsigned char sendingBuffer[ETHER_ARP_PACKET_LEN];
memset(sendingBuffer, 0, ETHER_ARP_PACKET_LEN);
struct ether_header *etherHeader = (struct ether_header *)sendingBuffer;
struct ether_arp *arpPacket = (struct ether_arp *)(sendingBuffer + sizeof(struct ether_header));
// Build the ethernet header
fillMacAddress(etherHeader->ether_dhost, targetMacAddress);
fillMacAddress(etherHeader->ether_shost, senderMacAddress);
etherHeader->ether_type = htons(ETHERTYPE_ARP);
// Build the ARP header
arpPacket->ea_hdr.ar_hrd = htons(ARPHRD_ETHER);
arpPacket->ea_hdr.ar_pro = htons(ETH_P_IP);
arpPacket->ea_hdr.ar_hln = ETHER_ADDR_LEN;
arpPacket->ea_hdr.ar_pln = ETHER_PROTOCOL_LEN;
arpPacket->ea_hdr.ar_op = htons(ARPOP_REQUEST);
// Write the ARP packet
fillMacAddress(arpPacket->arp_sha, senderMacAddress);
fillHostAddress(arpPacket->arp_spa, senderHostAddress);
fillMacAddress(arpPacket->arp_tha, targetMacAddress);
fillHostAddress(arpPacket->arp_tpa, targetHostAddress);
struct sockaddr_ll socketAddress;
memset(&socketAddress, 0, sizeof(socketAddress));
socketAddress.sll_family = AF_PACKET;
socketAddress.sll_protocol = htons(ETH_P_ARP);
socketAddress.sll_ifindex = networkInterfaceIndex;
socketAddress.sll_hatype = htons(ARPHRD_ETHER);
socketAddress.sll_pkttype = PACKET_BROADCAST;
socketAddress.sll_halen = ETH_ALEN;
memset(socketAddress.sll_addr, 0x00, 6);
//qCDebug(dcArpSocket()) << "Send ARP request to" << targetHostAddress.toString();
int bytesSent = sendto(m_socketDescriptor, sendingBuffer, ETHER_ARP_PACKET_LEN, 0, (struct sockaddr *)&socketAddress, sizeof(socketAddress));
if (bytesSent < 0) {
qCWarning(dcArpSocket()) << "Failed to send ARP packet data to" << targetHostAddress.toString() << strerror(errno);
return false;
}
return true;
}
void ArpSocket::processDataBuffer(unsigned char *receiveBuffer, int size)
{
// Parse data using structs header + arp
QByteArray receivedBufferBytes;
for (int i = 0; i < size; i++) {
receivedBufferBytes.append(receiveBuffer[i]);
}
struct ether_header *etherHeader = (struct ether_header *)(receiveBuffer);
struct ether_arp *arpPacket = (struct ether_arp *)(receiveBuffer + ETHER_HEADER_LEN);
MacAddress senderMacAddress = MacAddress(arpPacket->arp_sha);
QHostAddress senderHostAddress = getHostAddressString(arpPacket->arp_spa);
MacAddress targetMacAddress = MacAddress(arpPacket->arp_tha);
QHostAddress targetHostAddress = getHostAddressString(arpPacket->arp_tpa);
uint16_t etherType = htons(etherHeader->ether_type);
if (etherType != ETHERTYPE_ARP) {
qCWarning(dcArpSocketTraffic()) << "Received ARP socket data header with invalid type" << etherType;
return;
}
// Filter for ARP replies
uint16_t arpOperationCode = htons(arpPacket->arp_op);
switch (arpOperationCode) {
case ARPOP_REQUEST: {
// The sender of the arp request provides ip and mac.
// Lets find the corresponding interface and use it for the discovery and monitor
if (senderHostAddress.isNull())
return;
QNetworkInterface networkInterface = NetworkUtils::getInterfaceForHostaddress(senderHostAddress);
if (!networkInterface.isValid()) {
qCWarning(dcArpSocket()) << "Could not find local interface from ARP request" << senderHostAddress.toString() << senderMacAddress.toString();
return;
}
// Note: we are not interested in our own requests
if (senderMacAddress != MacAddress(networkInterface.hardwareAddress())) {
qCDebug(dcArpSocket()) << "ARP request" << receivedBufferBytes.toHex() << "from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString() << "on" << networkInterface.name();
emit arpRequestReceived(networkInterface, senderHostAddress, senderMacAddress);
}
break;
}
case ARPOP_REPLY: {
QNetworkInterface networkInterface = NetworkUtils::getInterfaceForMacAddress(targetMacAddress);
if (!networkInterface.isValid()) {
qCWarning(dcArpSocket()) << "Could not find local interface from ARP response" << targetHostAddress.toString() << targetMacAddress.toString();
return;
}
qCDebug(dcArpSocket()) << "ARP reply" << receivedBufferBytes.toHex() << "from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString() << "on" << networkInterface.name();
emit arpResponse(networkInterface, senderHostAddress, senderMacAddress);
break;
}
case ARPOP_RREQUEST:
qCDebug(dcArpSocketTraffic()) << "RARP request from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString();
break;
case ARPOP_RREPLY:
qCDebug(dcArpSocketTraffic()) << "PARP response from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString();
break;
case ARPOP_InREQUEST:
qCDebug(dcArpSocketTraffic()) << "InARP request from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString();
break;
case ARPOP_InREPLY:
qCDebug(dcArpSocketTraffic()) << "InARP response from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString();
break;
case ARPOP_NAK:
qCDebug(dcArpSocketTraffic()) << "(ATM)ARP NAK from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString();
break;
default:
qCWarning(dcArpSocketTraffic()) << "Received unhandled ARP operation code" << arpOperationCode << "from" << senderMacAddress.toString() << senderHostAddress.toString();
break;
}
}
QHostAddress ArpSocket::getHostAddressString(uint8_t *senderIpAddress)
{
QStringList values;
for (int i = 0; i < ETHER_PROTOCOL_LEN; i++) {
values.append(QString("%1").arg(senderIpAddress[i]));
}
return QHostAddress(values.join("."));
}
bool ArpSocket::loadArpCache(const QNetworkInterface &interface)
{
QFile arpFile("/proc/net/arp");
qCDebug(dcArpSocket()) << "Loading ARP cache from system" << arpFile.fileName() << "...";
if (!arpFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCWarning(dcArpSocket()) << "Failed to load ARP cache from" << arpFile.fileName() << arpFile.errorString();
return false;
}
// Read all data
QByteArray data = arpFile.readAll();
arpFile.close();
// Parse data line by line
int lineCount = -1;
QTextStream stream(&data);
while (!stream.atEnd()) {
QString line = stream.readLine();
lineCount += 1;
// Skip the first line since it's just the header
if (lineCount == 0)
continue;
qCDebug(dcArpSocket()) << "Checking line" << line;
QStringList columns = line.split(QLatin1Char(' '));
columns.removeAll("");
// Make sure we have enought token
if (columns.count() < 6) {
qCWarning(dcArpSocket()) << "ARP cache line has invalid column count" << line;
continue;
}
QHostAddress address(columns.at(0).trimmed());
if (address.isNull()) {
qCWarning(dcArpSocket()) << "ARP cache line has invalid IP address";
continue;
}
QString macAddressString = columns.at(3).trimmed();
MacAddress macAddress(macAddressString);
if (macAddress.isNull()) {
qCDebug(dcArpSocket()) << "ARP cache line has invalid MAC address" << macAddressString;
continue;
}
QNetworkInterface addressInterface = QNetworkInterface::interfaceFromName(columns.at(5));
if (!addressInterface.isValid()) {
qCDebug(dcArpSocket()) << "ARP cache line has invalid network interface identifier" << columns << columns.at(5);
continue;
}
// Check if we filter for specific interfaces
if (interface.isValid() && addressInterface.name() != interface.name())
continue;
qCDebug(dcArpSocket()) << "Loaded from cache" << address.toString() << macAddress.toString() << addressInterface.name();
emit arpResponse(addressInterface, address, macAddress);
}
return true;
}
void ArpSocket::fillMacAddress(uint8_t *targetArray, const MacAddress &macAddress)
{
QStringList macValues = macAddress.toString().split(":");
for (int i = 0; i < ETHER_ADDR_LEN; i++) {
targetArray[i] = macValues.at(i).toUInt(nullptr, 16);
}
}
void ArpSocket::fillHostAddress(uint8_t *targetArray, const QHostAddress &hostAddress)
{
QByteArray hostData;
QDataStream stream(&hostData, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
stream << hostAddress.toIPv4Address();
for (int i = 0; i < ETHER_PROTOCOL_LEN; i++) {
targetArray[i] = hostData.at(i);
}
}