Skip to content

Latest commit

 

History

History
380 lines (318 loc) · 17.8 KB

ntpd-receive.md

File metadata and controls

380 lines (318 loc) · 17.8 KB

ntpd packet logのための覚え書き(1)

これは何?

  • ntpdが送受信するパケットを記録したいと仮定する。
  • ntpdのログ機構では、どうやらそこまで細かいログを取ることが出来ない。
  • そこでちょっとソースを見てみる。
  • 覚え書きなのできっと勘違いや誤りがあるに違いないです。先にごめんなさいしておきます。
  • 追記、受信側だけで長くなったので送信側は稿を改めます。

NTPd

ntpdがパケットを受信する流れ

  • ntpd/ntpd.cmain()はこの辺。

  • 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()はこの辺から。

  • 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->receiverreceiveを設定している点に注意。この要素はコールバック関数で、receiventpd/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にあって、渡されたバッファrbfull_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についてはlibntp/recvbuff.cに一通りの定義があるようだが、受信バッファをadd_full_recv_buffer()で追加し、get_full_recv_buffer()で取り出すという動作であるようだ。

  • 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_u	X_recv_srcadr;
    		caddr_t		X_recv_srcclock;
    		struct peer *	X_recv_peer;
    	} X_from_where;
    #define recv_srcadr		X_from_where.X_recv_srcadr
    #define	recv_srcclock		X_from_where.X_recv_srcclock
    #define recv_peer		X_from_where.X_recv_peer
    #ifndef HAVE_IO_COMPLETION_PORT
    	sockaddr_u	srcadr;		/* where packet came from */
    #else
    	int		recv_srcadr_len;/* filled in on completion */
    #endif
    	endpt *		dstadr;		/* address pkt arrived on */
    	SOCKET		fd;		/* fd on which it was received */
    	int		msg_flags;	/* Flags received about the packet */
    	l_fp		recv_time;	/* time of arrival */
    	void		(*receiver)(struct recvbuf *); /* callback */
    	int		recv_length;	/* number of octets received */
    	union {
    		struct pkt	X_recv_pkt;
    		u_char		X_recv_buffer[RX_BUFF_SIZE];
    	} recv_space;
    #define	recv_pkt		recv_space.X_recv_pkt
    #define	recv_buffer		recv_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->receiverNULLでない時に(*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();
    			}
  • (*rbuf->receiver)(rbuf);でコールバック関数を呼び出すということは、add_full_recv_buffer()でコールバックに設定したreceive()を呼ぶということである。この関数はntpd/ntp_proto.cで定義されたreceive()であると思われる。

  • 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 pktinclude/ntp.hこの辺りで定義されている。上で使われているli_vn_modestratumのような要素があることがわかる。

    /*
     * 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_char	li_vn_mode;	/* peer leap indicator */
    	u_char	stratum;	/* peer stratum */
    	u_char	ppoll;		/* peer poll interval */
    	s_char	precision;	/* peer clock precision */
    	u_fp	rootdelay;	/* roundtrip delay to primary source */
    	u_fp	rootdisp;	/* dispersion to primary source*/
    	u_int32	refid;		/* reference id */
    	l_fp	reftime;	/* last update time */
    	l_fp	org;		/* originate time stamp */
    	l_fp	rec;		/* receive time stamp */
    	l_fp	xmt;		/* transmit time stamp */
    
    #define	LEN_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 */
    #define	MAX_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_int32	exten[(NTP_MAXEXTEN + MAX_MAC_LEN) / sizeof(u_int32)];
    #else	/* !AUTOKEY follows */
    	u_int32	exten[(MAX_MAC_LEN) / sizeof(u_int32)];
    #endif	/* !AUTOKEY */
    };
  • さて、receive()に戻って、hisversionからhisstratumまでを取り出した後、restrict_maskRES_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 */
    	}
  • 続いてhismode等を見ながらmode 7の処理をntpd/ntp_request.cprocess_private()に渡し、mode 6の処理をntpd/ntp_control.cprocess_control()に渡している。mode 6,7のパケットフォーマットを知るにはこれら関数(とその先)を見れば良いようだ。

  • Mode6, 7以外のパケットについては、そのまま分岐せずに認証周りやpeerの処理などをした後にこの辺process_packet()に渡して処理しているようだ。

  • というわけで、ntpdが受け取るパケットのログを取るならこの辺りhisversionその他を取り出した直後が良いであろう。ここなら各種制約に掛かって捨てられるパケットも見える位置である。