[CODE] 通过tcpserver实现对同一IP的最大连接数和连接频率的限制

本文介绍了如何通过ucspi-tcp的periplimit补丁来限制同一IP地址的最大连接数,并提供了修改后的补丁以允许特定IP不受限制。此外,还展示了另一个补丁,用于限制同一IP的连接频率。文章包括代码示例、配置说明以及测试工具的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 同一IP最大连接数的限制
使用 Balazs Nagy的 periplimit patch 实现同一IP的最大连接数的限制。
相关地址: http://js.hu/package/ucspi-tcp/
实现: ucspi-tcp-0.88-periplimit.6.patch
diff -ru ucspi-tcp-0.88-orig/Makefile ucspi-tcp-0.88/Makefile
--- ucspi-tcp-0.88-orig/Makefile Sat Mar 18 16:18:42 2000
+++ ucspi-tcp-0.88/Makefile Fri May 28 06:48:15 2004
@@ -753,7 +753,7 @@
 alloc.h buffer.h error.h strerr.h sgetopt.h subgetopt.h pathexec.h /
 socket.h uint16.h ndelay.h remoteinfo.h stralloc.h uint16.h rules.h /
 stralloc.h sig.h dns.h stralloc.h iopause.h taia.h tai.h uint64.h /
-taia.h
+taia.h uint32.h
  ./compile tcpserver.c
 
 time.a: /
diff -ru ucspi-tcp-0.88-orig/tcpserver.c ucspi-tcp-0.88/tcpserver.c
--- ucspi-tcp-0.88-orig/tcpserver.c Sat Mar 18 16:18:42 2000
+++ ucspi-tcp-0.88/tcpserver.c Thu Jul  1 08:37:03 2004
@@ -2,6 +2,7 @@
 #include <sys/param.h>
 #include <netdb.h>
 #include "uint16.h"
+#include "uint32.h"
 #include "str.h"
 #include "byte.h"
 #include "fmt.h"
@@ -242,6 +243,7 @@
 tcpserver: usage: tcpserver /
 [ -1UXpPhHrRoOdDqQv ] /
 [ -c limit ] /
+[ -s perip limit ] /
 [ -x rules.cdb ] /
 [ -B banner ] /
 [ -g gid ] /
@@ -254,8 +256,24 @@
 }
 
 unsigned long limit = 40;
+unsigned long periplimit = 0;
 unsigned long numchildren = 0;
 
+typedef struct
+{
+  pid_t pid;
+  int offset;
+} connections;
+
+typedef struct
+{
+  uint32 ipaddr;
+  unsigned long num;
+} ipchildren;
+
+connections *children;
+ipchildren *numipchildren;
+
 int flag1 = 0;
 unsigned long backlog = 20;
 unsigned long uid = 0;
@@ -278,6 +296,7 @@
 {
   int wstat;
   int pid;
+  int i;
  
   while ((pid = wait_nohang(&wstat)) > 0) {
     if (verbosity >= 2) {
@@ -286,6 +305,12 @@
       strerr_warn4("tcpserver: end ",strnum," status ",strnum2,0);
     }
     if (numchildren) --numchildren; printstatus();
+    for (i=0;i<limit;++i)
+      if (children[i].pid == pid) {
+        children[i].pid=0;
+        numipchildren[children[i].offset].num--;
+        break;
+      }
   }
 }
 
@@ -300,10 +325,11 @@
   int s;
   int t;
  
