commit a02b2065195dc11f1d667ebfcaed912578a5bf38 Author: Tim Niemeyer Date: Sun Nov 12 09:07:28 2017 +0100 Init diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f287728 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.0) + +project(macNock C CXX) + +option(MACNOCK_DEBUG "Enable debug messages" OFF) + +if(CMAKE_COMPILER_IS_GNUCXX) + add_definitions(-Wall -Wpedantic -Wextra -std=c++11) +endif() + +if(MACNOCK_DEBUG) + add_definitions(-DDEBUG) +endif(MACNOCK_DEBUG) + +set(MACNOCK_SRC + main.cpp + macnockserver.cpp + macnockclient.cpp + nockpackage.cpp + mac.cpp + tc.cpp + log.cpp + ) + +add_executable(macnock ${MACNOCK_SRC}) +target_link_libraries(macnock pthread) + +install(TARGETS macnock RUNTIME DESTINATION sbin) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/log.cpp b/log.cpp new file mode 100644 index 0000000..00eba8d --- /dev/null +++ b/log.cpp @@ -0,0 +1,29 @@ +#include "log.h" + +#include +#include + +namespace log { + +std::mutex g_output_mutex; + +#ifdef DEBUG +void debug(const std::string &dbg) +{ + std::lock_guard guard(g_output_mutex); + std::cout << dbg << std::endl; +} +#else +void debug(const std::string &) +{ + +} +#endif + +void error(const std::string &dbg) +{ + std::lock_guard guard(g_output_mutex); + std::cerr << dbg << std::endl; +} + +} // namespace log diff --git a/log.h b/log.h new file mode 100644 index 0000000..dce0af9 --- /dev/null +++ b/log.h @@ -0,0 +1,14 @@ +#ifndef _DEBUG_H +#define _DEBUG_H + +#include + +namespace log { + +void debug(const std::string &dbg); + +void error(const std::string &dbg); + +} // namespace log + +#endif // _DEBUG_H diff --git a/mac.cpp b/mac.cpp new file mode 100644 index 0000000..93ab0cb --- /dev/null +++ b/mac.cpp @@ -0,0 +1,14 @@ +#include "mac.h" + +#include +#include + +std::string to_string(const Mac &o) +{ + std::ostringstream stream; + for (Mac::const_iterator it=o.begin(); it!=o.end(); ++it) + { + stream << std::hex << std::setw(2) << std::setfill('0') << static_cast(*it); + } + return stream.str(); +} diff --git a/mac.h b/mac.h new file mode 100644 index 0000000..4944448 --- /dev/null +++ b/mac.h @@ -0,0 +1,13 @@ +#ifndef _MAC_H +#define _MAC_H + +#include +#include +#include + +using Mac = std::array; +using MacList = std::list; + +std::string to_string(const Mac &o); + +#endif // _MAC_H diff --git a/macnockclient.cpp b/macnockclient.cpp new file mode 100644 index 0000000..be072a0 --- /dev/null +++ b/macnockclient.cpp @@ -0,0 +1,107 @@ +#include "macnockclient.h" +#include "nockpackage.h" +#include "log.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +macNockClient::macNockClient(const std::string &interface, const std::string &hood) + : m_interface(interface) + , m_hood(hood) +{ + +} + +void macNockClient::stop() +{ + log::debug("Stopping Client"); + m_stop = true; +} + +bool macNockClient::run() +{ + const std::string host{"ff02::1"}; + + m_stop = false; + + int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + { + log::error(std::string("[c] ERROR: Can't create socket (") + strerror(errno) + ")."); + return false; + } + + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, m_interface.c_str(), m_interface.size()) < 0) + { + log::error(std::string("[c] WARNING: Can't bind to device (") + strerror(errno) + ")."); + } + + ifreq ifr; + memset(&ifr, 0, sizeof(ifreq)); + strncpy(ifr.ifr_name, m_interface.c_str(), IFNAMSIZ); + + if (!((ioctl(fd, SIOCGIFFLAGS, &ifr) == 0) && (ioctl(fd, SIOCGIFHWADDR, &ifr) == 0))) + { + log::error(std::string("[c] ERROR: Can't read MAC (") + strerror(errno) + ")."); + return false; + } + + sockaddr_in6 servaddr; + memset(&servaddr, 0, sizeof(sockaddr_in6)); + servaddr.sin6_family = AF_INET6; + servaddr.sin6_port = htons(PORT); + + if (inet_pton(AF_INET6, host.c_str(), &servaddr.sin6_addr) == 0) + { + log::error(std::string("[c] ERROR: Can't resolve host (") + strerror(errno) + ")."); + close(fd); + return false; + } + + const Mac mac{ static_cast(ifr.ifr_hwaddr.sa_data[0]), static_cast(ifr.ifr_hwaddr.sa_data[1]), + static_cast(ifr.ifr_hwaddr.sa_data[2]), static_cast(ifr.ifr_hwaddr.sa_data[3]), + static_cast(ifr.ifr_hwaddr.sa_data[4]), static_cast(ifr.ifr_hwaddr.sa_data[5]), }; + const NockPackage nock{mac, m_hood}; + + while (!m_stop) + { + log::debug("[c] sending"); + uint8_t buf[256]; + size_t len = nock.serialize(buf, 256); + + int sent = sendto(fd, buf, len, 0, reinterpret_cast(&servaddr), sizeof(servaddr)); + if (sent == -1) + { + log::error(std::string("[c] ERROR: Can't send data (") + strerror(errno) + ")."); + } + else if (static_cast(sent) != len) + { + log::error("[c] ERROR: Can't send all data."); + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + close(fd); + + log::debug("Client closed"); + return true; +} diff --git a/macnockclient.h b/macnockclient.h new file mode 100644 index 0000000..f7ca82d --- /dev/null +++ b/macnockclient.h @@ -0,0 +1,16 @@ +#include + +class macNockClient +{ +public: + macNockClient(const std::string &interface, const std::string &code); + bool run(); + void stop(); + +private: + +private: + const std::string &m_interface; + const std::string &m_hood; + bool m_stop; +}; diff --git a/macnockserver.cpp b/macnockserver.cpp new file mode 100644 index 0000000..ffc6805 --- /dev/null +++ b/macnockserver.cpp @@ -0,0 +1,116 @@ +#include "macnockserver.h" +#include "nockpackage.h" +#include "log.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +macNockServer::macNockServer(const std::string &interface, const std::string &hood) + : m_interface(interface) + , m_hood(hood) + , m_stop(false) + , m_tc(interface) +{ + +} + +bool macNockServer::run() +{ + m_sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + + if (m_sock < 0) + { + log::error(std::string("[s] ERROR: Can't create socket (") + strerror(errno) + ")."); + return false; + } + + // allow to reuse the port immediately as soon as the service exits. + int sockoptval = 1; + if (setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, &sockoptval, sizeof(int)) != 0) + { + log::error(std::string("[s] WARNING: Can't set socket options (") + strerror(errno) + ")."); + } + + sockaddr_in6 my_addr; + memset(&my_addr, 0, sizeof(sockaddr_in6)); + my_addr.sin6_family = AF_INET6; + my_addr.sin6_port = htons(PORT); + my_addr.sin6_addr = in6addr_any; + + if (bind(m_sock, reinterpret_cast(&my_addr), sizeof(sockaddr_in6)) < 0) + { + log::error(std::string("[s] ERROR: Can't bind to address (") + strerror(errno) + ")"); + return false; + } + + while (!m_stop) + { + log::debug(std::string("[s] waiting on port ") + std::to_string(PORT)); + + sockaddr_in6 client_addr; + socklen_t addrlen = sizeof(client_addr); + + static const size_t BUFSIZE = 2048; + uint8_t buf[BUFSIZE]; + + int recvlen = recvfrom(m_sock, buf, BUFSIZE, 0, reinterpret_cast(&client_addr), &addrlen); + + if (recvlen <= 0) + { + continue; + } + + char addrBuf[INET6_ADDRSTRLEN]; + const char *ret = inet_ntop(AF_INET6, &client_addr.sin6_addr, addrBuf, sizeof(addrBuf)); + log::debug(std::string("[s] received ") + std::to_string(recvlen) + " bytes from " + ret); + + NockPackage nock; + if (nock.deserialize(buf, recvlen)) + { + const std::string mac{to_string(nock.getMac())}; + log::debug(std::string("The MAC: ") + mac + ", the Hood: " + nock.getHood()); + + if (nock.getHood() == m_hood) + { + log::debug(std::string("[s] allowing ") + mac); + MacList::iterator found = std::find(m_filterList.begin(), m_filterList.end(), nock.getMac()); + if (found == m_filterList.end()) + { + m_filterList.push_back(nock.getMac()); + m_tc.allow_mac(mac); + } + } + else + { + log::debug(std::string("[s] not allowing ") + mac + ", wrong hood"); + MacList::iterator found = std::remove(m_filterList.begin(), m_filterList.end(), nock.getMac()); + if (found != m_filterList.end()) + { + m_filterList.erase(found); + m_tc.disallow_mac(mac); + } + } + } + else + { + log::error(std::string("[s] can't deserialize message \"") + std::string(buf, buf+recvlen) + "\""); + } + } + + close(m_sock); + log::debug("Server closed"); + return true; +} + +void macNockServer::stop() +{ + log::debug("Stopping Server"); + m_stop = true; + shutdown(m_sock, SHUT_RDWR); +} diff --git a/macnockserver.h b/macnockserver.h new file mode 100644 index 0000000..40b6951 --- /dev/null +++ b/macnockserver.h @@ -0,0 +1,24 @@ +#include "tc.h" +#include "mac.h" + +#include +#include + +class macNockServer +{ +public: + macNockServer(const std::string &interface, const std::string &hood); + bool run(); + void stop(); + +private: + void sendData(const int socket, const std::string &data) const; + +private: + const std::string &m_interface; + const std::string &m_hood; + bool m_stop; + int m_sock; + tc m_tc; + MacList m_filterList; +}; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..e95e078 --- /dev/null +++ b/main.cpp @@ -0,0 +1,65 @@ +#include "macnockserver.h" +#include "macnockclient.h" +#include +#include +#include +#include +#include +#include + +macNockServer *server = nullptr; +macNockClient *client = nullptr; + +void sigHandler(int) +{ + if (server) + { + server->stop(); + } + if (client) + { + client->stop(); + } +} + +int main(int argc, char *argv[]) +{ + if (argc < 3) + { + std::cout << "Send and listen for nock requests" << std::endl; + std::cout << std::endl; + std::cout << argv[0] << " interface code command" << std::endl; + std::cout << "Example:" << std::endl; + std::cout << argv[0] << " eth0.3 \"FuerthV2\"" << std::endl; + return -1; + } + + const std::string interface{argv[1]}; + const std::string hood{argv[2]}; + + server = new macNockServer{interface, hood}; + client = new macNockClient{interface, hood}; + + signal(SIGINT, &sigHandler); + signal(SIGTERM, &sigHandler); + + std::thread serverThread{[](){server->run();}}; + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + std::thread clientThread{[](){client->run();}}; + + serverThread.join(); + clientThread.join(); + + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + + delete client; + client = nullptr; + + delete server; + server = nullptr; + + return 0; +} diff --git a/nockpackage.cpp b/nockpackage.cpp new file mode 100644 index 0000000..5dd9160 --- /dev/null +++ b/nockpackage.cpp @@ -0,0 +1,75 @@ +#include "nockpackage.h" + +#include + +NockPackage::NockPackage() +{ + +} + +NockPackage::NockPackage(const Mac &sourceMac, const Hood &hoodName) + : m_sourceMac(sourceMac) + , m_hoodName(hoodName) +{ + +} + +size_t NockPackage::serialize(uint8_t *buf, size_t maxlen) const +{ + uint8_t *target = buf; + if (maxlen < (m_sourceMac.size() + 1)) + return 0; + + *target = VERSION; + target++; + + for (Mac::const_iterator it=m_sourceMac.begin(); it!=m_sourceMac.end(); ++it) + { + *target = *it; + target++; + } + + for (Hood::const_iterator it=m_hoodName.begin(); it!=m_hoodName.end(); ++it) + { + *target = *it; + target++; + } + + return target-buf; +} + +bool NockPackage::deserialize(const uint8_t *buf, size_t len) +{ + const uint8_t *source = buf; + + if (len < (m_sourceMac.size() + 1)) + { + return false; + } + + if (*source != VERSION) + { + return false; + } + source++; + + for (Mac::iterator it=m_sourceMac.begin(); it!=m_sourceMac.end(); ++it) + { + *it = *source; + source++; + } + + m_hoodName = std::string(source, buf+len); + + return true; +} + +Mac NockPackage::getMac() const +{ + return m_sourceMac; +} + +std::string NockPackage::getHood() const +{ + return m_hoodName; +} diff --git a/nockpackage.h b/nockpackage.h new file mode 100644 index 0000000..85bc735 --- /dev/null +++ b/nockpackage.h @@ -0,0 +1,33 @@ +#ifndef _PROTOCOL_H +#define _PROTOCOL_H + +#include "mac.h" + +#include + +static const int PORT{2342}; +static const int8_t VERSION{1}; + +class NockPackage +{ +public: + using Hood = std::string; + + NockPackage(); + + NockPackage(const Mac &sourceMac, const Hood &hoodName); + + size_t serialize(uint8_t *buf, size_t maxlen) const; + + bool deserialize(const uint8_t *buf, size_t len); + + Mac getMac() const; + + std::string getHood() const; + +private: + Mac m_sourceMac; + Hood m_hoodName; +}; + +#endif // _PROTOCOL_H diff --git a/tc.cpp b/tc.cpp new file mode 100644 index 0000000..ee02b90 --- /dev/null +++ b/tc.cpp @@ -0,0 +1,79 @@ +#include "tc.h" +#include "log.h" + +#include + +/* + * if=eth0 + * + * # qdisc anlegen: + * tc qdisc add dev $if ingress + * + * # alles sperren: + * tc filter add dev $if protocol all parent ffff: prio 65535 basic match "u32(u16 0x4305 0xffff at -2)" flowid :1 action drop + * + * # eine mac frei schalten: + * tc filter add dev $if protocol all parent ffff: prio 99 basic match "u32(u32 0xf81a67a5 0xffffffff at -8)" and "u32(u16 0xf4cb 0xffff at -4)" flowid :1 action pass + * + * # qdisc anzeigen + * tc qdisc + * + * # qdisc löschen + * tc qdisc del dev $if ingress + * + * # filter anzeigen + * tc filter show dev $if ingress + */ + +tc::tc(const std::string &interface) + : m_interface(interface) +{ + del_qdisc_ingress(); // in case a old session is sill there + + add_qdisc_ingress(); + block_all(); +} + +tc::~tc() +{ + del_qdisc_ingress(); +} + +void tc::add_qdisc_ingress() +{ + const std::string cmd{"/sbin/tc qdisc add dev "+m_interface+" ingress"}; + log::debug(std::string("CMD: ") + cmd); + std::system(cmd.c_str()); +} + +void tc::del_qdisc_ingress() +{ + const std::string cmd{"/sbin/tc qdisc del dev "+m_interface+" ingress"}; + log::debug(std::string("CMD: ") + cmd); + std::system(cmd.c_str()); +} + +void tc::block_all() +{ + const std::string cmd{"/sbin/tc filter add dev "+m_interface+" protocol all parent ffff: prio 65535 basic match \"u32(u16 0x4305 0xffff at -2)\" flowid :1 action drop"}; + log::debug(std::string("CMD: ") + cmd); + std::system(cmd.c_str()); +} + +void tc::allow_mac(const std::string &mac) +{ + const std::string cmd{"/sbin/tc filter add dev "+m_interface+" protocol all parent ffff: prio 99 " + "basic match \"u32(u32 0x"+mac.substr(0, 8)+" 0xffffffff at -8)\" " + "and \"u32(u16 0x"+mac.substr(8,10)+" 0xffff at -4)\" flowid :1 action pass"}; + log::debug(std::string("CMD: ") + cmd); + std::system(cmd.c_str()); +} + +void tc::disallow_mac(const std::string &mac) +{ + const std::string cmd{"/sbin/tc filter delete dev "+m_interface+" protocol all parent ffff: prio 99 " + "basic match \"u32(u32 0x"+mac.substr(0, 8)+" 0xffffffff at -8)\" " + "and \"u32(u16 0x"+mac.substr(8,10)+" 0xffff at -4)\" flowid :1 action pass"}; + log::debug(std::string("CMD: ") + cmd); + std::system(cmd.c_str()); +} diff --git a/tc.h b/tc.h new file mode 100644 index 0000000..e259961 --- /dev/null +++ b/tc.h @@ -0,0 +1,30 @@ +#ifndef _TC_H +#define _TC_H + +#include + +class tc +{ +public: + explicit tc(const std::string &interface); + + ~tc(); + + void add_qdisc_ingress(); + + void del_qdisc_ingress(); + + void block_all(); + + void allow_mac(const std::string &mac); + + void disallow_mac(const std::string &mac); + +private: + tc(const tc&); + tc(); + + const std::string m_interface; +}; + +#endif // _TC_H diff --git a/tc.txt b/tc.txt new file mode 100644 index 0000000..d3e551f --- /dev/null +++ b/tc.txt @@ -0,0 +1,12 @@ +if=eth0 +tc qdisc add dev $if ingress + + + +tc filter add dev $if protocol all parent ffff: prio 65535 basic match "u32(u16 0x4305 0xffff at -2)" flowid :1 action drop + +tc filter add dev $if protocol all parent ffff: prio 99 basic match "u32(u32 0xf81a67a5 0xffffffff at -8)" and "u32(u16 0xf4cb 0xffff at -4)" flowid :1 action pass + + + +