Test running ping through two tinc daemons.
authorGuus Sliepen <guus@tinc-vpn.org>
Thu, 5 Sep 2013 15:42:31 +0000 (17:42 +0200)
committerGuus Sliepen <guus@tinc-vpn.org>
Thu, 5 Sep 2013 15:42:31 +0000 (17:42 +0200)
This is a more complicated test with one tinc daemon using a tap interface
(therefore requiring root), and a second one using a multicast interface. A
separate program "pong" is listening on the same multicast address, and waits
for ARP and ICMP packets, responding to ICMP echo packets with replies.
This test doesn't require any configuration of the tap interface.

test/Makefile.am
test/ping.test [new file with mode: 0755]
test/pong.c [new file with mode: 0644]

index 7a09576..dac577d 100644 (file)
@@ -4,12 +4,18 @@ TESTS = \
        executables.test \
        import-export.test \
        invite-join.test \
+       ping.test \
        sptps-basic.test \
        variables.test
 
 EXTRA_DIST = testlib.sh
 
+check_PROGRAMS = pong
+
+pong_SOURCES = pong.c
+
 clean-local:
        -for pid in *.test.?/pid; do ../src/tinc --pidfile="$$pid" stop; done
        -killall ../src/sptps_test
+       -killall pong
        -rm -rf *.test.?
