Implements a Cisco IOS CLI emulator with four modes (user exec, privileged exec, global config, interface config), Cisco-style command abbreviation (e.g. sh run, conf t), enable password flow, and realistic show command output including running-config, interfaces, IP routes, and VLANs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
235 lines
7.4 KiB
Go
235 lines
7.4 KiB
Go
package cisco
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func showVersion(s *iosState) string {
|
|
days := 14 + rand.Intn(350)
|
|
hours := rand.Intn(24)
|
|
mins := rand.Intn(60)
|
|
|
|
return fmt.Sprintf(`Cisco IOS Software, %s Software (%s-UNIVERSALK9-M), Version %s, RELEASE SOFTWARE (fc3)
|
|
Technical Support: http://www.cisco.com/techsupport
|
|
Copyright (c) 1986-2019 by Cisco Systems, Inc.
|
|
Compiled Thu 30-Jan-19 10:08 by prod_rel_team
|
|
|
|
ROM: Bootstrap program is %s boot loader
|
|
BOOTLDR: %s Boot Loader (C2960-HBOOT-M) Version 15.0(2r)SE, RELEASE SOFTWARE (fc1)
|
|
|
|
%s uptime is %d days, %d hours, %d minutes
|
|
System returned to ROM by power-on
|
|
System image file is "flash:/%s-universalk9-mz.SPA.%s.bin"
|
|
|
|
This product contains cryptographic features and is subject to United States
|
|
and local country laws governing import, export, transfer and use.
|
|
|
|
cisco %s (%s) processor (revision K0) with 524288K bytes of memory.
|
|
Processor board ID %s
|
|
Last reset from power-on
|
|
2 Gigabit Ethernet interfaces
|
|
1 Virtual Ethernet interface
|
|
64K bytes of flash-simulated non-volatile configuration memory.
|
|
Total of 65536K bytes of APC System Flash (Read/Write)
|
|
|
|
Configuration register is 0x2102`,
|
|
s.model, s.model, s.iosVersion,
|
|
s.model, s.model,
|
|
s.hostname, days, hours, mins,
|
|
s.model, s.iosVersion,
|
|
s.model, processorForModel(s.model),
|
|
s.serial,
|
|
)
|
|
}
|
|
|
|
func processorForModel(model string) string {
|
|
if strings.HasPrefix(model, "C29") {
|
|
return "PowerPC405"
|
|
}
|
|
return "MIPS"
|
|
}
|
|
|
|
func showClock() string {
|
|
now := time.Now().UTC()
|
|
return fmt.Sprintf("*%s UTC", now.Format("15:04:05.000 Mon Jan 2 2006"))
|
|
}
|
|
|
|
func showIPRoute(s *iosState) string {
|
|
var b strings.Builder
|
|
b.WriteString("Codes: C - connected, S - static, R - RIP, M - mobile, B - BGP\n")
|
|
b.WriteString(" D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area\n")
|
|
b.WriteString(" N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2\n")
|
|
b.WriteString(" E1 - OSPF external type 1, E2 - OSPF external type 2\n")
|
|
b.WriteString(" i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2\n")
|
|
b.WriteString(" ia - IS-IS inter area, * - candidate default, U - per-user static route\n")
|
|
b.WriteString(" o - ODR, P - periodic downloaded static route\n\n")
|
|
b.WriteString("Gateway of last resort is 10.0.0.2 to network 0.0.0.0\n\n")
|
|
|
|
for _, iface := range s.interfaces {
|
|
if iface.ip == "unassigned" || iface.status != "up" {
|
|
continue
|
|
}
|
|
network := networkFromIP(iface.ip, iface.mask)
|
|
maskBits := maskBits(iface.mask)
|
|
fmt.Fprintf(&b, "C %s/%d is directly connected, %s\n", network, maskBits, iface.name)
|
|
}
|
|
b.WriteString("S* 0.0.0.0/0 [1/0] via 10.0.0.2")
|
|
return b.String()
|
|
}
|
|
|
|
func showIPInterfaceBrief(s *iosState) string {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "%-25s %-15s %-4s %-7s %-22s %s\n",
|
|
"Interface", "IP-Address", "OK?", "Method", "Status", "Protocol")
|
|
for _, iface := range s.interfaces {
|
|
ip := iface.ip
|
|
if ip == "" {
|
|
ip = "unassigned"
|
|
}
|
|
fmt.Fprintf(&b, "%-25s %-15s YES manual %-22s %s\n",
|
|
iface.name, ip, iface.status, iface.protocol)
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func showInterfaces(s *iosState) string {
|
|
var b strings.Builder
|
|
for i, iface := range s.interfaces {
|
|
if i > 0 {
|
|
b.WriteString("\n")
|
|
}
|
|
upDown := "up"
|
|
if iface.shutdown {
|
|
upDown = "administratively down"
|
|
}
|
|
fmt.Fprintf(&b, "%s is %s, line protocol is %s\n", iface.name, upDown, iface.protocol)
|
|
fmt.Fprintf(&b, " Hardware is Gigabit Ethernet, address is %s (bia %s)\n", iface.mac, iface.mac)
|
|
if iface.ip != "unassigned" && iface.ip != "" {
|
|
fmt.Fprintf(&b, " Internet address is %s/%d\n", iface.ip, maskBits(iface.mask))
|
|
}
|
|
fmt.Fprintf(&b, " MTU %d bytes, BW %s sec, DLY 10 usec,\n", iface.mtu, iface.bandwidth)
|
|
b.WriteString(" reliability 255/255, txload 1/255, rxload 1/255\n")
|
|
b.WriteString(" Encapsulation ARPA, loopback not set\n")
|
|
fmt.Fprintf(&b, " %d packets input, %d bytes, 0 no buffer\n", iface.rxPackets, iface.rxBytes)
|
|
fmt.Fprintf(&b, " %d packets output, %d bytes, 0 underruns", iface.txPackets, iface.txBytes)
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func showRunningConfig(s *iosState) string {
|
|
var b strings.Builder
|
|
b.WriteString("Building configuration...\n\n")
|
|
b.WriteString("Current configuration : 1482 bytes\n")
|
|
b.WriteString("!\n")
|
|
b.WriteString("! Last configuration change at 14:32:22 UTC Mon Feb 10 2025\n")
|
|
b.WriteString("!\n")
|
|
b.WriteString("version 15.0\n")
|
|
b.WriteString("service timestamps debug datetime msec\n")
|
|
b.WriteString("service timestamps log datetime msec\n")
|
|
b.WriteString("no service password-encryption\n")
|
|
b.WriteString("!\n")
|
|
fmt.Fprintf(&b, "hostname %s\n", s.hostname)
|
|
b.WriteString("!\n")
|
|
b.WriteString("boot-start-marker\n")
|
|
b.WriteString("boot-end-marker\n")
|
|
b.WriteString("!\n")
|
|
if s.enablePass != "" {
|
|
b.WriteString("enable secret 5 $1$mERr$hx5rVt7rPNoS4wqbXKX7m0\n")
|
|
}
|
|
b.WriteString("!\n")
|
|
b.WriteString("no aaa new-model\n")
|
|
b.WriteString("!\n")
|
|
|
|
for _, iface := range s.interfaces {
|
|
b.WriteString("!\n")
|
|
fmt.Fprintf(&b, "interface %s\n", iface.name)
|
|
if iface.desc != "" {
|
|
fmt.Fprintf(&b, " description %s\n", iface.desc)
|
|
}
|
|
if iface.ip != "unassigned" && iface.ip != "" {
|
|
fmt.Fprintf(&b, " ip address %s %s\n", iface.ip, iface.mask)
|
|
} else {
|
|
b.WriteString(" no ip address\n")
|
|
}
|
|
if iface.shutdown {
|
|
b.WriteString(" shutdown\n")
|
|
}
|
|
}
|
|
|
|
b.WriteString("!\n")
|
|
b.WriteString("ip forward-protocol nd\n")
|
|
b.WriteString("!\n")
|
|
b.WriteString("ip route 0.0.0.0 0.0.0.0 10.0.0.2\n")
|
|
b.WriteString("!\n")
|
|
b.WriteString("access-list 10 permit 192.168.1.0 0.0.0.255\n")
|
|
b.WriteString("access-list 10 deny any\n")
|
|
b.WriteString("!\n")
|
|
b.WriteString("line con 0\n")
|
|
b.WriteString(" logging synchronous\n")
|
|
b.WriteString("line vty 0 4\n")
|
|
b.WriteString(" login local\n")
|
|
b.WriteString(" transport input ssh\n")
|
|
b.WriteString("!\n")
|
|
b.WriteString("end")
|
|
return b.String()
|
|
}
|
|
|
|
func showVLANBrief() string {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "%-6s %-32s %-10s %s\n", "VLAN", "Name", "Status", "Ports")
|
|
b.WriteString("---- -------------------------------- --------- -------------------------------\n")
|
|
fmt.Fprintf(&b, "%-6s %-32s %-10s %s\n", "1", "default", "active", "Gi0/0, Gi0/1, Gi0/2")
|
|
fmt.Fprintf(&b, "%-6s %-32s %-10s %s\n", "10", "MGMT", "active", "")
|
|
fmt.Fprintf(&b, "%-6s %-32s %-10s %s\n", "20", "USERS", "active", "")
|
|
fmt.Fprintf(&b, "%-6s %-32s %-10s %s\n", "99", "NATIVE", "active", "")
|
|
fmt.Fprintf(&b, "%-6s %-32s %-10s %s\n", "1002", "fddi-default", "act/unsup", "")
|
|
fmt.Fprintf(&b, "%-6s %-32s %-10s %s\n", "1003", "token-ring-default", "act/unsup", "")
|
|
fmt.Fprintf(&b, "%-6s %-32s %-10s %s", "1004", "fddinet-default", "act/unsup", "")
|
|
return b.String()
|
|
}
|
|
|
|
// networkFromIP derives the network address from an IP and mask.
|
|
func networkFromIP(ip, mask string) string {
|
|
ipParts := parseIPv4(ip)
|
|
maskParts := parseIPv4(mask)
|
|
if ipParts == nil || maskParts == nil {
|
|
return ip
|
|
}
|
|
return fmt.Sprintf("%d.%d.%d.%d",
|
|
ipParts[0]&maskParts[0],
|
|
ipParts[1]&maskParts[1],
|
|
ipParts[2]&maskParts[2],
|
|
ipParts[3]&maskParts[3],
|
|
)
|
|
}
|
|
|
|
func maskBits(mask string) int {
|
|
parts := parseIPv4(mask)
|
|
if parts == nil {
|
|
return 24
|
|
}
|
|
bits := 0
|
|
for _, p := range parts {
|
|
for i := 7; i >= 0; i-- {
|
|
if p&(1<<uint(i)) != 0 {
|
|
bits++
|
|
} else {
|
|
return bits
|
|
}
|
|
}
|
|
}
|
|
return bits
|
|
}
|
|
|
|
func parseIPv4(s string) []int {
|
|
var a, b, c, d int
|
|
n, _ := fmt.Sscanf(s, "%d.%d.%d.%d", &a, &b, &c, &d)
|
|
if n != 4 {
|
|
return nil
|
|
}
|
|
return []int{a, b, c, d}
|
|
}
|