X11 пересылка с парамико
Я пытаюсь запустить команду с paramiko
, которая должна иметь возможность открыть окно X. script Я использую что-то следующее:
import paramiko
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect('192.168.122.55', username='user', password='password')
transport = ssh_client.get_transport()
session = transport.open_session()
session.request_x11()
stdin = session.makefile('wb')
stdout = session.makefile('rb')
stderr = session.makefile_stderr('rb')
session.exec_command('env; xterm')
transport.accept()
print 'Exit status:', session.recv_exit_status()
print 'stdout:\n{}'.format(stdout.read())
print 'stderr:\n{}'.format(stderr.read())
session.close()
К сожалению, когда я запускаю script выше, я получаю этот вывод:
Exit status: 1
stdout:
SHELL=/bin/bash
XDG_SESSION_COOKIE=8025e1ba5e6c47be0d2f3ad6504a25ee-1347286654.617967-1932974971
SSH_CLIENT=192.168.122.1 58654 22
USER=user
MAIL=/var/mail/user
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
PWD=/home/user
LANG=en_US.UTF-8
SHLVL=1
HOME=/home/user
LOGNAME=user
SSH_CONNECTION=192.168.122.1 58654 192.168.122.55 22
DISPLAY=localhost:10.0
_=/usr/bin/env
stderr:
xterm: Xt error: Can't open display: localhost:10.0
Если я запустил следующую команду в терминале:
ssh -X [email protected] 'env; xterm'
тогда я получаю одни и те же переменные среды (некоторые порты изменились, хотя), поэтому я бы сказал, что моя среда верна. Тем не менее, я все еще не могу что-то сделать, чтобы paramiko
работал с пересылкой x11.
Несколько вещей, которые я пробовал:
- Используйте параметр
handler
в request_x11
: кроме значений печати, я не получил больше, чем с обработчиком по умолчанию.
- Используйте параметр
auth_cookie
в request_x11
: попытался сделать hardcode значение cookie, которое использовалось в соответствии с выходом xauth list
. Идея сделать это заключалась в том, чтобы избежать проблем, которые могут произойти в соответствии со строкой документации в paramiko
:
Если вы опустите auth_cookie, новое безопасное случайное 128-битное значение будет генерируется, используется и возвращается. Вам нужно будет использовать это значение для проверить входящие запросы x11 и заменить их фактическими локальными x11 cookie (что требует некоторого knoweldge протокола x11).
Есть ли что-то еще, что я мог бы сделать, чтобы он работал или устранял проблему?
Примечание:
Об этом ранее говорилось в:
- superuser: единственный ответ указывает на документацию
request_x11
, которую я уже пытался использовать безрезультатно.
- stackoverflow: принятый ответ предлагает использовать параметр
handler
, но это неправильно.
- github: не более года предоставляется ответ.
Ответы
Ответ 1
- запрос
x11
может использовать MIT-MAGIC-COOKIE-1
, который вы не можете обработать должным образом
- С помощью ssh я сразу увидел, что нужно подтвердить запрос x11 (вызов cookie?)
- Файл
.Xauthority
также может быть проблемой
- вы можете попробовать
strace
процесс ssh и посмотреть нормальный поток
- в script, вы можете заменить
xterm
на strace xterm
и сравнить с приведенным выше.
некоторые ссылки:
удачи.
EDIT:
здание поверх Гэри отвечает с несколькими соединениями x11.
#!/usr/bin/env python
import os
import select
import sys
import getpass
import paramiko
import socket
import logging
import Xlib.support.connect as xlib_connect
LOGGER = logging.getLogger(__name__)
# connection settings
host = '192.168.122.55'
user = 'user'
password = getpass.getpass()
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(host, username=user, password=password)
del password
# maintain map
# { fd: (channel, remote channel), ... }
channels = {}
poller = select.poll()
def x11_handler(channel, (src_addr, src_port)):
'''handler for incoming x11 connections
for each x11 incoming connection,
- get a connection to the local display
- maintain bidirectional map of remote x11 channel to local x11 channel
- add the descriptors to the poller
- queue the channel (use transport.accept())'''
x11_chanfd = channel.fileno()
local_x11_socket = xlib_connect.get_socket(*local_x11_display[:3])
local_x11_socket_fileno = local_x11_socket.fileno()
channels[x11_chanfd] = channel, local_x11_socket
channels[local_x11_socket_fileno] = local_x11_socket, channel
poller.register(x11_chanfd, select.POLLIN)
poller.register(local_x11_socket, select.POLLIN)
LOGGER.debug('x11 channel on: %s %s', src_addr, src_port)
transport._queue_incoming_channel(channel)
def flush_out(session):
while session.recv_ready():
sys.stdout.write(session.recv(4096))
while session.recv_stderr_ready():
sys.stderr.write(session.recv_stderr(4096))
# get local disply
local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
# start x11 session
transport = ssh_client.get_transport()
session = transport.open_session()
session.request_x11(handler=x11_handler)
session.exec_command('xterm')
session_fileno = session.fileno()
poller.register(session_fileno, select.POLLIN)
# accept first remote x11 connection
transport.accept()
# event loop
while not session.exit_status_ready():
poll = poller.poll()
# accept subsequent x11 connections if any
if len(transport.server_accepts) > 0:
transport.accept()
if not poll: # this should not happen, as we don't have a timeout.
break
for fd, event in poll:
if fd == session_fileno:
flush_out(session)
# data either on local/remote x11 socket
if fd in channels.keys():
channel, counterpart = channels[fd]
try:
# forward data between local/remote x11 socket.
data = channel.recv(4096)
counterpart.sendall(data)
except socket.error:
channel.close()
counterpart.close()
del channels[fd]
print 'Exit status:', session.recv_exit_status()
flush_out(session)
session.close()
Ответ 2
Читая код paramiko, я понял, что paramiko только реализует способ установить канал x11. Он не подключает канал к локальному дисплею x11. Это вам.
Вот небольшая реализация, которую я только что написал:
#!/usr/bin/env python
import os
import select
import sys
import paramiko
import Xlib.support.connect as xlib_connect
local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
local_x11_socket = xlib_connect.get_socket(*local_x11_display[:3])
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect('server', username='username', password='password')
transport = ssh_client.get_transport()
session = transport.open_session()
session.request_x11(single_connection=True)
session.exec_command('xterm')
x11_chan = transport.accept()
session_fileno = session.fileno()
x11_chan_fileno = x11_chan.fileno()
local_x11_socket_fileno = local_x11_socket.fileno()
poller = select.poll()
poller.register(session_fileno, select.POLLIN)
poller.register(x11_chan_fileno, select.POLLIN)
poller.register(local_x11_socket, select.POLLIN)
while not session.exit_status_ready():
poll = poller.poll()
if not poll: # this should not happen, as we don't have a timeout.
break
for fd, event in poll:
if fd == session_fileno:
while session.recv_ready():
sys.stdout.write(session.recv(4096))
while session.recv_stderr_ready():
sys.stderr.write(session.recv_stderr(4096))
if fd == x11_chan_fileno:
local_x11_socket.sendall(x11_chan.recv(4096))
if fd == local_x11_socket_fileno:
x11_chan.send(local_x11_socket.recv(4096))
print 'Exit status:', session.recv_exit_status()
while session.recv_ready():
sys.stdout.write(session.recv(4096))
while session.recv_stderr_ready():
sys.stdout.write(session.recv_stderr(4096))
session.close()
Некоторые примечания:
-
Я использую некоторые вспомогательные функции из python-Xlib. Это чистая реализация Xlib на python. См. Этот вопрос для получения подробной информации об установке его: Как установить Python Xlib с помощью pip?
-
Некоторые детали того, как я реализовал это, заставляют меня думать, что он будет работать только для 1 x11-соединения (следовательно, session.request_x11(single_connection=True)
.) Я хотел бы продолжать работать над этим, чтобы заставить его обрабатывать несколько соединений, но это должно будет подождать еще один день.
-
Этот код по существу соединяет следующие каналы/сокеты вместе с асинхронным способом, используя select.poll
:
-
session.stdout
→ sys.stdout
-
session.stderr
→ sys.stderr
-
x11channel
→ local_x11_socket
-
local_x11_socket
→ x11channel
-
Модуль paramiko
выводит много полезной информации об отладке в модуль logging
. Вы можете просмотреть это, настроив модуль регистрации:
import logging
logging.basicConfig(level=logging.DEBUG)
Ответ 3
Спасибо Гэри ван дер Мерве и dnozay за свой код. Код ниже сильно зависит от него и служит для запуска X-программ в Windows. Значительная разница заключается в использовании select.select вместо опроса, поскольку опрос недоступен в Windows. Любые улучшения или исправления приветствуются.
import select
import sys
import paramiko
import Xlib.support.connect as xlib_connect
import os
import socket
import subprocess
# run xming
XmingProc = subprocess.Popen("C:/Program Files (x86)/Xming/Xming.exe :0 -clipboard -multiwindow")
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(SSHServerIP, SSHServerPort, username=user, password=pwd)
transport = ssh_client.get_transport()
channelOppositeEdges = {}
local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
inputSockets = []
def x11_handler(channel, (src_addr, src_port)):
local_x11_socket = xlib_connect.get_socket(*local_x11_display[:3])
inputSockets.append(local_x11_socket)
inputSockets.append(channel)
channelOppositeEdges[local_x11_socket.fileno()] = channel
channelOppositeEdges[channel.fileno()] = local_x11_socket
transport._queue_incoming_channel(channel)
session = transport.open_session()
inputSockets.append(session)
session.request_x11(handler = x11_handler)
session.exec_command('xterm')
transport.accept()
while not session.exit_status_ready():
readable, writable, exceptional = select.select(inputSockets,[],[])
if len(transport.server_accepts) > 0:
transport.accept()
for sock in readable:
if sock is session:
while session.recv_ready():
sys.stdout.write(session.recv(4096))
while session.recv_stderr_ready():
sys.stderr.write(session.recv_stderr(4096))
else:
try:
data = sock.recv(4096)
counterPartSocket = channelOppositeEdges[sock.fileno()]
counterPartSocket.sendall(data)
except socket.error:
inputSockets.remove(sock)
inputSockets.remove(counterPartSocket)
del channelOppositeEdges[sock.fileno()]
del channelOppositeEdges[counterPartSocket.fileno()]
sock.close()
counterPartSocket.close()
print 'Exit status:', session.recv_exit_status()
while session.recv_ready():
sys.stdout.write(session.recv(4096))
while session.recv_stderr_ready():
sys.stdout.write(session.recv_stderr(4096))
session.close()
XmingProc.terminate()
XmingProc.wait()
Ответ 4
Учитывая, что вы попросили минимальную версию, которую я понимаю, сделать ее максимально простой в использовании. Вот версия, основанная на обоих кодах, но это разделяет команды сеанса x11 из общего кода, делая основную программу простой и код сеанса многоразовым:
import paramiko
import os
import select
import sys
import Xlib.support.connect as xlib_connect
def run(transport, session, command):
def x11_handler(channel, (src_addr, src_port)):
x11_fileno = channel.fileno()
local_x11_channel = xlib_connect.get_socket(*local_x11_display[:3])
local_x11_fileno = local_x11_channel.fileno()
# Register both x11 and local_x11 channels
channels[x11_fileno] = channel, local_x11_channel
channels[local_x11_fileno] = local_x11_channel, channel
poller.register(x11_fileno, select.POLLIN)
poller.register(local_x11_fileno, select.POLLIN)
transport._queue_incoming_channel(channel)
def flush_out(channel):
while channel.recv_ready():
sys.stdout.write(channel.recv(4096))
while channel.recv_stderr_ready():
sys.stderr.write(channel.recv_stderr(4096))
local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
channels = {}
poller = select.poll()
session_fileno = session.fileno()
poller.register(session_fileno)
session.request_x11(handler=x11_handler)
session.exec_command(command)
transport.accept()
# event loop
while not session.exit_status_ready():
poll = poller.poll()
if not poll: # this should not happen, as we don't have a timeout.
break
for fd, event in poll:
if fd == session_fileno:
flush_out(session)
# data either on local/remote x11 channels/sockets
if fd in channels.keys():
sender, receiver = channels[fd]
try:
receiver.sendall(sender.recv(4096))
except:
sender.close()
receiver.close()
channels.remove(fd)
flush_out(session)
return session.recv_exit_status()
if __name__ == '__main__':
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect('192.168.122.55', username='user', password='password')
transport = ssh_client.get_transport()
session = transport.open_session()
run(transport, session, 'xterm')
Я знаю, что вы можете сделать это сами. Однако, просто копируя функцию run
, любой может использовать ее без лишних хлопот.
Правильный ответ fooobar.com/questions/440278/.... Этот пример облегчает для новичков.
Ответ 5
Для тех, кто работает в Mac OS X Leopard, нет select.poll(). Вот измененная версия dnozay answer, используя kqueue вместо опроса. Любые улучшения/исправления по этому поводу будут оценены.
#!/usr/bin/env python
import os
import select
import sys
import paramiko
import socket
import Xlib.support.connect as xlib_connect
# get local display
local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect('hostname', port=22, username='username', password='password')
channels = {}
kq = select.kqueue()
def x11Handler(x11_chan, (src_addr, src_port)):
x11_chan_fileno = x11_chan.fileno()
local_x11_socket = xlib_connect.get_socket(*local_x11_display[:3])
local_x11_socket_fileno = local_x11_socket.fileno()
channels[x11_chan_fileno] = x11_chan, local_x11_socket
channels[local_x11_socket_fileno] = local_x11_socket, x11_chan
ev = [select.kevent(x11_chan_fileno, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_ADD), select.kevent(local_x11_socket_fileno, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_ADD)]
kevents = kq.control(ev, 0, None)
transport._queue_incoming_channel(x11_chan)
def flushOut(session):
while session.recv_ready():
sys.stdout.write(session.recv(4096))
while session.recv_stderr_ready():
sys.stderr.write(session.recv_stderr(4096))
# start x11 session
transport = ssh_client.get_transport()
session = transport.open_session()
session.request_x11(handler=x11Handler)
session.exec_command('xterm')
# accept first remote x11 connection
x11_chan = transport.accept()
session_fileno = session.fileno()
session_ev = [select.kevent(session_fileno,
filter=select.KQ_FILTER_READ,
flags=select.KQ_EV_ADD)]
kevents_session = kq.control(session_ev, 0, None)
# event loop
while not session.exit_status_ready():
r_events = kq.control(None, 4)
# accept subsequent x11 connections if any
if len(transport.server_accepts) > 0:
transport.accept()
if not r_events: # this should not happen, as we don't have a timeout.
break
for event in r_events:
print event
if event.ident & session_fileno:
flushOut(session)
# data either on local/remote x11 socket
if event.ident in channels.keys():
x11_chan, counterpart = channels[event.ident]
try:
# forward data between local/remote x11 socket.
data = x11_chan.recv(4096)
counterpart.sendall(data)
except socket.error:
x11_chan.close()
counterpart.close()
del channels[event.ident]
flushOut(session)
kq.close()
session.close()