Linux下電騾aMule Kademlia網絡構建分析4

aMule中聯繫人的管理

aMule中主要經過CContact,CRoutingBin和CRoutingZone這樣幾個類來管理它的聯繫人。 node

CContact表示一個聯繫人,它包含了與一個聯繫人有關的全部的信息,這個類的對象多是根據從文件中讀出來的信息建立的,也多是根據其它節點發送的鏈接請求中的信息建立的。 算法

CRoutingBinCContact的容器,保存了一組CContact,也就是一個Zone的聯繫人。 網絡

CRoutingZone是aMule中管理聯繫人的核心。aMule用CRoutingZone將它的全部聯繫人組織爲一顆二叉樹。 dom

aMule聯繫人的表示CContact

先來簡單的看一下aMule中的聯繫人的表示CContact類。這個類在amule-2.3.1/src/kademlia/routing/Contact.h文件中定義: tcp

class CContact
{
public:
	~CContact();
	CContact(const CUInt128 &clientID,
		uint32_t ip, uint16_t udpPort, uint16_t tcpPort, uint8_t version,
		const CKadUDPKey& kadKey, bool ipVerified,
		const CUInt128 &target = CKademlia::GetPrefs()->GetKadID());

	CContact(const CContact& k1);

	const CUInt128& GetClientID() const throw()		{ return m_clientID; }
	void SetClientID(const CUInt128& clientID) throw()	{ m_clientID = clientID; m_distance = CKademlia::GetPrefs()->GetKadID() ^ clientID; }

	const wxString GetClientIDString() const		{ return m_clientID.ToHexString(); }

	const CUInt128& GetDistance() const throw()		{ return m_distance; }
	const wxString GetDistanceString() const		{ return m_distance.ToBinaryString(); }

	uint32_t GetIPAddress() const throw()			{ return m_ip; }
	void	 SetIPAddress(uint32_t ip) throw()		{ if (m_ip != ip) { SetIPVerified(false); m_ip = ip; } }

	uint16_t GetTCPPort() const throw()			{ return m_tcpPort; }
	void	 SetTCPPort(uint16_t port) throw()		{ m_tcpPort = port; }

	uint16_t GetUDPPort() const throw()			{ return m_udpPort; }
	void	 SetUDPPort(uint16_t port) throw()		{ m_udpPort = port; }

	uint8_t	 GetType() const throw()			{ return m_type; }

	void	 UpdateType() throw();
	void	 CheckingType() throw();

	bool	 InUse() const throw()				{ return m_inUse > 0; }
	void	 IncUse() throw()				{ m_inUse++; }
	void	 DecUse()					{ if (m_inUse) m_inUse--; else { wxFAIL; } }

	time_t	 GetCreatedTime() const throw()			{ return m_created; }

	void	 SetExpireTime(time_t value) throw()		{ m_expires = value; };	
	time_t	 GetExpireTime() const throw()			{ return m_expires; }

	time_t	 GetLastTypeSet() const throw()			{ return m_lastTypeSet; }

	time_t	 GetLastSeen() const throw();

	uint8_t	 GetVersion() const throw()			{ return m_version; }
	void	 SetVersion(uint8_t value) throw()		{ m_version = value; }

	const CKadUDPKey& GetUDPKey() const throw()		{ return m_udpKey; }
	void	 SetUDPKey(const CKadUDPKey& key) throw()	{ m_udpKey = key; }

	bool	 IsIPVerified() const throw()			{ return m_ipVerified; }
	void	 SetIPVerified(bool ipVerified) throw()		{ m_ipVerified = ipVerified; }

	bool	GetReceivedHelloPacket() const throw()		{ return m_receivedHelloPacket; }
	void	SetReceivedHelloPacket() throw()		{ m_receivedHelloPacket = true; }

private:
	CUInt128	m_clientID;
	CUInt128	m_distance;
	uint32_t	m_ip;
	uint16_t	m_tcpPort;
	uint16_t	m_udpPort;
	uint8_t		m_type;
	time_t		m_lastTypeSet;
	time_t		m_expires;
	time_t		m_created;
	uint32_t	m_inUse;
	uint8_t		m_version;
	bool		m_ipVerified;
	bool		m_receivedHelloPacket;
	CKadUDPKey	m_udpKey;
};

咱們主要關注這個類所具備的成員變量,以便於瞭解在aMule中,它會管理聯繫人的哪些信息。能夠看到有ip地址,clientID,client的TCP端口號,client的UDP端口好,版本號等靜態信息,以及對象建立時間,對象的有效期等動態的信息。 ide

注意CContact構造函數的聲明,target參數的默認值爲本節點KadID。
函數

再來看一下這個class的成員函數的實現: oop

CContact::~CContact()
{
	theStats::RemoveKadNode();
}

