mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,374 @@
|
||||
module ietf-keystore {
|
||||
yang-version 1.1;
|
||||
namespace "urn:ietf:params:xml:ns:yang:ietf-keystore";
|
||||
prefix ks;
|
||||
|
||||
import ietf-netconf-acm {
|
||||
prefix nacm;
|
||||
reference
|
||||
"RFC 8341: Network Configuration Access Control Model";
|
||||
}
|
||||
import ietf-crypto-types {
|
||||
prefix ct;
|
||||
reference
|
||||
"RFC AAAA: YANG Data Types and Groupings for Cryptography";
|
||||
}
|
||||
|
||||
organization
|
||||
"IETF NETCONF (Network Configuration) Working Group";
|
||||
contact
|
||||
"WG Web: https://datatracker.ietf.org/wg/netconf
|
||||
WG List: NETCONF WG list <mailto:netconf@ietf.org>
|
||||
Author: Kent Watsen <mailto:kent+ietf@watsen.net>";
|
||||
description
|
||||
"This module defines a 'keystore' to centralize management
|
||||
of security credentials.
|
||||
|
||||
Copyright (c) 2023 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 CCCC
|
||||
(https://www.rfc-editor.org/info/rfcCCCC); see the RFC
|
||||
itself for full legal notices.
|
||||
|
||||
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.";
|
||||
|
||||
revision 2023-12-28 {
|
||||
description
|
||||
"Initial version";
|
||||
reference
|
||||
"RFC CCCC: A YANG Data Model for a Keystore";
|
||||
}
|
||||
|
||||
feature central-keystore-supported {
|
||||
description
|
||||
"The 'central-keystore-supported' feature indicates that
|
||||
the server supports the central keystore (i.e., fully
|
||||
implements the 'ietf-keystore' module).";
|
||||
}
|
||||
|
||||
feature inline-definitions-supported {
|
||||
description
|
||||
"The 'inline-definitions-supported' feature indicates that
|
||||
the server supports locally-defined keys.";
|
||||
}
|
||||
|
||||
feature asymmetric-keys {
|
||||
description
|
||||
"The 'asymmetric-keys' feature indicates that the server
|
||||
implements the /keystore/asymmetric-keys subtree.";
|
||||
}
|
||||
|
||||
feature symmetric-keys {
|
||||
description
|
||||
"The 'symmetric-keys' feature indicates that the server
|
||||
implements the /keystore/symmetric-keys subtree.";
|
||||
}
|
||||
|
||||
typedef symmetric-key-ref {
|
||||
type leafref {
|
||||
path "/ks:keystore/ks:symmetric-keys/ks:symmetric-key/ks:name";
|
||||
}
|
||||
description
|
||||
"This typedef enables modules to easily define a reference
|
||||
to a symmetric key stored in the central keystore.";
|
||||
}
|
||||
|
||||
typedef asymmetric-key-ref {
|
||||
type leafref {
|
||||
path "/ks:keystore/ks:asymmetric-keys/ks:asymmetric-key/ks:name";
|
||||
}
|
||||
description
|
||||
"This typedef enables modules to easily define a reference
|
||||
to an asymmetric key stored in the central keystore.";
|
||||
}
|
||||
|
||||
grouping encrypted-by-grouping {
|
||||
description
|
||||
"A grouping that defines a 'choice' statement that can be
|
||||
augmented into the 'encrypted-by' node, present in the
|
||||
'symmetric-key-grouping' and 'asymmetric-key-pair-grouping'
|
||||
groupings defined in RFC AAAA, enabling references to keys
|
||||
in the central keystore.";
|
||||
choice encrypted-by {
|
||||
nacm:default-deny-write;
|
||||
mandatory true;
|
||||
description
|
||||
"A choice amongst other symmetric or asymmetric keys.";
|
||||
case symmetric-key-ref {
|
||||
if-feature "central-keystore-supported";
|
||||
if-feature "symmetric-keys";
|
||||
leaf symmetric-key-ref {
|
||||
type ks:symmetric-key-ref;
|
||||
description
|
||||
"Identifies the symmetric key used to encrypt the
|
||||
associated key.";
|
||||
}
|
||||
}
|
||||
case asymmetric-key-ref {
|
||||
if-feature "central-keystore-supported";
|
||||
if-feature "asymmetric-keys";
|
||||
leaf asymmetric-key-ref {
|
||||
type ks:asymmetric-key-ref;
|
||||
description
|
||||
"Identifies the asymmetric key whose public key
|
||||
encrypted the associated key.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grouping asymmetric-key-certificate-ref-grouping {
|
||||
description
|
||||
"Grouping for the reference to a certificate associated
|
||||
with an asymmetric key stored in the central keystore.";
|
||||
leaf asymmetric-key {
|
||||
nacm:default-deny-write;
|
||||
if-feature "central-keystore-supported";
|
||||
if-feature "asymmetric-keys";
|
||||
type ks:asymmetric-key-ref;
|
||||
must '../certificate';
|
||||
description
|
||||
"A reference to an asymmetric key in the keystore.";
|
||||
}
|
||||
leaf certificate {
|
||||
nacm:default-deny-write;
|
||||
type leafref {
|
||||
path "/ks:keystore/ks:asymmetric-keys/ks:asymmetric-key[ks:name = current()/../asymmetric-key]/ks:certificates/ks:certificate/ks:name";
|
||||
}
|
||||
must '../asymmetric-key';
|
||||
description
|
||||
"A reference to a specific certificate of the
|
||||
asymmetric key in the keystore.";
|
||||
}
|
||||
}
|
||||
|
||||
grouping inline-or-keystore-symmetric-key-grouping {
|
||||
description
|
||||
"A grouping for the configuration of a symmetric key. The
|
||||
symmetric key may be defined inline or as a reference to
|
||||
a symmetric key stored in the central keystore.
|
||||
|
||||
Servers that do not define the 'central-keystore-supported'
|
||||
feature SHOULD augment in custom 'case' statements enabling
|
||||
references to alternate keystore locations.";
|
||||
choice inline-or-keystore {
|
||||
nacm:default-deny-write;
|
||||
mandatory true;
|
||||
description
|
||||
"A choice between an inlined definition and a definition
|
||||
that exists in the keystore.";
|
||||
case inline {
|
||||
if-feature "inline-definitions-supported";
|
||||
container inline-definition {
|
||||
description
|
||||
"Container to hold the local key definition.";
|
||||
uses ct:symmetric-key-grouping;
|
||||
}
|
||||
}
|
||||
case central-keystore {
|
||||
if-feature "central-keystore-supported";
|
||||
if-feature "symmetric-keys";
|
||||
leaf central-keystore-reference {
|
||||
type ks:symmetric-key-ref;
|
||||
description
|
||||
"A reference to an symmetric key that exists in
|
||||
the central keystore.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grouping inline-or-keystore-asymmetric-key-grouping {
|
||||
description
|
||||
"A grouping for the configuration of an asymmetric key. The
|
||||
asymmetric key may be defined inline or as a reference to
|
||||
an asymmetric key stored in the central keystore.
|
||||
|
||||
Servers that do not define the 'central-keystore-supported'
|
||||
feature SHOULD augment in custom 'case' statements enabling
|
||||
references to alternate keystore locations.";
|
||||
choice inline-or-keystore {
|
||||
nacm:default-deny-write;
|
||||
mandatory true;
|
||||
description
|
||||
"A choice between an inlined definition and a definition
|
||||
that exists in the keystore.";
|
||||
case inline {
|
||||
if-feature "inline-definitions-supported";
|
||||
container inline-definition {
|
||||
description
|
||||
"Container to hold the local key definition.";
|
||||
uses ct:asymmetric-key-pair-grouping;
|
||||
}
|
||||
}
|
||||
case central-keystore {
|
||||
if-feature "central-keystore-supported";
|
||||
if-feature "asymmetric-keys";
|
||||
leaf central-keystore-reference {
|
||||
type ks:asymmetric-key-ref;
|
||||
description
|
||||
"A reference to an asymmetric key that exists in
|
||||
the central keystore. The intent is to reference
|
||||
just the asymmetric key without any regard for
|
||||
any certificates that may be associated with it.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grouping inline-or-keystore-asymmetric-key-with-certs-grouping {
|
||||
description
|
||||
"A grouping for the configuration of an asymmetric key and
|
||||
its associated certificates. The asymmetric key and its
|
||||
associated certificates may be defined inline or as a
|
||||
reference to an asymmetric key (and its associated
|
||||
certificates) in the central keystore.
|
||||
|
||||
Servers that do not define the 'central-keystore-supported'
|
||||
feature SHOULD augment in custom 'case' statements enabling
|
||||
references to alternate keystore locations.";
|
||||
choice inline-or-keystore {
|
||||
nacm:default-deny-write;
|
||||
mandatory true;
|
||||
description
|
||||
"A choice between an inlined definition and a definition
|
||||
that exists in the keystore.";
|
||||
case inline {
|
||||
if-feature "inline-definitions-supported";
|
||||
container inline-definition {
|
||||
description
|
||||
"Container to hold the local key definition.";
|
||||
uses ct:asymmetric-key-pair-with-certs-grouping;
|
||||
}
|
||||
}
|
||||
case central-keystore {
|
||||
if-feature "central-keystore-supported";
|
||||
if-feature "asymmetric-keys";
|
||||
leaf central-keystore-reference {
|
||||
type ks:asymmetric-key-ref;
|
||||
description
|
||||
"A reference to an asymmetric-key (and all of its
|
||||
associated certificates) in the keystore, when
|
||||
this module is implemented.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grouping inline-or-keystore-end-entity-cert-with-key-grouping {
|
||||
description
|
||||
"A grouping for the configuration of an asymmetric key and
|
||||
its associated end-entity certificate. The asymmetric key
|
||||
and its associated end-entity certificate may be defined
|
||||
inline or as a reference to an asymmetric key (and its
|
||||
associated end-entity certificate) in the central keystore.
|
||||
|
||||
Servers that do not define the 'central-keystore-supported'
|
||||
feature SHOULD augment in custom 'case' statements enabling
|
||||
references to alternate keystore locations.";
|
||||
choice inline-or-keystore {
|
||||
nacm:default-deny-write;
|
||||
mandatory true;
|
||||
description
|
||||
"A choice between an inlined definition and a definition
|
||||
that exists in the keystore.";
|
||||
case inline {
|
||||
if-feature "inline-definitions-supported";
|
||||
container inline-definition {
|
||||
description
|
||||
"Container to hold the local key definition.";
|
||||
uses ct:asymmetric-key-pair-with-cert-grouping;
|
||||
}
|
||||
}
|
||||
case central-keystore {
|
||||
if-feature "central-keystore-supported";
|
||||
if-feature "asymmetric-keys";
|
||||
container central-keystore-reference {
|
||||
description
|
||||
"A reference to a specific certificate associated with
|
||||
an asymmetric key stored in the central keystore.";
|
||||
uses asymmetric-key-certificate-ref-grouping;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grouping keystore-grouping {
|
||||
description
|
||||
"Grouping definition enables use in other contexts. If ever
|
||||
done, implementations MUST augment new 'case' statements
|
||||
into the various inline-or-keystore 'choice' statements to
|
||||
supply leafrefs to the model-specific location(s).";
|
||||
container asymmetric-keys {
|
||||
nacm:default-deny-write;
|
||||
if-feature "asymmetric-keys";
|
||||
description
|
||||
"A list of asymmetric keys.";
|
||||
list asymmetric-key {
|
||||
key "name";
|
||||
description
|
||||
"An asymmetric key.";
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"An arbitrary name for the asymmetric key.";
|
||||
}
|
||||
uses ct:asymmetric-key-pair-with-certs-grouping;
|
||||
}
|
||||
}
|
||||
container symmetric-keys {
|
||||
nacm:default-deny-write;
|
||||
if-feature "symmetric-keys";
|
||||
description
|
||||
"A list of symmetric keys.";
|
||||
list symmetric-key {
|
||||
key "name";
|
||||
description
|
||||
"A symmetric key.";
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"An arbitrary name for the symmetric key.";
|
||||
}
|
||||
uses ct:symmetric-key-grouping;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container keystore {
|
||||
nacm:default-deny-write;
|
||||
if-feature "central-keystore-supported";
|
||||
description
|
||||
"A central keystore containing a list of symmetric keys and
|
||||
a list of asymmetric keys.";
|
||||
uses keystore-grouping {
|
||||
augment "symmetric-keys/symmetric-key/key-type/encrypted-key/encrypted-key/encrypted-by" {
|
||||
description
|
||||
"Augments in a choice statement enabling the encrypting
|
||||
key to be any other symmetric or asymmetric key in the
|
||||
central keystore.";
|
||||
uses encrypted-by-grouping;
|
||||
}
|
||||
augment "asymmetric-keys/asymmetric-key/private-key-type/encrypted-private-key/encrypted-private-key/encrypted-by" {
|
||||
description
|
||||
"Augments in a choice statement enabling the encrypting
|
||||
key to be any other symmetric or asymmetric key in the
|
||||
central keystore.";
|
||||
uses encrypted-by-grouping;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
module ietf-netconf-acm {
|
||||
namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm";
|
||||
prefix nacm;
|
||||
|
||||
import ietf-yang-types {
|
||||
prefix yang;
|
||||
}
|
||||
|
||||
organization
|
||||
"IETF NETCONF (Network Configuration) Working Group";
|
||||
contact
|
||||
"WG Web: <https://datatracker.ietf.org/wg/netconf/>
|
||||
WG List: <mailto:netconf@ietf.org>
|
||||
|
||||
Author: Andy Bierman
|
||||
<mailto:andy@yumaworks.com>
|
||||
|
||||
Author: Martin Bjorklund
|
||||
<mailto:mbj@tail-f.com>";
|
||||
description
|
||||
"Network Configuration Access Control Model.
|
||||
|
||||
Copyright (c) 2012 - 2018 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 Simplified 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 8341; see
|
||||
the RFC itself for full legal notices.";
|
||||
|
||||
revision 2018-02-14 {
|
||||
description
|
||||
"Added support for YANG 1.1 actions and notifications tied to
|
||||
data nodes. Clarified how NACM extensions can be used by
|
||||
other data models.";
|
||||
reference
|
||||
"RFC 8341: Network Configuration Access Control Model";
|
||||
}
|
||||
revision 2012-02-22 {
|
||||
description
|
||||
"Initial version.";
|
||||
reference
|
||||
"RFC 6536: Network Configuration Protocol (NETCONF)
|
||||
Access Control Model";
|
||||
}
|
||||
|
||||
extension default-deny-write {
|
||||
description
|
||||
"Used to indicate that the data model node
|
||||
represents a sensitive security system parameter.
|
||||
|
||||
If present, the NETCONF server will only allow the designated
|
||||
'recovery session' to have write access to the node. An
|
||||
explicit access control rule is required for all other users.
|
||||
|
||||
If the NACM module is used, then it must be enabled (i.e.,
|
||||
/nacm/enable-nacm object equals 'true'), or this extension
|
||||
is ignored.
|
||||
|
||||
The 'default-deny-write' extension MAY appear within a data
|
||||
definition statement. It is ignored otherwise.";
|
||||
}
|
||||
extension default-deny-all {
|
||||
description
|
||||
"Used to indicate that the data model node
|
||||
controls a very sensitive security system parameter.
|
||||
|
||||
If present, the NETCONF server will only allow the designated
|
||||
'recovery session' to have read, write, or execute access to
|
||||
the node. An explicit access control rule is required for all
|
||||
other users.
|
||||
|
||||
If the NACM module is used, then it must be enabled (i.e.,
|
||||
/nacm/enable-nacm object equals 'true'), or this extension
|
||||
is ignored.
|
||||
|
||||
The 'default-deny-all' extension MAY appear within a data
|
||||
definition statement, 'rpc' statement, or 'notification'
|
||||
statement. It is ignored otherwise.";
|
||||
}
|
||||
|
||||
typedef user-name-type {
|
||||
type string {
|
||||
length "1..max";
|
||||
}
|
||||
description
|
||||
"General-purpose username string.";
|
||||
}
|
||||
|
||||
typedef matchall-string-type {
|
||||
type string {
|
||||
pattern '\*';
|
||||
}
|
||||
description
|
||||
"The string containing a single asterisk '*' is used
|
||||
to conceptually represent all possible values
|
||||
for the particular leaf using this data type.";
|
||||
}
|
||||
|
||||
typedef access-operations-type {
|
||||
type bits {
|
||||
bit create {
|
||||
description
|
||||
"Any protocol operation that creates a
|
||||
new data node.";
|
||||
}
|
||||
bit read {
|
||||
description
|
||||
"Any protocol operation or notification that
|
||||
returns the value of a data node.";
|
||||
}
|
||||
bit update {
|
||||
description
|
||||
"Any protocol operation that alters an existing
|
||||
data node.";
|
||||
}
|
||||
bit delete {
|
||||
description
|
||||
"Any protocol operation that removes a data node.";
|
||||
}
|
||||
bit exec {
|
||||
description
|
||||
"Execution access to the specified protocol operation.";
|
||||
}
|
||||
}
|
||||
description
|
||||
"Access operation.";
|
||||
}
|
||||
|
||||
typedef group-name-type {
|
||||
type string {
|
||||
length "1..max";
|
||||
pattern '[^\*].*';
|
||||
}
|
||||
description
|
||||
"Name of administrative group to which
|
||||
users can be assigned.";
|
||||
}
|
||||
|
||||
typedef action-type {
|
||||
type enumeration {
|
||||
enum "permit" {
|
||||
description
|
||||
"Requested action is permitted.";
|
||||
}
|
||||
enum "deny" {
|
||||
description
|
||||
"Requested action is denied.";
|
||||
}
|
||||
}
|
||||
description
|
||||
"Action taken by the server when a particular
|
||||
rule matches.";
|
||||
}
|
||||
|
||||
typedef node-instance-identifier {
|
||||
type yang:xpath1.0;
|
||||
description
|
||||
"Path expression used to represent a special
|
||||
data node, action, or notification instance-identifier
|
||||
string.
|
||||
|
||||
A node-instance-identifier value is an
|
||||
unrestricted YANG instance-identifier expression.
|
||||
All the same rules as an instance-identifier apply,
|
||||
except that predicates for keys are optional. If a key
|
||||
predicate is missing, then the node-instance-identifier
|
||||
represents all possible server instances for that key.
|
||||
|
||||
This XML Path Language (XPath) expression is evaluated in the
|
||||
following context:
|
||||
|
||||
o The set of namespace declarations are those in scope on
|
||||
the leaf element where this type is used.
|
||||
|
||||
o The set of variable bindings contains one variable,
|
||||
'USER', which contains the name of the user of the
|
||||
current session.
|
||||
|
||||
o The function library is the core function library, but
|
||||
note that due to the syntax restrictions of an
|
||||
instance-identifier, no functions are allowed.
|
||||
|
||||
o The context node is the root node in the data tree.
|
||||
|
||||
The accessible tree includes actions and notifications tied
|
||||
to data nodes.";
|
||||
}
|
||||
|
||||
container nacm {
|
||||
nacm:default-deny-all;
|
||||
description
|
||||
"Parameters for NETCONF access control model.";
|
||||
leaf enable-nacm {
|
||||
type boolean;
|
||||
default "true";
|
||||
description
|
||||
"Enables or disables all NETCONF access control
|
||||
enforcement. If 'true', then enforcement
|
||||
is enabled. If 'false', then enforcement
|
||||
is disabled.";
|
||||
}
|
||||
leaf read-default {
|
||||
type action-type;
|
||||
default "permit";
|
||||
description
|
||||
"Controls whether read access is granted if
|
||||
no appropriate rule is found for a
|
||||
particular read request.";
|
||||
}
|
||||
leaf write-default {
|
||||
type action-type;
|
||||
default "deny";
|
||||
description
|
||||
"Controls whether create, update, or delete access
|
||||
is granted if no appropriate rule is found for a
|
||||
particular write request.";
|
||||
}
|
||||
leaf exec-default {
|
||||
type action-type;
|
||||
default "permit";
|
||||
description
|
||||
"Controls whether exec access is granted if no appropriate
|
||||
rule is found for a particular protocol operation request.";
|
||||
}
|
||||
leaf enable-external-groups {
|
||||
type boolean;
|
||||
default "true";
|
||||
description
|
||||
"Controls whether the server uses the groups reported by the
|
||||
NETCONF transport layer when it assigns the user to a set of
|
||||
NACM groups. If this leaf has the value 'false', any group
|
||||
names reported by the transport layer are ignored by the
|
||||
server.";
|
||||
}
|
||||
leaf denied-operations {
|
||||
type yang:zero-based-counter32;
|
||||
config false;
|
||||
mandatory true;
|
||||
description
|
||||
"Number of times since the server last restarted that a
|
||||
protocol operation request was denied.";
|
||||
}
|
||||
leaf denied-data-writes {
|
||||
type yang:zero-based-counter32;
|
||||
config false;
|
||||
mandatory true;
|
||||
description
|
||||
"Number of times since the server last restarted that a
|
||||
protocol operation request to alter
|
||||
a configuration datastore was denied.";
|
||||
}
|
||||
leaf denied-notifications {
|
||||
type yang:zero-based-counter32;
|
||||
config false;
|
||||
mandatory true;
|
||||
description
|
||||
"Number of times since the server last restarted that
|
||||
a notification was dropped for a subscription because
|
||||
access to the event type was denied.";
|
||||
}
|
||||
container groups {
|
||||
description
|
||||
"NETCONF access control groups.";
|
||||
list group {
|
||||
key "name";
|
||||
description
|
||||
"One NACM group entry. This list will only contain
|
||||
configured entries, not any entries learned from
|
||||
any transport protocols.";
|
||||
leaf name {
|
||||
type group-name-type;
|
||||
description
|
||||
"Group name associated with this entry.";
|
||||
}
|
||||
leaf-list user-name {
|
||||
type user-name-type;
|
||||
description
|
||||
"Each entry identifies the username of
|
||||
a member of the group associated with
|
||||
this entry.";
|
||||
}
|
||||
}
|
||||
}
|
||||
list rule-list {
|
||||
key "name";
|
||||
ordered-by user;
|
||||
description
|
||||
"An ordered collection of access control rules.";
|
||||
leaf name {
|
||||
type string {
|
||||
length "1..max";
|
||||
}
|
||||
description
|
||||
"Arbitrary name assigned to the rule-list.";
|
||||
}
|
||||
leaf-list group {
|
||||
type union {
|
||||
type matchall-string-type;
|
||||
type group-name-type;
|
||||
}
|
||||
description
|
||||
"List of administrative groups that will be
|
||||
assigned the associated access rights
|
||||
defined by the 'rule' list.
|
||||
|
||||
The string '*' indicates that all groups apply to the
|
||||
entry.";
|
||||
}
|
||||
list rule {
|
||||
key "name";
|
||||
ordered-by user;
|
||||
description
|
||||
"One access control rule.
|
||||
|
||||
Rules are processed in user-defined order until a match is
|
||||
found. A rule matches if 'module-name', 'rule-type', and
|
||||
'access-operations' match the request. If a rule
|
||||
matches, the 'action' leaf determines whether or not
|
||||
access is granted.";
|
||||
leaf name {
|
||||
type string {
|
||||
length "1..max";
|
||||
}
|
||||
description
|
||||
"Arbitrary name assigned to the rule.";
|
||||
}
|
||||
leaf module-name {
|
||||
type union {
|
||||
type matchall-string-type;
|
||||
type string;
|
||||
}
|
||||
default "*";
|
||||
description
|
||||
"Name of the module associated with this rule.
|
||||
|
||||
This leaf matches if it has the value '*' or if the
|
||||
object being accessed is defined in the module with the
|
||||
specified module name.";
|
||||
}
|
||||
choice rule-type {
|
||||
description
|
||||
"This choice matches if all leafs present in the rule
|
||||
match the request. If no leafs are present, the
|
||||
choice matches all requests.";
|
||||
case protocol-operation {
|
||||
leaf rpc-name {
|
||||
type union {
|
||||
type matchall-string-type;
|
||||
type string;
|
||||
}
|
||||
description
|
||||
"This leaf matches if it has the value '*' or if
|
||||
its value equals the requested protocol operation
|
||||
name.";
|
||||
}
|
||||
}
|
||||
case notification {
|
||||
leaf notification-name {
|
||||
type union {
|
||||
type matchall-string-type;
|
||||
type string;
|
||||
}
|
||||
description
|
||||
"This leaf matches if it has the value '*' or if its
|
||||
value equals the requested notification name.";
|
||||
}
|
||||
}
|
||||
case data-node {
|
||||
leaf path {
|
||||
type node-instance-identifier;
|
||||
mandatory true;
|
||||
description
|
||||
"Data node instance-identifier associated with the
|
||||
data node, action, or notification controlled by
|
||||
this rule.
|
||||
|
||||
Configuration data or state data
|
||||
instance-identifiers start with a top-level
|
||||
data node. A complete instance-identifier is
|
||||
required for this type of path value.
|
||||
|
||||
The special value '/' refers to all possible
|
||||
datastore contents.";
|
||||
}
|
||||
}
|
||||
}
|
||||
leaf access-operations {
|
||||
type union {
|
||||
type matchall-string-type;
|
||||
type access-operations-type;
|
||||
}
|
||||
default "*";
|
||||
description
|
||||
"Access operations associated with this rule.
|
||||
|
||||
This leaf matches if it has the value '*' or if the
|
||||
bit corresponding to the requested operation is set.";
|
||||
}
|
||||
leaf action {
|
||||
type action-type;
|
||||
mandatory true;
|
||||
description
|
||||
"The access control action associated with the
|
||||
rule. If a rule has been determined to match a
|
||||
particular request, then this object is used
|
||||
to determine whether to permit or deny the
|
||||
request.";
|
||||
}
|
||||
leaf comment {
|
||||
type string;
|
||||
description
|
||||
"A textual description of the access rule.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
module ietf-truststore {
|
||||
yang-version 1.1;
|
||||
namespace "urn:ietf:params:xml:ns:yang:ietf-truststore";
|
||||
prefix ts;
|
||||
|
||||
import ietf-netconf-acm {
|
||||
prefix nacm;
|
||||
reference
|
||||
"RFC 8341: Network Configuration Access Control Model";
|
||||
}
|
||||
import ietf-crypto-types {
|
||||
prefix ct;
|
||||
reference
|
||||
"RFC AAAA: YANG Data Types and Groupings for Cryptography";
|
||||
}
|
||||
|
||||
organization
|
||||
"IETF NETCONF (Network Configuration) Working Group";
|
||||
contact
|
||||
"WG Web : https://datatracker.ietf.org/wg/netconf
|
||||
WG List : NETCONF WG list <mailto:netconf@ietf.org>
|
||||
Author : Kent Watsen <kent+ietf@watsen.net>";
|
||||
description
|
||||
"This module defines a 'truststore' to centralize management
|
||||
of trust anchors including certificates and public keys.
|
||||
|
||||
Copyright (c) 2023 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 BBBB
|
||||
(https://www.rfc-editor.org/info/rfcBBBB); see the RFC
|
||||
itself for full legal notices.
|
||||
|
||||
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.";
|
||||
|
||||
revision 2023-12-28 {
|
||||
description
|
||||
"Initial version";
|
||||
reference
|
||||
"RFC BBBB: A YANG Data Model for a Truststore";
|
||||
}
|
||||
|
||||
feature central-truststore-supported {
|
||||
description
|
||||
"The 'central-truststore-supported' feature indicates that
|
||||
the server supports the truststore (i.e., implements the
|
||||
'ietf-truststore' module).";
|
||||
}
|
||||
|
||||
feature inline-definitions-supported {
|
||||
description
|
||||
"The 'inline-definitions-supported' feature indicates that
|
||||
the server supports locally-defined trust anchors.";
|
||||
}
|
||||
|
||||
feature certificates {
|
||||
description
|
||||
"The 'certificates' feature indicates that the server
|
||||
implements the /truststore/certificate-bags subtree.";
|
||||
}
|
||||
|
||||
feature public-keys {
|
||||
description
|
||||
"The 'public-keys' feature indicates that the server
|
||||
implements the /truststore/public-key-bags subtree.";
|
||||
}
|
||||
|
||||
typedef certificate-bag-ref {
|
||||
type leafref {
|
||||
path "/ts:truststore/ts:certificate-bags/ts:certificate-bag/ts:name";
|
||||
}
|
||||
description
|
||||
"This typedef defines a reference to a certificate bag
|
||||
in the central truststore.";
|
||||
}
|
||||
|
||||
typedef certificate-ref {
|
||||
type leafref {
|
||||
path "/ts:truststore/ts:certificate-bags/ts:certificate-bag[ts:name = current()/../certificate-bag]/ts:certificate/ts:name";
|
||||
}
|
||||
description
|
||||
"This typedef defines a reference to a specific certificate
|
||||
in a certificate bag in the central truststore. This typedef
|
||||
requires that there exist a sibling 'leaf' node called
|
||||
'certificate-bag' that SHOULD have the typedef
|
||||
'certificate-bag-ref'.";
|
||||
}
|
||||
|
||||
typedef public-key-bag-ref {
|
||||
type leafref {
|
||||
path "/ts:truststore/ts:public-key-bags/ts:public-key-bag/ts:name";
|
||||
}
|
||||
description
|
||||
"This typedef defines a reference to a public key bag
|
||||
in the central truststore.";
|
||||
}
|
||||
|
||||
typedef public-key-ref {
|
||||
type leafref {
|
||||
path "/ts:truststore/ts:public-key-bags/ts:public-key-bag[ts:name = current()/../public-key-bag]/ts:public-key/ts:name";
|
||||
}
|
||||
description
|
||||
"This typedef defines a reference to a specific public key
|
||||
in a public key bag in the truststore. This typedef
|
||||
requires that there exist a sibling 'leaf' node called
|
||||
'public-key-bag' that SHOULD have the typedef
|
||||
'public-key-bag-ref'.";
|
||||
}
|
||||
|
||||
grouping certificate-ref-grouping {
|
||||
description
|
||||
"Grouping for the reference to a certificate in a
|
||||
certificate-bag in the central truststore.";
|
||||
leaf certificate-bag {
|
||||
nacm:default-deny-write;
|
||||
if-feature "central-truststore-supported";
|
||||
if-feature "certificates";
|
||||
type ts:certificate-bag-ref;
|
||||
must "../certificate";
|
||||
description
|
||||
"Reference to a certificate-bag in the truststore.";
|
||||
}
|
||||
leaf certificate {
|
||||
nacm:default-deny-write;
|
||||
type ts:certificate-ref;
|
||||
must "../certificate-bag";
|
||||
description
|
||||
"Reference to a specific certificate in the
|
||||
referenced certificate-bag.";
|
||||
}
|
||||
}
|
||||
|
||||
grouping public-key-ref-grouping {
|
||||
description
|
||||
"Grouping for the reference to a public key in a
|
||||
public-key-bag in the central truststore.";
|
||||
leaf public-key-bag {
|
||||
nacm:default-deny-write;
|
||||
if-feature "central-truststore-supported";
|
||||
if-feature "public-keys";
|
||||
type ts:public-key-bag-ref;
|
||||
description
|
||||
"Reference of a public key bag in the truststore inlucding
|
||||
the certificate to authenticate the TLS client.";
|
||||
}
|
||||
leaf public-key {
|
||||
nacm:default-deny-write;
|
||||
type ts:public-key-ref;
|
||||
description
|
||||
"Reference to a specific public key in the
|
||||
referenced public-key-bag.";
|
||||
}
|
||||
}
|
||||
|
||||
grouping inline-or-truststore-certs-grouping {
|
||||
description
|
||||
"A grouping for the configuration of a list of certificates.
|
||||
The list of certificate may be defined inline or as a
|
||||
reference to a certificate bag in the central truststore.
|
||||
|
||||
Servers that do not define the 'central-truststore-supported'
|
||||
feature SHOULD augment in custom 'case' statements enabling
|
||||
references to alternate truststore locations.";
|
||||
choice inline-or-truststore {
|
||||
nacm:default-deny-write;
|
||||
mandatory true;
|
||||
description
|
||||
"A choice between an inlined definition and a definition
|
||||
that exists in the truststore.";
|
||||
case inline {
|
||||
if-feature "inline-definitions-supported";
|
||||
container inline-definition {
|
||||
description
|
||||
"A container for locally configured trust anchor
|
||||
certificates.";
|
||||
list certificate {
|
||||
key "name";
|
||||
min-elements 1;
|
||||
description
|
||||
"A trust anchor certificate.";
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"An arbitrary name for this certificate.";
|
||||
}
|
||||
uses ct:trust-anchor-cert-grouping {
|
||||
refine "cert-data" {
|
||||
mandatory true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case central-truststore {
|
||||
if-feature "central-truststore-supported";
|
||||
if-feature "certificates";
|
||||
leaf central-truststore-reference {
|
||||
type ts:certificate-bag-ref;
|
||||
description
|
||||
"A reference to a certificate bag that exists in the
|
||||
central truststore.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grouping inline-or-truststore-public-keys-grouping {
|
||||
description
|
||||
"A grouping that allows the public keys to be either
|
||||
configured locally, within the using data model, or be a
|
||||
reference to a public key bag stored in the truststore.
|
||||
|
||||
Servers that do not define the 'central-truststore-supported'
|
||||
feature SHOULD augment in custom 'case' statements enabling
|
||||
references to alternate truststore locations.";
|
||||
choice inline-or-truststore {
|
||||
nacm:default-deny-write;
|
||||
mandatory true;
|
||||
description
|
||||
"A choice between an inlined definition and a definition
|
||||
that exists in the truststore.";
|
||||
case inline {
|
||||
if-feature "inline-definitions-supported";
|
||||
container inline-definition {
|
||||
description
|
||||
"A container to hold local public key definitions.";
|
||||
list public-key {
|
||||
key "name";
|
||||
description
|
||||
"A public key definition.";
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"An arbitrary name for this public key.";
|
||||
}
|
||||
uses ct:public-key-grouping;
|
||||
}
|
||||
}
|
||||
}
|
||||
case central-truststore {
|
||||
if-feature "central-truststore-supported";
|
||||
if-feature "public-keys";
|
||||
leaf central-truststore-reference {
|
||||
type ts:public-key-bag-ref;
|
||||
description
|
||||
"A reference to a bag of public keys that exists
|
||||
in the central truststore.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grouping truststore-grouping {
|
||||
description
|
||||
"A grouping definition that enables use in other contexts.
|
||||
Where used, implementations MUST augment new 'case'
|
||||
statements into the various inline-or-truststore 'choice'
|
||||
statements to supply leafrefs to the model-specific
|
||||
location(s).";
|
||||
container certificate-bags {
|
||||
nacm:default-deny-write;
|
||||
if-feature "certificates";
|
||||
description
|
||||
"A collection of certificate bags.";
|
||||
list certificate-bag {
|
||||
key "name";
|
||||
description
|
||||
"A bag of certificates. Each bag of certificates SHOULD
|
||||
be for a specific purpose. For instance, one bag could
|
||||
be used to authenticate a specific set of servers, while
|
||||
another could be used to authenticate a specific set of
|
||||
clients.";
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"An arbitrary name for this bag of certificates.";
|
||||
}
|
||||
leaf description {
|
||||
type string;
|
||||
description
|
||||
"A description for this bag of certificates. The
|
||||
intended purpose for the bag SHOULD be described.";
|
||||
}
|
||||
list certificate {
|
||||
key "name";
|
||||
description
|
||||
"A trust anchor certificate.";
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"An arbitrary name for this certificate.";
|
||||
}
|
||||
uses ct:trust-anchor-cert-grouping {
|
||||
refine "cert-data" {
|
||||
mandatory true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
container public-key-bags {
|
||||
nacm:default-deny-write;
|
||||
if-feature "public-keys";
|
||||
description
|
||||
"A collection of public key bags.";
|
||||
list public-key-bag {
|
||||
key "name";
|
||||
description
|
||||
"A bag of public keys. Each bag of keys SHOULD be for
|
||||
a specific purpose. For instance, one bag could be used
|
||||
authenticate a specific set of servers, while another
|
||||
could be used to authenticate a specific set of clients.";
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"An arbitrary name for this bag of public keys.";
|
||||
}
|
||||
leaf description {
|
||||
type string;
|
||||
description
|
||||
"A description for this bag public keys. The
|
||||
intended purpose for the bag SHOULD be described.";
|
||||
}
|
||||
list public-key {
|
||||
key "name";
|
||||
description
|
||||
"A public key.";
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"An arbitrary name for this public key.";
|
||||
}
|
||||
uses ct:public-key-grouping;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container truststore {
|
||||
nacm:default-deny-write;
|
||||
if-feature "central-truststore-supported";
|
||||
description
|
||||
"The truststore contains bags of certificates and
|
||||
public keys.";
|
||||
uses truststore-grouping;
|
||||
}
|
||||
}
|
||||
@@ -71,9 +71,6 @@ module infix-ethernet-interface {
|
||||
}
|
||||
|
||||
/* Deviations for statistics */
|
||||
deviation "/if:interfaces/if:interface/eth:ethernet/eth:statistics/eth:frame/eth:in-total-frames" {
|
||||
deviate not-supported;
|
||||
}
|
||||
deviation "/if:interfaces/if:interface/eth:ethernet/eth:statistics/eth:frame/eth:out-error-mac-internal-frames" {
|
||||
deviate not-supported;
|
||||
}
|
||||
@@ -82,5 +79,5 @@ module infix-ethernet-interface {
|
||||
}
|
||||
deviation "/if:interfaces/if:interface/eth:ethernet/eth:statistics/eth:mac-control" {
|
||||
deviate not-supported;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
*.pyc
|
||||
@@ -4,9 +4,9 @@ version = "1.0"
|
||||
description = "Linux to infix-YANG"
|
||||
license = "MIT"
|
||||
packages = [
|
||||
{ include = "yanger/*.py" },
|
||||
{ include = "cli_pretty/*.py" },
|
||||
{ include = "ospf_status/*.py" }
|
||||
{ include = "yanger" },
|
||||
{ include = "cli_pretty" },
|
||||
{ include = "ospf_status" }
|
||||
]
|
||||
authors = [
|
||||
"KernelKit developers"
|
||||
@@ -18,6 +18,6 @@ build-backend = "poetry.core.masonry.api"
|
||||
|
||||
|
||||
[tool.poetry.scripts]
|
||||
yanger = "yanger:main"
|
||||
yanger = "yanger.__main__:main"
|
||||
cli-pretty = "cli_pretty:main"
|
||||
ospf-status = "ospf_status:main"
|
||||
ospf-status = "ospf_status:main"
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from .yanger import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import logging
|
||||
import logging.handlers
|
||||
import subprocess
|
||||
import json
|
||||
import sys # (built-in module)
|
||||
import os
|
||||
import argparse
|
||||
|
||||
from . import common
|
||||
from . import host
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="YANG data creator")
|
||||
parser.add_argument("model", help="YANG Model")
|
||||
parser.add_argument("-p", "--param", default=None, help="Model dependent parameter")
|
||||
parser.add_argument("-t", "--test", default=None, help="Test data base path")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
# Set up syslog output for critical errors to aid debugging
|
||||
common.LOG = logging.getLogger('yanger')
|
||||
if os.path.exists('/dev/log'):
|
||||
log = logging.handlers.SysLogHandler(address='/dev/log')
|
||||
else:
|
||||
# Use /dev/null as a fallback for unit tests
|
||||
log = logging.FileHandler('/dev/null')
|
||||
|
||||
fmt = logging.Formatter('%(name)s[%(process)d]: %(message)s')
|
||||
log.setFormatter(fmt)
|
||||
common.LOG.setLevel(logging.INFO)
|
||||
common.LOG.addHandler(log)
|
||||
|
||||
if args.test:
|
||||
host.HOST = host.Testhost(args.test)
|
||||
else:
|
||||
host.HOST = host.Localhost()
|
||||
|
||||
if args.model == 'ietf-interfaces':
|
||||
from . import ietf_interfaces
|
||||
yang_data = ietf_interfaces.operational(args.param)
|
||||
elif args.model == 'ietf-routing':
|
||||
from . import ietf_routing
|
||||
yang_data = ietf_routing.operational()
|
||||
elif args.model == 'ietf-ospf':
|
||||
from . import ietf_ospf
|
||||
yang_data = ietf_ospf.operational()
|
||||
elif args.model == 'ietf-hardware':
|
||||
from . import ietf_hardware
|
||||
yang_data = ietf_hardware.operational()
|
||||
elif args.model == 'infix-containers':
|
||||
from . import infix_containers
|
||||
yang_data = infix_containers.operational()
|
||||
elif args.model == 'ietf-system':
|
||||
from . import ietf_system
|
||||
yang_data = ietf_system.operational()
|
||||
else:
|
||||
common.LOG.warning(f"Unsupported model {args.model}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(json.dumps(yang_data, indent=2))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,27 @@
|
||||
LOG = None
|
||||
|
||||
def lookup(obj, *keys):
|
||||
"""This function returns a value from a nested json object"""
|
||||
curr = obj
|
||||
for key in keys:
|
||||
if isinstance(curr, dict) and key in curr:
|
||||
curr = curr[key]
|
||||
else:
|
||||
return None
|
||||
return curr
|
||||
|
||||
|
||||
def insert(obj, *path_and_value):
|
||||
""""This function inserts a value into a nested json object"""
|
||||
if len(path_and_value) < 2:
|
||||
raise ValueError("Error: insert() takes at least two args")
|
||||
|
||||
*path, value = path_and_value
|
||||
|
||||
curr = obj
|
||||
for key in path[:-1]:
|
||||
if key not in curr or not isinstance(curr[key], dict):
|
||||
curr[key] = {}
|
||||
curr = curr[key]
|
||||
|
||||
curr[path[-1]] = value
|
||||
@@ -0,0 +1,133 @@
|
||||
import abc
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from . import common
|
||||
|
||||
HOST = None
|
||||
|
||||
class Host(abc.ABC):
|
||||
"""Host system API"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def now(self):
|
||||
"""Get the current time as a `datetime`"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def run(self, cmd, default=None, log=True):
|
||||
"""Get stdout of cmd
|
||||
|
||||
Run cmd, provided as an argv, and return the output it
|
||||
produces. On error, return the default value if provided,
|
||||
otherwise raise an exception.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def run_multiline(self, cmd, default=None):
|
||||
"""Get lines of stdout of cmd"""
|
||||
try:
|
||||
txt = self.run(cmd, log=(default is None))
|
||||
return txt.splitlines()
|
||||
except:
|
||||
if default is not None:
|
||||
return default
|
||||
raise
|
||||
|
||||
def run_json(self, cmd, default=None):
|
||||
"""Get JSON object from stdout of cmd"""
|
||||
try:
|
||||
txt = self.run(cmd, log=(default is None))
|
||||
return json.loads(txt)
|
||||
except:
|
||||
if default is not None:
|
||||
return default
|
||||
raise
|
||||
|
||||
@abc.abstractmethod
|
||||
def read(self, path):
|
||||
"""Get the contents of path
|
||||
|
||||
Returns `None` if the file is not readable for any reason.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def read_json(self, path, default=None):
|
||||
"""Get JSON object from path """
|
||||
try:
|
||||
txt = self.read(path)
|
||||
return json.loads(txt)
|
||||
except:
|
||||
if default is not None:
|
||||
return default
|
||||
raise
|
||||
|
||||
class Localhost(Host):
|
||||
def now(self):
|
||||
return datetime.datetime.now(tz=datetime.timezone.utc)
|
||||
|
||||
def run(self, cmd, default=None, log=True):
|
||||
try:
|
||||
result = subprocess.run(cmd, check=True, text=True,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL)
|
||||
return result.stdout
|
||||
except subprocess.CalledProcessError as err:
|
||||
if default is not None:
|
||||
return default
|
||||
|
||||
if log:
|
||||
common.LOG.error(f"Failed to run {err}")
|
||||
raise
|
||||
|
||||
def read(self, path):
|
||||
try:
|
||||
with open(path, 'r') as f:
|
||||
data = f.read().strip()
|
||||
return data
|
||||
except FileNotFoundError:
|
||||
# This is considered OK
|
||||
pass
|
||||
except IOError:
|
||||
common.LOG.error(f"Failed to read \"{path}\"")
|
||||
|
||||
return None
|
||||
|
||||
class Testhost(Host):
|
||||
def __init__(self, basedir):
|
||||
self.basedir = basedir
|
||||
|
||||
def now(self):
|
||||
return datetime.datetime(2023, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)
|
||||
|
||||
def run(self, cmd, default=None, log=True):
|
||||
slug = "_".join(cmd).replace("/", "+").replace(" ", "-")
|
||||
path = os.path.join(self.basedir, "run", slug)
|
||||
|
||||
try:
|
||||
with open(path, 'r') as f:
|
||||
return f.read()
|
||||
except:
|
||||
if default is not None:
|
||||
return default
|
||||
|
||||
if log:
|
||||
common.LOG.error(f"No recording found for run \"{path}\"")
|
||||
raise
|
||||
|
||||
def read(self, path):
|
||||
path = os.path.join(self.basedir, "rootfs", path[1:])
|
||||
try:
|
||||
with open(path, 'r') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
# This is considered OK
|
||||
pass
|
||||
except:
|
||||
common.LOG.error(f"No recording found for file \"{path}\"")
|
||||
raise
|
||||
@@ -0,0 +1,90 @@
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from .common import insert
|
||||
from .host import HOST
|
||||
|
||||
|
||||
def vpd_vendor_extensions(data):
|
||||
vendor_extensions = []
|
||||
for ext in data:
|
||||
vendor_extension = {}
|
||||
vendor_extension["iana-enterprise-number"] = ext[0]
|
||||
vendor_extension["extension-data"] = ext[1]
|
||||
vendor_extensions.append(vendor_extension)
|
||||
return vendor_extensions
|
||||
|
||||
|
||||
def vpd_component(vpd):
|
||||
component = {}
|
||||
component["name"] = vpd.get("board")
|
||||
component["infix-hardware:vpd-data"] = {}
|
||||
|
||||
if vpd.get("data"):
|
||||
component["class"] = "infix-hardware:vpd"
|
||||
if vpd["data"].get("manufacture-date"):
|
||||
mfgdate = datetime.datetime.strptime(vpd["data"]["manufacture-date"],
|
||||
"%m/%d/%Y %H:%M:%S")
|
||||
component["mfg-date"] = mfgdate.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
if vpd["data"].get("manufacter"):
|
||||
component["mfg-name"] = vpd["data"]["manufacturer"]
|
||||
if vpd["data"].get("product-name"):
|
||||
component["model-name"] = vpd["data"]["product-name"]
|
||||
if vpd["data"].get("serial-number"):
|
||||
component["serial-num"] = vpd["data"]["serial-number"]
|
||||
|
||||
# Set VPD-data entrys
|
||||
for k, v in vpd["data"].items():
|
||||
if vpd["data"].get(k):
|
||||
if k != "vendor-extension":
|
||||
component["infix-hardware:vpd-data"][k] = v
|
||||
else:
|
||||
vendor_extensions=vpd_vendor_extensions(v)
|
||||
component["infix-hardware:vpd-data"]["infix-hardware:vendor-extension"] = vendor_extensions
|
||||
return component
|
||||
|
||||
|
||||
def vpd_components(systemjson):
|
||||
return [vpd_component(vpd) for vpd in systemjson.get("vpd", {}).values()]
|
||||
|
||||
|
||||
def usb_port_components(systemjson):
|
||||
usb_ports = systemjson.get("usb-ports", [])
|
||||
|
||||
ports=[]
|
||||
names=[]
|
||||
for usb_port in usb_ports:
|
||||
port={}
|
||||
if usb_port.get("path"):
|
||||
if usb_port["name"] in names:
|
||||
continue
|
||||
|
||||
path = usb_port["path"]
|
||||
if os.path.basename(path) == "authorized_default":
|
||||
# TODO: Untestable. Should be done via the host API
|
||||
if os.path.exists(path):
|
||||
with open(path, "r") as f:
|
||||
names.append(usb_port["name"])
|
||||
data = int(f.readline().strip())
|
||||
enabled = "unlocked" if data == 1 else "locked"
|
||||
port["state"] = {}
|
||||
port["state"]["admin-state"] = enabled
|
||||
port["name"] = usb_port["name"]
|
||||
port["class"] = "infix-hardware:usb"
|
||||
port["state"]["oper-state"] = "enabled"
|
||||
ports.append(port)
|
||||
|
||||
return ports
|
||||
|
||||
|
||||
def operational():
|
||||
systemjson = HOST.read_json("/run/system.json")
|
||||
|
||||
return {
|
||||
"ietf-hardware:hardware": {
|
||||
"component":
|
||||
vpd_components(systemjson) +
|
||||
usb_port_components(systemjson) +
|
||||
[],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,564 @@
|
||||
from ..common import insert, lookup, LOG
|
||||
from ..host import HOST
|
||||
|
||||
|
||||
def json_get_yang_type(iface_in):
|
||||
if iface_in['link_type'] == "loopback":
|
||||
return "infix-if-type:loopback"
|
||||
|
||||
if iface_in['link_type'] in ("gre", "gre6"):
|
||||
return "infix-if-type:gre"
|
||||
|
||||
if iface_in['link_type'] != "ether":
|
||||
return "infix-if-type:other"
|
||||
|
||||
if 'parentbus' in iface_in and iface_in['parentbus'] == "virtio":
|
||||
return "infix-if-type:etherlike"
|
||||
|
||||
if 'linkinfo' not in iface_in:
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
if 'info_kind' not in iface_in['linkinfo']:
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "veth":
|
||||
return "infix-if-type:veth"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] in ("gretap", "ip6gretap"):
|
||||
return "infix-if-type:gretap"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "vlan":
|
||||
return "infix-if-type:vlan"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "bridge":
|
||||
return "infix-if-type:bridge"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "dsa":
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "dummy":
|
||||
return "infix-if-type:dummy"
|
||||
|
||||
# Fallback
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
|
||||
def json_get_yang_origin(addr):
|
||||
"""Translate kernel IP address origin to YANG"""
|
||||
xlate = {
|
||||
"kernel_ll": "link-layer",
|
||||
"kernel_ra": "link-layer",
|
||||
"static": "static",
|
||||
"dhcp": "dhcp",
|
||||
"random": "random",
|
||||
}
|
||||
proto = addr['protocol']
|
||||
|
||||
if proto in ("kernel_ll", "kernel_ra"):
|
||||
if "stable-privacy" in addr:
|
||||
return "random"
|
||||
|
||||
return xlate.get(proto, "other")
|
||||
|
||||
|
||||
|
||||
|
||||
def iface_is_dsa(iface_in):
|
||||
"""Check if interface is a DSA/intra-switch port"""
|
||||
if "linkinfo" not in iface_in:
|
||||
return False
|
||||
if "info_kind" not in iface_in['linkinfo']:
|
||||
return False
|
||||
if iface_in['linkinfo']['info_kind'] != "dsa":
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_bridge_port_pvid(ifname):
|
||||
data = HOST.run_json(['bridge', '-j', 'vlan', 'show', 'dev', ifname])
|
||||
if len(data) != 1:
|
||||
return None
|
||||
|
||||
iface = data[0]
|
||||
|
||||
for vlan in iface['vlans']:
|
||||
if 'flags' in vlan and 'PVID' in vlan['flags']:
|
||||
return vlan['vlan']
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_bridge_port_stp_state(ifname):
|
||||
data = HOST.run_json(['bridge', '-j', 'link', 'show', 'dev', ifname])
|
||||
if len(data) != 1:
|
||||
return None
|
||||
|
||||
iface = data[0]
|
||||
|
||||
states = ['disabled', 'listening', 'learning', 'forwarding', 'blocking']
|
||||
if 'state' in iface and iface['state'] in states:
|
||||
return iface['state']
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_brport_multicast(ifname):
|
||||
"""Check if multicast snooping is enabled on bridge, default: nope"""
|
||||
data = HOST.run_json(['mctl', '-p', 'show', 'igmp', 'json'], default={})
|
||||
multicast = {}
|
||||
|
||||
if ifname in data.get('fast-leave-ports', []):
|
||||
multicast["fast-leave"] = True
|
||||
else:
|
||||
multicast["fast-leave"] = False
|
||||
|
||||
if ifname in data.get('multicast-router-ports', []):
|
||||
multicast["router"] = "permanent"
|
||||
else:
|
||||
multicast["router"] = "auto"
|
||||
|
||||
return multicast
|
||||
|
||||
|
||||
# We always get all interfaces for two reasons.
|
||||
# 1) To increase speed on large iron with many ports.
|
||||
# 2) To simplify testing (single dummy file ip-link-show.json).
|
||||
def get_ip_link():
|
||||
"""Fetch interface link information from kernel"""
|
||||
return HOST.run_json(['ip', '-s', '-d', '-j', 'link', 'show'])
|
||||
|
||||
def netns_get_ip_link(netns):
|
||||
"""Fetch interface link information from within a network namespace"""
|
||||
return HOST.run_json(['ip', 'netns', 'exec', netns, 'ip', '-s', '-d', '-j', 'link', 'show'])
|
||||
|
||||
def get_ip_addr():
|
||||
"""Fetch interface address information from kernel"""
|
||||
return HOST.run_json(['ip', '-j', 'addr', 'show'])
|
||||
|
||||
def netns_get_ip_addr(netns):
|
||||
"""Fetch interface address information from within a network namespace"""
|
||||
return HOST.run_json(['ip', 'netns', 'exec', netns, 'ip', '-j', 'addr', 'show'])
|
||||
|
||||
def get_netns_list():
|
||||
"""Fetch a list of network namespaces"""
|
||||
return HOST.run_json(['ip', '-j', 'netns', 'list'], [])
|
||||
|
||||
def netns_find_ifname(ifname):
|
||||
"""Find which network namespace owns ifname (if any)"""
|
||||
for netns in get_netns_list():
|
||||
for iface in netns_get_ip_link(netns['name']):
|
||||
if 'ifalias' in iface and iface['ifalias'] == ifname:
|
||||
return netns['name']
|
||||
return None
|
||||
|
||||
def netns_ifindex_to_ifname(ifindex):
|
||||
"""Look through all network namespaces for an interface index and return its name"""
|
||||
for netns in get_netns_list():
|
||||
for iface in netns_get_ip_link(netns['name']):
|
||||
if iface['ifindex'] == ifindex:
|
||||
if 'ifalias' in iface:
|
||||
return iface['ifalias']
|
||||
if 'ifname' in iface:
|
||||
return iface['ifname']
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def add_bridge_port_common(ifname, iface_in, iface_out):
|
||||
li = iface_in.get("linkinfo", {})
|
||||
if not (li.get("info_slave_kind") == "bridge" or \
|
||||
li.get("info_kind") == "bridge"):
|
||||
return
|
||||
|
||||
pvid = get_bridge_port_pvid(ifname)
|
||||
if pvid is not None:
|
||||
insert(iface_out, "infix-interfaces:bridge-port", "pvid", pvid)
|
||||
|
||||
def add_bridge_port_lower(ifname, iface_in, iface_out):
|
||||
li = iface_in.get("linkinfo", {})
|
||||
if not li.get("info_slave_kind") == "bridge":
|
||||
return
|
||||
|
||||
insert(iface_out, "infix-interfaces:bridge-port", "bridge", iface_in['master'])
|
||||
|
||||
stp_state = get_bridge_port_stp_state(ifname)
|
||||
if stp_state is not None:
|
||||
insert(iface_out, "infix-interfaces:bridge-port", "stp-state", stp_state)
|
||||
|
||||
multicast = get_brport_multicast(ifname)
|
||||
insert(iface_out, "infix-interfaces:bridge-port", "multicast", multicast)
|
||||
|
||||
|
||||
def add_gre(iface_in, iface_out):
|
||||
if 'link_type' in iface_in:
|
||||
val = json_get_yang_type(iface_in)
|
||||
if val != "infix-if-type:gre" and val != "infix-if-type:gretap":
|
||||
return;
|
||||
gre={}
|
||||
info_data=iface_in.get("linkinfo", {}).get("info_data", {})
|
||||
gre["local"] = info_data.get("local")
|
||||
gre["remote"] = info_data.get("remote")
|
||||
insert(iface_out, "infix-interfaces:gre", gre)
|
||||
|
||||
|
||||
def add_ip_link(ifname, iface_in, iface_out):
|
||||
if 'ifname' in iface_in:
|
||||
iface_out['name'] = ifname
|
||||
|
||||
if 'ifindex' in iface_in:
|
||||
iface_out['if-index'] = iface_in['ifindex']
|
||||
|
||||
if 'ifalias' in iface_in:
|
||||
iface_out['description'] = iface_in['ifalias']
|
||||
|
||||
if 'address' in iface_in and not "POINTOPOINT" in iface_in["flags"]:
|
||||
iface_out['phys-address'] = iface_in['address']
|
||||
|
||||
add_bridge_port_common(ifname, iface_in, iface_out)
|
||||
add_bridge_port_lower(ifname, iface_in, iface_out)
|
||||
|
||||
if not iface_is_dsa(iface_in):
|
||||
if iface_in.get('link'):
|
||||
insert(iface_out, "infix-interfaces:vlan", "lower-layer-if", iface_in['link'])
|
||||
elif 'link_index' in iface_in:
|
||||
# 'link_index' is the only reference we have if the link iface is in a namespace
|
||||
lower = netns_ifindex_to_ifname(iface_in['link_index'])
|
||||
if lower:
|
||||
insert(iface_out, "infix-interfaces:vlan", "lower-layer-if", lower)
|
||||
|
||||
if 'flags' in iface_in:
|
||||
iface_out['admin-status'] = "up" if "UP" in iface_in['flags'] else "down"
|
||||
|
||||
if 'operstate' in iface_in:
|
||||
xlate = {
|
||||
"DOWN": "down",
|
||||
"UP": "up",
|
||||
"DORMANT": "dormant",
|
||||
"TESTING": "testing",
|
||||
"LOWERLAYERDOWN": "lower-layer-down",
|
||||
"NOTPRESENT": "not-present"
|
||||
}
|
||||
val = xlate.get(iface_in['operstate'], "unknown")
|
||||
iface_out['oper-status'] = val
|
||||
|
||||
if 'link_type' in iface_in:
|
||||
val = json_get_yang_type(iface_in)
|
||||
iface_out['type'] = val
|
||||
add_gre(iface_in, iface_out)
|
||||
|
||||
val = lookup(iface_in, "stats64", "rx", "bytes")
|
||||
if val is not None:
|
||||
insert(iface_out, "statistics", "out-octets", str(val))
|
||||
|
||||
val = lookup(iface_in, "stats64", "tx", "bytes")
|
||||
if val is not None:
|
||||
insert(iface_out, "statistics", "in-octets", str(val))
|
||||
|
||||
|
||||
def add_ip_addr(ifname, iface_in, iface_out):
|
||||
if 'mtu' in iface_in and ifname != "lo":
|
||||
insert(iface_out, "ietf-ip:ipv4", "mtu", iface_in['mtu'])
|
||||
|
||||
val = HOST.read(f"/proc/sys/net/ipv6/conf/{ifname}/mtu")
|
||||
if val is not None:
|
||||
insert(iface_out, "ietf-ip:ipv6", "mtu", int(val.strip()))
|
||||
|
||||
if 'addr_info' in iface_in:
|
||||
inet = []
|
||||
inet6 = []
|
||||
|
||||
for addr in iface_in['addr_info']:
|
||||
new = {}
|
||||
|
||||
if 'family' not in addr:
|
||||
LOG.error("'family' missing from 'addr_info'")
|
||||
continue
|
||||
|
||||
if 'local' in addr:
|
||||
new['ip'] = addr['local']
|
||||
if 'prefixlen' in addr:
|
||||
new['prefix-length'] = addr['prefixlen']
|
||||
if 'protocol' in addr:
|
||||
new['origin'] = json_get_yang_origin(addr)
|
||||
|
||||
if addr['family'] == "inet":
|
||||
inet.append(new)
|
||||
elif addr['family'] == "inet6":
|
||||
inet6.append(new)
|
||||
else:
|
||||
LOG.error("invalid 'family' in 'addr_info'")
|
||||
sys.exit(1)
|
||||
|
||||
insert(iface_out, "ietf-ip:ipv4", "address", inet)
|
||||
insert(iface_out, "ietf-ip:ipv6", "address", inet6)
|
||||
|
||||
|
||||
def add_ethtool_groups(ifname, iface_out):
|
||||
"""Fetch interface counters from kernel (need new JSON format!)"""
|
||||
cmd = ['ethtool', '--json', '-S', ifname, '--all-groups']
|
||||
try:
|
||||
data = HOST.run_json(cmd)
|
||||
if len(data) != 1:
|
||||
LOG.warning("%s: no counters available, skipping.", ifname)
|
||||
return
|
||||
except subprocess.CalledProcessError:
|
||||
# Allow comand to fail, not all NICs support --json yet
|
||||
return
|
||||
|
||||
iface_in = data[0]
|
||||
|
||||
# TODO: room for improvement, the "frame" creation could be more dynamic.
|
||||
if "eth-mac" in iface_in or "rmon" in iface_in:
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "statistics", "frame", {})
|
||||
frame = iface_out['ieee802-ethernet-interface:ethernet']['statistics']['frame']
|
||||
|
||||
if "eth-mac" in iface_in:
|
||||
mac_in = iface_in['eth-mac']
|
||||
|
||||
if "FramesTransmittedOK" in mac_in:
|
||||
frame['out-frames'] = str(mac_in['FramesTransmittedOK'])
|
||||
if "MulticastFramesXmittedOK" in mac_in:
|
||||
frame['out-multicast-frames'] = str(mac_in['MulticastFramesXmittedOK'])
|
||||
if "BroadcastFramesXmittedOK" in mac_in:
|
||||
frame['out-broadcast-frames'] = str(mac_in['BroadcastFramesXmittedOK'])
|
||||
if "FramesReceivedOK" in mac_in:
|
||||
frame['in-frames'] = str(mac_in['FramesReceivedOK'])
|
||||
if "MulticastFramesReceivedOK" in mac_in:
|
||||
frame['in-multicast-frames'] = str(mac_in['MulticastFramesReceivedOK'])
|
||||
if "BroadcastFramesReceivedOK" in mac_in:
|
||||
frame['in-broadcast-frames'] = str(mac_in['BroadcastFramesReceivedOK'])
|
||||
if "FrameCheckSequenceErrors" in mac_in:
|
||||
frame['in-error-fcs-frames'] = str(mac_in['FrameCheckSequenceErrors'])
|
||||
if "FramesLostDueToIntMACRcvError" in mac_in:
|
||||
frame['in-error-mac-internal-frames'] = str(mac_in['FramesLostDueToIntMACRcvError'])
|
||||
|
||||
if "OctetsTransmittedOK" in mac_in:
|
||||
frame['infix-ethernet-interface:out-good-octets'] = str(mac_in['OctetsTransmittedOK'])
|
||||
if "OctetsReceivedOK" in mac_in:
|
||||
frame['infix-ethernet-interface:in-good-octets'] = str(mac_in['OctetsReceivedOK'])
|
||||
|
||||
tot = 0
|
||||
found = False
|
||||
if "FramesReceivedOK" in mac_in:
|
||||
tot += mac_in['FramesReceivedOK']
|
||||
found = True
|
||||
if "FrameCheckSequenceErrors" in mac_in:
|
||||
tot += mac_in['FrameCheckSequenceErrors']
|
||||
found = True
|
||||
if "FramesLostDueToIntMACRcvError" in mac_in:
|
||||
tot += mac_in['FramesLostDueToIntMACRcvError']
|
||||
found = True
|
||||
if "AlignmentErrors" in mac_in:
|
||||
tot += mac_in['AlignmentErrors']
|
||||
found = True
|
||||
if "etherStatsOversizePkts" in mac_in:
|
||||
tot += mac_in['etherStatsOversizePkts']
|
||||
found = True
|
||||
if "etherStatsJabbers" in mac_in:
|
||||
tot += mac_in['etherStatsJabbers']
|
||||
found = True
|
||||
if found:
|
||||
frame['in-total-frames'] = str(tot)
|
||||
|
||||
if "rmon" in iface_in:
|
||||
rmon_in = iface_in['rmon']
|
||||
|
||||
if "undersize_pkts" in rmon_in:
|
||||
frame['in-error-undersize-frames'] = str(rmon_in['undersize_pkts'])
|
||||
|
||||
tot = 0
|
||||
found = False
|
||||
if "etherStatsJabbers" in rmon_in:
|
||||
tot += rmon_in['etherStatsJabbers']
|
||||
found = True
|
||||
if "etherStatsOversizePkts" in rmon_in:
|
||||
tot += rmon_in['etherStatsOversizePkts']
|
||||
found = True
|
||||
if found:
|
||||
frame['in-error-oversize-frames'] = str(tot)
|
||||
|
||||
def add_ethtool_std(ifname, iface_out):
|
||||
"""Fetch interface speed/duplex/autoneg from kernel"""
|
||||
keys = ['Speed', 'Duplex', 'Auto-negotiation']
|
||||
result = {}
|
||||
|
||||
lines = HOST.run_multiline(['ethtool', ifname])
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
key = line.split(':', 1)[0].strip()
|
||||
if key in keys:
|
||||
key, value = line.split(':', 1)
|
||||
result[key.strip()] = value.strip()
|
||||
|
||||
if "Auto-negotiation" in result:
|
||||
if result['Auto-negotiation'] == "on":
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "auto-negotiation", "enable", True)
|
||||
else:
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "auto-negotiation", "enable", False)
|
||||
|
||||
if "Duplex" in result:
|
||||
if result['Duplex'] == "Half":
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "duplex", "half")
|
||||
elif result['Duplex'] == "Full":
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "duplex", "full")
|
||||
else:
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "duplex", "unknown")
|
||||
|
||||
if "Speed" in result and result['Speed'] != "Unknown!":
|
||||
# Avoid importing re (performance)
|
||||
num = ''.join(filter(str.isdigit, result['Speed']))
|
||||
if num:
|
||||
num = round((int(num) / 1000), 3)
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "speed", str(num))
|
||||
|
||||
def get_querier_data(querier_data):
|
||||
multicast={}
|
||||
|
||||
if not querier_data:
|
||||
multicast["snooping"] = False
|
||||
return multicast
|
||||
multicast["snooping"] = True
|
||||
if(querier_data.get("query-interval")):
|
||||
multicast["query-interval"] = querier_data["query-interval"]
|
||||
|
||||
return multicast
|
||||
|
||||
def get_multicast_filters(filters):
|
||||
multicast_filters=[]
|
||||
for f in filters:
|
||||
multicast_filter={}
|
||||
multicast_filter["group"] = f["group"]
|
||||
multicast_filter["ports"] = []
|
||||
for p in f["ports"]:
|
||||
port={}
|
||||
port["port"] = p
|
||||
multicast_filter["ports"].append(port)
|
||||
multicast_filters.append(multicast_filter)
|
||||
return multicast_filters
|
||||
|
||||
def add_mdb_to_bridge(brname, iface_out, mc_status):
|
||||
filters = [entry for entry in mc_status.get("multicast-groups", []) if entry.get('vid') == None and entry.get('bridge') == brname]
|
||||
querier = next((querier for querier in mc_status.get('multicast-queriers', []) if (querier.get("interface", "") == brname) and (querier.get("vid") is None)), None)
|
||||
multicast = get_querier_data(querier)
|
||||
multicast_filters = get_multicast_filters(filters)
|
||||
insert(iface_out, "infix-interfaces:bridge", "multicast", multicast)
|
||||
insert(iface_out, "infix-interfaces:bridge", "multicast-filters", "multicast-filter", multicast_filters)
|
||||
|
||||
def add_container_ifaces(yang_ifaces):
|
||||
"""Add all podman interfaces with limited data"""
|
||||
interfaces={}
|
||||
try:
|
||||
containers = HOST.run_json(['podman', 'ps', '-a', '--format=json'], default=[])
|
||||
except Exception as e:
|
||||
logging.error(f"Error, unable to run podman: {e}")
|
||||
return
|
||||
|
||||
for container in containers:
|
||||
name = container.get('Names', ['Unknown'])[0]
|
||||
networks = container.get('Networks', [])
|
||||
|
||||
for network in networks:
|
||||
if not network in interfaces:
|
||||
interfaces[network] = []
|
||||
if name not in interfaces[network]:
|
||||
interfaces[network].append(name)
|
||||
|
||||
for ifname, containers in interfaces.items():
|
||||
iface_out = {}
|
||||
iface_out['name'] = ifname
|
||||
iface_out['type'] = "infix-if-type:other" # Fallback
|
||||
insert(iface_out, "infix-interfaces:container-network", "containers", containers)
|
||||
|
||||
netns = netns_find_ifname(ifname)
|
||||
if netns is not None:
|
||||
ip_link_data = netns_get_ip_link(netns)
|
||||
ip_link_data = next((d for d in ip_link_data if d.get('ifalias') == ifname), None)
|
||||
add_ip_link(ifname, ip_link_data, iface_out)
|
||||
|
||||
yang_ifaces.append(iface_out)
|
||||
|
||||
# Helper function to add tagged/untagged interfaces to a vlan dict in a list
|
||||
def _add_vlan_iface(vlans, multicast_filter, multicast, vid, key, val):
|
||||
for d in vlans:
|
||||
if d['vid'] == vid:
|
||||
if key in d:
|
||||
d[key].append(val)
|
||||
else:
|
||||
d[key] = [val]
|
||||
return
|
||||
|
||||
vlans.append({'vid': vid, "multicast-filters": {"multicast-filter": multicast_filter}, "multicast": multicast, key: [val]})
|
||||
|
||||
def add_vlans_to_bridge(brname, iface_out, mc_status):
|
||||
slaves = [] # Contains all interfaces that has this bridge as 'master'
|
||||
for iface in HOST.run_json(['bridge', '-j', 'link']):
|
||||
if "master" in iface and iface['master'] == brname:
|
||||
slaves.append(iface['ifname'])
|
||||
|
||||
vlans = [] # Contains all vlans and slaves belonging to this bridge
|
||||
for iface in HOST.run_json(['bridge', '-j', 'vlan']):
|
||||
if iface['ifname'] not in slaves and iface['ifname'] != brname:
|
||||
continue
|
||||
for vlan in iface['vlans']:
|
||||
querier = next((querier for querier in mc_status.get('multicast-queriers', []) if (querier.get("vid") == vlan['vlan'])), None)
|
||||
filters = [entry for entry in mc_status.get("multicast-groups", []) if (entry.get("vid") == vlan["vlan"])]
|
||||
if 'flags' in vlan and 'Egress Untagged' in vlan['flags']:
|
||||
_add_vlan_iface(vlans, get_multicast_filters(filters), get_querier_data(querier), vlan['vlan'], 'untagged', iface['ifname'])
|
||||
else:
|
||||
_add_vlan_iface(vlans, get_multicast_filters(filters), get_querier_data(querier), vlan['vlan'], 'tagged', iface['ifname'])
|
||||
|
||||
insert(iface_out, "infix-interfaces:bridge", "vlans", "vlan", vlans)
|
||||
|
||||
def get_iface_data(ifname, ip_link_data, ip_addr_data):
|
||||
iface_out = {}
|
||||
|
||||
add_ip_link(ifname, ip_link_data, iface_out)
|
||||
add_ip_addr(ifname, ip_addr_data, iface_out)
|
||||
|
||||
if 'type' in iface_out and iface_out['type'] == "infix-if-type:ethernet":
|
||||
add_ethtool_groups(ifname, iface_out)
|
||||
add_ethtool_std(ifname, iface_out)
|
||||
|
||||
if 'type' in iface_out and iface_out['type'] == "infix-if-type:bridge":
|
||||
# Fail silent, multicast snooping may not be enabled on bridge
|
||||
mc_status = HOST.run_json(['mctl', '-p', 'show', 'igmp', 'json'], default={})
|
||||
|
||||
add_vlans_to_bridge(ifname, iface_out, mc_status)
|
||||
add_mdb_to_bridge(ifname, iface_out, mc_status)
|
||||
|
||||
return iface_out
|
||||
|
||||
def _add_interface(ifname, ip_link_data, ip_addr_data, yang_ifaces):
|
||||
# We expect both ip addr and link data to exist.
|
||||
if not ip_link_data or not ip_addr_data:
|
||||
return
|
||||
|
||||
# Skip internal interfaces.
|
||||
if 'group' in ip_link_data and ip_link_data['group'] == "internal":
|
||||
return
|
||||
|
||||
yang_ifaces.append(get_iface_data(ifname, ip_link_data, ip_addr_data))
|
||||
|
||||
def operational(ifname=None):
|
||||
out = {
|
||||
"ietf-interfaces:interfaces": {
|
||||
"interface": []
|
||||
}
|
||||
}
|
||||
out_ifaces = out["ietf-interfaces:interfaces"]["interface"]
|
||||
|
||||
ip_link_data = get_ip_link()
|
||||
ip_addr_data = get_ip_addr()
|
||||
|
||||
if ifname:
|
||||
ip_link_data = next((d for d in ip_link_data if d.get('ifname') == ifname), None)
|
||||
ip_addr_data = next((d for d in ip_addr_data if d.get('ifname') == ifname), None)
|
||||
_add_interface(ifname, ip_link_data, ip_addr_data, out_ifaces)
|
||||
else:
|
||||
for link in ip_link_data:
|
||||
addr = next((d for d in ip_addr_data if d.get('ifname') == link["ifname"]), None)
|
||||
_add_interface(link["ifname"], link, addr, out_ifaces)
|
||||
|
||||
add_container_ifaces(out_ifaces)
|
||||
|
||||
return out
|
||||
@@ -0,0 +1,156 @@
|
||||
from .common import insert
|
||||
from .host import HOST
|
||||
|
||||
|
||||
def frr_to_ietf_neighbor_state(state):
|
||||
"""Fetch OSPF neighbor state from Frr"""
|
||||
state = state.split("/")[0]
|
||||
if state == "TwoWay":
|
||||
return "2-way"
|
||||
return state.lower()
|
||||
|
||||
|
||||
def add_routes(ospf):
|
||||
"""Fetch OSPF routes from Frr"""
|
||||
cmd = ['vtysh', '-c', "show ip ospf route json"]
|
||||
data = HOST.run_json(cmd, default=[])
|
||||
if data == []:
|
||||
return # No OSPF routes available
|
||||
|
||||
routes = []
|
||||
for prefix, info in data.items():
|
||||
if prefix.find("/") == -1: # Ignore router IDs
|
||||
continue
|
||||
|
||||
route = {}
|
||||
route["prefix"] = prefix
|
||||
|
||||
nexthops = []
|
||||
routetype = info["routeType"].split(" ")
|
||||
|
||||
if len(routetype) > 1:
|
||||
if routetype[1] == "E1":
|
||||
route["route-type"] = "external-1"
|
||||
elif routetype[1] == "E2":
|
||||
route["route-type"] = "external-2"
|
||||
elif routetype[1] == "IA":
|
||||
route["route-type"] = "inter-area"
|
||||
elif routetype[0] == "N":
|
||||
route["route-type"] = "intra-area"
|
||||
|
||||
for hop in info["nexthops"]:
|
||||
nexthop = {}
|
||||
if hop["ip"] != " ":
|
||||
nexthop["next-hop"] = hop["ip"]
|
||||
else:
|
||||
nexthop["outgoing-interface"] = hop["directlyAttachedTo"]
|
||||
nexthops.append(nexthop)
|
||||
|
||||
route["next-hops"] = {}
|
||||
route["next-hops"]["next-hop"] = nexthops
|
||||
routes.append(route)
|
||||
|
||||
insert(ospf, "ietf-ospf:local-rib", "ietf-ospf:route", routes)
|
||||
|
||||
|
||||
def add_areas(control_protocols):
|
||||
"""Populate OSPF status"""
|
||||
cmd = ['/usr/libexec/statd/ospf-status']
|
||||
data = HOST.run_json(cmd, default={})
|
||||
if data == {}:
|
||||
return # No OSPF data available
|
||||
|
||||
control_protocol = {}
|
||||
control_protocol["type"] = "infix-routing:ospfv2"
|
||||
control_protocol["name"] = "default"
|
||||
control_protocol["ietf-ospf:ospf"] = {}
|
||||
control_protocol["ietf-ospf:ospf"]["ietf-ospf:areas"] = {}
|
||||
|
||||
|
||||
control_protocol["ietf-ospf:ospf"]["ietf-ospf:router-id"] = data.get("routerId")
|
||||
control_protocol["ietf-ospf:ospf"]["ietf-ospf:address-family"] = "ipv4"
|
||||
areas = []
|
||||
|
||||
for area_id, values in data.get("areas", {}).items():
|
||||
area = {}
|
||||
area["ietf-ospf:area-id"] = area_id
|
||||
area["ietf-ospf:interfaces"] = {}
|
||||
if values.get("area-type"):
|
||||
area["ietf-ospf:area-type"] = values["area-type"]
|
||||
interfaces = []
|
||||
for iface in values.get("interfaces", {}):
|
||||
interface = {}
|
||||
interface["ietf-ospf:neighbors"] = {}
|
||||
interface["name"] = iface["name"]
|
||||
|
||||
if iface.get("drId"):
|
||||
interface["dr-router-id"] = iface["drId"]
|
||||
if iface.get("drAddress"):
|
||||
interface["dr-ip-addr"] = iface["drAddress"]
|
||||
if iface.get("bdrId"):
|
||||
interface["bdr-router-id"] = iface["bdrId"]
|
||||
if iface.get("bdrAddress"):
|
||||
interface["bdr-ip-addr"] = iface["bdrAddress"]
|
||||
|
||||
if iface.get("timerPassiveIface"):
|
||||
interface["passive"] = True
|
||||
else:
|
||||
interface["passive"] = False
|
||||
|
||||
interface["enabled"] = iface["ospfEnabled"]
|
||||
if iface["networkType"] == "POINTOPOINT":
|
||||
interface["interface-type"] = "point-to-point"
|
||||
if iface["networkType"] == "BROADCAST":
|
||||
interface["interface-type"] = "broadcast"
|
||||
|
||||
if iface.get("state"):
|
||||
# Wev've never seen "DependUpon", and has no entry in
|
||||
# the YANG model, but is listed before down in Frr
|
||||
xlate = {
|
||||
"DependUpon": "down",
|
||||
"Down": "down",
|
||||
"Waiting": "waiting",
|
||||
"Loopback": "loopback",
|
||||
"Point-To-Point": "point-to-point",
|
||||
"DROther": "dr-other",
|
||||
"Backup": "bdr",
|
||||
"DR": "dr"
|
||||
}
|
||||
val = xlate.get(iface["state"], "unknown")
|
||||
interface["state"] = val
|
||||
|
||||
neighbors = []
|
||||
for neigh in iface["neighbors"]:
|
||||
neighbor = {}
|
||||
neighbor["neighbor-router-id"] = neigh["neighborIp"]
|
||||
neighbor["address"] = neigh["ifaceAddress"]
|
||||
neighbor["dead-timer"] = neigh["routerDeadIntervalTimerDueMsec"]
|
||||
neighbor["state"] = frr_to_ietf_neighbor_state(neigh["nbrState"])
|
||||
if neigh.get("routerDesignatedId"):
|
||||
neighbor["dr-router-id"] = neigh["routerDesignatedId"]
|
||||
if neigh.get("routerDesignatedBackupId"):
|
||||
neighbor["bdr-router-id"] = neigh["routerDesignatedBackupId"]
|
||||
neighbors.append(neighbor)
|
||||
|
||||
interface["ietf-ospf:neighbors"] = {}
|
||||
interface["ietf-ospf:neighbors"]["ietf-ospf:neighbor"] = neighbors
|
||||
interfaces.append(interface)
|
||||
|
||||
area["ietf-ospf:interfaces"]["ietf-ospf:interface"] = interfaces
|
||||
areas.append(area)
|
||||
|
||||
add_routes(control_protocol["ietf-ospf:ospf"])
|
||||
control_protocol["ietf-ospf:ospf"]["ietf-ospf:areas"]["ietf-ospf:area"] = areas
|
||||
insert(control_protocols, "control-plane-protocol", [control_protocol])
|
||||
|
||||
|
||||
def operational():
|
||||
out = {
|
||||
"ietf-routing:routing": {
|
||||
"control-plane-protocols": {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_areas(out['ietf-routing:routing']['control-plane-protocols'])
|
||||
return out
|
||||
@@ -0,0 +1,149 @@
|
||||
from datetime import timedelta
|
||||
from re import match
|
||||
|
||||
from .common import insert
|
||||
from .host import HOST
|
||||
|
||||
|
||||
def uptime2datetime(uptime):
|
||||
"""
|
||||
Convert uptime to YANG format (YYYY-MM-DDTHH:MM:SS+00:00)
|
||||
|
||||
Handles the following input formats (frrtime):
|
||||
HH:MM:SS
|
||||
XdXXhXXm
|
||||
XXwXdXXh
|
||||
"""
|
||||
h = m = s = 0
|
||||
|
||||
# Format HH:MM:SS
|
||||
if match(r'^\d{2}:\d{2}:\d{2}$', uptime):
|
||||
h, m, s = map(int, uptime.split(':'))
|
||||
|
||||
# Format XdXXhXXm (days, hours, minutes)
|
||||
elif match(r'^\d+d\d{2}h\d{2}m$', uptime):
|
||||
days = int(uptime.split('d')[0])
|
||||
h = int(uptime.split('d')[1].split('h')[0])
|
||||
m = int(uptime.split('h')[1].split('m')[0])
|
||||
h += days * 24
|
||||
|
||||
# Format XwXdXXh (weeks, days, hours)
|
||||
elif match(r'^\d{2}w\d{1}d\d{2}h$', uptime):
|
||||
weeks = int(uptime.split('w')[0])
|
||||
days = int(uptime.split('w')[1].split('d')[0])
|
||||
h = int(uptime.split('d')[1].split('h')[0])
|
||||
h += weeks * 7 * 24
|
||||
h += days * 24
|
||||
|
||||
uptime_delta = timedelta(hours=h, minutes=m, seconds=s)
|
||||
current_time = HOST.now()
|
||||
last_updated = current_time - uptime_delta
|
||||
date_timestd = last_updated.strftime('%Y-%m-%dT%H:%M:%S%z')
|
||||
|
||||
return date_timestd[:-2] + ':' + date_timestd[-2:]
|
||||
|
||||
|
||||
def add_protocol(routes, proto):
|
||||
"""Populate routes from vtysh JSON output"""
|
||||
|
||||
frrproto = "ip" if proto == "ipv4" else proto
|
||||
data = HOST.run_json(['vtysh', '-c', f"show {frrproto} route json"], {})
|
||||
|
||||
# Mapping of FRR protocol names to IETF routing-protocol
|
||||
pmap = {
|
||||
'kernel': 'infix-routing:kernel',
|
||||
'connected': 'direct',
|
||||
'static': 'static',
|
||||
'ospf': 'ietf-ospf:ospfv2',
|
||||
'ospf6': 'ietf-ospf:ospfv3',
|
||||
}
|
||||
|
||||
out = {}
|
||||
out["route"] = []
|
||||
|
||||
if proto == "ipv4":
|
||||
default = "0.0.0.0/0"
|
||||
host_prefix_length = "32"
|
||||
else:
|
||||
default = "::/0"
|
||||
host_prefix_length = "128"
|
||||
|
||||
for prefix, entries in data.items():
|
||||
for route in entries:
|
||||
new = {}
|
||||
dst = route.get('prefix', default)
|
||||
if '/' not in dst:
|
||||
dst = f"{dst}/{route.get('prefixLen', host_prefix_length)}"
|
||||
|
||||
new[f'ietf-{proto}-unicast-routing:destination-prefix'] = dst
|
||||
frr = route.get('protocol', 'infix-routing:kernel')
|
||||
new['source-protocol'] = pmap.get(frr, 'infix-routing:kernel')
|
||||
new['route-preference'] = route.get('distance', 0)
|
||||
|
||||
# Metric only available in the model for OSPF routes
|
||||
if 'ospf' in frr:
|
||||
new['ietf-ospf:metric'] = route.get('metric', 0)
|
||||
|
||||
# See https://datatracker.ietf.org/doc/html/rfc7951#section-6.9
|
||||
# for details on how presence leaves are encoded in JSON: [null]
|
||||
if route.get('selected', False):
|
||||
new['active'] = [None]
|
||||
|
||||
new['last-updated'] = uptime2datetime(route.get('uptime', 0))
|
||||
installed = route.get('installed', False)
|
||||
|
||||
next_hops = []
|
||||
for hop in route.get('nexthops', []):
|
||||
next_hop = {}
|
||||
if hop.get('ip'):
|
||||
next_hop[f'ietf-{proto}-unicast-routing:address'] = hop['ip']
|
||||
elif hop.get('interfaceName'):
|
||||
next_hop['outgoing-interface'] = hop['interfaceName']
|
||||
# See zebra/zebra_vty.c:re_status_outpupt_char()
|
||||
if installed and hop.get('fib', False):
|
||||
next_hop['infix-routing:installed'] = [None]
|
||||
next_hops.append(next_hop)
|
||||
|
||||
if next_hops:
|
||||
new['next-hop'] = {'next-hop-list': {'next-hop': next_hops}}
|
||||
else:
|
||||
next_hop = {}
|
||||
protocol = route.get('protocol', 'unicast')
|
||||
if protocol == "blackhole":
|
||||
next_hop['special-next-hop'] = "blackhole"
|
||||
elif protocol == "unreachable":
|
||||
next_hop['special-next-hop'] = "unreachable"
|
||||
else:
|
||||
if route.get('interfaceName'):
|
||||
next_hop['outgoing-interface'] = route['interfaceName']
|
||||
if route.get('nexthop'):
|
||||
next_hop[f'ietf-{proto}-unicast-routing:next-hop-address'] = route['nexthop']
|
||||
|
||||
new['next-hop'] = next_hop
|
||||
|
||||
out['route'].append(new)
|
||||
|
||||
insert(routes, 'routes', out)
|
||||
|
||||
|
||||
def operational():
|
||||
out = {
|
||||
"ietf-routing:routing": {
|
||||
"ribs": {
|
||||
"rib": [{
|
||||
"name": "ipv4",
|
||||
"address-family": "ipv4"
|
||||
}, {
|
||||
"name": "ipv6",
|
||||
"address-family": "ipv6"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipv4routes = out['ietf-routing:routing']['ribs']['rib'][0]
|
||||
ipv6routes = out['ietf-routing:routing']['ribs']['rib'][1]
|
||||
add_protocol(ipv4routes, "ipv4")
|
||||
add_protocol(ipv6routes, "ipv6")
|
||||
|
||||
return out
|
||||
@@ -0,0 +1,139 @@
|
||||
import subprocess
|
||||
|
||||
from .common import insert
|
||||
from .host import HOST
|
||||
|
||||
def uboot_get_boot_order():
|
||||
data = HOST.run_multiline("fw_printenv BOOT_ORDER".split(), [])
|
||||
for line in data:
|
||||
if "BOOT_ORDER" in line:
|
||||
return line.strip().split("=")[1].split()
|
||||
|
||||
raise Exception
|
||||
|
||||
def grub_get_boot_order():
|
||||
data = HOST.run_multiline("grub-editenv /mnt/aux/grub/grubenv list".split(), [])
|
||||
for line in data:
|
||||
if "ORDER" in line:
|
||||
return line.split("=")[1].strip().split()
|
||||
|
||||
raise Exception
|
||||
|
||||
def get_boot_order():
|
||||
order = None
|
||||
try:
|
||||
order = uboot_get_boot_order()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
if order is None:
|
||||
order = grub_get_boot_order()
|
||||
except:
|
||||
pass
|
||||
|
||||
return order
|
||||
|
||||
def add_ntp(out):
|
||||
data = HOST.run_multiline(["chronyc", "-c", "sources"], [])
|
||||
source = []
|
||||
state_mode_map = {
|
||||
"^": "server",
|
||||
"=": "peer",
|
||||
"#": "local-clock"
|
||||
}
|
||||
source_state_map = {
|
||||
"*": "selected",
|
||||
"+": "candidate",
|
||||
"-": "outlier",
|
||||
"?": "unusable",
|
||||
"x": "falseticker",
|
||||
"~": "unstable"
|
||||
}
|
||||
for line in data:
|
||||
src = {}
|
||||
line = line.split(',')
|
||||
src["address"] = line[2]
|
||||
src["mode"] = state_mode_map[line[0]]
|
||||
src["state"] = source_state_map[line[1]]
|
||||
src["stratum"] = int(line[3])
|
||||
src["poll"] = int(line[4])
|
||||
source.append(src)
|
||||
|
||||
insert(out, "infix-system:ntp", "sources", "source", source)
|
||||
|
||||
def add_software_slots(out, data):
|
||||
slots = []
|
||||
for slot in data["slots"]:
|
||||
for key, value in slot.items():
|
||||
new = {}
|
||||
new["name"] = key
|
||||
new["bootname"] = slot[key].get("bootname")
|
||||
new["class"] = slot[key].get("class")
|
||||
new["state"] = slot[key].get("state")
|
||||
new["bundle"] = {}
|
||||
slot_status=value.get("slot_status", {})
|
||||
if slot_status.get("bundle", {}).get("compatible"):
|
||||
new["bundle"]["compatible"] = slot_status.get("bundle", {}).get("compatible")
|
||||
if slot_status.get("bundle", {}).get("version"):
|
||||
new["bundle"]["version"] = slot_status.get("bundle", {}).get("version")
|
||||
if slot_status.get("checksum", {}).get("size"):
|
||||
new["size"] = str(slot_status.get("checksum", {}).get("size"))
|
||||
if slot_status.get("checksum", {}).get("sha256"):
|
||||
new["sha256"] = slot_status.get("checksum", {}).get("sha256")
|
||||
|
||||
new["installed"] = {}
|
||||
if slot_status.get("installed", {}).get("timestamp"):
|
||||
new["installed"]["datetime"] = slot_status.get("installed", {}).get("timestamp")
|
||||
|
||||
if slot_status.get("installed", {}).get("count"):
|
||||
new["installed"]["count"] = slot_status.get("installed", {}).get("count")
|
||||
|
||||
new["activated"] = {}
|
||||
if slot_status.get("activated", {}).get("timestamp"):
|
||||
new["activated"]["datetime"] = slot_status.get("activated", {}).get("timestamp")
|
||||
|
||||
if slot_status.get("activated", {}).get("count"):
|
||||
new["activated"]["count"] = slot_status.get("activated", {}).get("count")
|
||||
slots.append(new)
|
||||
out["slot"] = slots
|
||||
|
||||
def add_software(out):
|
||||
software = {}
|
||||
try:
|
||||
data = HOST.run_json(["rauc", "status", "--detailed", "--output-format=json"])
|
||||
software["compatible"] = data["compatible"]
|
||||
software["variant"] = data["variant"]
|
||||
software["booted"] = data["booted"]
|
||||
boot_order = get_boot_order()
|
||||
if not boot_order is None:
|
||||
software["boot-order"] = boot_order
|
||||
add_software_slots(software, data)
|
||||
except subprocess.CalledProcessError:
|
||||
pass # Maybe an upgrade i progress, then rauc does not respond
|
||||
|
||||
installer_status = HOST.run_json(["rauc-installation-status"])
|
||||
installer = {}
|
||||
if installer_status.get("operation"):
|
||||
installer["operation"] = installer_status["operation"]
|
||||
if "progress" in installer_status:
|
||||
progress = {}
|
||||
|
||||
if installer_status["progress"].get("percentage"):
|
||||
progress["percentage"] = int(installer_status["progress"]["percentage"])
|
||||
if installer_status["progress"].get("message"):
|
||||
progress["message"] = installer_status["progress"]["message"]
|
||||
installer["progress"] = progress
|
||||
software["installer"] = installer
|
||||
|
||||
insert(out, "infix-system:software", software)
|
||||
|
||||
def operational():
|
||||
out = {
|
||||
"ietf-system:system-state": {
|
||||
}
|
||||
}
|
||||
out_state = out["ietf-system:system-state"]
|
||||
|
||||
add_software(out_state)
|
||||
add_ntp(out_state)
|
||||
return out
|
||||
@@ -0,0 +1,76 @@
|
||||
from .common import LOG
|
||||
from .host import HOST
|
||||
|
||||
def podman_inspect(name):
|
||||
"""Call podman inspect {name}, return object at {path} or None."""
|
||||
cmd = ['podman', 'inspect', name]
|
||||
try:
|
||||
return HOST.run_json(cmd, default=[])
|
||||
except Exception as e:
|
||||
LOG(f"Error running podman inspect: {e}")
|
||||
return []
|
||||
|
||||
def podman_ps():
|
||||
"""We list *all* containers, not just those in the configuraion."""
|
||||
return HOST.run_json("podman ps -a --format=json".split(), default=[])
|
||||
|
||||
|
||||
def network(ps, inspect):
|
||||
net = {}
|
||||
|
||||
# The 'podman ps' command lists ports even in host mode, but
|
||||
# that's not applicable, so skip networks and port forwardings
|
||||
networks = inspect.get("NetworkSettings", {}).get("Networks")
|
||||
if networks and "host" in networks:
|
||||
net = {"host": True}
|
||||
else:
|
||||
net = {
|
||||
"interface": [{"name": net} for net in ps["Networks"]],
|
||||
"publish": []
|
||||
}
|
||||
|
||||
if (ps["State"] == "running") and ps["Ports"]:
|
||||
for port in ps["Ports"]:
|
||||
addr = ""
|
||||
if port["host_ip"]:
|
||||
addr = f"{port['host_ip']}:"
|
||||
|
||||
pub = f"{addr}{port['host_port']}->{port['container_port']}/{port['protocol']}"
|
||||
net["publish"].append(pub)
|
||||
|
||||
return net
|
||||
|
||||
|
||||
def container(ps):
|
||||
out = {
|
||||
"name": ps["Names"][0],
|
||||
"id": ps["Id"],
|
||||
"image": ps["Image"],
|
||||
"image-id": ps["ImageID"],
|
||||
"running": ps["State"] == "running",
|
||||
"status": ps["Status"]
|
||||
}
|
||||
|
||||
# Bonus information, may not be available
|
||||
if ps["Command"]:
|
||||
out["command"] = " ".join(ps["Command"])
|
||||
|
||||
inspect = podman_inspect(out["name"])
|
||||
if inspect and isinstance(inspect, list) and len(inspect) > 0:
|
||||
inspect = inspect[0]
|
||||
else:
|
||||
inspect = {}
|
||||
|
||||
net = network(ps, inspect)
|
||||
if net:
|
||||
out["network"] = net
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def operational():
|
||||
return {
|
||||
"infix-containers:containers": {
|
||||
"container": [container(ps) for ps in podman_ps()]
|
||||
}
|
||||
}
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec env PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=$(readlink -f $(dirname "$0")/../) \
|
||||
python3 -m yanger "$@"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
SCRIPT_PATH="$(dirname "$(readlink -f "$0")")"
|
||||
ROOT_PATH="$SCRIPT_PATH/../../../"
|
||||
|
||||
YANGER_TOOL="$ROOT_PATH/src/statd/python/yanger/yanger.py"
|
||||
YANGER_TOOL="$ROOT_PATH/src/statd/python/yanger/yanger"
|
||||
|
||||
INTERFACES_OUTPUT_FILE="$(mktemp)"
|
||||
ROUTES_OUTPUT_FILE="$(mktemp)"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"vendor": "QEMU", "product-name": "VM", "part-number": null, "serial-number": null, "mac-address": "02:00:00:00:00:00", "factory-password-hash": "$5$mI/zpOAqZYKLC2WU$i7iPzZiIjOjrBF3NyftS9CCq8dfYwHwrmUK097Jca9A", "vpd": {"product": {"board": "product", "available": false, "trusted": true, "data": {}}}, "usb-ports": [{"name": "USB", "path": "/sys/bus/usb/devices/usb1/authorized"}, {"name": "USB", "path": "/sys/bus/usb/devices/usb1/authorized_default"}, {"name": "USB2", "path": "/sys/bus/usb/devices/usb2/authorized"}, {"name": "USB2", "path": "/sys/bus/usb/devices/usb2/authorized_default"}]}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"10.0.0.1/32":{"routeType":"N","transit":false,"cost":0,"area":"0.0.0.1","nexthops":[{"ip":" ","directlyAttachedTo":"lo"}]},"10.0.0.2/32":{"routeType":"N","transit":false,"cost":1,"area":"0.0.0.0","nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]},"10.0.0.3/32":{"routeType":"N","transit":false,"cost":2000,"area":"0.0.0.1","nexthops":[{"ip":"10.0.13.2","via":"e6","advertisedRouter":"10.0.0.3"}]},"10.0.0.4/32":{"routeType":"N IA","cost":2001,"area":"0.0.0.0","nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]},"10.0.12.0/30":{"routeType":"N","transit":true,"cost":1,"area":"0.0.0.0","nexthops":[{"ip":" ","directlyAttachedTo":"e5"}]},"10.0.13.0/30":{"routeType":"N","transit":true,"cost":2000,"area":"0.0.0.1","nexthops":[{"ip":" ","directlyAttachedTo":"e6"}]},"10.0.23.0/30":{"routeType":"N","transit":true,"cost":2001,"area":"0.0.0.1","nexthops":[{"ip":"10.0.13.2","via":"e6","advertisedRouter":"10.0.0.3"}]},"10.0.24.0/30":{"routeType":"N IA","cost":2001,"area":"0.0.0.0","nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]},"10.0.41.0/30":{"routeType":"N IA","cost":2002,"area":"0.0.0.0","nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]},"11.0.8.1/32":{"routeType":"N","transit":false,"cost":0,"area":"0.0.0.1","nexthops":[{"ip":" ","directlyAttachedTo":"lo"}]},"11.0.9.1/32":{"routeType":"N","transit":false,"cost":1,"area":"0.0.0.0","nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]},"11.0.10.1/32":{"routeType":"N","transit":false,"cost":1,"area":"0.0.0.0","nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]},"11.0.11.1/32":{"routeType":"N","transit":false,"cost":1,"area":"0.0.0.0","nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]},"11.0.12.1/32":{"routeType":"N","transit":false,"cost":1,"area":"0.0.0.0","nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]},"11.0.13.1/32":{"routeType":"N","transit":false,"cost":1,"area":"0.0.0.0","nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]},"11.0.14.1/32":{"routeType":"N","transit":false,"cost":1,"area":"0.0.0.0","nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]},"11.0.15.1/32":{"routeType":"N","transit":false,"cost":1,"area":"0.0.0.0","nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]},"192.168.3.0/24":{"routeType":"N","transit":false,"cost":2001,"area":"0.0.0.1","nexthops":[{"ip":"10.0.13.2","via":"e6","advertisedRouter":"10.0.0.3"}]},"1.1.1.1":{"routeType":"R ","cost":1,"area":"0.0.0.0","routerType":"abr","nexthops":[{"ip":"10.0.12.2","via":"e5"}]},"10.0.0.4":{"routeType":"R ","cost":2001,"area":"0.0.0.0","IA":true,"ia":true,"routerType":"asbr","nexthops":[{"ip":"10.0.12.2","via":"e5"}]},"192.168.4.0/24":{"routeType":"N E2","cost":2001,"type2cost":20,"tag":0,"nexthops":[{"ip":"10.0.12.2","via":"e5","advertisedRouter":"1.1.1.1"}]}}
|
||||
Executable
+114
@@ -0,0 +1,114 @@
|
||||
#!/bin/sh
|
||||
|
||||
ixdir=$(readlink -f $(dirname "$0")/../)
|
||||
. "$ixdir/utils/libix.sh"
|
||||
|
||||
resolve_host_dir
|
||||
|
||||
YANGLINT="$HOST_DIR/bin/yanglint"
|
||||
[ -x "$YANGLINT" ] || {
|
||||
echo "*** Error: yanglint is not installed" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
yangdir="$ixdir/src/confd/yang"
|
||||
|
||||
check()
|
||||
{
|
||||
find "$yangdir" -type f -name '*infix*.yang' | while read file; do
|
||||
head -n1 "$file" | grep -q "submodule" && continue
|
||||
|
||||
printf "%-40s " $(basename "$file")
|
||||
|
||||
"$YANGLINT" -p "$yangdir" -i "$file" || {
|
||||
printf "%-40s FAIL\n" $(basename "$file")
|
||||
continue
|
||||
}
|
||||
|
||||
echo ok
|
||||
done
|
||||
}
|
||||
|
||||
resolve_model()
|
||||
{
|
||||
local model="$1"
|
||||
|
||||
if [ -e "$model" ]; then
|
||||
file="$model"
|
||||
elif [ -e "$yangdir/$model" ]; then
|
||||
file="$yangdir/$model"
|
||||
elif [ -e "$yangdir/$model.yang" ]; then
|
||||
file="$yangdir/$model.yang"
|
||||
else
|
||||
file=$(find "$yangdir" -name "$model*" -print -quit)
|
||||
fi
|
||||
|
||||
if [ -z "$file" ]; then
|
||||
echo "*** Error: Found no model matching \"$model\"" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$file"
|
||||
}
|
||||
|
||||
lint()
|
||||
{
|
||||
local files=
|
||||
while [ $# -gt 0 ]; do
|
||||
if [ "$1" = "--" ]; then
|
||||
shift
|
||||
break
|
||||
fi
|
||||
|
||||
files="$files $(resolve_model $1)"
|
||||
shift
|
||||
done
|
||||
|
||||
"$YANGLINT" -p "$yangdir" -i "$@" $files
|
||||
}
|
||||
|
||||
yanger()
|
||||
{
|
||||
local model="$1"
|
||||
|
||||
"$ixdir/src/statd/python/yanger/yanger" \
|
||||
-t "$ixdir/test/case/cli/system-output" \
|
||||
"$model"
|
||||
}
|
||||
|
||||
yangerlint()
|
||||
{
|
||||
local model="$1"
|
||||
|
||||
yanger "$model" >/tmp/ixyang-yangerlint.json || {
|
||||
echo "*** Error: Failed to dump \"$model\"" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
case "$model" in
|
||||
ietf-interfaces)
|
||||
lint \
|
||||
ietf-interfaces \
|
||||
ieee802-ethernet-interface \
|
||||
infix-interfaces \
|
||||
infix-ethernet-interface \
|
||||
\
|
||||
/tmp/ixyang-yangerlint.json \
|
||||
-- -i -t data
|
||||
;;
|
||||
*)
|
||||
echo "*** Error: yangerlint not implemented for \"$model\"" >&2
|
||||
;;
|
||||
esac
|
||||
|
||||
rm /tmp/ixyang-yangerlint.json
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
check|lint|yanger|yangerlint)
|
||||
"$@"
|
||||
;;
|
||||
*)
|
||||
echo "*** Error: Unknown command \"$1\"" >&2
|
||||
return 1
|
||||
esac
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
resolve_o()
|
||||
{
|
||||
[ -n "$O" ] && return
|
||||
|
||||
if [ -f ".config" ] && [ -d "output" ]; then
|
||||
# Buildroot
|
||||
O=$(readlink -f ./output)
|
||||
elif [ -f "output/.config" ]; then
|
||||
# BR2_EXTERNAL
|
||||
O=$(readlink -f ./output)
|
||||
elif [ -f ".config" ] && [ -d "host" ]; then
|
||||
# Called from inside output/ directory
|
||||
O=$(readlink -f .)
|
||||
else
|
||||
echo "*** Error: cannot find Buildroot output dir!" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_host_dir()
|
||||
{
|
||||
[ -n "$HOST_DIR" ] && return
|
||||
|
||||
resolve_o
|
||||
|
||||
if ! [ -d "$O/host" ]; then
|
||||
echo "*** Error: cannot find Buildroot host binaries dir!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HOST_DIR="$O/host"
|
||||
}
|
||||
+3
-23
@@ -1,6 +1,8 @@
|
||||
#!/bin/sh
|
||||
# Interact with sysrepo in a Buildroot setup
|
||||
|
||||
. $(dirname $(readlink -f "$0"))/libix.sh
|
||||
|
||||
usage()
|
||||
{
|
||||
cat <<EOF
|
||||
@@ -30,29 +32,7 @@ cleanup()
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Figure out if we're called from output/ or top directory
|
||||
if [ -z "$O" ]; then
|
||||
if [ -f ".config" ] && [ -d "output" ]; then
|
||||
# Buildroot
|
||||
O=./output
|
||||
elif [ -f "output/.config" ]; then
|
||||
# BR2_EXTERNAL
|
||||
O=./output
|
||||
elif [ -f ".config" ] && [ -d "host" ]; then
|
||||
# Called from inside output/ directory
|
||||
O=.
|
||||
else
|
||||
echo "*** Error: cannot find Buildroot output dir!" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d "$O/host" ]; then
|
||||
HOST_DIR="$O/host"
|
||||
else
|
||||
echo "*** Error: cannot find Buildroot host binaries dir!" >&2
|
||||
exit 1
|
||||
fi
|
||||
resolve_host_dir
|
||||
|
||||
MOD="$O/target/usr/share/yang/modules/confd/"
|
||||
CTL="$HOST_DIR/bin/sysrepoctl"
|
||||
|
||||
Reference in New Issue
Block a user