Hello!
I need help on using libssh2 for forwading ports in c++ application
with high-level framework (qt4).
Currently I'm usging 1.2.6 libssh2 version.
I've started with direct_tcpip.c example and come with following:
1) establishing connection, setting up libssh2 session and authenticating
2) setting up listening tcp socket and starting framework event loop
3) on incoming connection setting up libssh2 channel
4) every second iterating over channel list and for each one
reading data from socket and writing to libssh2 channel and vice versa
5) closing and freeing channel on socket disconnect
Source that can be compiled and run attached to message.
Here's example with libssh2 function calls:
//authentication from example
int libssh2_error = libssh2_init(0);
...
LIBSSH2_SESSION *session = libssh2_session_init();
QTcpSocket socket;
socket.connectToHost(...);
if(!socket.waitForConnected())
...
libssh2_error = libssh2_session_startup(session,
socket.socketDescriptor());
...
libssh2_userauth_list(session, ...);
if(libssh2_userauth_password(...)
...
//setup local tcp socket
//on incoming connection
void openConnection()
{
QTcpServer *server = ...;
QTcpSocket *socket = server->nextPendingConnection();
libssh2_session_set_blocking(m_session, 1);
LIBSSH2_CHANNEL *channel = libssh2_channel_direct_tcpip_ex(
m_session,
tunnel.m_host.toLocal8Bit().constData(),
tunnel.m_hostPort,
server->serverAddress().toString().toLocal8Bit().constData(),
server->serverPort()
);
...
//reading and writing data
void transmit()
{
char buffer[4096];
libssh2_session_set_blocking(m_session, 1);
QTcpSocket *socket = ...;
LIBSSH2_CHANNEL *channel = ...;
int read = socket->read(buffer, sizeof(buffer));
int written = 0;
int i = 0;
if(read > 0)
{
do
{
i = libssh2_channel_write(
channel,
buffer,
read
);
if(i < 0)
{
...
}
written += i;
} while (i > 0 && written < read);
}
while(true)
{
read = libssh2_channel_read(channel,
buffer, sizeof(buffer));
if(LIBSSH2_ERROR_EAGAIN == read)
{
break;
}
else if(read < 0)
{
...
}
written = 0;
while(written < read)
{
i = socket->write(buffer +
written, read - written);
if(i < 0)
{
...
}
}
i = libssh2_channel_eof(channel);
if(i)
{
...
}
}
...
//closing connection
LIBSSH2_CHANNEL *channel = ...;
if(channel)
{
//there's no libssh2_channel_close close in example
//should I close channel on socket disconnect or not?
//libssh2_channel_close(channel);
//libssh2_channel_wait_closed(channel);
libssh2_channel_free(channel);
}
...
//cleanup
socket.disconnectFromHost();
libssh2_session_disconnect(session, "Client disconnecting normally");
libssh2_session_free(session);
libssh2_exit();
Example says all channel IO should go in unblocked mode, but in this mode
no data transfered without select()/poll() which I cant use in high
level networking API.
And in unblocked mode I can't setup new channel.
In blocking mode some data transferred, for example I can get html via
http with curl
through forwarded port but I have to wait about 15 seconds till
libssh2_channel_read
timeout. Using, say, ssh shell through forwarded port is unusable
because there's plenty of
timeouts to wait.
--
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?
A: Top-posting.
Q: What is the most annoying thing in e-mail?
#include <QApplication>
#include <QDialog>
#include <QPushbutton>
#include <QLayout>
#include <QLabel>
#include <QLineEdit>
#include <QSpinbox>
#include <QMessageBox>
#include <QFormLayout>
#include <QListView>
#include <QStringListModel>
#include <QInputDialog>
#include <QSettings>
#include <QTcpServer>
#include <QTcpSocket>
#include <QTimer>
#include <QFile>
#include <QMap>
#include <QList>
#include <QString>
#include <QDateTime>
#include "libssh2_config.h"
#include <libssh2.h>
#include <libssh2_sftp.h>
struct TunnelInfo
{
QString m_bindAddress;
int m_port;
QString m_host;
int m_hostPort;
};
class ConnectDialog: public QDialog
{
Q_OBJECT
public:
ConnectDialog() : QDialog(0)
{
setupUi();
}
QString user() const { return m_user; }
QString pass() const { return m_pass; }
int port() const { return m_port; }
QString host() const { return m_host; }
QList<TunnelInfo> localTunnels() { return m_localTunnels; };
public slots:
void setUser(const QString &user) { m_user = user; m_settings.setValue("user", user); }
void setPass(const QString &pass) { m_pass = pass; m_settings.setValue("pass", pass); }
void setPort(int port) { m_port = port; m_settings.setValue("port", port); }
void setHost(const QString &host) { m_host = host; m_settings.setValue("host", host); }
void addLocalTunnel()
{
QString s = QInputDialog::getText(
this,
tr("Add tunnel"),
tr("Format: [bind_address:]port:host:hostport")
);
QStringList l = s.split(":", QString::SkipEmptyParts);
if(l.size() < 3 || l.size() > 4)
{
QMessageBox::critical(this, tr("Add tunnel"), tr("Invalid format"));
return;
}
TunnelInfo lt;
if(l.size() == 4)
{
lt.m_bindAddress = l.takeFirst();
}
lt.m_port = l.takeFirst().toInt();
lt.m_host = l.takeFirst();
lt.m_hostPort = l.takeFirst().toInt();
m_localTunnels << lt;
m_localTunnelsModel.setStringList(
m_localTunnelsModel.stringList() << s
);
}
private:
void setupUi()
{
QVBoxLayout *layout = new QVBoxLayout(this);
QFormLayout *controlsLayout = new QFormLayout;
QLineEdit *userEdit = new QLineEdit(this);
connect(userEdit, SIGNAL(textChanged(const QString &)),
this, SLOT(setUser(const QString &)));
controlsLayout->addRow(tr("Username"), userEdit);
userEdit->setText(m_settings.value("user").toString());
QLineEdit *passEdit = new QLineEdit(this);
passEdit->setEchoMode(QLineEdit::Password);
connect(passEdit, SIGNAL(textChanged(const QString &)),
this, SLOT(setPass(const QString &)));
controlsLayout->addRow(tr("Password"), passEdit);
passEdit->setText(m_settings.value("pass").toString());
QLineEdit *hostEdit = new QLineEdit(this);
connect(hostEdit, SIGNAL(textChanged(const QString &)),
this, SLOT(setHost(const QString &)));
controlsLayout->addRow(tr("Host"), hostEdit);
hostEdit->setText(m_settings.value("host").toString());
QSpinBox *portEdit = new QSpinBox(this);
portEdit->setMinimum(1);
portEdit->setMaximum(50000);
connect(portEdit, SIGNAL(valueChanged(int)),
this, SLOT(setPort(int)));
controlsLayout->addRow(tr("Port"), portEdit);
layout->addLayout(controlsLayout);
portEdit->setValue(m_settings.value("port").toInt());
QListView *listView = new QListView;
listView->setModel(&m_localTunnelsModel);
layout->addWidget(listView);
QHBoxLayout *buttonLayout = new QHBoxLayout;
QPushButton *addLocalTunnelButton = new QPushButton(tr("Add tunnel"));
connect(addLocalTunnelButton, SIGNAL(clicked()), this, SLOT(addLocalTunnel()));
buttonLayout->addWidget(addLocalTunnelButton);
buttonLayout->addStretch();
QPushButton *okButton = new QPushButton(tr("Ok"));
connect(okButton, SIGNAL(clicked()), this, SLOT(accept()));
okButton->setDefault(true);
buttonLayout->addWidget(okButton);
QPushButton *cancelButton = new QPushButton(tr("Cancel"));
connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
buttonLayout->addWidget(cancelButton);
layout->addLayout(buttonLayout);
resize(300, 200);
}
private:
QString m_user, m_pass, m_host;
int m_port;
QList<TunnelInfo> m_localTunnels;
QStringListModel m_localTunnelsModel;
QSettings m_settings;
};
class LocalTunnelManager : public QObject
{
Q_OBJECT
public:
LocalTunnelManager(LIBSSH2_SESSION *session, const QList<TunnelInfo> &list)
:m_session(session)
{
for(QList<TunnelInfo>::const_iterator it = list.begin(); it != list.end(); ++it)
{
setupLocalServer(*it);
}
}
public slots:
void transmit()
{
qDebug("LocalTunnelManager: transmit() >>> %i channels", m_socketChannelMap.size());
char buffer[4096];
/*
while(true)
{
*/
//QApplication::processEvents();
/*
if(m_socketChannelMap.isEmpty())
{
break;
}*/
libssh2_session_set_blocking(m_session, 1);
for(SocketChannelMap::iterator it = m_socketChannelMap.begin();
it != m_socketChannelMap.end(); ++it)
{
QTcpSocket *socket = it.key();
LIBSSH2_CHANNEL *channel = it.value();
qDebug("LocalTunnelManager: socket->read() >>>");
int read = socket->read(buffer, sizeof(buffer));
QByteArray debug = QByteArray(buffer, read);
qDebug("LocalTunnelManager: socket->read() <<< %i\n%s", read, debug.constData());
int written = 0;
int i = 0;
if(read > 0)
{
do
{
qDebug("LocalTunnelManager: libssh2_channel_write() >>>");
/*
int i = libssh2_channel_write(
channel,
buffer + written,
read - written
);
*/
i = libssh2_channel_write(
channel,
buffer,
read
);
qDebug("LocalTunnelManager: libssh2_channel_write() <<< %i", i);
if(i < 0)
{
qDebug( "libssh2_channel_write: %d\n", i);
shutdownConnection(socket, channel);
goto next_socket;
}
written += i;
} while (i > 0 && written < read);
qDebug("LocalTunnelManager: >>> %i", read);
}
while(true)
{
qDebug("LocalTunnelManager: libssh2_channel_read() >>>");
read = libssh2_channel_read(channel, buffer, sizeof(buffer));
QByteArray debug = QByteArray(buffer, read);
qDebug("LocalTunnelManager: libssh2_channel_read() <<< %i\n%s", read, debug.constData());
if(LIBSSH2_ERROR_EAGAIN == read)
{
qDebug("LocalTunnelManager: LIBSSH2_ERROR_EAGAIN");
break;
}
else if(read < 0)
{
qDebug("LocalTunnelManager: error reading from channel");
shutdownConnection(socket, channel);
goto next_socket;
}
written = 0;
while(written < read)
{
qDebug("LocalTunnelManager: socket->write() >>>");
i = socket->write(buffer + written, read - written);
qDebug("LocalTunnelManager: socket->write() <<< %i", i);
if(i < 0)
{
qDebug("LocalTunnelManager: error writing to socket");
shutdownConnection(socket, channel);
goto next_socket;
}
written += i ;
}
qDebug("LocalTunnelManager: <<< %i", read);
i = libssh2_channel_eof(channel);
if(i)
{
qDebug("LocalTunnelManager: libssh2_channel_eof %i", i);
shutdownConnection(socket, channel);
goto next_socket;
}
}
next_socket:
continue;
}
libssh2_session_set_blocking(m_session, 1);
/*
}
*/
qDebug("LocalTunnelManager: transmit() <<<");
}
protected slots:
void openConnection()
{
qDebug("LocalTunnelManager: openConnection() >>>");
QTcpServer *server = static_cast<QTcpServer *>(sender());
QTcpSocket *socket = server->nextPendingConnection();
connect(socket, SIGNAL(disconnected()), this, SLOT(closeConnection()));
if(!socket || !m_serverTunnelInfoMap.contains(server))
{
qDebug("LocalTunnelManager: error opening connection");
return;
}
TunnelInfo tunnel = m_serverTunnelInfoMap[server];
//libssh2_session_set_blocking(m_session, 1);
LIBSSH2_CHANNEL *channel = libssh2_channel_direct_tcpip_ex(
m_session,
tunnel.m_host.toLocal8Bit().constData(),
tunnel.m_hostPort,
server->serverAddress().toString().toLocal8Bit().constData(),
server->serverPort()
);
if(!channel)
{
qDebug( "Could not open the direct-tcpip channel!\n"
"(Note that this can be a problem at the server!"
" Please review the server logs.)\n");
socket->deleteLater();
return;
}
m_socketChannelMap[socket] = channel;
qDebug("LocalTunnelManager: openConnection() <<<");
}
void closeConnection()
{
qDebug("LocalTunnelManager: closeConnection() >>>");
QTcpSocket *socket = static_cast<QTcpSocket *>(sender());
LIBSSH2_CHANNEL *channel = m_socketChannelMap[socket];
shutdownConnection(socket, channel);
qDebug("LocalTunnelManager: closeConnection() <<<");
}
private:
void shutdownConnection(QTcpSocket *socket, LIBSSH2_CHANNEL *channel)
{
if(socket->isOpen())
{
socket->waitForBytesWritten();
socket->close();
}
m_socketChannelMap.remove(socket);
socket->deleteLater();
if(channel)
{
/*libssh2_channel_close(channel);
libssh2_channel_wait_closed(channel);*/
libssh2_channel_free(channel);
}
}
void setupLocalServer(const TunnelInfo &tunnel)
{
QTcpServer *server = new QTcpServer;
connect(server, SIGNAL(newConnection()), this, SLOT(openConnection()));
if(!server->listen(QHostAddress(tunnel.m_bindAddress), tunnel.m_port))
{
qDebug("LocalTunnelManager: fail to listen on %s:%i",
tunnel.m_bindAddress.toLocal8Bit().constData(),
tunnel.m_port);
delete server;
}
qDebug("LocalTunnelManager: listening on %s:%i",
tunnel.m_bindAddress.toLocal8Bit().constData(),
tunnel.m_port);
m_serverTunnelInfoMap[server] = tunnel;
}
private:
LIBSSH2_SESSION *m_session;
typedef QMap<QTcpSocket *, LIBSSH2_CHANNEL *> SocketChannelMap;
typedef QMap<QTcpServer *, TunnelInfo> ServerTunnelInfoMap;
SocketChannelMap m_socketChannelMap;
ServerTunnelInfoMap m_serverTunnelInfoMap;
};
void myMessageOutput(QtMsgType type, const char *msg)
{
static QFile *pLog = 0;
if(!pLog)
{
pLog = new QFile("log.txt", QCoreApplication::instance());
pLog->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text);
}
QString s;
switch (type) {
case QtDebugMsg:
s.sprintf("Debug: %s\n", msg);
break;
case QtWarningMsg:
s.sprintf("Warning: %s\n", msg);
break;
case QtCriticalMsg:
s.sprintf("Critical: %s\n", msg);
break;
case QtFatalMsg:
s.sprintf("Fatal: %s\n", msg);
abort();
}
s = QDateTime::currentDateTime().toString(Qt::ISODate) + " " + s;
fprintf(stderr, s.toLocal8Bit().constData());
pLog->write(s.toLocal8Bit());
pLog->flush();
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qInstallMsgHandler(myMessageOutput);
QCoreApplication::setOrganizationName("Foto.ru");
QCoreApplication::setOrganizationDomain("foto.ru");
QCoreApplication::setApplicationName("libssh2");
ConnectDialog dlg;
if(dlg.exec() != QDialog::Accepted)
{
return -1;
}
int libssh2_error = libssh2_init(0);
if(libssh2_error)
{
qDebug("libssh2_init() error: %d", libssh2_error);
return -2;
}
QTcpSocket socket;
socket.connectToHost(dlg.host(), dlg.port());
if(!socket.waitForConnected())
{
qDebug("Error connecting to host %s", dlg.host().toLocal8Bit().constData());
return -1;
}
LIBSSH2_SESSION *session = libssh2_session_init();
if(!session)
{
qDebug("libssh2_session_init() failed");
return -2;
}
libssh2_error = libssh2_session_startup(session, socket.socketDescriptor());
if(libssh2_error)
{
qDebug("libssh2_session_startup() error: %d", libssh2_error);
return -3;
}
{
/* At this point we havn't yet authenticated. The first thing to do
* is check the hostkey's fingerprint against our known hosts Your app
* may have it hard coded, may go to a file, may present it to the
* user, that's your call
*/
const char *fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
qDebug( "Fingerprint: ");
for(int i = 0; i < 20; i++)
qDebug( "%02X ", (unsigned char)fingerprint[i]);
qDebug( "\n");
}
qDebug("Password authentication: [%s] [%s]", dlg.user().toLocal8Bit().constData(), dlg.pass().toLocal8Bit().constData());
libssh2_userauth_list(session, dlg.user().toLocal8Bit().constData(), dlg.user().toLocal8Bit().length());
if(libssh2_userauth_password(
session,
dlg.user().toLocal8Bit().constData(),
dlg.pass().toLocal8Bit().constData()
))
{
qDebug("Password authentication failed");
return -4;
}
LocalTunnelManager manager(session, dlg.localTunnels());
QTimer timer;
QObject::connect(&timer, SIGNAL(timeout()), &manager, SLOT(transmit()));
timer.start(1000);
//QTimer::singleShot(100, &manager, SLOT(transmit()));
app.exec();
socket.disconnectFromHost();
libssh2_session_disconnect(session, "Client disconnecting normally");
libssh2_session_free(session);
libssh2_exit();
return 0;
}
#include "qt.moc"
example.pro
Description: Binary data
_______________________________________________ libssh2-devel http://cool.haxx.se/cgi-bin/mailman/listinfo/libssh2-devel