CContact::CContact(const CUInt128 &clientID, uint32_t ip, uint16_t udpPort, uint16_t tcpPort, uint8_t version, const CKadUDPKey& key, bool ipVerified, const CUInt128 &target)
	: m_clientID(clientID),
	  m_distance(target ^ clientID),
	  m_ip(ip),
	  m_tcpPort(tcpPort),
	  m_udpPort(udpPort),
	  m_type(3),
	  m_lastTypeSet(time(NULL)),
	  m_expires(0),
	  m_created(m_lastTypeSet),
	  m_inUse(0),
	  m_version(version),
	  m_ipVerified(ipVerified),
	  m_receivedHelloPacket(false),
	  m_udpKey(key)
{
	wxASSERT(udpPort);
	theStats::AddKadNode();
}

CContact::CContact(const CContact& k1)
{
	*this = k1;
	theStats::AddKadNode();
}

void CContact::CheckingType() throw()
{
	time_t now = time(NULL);

	if(now - m_lastTypeSet < 10 || m_type == 4) {
		return;
	}

	m_lastTypeSet = now;

	m_expires = now + MIN2S(2);
	m_type++;
}

void CContact::UpdateType() throw()
{
	time_t now = time(NULL);
	uint32_t hours = (now - m_created) / HR2S(1);
	switch (hours) {
		case 0:
			m_type = 2;
			m_expires = now + HR2S(1);
			break;
		case 1:
			m_type = 1;
			m_expires = now + MIN2S(90); //HR2S(1.5)
			break;
		default:
			m_type = 0;
			m_expires = now + HR2S(2);
	}
}

time_t CContact::GetLastSeen() const throw()
{
	// calculating back from expire time, so we don't need an additional field.
	// might result in wrong values if doing CheckingType() for example, so don't use for important timing stuff
	if (m_expires != 0) {
		switch (m_type) {
			case 2: return m_expires - HR2S(1);
			case 1: return m_expires - MIN2S(90) /*(unsigned)HR2S(1.5)*/;
			case 0: return m_expires - HR2S(2);
		}
	}
	return 0;
}

這裏主要關注CContact的構造函數。能夠看到,m_distance被初始化爲clientID和target的異或。如前面CContact構造函數的聲明,則m_distance默認狀況下將是clientID和本節點的KadID的異或。m_lastTypeSet和m_created被設置爲了當前時間,m_expires節點有效期則被設置爲了0。 ui

跟type有關的數字,全用的是magic number,這code好爛。 this

CRoutingZone的構造

接着來看,CContact的二叉樹是如何被一步步構造出來的。

先來看一下CRoutingZone都有哪寫成員變量:

    time_t     m_nextBigTimer;
    time_t     m_nextSmallTimer;     /**
	 * Zone pair is an array of two. Either both entries are null, which
	 * means that *this* is a leaf zone, or both are non-null which means
	 * that *this* has been split into equally sized finer zones.
	 * The zone with index 0 is the one closer to our *self* token.
	 */
	CRoutingZone *m_subZones[2];
	CRoutingZone *m_superZone;

	static wxString m_filename;
	static CUInt128 me;

	/**
	 * The level indicates what size chunk of the address space
	 * this zone is representing. Level 0 is the whole space,
	 * level 1 is 1/2 of the space, level 2 is 1/4, etc.
	 */
	uint32_t m_level;

	/**
	 * This is the distance in number of zones from the zone at this level
	 * that contains the center of the system; distance is wrt the XOR metric.
	 */
	CUInt128 m_zoneIndex;

	/** List of contacts, if this zone is a leaf zone. */
	CRoutingBin *m_bin;
};

m_subZones和m_superZone是構造二叉樹所必須的結構,m_subZones[0]指向子樹0,m_subZones[1]指向子樹1,m_superZone則指向樹中本節點的父節點

m_bin是本CRoutingZone的CRoutingBin,如咱們在本文最開頭所述,這是CContact的容器,用來保存CContact,若是當前CRoutingZone不是葉子節點的話,則這個變量將爲NULL。

m_level表示當前CRoutingZone在二叉樹中的層次,最頂層的CRoutingZone該值爲0,子CRoutingZonem_level值是其父CRoutingZonem_level值加一。此外,如註釋中所述的那樣,這個值指示了當前zone表示的地址空間的塊大小。Level 0是整個空間,Level 1是1/2個空間,Level 2是1/4個,等等等等,依次類推。

回想 Linux下電騾aMule Kademlia網絡構建分析I 一文中,咱們有看到,CRoutingZone::ReadFile()函數在讀取了文件中的聯繫人信息以後,會調用AddUnfiltered()將一個聯繫人添加到CRoutingZone中。這裏咱們就來仔仔細細地看一下,CContact的添加過程(amule-2.3.1/src/kademlia/routing/RoutingZone.cpp):

// Returns true if a contact was added or updated, false if the routing table was not touched.
bool CRoutingZone::AddUnfiltered(const CUInt128& id, uint32_t ip, uint16_t port, uint16_t tport, uint8_t version, const CKadUDPKey& key, bool& ipVerified, bool update, bool fromHello)
{
	if (id != me) {
		CContact *contact = new CContact(id, ip, port, tport, version, key, ipVerified);
		if (fromHello) {
			contact->SetReceivedHelloPacket();
		}
		if (Add(contact, update, ipVerified)) {
			wxASSERT(!update);
			return true;
		} else {
			delete contact;
			return update;
		}
	}
	return false;
}

