diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index f5e59bc2..52bff545 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -47,6 +47,7 @@ file(GLOB common_SRC "src/common/network/rest/http/*.cpp" "src/common/network/tcp/*.cpp" "src/common/network/udp/*.cpp" + "src/common/network/viface/*.cpp" "src/common/yaml/*.cpp" "src/common/*.cpp" ) @@ -90,6 +91,7 @@ file(GLOB common_INCLUDE "src/common/network/rest/http/*.h" "src/common/network/tcp/*.h" "src/common/network/udp/*.h" + "src/common/network/viface/*.h" "src/common/yaml/*.h" "src/common/*.h" ) diff --git a/src/common/network/udp/Socket.h b/src/common/network/udp/Socket.h index 4e642825..dbf4f6cf 100644 --- a/src/common/network/udp/Socket.h +++ b/src/common/network/udp/Socket.h @@ -1,13 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-only -/** +/* * Digital Voice Modem - Common Library * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * @package DVM / Common Library - * @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost) - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * * Copyright (C) 2006-2016,2020 Jonathan Naylor, G4KLX * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL * diff --git a/src/common/network/viface/VIFace.cpp b/src/common/network/viface/VIFace.cpp new file mode 100644 index 00000000..3a65d8e6 --- /dev/null +++ b/src/common/network/viface/VIFace.cpp @@ -0,0 +1,673 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "network/viface/VIFace.h" +#include "Log.h" +#include "Utils.h" + +using namespace network; +using namespace network::viface; + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define DEFAULT_MTU_SIZE 496 + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +uint32_t VIFace::m_idSeq = 0U; + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +/** + * @brief Parses a string MAC address into bytes. + * @param[out] buffer Containing MAC address bytes. + * @param mac MAC address. + * @returns uint8_t* + */ +void parseMAC(uint8_t* buffer, std::string const& mac) +{ + assert(buffer != nullptr); + + uint32_t bytes[6U]; + int scans = sscanf(mac.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x", &bytes[0U], &bytes[1U], &bytes[2U], &bytes[3U], &bytes[4U], &bytes[5U]); + + if (scans != 6) { + return; + } + + for (uint8_t i = 0U; i < 6U; i++) + buffer[i] = (uint8_t)(bytes[i] & 0xFFU); +} + +/** + * @brief Helper routine to hook the virtual interface. + * @param name Name of the virtual interface. + * @param queues + */ +void hookVirtualInterface(std::string name, struct viface_queues* queues) +{ + int fd = -1; + + // creates Tx/Rx sockets and allocates queues + int i = 0; + for (i = 0; i < 2; i++) { + // creates the socket + fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (fd < 0) { + LogError(LOG_NET, "Unable to create the Tx/Rx socket channel %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... + } + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + + strncpy(ifr.ifr_name, name.c_str(), IFNAMSIZ - 1); + + // obtains the network index number + if (ioctl(fd, SIOCGIFINDEX, &ifr) != 0) { + LogError(LOG_NET, "Unable to get network index number %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... + } + + struct sockaddr_ll socket_addr; + memset(&socket_addr, 0, sizeof(struct sockaddr_ll)); + + socket_addr.sll_family = AF_PACKET; + socket_addr.sll_protocol = htons(ETH_P_ALL); + socket_addr.sll_ifindex = ifr.ifr_ifindex; + + // binds the socket to the 'socket_addr' address + if (bind(fd, (struct sockaddr*) &socket_addr, sizeof(socket_addr)) != 0) { + LogError(LOG_NET, "Unable to bind the Tx/Rx socket channel to the network interface %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + goto hookErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... + } + + ((int *)queues)[i] = fd; + } + + return; + +hookErr: + // Rollback close file descriptors + for (--i; i >= 0; i--) { + if (close(((int *)queues)[i]) < 0) { + LogError(LOG_NET, "Unable to close a Rx/Tx socket %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + } + } + + throw std::runtime_error("Failed to hook virtual network interface."); +} + +/** + * @brief Helper routine to allocate and create a virtual network inteface. + * @param name Name of the virtual interface. + * @param tap Tap device (default, true) or Tun device (false). + * @param queues + * @returns std::string + */ +std::string allocateVirtualInterface(std::string name, bool tap, struct viface_queues* queues) +{ + int fd = -1; + + /* + * create structure for ioctl call + * Flags: IFF_TAP - TAP device (layer 2, ethernet frame) + * IFF_TUN - TUN device (layer 3, IP packet) + * IFF_NO_PI - Do not provide packet information + * IFF_MULTI_QUEUE - Create a queue of multiqueue device + */ + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + + ifr.ifr_flags = IFF_NO_PI | IFF_MULTI_QUEUE; + if (tap) { + ifr.ifr_flags |= IFF_TAP; + } else { + ifr.ifr_flags |= IFF_TUN; + } + + strncpy(ifr.ifr_name, name.c_str(), IFNAMSIZ - 1); + + // allocate queues + int i = 0; + for (i = 0; i < 2; i++) { + // open TUN/TAP device + fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK); + if (fd < 0) { + LogError(LOG_NET, "Unable to open TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + goto allocErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... + } + + // register a network device with the kernel + if (ioctl(fd, TUNSETIFF, (void *)&ifr) != 0) { + LogError(LOG_NET, "Unable to register a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + if (close(fd) < 0) { + LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + } + + goto allocErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... + } + + ((int *)queues)[i] = fd; + } + + return std::string(ifr.ifr_name); + +allocErr: + // rollback close file descriptors + for (--i; i >= 0; i--) { + if (close(((int *)queues)[i]) < 0) { + LogError(LOG_NET, "Unable to close a TUN/TAP device %s, queue: %d, err: %d, error: %s", name.c_str(), i, errno, strerror(errno)); + } + } + + throw std::runtime_error("Failed to allocate virtual network interface."); +} + +/** + * @brief + * @param sockfd + * @param name + * @param ifr + */ +void readVIFlags(int sockfd, std::string name, struct ifreq& ifr) +{ + // prepare communication structure + ::memset(&ifr, 0, sizeof(struct ifreq)); + + // set interface name + strncpy(ifr.ifr_name, name.c_str(), IFNAMSIZ - 1); + + // read interface flags + if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) != 0) { + LogError(LOG_NET, "Unable to read virtual interface flags %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + } +} + +/** + * @brief + * @param name + * @param size + * @return uint + */ +uint32_t readMTU(std::string name, size_t size) +{ + int fd = -1, nread = -1; + char buffer[size + 1]; + + // opens MTU file + fd = open(("/sys/class/net/" + name + "/mtu").c_str(), O_RDONLY | O_NONBLOCK); + if (fd < 0) { + LogError(LOG_NET, "Unable to open MTU file for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... + } + + // reads MTU value + nread = read(fd, buffer, size); + buffer[size] = '\0'; + + // Handles errors + if (nread == -1) { + LogError(LOG_NET, "Unable to read MTU for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... + } + + if (close(fd) < 0) { + LogError(LOG_NET, "Unable to close MTU file for virtual interface %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + goto readMTUErr; // bryanb: no good very bad way to handle this -- but if its good enough for the Linux kernel its good enough for us right? this is easiset way to clean up quickly... + } + + return strtoul(buffer, nullptr, 10); + +readMTUErr: + // rollback close file descriptor + close(fd); + + throw std::runtime_error("Failed to read virtual network interface MTU."); +} + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the VIFace class. */ + +VIFace::VIFace(std::string name, bool tap, int id) : + m_ksFd(-1), + m_mac(), + m_ipv4Address("192.168.1.254"), + m_ipv4Netmask("255.255.255.0"), + m_ipv4Broadcast("192.168.1.255"), + m_mtu(DEFAULT_MTU_SIZE) +{ + // check name length + if (name.length() >= IFNAMSIZ) { + throw std::invalid_argument("Virtual interface name too long."); + } + + // create queues + struct viface_queues queues; + ::memset(&queues, 0, sizeof(struct viface_queues)); + + /* + * checks if the path name can be accessed. if so, + * it means that the network interface is already defined + */ + if (access(("/sys/class/net/" + name).c_str(), F_OK) == 0) { + hookVirtualInterface(name, &queues); + m_name = name; + + // read MTU value and resize buffer + m_mtu = readMTU(name, sizeof(m_mtu)); + } else { + m_name = allocateVirtualInterface(name, tap, &queues); + m_mtu = DEFAULT_MTU_SIZE; + } + + m_queues = queues; + + // create socket channels to the NET kernel for later ioctl + m_ksFd = -1; + m_ksFd = socket(AF_INET, SOCK_STREAM, 0); + if (m_ksFd < 0) { + LogError(LOG_NET, "Unable to create IPv4 socket channel to the NET kernel %s, err: %d, error: %s", name.c_str(), errno, strerror(errno)); + throw std::runtime_error("Unable to create IPv4 socket channel to the NET kernel."); + } + + // Set id + if (id < 0) { + m_id = m_idSeq; + m_idSeq++; + } else { + m_id = id; + } +} + +/* Finalizes a instance of the VIFace class. */ + +VIFace::~VIFace() +{ + close(m_queues.rxFd); + close(m_queues.txFd); + close(m_ksFd); +} + +/* Bring up the virtual interface. */ + +void VIFace::up() +{ + if (isUp()) { + LogError(LOG_NET, "Virtual interface %s is already up.", m_name.c_str()); + return; + } + + // read interface flags + struct ifreq ifr; + readVIFlags(m_ksFd, m_name, ifr); + + // set MAC address + if (!m_mac.empty()) { + uint8_t mac[6U]; + ::memset(mac, 0x00U, 6U); + + parseMAC(mac, m_mac); + + ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER; + for (int i = 0; i < 6; i++) { + ifr.ifr_hwaddr.sa_data[i] = mac[i]; + } + + if (ioctl(m_ksFd, SIOCSIFHWADDR, &ifr) != 0) { + LogError(LOG_NET, "Unable to set MAC address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return; + } + } + + // set IPv4 related + struct sockaddr_in* addr = (struct sockaddr_in*) &ifr.ifr_addr; + addr->sin_family = AF_INET; + + // address + if (!m_ipv4Address.empty()) { + if (!inet_pton(AF_INET, m_ipv4Address.c_str(), &addr->sin_addr)) { + LogError(LOG_NET, "Invalid cached IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return; + } + + if (ioctl(m_ksFd, SIOCSIFADDR, &ifr) != 0) { + LogError(LOG_NET, "Unable to set IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return; + } + } + + // netmask + if (!m_ipv4Netmask.empty()) { + if (!inet_pton(AF_INET, m_ipv4Netmask.c_str(), &addr->sin_addr)) { + LogError(LOG_NET, "Invalid cached IPv4 netmask %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return; + } + + if (ioctl(m_ksFd, SIOCSIFNETMASK, &ifr) != 0) { + LogError(LOG_NET, "Unable to set IPv4 netmask %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return; + } + } + + // broadcast + if (!m_ipv4Broadcast.empty()) { + if (!inet_pton(AF_INET, m_ipv4Broadcast.c_str(), &addr->sin_addr)) { + LogError(LOG_NET, "Invalid cached IPv4 broadcast %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return; + } + + if (ioctl(m_ksFd, SIOCSIFBRDADDR, &ifr) != 0) { + LogError(LOG_NET, "Unable to set IPv4 broadcast %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return; + } + } + + // MTU + ifr.ifr_mtu = m_mtu; + if (ioctl(m_ksFd, SIOCSIFMTU, &ifr) != 0) { + LogError(LOG_NET, "Unable to set MTU %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return; + } + + // bring-up interface + ifr.ifr_flags |= IFF_UP; + if (ioctl(m_ksFd, SIOCSIFFLAGS, &ifr) != 0) { + LogError(LOG_NET, "Unable to bring-up virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + } +} + +/* Bring down the virtual interface. */ + +void VIFace::down() const +{ + // read interface flags + struct ifreq ifr; + readVIFlags(m_ksFd, m_name, ifr); + + // bring-down interface + ifr.ifr_flags &= ~IFF_UP; + if (ioctl(m_ksFd, SIOCSIFFLAGS, &ifr) != 0) { + LogError(LOG_NET, "Unable to bring-down virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + } +} + +/* Flag indicating wether or not the virtual interface is up. */ + +bool VIFace::isUp() const +{ + // read interface flags + struct ifreq ifr; + readVIFlags(m_ksFd, m_name, ifr); + + return (ifr.ifr_flags & IFF_UP) != 0; +} + +/* Read a packet from the virtual interface. */ + +ssize_t VIFace::read(uint8_t* buffer) +{ + assert(buffer != nullptr); + + ::memset(buffer, 0x00U, m_mtu); + + int _errno = 0; + + // read packet into our buffer + ssize_t len = ::read(m_queues.rxFd, buffer, m_mtu); + _errno = errno; + + // kernel writes incoming packets to BOTH queues, so we + // need to read from both + if (((len < 0) && (_errno == EAGAIN)) || (len == 0)) { + len = ::read(m_queues.txFd, buffer, m_mtu); + _errno = errno; + } + + if (len == -1) { + LogError(LOG_NET, "Error returned from read, err: %d, error: %s", errno, strerror(errno)); + } + + return len; +} + +/* Write a packet to this virtual interface. */ + +bool VIFace::write(const uint8_t* buffer, uint32_t length, ssize_t* lenWritten) +{ + assert(buffer != nullptr); + + if (length < ETH_HLEN) { + if (lenWritten != nullptr) { + *lenWritten = -1; + } + + LogError(LOG_NET, "Packet is too small, err: %d, error: %s", errno, strerror(errno)); + return false; + } + + if (length > m_mtu) { + if (lenWritten != nullptr) { + *lenWritten = -1; + } + + LogError(LOG_NET, "Packet is too large, err: %d, error: %s", errno, strerror(errno)); + return false; + } + + // write packet to TX queue + bool result = false; + ssize_t sent = ::write(m_queues.txFd, buffer, length); + if (sent < 0) { + LogError(LOG_NET, "Error returned from write, err: %d, error: %s", errno, strerror(errno)); + + if (lenWritten != nullptr) { + *lenWritten = -1; + } + } + else { + if (sent == ssize_t(length)) + result = true; + + if (lenWritten != nullptr) { + *lenWritten = sent; + } + } + + return result; +} + +/* Set the MAC address of the virtual interface. */ + +void VIFace::setMAC(std::string mac) +{ + m_mac = mac; +} + +/* Gets the virtual interfaces associated MAC address. */ + +std::string VIFace::getMAC() const +{ + // read interface flags + struct ifreq ifr; + readVIFlags(m_ksFd, m_name, ifr); + + if (ioctl(m_ksFd, SIOCGIFHWADDR, &ifr) != 0) { + LogError(LOG_NET, "Unable to get MAC address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return std::string(); + } + + // convert binary MAC address to string + std::ostringstream addr; + addr << std::hex << std::setfill('0'); + for (int i = 0; i < 6; i++) { + addr << std::setw(2) << (uint8_t)(0xFF & ifr.ifr_hwaddr.sa_data[i]); + if (i != 5) { + addr << ":"; + } + } + + return addr.str(); +} + +/* Set the IPv4 address of the virtual interface. */ + +void VIFace::setIPv4(std::string address) +{ + struct in_addr addr; + if (!inet_pton(AF_INET, address.c_str(), &addr)) { + LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return; + } + + m_ipv4Address = address; +} + +/* Gets the IPV4 address of the virtual interface. */ + +std::string VIFace::getIPv4() const +{ + return ioctlGetIPv4(SIOCGIFADDR); +} + +/* Set the IPv4 netmask of the virtual interface. */ + +void VIFace::setIPv4Netmask(std::string netmask) +{ + struct in_addr addr; + if (!inet_pton(AF_INET, netmask.c_str(), &addr)) { + LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return; + } + + m_ipv4Netmask = netmask; +} + +/* Gets the IPv4 netmask of the virtual interface. */ + +std::string VIFace::getIPv4Netmask() const +{ + return ioctlGetIPv4(SIOCGIFNETMASK); +} + +/* Set the IPv4 broadcast address of the virtual interface. */ + +void VIFace::setIPv4Broadcast(std::string broadcast) +{ + struct in_addr addr; + if (!inet_pton(AF_INET, broadcast.c_str(), &addr)) { + LogError(LOG_NET, "Invalid IPv4 address %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return; + } + + m_ipv4Broadcast = broadcast; +} + +/* Gets the IPv4 broadcast address of the virtual interface. */ + +std::string VIFace::getIPv4Broadcast() const +{ + return ioctlGetIPv4(SIOCGIFBRDADDR); +} + +/* Sets the MTU of the virtual interface. */ + +void VIFace::setMTU(uint32_t mtu) +{ + if (mtu < ETH_HLEN) { + LogError(LOG_NET, "MTU %d is too small %s, err: %d, error: %s", mtu, m_name.c_str(), errno, strerror(errno)); + return; + } + + // are we sure about this upper validation? + // lo interface reports this number for its MTU + if (mtu > 65536) { + LogError(LOG_NET, "MTU %d is too large %s, err: %d, error: %s", mtu, m_name.c_str(), errno, strerror(errno)); + return; + } + + m_mtu = mtu; +} + +/* Gets the MTU of the virtual interface. */ + +uint32_t VIFace::getMTU() const +{ + // read interface flags + struct ifreq ifr; + readVIFlags(m_ksFd, m_name, ifr); + + if (ioctl(m_ksFd, SIOCGIFMTU, &ifr) != 0) { + LogError(LOG_NET, "Unable to get MTU address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return 0U; + } + + return ifr.ifr_mtu; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Internal helper that performs a kernel IOCTL to get the IPv4 address by request. */ + +std::string VIFace::ioctlGetIPv4(uint64_t request) const +{ + // read interface flags + struct ifreq ifr; + readVIFlags(m_ksFd, m_name, ifr); + + if (ioctl(m_ksFd, request, &ifr) != 0) { + LogError(LOG_NET, "Unable to get IPv4 address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return std::string(); + } + + // convert binary IP address to string + char addr[INET_ADDRSTRLEN]; + ::memset(&addr, 0, sizeof(addr)); + + struct sockaddr_in* ipaddr = (struct sockaddr_in*) &ifr.ifr_addr; + if (inet_ntop(AF_INET, &(ipaddr->sin_addr), addr, sizeof(addr)) == NULL) { + LogError(LOG_NET, "Unable to convert IPv4 address for virtual interface %s, err: %d, error: %s", m_name.c_str(), errno, strerror(errno)); + return std::string(); + } + + return std::string(addr); +} diff --git a/src/common/network/viface/VIFace.h b/src/common/network/viface/VIFace.h new file mode 100644 index 00000000..b3977426 --- /dev/null +++ b/src/common/network/viface/VIFace.h @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup viface Virtual Interface TUN/TAP + * @brief Implementation for handling, manipulating and interfacing with TUN/TAP interfaces. + * @ingroup socket + * + * @file VIFace.h + * @ingroup viface + * @file VIFace.cpp + * @ingroup viface + */ +#if !defined(__VIFACE_H__) +#define __VIFACE_H__ + +#include "common/Defines.h" + +#include + +namespace network +{ + namespace viface + { + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief + * @ingroup viface + */ + struct viface_queues + { + int rxFd; //! Receive Packet File Descriptor + int txFd; //! Transmit Packet File Descriptor + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements virtual network interface routines to communicate using a virtual + * network interface. + * @ingroup viface + */ + class HOST_SW_API VIFace { + public: + /** + * @brief Initializes a new instance of the VIFace class. + * @param name Name of the virtual interface. The placeholder %d can be used and a number will be assigned to it. + * @param tap Tap device (default, true) or Tun device (false). + * @param id Optional numeric ID. If given id < 0 a sequential number will be given. + */ + explicit VIFace(std::string name = "viface%d", bool tap = true, int id = -1); + /** + * @brief Finalizes a instance of the VIFace class. + */ + ~VIFace(); + + /** + * @brief Helper to get the unique associated numerical ID for the interface. + * @returns uint32_t The numeric id of the virtual interface. + */ + uint32_t getID() const { return m_id; } + + /** + * @brief Bring up the virtual interface. + * + * This call will configure and bring up the interface. + * Exceptions are thrown in case of configuration or bring-up + * failures. + */ + void up(); + /** + * @brief Bring down the virtual interface. + * + * Exceptions are thrown in case of misbehaviours. + */ + void down() const; + + /** + * @brief Flag indicating wether or not the virtual interface is up. + * @returns bool True, if virtual interface is up, otherwise false. + */ + bool isUp() const; + + /** + * @brief Read a packet from the virtual interface. + * + * Note: Reading a packet from a virtual interface means that another + * userspace program (or the kernel) sent a packet to the network + * interface with the name of the instance of this class. If not packet + * was available, and empty vector is returned. + * + * @param[out] buffer The packet (if tun) or frame (if tap) as a binary blob + * (array of bytes). + * @returns ssize_t Actual length of data read from remote UDP socket. + */ + ssize_t read(uint8_t* buffer); + /** + * @brief Write a packet to this virtual interface. + * + * Note: Writing a packet to this virtual interface means that it + * will reach any userspace program (or the kernel) listening for + * packets in the interface with the name of the instance of this + * class. + * + * @param[in] buffer Packet (if tun) or frame (if tap) to send as a + * binary blob (array of bytes). + * @param length Length of data to write. + * @param[out] lenWritten Total number of bytes written. + * @returns bool True, if message was sent otherwise, false. + */ + bool write(const uint8_t* buffer, uint32_t length, ssize_t* lenWritten = nullptr); + + /** + * @brief Set the MAC address of the virtual interface. + * + * The format of the MAC address is verified, but is just until up() + * is called that the library will try to attempt to write it. + * If you don't provide a MAC address (the default) one will be + * automatically assigned when bringing up the interface. + * + * @param mac New MAC address for this virtual interface in the form "d8:9d:67:d3:65:1f". + */ + void setMAC(std::string mac); + /** + * @brief Gets the virtual interfaces associated MAC address. + * @returns std::string The current MAC address of the virtual interface. + * An empty string means no associated MAC address. + */ + std::string getMAC() const; + + /** + * @brief Set the IPv4 address of the virtual interface. + * + * The format of the IPv4 address is verified, but is just until up() + * is called that the library will try to attempt to write it. + * + * @param addr New IPv4 address for this virtual interface in the form "172.17.42.1". + */ + void setIPv4(std::string address); + + /** + * @brief Gets the IPV4 address of the virtual interface. + * @returns std::string The current IPv4 address of the virtual interface. + * An empty string means no associated IPv4 address. + */ + std::string getIPv4() const; + + /** + * @brief Set the IPv4 netmask of the virtual interface. + * + * The format of the IPv4 netmask is verified, but is just until up() + * is called that the library will try to attempt to write it. + * + * @param netmask New IPv4 netmask for this virtual interface in the form "255.255.255.0". + */ + void setIPv4Netmask(std::string netmask); + /** + * @brief Gets the IPv4 netmask of the virtual interface. + * @return std::string The current IPv4 netmask of the virtual interface. + * An empty string means no associated IPv4 netmask. + */ + std::string getIPv4Netmask() const; + + /** + * @brief Set the IPv4 broadcast address of the virtual interface. + * + * The format of the IPv4 broadcast address is verified, but is just + * until up() is called that the library will try to attempt to write + * it. + * + * @param broadcast New IPv4 broadcast address for this virtual interface in the form "172.17.42.255". + */ + void setIPv4Broadcast(std::string broadcast); + /** + * @brief Gets the IPv4 broadcast address of the virtual interface. + * @return std::string The current IPv4 broadcast address of the virtual interface. + * An empty string means no associated IPv4 broadcast address. + */ + std::string getIPv4Broadcast() const; + + /** + * @brief Sets the MTU of the virtual interface. + * + * The range of the MTU is verified, but is just until up() is called + * that the library will try to attempt to write it. + * + * @param mtu New MTU for this virtual interface. + */ + void setMTU(uint32_t mtu); + /** + * @brief Gets the MTU of the virtual interface. + * + * MTU is the size of the largest packet or frame that can be sent + * using this interface. + * + * @returns uint32_t The current MTU of the virtual interface. + */ + uint32_t getMTU() const; + + public: + /** + * @brief Virtual Interface associated name. + */ + __PROPERTY(std::string, name, Name); + + private: + struct viface_queues m_queues; + + int m_ksFd; + + std::string m_mac; + std::string m_ipv4Address; + std::string m_ipv4Netmask; + std::string m_ipv4Broadcast; + + uint32_t m_mtu; + + uint32_t m_id; + static uint32_t m_idSeq; + + /** + * @brief Internal helper that performs a kernel IOCTL to get the IPv4 address by request. + * @param request IOCTL request. + * @return std::string IPv4 address. + */ + std::string ioctlGetIPv4(uint64_t request) const; + }; + } // namespace viface +} // namespace network + +#endif // __VIFACE_H__