From f063e5365e232c6c39b59473116b11173798a22e Mon Sep 17 00:00:00 2001 From: Will George Date: Tue, 25 Aug 2020 10:58:38 -0400 Subject: [PATCH 1/5] Add "specialCommands", "hostname" command, and simple state tracking This ensures that a "show running-config" or "show version" command will correctly display the configured hostname. This approach is NOT scalable, and is probably a dead-end. That said it meets the need for today for simple workflow testing in Netpalm. --- cis.go | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/cis.go b/cis.go index 108bb5b..872168c 100644 --- a/cis.go +++ b/cis.go @@ -1,8 +1,10 @@ package main import ( + "fmt" "log" "strconv" + "strings" "github.com/gliderlabs/ssh" "golang.org/x/crypto/ssh/terminal" @@ -11,7 +13,8 @@ import ( // ssh listernet func sshListener(portNumber int, done chan bool) { - supportedCommands := make(map[string]string) + basicCommands := make(map[string]string) + specialCommands := make(map[string]string) contextSearch := make(map[string]string) contextHierarchy := make(map[string]string) @@ -21,7 +24,7 @@ func sshListener(portNumber int, done chan bool) { contextSearch["enable"] = "#" contextSearch["en"] = "#" contextSearch["base"] = ">" - + contextHierarchy["(config)#"] = "#" contextHierarchy["#"] = ">" contextHierarchy[">"] = "exit" @@ -29,7 +32,7 @@ func sshListener(portNumber int, done chan bool) { hostname := "cisgo1000v" password := "admin" - supportedCommands["show version"] = `Cisco IOS XE Software, Version 16.04.01 + specialCommands["show version"] = `Cisco IOS XE Software, Version 16.04.01 Cisco IOS Software [Everest], CSR1000V Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.4.1, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2016 by Cisco Systems, Inc. @@ -44,7 +47,7 @@ documentation or "License Notice" file accompanying the IOS-XE software, or the applicable URL provided on the flyer accompanying the IOS-XE software. ROM: IOS-XE ROMMON -csr1000v uptime is 4 hours, 55 minutes +%[1]v uptime is 4 hours, 55 minutes Uptime for this control processor is 4 hours, 56 minutes System returned to ROM by reload System image file is "bootflash:packages.conf" @@ -73,7 +76,7 @@ Processor board ID 9FKLJWM5EB0 0K bytes of at webui:. Configuration register is 0x2102` - supportedCommands["show ip interface brief"] = `Interface IP-Address OK? Method Status Protocol + basicCommands["show ip interface brief"] = `Interface IP-Address OK? Method Status Protocol FastEthernet0/0 10.0.2.27 YES NVRAM up up Serial0/0 unassigned YES NVRAM administratively down down FastEthernet0/1 unassigned YES NVRAM administratively down down @@ -98,7 +101,7 @@ FastEthernet3/14 unassigned YES unset up down FastEthernet3/15 unassigned YES unset up down Vlan1 unassigned YES NVRAM up down` - supportedCommands["show running-config"] = `Building configuration... + specialCommands["show running-config"] = `Building configuration... Current configuration : 2114 bytes ! @@ -107,7 +110,7 @@ service timestamps debug datetime msec service timestamps log datetime msec no service password-encryption ! -hostname herpa derpa +hostname %[1]s ! boot-start-marker boot-end-marker @@ -265,8 +268,10 @@ line vty 5 15 transport input ssh ! ! -end` - +end +` + basicCommands["terminal length 0"] = " " + basicCommands["terminal width 511"] = " " ssh.Handle(func(s ssh.Session) { // io.WriteString(s, fmt.Sprintf(SHOW_VERSION_PAGING_DISABLED)) @@ -277,31 +282,51 @@ end` if err != nil { break } + response := line log.Println(line) - if supportedCommands[response] != "" { + if basicCommands[response] != "" { // lookup supported commands for response - term.Write(append([]byte(supportedCommands[response]), '\n')) + term.Write(append([]byte(basicCommands[response]), '\n')) + } else if response == "" { // return if nothing is entered term.Write(append([]byte(response))) + } else if contextSearch[response] != "" { // switch contexts as needed term.SetPrompt(string(hostname + contextSearch[response])) contextState = contextSearch[response] - } else if response == "exit" { + + } else if response == "exit" || response == "end" { // drop down configs if required if contextHierarchy[contextState] == "exit" { break + } else { term.SetPrompt(string(hostname + contextHierarchy[contextState])) contextState = contextHierarchy[contextState] } + + } else if specialCommands[response] != "" { + term.Write(append([]byte(fmt.Sprintf(specialCommands[response], hostname)))) + + } else if contextState != ">" { // we're in config mode + fields := strings.Fields(response) + command := fields[0] + if command == "hostname" { + hostname = strings.Join(fields[1:], " ") + log.Printf("Setting hostname to %s\n", hostname) + term.SetPrompt(hostname + contextState) + } + } else { term.Write(append([]byte("% Ambiguous command: \""+response+"\""), '\n')) } + } log.Println("terminal closed") + }) portString := ":" + strconv.Itoa(portNumber) From de89e894510d4bc9732d766144803e0629e9b42c Mon Sep 17 00:00:00 2001 From: Will George Date: Tue, 25 Aug 2020 11:01:54 -0400 Subject: [PATCH 2/5] Adds a simple example Dockerfile. Very likely this is not best-practice compliant. But it functions well enough for today --- Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5021a0c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM golang:1.15.0-buster +ADD . /app +WORKDIR /app +ENV GO111MODULE=on +RUN go mod download +CMD go run cis.go \ No newline at end of file From 700bcf53c34cfc3a3ac74482888af2d78141885f Mon Sep 17 00:00:00 2001 From: Will George Date: Tue, 25 Aug 2020 18:03:05 -0400 Subject: [PATCH 3/5] Add "reset state" command to quickly revert state. --- cis.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/cis.go b/cis.go index 872168c..142d631 100644 --- a/cis.go +++ b/cis.go @@ -29,7 +29,9 @@ func sshListener(portNumber int, done chan bool) { contextHierarchy["#"] = ">" contextHierarchy[">"] = "exit" - hostname := "cisgo1000v" + defaultHostname := "cisgo1000v" + defaultContextState := ">" + hostname := defaultHostname password := "admin" specialCommands["show version"] = `Cisco IOS XE Software, Version 16.04.01 @@ -74,7 +76,8 @@ Processor board ID 9FKLJWM5EB0 3985132K bytes of physical memory. 7774207K bytes of virtual hard disk at bootflash:. 0K bytes of at webui:. -Configuration register is 0x2102` +Configuration register is 0x2102 +` basicCommands["show ip interface brief"] = `Interface IP-Address OK? Method Status Protocol FastEthernet0/0 10.0.2.27 YES NVRAM up up @@ -276,7 +279,7 @@ end // io.WriteString(s, fmt.Sprintf(SHOW_VERSION_PAGING_DISABLED)) term := terminal.NewTerminal(s, hostname+contextSearch["base"]) - contextState := ">" + contextState := defaultContextState for { line, err := term.ReadLine() if err != nil { @@ -285,7 +288,13 @@ end response := line log.Println(line) - if basicCommands[response] != "" { + if response == "reset state" { + log.Println("resetting internal state") + contextState = defaultContextState + hostname = defaultHostname + term.SetPrompt(hostname + contextState) + + } else if basicCommands[response] != "" { // lookup supported commands for response term.Write(append([]byte(basicCommands[response]), '\n')) @@ -318,6 +327,9 @@ end hostname = strings.Join(fields[1:], " ") log.Printf("Setting hostname to %s\n", hostname) term.SetPrompt(hostname + contextState) + + } else { + term.Write(append([]byte("% Ambiguous command: \""+response+"\""), '\n')) } } else { From b29644bbc6ccf6696d61fee04872bc08ecc7520f Mon Sep 17 00:00:00 2001 From: Will George Date: Wed, 27 Jan 2021 11:10:19 -0500 Subject: [PATCH 4/5] re-add support for 'reset state' command --- fakedevices/genericFakeDevice.go | 4 +++- ssh_server/handlers/ciscohandlers.go | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/fakedevices/genericFakeDevice.go b/fakedevices/genericFakeDevice.go index 1374a6f..78a3ba3 100644 --- a/fakedevices/genericFakeDevice.go +++ b/fakedevices/genericFakeDevice.go @@ -15,6 +15,7 @@ type FakeDevice struct { Vendor string // Vendor of this fake device Platform string // Platform of this fake device Hostname string // Hostname of the fake device + DefaultHostname string // Default Hostname of the fake device (for resetting) Password string // Password of the fake device SupportedCommands SupportedCommands // What commands this fake device supports ContextSearch map[string]string // The available CLI prompt/contexts on this fake device @@ -69,7 +70,8 @@ func InitGeneric( myFakeDevice := FakeDevice{ Vendor: vendor, Platform: platform, - Hostname: deviceHostname, + Hostname: deviceHostname, + DefaultHostname: deviceHostname, Password: devicePassword, SupportedCommands: supportedCommands, ContextSearch: contextSearch, diff --git a/ssh_server/handlers/ciscohandlers.go b/ssh_server/handlers/ciscohandlers.go index 34ca11c..20783ea 100644 --- a/ssh_server/handlers/ciscohandlers.go +++ b/ssh_server/handlers/ciscohandlers.go @@ -68,6 +68,14 @@ func GenericCiscoHandler(myFakeDevice *fakedevices.FakeDevice) { ContextState = myFakeDevice.ContextHierarchy[ContextState] continue } + } else if userInput == "reset state" { + term.Write(append([]byte("Resetting State..."), '\n')) + ContextState = myFakeDevice.ContextSearch["base"] + myFakeDevice.Hostname = myFakeDevice.DefaultHostname + term.SetPrompt(string( + myFakeDevice.Hostname + ContextState, + )) + continue } // Split user input into fields @@ -108,7 +116,7 @@ func GenericCiscoHandler(myFakeDevice *fakedevices.FakeDevice) { continue } else { // If all else fails, we did not recognize the input! - term.Write(append([]byte("% Ambiguous command: \""+userInput+"\""), '\n')) + term.Write(append([]byte("% Unknown command: \""+userInput+"\""), '\n')) continue } } From 9de0ada39bd3b71222aecadba29867dfe12eb232 Mon Sep 17 00:00:00 2001 From: Will George Date: Wed, 27 Jan 2021 11:17:13 -0500 Subject: [PATCH 5/5] tabs v spaces --- fakedevices/genericFakeDevice.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fakedevices/genericFakeDevice.go b/fakedevices/genericFakeDevice.go index 78a3ba3..9c34f3c 100644 --- a/fakedevices/genericFakeDevice.go +++ b/fakedevices/genericFakeDevice.go @@ -70,8 +70,8 @@ func InitGeneric( myFakeDevice := FakeDevice{ Vendor: vendor, Platform: platform, - Hostname: deviceHostname, - DefaultHostname: deviceHostname, + Hostname: deviceHostname, + DefaultHostname: deviceHostname, Password: devicePassword, SupportedCommands: supportedCommands, ContextSearch: contextSearch,