在這個函數裏,會首先建立一個CContact,若是節點信息來自於一個鏈接創建請求的Hello消息,則設置ReceivedHelloPacket,而後調用CRoutingZone::Add()函數向RoutingZone中添加節點:

bool CRoutingZone::Add(CContact *contact, bool& update, bool& outIpVerified)
{
	// If we're not a leaf, call add on the correct branch.
	if (!IsLeaf()) {
		return m_subZones[contact->GetDistance().GetBitNumber(m_level)]->Add(contact, update, outIpVerified);
	} else {
		// Do we already have a contact with this KadID?
		CContact *contactUpdate = m_bin->GetContact(contact->GetClientID());
		if (contactUpdate) {
			if (update) {
				if (contactUpdate->GetUDPKey().GetKeyValue(theApp->GetPublicIP(false)) != 0 && contactUpdate->GetUDPKey().GetKeyValue(theApp->GetPublicIP(false)) != contact->GetUDPKey().GetKeyValue(theApp->GetPublicIP(false))) {
					// if our existing contact has a UDPSender-Key (which should be the case for all > = 0.49a clients)
					// except if our IP has changed recently, we demand that the key is the same as the key we received
					// from the packet which wants to update this contact in order to make sure this is not a try to
					// hijack this entry
					AddDebugLogLineN(logKadRouting, wxT("Sender (") + KadIPToString(contact->GetIPAddress()) + wxT(") tried to update contact entry but failed to provide the proper sender key (Sent Empty: ") + (contact->GetUDPKey().GetKeyValue(theApp->GetPublicIP(false)) == 0 ? wxT("Yes") : wxT("No")) + wxT(") for the entry (") + KadIPToString(contactUpdate->GetIPAddress()) + wxT(") - denying update"));
					update = false;
				} else if (contactUpdate->GetVersion() >= 1 && contactUpdate->GetVersion() < 6 && contactUpdate->GetReceivedHelloPacket()) {
					// legacy kad2 contacts are allowed only to update their RefreshTimer to avoid having them hijacked/corrupted by an attacker
					// (kad1 contacts do not have this restriction as they might turn out as kad2 later on)
					// only exception is if we didn't received a HELLO packet from this client yet
					if (contactUpdate->GetIPAddress() == contact->GetIPAddress() && contactUpdate->GetTCPPort() == contact->GetTCPPort() && contactUpdate->GetVersion() == contact->GetVersion() && contactUpdate->GetUDPPort() == contact->GetUDPPort()) {
						wxASSERT(!contact->IsIPVerified());	// legacy kad2 nodes should be unable to verify their IP on a HELLO
						outIpVerified = contactUpdate->IsIPVerified();
						m_bin->SetAlive(contactUpdate);
						AddDebugLogLineN(logKadRouting, CFormat(wxT("Updated kad contact refreshtimer only for legacy kad2 contact (%s, %u)")) % KadIPToString(contactUpdate->GetIPAddress()) % contactUpdate->GetVersion());
					} else {
						AddDebugLogLineN(logKadRouting, CFormat(wxT("Rejected value update for legacy kad2 contact (%s -> %s, %u -> %u)")) 
							% KadIPToString(contactUpdate->GetIPAddress()) % KadIPToString(contact->GetIPAddress()) % contactUpdate->GetVersion() % contact->GetVersion());
						update = false;
					}
				} else {
#ifdef __DEBUG__
					// just for outlining, get removed anyway
					//debug logging stuff - remove later
					if (contact->GetUDPKey().GetKeyValue(theApp->GetPublicIP(false)) == 0) {
						if (contact->GetVersion() >= 6 && contact->GetType() < 2) {
							AddDebugLogLineN(logKadRouting, wxT("Updating > 0.49a + type < 2 contact without valid key stored ") + KadIPToString(contact->GetIPAddress()));
						}
					} else {
						AddDebugLogLineN(logKadRouting, wxT("Updating contact, passed key check ") + KadIPToString(contact->GetIPAddress()));
					}

					if (contactUpdate->GetVersion() >= 1 && contactUpdate->GetVersion() < 6) {
						wxASSERT(!contactUpdate->GetReceivedHelloPacket());
						AddDebugLogLineN(logKadRouting, CFormat(wxT("Accepted update for legacy kad2 contact, because of first HELLO (%s -> %s, %u -> %u)"))
							% KadIPToString(contactUpdate->GetIPAddress()) % KadIPToString(contact->GetIPAddress()) % contactUpdate->GetVersion() % contact->GetVersion());
					}
#endif
					// All other nodes (Kad1, Kad2 > 0.49a with UDPKey checked or not set, first hello updates) are allowed to do full updates
					// do not let Kad1 responses overwrite Kad2 ones
					if (m_bin->ChangeContactIPAddress(contactUpdate, contact->GetIPAddress()) && contact->GetVersion() >= contactUpdate->GetVersion()) {
						contactUpdate->SetUDPPort(contact->GetUDPPort());
						contactUpdate->SetTCPPort(contact->GetTCPPort());
						contactUpdate->SetVersion(contact->GetVersion());
						contactUpdate->SetUDPKey(contact->GetUDPKey());
						// don't unset the verified flag (will clear itself on ipchanges)
						if (!contactUpdate->IsIPVerified()) {
							contactUpdate->SetIPVerified(contact->IsIPVerified());
						}
						outIpVerified = contactUpdate->IsIPVerified();
						m_bin->SetAlive(contactUpdate);
						if (contact->GetReceivedHelloPacket()) {
							contactUpdate->SetReceivedHelloPacket();
						}
					} else {
						update = false;
					}
				}
			}
			return false;
		} else if (m_bin->GetRemaining()) {
			update = false;
			// This bin is not full, so add the new contact
			return m_bin->AddContact(contact);
		} else if (CanSplit()) {
			// This bin was full and split, call add on the correct branch.
			Split();
			return m_subZones[contact->GetDistance().GetBitNumber(m_level)]->Add(contact, update, outIpVerified);
		} else {
			update = false;
			return false;
		}
	}
}

