mirror of
https://github.com/shanghai-edu/multissh.git
synced 2025-12-16 13:27:44 +00:00
first commit
This commit is contained in:
commit
9e410eaa21
6 changed files with 545 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
*.txt
|
||||
*.json
|
||||
*.exe
|
||||
104
README.MD
Normal file
104
README.MD
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
## multissh
|
||||
|
||||
一个简单的并行 SSH 工具,可以批量的对主机通过 SSH 执行命令组合。
|
||||
|
||||
#### 编译
|
||||
```
|
||||
go get ./...
|
||||
go build
|
||||
```
|
||||
|
||||
|
||||
#### 命令体系
|
||||
```
|
||||
./multissh -h
|
||||
-cmd string
|
||||
cmds // 需要执行的命令组合,多条命令以 ; 分割
|
||||
-cmdfile string
|
||||
cmdfile path //需要执行的命令组合文件,文件内命令按行分割
|
||||
-hostfile string
|
||||
hostfile path // 需要执行的主机列表文件,主机列表在文件内按行分割
|
||||
-hosts string
|
||||
host address list //需要执行的主机列表,多个主机以 ; 分割
|
||||
-ipfile string
|
||||
hostfile path //需要执行的主机(IP)列表文件,IP可以以地址段的方式逐行写在文本内
|
||||
-p string
|
||||
password // 主机的 SSH 密码
|
||||
-port int
|
||||
ssh port (default 22) //主机的 SSH 端口,默认 22
|
||||
-u string
|
||||
username //主机的 SSH 用户名
|
||||
```
|
||||
**cmdfile 示例**
|
||||
```
|
||||
show clock
|
||||
exit
|
||||
```
|
||||
**hostfile 示例**
|
||||
```
|
||||
10.10.15.101
|
||||
10.10.15.102
|
||||
```
|
||||
**ipfile 示例**
|
||||
```
|
||||
10.10.15.101-10.10.15.102
|
||||
```
|
||||
|
||||
## 用法
|
||||
#### cmd string & host string
|
||||
```
|
||||
./multissh -cmd "show clock;exit" -hosts "10.10.15.101;10.10.15.102" -u admin -p admin
|
||||
|
||||
10.10.15.101 ssh start
|
||||
sw-1#show clock
|
||||
05:26:40.649 UTC Tue Jun 6 2017
|
||||
sw-1#exit
|
||||
|
||||
10.10.15.101 ssh end
|
||||
|
||||
10.10.15.102 ssh start
|
||||
sw-2#show clock
|
||||
05:24:38.708 UTC Tue Jun 6 2017
|
||||
sw-2#exit
|
||||
|
||||
10.10.15.102 ssh end
|
||||
```
|
||||
|
||||
#### cmdfile & hostfile
|
||||
```
|
||||
./multissh -cmdfile cmd.txt -hostfile host.txt -u admin -p admin
|
||||
|
||||
10.10.15.101 ssh start
|
||||
sw-1#show clock
|
||||
05:29:43.269 UTC Tue Jun 6 2017
|
||||
sw-1#exit
|
||||
|
||||
10.10.15.101 ssh end
|
||||
|
||||
10.10.15.102 ssh start
|
||||
sw-2#show clock
|
||||
05:27:41.332 UTC Tue Jun 6 2017
|
||||
sw-2#exit
|
||||
|
||||
10.10.15.102 ssh end
|
||||
```
|
||||
|
||||
#### ipfile
|
||||
```
|
||||
./multissh -cmdfile cmd.txt -ipfile ip.txt -u admin -p admin
|
||||
|
||||
10.10.15.101 ssh start
|
||||
sw-1#show clock
|
||||
05:29:43.269 UTC Tue Jun 6 2017
|
||||
sw-1#exit
|
||||
|
||||
10.10.15.101 ssh end
|
||||
|
||||
10.10.15.102 ssh start
|
||||
sw-2#show clock
|
||||
05:27:41.332 UTC Tue Jun 6 2017
|
||||
sw-2#exit
|
||||
|
||||
10.10.15.102 ssh end
|
||||
```
|
||||
|
||||
185
cfg.go
Normal file
185
cfg.go
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetfileAll(filePath string) ([]byte, error) {
|
||||
result, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
log.Println("read file ", filePath, err)
|
||||
return result, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func Getfile(filePath string) ([]string, error) {
|
||||
result := []string{}
|
||||
b, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
log.Println("read file ", filePath, err)
|
||||
return result, err
|
||||
}
|
||||
s := string(b)
|
||||
for _, lineStr := range strings.Split(s, "\n") {
|
||||
lineStr = strings.TrimSpace(lineStr)
|
||||
if lineStr == "" {
|
||||
continue
|
||||
}
|
||||
result = append(result, lineStr)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetIpList(filePath string) ([]string, error) {
|
||||
res, err := Getfile(filePath)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
var allIp []string
|
||||
if len(res) > 0 {
|
||||
for _, sip := range res {
|
||||
aip := ParseIp(sip)
|
||||
for _, ip := range aip {
|
||||
allIp = append(allIp, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
return allIp, nil
|
||||
}
|
||||
|
||||
func ParseIp(ip string) []string {
|
||||
var availableIPs []string
|
||||
// if ip is "1.1.1.1/",trim /
|
||||
ip = strings.TrimRight(ip, "/")
|
||||
if strings.Contains(ip, "/") == true {
|
||||
if strings.Contains(ip, "/32") == true {
|
||||
aip := strings.Replace(ip, "/32", "", -1)
|
||||
availableIPs = append(availableIPs, aip)
|
||||
} else {
|
||||
availableIPs = GetAvailableIP(ip)
|
||||
}
|
||||
} else if strings.Contains(ip, "-") == true {
|
||||
ipRange := strings.SplitN(ip, "-", 2)
|
||||
availableIPs = GetAvailableIPRange(ipRange[0], ipRange[1])
|
||||
} else {
|
||||
availableIPs = append(availableIPs, ip)
|
||||
}
|
||||
return availableIPs
|
||||
}
|
||||
|
||||
func GetAvailableIPRange(ipStart, ipEnd string) []string {
|
||||
var availableIPs []string
|
||||
|
||||
firstIP := net.ParseIP(ipStart)
|
||||
endIP := net.ParseIP(ipEnd)
|
||||
if firstIP.To4() == nil || endIP.To4() == nil {
|
||||
return availableIPs
|
||||
}
|
||||
firstIPNum := ipToInt(firstIP.To4())
|
||||
EndIPNum := ipToInt(endIP.To4())
|
||||
pos := int32(1)
|
||||
|
||||
newNum := firstIPNum
|
||||
|
||||
for newNum <= EndIPNum {
|
||||
availableIPs = append(availableIPs, intToIP(newNum).String())
|
||||
newNum = newNum + pos
|
||||
}
|
||||
return availableIPs
|
||||
}
|
||||
|
||||
func GetAvailableIP(ipAndMask string) []string {
|
||||
var availableIPs []string
|
||||
|
||||
ipAndMask = strings.TrimSpace(ipAndMask)
|
||||
ipAndMask = IPAddressToCIDR(ipAndMask)
|
||||
_, ipnet, _ := net.ParseCIDR(ipAndMask)
|
||||
|
||||
firstIP, _ := networkRange(ipnet)
|
||||
ipNum := ipToInt(firstIP)
|
||||
size := networkSize(ipnet.Mask)
|
||||
pos := int32(1)
|
||||
max := size - 2 // -1 for the broadcast address, -1 for the gateway address
|
||||
|
||||
var newNum int32
|
||||
for attempt := int32(0); attempt < max; attempt++ {
|
||||
newNum = ipNum + pos
|
||||
pos = pos%max + 1
|
||||
availableIPs = append(availableIPs, intToIP(newNum).String())
|
||||
}
|
||||
return availableIPs
|
||||
}
|
||||
|
||||
func IPAddressToCIDR(ipAdress string) string {
|
||||
if strings.Contains(ipAdress, "/") == true {
|
||||
ipAndMask := strings.Split(ipAdress, "/")
|
||||
ip := ipAndMask[0]
|
||||
mask := ipAndMask[1]
|
||||
if strings.Contains(mask, ".") == true {
|
||||
mask = IPMaskStringToCIDR(mask)
|
||||
}
|
||||
return ip + "/" + mask
|
||||
} else {
|
||||
return ipAdress
|
||||
}
|
||||
}
|
||||
|
||||
func IPMaskStringToCIDR(netmask string) string {
|
||||
netmaskList := strings.Split(netmask, ".")
|
||||
var mint []int
|
||||
for _, v := range netmaskList {
|
||||
strv, _ := strconv.Atoi(v)
|
||||
mint = append(mint, strv)
|
||||
}
|
||||
myIPMask := net.IPv4Mask(byte(mint[0]), byte(mint[1]), byte(mint[2]), byte(mint[3]))
|
||||
ones, _ := myIPMask.Size()
|
||||
return strconv.Itoa(ones)
|
||||
}
|
||||
|
||||
func IPMaskCIDRToString(one string) string {
|
||||
oneInt, _ := strconv.Atoi(one)
|
||||
mIPmask := net.CIDRMask(oneInt, 32)
|
||||
var maskstring []string
|
||||
for _, v := range mIPmask {
|
||||
maskstring = append(maskstring, strconv.Itoa(int(v)))
|
||||
}
|
||||
return strings.Join(maskstring, ".")
|
||||
}
|
||||
|
||||
// Calculates the first and last IP addresses in an IPNet
|
||||
func networkRange(network *net.IPNet) (net.IP, net.IP) {
|
||||
netIP := network.IP.To4()
|
||||
firstIP := netIP.Mask(network.Mask)
|
||||
lastIP := net.IPv4(0, 0, 0, 0).To4()
|
||||
for i := 0; i < len(lastIP); i++ {
|
||||
lastIP[i] = netIP[i] | ^network.Mask[i]
|
||||
}
|
||||
return firstIP, lastIP
|
||||
}
|
||||
|
||||
// Given a netmask, calculates the number of available hosts
|
||||
func networkSize(mask net.IPMask) int32 {
|
||||
m := net.IPv4Mask(0, 0, 0, 0)
|
||||
for i := 0; i < net.IPv4len; i++ {
|
||||
m[i] = ^mask[i]
|
||||
}
|
||||
return int32(binary.BigEndian.Uint32(m)) + 1
|
||||
}
|
||||
|
||||
// Converts a 4 bytes IP into a 32 bit integer
|
||||
func ipToInt(ip net.IP) int32 {
|
||||
return int32(binary.BigEndian.Uint32(ip.To4()))
|
||||
}
|
||||
|
||||
// Converts 32 bit integer into a 4 bytes IP address
|
||||
func intToIP(n int32) net.IP {
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, uint32(n))
|
||||
return net.IP(b)
|
||||
}
|
||||
118
main.go
Normal file
118
main.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
// "github.com/bitly/go-simplejson"
|
||||
)
|
||||
|
||||
type sshhost struct {
|
||||
host string
|
||||
port int
|
||||
username string
|
||||
password string
|
||||
cmd []string
|
||||
}
|
||||
|
||||
func main() {
|
||||
hosts := flag.String("hosts", "", "host address list")
|
||||
cmd := flag.String("cmd", "", "cmds")
|
||||
username := flag.String("u", "", "username")
|
||||
password := flag.String("p", "", "password")
|
||||
port := flag.Int("port", 22, "ssh port")
|
||||
cmdfile := flag.String("cmdfile", "", "cmdfile path")
|
||||
hostfile := flag.String("hostfile", "", "hostfile path")
|
||||
ipfile := flag.String("ipfile", "", "hostfile path")
|
||||
cfg := flag.String("cfg", "", "cfg path")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
var cmdlist []string
|
||||
var hostlist []string
|
||||
var err error
|
||||
|
||||
sshhosts := []sshhost{}
|
||||
var host_struct sshhost
|
||||
|
||||
if *ipfile != "" {
|
||||
hostlist, err = GetIpList(*ipfile)
|
||||
if err != nil {
|
||||
log.Println("load hostlist error: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if *hostfile != "" {
|
||||
hostlist, err = Getfile(*hostfile)
|
||||
if err != nil {
|
||||
log.Println("load hostfile error: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if *hosts != "" {
|
||||
hostlist = strings.Split(*hosts, ";")
|
||||
|
||||
}
|
||||
|
||||
if *cmdfile != "" {
|
||||
cmdlist, err = Getfile(*cmdfile)
|
||||
if err != nil {
|
||||
log.Println("load cmdfile error: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if *cmd != "" {
|
||||
cmdlist = strings.Split(*cmd, ";")
|
||||
}
|
||||
|
||||
if *cfg == "" {
|
||||
for _, host := range hostlist {
|
||||
host_struct.host = host
|
||||
host_struct.username = *username
|
||||
host_struct.password = *password
|
||||
host_struct.port = *port
|
||||
host_struct.cmd = cmdlist
|
||||
sshhosts = append(sshhosts, host_struct)
|
||||
}
|
||||
}
|
||||
/*
|
||||
else {
|
||||
cfgjson, err := GetfileAll(*cfg)
|
||||
if err != nil {
|
||||
log.Println("load cfg error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
js, js_err := simplejson.NewJson(cfgjson)
|
||||
if js_err != nil {
|
||||
log.Println("json format error: ", js_err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
//fmt.Println(sshhosts)
|
||||
|
||||
chs := make([]chan string, len(sshhosts))
|
||||
for i, host := range sshhosts {
|
||||
chs[i] = make(chan string, 1)
|
||||
go dossh(host.username, host.password, host.host, host.cmd, host.port, chs[i])
|
||||
}
|
||||
for i, ch := range chs {
|
||||
fmt.Println(sshhosts[i].host, " ssh start")
|
||||
select {
|
||||
case res := <-ch:
|
||||
if res != "" {
|
||||
fmt.Println(res)
|
||||
}
|
||||
case <-time.After(30 * 1000 * 1000 * 1000):
|
||||
log.Println("SSH run timeout")
|
||||
}
|
||||
fmt.Println(sshhosts[i].host, " ssh end\n")
|
||||
}
|
||||
|
||||
}
|
||||
52
ssh_test.go
Normal file
52
ssh_test.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
// "bytes"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
username = ""
|
||||
password = ""
|
||||
ip = ""
|
||||
port = 22
|
||||
cmd = "date\n"
|
||||
)
|
||||
|
||||
func Test_SSH(t *testing.T) {
|
||||
session, err := connect(username, password, ip, port)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
//cmdlist := strings.Split(cmd, ";")
|
||||
|
||||
stdinBuf, err := session.StdinPipe()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
// var bt bytes.Buffer
|
||||
// session.Stdout = &bt
|
||||
t.Log(session.Stdout)
|
||||
t.Log(session.Stderr)
|
||||
session.Stdout = os.Stdout
|
||||
session.Stderr = os.Stderr
|
||||
session.Stdin = os.Stdin
|
||||
err = session.Shell()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
// for _, c := range cmdlist {
|
||||
// c = c + "\n"
|
||||
stdinBuf.Write([]byte(cmd))
|
||||
// }
|
||||
session.Wait()
|
||||
t.Error(err)
|
||||
// t.Log(bt.String())
|
||||
return
|
||||
}
|
||||
83
sshconnect.go
Normal file
83
sshconnect.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"net"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func connect(user, password, host string, port int) (*ssh.Session, error) {
|
||||
var (
|
||||
auth []ssh.AuthMethod
|
||||
addr string
|
||||
clientConfig *ssh.ClientConfig
|
||||
client *ssh.Client
|
||||
config ssh.Config
|
||||
session *ssh.Session
|
||||
err error
|
||||
)
|
||||
// get auth method
|
||||
auth = make([]ssh.AuthMethod, 0)
|
||||
auth = append(auth, ssh.Password(password))
|
||||
|
||||
config = ssh.Config{
|
||||
Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour"},
|
||||
}
|
||||
|
||||
clientConfig = &ssh.ClientConfig{
|
||||
User: user,
|
||||
Auth: auth,
|
||||
Timeout: 30 * time.Second,
|
||||
Config: config,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// connet to ssh
|
||||
addr = fmt.Sprintf("%s:%d", host, port)
|
||||
|
||||
if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create session
|
||||
if session, err = client.NewSession(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func dossh(username, password, ip string, cmdlist []string, port int, ch chan string) {
|
||||
session, err := connect(username, password, ip, port)
|
||||
if err != nil {
|
||||
ch <- fmt.Sprintf("<%s>", err.Error())
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// cmd := "ls;date;exit"
|
||||
|
||||
stdinBuf, _ := session.StdinPipe()
|
||||
|
||||
var outbt, errbt bytes.Buffer
|
||||
session.Stdout = &outbt
|
||||
|
||||
session.Stderr = &errbt
|
||||
|
||||
err = session.Shell()
|
||||
for _, c := range cmdlist {
|
||||
c = c + "\n"
|
||||
stdinBuf.Write([]byte(c))
|
||||
}
|
||||
session.Wait()
|
||||
ch <- (outbt.String() + errbt.String())
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue