随笔 - 298  文章 - 377  trackbacks - 0
<2007年8月>
2930311234
567891011
12131415161718
19202122232425
2627282930311
2345678

常用链接

留言簿(34)

随笔分类

随笔档案

文章档案

相册

收藏夹

搜索

  •  

最新评论

阅读排行榜

评论排行榜

SPI
随着WOSA模型的出现,在Ws2_32.dll和协议堆栈之间多了一层开放的接口,就是SPI。Winsock2 SPI和Winsock2 API在性质上是一样的,只是他们的服务对象不同,API提供的接口工作在应用层的上层,为应用程序提供接口,在Winsock之上,而SPI提供的接口工作在应用层的最底层,为核心的网络服务提供接口,在Winsock之下。如果按照OSI分层标准来划分,SPI应该是工作在会话层,API工作在应用层。如果有人非要象我一开始这样,非要分出个一二三四的话,这样的理解或许能让你得到一点满足,当然这种理解不是完全正确的,但是至少在层次的先后上,这样的理解应该是正确的。我们的工作逐渐进入了OSI的下层,要学习,就要多费一些口舌了。

SPI的上沿是Ws2_32.dll,而它的下沿是协议堆栈和名字空间,当然分层服务可以多层叠放,扩展上下沿。这里讲述向协议堆栈的服务。SPI包含两种服务,一种是基础服务,一种是分层服务。

SPI实例实际上就是一个动态库,开放了一个初始化函数:WSPStartup 或者 NSPStartup。其他函数的指针通过一个函数分配表在此初始化函数中指定给Ws2_32.dll。Ws2_32.dll在需要的时候将相应的服务/协议实现模块装载入内存,在不需要的时候卸载。

一般来说,当一个应用程序调用 Winsock2 API 函数的时候,Winsock2 都会调用相应的 Winsock2 SPI 函数。如 WSPAccept, WSPConnect, etc. 下述特例不经过SPI:
htonl,htons,ntohs,ntohl,inet_addr,inet_ntoa,gethostname,getXbyY,WSAAsnyGetXByY,wsacANCELaSYNCrEQUEST,WSAEnumProtocols,WSAIsBlocking,WSASetBlocking,WSAUnhookBlocking,WSAGetLastError,WSASetLastError,WSACreateEvent,WSACloseEvent,WSASetEvent,WSAResetEvent,WSAWaitForMultipleEvents.SPI的函数原型定义在ws2spi.h中,前缀都有WSP,NSP,WPU,WSC,有兴趣可以浏览一下。相应的对应函数有30个。都会在WSPStartup的实现中得到这些函数指针。

要注意的是:WSPStartup并不是在WSAStartup调用时被调用的,一旦要WSPStartup的时候也就是说明需要用到服务提供者了,那么Ws2_32什么时候加载服务提供者呢?应用程序创建了套接字的时候,Ws2_32就根据套接字的地址家族,类型,协议信息等加载相应的提供者,这时候,WSPStartup就会被调用,服务提供者就开始调度它的传输函数等等内容了。

现在有个问题,既然我们知道Windows Sockets动态库和协议堆栈之间没有直接联系了,那么,如果我的应用程序想要调用我的SPI实例的扩展函数的时候那该怎么办呢?Ws2_32.dll不可能再扩展专门的函数调用,让他按照规范再去调用协议堆栈提供者的扩展函数。其实,Ws2_32.dll提供了一个函数来解决这个额外的扩展问题,那就是WSAIoctl函数,通过命令码 SIO_GET_EXTENSION_FUNCTION_POINTER,输入缓冲区是扩展函数的标识符,输出参数就是该函数的指针了。那么,应用程序就可以直接跳过Ws2_32.dll而直接调用扩展函数。

在动手编写SPI实例之前,我们需要了解一下基础服务和分层服务的区别以及系统如何标志这两种服务,然后讲述如何在系统中安装一个自己的SPI实例,来扩展你的网络传输功能。以及如何卸载自己的SPI实例,来恢复系统原来默认的设置。最后再来讲述如何编写SPI的实例。

基础服务执行核心的网络传输协议功能,而分层服务在基础服务的基础上执行自定义的通讯控制,所有的真正的数据交换是通过基础服务提供者来实现的,分层服务提供者无需再去实现网络协议的功能。简单的网络封包的截取和管理可以在此进行。

需要提及套接字句柄。当调用下层SPI的时候,如果调用WSPSocket,WSPAccept,WSPJoinLeaf时,服务提供者必须返回一个套接字句柄,Winsock2允许在此句柄上直接调用 ReadFile/WriteFile来读写数据。这个句柄有两种类型:IFS(可安装文件系统句柄)和 Non IFS(不可安装的文件系统句柄)。微软的基础服务提供者都是IFS句柄,但是分层服务提供者可以是IFS,也可以是Non IFS。但是如果分层提供者是IFS提供者,就必须将下一级IFS提供者的句柄上传到上一级提供者或者WS2_32.dll,此时对套接字的ReadFile/WriteFile调用会跳过分层服务提供者的WSPSend/WSPRecv函数而直接通过基础服务提供者的功能进行数据读写,而且分层服务提供者将不能处理一个完成端口的重叠I/0操作,这些操作也将绕过分层提供者,而直接通过基础提供者完成数据的读写。所以,最好将分层服务提供者定义为Non IFS句柄的服务提供者,这样就可以对网络数据进行完整的监控。具体做法是在协议信息结构中将dwServiceFlags1标志的XP1_IFS_HANDLES标志去掉。这样,你的分层提供这就是一个Non IFS分层服务提供者,Non IFS服务提供者使用WSPCreateSocketHandle上调函数建立套接字句柄,Winsock2会把ReadFile/WriteFile的调用重定向到WSPSend和WSPReceive上去,但这无疑会对系统性能产生负面影响。

