ferencd@0: #include "flood_check.h" ferencd@0: ferencd@0: #include ferencd@0: ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: ferencd@0: std::mutex flood_check::locker; ferencd@0: ferencd@0: std::map flood_check::floods; ferencd@0: std::map flood_check::hostd_flood; ferencd@0: std::map flood_check::suspension_times; ferencd@0: std::map flood_check::rejected_hosts; ferencd@0: ferencd@0: void flood_check::attempt(std::string ip) ferencd@0: { ferencd@0: std::lock_guard guard(locker); ferencd@0: ferencd@0: std::chrono::time_point p2; ferencd@0: p2 = std::chrono::system_clock::now(); ferencd@0: std::time_t c = std::chrono::duration_cast(p2.time_since_epoch()).count(); ferencd@0: static std::time_t last_second = 0; ferencd@0: ferencd@0: // see if this host is in the kicked out hosts map and if his sentence is still valid or not ferencd@0: if(rejected_hosts.count(ip) > 0) ferencd@0: { ferencd@0: if(suspension_times.count(ip) > 0) ferencd@0: { ferencd@0: // sentence valid, kick him out ferencd@0: auto timediff = std::difftime(rejected_hosts[ip], c); ferencd@0: if(timediff <= suspension_times[ip] * config::instance().flood.default_suspension_time && timediff >= 0) ferencd@0: { ferencd@0: // debug() << "IP:" << ip << " still rejected for " << timediff << " seconds"; ferencd@0: // debug() << "LEAVE rejected throw"; ferencd@0: ferencd@0: throw std::runtime_error("rejected"); ferencd@0: } ferencd@0: if(timediff < 0) ferencd@0: { ferencd@0: // sentence invalid, host has served ts sentence, however we are suspicious. ferencd@0: if(abs(timediff) < 5) // if he tries again after 5 seconds of the expiration time ferencd@0: { ferencd@0: // kick him out again, with greater penalty ferencd@0: rejected_hosts[ip] = c + ( ++suspension_times[ip] * config::instance().flood.default_suspension_time ); ferencd@0: // debug() << "IP:" << ip << " rejection increased for " << rejected_hosts[ip] - c << " seconds since it tried between " << abs(timediff) << " seconds"; ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: // debug() << "IP:" << ip << " lifting rejection: timediff=" << abs(timediff); ferencd@0: // let's hope his intentions are honest for now and forgive him by erasing his sin ferencd@0: suspension_times.erase(ip); ferencd@0: hostd_flood.erase(ip); ferencd@0: rejected_hosts.erase(ip); ferencd@0: } ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: // debug() << "IP:" << ip << " is strangely behaving: " << timediff << " seconds, rejecting regardless"; ferencd@0: throw std::runtime_error("rejected"); ferencd@0: } ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: // we shouldn't really be here ferencd@0: log_critical() << "IP:" << ip << "has sentence, however no suspension counter"; ferencd@0: suspension_times.erase(ip); ferencd@0: hostd_flood.erase(ip); ferencd@0: rejected_hosts.erase(ip); ferencd@0: } ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: // debug() << "IP:" << ip << " not in rejected_hosts"; ferencd@0: } ferencd@0: ferencd@0: std::string key = std::to_string(static_cast(c)) + "_" + ip; ferencd@0: ferencd@0: if(floods.count(c) > 0) ferencd@0: { ferencd@0: flood_map& cfm = floods[c]; ferencd@0: if(cfm.count(key) > 0) ferencd@0: { ferencd@0: auto& flood_value = ++ cfm[key]; ferencd@0: // now check if flood_value > 100 and do something about it ferencd@0: if(flood_value > config::instance().flood.max_offense_per_second) ferencd@0: { ferencd@0: debug() << "IP:" << ip << " tries to flood"; ferencd@0: if(hostd_flood.count(ip) > 0) ferencd@0: { ferencd@0: auto& flood_cnt = ++ hostd_flood[ip].count; ferencd@0: debug() << "IP:" << ip << " tries to flood: " << flood_cnt; ferencd@0: ferencd@0: if(flood_cnt > config::instance().flood.max_accepted_offenses) ferencd@0: { ferencd@0: auto tdiff = std::difftime(c, hostd_flood[ip].first_time); ferencd@0: if(tdiff < config::instance().flood.max_offense_time) ferencd@0: { ferencd@0: // this host has tried to flood our system at least 10 times during the last minute, suspend him ferencd@0: if(suspension_times.count(ip) > 0) ferencd@0: { ferencd@0: rejected_hosts.emplace(ip, c + ( ++suspension_times[ip] * config::instance().flood.default_suspension_time )); ferencd@0: debug() << "IP:" << ip << " rejected for " << rejected_hosts[ip] - c << " seconds"; ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: debug() << "IP:" << ip << "mandated for rejection since:" << suspension_times.count(ip); ferencd@0: suspension_times.emplace(ip, 0); ferencd@0: } ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: // first offence happened long time ago, time to set it up again, don't reset count since we don't like flooding us ferencd@0: hostd_flood[ip].first_time = c; ferencd@0: debug() << "IP:" << ip << " tries to flood but too long time has passed:" << tdiff; ferencd@0: } ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: debug() << "IP:" << ip << " tries to flood but hasn't got enough flood_cnt: " << flood_cnt; ferencd@0: } ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: hostd_flood.emplace(ip, count_started()); ferencd@0: } ferencd@0: debug() << "LEAVE flood throw"; ferencd@0: throw std::runtime_error("flood"); ferencd@0: } ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: cfm[key] = 1; ferencd@0: } ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: // create a new entry ferencd@0: flood_map fm; ferencd@0: fm[key] = 1; ferencd@0: floods.emplace(c, fm); ferencd@0: } ferencd@0: ferencd@0: // just debug: ferencd@0: for(const auto& k : rejected_hosts) ferencd@0: { ferencd@0: std::stringstream ss; ferencd@0: ss << "rejected IP:" << k.first << " time:" << k.second - c; ferencd@0: // debug() << ss.str(); ferencd@0: } ferencd@0: ferencd@0: // now remove the previous seconds' attempts from the memory ferencd@0: if(last_second != c) ferencd@0: { ferencd@0: if(floods.count(last_second) > 0) ferencd@0: { ferencd@0: floods.erase(last_second); ferencd@0: } ferencd@0: last_second = c; ferencd@0: } ferencd@0: }