1218887Sdim// Copyright 2017 The Fuchsia Authors. All rights reserved.
2218887Sdim// Use of this source code is governed by a BSD-style license that can be
3353358Sdim// found in the LICENSE file.
4353358Sdim
5353358Sdim#include <arpa/inet.h>
6218887Sdim#include <errno.h>
7218887Sdim#include <fcntl.h>
8218887Sdim#include <limits.h>
9218887Sdim#include <netdb.h>
10218887Sdim#include <netinet/in.h>
11218887Sdim#include <netinet/ip_icmp.h>
12276479Sdim#include <poll.h>
13249423Sdim#include <stdlib.h>
14249423Sdim#include <stdio.h>
15249423Sdim#include <string.h>
16218887Sdim#include <unistd.h>
17218887Sdim
18239462Sdim#include <zircon/syscalls.h>
19218887Sdim#include <zircon/compiler.h>
20218887Sdim
21218887Sdimtypedef struct {
22218887Sdim    icmphdr hdr;
23218887Sdim    uint8_t payload[32];
24218887Sdim} __PACKED packet_t;
25218887Sdim
26218887Sdimint usage() {
27218887Sdim    fprintf(stderr, "usage: ping [ <option>* ] destination\n");
28218887Sdim    fprintf(stderr, "Send ICMP ECHO_REQUEST to a destination. This destination\n");
29341825Sdim    fprintf(stderr, "may be a hostname (google.com) or an IP address (8.8.8.8).\n");
30218887Sdim    fprintf(stderr, "-c count: Only receive count packets (default = 3)\n");
31341825Sdim    fprintf(stderr, "--help: View this help message\n");
32218887Sdim    return -1;
33218887Sdim}
34218887Sdim
35218887Sdimint main(int argc, const char** argv) {
36226633Sdim    size_t count = 3;
37218887Sdim    argv++;
38327952Sdim    argc--;
39327952Sdim    while (argc > 1) {
40239462Sdim        if (!strncmp(argv[0], "-c", strlen("-c"))) {
41327952Sdim            argv++;
42353358Sdim            argc--;
43327952Sdim            if (argc < 1) {
44327952Sdim                return usage();
45327952Sdim            }
46353358Sdim            char* endptr;
47327952Sdim            count = strtol(argv[0], &endptr, 10);
48327952Sdim            if (*endptr != '\0') {
49327952Sdim                return usage();
50327952Sdim            }
51327952Sdim            argv++;
52327952Sdim            argc--;
53327952Sdim        } else {
54327952Sdim            return usage();
55327952Sdim        }
56327952Sdim    }
57327952Sdim
58327952Sdim    if (argc == 0 || !strcmp(argv[0], "--help")) {
59327952Sdim        return usage();
60341825Sdim    }
61218887Sdim
62218887Sdim    const char* host = argv[0];
63327952Sdim
64327952Sdim    int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
65218887Sdim    if (s < 0) {
66218887Sdim        fprintf(stderr, "Could not acquire ICMP socket: %d\n", errno);
67327952Sdim        return -1;
68218887Sdim    }
69218887Sdim
70218887Sdim    struct addrinfo hints;
71218887Sdim    memset(&hints, 0, sizeof(hints));
72341825Sdim    hints.ai_family = AF_UNSPEC;
73218887Sdim    hints.ai_socktype = SOCK_DGRAM;
74218887Sdim    hints.ai_flags = 0;
75327952Sdim    hints.ai_protocol = IPPROTO_ICMP;
76218887Sdim    struct addrinfo* info;
77218887Sdim    if (getaddrinfo(host, NULL, &hints, &info)) {
78218887Sdim        fprintf(stderr, "ping: unknown host %s\n", host);
79341825Sdim        return -1;
80218887Sdim    }
81218887Sdim
82218887Sdim    struct sockaddr* saddr = info->ai_addr;
83218887Sdim    char buf[256];
84218887Sdim    if (saddr->sa_family == AF_INET) {
85218887Sdim        struct sockaddr_in* iaddr = reinterpret_cast<struct sockaddr_in*>(saddr);
86327952Sdim        inet_ntop(saddr->sa_family, &iaddr->sin_addr, buf, sizeof(buf));
87218887Sdim    } else {
88218887Sdim        struct sockaddr_in6* iaddr = reinterpret_cast<struct sockaddr_in6*>(saddr);
89218887Sdim        inet_ntop(saddr->sa_family, &iaddr->sin6_addr, buf, sizeof(buf));
90218887Sdim    }
91341825Sdim
92218887Sdim    printf("PING %s (%s)\n", host, buf);
93218887Sdim
94218887Sdim    uint16_t sequence = 1;
95327952Sdim    packet_t packet;
96341825Sdim    ssize_t r;
97218887Sdim    const zx_ticks_t ticks_per_usec = zx_ticks_per_second() / 1000000;
98218887Sdim
99341825Sdim    while (count-- > 0) {
100341825Sdim        memset(&packet, 0, sizeof(packet));
101218887Sdim        packet.hdr.type = ICMP_ECHO;
102327952Sdim        packet.hdr.code = 0;
103327952Sdim        packet.hdr.un.echo.id = 0;
104218887Sdim        packet.hdr.un.echo.sequence = htons(sequence++);
105327952Sdim        constexpr char kMessage[] = "This is an echo message!";
106327952Sdim        strcpy(reinterpret_cast<char *>(packet.payload), kMessage);
107341825Sdim        // Netstack will overwrite the checksum
108218887Sdim        zx_ticks_t before = zx_ticks_get();
109218887Sdim        r = sendto(s, &packet, sizeof(packet.hdr) + strlen(kMessage) + 1, 0,
110218887Sdim                   saddr, sizeof(*saddr));
111341825Sdim        if (r < 0) {
112234353Sdim            fprintf(stderr, "ping: Could not send packet\n");
113234353Sdim            return -1;
114234353Sdim        }
115234353Sdim
116234353Sdim        struct pollfd fd;
117234353Sdim        fd.fd = s;
118234353Sdim        fd.events = POLLIN;
119234353Sdim        switch (poll(&fd, 1, 1000)) {
120234353Sdim        case 1:
121234353Sdim            if (fd.revents & POLLIN) {
122234353Sdim                r = recvfrom(s, &packet, sizeof(packet), 0, NULL, NULL);
123234353Sdim                break;
124234353Sdim            } else {
125234353Sdim                fprintf(stderr, "ping: Spurious wakeup from poll\n");
126234353Sdim                r = -1;
127218887Sdim                break;
128218887Sdim            }
129218887Sdim        case 0:
130218887Sdim            fprintf(stderr, "ping: Timed out after one second\n");
131341825Sdim            __FALLTHROUGH;
132218887Sdim        default:
133341825Sdim            r = -1;
134239462Sdim        }
135239462Sdim
136239462Sdim        if (r < 0) {
137218887Sdim            fprintf(stderr, "ping: Could not read result of ping\n");
138239462Sdim            return -1;
139218887Sdim        }
140218887Sdim        zx_ticks_t after = zx_ticks_get();
141341825Sdim        int seq = ntohs(packet.hdr.un.echo.sequence);
142218887Sdim        uint64_t usec = (after - before) / ticks_per_usec;
143218887Sdim        printf("%" PRIu64 " bytes: icmp_seq=%d time=%" PRIu64 " us\n", r, seq, usec);
144239462Sdim        if (count > 0) {
145218887Sdim            sleep(1);
146218887Sdim        }
147218887Sdim    }
148218887Sdim    freeaddrinfo(info);
149218887Sdim    close(s);
150341825Sdim    return 0;
151218887Sdim}
152218887Sdim