這個函數在添加節點時,主要分爲如下的幾種狀況來處理

1. 當前的這個RoutingZone不是二叉樹的一個葉子節點。若當前的這個RoutingZone不是二叉樹的一個葉子節點,則把contact加入到子樹中。那究竟要將contact添加進0和1兩個子樹中的哪個呢?這主要由contact的m_distance,對應於當前層的那一位上的值決定。(contact的m_distance一般是contact與本Kademlia節點KadID的異或值。)

amule-2.3.1/src/kademlia/utils/UInt128.h:

/** Bit at level 0 being most significant. */
	unsigned GetBitNumber(unsigned bit) const throw()
	{
		return bit <= 127 ? (m_data[bit / 32] >> (31 - (bit % 32))) & 1 : 0;
	}

好比當前爲第0層,若是m_distance的第0位上是0,則節點將被放進當前RoutingZone的子樹0中,若爲1,則將被放入當前RoutingZone的子樹1中。

這個地方還能夠再看一下,UInt128的第n位的含義,0~31位存放於m_data[0],32~63位存放於m_data[1],其它各位存放的m_data index依此類推。

m_data[0]的最高位,也就是第31位爲UInt128的第0位,m_data[0]的第30位UInt128的第1位,UInt128的其它各位存放的位置依此類推。

2. 當前的RoutingZone爲葉子節點,但其m_bin中已經有了clientID與要加入的contact的clientID相同的contact。若是是這種狀況,則將更新原contact的信息。

3. 當前的RoutingZone爲葉子節點,其RoutingBin m_bin仍然能夠放下更多的contact。在這種狀況下,則直接向RoutingBin中添加contact。

4. 當前的RoutingZone爲葉子節點,其RoutingBin m_bin中沒法放下更多的節點,可是RoutingZone能夠被分割若是是這種狀況,則首先執行RoutingZone的分割,而後依照第1中狀況中的規則,添加contact。

這裏能夠再來看一下判斷RoutingZone是否能夠被分割的依據:

bool CRoutingZone::CanSplit() const throw()
{
	// Max levels allowed.
	if (m_level >= 127) {
		return false;
	}

	// Check if this zone is allowed to split.
	return ((m_zoneIndex < KK || m_level < KBASE) && m_bin->GetSize() == K);
}

CRoutingZone::CanSplit()根據多個條件來判斷一個RoutingZone是否能夠被分割。

若是m_level大於等於127,也就是KadID的位長度,則直接返回不能再分割。

此外只有當前RoutingZone的RoutingBin中所包含的contact數達到上限時纔有可能被分割。此上限也就是K,在amule-2.3.1/src/kademlia/kademlia/Defines.h中,咱們能夠看到,這個值是被定義爲了10。

RoutingBin中所包含的contact數達到上限,並非RoutingZone可以被分割的充分條件。在此以外,還須要知足兩個條件中的一個:RoutingZone的層次不能過低,也就是說m_level不能太大,具體點說,就是小於KBASE,在amule-2.3.1/src/kademlia/kademlia/Defines.h中,咱們能夠看到,這個值是被定義爲了4;或者m_zoneIndex不能太大,小於KK值,該值定義爲5。

再來看下分割動做具體如何執行

void CRoutingZone::Split()
{
	StopTimer();
		
	m_subZones[0] = GenSubZone(0);
	m_subZones[1] = GenSubZone(1);

	ContactList entries;
	m_bin->GetEntries(&entries);
	m_bin->m_dontDeleteContacts = true;
	delete m_bin;
	m_bin = NULL;

	for (ContactList::const_iterator it = entries.begin(); it != entries.end(); ++it) {
		if (!m_subZones[(*it)->GetDistance().GetBitNumber(m_level)]->m_bin->AddContact(*it)) {
			delete *it;
		}
	}
}

