-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
392 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,106 @@ | ||
# cssh | ||
Tool to connect to (recent) Cisco switches and accesspoints and execute commands. | ||
|
||
Tested on: | ||
- Cisco Nexus devices (n2k/n5k/n7k/n9k) | ||
- A lot of Cisco Catalyst types (2960, 3750, ..) | ||
- A lot of Cisco Aironet APs | ||
|
||
## use cases | ||
* configure a lot of devices easy and at the same time | ||
* get realtime output from a specified list of switches (e.g searching a mac address) | ||
* run simple commands without an interactive shell | ||
|
||
## building | ||
set your GOPATH correct (https://golang.org/doc/code.html#GOPATH) | ||
|
||
``` | ||
git clone https://github.com/42wim/cssh.git | ||
go get | ||
go build | ||
``` | ||
|
||
## usage | ||
|
||
``` | ||
$ ./cssh | ||
-cmd="": single command to execute | ||
-cmd-from="": file containing commands to execute | ||
-host="": host | ||
-host-from="": file containing hosts | ||
``` | ||
|
||
|
||
## config | ||
cssh looks for cssh.conf in current directory. | ||
This config file is necessary because it contains the credentials. | ||
|
||
Format is | ||
``` | ||
[device "yourswitch"] #yourswitch can be a FQDN or an IP or an regular expression | ||
username=admin | ||
password=abc | ||
enable=abc | ||
``` | ||
|
||
E.g. | ||
``` | ||
[device "switch-building-x-*"] | ||
username=admin | ||
password=abc | ||
enable=abc | ||
``` | ||
|
||
Also see cssh.conf.dist | ||
|
||
## examples | ||
### 1 host and 1 command | ||
``` | ||
$ cssh -host lab-switch-1 -cmd "sh clock" | ||
sh clock | ||
23:41:27.572 CET Wed Mar 11 2015 | ||
lab-switch-1# | ||
``` | ||
|
||
### 1 host and multiple commands | ||
see examples/cmd-configexample for the contents | ||
|
||
``` | ||
$ cssh -host lab-switch-1 -cmd-from cmd-configexample | ||
conf t | ||
Enter configuration commands, one per line. End with CNTL/Z. | ||
lab-switch-1(config)#interface GigabitEthernet0/2 | ||
lab-switch-1(config-if)#switchport trunk allowed vlan 234,336,337,356,445,488 | ||
lab-switch-1(config-if)#switchport mode trunk | ||
lab-switch-1(config-if)#switchport nonegotiate | ||
lab-switch-1(config-if)#switchport protected | ||
lab-switch-1(config-if)#storm-control broadcast level 50.00 30.00 | ||
lab-switch-1(config-if)#storm-control action trap | ||
lab-switch-1(config-if)#no cdp enable | ||
lab-switch-1(config-if)#spanning-tree portfast | ||
lab-switch-1(config-if)#spanning-tree bpduguard enable | ||
lab-switch-1(config-if)#ip dhcp snooping limit rate 30 | ||
lab-switch-1(config-if)#exit | ||
lab-switch-1(config)#exit | ||
lab-switch-1# | ||
``` | ||
|
||
### multiple hosts and 1 command | ||
labswitches contains | ||
``` | ||
lab-switch-1 | ||
lab-switch-2 | ||
``` | ||
|
||
``` | ||
$ cssh -host-from labswitches -cmd "sh clock" | ||
sh clock | ||
*22:45:16.946 UTC Wed Mar 11 2015 | ||
lab-switch-1# | ||
sh clock | ||
23:45:18.604 CET Wed Mar 11 2015 | ||
lab-switch-2# | ||
``` | ||
|
||
### multiple hosts and multiple commands | ||
left as an exercise for the reader |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package main | ||
|
||
import ( | ||
"code.google.com/p/gcfg" | ||
"io/ioutil" | ||
"log" | ||
"regexp" | ||
) | ||
|
||
type Credentials struct { | ||
Username string | ||
Password string | ||
Enable string | ||
} | ||
|
||
type config struct { | ||
Device map[string]*struct { | ||
Username string | ||
Password string | ||
Enable string | ||
} | ||
} | ||
|
||
func readConfig(cfgfile string) config { | ||
var cfg config | ||
content, err := ioutil.ReadFile(cfgfile) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
err = gcfg.ReadStringInto(&cfg, string(content)) | ||
if err != nil { | ||
log.Fatal("Failed to parse "+cfgfile+":", err) | ||
} | ||
return cfg | ||
} | ||
|
||
func getCredentials(host string) Credentials { | ||
var cred Credentials | ||
for device, _ := range cfg.Device { | ||
re := regexp.MustCompile(device) | ||
if re.MatchString(host) { | ||
cred.Username = cfg.Device[device].Username | ||
cred.Password = cfg.Device[device].Password | ||
cred.Enable = cfg.Device[device].Enable | ||
break | ||
} | ||
} | ||
return cred | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[device "switch-building-x-*"] | ||
username=admin | ||
password=abc | ||
enable=abc | ||
|
||
[device "lwap-*"] | ||
username=admin | ||
password=abc | ||
enable=abc | ||
|
||
[device "customer1-switch-*"] | ||
username=admin | ||
password=abc | ||
enable=abc | ||
|
||
[device "lab-*"] | ||
username=cisco | ||
password=Cisco | ||
enable=Cisco |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"github.com/42wim/cssh/device" | ||
"io/ioutil" | ||
"log" | ||
"strings" | ||
"time" | ||
) | ||
|
||
var host string | ||
var cmd string | ||
var cmdfrom string | ||
var hostfrom string | ||
var cfg config | ||
|
||
func init() { | ||
flag.StringVar(&host, "host", "", "host") | ||
flag.StringVar(&cmd, "cmd", "", "single command to execute") | ||
flag.StringVar(&cmdfrom, "cmd-from", "", "file containing commands to execute") | ||
flag.StringVar(&hostfrom, "host-from", "", "file containing hosts") | ||
flag.Parse() | ||
cfg = readConfig("cssh.conf") | ||
} | ||
|
||
func doCmd(d *device.CiscoDevice) { | ||
d.Cmd("terminal length 0") | ||
fmt.Print(d.Cmd(cmd)) | ||
fmt.Println() | ||
} | ||
|
||
func doCmdFrom(d *device.CiscoDevice) { | ||
dat, _ := ioutil.ReadFile(cmdfrom) | ||
lines := strings.Split(string(dat), "\n") | ||
d.Cmd("terminal length 0") | ||
for _, line := range lines { | ||
if line != "" { | ||
fmt.Print(d.Cmd(line)) | ||
} | ||
} | ||
fmt.Println() | ||
} | ||
|
||
func doHost(host string, c chan int) { | ||
if host != "" { | ||
//fmt.Println("connecting to " + host) | ||
cred := getCredentials(host) | ||
if cred.Username == "" { | ||
fmt.Println("couldn't get credentials for " + host) | ||
c <- 2 | ||
return | ||
} | ||
d := &device.CiscoDevice{Hostname: host, Username: cred.Username, Password: cred.Password, Enable: cred.Enable} | ||
err := d.Connect() | ||
if err != nil { | ||
log.Println("couldn't connect", err.Error()) | ||
c <- 1 | ||
return | ||
} else { | ||
defer d.Close() | ||
if cmdfrom != "" { | ||
doCmdFrom(d) | ||
} | ||
if cmd != "" { | ||
doCmd(d) | ||
} | ||
c <- 1 | ||
} | ||
} | ||
} | ||
|
||
func main() { | ||
if host == "" && hostfrom == "" { | ||
flag.PrintDefaults() | ||
return | ||
} | ||
if hostfrom != "" { | ||
dat, _ := ioutil.ReadFile(hostfrom) | ||
lines := strings.Split(string(dat), "\n") | ||
// make buffers | ||
c := make(chan int, len(lines)-1) | ||
for _, host := range lines[0 : len(lines)-1] { | ||
time.Sleep(time.Millisecond * 50) | ||
go doHost(host, c) | ||
} | ||
// wait for all goroutines | ||
for i := 0; i < len(lines)-1; i++ { | ||
<-c | ||
} | ||
return | ||
} | ||
if (cmd != "" || cmdfrom != "") && host != "" { | ||
c := make(chan int, 1) | ||
go doHost(host, c) | ||
<-c | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package device | ||
|
||
import ( | ||
"bufio" | ||
"github.com/ScriptRock/crypto/ssh" | ||
"io" | ||
"log" | ||
"regexp" | ||
"strings" | ||
"time" | ||
) | ||
|
||
type CiscoDevice struct { | ||
Username string | ||
Password string | ||
Enable string | ||
name string | ||
Hostname string | ||
stdin io.WriteCloser | ||
stdout io.Reader | ||
session *ssh.Session | ||
} | ||
|
||
func (d *CiscoDevice) Connect() error { | ||
config := &ssh.ClientConfig{ | ||
User: d.Username, | ||
Auth: []ssh.AuthMethod{ | ||
ssh.Password(d.Password), | ||
}, | ||
Config: ssh.Config{ | ||
Ciphers: ssh.AllSupportedCiphers(), | ||
}, | ||
} | ||
client, err := ssh.Dial("tcp", d.Hostname+":22", config) | ||
if err != nil { | ||
return err | ||
} | ||
session, err := client.NewSession() | ||
if err != nil { | ||
return err | ||
} | ||
d.stdin, _ = session.StdinPipe() | ||
d.stdout, _ = session.StdoutPipe() | ||
modes := ssh.TerminalModes{ | ||
ssh.ECHO: 0, // disable echoing | ||
ssh.OCRNL: 0, | ||
ssh.TTY_OP_ISPEED: 38400, // input speed = 14.4kbaud | ||
ssh.TTY_OP_OSPEED: 38400, // output speed = 14.4kbaud | ||
} | ||
session.RequestPty("vt100", 0, 2000, modes) | ||
session.Shell() | ||
d.init() | ||
d.session = session | ||
return nil | ||
} | ||
|
||
func (d *CiscoDevice) Close() { | ||
d.session.Close() | ||
} | ||
|
||
func (d *CiscoDevice) Cmd(cmd string) string { | ||
bufstdout := bufio.NewReader(d.stdout) | ||
io.WriteString(d.stdin, cmd+"\n") | ||
time.Sleep(time.Millisecond * 100) | ||
return strings.Replace(d.readln(bufstdout), "\r", "", -1) | ||
} | ||
|
||
func (d *CiscoDevice) init() { | ||
bufstdout := bufio.NewReader(d.stdout) | ||
io.WriteString(d.stdin, "enable\n") | ||
time.Sleep(time.Millisecond * 100) | ||
re := regexp.MustCompile("assword:") | ||
buf := make([]byte, 1000) | ||
loadStr := "" | ||
for { | ||
n, err := bufstdout.Read(buf) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
loadStr += string(buf[:n]) | ||
if re.MatchString(loadStr) { | ||
io.WriteString(d.stdin, d.Enable+"\n") | ||
break | ||
} else { | ||
break | ||
} | ||
} | ||
} | ||
|
||
func (d *CiscoDevice) readln(r *bufio.Reader) string { | ||
re := regexp.MustCompile(".*?#.?$") | ||
buf := make([]byte, 1000) | ||
loadStr := "" | ||
for { | ||
n, err := r.Read(buf) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
loadStr += string(buf[:n]) | ||
if re.MatchString(loadStr) { | ||
break | ||
} | ||
} | ||
return loadStr | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
conf t | ||
interface GigabitEthernet0/2 | ||
switchport trunk allowed vlan 234,336,337,356,445,488 | ||
switchport mode trunk | ||
switchport nonegotiate | ||
switchport protected | ||
storm-control broadcast level 50.00 30.00 | ||
storm-control action trap | ||
no cdp enable | ||
spanning-tree portfast | ||
spanning-tree bpduguard enable | ||
ip dhcp snooping limit rate 30 | ||
exit | ||
exit |
Oops, something went wrong.