在网上流传的gh0st3.6源代码中,远程桌面总是存在CPU占用率高和画面更新不及时等问题。于是想到了著名的开源远程控制RealVNC 它采用了远程帧缓存的协议(Remote Frame buffer)
在网上找到的一段关于RFB的描述
RFB 是真正意义上的“瘦客机”协议。RFB协议设计的重点在于减少对客户端的硬件需求。这样客户端就可以运行在许多不同的硬件上,客户机的任务实现上就会尽量的简单。
RFB协议对于客户端是无状态的。也就是说:如果客户端从服务器端断开,那么如果它重新连接相同的服务器,客户端的状态会被保存。甚至,一个不同的客户端可以用来连接相同的RFB服务器。而在新的客户端已经能够获得与前一个客户端相同的用户状态。因此,用户的应用接口变的非常便捷。
只要合适的网络连接存在,那么用户就可以使用自己的应用程序,并且这些应用会一直保存,即使在不同的接入点也不会变化。这样无论在哪,系统都会给用户提供一个熟悉、独特的计算环境。
显示协议显示协议是建立在“把像素数据放在一个由x,y 定位的方框内”这单一图形基础之上的。
乍一看上去,把这么多的用户接口组件绘制出来是非常低效的方法。但是,允许不同的像素数据编码方式,使得我们在处理不同的参数(如:网络带宽,客户端的绘制速度,服务器处理速度)有了很大程度的灵活性。
通过矩形的序列来完成帧缓存的更新。一次更新代表着从一个可用帧缓存状态转换到另一个可用,因此有点和视频的桢类似。尽管矩形的更新一般是分开的,但是并不是必须的。
显示协议的更新部分是由客户端通过命令驱动的。也就是说,更新只是在服务器端响应客户端的请求时发生的。这样就让协议更新质量是可变的。客户端/网络越慢,更新速度也就越慢。对于一些应用来说,相同区域的更新是连续不断的。如果用一个慢的客户端,那么帧缓存的缓存状态是可以被忽略的。这样也可以减少对客户端网络速度和绘制速度的要求。
输入协议输入协议是基于标准工作站的键盘和鼠标等设备的连接协议。
输入事件就是通过把客户端的输入发送到服务器端。
这些输入事件也可以通过非标准的I /O 设备来综合。
例如,手写笔引擎可能产生一个键盘事件。
像素数据的表示初始的交互涉及到RFB客户端和服务器之间传输像素数据格式和编码方式的协调。这种协调被设计的让客户端的工作尽量简单。而设计的底线是:服务器必须按照客户端的要求格式来提供像素数据。如果客户端可以同样的处理多种数据格式或编码格式,那么一般会选择服务器端易于生成的格式。
像素格式涉及如何通过像素值来实现不同颜色的重现。最常用的一般像素格式是24 位或16 位的“真彩色”,它通过位来直接实现像素值到红、绿、蓝亮度的转换。8 位“颜色映射”可以任意映射像素值到RGB亮度的转换。
编码指一个矩形的像素数据如何通过网线传输。每个像素数据的矩形都加上了一个头,给定矩形在屏幕上的X、Y坐标、矩形的宽和高,以及指定的编码类型。而后数据本身就是采用这种特定的编码方式。
数据本身遵循特定的编码。目前的编码方式主要有Raw、CopyRect、RRE、Hextile 和ZRLE.在实际应用中我们一般使用ZRLE、Hextile 和CopyRect,因为它们提供了典型桌面的最好压缩。其他可能的编码方式还包括,用于静态图片的JPEG和用于动态图像有效传输的MPEG。
协议可以通过增加新的编码方式来进行扩展。
协议扩展协议可以通过以下方式进行扩展:
新的编码方式
一种新的协议可以通过与现存的客户端和服务端进行相关兼容的添加。因为现存的服务器将会忽略它们所不支持的新编码方式。所以客户端通过新的编码方式进行请求也就不会有结果返回。
伪编码方式
除了真正的编码方式,客户端也可以请求“伪编码”通告服务器,它支持某一协议的扩展。服务器如果不支持这种扩展,那么它将忽略。值得注意的是:客户端必须先假设服务器端不支持这种扩展,直到它获得服务器端支持的确认。
新的安全方式
添加一个新型的安全方式会带来无限的灵活性,它通过修改协议的一些行为,但是并没有牺牲现存客户端和服务器端的兼容性。客户端和服务器端可以通过协议好的安全方式进行交流,当然并不一定与RFB协议类似。
无论如何你都不应使用不同的版本号。
RFB协议的版本是由RealVNC公司来制定的。如果你使用一个不同的协议版本可能与RFB/VNC不兼容,要保证协议的兼容性,请联系RealVNC公司。这样会减少在编码方式和安全类型上的冲突。
协议消息RFB协议可以进行可靠的传输,如字节流或基于消息的。和大多数协议一样,它也是通过TCP /IP协议簇连接。协议由三步完成连接。首先是握手报文,目的是对协议版本和加密方式进行协商。第二步是初始化报文,主要用于客户和服务器的初始化消息。最后就是正常协议的交互,客户端可以按需发送消息,然后可以获得服务器的回复。
所有的消息以消息类型开始,接下来是特定的消息数据。
协议消息描述的基本类型有:U8、U16、U32、S8、S16、S32。
U表示无符号整数,S表示有符号整数。
所有字节整数(除了像素值本身)遵从big endian顺序。
big endian或者little endian跟cpu有关,从而影响整数在内存中的排列顺序。big endian是高字节在前,little endian是低字节在前,网络字节序一般是big-endian。
PIXEL代表一个像素值bytesPerPixel字节,8XbytesPerPixel = bits-per-pixel
RealVNC 分为客户端和服务端
客户端名为 VNCViewer
服务端名为 VNC
客户端连接到服务器端
这时候 服务端 会给 客户端发送当前提供最大支持的版本号。
//初始化RFB协议
void SConnection::initialiseProtocol()
{
cp.writeVersion(os);
state_ = RFBSTATE_PROTOCOL_VERSION;
}
//写入版本
void ConnParams::writeVersion(rdr::OutStream* os)
{
char str[13];
sprintf(str, "RFB %03d.%03d\n", majorVersion, minorVersion);
os->writeBytes(str, 12);
os->flush();
}
这里对应的发送的内容如下
00000000 52 46 42 20 30 30 33 2E 30 30 38 0A RFB 003.008.
发送数据长度 12
发送数据内容 RFB 003.008
此时VNC 的数据处理状态为 RFBSTATE_PROTOCOL_VERSION
2.viewer收到来自vnc的版本号信息
因为viewer在init()函数中已经设置数据处理状态为 RFBSTATE_PROTOCOL_VERSION
void CConnection::initialiseProtocol()
{
state_ = RFBSTATE_PROTOCOL_VERSION;
}
void CConnection::processMsg()
{
switch (state_)
{
case RFBSTATE_PROTOCOL_VERSION:
processVersionMsg();
break;
case RFBSTATE_SECURITY_TYPES:
processSecurityTypesMsg();
break;
case RFBSTATE_SECURITY:
processSecurityMsg();
break;
case RFBSTATE_SECURITY_RESULT:
processSecurityResultMsg();
break;
case RFBSTATE_INITIALISATION:
processInitMsg();
break;
case RFBSTATE_NORMAL:
reader_->readMsg();
break;
case RFBSTATE_UNINITIALISED:
throw Exception("CConnection::processMsg: not initialised yet?");
default:
throw Exception("CConnection::processMsg: invalid state");
}
}
进入 processVersionMsg()函数
//处理 RFBSTATE_PROTOCOL_VERSION
void CConnection::processVersionMsg()
{
vlog.debug("reading protocol version");
bool done;
//这里读取服务器版本,如果读取失败 数据处理状态切换为 RFBSTATE_INVALID
if (!cp.readVersion(is, &done))
{
state_ = RFBSTATE_INVALID;
throw Exception("reading version failed: not an RFB server?");
}
//读取失败 返回
if (!done)
return;
vlog.info("Server supports RFB protocol version %d.%d",
cp.majorVersion, cp.minorVersion);
// The only official RFB protocol versions are currently 3.3, 3.7 and 3.8
//如果RFB版本号低于 3.3 不支持。数据处理状态切换为 RFB_INVALID 抛出异常
if (cp.beforeVersion(3, 3))
{
char msg[256];
sprintf(msg, "Server gave unsupported RFB protocol version %d.%d",
cp.majorVersion, cp.minorVersion);
vlog.error(msg);
state_ = RFBSTATE_INVALID;
throw Exception(msg);
}
else if (useProtocol3_3 || cp.beforeVersion(3, 7))
{
cp.setVersion(3, 3);
}
else if (cp.afterVersion(3, 8))
{
cp.setVersion(3, 8);
}
//这里写入版本号信息
cp.writeVersion(os);
//Viewer切换数据处理状态 RFBSTATE_SECURITY_TYPES
state_ = RFBSTATE_SECURITY_TYPES;
vlog.info("Using RFB protocol version %d.%d",
cp.majorVersion, cp.minorVersion);
}
//向服务器发送版本号
void ConnParams::writeVersion(rdr::OutStream* os)
{
char str[13];
sprintf(str, "RFB %03d.%03d\n", majorVersion, minorVersion);
os->writeBytes(str, 12);
os->flush();
}
这个函数发送的内容
00000000 52 46 42 20 30 30 33 2E 30 30 38 0A RFB 003.008.
长度为 12 个字节
这时候 VNCViewer的数据处理状态为 RFBSTATE_SECURITY_TYPES
3.vnc收到来自viewer的版本信息
因为VNC当前的数据处理状态是 RFBSTATE_PROTOCOL_VERSION
所以会进入processVersionMsg()函数处理viewer发送过来的版本信息
//处理 RFBSTATE_PROTOCOL_VERSION
void SConnection::processVersionMsg()
{
vlog.debug("reading protocol version");
bool done;
//读取来自viewer的版本号
if (!cp.readVersion(is, &done))
{
state_ = RFBSTATE_INVALID;
throw Exception("reading version failed: not an RFB client?");
}
if (!done)
return;
vlog.info("Client needs protocol version %d.%d",cp.majorVersion, cp.minorVersion);
if (cp.majorVersion != 3)
{
// unknown protocol version
char msg[256];
sprintf(msg, "Error: client needs protocol version %d.%d, server has %d.%d",cp.majorVersion, cp.minorVersion,defaultMajorVersion, defaultMinorVersion);
throwConnFailedException(msg);
}
if (cp.minorVersion != 3 && cp.minorVersion != 7 && cp.minorVersion != 8)
{
vlog.error("Client uses unofficial protocol version %d.%d",cp.majorVersion, cp.minorVersion);
if (cp.minorVersion >= 8)
cp.minorVersion = 8;
else if (cp.minorVersion == 7)
cp.minorVersion = 7;
else
cp.minorVersion = 3;
vlog.error("Assuming compatibility with version %d.%d",cp.majorVersion, cp.minorVersion);
}
versionReceived();
//这里是获取到加密类型
std::list<rdr::U8> secTypes;
std::list<rdr::U8>::iterator i;
securityFactory->getSecTypes(&secTypes, reverseConnection);
if (cp.isVersion(3, 3))
{
// cope with legacy 3.3 client only if "no authentication" or "vnc
// authentication" is supported.
for (i = secTypes.begin(); i != secTypes.end(); i++)
{
if (*i == secTypeNone || *i == secTypeVncAuth)
break;
}
if (i == secTypes.end())
{
char msg[256];
sprintf(msg, "No supported security type for %d.%d client",
cp.majorVersion, cp.minorVersion);
throwConnFailedException(msg);
}
os->writeU32(*i);
if (*i == secTypeNone)
os->flush();
state_ = RFBSTATE_SECURITY;
security = securityFactory->getSSecurity(*i, reverseConnection);
processSecurityMsg();
return;
}
// list supported security types for >=3.7 clients
if (secTypes.empty())
throwConnFailedException("No supported security types");
os->writeU8(secTypes.size());
for (i = secTypes.begin(); i != secTypes.end(); i++)
os->writeU8(*i);
os->flush();
state_ = RFBSTATE_SECURITY_TYPE;
}
这个函数主要是
a.验证RFB版本号
b.获取到加密类型
c.发送加密类型到VncViewer
d.设置数据处理标志为 RFBSTATE_SECURITY_TYPE
4.vncViewer状态为RFBSTATE_SECURITY_TYPES
收到vnc发送过来的加密类型后执行
processSecurityTypesMsg()
void CConnection::processSecurityTypesMsg()
{
vlog.debug("processing security types message");
int secType = secTypeInvalid;
if (cp.isVersion(3, 3))
{
// legacy 3.3 server may only offer "vnc authentication" or "none"
secType = is->readU32();
if (secType == secTypeInvalid)
{
throwConnFailedException();
}
else if (secType == secTypeNone || secType == secTypeVncAuth)
{
int j;
for (j = 0; j < nSecTypes; j++)
if (secTypes[j] == secType)
break;
if (j == nSecTypes)
secType = secTypeInvalid;
}
else
{
vlog.error("Unknown 3.3 security type %d", secType);
throw Exception("Unknown 3.3 security type");
}
}
else
{
// >=3.7 server will offer us a list
int nServerSecTypes = is->readU8();
if (nServerSecTypes == 0)
throwConnFailedException();
int secTypePos = nSecTypes;
for (int i = 0; i < nServerSecTypes; i++)
{
rdr::U8 serverSecType = is->readU8();
vlog.debug("Server offers security type %s(%d)",
secTypeName(serverSecType), serverSecType);
// If we haven't already chosen a secType, try this one
// If we are using the client's preference for types,
// we keep trying types, to find the one that matches and
// which appears first in the client's list of supported types.
if (secType == secTypeInvalid || clientSecTypeOrder)
{
for (int j = 0; j < nSecTypes; j++)
{
if (secTypes[j] == serverSecType && j < secTypePos)
{
secType = secTypes[j];
secTypePos = j;
break;
}
}
// NB: Continue reading the remaining server secTypes, but ignore them
}
}
// Inform the server of our decision
if (secType != secTypeInvalid)
{
os->writeU8(secType);
os->flush();
vlog.debug("Choosing security type %s(%d)", secTypeName(secType), secType);
}
}
if (secType == secTypeInvalid)
{
state_ = RFBSTATE_INVALID;
vlog.error("No matching security types");
throw Exception("No matching security types");
}
state_ = RFBSTATE_SECURITY;
//因为这里没有开启认证,所以security返回的是一个CSecurityNone类
security = getCSecurity(secType);
//调用getCSecurity函数进行下一步的认证工作
processSecurityMsg();
}
CSecurity* CConn::getCSecurity(int secType)
{
switch (secType)
{
case secTypeNone:
return new CSecurityNone();
case secTypeVncAuth:
return new CSecurityVncAuth(this);
default:
throw Exception("Unsupported secType?");
}
}
这个函数
a.获取加密类型
b.切换数据处理状态为 RFBSTATE_SECURITY
C.生成用例处理加密的类
d.调用processSecurityMsg()函数继续处理加密
void CConnection::processSecurityMsg()
{
vlog.debug("processing security message");
if (security->processMsg(this))
{
state_ = RFBSTATE_SECURITY_RESULT;
processSecurityResultMsg();
}
}
因为我这里没有开启认证,所以processMsg()函数直接返回TRUE就通过验证。
如果开启了VNC验证,会执行这个函数
bool CSecurityVncAuth::processMsg(CConnection* cc)
{
rdr::InStream* is = cc->getInStream();
rdr::OutStream* os = cc->getOutStream();
// Read the challenge & obtain the user's password
rdr::U8 challenge[vncAuthChallengeSize];
is->readBytes(challenge, vncAuthChallengeSize);
PlainPasswd passwd;
upg->getUserPasswd(0, &passwd.buf);
// Calculate the correct response
rdr::U8 key[8];
int pwdLen = strlen(passwd.buf);
for (int i = 0; i < 8; i++)
key[i] = i < pwdLen ? passwd.buf[i] : 0;
deskey(key, EN0);
for (int j = 0; j < vncAuthChallengeSize; j += 8)
des(challenge + j, challenge + j);
// Return the response to the server
os->writeBytes(challenge, vncAuthChallengeSize);
os->flush();
return true;
}
认证通过后数据处理状态切换为
RFBSTATE_SECURITY_RESULT
继续执行processSecurityResultMsg()函数
void CConnection::processSecurityResultMsg()
{
vlog.debug("processing security result message");
int result;
if (cp.beforeVersion(3, 8) && security->getType() == secTypeNone)
{
result = secResultOK;
}
else
{
if (!is->checkNoWait(1))
return;
result = is->readU32();
}
switch (result)
{
case secResultOK:
securityCompleted();
return;
case secResultFailed:
vlog.debug("auth failed");
break;
case secResultTooMany:
vlog.debug("auth failed - too many tries");
break;
default:
throw Exception("Unknown security result from server");
}
CharArray reason;
if (cp.beforeVersion(3, 8))
reason.buf = strDup("Authentication failure");
else
reason.buf = is->readString();
state_ = RFBSTATE_INVALID;
throw AuthFailureException(reason.buf);
}
验证通过
执行securityCompleted()函数
void CConnection::securityCompleted()
{
state_ = RFBSTATE_INITIALISATION;
reader_ = new CMsgReaderV3(this, is);
writer_ = new CMsgWriterV3(&cp, os);
vlog.debug("Authentication success!");
authSuccess();
writer_->writeClientInit(shared);
}
设置 vncViewer的数据处理状态为 RFBSTATE_INITIALISATION
创建CMsgReaderV3类用于读取
创建CMsgWriterV3类用于写入
调用 writeClientInit 函数
void CMsgWriterV3::writeClientInit(bool shared)
{
//是否分享屏幕 默认为false
os->writeU8(shared);
endMsg();
}
void CMsgWriterV3::endMsg()
{
os->flush();
}
这里,viewer数据处理状态为 RFBSTATE_INITIALISATION
所以这个函数发送了两次数据
第一次是发送加密类型 os->writeU8(secType)
第二次是发送的屏幕是否共享 os->writeU8(shared);
发送数据内容
5. vnc当前状态为 RFBSTATE_SECURITY_TYPE
调用processSecurityTypeMsg函数来处理Viewer的数据
//RFBSTATE_SECURITY_TYPE
void SConnection::processSecurityTypeMsg()
{
vlog.debug("processing security type message");
//第一次是读取 加密类型
int secType = is->readU8();
// Verify that the requested security type should be offered
std::list<rdr::U8> secTypes;
std::list<rdr::U8>::iterator i;
securityFactory->getSecTypes(&secTypes, reverseConnection);
for (i = secTypes.begin(); i != secTypes.end(); i++)
if (*i == secType)
break;
if (i == secTypes.end())
throw Exception("Requested security type not available");
vlog.info("Client requests security type %s(%d)",secTypeName(secType), secType);
try
{
state_ = RFBSTATE_SECURITY;
//同viewer一样,这里根据加密类型生成对应的加密处理类 如果有加密就是 CSecurityVncAuth 类 这里因为没有启动加密,所以生成的是SSecurityNone类
security = securityFactory->getSSecurity(secType, reverseConnection);
}
catch (rdr::Exception& e)
{
throwConnFailedException(e.str());
}
processSecurityMsg();
}
processSecurityMsg()继续处理数据
void SConnection::processSecurityMsg()
{
vlog.debug("processing security message");
try
{
//对于未加密的认证类处理processMsg函数,总是返回true
bool done = security->processMsg(this);
if (done)
{
state_ = RFBSTATE_QUERYING;
queryConnection(security->getUserName());
}
}
catch (AuthFailureException& e)
{
vlog.error("AuthFailureException: %s", e.str());
os->writeU32(secResultFailed);
if (!cp.beforeVersion(3, 8)) // 3.8 onwards have failure message
os->writeString(e.str());
os->flush();
throw;
}
}
这个函数切换vnc数据处理状态为 RFBSTATE_QUERYING
执行queryConnection函数
void SConnection::queryConnection(const char* userName)
{
approveConnection(true);
}
这个函数会进入 approveConnection()函数 ,传入参数为true 即是接受连接
void SConnection::approveConnection(bool accept, const char* reason)
{
if (state_ != RFBSTATE_QUERYING)
throw Exception("SConnection::approveConnection: invalid state");
if (!reason)
reason = "Authentication failure";
if (!cp.beforeVersion(3, 8) || security->getType() != secTypeNone)
{
if (accept)
{
os->writeU32(secResultOK);
}
else
{
os->writeU32(secResultFailed);
if (!cp.beforeVersion(3, 8)) // 3.8 onwards have failure message
os->writeString(reason);
}
os->flush();
}
if (accept)
{
state_ = RFBSTATE_INITIALISATION;
reader_ = new SMsgReaderV3(this, is);
writer_ = new SMsgWriterV3(&cp, os);
authSuccess();
}
else
{
state_ = RFBSTATE_INVALID;
throw AuthFailureException(reason);
}
}
如果RFB版本不低于3.8这个函数写入 secResultOK 也就是 0
否则写入错误信息
切换数据处理状态为RFBSTATE_INITIALISATION
然后调用一个空函数
void SConnection::authSuccess()
{
}
所以在这一次处理中 先读取了加密类型。但是对于发送过来的屏幕是否共享这个值是否有读取,我还没有找到相关的代码。
接着发送 secResultOK 的值 0
Vnc状态 RFBSTATE_INITIALISATION