Serenity Operating System
at master 140 lines 4.9 kB view raw
1/* 2 * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Assertions.h> 8#include <AK/DeprecatedString.h> 9#include <LibCore/ArgsParser.h> 10#include <LibCore/ElapsedTimer.h> 11#include <LibCore/System.h> 12#include <LibMain/Main.h> 13#include <arpa/inet.h> 14#include <errno.h> 15#include <netdb.h> 16#include <netinet/in.h> 17#include <netinet/ip_icmp.h> 18#include <serenity.h> 19#include <stdio.h> 20#include <string.h> 21#include <sys/socket.h> 22#include <unistd.h> 23 24ErrorOr<int> serenity_main(Main::Arguments arguments) 25{ 26 TRY(Core::System::pledge("stdio id inet unix")); 27 28 DeprecatedString host_name; 29 int max_hops = 30; 30 int max_retries = 3; 31 int echo_timeout = 5; 32 33 Core::ArgsParser args_parser; 34 args_parser.add_positional_argument(host_name, "destination", "destination", Core::ArgsParser::Required::Yes); 35 args_parser.add_option(max_hops, "use at most <hops> to the destination", "max-hops", 'h', "hops"); 36 args_parser.add_option(max_retries, "retry TTL at most <tries> times", "max-retries", 'r', "tries"); 37 args_parser.add_option(echo_timeout, "wait at most <seconds> for a response", "timeout", 't', "seconds"); 38 args_parser.parse(arguments); 39 40 if (max_hops < 1 || max_hops > 255) { 41 return Error::from_string_literal("Invalid maximum hops amount"); 42 } 43 44 if (max_retries < 1) { 45 return Error::from_string_literal("Invalid maximum retries amount"); 46 } 47 48 auto* hostent = gethostbyname(host_name.characters()); 49 if (!hostent) { 50 warnln("Lookup failed for '{}'", host_name); 51 return 1; 52 } 53 sockaddr_in host_address {}; 54 host_address.sin_family = AF_INET; 55 host_address.sin_port = 44444; 56 host_address.sin_addr.s_addr = *(in_addr_t const*)hostent->h_addr_list[0]; 57 58 int fd = TRY(Core::System::socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)); 59 60 TRY(Core::System::drop_privileges()); 61 TRY(Core::System::pledge("stdio inet unix")); 62 63 timeval timeout { echo_timeout, 0 }; 64 TRY(Core::System::setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout))); 65 66 struct icmp_request { 67 struct icmphdr header; 68 char msg[64 - sizeof(struct icmphdr)]; 69 }; 70 71 struct icmp_response { 72 u8 ip_header[20]; 73 struct icmphdr header; 74 char msg[64 - sizeof(struct icmphdr)]; 75 }; 76 77 // 1: reached target 78 // 0: got ttl exhausted response 79 // -1: error or no response 80 auto try_reach_host = [&](int ttl) -> ErrorOr<int> { 81 Core::ElapsedTimer m_timer { true }; 82 auto ttl_number = DeprecatedString::number(ttl); 83 for (auto i = 0; i < max_retries; i++) { 84 icmp_request request {}; 85 request.header = { ICMP_ECHO, 0, 0, { { 0, 0 } } }; 86 bool fits = ttl_number.copy_characters_to_buffer(request.msg, sizeof(request.msg)); 87 VERIFY(fits); 88 request.header.checksum = internet_checksum(&request, sizeof(request)); 89 90 m_timer.start(); 91 TRY(Core::System::sendto(fd, &request, sizeof(request), 0, (sockaddr*)&host_address, sizeof(host_address))); 92 93 icmp_response response {}; 94 sockaddr_in peer_address {}; 95 socklen_t peer_address_size = sizeof(peer_address); 96 97 auto result = Core::System::recvfrom(fd, &response, sizeof(response), 0, (sockaddr*)&peer_address, &peer_address_size); 98 if (result.is_error()) { 99 if (result.error().code() == EAGAIN) 100 return result.release_error(); 101 continue; 102 } 103 104 if (response.header.type != ICMP_ECHOREPLY && response.header.type != ICMP_TIME_EXCEEDED) 105 continue; 106 107 auto response_time = m_timer.elapsed(); 108 auto* peer = gethostbyaddr(&peer_address.sin_addr, sizeof(peer_address.sin_addr), AF_INET); 109 110 DeprecatedString peer_name; 111 if (peer) { 112 peer_name = peer->h_name; 113 } else { 114 char addr_buf[INET_ADDRSTRLEN]; 115 peer_name = inet_ntop(AF_INET, &peer_address.sin_addr, addr_buf, sizeof(addr_buf)); 116 } 117 outln("{:2}: {:50} {:4}ms", ttl, peer_name, response_time); 118 119 if (response.header.type == ICMP_TIME_EXCEEDED) 120 return 0; 121 if (response.header.type == ICMP_ECHOREPLY) 122 return 1; 123 } 124 return -1; 125 }; 126 127 for (auto ttl = 1; ttl <= max_hops; ttl++) { 128 TRY(Core::System::setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl))); 129 130 int result = TRY(try_reach_host(ttl)); 131 if (result < 0) { 132 outln("{:2}: no reply", ttl); 133 } else if (result == 1) { 134 outln(" Hops: {}", ttl); 135 return 0; 136 } 137 } 138 outln(" Too many hops: {}", max_hops); 139 return 0; 140}