Skip to content

Commit f747169

Browse files
committed
Added tooling to get debug switch output.
1 parent 8f51981 commit f747169

File tree

7 files changed

+325
-1
lines changed

7 files changed

+325
-1
lines changed

cmd/debugswitch/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# debugswitch
2+
An application that connects to a Cisco switch, enables l2t debug logging,
3+
and then prints all debug output.

cmd/debugswitch/main.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"golang.org/x/crypto/ssh"
7+
"log"
8+
"os/user"
9+
"time"
10+
11+
"github.com/chrismarget/cisco-l2t/foozler"
12+
"github.com/stephen-fox/sshutil"
13+
"github.com/stephen-fox/userutil"
14+
)
15+
16+
func main() {
17+
address := flag.String("a", "", "The address")
18+
flag.Parse()
19+
20+
username, err := userutil.GetUserInput("Username", userutil.PromptOptions{
21+
ShouldHideInput: true,
22+
})
23+
if err != nil {
24+
log.Fatalln(err.Error())
25+
}
26+
if len(username) == 0 {
27+
u, err := user.Current()
28+
if err != nil {
29+
log.Fatalln(err.Error())
30+
}
31+
32+
username = u.Username
33+
}
34+
35+
password, err := userutil.GetUserInput("Password", userutil.PromptOptions{
36+
ShouldHideInput: true,
37+
})
38+
39+
onHostKey := func(i sshutil.SSHHostKeyPromptInfo) bool {
40+
b, _ := userutil.GetYesOrNoUserInput(i.UserFacingPrompt, userutil.PromptOptions{})
41+
return b
42+
}
43+
44+
clientConfig := &ssh.ClientConfig{
45+
User: username,
46+
Auth: []ssh.AuthMethod{
47+
ssh.Password(password),
48+
},
49+
HostKeyCallback: sshutil.ImitateSSHClientHostKeyCallBack(onHostKey),
50+
Timeout: 10 * time.Second,
51+
}
52+
53+
clientConfig.Ciphers = append(clientConfig.Ciphers, "aes128-cbc")
54+
55+
d, err := foozler.ConnectTo(*address, 22, clientConfig)
56+
if err != nil {
57+
log.Fatalln(err.Error())
58+
}
59+
defer d.Close()
60+
61+
log.Println("ready")
62+
d.Enable()
63+
64+
for {
65+
select {
66+
case err := <-d.Wait():
67+
if err != nil {
68+
log.Fatalf("session ended - %s", err.Error())
69+
}
70+
return
71+
case s := <-d.Output():
72+
fmt.Println(s)
73+
}
74+
}
75+
}

foozler/doc.go

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Package foozler provides functionality for debugging a Cisco switch running
2+
// l2t. In particular, this some tooling to get debug output (the "fooz") into
3+
// another application (the "ler" action).
4+
package foozler