。。。。。。

CRoutingZone *CRoutingZone::GenSubZone(unsigned side)
{
	wxASSERT(side <= 1);

	CUInt128 newIndex(m_zoneIndex);
	newIndex <<= 1;
	newIndex += side;
	return new CRoutingZone(this, m_level + 1, newIndex);
}


void CRoutingZone::StopTimer()
{
    CKademlia::RemoveEvent(this);
}

在CRoutingZone::Split()中將本RoutingZone分割爲兩個。

1. 它首先調用StopTimer()停掉定時器。Linux下電騾aMule Kademlia網絡構建分析3 一文中,咱們有看到在按期會被執行的CKademlia::Process()函數中,會執行RoutingZoneOnSmallTimer()等函數。此處調用StopTimer()也就是停掉這些函數的按期執行。

2. 而後兩次調用GenSubZone(),建立兩個子RoutingZone。對於GenSubZone(),主要看一下RoutingZone的zoneIndex和level的計算。

3. 從RoutingBin m_bin中獲取全部的contacts。而後刪除m_bin。

4. 將全部的contacts依據規則添加進兩個子RoutingZone中。

由前面的分析咱們看到,不管是什麼樣的case,要實際添加contact,最終總會調到CRoutingBin::AddContact(),這裏咱們再來看一下這個函數的實現(amule-2.3.1/src/kademlia/routing/RoutingBin.cpp):

bool CRoutingBin::AddContact(CContact *contact)
{
	wxASSERT(contact != NULL);

	uint32_t sameSubnets = 0;
	// Check if we already have a contact with this ID in the list.
	for (ContactList::const_iterator it = m_entries.begin(); it != m_entries.end(); ++it) {
		if (contact->GetClientID() == (*it)->GetClientID()) {
			return false;
		}
		if ((contact->GetIPAddress() & 0xFFFFFF00) == ((*it)->GetIPAddress() & 0xFFFFFF00)) {
			sameSubnets++;
		}
	}
	// Several checks to make sure that we don't store multiple contacts from the same IP or too many contacts from the same subnet
	// This is supposed to add a bit of protection against several attacks and raise the resource needs (IPs) for a successful contact on the attacker side
	// Such IPs are not banned from Kad, they still can index, search, etc so multiple KAD clients behind one IP still work

	if (!CheckGlobalIPLimits(contact->GetIPAddress(), contact->GetUDPPort())) {
		return false;
	}

	// no more than 2 IPs from the same /24 netmask in one bin, except if its a LANIP (if we don't accept LANIPs they already have been filtered before)
	if (sameSubnets >= 2 && !::IsLanIP(wxUINT32_SWAP_ALWAYS(contact->GetIPAddress()))) {
		AddDebugLogLineN(logKadRouting, wxT("Ignored kad contact (IP=") + KadIPPortToString(contact->GetIPAddress(), contact->GetUDPPort()) + wxT(") - too many contact with the same subnet in RoutingBin"));
		return false;
	}

	// If not full, add to the end of list
	if (m_entries.size() < K) {
		m_entries.push_back(contact);
		AdjustGlobalTracking(contact->GetIPAddress(), true);
		return true;
	}
	return false;
}

即使是走到了CRoutingBin::AddContact(),contact也並不必定會真的被加進本節點的聯繫人列表中。

如上所示,在CRoutingBin::AddContact()中仍是會再次進行過濾。

1. CRoutingBin::AddContact()首先會遍歷已經保存的全部的contact。遍歷的過程當中會計算這一zone中,與所傳入contact處於相同子網段內的contact的數量。同時會檢查要添加的contact的clientID是否與某個已經保存了的contact的clientID相同,若是是則直接返回;如前面看到的,這種case應該在CRoutingZone::Add()中處理的。

2. 執行CheckGlobalIPLimits()對IP及UDPPort作一個檢查。若檢查不經過,則直接返回。

3. 確保一個RoutingBin中,來自同一個/24網絡掩碼內的contact IP很少於2個,除非它是一個LANIP。

4. 還要再次檢查RoutingBin已經保存的contacts的數量是否超出了上限K,也就是10個。沒有超出時,纔會真正地添加contact。並調整GlobalTracking。

此處的CheckGlobalIPLimits()和檢查LanIP的方法仍是值得咱們再看一下。

先來看CheckGlobalIPLimits(),其執行過程以下:

