//--------------------------------------------------------------------------- // // SCSI Target Emulator RaSCSI (*^..^*) // for Raspberry Pi // // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS // Copyright (C) akuker // // [ TAP Driver ] // //--------------------------------------------------------------------------- #include #include #ifdef __linux__ #include #include #include #endif #include "os.h" #include "ctapdriver.h" #include "log.h" #include "rasutil.h" #include "exceptions.h" #include #define BRIDGE_NAME "rascsi_bridge" using namespace std; using namespace ras_util; CTapDriver::CTapDriver() { m_hTAP = -1; memset(&m_MacAddr, 0, sizeof(m_MacAddr)); m_pcap = NULL; m_pcap_dumper = NULL; } //--------------------------------------------------------------------------- // // Initialization // //--------------------------------------------------------------------------- static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) { struct ifreq ifr; ifr.ifr_ifindex = if_nametoindex(ifname); if (ifr.ifr_ifindex == 0) { LOGERROR("Can't if_nametoindex: %s", strerror(errno)); return false; } strncpy(ifr.ifr_name, bridgename, IFNAMSIZ); if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) < 0) { LOGERROR("Can't ioctl %s: %s", add ? "SIOCBRADDIF" : "SIOCBRDELIF", strerror(errno)); return false; } return true; } static bool ip_link(int fd, const char* ifname, bool up) { struct ifreq ifr; strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // Need to save room for null terminator int err = ioctl(fd, SIOCGIFFLAGS, &ifr); if (err) { LOGERROR("Can't ioctl SIOCGIFFLAGS: %s", strerror(errno)); return false; } ifr.ifr_flags &= ~IFF_UP; if (up) { ifr.ifr_flags |= IFF_UP; } err = ioctl(fd, SIOCSIFFLAGS, &ifr); if (err) { LOGERROR("Can't ioctl SIOCSIFFLAGS: %s", strerror(errno)); return false; } return true; } static bool is_interface_up(const string& interface) { string file = "/sys/class/net/"; file += interface; file += "/carrier"; bool status = true; FILE *fp = fopen(file.c_str(), "r"); if (!fp || fgetc(fp) != '1') { status = false; } if (fp) { fclose(fp); } return status; } bool CTapDriver::Init(const map& const_params) { map params = const_params; if (params.count("interfaces")) { LOGWARN("You are using the deprecated 'interfaces' parameter. " "Provide the interface list and the IP address/netmask with the 'interface' and 'inet' parameters"); // TODO Remove the deprecated syntax in a future version const string& interfaces = params["interfaces"]; size_t separatorPos = interfaces.find(':'); if (separatorPos != string::npos) { params["interface"] = interfaces.substr(0, separatorPos); params["inet"] = interfaces.substr(separatorPos + 1); } } stringstream s(params["interface"]); string interface; while (getline(s, interface, ',')) { this->interfaces.push_back(interface); } this->inet = params["inet"]; LOGTRACE("Opening Tap device"); // TAP device initilization if ((m_hTAP = open("/dev/net/tun", O_RDWR)) < 0) { LOGERROR("Can't open tun: %s", strerror(errno)); return false; } LOGTRACE("Opened tap device %d",m_hTAP); // IFF_NO_PI for no extra packet information struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_TAP | IFF_NO_PI; char dev[IFNAMSIZ] = "ras0"; strncpy(ifr.ifr_name, dev, IFNAMSIZ); LOGTRACE("Going to open %s", ifr.ifr_name); int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr); if (ret < 0) { LOGERROR("Can't ioctl TUNSETIFF: %s", strerror(errno)); close(m_hTAP); return false; } LOGTRACE("Return code from ioctl was %d", ret); int ip_fd = socket(PF_INET, SOCK_DGRAM, 0); if (ip_fd < 0) { LOGERROR("Can't open ip socket: %s", strerror(errno)); close(m_hTAP); return false; } int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (br_socket_fd < 0) { LOGERROR("Can't open bridge socket: %s", strerror(errno)); close(m_hTAP); close(ip_fd); return false; } // Check if the bridge has already been created string sys_file = "/sys/class/net/"; sys_file += BRIDGE_NAME; if (access(sys_file.c_str(), F_OK)) { LOGINFO("%s is not yet available", BRIDGE_NAME); LOGTRACE("Checking which interface is available for creating the bridge"); string bridge_interface; for (const string& interface : interfaces) { if (is_interface_up(interface)) { LOGTRACE("%s", string("Interface " + interface + " is up").c_str()); bridge_interface = interface; break; } else { LOGTRACE("%s", string("Interface " + interface + " is not available or is not up").c_str()); } } if (bridge_interface.empty()) { LOGERROR("No interface is up, not creating bridge"); return false; } LOGINFO("Creating %s for interface %s", BRIDGE_NAME, bridge_interface.c_str()); if (bridge_interface == "eth0") { LOGTRACE("brctl addbr %s", BRIDGE_NAME); if ((ret = ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME)) < 0) { LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno)); close(m_hTAP); close(ip_fd); close(br_socket_fd); return false; } LOGTRACE("brctl addif %s %s", BRIDGE_NAME, bridge_interface.c_str()); if (!br_setif(br_socket_fd, BRIDGE_NAME, bridge_interface.c_str(), true)) { close(m_hTAP); close(ip_fd); close(br_socket_fd); return false; } } else { string address = inet; string netmask = "255.255.255.0"; size_t separatorPos = inet.find('/'); if (separatorPos != string::npos) { address = inet.substr(0, separatorPos); int m; if (!GetAsInt(inet.substr(separatorPos + 1), m) || m < 8 || m > 32) { LOGERROR("Invalid CIDR netmask notation '%s'", inet.substr(separatorPos + 1).c_str()); close(m_hTAP); close(ip_fd); close(br_socket_fd); return false; } // long long is required for compatibility with 32 bit platforms long long mask = pow(2, 32) - (1 << (32 - m)); char buf[16]; sprintf(buf, "%lld.%lld.%lld.%lld", (mask >> 24) & 0xff, (mask >> 16) & 0xff, (mask >> 8) & 0xff, mask & 0xff); netmask = buf; } LOGTRACE("brctl addbr %s", BRIDGE_NAME); if ((ret = ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME)) < 0) { LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno)); close(m_hTAP); close(ip_fd); close(br_socket_fd); return false; } struct ifreq ifr_a; ifr_a.ifr_addr.sa_family = AF_INET; strncpy(ifr_a.ifr_name, BRIDGE_NAME, IFNAMSIZ); struct sockaddr_in* addr = (struct sockaddr_in*)&ifr_a.ifr_addr; if (inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) { LOGERROR("Can't convert '%s' into a network address: %s", address.c_str(), strerror(errno)); close(m_hTAP); close(ip_fd); close(br_socket_fd); return false; } struct ifreq ifr_n; ifr_n.ifr_addr.sa_family = AF_INET; strncpy(ifr_n.ifr_name, BRIDGE_NAME, IFNAMSIZ); struct sockaddr_in* mask = (struct sockaddr_in*)&ifr_n.ifr_addr; if (inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) { LOGERROR("Can't convert '%s' into a netmask: %s", netmask.c_str(), strerror(errno)); close(m_hTAP); close(ip_fd); close(br_socket_fd); return false; } LOGTRACE("ip address add %s dev %s", inet.c_str(), BRIDGE_NAME); if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) { LOGERROR("Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK: %s", strerror(errno)); close(m_hTAP); close(ip_fd); close(br_socket_fd); return false; } } LOGTRACE("ip link set dev %s up", BRIDGE_NAME); if (!ip_link(ip_fd, BRIDGE_NAME, true)) { close(m_hTAP); close(ip_fd); close(br_socket_fd); return false; } } else { LOGINFO("%s is already available", BRIDGE_NAME); } LOGTRACE("ip link set ras0 up"); if (!ip_link(ip_fd, "ras0", true)) { close(m_hTAP); close(ip_fd); close(br_socket_fd); return false; } LOGTRACE("brctl addif %s ras0", BRIDGE_NAME); if (!br_setif(br_socket_fd, BRIDGE_NAME, "ras0", true)) { close(m_hTAP); close(ip_fd); close(br_socket_fd); return false; } // Get MAC address LOGTRACE("Getting the MAC address"); ifr.ifr_addr.sa_family = AF_INET; if ((ret = ioctl(m_hTAP, SIOCGIFHWADDR, &ifr)) < 0) { LOGERROR("Can't ioctl SIOCGIFHWADDR: %s", strerror(errno)); close(m_hTAP); close(ip_fd); close(br_socket_fd); return false; } LOGTRACE("Got the MAC"); // Save MAC address memcpy(m_MacAddr, ifr.ifr_hwaddr.sa_data, sizeof(m_MacAddr)); close(ip_fd); close(br_socket_fd); LOGINFO("Tap device %s created", ifr.ifr_name); return true; } void CTapDriver::OpenDump(const Filepath& path) { if (m_pcap == NULL) { m_pcap = pcap_open_dead(DLT_EN10MB, 65535); } if (m_pcap_dumper != NULL) { pcap_dump_close(m_pcap_dumper); } m_pcap_dumper = pcap_dump_open(m_pcap, path.GetPath()); if (m_pcap_dumper == NULL) { LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap)); throw io_exception("Can't open pcap file"); } LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.GetPath()); } void CTapDriver::Cleanup() { int br_socket_fd = -1; if ((br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { LOGERROR("Can't open bridge socket: %s", strerror(errno)); } else { LOGDEBUG("brctl delif %s ras0", BRIDGE_NAME); if (!br_setif(br_socket_fd, BRIDGE_NAME, "ras0", false)) { LOGWARN("Warning: Removing ras0 from the bridge failed."); LOGWARN("You may need to manually remove the ras0 tap device from the bridge"); } close(br_socket_fd); } // Release TAP defice if (m_hTAP != -1) { close(m_hTAP); m_hTAP = -1; } if (m_pcap_dumper != NULL) { pcap_dump_close(m_pcap_dumper); m_pcap_dumper = NULL; } if (m_pcap != NULL) { pcap_close(m_pcap); m_pcap = NULL; } } bool CTapDriver::Enable(){ int fd = socket(PF_INET, SOCK_DGRAM, 0); LOGDEBUG("%s: ip link set ras0 up", __PRETTY_FUNCTION__); bool result = ip_link(fd, "ras0", true); close(fd); return result; } bool CTapDriver::Disable(){ int fd = socket(PF_INET, SOCK_DGRAM, 0); LOGDEBUG("%s: ip link set ras0 down", __PRETTY_FUNCTION__); bool result = ip_link(fd, "ras0", false); close(fd); return result; } void CTapDriver::Flush(){ LOGTRACE("%s", __PRETTY_FUNCTION__); while(PendingPackets()){ (void)Rx(m_garbage_buffer); } } void CTapDriver::GetMacAddr(BYTE *mac) { ASSERT(mac); memcpy(mac, m_MacAddr, sizeof(m_MacAddr)); } //--------------------------------------------------------------------------- // // Receive // //--------------------------------------------------------------------------- bool CTapDriver::PendingPackets() { struct pollfd fds; ASSERT(m_hTAP != -1); // Check if there is data that can be received fds.fd = m_hTAP; fds.events = POLLIN | POLLERR; fds.revents = 0; poll(&fds, 1, 0); LOGTRACE("%s %u revents", __PRETTY_FUNCTION__, fds.revents); if (!(fds.revents & POLLIN)) { return false; }else { return true; } } // See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li uint32_t crc32(BYTE *buf, int length) { uint32_t crc = 0xffffffff; for (int i = 0; i < length; i++) { crc ^= buf[i]; for (int j = 0; j < 8; j++) { uint32_t mask = -(crc & 1); crc = (crc >> 1) ^ (0xEDB88320 & mask); } } return ~crc; } //--------------------------------------------------------------------------- // // Receive // //--------------------------------------------------------------------------- int CTapDriver::Rx(BYTE *buf) { ASSERT(m_hTAP != -1); // Check if there is data that can be received if (!PendingPackets()) { return 0; } // Receive DWORD dwReceived = read(m_hTAP, buf, ETH_FRAME_LEN); if (dwReceived == (DWORD)-1) { LOGWARN("%s Error occured while receiving an packet", __PRETTY_FUNCTION__); return 0; } // If reception is enabled if (dwReceived > 0) { // We need to add the Frame Check Status (FCS) CRC back onto the end of the packet. // The Linux network subsystem removes it, since most software apps shouldn't ever // need it. int crc = crc32(buf, dwReceived); buf[dwReceived + 0] = (BYTE)((crc >> 0) & 0xFF); buf[dwReceived + 1] = (BYTE)((crc >> 8) & 0xFF); buf[dwReceived + 2] = (BYTE)((crc >> 16) & 0xFF); buf[dwReceived + 3] = (BYTE)((crc >> 24) & 0xFF); LOGDEBUG("%s CRC is %08X - %02X %02X %02X %02X\n", __PRETTY_FUNCTION__, crc, buf[dwReceived+0], buf[dwReceived+1], buf[dwReceived+2], buf[dwReceived+3]); // Add FCS size to the received message size dwReceived += 4; } if (m_pcap_dumper != NULL) { struct pcap_pkthdr h = { .caplen = dwReceived, .len = dwReceived, }; gettimeofday(&h.ts, NULL); pcap_dump((u_char*)m_pcap_dumper, &h, buf); LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)dwReceived, buf[0], buf[dwReceived-1]); } // Return the number of bytes return dwReceived; } //--------------------------------------------------------------------------- // // Send // //--------------------------------------------------------------------------- int CTapDriver::Tx(const BYTE *buf, int len) { ASSERT(m_hTAP != -1); if (m_pcap_dumper != NULL) { struct pcap_pkthdr h = { .caplen = (bpf_u_int32)len, .len = (bpf_u_int32)len, }; gettimeofday(&h.ts, NULL); pcap_dump((u_char*)m_pcap_dumper, &h, buf); LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)h.len, buf[0], buf[h.len-1]); } // Start sending return write(m_hTAP, buf, len); }