Commit 20d49473 authored by Patrick Ohly's avatar Patrick Ohly Committed by David S. Miller
Browse files

net: socket infrastructure for SO_TIMESTAMPING



The overlap with the old SO_TIMESTAMP[NS] options is handled so
that time stamping in software (net_enable_timestamp()) is
enabled when SO_TIMESTAMP[NS] and/or SO_TIMESTAMPING_RX_SOFTWARE
is set.  It's disabled if all of these are off.

Signed-off-by: default avatarPatrick Ohly <patrick.ohly@intel.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent ac45f602
Loading
Loading
Loading
Loading
+41 −3
Original line number Diff line number Diff line
@@ -158,7 +158,7 @@ struct sock_common {
  *	@sk_allocation: allocation mode
  *	@sk_sndbuf: size of send buffer in bytes
  *	@sk_flags: %SO_LINGER (l_onoff), %SO_BROADCAST, %SO_KEEPALIVE,
  *		   %SO_OOBINLINE settings
  *		   %SO_OOBINLINE settings, %SO_TIMESTAMPING settings
  *	@sk_no_check: %SO_NO_CHECK setting, wether or not checkup packets
  *	@sk_route_caps: route capabilities (e.g. %NETIF_F_TSO)
  *	@sk_gso_type: GSO type (e.g. %SKB_GSO_TCPV4)
@@ -488,6 +488,13 @@ enum sock_flags {
	SOCK_RCVTSTAMPNS, /* %SO_TIMESTAMPNS setting */
	SOCK_LOCALROUTE, /* route locally only, %SO_DONTROUTE setting */
	SOCK_QUEUE_SHRUNK, /* write queue has been shrunk recently */
	SOCK_TIMESTAMPING_TX_HARDWARE,  /* %SOF_TIMESTAMPING_TX_HARDWARE */
	SOCK_TIMESTAMPING_TX_SOFTWARE,  /* %SOF_TIMESTAMPING_TX_SOFTWARE */
	SOCK_TIMESTAMPING_RX_HARDWARE,  /* %SOF_TIMESTAMPING_RX_HARDWARE */
	SOCK_TIMESTAMPING_RX_SOFTWARE,  /* %SOF_TIMESTAMPING_RX_SOFTWARE */
	SOCK_TIMESTAMPING_SOFTWARE,     /* %SOF_TIMESTAMPING_SOFTWARE */
	SOCK_TIMESTAMPING_RAW_HARDWARE, /* %SOF_TIMESTAMPING_RAW_HARDWARE */
	SOCK_TIMESTAMPING_SYS_HARDWARE, /* %SOF_TIMESTAMPING_SYS_HARDWARE */
};

static inline void sock_copy_flags(struct sock *nsk, struct sock *osk)
@@ -1346,13 +1353,44 @@ static __inline__ void
sock_recv_timestamp(struct msghdr *msg, struct sock *sk, struct sk_buff *skb)
{
	ktime_t kt = skb->tstamp;
	struct skb_shared_hwtstamps *hwtstamps = skb_hwtstamps(skb);

	if (sock_flag(sk, SOCK_RCVTSTAMP))
	/*
	 * generate control messages if
	 * - receive time stamping in software requested (SOCK_RCVTSTAMP
	 *   or SOCK_TIMESTAMPING_RX_SOFTWARE)
	 * - software time stamp available and wanted
	 *   (SOCK_TIMESTAMPING_SOFTWARE)
	 * - hardware time stamps available and wanted
	 *   (SOCK_TIMESTAMPING_SYS_HARDWARE or
	 *   SOCK_TIMESTAMPING_RAW_HARDWARE)
	 */
	if (sock_flag(sk, SOCK_RCVTSTAMP) ||
	    sock_flag(sk, SOCK_TIMESTAMPING_RX_SOFTWARE) ||
	    (kt.tv64 && sock_flag(sk, SOCK_TIMESTAMPING_SOFTWARE)) ||
	    (hwtstamps->hwtstamp.tv64 &&
	     sock_flag(sk, SOCK_TIMESTAMPING_RAW_HARDWARE)) ||
	    (hwtstamps->syststamp.tv64 &&
	     sock_flag(sk, SOCK_TIMESTAMPING_SYS_HARDWARE)))
		__sock_recv_timestamp(msg, sk, skb);
	else
		sk->sk_stamp = kt;
}

/**
 * sock_tx_timestamp - checks whether the outgoing packet is to be time stamped
 * @msg:	outgoing packet
 * @sk:		socket sending this packet
 * @shtx:	filled with instructions for time stamping
 *
 * Currently only depends on SOCK_TIMESTAMPING* flags. Returns error code if
 * parameters are invalid.
 */
extern int sock_tx_timestamp(struct msghdr *msg,
			     struct sock *sk,
			     union skb_shared_tx *shtx);


/**
 * sk_eat_skb - Release a skb if it is no longer needed
 * @sk: socket to eat this skb from
@@ -1421,7 +1459,7 @@ static inline struct sock *skb_steal_sock(struct sk_buff *skb)
	return NULL;
}

extern void sock_enable_timestamp(struct sock *sk);
extern void sock_enable_timestamp(struct sock *sk, int flag);
extern int sock_get_timestamp(struct sock *, struct timeval __user *);
extern int sock_get_timestampns(struct sock *, struct timespec __user *);

+12 −7
Original line number Diff line number Diff line
@@ -216,7 +216,7 @@ Efault:
int put_cmsg_compat(struct msghdr *kmsg, int level, int type, int len, void *data)
{
	struct compat_timeval ctv;
	struct compat_timespec cts;
	struct compat_timespec cts[3];
	struct compat_cmsghdr __user *cm = (struct compat_cmsghdr __user *) kmsg->msg_control;
	struct compat_cmsghdr cmhdr;
	int cmlen;
@@ -233,12 +233,17 @@ int put_cmsg_compat(struct msghdr *kmsg, int level, int type, int len, void *dat
		data = &ctv;
		len = sizeof(ctv);
	}
	if (level == SOL_SOCKET && type == SCM_TIMESTAMPNS) {
	if (level == SOL_SOCKET &&
	    (type == SCM_TIMESTAMPNS || type == SCM_TIMESTAMPING)) {
		int count = type == SCM_TIMESTAMPNS ? 1 : 3;
		int i;
		struct timespec *ts = (struct timespec *)data;
		cts.tv_sec = ts->tv_sec;
		cts.tv_nsec = ts->tv_nsec;
		for (i = 0; i < count; i++) {
			cts[i].tv_sec = ts[i].tv_sec;
			cts[i].tv_nsec = ts[i].tv_nsec;
		}
		data = &cts;
		len = sizeof(cts);
		len = sizeof(cts[0]) * count;
	}

	cmlen = CMSG_COMPAT_LEN(len);
@@ -455,7 +460,7 @@ int compat_sock_get_timestamp(struct sock *sk, struct timeval __user *userstamp)
	struct timeval tv;

	if (!sock_flag(sk, SOCK_TIMESTAMP))
		sock_enable_timestamp(sk);
		sock_enable_timestamp(sk, SOCK_TIMESTAMP);
	tv = ktime_to_timeval(sk->sk_stamp);
	if (tv.tv_sec == -1)
		return err;
@@ -479,7 +484,7 @@ int compat_sock_get_timestampns(struct sock *sk, struct timespec __user *usersta
	struct timespec ts;

	if (!sock_flag(sk, SOCK_TIMESTAMP))
		sock_enable_timestamp(sk);
		sock_enable_timestamp(sk, SOCK_TIMESTAMP);
	ts = ktime_to_timespec(sk->sk_stamp);
	if (ts.tv_sec == -1)
		return err;
+69 −12
Original line number Diff line number Diff line
@@ -120,6 +120,7 @@
#include <net/net_namespace.h>
#include <net/request_sock.h>
#include <net/sock.h>
#include <linux/net_tstamp.h>
#include <net/xfrm.h>
#include <linux/ipsec.h>

@@ -255,13 +256,16 @@ static void sock_warn_obsolete_bsdism(const char *name)
	}
}

static void sock_disable_timestamp(struct sock *sk)
static void sock_disable_timestamp(struct sock *sk, int flag)
{
	if (sock_flag(sk, SOCK_TIMESTAMP)) {
		sock_reset_flag(sk, SOCK_TIMESTAMP);
	if (sock_flag(sk, flag)) {
		sock_reset_flag(sk, flag);
		if (!sock_flag(sk, SOCK_TIMESTAMP) &&
		    !sock_flag(sk, SOCK_TIMESTAMPING_RX_SOFTWARE)) {
			net_disable_timestamp();
		}
	}
}


int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
@@ -614,13 +618,38 @@ set_rcvbuf:
			else
				sock_set_flag(sk, SOCK_RCVTSTAMPNS);
			sock_set_flag(sk, SOCK_RCVTSTAMP);
			sock_enable_timestamp(sk);
			sock_enable_timestamp(sk, SOCK_TIMESTAMP);
		} else {
			sock_reset_flag(sk, SOCK_RCVTSTAMP);
			sock_reset_flag(sk, SOCK_RCVTSTAMPNS);
		}
		break;

	case SO_TIMESTAMPING:
		if (val & ~SOF_TIMESTAMPING_MASK) {
			ret = EINVAL;
			break;
		}
		sock_valbool_flag(sk, SOCK_TIMESTAMPING_TX_HARDWARE,
				  val & SOF_TIMESTAMPING_TX_HARDWARE);
		sock_valbool_flag(sk, SOCK_TIMESTAMPING_TX_SOFTWARE,
				  val & SOF_TIMESTAMPING_TX_SOFTWARE);
		sock_valbool_flag(sk, SOCK_TIMESTAMPING_RX_HARDWARE,
				  val & SOF_TIMESTAMPING_RX_HARDWARE);
		if (val & SOF_TIMESTAMPING_RX_SOFTWARE)
			sock_enable_timestamp(sk,
					      SOCK_TIMESTAMPING_RX_SOFTWARE);
		else
			sock_disable_timestamp(sk,
					       SOCK_TIMESTAMPING_RX_SOFTWARE);
		sock_valbool_flag(sk, SOCK_TIMESTAMPING_SOFTWARE,
				  val & SOF_TIMESTAMPING_SOFTWARE);
		sock_valbool_flag(sk, SOCK_TIMESTAMPING_SYS_HARDWARE,
				  val & SOF_TIMESTAMPING_SYS_HARDWARE);
		sock_valbool_flag(sk, SOCK_TIMESTAMPING_RAW_HARDWARE,
				  val & SOF_TIMESTAMPING_RAW_HARDWARE);
		break;

	case SO_RCVLOWAT:
		if (val < 0)
			val = INT_MAX;
@@ -768,6 +797,24 @@ int sock_getsockopt(struct socket *sock, int level, int optname,
		v.val = sock_flag(sk, SOCK_RCVTSTAMPNS);
		break;

	case SO_TIMESTAMPING:
		v.val = 0;
		if (sock_flag(sk, SOCK_TIMESTAMPING_TX_HARDWARE))
			v.val |= SOF_TIMESTAMPING_TX_HARDWARE;
		if (sock_flag(sk, SOCK_TIMESTAMPING_TX_SOFTWARE))
			v.val |= SOF_TIMESTAMPING_TX_SOFTWARE;
		if (sock_flag(sk, SOCK_TIMESTAMPING_RX_HARDWARE))
			v.val |= SOF_TIMESTAMPING_RX_HARDWARE;
		if (sock_flag(sk, SOCK_TIMESTAMPING_RX_SOFTWARE))
			v.val |= SOF_TIMESTAMPING_RX_SOFTWARE;
		if (sock_flag(sk, SOCK_TIMESTAMPING_SOFTWARE))
			v.val |= SOF_TIMESTAMPING_SOFTWARE;
		if (sock_flag(sk, SOCK_TIMESTAMPING_SYS_HARDWARE))
			v.val |= SOF_TIMESTAMPING_SYS_HARDWARE;
		if (sock_flag(sk, SOCK_TIMESTAMPING_RAW_HARDWARE))
			v.val |= SOF_TIMESTAMPING_RAW_HARDWARE;
		break;

	case SO_RCVTIMEO:
		lv=sizeof(struct timeval);
		if (sk->sk_rcvtimeo == MAX_SCHEDULE_TIMEOUT) {
@@ -969,7 +1016,8 @@ void sk_free(struct sock *sk)
		rcu_assign_pointer(sk->sk_filter, NULL);
	}

	sock_disable_timestamp(sk);
	sock_disable_timestamp(sk, SOCK_TIMESTAMP);
	sock_disable_timestamp(sk, SOCK_TIMESTAMPING_RX_SOFTWARE);

	if (atomic_read(&sk->sk_omem_alloc))
		printk(KERN_DEBUG "%s: optmem leakage (%d bytes) detected.\n",
@@ -1787,7 +1835,7 @@ int sock_get_timestamp(struct sock *sk, struct timeval __user *userstamp)
{
	struct timeval tv;
	if (!sock_flag(sk, SOCK_TIMESTAMP))
		sock_enable_timestamp(sk);
		sock_enable_timestamp(sk, SOCK_TIMESTAMP);
	tv = ktime_to_timeval(sk->sk_stamp);
	if (tv.tv_sec == -1)
		return -ENOENT;
@@ -1803,7 +1851,7 @@ int sock_get_timestampns(struct sock *sk, struct timespec __user *userstamp)
{
	struct timespec ts;
	if (!sock_flag(sk, SOCK_TIMESTAMP))
		sock_enable_timestamp(sk);
		sock_enable_timestamp(sk, SOCK_TIMESTAMP);
	ts = ktime_to_timespec(sk->sk_stamp);
	if (ts.tv_sec == -1)
		return -ENOENT;
@@ -1815,10 +1863,19 @@ int sock_get_timestampns(struct sock *sk, struct timespec __user *userstamp)
}
EXPORT_SYMBOL(sock_get_timestampns);

void sock_enable_timestamp(struct sock *sk)
void sock_enable_timestamp(struct sock *sk, int flag)
{
	if (!sock_flag(sk, SOCK_TIMESTAMP)) {
		sock_set_flag(sk, SOCK_TIMESTAMP);
	if (!sock_flag(sk, flag)) {
		sock_set_flag(sk, flag);
		/*
		 * we just set one of the two flags which require net
		 * time stamping, but time stamping might have been on
		 * already because of the other one
		 */
		if (!sock_flag(sk,
				flag == SOCK_TIMESTAMP ?
				SOCK_TIMESTAMPING_RX_SOFTWARE :
				SOCK_TIMESTAMP))
			net_enable_timestamp();
	}
}
+64 −20
Original line number Diff line number Diff line
@@ -545,6 +545,18 @@ void sock_release(struct socket *sock)
	sock->file = NULL;
}

