linux/tools/testing/selftests/networking/timestamping/txtimestamp.c
Thomas Gleixner a61127c213 treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 335
Based on 1 normalized pattern(s):

  this program is free software you can redistribute it and or modify
  it under the terms and conditions of the gnu general public license
  version 2 as published by the free software foundation this program
  is distributed in the hope 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 st fifth floor boston ma 02110
  1301 usa

extracted by the scancode license scanner the SPDX license identifier

  GPL-2.0-only

has been chosen to replace the boilerplate/reference in 111 file(s).

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Alexios Zavras <alexios.zavras@intel.com>
Reviewed-by: Allison Randal <allison@lohutok.net>
Cc: linux-spdx@vger.kernel.org
Link: https://lkml.kernel.org/r/20190530000436.567572064@linutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-06-05 17:37:06 +02:00

778 lines
18 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2014 Google Inc.
* Author: willemb@google.com (Willem de Bruijn)
*
* Test software tx timestamping, including
*
* - SCHED, SND and ACK timestamps
* - RAW, UDP and TCP
* - IPv4 and IPv6
* - various packet sizes (to test GSO and TSO)
*
* Consult the command line arguments for help on running
* the various testcases.
*
* This test requires a dummy TCP server.
* A simple `nc6 [-u] -l -p $DESTPORT` will do
*/
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <asm/types.h>
#include <error.h>
#include <errno.h>
#include <inttypes.h>
#include <linux/errqueue.h>
#include <linux/if_ether.h>
#include <linux/ipv6.h>
#include <linux/net_tstamp.h>
#include <netdb.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <netpacket/packet.h>
#include <poll.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
/* command line parameters */
static int cfg_proto = SOCK_STREAM;
static int cfg_ipproto = IPPROTO_TCP;
static int cfg_num_pkts = 4;
static int do_ipv4 = 1;
static int do_ipv6 = 1;
static int cfg_payload_len = 10;
static int cfg_poll_timeout = 100;
static int cfg_delay_snd;
static int cfg_delay_ack;
static bool cfg_show_payload;
static bool cfg_do_pktinfo;
static bool cfg_loop_nodata;
static bool cfg_no_delay;
static bool cfg_use_cmsg;
static bool cfg_use_pf_packet;
static bool cfg_do_listen;
static uint16_t dest_port = 9000;
static struct sockaddr_in daddr;
static struct sockaddr_in6 daddr6;
static struct timespec ts_usr;
static int saved_tskey = -1;
static int saved_tskey_type = -1;
static bool test_failed;
static int64_t timespec_to_us64(struct timespec *ts)
{
return ts->tv_sec * 1000 * 1000 + ts->tv_nsec / 1000;
}
static void validate_key(int tskey, int tstype)
{
int stepsize;
/* compare key for each subsequent request
* must only test for one type, the first one requested
*/
if (saved_tskey == -1)
saved_tskey_type = tstype;
else if (saved_tskey_type != tstype)
return;
stepsize = cfg_proto == SOCK_STREAM ? cfg_payload_len : 1;
if (tskey != saved_tskey + stepsize) {
fprintf(stderr, "ERROR: key %d, expected %d\n",
tskey, saved_tskey + stepsize);
test_failed = true;
}
saved_tskey = tskey;
}
static void validate_timestamp(struct timespec *cur, int min_delay)
{
int max_delay = min_delay + 500 /* processing time upper bound */;
int64_t cur64, start64;
cur64 = timespec_to_us64(cur);
start64 = timespec_to_us64(&ts_usr);
if (cur64 < start64 + min_delay || cur64 > start64 + max_delay) {
fprintf(stderr, "ERROR: delay %lu expected between %d and %d\n",
cur64 - start64, min_delay, max_delay);
test_failed = true;
}
}
static void __print_timestamp(const char *name, struct timespec *cur,
uint32_t key, int payload_len)
{
if (!(cur->tv_sec | cur->tv_nsec))
return;
fprintf(stderr, " %s: %lu s %lu us (seq=%u, len=%u)",
name, cur->tv_sec, cur->tv_nsec / 1000,
key, payload_len);
if (cur != &ts_usr)
fprintf(stderr, " (USR %+" PRId64 " us)",
timespec_to_us64(cur) - timespec_to_us64(&ts_usr));
fprintf(stderr, "\n");
}
static void print_timestamp_usr(void)
{
if (clock_gettime(CLOCK_REALTIME, &ts_usr))
error(1, errno, "clock_gettime");
__print_timestamp(" USR", &ts_usr, 0, 0);
}
static void print_timestamp(struct scm_timestamping *tss, int tstype,
int tskey, int payload_len)
{
const char *tsname;
validate_key(tskey, tstype);
switch (tstype) {
case SCM_TSTAMP_SCHED:
tsname = " ENQ";
validate_timestamp(&tss->ts[0], 0);
break;
case SCM_TSTAMP_SND:
tsname = " SND";
validate_timestamp(&tss->ts[0], cfg_delay_snd);
break;
case SCM_TSTAMP_ACK:
tsname = " ACK";
validate_timestamp(&tss->ts[0], cfg_delay_ack);
break;
default:
error(1, 0, "unknown timestamp type: %u",
tstype);
}
__print_timestamp(tsname, &tss->ts[0], tskey, payload_len);
}
/* TODO: convert to check_and_print payload once API is stable */
static void print_payload(char *data, int len)
{
int i;
if (!len)
return;
if (len > 70)
len = 70;
fprintf(stderr, "payload: ");
for (i = 0; i < len; i++)
fprintf(stderr, "%02hhx ", data[i]);
fprintf(stderr, "\n");
}
static void print_pktinfo(int family, int ifindex, void *saddr, void *daddr)
{
char sa[INET6_ADDRSTRLEN], da[INET6_ADDRSTRLEN];
fprintf(stderr, " pktinfo: ifindex=%u src=%s dst=%s\n",
ifindex,
saddr ? inet_ntop(family, saddr, sa, sizeof(sa)) : "unknown",
daddr ? inet_ntop(family, daddr, da, sizeof(da)) : "unknown");
}
static void __poll(int fd)
{
struct pollfd pollfd;
int ret;
memset(&pollfd, 0, sizeof(pollfd));
pollfd.fd = fd;
ret = poll(&pollfd, 1, cfg_poll_timeout);
if (ret != 1)
error(1, errno, "poll");
}
static void __recv_errmsg_cmsg(struct msghdr *msg, int payload_len)
{
struct sock_extended_err *serr = NULL;
struct scm_timestamping *tss = NULL;
struct cmsghdr *cm;
int batch = 0;
for (cm = CMSG_FIRSTHDR(msg);
cm && cm->cmsg_len;
cm = CMSG_NXTHDR(msg, cm)) {
if (cm->cmsg_level == SOL_SOCKET &&
cm->cmsg_type == SCM_TIMESTAMPING) {
tss = (void *) CMSG_DATA(cm);
} else if ((cm->cmsg_level == SOL_IP &&
cm->cmsg_type == IP_RECVERR) ||
(cm->cmsg_level == SOL_IPV6 &&
cm->cmsg_type == IPV6_RECVERR) ||
(cm->cmsg_level == SOL_PACKET &&
cm->cmsg_type == PACKET_TX_TIMESTAMP)) {
serr = (void *) CMSG_DATA(cm);
if (serr->ee_errno != ENOMSG ||
serr->ee_origin != SO_EE_ORIGIN_TIMESTAMPING) {
fprintf(stderr, "unknown ip error %d %d\n",
serr->ee_errno,
serr->ee_origin);
serr = NULL;
}
} else if (cm->cmsg_level == SOL_IP &&
cm->cmsg_type == IP_PKTINFO) {
struct in_pktinfo *info = (void *) CMSG_DATA(cm);
print_pktinfo(AF_INET, info->ipi_ifindex,
&info->ipi_spec_dst, &info->ipi_addr);
} else if (cm->cmsg_level == SOL_IPV6 &&
cm->cmsg_type == IPV6_PKTINFO) {
struct in6_pktinfo *info6 = (void *) CMSG_DATA(cm);
print_pktinfo(AF_INET6, info6->ipi6_ifindex,
NULL, &info6->ipi6_addr);
} else
fprintf(stderr, "unknown cmsg %d,%d\n",
cm->cmsg_level, cm->cmsg_type);
if (serr && tss) {
print_timestamp(tss, serr->ee_info, serr->ee_data,
payload_len);
serr = NULL;
tss = NULL;
batch++;
}
}
if (batch > 1)
fprintf(stderr, "batched %d timestamps\n", batch);
}
static int recv_errmsg(int fd)
{
static char ctrl[1024 /* overprovision*/];
static struct msghdr msg;
struct iovec entry;
static char *data;
int ret = 0;
data = malloc(cfg_payload_len);
if (!data)
error(1, 0, "malloc");
memset(&msg, 0, sizeof(msg));
memset(&entry, 0, sizeof(entry));
memset(ctrl, 0, sizeof(ctrl));
entry.iov_base = data;
entry.iov_len = cfg_payload_len;
msg.msg_iov = &entry;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_control = ctrl;
msg.msg_controllen = sizeof(ctrl);
ret = recvmsg(fd, &msg, MSG_ERRQUEUE);
if (ret == -1 && errno != EAGAIN)
error(1, errno, "recvmsg");
if (ret >= 0) {
__recv_errmsg_cmsg(&msg, ret);
if (cfg_show_payload)
print_payload(data, cfg_payload_len);
}
free(data);
return ret == -1;
}
static uint16_t get_ip_csum(const uint16_t *start, int num_words,
unsigned long sum)
{
int i;
for (i = 0; i < num_words; i++)
sum += start[i];
while (sum >> 16)
sum = (sum & 0xFFFF) + (sum >> 16);
return ~sum;
}
static uint16_t get_udp_csum(const struct udphdr *udph, int alen)
{
unsigned long pseudo_sum, csum_len;
const void *csum_start = udph;
pseudo_sum = htons(IPPROTO_UDP);
pseudo_sum += udph->len;
/* checksum ip(v6) addresses + udp header + payload */
csum_start -= alen * 2;
csum_len = ntohs(udph->len) + alen * 2;
return get_ip_csum(csum_start, csum_len >> 1, pseudo_sum);
}
static int fill_header_ipv4(void *p)
{
struct iphdr *iph = p;
memset(iph, 0, sizeof(*iph));
iph->ihl = 5;
iph->version = 4;
iph->ttl = 2;
iph->saddr = daddr.sin_addr.s_addr; /* set for udp csum calc */
iph->daddr = daddr.sin_addr.s_addr;
iph->protocol = IPPROTO_UDP;
/* kernel writes saddr, csum, len */
return sizeof(*iph);
}
static int fill_header_ipv6(void *p)
{
struct ipv6hdr *ip6h = p;
memset(ip6h, 0, sizeof(*ip6h));
ip6h->version = 6;
ip6h->payload_len = htons(sizeof(struct udphdr) + cfg_payload_len);
ip6h->nexthdr = IPPROTO_UDP;
ip6h->hop_limit = 64;
ip6h->saddr = daddr6.sin6_addr;
ip6h->daddr = daddr6.sin6_addr;
/* kernel does not write saddr in case of ipv6 */
return sizeof(*ip6h);
}
static void fill_header_udp(void *p, bool is_ipv4)
{
struct udphdr *udph = p;
udph->source = ntohs(dest_port + 1); /* spoof */
udph->dest = ntohs(dest_port);
udph->len = ntohs(sizeof(*udph) + cfg_payload_len);
udph->check = 0;
udph->check = get_udp_csum(udph, is_ipv4 ? sizeof(struct in_addr) :
sizeof(struct in6_addr));
}
static void do_test(int family, unsigned int report_opt)
{
char control[CMSG_SPACE(sizeof(uint32_t))];
struct sockaddr_ll laddr;
unsigned int sock_opt;
struct cmsghdr *cmsg;
struct msghdr msg;
struct iovec iov;
char *buf;
int fd, i, val = 1, total_len;
total_len = cfg_payload_len;
if (cfg_use_pf_packet || cfg_proto == SOCK_RAW) {
total_len += sizeof(struct udphdr);
if (cfg_use_pf_packet || cfg_ipproto == IPPROTO_RAW)
if (family == PF_INET)
total_len += sizeof(struct iphdr);
else
total_len += sizeof(struct ipv6hdr);
/* special case, only rawv6_sendmsg:
* pass proto in sin6_port if not connected
* also see ANK comment in net/ipv4/raw.c
*/
daddr6.sin6_port = htons(cfg_ipproto);
}
buf = malloc(total_len);
if (!buf)
error(1, 0, "malloc");
fd = socket(cfg_use_pf_packet ? PF_PACKET : family,
cfg_proto, cfg_ipproto);
if (fd < 0)
error(1, errno, "socket");
/* reset expected key on each new socket */
saved_tskey = -1;
if (cfg_proto == SOCK_STREAM) {
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
(char*) &val, sizeof(val)))
error(1, 0, "setsockopt no nagle");
if (family == PF_INET) {
if (connect(fd, (void *) &daddr, sizeof(daddr)))
error(1, errno, "connect ipv4");
} else {
if (connect(fd, (void *) &daddr6, sizeof(daddr6)))
error(1, errno, "connect ipv6");
}
}
if (cfg_do_pktinfo) {
if (family == AF_INET6) {
if (setsockopt(fd, SOL_IPV6, IPV6_RECVPKTINFO,
&val, sizeof(val)))
error(1, errno, "setsockopt pktinfo ipv6");
} else {
if (setsockopt(fd, SOL_IP, IP_PKTINFO,
&val, sizeof(val)))
error(1, errno, "setsockopt pktinfo ipv4");
}
}
sock_opt = SOF_TIMESTAMPING_SOFTWARE |
SOF_TIMESTAMPING_OPT_CMSG |
SOF_TIMESTAMPING_OPT_ID;
if (!cfg_use_cmsg)
sock_opt |= report_opt;
if (cfg_loop_nodata)
sock_opt |= SOF_TIMESTAMPING_OPT_TSONLY;
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING,
(char *) &sock_opt, sizeof(sock_opt)))
error(1, 0, "setsockopt timestamping");
for (i = 0; i < cfg_num_pkts; i++) {
memset(&msg, 0, sizeof(msg));
memset(buf, 'a' + i, total_len);
if (cfg_use_pf_packet || cfg_proto == SOCK_RAW) {
int off = 0;
if (cfg_use_pf_packet || cfg_ipproto == IPPROTO_RAW) {
if (family == PF_INET)
off = fill_header_ipv4(buf);
else
off = fill_header_ipv6(buf);
}
fill_header_udp(buf + off, family == PF_INET);
}
print_timestamp_usr();
iov.iov_base = buf;
iov.iov_len = total_len;
if (cfg_proto != SOCK_STREAM) {
if (cfg_use_pf_packet) {
memset(&laddr, 0, sizeof(laddr));
laddr.sll_family = AF_PACKET;
laddr.sll_ifindex = 1;
laddr.sll_protocol = htons(family == AF_INET ? ETH_P_IP : ETH_P_IPV6);
laddr.sll_halen = ETH_ALEN;
msg.msg_name = (void *)&laddr;
msg.msg_namelen = sizeof(laddr);
} else if (family == PF_INET) {
msg.msg_name = (void *)&daddr;
msg.msg_namelen = sizeof(daddr);
} else {
msg.msg_name = (void *)&daddr6;
msg.msg_namelen = sizeof(daddr6);
}
}
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if (cfg_use_cmsg) {
memset(control, 0, sizeof(control));
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SO_TIMESTAMPING;
cmsg->cmsg_len = CMSG_LEN(sizeof(uint32_t));
*((uint32_t *) CMSG_DATA(cmsg)) = report_opt;
}
val = sendmsg(fd, &msg, 0);
if (val != total_len)
error(1, errno, "send");
/* wait for all errors to be queued, else ACKs arrive OOO */
if (!cfg_no_delay)
usleep(50 * 1000);
__poll(fd);
while (!recv_errmsg(fd)) {}
}
if (close(fd))
error(1, errno, "close");
free(buf);
usleep(100 * 1000);
}
static void __attribute__((noreturn)) usage(const char *filepath)
{
fprintf(stderr, "\nUsage: %s [options] hostname\n"
"\nwhere options are:\n"
" -4: only IPv4\n"
" -6: only IPv6\n"
" -h: show this message\n"
" -c N: number of packets for each test\n"
" -C: use cmsg to set tstamp recording options\n"
" -D: no delay between packets\n"
" -F: poll() waits forever for an event\n"
" -I: request PKTINFO\n"
" -l N: send N bytes at a time\n"
" -L listen on hostname and port\n"
" -n: set no-payload option\n"
" -p N: connect to port N\n"
" -P: use PF_PACKET\n"
" -r: use raw\n"
" -R: use raw (IP_HDRINCL)\n"
" -u: use udp\n"
" -v: validate SND delay (usec)\n"
" -V: validate ACK delay (usec)\n"
" -x: show payload (up to 70 bytes)\n",
filepath);
exit(1);
}
static void parse_opt(int argc, char **argv)
{
int proto_count = 0;
int c;
while ((c = getopt(argc, argv, "46c:CDFhIl:Lnp:PrRuv:V:x")) != -1) {
switch (c) {
case '4':
do_ipv6 = 0;
break;
case '6':
do_ipv4 = 0;
break;
case 'c':
cfg_num_pkts = strtoul(optarg, NULL, 10);
break;
case 'C':
cfg_use_cmsg = true;
break;
case 'D':
cfg_no_delay = true;
break;
case 'F':
cfg_poll_timeout = -1;
break;
case 'I':
cfg_do_pktinfo = true;
break;
case 'l':
cfg_payload_len = strtoul(optarg, NULL, 10);
break;
case 'L':
cfg_do_listen = true;
break;
case 'n':
cfg_loop_nodata = true;
break;
case 'p':
dest_port = strtoul(optarg, NULL, 10);
break;
case 'P':
proto_count++;
cfg_use_pf_packet = true;
cfg_proto = SOCK_DGRAM;
cfg_ipproto = 0;
break;
case 'r':
proto_count++;
cfg_proto = SOCK_RAW;
cfg_ipproto = IPPROTO_UDP;
break;
case 'R':
proto_count++;
cfg_proto = SOCK_RAW;
cfg_ipproto = IPPROTO_RAW;
break;
case 'u':
proto_count++;
cfg_proto = SOCK_DGRAM;
cfg_ipproto = IPPROTO_UDP;
break;
case 'v':
cfg_delay_snd = strtoul(optarg, NULL, 10);
break;
case 'V':
cfg_delay_ack = strtoul(optarg, NULL, 10);
break;
case 'x':
cfg_show_payload = true;
break;
case 'h':
default:
usage(argv[0]);
}
}
if (!cfg_payload_len)
error(1, 0, "payload may not be nonzero");
if (cfg_proto != SOCK_STREAM && cfg_payload_len > 1472)
error(1, 0, "udp packet might exceed expected MTU");
if (!do_ipv4 && !do_ipv6)
error(1, 0, "pass -4 or -6, not both");
if (proto_count > 1)
error(1, 0, "pass -P, -r, -R or -u, not multiple");
if (cfg_do_pktinfo && cfg_use_pf_packet)
error(1, 0, "cannot ask for pktinfo over pf_packet");
if (optind != argc - 1)
error(1, 0, "missing required hostname argument");
}
static void resolve_hostname(const char *hostname)
{
struct addrinfo hints = { .ai_family = do_ipv4 ? AF_INET : AF_INET6 };
struct addrinfo *addrs, *cur;
int have_ipv4 = 0, have_ipv6 = 0;
retry:
if (getaddrinfo(hostname, NULL, &hints, &addrs))
error(1, errno, "getaddrinfo");
cur = addrs;
while (cur && !have_ipv4 && !have_ipv6) {
if (!have_ipv4 && cur->ai_family == AF_INET) {
memcpy(&daddr, cur->ai_addr, sizeof(daddr));
daddr.sin_port = htons(dest_port);
have_ipv4 = 1;
}
else if (!have_ipv6 && cur->ai_family == AF_INET6) {
memcpy(&daddr6, cur->ai_addr, sizeof(daddr6));
daddr6.sin6_port = htons(dest_port);
have_ipv6 = 1;
}
cur = cur->ai_next;
}
if (addrs)
freeaddrinfo(addrs);
if (do_ipv6 && hints.ai_family != AF_INET6) {
hints.ai_family = AF_INET6;
goto retry;
}
do_ipv4 &= have_ipv4;
do_ipv6 &= have_ipv6;
}
static void do_listen(int family, void *addr, int alen)
{
int fd, type;
type = cfg_proto == SOCK_RAW ? SOCK_DGRAM : cfg_proto;
fd = socket(family, type, 0);
if (fd == -1)
error(1, errno, "socket rx");
if (bind(fd, addr, alen))
error(1, errno, "bind rx");
if (type == SOCK_STREAM && listen(fd, 10))
error(1, errno, "listen rx");
/* leave fd open, will be closed on process exit.
* this enables connect() to succeed and avoids icmp replies
*/
}
static void do_main(int family)
{
fprintf(stderr, "family: %s %s\n",
family == PF_INET ? "INET" : "INET6",
cfg_use_pf_packet ? "(PF_PACKET)" : "");
fprintf(stderr, "test SND\n");
do_test(family, SOF_TIMESTAMPING_TX_SOFTWARE);
fprintf(stderr, "test ENQ\n");
do_test(family, SOF_TIMESTAMPING_TX_SCHED);
fprintf(stderr, "test ENQ + SND\n");
do_test(family, SOF_TIMESTAMPING_TX_SCHED |
SOF_TIMESTAMPING_TX_SOFTWARE);
if (cfg_proto == SOCK_STREAM) {
fprintf(stderr, "\ntest ACK\n");
do_test(family, SOF_TIMESTAMPING_TX_ACK);
fprintf(stderr, "\ntest SND + ACK\n");
do_test(family, SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_TX_ACK);
fprintf(stderr, "\ntest ENQ + SND + ACK\n");
do_test(family, SOF_TIMESTAMPING_TX_SCHED |
SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_TX_ACK);
}
}
const char *sock_names[] = { NULL, "TCP", "UDP", "RAW" };
int main(int argc, char **argv)
{
if (argc == 1)
usage(argv[0]);
parse_opt(argc, argv);
resolve_hostname(argv[argc - 1]);
fprintf(stderr, "protocol: %s\n", sock_names[cfg_proto]);
fprintf(stderr, "payload: %u\n", cfg_payload_len);
fprintf(stderr, "server port: %u\n", dest_port);
fprintf(stderr, "\n");
if (do_ipv4) {
if (cfg_do_listen)
do_listen(PF_INET, &daddr, sizeof(daddr));
do_main(PF_INET);
}
if (do_ipv6) {
if (cfg_do_listen)
do_listen(PF_INET6, &daddr6, sizeof(daddr6));
do_main(PF_INET6);
}
return test_failed;
}