ntpd packet logのための覚え書き
これは何?
-
ntpdが送受信するパケットを記録したいと仮定する。
-
ntpdのログ機構では、どうやらそこまで細かいログを取ることが出来ない。
-
そこでちょっとソースを見てみる。
-
覚え書きなのできっと勘違いや誤りがあるに違いないです。先にごめんなさいしておきます。
-
追記、受信側だけで長くなったので送信側は稿を改めます。
NTPd
-
まず、ntpd本家とgithubリポジトリはこちら。
-
FreeBSD 12.1-RELEASEでは、/usr/src/usr.sbin/ntp(実態はほぼ/usr/src/contrib/ntp側にある)としてntp.orgのntpdを同梱している。
-
NTPの他の実装としては次のふたつが有名処か。
-
ntpdがパケットを受信する流れ
-
ntpd/ntpd.cのmain()はこの辺。
-
main()ではコマンドライン引数をparseした後、ntpdmain()を呼び出す。#ifdef NO_MAIN_ALLOWED CALL(ntpd,"ntpd",ntpdmain); #else/* !NO_MAIN_ALLOWED follows */ #ifndef SYS_WINNT int main( int argc, char *argv[] ) { return ntpdmain(argc, argv); } #endif /* !SYS_WINNT */ #endif /* !NO_MAIN_ALLOWED */ -
ntpdmain()ではいろいろと初期設定をした後に他にやることがなくなると(?)、io_handler()を呼び出す。if (!was_alarmed && !has_full_recv_buffer()) { /* * Nothing to do. Wait for something. */ io_handler(); } -
io_handler()はntpd/ntp_io.cにあってこの辺から始まり、input_handler_scan()を呼び出す。if (nfound > 0) { l_fp ts; get_systime(&ts); input_handler_scan(&ts, &rdfdes); } else if (nfound == -1 && errno != EINTR) { msyslog(LOG_ERR, "select() error: %m"); } # -
シグナルハンドラから呼ばれるinput_handler()もあるが、結局
input_handler_scan()を呼んでいる。} if (n > 0) input_handler_scan(cts, &fds); }
-
input_handler_scan()では、まずread_refclock_packet()を呼び出してReference Clockをチェックする。これがrefclockすなわち精密な時刻情報を提供する装置(通常ローカルにある)とのやり取りを担当するのであろう。#ifdef REFCLOCK /* * Check out the reference clocks first, if any */ for (rp = refio; rp != NULL; rp = rp->next) { fd = rp->fd; if (!FD_ISSET(fd, pfds)) continue; buflen = read_refclock_packet(fd, rp, ts); -
次いで
input_handler_scan()はread_network_packet()を呼び出してNetwork packetをチェックする。こちらはおそらくNTPサーバとの間やその他の通信を担当するのであろう。if (FD_ISSET(fd, pfds)) do { buflen = read_network_packet( fd, ep, ts); } while (buflen > 0); -
read_network_packet()では、struct recvbuf *rbにメモリ領域を確保してrecvfromないしrecvmsgからのデータを記録する。#ifndef HAVE_PACKET_TIMESTAMP rb->recv_length = recvfrom(fd, (char *)&rb->recv_space, sizeof(rb->recv_space), 0, &rb->recv_srcadr.sa, &fromlen); #else iovec.iov_base = &rb->recv_space; iovec.iov_len = sizeof(rb->recv_space); msghdr.msg_name = &rb->recv_srcadr; msghdr.msg_namelen = fromlen; msghdr.msg_iov = &iovec; msghdr.msg_iovlen = 1; msghdr.msg_control = (void *)&control; msghdr.msg_controllen = sizeof(control); msghdr.msg_flags = 0; rb->recv_length = recvmsg(fd, &msghdr, 0); #endif
-
さらに
add_full_recv_buffer()を呼び出して受信したデータを受信バッファに追加する。この時、rb->receiverにreceiveを設定している点に注意。この要素はコールバック関数で、receiveはntpd/ntp_proto.cで定義された関数である(後述)。rb->recv_time = ts; rb->receiver = receive; add_full_recv_buffer(rb); itf->received++; packets_received++; return (buflen); }
-
add_full_recv_buffer()はlibntp/recvbuff.cにあって、渡されたバッファ
rbをfull_recv_fifo変数に追加する。void add_full_recv_buffer(recvbuf_t *rb) { if (rb == NULL) { msyslog(LOG_ERR, "add_full_recv_buffer received NULL buffer"); return; } LOCK(); LINK_FIFO(full_recv_fifo, rb, link); full_recvbufs++; UNLOCK(); } -
recvbuf_t型ことstruct recvbuf型はinclude/recvbuff.hで定義されている。要素にコールバック関数void (*receiver)(struct recvbuf *)がある点に注意。typedef struct recvbuf recvbuf_t; struct recvbuf { recvbuf_t *link;/* next in list */ union { sockaddr_uX_recv_srcadr; caddr_tX_recv_srcclock; struct peer *X_recv_peer; } X_from_where; #define recv_srcadrX_from_where.X_recv_srcadr #definerecv_srcclockX_from_where.X_recv_srcclock #define recv_peerX_from_where.X_recv_peer #ifndef HAVE_IO_COMPLETION_PORT sockaddr_usrcadr;/* where packet came from */ #else intrecv_srcadr_len;/* filled in on completion */ #endif endpt *dstadr;/* address pkt arrived on */ SOCKETfd;/* fd on which it was received */ intmsg_flags;/* Flags received about the packet */ l_fprecv_time;/* time of arrival */ void(*receiver)(struct recvbuf *); /* callback */ intrecv_length;/* number of octets received */ union { struct pktX_recv_pkt; u_charX_recv_buffer[RX_BUFF_SIZE]; } recv_space; #definerecv_pktrecv_space.X_recv_pkt #definerecv_bufferrecv_space.X_recv_buffer int used;/* reference count */ }; -
get_full_recv_buffer()は
full_recv_fifo変数から受信バッファをひとつ取り出して返り値として返す。recvbuf_t * get_full_recv_buffer(void) { recvbuf_t *rbuf; LOCK(); #ifdef HAVE_SIGNALED_IO /* * make sure there are free buffers when we * wander off to do lengthy packet processing with * any buffer we grab from the full list. * * fixes malloc() interrupted by SIGIO risk * (Bug 889) */ if (NULL == free_recv_list || buffer_shortfall > 0) { /* * try to get us some more buffers */ create_buffers(RECV_INC); } #endif /* * try to grab a full buffer */ UNLINK_FIFO(rbuf, full_recv_fifo, link); if (rbuf != NULL) full_recvbufs--; UNLOCK(); return rbuf; } -
この
get_full_recv_buffer()を呼び出すのはntpdmain()の中のこの辺である。rbuf->receiverがNULLでない時に(*rbuf->receiver)(rbuf);でコールバック関数を呼び出している点に注意。rbuf = get_full_recv_buffer(); while (rbuf != NULL) { if (alarm_flag) { was_alarmed = TRUE; alarm_flag = FALSE; } UNBLOCK_IO_AND_ALARM(); if (was_alarmed) { /* avoid timer starvation during lengthy I/O handling */ timer(); was_alarmed = FALSE; } /* * Call the data procedure to handle each received * packet. */ if (rbuf->receiver != NULL) { # ifdef DEBUG_TIMING l_fp dts = pts; L_SUB(&dts, &rbuf->recv_time); DPRINTF(2, ("processing timestamp delta %s (with prec. fuzz)\n", lfptoa(&dts, 9))); collect_timing(rbuf, "buffer processing delay", 1, &dts); bufcount++; # endif (*rbuf->receiver)(rbuf); } else { msyslog(LOG_ERR, "fatal: receive buffer callback NULL"); abort(); } BLOCK_IO_AND_ALARM(); freerecvbuf(rbuf); rbuf = get_full_recv_buffer(); } -
receive()でパケットのフィールドを分別し始めるのはこの辺りから。
pktに受信したパケットのメモリアドレスを保持して、hisversion,hisleap,hismode,hisstratumを読み出している。余談だが、これもhisはダメで三人称単数のtheirにしろとかいうよくわからない話になるのであろうか。register struct pkt *pkt; : pkt = &rbufp->recv_pkt; DPRINTF(2, ("receive: at %ld %s<-%s flags %x restrict %03x org %#010x.%08x xmt %#010x.%08x\n", current_time, stoa(&rbufp->dstadr->sin), stoa(&rbufp->recv_srcadr), rbufp->dstadr->flags, restrict_mask, ntohl(pkt->org.l_ui), ntohl(pkt->org.l_uf), ntohl(pkt->xmt.l_ui), ntohl(pkt->xmt.l_uf))); hisversion = PKT_VERSION(pkt->li_vn_mode); hisleap = PKT_LEAP(pkt->li_vn_mode); hismode = (int)PKT_MODE(pkt->li_vn_mode); hisstratum = PKT_TO_STRATUM(pkt->stratum); -
ところで、この
struct pktはinclude/ntp.hのこの辺りで定義されている。上で使われているli_vn_modeやstratumのような要素があることがわかる。/* * NTP packet format. The mac field is optional. It isn't really * an l_fp either, but for now declaring it that way is convenient. * See Appendix A in the specification. * * Note that all u_fp and l_fp values arrive in network byte order * and must be converted (except the mac, which isn't, really). */ struct pkt { u_charli_vn_mode;/* peer leap indicator */ u_charstratum;/* peer stratum */ u_charppoll;/* peer poll interval */ s_charprecision;/* peer clock precision */ u_fprootdelay;/* roundtrip delay to primary source */ u_fprootdisp;/* dispersion to primary source*/ u_int32refid;/* reference id */ l_fpreftime;/* last update time */ l_fporg;/* originate time stamp */ l_fprec;/* receive time stamp */ l_fpxmt;/* transmit time stamp */ #defineLEN_PKT_NOMAC(12 * sizeof(u_int32)) /* min header length */ #define MIN_MAC_LEN(1 * sizeof(u_int32))/* crypto_NAK */ #define MAX_MD5_LEN(5 * sizeof(u_int32))/* MD5 */ #defineMAX_MAC_LEN(6 * sizeof(u_int32))/* SHA */ /* * The length of the packet less MAC must be a multiple of 64 * with an RSA modulus and Diffie-Hellman prime of 256 octets * and maximum host name of 128 octets, the maximum autokey * command is 152 octets and maximum autokey response is 460 * octets. A packet can contain no more than one command and one * response, so the maximum total extension field length is 864 * octets. But, to handle humungus certificates, the bank must * be broke. * * The different definitions of the 'exten' field are here for * the benefit of applications that want to send a packet from * an auto variable in the stack - not using the AUTOKEY version * saves 2KB of stack space. The receive buffer should ALWAYS be * big enough to hold a full extended packet if the extension * fields have to be parsed or skipped. */ #ifdef AUTOKEY u_int32exten[(NTP_MAXEXTEN + MAX_MAC_LEN) / sizeof(u_int32)]; #else/* !AUTOKEY follows */ u_int32exten[(MAX_MAC_LEN) / sizeof(u_int32)]; #endif/* !AUTOKEY */ }; -
さて、receive()に戻って、
hisversionからhisstratumまでを取り出した後、restrict_maskがRES_IGNOREだったら (正確にはそのビットが立っていたら)単純に無視する。このrestrict_maskは少し戻ったところで受信パケットのソースアドレスから計算されている。restrict_mask = restrictions(&rbufp->recv_srcadr); pkt = &rbufp->recv_pkt; DPRINTF(2, ("receive: at %ld %s<-%s flags %x restrict %03x org %#010x.%08x xmt %#010x.%08x\n", current_time, stoa(&rbufp->dstadr->sin), stoa(&rbufp->recv_srcadr), rbufp->dstadr->flags, restrict_mask, ntohl(pkt->org.l_ui), ntohl(pkt->org.l_uf), ntohl(pkt->xmt.l_ui), ntohl(pkt->xmt.l_uf))); hisversion = PKT_VERSION(pkt->li_vn_mode); hisleap = PKT_LEAP(pkt->li_vn_mode); hismode = (int)PKT_MODE(pkt->li_vn_mode); hisstratum = PKT_TO_STRATUM(pkt->stratum); if (restrict_mask & RES_IGNORE) { sys_restricted++; return;/* ignore everything */ } -
というわけで、ntpdが受け取るパケットのログを取るならこの辺りの
hisversionその他を取り出した直後が良いであろう。ここなら各種制約に掛かって捨てられるパケットも見える位置である。 -
続いて
hismode等を見ながらmode 7の処理をntpd/ntp_request.cのprocess_private()に渡し、mode 6の処理をntpd/ntp_control.cのprocess_control()に渡している。mode 6,7のパケットフォーマットを知るにはこれら関数(とその先)を見れば良いようだ。 -
(*rbuf->receiver)(rbuf);でコールバック関数を呼び出すということは、add_full_recv_buffer()でコールバックに設定したreceive()を呼ぶということである。この関数はntpd/ntp_proto.cで定義されたreceive()であると思われる。 -
受信バッファ
recvbufについてはlibntp/recvbuff.cに一通りの定義があるようだが、受信バッファをadd_full_recv_buffer()で追加し、get_full_recv_buffer()で取り出すという動作であるようだ。 -
input_handler_scan()はこの辺から。
-




