From 0a8d9e5287b8042e6a71e2258fb57dca87bf13d1 Mon Sep 17 00:00:00 2001 From: haskal Date: Tue, 16 Oct 2018 23:11:21 -0400 Subject: [PATCH] Somewhat functional --- .clang-format | 113 +++++++++++++++++++++++ .gitignore | 2 + CMakeLists.txt | 18 ++++ common.cpp | 84 +++++++++++++++++ common.h | 44 +++++++++ main.cpp | 240 ++++++++++++++++++++++++++++++++++++++++++++++++ tundev.cpp | 241 +++++++++++++++++++++++++++++++++++++++++++++++++ tundev.h | 72 +++++++++++++++ 8 files changed, 814 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 common.cpp create mode 100644 common.h create mode 100644 main.cpp create mode 100644 tundev.cpp create mode 100644 tundev.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b23e338 --- /dev/null +++ b/.clang-format @@ -0,0 +1,113 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 100 # changed +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 # changed +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never +... + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5880ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +compile_commands.json +/build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7f1441c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 2.8) + +project(h804tun) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall -Wextra") +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +option(WITH_SYSTEMD "Enable systemd new-style daemon support" ON) + +find_package(Threads) + +add_executable(h804tun main.cpp common.cpp tundev.cpp) +target_link_libraries(h804tun ${CMAKE_THREAD_LIBS_INIT}) + +if (WITH_SYSTEMD) +add_definitions(-DH804_SYSTEMD_ENABLE) +target_link_libraries(h804tun -lsystemd) +endif (WITH_SYSTEMD) diff --git a/common.cpp b/common.cpp new file mode 100644 index 0000000..cf49121 --- /dev/null +++ b/common.cpp @@ -0,0 +1,84 @@ +#include "common.h" + +#include +#include +#include + +namespace h804 { +void daemon_notify_ready() { +#ifdef H804_SYSTEMD_ENABLE + sd_notify(0, "READY=1"); +#endif +} + +// clang-format off +const char* _log_text[(size_t)LogLevel::_SIZE] = { +#ifdef H804_SYSTEMD_ENABLE + SD_DEBUG "[DEBUG] ", + SD_INFO "[INFO ] ", + SD_NOTICE "[NOTIC] ", + SD_WARNING "[WARN ] ", + SD_ERR "[ERROR] " +#else + "[DEBUG] ", + "[INFO ] ", + "[NOTIC] ", + "[WARN ] ", + "[ERROR] " +#endif +}; + +const char* _log_colors[(size_t)LogLevel::_SIZE] = { + "[DEBUG] ", + "\e[0;32m[INFO ] ", + "\e[0;36m[NOTIC] ", + "\e[1;93m[WARN ] ", + "\e[1;91m[ERROR] " +}; +// clang-format on +const char* log_reset = "\e[0m"; + +void _log(const LogLevel, const std::string&); +void _log(const LogLevel, const char*); + +void _log(const LogLevel level, const std::string& text) { + if (isatty(STDERR_FILENO)) { + std::cerr << _log_colors[(size_t)level] << text << log_reset << std::endl; + } else { + std::cerr << _log_text[(size_t)level] << text << std::endl; + } +} + +void _log(const LogLevel level, const char* text) { + if (isatty(STDERR_FILENO)) { + std::cerr << _log_colors[(size_t)level] << text << log_reset << std::endl; + } else { + std::cerr << _log_text[(size_t)level] << text << std::endl; + } +} + +_Logger::_Logger(LogLevel level) : level(level) {} + +void _Logger::operator<<(const std::string& str) const { _log(level, str); } + +void _Logger::operator<<(const char* str) const { _log(level, str); } + +errno_exception::errno_exception() noexcept : _what(strerror(errno)) {} + +errno_exception::errno_exception(const char* msg) noexcept + : _what(std::string(msg) + ": " + strerror(errno)) {} + +const char* errno_exception::what() const noexcept { return this->_what.c_str(); } + +void check(long x) { + if (x < 0) { + throw errno_exception(); + } +} +void check(long x, const char* msg) { + if (x < 0) { + throw errno_exception(msg); + } +} + +} // namespace h804 diff --git a/common.h b/common.h new file mode 100644 index 0000000..40f7dcf --- /dev/null +++ b/common.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#ifdef H804_SYSTEMD_ENABLE +#include +#endif + +namespace h804 { +enum class LogLevel : size_t { DEBUG, INFO, NOTICE, WARN, ERROR, _SIZE }; + +void daemon_notify_ready(); +void _log(const LogLevel, const std::string&); +void _log(const LogLevel, const char*); + +class _Logger { + LogLevel level; + + public: + _Logger(LogLevel level); + void operator<<(const std::string&) const; + void operator<<(const char*) const; +}; + +const _Logger debug(LogLevel::DEBUG); +const _Logger info(LogLevel::INFO); +const _Logger notice(LogLevel::NOTICE); +const _Logger warn(LogLevel::WARN); +const _Logger error(LogLevel::ERROR); + +class errno_exception : public std::exception { + const std::string _what; + + public: + errno_exception() noexcept; + errno_exception(const char* msg) noexcept; + const char* what() const noexcept; +}; + +void check(long x); +void check(long x, const char* msg); + +} // namespace h804 diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..4d5e213 --- /dev/null +++ b/main.cpp @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "tundev.h" + +using h804::debug; +using h804::error; +using h804::info; +using h804::notice; +using h804::warn; + +using namespace std::literals::chrono_literals; + +std::mutex main_mut; +std::condition_variable main_waiter; +bool do_exit = false; + +// If the first byte of a frame is this, then it specifies a special control message +constexpr uint8_t H804_PROT_MARKER = 0x84; +// On receiving this packet, a machine should add the sender's MAC to its peer list +constexpr uint8_t H804_PROT_PEER_REQ = 0x01; +// Request peer list from a machine +constexpr uint8_t H804_PROT_SHARE_REQ = 0x03; +// Peer list response +constexpr uint8_t H804_PROT_SHARE_RSP = 0x04; + +std::unordered_set peers; +std::mutex peers_mut; +#define PEERS_LOCK() std::lock_guard guard(peers_mut) + +struct __attribute__((packed)) h804_raw_mac_list { + size_t num_macs; + uint8_t macs[][6]; +}; + +struct __attribute__((packed)) h804_prot { + uint8_t marker; + uint8_t type; + union { + h804_raw_mac_list share_list; + uint8_t other; + }; +}; + +void send_peer_req(h804::tun::EthSocket& source, h804::tun::macaddr_t to) { + std::array buf{}; + h804_prot* prot = reinterpret_cast(buf.data()); + prot->marker = H804_PROT_MARKER; + prot->type = H804_PROT_PEER_REQ; + source.write(buf, 2, to); +} + +void send_share_req(h804::tun::EthSocket& source, h804::tun::macaddr_t to) { + info << "Sending share req to " + to.to_string(); + std::array buf{}; + h804_prot* prot = reinterpret_cast(buf.data()); + prot->marker = H804_PROT_MARKER; + prot->type = H804_PROT_SHARE_REQ; + source.write(buf, 2, to); +} + +void send_share_rsp(h804::tun::EthSocket& source, h804::tun::macaddr_t to) { + PEERS_LOCK(); + std::array buf{}; + h804_prot* prot = reinterpret_cast(buf.data()); + prot->marker = H804_PROT_MARKER; + prot->type = H804_PROT_SHARE_RSP; + prot->share_list.num_macs = peers.size(); + size_t i = 0; + for (const auto& it : peers) { + for (size_t j = 0; j < 6; j++) { + prot->share_list.macs[i][j] = it[j]; + } + i++; + } + source.write(buf, h804::tun::TUN_MTU, to); +} + +void handle_share_rsp(const h804::tun::EthSocket& source, const h804_prot* prot, size_t size, + h804::tun::macaddr_t from) { + PEERS_LOCK(); + size_t num_macs = prot->share_list.num_macs; + if (num_macs * 6 + sizeof(size_t) + 2 * sizeof(uint8_t) > size) { + warn << "Out of bounds num_macs in share rsp"; + return; + } + peers.insert(from); + for (size_t i = 0; i < num_macs; i++) { + peers.emplace(prot->share_list.macs[i]); + } + peers.erase(peers.find(source.get_source_mac())); +} + +void handle_peer_req(h804::tun::EthSocket& source, h804::tun::macaddr_t from) { + { + PEERS_LOCK(); + peers.insert(from); + } + send_share_rsp(source, from); +} + +bool handle_ll(const std::array& buf, size_t size, + h804::tun::macaddr_t from, h804::tun::EthSocket& source) { + if (size < 2) { + return false; + } + const h804_prot* prot = reinterpret_cast(buf.data()); + if (prot->marker == H804_PROT_MARKER) { + switch (prot->type) { + case H804_PROT_PEER_REQ: + handle_peer_req(source, from); + break; + case H804_PROT_SHARE_RSP: + handle_share_rsp(source, prot, size, from); + break; + case H804_PROT_SHARE_REQ: + send_share_rsp(source, from); + break; + default: + break; + } + + return true; + } + return false; +} + +int main(int argc, char* argv[]) { + (void)argc; + (void)argv; + + try { + std::ifstream infile("h804tun.conf"); + std::string interface; + std::string ip; + if (!std::getline(infile, interface)) { + error << "Need interface in conf"; + } + if (!std::getline(infile, ip)) { + error << "Need local IP in conf"; + } + + info << "Starting tunnel on " + interface + " " + ip; + h804::tun::TunnelDevice device{ip, "255.255.255.0"}; + h804::tun::EthSocket llsock{interface.c_str()}; + info << "Started"; + std::string cmd1("ip link show "); + cmd1 += device.get_name(); + system(cmd1.c_str()); + cmd1 = "ip addr show "; + cmd1 += device.get_name(); + system(cmd1.c_str()); + system("ip route"); + + struct sigaction handler; + handler.sa_handler = [](int) { + // std::unique_lock lk(main_mut); + do_exit = true; + std::cout << std::endl; + // main_waiter.notify_all(); + }; + sigemptyset(&handler.sa_mask); + handler.sa_flags = 0; + // sigaction(SIGINT, &handler, NULL); + + auto tun_thread = [&device, &llsock]() { + std::array buf; + while (!do_exit) { + size_t len = device.read(buf); + if (len > 0) { + PEERS_LOCK(); + for (const auto& addr : peers) { + llsock.write(buf, len, addr); + } + } + } + }; + + auto eth_thread = [&device, &llsock]() { + std::array buf; + h804::tun::macaddr_t addr; + while (!do_exit) { + size_t len = llsock.read(buf, addr); + if (len > 0) { + if (handle_ll(buf, len, addr, llsock)) { + continue; + } + + device.write(buf, len); + } + } + }; + + auto share_thread = [&llsock] { + while (!do_exit) { + std::this_thread::sleep_for(300s); + { + PEERS_LOCK(); + for (const auto& it : peers) { + send_share_req(llsock, it); + } + } + } + }; + + info << "Sending peer requests"; + h804::tun::macaddr_t bcast{"ff:ff:ff:ff:ff:ff"}; + for (size_t i = 0; i < 5; i++) { + send_peer_req(llsock, bcast); + } + + std::thread tun_thread_obj(tun_thread); + std::thread eth_thread_obj(eth_thread); + std::thread share_thread_obj(share_thread); + + tun_thread_obj.join(); + eth_thread_obj.join(); + share_thread_obj.join(); + + // { + // std::unique_lock lk(main_mut); + // main_waiter.wait(lk, [] { return do_exit; }); + // } + + info << "Exiting"; + } catch (std::exception& e) { + error << "Caught exception:"; + error << e.what(); + } + return 0; +} diff --git a/tundev.cpp b/tundev.cpp new file mode 100644 index 0000000..c7d5dfa --- /dev/null +++ b/tundev.cpp @@ -0,0 +1,241 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "tundev.h" + +using h804::check; + +namespace h804::tun { + +class _autoclose { + int fd; + bool do_close; + + public: + _autoclose(int fd) : fd(fd), do_close(true) {} + void defuse() { do_close = false; } + ~_autoclose() { + if (do_close) { + close(fd); + } + } +}; + +TunnelDevice::TunnelDevice(const std::string& local_ip, const std::string& local_netmask) { + // Create tunnel + ifreq ifr = {}; + + check((tun_fd = open("/dev/net/tun", O_RDWR)), "Failed to open /dev/net/tun"); + + _autoclose guard(tun_fd); + + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + strncpy(ifr.ifr_name, "tun%d", IFNAMSIZ); + + check(ioctl(tun_fd, TUNSETIFF, &ifr), "Failed to request tunnel"); + + name = std::string(ifr.ifr_name); + + info << "Tunnel name: " + name; + + // Configure tunnel + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + check(sockfd, "Failed to create socket"); + + _autoclose sockguard(sockfd); + + ifr = {}; + strncpy(ifr.ifr_name, name.c_str(), IFNAMSIZ); + // Set MTU + ifr.ifr_mtu = TUN_MTU; + check(ioctl(sockfd, SIOCSIFMTU, &ifr), "Failed to set MTU"); + // Set IP address + ifr.ifr_addr.sa_family = AF_INET; + inet_pton(AF_INET, local_ip.c_str(), ifr.ifr_addr.sa_data + 2); + check(ioctl(sockfd, SIOCSIFADDR, &ifr), "Failed to set IP address"); + + // Set netmask + inet_pton(AF_INET, local_netmask.c_str(), ifr.ifr_addr.sa_data + 2); + check(ioctl(sockfd, SIOCSIFNETMASK, &ifr), "Failed to set netmask"); + + // Bring up + check(ioctl(sockfd, SIOCGIFFLAGS, &ifr), "Failed to get iface flags"); + + strncpy(ifr.ifr_name, name.c_str(), IFNAMSIZ); + ifr.ifr_flags |= IFF_UP | IFF_NOARP | IFF_MULTICAST; + check(ioctl(sockfd, SIOCSIFFLAGS, &ifr), "Failed to bring interface up"); + + info << "Tunnel device up"; + guard.defuse(); +} + +void TunnelDevice::write(const std::array& buf, size_t len) const { + ssize_t bytes_written = ::write(tun_fd, buf.data(), len); + if (bytes_written < 0 && errno == EINVAL) { + warn << "Tunnel rejected packet"; + } else { + check(bytes_written, "Write failure"); + if (bytes_written != (ssize_t)len) { + throw std::runtime_error("Did not write all bytes"); + } + } +} + +size_t TunnelDevice::read(std::array& buf) const { + ssize_t read_bytes = ::read(tun_fd, buf.data(), TUN_MTU); + check(read_bytes, "Read failure"); + return read_bytes; +} + +const std::string& TunnelDevice::get_name() const { return name; } + +TunnelDevice::~TunnelDevice() { + debug << "Closing tunnel device"; + close(tun_fd); +} + +// macaddr_t + +macaddr_t::macaddr_t() { + for (size_t i = 0; i < 6; i++) { + this->operator[](i) = 0; + } +} + +macaddr_t::macaddr_t(const std::string& str) { + int data[6]; + constexpr auto fmt = "%x:%x:%x:%x:%x:%x"; + int num = sscanf(str.c_str(), fmt, &data[0], &data[1], &data[2], &data[3], &data[4], &data[5]); + if (num < 6) { + throw std::invalid_argument("Not a mac address"); + } + for (size_t i = 0; i < 6; i++) { + this->operator[](i) = data[i]; + } +} + +macaddr_t::macaddr_t(const uint8_t octets[6]) { + for (size_t i = 0; i < 6; i++) { + this->operator[](i) = octets[i]; + } +} + +macaddr_t::macaddr_t(const sockaddr_ll& linux_addr) : macaddr_t(&linux_addr.sll_addr[0]) {} + +sockaddr_ll macaddr_t::to_linux(int if_idx) const { + sockaddr_ll ret = {}; + ret.sll_ifindex = if_idx; + ret.sll_halen = ETH_ALEN; + for (size_t i = 0; i < 6; i++) { + ret.sll_addr[i] = this->operator[](i); + } + return ret; +} + +std::string macaddr_t::to_string() const { + std::ostringstream str; + str << std::hex << ((int)this->operator[](0)); + for (size_t i = 1; i < 6; i++) { + str << ":" << std::hex << ((int)this->operator[](i)); + } + return str.str(); +} + +macaddr_t::operator std::string() const { return this->to_string(); } + +bool macaddr_t::operator==(const macaddr_t& other) const { + for (size_t i = 0; i < 6; i++) { + if (this->operator[](i) != other[i]) { + return false; + } + } + return true; +} + +// EthSocket + +EthSocket::EthSocket(const std::string& if_name) : source_mac() { + info << "Starting packet interface"; + eth_fd = socket(AF_PACKET, SOCK_RAW, ETH_P_802_EX1); + check(eth_fd, "Failed to open socket"); + + _autoclose guard(eth_fd); + + ifreq req = {}; + strncpy(req.ifr_name, if_name.c_str(), IFNAMSIZ); + check(ioctl(eth_fd, SIOCGIFINDEX, &req), "Failed to get interface index"); + + if_index = req.ifr_ifindex; + req = {}; + strncpy(req.ifr_name, if_name.c_str(), IFNAMSIZ); + check(ioctl(eth_fd, SIOCGIFHWADDR, &req), "Failed to get machine mac address"); + + for (size_t i = 0; i < 6; i++) { + source_mac[i] = req.ifr_hwaddr.sa_data[i]; + } + info << "Packet interface up"; + info << "MAC: " + source_mac.to_string(); + + guard.defuse(); +} + +typedef std::array buf_t; + +void EthSocket::write(const buf_t& buf, size_t len, const macaddr_t& to) const { + if (len > TUN_MTU) { + warn << "Packet over MTU"; + } + std::vector packet(len + sizeof(ethhdr)); + ethhdr hdr = {}; + for (size_t i = 0; i < 6; i++) { + hdr.h_dest[i] = to[i]; + hdr.h_source[i] = source_mac[i]; + } + hdr.h_proto = ETH_TYPE; + memcpy(packet.data(), &hdr, sizeof(hdr)); + memcpy(&(packet.data())[sizeof(hdr)], buf.data(), len); + sockaddr_ll dest_addr = to.to_linux(if_index); + constexpr auto el = sizeof(ethhdr); + constexpr auto sl = sizeof(sockaddr_ll); + auto ret = sendto(eth_fd, packet.data(), len + el, 0, (sockaddr*)&dest_addr, sl); + check(ret, "sendto() failed"); +} + +size_t EthSocket::read(std::array& buf, macaddr_t& from_mac) const { + static std::array raw_buf; + auto ret = recvfrom(eth_fd, raw_buf.data(), TUN_MTU + sizeof(ethhdr), 0, NULL, 0); + check(ret, "recvfrom() failed"); + + if (ret < (ssize_t)sizeof(ethhdr)) { + error << "Invalid ethernet header recieved"; + } + + const ethhdr* hdr = reinterpret_cast(raw_buf.data()); + + for (size_t i = 0; i < 6; i++) { + from_mac[i] = hdr->h_source[i]; + } + + std::copy(raw_buf.begin() + sizeof(ethhdr), raw_buf.end(), buf.begin()); + + return ret - sizeof(ethhdr); +} + +const macaddr_t& EthSocket::get_source_mac() const { + return source_mac; +} + +EthSocket::~EthSocket() { + debug << "Closing packet interface"; + close(eth_fd); +} +} // namespace h804::tun diff --git a/tundev.h b/tundev.h new file mode 100644 index 0000000..3948b0c --- /dev/null +++ b/tundev.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include + +namespace h804::tun { +constexpr int TUN_MTU = 1450; + +class TunnelDevice { + int tun_fd; + std::string name; + + public: + TunnelDevice(const std::string& local_ip, const std::string& local_netmask); + + TunnelDevice(const TunnelDevice&) = delete; + TunnelDevice& operator=(const TunnelDevice&) = delete; + + virtual ~TunnelDevice(); + + void write(const std::array& buf, size_t length) const; + + size_t read(std::array& buf) const; + + const std::string& get_name() const; +}; + +constexpr short ETH_TYPE = ETH_P_802_EX1; + +class macaddr_t : public std::array { + public: + macaddr_t(); + macaddr_t(const std::string& str); + macaddr_t(const uint8_t octets[6]); + macaddr_t(const sockaddr_ll& linux_addr); + sockaddr_ll to_linux(int if_idx) const; + std::string to_string() const; + operator std::string() const; + bool operator==(const macaddr_t& other) const; +}; + +class EthSocket { + int eth_fd; + int if_index; + macaddr_t source_mac; + + public: + EthSocket(const std::string& if_name); + + EthSocket(const EthSocket&) = delete; + EthSocket& operator=(const EthSocket&) = delete; + + virtual ~EthSocket(); + + void write(const std::array& buf, size_t len, const macaddr_t& to) const; + + size_t read(std::array& buf, macaddr_t& from_mac) const; + + const macaddr_t& get_source_mac() const; +}; +} // namespace h804::tun + +namespace std { +template <> +struct hash { + std::size_t operator()(const h804::tun::macaddr_t& m) const { + return m[0] + m[1] + m[2] + m[3] + m[4] + m[5]; + } +}; +} // namespace std