try to solve bug: connect to ssh server, sometimes block when send data to client.

pull/236/head
Apex Liu 2019-11-22 05:52:14 +08:00
parent 958712745b
commit dbe893c88c
2 changed files with 102 additions and 60 deletions

View File

@ -86,7 +86,7 @@ void SshProxy::kill_sessions(const ex_astrs &sessions) {
for (size_t i = 0; i < sessions.size(); ++i) {
if (it->first->sid() == sessions[i]) {
EXLOGW("[ssh] try to kill %s\n", sessions[i].c_str());
it->first->check_noop_timeout(0, 0); // 立即结束
it->first->check_noop_timeout(0, 0); // 立即结束
}
}
}
@ -96,13 +96,15 @@ void SshProxy::_thread_loop() {
EXLOGI("[ssh] TeleportServer-SSH ready on %s:%d\n", m_host_ip.c_str(), m_host_port);
for (;;) {
// 注意ssh_new()出来的指针如果遇到停止标志本函数内部就释放了否则这个指针交给了SshSession类实例管理其析构时会释放。
// 注意ssh_new()出来的指针如果遇到停止标志本函数内部就释放了否则这个指针交给了SshSession类实例管理其析构时会释放。
ssh_session sess_to_client = ssh_new();
// int flag = SSH_LOG_FUNCTIONS;
// ssh_options_set(sess_to_client, SSH_OPTIONS_LOG_VERBOSITY, &flag);
// #ifdef EX_DEBUG
// int flag = SSH_LOG_FUNCTIONS;
// ssh_options_set(sess_to_client, SSH_OPTIONS_LOG_VERBOSITY, &flag);
// #endif
ssh_set_blocking(sess_to_client, 1);
//ssh_set_blocking(sess_to_client, 1);
struct sockaddr_storage sock_client;
char ip[32] = {0};
@ -145,14 +147,14 @@ void SshProxy::_thread_loop() {
sess->start();
}
// 等待所有工作线程退出
// 等待所有工作线程退出
//m_thread_mgr.stop_all();
{
ExThreadSmartLock locker(m_lock);
ts_ssh_sessions::iterator it = m_sessions.begin();
for (; it != m_sessions.end(); ++it) {
it->first->check_noop_timeout(0, 0); // 立即结束
it->first->check_noop_timeout(0, 0); // 立即结束
}
}
@ -173,7 +175,7 @@ void SshProxy::_on_stop() {
ExThreadBase::_on_stop();
if (m_is_running) {
// 用一个变通的方式来结束阻塞中的监听,就是连接一下它。
// 用一个变通的方式来结束阻塞中的监听,就是连接一下它。
ex_astr host_ip = m_host_ip;
if (host_ip == "0.0.0.0")
host_ip = "127.0.0.1";
@ -195,7 +197,7 @@ void SshProxy::_on_stop() {
}
void SshProxy::session_finished(SshSession *sess) {
// TODO: 向核心模块汇报此会话终止,以减少对应连接信息的引用计数
// TODO: 向核心模块汇报此会话终止,以减少对应连接信息的引用计数
ExThreadSmartLock locker(m_lock);
ts_ssh_sessions::iterator it = m_sessions.find(sess);

View File

@ -129,7 +129,7 @@ void SshSession::_record_end(TP_SSH_CHANNEL_PAIR *cp) {
if (cp->db_id > 0) {
//EXLOGD("[ssh] [channel:%d] channel end with code: %d\n", cp->channel_id, cp->state);
// 如果会话过程中没有发生错误,则将其状态改为结束,否则记录下错误值
// 如果会话过程中没有发生错误,则将其状态改为结束,否则记录下错误值
if (cp->state == TP_SESS_STAT_RUNNING || cp->state == TP_SESS_STAT_STARTED)
cp->state = TP_SESS_STAT_END;
@ -257,7 +257,7 @@ void SshSession::_run(void) {
int err = SSH_OK;
// 安全连接(密钥交换)
// 安全连接(密钥交换)
err = ssh_handle_key_exchange(m_cli_session);
if (err != SSH_OK) {
EXLOGE("[ssh] key exchange with client failed: %s\n", ssh_get_error(m_cli_session));
@ -275,7 +275,7 @@ void SshSession::_run(void) {
return;
}
// 认证,并打开一个通道
// 认证,并打开一个通道
while (!(m_is_logon && !m_channels.empty())) {
if (m_have_error)
break;
@ -296,7 +296,7 @@ void SshSession::_run(void) {
EXLOGW("[ssh] authenticated and got a channel.\n");
// 现在双方的连接已经建立好了,开始转发
// 现在双方的连接已经建立好了,开始转发
ssh_event_add_session(event_loop, m_srv_session);
do {
//err = ssh_event_dopoll(event_loop, 5000);
@ -333,11 +333,11 @@ void SshSession::_run(void) {
ssh_event_free(event_loop);
// 如果一边是走SSHv1另一边是SSHv2放在同一个event_loop时SSHv1会收不到数据放到循环中时SSHv2得不到数据
// 所以当SSHv1的远程主机连接后到建立好shell环境之后就进入另一种读取数据的循环不再使用ssh_event_dopoll()了。
// 如果一边是走SSHv1另一边是SSHv2放在同一个event_loop时SSHv1会收不到数据放到循环中时SSHv2得不到数据
// 所以当SSHv1的远程主机连接后到建立好shell环境之后就进入另一种读取数据的循环不再使用ssh_event_dopoll()了。
if (m_ssh_ver == 1) {
tp_channels::iterator it = m_channels.begin(); // SSHv1只能打开一个channel
tp_channels::iterator it = m_channels.begin(); // SSHv1只能打开一个channel
ssh_channel cli = (*it)->cli_channel;
ssh_channel srv = (*it)->srv_channel;
@ -447,7 +447,7 @@ int SshSession::_on_auth_password_request(ssh_session session, const char *user,
EXLOGV("[ssh] try to connect to real SSH server %s:%d\n", _this->m_conn_ip.c_str(), _this->m_conn_port);
_this->m_srv_session = ssh_new();
ssh_set_blocking(_this->m_srv_session, 1);
// ssh_set_blocking(_this->m_srv_session, 1);
ssh_options_set(_this->m_srv_session, SSH_OPTIONS_HOST, _this->m_conn_ip.c_str());
int port = (int) _this->m_conn_port;
@ -460,6 +460,9 @@ int SshSession::_on_auth_password_request(ssh_session session, const char *user,
// ssh_options_set(_this->m_srv_session, SSH_OPTIONS_LOG_VERBOSITY, &flag);
//#endif
int _timeout_cli = 120; // 120 sec.
ssh_options_set(_this->m_cli_session, SSH_OPTIONS_TIMEOUT, &_timeout_cli);
if (_this->m_auth_type != TP_AUTH_TYPE_NONE)
ssh_options_set(_this->m_srv_session, SSH_OPTIONS_USER, _this->m_acc_name.c_str());
@ -478,6 +481,11 @@ int SshSession::_on_auth_password_request(ssh_session session, const char *user,
return SSH_AUTH_ERROR;
}
if(ssh_is_blocking(_this->m_cli_session))
EXLOGD("[ssh] client session is blocking.\n");
if(ssh_is_blocking(_this->m_srv_session))
EXLOGD("[ssh] server session is blocking.\n");
// once the server are connected, change the timeout back to default.
_timeout = 120; // in seconds.
ssh_options_set(_this->m_srv_session, SSH_OPTIONS_TIMEOUT, &_timeout);
@ -635,13 +643,13 @@ int SshSession::_on_auth_password_request(ssh_session session, const char *user,
}
ssh_channel SshSession::_on_new_channel_request(ssh_session session, void *userdata) {
// 客户端尝试打开一个通道(然后才能通过这个通道发控制命令或者收发数据)
// 客户端尝试打开一个通道(然后才能通过这个通道发控制命令或者收发数据)
EXLOGV("[ssh] client open channel\n");
SshSession *_this = (SshSession *) userdata;
// TODO: 客户端与TP连接使用的总是SSHv2协议因为最开始连接时还不知道真正的远程主机是不是SSHv1。
// 因此此处行为与客户端直连远程主机有些不一样。直连时SecureCRT的克隆会话功能会因为以为连接的是SSHv1而自动重新连接而不是打开新通道。
// TODO: 客户端与TP连接使用的总是SSHv2协议因为最开始连接时还不知道真正的远程主机是不是SSHv1。
// 因此此处行为与客户端直连远程主机有些不一样。直连时SecureCRT的克隆会话功能会因为以为连接的是SSHv1而自动重新连接而不是打开新通道。
if (_this->m_ssh_ver == 1 && _this->m_channels.size() != 0) {
EXLOGE("[ssh] SSH1 supports only one execution channel. One has already been opened.\n");
return NULL;
@ -654,7 +662,7 @@ ssh_channel SshSession::_on_new_channel_request(ssh_session session, void *userd
}
ssh_set_channel_callbacks(cli_channel, &_this->m_cli_channel_cb);
// 我们也要向真正的服务器申请打开一个通道,来进行转发
// 我们也要向真正的服务器申请打开一个通道,来进行转发
ssh_channel srv_channel = ssh_channel_new(_this->m_srv_session);
if (srv_channel == NULL) {
EXLOGE("[ssh] can not create channel for server.\n");
@ -683,7 +691,7 @@ ssh_channel SshSession::_on_new_channel_request(ssh_session session, void *userd
return NULL;
}
// 将客户端和服务端的通道关联起来
// 将客户端和服务端的通道关联起来
{
ExThreadSmartLock locker(_this->m_lock);
_this->m_channels.push_back(cp);
@ -795,7 +803,7 @@ int SshSession::_on_client_channel_data(ssh_session session, ssh_channel channel
SshSession *_this = (SshSession *) userdata;
// 当前线程正在接收服务端返回的数据,因此我们直接返回,这样紧跟着会重新再发送此数据的
// 当前线程正在接收服务端返回的数据,因此我们直接返回,这样紧跟着会重新再发送此数据的
if (_this->m_recving_from_srv) {
// EXLOGD("recving from srv...try again later...\n");
return 0;
@ -816,14 +824,14 @@ int SshSession::_on_client_channel_data(ssh_session session, ssh_channel channel
int _len = len;
if (cp->type == TS_SSH_CHANNEL_TYPE_SHELL) {
// 在收取服务端数据直到显示命令行提示符之前,不允许发送客户端数据到服务端,避免日志记录混乱。
// 在收取服务端数据直到显示命令行提示符之前,不允许发送客户端数据到服务端,避免日志记录混乱。
if (!cp->server_ready) {
_this->m_recving_from_cli = false;
return 0;
}
// 不可以拆分!!否则执行 rz 命令会出错!
// xxxx 如果用户复制粘贴多行文本,我们将其拆分为每一行发送一次数据包
// 不可以拆分!!否则执行 rz 命令会出错!
// xxxx 如果用户复制粘贴多行文本,我们将其拆分为每一行发送一次数据包
// for (unsigned int i = 0; i < len; ++i) {
// if (((ex_u8 *) data)[i] == 0x0d) {
// _len = i + 1;
@ -890,7 +898,7 @@ int SshSession::_on_client_channel_subsystem_request(ssh_session session, ssh_ch
cp->last_access_timestamp = (ex_u32) time(NULL);
// 目前只支持SFTP子系统
// 目前只支持SFTP子系统
if (strcmp(subsystem, "sftp") != 0) {
EXLOGE("[ssh] support `sftp` subsystem only, but got `%s`.\n", subsystem);
cp->state = TP_SESS_STAT_ERR_UNSUPPORT_PROTOCOL;
@ -962,7 +970,7 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel
int ret = 0;
// 收到第一包服务端返回的数据时,在输出数据之前显示一些自定义的信息
// 收到第一包服务端返回的数据时,在输出数据之前显示一些自定义的信息
#if 1
if (!is_stderr && cp->is_first_server_data) {
cp->is_first_server_data = false;
@ -989,13 +997,15 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel
"\r\n"\
"%s\r\n"\
"Teleport SSH Bastion Server...\r\n"\
" - teleport to %s:%d\r\n"\
" - teleport to %s:%d [%d]\r\n"\
" - authroized by %s\r\n"\
"%s\r\n"\
"\r\n\r\n",
line.c_str(),
_this->m_conn_ip.c_str(),
_this->m_conn_port, auth_mode,
_this->m_conn_port,
cp->db_id,
auth_mode,
line.c_str()
);
@ -1014,15 +1024,45 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel
#endif
#if 1
// 直接转发数据到客户端
if (is_stderr)
ret = ssh_channel_write_stderr(cp->cli_channel, data, len);
else
ret = ssh_channel_write(cp->cli_channel, data, len);
//static int idx = 0;
ssh_set_blocking(_this->m_cli_session, 0);
int xx = 0;
for(xx = 0; xx < 10; ++xx) {
// idx++;
// EXLOGD(">>>>> %d . %d\n", cp->db_id, idx);
// 直接转发数据到客户端
if (is_stderr)
ret = ssh_channel_write_stderr(cp->cli_channel, data, len);
else
ret = ssh_channel_write(cp->cli_channel, data, len);
// EXLOGD("<<<<< %d . %d\n", cp->db_id, idx);
if(ret == SSH_OK) {
// EXLOGD("ssh_channel_write() ok.\n");
break;
}
else if(ret == SSH_AGAIN) {
// EXLOGD("ssh_channel_write() need again, %d.\n", xx);
ex_sleep_ms(500);
continue;
}
else {
// EXLOGD("ssh_channel_write() failed.\n");
break;
}
}
ssh_set_blocking(_this->m_cli_session, 1);
#else
// 分析收到的服务端数据包,如果包含类似 \033]0;AABB\007 这样的数据,客户端会根据此改变窗口标题
// 我们需要替换这部分数据,使之显示类似 \033]0;TP#ssh://remote-ip\007 这样的标题。
// 但是这样会降低一些性能,因此目前不启用,保留此部分代码备用。
// 分析收到的服务端数据包,如果包含类似 \033]0;AABB\007 这样的数据,客户端会根据此改变窗口标题
// 我们需要替换这部分数据,使之显示类似 \033]0;TP#ssh://remote-ip\007 这样的标题。
// 但是这样会降低一些性能,因此目前不启用,保留此部分代码备用。
if (is_stderr) {
ret = ssh_channel_write_stderr(cp->cli_channel, data, len);
}
@ -1039,7 +1079,7 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel
{
_end++;
// 这个包中含有改变标题的数据,将标题换为我们想要的
// 这个包中含有改变标题的数据,将标题换为我们想要的
EXLOGD("-- found title\n");
size_t len_end = len - (_end - (const ex_u8*)data);
MemBuffer mbuf;
@ -1061,7 +1101,7 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel
if (ret == SSH_ERROR)
break;
if (ret == mbuf.size()) {
ret = len; // 表示我们已经处理了所有的数据了。
ret = len; // 表示我们已经处理了所有的数据了。
break;
}
else {
@ -1140,7 +1180,7 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR *cp, int from, const e
if (TP_SSH_CLIENT_SIDE == from) {
if (len >= 2) {
if (((ex_u8 *) data)[len - 1] == 0x0d) {
// 疑似复制粘贴多行命令一次性执行,将其记录到日志文件中
// 疑似复制粘贴多行命令一次性执行,将其记录到日志文件中
ex_astr str((const char *) data, len - 1);
cp->rec.record_command(1, str);
@ -1149,13 +1189,13 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR *cp, int from, const e
}
}
// 客户端输入回车时,可能时执行了一条命令,需要根据服务端返回的数据进行进一步判断
// 客户端输入回车时,可能时执行了一条命令,需要根据服务端返回的数据进行进一步判断
cp->maybe_cmd = (data[len - 1] == 0x0d);
// if (cp->maybe_cmd)
// EXLOGD("[ssh] maybe cmd.\n");
// 有时在执行类似top命令的情况下输入一个字母'q'就退出程序,没有输入回车,可能会导致后续记录命令时将返回的命令行提示符作为命令
// 记录下来了,要避免这种情况,排除的方式是:客户端单个字母,后续服务端如果收到的是控制序列 1b 5b xx xx就不计做命令。
// 有时在执行类似top命令的情况下输入一个字母'q'就退出程序,没有输入回车,可能会导致后续记录命令时将返回的命令行提示符作为命令
// 记录下来了,要避免这种情况,排除的方式是:客户端单个字母,后续服务端如果收到的是控制序列 1b 5b xx xx就不计做命令。
cp->client_single_char = (len == 1 && isprint(data[0]));
cp->process_srv = true;
@ -1194,15 +1234,15 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR *cp, int from, const e
case 0x4b: { // 'K'
if (0 == esc_arg) {
// 删除光标到行尾的字符串
// 删除光标到行尾的字符串
cp->cmd_char_list.erase(cp->cmd_char_pos, cp->cmd_char_list.end());
cp->cmd_char_pos = cp->cmd_char_list.end();
} else if (1 == esc_arg) {
// 删除从开始到光标处的字符串
// 删除从开始到光标处的字符串
cp->cmd_char_list.erase(cp->cmd_char_list.begin(), cp->cmd_char_pos);
cp->cmd_char_pos = cp->cmd_char_list.end();
} else if (2 == esc_arg) {
// 删除整行
// 删除整行
cp->cmd_char_list.clear();
cp->cmd_char_pos = cp->cmd_char_list.begin();
}
@ -1211,7 +1251,7 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR *cp, int from, const e
break;
}
case 0x43: {// ^[C
// 光标右移
// 光标右移
if (esc_arg == 0)
esc_arg = 1;
for (int j = 0; j < esc_arg; ++j) {
@ -1222,7 +1262,7 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR *cp, int from, const e
break;
}
case 0x44: { // ^[D
// 光标左移
// 光标左移
if (esc_arg == 0)
esc_arg = 1;
for (int j = 0; j < esc_arg; ++j) {
@ -1234,7 +1274,7 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR *cp, int from, const e
break;
}
case 0x50: {// 'P' 删除指定数量的字符
case 0x50: {// 'P' 删除指定数量的字符
if (esc_arg == 0)
esc_arg = 1;
@ -1246,7 +1286,7 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR *cp, int from, const e
break;
}
case 0x40: { // '@' 插入指定数量的空白字符
case 0x40: { // '@' 插入指定数量的空白字符
if (esc_arg == 0)
esc_arg = 1;
for (int j = 0; j < esc_arg; ++j)
@ -1268,10 +1308,10 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR *cp, int from, const e
switch (ch) {
case 0x07:
// 响铃
// 响铃
break;
case 0x08: {
// 光标左移
// 光标左移
if (cp->cmd_char_pos != cp->cmd_char_list.begin())
cp->cmd_char_pos--;
break;
@ -1344,10 +1384,10 @@ void SshSession::_process_sftp_command(TP_SSH_CHANNEL_PAIR *cp, const ex_u8 *dat
// SFTP protocol: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13
//EXLOG_BIN(data, len, "[sftp] client channel data");
// TODO: 根据客户端的请求和服务端的返回,可以进一步判断用户是如何操作文件的,比如读、写等等,以及操作的结果是成功还是失败。
// 记录格式: time-offset,flag,action,result,file-path,[file-path]
// 其中flag目前总是为0可以忽略为保证与ssh-cmd格式一致time-offset/action/result 都是数字
// file-path是被操作的对象规格为 长度:实际内容,例如, 13:/root/abc.txt
// TODO: 根据客户端的请求和服务端的返回,可以进一步判断用户是如何操作文件的,比如读、写等等,以及操作的结果是成功还是失败。
// 记录格式: time-offset,flag,action,result,file-path,[file-path]
// 其中flag目前总是为0可以忽略为保证与ssh-cmd格式一致time-offset/action/result 都是数字
// file-path是被操作的对象规格为 长度:实际内容,例如, 13:/root/abc.txt
if (len < 9)
@ -1365,7 +1405,7 @@ void SshSession::_process_sftp_command(TP_SSH_CHANNEL_PAIR *cp, const ex_u8 *dat
return;
}
// 需要的数据至少14字节
// 需要的数据至少14字节
// uint32 + byte + uint32 + (uint32 + char + ...)
// pkg_len + cmd + req_id + string( length + content...)
if (len < 14)
@ -1398,13 +1438,13 @@ void SshSession::_process_sftp_command(TP_SSH_CHANNEL_PAIR *cp, const ex_u8 *dat
break;
case 0x12:
// 0x12 = 18 = SSH_FXP_RENAME
// rename操作数据中包含两个字符串
// rename操作数据中包含两个字符串
str2_ptr = str1_ptr + str1_len + 4;
str2_len = (int) ((str2_ptr[0] << 24) | (str2_ptr[1] << 16) | (str2_ptr[2] << 8) | str2_ptr[3]);
break;
case 0x15:
// 0x15 = 21 = SSH_FXP_LINK
// link操作数据中包含两个字符串前者是新的链接文件名后者是现有被链接的文件名
// link操作数据中包含两个字符串前者是新的链接文件名后者是现有被链接的文件名
str2_ptr = str1_ptr + str1_len + 4;
str2_len = (int) ((str2_ptr[0] << 24) | (str2_ptr[1] << 16) | (str2_ptr[2] << 8) | str2_ptr[3]);
break;