foozler/output.go

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package foozler
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"golang.org/x/crypto/ssh"
7+
"io"
8+
"strings"
9+
"time"
10+
)
11+
12+
// Debugee represents a Cisco switch that runs l2t. In this case - as a
13+
// a debug target. Upon connection, the target switch will be configured
14+
// to produce l2t debug output.
15+
type Debugee struct {
16+
session *ssh.Session
17+
stdin io.WriteCloser
18+
enable chan bool
19+
out chan string
20+
onDeath chan error
21+
stop chan chan struct{}
22+
}
23+
24+
// Enable enables output from the switch.
25+
func (o *Debugee) Enable() {
26+
o.enable <- true
27+
}
28+
29+
// Disable disables output from the switch.
30+
func (o *Debugee) Disable() {
31+
o.enable <- false
32+
}
33+
34+
// Wait returns a channel that receives nil, or an error when the SSH
35+
// session ends.
36+
func (o *Debugee) Wait() <-chan error {
37+
return o.onDeath
38+
}
39+
40+
// Output returns a channel that receives debug output from a switch
41+
// when output is enabled.
42+
func (o *Debugee) Output() <-chan string {
43+
return o.out
44+
}
45+
46+
// Close closes the SSH session.
47+
func (o *Debugee) Close() {
48+
rejoin := make(chan struct{})
49+
o.stop <- rejoin
50+
<-rejoin
51+
}
52+
53+
// Execute executes a command on the switch.
54+
func (o *Debugee) Execute(command string) error {
55+
_, err := io.WriteString(o.stdin, fmt.Sprintf("%s\r\n", command))
56+
if err != nil {
57+
return err
58+
}
59+
60+
return nil
61+
}
62+
63+
// ConnectTo connects to a Cisco switch via SSH to facilitate debugging.
64+
func ConnectTo(address string, port int, clientConfig *ssh.ClientConfig) (*Debugee, error) {
65+
client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", address, port), clientConfig)
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
session, err := client.NewSession()
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
sshIn, err := session.StdinPipe()
76+
if err != nil {
77+
return nil, err
78+
}
79+
80+
sshOut, err := session.StdoutPipe()
81+
if err != nil {
82+
return nil, err
83+
}
84+
85+
sshErr, err := session.StderrPipe()
86+
if err != nil {
87+
return nil, err
88+
}
89+
90+
onSessionDeath := make(chan error, 2)
91+
rawOutput := make(chan []byte)
92+
go func() {
93+
scanner := bufio.NewScanner(io.MultiReader(sshOut, sshErr))
94+
95+
for scanner.Scan() {
96+
rawOutput <- scanner.Bytes()
97+
}
98+
99+
err := scanner.Err()
100+
if err != nil {
101+
onSessionDeath <- fmt.Errorf("stderr/stdout scanner exited - %s", err.Error())
102+
}
103+
}()
104+
105+
d := &Debugee{
106+
session: session,
107+
stdin: sshIn,
108+
enable: make(chan bool),
109+
out: make(chan string, 1),
110+
onDeath: onSessionDeath,
111+
stop: make(chan chan struct{}),
112+
}
113+
114+
go func() {
115+
discardIntialLines := 9
116+
discardTimeout := time.NewTimer(5 * time.Second)
117+
keepAliveTicker := time.NewTicker(10 * time.Minute)
118+
119+
isEnabled := false
120+
for {
121+
select {
122+
case <-keepAliveTicker.C:
123+
d.Execute("show clock")
124+
case isEnabled = <-d.enable:
125+
case <-discardTimeout.C:
126+
discardIntialLines = 0
127+
case raw := <-rawOutput:
128+
if discardIntialLines > 0 {
129+
discardIntialLines--
130+
continue
131+
}
132+
if isEnabled {
133+
d.out <- removeTimestamp(string(raw))
134+
}
135+
case rejoin := <-d.stop:
136+
client.Close()
137+
keepAliveTicker.Stop()
138+
rejoin <- struct{}{}
139+
return
140+
}
141+
}
142+
}()
143+
144+
err = session.Shell()
145+
if err != nil {
146+
return nil, err
147+
}
148+
149+
err = d.Execute("no debug all")
150+
if err != nil {
151+
return nil, err
152+
}
153+
154+
err = d.Execute("debug l2trace")
155+
if err != nil {
156+
return nil, err
157+
}
158+
159+
err = d.Execute("terminal monitor")
160+
if err != nil {
161+
return nil, err
162+
}
163+
164+
go func() {
165+
onSessionDeath <- session.Wait()
166+
}()
167+
168+
return d, nil
169+
}
170+
171+
func removeTimestamp(s string) string {
172+
if strings.Count(s, ":") < 3 {
173+
return s
174+
}
175+
176+
firstSpace := strings.Index(s, " ")
177+
if firstSpace < 0 || firstSpace == len(s)-1 {
178+
return s
179+
}
180+
181+
secondSpace := strings.Index(s, " ")
182+
if secondSpace < 0 || secondSpace == len(s)-1 || secondSpace - firstSpace > 2 {
183+
return s
184+
}
185+
186+
end := strings.Index(s, ": ")
187+
if end - secondSpace < 10 || end - secondSpace > 20 {
188+
return s
189+
}
190+
191+
if end + 2 > len(s) {
192+
return s[end+1:]
193+
}
194+
195+
return s[end+2:]
196+
}

foozler/output_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package foozler
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestWhatever(t *testing.T) {
8+
s := removeTimestamp("Aug 21 15:45:09.385: trace_request->src_mac : ffff.ffff.ffff")
9+
10+
exp := "trace_request->src_mac : ffff.ffff.ffff"
11+
if s != exp {
12+
t.Fatalf("expected '%s', got '%s'", exp, s)
13+
}
14+
}

go.mod

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@ module github.com/chrismarget/cisco-l2t
22

33
go 1.12
44

5-
require github.com/cheggaaa/pb/v3 v3.0.1 // indirect
5+
require (
6+
github.com/cheggaaa/pb/v3 v3.0.1 // indirect
7+
github.com/stephen-fox/sshutil v0.0.1 // indirect
8+
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
9+
)

go.sum

+28
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,40 @@ github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdc
22
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
33
github.com/cheggaaa/pb/v3 v3.0.1 h1:m0BngUk2LuSRYdx4fujDKNRXNDpbNCfptPfVT2m6OJY=
44
github.com/cheggaaa/pb/v3 v3.0.1/go.mod h1:SqqeMF/pMOIu3xgGoxtPYhMNQP258xE4x/XRTYua+KU=
5+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
56
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
67
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
8+
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
9+
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
710
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
811
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
912
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
1013
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
1114
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
1215
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
16+
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
17+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
18+
github.com/pkg/sftp v1.8.3 h1:9jSe2SxTM8/3bXZjtqnkgTBW+lA8db0knZJyns7gpBA=
19+
github.com/pkg/sftp v1.8.3/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
20+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
21+
github.com/stephen-fox/sshutil v0.0.1 h1:GIHJ3v8JaHJyU13XQpTs1SSV3241VdDwS01/9SLCA5Q=
22+
github.com/stephen-fox/sshutil v0.0.1/go.mod h1:2FU9C9w35aujfQPwpMT85p3WHutFaEQFAN3JRKmbUSs=
23+
github.com/stephen-fox/userutil v1.0.0 h1:6+4m8M3q7UAY16aoITQhuXbZaHC8w3fiviQ286aOJLI=
24+
github.com/stephen-fox/userutil v1.0.0/go.mod h1:7TFao7wHR7gZSsflVw3FS2raXLMsb+woUeG5NQPtsAA=
25+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
26+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
27+
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
28+
golang.org/x/crypto v0.0.0-20190122013713-64072686203f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
29+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
30+
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
31+
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
32+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
33+
golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
34+
golang.org/x/sys v0.0.0-20190122071731-054c452bb702/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
35+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1336
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
37+
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
38+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
39+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
40+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
41+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

0 commit comments

Comments
 (0)