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}