Windows系统为服务提供者维护了一个目录,这些信息保存在注册表中,要更改/安装服务提供者,就必须对此目录信息进行维护。系统提供了一些函数简化对此信息的访问,他们都是以WSC开头。

对于基础服务提供者的安装相当简单,只要准备一个WSAPROTOCOL_INFOW结构,用来代表基础服务提供者信息,正确填充合适的值,然后调用WSCInstallProvider函数就可以完成基础服务提供者的安装。但是这种方法只对新增加的基础服务提供者来讲是很方便的,但是我们往往要利用系统的基础服务提供者来实现基本的协议,比如说TCP协议的实现,所以在这种情况下,方便的方法是不通过WSCInstallProvider函数,而是我们自己修改目录条目信息,对于基础提供者,只要把基础提供者的动态库路径信息改成我们自己虚拟的基础服务提供者路径就可以了。这样安装的后续工作必须是把所有调用传给原来的基础服务提供者,所以在我们虚拟的服务提供者程序中,必须能够检索到原来的服务提供者路径信息。记住,安装之前千万要备份原来的目录信息。否则,一旦发生错误会引起网络无法访问。

基础服务提供者安装代码实例如下所示:

void CBSPinstallDlg::OnBtnInstall() 
{
// Update m_strFileName which is our mimic Base Provider filename
UpdateData(TRUE);
if(0==BackupBSP()) InstallBSP();
}