-  while ((opt = getopt(argc,argv,"dDvqQhHrR1UXx:t:u:g:l:b:B:c:pPoO")) != opteof)
+  while ((opt = getopt(argc,argv,"dDvqQhHrR1UXx:t:u:g:l:b:B:c:s:pPoO")) != opteof)
     switch(opt) {
       case 'b': scan_ulong(optarg,&backlog); break;
       case 'c': scan_ulong(optarg,&limit); break;
+      case 's': scan_ulong(optarg,&periplimit); break;
       case 'X': flagallownorules = 1; break;
       case 'x': fnrules = optarg; break;
       case 'B': banner = optarg; break;
@@ -334,6 +360,11 @@
 
   if (!verbosity)
     buffer_2->fd = -1;
+
+  if (!periplimit)
+    periplimit = limit;
+  if (limit<periplimit)
+    strerr_die2x(111,FATAL,"periplimit must be smaller than or equal to connection limit");
  
   hostname = *argv++;
   if (!hostname) usage();
@@ -358,6 +389,11 @@
   sig_catch(sig_term,sigterm);
   sig_ignore(sig_pipe);
  
+  if (!(numipchildren = (ipchildren*)malloc(sizeof(ipchildren)*limit)))
+    strerr_die2x(111,FATAL,"out of memory");
+  byte_zero(numipchildren,sizeof(ipchildren)*limit);
+  if (!(children = (connections*)malloc(sizeof(connections)*limit)))
+    strerr_die2x(111,FATAL,"out of memory");
   if (!stralloc_copys(&tmp,hostname))
     strerr_die2x(111,FATAL,"out of memory");
   if (dns_ip4_qualify(&addresses,&fqdn,&tmp) == -1)
@@ -396,6 +432,13 @@
   printstatus();
  
   for (;;) {
+    uint32 ipaddr;
+    pid_t pid;
+    int i;
+    int lastempty = 0;
+    int freechild = 0;
+    ipchildren *ipcount;
+
     while (numchildren >= limit) sig_pause();
 
     sig_unblock(sig_child);
@@ -403,9 +446,43 @@
     sig_block(sig_child);
 
     if (t == -1) continue;
+
+    for (i=0;i<limit;++i) {
+      if (children[i].pid == 0) {
+        freechild = i;
+        break;
+      }
+    }
+    uint32_unpack(remoteip,&ipaddr);
+    for (i=0;i<limit;++i) {
+      ipcount = &numipchildren[i];
+      if (!ipcount->ipaddr || !ipcount->num)
+        lastempty = i;
+      else if (ipcount->ipaddr == ipaddr) {
+        ++ipcount->num;
+        break;
+      }
+    }
+    if (i == limit) {
+      if (lastempty) {
+        i = lastempty;
+        ipcount = &numipchildren[i];
+        ipcount->ipaddr = ipaddr;
+        ipcount->num = 1;
+      } else
+        /* never reached */
+        strerr_die2x(111,DROP,"internal problem");
+    }
+    if (ipcount->num > periplimit) {
+      remoteipstr[ip4_fmt(remoteipstr,remoteip)] = 0;
+      strerr_warn3(DROP, "per ip limit reached for ", remoteipstr, 0);
+      close(t);
+      --ipcount->num;
+      continue;
+    }
     ++numchildren; printstatus();
  
-    switch(fork()) {
+    switch(pid = fork()) {
       case 0:
         close(s);
         doit(t);
@@ -420,6 +497,10 @@
       case -1:
         strerr_warn2(DROP,"unable to fork: ",&strerr_sys);
         --numchildren; printstatus();
+        break;
+      default:
+       children[freechild].pid = pid;
+ children[freechild].offset = i;
     }
     close(t);
   }

2. 修改 periplimit patch, 实现对特定的IP地址不受限制 (实现: myhan)
上面的patch对所有的IP地址都限制了, 但是我们在使用的时候, 往往有部分IP地址, 如 127.0.0.1 或者本机地址, 不希望受此限制.
通过设置控制文件, 可以实现从控制文件中读取不受限制的IP地址.
控制文件的位置: /var/qmail/control/freeip
控制文件的格式: 每一行一个完整的IP地址

3. 实现对同一IP的连接频率的限制 (实现: qftang)

2和3 的实现: ucspi-tcp-0.88-frequencylimit.1.patch
--- tcpserver.c.1.2 2004-08-13 16:50:41.000000000 +0800
+++ tcpserver.c 2004-08-17 16:11:59.000000000 +0800
@@ -28,6 +28,7 @@
 #include "rules.h"
 #include "sig.h"
 #include "dns.h"
+#include <stdio.h>
 
 int verbosity = 1;
 int flagkillopts = 1;
@@ -236,6 +237,7 @@
 /* ---------------------------- parent */
 
 #define FATAL "tcpserver: fatal: "
+#define MAX_IP_TIME 4096
 
 void usage(void)
 {
@@ -244,6 +246,7 @@
 [ -1UXpPhHrRoOdDqQv ] /
 [ -c limit ] /
 [ -s perip limit ] /
+[ -f max connection per ip in one minite ] /
 [ -x rules.cdb ] /
 [ -B banner ] /
 [ -g gid ] /
@@ -258,6 +261,7 @@
 unsigned long limit = 40;
 unsigned long periplimit = 0;
 unsigned long numchildren = 0;
+unsigned long max_freq = 20;
 
 typedef struct
 {
@@ -271,14 +275,143 @@
   unsigned long num;
 } ipchildren;
 
+typedef struct freeip_t
+{
+  char ip[4];
+  uint32 ipaddr;
+  unsigned long num;
+  struct freeip_t *next;
+} freeip;
+
+typedef struct
+{
+  uint32 ipaddr;
+  unsigned long time; 
+} iptime;
+
+
 connections *children;
 ipchildren *numipchildren;
+freeip *freeiplist = NULL;
+iptime iptimebuf[MAX_IP_TIME];
 
 int flag1 = 0;
 unsigned long backlog = 20;
 unsigned long uid = 0;
 unsigned long gid = 0;
 
+void initfreeip(void)
+{
+    freeip *head = NULL;
+    freeip *tail = NULL;
+    char tempip[4];
+    char buf[256] = {0};
+    FILE *fp;
+
+    if (!(head = (freeip *)malloc(sizeof(freeip))))
+        strerr_die2x(111,FATAL,"out of memory");
+    if (!(tail = (freeip *)malloc(sizeof(freeip))))
+        strerr_die2x(111,FATAL,"out of memory");
+
+    uint32_unpack(localip, &head->ipaddr);
+    head->num  = 100;
+    head->next = NULL;
+
+    if (ip4_scan("127.0.0.1", tempip)) {
+        uint32_unpack(tempip, &tail->ipaddr);
+        tail->num  = 100;
+        tail->next = NULL;
+        head->next = tail;
+    }
+
+    fp = fopen("/var/qmail/control/freeip", "r");
+    if( !fp ) {
+        freeiplist = head;
+        return;
+    }
+    while(fgets(buf, 256, fp)) {
+        freeip *new;
+        if (ip4_scan(buf, tempip)) {
+            new = (freeip *)malloc(sizeof(freeip));
+            uint32_unpack(tempip, &new->ipaddr);
+            new->num  = 100;
+            new->next = NULL;
+            tail->next = new;
+            tail = tail->next;
+        } else {
+            continue;
+        }
+    }
+    
+    freeiplist = head;
+}
+
+void dumpfreeip(void)
+{
+    freeip *temp = freeiplist;
+    char tempip[4];
+    char ipstr[IP4_FMT];
+    char buf[256] = {0};
+    do {
+        uint32_pack(tempip, temp->ipaddr);
+        ipstr[ip4_fmt(ipstr, tempip)] = 0;
+        sprintf (buf, "freeip: %s, num: %d/n", ipstr, temp->num);
+        write(2, buf, strlen(buf));
+        temp = temp->next;
+    } while (temp);
+}
+
+int isfreeip(uint32 ipaddr)
+{
+    freeip *temp = freeiplist;
+    int isfree = 0;
+
+    do {
+        if (temp->ipaddr == ipaddr) {
+            isfree = 1;
+            break;
+        }
+        temp = temp->next;
+    } while (temp);
+    return isfree;
+}
+
+void freefreeip(void)
+{
+}
+
+int isoverfrequency(uint32 ipaddr)
+{
+  iptime *empty_iptime = NULL;
+  iptime *piptime = NULL;
+  time_t now = time(0);
+  int j;
+  int count = 0;
+  int emp_pos = 0;
+
+  for(j=0; j<MAX_IP_TIME; j++) {
+    piptime = &iptimebuf[j];
+    if(!empty_iptime) {
+      if( !piptime->ipaddr || piptime->time < (now - 60)) {
+        empty_iptime = piptime;
+        emp_pos = j;
+      }
+    }
+    if(piptime->ipaddr == ipaddr && piptime->time > (now - 60)) {
+      count ++;
+      if(empty_iptime && count >= max_freq)
+        break;
+    }
+  }
+
+  if (!empty_iptime) empty_iptime = &iptimebuf[0];
+      
+  empty_iptime->ipaddr = ipaddr;
+  empty_iptime->time = now;
+      
+  return (count >= max_freq) ? 1 : 0;
+}
+
 void printstatus(void)
 {
   if (verbosity < 2) return;
@@ -325,11 +458,12 @@
   int s;
   int t;
  
-  while ((opt = getopt(argc,argv,"dDvqQhHrR1UXx:t:u:g:l:b:B:c:s:pPoO")) != opteof)
+  while ((opt = getopt(argc,argv,"dDvqQhHrR1UXx:t:u:g:l:b:B:c:s:f:pPoO")) != opteof)
     switch(opt) {
       case 'b': scan_ulong(optarg,&backlog); break;
       case 'c': scan_ulong(optarg,&limit); break;
       case 's': scan_ulong(optarg,&periplimit); break;
+      case 'f': scan_ulong(optarg,&max_freq);break;
       case 'X': flagallownorules = 1; break;
       case 'x': fnrules = optarg; break;
       case 'B': banner = optarg; break;
@@ -394,6 +528,8 @@
   byte_zero(numipchildren,sizeof(ipchildren)*limit);
   if (!(children = (connections*)malloc(sizeof(connections)*limit)))
     strerr_die2x(111,FATAL,"out of memory");
+  byte_zero(children,sizeof(connections)*limit);
+  byte_zero(iptimebuf,sizeof(iptime)*MAX_IP_TIME); 
   if (!stralloc_copys(&tmp,hostname))
     strerr_die2x(111,FATAL,"out of memory");
   if (dns_ip4_qualify(&addresses,&fqdn,&tmp) == -1)
@@ -426,6 +562,10 @@
     buffer_puts(&b,"/n");
     buffer_flush(&b);
   }
+
+  initfreeip();
+
+  dumpfreeip();
  
   close(0);
   close(1);
@@ -473,13 +613,23 @@
         /* never reached */
         strerr_die2x(111,DROP,"internal problem");
     }
-    if (ipcount->num > periplimit) {
+    //if (ipcount->num > periplimit) {
+    if (!isfreeip(ipaddr) && (ipcount->num > periplimit)) {
       remoteipstr[ip4_fmt(remoteipstr,remoteip)] = 0;
       strerr_warn3(DROP, "per ip limit reached for ", remoteipstr, 0);
       close(t);
       --ipcount->num;
       continue;
     }
+     
+    if (isoverfrequency(ipaddr)) {
+      remoteipstr[ip4_fmt(remoteipstr, remoteip)] = 0;
+      strerr_warn3(DROP, "frequency limit reached for ", remoteipstr, 0);
+      close(t);
+      continue;
+    }
+    
+
     ++numchildren; printstatus();
  
     switch(pid = fork()) {
4. 测试工具 :)
tcpservertester.php
#!/usr/local/bin/php -f
<?php
error_reporting(0);
set_time_limit(6000);
$keepconnect = false;
for ($i=0; $i<50; $i++) {
        $fd = "fp_" . $i;
        //$$fd = fsockopen("localhost", 110, $errno, $errstr, 30);
        $$fd = fsockopen("172.18.10.37", 110, $errno, $errstr, 30);
        if ($$fd === false) {
                printf("No. %2d: [Error] ErrorNo: %d, ErrorInfo: %s/n", $i+1, $errno, $errstr);
        } else {
                printf("No. %2d: Connect Success!/n", $i+1);
                $buf = fgets($$fd, 1024);
                if (substr($buf, 0, 3) == '+OK') {
                    printf("/tS: %s", $buf);
                    if (!$keepconnect) {
                        fwrite($$fd, "QUIT/r/n");
                        printf("/tC: QUIT/n");
                        $reply = fgets($$fd, 1024);
                        printf("/tS: %s", $reply);
                        fclose($$fd);
                        printf("/tConnection closed/n");
                    }
                    printf("/tSleep 10 seconds/n");
                    sleep(10);
                } else {
                    printf("/tService is not available!/n");
                    if (!$keepconnect) {
                        fclose($$fd);
                        printf("/tConnection closed/n");
                    }
                    printf("/tSleep 15 seconds/n");
                    sleep(15);
                }
        }
}
if ($keepconnect) {
    sleep(1000);
}
?>
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值