diff --git a/test/ping.test b/test/ping.test
new file mode 100755 (executable)
index 0000000..7e1a136
--- /dev/null
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+. ./testlib.sh
+
+# Skip this test if we aren't root
+
+test "`id -u`" = "0" || exit 77
+
+# Initialize two nodes
+
+$tinc $c1 <<EOF
+init foo
+set Mode switch
+set Interface ping.test
+set Port 32573
+set Address localhost
+EOF
+
+cat >$d1/tinc-up <<EOF
+#!/bin/sh
+ifconfig \$INTERFACE up
+EOF
+
+$tinc $c2 <<EOF
+init bar
+set Mode switch
+set DeviceType multicast
+set Device 233.252.0.1 32754
+add ConnectTo foo
+EOF
+
+# Exchange configuration files
+
+$tinc $c1 export | $tinc $c2 exchange | $tinc $c1 import
+
+# Ensure we have a working multicast route
+
+ip route replace 233.252.0.0/16 dev lo
+
+# Start pong program in background
+
+./pong 233.252.0.1 32754 10.6.5.5 &
+pong=$!
+
+# Start tinc and try to ping
+
+$tinc $c1 start $r1
+$tinc $c2 start $r2
+
+sleep 1
+
+ping -r -I ping.test -c3 10.6.5.5
+
+# Clean up
+
+kill $pong
+$tinc $c2 stop
+$tinc $c1 stop
diff --git a/test/pong.c b/test/pong.c
new file mode 100644 (file)
index 0000000..65a9075
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+    pong.c -- ICMP echo reply generator
+    Copyright (C) 2013 Guus Sliepen <guus@tinc-vpn.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "../src/system.h"
+
+uint8_t mymac[6] = {6, 5, 5, 6, 5, 5};
+
+static ssize_t do_arp(uint8_t *buf, ssize_t len, struct sockaddr_in *in) {
+       struct ether_arp arp;
+       memcpy(&arp, buf + 14, sizeof arp);
+
+       // Is it a valid ARP request?
+       if(ntohs(arp.arp_hrd) != ARPHRD_ETHER || ntohs(arp.arp_pro) != ETH_P_IP || arp.arp_hln != ETH_ALEN || arp.arp_pln != sizeof in->sin_addr.s_addr || ntohs(arp.arp_op) != ARPOP_REQUEST)
+               return 0;
+
+       // Does it match our address?
+       if(memcmp(&in->sin_addr.s_addr, arp.arp_tpa, 4))
+               return 0;
+
+       // Swap addresses
+       memcpy(buf, buf + 6, 6);
+       memcpy(buf + 6, mymac, 6);
+
+       arp.arp_op = htons(ARPOP_REPLY);
+       memcpy(arp.arp_tpa, arp.arp_spa, sizeof arp.arp_tpa);
+       memcpy(arp.arp_tha, arp.arp_sha, sizeof arp.arp_tha);
+       memcpy(arp.arp_spa, &in->sin_addr.s_addr, sizeof in->sin_addr.s_addr);
+       memcpy(arp.arp_sha, mymac, 6);
+
+       memcpy(buf + 14, &arp, sizeof arp);
+
+       return len;
+}
+
+static ssize_t do_ipv4(uint8_t *buf, ssize_t len, struct sockaddr_in *in) {
+       struct ip ip;
+       struct icmp icmp;
+
+       // Does it match our address?
+       if(memcmp(buf, mymac, 6))
+               return 0;
+
+       memcpy(&ip, buf + 14, sizeof ip);
+       if(memcmp(&ip.ip_dst, &in->sin_addr.s_addr, 4))
+               return 0;
+
+       // Is it an ICMP echo request?
+       if(ip.ip_p != IPPROTO_ICMP)
+               return 0;
+
+       memcpy(&icmp, buf + 14 + sizeof ip, sizeof icmp);
+       if(icmp.icmp_type != ICMP_ECHO)
+               return 0;
+
+       // Return an echo reply
+       memcpy(buf, buf + 6, 6);
+       memcpy(buf + 6, mymac, 6);
+
+       ip.ip_dst = ip.ip_src;
+       memcpy(&ip.ip_src, &in->sin_addr.s_addr, 4);
+
+       icmp.icmp_type = ICMP_ECHOREPLY;
+
+       memcpy(buf + 14, &ip, sizeof ip);
+       memcpy(buf + 14 + sizeof ip, &icmp, sizeof icmp);
+
+       return len;
+}
+
+static ssize_t do_ipv6(uint8_t *buf, ssize_t len, struct sockaddr_in6 *in) {
+       return 0;
+}
+
+int main(int argc, char *argv[]) {
+       if(argc != 4) {
+               fprintf(stderr, "Usage: %s <multicast address> <port> <ping address>\n", argv[0]);
+               return 1;
+       }
+
+       struct addrinfo hints = {}, *ai = NULL;
+       hints.ai_socktype = SOCK_DGRAM;
+       hints.ai_flags = AI_ADDRCONFIG;
+
+       errno = ENOENT;
+       if(getaddrinfo(argv[1], argv[2], &hints, &ai) || !ai) {
+               fprintf(stderr, "Could not resolve %s port %s: %s\n", argv[1], argv[2], strerror(errno));
+               return 1;
+       }
+
+       int fd;
+       fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+       if(!fd) {
+               fprintf(stderr, "Could not create socket: %s\n", strerror(errno));
+               return 1;
+       }
+
+       static const int one = 1;
+       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof one);
+
+       if(bind(fd, ai->ai_addr, ai->ai_addrlen)) {
+               fprintf(stderr, "Could not bind socket: %s\n", strerror(errno));
+               return 1;
+       }
+
+       switch(ai->ai_family) {
+               case AF_INET: {
+                       struct ip_mreq mreq;
+                       struct sockaddr_in in;
+                       memcpy(&in, ai->ai_addr, sizeof in);
+                       mreq.imr_multiaddr.s_addr = in.sin_addr.s_addr;
+                       mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+                       if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof mreq)) {
+                               fprintf(stderr, "Cannot join multicast group: %s\n", strerror(errno));
+                               return 1;
+                       }
+#ifdef IP_MULTICAST_LOOP
+                       setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (const void *)&one, sizeof one);
+#endif
+               } break;
+
+#ifdef IPV6_JOIN_GROUP
+               case AF_INET6: {
+                       struct ipv6_mreq mreq;
+                       struct sockaddr_in6 in6;
+                       memcpy(&in6, ai->ai_addr, sizeof in6);
+                       memcpy(&mreq.ipv6mr_multiaddr, &in6.sin6_addr, sizeof mreq.ipv6mr_multiaddr);
+                       mreq.ipv6mr_interface = in6.sin6_scope_id;
+                       if(setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (void *)&mreq, sizeof mreq)) {
+                               fprintf(stderr, "Cannot join multicast group: %s\n", strerror(errno));
+                               return 1;
+                       }
+#ifdef IPV6_MULTICAST_LOOP
+                       setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (const void *)&one, sizeof one);
+#endif
+               } break;
+#endif
+
+               default:
+                       fprintf(stderr, "Multicast for address family %hx unsupported\n", ai->ai_family);
+                       return 1;
+       }
+
+       errno = ENOENT;
+       struct addrinfo *ai2 = NULL;
+       if(getaddrinfo(argv[3], NULL, &hints, &ai2) || !ai2) {
+               fprintf(stderr, "Could not resolve %s: %s\n", argv[3], strerror(errno));
+               return 1;
+       }
+
+       while(true) {
+               uint8_t buf[10000];
+               struct sockaddr src;
+               socklen_t srclen;
+               ssize_t len = recvfrom(fd, buf, sizeof buf, 0, &src, &srclen);
+               if(len <= 0)
+                       break;
+
+               // Ignore short packets.
+               if(len < 14)
+                       continue;
+
+               uint16_t type = buf[12] << 8 | buf[13];
+
+               if(ai2->ai_family == AF_INET && type == ETH_P_IP)
+                       len = do_ipv4(buf, len, (struct sockaddr_in *)ai2->ai_addr);
+               else if(ai2->ai_family == AF_INET && type == ETH_P_ARP)
+                       len = do_arp(buf, len, (struct sockaddr_in *)ai2->ai_addr);
+               else if(ai2->ai_family == AF_INET6 && type == ETH_P_IPV6)
+                       len = do_ipv6(buf, len, (struct sockaddr_in6 *)ai2->ai_addr);
+               else
+                       continue;
+
+               if(len > 0)
+                       sendto(fd, buf, len, 0, ai->ai_addr, ai->ai_addrlen);
+       }
+
+       return 0;
+}