working version of IP banning lists. Default settings should keep users safe from traffic re-routing based on DHT masquerading. Some GUI bugs remain.

git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@8308 b45a01b8-16f6-495d-af2f-9b41ad6348cc
This commit is contained in:
csoler 2015-05-28 21:44:43 +00:00
parent fc97cdcfa2
commit 562c7c2c20
7 changed files with 338 additions and 95 deletions

View File

@ -40,6 +40,7 @@
#include "pqi/p3linkmgr.h"
#include <retroshare/rspeers.h>
#include <retroshare/rsdht.h>
#include <retroshare/rsbanlist.h>
const int pqisslzone = 37714;
@ -1137,14 +1138,14 @@ int pqissl::SSL_Connection_Complete()
if (sslmode)
{
#ifdef PQISSL_LOG_DEBUG
rslog(RSL_DEBUG_BASIC, pqisslzone, "--------> Active Connect!");
rslog(RSL_DEBUG_BASIC, pqisslzone, "--------> Active Connect! Client side.");
#endif
err = SSL_connect(ssl_connection);
err = SSL_connect(ssl_connection);
}
else
{
#ifdef PQISSL_LOG_DEBUG
rslog(RSL_DEBUG_BASIC, pqisslzone, "--------> Passive Accept!");
rslog(RSL_DEBUG_BASIC, pqisslzone, "--------> Passive Accept! Server side.");
#endif
err = SSL_accept(ssl_connection);
}
@ -1310,6 +1311,12 @@ int pqissl::Authorise_SSL_Connection()
bool res = AuthSSL::getAuthSSL()->CheckCertificate(PeerId(), peercert);
bool certCorrect = true; /* WE know it okay already! */
if(!rsBanList->isAddressAccepted(remote_addr))
{
std::cerr << "(SS) connection attempt from banned IP address. Refusing it. Attack??" << std::endl;
reset_locked();
return 0 ;
}
if(rsDht->isAddressBanned(remote_addr))
{
std::cerr << "(SS) connection attempt from banned IP address. Refusing it. Attack??" << std::endl;
@ -1350,6 +1357,12 @@ int pqissl::accept(SSL *ssl, int fd, const struct sockaddr_storage &foreign_addr
int pqissl::accept_locked(SSL *ssl, int fd, const struct sockaddr_storage &foreign_addr) // initiate incoming connection.
{
if(!rsBanList->isAddressAccepted(foreign_addr))
{
std::cerr << "(SS) refusing incoming SSL connection from blacklisted foreign address " << sockaddr_storage_iptostring(foreign_addr) << std::endl;
reset_locked();
return -1;
}
if (waiting != WAITING_NOT)
{
rslog(RSL_WARNING, pqisslzone, "pqissl::accept() Peer: " + PeerId().toStdString() + " - Two connections in progress - Shut 1 down!");

View File

@ -38,10 +38,14 @@ extern RsBanList *rsBanList ;
#define RSBANLIST_REASON_UNKNOWN 0
#define RSBANLIST_REASON_USER 1
#define RSBANLIST_REASON_DHT 2
#define RSBANLIST_REASON_AUTO_RANGE 3
class BanListPeer
{
public:
BanListPeer() ;
struct sockaddr_storage addr;
uint8_t masked_bytes ; // 0 = []/32. 1=[]/24, 2=[]/16
uint32_t reason; // User, DHT
@ -60,6 +64,19 @@ public:
virtual bool isAddressAccepted(const struct sockaddr_storage& addr) =0;
virtual void getListOfBannedIps(std::list<BanListPeer>& list) =0;
virtual bool autoRangeEnabled() =0;
virtual void enableAutoRange(bool b) =0 ;
virtual int autoRangeLimit() =0;
virtual void setAutoRangeLimit(int n)=0;
virtual void enableIPsFromFriends(bool b) =0;
virtual bool IPsFromFriendsEnabled() =0;
virtual void enableIPsFromDHT(bool b) =0;
virtual bool iPsFromDHTEnabled() =0;
};

View File

@ -44,11 +44,15 @@
/* DEFINE INTERFACE POINTER! */
//RsBanList *rsBanList = NULL;
#define RSBANLIST_ENTRY_MAX_AGE (60 * 60 * 1) // 1 HOURS
#define RSBANLIST_SEND_PERIOD 600 // 10 Minutes.
#define RSBANLIST_DELAY_BETWEEN_TALK_TO_DHT 60 // should be more: e.g. 600 secs.
#define RSBANLIST_ENTRY_MAX_AGE (60 * 60 * 1) // 1 HOURS
#define RSBANLIST_SEND_PERIOD 600 // 10 Minutes.
#define RSBANLIST_DELAY_BETWEEN_TALK_TO_DHT 60 // should be more: e.g. 600 secs.
#define RSBANLIST_DEFAULT_AUTORANGE_LIMIT 3 // default number of IPs in same range to trigger a complete IP range filter.
#define RSBANLIST_DEFAULT_AUTORANGE_ENABLED true
#define RSBANLIST_DEFAULT_FRIEND_GATHERING_ENABLED true
#define RSBANLIST_DEFAULT_DHT_GATHERING_ENABLED true
#define RSBANLIST_DEFAULT_ENABLED true
/************ IMPLEMENTATION NOTES *********************************
*
@ -65,8 +69,13 @@ p3BanList::p3BanList(p3ServiceControl *sc, p3NetMgr *nm)
mSentListTime = 0;
mLastDhtInfoRequest = 0 ;
}
mIPFilteringEnabled = RSBANLIST_DEFAULT_ENABLED ;
mAutoRangeLimit = RSBANLIST_DEFAULT_AUTORANGE_LIMIT ;
mAutoRangeIps = RSBANLIST_DEFAULT_AUTORANGE_ENABLED ;
mIPFriendGatheringEnabled = RSBANLIST_DEFAULT_FRIEND_GATHERING_ENABLED ;
mIPDHTGatheringEnabled = RSBANLIST_DEFAULT_DHT_GATHERING_ENABLED ;
}
const std::string BANLIST_APP_NAME = "banlist";
const uint16_t BANLIST_APP_MAJOR_VERSION = 1;
@ -84,23 +93,127 @@ RsServiceInfo p3BanList::getServiceInfo()
BANLIST_MIN_MINOR_VERSION);
}
bool p3BanList::ipFilteringEnabled()
bool p3BanList::ipFilteringEnabled() { return mIPFilteringEnabled ; }
void p3BanList::enableIPFiltering(bool b) { mIPFilteringEnabled = b ; }
void p3BanList::enableIPsFromFriends(bool b) { mIPFriendGatheringEnabled = b; mLastDhtInfoRequest=0;}
void p3BanList::enableIPsFromDHT(bool b)
{
return mIPFilteringEnabled ;
mIPDHTGatheringEnabled = b;
mLastDhtInfoRequest=0;
}
void p3BanList::enableAutoRange(bool b)
{
mAutoRangeIps = b;
autoFigureOutBanRanges() ;
}
void p3BanList::setAutoRangeLimit(int n)
{
mAutoRangeLimit = n;
autoFigureOutBanRanges();
}
void p3BanList::enableIPFiltering(bool b)
class ZeroedInt
{
mIPFilteringEnabled = b ;
public:
ZeroedInt() { n = 0 ; }
uint32_t n ;
};
BanListPeer::BanListPeer()
{
masked_bytes=0;
reason=RSBANLIST_REASON_UNKNOWN ;
level=RSBANLIST_ORIGIN_UNKNOWN ;
state = false ;
connect_attempts=0;
mTs=0;
}
static sockaddr_storage make24BitsRange(const sockaddr_storage& addr)
{
sockaddr_storage s ;
sockaddr_storage_clear(s) ;
sockaddr_storage_copyip(s,addr) ;
sockaddr_in *ad = (sockaddr_in*)(&s) ;
ad->sin_addr.s_addr |= 0xff000000 ;
return s ;
}
void p3BanList::autoFigureOutBanRanges()
{
RS_STACK_MUTEX(mBanMtx) ;
mBanRanges.clear() ;
if(!mAutoRangeIps)
return ;
std::cerr << "Automatically figuring out IP ranges from banned IPs." << std::endl;
std::map<sockaddr_storage,ZeroedInt> range_map ;
for(std::map<sockaddr_storage,BanListPeer>::iterator it(mBanSet.begin());it!=mBanSet.end();++it)
++range_map[make24BitsRange(it->first)].n ;
time_t now = time(NULL) ;
for(std::map<sockaddr_storage,ZeroedInt>::const_iterator it=range_map.begin();it!=range_map.end();++it)
{
std::cerr << "Ban range: " << sockaddr_storage_iptostring(it->first) << " : " << it->second.n << std::endl;
if(it->second.n >= mAutoRangeLimit)
{
std::cerr << " --> creating new ban range." << std::endl;
BanListPeer& peer(mBanRanges[it->first]) ;
peer.addr = it->first ;
peer.masked_bytes = 1 ;
peer.reason = RSBANLIST_REASON_AUTO_RANGE ;
peer.level = RSBANLIST_ORIGIN_SELF ;
peer.state = true ;
if(peer.mTs == 0)
{
peer.mTs = now ;
peer.connect_attempts = 0 ;
}
}
}
condenseBanSources_locked() ;
}
bool p3BanList::isAddressAccepted(const sockaddr_storage &addr)
{
if(!mIPFilteringEnabled)
return true ;
// we should normally work this including entire ranges of IPs. For now, just check the exact IPs.
if(mBanSet.find(addr) != mBanSet.end())
return false ;
sockaddr_storage addr_24 = make24BitsRange(addr) ;
std::cerr << "p3BanList::isAddressAccepted() testing " << sockaddr_storage_iptostring(addr) << " and range " << sockaddr_storage_iptostring(addr_24) ;
std::map<sockaddr_storage,BanListPeer>::iterator it ;
if((it=mBanRanges.find(addr_24)) != mBanRanges.end())
{
++it->second.connect_attempts;
std::cerr << " returning false. attempts=" << it->second.connect_attempts << std::endl;
return false ;
}
if((it=mBanSet.find(addr)) != mBanSet.end())
{
++it->second.connect_attempts;
std::cerr << " returning false. attempts=" << it->second.connect_attempts << std::endl;
return false ;
}
std::cerr << " returning true " << std::endl;
return true ;
}
@ -108,22 +221,29 @@ void p3BanList::getListOfBannedIps(std::list<BanListPeer> &lst)
{
for(std::map<sockaddr_storage,BanListPeer>::const_iterator it(mBanSet.begin());it!=mBanSet.end();++it)
lst.push_back(it->second) ;
for(std::map<sockaddr_storage,BanListPeer>::const_iterator it(mBanRanges.begin());it!=mBanRanges.end();++it)
lst.push_back(it->second) ;
}
int p3BanList::tick()
{
processIncoming();
sendPackets();
processIncoming();
sendPackets();
time_t now = time(NULL) ;
if(mLastDhtInfoRequest + RSBANLIST_DELAY_BETWEEN_TALK_TO_DHT < now)
{
getDhtInfo() ;
if(mIPDHTGatheringEnabled)
getDhtInfo() ;
mLastDhtInfoRequest = now;
if(mAutoRangeIps)
autoFigureOutBanRanges() ;
}
return 0;
return 0;
}
int p3BanList::status()
@ -150,7 +270,9 @@ void p3BanList::getDhtInfo()
int int_reason = RSBANLIST_REASON_DHT ;
int time_stamp = (*it).mLastSeen ;
uint8_t masked_bytes = 0 ;
sockaddr_storage ad = *(sockaddr_storage*)&(*it).mAddr ;
sockaddr_storage ad ;
sockaddr_storage_setipv4(ad,&(*it).mAddr) ;
addBanEntry(ownId, ad, RSBANLIST_ORIGIN_SELF, int_reason, time_stamp, masked_bytes);
}
@ -195,7 +317,6 @@ bool p3BanList::processIncoming()
{
RsStackMutex stack(mBanMtx); /****** LOCKED MUTEX *******/
mBanSet.clear();
condenseBanSources_locked();
}
@ -236,7 +357,6 @@ void p3BanList::updatePeer(const RsPeerId& /*id*/, const struct sockaddr_storage
{
RsStackMutex stack(mBanMtx); /****** LOCKED MUTEX *******/
mBanSet.clear();
condenseBanSources_locked();
}
}
@ -332,7 +452,9 @@ bool p3BanList::addBanEntry(const RsPeerId &peerId, const struct sockaddr_storag
int p3BanList::condenseBanSources_locked()
{
time_t now = time(NULL);
mBanSet.clear();
time_t now = time(NULL);
RsPeerId ownId = mServiceCtrl->getOwnId();
#ifdef DEBUG_BANLIST
@ -360,70 +482,74 @@ int p3BanList::condenseBanSources_locked()
#endif
std::map<struct sockaddr_storage, BanListPeer>::const_iterator lit;
for(lit = it->second.mBanPeers.begin();
lit != it->second.mBanPeers.end(); ++lit)
{
/* check timestamp */
// if (now > RSBANLIST_ENTRY_MAX_AGE + lit->second.mTs)
// {
//#ifdef DEBUG_BANLIST_CONDENSE
// std::cerr << "p3BanList::condenseBanSources_locked()";
// std::cerr << " Ignoring Out-Of-Date Entry for: ";
// std::cerr << sockaddr_storage_iptostring(lit->second.addr);
// std::cerr << " time stamp= " << lit->second.mTs << ", age=" << now - lit->second.mTs;
// std::cerr << std::endl;
//#endif
// continue;
// }
for(lit = it->second.mBanPeers.begin(); lit != it->second.mBanPeers.end(); ++lit)
{
/* check timestamp */
// if (now > RSBANLIST_ENTRY_MAX_AGE + lit->second.mTs)
// {
//#ifdef DEBUG_BANLIST_CONDENSE
// std::cerr << "p3BanList::condenseBanSources_locked()";
// std::cerr << " Ignoring Out-Of-Date Entry for: ";
// std::cerr << sockaddr_storage_iptostring(lit->second.addr);
// std::cerr << " time stamp= " << lit->second.mTs << ", age=" << now - lit->second.mTs;
// std::cerr << std::endl;
//#endif
// continue;
// }
int lvl = lit->second.level;
if (it->first != ownId)
{
/* as from someone else, increment level */
lvl++;
}
int lvl = lit->second.level;
if (it->first != ownId)
{
/* as from someone else, increment level */
lvl++;
}
struct sockaddr_storage bannedaddr;
sockaddr_storage_clear(bannedaddr);
sockaddr_storage_copyip(bannedaddr, lit->second.addr);
sockaddr_storage_setport(bannedaddr, 0);
struct sockaddr_storage bannedaddr;
sockaddr_storage_clear(bannedaddr);
sockaddr_storage_copyip(bannedaddr, lit->second.addr);
sockaddr_storage_setport(bannedaddr, 0);
// check if not already filtered in a Ban Range
/* check if it exists in the Set already */
std::map<struct sockaddr_storage, BanListPeer>::iterator sit;
sit = mBanSet.find(bannedaddr);
if ((sit == mBanSet.end()) || (lvl < sit->second.level))
{
BanListPeer bp = lit->second;
bp.level = lvl;
sockaddr_storage_setport(bp.addr, 0);
mBanSet[bannedaddr] = bp;
if(mBanRanges.find(make24BitsRange(bannedaddr)) != mBanRanges.end())
continue ;
/* check if it exists in the Set already */
std::map<struct sockaddr_storage, BanListPeer>::iterator sit;
sit = mBanSet.find(bannedaddr);
if ((sit == mBanSet.end()) || (lvl < sit->second.level))
{
BanListPeer bp = lit->second;
bp.level = lvl;
sockaddr_storage_setport(bp.addr, 0);
mBanSet[bannedaddr] = bp;
#ifdef DEBUG_BANLIST_CONDENSE
std::cerr << "p3BanList::condenseBanSources_locked()";
std::cerr << " Added New Entry for: ";
std::cerr << sockaddr_storage_iptostring(bannedaddr);
std::cerr << std::endl;
std::cerr << "p3BanList::condenseBanSources_locked()";
std::cerr << " Added New Entry for: ";
std::cerr << sockaddr_storage_iptostring(bannedaddr);
std::cerr << std::endl;
#endif
}
else
{
}
else
{
#ifdef DEBUG_BANLIST_CONDENSE
std::cerr << "p3BanList::condenseBanSources_locked()";
std::cerr << " Merging Info for: ";
std::cerr << sockaddr_storage_iptostring(bannedaddr);
std::cerr << std::endl;
std::cerr << "p3BanList::condenseBanSources_locked()";
std::cerr << " Merging Info for: ";
std::cerr << sockaddr_storage_iptostring(bannedaddr);
std::cerr << std::endl;
#endif
/* update if necessary */
if (lvl == sit->second.level)
{
sit->second.reason |= lit->second.reason;
if (sit->second.mTs < lit->second.mTs)
{
sit->second.mTs = lit->second.mTs;
}
}
}
}
/* update if necessary */
if (lvl == sit->second.level)
{
sit->second.reason |= lit->second.reason;
if (sit->second.mTs < lit->second.mTs)
{
sit->second.mTs = lit->second.mTs;
}
}
}
}
}
@ -578,7 +704,7 @@ int p3BanList::printBanSources_locked(std::ostream &out)
out << std::endl;
}
}
return true ;
return true ;
}

View File

@ -61,11 +61,24 @@ public:
/***** overloaded from RsBanList *****/
virtual void enableIPFiltering(bool b) ;
virtual bool ipFilteringEnabled() ;
virtual bool isAddressAccepted(const struct sockaddr_storage& addr) ;
virtual void getListOfBannedIps(std::list<BanListPeer>& list) ;
virtual void enableIPFiltering(bool b) ;
virtual bool ipFilteringEnabled() ;
virtual bool autoRangeEnabled() { return mAutoRangeIps ; }
virtual void enableAutoRange(bool b) ;
virtual int autoRangeLimit() { return mAutoRangeLimit ; }
virtual void setAutoRangeLimit(int b) ;
virtual void enableIPsFromFriends(bool b) ;
virtual bool IPsFromFriendsEnabled() { return mIPFriendGatheringEnabled ;}
virtual void enableIPsFromDHT(bool b) ;
virtual bool iPsFromDHTEnabled() { return mIPDHTGatheringEnabled ;}
/***** overloaded from pqiNetAssistPeerShare *****/
virtual void updatePeer(const RsPeerId& id, const struct sockaddr_storage &addr, int type, int reason, int time_stamp);
@ -105,12 +118,12 @@ public:
//virtual void saveDone();
//virtual bool loadList(std::list<RsItem*>& load) ;
private:
void getDhtInfo() ;
RsMutex mBanMtx;
void autoFigureOutBanRanges();
int condenseBanSources_locked();
int printBanSources_locked(std::ostream &out);
int printBanSet_locked(std::ostream &out);
@ -118,12 +131,18 @@ private:
time_t mSentListTime;
std::map<RsPeerId, BanList> mBanSources;
std::map<struct sockaddr_storage, BanListPeer> mBanSet;
std::map<struct sockaddr_storage, BanListPeer> mBanRanges;
p3ServiceControl *mServiceCtrl;
p3NetMgr *mNetMgr;
time_t mLastDhtInfoRequest ;
bool mIPFilteringEnabled ;
bool mIPFriendGatheringEnabled ;
bool mIPDHTGatheringEnabled ;
uint32_t mAutoRangeLimit ;
bool mAutoRangeIps ;
};
#endif // SERVICE_RSBANLIST_HEADER

