net-timestamp: expand documentation
Expand Documentation/networking/timestamping.txt with new interfaces and bytestream timestamping. Also minor cleanup of the other text. Import txtimestamp.c test of the new features. Signed-off-by: Willem de Bruijn <willemb@google.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Родитель
c5a65680b3
Коммит
8fe2f761ca
|
@ -1,102 +1,307 @@
|
|||
The existing interfaces for getting network packages time stamped are:
|
||||
|
||||
1. Control Interfaces
|
||||
|
||||
The interfaces for receiving network packages timestamps are:
|
||||
|
||||
* SO_TIMESTAMP
|
||||
Generate time stamp for each incoming packet using the (not necessarily
|
||||
monotonous!) system time. Result is returned via recv_msg() in a
|
||||
control message as timeval (usec resolution).
|
||||
Generates a timestamp for each incoming packet in (not necessarily
|
||||
monotonic) system time. Reports the timestamp via recvmsg() in a
|
||||
control message as struct timeval (usec resolution).
|
||||
|
||||
* SO_TIMESTAMPNS
|
||||
Same time stamping mechanism as SO_TIMESTAMP, but returns result as
|
||||
timespec (nsec resolution).
|
||||
Same timestamping mechanism as SO_TIMESTAMP, but reports the
|
||||
timestamp as struct timespec (nsec resolution).
|
||||
|
||||
* IP_MULTICAST_LOOP + SO_TIMESTAMP[NS]
|
||||
Only for multicasts: approximate send time stamp by receiving the looped
|
||||
packet and using its receive time stamp.
|
||||
Only for multicast:approximate transmit timestamp obtained by
|
||||
reading the looped packet receive timestamp.
|
||||
|
||||
The following interface complements the existing ones: receive time
|
||||
stamps can be generated and returned for arbitrary packets and much
|
||||
closer to the point where the packet is really sent. Time stamps can
|
||||
be generated in software (as before) or in hardware (if the hardware
|
||||
has such a feature).
|
||||
* SO_TIMESTAMPING
|
||||
Generates timestamps on reception, transmission or both. Supports
|
||||
multiple timestamp sources, including hardware. Supports generating
|
||||
timestamps for stream sockets.
|
||||
|
||||
SO_TIMESTAMPING:
|
||||
|
||||
Instructs the socket layer which kind of information should be collected
|
||||
and/or reported. The parameter is an integer with some of the following
|
||||
bits set. Setting other bits is an error and doesn't change the current
|
||||
state.
|
||||
1.1 SO_TIMESTAMP:
|
||||
|
||||
Four of the bits are requests to the stack to try to generate
|
||||
timestamps. Any combination of them is valid.
|
||||
This socket option enables timestamping of datagrams on the reception
|
||||
path. Because the destination socket, if any, is not known early in
|
||||
the network stack, the feature has to be enabled for all packets. The
|
||||
same is true for all early receive timestamp options.
|
||||
|
||||
SOF_TIMESTAMPING_TX_HARDWARE: try to obtain send time stamps in hardware
|
||||
SOF_TIMESTAMPING_TX_SOFTWARE: try to obtain send time stamps in software
|
||||
SOF_TIMESTAMPING_RX_HARDWARE: try to obtain receive time stamps in hardware
|
||||
SOF_TIMESTAMPING_RX_SOFTWARE: try to obtain receive time stamps in software
|
||||
For interface details, see `man 7 socket`.
|
||||
|
||||
|
||||
1.2 SO_TIMESTAMPNS:
|
||||
|
||||
This option is identical to SO_TIMESTAMP except for the returned data type.
|
||||
Its struct timespec allows for higher resolution (ns) timestamps than the
|
||||
timeval of SO_TIMESTAMP (ms).
|
||||
|
||||
|
||||
1.3 SO_TIMESTAMPING:
|
||||
|
||||
Supports multiple types of timestamp requests. As a result, this
|
||||
socket option takes a bitmap of flags, not a boolean. In
|
||||
|
||||
err = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, (void *) val, &val);
|
||||
|
||||
val is an integer with any of the following bits set. Setting other
|
||||
bit returns EINVAL and does not change the current state.
|
||||
|
||||
|
||||
1.3.1 Timestamp Generation
|
||||
|
||||
Some bits are requests to the stack to try to generate timestamps. Any
|
||||
combination of them is valid. Changes to these bits apply to newly
|
||||
created packets, not to packets already in the stack. As a result, it
|
||||
is possible to selectively request timestamps for a subset of packets
|
||||
(e.g., for sampling) by embedding an send() call within two setsockopt
|
||||
calls, one to enable timestamp generation and one to disable it.
|
||||
Timestamps may also be generated for reasons other than being
|
||||
requested by a particular socket, such as when receive timestamping is
|
||||
enabled system wide, as explained earlier.
|
||||
|
||||
SOF_TIMESTAMPING_RX_HARDWARE:
|
||||
Request rx timestamps generated by the network adapter.
|
||||
|
||||
SOF_TIMESTAMPING_RX_SOFTWARE:
|
||||
Request rx timestamps when data enters the kernel. These timestamps
|
||||
are generated just after a device driver hands a packet to the
|
||||
kernel receive stack.
|
||||
|
||||
SOF_TIMESTAMPING_TX_HARDWARE:
|
||||
Request tx timestamps generated by the network adapter.
|
||||
|
||||
SOF_TIMESTAMPING_TX_SOFTWARE:
|
||||
Request tx timestamps when data leaves the kernel. These timestamps
|
||||
are generated in the device driver as close as possible, but always
|
||||
prior to, passing the packet to the network interface. Hence, they
|
||||
require driver support and may not be available for all devices.
|
||||
|
||||
SOF_TIMESTAMPING_TX_SCHED:
|
||||
Request tx timestamps prior to entering the packet scheduler. Kernel
|
||||
transmit latency is, if long, often dominated by queuing delay. The
|
||||
difference between this timestamp and one taken at
|
||||
SOF_TIMESTAMPING_TX_SOFTWARE will expose this latency independent
|
||||
of protocol processing. The latency incurred in protocol
|
||||
processing, if any, can be computed by subtracting a userspace
|
||||
timestamp taken immediately before send() from this timestamp. On
|
||||
machines with virtual devices where a transmitted packet travels
|
||||
through multiple devices and, hence, multiple packet schedulers,
|
||||
a timestamp is generated at each layer. This allows for fine
|
||||
grained measurement of queuing delay.
|
||||
|
||||
SOF_TIMESTAMPING_TX_ACK:
|
||||
Request tx timestamps when all data in the send buffer has been
|
||||
acknowledged. This only makes sense for reliable protocols. It is
|
||||
currently only implemented for TCP. For that protocol, it may
|
||||
over-report measurement, because the timestamp is generated when all
|
||||
data up to and including the buffer at send() was acknowledged: the
|
||||
cumulative acknowledgment. The mechanism ignores SACK and FACK.
|
||||
|
||||
|
||||
1.3.2 Timestamp Reporting
|
||||
|
||||
The other three bits control which timestamps will be reported in a
|
||||
generated control message. If none of these bits are set or if none of
|
||||
the set bits correspond to data that is available, then the control
|
||||
message will not be generated:
|
||||
generated control message. Changes to the bits take immediate
|
||||
effect at the timestamp reporting locations in the stack. Timestamps
|
||||
are only reported for packets that also have the relevant timestamp
|
||||
generation request set.
|
||||
|
||||
SOF_TIMESTAMPING_SOFTWARE: report systime if available
|
||||
SOF_TIMESTAMPING_SYS_HARDWARE: report hwtimetrans if available (deprecated)
|
||||
SOF_TIMESTAMPING_RAW_HARDWARE: report hwtimeraw if available
|
||||
SOF_TIMESTAMPING_SOFTWARE:
|
||||
Report any software timestamps when available.
|
||||
|
||||
It is worth noting that timestamps may be collected for reasons other
|
||||
than being requested by a particular socket with
|
||||
SOF_TIMESTAMPING_[TR]X_(HARD|SOFT)WARE. For example, most drivers that
|
||||
can generate hardware receive timestamps ignore
|
||||
SOF_TIMESTAMPING_RX_HARDWARE. It is still a good idea to set that flag
|
||||
in case future drivers pay attention.
|
||||
SOF_TIMESTAMPING_SYS_HARDWARE:
|
||||
This option is deprecated and ignored.
|
||||
|
||||
If timestamps are reported, they will appear in a control message with
|
||||
cmsg_level==SOL_SOCKET, cmsg_type==SO_TIMESTAMPING, and a payload like
|
||||
this:
|
||||
SOF_TIMESTAMPING_RAW_HARDWARE:
|
||||
Report hardware timestamps as generated by
|
||||
SOF_TIMESTAMPING_TX_HARDWARE when available.
|
||||
|
||||
|
||||
1.3.3 Timestamp Options
|
||||
|
||||
The interface supports one option
|
||||
|
||||
SOF_TIMESTAMPING_OPT_ID:
|
||||
|
||||
Generate a unique identifier along with each packet. A process can
|
||||
have multiple concurrent timestamping requests outstanding. Packets
|
||||
can be reordered in the transmit path, for instance in the packet
|
||||
scheduler. In that case timestamps will be queued onto the error
|
||||
queue out of order from the original send() calls. This option
|
||||
embeds a counter that is incremented at send() time, to order
|
||||
timestamps within a flow.
|
||||
|
||||
This option is implemented only for transmit timestamps. There, the
|
||||
timestamp is always looped along with a struct sock_extended_err.
|
||||
The option modifies field ee_info to pass an id that is unique
|
||||
among all possibly concurrently outstanding timestamp requests for
|
||||
that socket. In practice, it is a monotonically increasing u32
|
||||
(that wraps).
|
||||
|
||||
In datagram sockets, the counter increments on each send call. In
|
||||
stream sockets, it increments with every byte.
|
||||
|
||||
|
||||
1.4 Bytestream Timestamps
|
||||
|
||||
The SO_TIMESTAMPING interface supports timestamping of bytes in a
|
||||
bytestream. Each request is interpreted as a request for when the
|
||||
entire contents of the buffer has passed a timestamping point. That
|
||||
is, for streams option SOF_TIMESTAMPING_TX_SOFTWARE will record
|
||||
when all bytes have reached the device driver, regardless of how
|
||||
many packets the data has been converted into.
|
||||
|
||||
In general, bytestreams have no natural delimiters and therefore
|
||||
correlating a timestamp with data is non-trivial. A range of bytes
|
||||
may be split across segments, any segments may be merged (possibly
|
||||
coalescing sections of previously segmented buffers associated with
|
||||
independent send() calls). Segments can be reordered and the same
|
||||
byte range can coexist in multiple segments for protocols that
|
||||
implement retransmissions.
|
||||
|
||||
It is essential that all timestamps implement the same semantics,
|
||||
regardless of these possible transformations, as otherwise they are
|
||||
incomparable. Handling "rare" corner cases differently from the
|
||||
simple case (a 1:1 mapping from buffer to skb) is insufficient
|
||||
because performance debugging often needs to focus on such outliers.
|
||||
|
||||
In practice, timestamps can be correlated with segments of a
|
||||
bytestream consistently, if both semantics of the timestamp and the
|
||||
timing of measurement are chosen correctly. This challenge is no
|
||||
different from deciding on a strategy for IP fragmentation. There, the
|
||||
definition is that only the first fragment is timestamped. For
|
||||
bytestreams, we chose that a timestamp is generated only when all
|
||||
bytes have passed a point. SOF_TIMESTAMPING_TX_ACK as defined is easy to
|
||||
implement and reason about. An implementation that has to take into
|
||||
account SACK would be more complex due to possible transmission holes
|
||||
and out of order arrival.
|
||||
|
||||
On the host, TCP can also break the simple 1:1 mapping from buffer to
|
||||
skbuff as a result of Nagle, cork, autocork, segmentation and GSO. The
|
||||
implementation ensures correctness in all cases by tracking the
|
||||
individual last byte passed to send(), even if it is no longer the
|
||||
last byte after an skbuff extend or merge operation. It stores the
|
||||
relevant sequence number in skb_shinfo(skb)->tskey. Because an skbuff
|
||||
has only one such field, only one timestamp can be generated.
|
||||
|
||||
In rare cases, a timestamp request can be missed if two requests are
|
||||
collapsed onto the same skb. A process can detect this situation by
|
||||
enabling SOF_TIMESTAMPING_OPT_ID and comparing the byte offset at
|
||||
send time with the value returned for each timestamp. It can prevent
|
||||
the situation by always flushing the TCP stack in between requests,
|
||||
for instance by enabling TCP_NODELAY and disabling TCP_CORK and
|
||||
autocork.
|
||||
|
||||
These precautions ensure that the timestamp is generated only when all
|
||||
bytes have passed a timestamp point, assuming that the network stack
|
||||
itself does not reorder the segments. The stack indeed tries to avoid
|
||||
reordering. The one exception is under administrator control: it is
|
||||
possible to construct a packet scheduler configuration that delays
|
||||
segments from the same stream differently. Such a setup would be
|
||||
unusual.
|
||||
|
||||
|
||||
2 Data Interfaces
|
||||
|
||||
Timestamps are read using the ancillary data feature of recvmsg().
|
||||
See `man 3 cmsg` for details of this interface. The socket manual
|
||||
page (`man 7 socket`) describes how timestamps generated with
|
||||
SO_TIMESTAMP and SO_TIMESTAMPNS records can be retrieved.
|
||||
|
||||
|
||||
2.1 SCM_TIMESTAMPING records
|
||||
|
||||
These timestamps are returned in a control message with cmsg_level
|
||||
SOL_SOCKET, cmsg_type SCM_TIMESTAMPING, and payload of type
|
||||
|
||||
struct scm_timestamping {
|
||||
struct timespec systime;
|
||||
struct timespec hwtimetrans;
|
||||
struct timespec hwtimeraw;
|
||||
struct timespec ts[3];
|
||||
};
|
||||
|
||||
recvmsg() can be used to get this control message for regular incoming
|
||||
packets. For send time stamps the outgoing packet is looped back to
|
||||
the socket's error queue with the send time stamp(s) attached. It can
|
||||
be received with recvmsg(flags=MSG_ERRQUEUE). The call returns the
|
||||
original outgoing packet data including all headers preprended down to
|
||||
and including the link layer, the scm_timestamping control message and
|
||||
a sock_extended_err control message with ee_errno==ENOMSG and
|
||||
ee_origin==SO_EE_ORIGIN_TIMESTAMPING. A socket with such a pending
|
||||
bounced packet is ready for reading as far as select() is concerned.
|
||||
If the outgoing packet has to be fragmented, then only the first
|
||||
fragment is time stamped and returned to the sending socket.
|
||||
The structure can return up to three timestamps. This is a legacy
|
||||
feature. Only one field is non-zero at any time. Most timestamps
|
||||
are passed in ts[0]. Hardware timestamps are passed in ts[2].
|
||||
|
||||
All three values correspond to the same event in time, but were
|
||||
generated in different ways. Each of these values may be empty (= all
|
||||
zero), in which case no such value was available. If the application
|
||||
is not interested in some of these values, they can be left blank to
|
||||
avoid the potential overhead of calculating them.
|
||||
ts[1] used to hold hardware timestamps converted to system time.
|
||||
Instead, expose the hardware clock device on the NIC directly as
|
||||
a HW PTP clock source, to allow time conversion in userspace and
|
||||
optionally synchronize system time with a userspace PTP stack such
|
||||
as linuxptp. For the PTP clock API, see Documentation/ptp/ptp.txt.
|
||||
|
||||
systime is the value of the system time at that moment. This
|
||||
corresponds to the value also returned via SO_TIMESTAMP[NS]. If the
|
||||
time stamp was generated by hardware, then this field is
|
||||
empty. Otherwise it is filled in if SOF_TIMESTAMPING_SOFTWARE is
|
||||
set.
|
||||
2.1.1 Transmit timestamps with MSG_ERRQUEUE
|
||||
|
||||
hwtimeraw is the original hardware time stamp. Filled in if
|
||||
SOF_TIMESTAMPING_RAW_HARDWARE is set. No assumptions about its
|
||||
relation to system time should be made.
|
||||
For transmit timestamps the outgoing packet is looped back to the
|
||||
socket's error queue with the send timestamp(s) attached. A process
|
||||
receives the timestamps by calling recvmsg() with flag MSG_ERRQUEUE
|
||||
set and with a msg_control buffer sufficiently large to receive the
|
||||
relevant metadata structures. The recvmsg call returns the original
|
||||
outgoing data packet with two ancillary messages attached.
|
||||
|
||||
hwtimetrans is always zero. This field is deprecated. It used to hold
|
||||
hw timestamps converted to system time. Instead, expose the hardware
|
||||
clock device on the NIC directly as a HW PTP clock source, to allow
|
||||
time conversion in userspace and optionally synchronize system time
|
||||
with a userspace PTP stack such as linuxptp. For the PTP clock API,
|
||||
see Documentation/ptp/ptp.txt.
|
||||
A message of cm_level SOL_IP(V6) and cm_type IP(V6)_RECVERR
|
||||
embeds a struct sock_extended_err. This defines the error type. For
|
||||
timestamps, the ee_errno field is ENOMSG. The other ancillary message
|
||||
will have cm_level SOL_SOCKET and cm_type SCM_TIMESTAMPING. This
|
||||
embeds the struct scm_timestamping.
|
||||
|
||||
|
||||
SIOCSHWTSTAMP, SIOCGHWTSTAMP:
|
||||
2.1.1.2 Timestamp types
|
||||
|
||||
The semantics of the three struct timespec are defined by field
|
||||
ee_info in the extended error structure. It contains a value of
|
||||
type SCM_TSTAMP_* to define the actual timestamp passed in
|
||||
scm_timestamping.
|
||||
|
||||
The SCM_TSTAMP_* types are 1:1 matches to the SOF_TIMESTAMPING_*
|
||||
control fields discussed previously, with one exception. For legacy
|
||||
reasons, SCM_TSTAMP_SND is equal to zero and can be set for both
|
||||
SOF_TIMESTAMPING_TX_HARDWARE and SOF_TIMESTAMPING_TX_SOFTWARE. It
|
||||
is the first if ts[2] is non-zero, the second otherwise, in which
|
||||
case the timestamp is stored in ts[0].
|
||||
|
||||
|
||||
2.1.1.3 Fragmentation
|
||||
|
||||
Fragmentation of outgoing datagrams is rare, but is possible, e.g., by
|
||||
explicitly disabling PMTU discovery. If an outgoing packet is fragmented,
|
||||
then only the first fragment is timestamped and returned to the sending
|
||||
socket.
|
||||
|
||||
|
||||
2.1.1.4 Packet Payload
|
||||
|
||||
The calling application is often not interested in receiving the whole
|
||||
packet payload that it passed to the stack originally: the socket
|
||||
error queue mechanism is just a method to piggyback the timestamp on.
|
||||
In this case, the application can choose to read datagrams with a
|
||||
smaller buffer, possibly even of length 0. The payload is truncated
|
||||
accordingly. Until the process calls recvmsg() on the error queue,
|
||||
however, the full packet is queued, taking up budget from SO_RCVBUF.
|
||||
|
||||
|
||||
2.1.1.5 Blocking Read
|
||||
|
||||
Reading from the error queue is always a non-blocking operation. To
|
||||
block waiting on a timestamp, use poll or select. poll() will return
|
||||
POLLERR in pollfd.revents if any data is ready on the error queue.
|
||||
There is no need to pass this flag in pollfd.events. This flag is
|
||||
ignored on request. See also `man 2 poll`.
|
||||
|
||||
|
||||
2.1.2 Receive timestamps
|
||||
|
||||
On reception, there is no reason to read from the socket error queue.
|
||||
The SCM_TIMESTAMPING ancillary data is sent along with the packet data
|
||||
on a normal recvmsg(). Since this is not a socket error, it is not
|
||||
accompanied by a message SOL_IP(V6)/IP(V6)_RECVERROR. In this case,
|
||||
the meaning of the three fields in struct scm_timestamping is
|
||||
implicitly defined. ts[0] holds a software timestamp if set, ts[1]
|
||||
is again deprecated and ts[2] holds a hardware timestamp if set.
|
||||
|
||||
|
||||
3. Hardware Timestamping configuration: SIOCSHWTSTAMP and SIOCGHWTSTAMP
|
||||
|
||||
Hardware time stamping must also be initialized for each device driver
|
||||
that is expected to do hardware time stamping. The parameter is defined in
|
||||
|
@ -167,8 +372,7 @@ enum {
|
|||
*/
|
||||
};
|
||||
|
||||
|
||||
DEVICE IMPLEMENTATION
|
||||
3.1 Hardware Timestamping Implementation: Device Drivers
|
||||
|
||||
A driver which supports hardware time stamping must support the
|
||||
SIOCSHWTSTAMP ioctl and update the supplied struct hwtstamp_config with
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
# To compile, from the source root
|
||||
#
|
||||
# make headers_install
|
||||
# make M=documentation
|
||||
|
||||
# kbuild trick to avoid linker error. Can be omitted if a module is built.
|
||||
obj- := dummy.o
|
||||
|
||||
# List of programs to build
|
||||
hostprogs-y := timestamping hwtstamp_config
|
||||
hostprogs-y := timestamping txtimestamp hwtstamp_config
|
||||
|
||||
# Tell kbuild to always build the programs
|
||||
always := $(hostprogs-y)
|
||||
|
||||
HOSTCFLAGS_timestamping.o += -I$(objtree)/usr/include
|
||||
HOSTCFLAGS_txtimestamp.o += -I$(objtree)/usr/include
|
||||
HOSTCFLAGS_hwtstamp_config.o += -I$(objtree)/usr/include
|
||||
|
||||
clean:
|
||||
rm -f timestamping hwtstamp_config
|
||||
rm -f timestamping txtimestamp hwtstamp_config
|
||||
|
|
|
@ -0,0 +1,470 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <asm/types.h>
|
||||
#include <error.h>
|
||||
#include <errno.h>
|
||||
#include <linux/errqueue.h>
|
||||
#include <linux/if_ether.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 <stdint.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 uint16_t dest_port = 9000;
|
||||
|
||||
static struct sockaddr_in daddr;
|
||||
static struct sockaddr_in6 daddr6;
|
||||
static struct timespec ts_prev;
|
||||
|
||||
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 ((ts_prev.tv_sec | ts_prev.tv_nsec)) {
|
||||
int64_t cur_ms, prev_ms;
|
||||
|
||||
cur_ms = (long) cur->tv_sec * 1000 * 1000;
|
||||
cur_ms += cur->tv_nsec / 1000;
|
||||
|
||||
prev_ms = (long) ts_prev.tv_sec * 1000 * 1000;
|
||||
prev_ms += ts_prev.tv_nsec / 1000;
|
||||
|
||||
fprintf(stderr, " (%+ld us)", cur_ms - prev_ms);
|
||||
}
|
||||
|
||||
ts_prev = *cur;
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
static void print_timestamp_usr(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
struct timeval tv; /* avoid dependency on -lrt */
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
ts.tv_sec = tv.tv_sec;
|
||||
ts.tv_nsec = tv.tv_usec * 1000;
|
||||
|
||||
__print_timestamp(" USR", &ts, 0, 0);
|
||||
}
|
||||
|
||||
static void print_timestamp(struct scm_timestamping *tss, int tstype,
|
||||
int tskey, int payload_len)
|
||||
{
|
||||
const char *tsname;
|
||||
|
||||
switch (tstype) {
|
||||
case SCM_TSTAMP_SCHED:
|
||||
tsname = " ENQ";
|
||||
break;
|
||||
case SCM_TSTAMP_SND:
|
||||
tsname = " SND";
|
||||
break;
|
||||
case SCM_TSTAMP_ACK:
|
||||
tsname = " ACK";
|
||||
break;
|
||||
default:
|
||||
error(1, 0, "unknown timestamp type: %u",
|
||||
tstype);
|
||||
}
|
||||
__print_timestamp(tsname, &tss->ts[0], tskey, payload_len);
|
||||
}
|
||||
|
||||
static void __poll(int fd)
|
||||
{
|
||||
struct pollfd pollfd;
|
||||
int ret;
|
||||
|
||||
memset(&pollfd, 0, sizeof(pollfd));
|
||||
pollfd.fd = fd;
|
||||
ret = poll(&pollfd, 1, 100);
|
||||
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;
|
||||
|
||||
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)) {
|
||||
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
memset(data, 0, sizeof(data));
|
||||
|
||||
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");
|
||||
|
||||
__recv_errmsg_cmsg(&msg, ret);
|
||||
|
||||
free(data);
|
||||
return ret == -1;
|
||||
}
|
||||
|
||||
static void do_test(int family, unsigned int opt)
|
||||
{
|
||||
char *buf;
|
||||
int fd, i, val, total_len;
|
||||
|
||||
if (family == IPPROTO_IPV6 && cfg_proto != SOCK_STREAM) {
|
||||
/* due to lack of checksum generation code */
|
||||
fprintf(stderr, "test: skipping datagram over IPv6\n");
|
||||
return;
|
||||
}
|
||||
|
||||
total_len = cfg_payload_len;
|
||||
if (cfg_proto == SOCK_RAW) {
|
||||
total_len += sizeof(struct udphdr);
|
||||
if (cfg_ipproto == IPPROTO_RAW)
|
||||
total_len += sizeof(struct iphdr);
|
||||
}
|
||||
|
||||
buf = malloc(total_len);
|
||||
if (!buf)
|
||||
error(1, 0, "malloc");
|
||||
|
||||
fd = socket(family, cfg_proto, cfg_ipproto);
|
||||
if (fd < 0)
|
||||
error(1, errno, "socket");
|
||||
|
||||
if (cfg_proto == SOCK_STREAM) {
|
||||
val = 1;
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
opt |= SOF_TIMESTAMPING_SOFTWARE |
|
||||
SOF_TIMESTAMPING_OPT_ID;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING,
|
||||
(char *) &opt, sizeof(opt)))
|
||||
error(1, 0, "setsockopt timestamping");
|
||||
|
||||
for (i = 0; i < cfg_num_pkts; i++) {
|
||||
memset(&ts_prev, 0, sizeof(ts_prev));
|
||||
memset(buf, 'a' + i, total_len);
|
||||
buf[total_len - 2] = '\n';
|
||||
buf[total_len - 1] = '\0';
|
||||
|
||||
if (cfg_proto == SOCK_RAW) {
|
||||
struct udphdr *udph;
|
||||
int off = 0;
|
||||
|
||||
if (cfg_ipproto == IPPROTO_RAW) {
|
||||
struct iphdr *iph = (void *) buf;
|
||||
|
||||
memset(iph, 0, sizeof(*iph));
|
||||
iph->ihl = 5;
|
||||
iph->version = 4;
|
||||
iph->ttl = 2;
|
||||
iph->daddr = daddr.sin_addr.s_addr;
|
||||
iph->protocol = IPPROTO_UDP;
|
||||
/* kernel writes saddr, csum, len */
|
||||
|
||||
off = sizeof(*iph);
|
||||
}
|
||||
|
||||
udph = (void *) buf + off;
|
||||
udph->source = ntohs(9000); /* random spoof */
|
||||
udph->dest = ntohs(dest_port);
|
||||
udph->len = ntohs(sizeof(*udph) + cfg_payload_len);
|
||||
udph->check = 0; /* not allowed for IPv6 */
|
||||
}
|
||||
|
||||
print_timestamp_usr();
|
||||
if (cfg_proto != SOCK_STREAM) {
|
||||
if (family == PF_INET)
|
||||
val = sendto(fd, buf, total_len, 0, (void *) &daddr, sizeof(daddr));
|
||||
else
|
||||
val = sendto(fd, buf, total_len, 0, (void *) &daddr6, sizeof(daddr6));
|
||||
} else {
|
||||
val = send(fd, buf, cfg_payload_len, 0);
|
||||
}
|
||||
if (val != total_len)
|
||||
error(1, errno, "send");
|
||||
|
||||
/* wait for all errors to be queued, else ACKs arrive OOO */
|
||||
usleep(50 * 1000);
|
||||
|
||||
__poll(fd);
|
||||
|
||||
while (!recv_errmsg(fd)) {}
|
||||
}
|
||||
|
||||
if (close(fd))
|
||||
error(1, errno, "close");
|
||||
|
||||
free(buf);
|
||||
usleep(400 * 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"
|
||||
" -l N: send N bytes at a time\n"
|
||||
" -r: use raw\n"
|
||||
" -R: use raw (IP_HDRINCL)\n"
|
||||
" -p N: connect to port N\n"
|
||||
" -u: use udp\n",
|
||||
filepath);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void parse_opt(int argc, char **argv)
|
||||
{
|
||||
int proto_count = 0;
|
||||
char c;
|
||||
|
||||
while ((c = getopt(argc, argv, "46hl:p:rRu")) != -1) {
|
||||
switch (c) {
|
||||
case '4':
|
||||
do_ipv6 = 0;
|
||||
break;
|
||||
case '6':
|
||||
do_ipv4 = 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 'l':
|
||||
cfg_payload_len = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
case 'p':
|
||||
dest_port = strtoul(optarg, NULL, 10);
|
||||
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 -r, -R or -u, not multiple");
|
||||
|
||||
if (optind != argc - 1)
|
||||
error(1, 0, "missing required hostname argument");
|
||||
}
|
||||
|
||||
static void resolve_hostname(const char *hostname)
|
||||
{
|
||||
struct addrinfo *addrs, *cur;
|
||||
int have_ipv4 = 0, have_ipv6 = 0;
|
||||
|
||||
if (getaddrinfo(hostname, NULL, NULL, &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);
|
||||
|
||||
do_ipv4 &= have_ipv4;
|
||||
do_ipv6 &= have_ipv6;
|
||||
}
|
||||
|
||||
static void do_main(int family)
|
||||
{
|
||||
fprintf(stderr, "family: %s\n",
|
||||
family == PF_INET ? "INET" : "INET6");
|
||||
|
||||
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)
|
||||
do_main(PF_INET);
|
||||
if (do_ipv6)
|
||||
do_main(PF_INET6);
|
||||
|
||||
return 0;
|
||||
}
|
Загрузка…
Ссылка в новой задаче