一, 开发目的及原理
本公司在产品开发过程中,需要研究多种以太网交换机(又称智能集线器)的内部网管信息结构,为此,必须编写出一个“窃听”程序,把网管程序和交换机的通讯内容记录下来加以分析。本人在Visual C++ 6.0下用MFC Socket类编出程序,成功地实现了上述目的。
目前,标准的网络管理程序与支持网管的网络设备之间大多采用标准的简单网络管理协议(SNMP)进行通讯。SNMP是一种高层协议,建立于UDP/IP之上。通讯双方按照SNMP格式来传递各种网管信息和控制信息,并能进行事件实时报告或报警,从而使网络管理员能方便及时地控制网络当前的运行情况。
网管信息的范围十分广泛,如网络流量,连接状态等,因被管设备的不同而不同,厂家也能依照有关的国际标准自定义自家产品的网管信息。网管信息集中定义于管理信息库(MIB)中,整个体系是一个可扩展的树状结构。一条条的网管信息被包装在SNMP协议包内,再往下传给传输层,转成UDP包,然后通过Socket 机制发送出去。
本程序的基本原理是:插到网管程序和被管设备之间“欺上瞒下”,与网管程序通讯时冒充被管设备;与被管设备通讯时冒充网管程序,使二者对本程序“无话不谈”;本程序则暗中有序地记录下谈话内容,然后再“上传下达”,将收到的内容转发给真正的接收者,使谈话继续下去,如此循环不已。
二, 编程的思路和具体过程
本程序不需要复杂的图形界面,因此,只需用Project Wizard开出一个支持Socket而基于对话框的MFC 应用程序即可。对话框的类名为CChatDlg,然后再用资源编辑器在这个对话框上加上一个按钮,面上文字为“Listen”。接收到的所有信息将在Visual C++集成环境的Output窗口中用TRACE语句打出,这样做的目的是能方便及时地看到各种数据,当然也可用别的方法。在本程序中,Client指网管程序,Server指交换机。
接着给本项目添加两个类,它们都衍生自CSocket,可调用ClassWizard工具生成。CClientSocket用于接收来自网管程序的UDP数据,而CServerSocket则用于接收来自交换机的UDP数据。这两个类的定义如下:
class CClientSocket : public CSocket
{
// Attributes
public:
// Operations
public:
CClientSocket(CChatDlg* pdlg);
virtual ~CClientSocket();
// Overrides
public:
BOOL m_bFirst;
CChatDlg* pDlg;
// ClassWizard generated virtual
function overrides
//{{AFX_VIRTUAL(CClientSocket)
public:
virtual void OnReceive(int nErrorCode);
//}}AFX_VIRTUAL
// Generated message map functions
//{{AFX_MSG(CClientSocket)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
// Implementation
protected:
};
m_bFirst和pDlg是自定义的两个类别成员, 其作用见下文。
class CServerSocket : public CSocket
{
// Attributes
public:
// Operations
public:
CServerSocket(CChatDlg* pdlg);
virtual ~CServerSocket();
// Overrides
public: CChatDlg* pDlg;
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CServerSocket)
public:
virtual void OnReceive(int nErrorCode);
//}}AFX_VIRTUAL
// Generated message map functions
//{{AFX_MSG(CServerSocket)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
// Implementation
protected:
};
然后,在CChatDlg类中加入对 按钮Listen的处理函数如下:
void CChatDlg::OnListen()
{
pClientSocket = new CClientSocket(this);
if(pClientSocket != NULL)
{
if(!pClientSocket->Create(SNMP_SOCKET_PORT, SOCK_DGRAM))
AfxMessageBox("Can not create ClientSocket !");
else
::EnableWindow(GetDlgItem(IDC_LISTEN)-> m_hWnd,FALSE);
}
else
{
AfxMessageBox("Can not new ClientSocket !");
}
}
注意:SNMP_SOCKET_PORT应设为161。 然后,在CClientSocket中加入 虚函数OnReceive的实作内容:
void CClientSocket::OnReceive(int nErrorCode)
{
CSocket::OnReceive(nErrorCode);
unsigned char tmp[MAXTMPSIZE];
//MAXTMPSIZE是自定义宏,可为1024;
int i;
int RecNum;
UINT ClientPort;
CString ClientAddress;
if(m_bFirst)
{
m_bFirst = false;
RecNum = ReceiveFrom(tmp, MAXTMPSIZE, ClientAddress, ClientPort);
if(RecNum > 0)
{
TRACE("Received from client, %d bytes :/n", RecNum);
for(i=0; i<RecNum; i++)
{
if(i==0)
TRACE("/n],", tmp[i]);
else
TRACE("],", tmp[i]);
}
TRACE("/n/n");
pDlg->CreateServerSocket(ClientAddress, ClientPort);
pDlg->Send(true, tmp, RecNum);
}
else
AfxMessageBox("Error: fail to Receive from client the first time!");
}
else
{
RecNum = Receive(tmp, MAXTMPSIZE);
if(RecNum > 0)
{
TRACE("Received from client, %d bytes :/n", RecNum);
for(i=0; i<RecNum; i++)
{
if(i==0)
TRACE("/n],", tmp[i]);
else
TRACE("],", tmp[i]);
}
TRACE("/n/n");
pDlg->Send(true, tmp, RecNum);
}
else
AfxMessageBox("Error: fail to Receive from client!");
}
if(RecNum <= 0)
{
AfxMessageBox("Error: fail to Receive from client !");
return;
}
}
本段程序的大概意思是:如果本程序首次收到来自网管程序的UDP包,则要记录下它的Socket端口号和IP 地址,这是本程序最关键的地方之一。这样做的原因是:网管通讯开始时一般是由网管程序首先发出SNMP 请求包,所以要先响应网管程序;另一目的是由此获得事先未知的网管程序侦听的Socket端口号和IP地址,然后让CChatDlg由此创建CServerSocket。随后调用CChatDlg的Send函数将收到的UDP包转发给交换机,并在Output窗口按每行10个的格式显示出收到的数据。
上段程序中CChatDlg的Send和CreateServerSocket函数的内容如下:
void CChatDlg::CreateServerSocket (CString address, UINT port)
{
m_ClientAddress = address;
m_ClientPort = port;
pServerSocket = new CServerSocket(this);
if(pServerSocket != NULL)
{
if(!pServerSocket->Create(m_ClientPort, SOCK_DGRAM))
AfxMessageBox("Can not create ServerSocket !");
}
else
AfxMessageBox("Can not new ServerSocket !");
}
void CChatDlg::Send(BOOL ToServer, unsigned char* buf, int buf_len)
{
if(ToServer)
{
if(pServerSocket != NULL)
{
if(pServerSocket->SendTo(buf, buf_len, SNMP_SOCKET_PORT, m_ServerAddress)==SOCKET_ERROR)
AfxMessageBox("Error: fail to send data to server !");
}
}
else
{
if(pClientSocket != NULL)
{
if(pClientSocket->SendTo(buf, buf_len, m_ClientPort, m_ClientAddress)==SOCKET_ERROR)
AfxMessageBox("Error: fail to send data to client !");
}
}
}
注意:m_ServerAddress是交换机的IP地址,要事先在CChatDlg的OnInitDialog函数或其他地方设定。
最后,要处理接收到的来自于交换机的UDP包,将其中的数据在Output窗口中按每行10个的格式显示出来,然后调用CChatDlg的Send函数将其转发给网管程序。这在CServerSocket类的OnReceive虚函数中实现:
void CServerSocket::OnReceive(int nErrorCode)
{
CSocket::OnReceive(nErrorCode);
unsigned char tmp[MAXTMPSIZE];
int i;
int RecNum;
RecNum = Receive(tmp, MAXTMPSIZE);
if(RecNum > 0)
{
TRACE("Received from server, %d bytes:/n", RecNum);
for(i=0; i<RecNum; i++)
{
if(i==0)
TRACE("/n],", tmp[i]);
else
TRACE("],", tmp[i]);
}
TRACE("/n/n");
pDlg->Send(false, tmp, RecNum);
}
else
{
i = GetLastError();
TRACE("RecNum = %d, GetLastError() = %d/n", RecNum, i);
AfxMessageBox("Error: fail to Receive from server!");
}
}
以上就是本程序的主要功能部分,其中有一些变量因篇幅原因未作详细解释,但不影响对程序的理解。
三, 运行过程
分别在两台机器上装上本程序和网管程序,将它们连上交换机,先运行本程序,点Listen按钮,然后运行网管程序。一般的网管程序运行时,需要设置被管设备的IP的地址,此时,要将其设为本程序所在机器的IP地址,使网管程序将所有的SNMP包发给本程序。
之后两程序应能正确运行(如果不行,可能要将上述过程多重复几次。),在Output窗口可以看到数据源源不断地显示出来,这真是对网管过程的真实记录!当数据量足够后,结束本程序,可以看到网管程序界面上显示出“设备已断开连接!”的提示信息。然后可以将Output窗口中的数据拷贝到文本文件中,按照SNMP的格式和编码规则进行详细分析,网管协议就由此慢慢地破解出来了。
以上程序在Visual C++ 6.0下编译通过并运行成功,实践效果很好。