This repository has been archived on 2026-03-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
oubliette/internal/shell/cisco/output.go
Torjus Håkestad 5ba62afec3 feat: add Cisco IOS shell with mode state machine and abbreviation matching (PLAN.md 3.2)
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>
2026-02-15 14:58:26 +01:00

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}
}