bool CRoutingBin::CheckGlobalIPLimits(uint32_t ip, uint16_t DEBUG_ONLY(port))
{
	// no more than 1 KadID per IP
	uint32_t sameIPCount = 0;
	GlobalTrackingMap::const_iterator itIP = s_globalContactIPs.find(ip);
	if (itIP != s_globalContactIPs.end()) {
		sameIPCount = itIP->second;
	}
	if (sameIPCount >= MAX_CONTACTS_IP) {
		AddDebugLogLineN(logKadRouting, wxT("Ignored kad contact (IP=") + KadIPPortToString(ip, port) + wxT(") - too many contacts with the same IP (global)"));
		return false;
	}
	//  no more than 10 IPs from the same /24 netmask global, except if its a LANIP (if we don't accept LANIPs they already have been filtered before)
	uint32_t sameSubnetGlobalCount = 0;
	GlobalTrackingMap::const_iterator itSubnet = s_globalContactSubnets.find(ip & 0xFFFFFF00);
	if (itSubnet != s_globalContactSubnets.end()) {
		sameSubnetGlobalCount = itSubnet->second;
	}
	if (sameSubnetGlobalCount >= MAX_CONTACTS_SUBNET && !::IsLanIP(wxUINT32_SWAP_ALWAYS(ip))) {
		AddDebugLogLineN(logKadRouting, wxT("Ignored kad contact (IP=") + KadIPPortToString(ip, port) + wxT(") - too many contacts with the same subnet (global)"));
		return false;
	}
	return true;
}

這裏主要確保兩個事情:

1. 在全局全部的contact中,每一個IP很少於一個KadID。也就是對於一個IP而言,只能有一個contact。

2. 在全局全部的contact中,位於相同的/24子網中的contacts很少於10個,除非要加入的節點的ip是LanIP。

這個地方的檢查規則與前面CRoutingBin::AddContact()的很類似,只不過這裏是在全局contacts範圍內。

咱們能夠再來看一下,在aMule中,是如何判斷一個IP地址是不是LanIP的。在amule-2.3.1/src/NetworkFunctions.cpp中:

/**
 * Used to store the ranges.
 */
struct IPRange
{
	const wxChar *addr;
	unsigned int mask;
	bool isLAN;
};


const IPRange ranges[] = {
//	Here is reserved blocks from RFC 3330 at http://www.rfc-editor.org/rfc/rfc3330.txt
//
//Address Block                      Present Use                           Reference
//----------------------------------------------------------------------------------
{ wxT("0.0.0.0"),        8, false }, // "This" Network             [RFC1700, page 4]
{ wxT("10.0.0.0"),       8, true  }, // Private-Use Networks               [RFC1918]
// Acording to RFC3330, 24.* and 14.* must be parsed as normal ips.
//{ wxT("14.0.0.0"),       8, false }, // Public-Data Networks     [RFC1700, page 181]
//{ wxT("24.0.0.0"),       8, false }, // Cable Television Networks                 --
{ wxT("39.0.0.0"),       8, false }, // Reserved but subject
                                     //    to allocation                   [RFC1797]
{ wxT("127.0.0.0"),      8, false }, // Loopback                   [RFC1700, page 5]
{ wxT("128.0.0.0"),     16, false }, // Reserved but subject
                                     //    to allocation                          --
{ wxT("169.254.0.0"),   16, false }, // Link Local                                --
{ wxT("172.16.0.0"),    12, true  }, // Private-Use Networks               [RFC1918]
{ wxT("191.255.0.0"),   16, false }, // Reserved but subject
                                     //    to allocation                          --
{ wxT("192.0.0.0"),     24, false }, // Reserved but subject          
                                     //    to allocation                          --
{ wxT("192.0.2.0"),     24, false }, // Test-Net
{ wxT("192.88.99.0"),   24, false }, // 6to4 Relay Anycast                 [RFC3068]
{ wxT("192.168.0.0"),   16, true  }, // Private-Use Networks               [RFC1918]
{ wxT("198.18.0.0"),    15, false }, // Network Interconnect
                                     //    Device Benchmark Testing        [RFC2544]
{ wxT("223.255.255.0"), 24, false }, // Reserved but subject
                                     //    to allocation                          --
{ wxT("224.0.0.0"),      4, false }, // Multicast                          [RFC3171]
{ wxT("240.0.0.0"),      4, false }  // Reserved for Future Use    [RFC1700, page 4]
};


struct filter_st {
	uint32 addr;		// Address and mask in anti-host order.
	uint32 mask;
};

const unsigned int number_of_ranges = sizeof(ranges) / sizeof(IPRange);
static filter_st filters[number_of_ranges];


// This function is used to initialize the IP filter
bool SetupFilter()
{
	for (unsigned int i = 0; i < number_of_ranges; ++i) {
		filters[i].addr = StringIPtoUint32( ranges[i].addr );
		filters[i].mask = ~wxUINT32_SWAP_ALWAYS((1 << (32 - ranges[i].mask)) - 1);
	}
	return true;
}




bool IsLanIP(uint32_t ip) throw()
{
	for (unsigned int i = 0; i < number_of_ranges; i++) {
		if (((ip ^ filters[i].addr) & filters[i].mask) == 0) {
			return ranges[i].isLAN;
		}
	}
	return false;
}

這裏先是定義了一張表ranges,其中包含了RFC已有明肯定義的網段,網段多是Lan的,也可能不是。

