aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfuzzard <fuzzard@users.noreply.github.com>2023-10-17 11:12:43 +1000
committerGitHub <noreply@github.com>2023-10-17 11:12:43 +1000
commitdddc580d34846931f006a50209414c7544975cb3 (patch)
tree7f3eb94d72598c924c017ca0adc7a49ec26b7817
parent871b40cdd90adec56b94974ce9014dc854336cc0 (diff)
parent771a867dee47c55b14330f94cfcb3d5aa7a4e9b5 (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.txt8
-rw-r--r--xbmc/network/test/TestNetwork.cpp40
-rw-r--r--xbmc/platform/linux/network/NetworkLinux.cpp151
-rw-r--r--xbmc/platform/win32/PlatformDefs.h1
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>