diff --git a/mozillavpn.pro b/mozillavpn.pro index c820278632..9997d421df 100644 --- a/mozillavpn.pro +++ b/mozillavpn.pro @@ -29,7 +29,3 @@ QMLTEST { webextension { SUBDIRS += tests/nativemessaging } - -TOOLS { - SUBDIRS += tools/ipmonitor -} diff --git a/tools/ipmonitor/README.md b/tools/ipmonitor/README.md deleted file mode 100644 index d526a37504..0000000000 --- a/tools/ipmonitor/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# Mozilla VPN - IPMonitor - -IPMonitor is a simple libpcap app to see which packets are sent and received -outside the VPN tunnel. This is meant to be used as a debugging/testing tool -and it's not included in the MozillaVPN app bundle. - -### How to compile it - -You must have the pcap library: https://www.tcpdump.org/ - -On debian/ubuntu: `apt-get install libpcap0.8-dev` -On macos: `brew reinstall libpcap` - -After this, run the following commands: - -1. [for macOn + brew only] `export PKG_CONFIG_PATH=/usr/local/opt/libpcap/lib/pkgconfig` -2. `qmake && make` - -### How to fetch the list of servers from the MozillaVPN app - -First step is to retrieve the list of servers. You must have the mozillavpn -client up and running. Then, run the following command: - -`mozillavpn servers -j > servers.json` - -Use `servers.json` as argument for the ipmonitor app. - -### How to run the app - -The app has an help menu: - -``` -$ ./ipmonitor -h -Usage: ./ipmonitor [options] server.list -Mozilla VPN - IP Monitor - -Options: - -h, --help Displays help on commandline options. - --help-all Displays help including Qt specific options. - -v, --version Displays version information. - -l Log non-VPN and non-LAN packets into this file - -Arguments: - server.list The server list file -``` - -To run the app, pass the `server.json` as first argument. We use `sudo` to have -the permissions to open the network interfaces. - -``` -$ sudo ./ipmonitor server.list -make: Nothing to be done for `first'. -Mozilla VPN - IP Monitor 0.1 - -Processing the list of servers: 420 servers. -Retrieving the interface list: done. -The interfaces present on the system are: - 1 ap1 - - 2 en0 - - 3 awdl0 - - 4 llw0 - - 5 utun0 - - 6 utun1 - - 7 en5 - - 8 en3 - - 9 en4 - - 10 en1 - - 11 en2 - - 12 bridge0 - -Which interface do you want to use? -``` - -At this point you have to choose the network interface you want to monitor. -`en0` for MacOS and a `wlp*` interface on linux could be a good starting point. - -The app has this simple UI: -``` -Total: 832 - VPN: 539 - LAN: 69 - OUT: 224 -D: < - Src: 192.168.0.128 - Dest: 193.32.249.69 -Loc: Netherlands, Amsterdam -``` - -Where: - - Total: the total number of packets received + sent. - - VPN: the packets via the VPN tunnel interface - - LAN: the packets for your local-area-network - - OUT: the packets outside the VPN tunnel interface - - D: the direction. It can be '>', for a sent packet, or '<', for a receved one. - - Src: the source IP address - - Dest: the destination IP address - - Loc: the VPN location, if detected - -### How to log the result - -Use `-l ` to write some data related to non-VPN and non-LAN packets. -This part needs to be improved. diff --git a/tools/ipmonitor/ipmonitor.pro b/tools/ipmonitor/ipmonitor.pro deleted file mode 100644 index 51993aee82..0000000000 --- a/tools/ipmonitor/ipmonitor.pro +++ /dev/null @@ -1,43 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -QT += network - -TEMPLATE = app -TARGET = ipmonitor - -DEFINES += QT_DEPRECATED_WARNINGS -DEFINES += APP_VERSION=\\\"0.1\\\" - -macos { - DEFINES += MVPN_MACOS - CONFIG -= app_bundle -} -else:linux:!android { - DEFINES += MVPN_LINUX -} - -HEADERS += \ - ../../src/ipaddress.h \ - ../../src/leakdetector.h \ - ../../src/loghandler.h \ - ../../src/logger.h \ - ../../src/rfc/rfc1918.h -SOURCES += \ - main.cpp \ - ../../src/ipaddress.cpp \ - ../../src/leakdetector.cpp \ - ../../src/loghandler.cpp \ - ../../src/logger.cpp \ - ../../src/rfc/rfc1918.cpp - -CONFIG += link_pkgconfig -PKGCONFIG += libpcap - -INCLUDEPATH += ../../src - -OBJECTS_DIR = .obj -MOC_DIR = .moc -RCC_DIR = .rcc -UI_DIR = .ui diff --git a/tools/ipmonitor/main.cpp b/tools/ipmonitor/main.cpp deleted file mode 100644 index 148c56c29d..0000000000 --- a/tools/ipmonitor/main.cpp +++ /dev/null @@ -1,479 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#ifdef MVPN_LINUX -# include -#elif MVPN_MACOS -# include -#endif -#include -#include -#include -#include - -#include "rfc/rfc1918.h" - -// IP header. See https://www.tcpdump.org/pcap.html -struct sniff_ip { - u_char ip_vhl; - u_char ip_tos; - u_short ip_len; - u_short ip_id; - u_short ip_off; - u_char ip_ttl; - u_char ip_p; - u_short ip_sum; - in_addr ip_src, ip_dst; -#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f) -#define IP_V(ip) (((ip)->ip_vhl) >> 4) -}; - -class Output final : public QTextStream { - public: - enum Preset { - eNone, - eDone, - eError, - eInfo, - }; - - Output(Preset preset = eNone) : QTextStream(&m_buffer) { - switch (preset) { - case eNone: - break; - case eDone: - *this << Output::bold() << Output::green(); - break; - case eError: - *this << Output::bold() << Output::red(); - break; - case eInfo: - *this << Output::yellow(); - break; - } - } - - ~Output() { - // Reset of the sequences. - *this << reset(); - QTextStream(stdout) << m_buffer; - } - - void cleanTo(uint32_t pos) { - for (uint32_t i = m_buffer.length(); i < pos; ++i) { - m_buffer.append(" "); - } - } - - static QString fixSize(const QString& input, uint32_t size) { - QString buffer; - for (uint32_t i = input.length(); i < size; ++i) { - buffer.append(" "); - } - buffer.append(input); - return buffer; - } - - static const char* reset() { return "\033[0m"; } - - static const char* hideCursor() { return "\033[?25l"; } - static const char* showCursor() { return "\033[?25h"; } - - static const char* prevLine() { return "\033[1F"; } - - static const char* bold() { return "\033[1m"; } - static const char* italic() { return "\033[3m"; } - static const char* underline() { return "\033[4m"; } - - static const char* red() { return "\033[31m"; } - static const char* green() { return "\033[32m"; } - static const char* yellow() { return "\033[33m"; } - static const char* blue() { return "\033[34m"; } - static const char* magenta() { return "\033[35m"; } - static const char* cyan() { return "\033[36m"; } - - static QString indent(uint32_t recursive) { - return QString().fill(' ', recursive * 2); - } - - private: - QString m_buffer; -}; - -struct Server final { - QString countryName; - QString cityName; - QString ipv4AddrIn; -}; - -bool parseServerListFile(const QString& fileName, - QHash& list) { - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - Output(Output::eError) << "Failed to open the file"; - return false; - } - - QByteArray buffer = file.readAll(); - QJsonDocument json = QJsonDocument::fromJson(buffer); - if (!json.isArray()) { - Output(Output::eError) - << "The file is not valid. A JSON array is expected."; - return false; - } - - for (QJsonValue countryValue : json.array()) { - QJsonObject countryObj = countryValue.toObject(); - QString countryName = countryObj["name"].toString(); - for (QJsonValue cityValue : countryObj["cities"].toArray()) { - QJsonObject cityObj = cityValue.toObject(); - QString cityName = cityObj["name"].toString(); - for (QJsonValue serverValue : cityObj["servers"].toArray()) { - QJsonObject serverObj = serverValue.toObject(); - QString ipv4AddrIn = serverObj["ipv4-addr-in"].toString(); - list.insert(ipv4AddrIn, new Server{countryName, cityName, ipv4AddrIn}); - } - } - } - - return true; -} - -QStringList resolveCaptivePortal(const QString& domain, - uint32_t recursive = 0) { - QStringList ips; - QEventLoop loop; - QDnsLookup dns; - QObject::connect(&dns, &QDnsLookup::finished, [&]() { - if (dns.error() == QDnsLookup::NoError) { - for (const QDnsDomainNameRecord& record : dns.canonicalNameRecords()) { - Output() << Output::indent(recursive) << " - CNAME: " << Output::bold() - << record.value() << Qt::endl; - ips.append(resolveCaptivePortal(record.value(), recursive + 1)); - } - - for (const QDnsHostAddressRecord& record : dns.hostAddressRecords()) { - Output() << Output::indent(recursive) << " - A: " << Output::bold() - << record.value().toString() << Qt::endl; - if (record.value().protocol() == QAbstractSocket::IPv4Protocol) { - ips.append(record.value().toString()); - } - } - } - loop.exit(); - }); - - dns.setType(QDnsLookup::ANY); - dns.setName(domain); - dns.lookup(); - - loop.exec(); - return ips; -} - -void logPackage(QFile* logger, const QString& ipSrc, const QString& ipDst) { - QTextStream out(logger); - out << QDateTime::currentDateTime().toString() << " - Src: " << ipSrc - << " - Dest: " << ipDst << Qt::endl; -} - -int32_t stdinToInt32() { - QTextStream in(stdin); - bool ok = false; - int32_t id = in.readLine().toInt(&ok, 10); - if (!ok) return -1; - return id; -} - -int main(int argc, char* argv[]) { - char error[PCAP_ERRBUF_SIZE]; - - QCoreApplication app(argc, argv); - - QCoreApplication::setApplicationName("Mozilla VPN - IP Monitor"); - QCoreApplication::setApplicationVersion(APP_VERSION); - - QCommandLineParser parser; - parser.setApplicationDescription("Mozilla VPN - IP Monitor"); - parser.addHelpOption(); - parser.addVersionOption(); - parser.addPositionalArgument("server.list", "The server list file"); - - QCommandLineOption logFileOption( - "l", "Log non-VPN and non-LAN packages into this file", "file"); - parser.addOption(logFileOption); - - parser.process(app); - - Output() << Output::bold() << QCoreApplication::applicationName() << " " - << QCoreApplication::applicationVersion() << Qt::endl - << Qt::endl; - - const QStringList args = parser.positionalArguments(); - if (args.isEmpty()) { - Output(Output::eError) << "Please, the first argument is required." - << Qt::endl; - Output() << "To generate the server list, run: mozillavpn servers -j > " - "server.list" - << Qt::endl; - return 1; - } - - if (!QFile::exists(args[0])) { - Output(Output::eError) - << "Please, the first argument must be the path of the server " - "list file." - << Qt::endl; - return 1; - } - - QFile* logger = nullptr; - auto loggerGuard = qScopeGuard([&] { - if (logger) { - logger->close(); - delete logger; - } - }); - - QString logFile = parser.value(logFileOption); - if (!logFile.isEmpty()) { - Output(Output::eInfo) << "Opening log file: "; - logger = new QFile(logFile); - if (!logger->open(QIODevice::WriteOnly | QIODevice::Text)) { - Output(Output::eError) << "Failed to open the file"; - return 1; - } - Output(Output::eDone) << "done." << Qt::endl; - } - - QHash servers; - Output(Output::eInfo) << "Processing the list of servers: "; - if (!parseServerListFile(args[0], servers)) { - return 1; - } - if (servers.isEmpty()) { - Output(Output::eError) << "No servers found in the server list file." - << Qt::endl; - return 1; - } - Output(Output::eDone) << servers.count() << " servers." << Qt::endl; - - Output(Output::eInfo) << "Resolving the captive-portal URL..." << Qt::endl; - QStringList captivePortalIps = - resolveCaptivePortal("detectportal.firefox.com"); - - Output(Output::eInfo) << "Retrieving the interface list: "; - pcap_if_t* interfaces = nullptr; - if (pcap_findalldevs(&interfaces, error) == -1) { - Output(Output::eError) << "Error in pcap findall devs" << error << Qt::endl; - return 1; - } - Output(Output::eDone) << "done." << Qt::endl; - - auto interfacesGuard = qScopeGuard([&] { pcap_freealldevs(interfaces); }); - - Output(Output::eInfo) << "The interfaces present on the system are:" - << Qt::endl; - - int i = 0; - for (pcap_if_t* temp = interfaces; temp; temp = temp->next) { - if (!(temp->flags & PCAP_IF_UP) || !(temp->flags & PCAP_IF_RUNNING)) - continue; - - if ((temp->flags & PCAP_IF_LOOPBACK)) continue; - - Output() << " " << ++i << " " << Output::cyan() << temp->name - << Output::reset() << " - " << temp->description << Qt::endl; - } - - Output(Output::eInfo) << "Which interface do you want to use? " - << Output::reset(); - int32_t id = stdinToInt32(); - if (id > i || id < 1) { - Output(Output::eError) << "Sorry. I don't understand your answer." - << Qt::endl; - return 1; - } - - char* device = nullptr; - QStringList addresses; - for (pcap_if_t* temp = interfaces; temp; temp = temp->next) { - if (--id == 0) { - device = temp->name; - for (pcap_addr* address = temp->addresses; address; - address = address->next) { - if (address->addr->sa_family != AF_INET) { - continue; - } - const sockaddr_in* sin = (const sockaddr_in*)address->addr; - addresses.append(inet_ntoa(sin->sin_addr)); - } - break; - } - } - - Output(Output::eInfo) << "Opening device '" << device << "' (" - << addresses.join(",") << "): "; - pcap_t* handle = pcap_open_live(device, BUFSIZ, 1, 1, error); - if (handle == NULL) { - Output(Output::eError) << "Couldn't open device: " << error << Qt::endl; - return 1; - } - Output(Output::eDone) << "done." << Qt::endl; - - // Clean 3 lines. - winsize w; - if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, &w)) { - Output out; - out.cleanTo(w.ws_col); - out << Qt::endl; - out.cleanTo(w.ws_col); - out << Qt::endl; - out.cleanTo(w.ws_col); - } - - uint64_t packageCount = 0; - uint64_t vpnCount = 0; - uint64_t outCount = 0; - uint64_t lanCount = 0; - - while (true) { - pcap_pkthdr header; - const u_char* packet = pcap_next(handle, &header); - if (!packet) continue; - - ether_header* eptr = (ether_header*)packet; - // We care about IP packages. Let's ignore the rest. - if (ntohs(eptr->ether_type) != ETHERTYPE_IP) continue; - - u_int length = header.len; - length -= sizeof(ether_header); - if (length < sizeof(sniff_ip)) { - // truncate IP - continue; - } - - sniff_ip* ip = (sniff_ip*)(packet + sizeof(ether_header)); - if (IP_V(ip) != 4) { - // Invalid IP package version - continue; - } - - if (IP_HL(ip) < 5) { - // Bad length - continue; - } - - if (length < ntohs(ip->ip_len)) { - // Truncate IP - continue; - } - - // check to see if we have the first fragment - if ((ntohs(ip->ip_off) & 0x1fff)) { - continue; - } - - QString ipSrc = inet_ntoa(ip->ip_src); - QString ipDst = inet_ntoa(ip->ip_dst); - - if (ipSrc.isEmpty() || ipDst.isEmpty()) continue; - - bool outcome = addresses.contains(ipSrc); - bool income = addresses.contains(ipDst); - - if (income && captivePortalIps.contains(ipSrc)) continue; - if (outcome && captivePortalIps.contains(ipDst)) continue; - - char direction = '='; - // Let's ignore local and unrelated packages - if ((income && outcome) || (!income && !outcome)) continue; - - ++packageCount; - - if (income) - direction = '>'; - else - direction = '<'; - - Server* server = nullptr; - if (income) - server = servers.value(ipSrc); - else - server = servers.value(ipDst); - - if (server) - ++vpnCount; - else { - bool isLan = false; - if (income) - isLan = RFC1918::contains(QHostAddress(ipSrc)); - else - isLan = RFC1918::contains(QHostAddress(ipDst)); - - if (isLan) - ++lanCount; - else { - ++outCount; - if (logger) logPackage(logger, ipSrc, ipDst); - } - } - - // Let's check the terminal size each time in case the size changes. - int lineLength = 0; - if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, &w)) { - lineLength = w.ws_col; - } - - { - Output line1; - line1 << Output::hideCursor() << Output::prevLine() << Output::prevLine(); - line1 << "Total: " << Output::cyan() << packageCount << Output::reset(); - line1 << " - VPN: " << Output::bold() << Output::green() << vpnCount - << Output::reset(); - line1 << " - LAN: " << Output::bold() << Output::magenta() << lanCount - << Output::reset(); - line1 << " - OUT: " << Output::bold() << Output::red() << outCount - << Output::reset(); - line1.cleanTo(lineLength); - line1 << Qt::endl; - } - - { - Output line2; - line2 << "D: " << direction; - line2 << " - Src: " << Output::bold() << Output::fixSize(ipSrc, 15) - << Output::reset(); - line2 << " - Dest: " << Output::bold() << Output::fixSize(ipDst, 15) - << Output::reset(); - line2.cleanTo(lineLength); - line2 << Qt::endl; - } - - { - Output line3; - if (server) { - line3 << "Loc: " << Output::bold() << server->countryName << ", " - << server->cityName << Output::reset(); - } - line3.cleanTo(lineLength); - } - } - - pcap_close(handle); - return 0; -}