在SetupFilter()中,會根據ranges表,構造一個filters表,主要就是將ranges表中沒一個item的IP字段和mask字段作一個適當的轉換,以方便查詢。

判斷一個ip是不是LanIP,則遍歷filters表,找到該IP屬於哪一個網段。而後找到ranges表中的對應項,根據找到的項的屬性,來確認一個IP是不是LanIP。

聯繫人CContact的查找

CRoutingZone提供了多種查找一個聯繫人的方式。能夠根據contact的clientID來查找,能夠根據contact的ip和端口號來查詢,也可查詢知足maxType和minKadVersion的隨便的一個contact。

根據contact的clientID查詢

根據contact的clientID查詢使用CRoutingZone::GetContact(const CUInt128& id)函數,其定義以下:

CContact *CRoutingZone::GetContact(const CUInt128& id) const throw()
{
	if (IsLeaf()) {
		return m_bin->GetContact(id);
	} else {
		CUInt128 distance = CKademlia::GetPrefs()->GetKadID();
		distance ^= id;
		return m_subZones[distance.GetBitNumber(m_level)]->GetContact(id);
	}
}

若是當前RoutingZone是葉子節點,則直接在它的RoutingBin m_bin中查找相應的contact。不然的話,就計算出要查找的contact與本節點的distance,遞歸地在適當的子RoutingZone中查找。

而後來看CRoutingBin::GetContact(const CUInt128 &id),實現以下:

CContact *CRoutingBin::GetContact(const CUInt128 &id) const throw()
{
	for (ContactList::const_iterator it = m_entries.begin(); it != m_entries.end(); ++it) {
		if ((*it)->GetClientID() == id) {
			return *it;
		}
	}
	return NULL;
}

在這個函數中,遍歷保存的全部的contact,逐個對比clientID,找出知足條件的返回給調用者。找不到時返回NULL。

根據contact的ip和端口號查詢

根據contact的ip和端口號來查詢使用GetContact(uint32_t ip, uint16_t port, bool tcpPort),實現以下:

CContact *CRoutingZone::GetContact(uint32_t ip, uint16_t port, bool tcpPort) const throw()
{
	if (IsLeaf()) {
		return m_bin->GetContact(ip, port, tcpPort);
	} else {
		CContact *contact = m_subZones[0]->GetContact(ip, port, tcpPort);
		return (contact != NULL) ? contact : m_subZones[1]->GetContact(ip, port, tcpPort);
	}
}

一樣,若是當前RoutingZone是葉子節點,則直接在它的RoutingBin m_bin中查找相應的contact。不然,就遞歸地查找子zone 0,若是沒找到,則遞歸地查找子zone 1。在RoutingBin中:

CContact *CRoutingBin::GetContact(uint32_t ip, uint16_t port, bool tcpPort) const throw()
{
	for (ContactList::const_iterator it = m_entries.begin(); it != m_entries.end(); ++it) {
		CContact *contact = *it;
		if ((contact->GetIPAddress() == ip)
		    && ((!tcpPort && port == contact->GetUDPPort()) || (tcpPort && port == contact->GetTCPPort()) || port == 0)) {
			return contact;
		}
	}
	return NULL;
}

一樣是遍歷保存的全部的contact,查找徹底知足條件者。

查詢知足maxType和minKadVersion的隨機contact

而後是查詢知足maxType和minKadVersion的隨便的一個contact,使用CRoutingZone::GetRandomContact():

CContact *CRoutingZone::GetRandomContact(uint32_t maxType, uint32_t minKadVersion) const
{
	if (IsLeaf()) {
		return m_bin->GetRandomContact(maxType, minKadVersion);
	} else {
		unsigned zone = GetRandomUint16() & 1 /* GetRandomUint16() % 2 */;
		CContact *contact = m_subZones[zone]->GetRandomContact(maxType, minKadVersion);
		return (contact != NULL) ? contact : m_subZones[1 - zone]->GetRandomContact(maxType, minKadVersion);
	}
}

一樣,若是當前RoutingZone是葉子節點,則直接在它的RoutingBin m_bin中查找相應的contact。不然,隨機地從兩個子zone中選擇一個遞歸地查找,若是沒找到,則遞歸地查找另外一個子zone。在RoutingBin中:

CContact *CRoutingBin::GetRandomContact(uint32_t maxType, uint32_t minKadVersion) const
{
	if (m_entries.empty()) {
		return NULL;
	}

	// Find contact
	CContact *lastFit = NULL;
	uint32_t randomStartPos = GetRandomUint16() % m_entries.size();
	uint32_t index = 0;

	for (ContactList::const_iterator it = m_entries.begin(); it != m_entries.end(); ++it) {
		if ((*it)->GetType() <= maxType && (*it)->GetVersion() >= minKadVersion) {
			if (index >= randomStartPos) {
				return *it;
			} else {
				lastFit = *it;
			}
		}
		index++;
	}

	return lastFit;
}

這段code也沒有什麼太特別的地方,此處再也不贅述。

