こんにちは、habrauzers。 今日は、単純なNTPクライアントの作成方法についてお話します。 基本的に、パケットの構造と、NTPサーバーからの応答を処理する方法について説明します。 コードはpythonで書かれます。なぜなら、私にとっては、そのようなものに最適な言語は単に見つからないからです。 鑑定家は、コードとntplibコードの類似性に注意を払うでしょう-私はそれに触発されました。
NTPとは正確には何ですか? NTP-正確な時間サーバーとの相互作用のプロトコル。 このプロトコルは、多くの最新のマシンで使用されています。
たとえば、Windowsのw32tmサービス。NTPプロトコルには合計で5つのバージョンがあります。 最初の0番目のバージョン(1985、RFC958))は、現在は時代遅れと見なされています。 現在、新しいものが使用されています:1番目(1988、RFC1059)、2番目(1989、RFC1119)、3番目(1992、RFC1305)および4番目(1996、RFC2030)。 1-4バージョンは相互に互換性があり、サーバー操作アルゴリズムのみが異なります。
パッケージ形式
飛躍インジケータ (修正インジケータ)-2番目の調整に関する警告を示す数値。 値:
- 0-修正なし
- 1-1日の最後の分には61秒が含まれます
- 2-その日の最後の分には59秒が含まれます
- 3-サーバーの誤動作(時刻が同期されていない)
バージョン番号 -NTPプロトコルのバージョン番号(1〜4)。
モード -パケット送信者の動作モード。 最も一般的な0〜7の値:
- 3-クライアント
- 4-サーバー
- 5-ブロードキャストモード
階層 (階層化レベル)-サーバーと基準クロックの間の中間層の数(1-サーバーは基準クロックから直接データを取得します、2-サーバーはレベル1のサーバーからデータを取得しますなど)。
ポーリングは、連続したメッセージ間の最大間隔を表す符号付き整数です。 ここで、NTPクライアントはサーバーをポーリングする間隔を示し、NTPサーバーはポーリングする間隔を示します。 値は秒の2進対数です。
精度は、システムクロックの精度を表す符号付き整数です。 値は秒の2進対数です。
ルート遅延 (サーバー遅延)-クロックがNTPサーバーに到達する時間(固定小数点の秒数)。
ルート分散 (サーバー読み取り値の
分散 )-固定小数点を使用した秒数としてのNTPサーバーのクロックの分散。
Ref id (ソース識別子)-ウォッチのID。 サーバーのストラタムが1の場合、ref idは原子時計の名前(4 ASCII文字)です。 サーバーが別のサーバーを使用する場合、このサーバーのアドレスはref idに書き込まれます。
最後の4つのフィールドは、時間-32ビット-整数部、32ビット-小数部です。
参照 -サーバーの最新の時計。
Originate-パケットが送信された時間(サーバーによって満たされます-詳細は以下)。
受信 -パケットがサーバーによって受信された時間。
送信 -サーバーからクライアントにパケットを送信する時間(クライアントによって満たされます。詳細は以下を参照)。
最後の2つのフィールドは考慮しません。
パッケージを書きましょう:
パッケージコードclass NTPPacket: _FORMAT = "!BB bb 11I" def __init__(self, version_number=2, mode=3, transmit=0):
サーバーにパケットを送信(および受信)するには、パケットをバイトの配列に変換できる必要があります。
この(および逆の)操作のために、pack()およびunpack()の2つの関数を作成します。
パック機能 def pack(self): return struct.pack(NTPPacket._FORMAT, (self.leap_indicator << 6) + (self.version_number << 3) + self.mode, self.stratum, self.pool, self.precision, int(self.root_delay) + get_fraction(self.root_delay, 16), int(self.root_dispersion) + get_fraction(self.root_dispersion, 16), self.ref_id, int(self.reference), get_fraction(self.reference, 32), int(self.originate), get_fraction(self.originate, 32), int(self.receive), get_fraction(self.receive, 32), int(self.transmit), get_fraction(self.transmit, 32))
パッケージに書き込むために数値の小数部分を選択するには、get_fraction()関数が必要です。
get_fraction() def get_fraction(number, precision): return int((number - int(number)) * 2 ** precision)
アンパック機能 def unpack(self, data: bytes): unpacked_data = struct.unpack(NTPPacket._FORMAT, data) self.leap_indicator = unpacked_data[0] >> 6
怠け者の場合、アプリケーションとして-パッケージを美しい文字列に変えるコード def to_display(self): return "Leap indicator: {0.leap_indicator}\n" \ "Version number: {0.version_number}\n" \ "Mode: {0.mode}\n" \ "Stratum: {0.stratum}\n" \ "Pool: {0.pool}\n" \ "Precision: {0.precision}\n" \ "Root delay: {0.root_delay}\n" \ "Root dispersion: {0.root_dispersion}\n" \ "Ref id: {0.ref_id}\n" \ "Reference: {0.reference}\n" \ "Originate: {0.originate}\n" \ "Receive: {0.receive}\n" \ "Transmit: {0.transmit}"\ .format(self)
サーバーにパケットを送信する
バージョン 、
モード 、および
送信フィールドが入力されたパケットをサーバーに送信する必要があります。
送信では、ローカルマシンの現在の時間(1900年1月1日からの秒数)、バージョン-1〜4のいずれか、モード-3(クライアントモード)を指定する必要があります。
サーバーは、要求を受け入れた後、要求から
送信値を
送信 元フィールドにコピーすることにより、NTPパケットのすべてのフィールドに入力します。 クライアントが「
Originate」フィールドで自分の時間の値をすぐに入力できない理由は私には謎です。 その結果、パケットが戻ってくると、クライアントには4回あります-要求が送信された時間(
Originate )、サーバーが要求
を受信した時間(
Receive )、サーバーが応答を
送信した時間(
Transmit )、およびクライアントが応答を受信した時間-
到着 (パケットではありません) これらの値を使用して、正しい時間を設定できます。
サーバーからのデータを処理する
サーバーからのデータの処理は、レイモンド・M・サリアン(1978)の古い仕事の英国紳士の行動に似ています:「ある人は時計を持っていませんでしたが、一方で、彼は時々始めるのを忘れた正確な壁時計がありました。 時計を再び起動するのを忘れていた彼は、友人を訪ねてその場所で夜を過ごし、家に戻ったときに時計を正しく設定することができました。 移動時間が事前にわからなかった場合、どうやってこれをやったのでしょうか?答えは次のとおりです。 友人のもとに来て客を残して、彼は彼の到着と出発の時間を書き留めます。 これにより、彼はどのくらい訪問していたかを知ることができます。 家に戻って時計を見ると、人は不在の期間を決定します。 この時間から彼が訪問に費やした時間を引くと、人は往復に費やした時間を見つけます。 旅行に費やした時間の半分をゲストに残す時間を増やしたため、彼は帰宅時間を見つけ、それに応じて時計の針を翻訳する機会を得ました。
リクエストのサーバー稼働時間を確認します。
- クライアントからサーバーへのパケットパス時間を見つけます: ((到着-発信)-(送信-受信))/ 2
- クライアントとサーバーの時間の違いを見つける:
受信-発信-((到着-発信)-(送信-受信))/ 2 =
2 *受信-2 *発信-到着+発信+送信-受信=
受信-発信-到着+送信
得られた価値を現地時間に加えて、人生を楽しみましょう。
出力結果 time_different = answer.get_time_different(arrive_time) result = "Time difference: {}\nServer time: {}\n{}".format( time_different, datetime.datetime.fromtimestamp(time.time() + time_different).strftime("%c"), answer.to_display()) print(result)
便利な
リンク 。