int sock_tx_timestamp(struct msghdr *msg, struct sock *sk,
		      union skb_shared_tx *shtx)
{
	shtx->flags = 0;
	if (sock_flag(sk, SOCK_TIMESTAMPING_TX_HARDWARE))
		shtx->hardware = 1;
	if (sock_flag(sk, SOCK_TIMESTAMPING_TX_SOFTWARE))
		shtx->software = 1;
	return 0;
}
EXPORT_SYMBOL(sock_tx_timestamp);

static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,
				 struct msghdr *msg, size_t size)
{
@@ -595,33 +607,65 @@ int kernel_sendmsg(struct socket *sock, struct msghdr *msg,
	return result;
}

static int ktime2ts(ktime_t kt, struct timespec *ts)
{
	if (kt.tv64) {
		*ts = ktime_to_timespec(kt);
		return 1;
	} else {
		return 0;
	}
}

/*
 * called from sock_recv_timestamp() if sock_flag(sk, SOCK_RCVTSTAMP)
 */
void __sock_recv_timestamp(struct msghdr *msg, struct sock *sk,
	struct sk_buff *skb)
{
	ktime_t kt = skb->tstamp;
	int need_software_tstamp = sock_flag(sk, SOCK_RCVTSTAMP);
	struct timespec ts[3];
	int empty = 1;
	struct skb_shared_hwtstamps *shhwtstamps =
		skb_hwtstamps(skb);