查找與某個contact距離最近的一組contacts

CRoutingZone還提供了GetClosestTo()函數,來查找與某個contact距離最近的一組contact。其實現邏輯以下:

void CRoutingZone::GetClosestTo(uint32_t maxType, const CUInt128& target, const CUInt128& distance, uint32_t maxRequired, ContactMap *result, bool emptyFirst, bool inUse) const
{
	// If leaf zone, do it here
	if (IsLeaf()) {
		m_bin->GetClosestTo(maxType, target, maxRequired, result, emptyFirst, inUse);
		return;
	}

	// otherwise, recurse in the closer-to-the-target subzone first
	int closer = distance.GetBitNumber(m_level);
	m_subZones[closer]->GetClosestTo(maxType, target, distance, maxRequired, result, emptyFirst, inUse);

	// if still not enough tokens found, recurse in the other subzone too
	if (result->size() < maxRequired) {
		m_subZones[1-closer]->GetClosestTo(maxType, target, distance, maxRequired, result, false, inUse);
	}
}

一樣,若是當前RoutingZone是葉子節點,則直接在它的RoutingBin m_bin中查找。不然,從兩個子zone中與目標client距離更近的子zone中遞歸地查找。若是找到的contact數量不足,則遞歸地查找另外一個子zone。

此處還能夠再看一下,在aMule的Kademlia網絡中,兩個節點之間的距離的語義。距離distance是經過兩個Kademlia網絡節點的KadID經過異或操做計算而來,KadID是一個隨機的值。因此distance是在兩個Kademlia節點的KadID肯定以後的一個邏輯距離,而並無實際的物理意義,好比,這個距離與實際的網絡中,兩個節點的物理位置毫無關係。

那根據這個distance邏輯距離,又是怎麼肯定遠近的呢?假設本節點爲S,有A、B、C三個節點,它們與S的距離分別爲DA、DB、DC(算法爲兩個KadID的異或),將DA、DB、DC轉化爲數字值LA,LB、LC(算法爲distance值中每一個位上的值與該位的權值相乘後相加,其中位0的權值爲2^127,位1的權值爲2^126,位2的權值爲2^125,。。。),若abs(LA-LB) < abs(LA - LC),則認爲節點A與B的距離比節點A與C的距離要近。

也能夠經過CUInt128的幾個比較的operator的實現來看出這一點(在amule-2.3.1/src/kademlia/utils/UInt128.h中):

bool operator<  (const CUInt128& value) const throw() {return (CompareTo(value) <  0);}
	bool operator>  (const CUInt128& value) const throw() {return (CompareTo(value) >  0);}
	bool operator<= (const CUInt128& value) const throw() {return (CompareTo(value) <= 0);}
	bool operator>= (const CUInt128& value) const throw() {return (CompareTo(value) >= 0);}
	bool operator== (const CUInt128& value) const throw() {return (CompareTo(value) == 0);}
	bool operator!= (const CUInt128& value) const throw() {return (CompareTo(value) != 0);}
他們都經過CompareTo()來實現比較邏輯(在amule-2.3.1/src/kademlia/utils/UInt128.cpp中):
int CUInt128::CompareTo(const CUInt128 &other) const throw()
{
	for (int i = 0; i < 4; ++i) {
	    if (m_data[i] < other.m_data[i])
			return -1;
	    if (m_data[i] > other.m_data[i])
			return 1;
	}
	return 0;
}

RoutingBin中:

void CRoutingBin::GetClosestTo(uint32_t maxType, const CUInt128 &target, uint32_t maxRequired, ContactMap *result, bool emptyFirst, bool inUse) const
{
	// Empty list if requested.
	if (emptyFirst) {
		result->clear();
	}

	// No entries, no closest.
	if (m_entries.size() == 0) {
		return;
	}

	// First put results in sort order for target so we can insert them correctly.
	// We don't care about max results at this time.
	for (ContactList::const_iterator it = m_entries.begin(); it != m_entries.end(); ++it) {
		if ((*it)->GetType() <= maxType && (*it)->IsIPVerified()) {
			CUInt128 targetDistance((*it)->GetClientID() ^ target);
			(*result)[targetDistance] = *it;
			// This list will be used for an unknown time, Inc in use so it's not deleted.
			if (inUse) {
				(*it)->IncUse();
			}
		}
	}

	// Remove any extra results by least wanted first.
	while (result->size() > maxRequired) {
		// Dec in use count.
 		if (inUse) {
  			(--result->end())->second->DecUse();
		}
 		// Remove from results
 		result->erase(--result->end());
	}
}

這裏能夠看一下aMule中,所謂的distance closest的語義。在這個函數中也就是找出全部的type小於maxType,同時IP被Verified的contacts。

若是找到的節點過多,還會從結果map中再移除一些出去。

那IP Verified究竟又是什麼含義呢?咱們搜索全部建立CContact的地方,能夠知道,已經鏈接上的CContact的IP會是Verified的。

Done。

相關文章
相關標籤/搜索