1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LINUX アセンブリ FTPサーバから時間取得

Posted at

動作確認

1970年からNTPから時間を取得した時点までの秒数を表示します。
その後date命令を用いて何年何月何日何時何分何秒の形式で表示します。
私は中国に住んでいるのですが世界標準時間で出力された以下の2025-05-24 11:59:31に8時間を足すと丁度一致しました。日本だと9時間足す必要があります。

ubuntu@ubuntu:~/kaihatsu/ntp$ nasm -f elf64 ntp.asm -o ntp.o
ubuntu@ubuntu:~/kaihatsu/ntp$ ld ntp.o -o ntp
ubuntu@ubuntu:~/kaihatsu/ntp$ ./ntp
1748087971ubuntu@ubuntu:~/kaihatsu/ntp$ date -ud @1748087971 +"%Y-%m-%d %H:%M:%S UTC"
2025-05-24 11:59:31 UTC
ubuntu@ubuntu:~/kaihatsu/ntp$ 

予備知識

NTPタイムスタンプ

NTP(Network Time Protocol)のタイムスタンプは、​​64ビット​​の固定小数点数形式で表現されます。

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           Seconds                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                  Fraction(小数部 1/2^32秒単位)              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
​​上位32ビット(Seconds)​​:
1900年1月1日00:00:00 UTCからの経過秒数(​​秒単位​​)を表します。

​​下位32ビット(Fraction)​​:※今回未使用
秒未満の時間を1/232秒単位で表します。
(例: 0x80000000 = 0.5秒)

宛先情報の構造体の構造

; sockaddr_in構造体(IPv4用)
struc sockaddr_in
    .sin_family: resw 1    ; AF_INET = 2 IPv4用は2固定
    .sin_port:   resw 1    ; ポート番号(リトルエンディアン)
    .sin_addr:  resd 1     ; IPv4アドレス(4バイト)
    .sin_zero:  resb 8     ; 常に0埋め
endstruc

システムコールの引数及び返り値(C言語風)

1. ​​SYS_SOCKET(ソケット作成)​​

#include <sys/socket.h>

/**
 * ソケットを作成する(64ビットLinux用)
 * @param domain   通信ドメイン(32ビット整数: AF_INET=2)
 * @param type     ソケットタイプ(32ビット整数: SOCK_DGRAM=2)
 * @param protocol プロトコル(32ビット整数: 0=自動選択)
 * @return         成功: 64ビットファイルディスクリプタ(正の整数)
 *                 失敗: 64ビットエラーコード(-1)
 */
int64_t socket(int32_t domain, int32_t type, int32_t protocol);

2. ​​SYS_SENDTO(データ送信)

#include <sys/socket.h>

/**
 * UDPでデータを送信する(64ビットLinux用)
 * @param sockfd    64ビットファイルディスクリプタ
 * @param buf       送信データの64ビットポインタ
 * @param len       64ビット符号なし整数(データサイズ)
 * @param flags     32ビット整数(フラグ: 通常0)
 * @param dest_addr 宛先アドレスの64ビットポインタ(sockaddr_in*)
 * @param addrlen   32ビット整数(アドレス構造体のサイズ: 16)
 * @return          成功: 送信したバイト数(64ビット符号付き整数)
 *                  失敗: 64ビットエラーコード(-1)
 */
int64_t sendto(
    int64_t sockfd,
    const void *buf,      // 64ビットアドレス
    uint64_t len,         // 64ビット
    int32_t flags,        // 32ビット(下位32ビット使用)
    const struct sockaddr *dest_addr,  // 64ビットアドレス
    uint32_t addrlen      // 32ビット(下位32ビット使用)
);

3. ​​SYS_RECVFROM(データ受信)​​

#include <sys/socket.h>

/**
 * UDPでデータを受信する(64ビットLinux用)
 * @param sockfd    64ビットファイルディスクリプタ
 * @param buf       受信バッファの64ビットポインタ
 * @param len       64ビット符号なし整数(バッファサイズ)
 * @param flags     32ビット整数(フラグ: 通常0)
 * @param src_addr  送信元アドレスの64ビットポインタ(NULL可)
 * @param addrlen   送信元アドレスサイズの64ビットポインタ(NULL可)
 * @return          成功: 受信したバイト数(64ビット符号付き整数)
 *                  失敗: 64ビットエラーコード(-1)
 */
int64_t recvfrom(
    int64_t sockfd,
    void *buf,           // 64ビットアドレス
    uint64_t len,        // 64ビット
    int32_t flags,       // 32ビット(下位32ビット使用)
    struct sockaddr *src_addr,  // 64ビットアドレス(NULL可能)
    uint32_t *addrlen    // 64ビットアドレス(NULL可能)
);

検証コード

section .data
; NTPサーバー情報設定(time.nist.gov)
ntp_server: db 129,6,15,28   ; 接続先サーバー(NTP)のIPアドレス
port:       dw 0x7B00        ; UDPポート123(リトルエンディアン​​ 0x7B00)

