The main file for monitoring DDoS attacks in libnids is in scan. C. The main principle is to call the detect_scan function every time a SYN packet is sent during TCP processing. Check whether a DDoS attack exists based on the set parameters.
The algorithm involves the following two data structures:
9 struct scan { 10 u_int addr; 11 unsigned short port; 12 u_char flags; 13 }; 14 15 struct host { 16 struct host *next; 17 struct host *prev; 18 u_int addr; 19 int modtime; 20 int n_packets; 21 struct scan *packets; 22 };
The original scan_detect algorithm of libnids is mainly used to discover that hackers use the same IP address to scan different hosts or ports using scanners. Count the number of hosts or port numbers of the same source IP address within a period of time.
The number of connections before the launch. If the threshold value scan_num_ports is exceeded, a hacker is considered to be launching an attack on our system.
The main data structure of the algorithm is to construct and maintain a hash table. The element type of the hash table is the header pointer to the bidirectional linked list storing the host node, the host node contains information about each host connected to the libnids protected. The hash table uses the source IP address of the data packet to calculate the hash value, the host with the same hash value will be linked to the next location of the existing host linked list.
The data members of the host node include the Source ip addr, the timestamp modtime received by Syn packet, and the number of SYN packages sent by the same source IP address n_packets, which is used to store the pointer packets of the link information, here we will take it as an array for a better understanding. N_packets indicates the number of packages.
The struct scan information is the dst_address and dst_port of the target host.
The main control flow of the algorithm is as follows:
64 void 65 detect_scan(struct ip * iph) 66 { 67 int i; 68 struct tcphdr *th; 69 int hash; 70 struct host *this_host; 71 struct host *oldest; 72 int mtime = 2147483647; 73 74 if (nids_params.scan_num_hosts <= 0) 75 return; 76 77 th = (struct tcphdr *) (((char *) iph) + 4 * iph->ip_hl); 78 hash = scan_hash(iph->ip_src.s_addr); 79 this_host = hashhost[hash]; 80 oldest = 0; 81 timenow = 0; 82 83 for (i = 0; this_host && this_host->addr != iph->ip_src.s_addr; i++) { 84 if (this_host->modtime < mtime) { 85 mtime = this_host->modtime; 86 oldest = this_host; 87 } 88 this_host = this_host->next; 89 } 90 if (!this_host) { 91 if (i == 10) 92 this_host = oldest; 93 else { 94 this_host = (struct host *) malloc(sizeof(struct host) + \ 95 (nids_params.scan_num_ports + 1) * sizeof(struct scan)); 96 if (!this_host) 97 nids_params.no_mem("detect_scan"); 98 this_host->packets = (struct scan *) (((char *) this_host) + sizeof(struct host)); 99 if (hashhost[hash]) {100 hashhost[hash]->prev = this_host;101 this_host->next = hashhost[hash];102 }103 else104 this_host->next = 0;105 this_host->prev = 0;106 hashhost[hash] = this_host;107 }108 this_host->addr = iph->ip_src.s_addr;109 this_host->modtime = gettime();110 this_host->n_packets = 0;111 }112 if (this_host->modtime - gettime() > nids_params.scan_delay)113 this_host->n_packets = 0;114 this_host->modtime = gettime();115 for (i = 0; i < this_host->n_packets; i++)116 if (this_host->packets[i].addr == iph->ip_dst.s_addr &&117 this_host->packets[i].port == ntohs(th->th_dport))118 return;119 this_host->packets[this_host->n_packets].addr = iph->ip_dst.s_addr;120 this_host->packets[this_host->n_packets].port = ntohs(th->th_dport);121 this_host->packets[this_host->n_packets].flags = *((unsigned char *) (th) + 13);122 this_host->n_packets++;123 if (this_host->n_packets > nids_params.scan_num_ports) {124 nids_params.syslog(NIDS_WARN_SCAN, 0, 0, this_host);125 this_host->n_packets = 0;126 }127 }
Since there are not many source codes, the code of the entire function is pasted here. This Code provides the processing process for each SYN Packet: first, hash the data according to the source address and find the corresponding table items, find the host node with the same source IP address domain as the source IP address in the host two-way linked list that this table item points.
If no matching host node is found, if the maximum number of host nodes in each linked list is 10, the oldest host node is replaced. Otherwise, the system calls malloc to apply for a new host node and inserts it into an existing linked list or acts as the first element of the linked list based on whether the linked list is empty. Update the time domain of the host node to system time.
If a matched host node is found, <dst_addr, dst_port> Of the SYN packet is used to search for the packets array. If a matched array element is found, the system returns the result directly. If no matching array element exists, add n_packets to 1 and <dst_addr, dst_port> to the array Packets [n_packets.
Determine whether n_packets is greater than the set threshold value. If it is greater than, a warning is given and the n_packets is set to 0.
We can see from the above that this algorithm is a common algorithm. with a slight modification, we can monitor distributed tcp syn flood attacks (such as hash Based on dest ip) and UDP flood attacks.
We can see from the above that this algorithm is less efficient and can only be used in low-speed network environments. To use it in the environment, you need to optimize it. For example, remove malloc, adopt multi-core parallel technology, and use lockless data structures. Stream-based multi-core parallel execution leads to distribution of different destination addresses with the same source address to different CPU cores, which results in cache trashing and performance degradation, this problem can be considered to reduce the lock overhead through CAS atomic operations.
The high-performance code after optimization will not be posted here. It is mainly about algorithm ideas.