目的
dns-seederを自前で起動して、自前のnodeと接続したところgetaddrの返り値がonion addressになっていた。この原因を見つけるのとonion addressではなくてipv4 address でnodeからの返信を受け取るためにどんな修正 or 起動optionが必要なのかを調べること。対象はtestnet。
src/addrman.cpp
繋がっているnodeの情報や状態を管理しているっぽい
seeds nodeの初期設定の流れ
src/chainparamsseeds.hに最初のseeds nodeの情報がある
この値は、src/chainparams.cpp:247 でvFixedSeeds変数にロードしている
src/chainparams.h:80のFixedSeeds()がvFixedSeedsのgetter method
src/net.cpp:1790でaddrman.Add(convertSeed6(Params().FixedSeeds()), local);のコードで初期seeds nodeが読み込まれている模様
FixedSeeds()がロードされる理由
src/net.cpp:1783 に以下の記述があった。dnsseederからipが引けなかった時にFixedSeeds()の値を使うっぽい。
// Add seed nodes if DNS seeds are all down (an infrastructure attack?).
if (addrman.size() == 0 && (GetTime() - nStart > 60)) {
fixedSeedsの設定
fixedSeedsは chainparamsseeds.h で設定されている。
で、その値をどうやって作るか?っていうと contrib/sees/README.md にそのやり方が書いてある。以下抜粋。
python3 makeseeds.py < seeds_main.txt > nodes_main.txt
python3 generate-seeds.py . > ../../src/chainparamsseeds.h
testnetのfixedSeedsを書き換える場合は、 contrib/seeds/nodes_test.txt にip:portを追加して、 python3 generate-seeds.py . > ../../src/chainparamsseeds.hを叩く。
getAddrで返すアドレスの収集方法
addr messageに対して応答するpeer addressの一覧情報は、net_processing.cpp: line 2761 あたりにある、std::vector<CAddress> vAddr = connman->GetAddresses();で取得している。
connman->GetAddresses()はnet.cpp: line2501にあるstd::vector<CAddress> CConnman::GetAddresses()で、そこからさらにreturn addrman.GetAddr();を呼び出している。
addrman.GetAddr()はaddrman.h: line 610にあり、実態はGetAddr_(vAddr);である。
GetAddr_(vAddr)の定義はaddrman.cpp: line 474にある、void CAddrMan::GetAddr_(std::vector<CAddress>& vAddr)これ。
GET_Addr....
CAddrMan#Addを呼んでいるところ
adrvertiseされるaddressはCAddrManが保持しているaddessなので、CAddrManにaddressが追加する処理をまとめる。
net.cpp: line 1656: これは void CConnman::ThreadDNSAddressSeed()の処理なので、多分DNSの返答で受け取ったaddressを追加してる処理。
net.cpp: line 1790: コメントに// Add seed nodes if DNS seeds are all down (an infrastructure attack?).とあるので、DNSが全てダウンしているときに、fixedseedsとして定義されているaddressを追加する処理っぽい。
net.cpp: line 2498: void CConnman::AddNewAddresses(const std::vector<CAddress>& vAddr, const CAddress& addrFrom, int64_t nTimePenalty)としてラップされている。
net_processing.cpp: line 1899: else if (strCommand == NetMsgType::ADDR)のブロックの中なので、他のnodeからaddrで受け取ったaddressの一覧を記録している処理。
結論
AddrManが記録するaddressはDNSから渡されたものか、もしくは他のnodeからaddr messageで送られてきたものだけ。
addrを受けてから、返すaddressの数は1000個まで
まずはaddrman.cpp: line477-478で2500個のaddressを詰め込む。
code: cpp
if (nNodes > ADDRMAN_GETADDR_MAX)
nNodes = ADDRMAN_GETADDR_MAX;
これをnet_processing.cpp: line 2766 でpfrom->PushAddress(addr, insecure_rand);を呼び出して送る予定リストに入れている。
PushAddresの実装はnet.h: line 815で、ここでは1000個のaddressだけ詰め込んでる。でも事前に2500個までaddressを受け取るので、1000個を超えたものはランダムなものと入れ替える。
んで、最終的にはnet_processing.cpp: line 3317で他のpeerに送られる。
広告対象のaddrとして登録される処理について
addr messageで送るaddrのリストは接続node毎に管理しているぽい。
net.h: line 815に定義されている、void PushAddress(const CAddress& _addr, FastRandomContext &insecure_rand)を使って広告対象のaddressとして登録している。
PushAddressで登録されたaddressが、addr messageで他のnodeに送られる。
PushAddressを呼んでいるところ
net.cpp: line 180 void AdvertiseLocal(CNode *pnode)
多分、自分のaddressを広告する処理っぽい。
net_processing.cpp: line 1072 static void RelayAddress(const CAddress& addr, bool fReachable, CConnman* connman)
他のnodeからaddr messageで受け取ったaddressを別のnodeへ送るための処理っぽい。受け取ったaddressを元に24時間で変わるhash値を生成して識別することで、24時間以内に同じaddrを再送しないような処理もしてるっぽい。
net_processing.cpp: line 1756とnet_processing.cpp: line 1760あたり。net_processing.cpp: line 1745 if (!pfrom->fInbound)ブロックのなか。
自分から接続した他のノードを広告対象として追加している処理っぽい?
net_processing.cpp: line 2765。net_processing.cpp: line 2741 else if (strCommand == NetMsgType::GETADDR)のブロックの中
nodeからGETADDR messageが飛んできたときに、addr messageとして返すためのaddrの一覧を自分が知っているaddr一覧から集めている処理っぽい。
Validation::IsInitialBlockDownloadの処理
ここの判定がfalseの場合に、自分のaddressを接続先のnodeに広告するようになるので、この処理の中身を読む。
validation.cpp: line 1174に定義された関数
処理内容
1. if (latchToFalse.load(std::memory_order_relaxed)): 一度チェックが通ったら以降は素通りでfalseを返すっぽい。
2. if (fImporting || fReindex):起動時にbootstrap.datからブロック情報を取り込む処理、もしくは、re indexing処理中であればtrueを返す。
3. if (chainActive.Tip() == nullptr): 自分が持ってるchainの先端がnullだとtrueを返す。
4. if (chainActive.Tip()->nChainWork < nMinimumChainWork): chainの先端のblockのchain workが小さすぎたらtrue。nMinimumChainWorkはリリース毎にハードコーティングされている。checkpoint的なあれ?DoS対策?
5. if (chainActive.Tip()->GetBlockTime() < (GetTime() - nMaxTipAge)): chainの先端のblockのtimestampが1日以上古かったらtrue
ということで、自分が持っているblockの最新が1日以上古い場合はまだblockダウンロード中とみなされる。
p2pネットワークを構築する(=addressを共有する)ための処理のまとめ
相手nodeに送る候補のaddressはCAddrManからランダムに選択される。
CAddrManにaddressが追加されるのは、DNSにlookupした結果として受け取ったときと、他のnodeからaddr messageで送られてきたときだけ。自分が他のnodeに接続しただけではCAddrManにはaddressは追加されない。
他のnodeと接続したときに、自分のaddressをaddr messageで送るのは、自分のnodeがblockのダウンロードが終了していると判定されたとき。
blockのダウンロードが終了したという判定は、最終的に自分が持っている先端のblockのタイムスタンプが1日以内であればOK
getaddrをすると、相手nodeから自分が知らないaddressを最大1000個送ってきてくれる。
ただし、getaddrのレスポンスとしては送ってこない。30秒基準のランダムなwaitをかけてから返事を返してくる。
自分がすでに十分なaddressを知っている(多分1000個が条件ぽい?)なら、getaddr messageを投げない。
DNS lookup -> 受け取ったaddressからランダムに接続 -> blockのダウロードが完了して入れば相手にaddr messageで自分のaddressを伝える -> 相手のnodeはさらに別のnodeにaddr messageで伝搬する。