int CBSPinstallDlg::EnumRegisterKey(HKEY hKey,TCHAR* pszKeyName,TCHAR** ppBuf,DWORD* pdwBufSize)
{
HKEY hSubKey=NULL;
if(RegOpenKeyEx(hKey,pszKeyName,0,KEY_ALL_ACCESS,&hSubKey)!=ERROR_SUCCESS) 
return -1;
TCHAR szKey[20]={'\0'};
if(hKey==HKEY_CURRENT_CONFIG) strcpy(szKey,"HKEY_LOCAL_MACHINE"; else
if(hKey==HKEY_CURRENT_USER) strcpy(szKey,"HKEY_CURRENT_USER"; else
if(hKey==HKEY_LOCAL_MACHINE) strcpy(szKey,"HKEY_LOCAL_MACHINE"; else
if(hKey==HKEY_USERS) strcpy(szKey,"HKEY_USERS"; else
if(hKey==HKEY_PERFORMANCE_DATA) strcpy(szKey,"HKEY_PERFORMANCE_DATA"; else
if(hKey==HKEY_DYN_DATA) strcpy(szKey,"HKEY_DYN_DATA"; else 
return -1;

int len=strlen(szKey)+strlen(pszKeyName)+5;
TCHAR* pszItem=new TCHAR[len+1];
memset(pszItem,0,len+1);
sprintf(pszItem,"[%s\\%s]\r\n",szKey,pszKeyName);

TCHAR* pBuf=*ppBuf;
DWORD dwBufSize=*pdwBufSize;
TCHAR* pTmp=new TCHAR[dwBufSize+len];
memset(pTmp,0,dwBufSize+len);
memmove(pTmp,pBuf,dwBufSize);
memmove(pTmp+dwBufSize,pszItem,len);
delete[] pszItem; pszItem=NULL;
delete[] pBuf; pBuf=NULL;
dwBufSize+=len;
*ppBuf=pTmp;
*pdwBufSize=dwBufSize;

// "Num_Catalog_Entries"=dword:00000013
// "Next_Catalog_Entry_ID"=dword:0000043e
// "Serial_Access_Num"=dword:00000014
DWORD cbClass=0;
DWORD cSubKeys=0;
DWORD cbMaxSubKeyLen=0;
DWORD cbMaxClassLen=0;
DWORD cValues=0;
DWORD cbMaxValueNameLen=0;
DWORD cbMaxValueLen=0;
int nRet=RegQueryInfoKey(hSubKey,
NULL,
&cbClass,
NULL,
&cSubKeys,
&cbMaxSubKeyLen,
&cbMaxClassLen,
&cValues,
&cbMaxValueNameLen,
&cbMaxValueLen,
NULL,
NULL);
if(nRet!=ERROR_SUCCESS) 
return -1;
for(DWORD dwIndex=0;dwIndex<cValues;dwIndex++)
{
DWORD dwItemNameSize=cbMaxValueNameLen+1;
TCHAR* pszItemName=new TCHAR[dwItemNameSize];
memset(pszItemName,0,dwItemNameSize);
DWORD dwDataSize=cbMaxValueLen+1;
BYTE* pbyData=new BYTE[dwDataSize];
memset(pbyData,0,dwDataSize);
DWORD dwType=0;
nRet=RegEnumValue(hSubKey,dwIndex,pszItemName,&dwItemNameSize,NULL,&dwType,pbyData,&dwDataSize);
if(nRet!=ERROR_SUCCESS)
{
delete[] pszItemName; pszItemName=NULL;
delete[] pbyData; pbyData=NULL;
return -1;
}
TCHAR* pBuf=*ppBuf;
DWORD dwBufSize=*pdwBufSize;
if(dwDataSize>0)
{
TCHAR* szTmp=new TCHAR[dwDataSize*4+266];
memset(szTmp,0,dwDataSize*4+266);
szTmp[0]='"';
int nPos=1;
memmove(szTmp+nPos,pszItemName,strlen(pszItemName));
nPos+=strlen(pszItemName);
szTmp[nPos]='"';nPos++;
szTmp[nPos]='=';nPos++;
switch(dwType)
{
case REG_SZ:
szTmp[nPos]='"';nPos++;
memmove(szTmp,pbyData,dwDataSize);
nPos+=dwDataSize;
szTmp[nPos]='"';nPos++;
szTmp[nPos]='\r';nPos++;
szTmp[nPos]='\n';nPos++;
break;
case REG_DWORD:
{
char sz[20]="dword:\0";
sprintf(sz,"%s%08x",sz,*(DWORD*)pbyData);
memmove(szTmp+nPos,sz,strlen(sz));
nPos+=strlen(sz);
}
break;
case REG_BINARY:
{
char sz[20]="hex:\0";
memmove(szTmp+nPos,sz,strlen(sz));
nPos+=strlen(sz);
int j=0;
for(UINT i=0;i<dwDataSize;i++)
{
if(( (j==0 && nPos%78==0) || \
 ( j>0 && (nPos-81-(j-1)*80)%77==0) \
    && i<dwDataSize-1)
{
j++;
szTmp[nPos]='\\';nPos++;
szTmp[nPos]='\r';nPos++;
szTmp[nPos]='\n';nPos++;
if(j>0)
{
szTmp[nPos]=' ';nPos++;
szTmp[nPos]=' ';nPos++;
}
}
if(i<dwDataSize-1)
{
TCHAR szAppend[20]={'\0'};
sprintf(szAppend,"%02x,",pbyData[i]);
strcat(szTmp,szAppend);
nPos+=3;
}
else
{
TCHAR szAppend[20]={'\0'};
sprintf(szAppend,"%02x",pbyData[i]);
strcat(szTmp,szAppend);
nPos+=2;
}
}
}
default:
break;
}
delete[] pszItemName; pszItemName=NULL;
delete[] pbyData; pbyData=NULL;
szTmp[nPos]='\r';nPos++;
szTmp[nPos]='\n';nPos++;
TCHAR* pTmp=new TCHAR[dwBufSize+nPos];
memset(pTmp,0,dwBufSize+nPos);
memmove(pTmp,pBuf,dwBufSize);
memmove(pTmp+dwBufSize,szTmp,nPos);
delete[] szTmp;szTmp=NULL;
delete[] pBuf;pBuf=NULL;
dwBufSize+=nPos;
*ppBuf=pTmp;
*pdwBufSize=dwBufSize;
}
if(pszItemName) delete[] pszItemName; pszItemName=NULL;
if(pbyData) delete[] pbyData; pbyData=NULL;
}

{
TCHAR* pBuf=*ppBuf;
DWORD dwBufSize=*pdwBufSize;
TCHAR* pTmp=new TCHAR[dwBufSize+2];
memset(pTmp,0,dwBufSize+2);
memmove(pTmp,pBuf,dwBufSize);
pTmp[dwBufSize]='\r';
pTmp[dwBufSize+1]='\n';
*ppBuf=pTmp;
*pdwBufSize=dwBufSize+2;
delete[] pBuf; pBuf=NULL;
}

DWORD dwSubKeyLen=cbMaxSubKeyLen+1;
// [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WinSock2\Parameters\Protocol_Catalog9\Catalog_Entries]
for(dwIndex=0;dwIndex<cSubKeys;dwIndex++)
{
TCHAR* szKeyName=new TCHAR[dwSubKeyLen];
memset(szKeyName,0,dwSubKeyLen);
int nRet=RegEnumKey(hSubKey,dwIndex,szKeyName,dwSubKeyLen);
if(nRet!=ERROR_SUCCESS)
{
delete[] szKeyName; szKeyName=NULL;
return -1;
}
TCHAR szKeyNameAnother[266]={'\0'};
wsprintf(szKeyNameAnother,"%s\\%s",pszKeyName,szKeyName);
delete[] szKeyName; szKeyName=NULL;
nRet=EnumRegisterKey(HKEY_LOCAL_MACHINE,szKeyNameAnother,ppBuf,pdwBufSize);
if(nRet==-1) return -1;
}

RegCloseKey(hSubKey);
return 0;
}

int CBSPinstallDlg::BackupBSP()
{
TCHAR szSysDir[266]={'\0'};
GetWindowsDirectory(szSysDir,266);
TCHAR szBackupFile[266]={'\0'};
sprintf(szBackupFile,"%s\\MinBSP.reg",szSysDir);
CFileStatus status;
if(CFile::GetStatus(szBackupFile,status))
{
if(IDNO==::MessageBox(NULL,"你已经安装了基础服务提供者,是不是还要继续安装?","确认",MB_YESNO))
{
PostQuitMessage(0);
return -1;
}
}

if(m_strFileName.GetLength()==0)
{
AfxMessageBox("请指定基础服务提供者。";
m_edtFileName.SetFocus();
m_edtFileName.SetSel(0,-1);
return -1;
}

// 开始安装
// 首先备份原来的服务提供者目录条目信息
CFile f;
if(f.Open(szBackupFile,CFile::modeCreate|CFile::modeReadWrite))
{
TCHAR szTmp[266]="Windows Registry Editor Version 5.00\r\n\r\n\0";
DWORD dwBufSize=strlen(szTmp);
TCHAR* lpBuf=new TCHAR[dwBufSize];
memset(lpBuf,0,dwBufSize);
memmove(lpBuf,szTmp,dwBufSize);

TCHAR szKeyName[1024]="SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters\\Protocol_Catalog9\0";
int nRet=EnumRegisterKey(HKEY_LOCAL_MACHINE,szKeyName,&lpBuf,&dwBufSize);
if(nRet==-1) {f.Close();delete[] lpBuf;lpBuf=NULL;return -1;}

nRet=dwBufSize*2+2;
WCHAR* pwszResult=new WCHAR[nRet];
memset(pwszResult,0,nRet);
nRet=MultiByteToWideChar(CP_ACP,0,lpBuf,dwBufSize,pwszResult,nRet);
BYTE by=0xFF;
f.Write(&by,1);
by=0xFE;
f.Write(&by,1);
f.Write(pwszResult,nRet*2);
f.Close();
delete[] pwszResult; pwszResult=NULL;
delete[] lpBuf;lpBuf=NULL;
}
return 0;
}

void CBSPinstallDlg::InstallBSP()
{
// 备份完成
// 接下来,更改系统原来的基础服务提供者目录条目信息为我的基础提供者信息
// 枚举协议
LPWSAPROTOCOL_INFOW lpProtocolInfo=NULL;
DWORD dwProtocolInfoSize=0;
int nErrorCode=0;
int nRet=WSCEnumProtocols(NULL,lpProtocolInfo,&dwProtocolInfoSize,&nErrorCode);
if(nRet==SOCKET_ERROR && nErrorCode!=WSAENOBUFS)
{
TRACE1("WSCEnumProtocols() returns error: %d\n",nErrorCode);
return;
}
lpProtocolInfo=(LPWSAPROTOCOL_INFOW) new BYTE[dwProtocolInfoSize];
memset(lpProtocolInfo,0,dwProtocolInfoSize);
__try
{
// 取得协议信息
int nTotalProtocols=WSCEnumProtocols(NULL,lpProtocolInfo,&dwProtocolInfoSize,&nErrorCode);
if(nTotalProtocols==SOCKET_ERROR) return;
// 查找TCP/IP协议信息
TCHAR szTCPIP[20]="[TCP/IP]\0";
TCHAR szTCP95[20]=".TCP\0";
TCHAR szProtocol[MAX_PATH]={'\0'};
TCHAR szKeyName[]="SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters\\Protocol_Catalog9\\Catalog_Entries\0";
HKEY hSubKey=NULL;
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,szKeyName,0,KEY_ALL_ACCESS,&hSubKey)!=ERROR_SUCCESS) return;
for(int i=0;i<nTotalProtocols;i++)
{
memset(szProtocol,0,MAX_PATH);
WCHAR* pwsz=(lpProtocolInfo+i)->szProtocol;
BSTR bstr=SysAllocString(pwsz);
WideCharToMultiByte(CP_ACP,0,bstr,-1,szProtocol,255,NULL,NULL);
SysFreeString(bstr);
// if(strstr(szProtocol,szTCPIP)!=NULL || strstr(szProtocol,szTCP95)!=NULL) break;
if((lpProtocolInfo+i)->ProtocolChain.ChainLen==1)
{
// 更改基础服务提供者信息
TCHAR szSubKeyName[MAX_PATH]={'\0'};
int nRet=RegEnumKey(hSubKey,i,szSubKeyName,MAX_PATH);
if(nRet!=ERROR_SUCCESS) return;
HKEY hSubKeySub=NULL;
DWORD dwSubKeyLen=strlen(szKeyName);
TCHAR* pszKeyName=new TCHAR[dwSubKeyLen+strlen(szSubKeyName)+2];
memset(pszKeyName,0,dwSubKeyLen+strlen(szSubKeyName)+2);
wsprintf(pszKeyName,"%s\\%s",szKeyName,szSubKeyName);
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,pszKeyName,0,KEY_ALL_ACCESS,&hSubKeySub)!=ERROR_SUCCESS)
{
if(pszKeyName) delete[] pszKeyName; pszKeyName=NULL;
return;
}
DWORD cbClass=0;
DWORD cSubKeys=0;
DWORD cbMaxSubKeyLen=0;
DWORD cbMaxClassLen=0;
DWORD cValues=0;
DWORD cbMaxValueNameLen=0;
DWORD cbMaxValueLen=0;
nRet=RegQueryInfoKey(hSubKeySub,
NULL,
&cbClass,
NULL,
&cSubKeys,
&cbMaxSubKeyLen,
&cbMaxClassLen,
&cValues,
&cbMaxValueNameLen,
&cbMaxValueLen,
NULL,
NULL);
if(nRet!=ERROR_SUCCESS) 
{
cbMaxValueNameLen=MAX_PATH;
cbMaxValueLen=MAX_PATH+sizeof(WSAPROTOCOL_INFOW)+2;
}
DWORD dwItemNameSize=cbMaxValueNameLen+1;
TCHAR* pszItemName=new TCHAR[dwItemNameSize];
memset(pszItemName,0,dwItemNameSize);
DWORD dwDataSize=cbMaxValueLen+1;
BYTE* pbyData=new BYTE[dwDataSize];
memset(pbyData,0,dwDataSize);
DWORD dwType=0;
nRet=RegEnumValue(hSubKeySub,0,pszItemName,&dwItemNameSize,NULL,&dwType,pbyData,&dwDataSize);
if(nRet!=ERROR_SUCCESS)
{
if(pszKeyName) delete[] pszKeyName; pszKeyName=NULL;
if(pszItemName) delete[] pszItemName; pszItemName=NULL;
if(pbyData) delete[] pbyData; pbyData=NULL;
return;
}
memset(pbyData,0,MAX_PATH);
TCHAR szDllPath[MAX_PATH+2];
wsprintf(szDllPath,"%%SystemRoot%%\\System32\\%s",m_strFileName);
int nLen=strlen(szDllPath);
memmove(pbyData,szDllPath,nLen);
nRet=RegSetValueEx(hSubKeySub,pszItemName,NULL,dwType,pbyData,dwDataSize);
if(nRet!=ERROR_SUCCESS)
{
if(pszKeyName) delete[] pszKeyName; pszKeyName=NULL;
if(pszItemName) delete[] pszItemName; pszItemName=NULL;
if(pbyData) delete[] pbyData; pbyData=NULL;
return;
}
if(pszKeyName) delete[] pszKeyName; pszKeyName=NULL;
if(pszItemName) delete[] pszItemName; pszItemName=NULL;
if(pbyData) delete[] pbyData; pbyData=NULL;
}
}

if(i<nTotalProtocols) TRACE1("TCP/IP 协议信息在目录条目中的第 %d 项。\n",i+1);
else TRACE("无法根据协议描述判断TCP/IP协议信息。\n";
}
__finally
{
if(lpProtocolInfo) delete[] lpProtocolInfo;
lpProtocolInfo=NULL;
}
}

而对于分层服务提供者而言,安装要复杂一点。他需要建立两个WSAPROTOCOL_INFOW结构,一个代表分层提供者(ChainLen==0),另一个代表协议链(ChainLen>1),利用该协议链把分层服务提供者和基础服务提供者连接起来。
分层服务提供者安装的具体的步骤,概括起来大致分为五步:
第一,初始化两个协议信息结构,初始化可以借助于WSCEnumProtocols函数取回的信息来完成;
第二,用WSCInstallProvider调用来安装你的分层服务提供者;
第三,在第二步成功的基础上,列举所有的目录条目,获得新安装的分层提供者的目录编号。并利用这个目录信息来设置一个协议链的目录信息,通过这个协议链,将你的分层提供者和基础服务提供者(或者是它下一层的分层协议提供者)连接起来。
第四,调用WSCInstallProvider安装这个协议链。
第五,分层服务提供者安装成功后,Windows会在服务提供者目录的最后加入这个新的条目。但是我们为了让我们的分层服务提供者成为系统默认的TCP服务提供者的话,就必须对目录进行重新排列,让新安装的协议链目录条目放在最上面。这些排序过程通过调用WSCWriteProviderOrder完成,该函数将所有的服务提供者重新排序。需要包含 <sporder.h>头文件和sporder.lib库。

以下是单纯针对TCP协议(其他协议处理方法雷同)的分层服务提供者示例:

int CLSPinstallDlg::EnumProtocols(LPWSAPROTOCOL_INFOW* lppInfo,DWORD& dwProtocolInfoSize,int& nTotalProtocols)
{
LPWSAPROTOCOL_INFOW lpProtocolInfo=*lppInfo;
if(lpProtocolInfo!=NULL) delete[] lpProtocolInfo; lpProtocolInfo=NULL;
int nErrorCode=0;
int nRet=WSCEnumProtocols(NULL,lpProtocolInfo,&dwProtocolInfoSize,&nErrorCode);
if(nRet==SOCKET_ERROR&&nErrorCode!=WSAENOBUFS) return nErrorCode;
lpProtocolInfo = (LPWSAPROTOCOL_INFOW)new BYTE[dwProtocolInfoSize];
ASSERT(lpProtocolInfo!=NULL);
nTotalProtocols = WSCEnumProtocols(NULL,lpProtocolInfo,&dwProtocolInfoSize,&nErrorCode);
if(nTotalProtocols== SOCKET_ERROR) return -1;
*lppInfo=lpProtocolInfo;

return 0;
}

void CLSPinstallDlg::InstallLSP()
{
LPWSAPROTOCOL_INFOW lpProtocolInfo=NULL;
DWORD dwProtocolInfoSize=0;
int nTotalProtocols=0;
_try
{
// 第一步,初始化两个协议信息结构,初始化可以借助于WSCEnumProtocols函数取回的信息来完成;
int nRet=EnumProtocols(&lpProtocolInfo,dwProtocolInfoSize,nTotalProtocols);
if(nRet!=0)
{
TRACE1("WSCEnumProtocols() returns error: %d\n",nRet);
return;
}
DWORD dwLayeredCatalogId,dwTcpCatalogId;
WSAPROTOCOL_INFOW stuLayeredInfo,stuTcpChainInfo;
for(int i=0;i<nTotalProtocols;i++)
{
if(lpProtocolInfo[i].iAddressFamily == AF_INET && lpProtocolInfo[i].iProtocol == IPPROTO_TCP)
{
dwTcpCatalogId = lpProtocolInfo[i].dwCatalogEntryId;
memmove(&stuTcpChainInfo, &lpProtocolInfo[i], sizeof(WSAPROTOCOL_INFOW));
memmove(&stuLayeredInfo, &lpProtocolInfo[i], sizeof(WSAPROTOCOL_INFOW));
stuTcpChainInfo.dwServiceFlags1 = lpProtocolInfo[i].dwServiceFlags1 & ~XP1_IFS_HANDLES; 
break;
}
}

// 第二步,用WSCInstallProvider调用来安装你的分层服务提供者;
wcscpy(stuLayeredInfo.szProtocol, L"Mini Layered Provider";
stuLayeredInfo.ProtocolChain.ChainLen = LAYERED_PROTOCOL;
WCHAR wszDllPath[MAX_PATH*2+2]={'\0'};
swprintf(wszDllPath,L"%%SystemRoot%%\\System32\\%s",m_strFileName);
int nErrorCode=0;
nRet=WSCInstallProvider(&guidMiniProvider,wszDllPath,&stuLayeredInfo,1,&nErrorCode);
if(nRet==SOCKET_ERROR)
{
printf("WSCInstallProvider failed %d\n", nErrorCode);
return;
}

// 第三步,在第二步成功的基础上,列举所有的目录条目,获得新安装的分层提供者的目录编号。
// 并利用这个目录信息来设置一个协议链的目录信息,通过这个协议链,将分层提供者和基础服务提供者
// (或者是它下一层的分层协议提供者)连接起来。
nRet=EnumProtocols(&lpProtocolInfo,dwProtocolInfoSize,nTotalProtocols);
if(nRet!=0)
{
TRACE1("WSCEnumProtocols() returns error: %d\n",nRet);
return;
}
for (i=0;i<nTotalProtocols;i++)
{
if(memcmp(&lpProtocolInfo[i].ProviderId, &guidMiniProvider,sizeof (GUID))==0)
{
dwLayeredCatalogId = lpProtocolInfo[i].dwCatalogEntryId;
break;
}
}
WCHAR wszChainName[MAX_PATH*2+2]={'\0'};
swprintf(wszChainName, L"Mini Layered TCP [%s]", stuTcpChainInfo.szProtocol);
wcscpy(stuTcpChainInfo.szProtocol,wszChainName);
if (stuTcpChainInfo.ProtocolChain.ChainLen==BASE_PROTOCOL)
{
stuTcpChainInfo.ProtocolChain.ChainEntries[1] = dwTcpCatalogId;
}
else
{
for (i=stuTcpChainInfo.ProtocolChain.ChainLen;i>0;i--)
{
stuTcpChainInfo.ProtocolChain.ChainEntries[i+1] = stuTcpChainInfo.ProtocolChain.ChainEntries[i];
}
}
stuTcpChainInfo.ProtocolChain.ChainLen++;
stuTcpChainInfo.ProtocolChain.ChainEntries[0] = dwLayeredCatalogId;

// 第四步,调用WSCInstallProvider安装这个协议链
nRet=WSCInstallProvider(&guidMiniProviderChain,wszDllPath,&stuTcpChainInfo,1,&nErrorCode);
if(nRet==SOCKET_ERROR)
{
printf("WSCInstallProvider for protocol chain failed %d\n", nErrorCode);
return;
}

// 第五步,安装成功后将所有的服务提供者重新排序。
LPDWORD lpCatalogEntries=(LPDWORD)new BYTE[nTotalProtocols*sizeof(DWORD)];
ASSERT(lpCatalogEntries!=NULL);
memset(lpCatalogEntries,0,nTotalProtocols*sizeof(DWORD));
DWORD dwIndex=0;
for (i=0;i<nTotalProtocols;i++)
{
if(memcmp(&lpProtocolInfo[i].ProviderId, &guidMiniProvider, sizeof (GUID))==0 || \
   memcmp (&lpProtocolInfo[i].ProviderId, &guidMiniProviderChain, sizeof (GUID))==0)
lpCatalogEntries[dwIndex++] = lpProtocolInfo[i].dwCatalogEntryId;
}
for (i=0;i<nTotalProtocols;i++)
{
if(memcmp (&lpProtocolInfo[i].ProviderId, &guidMiniProvider, sizeof (GUID))!=0 && \
   memcmp (&lpProtocolInfo[i].ProviderId, &guidMiniProviderChain, sizeof (GUID))!=0)
lpCatalogEntries[dwIndex++]=lpProtocolInfo[i].dwCatalogEntryId;
}
nRet=WSCWriteProviderOrder(lpCatalogEntries,nTotalProtocols);
delete[] lpCatalogEntries; lpCatalogEntries=NULL; 
if(nRet!=ERROR_SUCCESS)
{
printf("WSCWriteProviderOrder failed %d\n", nErrorCode);
return;
}
}
__finally
{
if(lpProtocolInfo) delete[] lpProtocolInfo; lpProtocolInfo=NULL;
}
}

好了,到目前,我们安装了基础服务提供者和分层服务提供者,到了该卸载他们的时候了,对于基础服务提供者,简单地恢复原来的目录信息就可以了,对于分层服务提供者,调用WSCDeinstallProvider卸载函数可以简单的卸载,但是对于分层服务提供者,一定要维护好协议链,把所有和你的分层服务提供者的协议链相关的信息都清除掉,恢复安装之前的协议链状态,免得发生协议链断开的现象发生,如果断开了,就会影响网络的功能。Windows SPI在这方面存在缺陷,如果很多人装了很多个分层服务提供者,那怎么办呢?会很麻烦的。

接下来,我们最后来看看怎么样实现一个服务提供者的最基本的实例,我们可以试图在这里对网络封包进行截取。

以下是基础服务提供者实例,对于分层服务提供者,麻烦!我懒得写了:

int WSPAPI WSPStartup(
    WORD wVersion,
    LPWSPDATA lpWSPData,
    LPWSAPROTOCOL_INFOW lpProtocolInfo,
    WSPUPCALLTABLE UpCallTable,
    LPWSPPROC_TABLE lpProcTable)
{

int nTotalProtocols=0;
    int nProviderPathLen=MAX_PATH;
    TCHAR szLibraryPath[MAX_PATH]={'\0'};
    TCHAR szProviderPath[MAX_PATH]={'\0'};;
    LPWSAPROTOCOL_INFOW lpOrigProtocolInfo=NULL;
theApp.m_hLibOrigBase=NULL;
    LPWSPSTARTUP WSPStartupFunc = NULL;

    EnterCriticalSection(&theApp.m_CriticalSection);
    if (!theApp.m_nEntityCount)
    {
CFile f;
TCHAR szFileName[MAX_PATH]={'\0'};
GetWindowsDirectory(szFileName,MAX_PATH);
sprintf(szFileName,"%s\\%s",szFileName,OLD_BSP_CATALOG);
if(!f.Open(szFileName,CFile::modeRead)) return WSAEPROVIDERFAILEDINIT;
DWORD dwLen=f.GetLength();
DWORD dwSize=MAX_PATH+sizeof(WSAPROTOCOL_INFOW);
nTotalProtocols=dwLen/dwSize;
lpOrigProtocolInfo=new WSAPROTOCOL_INFOW[nTotalProtocols];
for(int i=0;i<nTotalProtocols;i++)
{
f.Seek(dwSize*i+MAX_PATH,0);
f.Read(&lpOrigProtocolInfo[i],sizeof(WSAPROTOCOL_INFOW));
if(lpProtocolInfo->ProviderId==lpOrigProtocolInfo->ProviderId)
{
f.Seek(dwSize*i,0);
f.Read(szProviderPath,MAX_PATH);
break;
}
}
f.Close();
delete[] lpOrigProtocolInfo; lpOrigProtocolInfo=NULL;
if(!ExpandEnvironmentStrings(szProviderPath,szLibraryPath,MAX_PATH)) return WSAEPROVIDERFAILEDINIT;

theApp.m_hLibOrigBase=LoadLibrary(szLibraryPath);
if(theApp.m_hLibOrigBase==NULL) return WSAEPROVIDERFAILEDINIT;

WSPStartupFunc=(LPWSPSTARTUP)GetProcAddress(theApp.m_hLibOrigBase,"WSPStartup";
if(WSPStartupFunc==NULL) return WSAEPROVIDERFAILEDINIT;

int nRet=(*WSPStartupFunc)(wVersion,lpWSPData,lpProtocolInfo,UpCallTable,lpProcTable);
if(nRet!=ERROR_SUCCESS) return nRet;

memmove(&theApp.m_NextProcTable, lpProcTable, sizeof(WSPPROC_TABLE));

lpProcTable->lpWSPAccept = WSPAccept;
lpProcTable->lpWSPAddressToString = WSPAddressToString;
lpProcTable->lpWSPAsyncSelect = WSPAsyncSelect;
lpProcTable->lpWSPBind = WSPBind;
lpProcTable->lpWSPCancelBlockingCall = WSPCancelBlockingCall;
lpProcTable->lpWSPCleanup = WSPCleanup;
lpProcTable->lpWSPCloseSocket = WSPCloseSocket;
lpProcTable->lpWSPConnect = WSPConnect;
lpProcTable->lpWSPDuplicateSocket = WSPDuplicateSocket;
lpProcTable->lpWSPEnumNetworkEvents = WSPEnumNetworkEvents;
lpProcTable->lpWSPEventSelect = WSPEventSelect;
lpProcTable->lpWSPGetOverlappedResult = WSPGetOverlappedResult;
lpProcTable->lpWSPGetPeerName = WSPGetPeerName;
lpProcTable->lpWSPGetSockOpt = WSPGetSockOpt;
lpProcTable->lpWSPGetSockName = WSPGetSockName;
lpProcTable->lpWSPGetQOSByName = WSPGetQOSByName;
lpProcTable->lpWSPIoctl = WSPIoctl;
lpProcTable->lpWSPJoinLeaf = WSPJoinLeaf;
lpProcTable->lpWSPListen = WSPListen;
lpProcTable->lpWSPRecv = WSPRecv;
lpProcTable->lpWSPRecvDisconnect = WSPRecvDisconnect;
lpProcTable->lpWSPRecvFrom = WSPRecvFrom;
lpProcTable->lpWSPSelect = WSPSelect;
lpProcTable->lpWSPSend = WSPSend;
lpProcTable->lpWSPSendDisconnect = WSPSendDisconnect;
lpProcTable->lpWSPSendTo = WSPSendTo;
lpProcTable->lpWSPSetSockOpt = WSPSetSockOpt;
lpProcTable->lpWSPShutdown = WSPShutdown;
lpProcTable->lpWSPSocket = WSPSocket;
lpProcTable->lpWSPStringToAddress = WSPStringToAddress;

theApp.m_lpWSPData = lpWSPData;
theApp.m_lpProcTable = lpProcTable;
    }
else
    {
        lpWSPData = theApp.m_lpWSPData;
        lpProcTable = theApp.m_lpProcTable;
    }
    theApp.m_nEntityCount++;
    LeaveCriticalSection(&theApp.m_CriticalSection);

   return 0;
}

int WSPAPI WSPCleanup (LPINT lpErrno)
{
int nRet=theApp.m_NextProcTable.lpWSPCleanup(lpErrno);

EnterCriticalSection(&theApp.m_CriticalSection);
theApp.m_nEntityCount--;
    if (theApp.m_nEntityCount == 0)
    {
        FreeLibrary(theApp.m_hLibOrigBase);
        theApp.m_hLibOrigBase = NULL;
    }
    LeaveCriticalSection(&theApp.m_CriticalSection);

return nRet;
}

对于不同的I/0操作模式,需要进行不同的处理
对于WSARecv,WSARecvFrom,WSASend,WSASendTo的重叠调用,保存重叠信息,重点是完成例程
// 客户程序如果用完成例程,那么将不能直接监视到收发的数据,为了解决这个问题
// 我们可以在服务提供者内提供我们自己的完成例程截获数据,在我们截获了数据之后,
// 再把控制权交还给客户机的完成例程。
//
// 首先,保存重叠操作参数信息的结构
typedef struct tagOVERLAPPED_RECORDER
{
DWORD                  dwType; // WSASend,WSASendTo,WSARecv,WSARecvFrom
                                   // 用来标志监视的数据操作方式
    SOCKET                 s,                                                 
    LPWSABUF               lpBuffers,                                       
    DWORD                  dwBufferCount,                                      
    LPDWORD                lpNumberOfBytesSent,                              
    DWORD                  dwFlags,                                            
  const SOCKADDR_IN FAR  *lpFromOrTo,                         
    int                    iFromOrTolen,                        
    LPWSAOVERLAPPED        lpOverlapped,                             
LPWSAOVERLAPPED_COMPLETION_ROUTINE   lpCompletionRoutine,
    LPWSATHREADID          lpThreadId,                            
  LPINT                  lpErrno  
}
OVERLAPPED_RECORDER, *POVERLAPPED_RECORDER, *LPOVERLAPPED_RECORDER;

// 其次替换重叠操作的完成例程为自定义的例程
// 例程完成后,控制权交给客户机的完成例程,我们可以删除上面保存的重叠参数信息结构了
// 完成例程原型如下:
void CALLBACK CompletionRoutine (
 IN    DWORD             dwError, 
 IN    DWORD             cbTransferred, 
 IN    LPWSAOVERLAPPED   lpOverlapped, 
 IN    DWORD             dwFlags 
);

posted on 2007-08-17 13:19 聂文龙 阅读(1722) 评论(2)  编辑 收藏 引用 所属分类: net work

FeedBack:
# re: SPI 2015-11-25 10:35 李超
最近在弄这方面的东西,大神能否知道一下怎么处理不同的I/O操作?  回复  更多评论
  
# re: SPI 2015-11-25 10:36 李超
可能比较久远了。。。  回复  更多评论
  

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理