	if (!sock_flag(sk, SOCK_RCVTSTAMPNS)) {
		struct timeval tv;
	/* Race occurred between timestamp enabling and packet
	   receiving.  Fill in the current time for now. */
		if (kt.tv64 == 0)
			kt = ktime_get_real();
		skb->tstamp = kt;
		tv = ktime_to_timeval(kt);
		put_cmsg(msg, SOL_SOCKET, SCM_TIMESTAMP, sizeof(tv), &tv);
	if (need_software_tstamp && skb->tstamp.tv64 == 0)
		__net_timestamp(skb);

	if (need_software_tstamp) {
		if (!sock_flag(sk, SOCK_RCVTSTAMPNS)) {
			struct timeval tv;
			skb_get_timestamp(skb, &tv);
			put_cmsg(msg, SOL_SOCKET, SCM_TIMESTAMP,
				 sizeof(tv), &tv);
		} else {
			struct timespec ts;
		/* Race occurred between timestamp enabling and packet
		   receiving.  Fill in the current time for now. */
		if (kt.tv64 == 0)
			kt = ktime_get_real();
		skb->tstamp = kt;
		ts = ktime_to_timespec(kt);
		put_cmsg(msg, SOL_SOCKET, SCM_TIMESTAMPNS, sizeof(ts), &ts);
			skb_get_timestampns(skb, &ts);
			put_cmsg(msg, SOL_SOCKET, SCM_TIMESTAMPNS,
				 sizeof(ts), &ts);
		}
	}


	memset(ts, 0, sizeof(ts));
	if (skb->tstamp.tv64 &&
	    sock_flag(sk, SOCK_TIMESTAMPING_SOFTWARE)) {
		skb_get_timestampns(skb, ts + 0);
		empty = 0;
	}
	if (shhwtstamps) {
		if (sock_flag(sk, SOCK_TIMESTAMPING_SYS_HARDWARE) &&
		    ktime2ts(shhwtstamps->syststamp, ts + 1))
			empty = 0;
		if (sock_flag(sk, SOCK_TIMESTAMPING_RAW_HARDWARE) &&
		    ktime2ts(shhwtstamps->hwtstamp, ts + 2))
			empty = 0;
	}
	if (!empty)
		put_cmsg(msg, SOL_SOCKET,
			 SCM_TIMESTAMPING, sizeof(ts), &ts);
}

EXPORT_SYMBOL_GPL(__sock_recv_timestamp);