diff --git a/internal/device/parser.go b/internal/device/parser.go index a462980..8234c33 100644 --- a/internal/device/parser.go +++ b/internal/device/parser.go @@ -80,9 +80,9 @@ func DiscoverDevice(ip string, deviceType models.DeviceType, username, password // 执行命令 - 允许部分命令失败,增加详细日志和延迟防止设备速率限制 outputs := make([]string, 0, len(commands)) for i, cmd := range commands { - // 每个命令之间等待500毫秒,防止设备速率限制导致返回空数据 + // 每个命令之间等待2秒,防止H3C交换机速率限制导致返回空数据或执行失败 if i > 0 { - time.Sleep(500 * time.Millisecond) + time.Sleep(2 * time.Second) } fmt.Printf("[PARSER] Executing command %d/%d: %s\n", i+1, len(commands), cmd) diff --git a/internal/ssh/client.go b/internal/ssh/client.go index f2d4371..b97ba4f 100644 --- a/internal/ssh/client.go +++ b/internal/ssh/client.go @@ -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) + + err = session.Run(command) + if err != nil { + return stdoutBuf.String(), fmt.Errorf("command '%s' failed: %w", command, 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]) - 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) - } - - 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 执行多个命令