/* TODO: 1) ДОбавить среднюю нагрузку за 30 секунд/минуту/5 минут, хз как ее сделать :) 2) Добавить проверку на существование конфигам с сетями 3) Подумать на тему выноса всех параметров в конфиг 4) Сделать трейсер 100-200 пакетов при бане */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for boost split #include #ifdef ULOG2 #include "libipulog.h" #endif /* Custom pcap: Install PCAP from sources: http://www.stableit.ru/2013/10/lib-pcap-debian-squeeze.html g++ sniffer.cpp -lpcap -I/opt/libpcap140/include -L/opt/libpcap140/lib g++ sniffer.cpp -Linclude LD_LIBRARY_PATH=/opt/libpcap140/lib ./a.out Pcap docs: http://www.linuxforu.com/2011/02/capturing-packets-c-program-libpcap/ http://vichargrave.com/develop-a-packet-sniffer-with-libpcap/ парсер отсюда */ using namespace std; // not a good idea //using namespace boost; // main data structure for storing traffic data for all our IPs typedef map map_for_counters; map_for_counters PacketsCounterIncoming; map_for_counters PacketsCounterOutgoing; map_for_counters TrafficCounterIncoming; map_for_counters TrafficCounterOutgoing; enum direction {INCOMING, OUTGOING, INTERNAL, OTHER}; #ifdef ULOG2 // для подсчета числа ошибок буфера при работе по netlink int netlink_error_counter = 0; int netlink_packets_counter = 0; #endif string get_direction_name(direction direction_value) { string direction_name; switch (direction_value) { case INCOMING: direction_name = "incoming"; break; case OUTGOING: direction_name = "outgoing"; break; case INTERNAL: direction_name = "internal"; break; case OTHER: direction_name = "other"; break; default: direction_name = "unknown"; break; } } // делаем глобальной, так как нам нужно иметь к ней доступ из обработчика сигнала pcap_t* descr; int total_count_of_incoming_packets = 0; int total_count_of_outgoing_packets = 0; int total_count_of_other_packets = 0; int total_count_of_internal_packets = 0; int total_count_of_incoming_bytes = 0; int total_count_of_outgoing_bytes = 0; int total_count_of_other_bytes = 0; int total_count_of_internal_bytes = 0; map ban_list; time_t start_time; int DEBUG = 0; // Период, через который мы пересчитываем pps/трафик int check_period = 3; // Увеличиваем буфер, чтобы минимизировать потери пакетов int pcap_buffer_size_mbytes = 10; // Нас не интересуют запросы IP, у которых менее XXX pps в секунду int threshhold = 2000; // Баним IP, если он превысил данный порог int ban_threshhold = 10000; // data structure for storing data in Vector typedef pair pair_of_map_elements; /* Тут кроется огромный баго-фич: В случае прослушивания any интерфейсов мы ловим фичу-баг, вместо эзернет хидера у нас тип 113, который LINUX SLL, а следовательно размер хидера не 14, а 16 байт! Если мы сниффим один интерфейсе - у нас хидер эзернет, 14 байт, а если ANY, то хидер у нас 16 !!! packetptr += 14; // Ethernet packetptr += 16; // LINUX SLL, только в случае указания any интерфейса Подробнее: https://github.com/the-tcpdump-group/libpcap/issues/324 http://comments.gmane.org/gmane.network.tcpdump.devel/5043 http://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL.html https://github.com/the-tcpdump-group/libpcap/issues/163 */ // стандартно у нас смещение для типа DLT_EN10MB, Ethernet int DATA_SHIFT_VALUE = 14; typedef pair subnet; vector our_networks; vector whitelist_networks; // prototypes void signal_handler(int signal_number); uint32_t convert_cidr_to_binary_netmask(int cidr); bool belongs_to_networks(vector networks_list, uint32_t ip); // Function for sorting Vector of pairs bool compare_function (pair_of_map_elements a, pair_of_map_elements b) { return a.second > b.second; } uint32_t convert_ip_as_string_to_uint(string ip) { struct in_addr ip_addr; inet_aton(ip.c_str(), &ip_addr); // in network byte order return ip_addr.s_addr; } string convert_ip_as_uint_to_string(uint32_t ip_as_string) { struct in_addr ip_addr; ip_addr.s_addr = ip_as_string; return (string)inet_ntoa(ip_addr); } vector exec(string cmd) { vector output_list; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) return output_list; char buffer[256]; std::string result = ""; while(!feof(pipe)) { if(fgets(buffer, 256, pipe) != NULL) { size_t newbuflen = strlen(buffer); // remove newline at the end if (buffer[newbuflen - 1] == '\n') { buffer[newbuflen - 1] = '\0'; } output_list.push_back(buffer); } } pclose(pipe); return output_list; } void draw_table(map_for_counters& my_map_packets, map_for_counters& my_map_traffic, string data_direction) { std::vector vector_for_sort; /* Вобщем-то весь код ниже зависит лишь от входных векторов и порядка сортировки данных */ for( map::iterator ii=my_map_packets.begin(); ii!=my_map_packets.end(); ++ii) { // кладем все наши элементы в массив для последующей сортировки при отображении pair_of_map_elements current_pair; current_pair.first = (*ii).first; current_pair.second = (*ii).second; vector_for_sort.push_back(current_pair); } std::sort( vector_for_sort.begin(), vector_for_sort.end(), compare_function); for( vector::iterator ii=vector_for_sort.begin(); ii!=vector_for_sort.end(); ++ii) { int pps = (*ii).second / check_period; uint32_t client_ip = (*ii).first; string client_ip_as_string = convert_ip_as_uint_to_string((*ii).first); if (pps >= threshhold) { string pps_as_string; std::stringstream out; out << pps; pps_as_string = out.str(); // еси клиента еще нету в бан листе if (pps > ban_threshhold) { if (belongs_to_networks(whitelist_networks, client_ip)) { // IP в белом списке } else { cout<<"!!!ALARM!!! WE MUST BAN THIS IP!!! "; // add IP to BAN list if (ban_list.count(client_ip) == 0) { ban_list[client_ip] = pps; cout << "*BAN EXECUTED* "; exec("echo 'Please execute reglaments and notify client' | mail -s \"Myflower Guard: IP " + client_ip_as_string +" was locked, " + pps_as_string + " pps/" + data_direction + "\" odintsov@fastvps.ru,hohryakov@fastvps.ru,ziltsov@fastvps.ee"); } else { cout << "*BAN EXECUTED* "; // already in ban list } } } // determine attack speed int bps = my_map_traffic[ (*ii).first ] / check_period; // convert to mbps int mbps = bps / 1024 / 1024 * 8; cout << client_ip_as_string << "\t\t" << pps << " pps " << mbps << " Mbps" << endl; } } } #include // bring 'operator+=()' into scope using namespace boost::assign; bool file_exists(string path) { FILE* check_file = fopen(path.c_str(), "r"); if (check_file) { fclose(check_file); return true; } else { return false; } } bool load_our_networks_list() { // вносим в белый список, IP из этой сети мы не баним subnet white_subnet = std::make_pair(convert_ip_as_string_to_uint("159.253.17.0"), convert_cidr_to_binary_netmask(24)); whitelist_networks.push_back(white_subnet); vector networks_list_as_string; // если мы на openvz ноде, то "свои" IP мы можем получить из спец-файла в /proc string our_networks_netmask; if (file_exists("/proc/vz/version")) { cout<<"We found OpenVZ"< network_list_from_config = exec("cat /etc/networks_list"); networks_list_as_string.insert(networks_list_as_string.end(), network_list_from_config.begin(), network_list_from_config.end()); } // если это ложь, то в моих функциях косяк assert( convert_ip_as_string_to_uint("255.255.255.0") == convert_cidr_to_binary_netmask(24) ); assert( convert_ip_as_string_to_uint("255.255.255.255") == convert_cidr_to_binary_netmask(32) ); for( vector::iterator ii=networks_list_as_string.begin(); ii!=networks_list_as_string.end(); ++ii) { vector subnet_as_string; split( subnet_as_string, *ii, boost::is_any_of("/"), boost::token_compress_on ); int cidr = atoi(subnet_as_string[1].c_str()); uint32_t subnet_as_int = convert_ip_as_string_to_uint(subnet_as_string[0]); uint32_t netmask_as_int = convert_cidr_to_binary_netmask(cidr); subnet current_subnet = std::make_pair(subnet_as_int, netmask_as_int); //current_subnet.first = subnet_as_int; //current_subnet.second = netmask_as_int; our_networks.push_back(current_subnet); } return true; } uint32_t convert_cidr_to_binary_netmask(int cidr) { uint32_t binary_netmask = 0xFFFFFFFF; binary_netmask = binary_netmask << ( 32 - cidr ); // htonl from host byte order to network // ntohl from network byte order to host // поидее, на выходе тут нужен network byte order return htonl(binary_netmask); } bool belongs_to_networks(vector networks_list, uint32_t ip) { for( vector::iterator ii=networks_list.begin(); ii!=networks_list.end(); ++ii) { if ( (ip & (*ii).second) == ((*ii).first & (*ii).second) ) { return true; } } return false; } // в случае прямого вызова скрипта колбэка - нужно конст, напрямую в хендлере - конст не нужно void parse_packet(u_char *user, struct pcap_pkthdr *packethdr, const u_char *packetptr) { struct ip* iphdr; struct icmphdr* icmphdr; struct tcphdr* tcphdr; struct udphdr* udphdr; char iphdrInfo[256], srcip_char[256], dstip_char[256]; unsigned short id, seq; int packet_length; // Skip the datalink layer header and get the IP header fields. packetptr += DATA_SHIFT_VALUE; iphdr = (struct ip*)packetptr; // исходящий/входящий айпи это in_addr, http://man7.org/linux/man-pages/man7/ip.7.html strcpy(srcip_char, inet_ntoa(iphdr->ip_src)); strcpy(dstip_char, inet_ntoa(iphdr->ip_dst)); uint32_t src_ip = iphdr->ip_src.s_addr; uint32_t dst_ip = iphdr->ip_dst.s_addr; //cout< "<ip_len); direction packet_direction; if (belongs_to_networks(our_networks, src_ip) && belongs_to_networks(our_networks, dst_ip)) { packet_direction = INTERNAL; total_count_of_internal_packets ++; total_count_of_internal_bytes += packet_length; } else if (belongs_to_networks(our_networks, src_ip)) { packet_direction = OUTGOING; total_count_of_outgoing_packets ++; total_count_of_outgoing_bytes += packet_length; PacketsCounterOutgoing[ src_ip ]++; TrafficCounterOutgoing[ src_ip ] += packet_length; } else if (belongs_to_networks(our_networks, dst_ip)) { packet_direction = INCOMING; total_count_of_incoming_packets++; total_count_of_incoming_bytes += packet_length; PacketsCounterIncoming[ dst_ip ]++; TrafficCounterIncoming[ dst_ip ] += packet_length; } else { packet_direction = OTHER; total_count_of_other_packets ++; total_count_of_other_bytes += packet_length; } time_t current_time; time(¤t_time); if ( difftime(current_time, start_time) >= check_period ) { // clean up screen system("clear"); cout<<"Below you can see all clients with more than "< 0) { cout<::iterator ii=ban_list.begin(); ii!=ban_list.end(); ++ii) { cout<ip_hl; switch (iphdr->ip_p) { case IPPROTO_TCP: tcphdr = (struct tcphdr*)packetptr; if (DEBUG) { printf("TCP %s:%d -> %s:%d\n", srcip_char, ntohs(tcphdr->source), dstip_char, ntohs(tcphdr->dest)); } //printf("%s\n", iphdrInfo); /* printf("%c%c%c%c%c%c Seq: 0x%x Ack: 0x%x Win: 0x%x TcpLen: %d\n", (tcphdr->urg ? 'U' : '*'), (tcphdr->ack ? 'A' : '*'), (tcphdr->psh ? 'P' : '*'), (tcphdr->rst ? 'R' : '*'), (tcphdr->syn ? 'S' : '*'), (tcphdr->fin ? 'F' : '*'), ntohl(tcphdr->seq), ntohl(tcphdr->ack_seq), ntohs(tcphdr->window), 4*tcphdr->doff); */ break; case IPPROTO_UDP: udphdr = (struct udphdr*)packetptr; if (DEBUG) { printf("UDP %s:%d -> %s:%d\n", srcip_char, ntohs(udphdr->source), dstip_char, ntohs(udphdr->dest)); } //printf("%s\n", iphdrInfo); break; case IPPROTO_ICMP: icmphdr = (struct icmphdr*)packetptr; if (DEBUG) { printf("ICMP %s -> %s\n", srcip_char, dstip_char); } break; } } int main(int argc,char **argv) { int i; char *dev; char errbuf[PCAP_ERRBUF_SIZE]; const u_char *packet; struct pcap_pkthdr hdr; struct ether_header *eptr; /* net/ethernet.h */ struct bpf_program fp; /* hold compiled program */ time(&start_time); printf("I need few seconds for collecting data, please wait. Thank you!\n"); #ifdef PCAP if (argc != 2) { fprintf(stdout, "Usage: %s \"eth0\" or \"any\"\n", argv[0]); cout<< "We must automatically select interface"< /sys/module/ipt_ULOG/parameters/nlbufsiz"); /* Size of the receive buffer for the netlink socket. Should be at least of RMEM_DEFAULT size. */ int ULOGD_BUFSIZE_DEFAULT = 150000; int ULOGD_NLGROUP_DEFAULT = 1; struct ipulog_handle *libulog_h; unsigned char *libulog_buf; libulog_buf = (unsigned char*)malloc(ULOGD_BUFSIZE_DEFAULT); if (!libulog_buf) { printf("Can't allocate buffer"); exit(1); } libulog_h = ipulog_create_handle(ipulog_group2gmask(ULOGD_NLGROUP_DEFAULT), ULOGD_RMEM_DEFAULT); if (!libulog_h) { printf("Can't create ipulog handle"); exit(0); } int len; while ( len = ipulog_read(libulog_h, libulog_buf, ULOGD_BUFSIZE_DEFAULT) ) { if (len <= 0) { if (errno == EAGAIN) { break; } if (errno == 105) { // Наш уютный бажик: errno = '105' ('No buffer space available' netlink_error_counter++; continue; } // поймали ошибку - зафиксируем ее при расчетах printf("ipulog_read = '%d'! " "ipulog_errno = '%d' ('%s'), " "errno = '%d' ('%s')\n", len, ipulog_errno, ipulog_strerror(ipulog_errno), errno, strerror(errno)); continue; } // успешний прием пакета netlink_packets_counter++; ulog_packet_msg_t *upkt; while ((upkt = ipulog_get_packet(libulog_h, libulog_buf, len))) { // вот такой хитрый хак, так как данные начинаются без ethernet хидера DATA_SHIFT_VALUE = 0; parse_packet(NULL, NULL, upkt->payload); } } free(libulog_buf); #endif return 0; } // для корректной останвоки программы void signal_handler(int signal_number) { #ifdef PCAP // останавливаем PCAP цикл pcap_breakloop(descr); #endif exit(1); }