mirror of
https://github.com/akuker/RASCSI.git
synced 2026-10-25 12:02:59 +00:00
Refactor the DaynaPort TAP driver around explicit bridge and proxy-ARP profiles. Validate the selected interface and profile before creating piscsi0, attach it only to the preconfigured bridge when appropriate, derive a stable DaynaPort MAC address from the uplink, and make TAP setup and cleanup robust. Add managed host networking profiles for wired bridging and Wi-Fi proxy ARP. Install the PiSCSI NetworkManager templates, profile-management command, parprouted and DHCP relay system services, udev/sysctl hooks, and configuration file so a selected Wi-Fi uplink can provide proxy-ARP networking without altering connectivity during package installation. Expose DaynaPort profiles consistently through the protocol, daemon, scsictl, and web UI. Report interface type, state, and supported modes; require or default explicit mode/interface pairs on attach; present eligible wired-bridge and Wi-Fi proxy-ARP choices; and validate selections before attaching an SCDP device. Package and document the new networking workflow. Add the profile utility and required runtime assets to Debian packages, expand the manual-page set and web manual index, update DaynaPort command help, and extend unit and handler tests for the profile-aware behavior.
232 lines
5.7 KiB
C++
232 lines
5.7 KiB
C++
//---------------------------------------------------------------------------
|
|
//
|
|
// SCSI Target Emulator PiSCSI
|
|
// for Raspberry Pi
|
|
//
|
|
// Copyright (C) 2023 Uwe Seimet
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
#include "network_util.h"
|
|
#include <cstring>
|
|
#include <ifaddrs.h>
|
|
#include <sys/ioctl.h>
|
|
#include <netinet/in.h>
|
|
#include <net/if.h>
|
|
#include <unistd.h>
|
|
#include <netdb.h>
|
|
#include <filesystem>
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <string_view>
|
|
|
|
using namespace std;
|
|
|
|
namespace {
|
|
|
|
#ifdef __linux__
|
|
bool IsVirtualInterface(const string& name)
|
|
{
|
|
const filesystem::path path = filesystem::path("/sys/class/net") / name;
|
|
error_code error;
|
|
return filesystem::weakly_canonical(path, error).string().find("/devices/virtual/net/") != string::npos;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
bool network_util::IsInterfaceUp(const string& interface)
|
|
{
|
|
if (!IsValidInterfaceName(interface)) {
|
|
return false;
|
|
}
|
|
|
|
ifreq ifr = {};
|
|
strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
|
|
const int fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
|
|
if (fd < 0) {
|
|
return false;
|
|
}
|
|
|
|
if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) {
|
|
close(fd);
|
|
return true;
|
|
}
|
|
|
|
close(fd);
|
|
return false;
|
|
}
|
|
|
|
bool network_util::IsValidInterfaceName(const string& interface)
|
|
{
|
|
return !interface.empty() && interface.length() < IFNAMSIZ && ranges::all_of(interface, [] (const char c) {
|
|
return isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '-' || c == '.';
|
|
});
|
|
}
|
|
|
|
vector<uint8_t> network_util::GetMacAddress(const string& interface)
|
|
{
|
|
#ifdef __linux__
|
|
if (!IsValidInterfaceName(interface)) {
|
|
return {};
|
|
}
|
|
|
|
ifreq ifr = {};
|
|
strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
|
|
const int fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
|
|
if (fd < 0) {
|
|
return {};
|
|
}
|
|
|
|
if (!ioctl(fd, SIOCGIFHWADDR, &ifr)) {
|
|
close(fd);
|
|
return vector<uint8_t>(reinterpret_cast<uint8_t*>(ifr.ifr_hwaddr.sa_data),
|
|
reinterpret_cast<uint8_t*>(ifr.ifr_hwaddr.sa_data) + 6);
|
|
}
|
|
|
|
close(fd);
|
|
#else
|
|
(void)interface;
|
|
#endif
|
|
|
|
return {};
|
|
}
|
|
|
|
optional<string> network_util::ParseProxyArpUplink(const string& configuration)
|
|
{
|
|
constexpr string_view key = "PISCSI_PROXYARP_UPLINK=";
|
|
optional<string> uplink;
|
|
for (size_t start = 0; start < configuration.size();) {
|
|
const size_t end = configuration.find('\n', start);
|
|
const string_view line(configuration.data() + start, (end == string::npos ? configuration.size() : end) - start);
|
|
if (line.starts_with(key)) {
|
|
if (uplink) {
|
|
return nullopt;
|
|
}
|
|
uplink = line.substr(key.length());
|
|
}
|
|
if (end == string::npos) {
|
|
break;
|
|
}
|
|
start = end + 1;
|
|
}
|
|
|
|
if (!uplink || !IsValidInterfaceName(*uplink)) {
|
|
return nullopt;
|
|
}
|
|
return uplink;
|
|
}
|
|
|
|
optional<string> network_util::ReadProxyArpUplinkFile(const filesystem::path& path)
|
|
{
|
|
error_code error;
|
|
if (!filesystem::is_regular_file(path, error) || error) {
|
|
return nullopt;
|
|
}
|
|
|
|
try {
|
|
ifstream configuration(path);
|
|
if (!configuration) {
|
|
return nullopt;
|
|
}
|
|
return ParseProxyArpUplink({ istreambuf_iterator<char>(configuration), {} });
|
|
}
|
|
catch (const ios_base::failure&) {
|
|
return nullopt;
|
|
}
|
|
}
|
|
|
|
optional<string> network_util::GetConfiguredProxyArpUplink()
|
|
{
|
|
return ReadProxyArpUplinkFile("/etc/piscsi/network.conf");
|
|
}
|
|
|
|
network_util::NetworkInterfaceType network_util::ClassifyInterface(const bool wireless, const bool bridge,
|
|
const bool tap, const bool virtual_interface, const string& name)
|
|
{
|
|
if (name == "lo") {
|
|
return NetworkInterfaceType::LOOPBACK;
|
|
}
|
|
if (bridge) {
|
|
return NetworkInterfaceType::BRIDGE;
|
|
}
|
|
if (wireless) {
|
|
return NetworkInterfaceType::WIFI;
|
|
}
|
|
if (tap) {
|
|
return NetworkInterfaceType::TAP;
|
|
}
|
|
if (virtual_interface) {
|
|
return NetworkInterfaceType::VIRTUAL;
|
|
}
|
|
return NetworkInterfaceType::ETHERNET;
|
|
}
|
|
|
|
network_util::network_interface_map network_util::GetNetworkInterfaceInfo()
|
|
{
|
|
network_interface_map interfaces;
|
|
|
|
#ifdef __linux__
|
|
ifaddrs *addrs = nullptr;
|
|
if (getifaddrs(&addrs) != 0) {
|
|
return interfaces;
|
|
}
|
|
|
|
for (ifaddrs *entry = addrs; entry != nullptr; entry = entry->ifa_next) {
|
|
if (!entry->ifa_addr || entry->ifa_addr->sa_family != AF_PACKET) {
|
|
continue;
|
|
}
|
|
|
|
const string name = entry->ifa_name;
|
|
if (!IsValidInterfaceName(name) || interfaces.contains(name)) {
|
|
continue;
|
|
}
|
|
|
|
const filesystem::path path = filesystem::path("/sys/class/net") / name;
|
|
const bool bridge = filesystem::is_directory(path / "bridge");
|
|
const bool wireless = filesystem::is_directory(path / "wireless");
|
|
const bool tap = filesystem::exists(path / "tun_flags");
|
|
interfaces.emplace(name, NetworkInterfaceInfo {
|
|
.name = name,
|
|
.type = ClassifyInterface(wireless, bridge, tap, IsVirtualInterface(name), name),
|
|
.up = (entry->ifa_flags & IFF_UP) != 0,
|
|
});
|
|
}
|
|
|
|
freeifaddrs(addrs);
|
|
#endif
|
|
|
|
return interfaces;
|
|
}
|
|
|
|
set<string, less<>> network_util::GetNetworkInterfaces()
|
|
{
|
|
set<string, less<>> network_interfaces;
|
|
|
|
for (const auto& [name, interface] : GetNetworkInterfaceInfo()) {
|
|
if (interface.up && ((interface.type == NetworkInterfaceType::BRIDGE && name == "piscsi_bridge") ||
|
|
interface.type == NetworkInterfaceType::ETHERNET || interface.type == NetworkInterfaceType::WIFI)) {
|
|
network_interfaces.insert(name);
|
|
}
|
|
}
|
|
|
|
return network_interfaces;
|
|
}
|
|
|
|
bool network_util::ResolveHostName(const string& host, sockaddr_in *addr)
|
|
{
|
|
addrinfo hints = {};
|
|
hints.ai_family = AF_INET;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
if (addrinfo *result; !getaddrinfo(host.c_str(), nullptr, &hints, &result)) {
|
|
*addr = *reinterpret_cast<sockaddr_in *>(result->ai_addr); //NOSONAR bit_cast is not supported by the bullseye compiler
|
|
freeaddrinfo(result);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|