; NTPリクエストパケットの初期化
ntp_packet: db 0x1B           ; [重要] フラグ設定:LI=0(正常), VN=3(バージョン3), Mode=3(クライアントモード)
times 47 db 0                 ; 48バイトのNTPパケット形式を満たすため0埋め

section .text
global _start

; Linuxシステムコール番号
%define SYS_SOCKET   41
%define SYS_SENDTO   44
%define SYS_RECVFROM 45
%define SYS_CLOSE    3
%define SYS_EXIT     60
%define SYS_WRITE    1

_start:
    ; [ソケット作成] NTPサーバーと通信するためのUDPソケットを作成
    mov rax, SYS_SOCKET
    mov rdi, 2              ; AF_INET(IPv4通信を指定)
    mov rsi, 2              ; SOCK_DGRAM(UDP通信を指定)
    mov rdx, 0              ; プロトコル自動選択(通常UDPは0)
    syscall
    cmp rax, 0              ; エラーチェック:負の値は失敗
    jl error
    mov [fd], rax           ; 作成したソケットを保存

    ; [宛先設定] 接続先サーバーのアドレス情報を構造体に設定
    mov word [sockaddr], 2  ; sin_family = AF_INET(IPv4)
    mov ax, [port]          ; ポート番号を設定
    mov word [sockaddr+2], ax ; sin_port フィールド
    mov eax, [ntp_server]   ; IPアドレスを設定(4バイト)
    mov dword [sockaddr+4], eax ; sin_addr フィールド

    ; [リクエスト送信] NTPサーバーに時刻取得要求を送信
    mov rax, SYS_SENDTO
    mov rdi, [fd]           ; 使用するソケットを指定
    lea rsi, [ntp_packet]   ; 送信データ(事前に準備したNTPリクエスト)
    mov rdx, 48             ; NTPパケットの標準サイズ(48バイト)
    mov r10, 0              ; フラグなし(通常は0)
    lea r8, [sockaddr]      ; 宛先アドレス構造体のポインタ
    mov r9, 16              ; アドレス構造体のサイズ(sockaddr_inは16バイト)
    syscall
    cmp rax, 48             ; 正常に48バイト送信されたか確認
    jne error

    ; [レスポンス受信] サーバーからの時刻情報を受信
    mov rax, SYS_RECVFROM
    mov rdi, [fd]           ; 同じソケットを使用
    lea rsi, [recv_buf]     ; 受信データ格納先バッファ
    mov rdx, 48             ; 受信予定データサイズ(NTPレスポンスも48バイト)
    mov r10, 0              ; フラグなし
    mov r8, 0               ; 送信元アドレス情報不要(NULL指定)
    mov r9, 0               ; アドレスサイズ情報不要(NULL指定)
    syscall
    cmp rax, 48             ; 48バイト受信したか確認
    jne error

    ; [時刻データ抽出] 受信データをeaxへ格納
    mov eax, [recv_buf + 32]    ; タイムスタンプ上位32ビット(NTPフォーマット)
    mov ebx, [recv_buf + 36]    ; タイムスタンプ下位32ビット(今回は未使用)
    bswap eax                   ; ビッグエンディアン→リトルエンディアン変換
    sub eax, 2208988800         ; NTP時間(1900年起点)→UNIX時間(1970年起点)に変換
                                ; 差は70年分(2208988800秒)

    ; (注)ここでeaxにUNIX時間が格納される
    

; [時刻データ表示] 数値をASCII文字列に変換して出力
    mov edi, output_buf          ; 出力バッファのアドレス
    call int_to_ascii            ; 数値→ASCII変換関数

    mov rax, SYS_WRITE         ; writeシステムコール番号設定
    mov rdi, 1                 ; 標準出力のファイルディスクリプタ
    mov rsi, output_buf        ; 出力データのポインタ
    mov rdx, 10                 ; 出力サイズ(10バイト)
    syscall

    ; [ソケット解放] 通信が完了したのでソケットを閉じる
    mov rax, SYS_CLOSE
    mov rdi, [fd]
    syscall

    ; プログラム正常終了
    mov rdi, 0
    jmp exit

error:
    ; エラー発生時の処理
    mov rdi, 1              ; 終了1(エラー)

exit:
    ; システム終了処理
    mov rax, SYS_EXIT
    syscall
    
; 数値→ASCII変換関数(32bit符号なし整数用)
int_to_ascii:
    mov ecx, 10              ; 除数
    mov ebx, 10              ; カウンタ(10桁)
.convert_loop:
    dec ebx
    xor edx, edx
    div ecx                  ; EDX:EAX / ECX
    add dl, '0'              ; 余りをASCIIに変換
    mov [edi + ebx], dl
    test ebx, ebx
    jnz .convert_loop
    ret

section .bss
fd:         resq 1          ; ソケットファイルディスクリプタ保存用
sockaddr:   resb 16         ; ソケットアドレス情報格納用(AF_INETの場合16バイト)
recv_buf:   resb 48         ; NTPレスポンス受信用バッファ
output_buf: resb 10          ; 追加: 出力用バッファ
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?