Test running ping through two tinc daemons.

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.
This commit is contained in:
Guus Sliepen 2013-09-05 17:42:31 +02:00
parent fe1d0043c8
commit b80cbaba04
3 changed files with 258 additions and 0 deletions

View 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.?

58
test/ping.test Executable file
View file

@ -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

194
test/pong.c Normal file
View file

@ -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;
}