Skip to main content

ios_config_core/
ir.rs

1//! Intermediate representation (IR) for Cisco IOS configurations.
2//!
3//! All types are [`serde`]-serializable, so you can dump a parsed config
4//! directly to JSON, TOML, or any other format:
5//!
6//! ```rust,ignore
7//! let config = ios_config::parse(&raw).unwrap();
8//! let json = serde_json::to_string_pretty(&config).unwrap();
9//! ```
10
11// Self-evident struct fields (e.g. `asn: u32`, `name: String`, `prefix: IpNet`)
12// are exempt from the missing_docs lint — their types and names are documentation enough.
13#![allow(missing_docs)]
14
15use ipnet::IpNet;
16use serde::{Deserialize, Serialize};
17use std::net::IpAddr;
18
19// ---------------------------------------------------------------------------
20// Confidence
21// ---------------------------------------------------------------------------
22
23/// Parser confidence level for a recognized element.
24///
25/// Every [`Interface`] carries a `Confidence` value so callers can
26/// distinguish between a cleanly parsed command and one that required
27/// a best-effort interpretation.
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub enum Confidence {
30    /// The command was fully recognized and losslessly represented.
31    Exact,
32    /// An equivalent exists but with semantic differences; see `note`.
33    Approximate { note: String },
34    /// No structured representation is possible; see `reason`.
35    Manual { reason: String },
36    /// The parser did not recognize the command; raw text is preserved.
37    Unknown { raw: String },
38}
39
40// ---------------------------------------------------------------------------
41// NetworkConfig
42// ---------------------------------------------------------------------------
43
44/// Root object returned by the `ios_config::parse` function.
45///
46/// Every top-level section of a `show running-config` output maps to a
47/// field here. Unrecognized commands are never silently dropped — they
48/// are collected in [`unknown_blocks`][NetworkConfig::unknown_blocks].
49#[derive(Debug, Clone, Default, Serialize, Deserialize)]
50pub struct NetworkConfig {
51    /// Value of the `hostname` command.
52    pub hostname: Option<String>,
53    /// Value of the `ip domain-name` command.
54    pub domain_name: Option<String>,
55    /// All `interface` blocks in config order.
56    pub interfaces: Vec<Interface>,
57    /// All `vlan <id>` database blocks.
58    pub vlans: Vec<Vlan>,
59    /// Routing table: static routes, OSPF, BGP, EIGRP.
60    pub routing: RoutingConfig,
61    /// Named and numbered access control lists.
62    pub acls: Vec<Acl>,
63    /// NAT rules (`ip nat inside source …`).
64    pub nat: Vec<NatRule>,
65    /// NTP servers (`ntp server …`).
66    pub ntp: Vec<NtpServer>,
67    /// DNS name servers (`ip name-server …`), in config order.
68    pub dns: Vec<IpAddr>,
69    /// SNMP configuration (`snmp-server …`).
70    pub snmp: Option<SnmpConfig>,
71    /// AAA configuration (`aaa new-model`, authentication/authorization lists).
72    pub aaa: Option<AaaConfig>,
73    /// Global spanning-tree settings (`spanning-tree mode …`).
74    pub stp: Option<GlobalStp>,
75    /// Logging configuration (`logging buffered …`, `logging host …`).
76    pub logging: Option<LoggingConfig>,
77    /// Local user accounts (`username … privilege … secret …`).
78    pub users: Vec<LocalUser>,
79    /// `line vty` configuration (exec-timeout, transport-input).
80    pub line_vty: Option<LineVty>,
81    /// `ip ssh` configuration.
82    pub ssh: Option<SshConfig>,
83    /// `banner motd` text (delimiters stripped).
84    pub banner: Option<String>,
85    /// Commands the parser did not recognize, grouped by their config block.
86    /// Nothing is silently dropped — use this to audit parse coverage.
87    pub unknown_blocks: Vec<UnknownBlock>,
88    /// Platform-specific commands that have no vendor-neutral equivalent.
89    pub platform_specific: Vec<UnknownBlock>,
90}
91
92// ---------------------------------------------------------------------------
93// Global STP
94// ---------------------------------------------------------------------------
95
96/// Global spanning-tree configuration (`spanning-tree …` at global level).
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct GlobalStp {
99    /// STP mode (`rapid-pvst`, `pvst`, `mst`, `rstp`).
100    pub mode: StpMode,
101    /// `spanning-tree loopguard default`
102    pub loopguard: bool,
103    /// `spanning-tree portfast default`
104    pub portfast_default: bool,
105    /// `spanning-tree portfast bpduguard default`
106    pub bpduguard_default: bool,
107    /// Per-VLAN priority overrides (`spanning-tree vlan <id> priority <n>`).
108    pub vlan_priorities: Vec<StpVlanPriority>,
109}
110
111/// STP operational mode.
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub enum StpMode {
114    RapidPvst,
115    Pvst,
116    Mst,
117    Rstp,
118}
119
120/// Per-VLAN STP priority override.
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct StpVlanPriority {
123    /// VLAN IDs this priority applies to.
124    pub vlans: Vec<u16>,
125    /// STP bridge priority (must be a multiple of 4096).
126    pub priority: u32,
127}
128
129// ---------------------------------------------------------------------------
130// Logging
131// ---------------------------------------------------------------------------
132
133/// Logging subsystem configuration.
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct LoggingConfig {
136    /// `logging buffered <size>` — internal buffer size in bytes.
137    pub buffered_size: Option<u32>,
138    /// `logging console <level>` — severity level keyword (e.g. `"warnings"`).
139    pub console_level: Option<String>,
140    /// Remote syslog hosts (`logging host <ip>`).
141    pub hosts: Vec<std::net::IpAddr>,
142}
143
144// ---------------------------------------------------------------------------
145// Local users
146// ---------------------------------------------------------------------------
147
148/// A local user account defined by `username … privilege … secret/password …`.
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct LocalUser {
151    pub name: String,
152    /// Privilege level (0–15).
153    pub privilege: u8,
154    /// Hash algorithm used to store the password.
155    pub password_type: PasswordType,
156    /// Password string exactly as it appears in the config
157    /// (plaintext, Type-7 encoded, or hash).
158    pub password_hash: String,
159}
160
161/// Password storage type as encoded in the config.
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163pub enum PasswordType {
164    /// `password 0 …` or bare `password …` — cleartext.
165    Plaintext,
166    /// `password 7 …` — Cisco Type-7 (XOR-based, reversible).
167    Type7,
168    /// `secret 5 …` — MD5 hash (`$1$…`).
169    Md5,
170    /// `secret 9 …` — scrypt hash (`$9$…`).
171    Scrypt,
172}
173
174// ---------------------------------------------------------------------------
175// Line VTY
176// ---------------------------------------------------------------------------
177
178/// `line vty 0 4` (or `0 15`) configuration block.
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct LineVty {
181    /// `exec-timeout <min>` portion.
182    pub exec_timeout_min: u32,
183    /// `exec-timeout <min> <sec>` seconds portion.
184    pub exec_timeout_sec: u32,
185    /// `transport input` protocols (e.g. `["ssh"]`, `["ssh", "telnet"]`).
186    pub transport_input: Vec<String>,
187    /// `logging synchronous`
188    pub logging_synchronous: bool,
189}
190
191// ---------------------------------------------------------------------------
192// SSH
193// ---------------------------------------------------------------------------
194
195/// `ip ssh` configuration.
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct SshConfig {
198    /// `ip ssh version <n>` — 1 or 2.
199    pub version: u8,
200    /// `ip ssh time-out <seconds>`
201    pub timeout: Option<u32>,
202    /// `ip ssh authentication-retries <n>`
203    pub retries: Option<u8>,
204}
205
206// ---------------------------------------------------------------------------
207// Interface
208// ---------------------------------------------------------------------------
209
210/// A single `interface …` block.
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct Interface {
213    /// Parsed and normalized interface name.
214    pub name: InterfaceName,
215    /// `description` line text.
216    pub description: Option<String>,
217    /// IP addresses (primary first, then secondary).
218    pub addresses: Vec<IpAddress>,
219    /// `shutdown` is present (true) or absent (false).
220    pub shutdown: bool,
221    /// `mtu <bytes>`
222    pub mtu: Option<u32>,
223    /// `speed <mbps>` or `speed auto`.
224    pub speed: Option<InterfaceSpeed>,
225    /// `duplex full/half/auto`
226    pub duplex: Option<Duplex>,
227    /// Layer-2 switchport configuration, if present.
228    pub l2: Option<L2Config>,
229    /// `ip helper-address` entries.
230    pub helper_addresses: Vec<IpAddr>,
231    /// Inbound ACL name (`ip access-group … in`).
232    pub acl_in: Option<String>,
233    /// Outbound ACL name (`ip access-group … out`).
234    pub acl_out: Option<String>,
235    /// `ip nat inside` / `ip nat outside`.
236    pub nat_direction: Option<NatDirection>,
237    /// HSRP groups configured on this interface.
238    pub hsrp: Vec<HsrpGroup>,
239    /// Per-interface OSPF settings (`ip ospf …`).
240    pub ospf: Option<InterfaceOspf>,
241    /// `switchport voice vlan <id>`
242    pub voice_vlan: Option<u16>,
243    /// `storm-control` thresholds.
244    pub storm_control: Option<StormControl>,
245    /// Per-interface STP settings.
246    pub stp: InterfaceStp,
247    /// Parser confidence for this interface block.
248    pub confidence: Confidence,
249}
250
251/// `storm-control` broadcast/multicast/unicast levels (percentage).
252#[derive(Debug, Clone, Default, Serialize, Deserialize)]
253pub struct StormControl {
254    pub broadcast_level: Option<f32>,
255    pub multicast_level: Option<f32>,
256    pub unicast_level: Option<f32>,
257}
258
259/// Per-interface spanning-tree settings.
260#[derive(Debug, Clone, Default, Serialize, Deserialize)]
261pub struct InterfaceStp {
262    /// `spanning-tree portfast`
263    pub portfast: bool,
264    /// `spanning-tree bpduguard enable`
265    pub bpduguard: bool,
266    /// `spanning-tree bpdufilter enable`
267    pub bpdufilter: bool,
268    /// `spanning-tree guard root`
269    pub guard_root: bool,
270}
271
272/// Parsed and normalized interface name.
273///
274/// Abbreviated names (`Gi`, `Fa`, `Te`, `Lo`, …) are expanded to their
275/// canonical long form. The original string is preserved in [`original`][InterfaceName::original].
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct InterfaceName {
278    /// Interface type.
279    pub kind: InterfaceKind,
280    /// Slot/port identifier as a string: `"0/0"`, `"0/0/0"`, `"1"`, etc.
281    pub id: String,
282    /// Verbatim name from the config, before any normalization.
283    pub original: String,
284}
285
286/// Interface type discriminant.
287#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
288pub enum InterfaceKind {
289    GigabitEthernet,
290    FastEthernet,
291    TenGigabitEthernet,
292    Loopback,
293    /// Layer-3 VLAN interface (`interface Vlan<id>`).
294    Vlan,
295    Tunnel,
296    Serial,
297    BundleEther,
298    Management,
299    /// Any interface type not matched by the parser.
300    Unknown(String),
301}
302
303/// An IPv4 address/prefix assigned to an interface.
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct IpAddress {
306    /// Address and prefix length in CIDR notation.
307    pub prefix: IpNet,
308    /// True if this is a `secondary` address.
309    pub secondary: bool,
310}
311
312/// Interface speed configuration.
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub enum InterfaceSpeed {
315    /// Explicit speed in Mbps (`speed 100`, `speed 1000`, …).
316    Mbps(u32),
317    /// `speed auto`
318    Auto,
319}
320
321/// Interface duplex setting.
322#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub enum Duplex {
324    Full,
325    Half,
326    Auto,
327}
328
329/// Layer-2 switchport configuration.
330#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct L2Config {
332    /// `switchport mode access` or `switchport mode trunk`.
333    pub mode: L2Mode,
334    /// `switchport access vlan <id>`
335    pub access_vlan: Option<u16>,
336    /// `switchport trunk allowed vlan …` — expanded VLAN list.
337    pub trunk_allowed: Option<Vec<u16>>,
338    /// `switchport trunk native vlan <id>`
339    pub trunk_native: Option<u16>,
340}
341
342/// Switchport operating mode.
343#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
344pub enum L2Mode {
345    Access,
346    Trunk,
347}
348
349/// `ip nat inside` / `ip nat outside` marker on an interface.
350#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
351pub enum NatDirection {
352    Inside,
353    Outside,
354}
355
356// ---------------------------------------------------------------------------
357// HSRP
358// ---------------------------------------------------------------------------
359
360/// An HSRP group configured on an interface.
361///
362/// Note: when converting to platforms that use VRRP (e.g. Huawei VRP),
363/// the parser sets [`Interface::confidence`] to [`Confidence::Approximate`]
364/// because HSRP and VRRP have different timer semantics.
365#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct HsrpGroup {
367    pub group_id: u16,
368    pub virtual_ip: IpAddr,
369    /// `standby <id> priority <n>` — default 100.
370    pub priority: Option<u16>,
371    /// `standby <id> preempt`
372    pub preempt: bool,
373    /// `standby <id> preempt delay minimum <sec>`
374    pub preempt_delay: Option<u32>,
375    pub timers: Option<HsrpTimers>,
376    pub track: Vec<HsrpTrack>,
377}
378
379/// HSRP hello/hold timers in milliseconds.
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct HsrpTimers {
382    pub hello_ms: u32,
383    pub hold_ms: u32,
384}
385
386/// `standby <id> track <object> decrement <n>`
387#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct HsrpTrack {
389    pub object: u32,
390    pub decrement: u16,
391}
392
393// ---------------------------------------------------------------------------
394// OSPF (per-interface)
395// ---------------------------------------------------------------------------
396
397/// Per-interface OSPF parameters (`ip ospf …`).
398#[derive(Debug, Clone, Serialize, Deserialize)]
399pub struct InterfaceOspf {
400    pub process_id: u32,
401    pub area: OspfArea,
402    /// `ip ospf cost <n>`
403    pub cost: Option<u32>,
404    /// `ip ospf priority <n>`
405    pub priority: Option<u8>,
406    pub timers: Option<OspfIfTimers>,
407    pub auth: Option<OspfAuth>,
408    /// `ip ospf passive-interface`
409    pub passive: bool,
410    /// `ip ospf network <type>`
411    pub network_type: Option<OspfNetworkType>,
412}
413
414/// OSPF interface hello/dead timers (seconds).
415#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct OspfIfTimers {
417    pub hello_interval: u32,
418    pub dead_interval: u32,
419}
420
421// ---------------------------------------------------------------------------
422// VLAN
423// ---------------------------------------------------------------------------
424
425/// A `vlan <id>` database entry.
426#[derive(Debug, Clone, Serialize, Deserialize)]
427pub struct Vlan {
428    pub id: u16,
429    /// `name <string>`
430    pub name: Option<String>,
431    /// False if `state suspend` is set.
432    pub active: bool,
433}
434
435// ---------------------------------------------------------------------------
436// Routing
437// ---------------------------------------------------------------------------
438
439/// All routing configuration parsed from the config.
440#[derive(Debug, Clone, Default, Serialize, Deserialize)]
441pub struct RoutingConfig {
442    /// `ip route …` entries.
443    pub static_routes: Vec<StaticRoute>,
444    /// `router ospf <pid>` blocks (multiple processes supported).
445    pub ospf: Vec<OspfProcess>,
446    /// `router bgp <asn>` block (at most one per device).
447    pub bgp: Option<BgpConfig>,
448    /// `router eigrp <asn>` blocks.
449    pub eigrp: Vec<EigrpProcess>,
450}
451
452/// A single `ip route <prefix> …` entry.
453#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct StaticRoute {
455    pub prefix: IpNet,
456    pub next_hop: NextHop,
457    /// Administrative distance (default 1 if absent).
458    pub distance: Option<u8>,
459    pub tag: Option<u32>,
460    /// `name <string>` label.
461    pub name: Option<String>,
462    /// `permanent` flag — route survives interface going down.
463    pub permanent: bool,
464}
465
466/// Next-hop specification for a static route.
467#[derive(Debug, Clone, Serialize, Deserialize)]
468pub enum NextHop {
469    /// `ip route … <next-hop-ip>`
470    Ip(IpAddr),
471    /// `ip route … <exit-interface>`
472    Interface(String),
473    /// `ip route … <exit-interface> <next-hop-ip>`
474    IpAndInterface(IpAddr, String),
475    /// `ip route … Null0`
476    Null0,
477}
478
479/// A `router ospf <pid>` process block.
480#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct OspfProcess {
482    pub process_id: u32,
483    pub router_id: Option<IpAddr>,
484    /// Area configurations including `network` statements.
485    pub areas: Vec<OspfAreaConfig>,
486    /// Interfaces excluded from OSPF hellos (`passive-interface`).
487    pub passive_interfaces: Vec<String>,
488    /// `default-information originate` settings.
489    pub default_originate: Option<OspfDefaultOriginate>,
490    /// Redistributed protocol sources.
491    pub redistribute: Vec<OspfRedistribute>,
492    /// `max-metric router-lsa` (stub router advertisement).
493    pub max_metric: bool,
494    /// Process-level authentication.
495    pub auth: Option<OspfAuth>,
496    /// `log-adjacency-changes`
497    pub log_adjacency: bool,
498}
499
500/// Per-area OSPF configuration.
501#[derive(Debug, Clone, Serialize, Deserialize)]
502pub struct OspfAreaConfig {
503    pub area: OspfArea,
504    /// `network <address> <wildcard> area <id>` entries.
505    pub networks: Vec<OspfNetwork>,
506    pub area_type: OspfAreaType,
507    pub auth: Option<OspfAuth>,
508}
509
510/// OSPF area identifier.
511#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
512pub enum OspfArea {
513    /// Area 0 (backbone), regardless of how it was written in config.
514    Backbone,
515    /// `area <n>` where n > 0.
516    Normal(u32),
517    /// `area 0.0.0.1` dotted-decimal format.
518    IpFormat(IpAddr),
519}
520
521/// A `network <addr> <wildcard> area <id>` statement.
522#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct OspfNetwork {
524    /// The network prefix. If the config used a wildcard mask,
525    /// it is converted to prefix length.
526    pub prefix: IpNet,
527    /// True if the original config used a wildcard mask (not prefix length).
528    pub wildcard: bool,
529}
530
531/// OSPF area type.
532#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
533pub enum OspfAreaType {
534    Normal,
535    Stub,
536    /// `area <id> stub no-summary`
537    StubNoSummary,
538    Nssa,
539    /// `area <id> nssa no-summary`
540    NssaNoSummary,
541}
542
543/// OSPF authentication configuration.
544#[derive(Debug, Clone, Serialize, Deserialize)]
545pub enum OspfAuth {
546    /// `ip ospf authentication-key <key>` (plaintext).
547    Simple(String),
548    /// `ip ospf message-digest-key <id> md5 <key>`.
549    Md5 { key_id: u8, key: String },
550}
551
552/// OSPF network type for an interface.
553#[derive(Debug, Clone, Serialize, Deserialize)]
554pub enum OspfNetworkType {
555    Broadcast,
556    PointToPoint,
557    PointToMultipoint,
558    NonBroadcast,
559}
560
561/// `default-information originate` parameters.
562#[derive(Debug, Clone, Serialize, Deserialize)]
563pub struct OspfDefaultOriginate {
564    /// `always` keyword — advertise even without a default route in the table.
565    pub always: bool,
566    pub metric: Option<u32>,
567    /// Metric type: 1 (E1) or 2 (E2, default).
568    pub metric_type: Option<u8>,
569}
570
571/// A `redistribute <source> …` statement inside a routing process.
572#[derive(Debug, Clone, Serialize, Deserialize)]
573pub struct OspfRedistribute {
574    pub source: RedistributeSource,
575    pub metric: Option<u32>,
576    pub metric_type: Option<u8>,
577    /// `subnets` keyword (required for redistributing into OSPF).
578    pub subnets: bool,
579    pub tag: Option<u32>,
580    pub route_map: Option<String>,
581}
582
583/// Source protocol for redistribution.
584#[derive(Debug, Clone, Serialize, Deserialize)]
585pub enum RedistributeSource {
586    Connected,
587    Static,
588    Bgp(u32),
589    Eigrp(u32),
590    Rip,
591}
592
593// ---------------------------------------------------------------------------
594// BGP
595// ---------------------------------------------------------------------------
596
597/// A `router bgp <asn>` block.
598#[derive(Debug, Clone, Serialize, Deserialize)]
599pub struct BgpConfig {
600    pub asn: u32,
601    pub router_id: Option<IpAddr>,
602    /// All `neighbor` entries defined at the global BGP level.
603    pub neighbors: Vec<BgpNeighbor>,
604    pub peer_groups: Vec<BgpPeerGroup>,
605    /// `network` statements in the global context (outside any address-family).
606    pub networks: Vec<IpNet>,
607    /// `address-family` blocks.
608    pub address_families: Vec<BgpAddressFamily>,
609    pub redistribute: Vec<OspfRedistribute>,
610    /// `bgp log-neighbor-changes`
611    pub log_neighbor_changes: bool,
612    /// `bgp bestpath …` policy string, if present.
613    pub bestpath: Option<String>,
614}
615
616/// A BGP neighbor entry (`neighbor <addr|peer-group> …`).
617#[derive(Debug, Clone, Serialize, Deserialize)]
618pub struct BgpNeighbor {
619    pub address: BgpNeighborAddr,
620    pub remote_as: u32,
621    pub description: Option<String>,
622    /// `update-source <interface>`
623    pub update_source: Option<String>,
624    /// `next-hop-self`
625    pub next_hop_self: bool,
626    pub password: Option<String>,
627    pub shutdown: bool,
628    /// Peer-group this neighbor inherits from.
629    pub peer_group: Option<String>,
630    pub route_map_in: Option<String>,
631    pub route_map_out: Option<String>,
632    pub prefix_list_in: Option<String>,
633    pub prefix_list_out: Option<String>,
634    pub soft_reconfiguration: bool,
635    pub send_community: bool,
636    pub remove_private_as: bool,
637    pub default_originate: bool,
638    pub activate: bool,
639}
640
641/// BGP neighbor address — either an IP or a peer-group name.
642#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
643pub enum BgpNeighborAddr {
644    Ip(IpAddr),
645    PeerGroup(String),
646}
647
648impl std::fmt::Display for BgpNeighborAddr {
649    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
650        match self {
651            BgpNeighborAddr::Ip(ip) => write!(f, "{}", ip),
652            BgpNeighborAddr::PeerGroup(name) => write!(f, "{}", name),
653        }
654    }
655}
656
657/// A BGP peer-group definition.
658#[derive(Debug, Clone, Serialize, Deserialize)]
659pub struct BgpPeerGroup {
660    pub name: String,
661    pub remote_as: Option<u32>,
662    pub update_source: Option<String>,
663    pub next_hop_self: bool,
664    pub route_map_in: Option<String>,
665    pub route_map_out: Option<String>,
666    pub send_community: bool,
667}
668
669/// A BGP `address-family <afi> <safi>` block.
670#[derive(Debug, Clone, Serialize, Deserialize)]
671pub struct BgpAddressFamily {
672    pub afi: BgpAfi,
673    pub safi: BgpSafi,
674    /// `network` statements inside this address-family.
675    pub networks: Vec<IpNet>,
676    pub redistribute: Vec<OspfRedistribute>,
677    /// Neighbors activated with `neighbor <addr> activate`.
678    pub activated_neighbors: Vec<BgpNeighborAddr>,
679    /// Neighbors explicitly deactivated with `no neighbor <addr> activate`.
680    pub deactivated_neighbors: Vec<BgpNeighborAddr>,
681    /// Per-neighbor settings overridden inside this address-family.
682    pub neighbor_settings: Vec<BgpNeighbor>,
683    pub default_information: bool,
684    /// `aggregate-address` entries.
685    pub aggregate_addresses: Vec<BgpAggregate>,
686}
687
688/// BGP address family indicator.
689#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
690pub enum BgpAfi {
691    Ipv4,
692    Ipv6,
693    Vpnv4,
694    L2vpn,
695}
696
697/// BGP subsequent address family indicator.
698#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
699pub enum BgpSafi {
700    Unicast,
701    Multicast,
702    Labeled,
703    Evpn,
704}
705
706/// A `aggregate-address <prefix> …` entry.
707#[derive(Debug, Clone, Serialize, Deserialize)]
708pub struct BgpAggregate {
709    pub prefix: IpNet,
710    /// `summary-only` — suppress more-specific prefixes.
711    pub summary_only: bool,
712    /// `as-set` — include AS path information from contributing routes.
713    pub as_set: bool,
714}
715
716/// A `router eigrp <asn>` block.
717#[derive(Debug, Clone, Serialize, Deserialize)]
718pub struct EigrpProcess {
719    pub asn: u32,
720    pub networks: Vec<OspfNetwork>,
721    pub passive_interfaces: Vec<String>,
722    pub redistribute: Vec<OspfRedistribute>,
723}
724
725// ---------------------------------------------------------------------------
726// ACL
727// ---------------------------------------------------------------------------
728
729/// An IP access control list (standard or extended, named or numbered).
730#[derive(Debug, Clone, Serialize, Deserialize)]
731pub struct Acl {
732    pub name: AclName,
733    pub acl_type: AclType,
734    pub entries: Vec<AclEntry>,
735}
736
737/// ACL identifier.
738#[derive(Debug, Clone, Serialize, Deserialize)]
739pub enum AclName {
740    /// `ip access-list standard|extended <name>`
741    Named(String),
742    /// `access-list <number> …`
743    Numbered(u32),
744}
745
746/// ACL type controlling which fields are matched.
747#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
748pub enum AclType {
749    /// Standard — matches source IP only.
750    Standard,
751    /// Extended — matches source, destination, protocol, and ports.
752    Extended,
753}
754
755/// A single ACE (access control entry) line.
756#[derive(Debug, Clone, Serialize, Deserialize)]
757pub struct AclEntry {
758    /// Explicit sequence number if present (`10 permit …`).
759    pub sequence: Option<u32>,
760    pub action: AclAction,
761    pub protocol: Option<AclProtocol>,
762    pub src: AclMatch,
763    pub dst: Option<AclMatch>,
764    pub src_port: Option<AclPort>,
765    pub dst_port: Option<AclPort>,
766    /// `established` keyword (TCP only).
767    pub established: bool,
768    /// `log` keyword.
769    pub log: bool,
770    /// `remark <text>` line.
771    pub remark: Option<String>,
772}
773
774/// ACE action.
775#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
776pub enum AclAction {
777    Permit,
778    Deny,
779}
780
781/// Protocol field in an extended ACE.
782#[derive(Debug, Clone, Serialize, Deserialize)]
783pub enum AclProtocol {
784    Ip,
785    Tcp,
786    Udp,
787    Icmp,
788    Esp,
789    Ahp,
790    Number(u8),
791}
792
793/// Address match criterion in an ACE.
794#[derive(Debug, Clone, Serialize, Deserialize)]
795pub enum AclMatch {
796    /// `any`
797    Any,
798    /// `host <ip>`
799    Host(IpAddr),
800    /// `<addr> <wildcard>` — stored as-is for fidelity.
801    Network { addr: IpAddr, wildcard: IpAddr },
802    /// CIDR prefix (used when the config already specifies prefix length).
803    Prefix(IpNet),
804}
805
806/// Port match operator in an extended ACE.
807#[derive(Debug, Clone, Serialize, Deserialize)]
808pub enum AclPort {
809    Eq(u16),
810    Ne(u16),
811    Lt(u16),
812    Gt(u16),
813    Range(u16, u16),
814}
815
816// ---------------------------------------------------------------------------
817// NAT
818// ---------------------------------------------------------------------------
819
820/// A NAT rule (`ip nat inside source …`).
821#[derive(Debug, Clone, Serialize, Deserialize)]
822pub struct NatRule {
823    pub rule_type: NatType,
824    /// Source ACL name (for dynamic/PAT NAT).
825    pub acl: Option<String>,
826    /// Address pool (resolved even when declared after the `ip nat` statement).
827    pub pool: Option<NatPool>,
828    /// True when `interface <if> overload` is used instead of a pool.
829    pub interface_overload: bool,
830    /// Static NAT mapping entry, if this is a static rule.
831    pub static_entry: Option<NatStaticEntry>,
832}
833
834/// NAT rule type.
835#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
836pub enum NatType {
837    /// `ip nat inside source list <acl> pool <pool>`
838    Dynamic,
839    /// `ip nat inside source list <acl> interface <if> overload` (PAT).
840    Overload,
841    /// `ip nat inside source static <local> <global>`
842    Static,
843}
844
845/// An `ip nat pool <name> …` definition.
846#[derive(Debug, Clone, Serialize, Deserialize)]
847pub struct NatPool {
848    pub name: String,
849    pub start: IpAddr,
850    pub end: IpAddr,
851    /// Network prefix if `prefix-length` was specified.
852    pub prefix: Option<IpNet>,
853    pub overload: bool,
854}
855
856/// A static NAT mapping (`ip nat inside source static …`).
857#[derive(Debug, Clone, Serialize, Deserialize)]
858pub struct NatStaticEntry {
859    pub local: IpAddr,
860    pub global: IpAddr,
861    /// Port for static port-NAT (`local_port` / `global_port` may differ).
862    pub local_port: Option<u16>,
863    pub global_port: Option<u16>,
864    pub protocol: Option<AclProtocol>,
865}
866
867// ---------------------------------------------------------------------------
868// NTP / DNS / SNMP / AAA
869// ---------------------------------------------------------------------------
870
871/// An `ntp server …` entry.
872#[derive(Debug, Clone, Serialize, Deserialize)]
873pub struct NtpServer {
874    pub address: IpAddr,
875    /// `prefer` keyword.
876    pub prefer: bool,
877    /// `key <id>` for NTP authentication.
878    pub key: Option<u32>,
879    /// `source <interface>` for NTP packets.
880    pub source_interface: Option<String>,
881}
882
883/// `snmp-server` configuration.
884#[derive(Debug, Clone, Serialize, Deserialize)]
885pub struct SnmpConfig {
886    /// Community strings with access level and optional ACL.
887    pub communities: Vec<SnmpCommunity>,
888    /// `snmp-server location <text>`
889    pub location: Option<String>,
890    /// `snmp-server contact <text>`
891    pub contact: Option<String>,
892    /// Enabled trap types (`snmp-server enable traps …`).
893    pub traps: Vec<String>,
894}
895
896/// An `snmp-server community <name> RO|RW` entry.
897#[derive(Debug, Clone, Serialize, Deserialize)]
898pub struct SnmpCommunity {
899    pub name: String,
900    pub access: SnmpAccess,
901    /// Optional ACL restricting which hosts can use this community.
902    pub acl: Option<String>,
903}
904
905/// SNMP community access level.
906#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
907pub enum SnmpAccess {
908    Ro,
909    Rw,
910}
911
912/// `aaa new-model` configuration.
913#[derive(Debug, Clone, Serialize, Deserialize)]
914pub struct AaaConfig {
915    pub new_model: bool,
916    /// `aaa authentication …` method lists.
917    pub authentication: Vec<AaaMethod>,
918    /// `aaa authorization …` method lists.
919    pub authorization: Vec<AaaMethod>,
920}
921
922/// An AAA method list entry.
923#[derive(Debug, Clone, Serialize, Deserialize)]
924pub struct AaaMethod {
925    /// List name (`default` or custom).
926    pub list_name: String,
927    /// Ordered list of authentication/authorization methods.
928    pub methods: Vec<String>,
929}
930
931// ---------------------------------------------------------------------------
932// UnknownBlock
933// ---------------------------------------------------------------------------
934
935/// A command that the parser did not recognize.
936///
937/// Unrecognized commands are never silently discarded. Inspect
938/// [`NetworkConfig::unknown_blocks`] to audit what the parser missed.
939#[derive(Debug, Clone, Serialize, Deserialize)]
940pub struct UnknownBlock {
941    /// Source line number in the original config (0 if unavailable).
942    pub line: usize,
943    /// Config block context, e.g. `"global"`, `"interface GigabitEthernet0/0"`,
944    /// `"router ospf 1"`.
945    pub context: String,
946    /// Verbatim command text.
947    pub raw: String,
948}
949
950// ---------------------------------------------------------------------------
951// Helpers
952// ---------------------------------------------------------------------------
953
954#[doc(hidden)]
955pub type ProcessId = u32;
956
957impl Default for Interface {
958    fn default() -> Self {
959        Interface {
960            name: InterfaceName {
961                kind: InterfaceKind::Unknown(String::new()),
962                id: String::new(),
963                original: String::new(),
964            },
965            description: None,
966            addresses: vec![],
967            shutdown: false,
968            mtu: None,
969            speed: None,
970            duplex: None,
971            l2: None,
972            helper_addresses: vec![],
973            acl_in: None,
974            acl_out: None,
975            nat_direction: None,
976            hsrp: vec![],
977            ospf: None,
978            voice_vlan: None,
979            storm_control: None,
980            stp: InterfaceStp::default(),
981            confidence: Confidence::Exact,
982        }
983    }
984}
985
986impl InterfaceName {
987    /// Parse a raw interface name string, expanding Cisco abbreviations.
988    ///
989    /// ```
990    /// use ios_config_core::ir::{InterfaceName, InterfaceKind};
991    /// let name = InterfaceName::parse("Gi0/1");
992    /// assert_eq!(name.kind, InterfaceKind::GigabitEthernet);
993    /// assert_eq!(name.id, "0/1");
994    /// assert_eq!(name.original, "Gi0/1");
995    /// ```
996    pub fn parse(raw: &str) -> Self {
997        let original = raw.to_string();
998        let expanded = expand_interface_name(raw);
999        let (kind_str, id) = split_interface_name(&expanded);
1000
1001        let kind = match kind_str.to_lowercase().as_str() {
1002            "gigabitethernet" => InterfaceKind::GigabitEthernet,
1003            "fastethernet" => InterfaceKind::FastEthernet,
1004            "tengigabitethernet" | "tengige" => InterfaceKind::TenGigabitEthernet,
1005            "loopback" => InterfaceKind::Loopback,
1006            "vlan" => InterfaceKind::Vlan,
1007            "tunnel" => InterfaceKind::Tunnel,
1008            "serial" => InterfaceKind::Serial,
1009            "bundle-ether" | "bundleether" => InterfaceKind::BundleEther,
1010            "management" => InterfaceKind::Management,
1011            other => InterfaceKind::Unknown(other.to_string()),
1012        };
1013
1014        InterfaceName { kind, id, original }
1015    }
1016}
1017
1018fn expand_interface_name(raw: &str) -> String {
1019    let prefixes = [
1020        ("Gi", "GigabitEthernet"),
1021        ("Fa", "FastEthernet"),
1022        ("Te", "TenGigabitEthernet"),
1023        ("Lo", "Loopback"),
1024        ("Tu", "Tunnel"),
1025        ("Se", "Serial"),
1026        ("Vl", "Vlan"),
1027        ("Mg", "Management"),
1028    ];
1029    for (short, full) in &prefixes {
1030        if raw.starts_with(short) && !raw.to_lowercase().starts_with(&full.to_lowercase()) {
1031            return format!("{}{}", full, &raw[short.len()..]);
1032        }
1033    }
1034    raw.to_string()
1035}
1036
1037fn split_interface_name(name: &str) -> (&str, String) {
1038    let pos = name.find(|c: char| c.is_ascii_digit());
1039    match pos {
1040        Some(i) => (&name[..i], name[i..].to_string()),
1041        None => (name, String::new()),
1042    }
1043}
1044
1045impl OspfArea {
1046    /// Parse an area identifier string (`"0"`, `"1"`, `"0.0.0.1"`, …).
1047    pub fn parse(s: &str) -> Self {
1048        if let Ok(n) = s.parse::<u32>() {
1049            if n == 0 { OspfArea::Backbone } else { OspfArea::Normal(n) }
1050        } else if let Ok(ip) = s.parse::<IpAddr>() {
1051            if ip == "0.0.0.0".parse::<IpAddr>().unwrap() {
1052                OspfArea::Backbone
1053            } else {
1054                OspfArea::IpFormat(ip)
1055            }
1056        } else {
1057            OspfArea::Normal(0)
1058        }
1059    }
1060}