diff --git a/include/common.h b/include/common.h index 5df556e9d..995993bcb 100644 --- a/include/common.h +++ b/include/common.h @@ -16,6 +16,7 @@ namespace kiwix { +enum class IpMode { ipv4, ipv6, all }; typedef zim::size_type size_type; typedef zim::offset_type offset_type; diff --git a/include/server.h b/include/server.h index d1981c8c2..d44c5207a 100644 --- a/include/server.h +++ b/include/server.h @@ -22,6 +22,7 @@ #include #include +#include "common.h" namespace kiwix { @@ -62,8 +63,10 @@ namespace kiwix { m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; } void setBlockExternalLinks(bool blockExternalLinks) { m_blockExternalLinks = blockExternalLinks; } + void setIpMode(IpMode mode) { m_ipMode = mode; } int getPort(); std::string getAddress(); + IpMode getIpMode() const; protected: std::shared_ptr mp_library; @@ -78,6 +81,7 @@ namespace kiwix bool m_withTaskbar = true; bool m_withLibraryButton = true; bool m_blockExternalLinks = false; + IpMode m_ipMode = IpMode::ipv4; int m_ipConnectionLimit = 0; std::unique_ptr mp_server; }; diff --git a/include/tools.h b/include/tools.h index 2ea9e8b29..8ac60312e 100644 --- a/include/tools.h +++ b/include/tools.h @@ -26,6 +26,11 @@ #include namespace kiwix { +struct IpAddress{ + std::string addr; + std::string addr6; +}; + typedef std::pair LangNameCodePair; typedef std::vector FeedLanguages; typedef std::vector FeedCategories; @@ -217,14 +222,14 @@ std::string getMimeTypeForFile(const std::string& filename); /** Provides all available network interfaces * - * This function provides the available IPv4 network interfaces + * This function provides the available IPv4 and IPv6 network interfaces */ -std::map getNetworkInterfaces(); +std::map getNetworkInterfaces(); /** Provides the best IP address * This function provides the best IP address from the list given by getNetworkInterfaces */ -std::string getBestPublicIp(); +std::string getBestPublicIp(bool ipv6); /** Converts file size to human readable format. * diff --git a/meson.build b/meson.build index 50c8afe4e..582ef5eff 100644 --- a/meson.build +++ b/meson.build @@ -48,7 +48,10 @@ if host_machine.system() == 'windows' and static_deps endif if host_machine.system() == 'windows' - add_project_arguments('-DNOMINMAX', language: 'cpp') + add_project_arguments('-DNOMINMAX', language: 'cpp') + extra_libs += ['-liphlpapi'] +else + extra_link_args = [] endif all_deps = [thread_dep, libicu_dep, libzim_dep, pugixml_dep, libcurl_dep, microhttpd_dep, zlib_dep, xapian_dep] @@ -58,12 +61,6 @@ inc = include_directories('include', extra_include) conf = configuration_data() conf.set('LIBKIWIX_VERSION', '"@0@"'.format(meson.project_version())) -if build_machine.system() == 'windows' - extra_link_args = ['-lshlwapi', '-lwinmm'] -else - extra_link_args = [] -endif - subdir('include') subdir('scripts') subdir('static') diff --git a/src/server.cpp b/src/server.cpp index 1a4ecfa9e..89491c371 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -51,6 +51,7 @@ bool Server::start() { m_withTaskbar, m_withLibraryButton, m_blockExternalLinks, + m_ipMode, m_indexTemplateString, m_ipConnectionLimit)); return mp_server->start(); @@ -84,4 +85,9 @@ std::string Server::getAddress() return mp_server->getAddress(); } +IpMode Server::getIpMode() const +{ + return mp_server->getIpMode(); +} + } diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index 910ade8a4..ccc94a49b 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -416,6 +416,7 @@ InternalServer::InternalServer(LibraryPtr library, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks, + IpMode ipMode, std::string indexTemplateString, int ipConnectionLimit) : m_addr(addr), @@ -428,6 +429,7 @@ InternalServer::InternalServer(LibraryPtr library, m_withTaskbar(withTaskbar), m_withLibraryButton(withLibraryButton), m_blockExternalLinks(blockExternalLinks), + m_ipMode(ipMode), m_indexTemplateString(indexTemplateString.empty() ? RESOURCE::templates::index_html : indexTemplateString), m_ipConnectionLimit(ipConnectionLimit), mp_daemon(nullptr), @@ -451,28 +453,48 @@ bool InternalServer::start() { if (m_verbose.load()) flags |= MHD_USE_DEBUG; - struct sockaddr_in sockAddr; - memset(&sockAddr, 0, sizeof(sockAddr)); - sockAddr.sin_family = AF_INET; - sockAddr.sin_port = htons(m_port); + + struct sockaddr_in sockAddr4={0}; + sockAddr4.sin_family = AF_INET; + sockAddr4.sin_port = htons(m_port); + struct sockaddr_in6 sockAddr6={0}; + sockAddr6.sin6_family = AF_INET6; + sockAddr6.sin6_port = htons(m_port); + if (m_addr.empty()) { if (0 != INADDR_ANY) { - sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); + sockAddr6.sin6_addr = in6addr_any; + sockAddr4.sin_addr.s_addr = htonl(INADDR_ANY); } - m_addr = kiwix::getBestPublicIp(); + m_addr = kiwix::getBestPublicIp(m_ipMode == IpMode::ipv6 || m_ipMode == IpMode::all); } else { - if (inet_pton(AF_INET, m_addr.c_str(), &(sockAddr.sin_addr.s_addr)) == 0) { + bool ipv6 = inet_pton(AF_INET6, m_addr.c_str(), &(sockAddr6.sin6_addr.s6_addr)) == 1; + bool ipv4 = inet_pton(AF_INET, m_addr.c_str(), &(sockAddr4.sin_addr.s_addr)) == 1; + if (ipv6){ + m_ipMode = IpMode::all; + } else if (!ipv4) { std::cerr << "Ip address " << m_addr << " is not a valid ip address" << std::endl; return false; } } + + if (m_ipMode == IpMode::all) { + flags|=MHD_USE_DUAL_STACK; + } else if (m_ipMode == IpMode::ipv6) { + flags|=MHD_USE_IPv6; + } + + struct sockaddr* sockaddr = (m_ipMode==IpMode::all || m_ipMode==IpMode::ipv6) + ? (struct sockaddr*)&sockAddr6 + : (struct sockaddr*)&sockAddr4; + mp_daemon = MHD_start_daemon(flags, m_port, NULL, NULL, &staticHandlerCallback, this, - MHD_OPTION_SOCK_ADDR, &sockAddr, + MHD_OPTION_SOCK_ADDR, sockaddr, MHD_OPTION_THREAD_POOL_SIZE, m_nbThreads, MHD_OPTION_PER_IP_CONNECTION_LIMIT, m_ipConnectionLimit, MHD_OPTION_END); diff --git a/src/server/internalServer.h b/src/server/internalServer.h index 53b39d986..14694fd19 100644 --- a/src/server/internalServer.h +++ b/src/server/internalServer.h @@ -103,6 +103,7 @@ class InternalServer { bool withTaskbar, bool withLibraryButton, bool blockExternalLinks, + IpMode ipMode, std::string indexTemplateString, int ipConnectionLimit); virtual ~InternalServer(); @@ -118,6 +119,7 @@ class InternalServer { void stop(); std::string getAddress() { return m_addr; } int getPort() { return m_port; } + IpMode getIpMode() const { return m_ipMode; } private: // functions std::unique_ptr handle_request(const RequestContext& request); @@ -174,6 +176,7 @@ class InternalServer { bool m_withTaskbar; bool m_withLibraryButton; bool m_blockExternalLinks; + IpMode m_ipMode; std::string m_indexTemplateString; int m_ipConnectionLimit; struct MHD_Daemon* mp_daemon; diff --git a/src/tools/networkTools.cpp b/src/tools/networkTools.cpp index 191eb8543..c1b61335f 100644 --- a/src/tools/networkTools.cpp +++ b/src/tools/networkTools.cpp @@ -32,12 +32,14 @@ #include #ifdef _WIN32 +#include #include #include #include #else #include -#include +#include +#include #include #include #include @@ -75,90 +77,126 @@ std::string kiwix::download(const std::string& url) { return ss.str(); } -std::map kiwix::getNetworkInterfaces() { - std::map interfaces; - #ifdef _WIN32 - SOCKET sd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0); - if (sd == INVALID_SOCKET) { - std::cerr << "Failed to get a socket. Error " << WSAGetLastError() << std::endl; - return interfaces; - } - INTERFACE_INFO InterfaceList[20]; - unsigned long nBytesReturned; - if (WSAIoctl(sd, SIO_GET_INTERFACE_LIST, 0, 0, &InterfaceList, - sizeof(InterfaceList), &nBytesReturned, 0, 0) == SOCKET_ERROR) { - std::cerr << "Failed calling WSAIoctl: error " << WSAGetLastError() << std::endl; - return interfaces; - } +std::map getNetworkInterfacesWin() { + std::map interfaces; - int nNumInterfaces = nBytesReturned / sizeof(INTERFACE_INFO); - for (int i = 0; i < nNumInterfaces; ++i) { - sockaddr_in *pAddress; - pAddress = (sockaddr_in *) & (InterfaceList[i].iiAddress.AddressIn); - if(pAddress->sin_family == AF_INET) { - /* Add to the map */ - std::string interfaceName = std::string(inet_ntoa(pAddress->sin_addr)); - interfaces[interfaceName] = interfaceName; - } - } -#else - /* Get Network interfaces information */ - char buf[16384]; - struct ifconf ifconf; - int fd = socket(PF_INET, SOCK_DGRAM, 0); /* Only IPV4 */ - ifconf.ifc_len = sizeof(buf); - ifconf.ifc_buf=buf; - if(ioctl(fd, SIOCGIFCONF, &ifconf)!=0) { - perror("ioctl(SIOCGIFCONF)"); - } + const int working_buffer_size = 15000; + const int max_tries = 3; - /* Go through each interface */ - struct ifreq *ifreq; - ifreq = ifconf.ifc_req; - for (int i = 0; i < ifconf.ifc_len; ) { - if (ifreq->ifr_addr.sa_family == AF_INET) { - /* Get the network interface ip */ - char host[128] = { 0 }; - const int error = getnameinfo(&(ifreq->ifr_addr), sizeof(ifreq->ifr_addr), - host, sizeof(host), - 0, 0, NI_NUMERICHOST); - if (!error) { - std::string interfaceName = std::string(ifreq->ifr_name); - std::string interfaceIp = std::string(host); - /* Add to the map */ - interfaces[interfaceName] = interfaceIp; - } else { - perror("getnameinfo()"); - } + ULONG flags = GAA_FLAG_INCLUDE_PREFIX; + + // default to unspecified address family (both) + ULONG family = AF_UNSPEC; + + ULONG outBufLen = working_buffer_size; + ULONG Iterations = 0; + DWORD dwRetVal = 0; + PIP_ADAPTER_ADDRESSES interfacesHead = NULL; + + // Successively allocate the required memory until GetAdaptersAddresses does not + // results in ERROR_BUFFER_OVERFLOW for a maximum of max_tries + do{ + interfacesHead = (IP_ADAPTER_ADDRESSES *) malloc(outBufLen); + if (interfacesHead == NULL) { + std::cerr << "Memory allocation failed for IP_ADAPTER_ADDRESSES struct" << std::endl; + return interfaces; } - /* some systems have ifr_addr.sa_len and adjust the length that - * way, but not mine. weird */ - size_t len; -#ifndef __linux__ - len = IFNAMSIZ + ifreq->ifr_addr.sa_len; -#else - len = sizeof(*ifreq); -#endif - ifreq = (struct ifreq*)((char*)ifreq+len); - i += len; + dwRetVal = GetAdaptersAddresses(family, flags, NULL, interfacesHead, &outBufLen); + } while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < max_tries)); + + if (dwRetVal == NO_ERROR) { + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL; + unsigned int i = 0; + for (PIP_ADAPTER_ADDRESSES temp = interfacesHead; temp != NULL; + temp = temp->Next) { + pUnicast = temp->FirstUnicastAddress; + if (pUnicast != NULL) { + for (i = 0; pUnicast != NULL; i++){ + if (pUnicast->Address.lpSockaddr->sa_family == AF_INET) + { + sockaddr_in *si = (sockaddr_in *)(pUnicast->Address.lpSockaddr); + char host[INET_ADDRSTRLEN]={0}; + inet_ntop(AF_INET, &(si->sin_addr), host, sizeof(host)); + interfaces[temp->AdapterName].addr=host; + } + else if (pUnicast->Address.lpSockaddr->sa_family == AF_INET6) + { + sockaddr_in6 *si = (sockaddr_in6 *)(pUnicast->Address.lpSockaddr); + char host[INET6_ADDRSTRLEN]={0}; + inet_ntop(AF_INET6, &(si->sin6_addr), host, sizeof(host)); + if (!IN6_IS_ADDR_LINKLOCAL(&(si->sin6_addr))) + interfaces[temp->AdapterName].addr6=host; + } + pUnicast = pUnicast->Next; + } + } + } + } else { + std::cerr << "Call to GetAdaptersAddresses failed with error: "<< dwRetVal << std::endl; } -#endif + + if (interfacesHead) free(interfacesHead); + return interfaces; } -std::string kiwix::getBestPublicIp() { - auto interfaces = getNetworkInterfaces(); +#else + +std::map getNetworkInterfacesPosix() { + std::map interfaces; + + struct ifaddrs *interfacesHead; + if (getifaddrs(&interfacesHead) == -1) { + perror("getifaddrs"); + } + + for (ifaddrs *temp = interfacesHead; temp != NULL; temp = temp->ifa_next) { + if (temp->ifa_addr == NULL) continue; + + if (temp->ifa_addr->sa_family == AF_INET) { + sockaddr_in *si = (sockaddr_in *)(temp->ifa_addr); + char host[INET_ADDRSTRLEN] = {0}; + inet_ntop(AF_INET, &(si->sin_addr), host, sizeof(host)); + interfaces[temp->ifa_name].addr=host; + } else if (temp->ifa_addr->sa_family == AF_INET6) { + sockaddr_in6 *si = (sockaddr_in6 *)(temp->ifa_addr); + char host[INET6_ADDRSTRLEN] = {0}; + inet_ntop(AF_INET6, &(si->sin6_addr), host, sizeof(host)); + if (!IN6_IS_ADDR_LINKLOCAL(&(si->sin6_addr))) + interfaces[temp->ifa_name].addr6=host; + } + } + + freeifaddrs(interfacesHead); + + return interfaces; +} + +#endif + +std::map kiwix::getNetworkInterfaces() { +#ifdef _WIN32 + return getNetworkInterfacesWin(); +#else + return getNetworkInterfacesPosix(); +#endif +} + +std::string kiwix::getBestPublicIp(bool ipv6) { + kiwix::IpAddress bestPublicIp = kiwix::IpAddress{"127.0.0.1","::1"}; + std::map interfaces = getNetworkInterfaces(); #ifndef _WIN32 const char* const prioritizedNames[] = { "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" }; for(auto name: prioritizedNames) { - auto it = interfaces.find(name); - if(it != interfaces.end()) { - return it->second; + auto it=interfaces.find(name); + if(it != interfaces.end() && !(ipv6 && (*it).second.addr6.empty())) { + bestPublicIp = (*it).second; + break; } } #endif @@ -166,12 +204,12 @@ std::string kiwix::getBestPublicIp() { const char* const prefixes[] = { "192.168", "172.16.", "10.0" }; for(auto prefix : prefixes){ for(auto& itr : interfaces) { - auto interfaceIp = itr.second; - if (interfaceIp.find(prefix) == 0) { - return interfaceIp; + std::string interfaceIp(itr.second.addr); + if (interfaceIp.find(prefix) == 0 && !(ipv6 && itr.second.addr6.empty())) { + bestPublicIp = itr.second; + break; } } } - - return "127.0.0.1"; + return ipv6 ? bestPublicIp.addr6 : bestPublicIp.addr; }