|
|
@@ -5,34 +5,32 @@ import (
|
|
|
"fmt"
|
|
|
"net"
|
|
|
"os"
|
|
|
- "strings"
|
|
|
"time"
|
|
|
- "regexp"
|
|
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
)
|
|
|
|
|
|
// Client SSH客户端
|
|
|
type Client struct {
|
|
|
- client *ssh.Client
|
|
|
- timeout time.Duration
|
|
|
- host string
|
|
|
- port int
|
|
|
- username string
|
|
|
- password string
|
|
|
- keyFile string
|
|
|
- insecureCiphers bool
|
|
|
+ client *ssh.Client
|
|
|
+ timeout time.Duration
|
|
|
+ host string
|
|
|
+ port int
|
|
|
+ username string
|
|
|
+ password string
|
|
|
+ keyFile string
|
|
|
+ insecureCiphers bool
|
|
|
}
|
|
|
|
|
|
// Config SSH客户端配置
|
|
|
type Config struct {
|
|
|
- Host string
|
|
|
- Port int
|
|
|
- Username string
|
|
|
- Password string
|
|
|
- KeyFile string
|
|
|
- Timeout time.Duration
|
|
|
- InsecureCiphers bool // 启用不安全的加密算法(用于兼容老旧设备)
|
|
|
+ Host string
|
|
|
+ Port int
|
|
|
+ Username string
|
|
|
+ Password string
|
|
|
+ KeyFile string
|
|
|
+ Timeout time.Duration
|
|
|
+ InsecureCiphers bool // 启用不安全的加密算法(用于兼容老旧设备)
|
|
|
}
|
|
|
|
|
|
// NewClient 创建新的SSH客户端
|
|
|
@@ -41,24 +39,24 @@ func NewClient(config Config) *Client {
|
|
|
config.Port = 22
|
|
|
}
|
|
|
if config.Timeout == 0 {
|
|
|
- config.Timeout = 10 * time.Second
|
|
|
+ config.Timeout = 30 * time.Second
|
|
|
}
|
|
|
return &Client{
|
|
|
- host: config.Host,
|
|
|
- port: config.Port,
|
|
|
- username: config.Username,
|
|
|
- password: config.Password,
|
|
|
- keyFile: config.KeyFile,
|
|
|
- timeout: config.Timeout,
|
|
|
- insecureCiphers: config.InsecureCiphers,
|
|
|
+ host: config.Host,
|
|
|
+ port: config.Port,
|
|
|
+ username: config.Username,
|
|
|
+ password: config.Password,
|
|
|
+ keyFile: config.KeyFile,
|
|
|
+ timeout: config.Timeout,
|
|
|
+ insecureCiphers: config.InsecureCiphers,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Connect 连接到SSH服务器
|
|
|
func (c *Client) Connect() error {
|
|
|
config := &ssh.ClientConfig{
|
|
|
- User: c.username,
|
|
|
- Auth: []ssh.AuthMethod{},
|
|
|
+ User: c.username,
|
|
|
+ Auth: []ssh.AuthMethod{},
|
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
|
Timeout: c.timeout,
|
|
|
}
|
|
|
@@ -117,129 +115,29 @@ func (c *Client) Close() error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// ExecuteCommand 执行命令并返回输出
|
|
|
+// ExecuteCommand 执行命令并返回输出(每个命令使用新会话)
|
|
|
func (c *Client) ExecuteCommand(command string) (string, error) {
|
|
|
if c.client == nil {
|
|
|
return "", fmt.Errorf("not connected")
|
|
|
}
|
|
|
|
|
|
- // 重试机制,处理会话创建失败的情况
|
|
|
- var session *ssh.Session
|
|
|
- var err error
|
|
|
- maxRetries := 3
|
|
|
-
|
|
|
- for i := 0; i < maxRetries; i++ {
|
|
|
- session, err = c.client.NewSession()
|
|
|
- if err == nil {
|
|
|
- break
|
|
|
- }
|
|
|
-
|
|
|
- // 如果是会话拒绝错误,等待后重试
|
|
|
- if i < maxRetries-1 {
|
|
|
- time.Sleep(time.Duration(i+1) * 500 * time.Millisecond)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
+ // 创建新会话
|
|
|
+ session, err := c.client.NewSession()
|
|
|
if err != nil {
|
|
|
- return "", fmt.Errorf("failed to create session after %d retries: %w", maxRetries, err)
|
|
|
+ return "", fmt.Errorf("failed to create session: %w", err)
|
|
|
}
|
|
|
defer session.Close()
|
|
|
|
|
|
- // H3C/华为设备需要先禁用分页
|
|
|
- // 使用 Shell 模式而不是 Run 模式
|
|
|
- modes := ssh.TerminalModes{
|
|
|
- ssh.ECHO: 0, // 禁用回显
|
|
|
- ssh.TTY_OP_ISPEED: 14400, // 输入速度
|
|
|
- ssh.TTY_OP_OSPEED: 14400, // 输出速度
|
|
|
- }
|
|
|
-
|
|
|
- if err := session.RequestPty("dumb", 200, 1000, modes); err != nil {
|
|
|
- return "", fmt.Errorf("failed to request pty: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- // 启动 shell
|
|
|
- shell, err := session.StdinPipe()
|
|
|
- if err != nil {
|
|
|
- return "", fmt.Errorf("failed to get stdin pipe: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
var stdoutBuf bytes.Buffer
|
|
|
session.Stdout = &stdoutBuf
|
|
|
session.Stderr = &stdoutBuf
|
|
|
-
|
|
|
- if err := session.Shell(); err != nil {
|
|
|
- return "", fmt.Errorf("failed to start shell: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- // 先发送禁用分页命令(H3C/华为)
|
|
|
- if _, err := shell.Write([]byte("screen-length disable\n")); err != nil {
|
|
|
- return "", fmt.Errorf("failed to send screen-length disable: %w", err)
|
|
|
- }
|
|
|
- time.Sleep(500 * time.Millisecond) // 等待命令执行
|
|
|
-
|
|
|
- // 发送实际命令
|
|
|
- if _, err := shell.Write([]byte(command + "\n")); err != nil {
|
|
|
- return "", fmt.Errorf("failed to send command: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- // 等待命令执行完成
|
|
|
- time.Sleep(2 * time.Second)
|
|
|
- if _, err := shell.Write([]byte("exit\n")); err != nil {
|
|
|
- return "", fmt.Errorf("failed to send exit: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- // 等待会话结束
|
|
|
- session.Wait()
|
|
|
-
|
|
|
- output := stdoutBuf.String()
|
|
|
-
|
|
|
- // 调试:输出原始输出长度和前100个字符
|
|
|
- fmt.Printf("[SSH DEBUG] Raw output length: %d, first 100 chars: %q\n", len(output), output[:min(len(output), 100)])
|
|
|
-
|
|
|
- // 清理输出:移除命令回显和分页提示
|
|
|
- lines := strings.Split(output, "\n")
|
|
|
- var cleanLines []string
|
|
|
- skipNext := false
|
|
|
- for _, line := range lines {
|
|
|
- trimmedLine := strings.TrimSpace(line)
|
|
|
-
|
|
|
- // 跳过空行
|
|
|
- if trimmedLine == "" {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- // 跳过分页提示
|
|
|
- if strings.Contains(trimmedLine, "---- More ----") {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- // 跳过命令本身的回显(精确匹配)
|
|
|
- if trimmedLine == strings.TrimSpace(command) || trimmedLine == "screen-length disable" {
|
|
|
- skipNext = true
|
|
|
- fmt.Printf("[SSH DEBUG] Skipping command echo: %s\n", trimmedLine)
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- // 跳过提示符行(如 <hostname> 或 [hostname])
|
|
|
- if regexp.MustCompile(`^[<\[]\S+[>\]]$`).MatchString(trimmedLine) {
|
|
|
- fmt.Printf("[SSH DEBUG] Skipping prompt: %s\n", trimmedLine)
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- // 如果是 "screen-length disable" 后的第一行(通常是提示符),跳过
|
|
|
- if skipNext {
|
|
|
- skipNext = false
|
|
|
- fmt.Printf("[SSH DEBUG] Skipping line after command: %s\n", trimmedLine)
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- cleanLines = append(cleanLines, trimmedLine)
|
|
|
+
|
|
|
+ err = session.Run(command)
|
|
|
+ if err != nil {
|
|
|
+ return stdoutBuf.String(), fmt.Errorf("command '%s' failed: %w", command, err)
|
|
|
}
|
|
|
-
|
|
|
- cleanOutput := strings.Join(cleanLines, "\n")
|
|
|
- fmt.Printf("[SSH DEBUG] Clean output length: %d, first 100 chars: %q\n", len(cleanOutput), cleanOutput[:min(len(cleanOutput), 100)])
|
|
|
-
|
|
|
- return cleanOutput, nil
|
|
|
+
|
|
|
+ return stdoutBuf.String(), nil
|
|
|
}
|
|
|
|
|
|
// ExecuteCommands 执行多个命令
|