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: Patrick Ohly <patrick.ohly@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Patrick Ohly 2009-02-12 05:03:38 +00:00 committed by David S. Miller
parent ac45f602ee
commit 20d4947353
4 changed files with 185 additions and 41 deletions

View file

@ -158,7 +158,7 @@ struct sock_common {
* @sk_allocation: allocation mode * @sk_allocation: allocation mode
* @sk_sndbuf: size of send buffer in bytes * @sk_sndbuf: size of send buffer in bytes
* @sk_flags: %SO_LINGER (l_onoff), %SO_BROADCAST, %SO_KEEPALIVE, * @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_no_check: %SO_NO_CHECK setting, wether or not checkup packets
* @sk_route_caps: route capabilities (e.g. %NETIF_F_TSO) * @sk_route_caps: route capabilities (e.g. %NETIF_F_TSO)
* @sk_gso_type: GSO type (e.g. %SKB_GSO_TCPV4) * @sk_gso_type: GSO type (e.g. %SKB_GSO_TCPV4)
@ -488,6 +488,13 @@ enum sock_flags {
SOCK_RCVTSTAMPNS, /* %SO_TIMESTAMPNS setting */ SOCK_RCVTSTAMPNS, /* %SO_TIMESTAMPNS setting */
SOCK_LOCALROUTE, /* route locally only, %SO_DONTROUTE setting */ SOCK_LOCALROUTE, /* route locally only, %SO_DONTROUTE setting */
SOCK_QUEUE_SHRUNK, /* write queue has been shrunk recently */ 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) 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) sock_recv_timestamp(struct msghdr *msg, struct sock *sk, struct sk_buff *skb)
{ {
ktime_t kt = skb->tstamp; 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); __sock_recv_timestamp(msg, sk, skb);
else else
sk->sk_stamp = kt; 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_eat_skb - Release a skb if it is no longer needed
* @sk: socket to eat this skb from * @sk: socket to eat this skb from
@ -1421,7 +1459,7 @@ static inline struct sock *skb_steal_sock(struct sk_buff *skb)
return NULL; 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_timestamp(struct sock *, struct timeval __user *);
extern int sock_get_timestampns(struct sock *, struct timespec __user *); extern int sock_get_timestampns(struct sock *, struct timespec __user *);

View file

@ -216,7 +216,7 @@ int cmsghdr_from_user_compat_to_kern(struct msghdr *kmsg, struct sock *sk,
int put_cmsg_compat(struct msghdr *kmsg, int level, int type, int len, void *data) int put_cmsg_compat(struct msghdr *kmsg, int level, int type, int len, void *data)
{ {
struct compat_timeval ctv; 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 __user *cm = (struct compat_cmsghdr __user *) kmsg->msg_control;
struct compat_cmsghdr cmhdr; struct compat_cmsghdr cmhdr;
int cmlen; int cmlen;
@ -233,12 +233,17 @@ int put_cmsg_compat(struct msghdr *kmsg, int level, int type, int len, void *dat
data = &ctv; data = &ctv;
len = sizeof(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; struct timespec *ts = (struct timespec *)data;
cts.tv_sec = ts->tv_sec; for (i = 0; i < count; i++) {
cts.tv_nsec = ts->tv_nsec; cts[i].tv_sec = ts[i].tv_sec;
cts[i].tv_nsec = ts[i].tv_nsec;
}
data = &cts; data = &cts;
len = sizeof(cts); len = sizeof(cts[0]) * count;
} }
cmlen = CMSG_COMPAT_LEN(len); cmlen = CMSG_COMPAT_LEN(len);
@ -455,7 +460,7 @@ int compat_sock_get_timestamp(struct sock *sk, struct timeval __user *userstamp)
struct timeval tv; struct timeval tv;
if (!sock_flag(sk, SOCK_TIMESTAMP)) if (!sock_flag(sk, SOCK_TIMESTAMP))
sock_enable_timestamp(sk); sock_enable_timestamp(sk, SOCK_TIMESTAMP);
tv = ktime_to_timeval(sk->sk_stamp); tv = ktime_to_timeval(sk->sk_stamp);
if (tv.tv_sec == -1) if (tv.tv_sec == -1)
return err; return err;
@ -479,7 +484,7 @@ int compat_sock_get_timestampns(struct sock *sk, struct timespec __user *usersta
struct timespec ts; struct timespec ts;
if (!sock_flag(sk, SOCK_TIMESTAMP)) if (!sock_flag(sk, SOCK_TIMESTAMP))
sock_enable_timestamp(sk); sock_enable_timestamp(sk, SOCK_TIMESTAMP);
ts = ktime_to_timespec(sk->sk_stamp); ts = ktime_to_timespec(sk->sk_stamp);
if (ts.tv_sec == -1) if (ts.tv_sec == -1)
return err; return err;

View file

@ -120,6 +120,7 @@
#include <net/net_namespace.h> #include <net/net_namespace.h>
#include <net/request_sock.h> #include <net/request_sock.h>
#include <net/sock.h> #include <net/sock.h>
#include <linux/net_tstamp.h>
#include <net/xfrm.h> #include <net/xfrm.h>
#include <linux/ipsec.h> #include <linux/ipsec.h>
@ -255,11 +256,14 @@ 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)) { if (sock_flag(sk, flag)) {
sock_reset_flag(sk, SOCK_TIMESTAMP); sock_reset_flag(sk, flag);
net_disable_timestamp(); if (!sock_flag(sk, SOCK_TIMESTAMP) &&
!sock_flag(sk, SOCK_TIMESTAMPING_RX_SOFTWARE)) {
net_disable_timestamp();
}
} }
} }
@ -614,13 +618,38 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
else else
sock_set_flag(sk, SOCK_RCVTSTAMPNS); sock_set_flag(sk, SOCK_RCVTSTAMPNS);
sock_set_flag(sk, SOCK_RCVTSTAMP); sock_set_flag(sk, SOCK_RCVTSTAMP);
sock_enable_timestamp(sk); sock_enable_timestamp(sk, SOCK_TIMESTAMP);
} else { } else {
sock_reset_flag(sk, SOCK_RCVTSTAMP); sock_reset_flag(sk, SOCK_RCVTSTAMP);
sock_reset_flag(sk, SOCK_RCVTSTAMPNS); sock_reset_flag(sk, SOCK_RCVTSTAMPNS);
} }
break; 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: case SO_RCVLOWAT:
if (val < 0) if (val < 0)
val = INT_MAX; val = INT_MAX;
@ -768,6 +797,24 @@ int sock_getsockopt(struct socket *sock, int level, int optname,
v.val = sock_flag(sk, SOCK_RCVTSTAMPNS); v.val = sock_flag(sk, SOCK_RCVTSTAMPNS);
break; 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: case SO_RCVTIMEO:
lv=sizeof(struct timeval); lv=sizeof(struct timeval);
if (sk->sk_rcvtimeo == MAX_SCHEDULE_TIMEOUT) { if (sk->sk_rcvtimeo == MAX_SCHEDULE_TIMEOUT) {
@ -969,7 +1016,8 @@ void sk_free(struct sock *sk)
rcu_assign_pointer(sk->sk_filter, NULL); 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)) if (atomic_read(&sk->sk_omem_alloc))
printk(KERN_DEBUG "%s: optmem leakage (%d bytes) detected.\n", 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; struct timeval tv;
if (!sock_flag(sk, SOCK_TIMESTAMP)) if (!sock_flag(sk, SOCK_TIMESTAMP))
sock_enable_timestamp(sk); sock_enable_timestamp(sk, SOCK_TIMESTAMP);
tv = ktime_to_timeval(sk->sk_stamp); tv = ktime_to_timeval(sk->sk_stamp);
if (tv.tv_sec == -1) if (tv.tv_sec == -1)
return -ENOENT; return -ENOENT;
@ -1803,7 +1851,7 @@ int sock_get_timestampns(struct sock *sk, struct timespec __user *userstamp)
{ {
struct timespec ts; struct timespec ts;
if (!sock_flag(sk, SOCK_TIMESTAMP)) if (!sock_flag(sk, SOCK_TIMESTAMP))
sock_enable_timestamp(sk); sock_enable_timestamp(sk, SOCK_TIMESTAMP);
ts = ktime_to_timespec(sk->sk_stamp); ts = ktime_to_timespec(sk->sk_stamp);
if (ts.tv_sec == -1) if (ts.tv_sec == -1)
return -ENOENT; return -ENOENT;
@ -1815,11 +1863,20 @@ int sock_get_timestampns(struct sock *sk, struct timespec __user *userstamp)
} }
EXPORT_SYMBOL(sock_get_timestampns); 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)) { if (!sock_flag(sk, flag)) {
sock_set_flag(sk, SOCK_TIMESTAMP); sock_set_flag(sk, flag);
net_enable_timestamp(); /*
* 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();
} }
} }

View file

@ -545,6 +545,18 @@ void sock_release(struct socket *sock)
sock->file = NULL; 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, static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size) struct msghdr *msg, size_t size)
{ {
@ -595,33 +607,65 @@ int kernel_sendmsg(struct socket *sock, struct msghdr *msg,
return result; 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) * called from sock_recv_timestamp() if sock_flag(sk, SOCK_RCVTSTAMP)
*/ */
void __sock_recv_timestamp(struct msghdr *msg, struct sock *sk, void __sock_recv_timestamp(struct msghdr *msg, struct sock *sk,
struct sk_buff *skb) 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)) { /* Race occurred between timestamp enabling and packet
struct timeval tv; receiving. Fill in the current time for now. */
/* Race occurred between timestamp enabling and packet if (need_software_tstamp && skb->tstamp.tv64 == 0)
receiving. Fill in the current time for now. */ __net_timestamp(skb);
if (kt.tv64 == 0)
kt = ktime_get_real(); if (need_software_tstamp) {
skb->tstamp = kt; if (!sock_flag(sk, SOCK_RCVTSTAMPNS)) {
tv = ktime_to_timeval(kt); struct timeval tv;
put_cmsg(msg, SOL_SOCKET, SCM_TIMESTAMP, sizeof(tv), &tv); skb_get_timestamp(skb, &tv);
} else { put_cmsg(msg, SOL_SOCKET, SCM_TIMESTAMP,
struct timespec ts; sizeof(tv), &tv);
/* Race occurred between timestamp enabling and packet } else {
receiving. Fill in the current time for now. */ struct timespec ts;
if (kt.tv64 == 0) skb_get_timestampns(skb, &ts);
kt = ktime_get_real(); put_cmsg(msg, SOL_SOCKET, SCM_TIMESTAMPNS,
skb->tstamp = kt; sizeof(ts), &ts);
ts = ktime_to_timespec(kt); }
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); EXPORT_SYMBOL_GPL(__sock_recv_timestamp);