- 论坛徽章:
- 0
|
请你不要偷换概念,我针对的是楼主分析可能被 DDoS 的情况
请认真阅读 16 楼的贴子
我针对说他“疯了”不仅是说他用 iptables 的 -j LOG 替代 tcpdump,还包括了 16 楼最后红色的那几个字
1、全部 NEW
2、十几分钟
按照1个包产生100字节的日志纪录,如果是每秒1k的包,那流量不过100KB。
对不起,不能同意你的假设
我数了一下,一条 -j LOG 的记录占用了 196 字节(平均)
- Oct 15 02:15:49 PT_LINUX kernel: IN=ppp0 OUT=ath0 SRC=60.166.224.239 DST=192.168.39.200 LEN=40 TOS=0x00 PREC=0x00 TTL=112 ID=53284 DF PROTO=TCP SPT=14114 DPT=26881 WINDOW=0 RES=0x00 ACK RST URGP=0
复制代码
而 tcpdump -i ppp0 -c 10 -w test 之后的 test 文件只有 838 字节,平均每个包是 83.8 字节,相差了 2 倍多
我想,如果 LOG 的时候使用了 --log-prefix 参数,恐怕还不止这个数字吧?
如果不写文件,iptables处理比tcpdump效率高得多,这没人有意见吧。
如果都写日志,那iptables同样比tcpdump高得多,这也是显而易见的吧。
“的吧”、“的吧”,你的猜测和假设也太多了点
我仍然很抱歉,这两句话还不足以说服我,我有意见,而且你的论点相反让我感觉非常站不住脚,下面来具体说一下为什么
ipt_LOG.c 是通过 printk 大量的信息来实现的,那么 printk 什么东西呢?
因为前面说了做所有 NEW 状态包的 LOG,那么应该写成 iptables -I INPUT -m state --state NEW -j LOG 吧?(因为那位朋友说的可是记录所有进入的 NEW 包哦)
懂 netfilter 的朋友都就应该知道,由于 conntrack 的机制,NEW 状态可以是任何包,包括 tcp、icmp、udp,以及其它
针对那位朋友说的所有 NEW 状态包,我们看一下 ipt_LOG.c 都要做什么,看它是如何处理数据并 printk 的
- /* One level of recursion won't kill us */
- static void dump_packet(const struct ipt_log_info *info,
- struct iphdr *iph, unsigned int len, int recurse)
- {
- void *protoh = (u_int32_t *)iph + iph->ihl;
- unsigned int datalen = len - iph->ihl * 4;
- /* Important fields:
- * TOS, len, DF/MF, fragment offset, TTL, src, dst, options. */
- /* Max length: 40 "SRC=255.255.255.255 DST=255.255.255.255 " */
- printk("SRC=%u.%u.%u.%u DST=%u.%u.%u.%u ",
- NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
- /* Max length: 46 "LEN=65535 TOS=0xFF PREC=0xFF TTL=255 ID=65535 " */
- printk("LEN=%u TOS=0x%02X PREC=0x%02X TTL=%u ID=%u ",
- ntohs(iph->tot_len), iph->tos & IPTOS_TOS_MASK,
- iph->tos & IPTOS_PREC_MASK, iph->ttl, ntohs(iph->id));
- /* Max length: 6 "CE DF MF " */
- if (ntohs(iph->frag_off) & IP_CE)
- printk("CE ");
- if (ntohs(iph->frag_off) & IP_DF)
- printk("DF ");
- if (ntohs(iph->frag_off) & IP_MF)
- printk("MF ");
- /* Max length: 11 "FRAG:65535 " */
- if (ntohs(iph->frag_off) & IP_OFFSET)
- printk("FRAG:%u ", ntohs(iph->frag_off) & IP_OFFSET);
- if ((info->logflags & IPT_LOG_IPOPT)
- && iph->ihl * 4 > sizeof(struct iphdr)
- && iph->ihl * 4 <= len) {
- unsigned int i;
- /* Max length: 127 "OPT (" 15*4*2chars ") " */
- printk("OPT (");
- for (i = sizeof(struct iphdr); i < iph->ihl * 4; i++)
- printk("%02X", ((u_int8_t *)iph)[i]);
- printk(") ");
- }
- switch (iph->protocol) {
- case IPPROTO_TCP: {
- struct tcphdr *tcph = protoh;
- /* Max length: 10 "PROTO=TCP " */
- printk("PROTO=TCP ");
- if (ntohs(iph->frag_off) & IP_OFFSET)
- break;
- /* Max length: 25 "INCOMPLETE [65535 bytes] " */
- if (datalen < sizeof (*tcph)) {
- printk("INCOMPLETE [%u bytes] ", datalen);
- break;
- }
- /* Max length: 20 "SPT=65535 DPT=65535 " */
- printk("SPT=%u DPT=%u ",
- ntohs(tcph->source), ntohs(tcph->dest));
- /* Max length: 30 "SEQ=4294967295 ACK=4294967295 " */
- if (info->logflags & IPT_LOG_TCPSEQ)
- printk("SEQ=%u ACK=%u ",
- ntohl(tcph->seq), ntohl(tcph->ack_seq));
- /* Max length: 13 "WINDOW=65535 " */
- printk("WINDOW=%u ", ntohs(tcph->window));
- /* Max length: 9 "RES=0x3F " */
- printk("RES=0x%02x ", (u_int8_t)(ntohl(tcp_flag_word(tcph) & TCP_RESERVED_BITS) >> 22));
- /* Max length: 32 "CWR ECE URG ACK PSH RST SYN FIN " */
- if (tcph->cwr)
- printk("CWR ");
- if (tcph->ece)
- printk("ECE ");
- if (tcph->urg)
- printk("URG ");
- if (tcph->ack)
- printk("ACK ");
- if (tcph->psh)
- printk("PSH ");
- if (tcph->rst)
- printk("RST ");
- if (tcph->syn)
- printk("SYN ");
- if (tcph->fin)
- printk("FIN ");
- /* Max length: 11 "URGP=65535 " */
- printk("URGP=%u ", ntohs(tcph->urg_ptr));
- if ((info->logflags & IPT_LOG_TCPOPT)
- && tcph->doff * 4 > sizeof(struct tcphdr)
- && tcph->doff * 4 <= datalen) {
- unsigned int i;
- /* Max length: 127 "OPT (" 15*4*2chars ") " */
- printk("OPT (");
- for (i =sizeof(struct tcphdr); i < tcph->doff * 4; i++)
- printk("%02X", ((u_int8_t *)tcph)[i]);
- printk(") ");
- }
- break;
- }
- case IPPROTO_UDP: {
- struct udphdr *udph = protoh;
- /* Max length: 10 "PROTO=UDP " */
- printk("PROTO=UDP ");
- if (ntohs(iph->frag_off) & IP_OFFSET)
- break;
- /* Max length: 25 "INCOMPLETE [65535 bytes] " */
- if (datalen < sizeof (*udph)) {
- printk("INCOMPLETE [%u bytes] ", datalen);
- break;
- }
- /* Max length: 20 "SPT=65535 DPT=65535 " */
- printk("SPT=%u DPT=%u LEN=%u ",
- ntohs(udph->source), ntohs(udph->dest),
- ntohs(udph->len));
- break;
- }
- case IPPROTO_ICMP: {
- struct icmphdr *icmph = protoh;
- static size_t required_len[NR_ICMP_TYPES+1]
- = { [ICMP_ECHOREPLY] = 4,
- [ICMP_DEST_UNREACH]
- = 8 + sizeof(struct iphdr) + 8,
- [ICMP_SOURCE_QUENCH]
- = 8 + sizeof(struct iphdr) + 8,
- [ICMP_REDIRECT]
- = 8 + sizeof(struct iphdr) + 8,
- [ICMP_ECHO] = 4,
- [ICMP_TIME_EXCEEDED]
- = 8 + sizeof(struct iphdr) + 8,
- [ICMP_PARAMETERPROB]
- = 8 + sizeof(struct iphdr) + 8,
- [ICMP_TIMESTAMP] = 20,
- [ICMP_TIMESTAMPREPLY] = 20,
- [ICMP_ADDRESS] = 12,
- [ICMP_ADDRESSREPLY] = 12 };
- /* Max length: 11 "PROTO=ICMP " */
- printk("PROTO=ICMP ");
- if (ntohs(iph->frag_off) & IP_OFFSET)
- break;
- /* Max length: 25 "INCOMPLETE [65535 bytes] " */
- if (datalen < 4) {
- printk("INCOMPLETE [%u bytes] ", datalen);
- break;
- }
- /* Max length: 18 "TYPE=255 CODE=255 " */
- printk("TYPE=%u CODE=%u ", icmph->type, icmph->code);
- /* Max length: 25 "INCOMPLETE [65535 bytes] " */
- if (icmph->type <= NR_ICMP_TYPES
- && required_len[icmph->type]
- && datalen < required_len[icmph->type]) {
- printk("INCOMPLETE [%u bytes] ", datalen);
- break;
- }
- switch (icmph->type) {
- case ICMP_ECHOREPLY:
- case ICMP_ECHO:
- /* Max length: 19 "ID=65535 SEQ=65535 " */
- printk("ID=%u SEQ=%u ",
- ntohs(icmph->un.echo.id),
- ntohs(icmph->un.echo.sequence));
- break;
- case ICMP_PARAMETERPROB:
- /* Max length: 14 "PARAMETER=255 " */
- printk("PARAMETER=%u ",
- ntohl(icmph->un.gateway) >> 24);
- break;
- case ICMP_REDIRECT:
- /* Max length: 24 "GATEWAY=255.255.255.255 " */
- printk("GATEWAY=%u.%u.%u.%u ", NIPQUAD(icmph->un.gateway));
- /* Fall through */
- case ICMP_DEST_UNREACH:
- case ICMP_SOURCE_QUENCH:
- case ICMP_TIME_EXCEEDED:
- /* Max length: 3+maxlen */
- if (recurse) {
- printk("[");
- dump_packet(info,
- (struct iphdr *)(icmph + 1),
- datalen-sizeof(struct icmphdr),
- 0);
- printk("] ");
- }
- /* Max length: 10 "MTU=65535 " */
- if (icmph->type == ICMP_DEST_UNREACH
- && icmph->code == ICMP_FRAG_NEEDED)
- printk("MTU=%u ", ntohs(icmph->un.frag.mtu));
- }
- break;
- }
- /* Max Length */
- case IPPROTO_AH: {
- struct ahhdr *ah = protoh;
- /* Max length: 9 "PROTO=AH " */
- printk("PROTO=AH ");
- if (ntohs(iph->frag_off) & IP_OFFSET)
- break;
- /* Max length: 25 "INCOMPLETE [65535 bytes] " */
- if (datalen < sizeof (*ah)) {
- printk("INCOMPLETE [%u bytes] ", datalen);
- break;
- }
- /* Length: 15 "SPI=0xF1234567 " */
- printk("SPI=0x%x ", ntohl(ah->spi) );
- break;
- }
- case IPPROTO_ESP: {
- struct esphdr *esph = protoh;
- /* Max length: 10 "PROTO=ESP " */
- printk("PROTO=ESP ");
- if (ntohs(iph->frag_off) & IP_OFFSET)
- break;
- /* Max length: 25 "INCOMPLETE [65535 bytes] " */
- if (datalen < sizeof (*esph)) {
- printk("INCOMPLETE [%u bytes] ", datalen);
- break;
- }
- /* Length: 15 "SPI=0xF1234567 " */
- printk("SPI=0x%x ", ntohl(esph->spi) );
- break;
- }
- /* Max length: 10 "PROTO 255 " */
- default:
- printk("PROTO=%u ", iph->protocol);
- }
- /* Proto Max log string length */
- /* IP: 40+46+6+11+127 = 230 */
- /* TCP: 10+max(25,20+30+13+9+32+11+127) = 252 */
- /* UDP: 10+max(25,20) = 35 */
- /* ICMP: 11+max(25, 18+25+max(19,14,24+3+n+10,3+n+10)) = 91+n */
- /* ESP: 10+max(25)+15 = 50 */
- /* AH: 9+max(25)+15 = 49 */
- /* unknown: 10 */
- /* (ICMP allows recursion one level deep) */
- /* maxlen = IP + ICMP + IP + max(TCP,UDP,ICMP,unknown) */
- /* maxlen = 230+ 91 + 230 + 252 = 803 */
- }
复制代码
这个处理是不是有点复杂?要判断那么多东西,如果包速率很快的话。。。。
相比之下,tcpudmp 又是如何处理的呢?
tcpdump 只是简单的利用 libpcap 库抓取信息,存入文件而已,没上面那么复杂的处理过程
下面来说最主要的部分,也就是我为什么说他 “疯了” 的地方——文件写入机制
又有人说tcpdump不写日志,难道就你tcpdump能不写日志吗?凭什么认为tcpdump可以不写日志就咬定使用iptables不行?我还可以把iptables日志转储到ramfs呢。 对于说使用iptables记录log会导致磁盘i/o瓶颈的问题,我从理论上怀疑 另外加一个个人推测,别以为tcpdump不写日志就一定比iptables+log耗费更多的系统资源,如果算上libpcap著名的低性能和内核磁盘缓冲带来的异步些文件,谁比谁高效还真没人敢下定论,怎么就有人牛到敢直斥别人“疯了”?!
若你非要把 iptables 的日志放到 ramfs 上,tcpdump 又为何就不可呢?
既然是比较,大家都放在同一起跑线比较好,我们自然都是要比有文件存储记录的情况
请不要凭空猜测或怀疑什么,我们来用事实说话吧
tcpdump 虽然利用了 libpcap,效率确实比 netfilter 要低,因为要把网卡设置为混杂模式,但写文件过程却一直是连续的
如果你写过用户态的程序你会知道如何打开、写入、关闭文件,而 ipt_LOG.c 是如何实现的呢?
ipt_LOG.c 通过 printk 来打印一些重要信息,利用 syslogd 将信息存入 /etc/syslog.conf 指定的文件中
而 syslog 是如何工作的呢?他是通过不断检查 /proc/kmsg 有无新信息,有就马上写入文件来实现的
下面看一下 syslogd.c 的工作方式,看一下它是如何打开、关闭文件的
- /* Path for the file where all log messages are written */
- #define __LOG_FILE "/var/log/messages"
- static char *logFilePath = __LOG_FILE;
- /* Note: There is also a function called "message()" in init.c */
- /* Print a message to the log file. */
- static void message (char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
- static void message (char *fmt, ...)
- {
- int fd;
- struct flock fl;
- va_list arguments;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- fl.l_len = 1;
- if ((fd = device_open (logFilePath,
- O_WRONLY | O_CREAT | O_NOCTTY | O_APPEND |
- O_NONBLOCK)) >= 0) {
- fl.l_type = F_WRLCK;
- fcntl (fd, F_SETLKW, &fl);
- va_start (arguments, fmt);
- vdprintf (fd, fmt, arguments);
- va_end (arguments);
- fl.l_type = F_UNLCK;
- fcntl (fd, F_SETLKW, &fl);
- close (fd);
- } else {
- /* Always send console messages to /dev/console so people will see them. */
- if ((fd = device_open (_PATH_CONSOLE,
- O_WRONLY | O_NOCTTY | O_NONBLOCK)) >= 0) {
- va_start (arguments, fmt);
- vdprintf (fd, fmt, arguments);
- va_end (arguments);
- close (fd);
- } else {
- fprintf (stderr, "Bummer, can't print: ");
- va_start (arguments, fmt);
- vfprintf (stderr, fmt, arguments);
- fflush (stderr);
- va_end (arguments);
- }
- }
- }
复制代码
是否注意到了,在 message 函数里,有一个完整的打开、关闭文件的过程
下面再看一下它是如何记录一条信息的
- static void logMessage (int pri, char *msg)
- {
- time_t now;
- char *timestamp;
- static char res[20] = "";
- CODE *c_pri, *c_fac;
- if (pri != 0) {
- for (c_fac = facilitynames;
- c_fac->c_name && !(c_fac->c_val == LOG_FAC(pri) << 3); c_fac++);
- for (c_pri = prioritynames;
- c_pri->c_name && !(c_pri->c_val == LOG_PRI(pri)); c_pri++);
- if (*c_fac->c_name == '\0' || *c_pri->c_name == '\0')
- snprintf(res, sizeof(res), "<%d>", pri);
- else
- snprintf(res, sizeof(res), "%s.%s", c_fac->c_name, c_pri->c_name);
- }
- if (strlen(msg) < 16 || msg[3] != ' ' || msg[6] != ' ' ||
- msg[9] != ':' || msg[12] != ':' || msg[15] != ' ') {
- time(&now);
- timestamp = ctime(&now) + 4;
- timestamp[15] = '\0';
- } else {
- timestamp = msg;
- timestamp[15] = '\0';
- msg += 16;
- }
- /* todo: supress duplicates */
- /* now spew out the message to wherever it is supposed to go */
- message("%s %s %s %s\n", timestamp, LocalHostName, res, msg);
- }
复制代码
最后一个调用 message 函数的语句是否看到了?不知道现在是否能看明白差别了呢?
每条信息的写入,都要单独打开、关闭文件,我说了很多次是磁盘 I/O 的瓶颈,如果遇到了 DDoS 呢?
我的机器很慢,磁盘转速很低,噪音很大,所以 -j LOG 每个数据包的时候都能听到很明显的 “嘎拉” 一下的声音,数据包多的话,这个 “嘎拉” 的声音就非常频繁,但由于转速低,这个声音也有个频繁的范围,如果包速非常快的时候就会造成 “超载”,而反之用 tcpdump 就一点问题没有
另外,上面两位朋友有几个误区,我这里说一下
同时我自己的经验也证明写日志不会导致很严重的问题,至少在100Mbps环境下,普通PC的硬件都不会有太大问题
同样是 100Mbps 的情况下,正常传输(最大吞吐量)与线速传输是截然不同的两个概念,二者对系统的负载差非常大
个人经验:
双Xeon机器,SCSI硬盘,iptables记录,几分钟内就能搞出G为单位的日志,系统负载1左右,日志记录没有明显拖慢网络
与存放多大数据无关,且与负载无关,网络方面的东西不要看 load,要看 CPU,为什么呢?请去 google 一下 load 是什么。至于双 Xeon 配 SCSI 硬盘的机器我没有发言权,即便真的可以承受 syslog 的实时文件写入的高负载,我也没有钱去买那么好的机器去做实验,太烧包了。。。
另外针对上面的数据,我还有一些分析也可以证明用 -j LOG 不可行
client
P-M 1.7G MEM 512M
60秒测试时间 2.5M测试数据
iptable -L系统负载
procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
0 0 10428 31536 8164 65880 0 0 0 24 1067 51 0 0 100 0
0 0 10428 31176 8428 65956 0 0 80 644 1246 478 2 4 83 11
0 0 10428 30600 8980 65972 0 0 0 1180 1442 920 0 5 89 6
0 0 10428 30096 9480 65984 0 0 4 1052 1380 842 2 4 86 8
0 0 10428 29592 9976 65996 0 0 0 1052 1407 839 3 4 88 5
0 0 10428 29064 10472 66012 0 0 0 1068 1401 838 0 2 92 6
0 0 10428 28560 10976 66024 0 0 0 1068 1399 851 0 1 93 6
0 0 10428 28008 11512 66036 0 0 0 1132 1414 900 1 1 92 6
1 1 10428 27504 12012 66052 0 0 4 1080 1386 847 0 2 90 8
0 1 10428 26976 12532 66064 0 0 0 1100 1393 877 0 0 93 7
0 0 10428 26448 13052 66076 0 0 0 1088 1401 866 0 5 90 5
0 0 10428 25872 13604 66092 0 0 0 1180 1441 929 2 4 87 7
0 0 10428 25344 14124 66104 0 0 0 1100 1390 873 4 3 87 6
0 0 10428 24840 14628 66120 0 0 0 1088 1381 851 0 3 91 6
0 0 10428 24312 15140 66132 0 0 0 1084 1377 861 0 2 92 6
0 1 10428 23788 15636 66144 0 0 0 1104 1389 853 1 1 92 6
0 0 10428 23284 16140 66160 0 0 0 1080 1364 844 0 1 93 6
0 0 10428 22828 16576 66172 0 0 0 1084 1385 861 0 2 92 6
0 0 10428 22444 16952 66184 0 0 0 1100 1411 871 0 3 91 6
0 0 10428 21988 17384 66200 0 0 0 1100 1412 863 1 4 89 6
0 0 10428 21652 17720 66212 0 0 0 1004 1415 799 3 3 88 6
如果是普通传输数据的话,应该都是大包,既最大吞吐量的情况
60 秒 2.5MB 数据
2.5 * 1024 / 60 = 43.7kB/s
按照大包 1500bytes 算,43.7 * 1024 / 1500 = 30pps
根据上面的数据,通过 id 来看,CPU 已经被占用了 10%,但是 30pps 与 1kpps 又差了多少呢?30 多倍!10% 与 100% 又差多少呢?即便按照 800bytes 算每个包,1kpps 又会如何呢?届时 CPU 的 idle 不知又剩多少呢?
如果是 syn-flood 攻击的话,64bytes 小包,1kpps 的话,才有 64kB/s 的流量,但被攻击的时候,包速率又何止 1kpps 呢?
另外再补充一点,人家明明已经说了原来是2M的带宽,增加到4M
4M的数据,你们能知道这个数据的含义么?
如果连4M的数据都搞不定的话-_-b 别玩了。你们还是比较适合玩windows
100Mbps 线速是 14.88Wpps,4Mbps 的线速可以达到 5952pps,也就是大概 6kpps 的速度
不是说风凉话,也不是说话尖酸刻薄,但我真的感觉我更比你了解 4Mbps 的数据是什么概念 ^_^
通过 syslogd.c 也看到了,包速率越快,-j LOG 记录的内容就越多,磁盘 I/O 就越大
以一个 6kpps 的 syn-flood 攻击来举例,系统的 application 就要每秒至少产生 6000 次的 “开、关” 文件操作,而且内容还是经过存放的内容还是经过处理过的,如果处理不完就要等待,等待就会造成延时,这个效率。。。
至少对于楼主的问题---分析是否被攻击,我相信这绝对是可行的方法
让我感到不爽的是至少有2个人质疑提出这个建议的人是否“疯了”,另外更有人竟然从分析简易/困难程度,甚至扯到i/o瓶颈去
你自己有没有感觉你在这个讨论里的猜测和假设太多了点?
I/O 问题是我提出来的,不用“竟然有人”,下次直接点我名就好
I/O 的问题我不能不说,还是我前面反反复复说的那句话,磁盘是个瓶颈,而且我记得不止说了一次
算了,我不想一遍又一遍说重复的话了,信不信由你们,何去何从与我无关,这个讨论我就参与到这里了
[ 本帖最后由 platinum 于 2006-10-15 07:13 编辑 ] |
|