diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 9cfcd804b..6454756aa 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -8,6 +8,7 @@ All notable changes to the project are documented in this file. ### Changes +- Add NTP server support, issue #904 - Add support for configurable OSPF debug logging, issue #1281. Debug options can now be enabled per category (bfd, packet, ism, nsm, default-information, nssa). All debug options are disabled by default to prevent log flooding in diff --git a/doc/ntp.md b/doc/ntp.md new file mode 100644 index 000000000..a6a71bcb8 --- /dev/null +++ b/doc/ntp.md @@ -0,0 +1,160 @@ +# NTP Server + +The NTP (Network Time Protocol) server provides accurate time synchronization +for network clients. It supports both standalone operation with a local +reference clock and hybrid mode where it synchronizes with upstream servers +while serving time to downstream clients. + +> [!NOTE] +> The NTP server is mutually exclusive with the NTP client in system +> configuration context. + +## Standalone Mode + +Configure a standalone NTP server using only a local reference clock: + +``` +admin@example:/> configure +admin@example:/config/> edit ntp +admin@example:/config/ntp/> leave +``` + +When setting up NTP via the CLI the system automatically configures a local +reference clock with [stratum](#ntp-stratum-levels) 10. + +View the configuration: + +``` +admin@example:/> show running-config + "ietf-ntp:ntp": { + "refclock-master": { + "master-stratum": 10 + } + } +``` + +## Server Mode + +Synchronize from upstream NTP servers while serving time to clients: + +``` +admin@example:/config/> edit ntp +admin@example:/config/ntp/> edit unicast-configuration 0.pool.ntp.org type uc-server +admin@example:/config/ntp/…/0.pool.ntp.org/type/uc-server/> set iburst true +admin@example:/config/ntp/…/0.pool.ntp.org/type/uc-server/> end +admin@example:/config/ntp/> edit unicast-configuration 1.pool.ntp.org type uc-server +admin@example:/config/ntp/…/1.pool.ntp.org/type/uc-server/> set iburst true +admin@example:/config/ntp/…/1.pool.ntp.org/type/uc-server/> end +admin@example:/config/ntp/> leave +``` + +The `unicast-configuration` uses a composite key with both address and type. +Both hostnames and IP addresses are supported. The `iburst` option enables +fast initial synchronization. The local reference clock (stratum 10) is +automatically configured as a fallback. + +## Peer Mode + +In peer mode, two NTP servers synchronize with each other bidirectionally. +Each server acts as both client and server to the other: + +**First peer:** + +``` +admin@peer1:/config/> edit ntp +admin@peer1:/config/ntp/> edit unicast-configuration 192.168.1.2 type uc-peer +admin@peer1:/config/ntp/…/192.168.1.2/type/uc-peer/> end +admin@peer1:/config/ntp/> set refclock-master master-stratum 8 +admin@peer1:/config/ntp/> leave +``` + +**Second peer:** + +``` +admin@peer2:/config/> edit ntp +admin@peer2:/config/ntp/> edit unicast-configuration 192.168.1.1 type uc-peer +admin@peer2:/config/ntp/…/192.168.1.1/type/uc-peer/> end +admin@peer2:/config/ntp/> set refclock-master master-stratum 8 +admin@peer2:/config/ntp/> leave +``` + +This configuration provides mutual synchronization between peers. If one peer +fails, the other continues to serve time to clients. + +> [!NOTE] +> The `iburst` and `burst` options are not supported in peer mode. + +## Timing Configuration + +### Poll Intervals + +Control how often the NTP server polls upstream sources: + +``` +admin@example:/config/ntp/> edit unicast-configuration 0.pool.ntp.org type uc-server +admin@example:/config/ntp/…/0.pool.ntp.org/type/uc-server/> set minpoll 4 +admin@example:/config/ntp/…/0.pool.ntp.org/type/uc-server/> set maxpoll 10 +admin@example:/config/ntp/…/0.pool.ntp.org/type/uc-server/> end +``` + +Poll intervals are specified as powers of 2: +- `minpoll 4` = poll every 2^4 = 16 seconds (minimum polling rate) +- `maxpoll 10` = poll every 2^10 = 1024 seconds (maximum polling rate) +- Defaults: minpoll 6 (64 seconds), maxpoll 10 (1024 seconds) + +Use shorter intervals (minpoll 2-4) for faster convergence in test environments +or peer configurations. Use defaults for production servers. + +### Initial Synchronization + +Enable clock stepping for systems that boot with incorrect time: + +``` +admin@example:/config/ntp/> edit makestep +admin@example:/config/ntp/makestep/> set threshold 1.0 +admin@example:/config/ntp/makestep/> set limit 3 +admin@example:/config/ntp/makestep/> end +``` + +The `makestep` directive is automatically configured with safe defaults (1.0 +seconds threshold, 3 updates limit) when creating an NTP server. This is +critical for embedded systems without RTC that boot with epoch time. + +- **threshold** - If clock offset exceeds this (in seconds), step immediately + instead of slewing slowly +- **limit** - Number of updates during which stepping is allowed. After this, + only gradual slewing is used for security + +With these defaults, a device booting at epoch time (1970-01-01) will sync to +correct time within seconds instead of hours. + +## Monitoring + +Check NTP server statistics: + +``` +admin@example:/> show ntp server +NTP SERVER CONFIGURATION +Local Stratum : 10 + +SERVER STATISTICS +Packets Received : 142 +Packets Sent : 142 +Packets Dropped : 0 +Send Failures : 0 +``` + +## NTP Stratum Levels + +NTP uses a hierarchical system called **stratum** to indicate distance from +authoritative time sources: + +- **Stratum 0**: Reference clocks (atomic clocks, GPS receivers) +- **Stratum 1**: Servers directly connected to stratum 0 +- **Stratum 2-15**: Servers that sync from lower stratum (each hop adds one) +- **Stratum 16**: Unsynchronized (invalid) + +**Default Stratum 10**: Infix uses stratum 10 as the default for local +reference clocks. This is a safe, low-priority value that ensures clients +will prefer upstream-synchronized servers (stratum 1-9) while still having +a fallback time source in isolated networks. diff --git a/mkdocs.yml b/mkdocs.yml index 5d768c0c8..48c7eccc5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,6 +37,7 @@ nav: - Services: - Device Discovery: discovery.md - DHCP Server: dhcp.md + - NTP Server: ntp.md - System: - Boot Procedure: boot.md - Configuration: system.md diff --git a/src/confd/src/Makefile.am b/src/confd/src/Makefile.am index 8db05041e..1d379e698 100644 --- a/src/confd/src/Makefile.am +++ b/src/confd/src/Makefile.am @@ -40,6 +40,7 @@ confd_plugin_la_SOURCES = \ if-wifi.c \ keystore.c \ system.c \ + ntp.c \ syslog.c \ factory-default.c \ routing.c \ diff --git a/src/confd/src/core.c b/src/confd/src/core.c index e24dca9e3..4b875c910 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -206,6 +206,10 @@ static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *mod if ((rc = keystore_change(session, config, diff, event, confd))) goto free_diff; + /* ietf-ntp */ + if ((rc = ntp_change(session, config, diff, event, confd))) + goto free_diff; + /* infix-services */ if ((rc = services_change(session, config, diff, event, confd))) goto free_diff; @@ -244,6 +248,20 @@ static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *mod if ((rc = meta_change_cb(session, config, diff, event, confd))) goto free_diff; + /* + * Manage chronyd service enable/disable state. Must be done + * after both ietf-system:ntp and ietf-ntp have are done. + */ + if (event == SR_EV_DONE && config) { + bool client = false; + bool server = false; + + client = srx_enabled(session, "/ietf-system:system/ntp/enabled"); + server = lydx_get_xpathf(config, "/ietf-ntp:ntp") != NULL; + + systemf("initctl -nbq %s chronyd", client || server ? "enable" : "disable"); + } + if (cfg) sr_release_data(cfg); @@ -332,6 +350,11 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv) ERROR("Failed to subscribe to ietf-netconf-acm"); goto err; } + rc = subscribe_model("ietf-ntp", &confd, 0); + if (rc) { + ERROR("Failed to subscribe to ietf-ntp"); + goto err; + } rc = subscribe_model("infix-dhcp-client", &confd, 0); if (rc) { ERROR("Failed to subscribe to infix-dhcp-client"); @@ -432,6 +455,10 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv) goto err; rc = dhcp_server_candidate_init(&confd); + if (rc) + goto err; + + rc = ntp_candidate_init(&confd); if (rc) goto err; /* YOUR_INIT GOES HERE */ diff --git a/src/confd/src/core.h b/src/confd/src/core.h index d376aa49c..e65f27053 100644 --- a/src/confd/src/core.h +++ b/src/confd/src/core.h @@ -247,4 +247,10 @@ int firewall_rpc_init(struct confd *confd); int firewall_candidate_init(struct confd *confd); int firewall_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd); +/* ntp.c */ +int ntp_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd); +int ntp_cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module, + const char *path, sr_event_t event, unsigned request_id, void *priv); +int ntp_candidate_init(struct confd *confd); + #endif /* CONFD_CORE_H_ */ diff --git a/src/confd/src/ntp.c b/src/confd/src/ntp.c new file mode 100644 index 000000000..010aaded1 --- /dev/null +++ b/src/confd/src/ntp.c @@ -0,0 +1,311 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include +#include + +#include +#include +#include + +#include "core.h" + +#define XPATH_NTP_ "/ietf-ntp:ntp" +#define NTP_CONF "/etc/chrony/conf.d/ntp-server.conf" +#define NTP_PREV NTP_CONF "-" +#define NTP_NEXT NTP_CONF "+" + +/* + * NTP Server configuration handler + * + * This implements ietf-ntp (RFC 9249) for basic NTP server functionality + * using chronyd as the underlying daemon. The implementation is minimal, + * focusing on: + * + * - Local reference clock (stratum configuration) + * - Custom port (if not default 123) + * - Interface binding (restrict to specific interfaces) + * - Server mode enabling (allow clients to query) + * + * Mutual exclusion with ietf-system:ntp is enforced by YANG when statement, + * so no runtime validation is needed here. + */ +static int change_ntp_server(sr_session_ctx_t *session, + struct lyd_node *config, + struct lyd_node *diff, + sr_event_t event, + struct confd *confd) +{ + int port = 0, stratum = 0; + sr_val_t *val; + size_t cnt; + FILE *fp; + int rc; + + if (diff && !lydx_get_xpathf(diff, XPATH_NTP_)) + return SR_ERR_OK; + + switch (event) { + case SR_EV_ENABLED: /* first time, on register. */ + case SR_EV_CHANGE: /* regular change (copy cand running) */ + /* Generate next config */ + break; + + case SR_EV_ABORT: /* User abort, or other plugin failed */ + (void)remove(NTP_NEXT); + return SR_ERR_OK; + + case SR_EV_DONE: + /* Check if NTP container exists (presence container) */ + if (!lydx_get_xpathf(config, XPATH_NTP_)) { + DEBUG("NTP server disabled, removing config"); + systemf("rm -f %s", NTP_CONF); + + return SR_ERR_OK; + } + + /* Check if passed validation in previous event */ + if (!fexist(NTP_NEXT)) + return SR_ERR_OK; + + (void)remove(NTP_PREV); + (void)rename(NTP_CONF, NTP_PREV); + (void)rename(NTP_NEXT, NTP_CONF); + + /* Reload chronyd to pick up new config */ + if (systemf("chronyc reload sources >/dev/null 2>&1")) + ERRNO("Failed reloading chronyd sources"); + + systemf("initctl -nbq touch chronyd"); + return SR_ERR_OK; + + default: + return SR_ERR_OK; + } + + fp = fopen(NTP_NEXT, "w"); + if (!fp) { + ERROR("Failed creating %s: %s", NTP_NEXT, strerror(errno)); + return SR_ERR_SYS; + } + + fprintf(fp, "# Generated by ietf-ntp\n"); + fprintf(fp, "# This file configures chronyd as an NTP server\n\n"); + + /* Port configuration (optional) */ + SRX_GET_UINT32(session, port, XPATH_NTP_"/port"); + if (port && port != 123) { + fprintf(fp, "# Custom NTP port\n"); + fprintf(fp, "port %d\n\n", port); + NOTE("NTP server configured on port %d", port); + } + + /* makestep configuration - allow clock stepping for fast initial sync */ + if (lydx_get_xpathf(config, XPATH_NTP_"/infix-ntp:makestep")) { + char *threshold_str = srx_get_str(session, XPATH_NTP_"/infix-ntp:makestep/threshold"); + char *limit_str = srx_get_str(session, XPATH_NTP_"/infix-ntp:makestep/limit"); + double threshold = 1.0; /* Default */ + int limit = 3; /* Default */ + + if (threshold_str) { + threshold = atof(threshold_str); + free(threshold_str); + } + if (limit_str) { + limit = atoi(limit_str); + free(limit_str); + } + + fprintf(fp, "# Allow clock stepping for fast initial sync\n"); + fprintf(fp, "makestep %.1f %d\n\n", threshold, limit); + NOTE("NTP makestep configured: threshold %.1f seconds, limit %d updates", + threshold, limit); + } + + /* Upstream server/peer configuration (unicast-configuration) */ + rc = sr_get_items(session, XPATH_NTP_"/unicast-configuration", 0, 0, &val, &cnt); + if (rc == SR_ERR_OK && cnt > 0) { + fprintf(fp, "# Upstream NTP servers and peers\n"); + + for (size_t i = 0; i < cnt; i++) { + char *address = srx_get_str(session, "%s/address", val[i].xpath); + char *type = srx_get_str(session, "%s/type", val[i].xpath); + const char *directive = NULL; + + if (address) { + char *minpoll = srx_get_str(session, "%s/minpoll", val[i].xpath); + char *maxpoll = srx_get_str(session, "%s/maxpoll", val[i].xpath); + char *version = srx_get_str(session, "%s/version", val[i].xpath); + char *srcport = srx_get_str(session, "%s/port", val[i].xpath); + bool prefer = srx_enabled(session, "%s/prefer", val[i].xpath); + bool burst = srx_enabled(session, "%s/burst", val[i].xpath); + bool iburst = srx_enabled(session, "%s/iburst", val[i].xpath); + + if (type && strstr(type, "uc-server")) + directive = "server"; + else if (type && strstr(type, "uc-peer")) + directive = "peer"; + + if (directive) { + fprintf(fp, "%s %s", directive, address); + if (srcport) + fprintf(fp, " port %s", srcport); + if (iburst) + fprintf(fp, " iburst"); + if (burst) + fprintf(fp, " burst"); + if (prefer) + fprintf(fp, " prefer"); + if (minpoll) + fprintf(fp, " minpoll %s", minpoll); + if (maxpoll) + fprintf(fp, " maxpoll %s", maxpoll); + if (version) + fprintf(fp, " version %s", version); + fprintf(fp, "\n"); + + DEBUG("NTP %s: %s port %s%s%s%s minpoll %s maxpoll %s version %s", directive, address, + srcport ?: "123", + iburst ? " iburst" : "", + burst ? " burst " : "", + prefer ? " prefer" : "", + minpoll ?: "6", + maxpoll ?: "10", + version ?: "4"); + } + + if (minpoll) + free(minpoll); + if (maxpoll) + free(maxpoll); + if (version) + free(version); + if (srcport) + free(srcport); + free(address); + } + if (type) + free(type); + } + fprintf(fp, "\n"); + sr_free_values(val, cnt); + } + + /* Reference clock (local stratum) - fallback time source */ + if (lydx_get_xpathf(config, XPATH_NTP_"/refclock-master")) { + SRX_GET_UINT8(session, stratum, XPATH_NTP_"/refclock-master/master-stratum"); + if (!stratum) + stratum = 10; /* Default from RFC 9249 */ + + fprintf(fp, "# Local reference clock - fallback stratum %d source\n", stratum); + fprintf(fp, "local stratum %d orphan\n\n", stratum); + NOTE("NTP server configured with stratum %d fallback", stratum); + } else { + /* No refclock-master means server mode without local clock */ + /* This is valid - device can still serve time from upstream servers */ + DEBUG("NTP server without local reference clock"); + } + + /* + * Enable NTP server mode - allow clients to query us + * + * Using 'allow' without arguments permits all clients. + * In a future version with access-rules support, we could + * restrict to specific subnets. + */ + fprintf(fp, "# Allow NTP clients to query this server\n"); + fprintf(fp, "allow\n\n"); + + /* + * Enable RTC synchronization + * + * On Linux, the kernel will copy system time to the hardware RTC + * every 11 minutes when the clock is synchronized. This keeps the + * RTC accurate for the next boot, which is important for embedded + * systems without continuous network connectivity. + */ + fprintf(fp, "# Synchronize system time to hardware RTC\n"); + fprintf(fp, "rtcsync\n"); + + fclose(fp); + return SR_ERR_OK; +} + +/* + * Inference callback for NTP server configuration + * + * When a user creates the /ietf-ntp:ntp presence container without + * any configuration, we infer sensible defaults: + * + * - refclock-master with stratum 10 (local reference clock fallback) + * - makestep with threshold 1.0 and limit 3 (fast initial sync for embedded) + * + * This matches the DHCP client inference pattern where an empty + * container gets reasonable defaults. + */ +static int cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module, + const char *path, sr_event_t event, unsigned request_id, void *priv) +{ + sr_val_t inferred_uint8 = { .type = SR_UINT8_T }; + sr_val_t inferred_dec64 = { .type = SR_DECIMAL64_T }; + sr_val_t inferred_int32 = { .type = SR_INT32_T }; + size_t cnt = 0; + + if (event != SR_EV_UPDATE && event != SR_EV_CHANGE) + return SR_ERR_OK; + + /* Check if NTP container exists */ + if (srx_nitems(session, &cnt, XPATH_NTP_) || !cnt) + return SR_ERR_OK; + + /* Check if refclock-master already configured */ + if (!srx_nitems(session, &cnt, XPATH_NTP_"/refclock-master") && cnt > 0) + return SR_ERR_OK; + + /* Check if unicast-configuration already configured */ + if (!srx_nitems(session, &cnt, XPATH_NTP_"/unicast-configuration") && cnt > 0) + return SR_ERR_OK; + + /* Infer refclock-master with stratum 10 (safe fallback) */ + DEBUG("Inferring NTP refclock-master with stratum 10"); + inferred_uint8.data.uint8_val = 10; + srx_set_item(session, &inferred_uint8, 0, XPATH_NTP_"/refclock-master/master-stratum"); + + /* Infer makestep for fast initial sync (critical for embedded systems) */ + if (!srx_nitems(session, &cnt, XPATH_NTP_"/infix-ntp:makestep") || cnt == 0) { + DEBUG("Inferring NTP makestep with threshold 1.0, limit 3"); + + /* Create presence container by setting threshold */ + inferred_dec64.data.decimal64_val = 10; /* 1.0 with fraction-digits 1 */ + srx_set_item(session, &inferred_dec64, 0, XPATH_NTP_"/infix-ntp:makestep/threshold"); + + /* Set limit */ + inferred_int32.data.int32_val = 3; + srx_set_item(session, &inferred_int32, 0, XPATH_NTP_"/infix-ntp:makestep/limit"); + } + + return SR_ERR_OK; +} + +int ntp_change(sr_session_ctx_t *session, struct lyd_node *config, + struct lyd_node *diff, sr_event_t event, struct confd *confd) +{ + return change_ntp_server(session, config, diff, event, confd); +} + +int ntp_cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module, + const char *path, sr_event_t event, unsigned request_id, void *priv) +{ + return cand(session, sub_id, module, path, event, request_id, priv); +} + +int ntp_candidate_init(struct confd *confd) +{ + int rc; + + REGISTER_CHANGE(confd->cand, "ietf-ntp", XPATH_NTP_ "//.", SR_SUBSCR_UPDATE, ntp_cand, confd, &confd->sub); + + return SR_ERR_OK; +fail: + ERROR("init failed: %s", sr_strerror(rc)); + return rc; +} diff --git a/src/confd/src/system.c b/src/confd/src/system.c index 05ddc3eaa..33a371bed 100644 --- a/src/confd/src/system.c +++ b/src/confd/src/system.c @@ -283,7 +283,7 @@ static int change_clock(sr_session_ctx_t *session, struct lyd_node *config, stru return rc; } -static int change_ntp(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd) +static int change_ntp_client(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd) { sr_change_iter_t *iter = NULL; int rc, err = SR_ERR_OK; @@ -305,7 +305,8 @@ static int change_ntp(sr_session_ctx_t *session, struct lyd_node *config, struct case SR_EV_DONE: if (!srx_enabled(session, XPATH_NTP_"/enabled")) { systemf("rm -rf /etc/chrony/conf.d/* /etc/chrony/sources.d/*"); - systemf("initctl -nbq disable chronyd"); + /* Note: chronyd enable/disable is managed centrally in core.c */ + systemf("initctl -nbq touch chronyd"); return SR_ERR_OK; } @@ -314,7 +315,8 @@ static int change_ntp(sr_session_ctx_t *session, struct lyd_node *config, struct erase("/run/chrony/.changes"); } - systemf("initctl -nbq enable chronyd"); + /* Note: chronyd enable/disable is managed centrally in core.c */ + systemf("initctl -nbq touch chronyd"); return SR_ERR_OK; default: @@ -1608,7 +1610,7 @@ int system_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd if ((rc = change_auth(session, config, diff, event, confd))) return rc; - if ((rc = change_ntp(session, config, diff, event, confd))) + if ((rc = change_ntp_client(session, config, diff, event, confd))) return rc; if ((rc = change_dns(session, config, diff, event, confd))) return rc; diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index c9bb5e531..15d9287c2 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -3,6 +3,10 @@ MODULES=( "ietf-system@2014-08-06.yang -e authentication -e local-users -e ntp -e ntp-udp-port -e timezone-name" "iana-timezones@2013-11-19.yang" + "ietf-ntp@2022-07-05.yang -e ntp-port -e unicast-configuration" + "ietf-access-control-list@2019-03-04.yang" + "ietf-packet-fields@2019-03-04.yang" + "ietf-ethertypes@2019-03-04.yang" "ietf-interfaces@2018-02-20.yang -e if-mib" "ietf-ip@2018-02-22.yang -e ipv6-privacy-autoconf" "ietf-network-instance@2019-01-21.yang" @@ -47,4 +51,5 @@ MODULES=( "infix-crypto-types@2025-06-17.yang" "ietf-keystore -e symmetric-keys" "infix-keystore@2025-06-17.yang" + "infix-ntp@2025-12-03.yang" ) diff --git a/src/confd/yang/confd/ietf-access-control-list@2019-03-04.yang b/src/confd/yang/confd/ietf-access-control-list@2019-03-04.yang new file mode 100644 index 000000000..8bb0b6261 --- /dev/null +++ b/src/confd/yang/confd/ietf-access-control-list@2019-03-04.yang @@ -0,0 +1,668 @@ +module ietf-access-control-list { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-access-control-list"; + prefix acl; + + import ietf-yang-types { + prefix yang; + reference + "RFC 6991 - Common YANG Data Types."; + } + + import ietf-packet-fields { + prefix pf; + reference + "RFC 8519 - YANG Data Model for Network Access Control + Lists (ACLs)."; + } + + import ietf-interfaces { + prefix if; + reference + "RFC 8343 - A YANG Data Model for Interface Management."; + } + + organization + "IETF NETMOD (Network Modeling) Working Group."; + + contact + "WG Web: + WG List: netmod@ietf.org + + Editor: Mahesh Jethanandani + mjethanandani@gmail.com + Editor: Lisa Huang + huangyi_99@yahoo.com + Editor: Sonal Agarwal + sagarwal12@gmail.com + Editor: Dana Blair + dana@blairhome.com"; + + description + "This YANG module defines a component that describes the + configuration and monitoring of Access Control Lists (ACLs). + + The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', + 'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', + 'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document + are to be interpreted as described in BCP 14 (RFC 2119) + (RFC 8174) when, and only when, they appear in all + capitals, as shown here. + + Copyright (c) 2019 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD + License set forth in Section 4.c of the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8519; see + the RFC itself for full legal notices."; + + revision 2019-03-04 { + description + "Initial version."; + reference + "RFC 8519: YANG Data Model for Network Access Control + Lists (ACLs)."; + } + + /* + * Identities + */ + /* + * Forwarding actions for a packet + */ + + identity forwarding-action { + description + "Base identity for actions in the forwarding category."; + } + + identity accept { + base forwarding-action; + description + "Accept the packet."; + } + + identity drop { + base forwarding-action; + description + "Drop packet without sending any ICMP error message."; + } + + identity reject { + base forwarding-action; + description + "Drop the packet and send an ICMP error message to the source."; + } + + /* + * Logging actions for a packet + */ + + identity log-action { + description + "Base identity for defining the destination for logging + actions."; + } + + identity log-syslog { + base log-action; + description + "System log (syslog) the information for the packet."; + } + identity log-none { + base log-action; + description + "No logging for the packet."; + } + + /* + * ACL type identities + */ + + identity acl-base { + description + "Base Access Control List type for all Access Control List type + identifiers."; + } + + identity ipv4-acl-type { + base acl:acl-base; + if-feature "ipv4"; + description + "An ACL that matches on fields from the IPv4 header + (e.g., IPv4 destination address) and Layer 4 headers (e.g., TCP + destination port). An ACL of type ipv4 does not contain + matches on fields in the Ethernet header or the IPv6 header."; + } + + identity ipv6-acl-type { + base acl:acl-base; + if-feature "ipv6"; + description + "An ACL that matches on fields from the IPv6 header + (e.g., IPv6 destination address) and Layer 4 headers (e.g., TCP + destination port). An ACL of type ipv6 does not contain + matches on fields in the Ethernet header or the IPv4 header."; + } + + identity eth-acl-type { + base acl:acl-base; + if-feature "eth"; + description + "An ACL that matches on fields in the Ethernet header, + like 10/100/1000baseT or a Wi-Fi Access Control List. An ACL + of type ethernet does not contain matches on fields in the + IPv4 header, the IPv6 header, or Layer 4 headers."; + } + + identity mixed-eth-ipv4-acl-type { + base acl:eth-acl-type; + base acl:ipv4-acl-type; + if-feature "mixed-eth-ipv4"; + description + "An ACL that contains a mix of entries that match + on fields in Ethernet headers and in IPv4 headers. + Matching on Layer 4 header fields may also exist in the + list."; + } + + identity mixed-eth-ipv6-acl-type { + base acl:eth-acl-type; + base acl:ipv6-acl-type; + if-feature "mixed-eth-ipv6"; + description + "An ACL that contains a mix of entries that match on fields + in Ethernet headers and in IPv6 headers. Matching + on Layer 4 header fields may also exist in the list."; + } + + identity mixed-eth-ipv4-ipv6-acl-type { + base acl:eth-acl-type; + base acl:ipv4-acl-type; + base acl:ipv6-acl-type; + if-feature "mixed-eth-ipv4-ipv6"; + description + "An ACL that contains a mix of entries that + match on fields in Ethernet headers, IPv4 headers, and IPv6 + headers. Matching on Layer 4 header fields may also exist + in the list."; + } + + /* + * Features + */ + + /* + * Features supported by device + */ + feature match-on-eth { + description + "The device can support matching on Ethernet headers."; + } + + feature match-on-ipv4 { + description + "The device can support matching on IPv4 headers."; + } + + feature match-on-ipv6 { + description + "The device can support matching on IPv6 headers."; + } + + feature match-on-tcp { + description + "The device can support matching on TCP headers."; + } + + feature match-on-udp { + description + "The device can support matching on UDP headers."; + } + + feature match-on-icmp { + description + "The device can support matching on ICMP (v4 and v6) headers."; + } + + /* + * Header classifications combinations supported by + * device + */ + + feature eth { + if-feature "match-on-eth"; + description + "Plain Ethernet ACL supported."; + } + + feature ipv4 { + if-feature "match-on-ipv4"; + description + "Plain IPv4 ACL supported."; + } + + feature ipv6 { + if-feature "match-on-ipv6"; + description + "Plain IPv6 ACL supported."; + } + + feature mixed-eth-ipv4 { + if-feature "match-on-eth and match-on-ipv4"; + description + "Ethernet and IPv4 ACL combinations supported."; + } + feature mixed-eth-ipv6 { + if-feature "match-on-eth and match-on-ipv6"; + description + "Ethernet and IPv6 ACL combinations supported."; + } + + feature mixed-eth-ipv4-ipv6 { + if-feature + "match-on-eth and match-on-ipv4 + and match-on-ipv6"; + description + "Ethernet, IPv4, and IPv6 ACL combinations supported."; + } + + /* + * Stats Features + */ + feature interface-stats { + description + "ACL counters are available and reported only per interface."; + } + + feature acl-aggregate-stats { + description + "ACL counters are aggregated over all interfaces and reported + only per ACL entry."; + } + + /* + * Attachment point features + */ + feature interface-attachment { + description + "ACLs are set on interfaces."; + } + + /* + * Typedefs + */ + typedef acl-type { + type identityref { + base acl-base; + } + description + "This type is used to refer to an ACL type."; + } + + /* + * Groupings + */ + grouping acl-counters { + description + "Common grouping for ACL counters."; + leaf matched-packets { + type yang:counter64; + config false; + description + "Count of the number of packets matching the current ACL + entry. + + An implementation should provide this counter on a + per-interface, per-ACL-entry basis if possible. + + If an implementation only supports ACL counters on a per- + entry basis (i.e., not broken out per interface), then the + value should be equal to the aggregate count across all + interfaces. + + An implementation that provides counters on a per-entry, per- + interface basis is not required to also provide an aggregate + count, e.g., per entry -- the user is expected to be able to + implement the required aggregation if such a count is + needed."; + } + + leaf matched-octets { + type yang:counter64; + config false; + description + "Count of the number of octets (bytes) matching the current + ACL entry. + + An implementation should provide this counter on a + per-interface, per-ACL-entry basis if possible. + + If an implementation only supports ACL counters per entry + (i.e., not broken out per interface), then the value + should be equal to the aggregate count across all interfaces. + + An implementation that provides counters per entry per + interface is not required to also provide an aggregate count, + e.g., per entry -- the user is expected to be able to + implement the required aggregation if such a count is needed."; + } + } + + /* + * Configuration and monitoring data nodes + */ + + container acls { + description + "This is a top-level container for Access Control Lists. + It can have one or more acl nodes."; + list acl { + key "name"; + description + "An ACL is an ordered list of ACEs. Each ACE has a + list of match criteria and a list of actions. + Since there are several kinds of ACLs implemented + with different attributes for different vendors, + this model accommodates customizing ACLs for + each kind and for each vendor."; + leaf name { + type string { + length "1..64"; + } + description + "The name of the access list. A device MAY further + restrict the length of this name; space and special + characters are not allowed."; + } + leaf type { + type acl-type; + description + "Type of ACL. Indicates the primary intended + type of match criteria (e.g., Ethernet, IPv4, IPv6, mixed, + etc.) used in the list instance."; + } + container aces { + description + "The aces container contains one or more ACE nodes."; + list ace { + key "name"; + ordered-by user; + description + "List of ACEs."; + leaf name { + type string { + length "1..64"; + } + description + "A unique name identifying this ACE."; + } + container matches { + description + "The rules in this set determine what fields will be + matched upon before any action is taken on them. + The rules are selected based on the feature set + defined by the server and the acl-type defined. + If no matches are defined in a particular container, + then any packet will match that container. If no + matches are specified at all in an ACE, then any + packet will match the ACE."; + + choice l2 { + container eth { + when "derived-from-or-self(/acls/acl/type, " + + "'acl:eth-acl-type')"; + if-feature "match-on-eth"; + uses pf:acl-eth-header-fields; + description + "Rule set that matches Ethernet headers."; + } + description + "Match Layer 2 headers, for example, Ethernet + header fields."; + } + + choice l3 { + container ipv4 { + when "derived-from-or-self(/acls/acl/type, " + + "'acl:ipv4-acl-type')"; + if-feature "match-on-ipv4"; + uses pf:acl-ip-header-fields; + uses pf:acl-ipv4-header-fields; + description + "Rule set that matches IPv4 headers."; + } + + container ipv6 { + when "derived-from-or-self(/acls/acl/type, " + + "'acl:ipv6-acl-type')"; + if-feature "match-on-ipv6"; + uses pf:acl-ip-header-fields; + uses pf:acl-ipv6-header-fields; + description + "Rule set that matches IPv6 headers."; + } + description + "Choice of either IPv4 or IPv6 headers"; + } + choice l4 { + container tcp { + if-feature "match-on-tcp"; + uses pf:acl-tcp-header-fields; + container source-port { + choice source-port { + case range-or-operator { + uses pf:port-range-or-operator; + description + "Source port definition from range or + operator."; + } + description + "Choice of source port definition using + range/operator or a choice to support future + 'case' statements, such as one enabling a + group of source ports to be referenced."; + } + description + "Source port definition."; + } + container destination-port { + choice destination-port { + case range-or-operator { + uses pf:port-range-or-operator; + description + "Destination port definition from range or + operator."; + } + description + "Choice of destination port definition using + range/operator or a choice to support future + 'case' statements, such as one enabling a + group of destination ports to be referenced."; + } + description + "Destination port definition."; + } + description + "Rule set that matches TCP headers."; + } + + container udp { + if-feature "match-on-udp"; + uses pf:acl-udp-header-fields; + container source-port { + choice source-port { + case range-or-operator { + uses pf:port-range-or-operator; + description + "Source port definition from range or + operator."; + } + description + "Choice of source port definition using + range/operator or a choice to support future + 'case' statements, such as one enabling a + group of source ports to be referenced."; + } + description + "Source port definition."; + } + container destination-port { + choice destination-port { + case range-or-operator { + uses pf:port-range-or-operator; + description + "Destination port definition from range or + operator."; + } + description + "Choice of destination port definition using + range/operator or a choice to support future + 'case' statements, such as one enabling a + group of destination ports to be referenced."; + } + description + "Destination port definition."; + } + description + "Rule set that matches UDP headers."; + } + + container icmp { + if-feature "match-on-icmp"; + uses pf:acl-icmp-header-fields; + description + "Rule set that matches ICMP headers."; + } + description + "Choice of TCP, UDP, or ICMP headers."; + } + + leaf egress-interface { + type if:interface-ref; + description + "Egress interface. This should not be used if this ACL + is attached as an egress ACL (or the value should + equal the interface to which the ACL is attached)."; + } + + leaf ingress-interface { + type if:interface-ref; + description + "Ingress interface. This should not be used if this ACL + is attached as an ingress ACL (or the value should + equal the interface to which the ACL is attached)."; + } + } + + container actions { + description + "Definition of actions for this ace entry."; + leaf forwarding { + type identityref { + base forwarding-action; + } + mandatory true; + description + "Specifies the forwarding action per ace entry."; + } + + leaf logging { + type identityref { + base log-action; + } + default "log-none"; + description + "Specifies the log action and destination for + matched packets. Default value is not to log the + packet."; + } + } + container statistics { + if-feature "acl-aggregate-stats"; + config false; + description + "Statistics gathered across all attachment points for the + given ACL."; + uses acl-counters; + } + } + } + } + container attachment-points { + description + "Enclosing container for the list of + attachment points on which ACLs are set."; + /* + * Groupings + */ + grouping interface-acl { + description + "Grouping for per-interface ingress ACL data."; + container acl-sets { + description + "Enclosing container for the list of ingress ACLs on the + interface."; + list acl-set { + key "name"; + ordered-by user; + description + "List of ingress ACLs on the interface."; + leaf name { + type leafref { + path "/acls/acl/name"; + } + description + "Reference to the ACL name applied on the ingress."; + } + list ace-statistics { + if-feature "interface-stats"; + key "name"; + config false; + description + "List of ACEs."; + leaf name { + type leafref { + path "/acls/acl/aces/ace/name"; + } + description + "Name of the ace entry."; + } + uses acl-counters; + } + } + } + } + + list interface { + if-feature "interface-attachment"; + key "interface-id"; + description + "List of interfaces on which ACLs are set."; + + leaf interface-id { + type if:interface-ref; + description + "Reference to the interface id list key."; + } + + container ingress { + uses interface-acl; + description + "The ACLs applied to the ingress interface."; + } + container egress { + uses interface-acl; + description + "The ACLs applied to the egress interface."; + } + } + } + } +} diff --git a/src/confd/yang/confd/ietf-ethertypes@2019-03-04.yang b/src/confd/yang/confd/ietf-ethertypes@2019-03-04.yang new file mode 100644 index 000000000..fd055074a --- /dev/null +++ b/src/confd/yang/confd/ietf-ethertypes@2019-03-04.yang @@ -0,0 +1,381 @@ +module ietf-ethertypes { + namespace "urn:ietf:params:xml:ns:yang:ietf-ethertypes"; + prefix ethertypes; + + organization + "IETF NETMOD (Network Modeling) Working Group."; + + contact + "WG Web: + WG List: + + Editor: Mahesh Jethanandani + "; + + description + "This module contains common definitions for the + Ethertype used by different modules. It is a + placeholder module, till such time that IEEE + starts a project to define these Ethertypes + and publishes a standard. + + At that time, this module can be deprecated. + + Copyright (c) 2019 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD + License set forth in Section 4.c of the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8519; see + the RFC itself for full legal notices."; + + revision 2019-03-04 { + description + "Initial revision."; + reference + "RFC 8519: YANG Data Model for Network Access Control + Lists (ACLs)."; + } + + typedef ethertype { + type union { + type uint16; + type enumeration { + enum ipv4 { + value 2048; + description + "Internet Protocol version 4 (IPv4) with a + hex value of 0x0800."; + reference + "RFC 791: Internet Protocol."; + } + enum arp { + value 2054; + description + "Address Resolution Protocol (ARP) with a + hex value of 0x0806."; + reference + "RFC 826: An Ethernet Address Resolution Protocol: Or + Converting Network Protocol Addresses to 48.bit + Ethernet Address for Transmission on Ethernet + Hardware."; + } + enum wlan { + value 2114; + description + "Wake-on-LAN. Hex value of 0x0842."; + } + enum trill { + value 8947; + description + "Transparent Interconnection of Lots of Links. + Hex value of 0x22F3."; + reference + "RFC 6325: Routing Bridges (RBridges): Base Protocol + Specification."; + } + enum srp { + value 8938; + description + "Stream Reservation Protocol. Hex value of + 0x22EA."; + reference + "IEEE 801.1Q-2011."; + } + enum decnet { + value 24579; + description + "DECnet Phase IV. Hex value of 0x6003."; + } + enum rarp { + value 32821; + description + "Reverse Address Resolution Protocol. + Hex value 0x8035."; + reference + "RFC 903: A Reverse Address Resolution Protocol."; + } + enum appletalk { + value 32923; + description + "Appletalk (Ethertalk). Hex value of 0x809B."; + } + enum aarp { + value 33011; + description + "Appletalk Address Resolution Protocol. Hex value + of 0x80F3."; + } + enum vlan { + value 33024; + description + "VLAN-tagged frame (IEEE 802.1Q) and Shortest Path + Bridging IEEE 802.1aq with Network-Network + Interface (NNI) compatibility. Hex value of + 0x8100."; + reference + "IEEE 802.1Q."; + } + enum ipx { + value 33079; + description + "Internetwork Packet Exchange (IPX). Hex value + of 0x8137."; + } + enum qnx { + value 33284; + description + "QNX Qnet. Hex value of 0x8204."; + } + enum ipv6 { + value 34525; + description + "Internet Protocol Version 6 (IPv6). Hex value + of 0x86DD."; + reference + "RFC 8200: Internet Protocol, Version 6 (IPv6) + Specification + RFC 8201: Path MTU Discovery for IP version 6."; + } + enum efc { + value 34824; + description + "Ethernet flow control using pause frames. + Hex value of 0x8808."; + reference + "IEEE 802.1Qbb."; + } + enum esp { + value 34825; + description + "Ethernet Slow Protocol. Hex value of 0x8809."; + reference + "IEEE 802.3-2015."; + } + enum cobranet { + value 34841; + description + "CobraNet. Hex value of 0x8819."; + } + enum mpls-unicast { + value 34887; + description + "Multiprotocol Label Switching (MPLS) unicast traffic. + Hex value of 0x8847."; + reference + "RFC 3031: Multiprotocol Label Switching Architecture."; + } + enum mpls-multicast { + value 34888; + description + "MPLS multicast traffic. Hex value of 0x8848."; + reference + "RFC 3031: Multiprotocol Label Switching Architecture."; + } + enum pppoe-discovery { + value 34915; + description + "Point-to-Point Protocol over Ethernet. Used during + the discovery process. Hex value of 0x8863."; + reference + "RFC 2516: A Method for Transmitting PPP Over Ethernet + (PPPoE)."; + } + enum pppoe-session { + value 34916; + description + "Point-to-Point Protocol over Ethernet. Used during + session stage. Hex value of 0x8864."; + reference + "RFC 2516: A Method for Transmitting PPP Over Ethernet + (PPPoE)."; + } + enum intel-ans { + value 34925; + description + "Intel Advanced Networking Services. Hex value of + 0x886D."; + } + enum jumbo-frames { + value 34928; + description + "Jumbo frames or Ethernet frames with more than + 1500 bytes of payload, up to 9000 bytes."; + } + enum homeplug { + value 34939; + description + "Family name for the various power line + communications. Hex value of 0x887B."; + } + enum eap { + value 34958; + description + "Ethernet Access Protocol (EAP) over LAN. Hex value + of 0x888E."; + reference + "IEEE 802.1X."; + } + enum profinet { + value 34962; + description + "PROcess FIeld Net (PROFINET). Hex value of 0x8892."; + } + enum hyperscsi { + value 34970; + description + "Small Computer System Interface (SCSI) over Ethernet. + Hex value of 0x889A."; + } + enum aoe { + value 34978; + description + "Advanced Technology Advancement (ATA) over Ethernet. + Hex value of 0x88A2."; + } + enum ethercat { + value 34980; + description + "Ethernet for Control Automation Technology (EtherCAT). + Hex value of 0x88A4."; + } + enum provider-bridging { + value 34984; + description + "Provider Bridging (802.1ad) and Shortest Path Bridging + (801.1aq). Hex value of 0x88A8."; + reference + "IEEE 802.1ad and IEEE 802.1aq)."; + } + enum ethernet-powerlink { + value 34987; + description + "Ethernet Powerlink. Hex value of 0x88AB."; + } + enum goose { + value 35000; + description + "Generic Object Oriented Substation Event (GOOSE). + Hex value of 0x88B8."; + reference + "IEC/ISO 8802-2 and 8802-3."; + } + enum gse { + value 35001; + description + "Generic Substation Events. Hex value of 88B9."; + reference + "IEC 61850."; + } + enum sv { + value 35002; + description + "Sampled Value Transmission. Hex value of 0x88BA."; + reference + "IEC 61850."; + } + enum lldp { + value 35020; + description + "Link Layer Discovery Protocol (LLDP). Hex value of + 0x88CC."; + reference + "IEEE 802.1AB."; + } + enum sercos { + value 35021; + description + "Sercos Interface. Hex value of 0x88CD."; + } + enum wsmp { + value 35036; + description + "WAVE Short Message Protocol (WSMP). Hex value of + 0x88DC."; + } + enum homeplug-av-mme { + value 35041; + description + "HomePlug AV Mobile Management Entity (MME). Hex value + of 88E1."; + } + enum mrp { + value 35043; + description + "Media Redundancy Protocol (MRP). Hex value of + 0x88E3."; + reference + "IEC 62439-2."; + } + enum macsec { + value 35045; + description + "MAC Security. Hex value of 0x88E5."; + reference + "IEEE 802.1AE."; + } + enum pbb { + value 35047; + description + "Provider Backbone Bridges (PBB). Hex value of + 0x88E7."; + reference + "IEEE 802.1ah."; + } + enum cfm { + value 35074; + description + "Connectivity Fault Management (CFM). Hex value of + 0x8902."; + reference + "IEEE 802.1ag."; + } + enum fcoe { + value 35078; + description + "Fiber Channel over Ethernet (FCoE). Hex value of + 0x8906."; + reference + "T11 FC-BB-5."; + } + enum fcoe-ip { + value 35092; + description + "FCoE Initialization Protocol. Hex value of 0x8914."; + } + enum roce { + value 35093; + description + "RDMA over Converged Ethernet (RoCE). Hex value of + 0x8915."; + } + enum tte { + value 35101; + description + "TTEthernet Protocol Control Frame (TTE). Hex value + of 0x891D."; + reference + "SAE AS6802."; + } + enum hsr { + value 35119; + description + "High-availability Seamless Redundancy (HSR). Hex + value of 0x892F."; + reference + "IEC 62439-3:2016."; + } + } + } + description + "The uint16 type placeholder is defined to enable + users to manage their own ethertypes not + covered by the module. Otherwise, the module contains + enum definitions for the more commonly used ethertypes."; + } +} diff --git a/src/confd/yang/confd/ietf-ntp@2022-07-05.yang b/src/confd/yang/confd/ietf-ntp@2022-07-05.yang new file mode 100644 index 000000000..0ac851e25 --- /dev/null +++ b/src/confd/yang/confd/ietf-ntp@2022-07-05.yang @@ -0,0 +1,1482 @@ +module ietf-ntp { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-ntp"; + prefix ntp; + + import ietf-yang-types { + prefix yang; + reference + "RFC 6991: Common YANG Data Types"; + } + import ietf-inet-types { + prefix inet; + reference + "RFC 6991: Common YANG Data Types"; + } + import ietf-interfaces { + prefix if; + reference + "RFC 8343: A YANG Data Model for Interface Management"; + } + import ietf-system { + prefix sys; + reference + "RFC 7317: A YANG Data Model for System Management"; + } + import ietf-access-control-list { + prefix acl; + reference + "RFC 8519: YANG Data Model for Network Access Control + Lists (ACLs)"; + } + import ietf-routing-types { + prefix rt-types; + reference + "RFC 8294: Common YANG Data Types for the Routing Area"; + } + import ietf-netconf-acm { + prefix nacm; + reference + "RFC 8341: Network Configuration Access Control Model"; + } + + organization + "IETF NTP (Network Time Protocol) Working Group"; + contact + "WG Web: + WG List: + Editor: Ankit Kumar Sinha + "; + description + "This document defines a YANG data model that can be used + to configure and manage Network Time Protocol (NTP) version 4. + It can also be used to configure and manage version 3. + The data model includes configuration data and state data. + + The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL + NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', + 'MAY', and 'OPTIONAL' in this document are to be interpreted as + described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, + they appear in all capitals, as shown here. + + Copyright (c) 2022 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Revised BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 9249; see the + RFC itself for full legal notices."; + + revision 2022-07-05 { + description + "Initial revision"; + reference + "RFC 9249: A YANG Data Model for NTP"; + } + + /* Typedef Definitions */ + + typedef ntp-stratum { + type uint8 { + range "1..16"; + } + description + "The level of each server in the hierarchy is defined by + a stratum. Primary servers are assigned with stratum + one; secondary servers at each lower level are assigned with + one stratum greater than the preceding level."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 3"; + } + + typedef ntp-version { + type uint8 { + range "3..max"; + } + default "4"; + description + "The current NTP version supported by the corresponding + association"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 1"; + } + + typedef refid { + type union { + type inet:ipv4-address; + type uint32; + type string { + length "4"; + } + } + description + "A code identifying the particular server or reference + clock. The interpretation depends upon stratum. It + could be an IPv4 address, the first 32 bits of the MD5 hash + of the IPv6 address, or a string for the Reference Identifier + and kiss codes. Some examples: + + -- a refclock ID like '127.127.1.0' for local clock sync + + -- uni/multi/broadcast associations for IPv4 will look like + '203.0.113.1' and '0x4321FEDC' for IPv6 + + -- sync with a primary source will look like 'DCN', 'NIST', + 'ATOM' + + -- kiss codes will look like 'AUTH', 'DROP', or 'RATE' + + Note that the use of an MD5 hash for IPv6 addresses is not + for cryptographic purposes."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.3"; + } + + typedef ntp-date-and-time { + type union { + type yang:date-and-time; + type uint8; + } + description + "Follows the date-and-time format when valid values exist. + Otherwise, allows for setting a special value such as + zero."; + reference + "RFC 6991: Common YANG Data Types"; + } + + typedef log2seconds { + type int8; + description + "An 8-bit signed integer that represents signed log2 + seconds."; + } + + /* features */ + + feature ntp-port { + description + "Support for NTP port configuration"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.2"; + } + + feature authentication { + description + "Support for NTP symmetric key authentication"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.3"; + } + + feature deprecated { + description + "Support deprecated MD5-based authentication (RFC 8573), + SHA-1, or any other deprecated authentication mechanism. + It is enabled to support legacy compatibility when secure + cryptographic algorithms are not available to use. + It is also used to configure keystrings in ASCII format."; + reference + "RFC 1321: The MD5 Message-Digest Algorithm, + RFC 3174: US Secure Hash Algorithm 1 (SHA1), + SHS: Secure Hash Standard (SHS) (FIPS PUB 180-4)"; + } + + feature hex-key-string { + description + "Support hexadecimal key string"; + } + + feature access-rules { + description + "Support for NTP access control"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 9.2"; + } + + feature unicast-configuration { + description + "Support for NTP client/server or active/passive + in unicast"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 3"; + } + + feature broadcast-server { + description + "Support for broadcast server"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 3"; + } + + feature broadcast-client { + description + "Support for broadcast client"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 3"; + } + + feature multicast-server { + description + "Support for multicast server"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 3.1"; + } + feature multicast-client { + description + "Support for multicast client"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 3.1"; + } + + feature manycast-server { + description + "Support for manycast server"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 3.1"; + } + + feature manycast-client { + description + "Support for manycast client"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 3.1"; + } + + /* Identity */ + /* unicast-configurations types */ + + identity unicast-configuration-type { + if-feature "unicast-configuration"; + description + "This defines NTP unicast mode of operation as used + for unicast-configurations."; + } + + identity uc-server { + if-feature "unicast-configuration"; + base unicast-configuration-type; + description + "Use client association mode where the unicast server + address is configured."; + } + + identity uc-peer { + if-feature "unicast-configuration"; + base unicast-configuration-type; + description + "Use symmetric active association mode where the peer + address is configured."; + } + + /* association-modes */ + + identity association-mode { + description + "The NTP association modes"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 3"; + } + + identity active { + base association-mode; + description + "Use symmetric active association mode (mode 1). + This device may synchronize with its NTP peer + or provide synchronization to a configured NTP peer."; + } + + identity passive { + base association-mode; + description + "Use symmetric passive association mode (mode 2). + This device has learned this association dynamically. + This device may synchronize with its NTP peer."; + } + + identity client { + base association-mode; + description + "Use client association mode (mode 3). + This device will not provide synchronization + to the configured NTP server."; + } + + identity server { + base association-mode; + description + "Use server association mode (mode 4). + This device will provide synchronization to + NTP clients."; + } + + identity broadcast-server { + base association-mode; + description + "Use broadcast server mode (mode 5). + This mode defines that it's either working + as a broadcast server or a multicast server."; + } + + identity broadcast-client { + base association-mode; + description + "This mode defines that it's either working + as a broadcast client (mode 6) or a multicast client."; + } + + /* access-mode */ + + identity access-mode { + if-feature "access-rules"; + description + "This defines NTP access-modes. These identify + how the ACL is applied with NTP."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 9.2"; + } + + identity peer-access-mode { + if-feature "access-rules"; + base access-mode; + description + "Permit others to synchronize their time with this NTP + or vice versa. + NTP control queries are also accepted. This enables + full access authority."; + } + + identity server-access-mode { + if-feature "access-rules"; + base access-mode; + description + "Permit others to synchronize their time with this NTP + entity, but vice versa is not supported. NTP control + queries are accepted."; + } + + identity server-only-access-mode { + if-feature "access-rules"; + base access-mode; + description + "Permit others to synchronize their time with this NTP + entity, but vice versa is not supported. NTP control + queries are not accepted."; + } + + identity query-only-access-mode { + if-feature "access-rules"; + base access-mode; + description + "Only control queries are accepted."; + } + + /* clock-state */ + + identity clock-state { + description + "This defines NTP clock status at a high level."; + } + + identity synchronized { + base clock-state; + description + "Indicates that the local clock has been synchronized with + an NTP server or the reference clock."; + } + + identity unsynchronized { + base clock-state; + description + "Indicates that the local clock has not been synchronized + with any NTP server."; + } + + /* ntp-sync-state */ + + identity ntp-sync-state { + description + "This defines NTP clock sync state at a more granular + level. Referred to as 'Clock state definitions' in + RFC 5905."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Appendix A.1.1"; + } + + identity clock-never-set { + base ntp-sync-state; + description + "Indicates the clock was never set."; + } + + identity freq-set-by-cfg { + base ntp-sync-state; + description + "Indicates the clock frequency is set by + NTP configuration or file."; + } + + identity spike { + base ntp-sync-state; + description + "Indicates a spike is detected."; + } + + identity freq { + base ntp-sync-state; + description + "Indicates the frequency mode."; + } + + identity clock-synchronized { + base ntp-sync-state; + description + "Indicates that the clock is synchronized."; + } + + /* crypto-algorithm */ + + identity crypto-algorithm { + description + "Base identity of cryptographic algorithm options."; + } + + identity md5 { + if-feature "deprecated"; + base crypto-algorithm; + description + "The MD5 algorithm. Note that RFC 8573 + deprecates the use of MD5-based authentication."; + reference + "RFC 1321: The MD5 Message-Digest Algorithm"; + } + + identity sha-1 { + if-feature "deprecated"; + base crypto-algorithm; + description + "The SHA-1 algorithm"; + reference + "RFC 3174: US Secure Hash Algorithm 1 (SHA1)"; + } + + identity hmac-sha-1 { + if-feature "deprecated"; + base crypto-algorithm; + description + "HMAC-SHA-1 authentication algorithm"; + reference + "SHS: Secure Hash Standard (SHS) (FIPS PUB 180-4)"; + } + + identity hmac-sha1-12 { + if-feature "deprecated"; + base crypto-algorithm; + description + "The HMAC-SHA1-12 algorithm"; + } + + identity hmac-sha-256 { + description + "HMAC-SHA-256 authentication algorithm"; + reference + "SHS: Secure Hash Standard (SHS) (FIPS PUB 180-4)"; + } + + identity hmac-sha-384 { + description + "HMAC-SHA-384 authentication algorithm"; + reference + "SHS: Secure Hash Standard (SHS) (FIPS PUB 180-4)"; + } + + identity hmac-sha-512 { + description + "HMAC-SHA-512 authentication algorithm"; + reference + "SHS: Secure Hash Standard (SHS) (FIPS PUB 180-4)"; + } + + identity aes-cmac { + base crypto-algorithm; + description + "The AES-CMAC algorithm -- required by + RFC 8573 for MAC for the NTP."; + reference + "RFC 4493: The AES-CMAC Algorithm, + RFC 8573: Message Authentication Code for the Network + Time Protocol"; + } + + /* Groupings */ + + grouping key { + description + "The key"; + nacm:default-deny-all; + choice key-string-style { + description + "Key string styles"; + case keystring { + leaf keystring { + if-feature "deprecated"; + type string; + description + "Key string in ASCII format"; + } + } + case hexadecimal { + if-feature "hex-key-string"; + leaf hexadecimal-string { + type yang:hex-string; + description + "Key in hexadecimal string format. When compared + to ASCII, specification in hexadecimal affords + greater key entropy with the same number of + internal key-string octets. Additionally, it + discourages use of well-known words or + numbers."; + } + } + } + } + + grouping authentication-key { + description + "To define an authentication key for an NTP + time source."; + leaf keyid { + type uint32 { + range "1..max"; + } + description + "Authentication key identifier"; + } + leaf algorithm { + type identityref { + base crypto-algorithm; + } + description + "Authentication algorithm. Note that RFC 8573 + deprecates the use of MD5-based authentication + and recommends AES-CMAC."; + } + container key { + uses key; + description + "The key. Note that RFC 8573 deprecates the use + of MD5-based authentication."; + } + leaf istrusted { + type boolean; + description + "Keyid is trusted or not"; + } + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Sections 7.3 and 7.4"; + } + + grouping authentication { + description + "Authentication"; + choice authentication-type { + description + "Type of authentication"; + case symmetric-key { + leaf keyid { + type leafref { + path "/ntp:ntp/ntp:authentication/" + + "ntp:authentication-keys/ntp:keyid"; + } + description + "Authentication key id referenced in this + association."; + } + } + } + } + + grouping statistics { + description + "NTP packet statistic"; + leaf discontinuity-time { + type ntp-date-and-time; + description + "The time on the most recent occasion at which any one or + more of these NTP counters suffered a discontinuity. If + no such discontinuities have occurred, then this node + contains the time the NTP association was + (re-)initialized."; + } + leaf packet-sent { + type yang:counter32; + description + "The total number of NTP packets delivered to the + transport service by this NTP entity for this + association. + Discontinuities in the value of this counter can occur + upon cold start, reinitialization of the NTP entity or the + management system, and at other times."; + } + leaf packet-sent-fail { + type yang:counter32; + description + "The number of times NTP packet sending failed."; + } + leaf packet-received { + type yang:counter32; + description + "The total number of NTP packets delivered to the + NTP entity from this association. + Discontinuities in the value of this counter can occur + upon cold start, reinitialization of the NTP entity or the + management system, and at other times."; + } + leaf packet-dropped { + type yang:counter32; + description + "The total number of NTP packets that were delivered + to this NTP entity from this association and that this + entity was not able to process due to an NTP error. + Discontinuities in the value of this counter can occur + upon cold start, reinitialization of the NTP entity or the + management system, and at other times."; + } + } + + grouping common-attributes { + description + "NTP common attributes for configuration"; + leaf minpoll { + type log2seconds; + default "6"; + description + "The minimum poll interval used in this association"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.2"; + } + leaf maxpoll { + type log2seconds; + default "10"; + description + "The maximum poll interval used in this association"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.2"; + } + leaf port { + if-feature "ntp-port"; + type inet:port-number { + range "123 | 1024..max"; + } + default "123"; + description + "Specify the port used to send NTP packets."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.2"; + } + leaf version { + type ntp-version; + description + "NTP version"; + } + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification"; + } + + grouping association-ref { + description + "Reference to NTP association mode"; + leaf associations-address { + type leafref { + path "/ntp:ntp/ntp:associations/ntp:association" + + "/ntp:address"; + } + description + "Indicates the association's address + that results in clock synchronization."; + } + leaf associations-local-mode { + type leafref { + path "/ntp:ntp/ntp:associations/ntp:association" + + "/ntp:local-mode"; + } + description + "Indicates the association's local-mode + that results in clock synchronization."; + } + leaf associations-isconfigured { + type leafref { + path "/ntp:ntp/ntp:associations/ntp:association/" + + "ntp:isconfigured"; + } + description + "Indicates if the association (that resulted in the + clock synchronization) is explicitly configured."; + } + } + + container ntp { + when 'false() = boolean(/sys:system/sys:ntp)' { + description + "Applicable when the system /sys/ntp/ is not used."; + } + presence "NTP is enabled and system should attempt to + synchronize the system clock with an NTP server + from the 'ntp/associations' list."; + description + "Configuration parameters for NTP"; + leaf port { + if-feature "ntp-port"; + type inet:port-number { + range "123 | 1024..max"; + } + default "123"; + description + "Specify the port used to send and receive NTP packets."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.2"; + } + container refclock-master { + presence "NTP master clock is enabled."; + description + "Configures the local clock of this device as NTP server."; + leaf master-stratum { + type ntp-stratum; + default "16"; + description + "Stratum level from which NTP clients get their time + synchronized."; + } + } + container authentication { + if-feature "authentication"; + description + "Configuration of authentication"; + leaf auth-enabled { + type boolean; + default "false"; + description + "Controls whether NTP authentication is enabled + or disabled on this device."; + } + list authentication-keys { + key "keyid"; + uses authentication-key; + description + "List of authentication keys"; + } + } + container access-rules { + if-feature "access-rules"; + description + "Configuration to control access to NTP service + by using the NTP access-group feature. + The access-mode identifies how the ACL is + applied with NTP."; + list access-rule { + key "access-mode"; + description + "List of access rules"; + leaf access-mode { + type identityref { + base access-mode; + } + description + "The NTP access-mode. Some of the possible values + include peer, server, synchronization, query, + etc."; + } + leaf acl { + type leafref { + path "/acl:acls/acl:acl/acl:name"; + } + description + "Control access configuration to be used."; + } + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 9.2"; + } + } + container clock-state { + config false; + description + "Clock operational state of the NTP"; + container system-status { + description + "System status of NTP"; + leaf clock-state { + type identityref { + base clock-state; + } + mandatory true; + description + "The state of the system clock. Some of the possible + values include synchronized and unsynchronized."; + } + leaf clock-stratum { + type ntp-stratum; + mandatory true; + description + "The NTP entity's own stratum value. Should be one + greater than the preceding level. + 16 if unsynchronized."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 3"; + } + leaf clock-refid { + type refid; + mandatory true; + description + "A code identifying the particular server or reference + clock. The interpretation depends upon stratum. It + could be an IPv4 address, the first 32 bits of the MD5 + hash of the IPv6 address, or a string for the Reference + Identifier and kiss codes. Some examples: + + -- a refclock ID like '127.127.1.0' for local clock sync + + -- uni/multi/broadcast associations for IPv4 will look + like '203.0.113.1' and '0x4321FEDC' for IPv6 + + -- sync with primary source will look like 'DCN', + 'NIST', 'ATOM' + + -- kiss codes will look like 'AUTH', 'DROP', 'RATE' + + Note that the use of MD5 hash for IPv6 address is not + for cryptographic purposes."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.3"; + } + uses association-ref { + description + "Reference to association"; + } + leaf nominal-freq { + type decimal64 { + fraction-digits 4; + } + units "Hz"; + mandatory true; + description + "The nominal frequency of the local clock. An ideal + frequency with zero uncertainty."; + } + leaf actual-freq { + type decimal64 { + fraction-digits 4; + } + units "Hz"; + mandatory true; + description + "The actual frequency of the local clock"; + } + leaf clock-precision { + type log2seconds; + mandatory true; + description + "Clock precision of this system in signed integer format, + in log 2 seconds - (prec=2^(-n)). A value of 5 would + mean 2^-5 = 0.03125 seconds = 31.25 ms."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.3"; + } + leaf clock-offset { + type decimal64 { + fraction-digits 3; + } + units "milliseconds"; + description + "The signed time offset to the current selected reference + time source, e.g., '0.032ms' or '1.232ms'. The negative + value indicates that the local clock is behind the + current selected reference time source."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 9.1"; + } + leaf root-delay { + type decimal64 { + fraction-digits 3; + } + units "milliseconds"; + description + "Total delay along the path to the root clock"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Sections 4 and 7.3"; + } + leaf root-dispersion { + type decimal64 { + fraction-digits 3; + } + units "milliseconds"; + description + "The dispersion to the local clock + and the root clock, e.g., '6.927ms'."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Sections 4, 7.3, and 10"; + } + leaf reference-time { + type ntp-date-and-time; + description + "The reference timestamp. Time when the system clock was + last set or corrected."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.3"; + } + leaf sync-state { + type identityref { + base ntp-sync-state; + } + mandatory true; + description + "The synchronization status of the local clock. Referred + to as 'Clock state definitions' in RFC 5905."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Appendix A.1.1"; + } + } + } + list unicast-configuration { + if-feature "unicast-configuration"; + key "address type"; + description + "List of NTP unicast-configurations"; + leaf address { + type inet:ip-address; + description + "Address of this association"; + } + leaf type { + type identityref { + base unicast-configuration-type; + } + description + "The unicast configuration type, for example, + unicast-server"; + } + container authentication { + if-feature "authentication"; + description + "Authentication used for this association"; + uses authentication; + } + leaf prefer { + type boolean; + default "false"; + description + "Whether or not this association is preferred"; + } + leaf burst { + type boolean; + default "false"; + description + "If set, a series of packets are sent instead of a single + packet within each synchronization interval to achieve + faster synchronization."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 13.1"; + } + leaf iburst { + type boolean; + default "false"; + description + "If set, a series of packets are sent instead of a single + packet within the initial synchronization interval to + achieve faster initial synchronization."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 13.1"; + } + leaf source { + type if:interface-ref; + description + "The interface whose IP address is used by this association + as the source address."; + } + uses common-attributes { + description + "Common attributes like port, version, and min and max + poll."; + } + } + container associations { + description + "Association parameters"; + list association { + key "address local-mode isconfigured"; + config false; + description + "List of NTP associations. Here address, local-mode, + and isconfigured are required to uniquely identify + a particular association. Let's take the following + examples: + + 1) If RT1 is acting as broadcast server + and RT2 is acting as broadcast client, then RT2 + will form a dynamic association with the address as + RT1, local-mode as client, and isconfigured as false. + + 2) When RT2 is configured with unicast server RT1, + then RT2 will form an association with the address as + RT1, local-mode as client, and isconfigured as true. + + Thus, all three leaves are needed as key to uniquely + identify the association."; + leaf address { + type inet:ip-address; + description + "The remote address of this association. Represents the + IP address of a unicast/multicast/broadcast address."; + } + leaf local-mode { + type identityref { + base association-mode; + } + description + "Local-mode of this NTP association"; + } + leaf isconfigured { + type boolean; + description + "Indicates if this association is configured (true) or + dynamically learned (false)."; + } + leaf stratum { + type ntp-stratum; + description + "The association stratum value"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 3"; + } + leaf refid { + type refid; + description + "A code identifying the particular server or reference + clock. The interpretation depends upon stratum. It + could be an IPv4 address or first 32 bits of the MD5 + hash of the IPv6 address or a string for the Reference + Identifier and kiss codes. Some examples: + + -- a refclock ID like '127.127.1.0' for local clock sync + + -- uni/multi/broadcast associations for IPv4 will look + like '203.0.113.1' and '0x4321FEDC' for IPv6 + + -- sync with primary source will look like 'DCN', + 'NIST', or 'ATOM' + + -- kiss codes will look like 'AUTH', 'DROP', or 'RATE' + + Note that the use of an MD5 hash for IPv6 address is + not for cryptographic purposes."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.3"; + } + leaf authentication { + if-feature "authentication"; + type leafref { + path "/ntp:ntp/ntp:authentication/" + + "ntp:authentication-keys/ntp:keyid"; + } + description + "Authentication key used for this association"; + } + leaf prefer { + type boolean; + default "false"; + description + "Indicates if this association is preferred"; + } + leaf peer-interface { + type if:interface-ref; + description + "The interface that is used for communication"; + } + uses common-attributes { + description + "Common attributes like port, version, and min and + max poll"; + } + leaf reach { + type uint8; + description + "An 8-bit shift register that tracks packet + generation and receipt. It is used to determine + whether the server is reachable and the data are + fresh."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Sections 9.2 and 13"; + } + leaf unreach { + type uint8; + units "seconds"; + description + "A count of how long in second the server has been + unreachable, i.e., the reach value has been zero."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Sections 9.2 and 13"; + } + leaf poll { + type log2seconds; + description + "The polling interval for current association in signed + log2 seconds."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 7.3"; + } + leaf now { + type uint32; + units "seconds"; + description + "The time since the last NTP packet was + received or last synchronized."; + } + leaf offset { + type decimal64 { + fraction-digits 3; + } + units "milliseconds"; + description + "The signed offset between the local clock + and the peer clock, e.g., '0.032ms' or '1.232ms'. The + negative value indicates that the local clock is behind + the peer."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 8"; + } + leaf delay { + type decimal64 { + fraction-digits 3; + } + units "milliseconds"; + description + "The network delay between the local clock + and the peer clock"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 8"; + } + leaf dispersion { + type decimal64 { + fraction-digits 3; + } + units "milliseconds"; + description + "The root dispersion between the local clock + and the peer clock."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 10"; + } + leaf originate-time { + type ntp-date-and-time; + description + "This is the local time, in timestamp format, + when the latest NTP packet was sent to the peer + (called T1)."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol and + Algorithms Specification, Section 8"; + } + leaf receive-time { + type ntp-date-and-time; + description + "This is the local time, in timestamp format, + when the latest NTP packet arrived at the peer + (called T2). If the peer becomes unreachable, + the value is set to zero."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 8"; + } + leaf transmit-time { + type ntp-date-and-time; + description + "This is the local time, in timestamp format, + at which the NTP packet departed the peer + (called T3). If the peer becomes unreachable, + the value is set to zero."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 8"; + } + leaf input-time { + type ntp-date-and-time; + description + "This is the local time, in timestamp format, + when the latest NTP message from the peer arrived + (called T4). If the peer becomes unreachable, + value is set to zero."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 8"; + } + container ntp-statistics { + description + "Per peer packet send and receive statistics"; + uses statistics { + description + "NTP send and receive packet statistics"; + } + } + } + } + container interfaces { + description + "Configuration parameters for NTP interfaces"; + list interface { + key "name"; + description + "List of interfaces"; + leaf name { + type if:interface-ref; + description + "The interface name"; + } + container broadcast-server { + if-feature "broadcast-server"; + presence "NTP broadcast-server is configured on this + interface."; + description + "Configuration of broadcast server"; + leaf ttl { + type uint8; + description + "Specifies the time to live (TTL) for a + broadcast packet"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 3.1"; + } + container authentication { + if-feature "authentication"; + description + "Authentication used on this interface"; + uses authentication; + } + uses common-attributes { + description + "Common attributes such as port, version, and min and + max poll"; + } + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 3.1"; + } + container broadcast-client { + if-feature "broadcast-client"; + presence "NTP broadcast-client is configured on this + interface."; + description + "Configuration of broadcast client"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 3.1"; + } + list multicast-server { + if-feature "multicast-server"; + key "address"; + description + "Configuration of multicast server"; + leaf address { + type rt-types:ip-multicast-group-address; + description + "The IP address to send NTP multicast packets"; + } + leaf ttl { + type uint8; + description + "Specifies the TTL for a multicast packet"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 3.1"; + } + container authentication { + if-feature "authentication"; + description + "Authentication used on this interface"; + uses authentication; + } + uses common-attributes { + description + "Common attributes such as port, version, and min and + max poll"; + } + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 3.1"; + } + list multicast-client { + if-feature "multicast-client"; + key "address"; + description + "Configuration of a multicast client"; + leaf address { + type rt-types:ip-multicast-group-address; + description + "The IP address of the multicast group to + join"; + } + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 3.1"; + } + list manycast-server { + if-feature "manycast-server"; + key "address"; + description + "Configuration of a manycast server"; + leaf address { + type rt-types:ip-multicast-group-address; + description + "The multicast group IP address to receive + manycast client messages."; + } + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 3.1"; + } + list manycast-client { + if-feature "manycast-client"; + key "address"; + description + "Configuration of manycast-client"; + leaf address { + type rt-types:ip-multicast-group-address; + description + "The group IP address that the manycast client + broadcasts the request message to"; + } + container authentication { + if-feature "authentication"; + description + "Authentication used on this interface"; + uses authentication; + } + leaf ttl { + type uint8; + description + "Specifies the maximum TTL for the expanding + ring search"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 3.1"; + } + leaf minclock { + type uint8; + description + "The minimum manycast survivors in this + association"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 13.2"; + } + leaf maxclock { + type uint8; + description + "The maximum manycast candidates in this + association"; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 13.2"; + } + leaf beacon { + type log2seconds; + description + "The beacon is the upper limit of the poll interval. + When the TTL reaches its limit without finding the + minimum number of manycast servers, the poll interval + increases until reaching the beacon value, when it + starts over from the beginning."; + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 13.2"; + } + uses common-attributes { + description + "Common attributes like port, version, and min and + max poll"; + } + reference + "RFC 5905: Network Time Protocol Version 4: Protocol + and Algorithms Specification, Section 3.1"; + } + } + } + container ntp-statistics { + config false; + description + "Total NTP packet statistics"; + uses statistics { + description + "NTP send and receive packet statistics"; + } + } + } + + rpc statistics-reset { + description + "Reset statistics collected."; + input { + choice association-or-all { + description + "Resets statistics for a particular association or + all."; + case association { + uses association-ref; + description + "This resets all the statistics collected for + the association."; + } + case all { + description + "This resets all the statistics collected."; + } + } + } + } +} diff --git a/src/confd/yang/confd/ietf-packet-fields@2019-03-04.yang b/src/confd/yang/confd/ietf-packet-fields@2019-03-04.yang new file mode 100644 index 000000000..2fb797bd8 --- /dev/null +++ b/src/confd/yang/confd/ietf-packet-fields@2019-03-04.yang @@ -0,0 +1,576 @@ +module ietf-packet-fields { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-packet-fields"; + prefix packet-fields; + + import ietf-inet-types { + prefix inet; + reference + "RFC 6991 - Common YANG Data Types."; + } + + import ietf-yang-types { + prefix yang; + reference + "RFC 6991 - Common YANG Data Types."; + } + + import ietf-ethertypes { + prefix eth; + reference + "RFC 8519 - YANG Data Model for Network Access Control + Lists (ACLs)."; + } + + organization + "IETF NETMOD (Network Modeling) Working Group."; + + contact + "WG Web: + WG List: netmod@ietf.org + + Editor: Mahesh Jethanandani + mjethanandani@gmail.com + Editor: Lisa Huang + huangyi_99@yahoo.com + Editor: Sonal Agarwal + sagarwal12@gmail.com + Editor: Dana Blair + dana@blairhome.com"; + + description + "This YANG module defines groupings that are used by + the ietf-access-control-list YANG module. Their usage + is not limited to ietf-access-control-list and can be + used anywhere as applicable. + + Copyright (c) 2019 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD + License set forth in Section 4.c of the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8519; see + the RFC itself for full legal notices."; + + revision 2019-03-04 { + description + "Initial version."; + reference + "RFC 8519: YANG Data Model for Network Access Control + Lists (ACLs)."; + } + + /* + * Typedefs + */ + typedef operator { + type enumeration { + enum lte { + description + "Less than or equal to."; + } + enum gte { + description + "Greater than or equal to."; + } + enum eq { + description + "Equal to."; + } + enum neq { + description + "Not equal to."; + } + } + description + "The source and destination port range definitions + can be further qualified using an operator. An + operator is needed only if the lower-port is specified + and the upper-port is not specified. The operator + therefore further qualifies the lower-port only."; + } + + /* + * Groupings + */ + grouping port-range-or-operator { + choice port-range-or-operator { + case range { + leaf lower-port { + type inet:port-number; + must '. <= ../upper-port' { + error-message + "The lower-port must be less than or equal to + the upper-port."; + } + mandatory true; + description + "Lower boundary for a port."; + } + leaf upper-port { + type inet:port-number; + mandatory true; + description + "Upper boundary for a port."; + } + } + case operator { + leaf operator { + type operator; + default "eq"; + description + "Operator to be applied on the port below."; + } + leaf port { + type inet:port-number; + mandatory true; + description + "Port number along with the operator on which to + match."; + } + } + description + "Choice of specifying a port range or a single + port along with an operator."; + } + description + "Grouping for port definitions in the form of a + choice statement."; + } + + grouping acl-ip-header-fields { + description + "IP header fields common to IPv4 and IPv6"; + reference + "RFC 791: Internet Protocol."; + + leaf dscp { + type inet:dscp; + description + "Differentiated Services Code Point."; + reference + "RFC 2474: Definition of the Differentiated Services + Field (DS Field) in the IPv4 and IPv6 + Headers."; + } + + leaf ecn { + type uint8 { + range "0..3"; + } + description + "Explicit Congestion Notification."; + reference + "RFC 3168: The Addition of Explicit Congestion + Notification (ECN) to IP."; + } + + leaf length { + type uint16; + description + "In the IPv4 header field, this field is known as the Total + Length. Total Length is the length of the datagram, measured + in octets, including internet header and data. + + In the IPv6 header field, this field is known as the Payload + Length, which is the length of the IPv6 payload, i.e., the rest + of the packet following the IPv6 header, in octets."; + reference + "RFC 791: Internet Protocol + RFC 8200: Internet Protocol, Version 6 (IPv6) Specification."; + } + leaf ttl { + type uint8; + description + "This field indicates the maximum time the datagram is allowed + to remain in the internet system. If this field contains the + value zero, then the datagram must be dropped. + + In IPv6, this field is known as the Hop Limit."; + reference + "RFC 791: Internet Protocol + RFC 8200: Internet Protocol, Version 6 (IPv6) Specification."; + } + leaf protocol { + type uint8; + description + "Internet Protocol number. Refers to the protocol of the + payload. In IPv6, this field is known as 'next-header', + and if extension headers are present, the protocol is + present in the 'upper-layer' header."; + reference + "RFC 791: Internet Protocol + RFC 8200: Internet Protocol, Version 6 (IPv6) Specification."; + } + } + + grouping acl-ipv4-header-fields { + description + "Fields in the IPv4 header."; + leaf ihl { + type uint8 { + range "5..60"; + } + description + "In an IPv4 header field, the Internet Header Length (IHL) is + the length of the internet header in 32-bit words and + thus points to the beginning of the data. Note that the + minimum value for a correct header is 5."; + } + leaf flags { + type bits { + bit reserved { + position 0; + description + "Reserved. Must be zero."; + } + bit fragment { + position 1; + description + "Setting the value to 0 indicates may fragment, while + setting the value to 1 indicates do not fragment."; + } + bit more { + position 2; + description + "Setting the value to 0 indicates this is the last fragment, + and setting the value to 1 indicates more fragments are + coming."; + } + } + description + "Bit definitions for the Flags field in the IPv4 header."; + } + leaf offset { + type uint16 { + range "20..65535"; + } + description + "The fragment offset is measured in units of 8 octets (64 bits). + The first fragment has offset zero. The length is 13 bits"; + } + leaf identification { + type uint16; + description + "An identifying value assigned by the sender to aid in + assembling the fragments of a datagram."; + } + + choice destination-network { + case destination-ipv4-network { + leaf destination-ipv4-network { + type inet:ipv4-prefix; + description + "Destination IPv4 address prefix."; + } + } + description + "Choice of specifying a destination IPv4 address or + referring to a group of IPv4 destination addresses."; + } + + choice source-network { + case source-ipv4-network { + leaf source-ipv4-network { + type inet:ipv4-prefix; + description + "Source IPv4 address prefix."; + } + } + description + "Choice of specifying a source IPv4 address or + referring to a group of IPv4 source addresses."; + } + } + + grouping acl-ipv6-header-fields { + description + "Fields in the IPv6 header."; + + choice destination-network { + case destination-ipv6-network { + leaf destination-ipv6-network { + type inet:ipv6-prefix; + description + "Destination IPv6 address prefix."; + } + } + description + "Choice of specifying a destination IPv6 address + or referring to a group of IPv6 destination + addresses."; + } + + choice source-network { + case source-ipv6-network { + leaf source-ipv6-network { + type inet:ipv6-prefix; + description + "Source IPv6 address prefix."; + } + } + description + "Choice of specifying a source IPv6 address or + referring to a group of IPv6 source addresses."; + } + + leaf flow-label { + type inet:ipv6-flow-label; + description + "IPv6 Flow label."; + } + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text + Representation."; + } + + grouping acl-eth-header-fields { + description + "Fields in the Ethernet header."; + leaf destination-mac-address { + type yang:mac-address; + description + "Destination IEEE 802 Media Access Control (MAC) + address."; + } + leaf destination-mac-address-mask { + type yang:mac-address; + description + "Destination IEEE 802 MAC address mask."; + } + leaf source-mac-address { + type yang:mac-address; + description + "Source IEEE 802 MAC address."; + } + leaf source-mac-address-mask { + type yang:mac-address; + description + "Source IEEE 802 MAC address mask."; + } + leaf ethertype { + type eth:ethertype; + description + "The Ethernet Type (or Length) value represented + in the canonical order defined by IEEE 802. + The canonical representation uses lowercase + characters."; + reference + "IEEE 802-2014, Clause 9.2."; + } + reference + "IEEE 802: IEEE Standard for Local and Metropolitan + Area Networks: Overview and Architecture."; + } + + grouping acl-tcp-header-fields { + description + "Collection of TCP header fields that can be used to + set up a match filter."; + leaf sequence-number { + type uint32; + description + "Sequence number that appears in the packet."; + } + leaf acknowledgement-number { + type uint32; + description + "The acknowledgement number that appears in the + packet."; + } + leaf data-offset { + type uint8 { + range "5..15"; + } + description + "Specifies the size of the TCP header in 32-bit + words. The minimum size header is 5 words and + the maximum is 15 words; thus, this gives a + minimum size of 20 bytes and a maximum of 60 + bytes, allowing for up to 40 bytes of options + in the header."; + } + leaf reserved { + type uint8; + description + "Reserved for future use."; + } + leaf flags { + type bits { + bit cwr { + position 1; + description + "The Congestion Window Reduced (CWR) flag is set + by the sending host to indicate that it received + a TCP segment with the ECN-Echo (ECE) flag set + and had responded in the congestion control + mechanism."; + reference + "RFC 3168: The Addition of Explicit Congestion + Notification (ECN) to IP."; + } + bit ece { + position 2; + description + "ECN-Echo has a dual role, depending on the value + of the SYN flag. It indicates the following: if + the SYN flag is set (1), the TCP peer is ECN + capable, and if the SYN flag is clear (0), a packet + with the Congestion Experienced flag set (ECN=11) + in the IP header was received during normal + transmission (added to the header by RFC 3168). + This serves as an indication of network congestion + (or impending congestion) to the TCP sender."; + reference + "RFC 3168: The Addition of Explicit Congestion + Notification (ECN) to IP."; + } + bit urg { + position 3; + description + "Indicates that the Urgent Pointer field is significant."; + } + bit ack { + position 4; + description + "Indicates that the Acknowledgement field is significant. + All packets after the initial SYN packet sent by the + client should have this flag set."; + } + bit psh { + position 5; + description + "Push function. Asks to push the buffered data to the + receiving application."; + } + bit rst { + position 6; + description + "Reset the connection."; + } + bit syn { + position 7; + description + "Synchronize sequence numbers. Only the first packet + sent from each end should have this flag set. Some + other flags and fields change meaning based on this + flag, and some are only valid for when it is set, + and others when it is clear."; + } + bit fin { + position 8; + description + "Last package from the sender."; + } + } + description + "Also known as Control Bits. Contains nine 1-bit flags."; + reference + "RFC 793: Transmission Control Protocol."; + } + leaf window-size { + type uint16; + units "bytes"; + description + "The size of the receive window, which specifies + the number of window size units beyond the segment + identified by the sequence number in the Acknowledgement + field that the sender of this segment is currently + willing to receive."; + } + leaf urgent-pointer { + type uint16; + description + "This field is an offset from the sequence number + indicating the last urgent data byte."; + } + leaf options { + type binary { + length "1..40"; + } + description + "The length of this field is determined by the + Data Offset field. Options have up to three + fields: Option-Kind (1 byte), Option-Length + (1 byte), and Option-Data (variable). The Option-Kind + field indicates the type of option and is the + only field that is not optional. Depending on + what kind of option we are dealing with, + the next two fields may be set: the Option-Length + field indicates the total length of the option, + and the Option-Data field contains the value of + the option, if applicable."; + } + } + + grouping acl-udp-header-fields { + description + "Collection of UDP header fields that can be used + to set up a match filter."; + leaf length { + type uint16; + description + "A field that specifies the length in bytes of + the UDP header and UDP data. The minimum + length is 8 bytes because that is the length of + the header. The field size sets a theoretical + limit of 65,535 bytes (8-byte header plus 65,527 + bytes of data) for a UDP datagram. However, the + actual limit for the data length, which is + imposed by the underlying IPv4 protocol, is + 65,507 bytes (65,535 minus 8-byte UDP header + minus 20-byte IP header). + + In IPv6 jumbograms, it is possible to have + UDP packets of a size greater than 65,535 bytes. + RFC 2675 specifies that the Length field is set + to zero if the length of the UDP header plus + UDP data is greater than 65,535."; + } + } + + grouping acl-icmp-header-fields { + description + "Collection of ICMP header fields that can be + used to set up a match filter."; + leaf type { + type uint8; + description + "Also known as control messages."; + reference + "RFC 792: Internet Control Message Protocol + RFC 4443: Internet Control Message Protocol (ICMPv6) + for Internet Protocol Version 6 (IPv6) + Specification."; + } + leaf code { + type uint8; + description + "ICMP subtype. Also known as control messages."; + reference + "RFC 792: Internet Control Message Protocol + RFC 4443: Internet Control Message Protocol (ICMPv6) + for Internet Protocol Version 6 (IPv6) + Specification."; + } + leaf rest-of-header { + type binary; + description + "Unbounded in length, the contents vary based on the + ICMP type and code. Also referred to as 'Message Body' + in ICMPv6."; + reference + "RFC 792: Internet Control Message Protocol + RFC 4443: Internet Control Message Protocol (ICMPv6) + for Internet Protocol Version 6 (IPv6) + Specification."; + } + } +} diff --git a/src/confd/yang/confd/infix-ntp.yang b/src/confd/yang/confd/infix-ntp.yang new file mode 100644 index 000000000..d6ff61171 --- /dev/null +++ b/src/confd/yang/confd/infix-ntp.yang @@ -0,0 +1,114 @@ +module infix-ntp { + yang-version 1.1; + namespace "urn:infix:ntp:ns:yang:1.0"; + prefix infix-ntp; + + import ietf-ntp { + prefix ntp; + reference + "RFC 9249: A YANG Data Model for NTP"; + } + + import ietf-inet-types { + prefix inet; + reference + "RFC 6991: Common YANG Data Types"; + } + + organization "KernelKit"; + contact "kernelkit@googlegroups.com"; + description "Infix deviations and augments to ietf-ntp."; + + revision 2025-12-03 { + description + "Initial release - NTP server support. + + Implements NTP server functionality using chronyd as the + underlying daemon. Supports standalone, server, and peer modes + with configurable poll intervals, makestep for fast initial sync, + and rtcsync for hardware RTC synchronization. Mutual exclusion + with ietf-system:ntp enforced via YANG when statement."; + reference "internal"; + } + + /* + * Augments for Infix-specific features + */ + + augment "/ntp:ntp" { + container makestep { + presence "Enable clock stepping for large offsets"; + description + "Allow system clock to step (jump) immediately when offset is large. + This is useful for fast initial synchronization on systems that boot + with incorrect time (e.g., epoch time when no RTC present)."; + + leaf threshold { + type decimal64 { + fraction-digits 1; + range "0.1..max"; + } + default "1.0"; + units "seconds"; + description + "Threshold in seconds. If clock offset exceeds this value, + step the clock instead of slewing it slowly."; + } + + leaf limit { + type int32 { + range "-1 | 0..max"; + } + default "3"; + description + "Number of clock updates during which stepping is allowed. + After this many updates, only slewing is used. Special values: + -1 = always allow stepping (not recommended) + 0 = never step (defeats purpose of this directive) + 1-N = allow stepping for first N updates (recommended: 3)"; + } + } + } + + /* + * Deviations for unsupported features + */ + + deviation "/ntp:ntp/ntp:access-rules" { + deviate not-supported; + } + + deviation "/ntp:statistics-reset" { + deviate not-supported; + } + + deviation "/ntp:ntp/ntp:interfaces" { + deviate not-supported; + } + + deviation "/ntp:ntp/ntp:unicast-configuration/ntp:address" { + deviate replace { + type inet:host; + } + } + + deviation "/ntp:ntp/ntp:unicast-configuration/ntp:iburst" { + deviate add { + must "not(ntp:iburst and ../type = 'ntp:uc-peer')" { + error-message "iburst option is not supported in peer mode"; + } + } + } + + deviation "/ntp:ntp/ntp:unicast-configuration/ntp:burst" { + deviate add { + must "not(ntp:burst and ../type = 'ntp:uc-peer')" { + error-message "burst option is not supported in peer mode"; + } + } + } + + deviation "/ntp:ntp/ntp:unicast-configuration/ntp:source" { + deviate not-supported; + } +} diff --git a/src/confd/yang/confd/infix-ntp@2025-12-03.yang b/src/confd/yang/confd/infix-ntp@2025-12-03.yang new file mode 120000 index 000000000..8bcf0feb5 --- /dev/null +++ b/src/confd/yang/confd/infix-ntp@2025-12-03.yang @@ -0,0 +1 @@ +infix-ntp.yang \ No newline at end of file diff --git a/src/klish-plugin-infix/xml/infix.xml b/src/klish-plugin-infix/xml/infix.xml index fb6dc9468..73385a462 100644 --- a/src/klish-plugin-infix/xml/infix.xml +++ b/src/klish-plugin-infix/xml/infix.xml @@ -329,6 +329,9 @@ show ntp + + show ntp-server + doas ntp tracking diff --git a/src/show/show.py b/src/show/show.py index 288d6ddeb..ee9fd85df 100755 --- a/src/show/show.py +++ b/src/show/show.py @@ -79,6 +79,17 @@ def ntp(args: List[str]) -> None: return cli_pretty(data, "show-ntp") +def ntp_server(args: List[str]) -> None: + data = run_sysrepocfg("/ietf-ntp:ntp") + if not data: + print("No ntp server data retrieved.") + return + + if RAW_OUTPUT: + print(json.dumps(data, indent=2)) + return + cli_pretty(data, "show-ntp-server") + def is_valid_interface_name(interface_name: str) -> bool: """ Validates a Linux network interface name. @@ -444,6 +455,7 @@ def execute_command(command: str, args: List[str]): 'interface': interface, 'lldp': lldp, 'ntp': ntp, + 'ntp-server': ntp_server, 'ospf': ospf, 'routes': routes, 'services' : services, diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 0c6d3e694..149b2a1c9 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -1799,6 +1799,47 @@ def show_ntp(json): print(row) +def show_ntp_server(json): + """Display NTP server status and statistics""" + ntp_data = json.get("ietf-ntp:ntp") + if not ntp_data: + print("NTP server not enabled.") + return + + # Server configuration + print(Decore.invert("NTP SERVER CONFIGURATION")) + + refclock = ntp_data.get("refclock-master") + if refclock: + stratum = refclock.get("master-stratum", 10) + print(f"{'Local Stratum':<20}: {stratum}") + else: + print(f"{'Mode':<20}: Relay (no local reference clock)") + + port = ntp_data.get("port", 123) + print(f"{'Port':<20}: {port}") + + # Interface bindings (if configured) + interfaces = ntp_data.get("interfaces", {}).get("interface", []) + if interfaces: + print(f"{'Interfaces':<20}: {', '.join([iface.get('name', '?') for iface in interfaces])}") + else: + print(f"{'Interfaces':<20}: All") + + # Server statistics + stats = ntp_data.get("ntp-statistics") + if stats: + print() + print(Decore.invert("SERVER STATISTICS")) + print(f"{'Packets Received':<20}: {stats.get('packet-received', 0):,}") + print(f"{'Packets Sent':<20}: {stats.get('packet-sent', 0):,}") + print(f"{'Packets Dropped':<20}: {stats.get('packet-dropped', 0):,}") + print(f"{'Send Failures':<20}: {stats.get('packet-sent-fail', 0):,}") + else: + print() + print("No server statistics available.") + + def show_system(json): """System information overivew""" if not json.get("ietf-system:system-state"): @@ -3537,6 +3578,7 @@ def main(): .add_argument('name', nargs='?', help='Service name') subparsers.add_parser('show-ntp', help='Show NTP sources') + subparsers.add_parser('show-ntp-server', help='Show NTP server status') subparsers.add_parser('show-bfd', help='Show BFD sessions') subparsers.add_parser('show-bfd-status', help='Show BFD status') @@ -3584,6 +3626,8 @@ def main(): show_firewall_service(json_data, args.name) elif args.command == "show-ntp": show_ntp(json_data) + elif args.command == "show-ntp-server": + show_ntp_server(json_data) elif args.command == "show-bfd": show_bfd(json_data) elif args.command == "show-bfd-status": diff --git a/src/statd/python/yanger/__main__.py b/src/statd/python/yanger/__main__.py index 00814001c..56ccfe305 100644 --- a/src/statd/python/yanger/__main__.py +++ b/src/statd/python/yanger/__main__.py @@ -75,6 +75,9 @@ def dirpath(path): elif args.model == 'ietf-system': from . import ietf_system yang_data = ietf_system.operational() + elif args.model == 'ietf-ntp': + from . import ietf_ntp + yang_data = ietf_ntp.operational() elif args.model == 'ieee802-dot1ab-lldp': from . import infix_lldp yang_data = infix_lldp.operational() diff --git a/src/statd/python/yanger/ietf_ntp.py b/src/statd/python/yanger/ietf_ntp.py new file mode 100644 index 000000000..44a28ef76 --- /dev/null +++ b/src/statd/python/yanger/ietf_ntp.py @@ -0,0 +1,213 @@ +import subprocess + +from .common import insert +from .host import HOST + + +def add_ntp_server_stats(out): + """Add NTP server statistics if ietf-ntp is active""" + try: + # Get server statistics from chronyd + data = HOST.run_multiline(["chronyc", "-c", "serverstats"], []) + if not data or len(data) == 0: + return + + # Parse serverstats output (CSV format) + # Format: NTPpacketsreceived,NTPpacketsdropped,Cmdpacketsreceived, + # Cmdpacketsdropped,Clientlogsizeactive,Clientlogmemory, + # Ratelimitdrops,NTPpktsresp,NTPpktsresp-fail + parts = data[0].split(',') + if len(parts) < 9: + return + + stats = {} + stats["packet-received"] = int(parts[0]) + stats["packet-dropped"] = int(parts[1]) + stats["packet-sent"] = int(parts[7]) + stats["packet-sent-fail"] = int(parts[8]) + + insert(out, "ietf-ntp:ntp", "ntp-statistics", stats) + except Exception: + # NTP server not running or not configured, silently skip + pass + + +def add_ntp_associations(out): + """Add NTP association information from chronyc sources""" + try: + data = HOST.run_multiline(["chronyc", "-c", "sources"], []) + if not data: + return + + associations = [] + # Map chronyd mode indicators to ietf-ntp association-mode identities + mode_map = { + "^": "ietf-ntp:client", # We're client to this server + "=": "ietf-ntp:active", # Peer mode (symmetric active) + "#": "ietf-ntp:broadcast-client" # Local refclock (closest match) + } + + for line in data: + parts = line.split(',') + if len(parts) < 5: + continue + + mode_indicator = parts[0] + stratum = int(parts[3]) + + # Skip sources with invalid stratum (0 means unreachable/not yet synced) + # YANG model requires stratum to be in range 1..16 + if stratum < 1 or stratum > 16: + continue + + assoc = {} + assoc["address"] = parts[2] + assoc["local-mode"] = mode_map.get(mode_indicator, "ietf-ntp:client") + assoc["isconfigured"] = True # Sources from config + assoc["stratum"] = stratum + associations.append(assoc) + + if associations: + insert(out, "ietf-ntp:ntp", "associations", "association", associations) + except Exception: + # NTP not running or no sources configured, silently skip + pass + + +def add_ntp_clock_state(out): + """Add NTP clock state from chronyc tracking""" + try: + data = HOST.run_multiline(["chronyc", "-c", "tracking"], []) + if not data or len(data) == 0: + return + + # Parse tracking output (CSV format) + # Format: Ref-ID,Stratum,Ref-time,System-time,Last-offset,RMS-offset, + # Frequency,Residual-freq,Skew,Root-delay,Root-dispersion, + # Update-interval,Leap-status + parts = data[0].split(',') + if len(parts) < 13: + return + + clock_state = {} + system_status = {} + + # Determine clock-state based on stratum + stratum = int(parts[1]) + if stratum == 16: + system_status["clock-state"] = "ietf-ntp:unsynchronized" + else: + system_status["clock-state"] = "ietf-ntp:synchronized" + + system_status["clock-stratum"] = stratum + system_status["clock-refid"] = parts[0] + + # Add clock frequencies (in Hz) + # chronyd reports frequency offset in ppm, need to convert to Hz + # Nominal frequency is typically 1000000000 Hz for system clock + # We'll use a simplified approach: report the frequency offset + try: + freq_ppm = float(parts[6]) # Frequency offset in ppm + # For simplicity, use nominal as base and actual with offset applied + nominal = 1000000000.0 # 1 GHz nominal + system_status["nominal-freq"] = nominal + system_status["actual-freq"] = nominal * (1.0 + freq_ppm / 1000000.0) + except (ValueError, IndexError): + pass + + # Clock precision (use skew as approximation, converted to log2 seconds) + # chronyd reports skew in ppm, we'll use a fixed precision value + # Most systems have precision around -6 to -20 (2^-6 to 2^-20 seconds) + system_status["clock-precision"] = -20 # ~1 microsecond precision + + # Clock offset (System-time column, already in seconds) + try: + offset_sec = float(parts[3]) + system_status["clock-offset"] = offset_sec * 1000.0 # Convert to milliseconds + except (ValueError, IndexError): + pass + + # Root delay (in seconds, convert to milliseconds) + try: + root_delay_sec = float(parts[9]) + system_status["root-delay"] = root_delay_sec * 1000.0 + except (ValueError, IndexError): + pass + + # Root dispersion (in seconds, convert to milliseconds) + try: + root_disp_sec = float(parts[10]) + system_status["root-dispersion"] = root_disp_sec * 1000.0 + except (ValueError, IndexError): + pass + + # Reference time (Ref-time in seconds since epoch) + # YANG expects ntp-date-and-time format, but we'll provide Unix timestamp + try: + ref_time = float(parts[2]) + if ref_time > 0: + # Convert to ISO 8601 timestamp + from datetime import datetime + dt = datetime.utcfromtimestamp(ref_time) + system_status["reference-time"] = dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")[:-4] + "Z" + except (ValueError, IndexError, OSError): + pass + + # Sync state based on leap status + # chronyd leap status: 0=Normal, 1=Insert second, 2=Delete second, 3=Not synchronized + try: + leap_status = int(parts[12]) + if leap_status == 3 or stratum == 16: + system_status["sync-state"] = "ietf-ntp:freq-not-determined" + else: + system_status["sync-state"] = "ietf-ntp:synchronized" + except (ValueError, IndexError): + # Default based on stratum + if stratum == 16: + system_status["sync-state"] = "ietf-ntp:freq-not-determined" + else: + system_status["sync-state"] = "ietf-ntp:synchronized" + + clock_state["system-status"] = system_status + insert(out, "ietf-ntp:ntp", "clock-state", clock_state) + except Exception: + # NTP not running, silently skip + pass + + +def add_ntp_makestep(out): + """Add makestep configuration as operational state""" + try: + # Read chronyd config to check if makestep is configured + with open("/etc/chrony/conf.d/ntp-server.conf", "r") as f: + config = f.read() + + # Parse makestep directive if present + for line in config.split('\n'): + line = line.strip() + if line.startswith("makestep "): + parts = line.split() + if len(parts) >= 3: + try: + # Keep as strings for YANG decimal64 and int32 encoding + makestep_data = { + "threshold": parts[1], # decimal64 must be string-encoded + "limit": int(parts[2]) + } + insert(out, "ietf-ntp:ntp", "infix-ntp:makestep", makestep_data) + break + except (ValueError, IndexError): + pass + except Exception: + # Config file doesn't exist or can't be read, silently skip + pass + + +def operational(): + """Get operational state for ietf-ntp module""" + out = {} + add_ntp_server_stats(out) + add_ntp_associations(out) + add_ntp_clock_state(out) + add_ntp_makestep(out) + return out diff --git a/src/statd/python/yanger/ietf_system.py b/src/statd/python/yanger/ietf_system.py index 23e08d0f5..cf4ee7b21 100644 --- a/src/statd/python/yanger/ietf_system.py +++ b/src/statd/python/yanger/ietf_system.py @@ -63,6 +63,7 @@ def add_ntp(out): insert(out, "infix-system:ntp", "sources", "source", source) + def add_dns(out): options = {} servers = [] diff --git a/src/statd/statd.c b/src/statd/statd.c index f330bc418..cd3da5e7d 100644 --- a/src/statd/statd.c +++ b/src/statd/statd.c @@ -45,6 +45,7 @@ #define XPATH_DHCP_SERVER_BASE "/infix-dhcp-server:dhcp-server" #define XPATH_LLDP_BASE "/ieee802-dot1ab-lldp:lldp" #define XPATH_FIREWALL_BASE "/infix-firewall:firewall" +#define XPATH_NTP_BASE "/ietf-ntp:ntp" TAILQ_HEAD(sub_head, sub); @@ -398,6 +399,8 @@ static int subscribe_to_all(struct statd *statd) return SR_ERR_INTERNAL; if (subscribe(statd, "infix-firewall", XPATH_FIREWALL_BASE, sr_generic_cb)) return SR_ERR_INTERNAL; + if (subscribe(statd, "ietf-ntp", XPATH_NTP_BASE, sr_generic_cb)) + return SR_ERR_INTERNAL; INFO("Successfully subscribed to all models"); return SR_ERR_OK; diff --git a/test/case/all.yaml b/test/case/all.yaml index 1f6987111..9177fcdf6 100644 --- a/test/case/all.yaml +++ b/test/case/all.yaml @@ -32,6 +32,9 @@ - name: "Interfaces" suite: interfaces/all.yaml +- name: "NTP Server" + suite: ntp/all.yaml + - name: "Routing" suite: routing/all.yaml diff --git a/test/case/ntp/Readme.adoc b/test/case/ntp/Readme.adoc new file mode 100644 index 000000000..e010d7de2 --- /dev/null +++ b/test/case/ntp/Readme.adoc @@ -0,0 +1,28 @@ +:testgroup: +== NTP Server Tests + +Tests for NTP server functionality across different operational modes: + + - Standalone mode: local reference clock only + - Server mode: sync from upstream while serving clients + - Peer mode: bidirectional synchronization between peers + - Server and client interoperability + - Makestep configuration for initial sync + +include::server_mode_standalone/Readme.adoc[] + +<<< + +include::server_mode_server/Readme.adoc[] + +<<< + +include::server_mode_peer/Readme.adoc[] + +<<< + +include::server_client/Readme.adoc[] + +<<< + +include::server_makestep/Readme.adoc[] diff --git a/test/case/ntp/all.yaml b/test/case/ntp/all.yaml new file mode 100644 index 000000000..3f00312e9 --- /dev/null +++ b/test/case/ntp/all.yaml @@ -0,0 +1,15 @@ +--- +- name: NTP server standalone mode + case: server_mode_standalone/test.py + +- name: NTP server mode + case: server_mode_server/test.py + +- name: NTP peer mode + case: server_mode_peer/test.py + +- name: NTP server and client interoperability + case: server_client/test.py + +- name: NTP server makestep configuration + case: server_makestep/test.py diff --git a/test/case/ntp/ntp.yaml b/test/case/ntp/ntp.yaml new file mode 100644 index 000000000..895fc0ede --- /dev/null +++ b/test/case/ntp/ntp.yaml @@ -0,0 +1,5 @@ +name: ntp +description: NTP server and client tests +tests: + - server_basic + - server_mutual_exclusion diff --git a/test/case/ntp/server_client/Readme.adoc b/test/case/ntp/server_client/Readme.adoc new file mode 120000 index 000000000..ae32c8412 --- /dev/null +++ b/test/case/ntp/server_client/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/ntp/server_client/test.adoc b/test/case/ntp/server_client/test.adoc new file mode 100644 index 000000000..e0e581ea6 --- /dev/null +++ b/test/case/ntp/server_client/test.adoc @@ -0,0 +1,28 @@ +=== NTP server and client interoperability + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_client] + +==== Description + +Verify NTP server (ietf-ntp) and client (ietf-system:ntp) work together. + +This test verifies: +1. Server uses ietf-ntp YANG model with refclock-master +2. Client uses ietf-system:ntp YANG model +3. Client successfully synchronizes from server +4. Server shows packet statistics +5. Mutual exclusion prevents both modes on same device + +==== Topology + +image::topology.svg[NTP server and client interoperability topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to devices +. Configure NTP server using ietf-ntp model +. Configure NTP client using ietf-system:ntp model +. Verify NTP server has received packets +. Verify NTP client has synchronized + + diff --git a/test/case/ntp/server_client/test.py b/test/case/ntp/server_client/test.py new file mode 100755 index 000000000..aeddab315 --- /dev/null +++ b/test/case/ntp/server_client/test.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""NTP server and client interoperability test + +Verify NTP server (ietf-ntp) and client (ietf-system:ntp) work together. + +This test verifies: +1. Server uses ietf-ntp YANG model with refclock-master +2. Client uses ietf-system:ntp YANG model +3. Client successfully synchronizes from server +4. Server shows packet statistics +5. Mutual exclusion prevents both modes on same device +""" + +import infamy +from infamy import until +import infamy.ntp as ntp + + +with infamy.Test() as test: + with test.step("Set up topology and attach to devices"): + env = infamy.Env() + server = env.attach("server", "mgmt") + client = env.attach("client", "mgmt") + + _, server_data = env.ltop.xlate("server", "data") + _, client_data = env.ltop.xlate("client", "data") + + with test.step("Configure NTP server using ietf-ntp model"): + server.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": server_data, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.3.1", + "prefix-length": 24 + }] + } + }] + } + }, + "ietf-ntp": { + "ntp": { + "refclock-master": { + "master-stratum": 8 + }, + "interfaces": { + "interface": [ + {"name": server_data} + ] + } + } + } + }) + + with test.step("Configure NTP client using ietf-system:ntp model"): + client.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": client_data, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.3.2", + "prefix-length": 24 + }] + } + }] + } + }, + "ietf-system": { + "system": { + "ntp": { + "enabled": True, + "server": [{ + "name": "ntp-server", + "udp": { + "address": "192.168.3.1" + }, + "iburst": True + }] + } + } + } + }) + + with test.step("Verify NTP server has received packets"): + until(lambda: ntp.server_has_received_packets(server), attempts=30) + + with test.step("Verify NTP client has synchronized"): + until(lambda: ntp.any_source_selected(client), attempts=30) + + test.succeed() diff --git a/test/case/ntp/server_client/topology.dot b/test/case/ntp/server_client/topology.dot new file mode 100644 index 000000000..a94db5336 --- /dev/null +++ b/test/case/ntp/server_client/topology.dot @@ -0,0 +1,33 @@ +graph "ntp-server-client-interop" { + layout="neato"; + overlap="false"; + esep="+22"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="{ mgmt1 | \n\nhost\n\n\n | mgmt2 }", + pos="0,15!", + requires="controller", + ]; + + server [ + label="{ mgmt | data } | { server }", + pos="2,15.25!", + fontsize=12, + requires="infix", + ]; + + client [ + label="{ data | mgmt } | { client }", + pos="2,14.75!", + fontsize=12, + requires="infix", + ]; + + host:mgmt1 -- server:mgmt [requires="mgmt", color="lightgray"] + host:mgmt2 -- client:mgmt [requires="mgmt" color="lightgrey"] + + server:data -- client:data [label="\n\n192.168.3.0/24 "] +} diff --git a/test/case/ntp/server_client/topology.svg b/test/case/ntp/server_client/topology.svg new file mode 100644 index 000000000..68ee821bf --- /dev/null +++ b/test/case/ntp/server_client/topology.svg @@ -0,0 +1,58 @@ + + + + + + +ntp-server-client-interop + + + +host + +mgmt1 + +host + +mgmt2 + + + +server + +mgmt + +data + +server + + + +host:mgmt1--server:mgmt + + + + +client + +data + +mgmt + +client + + + +host:mgmt2--client:mgmt + + + + +server:data--client:data + +192.168.3.0/24   + + + diff --git a/test/case/ntp/server_makestep/Readme.adoc b/test/case/ntp/server_makestep/Readme.adoc new file mode 120000 index 000000000..ae32c8412 --- /dev/null +++ b/test/case/ntp/server_makestep/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/ntp/server_makestep/test.adoc b/test/case/ntp/server_makestep/test.adoc new file mode 100644 index 000000000..e1e312b89 --- /dev/null +++ b/test/case/ntp/server_makestep/test.adoc @@ -0,0 +1,27 @@ +=== NTP server makestep configuration + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_makestep] + +==== Description + +Verify makestep directive works properly for initial sync. + +The makestep directive is critical for embedded systems that boot with +epoch time (no RTC), allowing immediate clock correction instead of the +default slow slewing over several hours. + +==== Topology + +image::topology.svg[NTP server makestep configuration topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to target DUT +. Configure NTP server with makestep defaults +. Verify makestep operational state +. Configure custom makestep values +. Verify custom makestep operational state +. Delete makestep configuration +. Verify makestep removed from operational state + + diff --git a/test/case/ntp/server_makestep/test.py b/test/case/ntp/server_makestep/test.py new file mode 100755 index 000000000..178e094ac --- /dev/null +++ b/test/case/ntp/server_makestep/test.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""NTP server makestep + +Verify makestep directive works properly for initial sync. + +The makestep directive is critical for embedded systems that boot with +epoch time (no RTC), allowing immediate clock correction instead of the +default slow slewing over several hours. + +""" + +import infamy + + +with infamy.Test() as test: + with test.step("Set up topology and attach to target DUT"): + env = infamy.Env() + target = env.attach("target", "mgmt") + + with test.step("Configure NTP server with makestep defaults"): + target.put_config_dicts({ + "ietf-ntp": { + "ntp": { + "refclock-master": { + "master-stratum": 10 + }, + "infix-ntp:makestep": { + "threshold": 1.0, + "limit": 3 + } + } + } + }) + + with test.step("Verify makestep operational state"): + data = target.get_data("/ietf-ntp:ntp") + makestep = data.get("ntp", {}).get("makestep", {}) + + assert makestep, f"makestep should be present in operational: {data}" + assert makestep.get("threshold") == 1.0, \ + f"Expected threshold 1.0, got {makestep.get('threshold')}" + assert makestep.get("limit") == 3, \ + f"Expected limit 3, got {makestep.get('limit')}" + + with test.step("Configure custom makestep values"): + target.put_config_dicts({ + "ietf-ntp": { + "ntp": { + "refclock-master": { + "master-stratum": 10 + }, + "infix-ntp:makestep": { + "threshold": 2.5, + "limit": 1 + } + } + } + }) + + with test.step("Verify custom makestep operational state"): + data = target.get_data("/ietf-ntp:ntp") + makestep = data.get("ntp", {}).get("makestep", {}) + + assert makestep.get("threshold") == 2.5, \ + f"Expected threshold 2.5, got {makestep.get('threshold')}" + assert makestep.get("limit") == 1, \ + f"Expected limit 1, got {makestep.get('limit')}" + + with test.step("Delete makestep configuration"): + target.delete_xpath("/ietf-ntp:ntp/infix-ntp:makestep") + + with test.step("Verify makestep removed from operational state"): + data = target.get_data("/ietf-ntp:ntp") + makestep = data.get("ntp", {}).get("makestep", {}) + + assert not makestep, \ + f"makestep should not be present after deletion, got: {makestep}" + + test.succeed() diff --git a/test/case/ntp/server_makestep/topology.dot b/test/case/ntp/server_makestep/topology.dot new file mode 100644 index 000000000..2bc23fbc2 --- /dev/null +++ b/test/case/ntp/server_makestep/topology.dot @@ -0,0 +1,22 @@ +graph "1x1" { + layout = "neato"; + overlap = false; + esep = "+80"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="host | { mgmt }", + pos="1,1!", + requires="controller" + ]; + + target [ + label="{ mgmt } | target", + pos="3,1!", + requires="infix", + ]; + + host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"] +} diff --git a/test/case/ntp/server_makestep/topology.svg b/test/case/ntp/server_makestep/topology.svg new file mode 100644 index 000000000..6fc6f47a8 --- /dev/null +++ b/test/case/ntp/server_makestep/topology.svg @@ -0,0 +1,33 @@ + + + + + + +1x1 + + + +host + +host + +mgmt + + + +target + +mgmt + +target + + + +host:mgmt--target:mgmt + + + + diff --git a/test/case/ntp/server_mode_peer/Readme.adoc b/test/case/ntp/server_mode_peer/Readme.adoc new file mode 120000 index 000000000..ae32c8412 --- /dev/null +++ b/test/case/ntp/server_mode_peer/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/ntp/server_mode_peer/test.adoc b/test/case/ntp/server_mode_peer/test.adoc new file mode 100644 index 000000000..204b2bcee --- /dev/null +++ b/test/case/ntp/server_mode_peer/test.adoc @@ -0,0 +1,30 @@ +=== NTP peer mode + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_mode_peer] + +==== Description + +Verify NTP server operating in peer mode with bidirectional synchronization. + +This test validates peer mode where two NTP servers synchronize with each other +bidirectionally. Each server acts as both client and server to the other: +- peer1: Stratum 8 local clock, peered with peer2 +- peer2: Stratum 8 local clock, peered with peer1 + +The test verifies mutual synchronization between peers. + +==== Topology + +image::topology.svg[NTP peer mode topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to devices +. Configure peer1 with peer relationship to peer2 +. Configure peer2 with peer relationship to peer1 +. Verify peer1 sees peer2 in sources +. Verify peer2 sees peer1 in sources +. Verify peer1 can reach peer2 +. Verify peer2 can reach peer1 + + diff --git a/test/case/ntp/server_mode_peer/test.py b/test/case/ntp/server_mode_peer/test.py new file mode 100755 index 000000000..6bcc86da2 --- /dev/null +++ b/test/case/ntp/server_mode_peer/test.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""NTP peer mode test + +Verify NTP server operating in peer mode with bidirectional synchronization. + +This test validates peer mode where two NTP servers synchronize with each other +bidirectionally. Each server acts as both client and server to the other: +- peer1: Stratum 8 local clock, peered with peer2 +- peer2: Stratum 8 local clock, peered with peer1 + +The test verifies mutual synchronization between peers. +""" + +import infamy +from infamy import until +import infamy.ntp as ntp + + +with infamy.Test() as test: + with test.step("Set up topology and attach to devices"): + env = infamy.Env() + peer1 = env.attach("peer1", "mgmt") + peer2 = env.attach("peer2", "mgmt") + + _, peer1_data = env.ltop.xlate("peer1", "data") + _, peer2_data = env.ltop.xlate("peer2", "data") + + with test.step("Configure peer1 with peer relationship to peer2"): + peer1.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": peer1_data, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.3.1", + "prefix-length": 24 + }] + } + }] + } + }, + "ietf-ntp": { + "ntp": { + "unicast-configuration": [{ + "address": "192.168.3.2", + "type": "uc-peer", + "minpoll": 2 + }], + "refclock-master": { + "master-stratum": 8 + } + } + } + }) + + with test.step("Configure peer2 with peer relationship to peer1"): + peer2.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": peer2_data, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.3.2", + "prefix-length": 24 + }] + } + }] + } + }, + "ietf-ntp": { + "ntp": { + "unicast-configuration": [{ + "address": "192.168.3.1", + "type": "uc-peer", + "minpoll": 2 + }], + "refclock-master": { + "master-stratum": 8 + } + } + } + }) + + with test.step("Verify peer1 sees peer2 in sources"): + until(lambda: ntp.server_has_peer(peer1, "192.168.3.2"), attempts=20) + + with test.step("Verify peer2 sees peer1 in sources"): + until(lambda: ntp.server_has_peer(peer2, "192.168.3.1"), attempts=20) + + with test.step("Verify peer1 can reach peer2"): + until(lambda: ntp.server_peer_reachable(peer1, "192.168.3.2"), attempts=60) + + with test.step("Verify peer2 can reach peer1"): + until(lambda: ntp.server_peer_reachable(peer2, "192.168.3.1"), attempts=60) + + test.succeed() diff --git a/test/case/ntp/server_mode_peer/topology.dot b/test/case/ntp/server_mode_peer/topology.dot new file mode 100644 index 000000000..b99361465 --- /dev/null +++ b/test/case/ntp/server_mode_peer/topology.dot @@ -0,0 +1,33 @@ +graph "ntp-peer-mode" { + layout="neato"; + overlap="false"; + esep="+22"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="{ mgmt1 | \n\nhost\n\n\n | mgmt2 }", + pos="0,15!", + requires="controller", + ]; + + peer1 [ + label="{ mgmt | data } | { peer1 }", + pos="2,15.25!", + fontsize=12, + requires="infix", + ]; + + peer2 [ + label="{ data | mgmt } | { peer2 }", + pos="2,14.75!", + fontsize=12, + requires="infix", + ]; + + host:mgmt1 -- peer1:mgmt [requires="mgmt", color="lightgray"] + host:mgmt2 -- peer2:mgmt [requires="mgmt" color="lightgrey"] + + peer1:data -- peer2:data [label="\n\n192.168.3.0/24 ", dir="both"] +} diff --git a/test/case/ntp/server_mode_peer/topology.svg b/test/case/ntp/server_mode_peer/topology.svg new file mode 100644 index 000000000..b41dcc776 --- /dev/null +++ b/test/case/ntp/server_mode_peer/topology.svg @@ -0,0 +1,60 @@ + + + + + + +ntp-peer-mode + + + +host + +mgmt1 + +host + +mgmt2 + + + +peer1 + +mgmt + +data + +peer1 + + + +host:mgmt1--peer1:mgmt + + + + +peer2 + +data + +mgmt + +peer2 + + + +host:mgmt2--peer2:mgmt + + + + +peer1:data--peer2:data + + + +192.168.3.0/24   + + + diff --git a/test/case/ntp/server_mode_server/Readme.adoc b/test/case/ntp/server_mode_server/Readme.adoc new file mode 120000 index 000000000..ae32c8412 --- /dev/null +++ b/test/case/ntp/server_mode_server/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/ntp/server_mode_server/test.adoc b/test/case/ntp/server_mode_server/test.adoc new file mode 100644 index 000000000..bc869609e --- /dev/null +++ b/test/case/ntp/server_mode_server/test.adoc @@ -0,0 +1,32 @@ +=== NTP server mode + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_mode_server] + +==== Description + +Verify NTP server operating in server mode, syncing from upstream while serving clients. + +This test validates server mode where devices synchronize from upstream NTP servers +while simultaneously serving time to downstream clients. It creates a two-tier hierarchy: +- Upstream: NTP server with local reference clock (stratum 8) +- Downstream: NTP server that syncs from upstream and serves to clients (stratum 9) + +The test verifies both servers operate correctly and serve accurate time. + +==== Topology + +image::topology.svg[NTP server mode topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to devices +. Configure upstream NTP server with local reference clock +. Configure downstream NTP server syncing from upstream +. Verify network connectivity with upstream NTP server +. Query time from upstream NTP server +. Verify upstream NTP server statistics +. Verify network connectivity with downstream NTP server +. Wait for downstream to sync from upstream +. Verify downstream NTP server statistics + + diff --git a/test/case/ntp/server_mode_server/test.py b/test/case/ntp/server_mode_server/test.py new file mode 100755 index 000000000..7c0f8d258 --- /dev/null +++ b/test/case/ntp/server_mode_server/test.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +"""NTP server mode test + +Verify NTP server operating in server mode, syncing from upstream while serving clients. + +This test validates server mode where devices synchronize from upstream NTP servers +while simultaneously serving time to downstream clients. It creates a two-tier hierarchy: +- Upstream: NTP server with local reference clock (stratum 8) +- Downstream: NTP server that syncs from upstream and serves to clients (stratum 9) + +The test verifies both servers operate correctly and serve accurate time. +""" + +import infamy +from infamy import until +import infamy.ntp as ntp + + +with infamy.Test() as test: + with test.step("Set up topology and attach to devices"): + env = infamy.Env() + upstream = env.attach("upstream", "mgmt") + downstream = env.attach("downstream", "mgmt") + + # Get interface names for each device + _, upstream_data1 = env.ltop.xlate("upstream", "data1") + _, upstream_conn = env.ltop.xlate("upstream", "conn") + _, hport1 = env.ltop.xlate("host", "data1") + + _, downstream_data2 = env.ltop.xlate("downstream", "data2") + _, downstream_conn = env.ltop.xlate("downstream", "conn") + _, hport2 = env.ltop.xlate("host", "data2") + + with test.step("Configure upstream NTP server with local reference clock"): + upstream.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": upstream_data1, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.1.1", + "prefix-length": 24 + }] + } + }, { + "name": upstream_conn, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.3.1", + "prefix-length": 24 + }] + } + }] + } + }, + "ietf-ntp": { + "ntp": { + "refclock-master": { + "master-stratum": 8 + } + } + } + }) + + with test.step("Configure downstream NTP server syncing from upstream"): + downstream.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": downstream_data2, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.2.1", + "prefix-length": 24 + }] + } + }, { + "name": downstream_conn, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.3.2", + "prefix-length": 24 + }] + } + }] + } + }, + "ietf-ntp": { + "ntp": { + "unicast-configuration": [{ + "address": "192.168.3.1", + "type": "uc-server", + "iburst": True + }], + "refclock-master": { + "master-stratum": 10 + } + } + } + }) + + with infamy.IsolatedMacVlan(hport1) as ns1: + ns1.addip("192.168.1.2") + + with test.step("Verify network connectivity with upstream NTP server"): + ns1.must_reach("192.168.1.1") + + with test.step("Query time from upstream NTP server"): + until(lambda: ntp.server_query(ns1, "192.168.1.1"), attempts=20) + + with test.step("Verify upstream NTP server statistics"): + until(lambda: ntp.server_has_received_packets(upstream), attempts=20) + + with infamy.IsolatedMacVlan(hport2) as ns2: + ns2.addip("192.168.2.2") + + with test.step("Verify network connectivity with downstream NTP server"): + ns2.must_reach("192.168.2.1") + + with test.step("Wait for downstream to sync from upstream"): + # Give downstream time to sync from upstream + until(lambda: ntp.server_query(ns2, "192.168.2.1"), attempts=30) + + with test.step("Verify downstream NTP server statistics"): + until(lambda: ntp.server_has_received_packets(downstream), attempts=20) + + test.succeed() diff --git a/test/case/ntp/server_mode_server/topology.dot b/test/case/ntp/server_mode_server/topology.dot new file mode 100644 index 000000000..317fdd0df --- /dev/null +++ b/test/case/ntp/server_mode_server/topology.dot @@ -0,0 +1,36 @@ +graph "ntp-upstream-downstream" { + layout="neato"; + overlap="false"; + esep="+22"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="host | { mgmt1 | data1 | <> \n\n\n | mgmt2 | data2 }", + pos="0,15!", + requires="controller", + ]; + + upstream [ + label="{ mgmt | data1 } | { \n upstream \n\n | conn }", + pos="2,15.25!", + fontsize=12, + requires="infix", + ]; + + downstream [ + label="{ mgmt | data2 } | { conn | \n downstream \n\n }", + pos="2,14.75!", + fontsize=12, + requires="infix", + ]; + + host:mgmt1 -- upstream:mgmt [requires="mgmt", color="lightgray"] + host:data1 -- upstream:data1 [taillabel="192.168.1.2", headlabel="192.168.1.1"] + + host:mgmt2 -- downstream:mgmt [requires="mgmt" color="lightgrey"] + host:data2 -- downstream:data2 [taillabel="192.168.2.2", headlabel="192.168.2.1"] + + upstream:conn -- downstream:conn [label="Client/Server\n192.168.3.x"] +} diff --git a/test/case/ntp/server_mode_server/topology.svg b/test/case/ntp/server_mode_server/topology.svg new file mode 100644 index 000000000..a1f20af43 --- /dev/null +++ b/test/case/ntp/server_mode_server/topology.svg @@ -0,0 +1,82 @@ + + + + + + +ntp-upstream-downstream + + + +host + +host + +mgmt1 + +data1 + + +mgmt2 + +data2 + + + +upstream + +mgmt + +data1 + + upstream + +conn + + + +host:mgmt1--upstream:mgmt + + + + +host:data1--upstream:data1 + +192.168.1.1 +192.168.1.2 + + + +downstream + +mgmt + +data2 + +conn + + downstream + + + +host:mgmt2--downstream:mgmt + + + + +host:data2--downstream:data2 + +192.168.2.1 +192.168.2.2 + + + +upstream:conn--downstream:conn + +Client/Server +192.168.3.x + + + diff --git a/test/case/ntp/server_mode_standalone/Readme.adoc b/test/case/ntp/server_mode_standalone/Readme.adoc new file mode 120000 index 000000000..ae32c8412 --- /dev/null +++ b/test/case/ntp/server_mode_standalone/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/ntp/server_mode_standalone/svg b/test/case/ntp/server_mode_standalone/svg new file mode 100644 index 000000000..cf0ddb452 --- /dev/null +++ b/test/case/ntp/server_mode_standalone/svg @@ -0,0 +1,35 @@ +graph "1x2" { + graph [bb="0,0,432.03,50.5", + esep="+80", + layout=neato, + overlap=false + ]; + node [fontname="DejaVu Sans Mono, Book", + label="\N", + shape=record + ]; + edge [color=cornflowerblue, + fontname="DejaVu Serif, Book", + penwidth=2 + ]; + host [height=0.65278, + label="host | { mgmt | data1 }", + pos="54,27", + rects="0,4,50,50 50,27,108,50 50,4,108,27", + requires=controller, + width=1.5]; + target [height=0.65278, + label="{ mgmt | data1 } | target", + pos="370.03,27", + rects="308.03,27,366.03,50 308.03,4,366.03,27 366.03,4,432.03,50", + requires=infix, + width=1.7222]; + host:mgmt -- target:mgmt [color=lightgray, + pos="108,39 108,39 308.03,39 308.03,39", + requires=mgmt]; + host:data1 -- target:data1 [color=blue, + fontcolor=blue, + label="192.168.1.0/24", + lp="235.27,7.5", + pos="108,15 108,15 308.03,15 308.03,15"]; +} diff --git a/test/case/ntp/server_mode_standalone/test.adoc b/test/case/ntp/server_mode_standalone/test.adoc new file mode 100644 index 000000000..4f7cef5c7 --- /dev/null +++ b/test/case/ntp/server_mode_standalone/test.adoc @@ -0,0 +1,25 @@ +=== NTP server standalone mode + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_mode_standalone] + +==== Description + +Verify NTP server operating in standalone mode with only a local reference clock. + +This test validates the basic standalone mode where the NTP server uses only +its local reference clock (stratum 8) to serve time to clients, without +syncing from any upstream sources. + +==== Topology + +image::topology.svg[NTP server standalone mode topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to target DUT +. Configure interface and NTP server +. Verify network connectivity with NTP server +. Query time from NTP server +. Verify NTP server statistics + + diff --git a/test/case/ntp/server_mode_standalone/test.py b/test/case/ntp/server_mode_standalone/test.py new file mode 100755 index 000000000..57b78fbdf --- /dev/null +++ b/test/case/ntp/server_mode_standalone/test.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""NTP server standalone mode test + +Verify NTP server operating in standalone mode with only a local reference clock. + +This test validates the basic standalone mode where the NTP server uses only +its local reference clock (stratum 8) to serve time to clients, without +syncing from any upstream sources. +""" + +import infamy +from infamy import until +import infamy.ntp as ntp + + +with infamy.Test() as test: + with test.step("Set up topology and attach to target DUT"): + env = infamy.Env() + target = env.attach("target", "mgmt") + _, data1 = env.ltop.xlate("target", "data1") + _, hport1 = env.ltop.xlate("host", "data1") + + with test.step("Configure interface and NTP server"): + + target.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": data1, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.1.1", + "prefix-length": 24 + }] + } + }] + } + }, + "ietf-ntp": { + "ntp": { + "refclock-master": { + "master-stratum": 8 + } + } + } + }) + + with infamy.IsolatedMacVlan(hport1) as ns1: + ns1.addip("192.168.1.2") + + with test.step("Verify network connectivity with NTP server"): + ns1.must_reach("192.168.1.1") + + with test.step("Query time from NTP server"): + until(lambda: ntp.server_query(ns1, "192.168.1.1"), attempts=20) + + with test.step("Verify NTP server statistics"): + until(lambda: ntp.server_has_received_packets(target), attempts=20) + + test.succeed() diff --git a/test/case/ntp/server_mode_standalone/topology.dot b/test/case/ntp/server_mode_standalone/topology.dot new file mode 100644 index 000000000..59a2c81cb --- /dev/null +++ b/test/case/ntp/server_mode_standalone/topology.dot @@ -0,0 +1,23 @@ +graph "1x2" { + layout = "neato"; + overlap = false; + esep = "+80"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="host | { mgmt | data1 }", + pos="1,1!", + requires="controller" + ]; + + target [ + label="{ mgmt | data1 } | target", + pos="3,1!", + requires="infix", + ]; + + host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"] + host:data1 -- target:data1 [color=blue, fontcolor=blue, label="192.168.1.0/24"] +} diff --git a/test/case/ntp/server_mode_standalone/topology.dot.dot b/test/case/ntp/server_mode_standalone/topology.dot.dot new file mode 100644 index 000000000..cf0ddb452 --- /dev/null +++ b/test/case/ntp/server_mode_standalone/topology.dot.dot @@ -0,0 +1,35 @@ +graph "1x2" { + graph [bb="0,0,432.03,50.5", + esep="+80", + layout=neato, + overlap=false + ]; + node [fontname="DejaVu Sans Mono, Book", + label="\N", + shape=record + ]; + edge [color=cornflowerblue, + fontname="DejaVu Serif, Book", + penwidth=2 + ]; + host [height=0.65278, + label="host | { mgmt | data1 }", + pos="54,27", + rects="0,4,50,50 50,27,108,50 50,4,108,27", + requires=controller, + width=1.5]; + target [height=0.65278, + label="{ mgmt | data1 } | target", + pos="370.03,27", + rects="308.03,27,366.03,50 308.03,4,366.03,27 366.03,4,432.03,50", + requires=infix, + width=1.7222]; + host:mgmt -- target:mgmt [color=lightgray, + pos="108,39 108,39 308.03,39 308.03,39", + requires=mgmt]; + host:data1 -- target:data1 [color=blue, + fontcolor=blue, + label="192.168.1.0/24", + lp="235.27,7.5", + pos="108,15 108,15 308.03,15 308.03,15"]; +} diff --git a/test/case/ntp/server_mode_standalone/topology.svg b/test/case/ntp/server_mode_standalone/topology.svg new file mode 100644 index 000000000..250d9d3ab --- /dev/null +++ b/test/case/ntp/server_mode_standalone/topology.svg @@ -0,0 +1,43 @@ + + + + + + +1x2 + + + +host + +host + +mgmt + +data1 + + + +target + +mgmt + +data1 + +target + + + +host:mgmt--target:mgmt + + + + +host:data1--target:data1 + +192.168.1.0/24 + + + diff --git a/test/infamy/netconf.py b/test/infamy/netconf.py index f2c7e092e..ebeacb8a2 100644 --- a/test/infamy/netconf.py +++ b/test/infamy/netconf.py @@ -319,7 +319,12 @@ def put_config_dicts(self, models): infer_put_dict(self.name, models) for model in models.keys(): - mod = self.ly.get_module(model) + try: + mod = self.ly.get_module(model) + except libyang.util.LibyangError: + raise Exception(f"YANG model '{model}' not found on device. " + f"Model may not be installed or enabled. " + f"Available models can be checked with get_schema_list()") from None lyd = mod.parse_data_dict(models[model], no_state=True, validate=False) config += lyd.print_mem("xml", with_siblings=True, pretty=False) + "\n" # print(f"Send new XML config: {config}") @@ -327,7 +332,12 @@ def put_config_dicts(self, models): def put_config_dict(self, modname, edit): """Convert Python dictionary to XMl and send as configuration""" - mod = self.ly.get_module(modname) + try: + mod = self.ly.get_module(modname) + except libyang.util.LibyangError: + raise Exception(f"YANG model '{modname}' not found on device. " + f"Model may not be installed or enabled. " + f"Available models can be checked with get_schema_list()") from None lyd = mod.parse_data_dict(edit, no_state=True, validate=False) config = lyd.print_mem("xml", with_siblings=True, pretty=False) # print(f"Send new XML config: {config}") @@ -339,7 +349,12 @@ def call(self, call): def call_dict(self, modname, call): """Call RPC, Python dictionary version""" - mod = self.ly.get_module(modname) + try: + mod = self.ly.get_module(modname) + except libyang.util.LibyangError: + raise Exception(f"YANG model '{modname}' not found on device. " + f"Model may not be installed or enabled. " + f"Available models can be checked with get_schema_list()") from None lyd = mod.parse_data_dict(call, rpc=True) return self.call(lyd.print_mem("xml", with_siblings=True, pretty=False)) diff --git a/test/infamy/ntp.py b/test/infamy/ntp.py index 03a599af9..402082241 100644 --- a/test/infamy/ntp.py +++ b/test/infamy/ntp.py @@ -1,5 +1,5 @@ """ -NTP client helper +NTP client and server helpers """ @@ -36,3 +36,78 @@ def number_of_sources(target): sources = _get_ntp_sources(target) return len(sources) + + +def server_has_received_packets(target): + """Verify NTP server (ietf-ntp) has received packets.""" + try: + data = target.get_data("/ietf-ntp:ntp/ntp-statistics") + if not data: + return False + + stats = data["ntp"].get("ntp-statistics", {}) + if not stats: + return False + + packets_received = int(stats.get("packet-received", 0)) + return packets_received > 0 + except Exception: + return False + + +def server_query(netns, server_ip, expected_stratum=None): + """Query NTP server from a network namespace and return True if successful. + + Optionally verify the stratum level if expected_stratum is provided. + """ + result = netns.runsh(f"timeout 1 ntpd -qwp {server_ip}") + output = result.stdout if result.stdout else "" + + if f"ntpd: reply from {server_ip}" not in output or "offset" not in output: + return False + + if expected_stratum is not None: + # Extract stratum from output like: "stratum 8" + for line in output.split('\n'): + if 'stratum' in line.lower(): + try: + stratum = int(line.split()[-1]) + return stratum == expected_stratum + except (ValueError, IndexError): + pass + return False + + return True + + +def server_has_peer(target, peer_address): + """Verify NTP server (ietf-ntp) has a peer association with given address.""" + try: + data = target.get_data("/ietf-ntp:ntp/associations") + if not data: + return False + + associations = data.get("ntp", {}).get("associations", {}).get("association", []) + if not associations: + return False + + # Check if peer association exists with the given address + # local-mode will be "ietf-ntp:active" or "active" depending on namespace handling + for assoc in associations: + local_mode = assoc.get("local-mode", "") + if (assoc.get("address") == peer_address and + (local_mode == "ietf-ntp:active" or local_mode == "active")): + return True + + return False + except Exception: + return False + + +def server_peer_reachable(target, peer_address): + """Verify NTP peer association exists (peer is configured and running).""" + # For now, just check if the association exists + # The YANG associations container doesn't expose reach/state info + # but if the association shows up, it means chronyd is running and + # communicating with the peer + return server_has_peer(target, peer_address) diff --git a/test/infamy/restconf.py b/test/infamy/restconf.py index 04c922014..48654138b 100644 --- a/test/infamy/restconf.py +++ b/test/infamy/restconf.py @@ -266,7 +266,12 @@ def put_config_dicts(self, models): running = self.get_running() for model in models.keys(): - mod = self.lyctx.get_module(model) + try: + mod = self.lyctx.get_module(model) + except libyang.util.LibyangError: + raise Exception(f"YANG model '{model}' not found on device. " + f"Model may not be installed or enabled. " + f"Available models can be checked with get_schema_list()") from None lyd = mod.parse_data_dict(models[model], no_state=True, validate=False) running.merge(lyd) @@ -279,7 +284,12 @@ def put_config_dict(self, modname, edit): # This is hacky, refactor when rousette have PATCH support. running = self.get_running() - mod = self.lyctx.get_module(modname) + try: + mod = self.lyctx.get_module(modname) + except libyang.util.LibyangError: + raise Exception(f"YANG model '{modname}' not found on device. " + f"Model may not be installed or enabled. " + f"Available models can be checked with get_schema_list()") from None for k, _ in edit.items(): module = modname + ":" + k