/ libi2pd_client / AddressBook.cpp
AddressBook.cpp
1 /* 2 * Copyright (c) 2013-2025, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 */ 8 9 #include <string.h> 10 #include <inttypes.h> 11 #include <string> 12 #include <unordered_map> 13 #include <fstream> 14 #include <chrono> 15 #include <condition_variable> 16 #include <openssl/rand.h> 17 #include <boost/algorithm/string.hpp> 18 #include "Base.h" 19 #include "util.h" 20 #include "Timestamp.h" 21 #include "Identity.h" 22 #include "FS.h" 23 #include "Log.h" 24 #include "HTTP.h" 25 #include "NetDb.hpp" 26 #include "ClientContext.h" 27 #include "AddressBook.h" 28 #include "Config.h" 29 30 #if STD_FILESYSTEM 31 #include <filesystem> 32 namespace fs_lib = std::filesystem; 33 #else 34 #include <boost/filesystem.hpp> 35 namespace fs_lib = boost::filesystem; 36 #endif 37 38 namespace i2p 39 { 40 namespace client 41 { 42 // TODO: this is actually proxy class 43 class AddressBookFilesystemStorage: public AddressBookStorage 44 { 45 public: 46 47 AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") 48 { 49 i2p::config::GetOption("persist.addressbook", m_IsPersist); 50 if (m_IsPersist) 51 i2p::config::GetOption("addressbook.hostsfile", m_HostsFile); 52 } 53 std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const i2p::data::IdentHash& ident) override; 54 void AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address) override; 55 void RemoveAddress (const i2p::data::IdentHash& ident) override; 56 void CleanUpCache () override; 57 58 bool Init () override; 59 int Load (Addresses& addresses) override; 60 int LoadLocal (Addresses& addresses) override; 61 int Save (const Addresses& addresses) override; 62 63 void SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) override; 64 bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) override; 65 void ResetEtags () override; 66 67 private: 68 69 int LoadFromFile (const std::string& filename, Addresses& addresses); // returns -1 if can't open file, otherwise number of records 70 71 private: 72 73 i2p::fs::HashedStorage storage; 74 std::string etagsPath, indexPath, localPath; 75 bool m_IsPersist; 76 std::string m_HostsFile; // file to dump hosts.txt, empty if not used 77 std::unordered_map<i2p::data::IdentHash, std::pair<std::vector<uint8_t>, uint64_t> > m_FullAddressCache; // ident hash -> (full ident buffer, last access timestamp) 78 std::mutex m_FullAddressCacheMutex; 79 }; 80 81 bool AddressBookFilesystemStorage::Init() 82 { 83 storage.SetPlace(i2p::fs::GetDataDir()); 84 // init storage 85 if (storage.Init(i2p::data::GetBase32SubstitutionTable(), 32)) 86 { 87 // init ETags 88 etagsPath = i2p::fs::StorageRootPath (storage, "etags"); 89 if (!i2p::fs::Exists (etagsPath)) 90 i2p::fs::CreateDirectory (etagsPath); 91 // init address files 92 indexPath = i2p::fs::StorageRootPath (storage, "addresses.csv"); 93 localPath = i2p::fs::StorageRootPath (storage, "local.csv"); 94 return true; 95 } 96 return false; 97 } 98 99 std::shared_ptr<const i2p::data::IdentityEx> AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) 100 { 101 auto ts = i2p::util::GetMonotonicSeconds (); 102 { 103 std::lock_guard<std::mutex> l(m_FullAddressCacheMutex); 104 auto it = m_FullAddressCache.find (ident); 105 if (it != m_FullAddressCache.end ()) 106 { 107 it->second.second = ts; 108 return std::make_shared<i2p::data::IdentityEx>(it->second.first.data (), it->second.first.size ()); 109 } 110 } 111 112 if (!m_IsPersist) 113 { 114 LogPrint(eLogDebug, "Addressbook: Persistence is disabled"); 115 return nullptr; 116 } 117 std::string filename = storage.Path(ident.ToBase32()); 118 std::ifstream f(filename, std::ifstream::binary); 119 if (!f.is_open ()) 120 { 121 LogPrint(eLogDebug, "Addressbook: Requested, but not found: ", filename); 122 return nullptr; 123 } 124 125 f.seekg (0,std::ios::end); 126 size_t len = f.tellg (); 127 if (len < i2p::data::DEFAULT_IDENTITY_SIZE) 128 { 129 LogPrint (eLogError, "Addressbook: File ", filename, " is too short: ", len); 130 return nullptr; 131 } 132 f.seekg(0, std::ios::beg); 133 std::vector<uint8_t> buf(len); 134 f.read((char *)buf.data (), len); 135 if (!f) 136 { 137 LogPrint (eLogError, "Addressbook: Couldn't read ", filename); 138 return nullptr; 139 } 140 { 141 std::lock_guard<std::mutex> l(m_FullAddressCacheMutex); 142 m_FullAddressCache.try_emplace (ident, buf, ts); 143 } 144 return std::make_shared<i2p::data::IdentityEx>(buf.data (), len); 145 } 146 147 void AddressBookFilesystemStorage::AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address) 148 { 149 if (!address) return; 150 size_t len = address->GetFullLen (); 151 std::vector<uint8_t> buf; 152 if (!len) return; // invalid address 153 { 154 std::lock_guard<std::mutex> l(m_FullAddressCacheMutex); 155 auto [it, inserted] = m_FullAddressCache.try_emplace (address->GetIdentHash(), len, i2p::util::GetMonotonicSeconds ()); 156 if (inserted) 157 address->ToBuffer (it->second.first.data (), len); 158 if (m_IsPersist) 159 buf = it->second.first; 160 } 161 if (m_IsPersist && !buf.empty ()) 162 { 163 std::string path = storage.Path(address->GetIdentHash().ToBase32()); 164 std::ofstream f (path, std::ofstream::binary | std::ofstream::out); 165 if (!f.is_open ()) 166 { 167 LogPrint (eLogError, "Addressbook: Can't open file ", path); 168 return; 169 } 170 f.write ((const char *)buf.data (), len); 171 } 172 } 173 174 void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident) 175 { 176 { 177 std::lock_guard<std::mutex> l(m_FullAddressCacheMutex); 178 m_FullAddressCache.erase (ident); 179 } 180 if (!m_IsPersist) return; 181 storage.Remove( ident.ToBase32() ); 182 } 183 184 int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, Addresses& addresses) 185 { 186 int num = 0; 187 std::ifstream f (filename, std::ifstream::in); // in text mode 188 if (!f) return -1; 189 190 addresses.clear (); 191 while (!f.eof ()) 192 { 193 std::string s; 194 getline(f, s); 195 if (!s.length()) continue; // skip empty line 196 197 std::size_t pos = s.find(','); 198 if (pos != std::string::npos) 199 { 200 std::string name = s.substr(0, pos++); 201 std::string addr = s.substr(pos); 202 203 addresses[name] = std::make_shared<Address>(addr); 204 num++; 205 } 206 } 207 return num; 208 } 209 210 int AddressBookFilesystemStorage::Load (Addresses& addresses) 211 { 212 int num = LoadFromFile (indexPath, addresses); 213 if (num < 0) 214 { 215 LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath); 216 return 0; 217 } 218 LogPrint(eLogInfo, "Addressbook: Using index file ", indexPath); 219 LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded from storage"); 220 221 return num; 222 } 223 224 int AddressBookFilesystemStorage::LoadLocal (Addresses& addresses) 225 { 226 int num = LoadFromFile (localPath, addresses); 227 if (num < 0) return 0; 228 LogPrint (eLogInfo, "Addressbook: ", num, " local addresses loaded"); 229 return num; 230 } 231 232 int AddressBookFilesystemStorage::Save (const Addresses& addresses) 233 { 234 if (addresses.empty()) 235 { 236 LogPrint(eLogWarning, "Addressbook: Not saving empty addressbook"); 237 return 0; 238 } 239 240 int num = 0; 241 { 242 // save index file 243 std::ofstream f (indexPath, std::ofstream::out); // in text mode 244 if (f.is_open ()) 245 { 246 for (const auto& it: addresses) 247 { 248 if (it.second->IsValid ()) 249 { 250 f << it.first << ","; 251 if (it.second->IsIdentHash ()) 252 f << it.second->identHash.ToBase32 (); 253 else 254 f << it.second->blindedPublicKey->ToB33 (); 255 f << std::endl; 256 num++; 257 } 258 else 259 LogPrint (eLogWarning, "Addressbook: Invalid address ", it.first); 260 } 261 LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); 262 } 263 else 264 LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath); 265 } 266 if (!m_HostsFile.empty ()) 267 { 268 // dump full hosts.txt 269 std::ofstream f (m_HostsFile, std::ofstream::out); // in text mode 270 if (f.is_open ()) 271 { 272 for (const auto& it: addresses) 273 { 274 std::shared_ptr<const i2p::data::IdentityEx> addr; 275 if (it.second->IsIdentHash ()) 276 { 277 addr = GetAddress (it.second->identHash); 278 if (addr) 279 f << it.first << "=" << addr->ToBase64 () << std::endl; 280 } 281 } 282 } 283 else 284 LogPrint (eLogWarning, "Addressbook: Can't open ", m_HostsFile); 285 } 286 287 return num; 288 } 289 290 void AddressBookFilesystemStorage::SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) 291 { 292 std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt"; 293 std::ofstream f (fname, std::ofstream::out | std::ofstream::trunc); 294 if (f) 295 { 296 f << etag << std::endl; 297 f<< lastModified << std::endl; 298 } 299 } 300 301 bool AddressBookFilesystemStorage::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) 302 { 303 std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt"; 304 std::ifstream f (fname, std::ofstream::in); 305 if (!f || f.eof ()) return false; 306 std::getline (f, etag); 307 if (f.eof ()) return false; 308 std::getline (f, lastModified); 309 return true; 310 } 311 312 void AddressBookFilesystemStorage::ResetEtags () 313 { 314 LogPrint (eLogError, "Addressbook: Resetting eTags"); 315 for (fs_lib::directory_iterator it (etagsPath); it != fs_lib::directory_iterator (); ++it) 316 { 317 if (!fs_lib::is_regular_file (it->status ())) 318 continue; 319 fs_lib::remove (it->path ()); 320 } 321 } 322 323 void AddressBookFilesystemStorage::CleanUpCache () 324 { 325 auto ts = i2p::util::GetMonotonicSeconds (); 326 std::lock_guard<std::mutex> l(m_FullAddressCacheMutex); 327 for (auto it = m_FullAddressCache.begin (); it != m_FullAddressCache.end ();) 328 { 329 if (ts > it->second.second + ADDRESS_CACHE_EXPIRATION_TIMEOUT) 330 it = m_FullAddressCache.erase (it); 331 else 332 it++; 333 } 334 } 335 336 //--------------------------------------------------------------------- 337 338 Address::Address (std::string_view b32): 339 addressType (eAddressInvalid) 340 { 341 if (b32.length () <= B33_ADDRESS_THRESHOLD) 342 { 343 if (identHash.FromBase32 (b32) > 0) 344 addressType = eAddressIndentHash; 345 } 346 else 347 { 348 blindedPublicKey = std::make_shared<i2p::data::BlindedPublicKey>(b32); 349 if (blindedPublicKey->IsValid ()) 350 addressType = eAddressBlindedPublicKey; 351 } 352 } 353 354 Address::Address (const i2p::data::IdentHash& hash) 355 { 356 addressType = eAddressIndentHash; 357 identHash = hash; 358 } 359 360 AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), 361 m_NumRetries (0), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr), 362 m_IsEnabled (true) 363 { 364 } 365 366 AddressBook::~AddressBook () 367 { 368 Stop (); 369 } 370 371 void AddressBook::Start () 372 { 373 i2p::config::GetOption("addressbook.enabled", m_IsEnabled); 374 if (m_IsEnabled) 375 { 376 if (!m_Storage) 377 m_Storage = new AddressBookFilesystemStorage; 378 m_Storage->Init(); 379 LoadHosts (); /* try storage, then hosts.txt, then download */ 380 StartSubscriptions (); 381 StartLookups (); 382 ScheduleCacheUpdate (); 383 } 384 } 385 386 void AddressBook::StartResolvers () 387 { 388 LoadLocal (); 389 } 390 391 void AddressBook::Stop () 392 { 393 StopLookups (); 394 StopSubscriptions (); 395 if (m_SubscriptionsUpdateTimer) 396 { 397 m_SubscriptionsUpdateTimer->cancel (); 398 m_SubscriptionsUpdateTimer = nullptr; 399 } 400 if (m_AddressCacheUpdateTimer) 401 { 402 m_AddressCacheUpdateTimer->cancel (); 403 m_AddressCacheUpdateTimer = nullptr; 404 } 405 bool isDownloading = m_Downloading.valid (); 406 if (isDownloading) 407 { 408 if (m_Downloading.wait_for(std::chrono::seconds(0)) == std::future_status::ready) 409 isDownloading = false; 410 else 411 { 412 LogPrint (eLogInfo, "Addressbook: Subscriptions are downloading, abort"); 413 for (int i = 0; i < 30; i++) 414 { 415 if (m_Downloading.wait_for(std::chrono::seconds(1)) == std::future_status::ready) // wait for 1 seconds 416 { 417 isDownloading = false; 418 LogPrint (eLogInfo, "Addressbook: Subscriptions download complete"); 419 break; 420 } 421 } 422 } 423 if (!isDownloading) 424 m_Downloading.get (); 425 else 426 LogPrint (eLogError, "Addressbook: Subscription download timeout"); 427 } 428 if (m_Storage) 429 { 430 m_Storage->Save (m_Addresses); 431 delete m_Storage; 432 m_Storage = nullptr; 433 } 434 m_DefaultSubscription = nullptr; 435 m_Subscriptions.clear (); 436 } 437 438 std::shared_ptr<const Address> AddressBook::GetAddress (std::string_view address) 439 { 440 auto pos = address.find(".b32.i2p"); 441 if (pos != std::string::npos) 442 { 443 auto addr = std::make_shared<const Address>(address.substr (0, pos)); 444 return addr->IsValid () ? addr : nullptr; 445 } 446 else 447 #if __cplusplus >= 202002L // C++20 448 if (address.ends_with (".i2p")) 449 #else 450 if (address.find (".i2p") != std::string::npos) 451 #endif 452 { 453 if (!m_IsEnabled) return nullptr; 454 auto addr = FindAddress (address); 455 if (!addr) 456 LookupAddress (address); // TODO: 457 return addr; 458 } 459 // if not .b32 we assume full base64 address 460 i2p::data::IdentityEx dest; 461 if (!dest.FromBase64 (address)) 462 return nullptr; 463 return std::make_shared<const Address>(dest.GetIdentHash ()); 464 } 465 466 std::shared_ptr<const Address> AddressBook::FindAddress (std::string_view address) 467 { 468 auto it = m_Addresses.find (address); 469 if (it != m_Addresses.end ()) 470 return it->second; 471 return nullptr; 472 } 473 474 bool AddressBook::RecordExists (const std::string& address, const std::string& jump) 475 { 476 auto addr = FindAddress(address); 477 if (!addr) 478 return false; 479 480 auto pos = jump.find(".b32.i2p"); 481 if (pos != std::string::npos) 482 { 483 i2p::data::IdentHash identHash; 484 if (identHash.FromBase32(jump.substr (0, pos)) && identHash == addr->identHash) 485 return true; 486 } 487 else 488 { 489 i2p::data::IdentityEx ident; 490 if (ident.FromBase64 (jump) && ident.GetIdentHash () == addr->identHash) 491 return true; 492 } 493 494 return false; 495 } 496 497 void AddressBook::InsertAddress (const std::string& address, const std::string& jump) 498 { 499 auto pos = jump.find(".b32.i2p"); 500 if (pos != std::string::npos) 501 { 502 m_Addresses[address] = std::make_shared<Address>(jump.substr (0, pos)); 503 LogPrint (eLogInfo, "Addressbook: Added ", address," -> ", jump); 504 } 505 else 506 { 507 // assume base64 508 auto ident = std::make_shared<i2p::data::IdentityEx>(); 509 if (ident->FromBase64 (jump)) 510 { 511 if (m_Storage) m_Storage->AddAddress (ident); 512 m_Addresses[address] = std::make_shared<Address>(ident->GetIdentHash ()); 513 LogPrint (eLogInfo, "Addressbook: Added ", address," -> ", ToAddress(ident->GetIdentHash ())); 514 } 515 else 516 LogPrint (eLogError, "Addressbook: Malformed address ", jump); 517 } 518 } 519 520 void AddressBook::InsertFullAddress (std::shared_ptr<const i2p::data::IdentityEx> address) 521 { 522 if (m_Storage) m_Storage->AddAddress (address); 523 } 524 525 std::shared_ptr<const i2p::data::IdentityEx> AddressBook::GetFullAddress (const std::string& address) 526 { 527 auto addr = GetAddress (address); 528 if (!addr || !addr->IsIdentHash ()) return nullptr; 529 return m_Storage ? m_Storage->GetAddress (addr->identHash) : nullptr; 530 } 531 532 void AddressBook::LoadHosts () 533 { 534 if (!m_Storage) return; 535 if (m_Storage->Load (m_Addresses) > 0) 536 { 537 m_IsLoaded = true; 538 return; 539 } 540 541 // then try hosts.txt 542 std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode 543 if (f.is_open ()) 544 { 545 LoadHostsFromStream (f, false); 546 m_IsLoaded = true; 547 } 548 549 // reset eTags, because we don’t know how old hosts.txt is or can't load addressbook 550 m_Storage->ResetEtags (); 551 } 552 553 bool AddressBook::LoadHostsFromStream (std::istream& f, bool is_update) 554 { 555 std::unique_lock<std::mutex> l(m_AddressBookMutex); 556 int numAddresses = 0; 557 bool incomplete = false; 558 std::string s; 559 while (!f.eof ()) 560 { 561 getline(f, s); 562 563 if (!s.length() || s[0] == '#') 564 continue; // skip empty or comment line 565 566 size_t pos = s.find('='); 567 568 if (pos != std::string::npos) 569 { 570 std::string_view name = std::string_view(s).substr(0, pos++); 571 std::string_view addr = std::string_view(s).substr(pos); 572 573 size_t pos = addr.find('#'); 574 if (pos != addr.npos) 575 addr = addr.substr(0, pos); // remove comments 576 #if __cplusplus >= 202002L // C++20 577 if (name.ends_with (".b32.i2p")) 578 #else 579 if (name.find(".b32.i2p") != name.npos) 580 #endif 581 { 582 LogPrint (eLogError, "Addressbook: Skipped adding of b32 address: ", name); 583 continue; 584 } 585 586 #if __cplusplus >= 202002L // C++20 587 if (!name.ends_with (".i2p")) 588 #else 589 if (name.find(".i2p") == name.npos) 590 #endif 591 { 592 LogPrint (eLogError, "Addressbook: Malformed domain: ", name); 593 continue; 594 } 595 596 auto ident = std::make_shared<i2p::data::IdentityEx> (); 597 if (!ident->FromBase64(addr)) 598 { 599 LogPrint (eLogError, "Addressbook: Malformed address ", addr, " for ", name); 600 incomplete = f.eof (); 601 continue; 602 } 603 numAddresses++; 604 auto it = m_Addresses.find (name); 605 if (it != m_Addresses.end ()) // already exists ? 606 { 607 if (it->second->IsIdentHash () && it->second->identHash != ident->GetIdentHash () && // address changed? 608 ident->GetSigningKeyType () != i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) // don't replace by DSA 609 { 610 it->second->identHash = ident->GetIdentHash (); 611 if (m_Storage) 612 { 613 m_Storage->AddAddress (ident); 614 m_Storage->RemoveAddress (it->second->identHash); 615 } 616 LogPrint (eLogInfo, "Addressbook: Updated host: ", name); 617 } 618 } 619 else 620 { 621 m_Addresses.emplace (name, std::make_shared<Address>(ident->GetIdentHash ())); 622 if (m_Storage) m_Storage->AddAddress (ident); 623 if (is_update) 624 LogPrint (eLogInfo, "Addressbook: Added new host: ", name); 625 } 626 } 627 else 628 incomplete = f.eof (); 629 } 630 LogPrint (eLogInfo, "Addressbook: ", numAddresses, " addresses processed"); 631 if (numAddresses > 0) 632 { 633 if (!incomplete) m_IsLoaded = true; 634 if (m_Storage) m_Storage->Save (m_Addresses); 635 } 636 return !incomplete; 637 } 638 639 void AddressBook::LoadSubscriptions () 640 { 641 if (!m_Subscriptions.size ()) 642 { 643 std::ifstream f (i2p::fs::DataDirPath ("subscriptions.txt"), std::ifstream::in); // in text mode 644 if (f.is_open ()) 645 { 646 std::string s; 647 while (!f.eof ()) 648 { 649 getline(f, s); 650 if (s.empty () || s[0] == '#') continue; // skip empty line or comment 651 m_Subscriptions.push_back (std::make_shared<AddressBookSubscription> (*this, s)); 652 } 653 LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); 654 LogPrint (eLogWarning, "Addressbook: subscriptions.txt usage is deprecated, use config file instead"); 655 } 656 else 657 { 658 LogPrint (eLogInfo, "Addressbook: Loading subscriptions from config"); 659 // using config file items 660 std::string subscriptionURLs; i2p::config::GetOption("addressbook.subscriptions", subscriptionURLs); 661 std::vector<std::string> subsList; 662 boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on); 663 664 for (const auto& s: subsList) 665 if (!s.empty ()) 666 m_Subscriptions.push_back (std::make_shared<AddressBookSubscription> (*this, s)); 667 LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); 668 } 669 } 670 else 671 LogPrint (eLogError, "Addressbook: Subscriptions already loaded"); 672 } 673 674 void AddressBook::LoadLocal () 675 { 676 if (!m_Storage) return; 677 AddressBookStorage::Addresses localAddresses; 678 m_Storage->LoadLocal (localAddresses); 679 for (const auto& it: localAddresses) 680 { 681 if (!it.second->IsIdentHash ()) continue; // skip blinded for now 682 auto dot = it.first.find ('.'); 683 if (dot != std::string::npos) 684 { 685 auto domain = it.first.substr (dot + 1); 686 auto it1 = m_Addresses.find (domain); // find domain in our addressbook 687 if (it1 != m_Addresses.end () && it1->second->IsIdentHash ()) 688 { 689 auto dest = context.FindLocalDestination (it1->second->identHash); 690 if (dest) 691 { 692 // address is ours 693 std::shared_ptr<AddressResolver> resolver; 694 auto it2 = m_Resolvers.find (it1->second->identHash); 695 if (it2 != m_Resolvers.end ()) 696 resolver = it2->second; // resolver exists 697 else 698 { 699 // create new resolver 700 resolver = std::make_shared<AddressResolver>(dest); 701 m_Resolvers.insert (std::make_pair(it1->second->identHash, resolver)); 702 } 703 resolver->AddAddress (it.first, it.second->identHash); 704 } 705 } 706 } 707 } 708 } 709 710 bool AddressBook::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) 711 { 712 if (m_Storage) 713 return m_Storage->GetEtag (subscription, etag, lastModified); 714 else 715 return false; 716 } 717 718 void AddressBook::DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) 719 { 720 m_NumRetries++; 721 int nextUpdateTimeout = m_NumRetries*CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT; 722 if (m_NumRetries > CONTINIOUS_SUBSCRIPTION_MAX_NUM_RETRIES || nextUpdateTimeout > CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT) 723 nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT; 724 if (success) 725 { 726 m_NumRetries = 0; 727 if (m_DefaultSubscription) m_DefaultSubscription = nullptr; 728 if (m_IsLoaded) 729 nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT; 730 else 731 m_IsLoaded = true; 732 if (m_Storage) m_Storage->SaveEtag (subscription, etag, lastModified); 733 } 734 if (m_SubscriptionsUpdateTimer) 735 { 736 m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(nextUpdateTimeout)); 737 m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, 738 this, std::placeholders::_1)); 739 } 740 } 741 742 void AddressBook::StartSubscriptions () 743 { 744 LoadSubscriptions (); 745 if (m_IsLoaded && m_Subscriptions.empty ()) return; 746 747 auto dest = i2p::client::context.GetSharedLocalDestination (); 748 if (dest) 749 { 750 m_SubscriptionsUpdateTimer = std::make_unique<boost::asio::deadline_timer>(dest->GetService ()); 751 m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT)); 752 m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, 753 this, std::placeholders::_1)); 754 } 755 else 756 LogPrint (eLogCritical, "Addressbook: Can't start subscriptions: missing shared local destination"); 757 } 758 759 void AddressBook::StopSubscriptions () 760 { 761 if (m_SubscriptionsUpdateTimer) 762 m_SubscriptionsUpdateTimer->cancel (); 763 } 764 765 void AddressBook::HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode) 766 { 767 if (ecode != boost::asio::error::operation_aborted) 768 { 769 auto dest = i2p::client::context.GetSharedLocalDestination (); 770 if (!dest) { 771 LogPrint(eLogWarning, "Addressbook: Missing local destination, skip subscription update"); 772 return; 773 } 774 bool isDownloading = m_Downloading.valid (); 775 if (isDownloading && m_Downloading.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active? 776 { 777 m_Downloading.get (); 778 isDownloading = false; 779 } 780 if (!isDownloading && dest->IsReady ()) 781 { 782 if (!m_IsLoaded) 783 { 784 // download it from default subscription 785 LogPrint (eLogInfo, "Addressbook: Trying to download it from default subscription."); 786 std::string defaultSubURL; i2p::config::GetOption("addressbook.defaulturl", defaultSubURL); 787 if (!m_DefaultSubscription) 788 m_DefaultSubscription = std::make_shared<AddressBookSubscription>(*this, defaultSubURL); 789 m_Downloading = std::async (std::launch::async, 790 std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); 791 } 792 else if (!m_Subscriptions.empty ()) 793 { 794 // pick random subscription 795 auto ind = rand () % m_Subscriptions.size(); 796 m_Downloading = std::async (std::launch::async, 797 std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind])); 798 } 799 } 800 else 801 { 802 // try it again later 803 m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_RETRY_TIMEOUT)); 804 m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, 805 this, std::placeholders::_1)); 806 } 807 } 808 } 809 810 void AddressBook::StartLookups () 811 { 812 auto dest = i2p::client::context.GetSharedLocalDestination (); 813 if (dest) 814 { 815 auto datagram = dest->GetDatagramDestination (); 816 if (!datagram) 817 datagram = dest->CreateDatagramDestination (); 818 datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this, 819 std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), 820 ADDRESS_RESPONSE_DATAGRAM_PORT); 821 } 822 } 823 824 void AddressBook::StopLookups () 825 { 826 auto dest = i2p::client::context.GetSharedLocalDestination (); 827 if (dest) 828 { 829 auto datagram = dest->GetDatagramDestination (); 830 if (datagram) datagram->ResetReceiver (ADDRESS_RESPONSE_DATAGRAM_PORT); 831 } 832 } 833 834 void AddressBook::LookupAddress (std::string_view address) 835 { 836 std::shared_ptr<const Address> addr; 837 auto dot = address.find ('.'); 838 if (dot != std::string::npos) 839 addr = FindAddress (address.substr (dot + 1)); 840 if (!addr || !addr->IsIdentHash ()) // TODO: 841 { 842 LogPrint (eLogError, "Addressbook: Can't find domain for ", address); 843 return; 844 } 845 846 auto dest = i2p::client::context.GetSharedLocalDestination (); 847 if (dest) 848 { 849 auto datagram = dest->GetDatagramDestination (); 850 if (datagram) 851 { 852 uint32_t nonce; 853 RAND_bytes ((uint8_t *)&nonce, 4); 854 { 855 std::unique_lock<std::mutex> l(m_LookupsMutex); 856 m_Lookups[nonce] = address; 857 } 858 LogPrint (eLogDebug, "Addressbook: Lookup of ", address, " to ", addr->identHash.ToBase32 (), " nonce=", nonce); 859 size_t len = address.length () + 9; 860 uint8_t * buf = new uint8_t[len]; 861 memset (buf, 0, 4); 862 htobe32buf (buf + 4, nonce); 863 buf[8] = address.length (); 864 memcpy (buf + 9, address.data (), address.length ()); 865 datagram->SendDatagramTo (buf, len, addr->identHash, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT); 866 delete[] buf; 867 } 868 } 869 } 870 871 void AddressBook::HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) 872 { 873 if (len < 44) 874 { 875 LogPrint (eLogError, "Addressbook: Lookup response is too short ", len); 876 return; 877 } 878 uint32_t nonce = bufbe32toh (buf + 4); 879 LogPrint (eLogDebug, "Addressbook: Lookup response received from ", from.GetIdentHash ().ToBase32 (), " nonce=", nonce); 880 std::string address; 881 { 882 std::unique_lock<std::mutex> l(m_LookupsMutex); 883 auto it = m_Lookups.find (nonce); 884 if (it != m_Lookups.end ()) 885 { 886 address = it->second; 887 m_Lookups.erase (it); 888 } 889 } 890 if (address.length () > 0) 891 { 892 // TODO: verify from 893 i2p::data::IdentHash hash(buf + 8); 894 if (!hash.IsZero ()) 895 m_Addresses[address] = std::make_shared<Address>(hash); 896 else 897 LogPrint (eLogInfo, "AddressBook: Lookup response: ", address, " not found"); 898 } 899 } 900 901 void AddressBook::ScheduleCacheUpdate () 902 { 903 if (!m_AddressCacheUpdateTimer) 904 { 905 auto dest = i2p::client::context.GetSharedLocalDestination (); 906 if(dest) 907 m_AddressCacheUpdateTimer = std::make_unique<boost::asio::deadline_timer>(dest->GetService ()); 908 } 909 if (m_AddressCacheUpdateTimer) 910 { 911 m_AddressCacheUpdateTimer->expires_from_now (boost::posix_time::seconds(ADDRESS_CACHE_UPDATE_INTERVAL )); 912 m_AddressCacheUpdateTimer->async_wait ( 913 [this](const boost::system::error_code& ecode) 914 { 915 if (ecode != boost::asio::error::operation_aborted) 916 { 917 if (m_Storage) m_Storage->CleanUpCache (); 918 ScheduleCacheUpdate (); 919 } 920 }); 921 } 922 } 923 924 AddressBookSubscription::AddressBookSubscription (AddressBook& book, std::string_view link): 925 m_Book (book), m_Link (link) 926 { 927 } 928 929 void AddressBookSubscription::CheckUpdates () 930 { 931 i2p::util::SetThreadName("Addressbook"); 932 933 bool result = MakeRequest (); 934 m_Book.DownloadComplete (result, m_Ident, m_Etag, m_LastModified); 935 } 936 937 bool AddressBookSubscription::MakeRequest () 938 { 939 i2p::http::URL url; 940 // must be run in separate thread 941 LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link); 942 if (!url.parse(m_Link)) 943 { 944 LogPrint(eLogError, "Addressbook: Failed to parse url: ", m_Link); 945 return false; 946 } 947 auto addr = m_Book.GetAddress (url.host); 948 if (!addr || !addr->IsIdentHash ()) 949 { 950 LogPrint (eLogError, "Addressbook: Can't resolve ", url.host); 951 return false; 952 } 953 else 954 m_Ident = addr->identHash; 955 // save url parts for later use 956 std::string dest_host = url.host; 957 int dest_port = url.port ? url.port : 80; 958 // try to create stream to addressbook site 959 auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (m_Ident, dest_port); 960 if (!stream) 961 { 962 LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found"); 963 return false; 964 } 965 if (m_Etag.empty() && m_LastModified.empty()) 966 { 967 m_Book.GetEtag (m_Ident, m_Etag, m_LastModified); 968 LogPrint (eLogDebug, "Addressbook: Loaded for ", url.host, ": ETag: ", m_Etag, ", Last-Modified: ", m_LastModified); 969 } 970 // create http request & send it 971 i2p::http::HTTPReq req; 972 req.AddHeader("Host", dest_host); 973 req.AddHeader("User-Agent", "Wget/1.11.4"); 974 req.AddHeader("Accept-Encoding", "gzip"); 975 req.AddHeader("X-Accept-Encoding", "x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0"); 976 req.AddHeader("Connection", "close"); 977 if (!m_Etag.empty()) 978 req.AddHeader("If-None-Match", m_Etag); 979 if (!m_LastModified.empty()) 980 req.AddHeader("If-Modified-Since", m_LastModified); 981 // convert url to relative 982 url.schema = ""; 983 url.host = ""; 984 req.uri = url.to_string(); 985 req.version = "HTTP/1.1"; 986 std::string request = req.to_string(); 987 stream->Send ((const uint8_t *) request.data(), request.length()); 988 // read response 989 std::string response; 990 uint8_t recv_buf[4096]; 991 bool end = false; 992 int numAttempts = 0; 993 while (!end) 994 { 995 size_t received = stream->Receive (recv_buf, 4096, SUBSCRIPTION_REQUEST_TIMEOUT); 996 if (received) 997 { 998 response.append ((char *)recv_buf, received); 999 if (!stream->IsOpen ()) end = true; 1000 } 1001 else if (!stream->IsOpen ()) 1002 end = true; 1003 else 1004 { 1005 LogPrint (eLogError, "Addressbook: Subscriptions request timeout expired"); 1006 numAttempts++; 1007 if (numAttempts > 5) end = true; 1008 } 1009 } 1010 // process remaining buffer 1011 while (size_t len = stream->ReadSome (recv_buf, sizeof(recv_buf))) 1012 response.append ((char *)recv_buf, len); 1013 // parse response 1014 i2p::http::HTTPRes res; 1015 int res_head_len = res.parse(response); 1016 if (res_head_len < 0) 1017 { 1018 LogPrint(eLogError, "Addressbook: Can't parse http response from ", dest_host); 1019 return false; 1020 } 1021 if (res_head_len == 0) 1022 { 1023 LogPrint(eLogError, "Addressbook: Incomplete http response from ", dest_host, ", interrupted by timeout"); 1024 return false; 1025 } 1026 // assert: res_head_len > 0 1027 response.erase(0, res_head_len); 1028 if (res.code == 304) 1029 { 1030 LogPrint (eLogInfo, "Addressbook: No updates from ", dest_host, ", code 304"); 1031 return false; 1032 } 1033 if (res.code != 200) 1034 { 1035 LogPrint (eLogWarning, "Adressbook: Can't get updates from ", dest_host, ", response code ", res.code); 1036 return false; 1037 } 1038 int len = res.content_length(); 1039 if (response.empty()) 1040 { 1041 LogPrint(eLogError, "Addressbook: Empty response from ", dest_host, ", expected ", len, " bytes"); 1042 return false; 1043 } 1044 if (!res.is_gzipped () && len > 0 && len != (int) response.length()) 1045 { 1046 LogPrint(eLogError, "Addressbook: Response size mismatch, expected: ", len, ", got: ", response.length(), "bytes"); 1047 return false; 1048 } 1049 // assert: res.code == 200 1050 auto it = res.headers.find("ETag"); 1051 if (it != res.headers.end()) m_Etag = it->second; 1052 it = res.headers.find("Last-Modified"); 1053 if (it != res.headers.end()) m_LastModified = it->second; 1054 if (res.is_chunked()) 1055 { 1056 std::stringstream in(response), out; 1057 i2p::http::MergeChunkedResponse (in, out); 1058 response = out.str(); 1059 } 1060 if (res.is_gzipped()) 1061 { 1062 std::stringstream out; 1063 i2p::data::GzipInflator inflator; 1064 inflator.Inflate ((const uint8_t *) response.data(), response.length(), out); 1065 if (out.fail()) 1066 { 1067 LogPrint(eLogError, "Addressbook: Can't gunzip http response"); 1068 return false; 1069 } 1070 response = out.str(); 1071 } 1072 std::stringstream ss(response); 1073 LogPrint (eLogInfo, "Addressbook: Got update from ", dest_host); 1074 m_Book.LoadHostsFromStream (ss, true); 1075 return true; 1076 } 1077 1078 AddressResolver::AddressResolver (std::shared_ptr<ClientDestination> destination): 1079 m_LocalDestination (destination) 1080 { 1081 if (m_LocalDestination) 1082 { 1083 auto datagram = m_LocalDestination->GetDatagramDestination (); 1084 if (!datagram) 1085 datagram = m_LocalDestination->CreateDatagramDestination (); 1086 datagram->SetReceiver (std::bind (&AddressResolver::HandleRequest, this, 1087 std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), 1088 ADDRESS_RESOLVER_DATAGRAM_PORT); 1089 } 1090 } 1091 1092 AddressResolver::~AddressResolver () 1093 { 1094 if (m_LocalDestination) 1095 { 1096 auto datagram = m_LocalDestination->GetDatagramDestination (); 1097 if (datagram) 1098 datagram->ResetReceiver (ADDRESS_RESOLVER_DATAGRAM_PORT); 1099 } 1100 } 1101 1102 void AddressResolver::HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) 1103 { 1104 if (len < 9 || len < buf[8] + 9U) 1105 { 1106 LogPrint (eLogError, "Addressbook: Address request is too short ", len); 1107 return; 1108 } 1109 // read requested address 1110 uint8_t l = buf[8]; 1111 char address[255]; 1112 memcpy (address, buf + 9, l); 1113 address[l] = 0; 1114 LogPrint (eLogDebug, "Addressbook: Address request ", address); 1115 // send response 1116 uint8_t response[44]; 1117 memset (response, 0, 4); // reserved 1118 memcpy (response + 4, buf + 4, 4); // nonce 1119 auto it = m_LocalAddresses.find (address); // address lookup 1120 if (it != m_LocalAddresses.end ()) 1121 memcpy (response + 8, it->second, 32); // ident 1122 else 1123 memset (response + 8, 0, 32); // not found 1124 memset (response + 40, 0, 4); // set expiration time to zero 1125 m_LocalDestination->GetDatagramDestination ()->SendDatagramTo (response, 44, from.GetIdentHash(), toPort, fromPort); 1126 } 1127 1128 void AddressResolver::AddAddress (const std::string& name, const i2p::data::IdentHash& ident) 1129 { 1130 m_LocalAddresses[name] = ident; 1131 } 1132 1133 } 1134 }