From 4a4f5b969367dac2be920d4fa9d491e1d2e03310 Mon Sep 17 00:00:00 2001 From: Jauder Ho Date: Sun, 28 Jul 2024 21:44:13 +0000 Subject: [PATCH] Take #2 of generating from nts-sources.yml Signed-off-by: Jauder Ho --- README.md | 1 + nts-sources.yml | 52 ++++++++++++++++++++++++++- scripts/ntpServerConverter.py | 68 +++++++++++++++++++++++++++-------- scripts/verifyNTSServers.py | 50 ++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 15 deletions(-) create mode 100755 scripts/verifyNTSServers.py diff --git a/README.md b/README.md index c3b1555..bcdf1cb 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This is intended to bootstrap a list of NTP servers with NTS support given that - There is no endorsement of any particular server. Please carefully vet before usage - Before using anycast NTP servers, make sure that you understand the [limitations](https://www.rfc-editor.org/rfc/rfc8633.html#page-17) - Use [at least 4 time sources](https://support.ntp.org/Support/SelectingOffsiteNTPServers#Upstream_Time_Server_Quantity) as a best practice. No more than 10 should be used +- It is not possible to mix and match NTP and NTS at this time. Only NTS servers should be specified as the NTP entries will not be used - Generally, virtualized systems do not make for good time sources as there is too much jitter. Submissions should strive to ensure that high quality time is available - Verify connectivity using the following command (h/t [@cadusilva](https://github.com/cadusilva)) - `chronyd -Q -t 3 'server iburst nts maxsamples 1'` diff --git a/nts-sources.yml b/nts-sources.yml index 84bd167..05f1294 100644 --- a/nts-sources.yml +++ b/nts-sources.yml @@ -4,230 +4,280 @@ servers: location: All owner: Cloudflare notes: Anycast + vm: false - hostname: a.st1.ntp.br stratum: 1 location: Brazil owner: ntp.br + vm: false - hostname: b.st1.ntp.br stratum: 1 location: Brazil owner: ntp.br + vm: false - hostname: c.st1.ntp.br stratum: 1 location: Brazil owner: ntp.br + vm: false - hostname: d.st1.ntp.br stratum: 1 location: Brazil owner: ntp.br + vm: false - hostname: gps.ntp.br stratum: 1 location: Brazil owner: ntp.br + vm: false - hostname: brazil.time.system76.com stratum: 2 location: Brazil owner: System76 + vm: false - hostname: time.bolha.one stratum: 1 location: Brazil owner: Cadu Silva + vm: false - hostname: ntp.miuku.net stratum: 3 location: Finland owner: miuku.net + vm: false - hostname: paris.time.system76.com stratum: 2 location: France owner: System76 + vm: false - hostname: ntp3.fau.de stratum: 1 location: Germany owner: FAU + vm: false - hostname: ntp3.ipv6.fau.de stratum: 1 location: Germany owner: FAU notes: IPv6 only + vm: false - hostname: ptbtime1.ptb.de stratum: 1 location: Germany owner: PTB + vm: false - hostname: ptbtime2.ptb.de stratum: 1 location: Germany owner: PTB + vm: false - hostname: ptbtime3.ptb.de stratum: 1 location: Germany owner: PTB + vm: false - hostname: ptbtime4.ptb.de stratum: 1 location: Germany owner: PTB + vm: false - hostname: www.jabber-germany.de stratum: 2 location: Germany owner: Jörg Morbitzer + vm: false - hostname: www.masters-of-cloud.de stratum: 2 location: Germany owner: Jörg Morbitzer + vm: false - hostname: ntp.nanosrvr.cloud stratum: 1 location: Germany owner: Michael Byczkowski + vm: false - hostname: ntppool1.time.nl stratum: 1 location: Netherlands owner: TimeNL + vm: false - hostname: ntppool2.time.nl stratum: 1 location: Netherlands owner: TimeNL + vm: false - hostname: ntpmon.dcs1.biz stratum: 1 location: Singapore owner: Sanjeev Gupta + vm: false - hostname: nts.netnod.se stratum: 1 location: Sweden owner: Netnod notes: Anycast + vm: false - hostname: gbg1.nts.netnod.se stratum: 1 location: Sweden owner: Netnod notes: For users close to Göteborg + vm: false - hostname: gbg2.nts.netnod.se stratum: 1 location: Sweden owner: Netnod notes: For users close to Göteborg + vm: false - hostname: lul1.nts.netnod.se stratum: 1 location: Sweden owner: Netnod notes: For users close to Luleå + vm: false - hostname: lul2.nts.netnod.se stratum: 1 location: Sweden owner: Netnod notes: For users close to Luleå + vm: false - hostname: mmo1.nts.netnod.se stratum: 1 location: Sweden owner: Netnod notes: For users close to Malmö + vm: false - hostname: mmo2.nts.netnod.se stratum: 1 location: Sweden owner: Netnod notes: For users close to Malmö + vm: false - hostname: sth1.nts.netnod.se stratum: 1 location: Sweden owner: Netnod notes: For users close to Stockholm + vm: false - hostname: sth2.nts.netnod.se stratum: 1 location: Sweden owner: Netnod notes: For users close to Stockholm + vm: false - hostname: svl1.nts.netnod.se stratum: 1 location: Sweden owner: Netnod notes: For users close to Sundsvall + vm: false - hostname: svl2.nts.netnod.se stratum: 1 location: Sweden owner: Netnod notes: For users close to Sundsvall + vm: false - hostname: ntp.3eck.net stratum: 2 location: Switzerland owner: Adrian Zaugg + vm: false - hostname: ntp.trifence.ch stratum: 2 location: Switzerland owner: Marcel Waldvogel + vm: false - hostname: ntp.zeitgitter.net stratum: 2 location: Switzerland owner: Marcel Waldvogel + vm: false - hostname: time.signorini.ch stratum: 1 location: Switzerland owner: Attilio Signorini + vm: false - hostname: ohio.time.system76.com stratum: 2 location: US owner: System76 + vm: false - hostname: oregon.time.system76.com stratum: 2 location: US owner: System76 + vm: false - hostname: virginia.time.system76.com stratum: 2 location: US owner: System76 + vm: false - hostname: stratum1.time.cifelli.xyz stratum: 1 location: US owner: Mike Cifelli + vm: false - hostname: time.cifelli.xyz stratum: 2 location: US owner: Mike Cifelli + vm: false - hostname: time.txryan.com stratum: 2 location: US owner: Tanner Ryan + vm: false + + - hostname: "[ntp.viarouge.net](http://ntp.viarouge.net)" + stratum: 2 + location: France + owner: Vincent Viarouge + vm: true - hostname: time.xargs.org stratum: 2 location: US - owner: Michael Driscoll + owner: Unknown + vm: true diff --git a/scripts/ntpServerConverter.py b/scripts/ntpServerConverter.py index 26efb2a..b0bbed3 100755 --- a/scripts/ntpServerConverter.py +++ b/scripts/ntpServerConverter.py @@ -1,42 +1,81 @@ +#!/usr/bin/python3 + import yaml import argparse +import re def load_yaml(file_path): with open(file_path, 'r') as file: return yaml.safe_load(file) +def extract_hostname(hostname_field): + # Extract hostname from potential markdown link + match = re.search(r'\[([^\]]+)\]', hostname_field) + if match: + return match.group(1) + return hostname_field + def generate_markdown(data): markdown = "|Hostname|Stratum|Location|Owner|Notes|\n|---|:---:|---|---|---|\n" current_location = None + vm_servers = [] for server in data['servers']: + if server['vm']: + vm_servers.append(server) + continue if server['location'] != current_location: if current_location is not None: markdown += "|||\n" current_location = server['location'] - - hostname = server['hostname'] + + hostname = extract_hostname(server['hostname']) stratum = server['stratum'] location = server['location'] owner = server['owner'] notes = server.get('notes', '') - + markdown += f"|{hostname}|{stratum}|{location}|{owner}|{notes}|\n" - + + # Add VM servers in a separate table + if vm_servers: + markdown += "\nThe following servers are known to be virtualized and may be less accurate. YMMV.\n" + markdown += "|Hostname|Stratum|Location|Owner|Notes|\n|---|:---:|---|---|---|\n" + for server in vm_servers: + hostname = extract_hostname(server['hostname']) + stratum = server['stratum'] + location = server['location'] + owner = server['owner'] + notes = server.get('notes', '') + + markdown += f"|{hostname}|{stratum}|{location}|{owner}|{notes}|\n" + return markdown def generate_chrony_conf(data): - chrony_conf = "#\n# NTS servers in chrony format\n#\n" + chrony_conf = "#\n# NTS servers in chrony format\n#\n\n" current_location = None + vm_servers = [] + # Process non-VM servers for server in data['servers']: + if server['vm']: + vm_servers.append(server) + continue if server['location'] != current_location: if current_location is not None: chrony_conf += "\n" chrony_conf += f"# {server['location']}\n" current_location = server['location'] - - hostname = server['hostname'] - chrony_conf += f"server {hostname} nts iburst\n" - + + hostname = extract_hostname(server['hostname']) + chrony_conf += f"server {hostname} iburst nts\n" + + # Add VM servers at the end + if vm_servers: + chrony_conf += "\n# Virtual Machine Servers (may be less accurate)\n" + for server in vm_servers: + hostname = extract_hostname(server['hostname']) + chrony_conf += f"server {hostname} iburst nts\n" + return chrony_conf def main(): @@ -44,20 +83,21 @@ def main(): parser.add_argument("input_file", help="Path to the input YAML file") parser.add_argument("output_format", choices=["markdown", "chrony"], help="Output format (markdown or chrony)") parser.add_argument("output_file", help="Path to the output file") - + args = parser.parse_args() - + data = load_yaml(args.input_file) - + if args.output_format == "markdown": output = generate_markdown(data) else: output = generate_chrony_conf(data) - + with open(args.output_file, 'w') as file: file.write(output) - + print(f"Output written to {args.output_file}") if __name__ == "__main__": main() + diff --git a/scripts/verifyNTSServers.py b/scripts/verifyNTSServers.py new file mode 100755 index 0000000..6f37e57 --- /dev/null +++ b/scripts/verifyNTSServers.py @@ -0,0 +1,50 @@ +#!/usr/bin/python3 + +import yaml +import argparse +import subprocess +import re + +def load_yaml(file_path): + with open(file_path, 'r') as file: + return yaml.safe_load(file) + +def extract_hostname(hostname_field): + # Extract hostname from potential markdown link + match = re.search(r'\[([^\]]+)\]', hostname_field) + if match: + return match.group(1) + return hostname_field + +def verify_ntp_server(hostname): + print(f"Verifying {hostname} ...", end="", flush=True) + command = f"chronyd -Q -t 5 'server {hostname} iburst nts maxsamples 1'" + try: + result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True) + print(" Good") + print(result.stdout.strip()) + except subprocess.CalledProcessError as e: + print(" Failed") + print(f"Error verifying {hostname}: {e}") + print(e.output.strip()) + print() # Add a newline for better readability + +def main(): + parser = argparse.ArgumentParser(description="Verify NTP server connectivity") + parser.add_argument("yaml_file", help="Path to the input YAML file") + parser.add_argument("--hostname", help="Specific hostname to verify (optional)") + + args = parser.parse_args() + + data = load_yaml(args.yaml_file) + + if args.hostname: + verify_ntp_server(args.hostname) + else: + hostnames = [extract_hostname(server['hostname']) for server in data['servers']] + for hostname in hostnames: + verify_ntp_server(hostname) + +if __name__ == "__main__": + main() +