摘要:
限制网络流量的方法包括路由器端限制和本地限制,一般企业采用的是路由器限制的方法,但是并不是所有的路由器都支持流量限制功能。本文尝试解决如下问题:局域网内的机器上都安装一客户端软件,并连接到一内网服务器;客户端软件控制客户机网络流量,服务器负责制定并分发控制规则。本文采用的是Windows系统提供的流量控制功能。
一、问题提出及描述
在Windows系统上利用客户端软件控制网络流量,对于内网和外网连接区别对待。如某用户使用BT下载,占用带宽,影响其他同事正常上网,这种情况是需要限制的。而局域网内部应用,则不应受限制。限制外网连接的上传、下载速度,避免造成阻塞,影响正常工作。
二、问题分析
问题的关键是网络流量控制,Windows系统本身提供了流量控制功能,看一下MSDN关于Traffic Control的描述:
Traffic Control APIThe traffic control application programming interface (TC API), is a programmatic interface to the components that regulate network traffic on local hosts; both from an internal perspective (within the kernel itself), and from a network perspective (prioritization and queuing of packets based on transmission priority). Traffic control is implicitly invoked through calls made to the QOS API and subsequently serviced by the RSVP service provider (RSVP SP). However, applications that require further or specific control over traffic control can use the TC API. The RSVP Service also uses TC API calls. Note that the use of functions in the TC API requiresadministrative privilege. |
Windows本身提供了流量控制功能,我们要做的就是call TC API.
流量控制的原理:
从上面的描述中我们可以看到,Windows提供的Traffic Control使用的是QOS中的RSVP(资源预留)协议。
资源预留(适于综合服务): 根据某个应用对QoS的要求来分配网络资源, 并且服从带宽管理策略 RSVP 提 供 网 络 资 源 预 留 的 信 令。 尽 管 RSVP 经 常 用 于 单 个 流, 但 也 用 于 聚 合 流 的 资 源 预 留 。 RSVP 是 一 个 信 令 协 议, 它 提 供 建 立 连 接 的 资 源 预 留, 控 制 综 合 业 务, 往 往 在 IP 网 络 上 提 供 仿 真 电 路。 RSVP 是 所 有 QoS 技 术 中 最 复 杂 的 一 种, 与 尽 力 而 为 的 IP 服 务 标 准 差 别 最 大 , 它 能 提 供 最 高 的 QoS 等 级, 使 得 服 务 得 到 保 障、 资 源 分 配 量 化, 服 务 质 量 的 细 微 变 化 能 反 馈 给 支 持 QoS 的 应 用 和 用 户。 |
Traffic Control对系统的依赖:
系统需求:Windows 2000及以上版本
系统软件需求:需要客户机安装
各系统测试结果:
系统 | 需要的组件 | 默认是否安装 | 安装后是否可控 |
Win2000 Professional | Qos数据包安装计划 | 否 | 可 |
Windows Server 2003 En | Qos数据包安装计划 | 否 | 可 |
Windows XP Professional | Qos数据包安装计划 | 是 | 可 |
Windows vista | Qos数据包安装计划 | 是 | 可 |
如何限制用户访问外网速度而不限制内网访问速度?
第一步限制所有的网段到期望的速率,ip:0.0.0.0,Mask:0.0.0.0
第二步重新设置对内网的限制:如,132.132.0.0,Mask:255.255.0.0
此方法已经过测试,证明可行
如何调整速率限制的大小?
The TcModifyFlow function modifies an existing flow. When callingTcModifyFlow, newFlowspec parameters and any traffic control objects should be filled.
三、问题解决
下面是TC API列表,函数的详细描述请参见MSDN,本文会解释部分用到的关键函数。
根据调用顺序排序: TcRegisterClient // 调用Tc API,必须首先调用这个。注册一个使用TCI(流量控制接口)的客户端 TcEnumerateInterfaces // 枚举可用户流量控制的接口。接口=网卡?待确定 TcOpenInterface // 打开接口 TcAddFlow // 添加一个Flow,一个Flow可以看做一个或一类连接(Connection) TcAddFilter // 添加一个Filter,Filter是实现流量控制的关键对象,我们将在下文解释 TcDeleteFilter // 删除Fliter TcDeleteFlow // 删除Flow TcCloseInterface // 关闭Tc接口 // 下面这些是一些功能函数,对限制流量功能来说并不是必须调用的,详细解释可以查看MSDN。 |
结合一下代码来详细解释一下程序执行过程。
/****************************************************************** * 函数介绍:限制一个Connection的网络流量 * 输入参数:nProtocol:要限制流量的协议类型 pzSrcIp:源IP nSrcPort:源端口 pzDstIp:目标IP nDstPort:目标端口 nTraffic:限制流量 * 输出参数: * 返回值 :成功返回0;失败返回错误码 *******************************************************************/ 注意:设置网段流量限制的函数和这个函数大体相同 int CTrafficControl::AddTrafficControl( int nProtocol, const char *pzSrcIp, const int nSrcPort, const char *pzDstIp, const int nDstPort, int nTraffic ) { HANDLE ClientHandle; HANDLE ifcHandle; HANDLE flowHandle; HANDLE FilterHandle;
// QOS函数列表,处理各种事件通知回调函数 TCI_CLIENT_FUNC_LIST QoSFunctions; QoSFunctions.ClAddFlowCompleteHandler = NULL; QoSFunctions.ClDeleteFlowCompleteHandler = NULL; QoSFunctions.ClModifyFlowCompleteHandler = NULL; QoSFunctions.ClNotifyHandler = (TCI_NOTIFY_HANDLER)MyClNotifyHandler;
// 注册Client,获取Client句柄 long result = TcRegisterClient(CURRENT_TCI_VERSION, NULL, &QoSFunctions , &ClientHandle); if( NO_ERROR != result ) { //cout << "TcRegisterClient error" << endl; return result; }
// 获取TC接口。枚举接口->根据接口名称打开接口 TC_IFC_DESCRIPTOR InterfaceBuffer[40]; PTC_IFC_DESCRIPTOR pInterfaceBuffer = &InterfaceBuffer[0]; ULONG BufferSize = 40 * sizeof(TC_IFC_DESCRIPTOR); result = TcEnumerateInterfaces(ClientHandle, &BufferSize, pInterfaceBuffer); if( NO_ERROR != result ) { //cout << "TcEnumerateInterfaces error:" << result << endl; return result; }
// 打开接口。注1:项目编码请使用Unicode编码,否则这里会出错,出现ERROR_NOT_FOUND找不到接口的错误。 result = TcOpenInterface( InterfaceBuffer[0].pInterfaceName, ClientHandle, NULL, &ifcHandle ); if( NO_ERROR != result ) { //cout << "TcOpenInterface error:" << result << endl; return result; }
// 创建一个Flow,下面是关键代码 int curSize = sizeof(TC_GEN_FLOW) + sizeof(QOS_DS_CLASS) + sizeof(QOS_TRAFFIC_CLASS) + sizeof(QOS_OBJECT_HDR); char *bufFlow = new char[curSize]; PTC_GEN_FLOW newFlow = ( PTC_GEN_FLOW )bufFlow; LPQOS_OBJECT_HDR objHdr = NULL;
// 设置FlowerSpec属性. SetFlowSpec函数实现参见附件中的代码 FLOWSPEC sendFlowspec, recvFlowspec; SetFlowSpec( sendFlowspec, nTraffic, SERVICETYPE_QUALITATIVE ); SetFlowSpec( recvFlowspec, nTraffic, SERVICETYPE_QUALITATIVE ); newFlow->SendingFlowspec = sendFlowspec; newFlow->ReceivingFlowspec = recvFlowspec;
newFlow-> TcObjectsLength = sizeof(QOS_DS_CLASS) + sizeof(QOS_TRAFFIC_CLASS) + sizeof(QOS_OBJECT_HDR);
LPQOS_DS_CLASS pQOSClass = (LPQOS_DS_CLASS)(&(newFlow-> TcObjects[0]) ); pQOSClass-> ObjectHdr.ObjectType = QOS_OBJECT_DS_CLASS; pQOSClass-> ObjectHdr.ObjectLength = sizeof(QOS_DS_CLASS); pQOSClass-> DSField = 0x24; LPQOS_TRAFFIC_CLASS pTRClass = (LPQOS_TRAFFIC_CLASS)((char*)&(newFlow-> TcObjects[0])+ sizeof(QOS_DS_CLASS)); pTRClass-> ObjectHdr.ObjectType = QOS_OBJECT_TRAFFIC_CLASS; pTRClass-> ObjectHdr.ObjectLength = sizeof(QOS_TRAFFIC_CLASS); pTRClass-> TrafficClass = 0x3; objHdr = (LPQOS_OBJECT_HDR)((char *)&(newFlow-> TcObjects[0]) + sizeof(QOS_DS_CLASS) + sizeof (QOS_TRAFFIC_CLASS)); objHdr-> ObjectType = QOS_OBJECT_END_OF_LIST; objHdr-> ObjectLength = sizeof(QOS_OBJECT_HDR); // 添加Flow到打开的接口中。 // 注意,FlowSpec设置错误会导致这里添加失败。相关规则请参见MSDN中FLOWSPEC的解释 result = TcAddFlow( ifcHandle, /*ClientHandle*/NULL, 0, newFlow, &flowHandle ); if( NO_ERROR != result ) { //cout << "TcAddFlow Error::" << result << endl; return result; }
// 创建Filter。 // Flow设定了流量的限制,Filter设定需要限制流量的条件 TC_GEN_FILTER GenericFilter; IP_PATTERN Pattern, Mask; memset(&Pattern,0,sizeof(IP_PATTERN)); memset(&Mask,0,sizeof(IP_PATTERN)); GenericFilter.AddressType = NDIS_PROTOCOL_ID_TCP_IP; // TCP/IP GenericFilter.PatternSize = sizeof(IP_PATTERN); GenericFilter.Pattern = &Pattern; // pattern to match, defined below GenericFilter.Mask = &Mask;
// 过滤器模式 // 根据源、目标ip和端口,我们可以过滤到一个特定的连接。 // 如果是过滤一个网段,需要用到Mask参数。 Pattern.Reserved1 = 0; Pattern.Reserved2 = 0; Pattern.SrcAddr = inet_addr( pzSrcIp ); // 源IP Pattern.DstAddr = inet_addr( pzDstIp ); // 目标IP Pattern.tcSrcPort = htons( nSrcPort ); // 源端口 Pattern.tcDstPort = htons( nDstPort ); // 目标端口 Pattern.ProtocolId = nProtocol; // 协议类型。TCP or UDP or else Pattern.Reserved3[0] = 0; Pattern.Reserved3[1] = 0; Pattern.Reserved3[2] = 0;
// 掩码。设置Pattern中哪些位是有效的,也就是IP地址中的掩码 // 如要限制212.73.0.0这个网段与本地之间的流量,掩码设置为255.255.0.0 Mask.Reserved1 = 0; Mask.Reserved2 = 0; Mask.SrcAddr = htonl( 0xFFFFFFFF ); Mask.DstAddr = htonl(0xFFFFFFFF); Mask.tcSrcPort = htons( 0xFFFF ); Mask.tcDstPort = htons( 0xFFFF ); Mask.ProtocolId = nProtocol; Mask.Reserved3[0] = 0; Mask.Reserved3[1] = 0; result = TcAddFilter(flowHandle, &GenericFilter, &FilterHandle); if( NO_ERROR != result ) { //cout << "TcAddFilter error:" << result << endl; return result; }
return result; } |
四、示例代码
是一个基于对话框的MFC工程,在Winxp + VC6 + Windows Server 2003 SP1 Platform SDK下编译、运行通过,其他平台未测试。
编译的时候需要安装Windows Platform SDK,我安装的是Windows Server 2003 SP1 Platform SDK。
五、附件
附件1 | Htm | |
附件2 | Rar | |
附件3 | Txt | |
附件4 | rar |
六、结束语
以上内容是关于Windows系统中限制网络流量的方法的简单描述,并提供了完整的演示工程代码。
相关概念和函数的描述并不是很详细,不过大部分都可以从MSDN中找到。
实际运行效果以文档中提供的演示工程为准。
如有错误,欢迎指正。