├── test ├── Makefile ├── basic.sh └── lib.sh ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── ChangeLog.md ├── mping.1 └── mping.c /test/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | true 3 | 4 | check: all 5 | unshare -mrun --map-auto ./basic.sh 6 | 7 | clean: 8 | true 9 | 10 | distclean: clean 11 | $(RM) *~ 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | RUN apk --no-cache add musl-dev git gcc make 3 | 4 | COPY . /tmp/mping 5 | RUN git clone --depth=1 file:///tmp/mping /root/mping 6 | WORKDIR /root/mping 7 | 8 | RUN make -j5 && make install-strip 9 | 10 | FROM alpine:latest 11 | COPY --from=0 /usr/local/bin/mping /usr/bin/mping 12 | 13 | CMD [ "/usr/bin/mping" ] 14 | -------------------------------------------------------------------------------- /test/basic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck source=/dev/null 4 | . "$(dirname "$0")/lib.sh" 5 | 6 | print "Creating world ..." 7 | ip link set lo up 8 | ip link set lo multicast on 9 | echo "Links:" 10 | ip -br l | awk '$0=" "$0' 11 | echo "Addresses:" 12 | ip -br a | awk '$0=" "$0' 13 | 14 | print "Phase 1: Verify too few pings ..." 15 | ../mping -r -c 2 -i lo & 16 | PID=$! 17 | sleep 1 18 | 19 | ../mping -s -c 3 -i lo -W 2 20 | rc=$? 21 | 22 | kill -9 $PID 2>/dev/null 23 | [ $rc -eq 0 ] && FAIL 24 | echo 25 | 26 | print "Phase 2: Verify successful ping ..." 27 | ../mping -qr -c 3 -i lo & 28 | PID=$! 29 | sleep 1 30 | 31 | ../mping -qs -c 3 -i lo -W 3 32 | rc=$? 33 | 34 | kill -9 $PID 2>/dev/null 35 | [ $rc -ne 0 ] && FAIL 36 | OK 37 | -------------------------------------------------------------------------------- /test/lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Helper functions for testing SMCRoute 3 | 4 | # Test name, used everywhere as /tmp/$NM/foo 5 | NM=$(basename "$0" .sh) 6 | 7 | # Print heading for test phases 8 | print() 9 | { 10 | printf "\e[7m>> %-80s\e[0m\n" "$1" 11 | } 12 | 13 | SKIP() 14 | { 15 | print "TEST: SKIP" 16 | [ $# -gt 0 ] && echo "$*" 17 | exit 77 18 | } 19 | 20 | FAIL() 21 | { 22 | print "TEST: FAIL" 23 | [ $# -gt 0 ] && echo "$*" 24 | exit 99 25 | } 26 | 27 | OK() 28 | { 29 | print "TEST: OK" 30 | [ $# -gt 0 ] && echo "$*" 31 | exit 0 32 | } 33 | 34 | check_dep() 35 | { 36 | if [ -n "$2" ]; then 37 | if ! $@; then 38 | SKIP "$* is not supported on this system." 39 | fi 40 | elif ! command -v "$1"; then 41 | SKIP "Cannot find $1, skipping test." 42 | fi 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Joachim Wiberg 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME = mping 2 | VERSION := $(shell awk -F '"' '/define VERSION/ { print $$2; }' mping.c) 3 | PKG = $(NAME)-$(VERSION) 4 | ARCHIVE = $(PKG).tar.gz 5 | 6 | prefix ?= /usr/local 7 | bindir = $(prefix)/bin 8 | docdir = $(prefix)/share/doc/$(NAME) 9 | mandir = $(prefix)/share/man/man1 10 | MAN1 = mping.1 11 | DOCFILES = README.md LICENSE 12 | 13 | CPPFLAGS ?= -W -Wall -Wextra 14 | CFLAGS ?= -g -O2 -std=gnu99 15 | 16 | all: $(NAME) 17 | 18 | check: all 19 | $(MAKE) -C test $@ 20 | 21 | clean: 22 | -$(RM) $(NAME) *.o 23 | 24 | install: $(LIBNAME) 25 | install -d $(DESTDIR)$(bindir) 26 | install -d $(DESTDIR)$(docdir) 27 | install -d $(DESTDIR)$(mandir) 28 | install -m 0755 $(NAME) $(DESTDIR)$(bindir)/$(NAME) 29 | install -m 0655 $(MAN1) $(DESTDIR)$(mandir)/$(MAN1) 30 | gzip -f $(DESTDIR)$(mandir)/$(MAN1) 31 | for file in $(DOCFILES); do \ 32 | install -m 0644 $$file $(DESTDIR)$(docdir)/$$file; \ 33 | done 34 | 35 | install-strip: install 36 | strip $(DESTDIR)$(bindir)/$(NAME) 37 | 38 | uninstall: 39 | -$(RM) $(DESTDIR)$(bindir)/$(NAME) 40 | -$(RM) $(DESTDIR)$(mandir)/$(MAN1) 41 | -$(RM) -r $(DESTDIR)$(docdir) 42 | 43 | dist: 44 | git archive --format=tar.gz --prefix=$(PKG)/ -o ../$(ARCHIVE) v$(VERSION) 45 | 46 | distclean: clean 47 | -$(RM) *~ 48 | 49 | release: dist 50 | (cd ..; \ 51 | md5sum $(ARCHIVE) > $(ARCHIVE).md5; \ 52 | sha256sum $(ARCHIVE) > $(ARCHIVE).sha256) 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | a simple multicast ping program 2 | =============================== 3 | 4 | mping aspires to be an easy to use and script friendly program with 5 | support for both IPv4 and IPv6. Similar to the standard ping program, 6 | but unlike it, the response to multicast ping is sent by another mping. 7 | 8 | ``` 9 | mping --> Hello! 10 | .----. ._ Hi! <-- mping 11 | | | .-(` ) .----. 12 | | |--------------:( )) | | 13 | '----' `( ) ))------------| | 14 | Host A ` __.:' '----' 15 | Host B 16 | ^ 17 | | 18 | Multicast friendly network ---' 19 | ``` 20 | 21 | > A multicast "friendly" network is one that supports multicast routing, 22 | > and/or supports IGMP/MLD snooping. Remember: the reverse path is in 23 | > the unicast routing table (e.g., default route), the MULTICAST flag 24 | > on your interfaces, and ... the TTL in routed networks! 25 | 26 | By default, mping starts in receiver/reflector mode, joining the group 27 | given as command line argument. To start as a sender, use `-s` and 28 | remember to set the `-t TTL` value greater than the number of routing 29 | "hops" when testing in a routed topology. 30 | 31 | 32 | Usage 33 | ----- 34 | 35 | ``` 36 | Usage: 37 | mping [-6dhqrsv] [-c COUNT] [-i IFNAME] [-p PORT] [-t TTL] [-w SEC] [-W SEC] [GROUP] 38 | 39 | Options: 40 | -6 Use IPv6 instead of IPv4, see below for defaults 41 | -c COUNT Stop after sending/receiving COUNT packets 42 | -d Debug messages 43 | -h This help text 44 | -i IFNAME Interface to use for sending/receiving 45 | -p PORT Multicast port to listen/send to, default 4321 46 | -q Quiet output, only startup and and summary lines 47 | -r Receiver/reflector mode, default 48 | -s Sender mode 49 | -t TTL Multicast time to live to send, IPv6 hops, default 1 50 | -v Show program version and contact information 51 | -w DEADLINE Timeout before exiting, waiting for COUNT replies 52 | -W TIMEOUT Time to wait for a response, in seconds, default 5 53 | 54 | Defaults to use multicast group 225.1.2.3, UDP dst port 4321, unless -6 in which 55 | case a multicast group ff2e::42 is used. When a group argument is given, the 56 | address family is chosen from that. The selected outbound interface is chosen 57 | by querying the routing table, unless -i IFNAME 58 | ``` 59 | 60 | > **Note:** the `mping` receiver/reflector also needs to set the TTL 61 | > value, this is crucial in a routed setup or the reply is dropped. 62 | 63 | 64 | Origin 65 | ------ 66 | 67 | Initially based on an example program from the book "Multicast Sockets", 68 | but since then completely rewritten and extended. Please send any bug 69 | reports, feature requests, patches/pull requests, and documentation 70 | fixes to [GitHub](https://github.com/troglobit/mping). 71 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | ChangeLog 2 | ========= 3 | 4 | All notable changes to the project are documented in this file. 5 | 6 | 7 | [v2.0][] - 2022-05-12 8 | --------------------- 9 | 10 | ### Changes 11 | - Add IPv6 support, still ASM 12 | - Detect address family from command line group argument, or `-6` 13 | 14 | 15 | [v1.6][] - 2021-10-31 16 | --------------------- 17 | 18 | ### Changes 19 | - Add Dockerfile with build + deploy to ghcr.io 20 | - Use `clock_gettime()` to reliably measure time instead of unreliable 21 | `gettimeofday()`, which only gives the (auto-)adjustable wall clock 22 | - Improved error messages from `init_socket()`, e.g. inform the user if 23 | they are trying to use an interface without MULTICAST flag set 24 | - Replace `etime=` and `atime=` with ping-like `time=`. This change is 25 | actually a follow-up to [v1.3][] where the random delay in the receiver 26 | was dropped 27 | - The mping `VERSION` define is now extracted from the sources rather 28 | than defined in the Makefile. This facilitates easier import in other 29 | project's build systems and ensures the version is not lost 30 | 31 | 32 | [v1.5][] - 2021-09-05 33 | --------------------- 34 | 35 | ### Changes 36 | - Update README, tips and usage section 37 | - Add `-w DEADLINE`, like regular ping(1) tool, this adds the ping(1) 38 | behavior to the `-c COUNT` option, instead of exit after COUNT sent 39 | mpings, we now wait DEADLINE seconds to *receive* COUNT mpings 40 | 41 | ### Fixes 42 | - Don't exit with error if receiving more than COUNT packets 43 | 44 | 45 | [v1.4][] - 2021-08-31 46 | --------------------- 47 | 48 | ### Changes 49 | - Add man page to section 1 50 | - Add missing license heading to source file 51 | 52 | ### Fixes 53 | - Fix missing newline and options in usage text 54 | 55 | 56 | [v1.3][] - 2021-08-30 57 | --------------------- 58 | 59 | ### Changes 60 | - Reindent to Linux KNF 61 | - Simplfy command line args 62 | - Add `-c COUNT` to stop after COUNT packet instead of hardcoded 5 63 | - Add `-W SEC` for sender timeout 64 | - Add `-q` for quiet output 65 | - Change `-v` to `-d` for debug output 66 | - Change `-V` to `-v` to show version information 67 | - Drop random delay in receiver mode, not useful in production 68 | - Add basic test case 69 | 70 | ### Fixes 71 | - Interface address and default interface lookup 72 | - Fix formatting of messages to align between sender and receiver 73 | - Exit with error code if we receive fewer packets than expected 74 | - Set IP header TTL value to allow for traversing routers 75 | 76 | 77 | v1.2 - 2014-06-20 78 | ----------------- 79 | 80 | Initial version, available from the toolbox repo. 81 | 82 | [UNRELEASED]: https://github.com/troglobit/mping/compare/v2.0...HEAD 83 | [v2.0]: https://github.com/troglobit/mping/compare/v1.6...v2.0 84 | [v1.6]: https://github.com/troglobit/mping/compare/v1.5...v1.6 85 | [v1.5]: https://github.com/troglobit/mping/compare/v1.4...v1.5 86 | [v1.4]: https://github.com/troglobit/mping/compare/v1.3...v1.4 87 | [v1.3]: https://github.com/troglobit/mping/compare/v1.2...v1.3 88 | -------------------------------------------------------------------------------- /mping.1: -------------------------------------------------------------------------------- 1 | .\" Hey Emacs, this is an -*- nroff -*- document 2 | .\" 3 | .\" Copyright (c) 2021-2022 Joachim Wiberg 4 | .\" 5 | .\" Permission is hereby granted, free of charge, to any person obtaining a copy 6 | .\" of this software and associated documentation files (the "Software"), to deal 7 | .\" in the Software without restriction, including without limitation the rights 8 | .\" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | .\" copies of the Software, and to permit persons to whom the Software is 10 | .\" furnished to do so, subject to the following conditions: 11 | .\" 12 | .\" The above copyright notice and this permission notice shall be included in 13 | .\" all copies or substantial portions of the Software. 14 | .\" 15 | .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | .\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | .\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | .\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | .\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | .\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | .\" THE SOFTWARE. 22 | .\" 23 | .Dd Sep 15, 2024 24 | .Dt MPING 1 25 | .Os 26 | .Sh NAME 27 | .Nm mping 28 | .Nd a simple multicast ping program 29 | .Sh SYNOPSIS 30 | .Nm 31 | .Op Fl 6dhqrsv 32 | .Op Fl b Ar BYTES 33 | .Op Fl c Ar COUNT 34 | .Op Fl i Ar IFNAME 35 | .Op Fl p Ar PORT 36 | .Op Fl t Ar TTL 37 | .Op Fl w Ar SEC 38 | .Op Fl W Ar SEC 39 | .Op Ar GROUP 40 | .Sh DESCRIPTION 41 | .Nm 42 | aspires to be an easy to use and script friendly program with support 43 | for both IPv4 and IPv6. Similar to the standard 44 | .Xr ping 1 45 | program, but unlike it, the response to multicast ping is sent by 46 | another 47 | .Nm . 48 | It can be used to verify intended IGMP/MLD snooping functionality in any 49 | layer-2 bridges (switches), as well as verify forwarding of multicast in 50 | static 51 | .Nm ( SMCRoute ) 52 | or dynamic 53 | .Nm ( mrouted , 54 | .Nm pimd , 55 | .Nm pimd-dense , 56 | or 57 | .Nm pim6sd ) 58 | multicast routing setups. 59 | .Pp 60 | By default, 61 | .Nm 62 | starts in receiver/reflector mode, joining group 225.1.2.3, and for each 63 | received UDP packet from the sender, it is looped back. For unicast 64 | ping, this is handled by the TCP/IP stack. To run in sender mode, use 65 | the 66 | .Fl s 67 | command line option. Remember to adjust the TTL value if you are in a 68 | routed setup, and to set the multicast interface with 69 | .Fl i Ar IFNAME , 70 | otherwise the unicast routing table is used by the kernel to select the 71 | outbound interface, which often is not what you want. 72 | .Ss Limitations 73 | .Nm 74 | currently only supports any-source multicast, ASM (*,G). 75 | .Pp 76 | .Nm 77 | does not create or send IGMP/MLD frames directly. Instead, it asks the 78 | kernel for groups from a specific interface, which is then converted to 79 | the IGMPv2/MLDv1 join messages, or IGMPv3/MLDv2 membership reports by 80 | the kernel. 81 | .Ss Changing IGMP Version on Linux 82 | On Linux systems you can change the IGMP version of an interface, and 83 | thus what type of packets the kernel generates, by writing to the file 84 | .Pa /proc/sys/net/ipv4/conf/eth0/force_igmp_version . 85 | E.g., to change 86 | .Cm eth0 87 | to IGMPv2: 88 | .Bd -literal -offset indent 89 | echo 2 | sudo tee /proc/sys/net/ipv4/conf/eth0/force_igmp_version 90 | .Ed 91 | .Sh OPTIONS 92 | .Bl -tag -width Ds 93 | .It Fl 6 94 | Use IPv6 instead of IPv4. Defaults to group ff2e::42, unless an IPv6 95 | multicast grop is the first non-option argument. The optional group 96 | argument overrides the address family, so if an IPv4 group is detected 97 | this option is ignored 98 | .It Fl b Ar BYTES 99 | Extra payload bytes (empty data) to pad each packet with, default: 0. 100 | .It Fl c Ar COUNT 101 | Stop sending/receiving after COUNT number of packets. The sender 102 | .Nm 103 | has two modes of operation available. The default is to exit after 104 | .Ar COUNT 105 | number of packets have been sent. The other mode is to wait for 106 | .Ar COUNT 107 | packets to be recived. See 108 | .Fl w 109 | option, below, for more information. 110 | .It Fl d 111 | Enable debug messages. 112 | .It Fl h 113 | Print a summary of the options and exit 114 | .It Fl i Ar IFNAME 115 | Interface to use for sending/receiving multicast. The default is to 116 | automatically look up the default interface from the unicast routing 117 | table. 118 | .It Fl p Ar PORT 119 | UDP port number to send/listen to, default: 4321 120 | .It Fl q 121 | Quiet output, only startup message and summary lines are printed 122 | .It Fl r 123 | Act as receiver/reflector, looping back packets to the sender, default: 124 | yes 125 | .It Fl s 126 | Act as sender, sends packets to select groups, default: no 127 | .It Fl t Ar TTL 128 | TTL to use when sending multicast packets, default: 1 129 | .It Fl v 130 | Show version information 131 | .It Fl w Ar DEADLINE 132 | Timeout, in seconds, before exiting, waiting for 133 | .Fl c Ar COUNT 134 | replies. If 135 | .Ar COUNT 136 | replies are received before the deadline, 137 | .Nm 138 | exits. 139 | .It Fl W Ar TIMEOUT 140 | Timeout, in seconds, after the last received packet. 141 | .El 142 | .Sh SEE ALSO 143 | .Xr ping 1 , 144 | .Xr mcjoin 1 , 145 | .Xr nemesis 1 146 | .Sh BUGS 147 | Use the project's GitHub page to file bug reports, feature requests or 148 | patches (preferably as GitHub pull requests), or questions at 149 | .Aq https://github.com/troglobit/mping 150 | .Sh AUTHORS 151 | Originally based on an example from the book Multicast Sockets, by David 152 | Makofske and Kevin Almeroth. Since then completely rewritten and 153 | expanded on by Joachim Wiberg at GitHub. 154 | -------------------------------------------------------------------------------- /mping.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2022 Joachim Wiberg 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef _GNU_SOURCE 23 | #define _GNU_SOURCE /* For TIMESPEC_TO_TIMEVAL() in GLIBC */ 24 | #endif 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #ifndef VERSION 45 | #define VERSION "2.0" 46 | #endif 47 | 48 | #define dbg(fmt,args...) do { if (debug) printf(fmt "\n", ##args); } while (0) 49 | #define sig(s,c) do { struct sigaction a = {.sa_handler=c};sigaction(s,&a,0); } while(0) 50 | 51 | #if __BIG_ENDIAN__ 52 | # define htonll(x) (x) 53 | # define ntohll(x) (x) 54 | #else 55 | # define htonll(x) (((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) 56 | # define ntohll(x) (((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) 57 | #endif 58 | 59 | #define MC_GROUP_DEFAULT "225.1.2.3" 60 | #define MC_GROUP_INET6 "ff2e::42" 61 | #define MC_PORT_DEFAULT 4321 62 | #define MC_TTL_DEFAULT 1 63 | 64 | #define MAX_BUF_LEN 2048 65 | #define MAX_HOSTNAME_LEN 256 66 | 67 | #define SENDER 's' 68 | #define RECEIVER 'r' 69 | 70 | #define INET_ADDRSTR_LEN 64 71 | typedef struct sockaddr_storage inet_addr_t; 72 | 73 | struct mping { 74 | char version[4]; 75 | 76 | unsigned char type; 77 | unsigned char ttl; 78 | 79 | inet_addr_t src_host; 80 | inet_addr_t dest_host; 81 | 82 | unsigned int seq_no; 83 | pid_t pid; 84 | 85 | struct timeval tv; 86 | 87 | char payload[0]; /* optional payload */ 88 | }; 89 | 90 | /*#define BANDWIDTH 10000.0 */ /* bw in bytes/sec for mping */ 91 | #define BANDWIDTH 100.0 /* bw in bytes/sec for mping */ 92 | 93 | #define MAX_PAYLOAD (MAX_BUF_LEN - sizeof(struct mping)) 94 | 95 | /* pointer to mping packet buffer */ 96 | struct mping *rcvd_pkt; 97 | 98 | int sd; /* socket descriptor */ 99 | pid_t pid; /* our process id */ 100 | 101 | inet_addr_t myaddr; 102 | inet_addr_t mcaddr; 103 | int ipproto; 104 | struct group_req gr; 105 | 106 | struct timeval start; /* start time for sender */ 107 | 108 | /* Cleared by signal handler */ 109 | volatile sig_atomic_t running = 1; 110 | 111 | #ifdef AF_INET6 112 | #define OPTSTR "6" 113 | #else 114 | #define OPTSTR "" 115 | #endif 116 | 117 | /* counters and statistics variables */ 118 | int packets_sent = 0; 119 | int packets_rcvd = 0; 120 | 121 | double rtt_total = 0; 122 | double rtt_max = 0; 123 | double rtt_min = 999999999.0; 124 | 125 | /* default command-line arguments */ 126 | char arg_mcaddr[INET_ADDRSTR_LEN] = MC_GROUP_DEFAULT; 127 | int arg_mcport = MC_PORT_DEFAULT; 128 | int arg_count = -1; 129 | int arg_payload = 0; 130 | int arg_timeout = 5; 131 | int arg_deadline = 0; 132 | unsigned char arg_ttl = MC_TTL_DEFAULT; 133 | 134 | int debug = 0; 135 | int quiet = 0; 136 | 137 | static void init_socket(int family, int ifindex) 138 | { 139 | int off = 0; 140 | int on = 1; 141 | 142 | /* create a UDP socket */ 143 | if ((sd = socket(family, SOCK_DGRAM, IPPROTO_UDP)) < 0) 144 | err(1, "failed creating UDP socket"); 145 | 146 | /* set reuse port to on to allow multiple binds per host */ 147 | if ((setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) 148 | err(1, "Failed enabling SO_REUSEADDR"); 149 | 150 | #ifdef AF_INET6 151 | if (family == AF_INET6) { 152 | int hops = arg_ttl; 153 | 154 | ipproto = IPPROTO_IPV6; 155 | 156 | if (setsockopt(sd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof(hops))) 157 | err(1, "Failed setting IPV6_MULTICAST_HOPS: %s", strerror(errno)); 158 | 159 | if (setsockopt(sd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex))) 160 | err(1, "Failed setting IPV6_MULTICAST_IF: %s", strerror(errno)); 161 | } else 162 | #endif 163 | { 164 | struct ip_mreqn imr = { .imr_ifindex = ifindex }; 165 | 166 | ipproto = IPPROTO_IP; 167 | 168 | if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, &arg_ttl, sizeof(arg_ttl))) 169 | err(1, "Failed setting IP_MULTICAST_TTL"); 170 | 171 | if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &off, sizeof(off)) < 0) 172 | err(1, "Failed disabling IP_MULTICAST_LOOP"); 173 | 174 | if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, &imr, sizeof(imr))) 175 | err(1, "Failed setting IP_MULTICAST_IF %d", ifindex); 176 | } 177 | 178 | /* bind to multicast address to socket */ 179 | if ((bind(sd, (struct sockaddr *)&mcaddr, sizeof(mcaddr))) < 0) 180 | err(1, "bind() failed"); 181 | 182 | /* construct a IGMP/MLD join request structure */ 183 | memset(&gr, 0, sizeof(gr)); 184 | gr.gr_group = mcaddr; 185 | gr.gr_interface = ifindex; 186 | if ((setsockopt(sd, ipproto, MCAST_JOIN_GROUP, &gr, sizeof(gr))) < 0) 187 | err(1, "failed joining group %s on ifindex %d", arg_mcaddr, ifindex); 188 | } 189 | 190 | static size_t strlencpy(char *dst, const char *src, size_t len) 191 | { 192 | const char *p = src; 193 | size_t num = len; 194 | 195 | while (num > 0) { 196 | num--; 197 | if ((*dst++ = *src++) == 0) 198 | break; 199 | } 200 | 201 | if (num == 0 && len > 0) 202 | *dst = 0; 203 | 204 | return src - p - 1; 205 | } 206 | 207 | const char *inet_address(inet_addr_t *ss, char *buf, size_t len) 208 | { 209 | static char fallback[INET_ADDRSTR_LEN]; 210 | struct sockaddr_in *sin; 211 | 212 | if (!buf) { 213 | buf = fallback; 214 | len = sizeof(fallback); 215 | } 216 | 217 | #ifdef AF_INET6 218 | if (ss->ss_family == AF_INET6) { 219 | struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; 220 | return inet_ntop(AF_INET6, &sin6->sin6_addr, buf, len); 221 | } 222 | #endif 223 | 224 | sin = (struct sockaddr_in *)ss; 225 | return inet_ntop(AF_INET, &sin->sin_addr, buf, len); 226 | } 227 | 228 | static int address_inet(const char *address, inet_addr_t *ina) 229 | { 230 | struct sockaddr_in *sin = (struct sockaddr_in *)ina; 231 | void *ptr = &sin->sin_addr; 232 | 233 | #ifdef AF_INET6 234 | if (strchr(address, ':')) { 235 | struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ina; 236 | void *ptr6 = &sin6->sin6_addr; 237 | 238 | if (inet_pton(AF_INET6, arg_mcaddr, ptr6)) { 239 | mcaddr.ss_family = AF_INET6; 240 | sin6->sin6_port = htons(arg_mcport); 241 | return 0; 242 | } 243 | 244 | return 1; /* 225.1.2.3:4321 unsupported atm. */ 245 | } 246 | #endif 247 | 248 | if (inet_pton(AF_INET, arg_mcaddr, ptr)) { 249 | mcaddr.ss_family = AF_INET; 250 | sin->sin_port = htons(arg_mcport); 251 | return 0; 252 | } 253 | 254 | return -1; 255 | } 256 | 257 | /* 258 | * Find first interface that is not loopback, but is up, has link, and 259 | * multicast capable. 260 | */ 261 | static char *ifany(char *iface, size_t len) 262 | { 263 | struct ifaddrs *ifaddr, *ifa; 264 | 265 | if (getifaddrs(&ifaddr) == -1) 266 | return NULL; 267 | 268 | for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { 269 | int ifindex; 270 | 271 | if (!(ifa->ifa_flags & IFF_UP)) 272 | continue; 273 | 274 | if (!(ifa->ifa_flags & IFF_RUNNING)) 275 | continue; 276 | 277 | if (!(ifa->ifa_flags & IFF_MULTICAST)) 278 | continue; 279 | 280 | ifindex = if_nametoindex(ifa->ifa_name); 281 | dbg("Found iface %s, ifindex %d", ifa->ifa_name, ifindex); 282 | strlencpy(iface, ifa->ifa_name, len); 283 | iface[len] = 0; 284 | break; 285 | } 286 | freeifaddrs(ifaddr); 287 | 288 | return iface; 289 | } 290 | 291 | /* The BSD's or SVR4 systems like Solaris don't have /proc/net/route */ 292 | static char *altdefault(char *iface, size_t len) 293 | { 294 | char buf[256]; 295 | FILE *fp; 296 | 297 | fp = popen("netstat -rn", "r"); 298 | if (!fp) 299 | return NULL; 300 | 301 | while (fgets(buf, sizeof(buf), fp)) { 302 | char *token; 303 | 304 | if (strncmp(buf, "default", 7) && strncmp(buf, "0.0.0.0", 7)) 305 | continue; 306 | 307 | token = strtok(buf, " \t\n"); 308 | while (token) { 309 | if (if_nametoindex(token)) { 310 | strlencpy(iface, token, len); 311 | pclose(fp); 312 | 313 | return iface; 314 | } 315 | 316 | token = strtok(NULL, " \t\n"); 317 | } 318 | } 319 | 320 | pclose(fp); 321 | return NULL; 322 | } 323 | 324 | /* Find default outbound *LAN* interface, i.e. skipping tunnels */ 325 | char *ifdefault(char *iface, size_t len) 326 | { 327 | uint32_t dest, gw, mask; 328 | char buf[256], ifname[17]; 329 | char *ptr; 330 | FILE *fp; 331 | int rc, flags, cnt, use, metric, mtu, win, irtt; 332 | int best = 100000, found = 0; 333 | 334 | fp = fopen("/proc/net/route", "r"); 335 | if (!fp) { 336 | ptr = altdefault(iface, len); 337 | if (!ptr) 338 | goto fallback; 339 | 340 | return ptr; 341 | } 342 | 343 | /* Skip heading */ 344 | ptr = fgets(buf, sizeof(buf), fp); 345 | if (!ptr) 346 | goto end; 347 | 348 | while (fgets(buf, sizeof(buf), fp) != NULL) { 349 | rc = sscanf(buf, "%16s %X %X %X %d %d %d %X %d %d %d\n", 350 | ifname, &dest, &gw, &flags, &cnt, &use, &metric, 351 | &mask, &mtu, &win, &irtt); 352 | 353 | if (rc < 10 || !(flags & 1)) /* IFF_UP */ 354 | continue; 355 | 356 | if (dest != 0 || mask != 0) 357 | continue; 358 | 359 | if (!iface[0] || !strncmp(iface, "tun", 3)) { 360 | if (metric >= best) 361 | continue; 362 | 363 | strlencpy(iface, ifname, len); 364 | iface[len] = 0; 365 | best = metric; 366 | found = 1; 367 | 368 | dbg("Found default inteface %s", iface); 369 | } 370 | } 371 | 372 | end: 373 | fclose(fp); 374 | if (found) 375 | return iface; 376 | fallback: 377 | return ifany(iface, len); 378 | } 379 | 380 | static void hton_packet(struct mping *packet) 381 | { 382 | packet->seq_no = htonl(packet->seq_no); 383 | 384 | if (sizeof(packet->tv.tv_sec) == 8) 385 | packet->tv.tv_sec = htonll(packet->tv.tv_sec); 386 | else 387 | packet->tv.tv_sec = htonl(packet->tv.tv_sec); 388 | 389 | if (sizeof(packet->tv.tv_usec) == 8) 390 | packet->tv.tv_usec = htonll(packet->tv.tv_usec); 391 | else 392 | packet->tv.tv_usec = htonl(packet->tv.tv_usec); 393 | } 394 | 395 | static void ntoh_packet(struct mping *packet) 396 | { 397 | packet->seq_no = ntohl(packet->seq_no); 398 | 399 | if (sizeof(packet->tv.tv_sec) == 8) 400 | packet->tv.tv_sec = ntohll(packet->tv.tv_sec); 401 | else 402 | packet->tv.tv_sec = ntohl(packet->tv.tv_sec); 403 | 404 | if (sizeof(packet->tv.tv_usec) == 8) 405 | packet->tv.tv_usec = ntohll(packet->tv.tv_usec); 406 | else 407 | packet->tv.tv_usec = ntohl(packet->tv.tv_usec); 408 | } 409 | 410 | /* Find IP address of default outbound LAN interface */ 411 | int ifinfo(char *iface, inet_addr_t *addr, int family) 412 | { 413 | char buf[INET_ADDRSTR_LEN] = { 0 }; 414 | struct ifaddrs *ifaddr, *ifa; 415 | char ifname[16] = { 0 }; 416 | int found = 0; 417 | int rc = -1; 418 | 419 | if (!iface || !iface[0]) 420 | iface = ifdefault(ifname, sizeof(ifname)); 421 | if (!iface || !iface[0]) 422 | errx(1, "no suitable default interface available"); 423 | 424 | rc = getifaddrs(&ifaddr); 425 | if (rc == -1) 426 | err(1, "failed querying available interfaces"); 427 | 428 | rc = -1; /* Return -1 if iface with family is not found */ 429 | for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { 430 | if (strcmp(iface, ifa->ifa_name)) 431 | continue; 432 | 433 | found = 1; 434 | if (!(ifa->ifa_flags & IFF_UP)) { 435 | dbg("%s is not UP, skipping ...", iface); 436 | continue; 437 | } 438 | 439 | if (!ifa->ifa_addr) { 440 | dbg("%s has no address, skipping ...", iface); 441 | continue; 442 | } 443 | 444 | if (family == AF_UNSPEC) { 445 | if (ifa->ifa_addr->sa_family != AF_INET && 446 | ifa->ifa_addr->sa_family != AF_INET6) { 447 | dbg("%s no IPv4 or IPv6 address, skipping ...", iface); 448 | continue; 449 | } 450 | } else if (ifa->ifa_addr->sa_family != family) { 451 | dbg("%s not matching address family, skipping ...", iface); 452 | continue; 453 | } 454 | 455 | if (!(ifa->ifa_flags & IFF_MULTICAST)) { 456 | warnx("%s is not multicast capable, check interface flags.", iface); 457 | break; 458 | } 459 | 460 | dbg("Found %s addr %s", ifa->ifa_name, inet_address((inet_addr_t *)ifa->ifa_addr, buf, sizeof(buf))); 461 | *addr = *(inet_addr_t *)ifa->ifa_addr; 462 | rc = if_nametoindex(ifa->ifa_name); 463 | dbg("iface %s, ifindex %d, addr %s", ifa->ifa_name, rc, buf); 464 | break; 465 | } 466 | freeifaddrs(ifaddr); 467 | 468 | if (!found) 469 | warnx("no such interface %s.", iface); 470 | if (rc == -1) 471 | warnx("no %s address on on interface %s.", family == AF_INET ? "ipv4" : "ipv6", iface); 472 | 473 | return rc; 474 | } 475 | 476 | /* subtract sub from val and leave result in val */ 477 | void subtract_timeval(struct timeval *val, const struct timeval *sub) 478 | { 479 | val->tv_sec -= sub->tv_sec; 480 | val->tv_usec -= sub->tv_usec; 481 | if (val->tv_usec < 0) { 482 | val->tv_sec--; 483 | val->tv_usec += 1000000; 484 | } 485 | } 486 | 487 | /* return the timeval converted to a number of milliseconds */ 488 | double timeval_to_ms(const struct timeval *val) 489 | { 490 | return val->tv_sec * 1000.0 + val->tv_usec / 1000.0; 491 | } 492 | 493 | static int cleanup(void) 494 | { 495 | if ((setsockopt(sd, ipproto, MCAST_LEAVE_GROUP, &gr, sizeof(gr))) < 0) 496 | err(1, "setsockopt() failed"); 497 | 498 | close(sd); 499 | 500 | printf("\n--- %s mping statistics ---\n", arg_mcaddr); 501 | printf("%d packets transmitted, %d packets received\n", packets_sent, packets_rcvd); 502 | if (packets_rcvd == 0) 503 | printf("round-trip min/avg/max = NA/NA/NA ms\n"); 504 | else 505 | printf("round-trip min/avg/max = %.3f/%.3f/%.3f ms\n", 506 | rtt_min, (rtt_total / packets_rcvd), rtt_max); 507 | 508 | if (arg_count > 0 && arg_count > packets_rcvd) 509 | return 1; 510 | 511 | return 0; 512 | } 513 | 514 | static void clean_exit(int signo) 515 | { 516 | (void)signo; 517 | running = 0; 518 | } 519 | 520 | void send_packet(struct mping *packet, ssize_t len) 521 | { 522 | if ((sendto(sd, packet, len, 0, (struct sockaddr *)&mcaddr, sizeof(mcaddr))) != len) 523 | err(1, "sendto() sent incorrect number of bytes"); 524 | 525 | packets_sent++; 526 | } 527 | 528 | void send_mping(int signo) 529 | { 530 | static char buf[MAX_BUF_LEN + 1]; 531 | struct mping *packet = (struct mping *)buf; 532 | static int seqno = 0; 533 | struct timespec now; 534 | 535 | (void)signo; 536 | clock_gettime(CLOCK_MONOTONIC, &now); 537 | 538 | /* 539 | * Tracks number of sent mpings. If deadline mode is enabled we 540 | * ignore this exit and wait for arg_count number of replies or 541 | * deadline timeout. 542 | */ 543 | if (arg_deadline) { 544 | struct timeval tv; 545 | 546 | TIMESPEC_TO_TIMEVAL(&tv, &now); 547 | subtract_timeval(&tv, &start); 548 | 549 | if ((arg_count > 0 && packets_rcvd >= arg_count) || 550 | (tv.tv_sec >= arg_deadline)) { 551 | running = 0; 552 | return; 553 | } 554 | } else if (arg_count > 0 && seqno >= arg_count) { 555 | sig(SIGALRM, clean_exit); 556 | alarm(arg_timeout); 557 | return; 558 | } 559 | 560 | memset(buf, 0, sizeof(buf)); 561 | TIMESPEC_TO_TIMEVAL(&packet->tv, &now); 562 | strlencpy(packet->version, VERSION, sizeof(packet->version)); 563 | packet->type = SENDER; 564 | packet->ttl = arg_ttl; 565 | packet->src_host = myaddr; 566 | packet->dest_host = mcaddr; 567 | packet->seq_no = seqno; 568 | packet->pid = pid; 569 | 570 | ntoh_packet(packet); 571 | 572 | send_packet(packet, sizeof(struct mping) + arg_payload); 573 | seqno++; 574 | 575 | /* set another alarm call to send in 1 second */ 576 | sig(SIGALRM, send_mping); 577 | alarm(1); 578 | } 579 | 580 | int process_mping(char *packet, int len, unsigned char type) 581 | { 582 | if (len < (int)sizeof(struct mping)) { 583 | dbg("Discarding packet: too small (%zu bytes)", strlen(packet)); 584 | return -1; 585 | } 586 | 587 | rcvd_pkt = (struct mping *)packet; 588 | 589 | ntoh_packet(rcvd_pkt); 590 | 591 | if (strcmp(rcvd_pkt->version, VERSION)) { 592 | dbg("Discarding packet: version mismatch (%s)", rcvd_pkt->version); 593 | return -1; 594 | } 595 | 596 | if (rcvd_pkt->type != type) { 597 | if (debug) { 598 | switch (rcvd_pkt->type) { 599 | case SENDER: 600 | printf("Discarding sender packet\n"); 601 | break; 602 | 603 | case RECEIVER: 604 | printf("Discarding receiver packet\n"); 605 | break; 606 | 607 | case '?': 608 | printf("Discarding packet: unknown type(%c)\n", rcvd_pkt->type); 609 | break; 610 | } 611 | } 612 | 613 | return -1; 614 | } 615 | 616 | if (rcvd_pkt->type == RECEIVER) { 617 | if (rcvd_pkt->pid != pid) { 618 | dbg("Discarding packet: pid mismatch (%u/%u)", pid, rcvd_pkt->pid); 619 | return -1; 620 | } 621 | } 622 | 623 | packets_rcvd++; 624 | 625 | return 0; 626 | } 627 | 628 | void sender_listen_loop(void) 629 | { 630 | send_mping(0); 631 | 632 | while (running) { 633 | char recv_packet[MAX_BUF_LEN + 1] = { 0 }; 634 | int len; 635 | 636 | if ((len = recvfrom(sd, recv_packet, MAX_BUF_LEN, 0, NULL, 0)) < 0) { 637 | if (errno == EINTR) 638 | continue; /* interrupt is ok */ 639 | err(1, "recvfrom() failed"); 640 | } 641 | 642 | if (process_mping(recv_packet, len, RECEIVER) == 0) { 643 | struct timespec now; 644 | struct timeval tv; 645 | double rtt; /* round trip time */ 646 | 647 | clock_gettime(CLOCK_MONOTONIC, &now); 648 | TIMESPEC_TO_TIMEVAL(&tv, &now); 649 | 650 | /* calculate round trip time in milliseconds */ 651 | subtract_timeval(&tv, &rcvd_pkt->tv); 652 | rtt = timeval_to_ms(&tv); 653 | 654 | /* keep rtt total, min and max */ 655 | rtt_total += rtt; 656 | if (rtt > rtt_max) 657 | rtt_max = rtt; 658 | if (rtt < rtt_min) 659 | rtt_min = rtt; 660 | 661 | /* output received packet information */ 662 | if (!quiet) 663 | printf("%d bytes from %s: seqno=%u ttl=%d time=%.1f ms\n", 664 | len, inet_address(&rcvd_pkt->src_host, NULL, 0), 665 | rcvd_pkt->seq_no, rcvd_pkt->ttl, rtt); 666 | } 667 | } 668 | } 669 | 670 | void receiver_listen_loop(void) 671 | { 672 | printf("Listening on %s:%d\n", arg_mcaddr, arg_mcport); 673 | 674 | while (running) { 675 | char recv_packet[MAX_BUF_LEN + 1]; 676 | int len; 677 | 678 | if ((len = recvfrom(sd, recv_packet, MAX_BUF_LEN, 0, NULL, 0)) < 0) { 679 | if (errno == EINTR) 680 | continue; /* interrupt is ok */ 681 | 682 | err(1, "recvfrom() failed"); 683 | } 684 | 685 | if (process_mping(recv_packet, len, SENDER) == 0) { 686 | if (!quiet) 687 | printf("Received mping from %s bytes=%d seqno=%u ttl=%d\n", 688 | inet_address(&rcvd_pkt->src_host, NULL, 0), 689 | len, rcvd_pkt->seq_no, rcvd_pkt->ttl); 690 | 691 | rcvd_pkt->type = RECEIVER; 692 | rcvd_pkt->src_host = myaddr; 693 | rcvd_pkt->dest_host = rcvd_pkt->src_host; 694 | 695 | hton_packet(rcvd_pkt); 696 | 697 | /* send reply immediately */ 698 | send_packet(rcvd_pkt, len); 699 | 700 | if (arg_count > 0 && packets_sent >= arg_count) 701 | exit(0); 702 | } 703 | } 704 | } 705 | 706 | int usage(void) 707 | { 708 | fprintf(stderr, 709 | "Usage:\n" 710 | " mping [-" OPTSTR "dhqrsv] [-b BYTES] [-c COUNT] [-i IFNAME] [-p PORT] [-t TTL]\n" 711 | " [-w SEC] [-W SEC] [GROUP]\n" 712 | "\n" 713 | "Options:\n" 714 | #ifdef AF_INET6 715 | " -6 Use IPv6 instead of IPv4, see below for defaults\n" 716 | #endif 717 | " -b BYTES Extra payload bytes (empty data), default: 0\n" 718 | " -c COUNT Stop after sending/receiving COUNT packets\n" 719 | " -d Debug messages\n" 720 | " -h This help text\n" 721 | " -i IFNAME Interface to use for sending/receiving\n" 722 | " -p PORT Multicast port to listen/send to, default %d\n" 723 | " -q Quiet output, only startup and and summary lines\n" 724 | " -r Receiver/reflector mode, default\n" 725 | " -s Sender mode\n" 726 | " -t TTL Multicast time to live to send, IPv6 hops, default %d\n" 727 | " -v Show program version and contact information\n" 728 | " -w DEADLINE Timeout before exiting, waiting for COUNT replies\n" 729 | " -W TIMEOUT Time to wait for a response, in seconds, default 5\n" 730 | "\n" 731 | "Defaults to use multicast group %s, UDP dst port %d, unless -6 in which\n" 732 | "case a multicast group %s is used. When a group argument is given, the\n" 733 | "address family is chosen from that. The selected outbound interface is chosen\n" 734 | "by querying the routing table, unless -i IFNAME\n", 735 | MC_PORT_DEFAULT, MC_TTL_DEFAULT, MC_GROUP_DEFAULT, MC_PORT_DEFAULT, 736 | MC_GROUP_INET6); 737 | 738 | return 0; 739 | } 740 | 741 | int main(int argc, char **argv) 742 | { 743 | int family = AF_INET; 744 | char *iface = NULL; 745 | inet_addr_t addr; 746 | char ifname[16]; 747 | int mode = 'r'; 748 | int ifindex; 749 | int c; 750 | 751 | while ((c = getopt(argc, argv, OPTSTR "b:c:dh?i:p:qrst:vW:w:")) != -1) { 752 | switch (c) { 753 | case 'b': 754 | arg_payload = atoi(optarg); 755 | if (arg_payload < 0 || arg_payload > (int)MAX_PAYLOAD) 756 | errx(1, "Invalid or too large payload, max %zu", MAX_PAYLOAD); 757 | break; 758 | #ifdef AF_INET6 759 | case '6': 760 | family = AF_INET6; 761 | break; 762 | #endif 763 | 764 | case 'c': 765 | arg_count = atoi(optarg); 766 | break; 767 | 768 | case 'd': 769 | debug = 1; 770 | break; 771 | 772 | case 'i': 773 | strlencpy(ifname, optarg, sizeof(ifname)); 774 | iface = ifname; 775 | break; 776 | 777 | case 'p': 778 | arg_mcport = atoi(optarg); 779 | break; 780 | 781 | case 'q': 782 | quiet = 1; 783 | break; 784 | 785 | case 'r': 786 | mode = 'r'; 787 | break; 788 | 789 | case 's': 790 | mode = 's'; 791 | break; 792 | 793 | case 't': 794 | arg_ttl = atoi(optarg); 795 | break; 796 | 797 | case 'v': 798 | printf("mping version %s\n" 799 | "\n" 800 | "Bug report address: https://github.com/troglobit/mping/issues\n" 801 | "Project homepage: https://github.com/troglobit/mping/\n", VERSION); 802 | return 0; 803 | 804 | case 'w': 805 | arg_deadline = atoi(optarg); 806 | break; 807 | 808 | case 'W': 809 | arg_timeout = atoi(optarg); 810 | break; 811 | 812 | case '?': 813 | case 'h': 814 | default: 815 | return usage(); 816 | } 817 | } 818 | 819 | if (optind < argc) 820 | strlencpy(arg_mcaddr, argv[optind], sizeof(arg_mcaddr)); 821 | #ifdef AF_INET6 822 | else if (family == AF_INET6) 823 | strlencpy(arg_mcaddr, MC_GROUP_INET6, sizeof(arg_mcaddr)); 824 | #endif 825 | 826 | if (address_inet(arg_mcaddr, &mcaddr)) 827 | errx(1, "invalid multicast group"); 828 | 829 | if (mcaddr.ss_family != family) 830 | family = mcaddr.ss_family; 831 | 832 | pid = getpid(); 833 | ifindex = ifinfo(iface, &addr, family); 834 | if (ifindex <= 0) 835 | exit(1); 836 | 837 | if (debug) { 838 | struct mping packet; 839 | printf("tv_sec/tv_usec size: %zu/%zu\n", sizeof(packet.tv.tv_sec), sizeof(packet.tv.tv_usec)); 840 | } 841 | 842 | init_socket(mcaddr.ss_family, ifindex); 843 | myaddr = addr; 844 | 845 | if (mode == 's') { 846 | struct timespec now; 847 | 848 | clock_gettime(CLOCK_MONOTONIC, &now); 849 | TIMESPEC_TO_TIMEVAL(&start, &now); 850 | printf("MPING %s:%d (ttl %d)\n", arg_mcaddr, arg_mcport, arg_ttl); 851 | 852 | sig(SIGINT, clean_exit); 853 | sig(SIGALRM, send_mping); 854 | 855 | sender_listen_loop(); 856 | } else 857 | receiver_listen_loop(); 858 | 859 | return cleanup(); 860 | } 861 | 862 | /** 863 | * Local Variables: 864 | * indent-tabs-mode: t 865 | * c-file-style: "linux" 866 | * End: 867 | */ 868 | --------------------------------------------------------------------------------