2025-05-27 15:46:31 +08:00

212 lines
8.2 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import paramiko
import time
import os
import pexpect
import atexit
import socket
import subprocess
def execute_command_on_remote(host, username, password, command, port=22):
# 创建SSH客户端
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
# 连接到远程服务器,指定端口
ssh.connect(hostname=host, port=port, username=username, password=password)
# 执行命令
stdin, stdout, stderr = ssh.exec_command(command)
# 获取命令输出和错误信息
output = stdout.read().decode('utf-8')
error = stderr.read().decode('utf-8')
return output, error
finally:
# 关闭SSH连接
ssh.close()
def execute_command_on_remote_subprocess(host, username, password, command, port=22, timeout=30):
# 使用 sshpass 来传递密码
ssh_command = [
"sshpass",
"-p", password, # 使用提供的密码
"ssh",
"-p", str(port),
f"{username}@{host}",
command
]
try:
# 执行命令并获取输出
result = subprocess.run(ssh_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout, text=True)
# 获取命令输出和错误信息
output = result.stdout
error = result.stderr
return output, error
except subprocess.TimeoutExpired:
return None, "Command timed out"
except Exception as e:
return None, str(e)
# # Ping 检查远程主机是否在线
# def is_host_down(host, timeout=60):
# start_time = time.time()
# while time.time() - start_time < timeout:
# response = os.system(f"ping -c 1 {host}")
# if response != 0: # ping 不通
# return True
# time.sleep(5)
# return False
def is_host_down(host, port=22, timeout=20):
def check_ssh_port(host, port):
try:
with socket.create_connection((host, port), timeout=1):
return False # 端口开放,主机未关闭
except (socket.timeout, ConnectionRefusedError, OSError):
return True # 端口不可连接,主机可能已关闭
start_time = time.time()
while time.time() - start_time < timeout:
if check_ssh_port(host, port):
return True # 端口不可连接,主机可能已经关闭
time.sleep(0.1)
return False
# class ReverseSSH:
# def __init__(self, server_ip, server_user, server_password, local_port=22, remote_port=2222):
# self.server_ip = server_ip
# self.server_user = server_user
# self.server_password = server_password
# self.local_port = local_port
# self.remote_port = remote_port
# self.tunnel_process = None
# atexit.register(self.stop_tunnel)
# def start_tunnel(self):
# try:
# # 构建反向隧道的 SSH 命令,去掉 '-f'
# command = f"ssh -o StrictHostKeyChecking=no -N -R {self.remote_port}:localhost:{self.local_port} {self.server_user}@{self.server_ip}"
# # 使用 pexpect 启动进程并等待密码提示
# self.tunnel_process = pexpect.spawn(command)
# self.tunnel_process.expect("password:")
# # 输入密码
# self.tunnel_process.sendline(self.server_password)
# return {"status": "success", "message": "反向隧道已建立成功!"}
# except Exception as e:
# return {"status": "error", "message": str(e)}
# def stop_tunnel(self):
# if self.tunnel_process:
# self.tunnel_process.terminate(force=True)
# return {"status": "success", "message": "反向隧道已关闭。"}
# return {"status": "error", "message": "隧道未启动,无法关闭。"}
class ReverseSSH:
def __init__(self, server_ip, server_user, server_password, local_port=22, max_retries=3, retry_interval=2):
self.server_ip = server_ip
self.server_user = server_user
self.server_password = server_password
self.local_port = local_port
self.remote_port = None # 将在隧道建立后自动分配
self.tunnel_process = None
self.max_retries = max_retries
self.retry_interval = retry_interval
atexit.register(self.stop_tunnel)
def start_tunnel(self):
for attempt in range(self.max_retries):
print(f"Attempt {attempt + 1}/{self.max_retries} to establish reverse SSH tunnel")
try:
# 构建反向隧道的 autossh 命令指定远程端口为0以自动分配
command = (
f"autossh -M 0 -o ServerAliveInterval=60 -o ServerAliveCountMax=3 "
f"-o StrictHostKeyChecking=no -N -R 0:localhost:{self.local_port} {self.server_user}@{self.server_ip}"
)
print(f"Reverse SSH command: {command}")
# 使用 pexpect 启动进程并等待密码提示
self.tunnel_process = pexpect.spawn(command)
self.tunnel_process.expect("password:")
self.tunnel_process.sendline(self.server_password)
# 获取服务器分配的端口
self.tunnel_process.expect(r"Allocated port (\d+)", timeout=5)
self.remote_port = int(self.tunnel_process.match.group(1))
return {"status": "success", "message": f"反向隧道已成功建立在端口 {self.remote_port}", "port": self.remote_port}
except Exception as e:
print(f"Failed to establish tunnel on attempt {attempt + 1}: {e}")
self.stop_tunnel() # 确保关闭任何正在运行的进程
time.sleep(self.retry_interval)
return {"status": "error", "message": "Failed to establish tunnel after multiple attempts."}
def stop_tunnel(self):
if self.tunnel_process:
# 优雅地关闭隧道进程
self.tunnel_process.terminate()
time.sleep(1) # 等待进程终止
# 检查是否完全关闭
if self.tunnel_process.isalive():
# 强制终止进程
self.tunnel_process.kill()
time.sleep(1) # 等待进程完全释放资源
# 检查进程是否已经完全停止
if not self.tunnel_process.isalive():
self.tunnel_process = None
return {"status": "success", "message": "反向隧道已成功关闭,端口已释放。"}
else:
return {"status": "error", "message": "反向隧道关闭失败,端口可能仍在占用。"}
return {"status": "error", "message": "隧道未启动,无法关闭。"}
def check_tunnel_status(self):
"""
检查反向隧道的连接状态。
如果隧道进程仍在运行,返回连接正常的状态。
如果隧道进程已停止,返回连接断开的状态。
"""
if self.tunnel_process and self.tunnel_process.isalive():
return {"status": "connected", "message": f"隧道仍然连接在端口 {self.remote_port}"}
else:
return {"status": "disconnected", "message": "隧道已断开"}
if __name__ == '__main__':
# SERVER_IP = "8.138.8.114" # 替换为您的服务器 IP
# SERVER_USER = "root" # 替换为 root 用户
# SERVER_PASSWORD = "JuShenFengBao209" # 替换为 root 用户密码
# reverse_ssh = ReverseSSH(SERVER_IP, SERVER_USER, SERVER_PASSWORD)
# result = reverse_ssh.start_tunnel()
# print(result)
# time.sleep(200)
# result = reverse_ssh.stop_tunnel()
remote_host = "192.168.100.20" # 远程主机的 IP 地址
remote_username = "root" # 远程主机的用户名
remote_password = "bestcobot" # 远程主机的密码
remote_port = 8822 # 远程主机的 SSH 端口
sudo_password = "jsfb"
if not is_host_down(remote_host):
print("Remote host is alive, processing shutdown")
remote_command = "shutdown -h now"
stdout, stderr = execute_command_on_remote(
remote_host,
remote_username,
remote_password,
remote_command,
port=remote_port,
)