/* SPDX-License-Identifier: BSD-2-Clause */
/* X-SPDX-Copyright-Text: (c) Solarflare Communications Inc */
/**************************************************************************\
*//*! \file
**
** \author ab
** \brief Example for RX timestamping sockets API
** \date 2014/04/03
** \cop (c) Level 5 Networks Limited.
**
*//*
\**************************************************************************/
/* Example application to demonstrate use of the timestamping API
*
* This application will receive packets, and display their
* hardware timestamps.
*
* Invoke with "--help" to see the options it supports.
*
* Example:
* (host1)$ rx_timestamping
* UDP socket created, listening on port 9000
* Selecting software timestamping mode.
* (host2)$ echo payload | nc -u host1 9000
* Packet 0 - 8 bytes timestamp 1395768726.443243000
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef ONLOADEXT_AVAILABLE
#include "onload/extensions_timestamping.h"
#endif
/* Use the kernel definitions if possible -
* But if not, use our own local definitions, and Onload will allow it.
* - Though you still need a reasonably recent kernel to get hardware
* timestamping.
*/
#ifdef NO_KERNEL_TS_INCLUDE
#include
struct hwtstamp_config {
int flags; /* no flags defined right now, must be zero */
int tx_type; /* HWTSTAMP_TX_* */
int rx_filter; /* HWTSTAMP_FILTER_* */
};
enum {
SOF_TIMESTAMPING_TX_HARDWARE = (1<<0),
SOF_TIMESTAMPING_TX_SOFTWARE = (1<<1),
SOF_TIMESTAMPING_RX_HARDWARE = (1<<2),
SOF_TIMESTAMPING_RX_SOFTWARE = (1<<3),
SOF_TIMESTAMPING_SOFTWARE = (1<<4),
SOF_TIMESTAMPING_SYS_HARDWARE = (1<<5),
SOF_TIMESTAMPING_RAW_HARDWARE = (1<<6),
SOF_TIMESTAMPING_MASK =
(SOF_TIMESTAMPING_RAW_HARDWARE - 1) |
SOF_TIMESTAMPING_RAW_HARDWARE
};
#else
#include
#include
#endif
/* These are defined in socket.h, but older versions might not have all 3 */
#ifndef SO_TIMESTAMP
#define SO_TIMESTAMP 29
#endif
#ifndef SO_TIMESTAMPNS
#define SO_TIMESTAMPNS 35
#endif
#ifndef SO_TIMESTAMPING
#define SO_TIMESTAMPING 37
#endif
/* Seconds.nanoseconds format */
#define TIME_FMT "%" PRIu64 ".%.9" PRIu64 " "
#define OTIME_FMT "%" PRIu64 ".%.9" PRIu32 " "
/* Assert-like macros */
#define TEST(x) \
do { \
if( ! (x) ) { \
fprintf(stderr, "ERROR: '%s' failed\n", #x); \
fprintf(stderr, "ERROR: at %s:%d\n", __FILE__, __LINE__); \
exit(1); \
} \
} while( 0 )
#define TRY(x) \
do { \
int __rc = (x); \
if( __rc < 0 ) { \
fprintf(stderr, "ERROR: TRY(%s) failed\n", #x); \
fprintf(stderr, "ERROR: at %s:%d\n", __FILE__, __LINE__); \
fprintf(stderr, "ERROR: rc=%d errno=%d (%s)\n", \
__rc, errno, strerror(errno)); \
exit(1); \
} \
} while( 0 )
struct configuration {
char const* cfg_ioctl; /* e.g. eth6 - calls the ts enable ioctl */
unsigned short cfg_port; /* listen port */
int cfg_protocol; /* udp or tcp? */
unsigned int cfg_max_packets; /* Stop after this many (0=forever) */
int cfg_ext; /* Use extension API? */
};
/* Commandline options, configuration etc. */
void print_help(void)
{
printf("Usage:\n"
"\t--ioctl\t\tDevice to send timestamping enable ioctl. "
"Default: None\n"
"\t--port\t\tPort to listen on. "
"Default: 9000\n"
"\t--proto\t[TCP|UDP]. "
"Default: UDP\n"
"\t--max\t\tStop after n packets. "
"Default: Run forever\n"
#ifdef ONLOADEXT_AVAILABLE
"\t--ext\t\tUse extensions API rather than SO_TIMESTAMPING.\n"
#endif
);
exit(-1);
}
static void get_protcol(struct configuration* cfg, const char* protocol)
{
if( 0 == strcasecmp(protocol, "UDP") ) {
cfg->cfg_protocol = IPPROTO_UDP;
}
else if( 0 == strcasecmp(protocol, "TCP") ) {
cfg->cfg_protocol = IPPROTO_TCP;
}
else {
printf("ERROR: '%s' is not a recognised protocol (TCP or UCP).\n",
protocol);
exit(-EINVAL);
}
}
static void parse_options( int argc, char** argv, struct configuration* cfg )
{
int option_index = 0;
int opt;
static struct option long_options[] = {
{ "ioctl", required_argument, 0, 'i' },
{ "port", required_argument, 0, 'p' },
{ "proto", required_argument, 0, 'P' },
{ "max", required_argument, 0, 'n' },
{ "ext", no_argument, 0, 'e' },
{ 0, no_argument, 0, 0 }
};
const char* optstring = "i:p:P:n:";
/* Defaults */
bzero(cfg, sizeof(struct configuration));
cfg->cfg_port = 9000;
cfg->cfg_protocol = IPPROTO_UDP;
opt = getopt_long(argc, argv, optstring, long_options, &option_index);
while( opt != -1 ) {
switch( opt ) {
case 'i':
cfg->cfg_ioctl = optarg;
break;
case 'p':
cfg->cfg_port = atoi(optarg);
break;
case 'P':
get_protcol(cfg, optarg);
break;
case 'n':
cfg->cfg_max_packets = atoi(optarg);
break;
#ifdef ONLOADEXT_AVAILABLE
case 'e':
cfg->cfg_ext = 1;
break;
#endif
default:
print_help();
break;
}
opt = getopt_long(argc, argv, optstring, long_options, &option_index);
}
}
/* Connection */
static void make_address(unsigned short port, struct sockaddr_in* host_address)
{
bzero(host_address, sizeof(struct sockaddr_in));
host_address->sin_family = AF_INET;
host_address->sin_port = htons(port);
host_address->sin_addr.s_addr = INADDR_ANY;
}
/* This requires a bit of explanation.
* Typically, you have to enable hardware timestamping on an interface.
* Any application can do it, and then it's available to everyone.
* The easiest way to do this, is just to run sfptpd.
*
* But in case you need to do it manually; here is the code, but
* that's only supported on reasonably recent versions
*
* Option: --ioctl ethX
*
* NOTE:
* Usage of the ioctl call is discouraged. A better method, if using
* hardware timestamping, would be to use sfptpd as it will effectively
* make the ioctl call for you.
*
*/
static void do_ioctl(struct configuration* cfg, int sock)
{
#ifdef SIOCSHWTSTAMP
struct ifreq ifr;
struct hwtstamp_config hwc;
#endif
if( cfg->cfg_ioctl == NULL )
return;
#ifdef SIOCSHWTSTAMP
bzero(&ifr, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", cfg->cfg_ioctl);
/* Standard kernel ioctl options */
hwc.flags = 0;
hwc.tx_type = 0;
hwc.rx_filter = HWTSTAMP_FILTER_ALL;
ifr.ifr_data = (char*)&hwc;
TRY( ioctl(sock, SIOCSHWTSTAMP, &ifr) );
return;
#else
(void) sock;
printf("SIOCHWTSTAMP ioctl not supported on this kernel.\n");
exit(-ENOTSUP);
return;
#endif
}
/* This routine selects the correct socket option to enable timestamping. */
static void do_ts_sockopt(struct configuration* cfg, int sock)
{
printf("Selecting hardware timestamping mode.\n");
#ifdef ONLOADEXT_AVAILABLE
if( cfg->cfg_ext )
TRY(onload_timestamping_request(sock, ONLOAD_TIMESTAMPING_FLAG_RX_NIC |
ONLOAD_TIMESTAMPING_FLAG_RX_CPACKET));
else
#endif
{
int enable = SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE |
SOF_TIMESTAMPING_SYS_HARDWARE | SOF_TIMESTAMPING_SOFTWARE;
TRY(setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING, &enable, sizeof(int)));
}
}
static int add_socket(struct configuration* cfg)
{
int s;
struct sockaddr_in host_address;
int domain = SOCK_DGRAM;
if ( cfg->cfg_protocol == IPPROTO_TCP )
domain = SOCK_STREAM;
make_address(cfg->cfg_port, &host_address);
s = socket(PF_INET, domain, cfg->cfg_protocol);
TEST(s >= 0);
TRY(bind(s, (struct sockaddr*)&host_address, sizeof(host_address)) );
printf("Socket created, listening on port %d\n", cfg->cfg_port);
return s;
}
static int accept_child(int parent)
{
int child;
socklen_t clilen;
struct sockaddr_in cli_addr;
clilen = sizeof(cli_addr);
TRY(listen(parent, 1));
child = accept(parent, (struct sockaddr* ) &cli_addr, &clilen);
TEST(child >= 0);
printf("Socket accepted\n");
return child;
}
/* Processing */
static void print_time(struct timespec* ts)
{
if( ts != NULL ) {
/* Hardware timestamping provides three timestamps -
* system (software)
* transformed (hw converted to sw)
* raw (hardware)
* in that order - though depending on socket option, you may have 0 in
* some of them.
*/
printf("timestamps " TIME_FMT TIME_FMT TIME_FMT "\n",
(uint64_t)ts[0].tv_sec, (uint64_t)ts[0].tv_nsec,
(uint64_t)ts[1].tv_sec, (uint64_t)ts[1].tv_nsec,
(uint64_t)ts[2].tv_sec, (uint64_t)ts[2].tv_nsec );
} else
{
printf( "no timestamp\n" );
}
}
/* Given a packet, extract the timestamp(s) */
static void handle_time(struct msghdr* msg, struct configuration* cfg)
{
struct timespec* ts = NULL;
struct cmsghdr* cmsg;
for( cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg,cmsg) ) {
if( cmsg->cmsg_level != SOL_SOCKET )
continue;
switch( cmsg->cmsg_type ) {
case SO_TIMESTAMPNS:
ts = (struct timespec*) CMSG_DATA(cmsg);
break;
case SO_TIMESTAMPING:
#ifdef ONLOADEXT_AVAILABLE
if( cfg->cfg_ext ) {
struct onload_timestamp* ts = (struct onload_timestamp*) CMSG_DATA(cmsg);
printf("timestamps " OTIME_FMT OTIME_FMT "\n",
ts[0].sec, ts[0].nsec, ts[1].sec, ts[1].nsec);
return;
}
#endif
ts = (struct timespec*) CMSG_DATA(cmsg);
break;
default:
/* Ignore other cmsg options */
break;
}
}
print_time(ts);
}
/* Receive a packet, and print out the timestamps from it */
static int do_recv(int sock, unsigned int pkt_num, struct configuration* cfg)
{
struct msghdr msg;
struct iovec iov;
struct sockaddr_in host_address;
char buffer[2048];
char control[1024];
int got;
/* recvmsg header structure */
make_address(0, &host_address);
iov.iov_base = buffer;
iov.iov_len = 2048;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_name = &host_address;
msg.msg_namelen = sizeof(struct sockaddr_in);
msg.msg_control = control;
msg.msg_controllen = 1024;
/* block for message */
got = recvmsg(sock, &msg, 0);
if( !got && errno == EAGAIN )
return 0;
printf("Packet %d - %d bytes\t", pkt_num, got);
handle_time(&msg, cfg);
return got;
};
int main(int argc, char** argv)
{
struct configuration cfg;
int parent, sock, got;
unsigned int pkt_num = 0;
parse_options(argc, argv, &cfg);
/* Initialise */
parent = add_socket(&cfg);
do_ioctl(&cfg, parent);
sock = parent;
if( cfg.cfg_protocol == IPPROTO_TCP )
sock = accept_child(parent);
do_ts_sockopt(&cfg, sock);
/* Run forever */
while((pkt_num++ < cfg.cfg_max_packets || (cfg.cfg_max_packets == 0) ) ) {
got = do_recv(sock, pkt_num, &cfg);
/* TCP can detect an exit; for UDP, zero payload packets are valid */
if ( got == 0 && cfg.cfg_protocol == IPPROTO_TCP ) {
printf( "recvmsg returned 0 - end of stream\n" );
break;
}
}
close(sock);
if( cfg.cfg_protocol == IPPROTO_TCP )
close(parent);
return 0;
}