View File

@ -71,9 +71,12 @@ ServerPage::ServerPage(QWidget * parent, Qt::WindowFlags flags)
ui.filteredIpsTable->verticalHeader()->hide() ;
QObject::connect(ui.filteredIpsTable,SIGNAL(customContextMenuRequested(const QPoint&)),this,SLOT(ipFilterContextMenu(const QPoint&))) ;
QObject::connect(ui.denyAll_CB,SIGNAL(toggled(bool)),this,SLOT(toggleIpFiltering(bool)));
QObject::connect(ui.includeFromDHT_CB,SIGNAL(toggled(bool)),this,SLOT(toggleAutoIncludeDHT(bool)));
QObject::connect(ui.includeFromFriends_CB,SIGNAL(toggled(bool)),this,SLOT(toggleAutoIncludeFriends(bool)));
QObject::connect(ui.groupIPRanges_CB,SIGNAL(toggled(bool)),this,SLOT(toggleGroupIps(bool)));
QObject::connect(ui.groupIPRanges_SB,SIGNAL(valueChanged(int)),this,SLOT(setGroupIpLimit(int)));
QTimer *timer = new QTimer(this);
timer->connect(timer, SIGNAL(timeout()), this, SLOT(updateStatus()));
@ -289,6 +292,15 @@ std::string print_addr_range(const struct sockaddr_storage& addr,uint8_t masked_
return std::string(str) ;
}
void ServerPage::toggleAutoIncludeFriends(bool b)
{
rsBanList->enableIPsFromFriends(b) ;
}
void ServerPage::toggleAutoIncludeDHT(bool b)
{
rsBanList->enableIPsFromDHT(b) ;
}
void ServerPage::toggleIpFiltering(bool b)
{
rsBanList->enableIPFiltering(b) ;
@ -297,20 +309,18 @@ void ServerPage::toggleIpFiltering(bool b)
void ServerPage::loadFilteredIps()
{
ui.includeFromFriends_CB->setChecked(false) ;
ui.includeFromDHT_CB->setChecked(true) ;
ui.ipInput_LE->setEnabled(false) ;
ui.ipInputRange_SB->setEnabled(false) ;
ui.ipInputComment_LE->setEnabled(false) ;
ui.ipInputAdd_PB->setEnabled(false) ;
if(rsBanList->ipFilteringEnabled())
{
ui.denyAll_CB->setChecked(true) ;
ui.filteredIpsTable->setEnabled(true) ;
ui.includeFromFriends_CB->setEnabled(false) ;
ui.includeFromDHT_CB->setEnabled(false) ;
ui.includeFromFriends_CB->setEnabled(true) ;
ui.includeFromDHT_CB->setEnabled(true) ;
ui.ipInput_LE->setEnabled(false) ;
ui.ipInputRange_SB->setEnabled(false) ;
ui.ipInputComment_LE->setEnabled(false) ;
ui.ipInputAdd_PB->setEnabled(false) ;
ui.groupIPRanges_CB->setEnabled(true) ;
ui.groupIPRanges_SB->setEnabled(true) ;
}
else
{
@ -318,8 +328,19 @@ void ServerPage::loadFilteredIps()
ui.filteredIpsTable->setEnabled(false) ;
ui.includeFromFriends_CB->setEnabled(false) ;
ui.includeFromDHT_CB->setEnabled(false) ;
ui.ipInput_LE->setEnabled(false) ;
ui.ipInputRange_SB->setEnabled(false) ;
ui.ipInputComment_LE->setEnabled(false) ;
ui.ipInputAdd_PB->setEnabled(false) ;
ui.groupIPRanges_CB->setEnabled(false) ;
ui.groupIPRanges_SB->setEnabled(false) ;
}
ui.includeFromFriends_CB->setChecked(rsBanList->IPsFromFriendsEnabled()) ;
ui.includeFromDHT_CB->setChecked(rsBanList->iPsFromDHTEnabled()) ;
ui.groupIPRanges_CB->setChecked(rsBanList->autoRangeEnabled()) ;
ui.groupIPRanges_SB->setValue(rsBanList->autoRangeLimit()) ;
std::list<BanListPeer> lst ;
rsBanList->getListOfBannedIps(lst) ;
@ -408,6 +429,9 @@ static bool parseAddrFromQString(const QString& s,struct sockaddr_storage& addr,
return ok;
}
void ServerPage::toggleGroupIps(bool b) { rsBanList->enableAutoRange(b) ; }
void ServerPage::setGroupIpLimit(int n) { rsBanList->setAutoRangeLimit(n) ; }
void ServerPage::ipFilterContextMenu(const QPoint& point)
{
QMenu contextMenu(this) ;

View File

@ -50,6 +50,10 @@ public slots:
void updateStatus();
private slots:
void setGroupIpLimit(int n);
void toggleGroupIps(bool b);
void toggleAutoIncludeDHT(bool b);
void toggleAutoIncludeFriends(bool b);
void toggleIpFiltering(bool b);
void ipFilterContextMenu(const QPoint &);
void removeBannedIp();

View File

@ -515,6 +515,9 @@ behind a firewall or a VPN.</string>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This list get automatically filled from information gathered at multiple sources: masquerading peers reported by the DHT, IP ranged entered by you, and IP ranges reported by your friends. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
@ -554,14 +557,51 @@ behind a firewall or a VPN.</string>
<item>
<widget class="QCheckBox" name="includeFromFriends_CB">
<property name="text">
<string> Automatically include the IPs denied by your friends</string>
<string>Include the IPs reported by your friends</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QCheckBox" name="groupIPRanges_CB">
<property name="text">
<string>Group IPs by ranges when at least :</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="groupIPRanges_SB">
<property name="suffix">
<string> IPs</string>
</property>
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="includeFromDHT_CB">
<property name="text">
<string>Automatically filter suspicious IPs detected by DHT</string>
<string>Include masquerading IPs reported by DHT</string>
</property>
</widget>
</item>