I\'m trying to run a command with paramiko
that should be able to open an X window. The script I\'m using would something as follows:
import par
For those working in Mac OS X Leopard, there is no select.poll(). Here is a modified version of dnozay's answer using kqueue instead of poll. Any improvements/corrections on this would be appreciated.
#!/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()
x11
request may use a MIT-MAGIC-COOKIE-1
that you may not handled properly.Xauthority
file may also be an issuestrace
ssh process and see a normal flowxterm
with strace xterm
and compare with the above.some links:
good luck.
EDIT: building on top of Gary's answer, with multiple x11 connections.
#!/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()
Given that you asked for a minimal version, which I understand as make it as easy to to use as possible. Here is a version based in both codes, but this separate the x11 session commands from the general code, making the main program simple and the session code reusable:
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')
I know you could do it by yourself. However, but just by copying the function run
anybody could use it without hassle.
The right answer is https://stackoverflow.com/a/12903844/278878. This example is to make it easier for newcomers.
Reading the paramiko code, I realized that paramiko only implements a way to establish an x11 channel. It does not connect the channel to the local x11 display. That is left to you.
Here is a small implementation that I have just written:
#!/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()
Some notes:
I'm using some helper functions from python-Xlib. This is a pure python implementation of Xlib. See this question for details on installing it: How do you install Python Xlib with pip?
Some of the details of how I have implemented this make me believe it will only work for 1 x11 connection (hence session.request_x11(single_connection=True)
.) I would like to keep working at this to get it to handle multiple connections, but that will have to wait for another day.
This code essentially connects the following channels/sockets together in a async fashion using select.poll:
session.stdout
-> sys.stdout
session.stderr
-> sys.stderr
x11channel
-> local_x11_socket
local_x11_socket
- > x11channel
The paramiko
module outputs alot of usefull debuging info to the logging
module. You can view this by configuring the logging module:
import logging
logging.basicConfig(level=logging.DEBUG)
Thanks Gary van der Merwe and dnozay for their code. The code below heavily relies on it and serves for running X programs on Windows. The notable difference is using select.select instead of poll, as poll is not available in Windows. Any improvements or corrections are welcome.
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()