Linuxでパケット受信契機のタイムスタンプ採取方法。
ユーザーアプリからはsetsockoptでSO_TIMESTAMPを設定しておき、recvmsgで受信する都度せっせとCMSG_DATA(cmsg)経由で取得する形での仕組みが用意されている。具体的なコーディングサンプルだと
char inbuf[BUFSIZ];な感じだ。
char cmsgbuf[CMSG_SPACE(sizeof(struct timeval))];
struct cmsghdr *cmsg;
struct msghdr msghdr;
struct iovec msg_iov;
struct timeval *pTime, tv;
const int on = 1;
setsockopt(sock, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof(on));
msg_iov.iov_base = inbuf;
msg_iov.iov_len = sizeof(inbuf);
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
msghdr.msg_iov = &msg_iov;
msghdr.msg_iovlen = 1;
msghdr.msg_control = cmsgbuf;
msghdr.msg_controllen = sizeof(cmsgbuf);
while ( recvmsg(sock, &msghdr, ZERO) ){
/* Receive auxiliary data in msgh */
for (cmsg = CMSG_FIRSTHDR(&msghdr);
cmsg != NULL;
cmsg = CMSG_NXTHDR(&msghdr,cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET
&& cmsg->cmsg_type == SO_TIMESTAMP) {
pTime = (struct timeval *) CMSG_DATA(cmsg);
printf("cmsg time=%ld,%ld\n", pTime->tv_sec, pTime->tv_usec);
break;
}
}
}
が、実際にタイムスタンプを採取してみると微妙にタイムラグが気になったので調べてみたところ、sock_recv_timestampを呼んでタイムスタンプの契機を与えているのはrecvmsg。て事は、受信データがバッファがコピーされた時間であり、実際にパケットが届いた時間とは言い切れない。
というわけで、取り敢えず今回のターゲットはUDP限定なので、カーネルのnet/ipv4/udp.cに
*** udp.c.orig 2007-07-07 13:52:58.000000000 +0900を追加。あとはリビルドすればめでたしめでたし。
--- udp.c 2007-09-05 09:47:58.000000000 +0900
***************
*** 1231,1236 ****
--- 1231,1243 ----
if (sk != NULL) {
int ret = udp_queue_rcv_skb(sk, skb);
+ // for UDP TIMESTAMP TEST 2007/09/05
+ if (sock_flag(sk, SOCK_RCVTSTAMP)) {
+ struct timeval stamp;
+ do_gettimeofday(&stamp);
+ skb_set_timestamp(skb, &stamp);
+ }
+ // for UDP TIMESTAMP TEST 2007/09/05
sock_put(sk);
/* a return value > 0 means to resubmit the input, but
なお、上記パッチはカーネル版数2.6.20.xあたりで確認してあるが、言うまでもなく無保証なのでそこんとこよろしく。
【参照】
●JM Project (Japanese) http://www.linux.or.jp/JM/
┣Manpage of RECV
┗Manpage of CMSG