From efe4903d9ce78a4e81ad2b46b0be1a807f6ccff3 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 3 Dec 2025 20:15:34 +0100 Subject: [PATCH] confd: initial support for NTP server Fixes #904 Signed-off-by: Joachim Wiberg --- src/confd/src/Makefile.am | 1 + src/confd/src/core.c | 27 + src/confd/src/core.h | 6 + src/confd/src/ntp.c | 243 +++ src/confd/src/system.c | 10 +- src/confd/yang/confd.inc | 5 + .../ietf-access-control-list@2019-03-04.yang | 668 ++++++++ .../confd/ietf-ethertypes@2019-03-04.yang | 381 +++++ src/confd/yang/confd/ietf-ntp@2022-07-05.yang | 1482 +++++++++++++++++ .../confd/ietf-packet-fields@2019-03-04.yang | 576 +++++++ src/confd/yang/confd/infix-ntp.yang | 171 ++ .../yang/confd/infix-ntp@2025-12-03.yang | 1 + src/klish-plugin-infix/xml/infix.xml | 16 +- src/show/show.py | 75 +- src/statd/python/cli_pretty/cli_pretty.py | 479 +++++- src/statd/python/yanger/__main__.py | 3 + src/statd/python/yanger/ietf_ntp.py | 363 ++++ src/statd/python/yanger/ietf_system.py | 1 + src/statd/statd.c | 3 + test/case/statd/system/cli/show-ntp | 10 +- 20 files changed, 4487 insertions(+), 34 deletions(-) create mode 100644 src/confd/src/ntp.c create mode 100644 src/confd/yang/confd/ietf-access-control-list@2019-03-04.yang create mode 100644 src/confd/yang/confd/ietf-ethertypes@2019-03-04.yang create mode 100644 src/confd/yang/confd/ietf-ntp@2022-07-05.yang create mode 100644 src/confd/yang/confd/ietf-packet-fields@2019-03-04.yang create mode 100644 src/confd/yang/confd/infix-ntp.yang create mode 120000 src/confd/yang/confd/infix-ntp@2025-12-03.yang create mode 100644 src/statd/python/yanger/ietf_ntp.py diff --git a/src/confd/src/Makefile.am b/src/confd/src/Makefile.am index 8db05041..1d379e69 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 df8754b7..fab04e78 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -328,6 +328,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; @@ -366,6 +370,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); @@ -454,6 +472,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"); @@ -554,6 +577,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 d376aa49..e65f2705 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 00000000..9813a6f2 --- /dev/null +++ b/src/confd/src/ntp.c @@ -0,0 +1,243 @@ +/* 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 "+" + +static int change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, + sr_event_t event, struct confd *confd) +{ + struct lyd_node *ntp, *entry, *makestep, *refclock; + const char *port; + FILE *fp; + + 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; + } + + ntp = lydx_get_xpathf(config, XPATH_NTP_); + if (!ntp) + 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 confd\n"); + fprintf(fp, "# This file configures chronyd as an NTP server\n\n"); + + /* Port configuration (optional) */ + port = lydx_get_cattr(ntp, "port"); + if (port) { + fprintf(fp, "# Custom NTP port\n"); + fprintf(fp, "port %d\n\n", atoi(port)); + } + + /* makestep configuration - allow clock stepping for fast initial sync */ + makestep = lydx_get_child(ntp, "makestep"); + if (makestep) { + const char *threshold = lydx_get_cattr(makestep, "threshold"); + const char *limit = lydx_get_cattr(makestep, "limit"); + + fprintf(fp, "# Allow clock stepping for fast initial sync\n"); + fprintf(fp, "makestep %.1f %d\n\n", atof(threshold), atoi(limit)); + } + + fprintf(fp, "# Upstream NTP servers and peers\n"); + LYX_LIST_FOR_EACH(lyd_child(ntp), entry, "unicast-configuration") { + const char *address, *type, *minpoll, *maxpoll, *version, *srcport; + const char *directive = NULL; + bool prefer, burst, iburst; + + address = lydx_get_cattr(entry, "address"); + type = lydx_get_cattr(entry, "type"); + minpoll = lydx_get_cattr(entry, "minpoll"); + maxpoll = lydx_get_cattr(entry, "maxpoll"); + version = lydx_get_cattr(entry, "version"); + srcport = lydx_get_cattr(entry, "port"); + prefer = lydx_get_bool(entry, "prefer"); + burst = lydx_get_bool(entry, "burst"); + iburst = lydx_get_bool(entry, "iburst"); + + if (type && strstr(type, "uc-server")) + directive = "server"; + else if (type && strstr(type, "uc-peer")) + directive = "peer"; + + if (directive && address) { + 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"); + } + } + fprintf(fp, "\n"); + + /* Reference clock (local stratum) - fallback time source */ + refclock = lydx_get_child(ntp, "refclock-master"); + if (refclock) { + int stratum = atoi(lydx_get_cattr(refclock, "master-stratum")); + + /* Only configure local clock if stratum is valid (1-15) */ + if (stratum >= 1 && stratum <= 15) { + fprintf(fp, "# Local reference clock - fallback stratum %d source\n", stratum); + fprintf(fp, "local stratum %d orphan\n\n", stratum); + } + } + + /* + * 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 container (stratum defaults to 16 per YANG model) + * - makestep with threshold 1.0 and limit 3 (fast initial sync for embedded) + * + * This provides a usable NTP server configuration with just 'edit ntp' + 'leave'. + */ +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_container = { .type = SR_CONTAINER_PRESENCE_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 container (let YANG provide default stratum 16) */ + DEBUG("Inferring NTP refclock-master container"); + srx_set_item(session, &inferred_container, 0, XPATH_NTP_"/refclock-master"); + + /* 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 container"); + /* Create presence container (let YANG provide defaults: threshold 1.0, limit 3) */ + srx_set_item(session, &inferred_container, 0, XPATH_NTP_"/infix-ntp:makestep"); + } + + 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(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 05ddc3ea..33a371be 100644 --- a/src/confd/src/system.c +++ b/src/confd/src/system.c @@ -283,7 +283,7 @@ err: 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 02566505..bb136003 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" @@ -48,4 +52,5 @@ MODULES=( "infix-crypto-types@2025-06-17.yang" "ietf-keystore -e symmetric-keys" "infix-keystore@2025-12-10.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 00000000..8bb0b626 --- /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 00000000..fd055074 --- /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 00000000..0ac851e2 --- /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 00000000..2fb797bd --- /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 00000000..d3576b85 --- /dev/null +++ b/src/confd/yang/confd/infix-ntp.yang @@ -0,0 +1,171 @@ +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)"; + } + } + } + + /* + * Additional operational state fields from chronyd + * that are not part of the standard ietf-ntp model + */ + augment "/ntp:ntp/ntp:clock-state/ntp:system-status" { + leaf last-offset { + type decimal64 { + fraction-digits 9; + } + config false; + units "seconds"; + description + "Estimated offset of the last clock update."; + } + + leaf rms-offset { + type decimal64 { + fraction-digits 9; + } + config false; + units "seconds"; + description + "Long-term average of the offset value."; + } + + leaf residual-freq { + type decimal64 { + fraction-digits 3; + } + config false; + units "ppm"; + description + "Residual frequency indicating how much the frequency has + changed relative to the previous value."; + } + + leaf skew { + type decimal64 { + fraction-digits 3; + } + config false; + units "ppm"; + description + "Estimated error bound on the frequency."; + } + + leaf update-interval { + type decimal64 { + fraction-digits 1; + } + config false; + units "seconds"; + description + "Interval between the last two clock updates."; + } + } + + /* + * 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 00000000..8bcf0feb --- /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 bb3d6a1e..4a4317c1 100644 --- a/src/klish-plugin-infix/xml/infix.xml +++ b/src/klish-plugin-infix/xml/infix.xml @@ -324,15 +324,19 @@ - - - show ntp - - + + + + + + + show ntp source $KLISH_PARAM_address + - doas ntp tracking + show ntp tracking + show ntp diff --git a/src/show/show.py b/src/show/show.py index 2ec78ba3..e0fd57a9 100755 --- a/src/show/show.py +++ b/src/show/show.py @@ -69,15 +69,84 @@ def hardware(args: List[str]) -> None: cli_pretty(data, "show-hardware") def ntp(args: List[str]) -> None: - data = run_sysrepocfg("/system-state/ntp") + # Create argument parser for ntp subcommands + parser = argparse.ArgumentParser(prog='show ntp', add_help=False) + + # Add subparsers for source and tracking + subparsers = parser.add_subparsers(dest='subcommand', help='NTP subcommands') + + # source subcommand + source_parser = subparsers.add_parser('source', help='Show NTP source(s)') + source_parser.add_argument('address', nargs='?', help='Show details for specific source address') + + # tracking subcommand + tracking_parser = subparsers.add_parser('tracking', help='Show NTP tracking status') + + # Parse the arguments + try: + parsed_args = parser.parse_args(args) + except SystemExit: + # argparse calls sys.exit on error, catch it + return + + # Dispatch to appropriate handler + if parsed_args.subcommand == 'source': + return ntp_source([parsed_args.address] if parsed_args.address else []) + elif parsed_args.subcommand == 'tracking': + return ntp_tracking([]) + + # Default: show ntp (no subcommand or address) + # Fetch both client and server operational data + client_data = run_sysrepocfg("/system-state/ntp") + server_data = run_sysrepocfg("/ietf-ntp:ntp") + + # Merge into single data structure + data = {} + if client_data: + data.update(client_data) + if server_data: + data.update(server_data) + + if RAW_OUTPUT: + if not data: + print("No ntp data retrieved.") + return + print(json.dumps(data, indent=2)) + return + + # Always call cli_pretty, even with empty data, to show proper message if not data: - print("No ntp data retrieved.") + data = {} + + # Default show ntp - summary view (no address support at top level) + cli_pretty(data, "show-ntp") + +def ntp_tracking(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") + cli_pretty(data, "show-ntp-tracking") + +def ntp_source(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 + + # Pass address argument if provided + if len(args) > 0 and args[0]: + cli_pretty(data, "show-ntp-source", "-a", args[0]) + else: + cli_pretty(data, "show-ntp-source") def is_valid_interface_name(interface_name: str) -> bool: """ diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 7405e70c..80e3379e 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -2387,24 +2387,461 @@ def show_container_detail(json, name): print(f"CPU Usage : {cpu_usage}%") -def show_ntp(json): - if not json.get("ietf-system:system-state"): - print("NTP client not enabled.") +def show_ntp_source_detail_single(source, is_association=False): + """Display detailed information for a single NTP source (auto-detected single source)""" + print(f"{'Server address':<20}: {source.get('address', 'N/A')}") + + # State + if is_association: + prefer = source.get("prefer", False) + state_desc = "Selected sync source" if prefer else "Candidate" + else: + state = source.get('state', 'unknown') + state_desc = { + 'selected': 'Selected sync source', + 'candidate': 'Candidate', + 'unreach': 'Unreachable', + 'not-combined': 'Not combined' + }.get(state, state) + print(f"{'State':<20}: {state_desc}") + + # Stratum + stratum = source.get('stratum') + if stratum is not None: + print(f"{'Stratum':<20}: {stratum}") + + # Poll interval + poll = source.get('poll') + if poll is not None: + poll_seconds = 2 ** poll + print(f"{'Poll interval':<20}: {poll} (2^{poll} seconds = {poll_seconds}s)") + +def show_ntp(json, address=None): + """Unified NTP status display for both client and server modes""" + ntp_data = json.get("ietf-ntp:ntp", {}) + port = ntp_data.get("port") + is_server = port is not None + + sources = [] + if is_server: + associations = ntp_data.get("associations", {}).get("association", []) + sources = associations + else: + system_state = json.get("ietf-system:system-state", {}) + if system_state: + sources = get_json_data({}, json, 'ietf-system:system-state', 'infix-system:ntp', 'sources', 'source') + + if address: + matching = [s for s in sources if s.get('address') == address] + if not matching: + print(f"No NTP source found with address: {address}") + return + if is_server: + show_ntp_association_detail(matching[0]) + else: + show_ntp_source_detail_single(matching[0], False) return - hdr = (f"{'ADDRESS':<{PadNtpSource.address}}" - f"{'MODE':<{PadNtpSource.mode}}" - f"{'STATE':<{PadNtpSource.state}}" - f"{'STRATUM':>{PadNtpSource.stratum}}" - f"{'POLL-INTERVAL':>{PadNtpSource.poll}}" - ) + + if is_server: + if sources: + print(f"{'Mode':<20}: Relay (no local reference clock)") + else: + print(f"{'Mode':<20}: Server (local reference clock)") + print(f"{'Port':<20}: {port}") + + # Show operational stratum + refclock = ntp_data.get("refclock-master") + if refclock: + stratum = refclock.get("master-stratum") + if stratum is not None: + print(f"{'Stratum':<20}: {stratum}") + + # Show reference time + clock_state = ntp_data.get("clock-state", {}).get("system-status", {}) + ref_time = clock_state.get("reference-time") + if ref_time: + from datetime import datetime + try: + dt = datetime.fromisoformat(ref_time.replace("Z", "+00:00")) + ref_time_str = dt.strftime("%a %b %d %H:%M:%S %Y") + print(f"{'Ref time (UTC)':<20}: {ref_time_str}") + except (ValueError, AttributeError): + pass + + 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") + + stats = ntp_data.get("ntp-statistics") + if stats: + 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(f"{'Mode':<20}: Client") + + # Show local clock state in client mode + clock_state = ntp_data.get("clock-state", {}).get("system-status", {}) + + # Show local operational stratum + stratum = clock_state.get("clock-stratum") + if stratum is not None: + print(f"{'Stratum':<20}: {stratum}") + + # Show reference time + ref_time = clock_state.get("reference-time") + if ref_time: + from datetime import datetime + try: + dt = datetime.fromisoformat(ref_time.replace("Z", "+00:00")) + ref_time_str = dt.strftime("%a %b %d %H:%M:%S %Y") + print(f"{'Ref time (UTC)':<20}: {ref_time_str}") + except (ValueError, AttributeError): + pass + + if len(sources) == 0: + return + if len(sources) == 1: + show_ntp_source_detail_single(sources[0], is_server) + return + print() + hdr = (f"{'ADDRESS':<{PadNtpSource.address}}" + f"{'MODE':<{PadNtpSource.mode}}" + f"{'STATE':<{PadNtpSource.state}}" + f"{'STRATUM':>{PadNtpSource.stratum}}" + f"{'POLL':>{PadNtpSource.poll}}") print(Decore.invert(hdr)) - sources = get_json_data({}, json, 'ietf-system:system-state', 'infix-system:ntp', 'sources', 'source') for source in sources: - row = f"{source['address']:<{PadNtpSource.address}}" - row += f"{source['mode']:<{PadNtpSource.mode}}" - row += f"{source['state'] if source['state'] != 'not-combined' else 'not combined':<{PadNtpSource.state}}" - row += f"{source['stratum']:>{PadNtpSource.stratum}}" - row += f"{source['poll']:>{PadNtpSource.poll}}" + # Extract fields - handle both association and ietf-system format + address = source.get('address', 'N/A') + + if is_server: + # Association format + local_mode = source.get("local-mode", "") + if ":" in local_mode: + local_mode = local_mode.split(":")[-1] + mode_str = local_mode + prefer = source.get("prefer", False) + state_str = "selected" if prefer else "candidate" + else: + # ietf-system format + mode_str = source.get('mode', 'unknown') + state_str = source.get('state', 'unknown') + if state_str == 'not-combined': + state_str = 'not combined' + + stratum = source.get('stratum', 0) + poll = source.get('poll', 0) + poll_str = f"{2**poll}s" if poll else "-" + + row = f"{address:<{PadNtpSource.address}}" + row += f"{mode_str:<{PadNtpSource.mode}}" + row += f"{state_str:<{PadNtpSource.state}}" + row += f"{stratum:>{PadNtpSource.stratum}}" + row += f"{poll_str:>{PadNtpSource.poll}}" + print(row) + + +def show_ntp_tracking(json): + """Display NTP clock tracking state using YANG operational data""" + ntp_data = json.get("ietf-ntp:ntp") + if not ntp_data: + print("NTP server not enabled.") + return + + clock_state = ntp_data.get("clock-state", {}).get("system-status", {}) + if not clock_state: + print("No clock state data available.") + return + + # Reference ID + refid = clock_state.get("clock-refid", "N/A") + print(f"{'Reference ID':<20}: {refid}") + + # Stratum + stratum = clock_state.get("clock-stratum", 16) + print(f"{'Stratum':<20}: {stratum}") + + # Reference time (show epoch if not set) + ref_time = clock_state.get("reference-time") + if ref_time: + from datetime import datetime + try: + dt = datetime.fromisoformat(ref_time.replace("Z", "+00:00")) + ref_time_str = dt.strftime("%a %b %d %H:%M:%S %Y") + except (ValueError, AttributeError): + ref_time_str = ref_time + else: + ref_time_str = "Thu Jan 01 00:00:00 1970" + print(f"{'Ref time (UTC)':<20}: {ref_time_str}") + + # System time offset (3 fraction-digits in ms = microsecond precision) + offset = clock_state.get("clock-offset") + if offset is not None: + offset_sec = float(offset) / 1000.0 + sign = "slow" if offset_sec >= 0 else "fast" + print(f"{'System time':<20}: {abs(offset_sec):.6f} seconds {sign} of NTP time") + + # Last offset (infix-ntp augment) + last_offset = clock_state.get("infix-ntp:last-offset") + if last_offset is not None: + last_offset_sec = float(last_offset) + sign = "+" if last_offset_sec >= 0 else "" + print(f"{'Last offset':<20}: {sign}{last_offset_sec:.9f} seconds") + else: + print(f"{'Last offset':<20}: N/A") + + # RMS offset (infix-ntp augment) + rms_offset = clock_state.get("infix-ntp:rms-offset") + if rms_offset is not None: + print(f"{'RMS offset':<20}: {float(rms_offset):.9f} seconds") + else: + print(f"{'RMS offset':<20}: N/A") + + # Frequency (convert from Hz difference to ppm) + nominal_freq = clock_state.get("nominal-freq") + actual_freq = clock_state.get("actual-freq") + if nominal_freq and actual_freq: + freq_diff_hz = float(actual_freq) - float(nominal_freq) + freq_ppm = (freq_diff_hz / float(nominal_freq)) * 1000000.0 + if freq_ppm == 0.0: + direction = "slow" + else: + direction = "slow" if freq_ppm < 0 else "fast" + print(f"{'Frequency':<20}: {abs(freq_ppm):.3f} ppm {direction}") + + # Residual freq (infix-ntp augment) + residual_freq = clock_state.get("infix-ntp:residual-freq") + if residual_freq is not None: + residual_freq_val = float(residual_freq) + sign = "+" if residual_freq_val >= 0 else "" + print(f"{'Residual freq':<20}: {sign}{residual_freq_val:.3f} ppm") + else: + print(f"{'Residual freq':<20}: N/A") + + # Skew (infix-ntp augment) + skew = clock_state.get("infix-ntp:skew") + if skew is not None: + print(f"{'Skew':<20}: {float(skew):.3f} ppm") + else: + print(f"{'Skew':<20}: N/A") + + # Root delay (3 fraction-digits in ms = microsecond precision) + root_delay = clock_state.get("root-delay") + if root_delay is not None: + root_delay_sec = float(root_delay) / 1000.0 + print(f"{'Root delay':<20}: {root_delay_sec:.6f} seconds") + + # Root dispersion (3 fraction-digits in ms = microsecond precision) + root_disp = clock_state.get("root-dispersion") + if root_disp is not None: + root_disp_sec = float(root_disp) / 1000.0 + print(f"{'Root dispersion':<20}: {root_disp_sec:.6f} seconds") + + # Update interval (infix-ntp augment) + update_interval = clock_state.get("infix-ntp:update-interval") + if update_interval is not None: + print(f"{'Update interval':<20}: {float(update_interval):.1f} seconds") + else: + print(f"{'Update interval':<20}: N/A") + + # Leap status + sync_state = clock_state.get("sync-state", "") + if "clock-synchronized" in sync_state: + leap_status = "Normal" + elif "clock-never-set" in sync_state: + leap_status = "Not synchronised" + else: + leap_status = "Unknown" + print(f"{'Leap status':<20}: {leap_status}") + + +def show_ntp_association_detail(assoc): + """Display detailed information for a single NTP association""" + print(f"{'Address':<20}: {assoc.get('address', 'N/A')}") + + # Mode + local_mode = assoc.get("local-mode", "") + if ":" in local_mode: + local_mode = local_mode.split(":")[-1] + + mode_desc = { + 'client': 'Server (client mode) [^]', + 'active': 'Peer (symmetric active) [=]', + 'broadcast-client': 'Broadcast/Local refclock [#]' + }.get(local_mode, local_mode) + print(f"{'Mode':<20}: {mode_desc}") + + # State/Prefer + prefer = assoc.get("prefer", False) + state_desc = "Selected sync source [*]" if prefer else "Candidate [+]" + print(f"{'State':<20}: {state_desc}") + + # Configured + isconfigured = assoc.get("isconfigured", False) + print(f"{'Configured':<20}: {'Yes' if isconfigured else 'No (dynamic)'}") + + # Stratum + stratum = assoc.get("stratum") + if stratum is not None: + print(f"{'Stratum':<20}: {stratum}") + + # Poll interval + poll = assoc.get("poll") + if poll is not None: + print(f"{'Poll interval':<20}: {poll} (2^{poll} seconds = {2**poll}s)") + + # Reachability + reach = assoc.get("reach") + if reach is not None: + print(f"{'Reachability':<20}: {reach:03o} (octal) = {reach:08b}b") + + # Time since last packet + now = assoc.get("now") + if now is not None: + print(f"{'Last RX':<20}: {now}s ago") + + # Offset + offset = assoc.get("offset") + if offset is not None: + offset_ms = float(offset) + if abs(offset_ms) < 1.0: + offset_str = f"{offset_ms * 1000.0:+.1f}us ({offset_ms:+.6f}ms)" + else: + offset_str = f"{offset_ms:+.3f}ms ({offset_ms / 1000.0:+.6f}s)" + print(f"{'Offset':<20}: {offset_str}") + + # Delay + delay = assoc.get("delay") + if delay is not None: + delay_ms = float(delay) + if abs(delay_ms) < 1.0: + delay_str = f"{delay_ms * 1000.0:.1f}us ({delay_ms:.6f}ms)" + else: + delay_str = f"{delay_ms:.3f}ms ({delay_ms / 1000.0:.6f}s)" + print(f"{'Delay':<20}: {delay_str}") + + # Dispersion + dispersion = assoc.get("dispersion") + if dispersion is not None: + disp_ms = float(dispersion) + if abs(disp_ms) < 1.0: + disp_str = f"{disp_ms * 1000.0:.1f}us ({disp_ms:.6f}ms)" + else: + disp_str = f"{disp_ms:.3f}ms ({disp_ms / 1000.0:.6f}s)" + print(f"{'Dispersion':<20}: {disp_str}") + +def show_ntp_source(json, address=None): + """Display NTP associations/sources""" + ntp_data = json.get("ietf-ntp:ntp") + if not ntp_data: + print("NTP server not enabled.") + return + + associations = ntp_data.get("associations", {}).get("association", []) + if not associations: + print("No NTP associations found.") + return + + # If address specified, show detailed view for that association + if address: + matching = [a for a in associations if a.get('address') == address] + if not matching: + print(f"No NTP association found with address: {address}") + return + show_ntp_association_detail(matching[0]) + return + + # If single association, show detailed view automatically + if len(associations) == 1: + show_ntp_association_detail(associations[0]) + return + + # First pass: determine maximum address width needed + max_addr_len = len("Name/IP address") # Minimum width to match chronyc header + for assoc in associations: + addr_len = len(assoc.get("address", "")) + if addr_len > max_addr_len: + max_addr_len = addr_len + + # Cap at reasonable maximum (IPv6 can be up to 39 chars uncompressed) + max_addr_len = min(max_addr_len, 39) + + # Table header - similar to chronyc sources + hdr = f"{'MS':<3}{f'Name/IP address':<{max_addr_len}} {'Stratum':>7} {'Poll':>4} {'Reach':>5} {'LastRx':>6} {'Last sample':>24}" + print(Decore.invert(hdr)) + + # Display each association + for assoc in associations: + # State indicator: * = prefer (sync source), + = candidate + prefer = assoc.get("prefer", False) + state = "*" if prefer else "+" + + # Mode indicator + local_mode = assoc.get("local-mode", "") + if ":" in local_mode: + local_mode = local_mode.split(":")[-1] + # Map to chronyc-style mode indicators + mode_indicator = "^" # Default to server mode + if local_mode == "active": + mode_indicator = "=" + elif local_mode == "broadcast-client": + mode_indicator = "#" + + address = assoc.get("address", "N/A") + stratum = assoc.get("stratum", 0) + + # Poll interval (log2 seconds) + poll = assoc.get("poll") + poll_str = str(poll) if poll is not None else "-" + + # Reachability register (display as octal) + reach = assoc.get("reach") + if reach is not None: + reach_str = f"{reach:03o}" + else: + reach_str = "-" + + # Time since last packet (LastRx) + now = assoc.get("now") + now_str = str(now) if now is not None else "-" + + # Offset (in milliseconds, convert to microseconds for display) + offset = assoc.get("offset") + if offset is not None: + offset_ms = float(offset) + if abs(offset_ms) < 1.0: + # Show in microseconds if less than 1ms + offset_str = f"{offset_ms * 1000.0:+.0f}us" + else: + # Show in milliseconds + offset_str = f"{offset_ms:+.3f}ms" + else: + offset_str = "-" + + # Delay (in milliseconds) - show as +/- similar to chronyc + delay = assoc.get("delay") + if delay is not None: + delay_ms = float(delay) + if abs(delay_ms) < 1.0: + delay_str = f"+/- {delay_ms * 1000.0:.0f}us" + else: + delay_str = f"+/- {delay_ms:.3f}ms" + else: + delay_str = "" + + # Last sample column combines offset and delay like chronyc + if offset_str != "-": + last_sample = f"{offset_str:>12} {delay_str}" + else: + last_sample = "-" + + # Format row + ms_col = f"{mode_indicator}{state}" + row = f"{ms_col:<3}{address:<{max_addr_len}} {stratum:>7} {poll_str:>4} {reach_str:>5} {now_str:>6} {last_sample:>24}" print(row) @@ -4392,7 +4829,11 @@ def main(): subparsers.add_parser('show-firewall-log', help='Show firewall log') \ .add_argument('limit', nargs='?', help='Last N lines, default: all') - subparsers.add_parser('show-ntp', help='Show NTP sources') + subparsers.add_parser('show-ntp', help='Show NTP status') \ + .add_argument('-a', '--address', help='Show details for specific address') + subparsers.add_parser('show-ntp-tracking', help='Show NTP tracking status') + subparsers.add_parser('show-ntp-source', help='Show NTP associations/sources') \ + .add_argument('-a', '--address', help='Show details for specific source') subparsers.add_parser('show-bfd', help='Show BFD sessions') subparsers.add_parser('show-bfd-status', help='Show BFD status') @@ -4452,7 +4893,11 @@ def main(): elif args.command == "show-firewall-log": show_firewall_logs(args.limit) elif args.command == "show-ntp": - show_ntp(json_data) + show_ntp(json_data, args.address) + elif args.command == "show-ntp-tracking": + show_ntp_tracking(json_data) + elif args.command == "show-ntp-source": + show_ntp_source(json_data, args.address) elif args.command == "show-wifi-radio": show_wifi_radio(json_data) elif args.command == "show-bfd": diff --git a/src/statd/python/yanger/__main__.py b/src/statd/python/yanger/__main__.py index ddffaa09..09b76adb 100644 --- a/src/statd/python/yanger/__main__.py +++ b/src/statd/python/yanger/__main__.py @@ -78,6 +78,9 @@ def main(): 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 00000000..cdfade9e --- /dev/null +++ b/src/statd/python/yanger/ietf_ntp.py @@ -0,0 +1,363 @@ +import subprocess + +from .common import insert +from .host import HOST + + +def add_ntp_associations(out): + """Add NTP association information from chronyc sources and sourcestats""" + try: + # Get basic source information + sources_data = HOST.run_multiline(["chronyc", "-c", "sources"], []) + if not sources_data: + return + + # Get statistical information (offset, dispersion) + stats_data = HOST.run_multiline(["chronyc", "-c", "sourcestats"], []) + + # Build a map of address -> stats for quick lookup + stats_map = {} + if stats_data: + for line in stats_data: + parts = line.split(',') + if len(parts) >= 8: + address = parts[0] + stats_map[address] = { + "offset": parts[6], # Estimated offset in seconds + "std_dev": parts[7] # Standard deviation in seconds + } + + 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) + } + + # chronyc -c sources format: + # [0]=Mode, [1]=State, [2]=Address, [3]=Stratum, [4]=Poll, [5]=Reach, + # [6]=LastRx, [7]=LastOffset, [8]=OffsetAtLastUpdate, [9]=Error + for line in sources_data: + parts = line.split(',') + if len(parts) < 10: + continue + + mode_indicator = parts[0] + state_indicator = parts[1] + address = parts[2] + 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"] = address + assoc["local-mode"] = mode_map.get(mode_indicator, "ietf-ntp:client") + assoc["isconfigured"] = True + assoc["stratum"] = stratum + + # Prefer indicator: * means current sync source + if state_indicator == "*": + assoc["prefer"] = True + + # Reachability register (octal string to decimal uint8) + try: + reach_octal = parts[5] + assoc["reach"] = int(reach_octal, 8) + except (ValueError, IndexError): + pass + + # Poll interval (already in log2 seconds) + try: + assoc["poll"] = int(parts[4]) + except (ValueError, IndexError): + pass + + # Time since last packet (now) + try: + assoc["now"] = int(parts[6]) + except (ValueError, IndexError): + pass + + # Offset: prefer sourcestats data if available, otherwise use sources + # Convert from seconds to milliseconds with 3 fraction-digits + try: + if address in stats_map: + offset_sec = float(stats_map[address]["offset"]) + else: + # Use last offset from sources output (parts[7]) + offset_sec = float(parts[7]) + assoc["offset"] = f"{offset_sec * 1000.0:.3f}" + except (ValueError, IndexError): + pass + + # Delay: use error estimate from sources (parts[9]) + # Convert from seconds to milliseconds with 3 fraction-digits + try: + delay_sec = float(parts[9]) + # chronyd reports this as error bound, use absolute value + assoc["delay"] = f"{abs(delay_sec) * 1000.0:.3f}" + except (ValueError, IndexError): + pass + + # Dispersion: use standard deviation from sourcestats + # Convert from seconds to milliseconds with 3 fraction-digits + try: + if address in stats_map: + disp_sec = float(stats_map[address]["std_dev"]) + assoc["dispersion"] = f"{disp_sec * 1000.0:.3f}" + except (ValueError, IndexError): + pass + + 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(IP),Ref-ID(name),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) < 14: + return + + clock_state = {} + system_status = {} + + # chronyd uses stratum 0 for "not synchronized", YANG requires 1-16 + stratum_raw = int(parts[2]) + stratum = 16 if stratum_raw == 0 else stratum_raw + + if stratum == 16: + system_status["clock-state"] = "ietf-ntp:unsynchronized" + else: + system_status["clock-state"] = "ietf-ntp:synchronized" + + system_status["clock-stratum"] = stratum + + # Convert hex Ref-ID to IPv4 dotted notation + # "00000000" -> "0.0.0.0", "7F7F0101" -> "127.127.1.1" + refid_ip = parts[0] + refid_name = parts[1] + + if refid_name: + system_status["clock-refid"] = refid_name + elif refid_ip and len(refid_ip) == 8: + try: + a = int(refid_ip[0:2], 16) + b = int(refid_ip[2:4], 16) + c = int(refid_ip[4:6], 16) + d = int(refid_ip[6:8], 16) + system_status["clock-refid"] = f"{a}.{b}.{c}.{d}" + except ValueError: + system_status["clock-refid"] = refid_ip if refid_ip else "0.0.0.0" + else: + system_status["clock-refid"] = refid_ip if refid_ip else "0.0.0.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 + # Format with fraction-digits 4 to avoid scientific notation + try: + freq_ppm = float(parts[7]) + nominal = 1000000000.0 + actual = nominal * (1.0 + freq_ppm / 1000000.0) + system_status["nominal-freq"] = f"{nominal:.4f}" + system_status["actual-freq"] = f"{actual:.4f}" + 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) + # Convert to milliseconds with fraction-digits 3 to match YANG + try: + offset_sec = float(parts[4]) + system_status["clock-offset"] = f"{offset_sec * 1000.0:.3f}" + except (ValueError, IndexError): + pass + + # Root delay (in seconds, convert to milliseconds) + # Format with fraction-digits 3 to match YANG + try: + root_delay_sec = float(parts[10]) + system_status["root-delay"] = f"{root_delay_sec * 1000.0:.3f}" + except (ValueError, IndexError): + pass + + # Root dispersion (in seconds, convert to milliseconds) + # Format with fraction-digits 3 to match YANG + try: + root_disp_sec = float(parts[11]) + system_status["root-dispersion"] = f"{root_disp_sec * 1000.0:.3f}" + 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[3]) + 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_str = parts[13].strip() + if leap_status_str == "Not synchronised" or stratum == 16: + system_status["sync-state"] = "ietf-ntp:clock-never-set" + else: + system_status["sync-state"] = "ietf-ntp:clock-synchronized" + except (ValueError, IndexError): + # Default based on stratum + if stratum == 16: + system_status["sync-state"] = "ietf-ntp:clock-never-set" + else: + system_status["sync-state"] = "ietf-ntp:clock-synchronized" + + # Infix-specific augments: additional chronyd operational data + # Last offset (parts[5], in seconds, 9 fraction-digits) + try: + last_offset = float(parts[5]) + system_status["infix-ntp:last-offset"] = f"{last_offset:.9f}" + except (ValueError, IndexError): + pass + + # RMS offset (parts[6], in seconds, 9 fraction-digits) + try: + rms_offset = float(parts[6]) + system_status["infix-ntp:rms-offset"] = f"{rms_offset:.9f}" + except (ValueError, IndexError): + pass + + # Residual frequency (parts[8], in ppm, 3 fraction-digits) + try: + residual_freq = float(parts[8]) + system_status["infix-ntp:residual-freq"] = f"{residual_freq:.3f}" + except (ValueError, IndexError): + pass + + # Skew (parts[9], in ppm, 3 fraction-digits) + try: + skew = float(parts[9]) + system_status["infix-ntp:skew"] = f"{skew:.3f}" + except (ValueError, IndexError): + pass + + # Update interval (parts[12], in seconds, 1 fraction-digit) + try: + update_interval = float(parts[12]) + system_status["infix-ntp:update-interval"] = f"{update_interval:.1f}" + except (ValueError, IndexError): + pass + + 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_server_status(out): + """Add NTP server operational status (port and stratum) + + Note: This must be called after add_ntp_clock_state() so we can + reuse the stratum already extracted from chronyc tracking. + """ + try: + ntp_data = out.get("ietf-ntp:ntp", {}) + clock_state = ntp_data.get("clock-state", {}) + system_status = clock_state.get("system-status", {}) + stratum = system_status.get("clock-stratum") + + if stratum is not None: + # Populate refclock-master with operational stratum + # This shows what stratum we're actually operating at + refclock = { + "master-stratum": stratum + } + insert(out, "ietf-ntp:ntp", "refclock-master", refclock) + + # Get actual listening port, excluding loopback (command port) + # UNCONN 0 0 0.0.0.0:123 0.0.0.0:* users:(("chronyd",pid=5441)) + # UNCONN 0 0 *:123 *:* users:(("chronyd",pid=5441)) + ss_lines = HOST.run_multiline(["ss", "-ulnp"], []) + for line in ss_lines: + if "chronyd" not in line: + continue + if "127.0.0.1" in line or "[::1]" in line: + continue + + parts = line.split() + if len(parts) >= 5: + local_addr = parts[3] + port_str = local_addr.split(':')[-1] + if port_str.isdigit(): + insert(out, "ietf-ntp:ntp", "port", int(port_str)) + break + + except Exception: + # NTP server not running, silently skip + pass + + +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 operational(): + """Get operational state for ietf-ntp module""" + out = {} + add_ntp_associations(out) + add_ntp_clock_state(out) + add_ntp_server_status(out) + add_ntp_server_stats(out) + + return out diff --git a/src/statd/python/yanger/ietf_system.py b/src/statd/python/yanger/ietf_system.py index ba7aa1b0..5cff7ed7 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 bdd0024e..da02b730 100644 --- a/src/statd/statd.c +++ b/src/statd/statd.c @@ -50,6 +50,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); @@ -444,6 +445,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/statd/system/cli/show-ntp b/test/case/statd/system/cli/show-ntp index 5ed95397..46b6a422 100644 --- a/test/case/statd/system/cli/show-ntp +++ b/test/case/statd/system/cli/show-ntp @@ -1,4 +1,6 @@ -ADDRESS MODE STATE STRATUM POLL-INTERVAL -192.168.1.1 server candidate 1 6 -192.168.2.1 server candidate 1 6 -192.168.3.1 server selected 1 6 +Mode : Client + +ADDRESS MODE STATE STRATUM POLL +192.168.1.1 server candidate 1 64s +192.168.2.1 server candidate 1 64s +192.168.3.1 server selected 1 64s