From e1a8bbf02005e4df1ad631d294403e4de43fa5dd Mon Sep 17 00:00:00 2001 From: cesare Date: Mon, 10 Sep 2018 16:41:32 +0100 Subject: [PATCH] Added support for finding the broadcast address of an interface --- extras/UnitTestRunner/Source/Main.cpp | 30 +++- modules/juce_core/juce_core.cpp | 2 + .../juce_core/native/juce_android_Network.cpp | 6 + .../native/juce_mac_linux_IPAddress.h | 140 ++++++++++++++++++ .../juce_core/native/juce_win32_Network.cpp | 7 + modules/juce_core/network/juce_IPAddress.cpp | 134 +++++++++++------ modules/juce_core/network/juce_IPAddress.h | 8 + 7 files changed, 281 insertions(+), 46 deletions(-) create mode 100644 modules/juce_core/native/juce_mac_linux_IPAddress.h diff --git a/extras/UnitTestRunner/Source/Main.cpp b/extras/UnitTestRunner/Source/Main.cpp index 41511a3c7d..67c0d9f5c5 100644 --- a/extras/UnitTestRunner/Source/Main.cpp +++ b/extras/UnitTestRunner/Source/Main.cpp @@ -48,14 +48,40 @@ class ConsoleUnitTestRunner : public UnitTestRunner } }; + //============================================================================== -int main() +int main (int argc, char **argv) { ConsoleLogger logger; Logger::setCurrentLogger (&logger); ConsoleUnitTestRunner runner; - runner.runAllTests(); + + ArgumentList args (argc, argv); + + if (args.size() == 0) + { + runner.runAllTests(); + } + else + { + if (args.containsOption ("--help|-h")) + { + std::cout << argv[0] << " [--help|-h] [--category category] [--list-categories]" << std::endl; + return 0; + } + + if (args.containsOption ("--list-categories")) + { + for (auto& category : UnitTest::getAllCategories()) + std::cout << category << std::endl; + + return 0; + } + + if (args.containsOption ("--category")) + runner.runTestsInCategory (args.getArgumentAfterOption ("--category").text); + } Logger::setCurrentLogger (nullptr); diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp index b7dd88cd75..f3eb737f68 100644 --- a/modules/juce_core/juce_core.cpp +++ b/modules/juce_core/juce_core.cpp @@ -190,6 +190,7 @@ //============================================================================== #if JUCE_MAC || JUCE_IOS #include "native/juce_mac_Files.mm" +#include "native/juce_mac_linux_IPAddress.h" #include "native/juce_mac_Network.mm" #include "native/juce_mac_Strings.mm" #include "native/juce_mac_SystemStats.mm" @@ -207,6 +208,7 @@ #elif JUCE_LINUX #include "native/juce_linux_CommonFile.cpp" #include "native/juce_linux_Files.cpp" +#include "native/juce_mac_linux_IPAddress.h" #include "native/juce_linux_Network.cpp" #if JUCE_USE_CURL #include "native/juce_curl_Network.cpp" diff --git a/modules/juce_core/native/juce_android_Network.cpp b/modules/juce_core/native/juce_android_Network.cpp index bc231a7347..99a0bee8d5 100644 --- a/modules/juce_core/native/juce_android_Network.cpp +++ b/modules/juce_core/native/juce_android_Network.cpp @@ -400,4 +400,10 @@ void IPAddress::findAllAddresses (Array& result, bool /*includeIPv6*/ } } +IPAddress IPAddress::getInterfaceBroadcastAddress (const IPAddress&) +{ + return {}; // TODO +} + + } // namespace juce diff --git a/modules/juce_core/native/juce_mac_linux_IPAddress.h b/modules/juce_core/native/juce_mac_linux_IPAddress.h new file mode 100644 index 0000000000..024977b8ae --- /dev/null +++ b/modules/juce_core/native/juce_mac_linux_IPAddress.h @@ -0,0 +1,140 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +namespace +{ + static IPAddress makeAddress (const sockaddr_in6* addr_in) + { + if (addr_in == nullptr) + return {}; + + in6_addr addr = addr_in->sin6_addr; + + union ByteUnion + { + uint16 combined; + uint8 split[2]; + }; + + ByteUnion temp; + uint16 arr[8]; + + for (int i = 0; i < 8; ++i) // Swap bytes from network to host order + { + temp.split[0] = addr.s6_addr[i * 2 + 1]; + temp.split[1] = addr.s6_addr[i * 2]; + + arr[i] = temp.combined; + } + + return IPAddress (arr); + } + + static IPAddress makeAddress (const sockaddr_in *addr_in) + { + if (addr_in->sin_addr.s_addr == INADDR_NONE) + return {}; + + return IPAddress (ntohl (addr_in->sin_addr.s_addr)); + } + + struct InterfaceInfo + { + IPAddress interfaceAddress; + IPAddress broadcastAddress; + }; + + bool operator== (const InterfaceInfo& lhs, const InterfaceInfo& rhs) + { + return lhs.interfaceAddress == rhs.interfaceAddress + && lhs.broadcastAddress == rhs.broadcastAddress; + } + + bool populateInterfaceInfo (struct ifaddrs* ifa, InterfaceInfo& interfaceInfo) + { + if (ifa->ifa_addr != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_INET) + { + auto* interfaceAddressInfo = (sockaddr_in*) ifa->ifa_addr; + auto* broadcastAddressInfo = (sockaddr_in*) ifa->ifa_dstaddr; + + if (interfaceAddressInfo->sin_addr.s_addr != INADDR_NONE) + { + interfaceInfo.interfaceAddress = makeAddress (interfaceAddressInfo); + interfaceInfo.broadcastAddress = makeAddress (broadcastAddressInfo); + return true; + } + } + else if (ifa->ifa_addr->sa_family == AF_INET6) + { + interfaceInfo.interfaceAddress = makeAddress ((sockaddr_in6*) ifa->ifa_addr); + interfaceInfo.broadcastAddress = makeAddress ((sockaddr_in6*) ifa->ifa_dstaddr); + return true; + } + } + + return false; + } + + Array getAllInterfaceInfo() + { + Array interfaces; + struct ifaddrs* ifaddr = nullptr; + + if (getifaddrs (&ifaddr) != -1) + { + for (auto* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + InterfaceInfo i; + + if (populateInterfaceInfo (ifa, i)) + interfaces.addIfNotAlreadyThere (i); + } + + freeifaddrs (ifaddr); + } + + return interfaces; + } +} + +void IPAddress::findAllAddresses (Array& result, bool includeIPv6) +{ + for (auto& i : getAllInterfaceInfo()) + if (includeIPv6 || ! i.interfaceAddress.isIPv6) + result.addIfNotAlreadyThere (i.interfaceAddress); +} + +IPAddress IPAddress::getInterfaceBroadcastAddress (const IPAddress& interfaceAddress) +{ + for (auto& i : getAllInterfaceInfo()) + if (i.interfaceAddress == interfaceAddress) + return i.broadcastAddress; + + return {}; +} + +} // namespace juce diff --git a/modules/juce_core/native/juce_win32_Network.cpp b/modules/juce_core/native/juce_win32_Network.cpp index 8c4716b298..96f10db90d 100644 --- a/modules/juce_core/native/juce_win32_Network.cpp +++ b/modules/juce_core/native/juce_win32_Network.cpp @@ -618,6 +618,13 @@ void IPAddress::findAllAddresses (Array& result, bool includeIPv6) } } +IPAddress IPAddress::getInterfaceBroadcastAddress (const IPAddress&) +{ + // TODO + return IPAddress {}; +} + + //============================================================================== bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& targetEmailAddress, const String& emailSubject, diff --git a/modules/juce_core/network/juce_IPAddress.cpp b/modules/juce_core/network/juce_IPAddress.cpp index 9e021c24ff..7a5ade821a 100644 --- a/modules/juce_core/network/juce_IPAddress.cpp +++ b/modules/juce_core/network/juce_IPAddress.cpp @@ -85,6 +85,15 @@ IPAddress::IPAddress (uint32 n) noexcept : isIPv6 (false) zeroUnusedBytes(); } +bool IPAddress::isNull() const +{ + for (int i = 0; i < 16; ++i) + if (address[i] != 0) + return false; + + return true; +} + static String removePort (const String& adr) { if (adr.containsAnyOf ("[]")) @@ -350,57 +359,94 @@ Array IPAddress::getAllAddresses (bool includeIPv6) return addresses; } -#if (! JUCE_WINDOWS) && (! JUCE_ANDROID) -static void addAddress (const sockaddr_in* addr_in, Array& result) +//============================================================================== +#if JUCE_UNIT_TESTS + +struct IPAddressTests : public UnitTest { - auto addr = addr_in->sin_addr.s_addr; - - if (addr != INADDR_NONE) - result.addIfNotAlreadyThere (IPAddress (ntohl (addr))); -} - -static void addAddress (const sockaddr_in6* addr_in, Array& result) -{ - in6_addr addr = addr_in->sin6_addr; - - union ByteUnion + IPAddressTests() + : UnitTest ("IPAddress", "Networking") { - uint16 combined; - uint8 split[2]; - }; - - ByteUnion temp; - uint16 arr[8]; - - for (int i = 0; i < 8; ++i) // Swap bytes from network to host order - { - temp.split[0] = addr.s6_addr[i * 2 + 1]; - temp.split[1] = addr.s6_addr[i * 2]; - - arr[i] = temp.combined; } - result.addIfNotAlreadyThere (IPAddress (arr)); -} - -void IPAddress::findAllAddresses (Array& result, bool includeIPv6) -{ - struct ifaddrs* ifaddr = nullptr; - - if (getifaddrs (&ifaddr) == -1) - return; - - for (auto* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + void runTest() override { - if (ifa->ifa_addr == nullptr) - continue; - - if (ifa->ifa_addr->sa_family == AF_INET) addAddress ((const sockaddr_in*) ifa->ifa_addr, result); - else if (ifa->ifa_addr->sa_family == AF_INET6 && includeIPv6) addAddress ((const sockaddr_in6*) ifa->ifa_addr, result); + testConstructors(); + testFindAllAddresses(); + testFindBroadcastAddress(); } - freeifaddrs (ifaddr); -} + void testConstructors() + { + beginTest ("constructors"); + + // Default IPAdress should be null + IPAddress defaultConstructed; + expect (defaultConstructed.isNull()); + + auto local = IPAddress::local(); + expect (! local.isNull()); + + IPAddress ipv4{1, 2, 3, 4}; + expect (! ipv4.isNull()); + expect (! ipv4.isIPv6); + expect (ipv4.toString() == "1.2.3.4"); + } + + void testFindAllAddresses() + { + beginTest ("find all addresses"); + + Array ipv4Addresses; + Array allAddresses; + + IPAddress::findAllAddresses (ipv4Addresses, false); + IPAddress::findAllAddresses (allAddresses, true); + + expect (allAddresses.size() >= ipv4Addresses.size()); + + for (auto& a : ipv4Addresses) + { + expect (! a.isNull()); + expect (! a.isIPv6); + } + + for (auto& a : allAddresses) + { + expect (! a.isNull()); + } + } + + void testFindBroadcastAddress() + { + beginTest ("broadcast addresses"); + + Array addresses; + + // Only IPv4 interfaces have broadcast + IPAddress::findAllAddresses (addresses, false); + + for (auto& a : addresses) + { + expect (! a.isNull()); + + auto broadcastAddress = IPAddress::getInterfaceBroadcastAddress (a); + + // If we retrieve an address, it should be an IPv4 address + if (! broadcastAddress.isNull()) + { + expect (! a.isIPv6); + } + } + + // Expect to fail to find a broadcast for this address + IPAddress address{1, 2, 3, 4}; + expect (IPAddress::getInterfaceBroadcastAddress (address).isNull()); + } +}; + +static IPAddressTests iPAddressTests; + #endif } // namespace juce diff --git a/modules/juce_core/network/juce_IPAddress.h b/modules/juce_core/network/juce_IPAddress.h index b77bbb9275..e93b716a05 100644 --- a/modules/juce_core/network/juce_IPAddress.h +++ b/modules/juce_core/network/juce_IPAddress.h @@ -87,6 +87,9 @@ public: /** Parses a string IP address of the form "1.2.3.4" (IPv4) or "1:2:3:4:5:6:7:8" (IPv6). */ explicit IPAddress (const String& address); + /** Returns whether the address contains the null address (e.g. 0.0.0.0). */ + bool isNull() const; + //============================================================================== /** Returns a dot- or colon-separated string in the form "1.2.3.4" (IPv4) or "1:2:3:4:5:6:7:8" (IPv6). */ String toString() const; @@ -130,6 +133,11 @@ public: /** Converts an IPv4 address to an IPv4-mapped IPv6 address. */ static IPAddress convertIPv4AddressToIPv4Mapped (const IPAddress& addressToMap); + /** If the IPAdress is the address of an interface on the machine, returns the associated broadcast address. + If the address is not an interface, it will return a null address. + */ + static IPAddress getInterfaceBroadcastAddress (const IPAddress& interfaceAddress); + private: /** Union used to split a 16-bit unsigned integer into 2 8-bit unsigned integers or vice-versa */ union ByteUnion