Skip to content

Commit

Permalink
to v0.0.11
Browse files Browse the repository at this point in the history
  • Loading branch information
FObersteiner committed Jun 16, 2024
1 parent a4a65ce commit 4edc36f
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 137 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ zig-cache/
zig-out/
autodoc/
.old/
perf.data

5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

# NTP Client

Command line app to query an NTP server, to verify your OS clock setting. The original repository is hosted [on Codeberg](https://codeberg.org/FObersteiner/ntp_client).
Command line app to query an NTP server, to verify your OS clock setting.

- [on Codeberg](https://codeberg.org/FObersteiner/ntp_client)
- [on github](https://github.com/FObersteiner/ntp-client)

```text
Usage: ntp_client [options]
Expand Down
2 changes: 1 addition & 1 deletion build.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const std = @import("std");
const log = std.log.scoped(.ntp_client_build);
const client_version = std.SemanticVersion{ .major = 0, .minor = 0, .patch = 9 };
const client_version = std.SemanticVersion{ .major = 0, .minor = 0, .patch = 11 };

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
Expand Down
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.{
.name = "ntp_client",
.version = "0.0.9",
.version = "0.0.11",
.dependencies = .{
.zdt = .{
.url = "https://codeberg.org/FObersteiner/zdt/archive/v0.1.3.tar.gz",
Expand Down
152 changes: 101 additions & 51 deletions docs/NTP_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,6 @@
- <https://github.com/beevik/ntp>
- <https://lettier.github.io/posts/2016-04-26-lets-make-a-ntp-client-in-c.html>

NTP v4 data format, from <https://datatracker.ietf.org/doc/html/rfc5905>:

```text
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN |Mode | Stratum | Poll | Precision |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Delay |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Dispersion |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reference ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Reference Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Origin Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Receive Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Transmit Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Extension Field 1 (variable) .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Extension Field 2 (variable) .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Key Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| dgst (128) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

## limits

Only fields up to and including Transmit Timestamp are used further on.
Expand All @@ -80,4 +30,104 @@ Extensions are not supported (yet).

## on Linux

On a Linux running timedatectl, check via `timedatectl timesync-status`
On a Linux running `timedatectl`, check via `timedatectl timesync-status`

## Specs

NTP v4 data format, <https://datatracker.ietf.org/doc/html/rfc5905>

### Packet

```text
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN |Mode | Stratum | Poll | Precision |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Delay |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Dispersion |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reference ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Reference Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Origin Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Receive Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Transmit Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Extension Field 1 (variable) .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Extension Field 2 (variable) .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Key Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| dgst (128) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

### Kiss Codes

```text
+------+------------------------------------------------------------+
| Code | Meaning |
+------+------------------------------------------------------------+
| ACST | The association belongs to a unicast server. |
| AUTH | Server authentication failed. |
| AUTO | Autokey sequence failed. |
| BCST | The association belongs to a broadcast server. |
| CRYP | Cryptographic authentication or identification failed. |
| DENY | Access denied by remote server. |
| DROP | Lost peer in symmetric mode. |
| RSTR | Access denied due to local policy. |
| INIT | The association has not yet synchronized for the first |
| | time. |
| MCST | The association belongs to a dynamically discovered server.|
| NKEY | No key found. Either the key was never installed or is |
| | not trusted. |
| RATE | Rate exceeded. The server has temporarily denied access |
| | because the client exceeded the rate threshold. |
| RMOT | Alteration of association from a remote host running |
| | ntpdc. |
| STEP | A step change in system time has occurred, but the |
| | association has not yet resynchronized. |
+------+------------------------------------------------------------+
```

### Globals / Boundaries

```text
+-----------+-------+----------------------------------+
| Name | Value | Description |
+-----------+-------+----------------------------------+
| PORT | 123 | NTP port number |
| VERSION | 4 | NTP version number |
| TOLERANCE | 15e-6 | frequency tolerance PHI (s/s) |
| MINPOLL | 4 | minimum poll exponent (16 s) |
| MAXPOLL | 17 | maximum poll exponent (36 h) |
| MAXDISP | 16 | maximum dispersion (16 s) |
| MINDISP | .005 | minimum dispersion increment (s) |
| MAXDIST | 1 | distance threshold (1 s) |
| MAXSTRAT | 16 | maximum stratum number |
+-----------+-------+----------------------------------+
```
15 changes: 15 additions & 0 deletions docs/change.log
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 2024-06-16, v0.0.11

- keep client timestamps local to client
- send random number as client xmt

## 2024-06-16, v0.0.10

- add 5 second timeout if server doesn't answer
- revise pprint to console
- more tests, some new functionality (correctTime, refIDprintable)

## 2024-06-15, v0.0.9

- dependency update

## 2024-06-14, v0.0.8

- add IPv6 support
53 changes: 39 additions & 14 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ test {
_ = ntp;
}

//-----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------
const timeout_sec: isize = 5; // wait-for-reply timeout
const mtu: usize = 1024; // buffer size for transmission
//-----------------------------------------------------------------------------
const poll_int: u8 = 0; // one-shot program, do not set poll interval
// ------------------------------------------------------------------------------------

pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
Expand Down Expand Up @@ -53,7 +55,7 @@ pub fn main() !void {
}
}

// ------------------------------------------------------------------------
// --- prepare connection ---------------------------------------------------------

// resolve hostname
const addrlist = net.getAddressList(allocator, cli.flags.server, port) catch {
Expand All @@ -67,27 +69,40 @@ pub fn main() !void {

const sock = try posix.socket(
addr_src.any.family,
// Notes on flags:
// NONBLOCK can be used to create timeout behavior.
// CLOEXEC not strictly needed here; see open(2) man page.
posix.SOCK.DGRAM | posix.SOCK.CLOEXEC,
posix.IPPROTO.UDP,
);
try posix.bind(sock, &addr_src.any, addr_src.getOsSockLen());
defer posix.close(sock);

if (timeout_sec != 0) { // make this configurable ? ...0 would mean no timeout
try posix.setsockopt(
sock,
posix.SOL.SOCKET,
posix.SO.RCVTIMEO,
&std.mem.toBytes(posix.timespec{ .tv_sec = timeout_sec, .tv_nsec = 0 }),
);
}

// --- query server(s) ------------------------------------------------------------

var buf: [mtu]u8 = std.mem.zeroes([mtu]u8);

iter_addrs: for (addrlist.addrs) |dst| {
iter_addrs: for (addrlist.addrs, 0..) |dst, i| {
var dst_addr_sock: posix.sockaddr = undefined; // must not use dst.any
var dst_addr_len: posix.socklen_t = dst.getOsSockLen();

ntp.Packet.toBytesBuffer(proto_vers, true, &buf);
ntp.Packet.toBytesBuffer(proto_vers, poll_int, &buf);

// packet created!
const T1: ntp.Time = ntp.Time.fromUnixNanos(@as(u64, @intCast(std.time.nanoTimestamp())));

_ = posix.sendto(
sock,
buf[0..ntp.packet_len],
0,
&dst.any, // &dst_addr_sock,
&dst.any,
dst_addr_len,
) catch |err| switch (err) {
error.AddressFamilyNotSupported => {
Expand All @@ -100,23 +115,33 @@ pub fn main() !void {
else => |e| return e,
};

const n_recv: usize = try posix.recvfrom(
const n_recv: usize = posix.recvfrom(
sock,
buf[0..],
0,
&dst_addr_sock,
&dst_addr_len,
);
) catch |err| switch (err) {
error.WouldBlock => {
println("Error: connection timed out", .{});
if (i < addrlist.addrs.len - 1) println("Try next server.", .{});
continue :iter_addrs;
},
else => |e| return e,
};

const ts_dst = std.time.nanoTimestamp();
// reply received!
const T4: ntp.Time = ntp.Time.fromUnixNanos(@as(u64, @intCast(std.time.nanoTimestamp())));

if (n_recv != ntp.packet_len) {
println("invalid reply length, try next server.", .{});
println("Error: invalid reply length", .{});
if (i < addrlist.addrs.len - 1) println("Try next server.", .{});
continue :iter_addrs;
}

const p_result: ntp.Packet = ntp.Packet.parse(buf[0..ntp.packet_len].*);
const result: ntp.Result = ntp.Result.fromPacket(p_result, ts_dst);
const result: ntp.Result = ntp.Result.fromPacket(p_result, T1, T4);

println("Server name: {s}", .{cli.flags.server});
println("Server address: {any}", .{dst});
println("---", .{});
Expand All @@ -127,7 +152,7 @@ pub fn main() !void {
}
}

//-----------------------------------------------------------------------------
// --- helpers ------------------------------------------------------------------------

/// Print to stdout with trailing newline, unbuffered, and silently returning on failure.
fn println(comptime fmt: []const u8, args: anytype) void {
Expand Down
Loading

0 comments on commit 4edc36f

Please sign in to comment.