From ee34164a6c1c4f905332cfcfef938a0ccb48333b Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Sat, 29 Jul 2017 01:20:23 +0200 Subject: Add basic pcap import/export support --- src/monitor-gui.cc | 249 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) (limited to 'src/monitor-gui.cc') diff --git a/src/monitor-gui.cc b/src/monitor-gui.cc index 8a2509d..d73c98d 100644 --- a/src/monitor-gui.cc +++ b/src/monitor-gui.cc @@ -14,6 +14,12 @@ #include #include +#if HAVE_PCAP +# include +# include +# include +#endif + #include "config.hh" #include "data.hh" #include "gui_about.hh" @@ -679,6 +685,11 @@ public: file_filter_.emplace_back(); file_filter_.back().name = "TransparentProxy Packages (*.tpp)"; file_filter_.back().masks.emplace_back("*.tpp"); +#if HAVE_PCAP + file_filter_.emplace_back(); + file_filter_.back().name = "Packet Capture (*.pcap)"; + file_filter_.back().masks.emplace_back("*.pcap"); +#endif // HAVE_PCAP file_filter_.emplace_back(); file_filter_.back().name = "All files"; file_filter_.back().masks.emplace_back("*.*"); @@ -1084,7 +1095,244 @@ private: message->show(main_.get()); } +#if HAVE_PCAP + static bool is_pcap(std::string const& file) { + return file.size() >= 5 && file.compare(file.size() - 5, 5, ".pcap") == 0; + } + + bool load_pcap(std::string const& file, PackageList* packages) { + char errbuf[PCAP_ERRBUF_SIZE]; + auto pcap = pcap_open_offline(file.c_str(), errbuf); + if (!pcap) { + show_error(errbuf); + return false; + } + size_t offset; + switch (pcap_datalink(pcap)) { + case DLT_RAW: + offset = 0; + break; + case DLT_EN10MB: + offset = 14; + break; + default: + snprintf(errbuf, sizeof(errbuf), "Unsupported dataline: %d", + pcap_datalink(pcap)); + show_error(errbuf); + return false; + } + while (true) { + struct pcap_pkthdr* header; + u_char const* data; + auto ret = pcap_next_ex(pcap, &header, &data); + if (ret == 1) { + auto len = std::min(header->caplen, header->len); + if (len < offset) continue; + ::Package pkg; + pkg.timestamp.tv_sec = header->ts.tv_sec; + pkg.timestamp.tv_nsec = header->ts.tv_usec * 1000; + auto len_ip = is_ipv4(data + offset, len - offset, &pkg); + if (len_ip < 0) continue; + auto len_tcp = is_tcp(data + offset + len_ip, len - len_ip - offset, + &pkg); + if (len_tcp < 0) continue; + if (offset + len_ip + len_tcp == len) continue; + packages->package(pkg); + packages->package_data(pkg.id, reinterpret_cast(data) + + offset + len_ip + len_tcp, + len - offset - len_ip - len_tcp, true); + } else if (ret == -2) { + break; + } else if (ret == -1) { + show_error(pcap_geterr(pcap)); + pcap_close(pcap); + return false; + } else { + assert(false); + show_error("Unexpected return value"); + pcap_close(pcap); + return false; + } + } + pcap_close(pcap); + return true; + } + + static ssize_t is_ipv4(const uint8_t* data, size_t max, ::Package* package) { + if (max < 1) return -1; + if ((data[0] >> 4) != 4) return -1; + uint8_t ihl = data[0] & 0xf; + if (ihl < 5) return -1; + if (ihl * 4 > max) return -1; + if (data[9] != 0x6) return -1; + char tmp[INET_ADDRSTRLEN]; + struct in_addr addr; + addr.s_addr = htonl(read_u32(data + 12)); + if (!inet_ntop(AF_INET, &addr, tmp, sizeof(tmp))) return -1; + package->source_host.assign(tmp); + addr.s_addr = htonl(read_u32(data + 16)); + if (!inet_ntop(AF_INET, &addr, tmp, sizeof(tmp))) return -1; + package->target_host.assign(tmp); + return ihl * 4; + } + + static ssize_t is_tcp(const uint8_t* data, size_t max, ::Package* package) { + if (max < 20) return -1; + uint8_t data_offset = data[12] >> 4; + if (data_offset < 5) return -1; + if (data_offset * 4 > max) return -1; + package->source_port = read_u16(data); + package->target_port = read_u16(data + 2); + package->id = read_u32(data + 4); + return data_offset * 4; + } + + bool save_pcap(PackageList const* packages, std::string const& file) { + auto pcap = pcap_open_dead(DLT_RAW, 65535); + auto dumper = pcap_dump_open(pcap, file.c_str()); + if (!dumper) { + show_error(pcap_geterr(pcap)); + pcap_close(pcap); + return false; + } + uint16_t id = 0; + std::string data; + std::unordered_map cache; + for (size_t index = 0; index < packages->rows(); ++index) { + auto const& pkg = packages->package(index); + struct pcap_pkthdr header; + header.ts.tv_sec = pkg.pkg.timestamp.tv_sec; + header.ts.tv_usec = pkg.pkg.timestamp.tv_nsec / 1000; + + uint8_t ip[20]; + ip[0] = (4 << 4) | 5; // Version | IHL + ip[1] = 0; // DCSP | ECN + ip[6] = 0; // Flags | Fragment Offset + ip[7] = 0; // Fragment Offset + ip[8] = 127; // TTL + ip[9] = 6; // Protocol (TCP) + auto it = cache.find(pkg.pkg.source_host); + if (it == cache.end()) { + it = cache.emplace(pkg.pkg.source_host, + get_ip(pkg.pkg.source_host)).first; + } + write_u32(ip + 12, it->second); + it = cache.find(pkg.pkg.target_host); + if (it == cache.end()) { + it = cache.emplace(pkg.pkg.target_host, + get_ip(pkg.pkg.target_host)).first; + } + write_u32(ip + 16, it->second); + + uint8_t tcp[20]; + write_u16(tcp, pkg.pkg.source_port); + write_u16(tcp + 2, pkg.pkg.target_port); + write_u32(tcp + 8, 0); // ACK + tcp[12] = (5 << 4); // Data offset | NS + write_u16(tcp + 14, 0); // Window size + write_u16(tcp + 18, 0); // Urgent pointer + + size_t offset = 0; + size_t len = pkg.data.size(); + while (offset < len) { + size_t partlen = std::min(len - offset, + 65535 - sizeof(ip) - sizeof(tcp)); + header.len = sizeof(ip) + sizeof(tcp) + partlen; + header.caplen = header.len; + write_u16(ip + 2, header.len); // Total Length + write_u16(ip + 4, id); // Identificiation + ++id; + write_u16(ip + 10, 0); + uint16_t checksum = ipv4_checksum(ip, sizeof(ip)); + write_u16(ip + 10, checksum); // Checksum + + write_u32(tcp + 4, pkg.pkg.id); + tcp[13] = ((offset == 0) ? 2 : 0) | ((offset + partlen == len) ? 1 : 0); + write_u16(tcp + 16, 0); + checksum = tcp_checksum(ip, tcp, + reinterpret_cast(pkg.data.data()) + + offset, partlen); + write_u16(tcp + 16, checksum); // Checksum + + data.assign(reinterpret_cast(ip), sizeof(ip)); + data.append(reinterpret_cast(tcp), sizeof(tcp)); + data.append(pkg.data.data() + offset, partlen); + pcap_dump(reinterpret_cast(dumper), &header, + reinterpret_cast(data.data())); + offset += partlen; + } + } + pcap_dump_close(dumper); + pcap_close(pcap); + return true; + } + + static uint16_t ipv4_checksum(uint8_t const* header, size_t size) { + assert((size % 2) == 0); + uint16_t sum = 0; + for (size_t i = 0; i < size; i += 2) { + sum += (header[i] << 8) | header[i + 1]; + } + return ~sum; + } + + static uint32_t get_ip(std::string const& host) { + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + struct addrinfo* ret; + if (getaddrinfo(host.c_str(), nullptr, &hints, &ret)) { + return 0; + } + uint32_t ip = 0; + for (auto p = ret; p; p = p->ai_next) { + if (p->ai_family == AF_INET) { + ip = ntohl( + reinterpret_cast(p->ai_addr)->sin_addr.s_addr); + break; + } + } + freeaddrinfo(ret); + return ip; + } + + static uint16_t tcp_checksum(uint8_t const* ip, uint8_t const* tcp, + uint8_t const* data, size_t size) { + uint16_t sum = 0; + sum += ip[12] << 8 | ip[13]; + sum += ip[14] << 8 | ip[15]; + sum += ip[16] << 8 | ip[17]; + sum += ip[18] << 8 | ip[19]; + sum += 0x06; + sum += 20; + for (size_t i = 0; i < 20; i += 2) { + sum += (tcp[i] << 8) | tcp[i + 1]; + } + for (size_t i = 0; i < size / 2; ++i) { + sum += (data[i * 2] << 8) | data[i * 2 + 1]; + } + if (size % 2) sum += data[size - 1] << 8; + return ~size; + } +#else // HAVE_PCAP + static bool is_pcap(std::string const& UNUSED(file)) { + return false; + } + + static bool load_pcap(std::string const& UNUSED(file), + PackageList* UNUSED(packages)) { + return false; + } + + static bool save_pcap(PackageList const* UNUSED(packages), + std::string const& UNUSED(file)) { + return false; + } +#endif // HAVE_PCAP + bool load(std::string const& file, PackageList* packages) { + if (is_pcap(file)) return load_pcap(file, packages); std::ifstream in(file); if (!in.good()) { show_error("Unable to open " + file + " for reading"); @@ -1156,6 +1404,7 @@ private: } bool save(PackageList const* packages, std::string const& file) { + if (is_pcap(file)) return save_pcap(packages, file); std::ofstream out(file); if (!out.good()) { show_error("Unable to open " + file + " for writing"); -- cgit v1.2.3-70-g09d2