h804tun/tundev.cpp

242 lines
6.6 KiB
C++

#include <arpa/inet.h>
#include <fcntl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cstring>
#include <sstream>
#include <vector>
#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<uint8_t, TUN_MTU>& 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<uint8_t, TUN_MTU>& 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<uint8_t, TUN_MTU> 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<uint8_t> 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<uint8_t, TUN_MTU>& buf, macaddr_t& from_mac) const {
static std::array<uint8_t, TUN_MTU + sizeof(ethhdr)> 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<const ethhdr*>(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