diff options
author | fuzzard <fuzzard@users.noreply.github.com> | 2023-10-17 11:12:43 +1000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-17 11:12:43 +1000 |
commit | dddc580d34846931f006a50209414c7544975cb3 (patch) | |
tree | 7f3eb94d72598c924c017ca0adc7a49ec26b7817 | |
parent | 871b40cdd90adec56b94974ce9014dc854336cc0 (diff) | |
parent | 771a867dee47c55b14330f94cfcb3d5aa7a4e9b5 (diff) |
Merge pull request #23870 from lrusak/linux-ping
CNetworkPosix: implement ping using sockets instead of system call to ping
-rw-r--r-- | xbmc/network/test/CMakeLists.txt | 8 | ||||
-rw-r--r-- | xbmc/network/test/TestNetwork.cpp | 40 | ||||
-rw-r--r-- | xbmc/platform/linux/network/NetworkLinux.cpp | 151 | ||||
-rw-r--r-- | xbmc/platform/win32/PlatformDefs.h | 1 |
4 files changed, 182 insertions, 18 deletions
diff --git a/xbmc/network/test/CMakeLists.txt b/xbmc/network/test/CMakeLists.txt index a323d1835b..05eb260c0b 100644 --- a/xbmc/network/test/CMakeLists.txt +++ b/xbmc/network/test/CMakeLists.txt @@ -1,5 +1,7 @@ -if(MICROHTTPD_FOUND) - set(SOURCES TestWebServer.cpp) +set(SOURCES TestNetwork.cpp) - core_add_test_library(network_test) +if(MICROHTTPD_FOUND) + list(APPEND SOURCES TestWebServer.cpp) endif() + +core_add_test_library(network_test) diff --git a/xbmc/network/test/TestNetwork.cpp b/xbmc/network/test/TestNetwork.cpp new file mode 100644 index 0000000000..df4e848b11 --- /dev/null +++ b/xbmc/network/test/TestNetwork.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ServiceBroker.h" +#include "network/Network.h" + +#include <arpa/inet.h> +#include <gtest/gtest.h> + +class TestNetwork : public testing::Test +{ +public: + TestNetwork() = default; + ~TestNetwork() = default; + + bool PingHost(const std::string& ip) const + { + static auto& network = CServiceBroker::GetNetwork(); + + return network.PingHost(inet_addr(ip.c_str()), GetPort(), GetTimeout()); + } + + unsigned int GetPort() const { return m_port; } + unsigned int GetTimeout() const { return m_timeoutMs; } + +private: + unsigned int m_port{0}; + unsigned int m_timeoutMs{100}; +}; + +TEST_F(TestNetwork, PingHost) +{ + EXPECT_TRUE(PingHost("127.0.0.1")); + EXPECT_FALSE(PingHost("10.254.254.254")); +} diff --git a/xbmc/platform/linux/network/NetworkLinux.cpp b/xbmc/platform/linux/network/NetworkLinux.cpp index 4e9eda0d8d..7afeb9de61 100644 --- a/xbmc/platform/linux/network/NetworkLinux.cpp +++ b/xbmc/platform/linux/network/NetworkLinux.cpp @@ -8,17 +8,63 @@ #include "NetworkLinux.h" +#include "utils/FileHandle.h" #include "utils/StringUtils.h" #include "utils/log.h" +#include <chrono> #include <errno.h> #include <utility> #include <arpa/inet.h> #include <net/if.h> #include <net/if_arp.h> +#include <netinet/ip_icmp.h> #include <resolv.h> +#include <sys/epoll.h> #include <sys/ioctl.h> +#include <sys/socket.h> +#include <unistd.h> + +using namespace KODI::UTILS::POSIX; + +namespace +{ + +constexpr unsigned int ICMP_PACKET_SIZE{64}; +constexpr unsigned int TTL{64}; + +struct IcmpPacket +{ + icmphdr header; + uint8_t data[ICMP_PACKET_SIZE - sizeof(icmphdr)]; + + uint16_t Checksum() + { + auto data = reinterpret_cast<const uint16_t*>(&header); + unsigned int length = sizeof(header) + sizeof(data); + + unsigned int sum; + + for (sum = 0; length > 1; length -= 2) + { + sum += *data++; + } + + if (length == 1) + { + sum += *data; + } + + sum = (sum >> 16) + (sum & 0xFFFF); + + sum += (sum >> 16); + + return ~sum; + } +}; + +} // namespace CNetworkInterfaceLinux::CNetworkInterfaceLinux(CNetworkPosix* network, std::string interfaceName, @@ -199,25 +245,100 @@ std::vector<std::string> CNetworkLinux::GetNameServers() bool CNetworkLinux::PingHost(unsigned long remote_ip, unsigned int timeout_ms) { - char cmd_line[64]; + CFileHandle fd(socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_ICMP)); + if (!fd) + { + CLog::Log(LOGERROR, "socket failed: {} ({})", strerror(errno), errno); + return false; + } + + int ret = setsockopt(fd, SOL_IP, IP_TTL, &TTL, sizeof(TTL)); + if (ret != 0) + { + CLog::Log(LOGERROR, "setsockopt failed: {} ({})", strerror(errno), errno); + return false; + } + + CFileHandle epfd(epoll_create1(EPOLL_CLOEXEC)); + if (!epfd) + { + CLog::Log(LOGERROR, "epoll_create1 failed: {} ({})", strerror(errno), errno); + return false; + } + + epoll_event event; + event.events = EPOLLIN; + event.data.fd = fd; + ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); + if (ret < 0) + { + CLog::Log(LOGERROR, "epoll_ctl failed: {} ({})", strerror(errno), errno); + return false; + } + + IcmpPacket packet = {}; + + packet.header.type = ICMP_ECHO; + packet.header.un.echo.id = getpid(); + + packet.header.un.echo.sequence = 0; + packet.header.checksum = packet.Checksum(); - struct in_addr host_ip; - host_ip.s_addr = remote_ip; + sockaddr_in addr = {}; + addr.sin_addr.s_addr = remote_ip; + addr.sin_family = AF_INET; + + const auto start = std::chrono::steady_clock::now(); + + ret = sendto(fd, &packet, sizeof(packet), 0, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)); + if (ret < 1) + { + CLog::Log(LOGERROR, "sendto failed: {} ({})", strerror(errno), errno); + return false; + } + + event = {}; + ret = epoll_wait(epfd, &event, 1, timeout_ms); + if (ret < 1) + { + if (ret == 0) + { + CLog::Log(LOGERROR, "timed out while waiting to receive ({} ms)", timeout_ms); + } + else + { + CLog::Log(LOGERROR, "epoll_wait failed: {} ({})", strerror(errno), errno); + } - snprintf(cmd_line, sizeof(cmd_line), "ping -c 1 -w %d %s", - timeout_ms / 1000 + (timeout_ms % 1000) != 0, inet_ntoa(host_ip)); + return false; + } - int status = -1; - status = system(cmd_line); - int result = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + if (event.events & EPOLLIN) + { + socklen_t length = sizeof(addr); + ret = recvfrom(fd, &packet, sizeof(packet), 0, reinterpret_cast<sockaddr*>(&addr), &length); + if (ret < 1) + { + CLog::Log(LOGERROR, "recvfrom failed: {} ({})", strerror(errno), errno); + return false; + } + } - // http://linux.about.com/od/commands/l/blcmdl8_ping.htm ; - // 0 reply - // 1 no reply - // else some error + const auto end = std::chrono::steady_clock::now(); - if (result < 0 || result > 1) - CLog::Log(LOGERROR, "Ping fail : status = {}, errno = {} : '{}'", status, errno, cmd_line); + if (!(packet.header.type == ICMP_ECHOREPLY && packet.header.code == 0)) + { + CLog::Log(LOGERROR, "unexpected ping reply: type={} code={}", packet.header.type, + packet.header.code); + return false; + } + else + { + const auto duration = + std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(end - start); - return result == 0; + CLog::Log(LOGDEBUG, "PING {}: icmp_seq={} ttl={} time={:0.3f} ms", inet_ntoa(addr.sin_addr), + packet.header.un.echo.sequence, TTL, duration.count()); + return true; + } } diff --git a/xbmc/platform/win32/PlatformDefs.h b/xbmc/platform/win32/PlatformDefs.h index a7ff4ba261..213bccc848 100644 --- a/xbmc/platform/win32/PlatformDefs.h +++ b/xbmc/platform/win32/PlatformDefs.h @@ -9,6 +9,7 @@ #pragma once #include <windows.h> +#include <winsock2.h> #define __STDC_FORMAT_MACROS #include <inttypes.h> |