diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index c979e43f..83b68d71 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -29,3 +29,7 @@ jobs: run: | mkdir -p obj/Win32 obj/libi2pd obj/libi2pd_client obj/daemon make USE_UPNP=yes DEBUG=no -j3 + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + path: i2pd.exe diff --git a/Makefile b/Makefile index 3e0b23cf..7357f220 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,7 @@ mk_obj_dir: @mkdir -p obj/$(DAEMON_SRC_DIR) api: mk_obj_dir $(SHLIB) $(ARLIB) +client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time @@ -128,6 +129,7 @@ doxygen: .PHONY: last-dist .PHONY: api .PHONY: api_client +.PHONY: client .PHONY: mk_obj_dir .PHONY: install .PHONY: strip diff --git a/contrib/i2pd.service b/contrib/i2pd.service index 8ce851b0..45fe4cc6 100644 --- a/contrib/i2pd.service +++ b/contrib/i2pd.service @@ -17,7 +17,12 @@ PIDFile=/run/i2pd/i2pd.pid ### Uncomment, if auto restart needed #Restart=on-failure -KillSignal=SIGQUIT +# Use SIGTERM to stop i2pd immediately. +# Some cleanup processes can delay stopping, so we set 30 seconds timeout and then SIGKILL i2pd. +KillSignal=SIGTERM +TimeoutStopSec=30s +SendSIGKILL=yes + # If you have the patience waiting 10 min on restarting/stopping it, uncomment this. # i2pd stops accepting new tunnels and waits ~10 min while old ones do not die. #KillSignal=SIGINT diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index aba30fd7..89960e0c 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -263,6 +263,9 @@ namespace http { case eRouterErrorOffline: s << " - Offline"; break; + case eRouterErrorSymmetricNAT: + s << " - Symmetric NAT"; + break; default: ; } break; @@ -706,23 +709,23 @@ namespace http { std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; for (const auto& it: sessions ) { - if (it.second && it.second->IsEstablished () && !it.second->GetSocket ().remote_endpoint ().address ().is_v6 ()) + if (it.second && it.second->IsEstablished () && !it.second->GetRemoteEndpoint ().address ().is_v6 ()) { tmp_s << "
\r\n"; if (it.second->IsOutgoing ()) tmp_s << " ⇒ "; tmp_s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " - << it.second->GetSocket ().remote_endpoint().address ().to_string (); + << it.second->GetRemoteEndpoint ().address ().to_string (); if (!it.second->IsOutgoing ()) tmp_s << " ⇒ "; tmp_s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; tmp_s << "
\r\n" << std::endl; cnt++; } - if (it.second && it.second->IsEstablished () && it.second->GetSocket ().remote_endpoint ().address ().is_v6 ()) + if (it.second && it.second->IsEstablished () && it.second->GetRemoteEndpoint ().address ().is_v6 ()) { tmp_s6 << "
\r\n"; if (it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; tmp_s6 << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " - << "[" << it.second->GetSocket ().remote_endpoint().address ().to_string () << "]"; + << "[" << it.second->GetRemoteEndpoint ().address ().to_string () << "]"; if (!it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; tmp_s6 << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; tmp_s6 << "
\r\n" << std::endl; diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index 6ea33c46..25e24bb5 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -81,10 +81,10 @@ namespace transport void UPnP::Discover () { bool isError; - int err; + int err; #if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) - err = UPNPDISCOVER_SUCCESS; + err = UPNPDISCOVER_SUCCESS; #if (MINIUPNPC_API_VERSION >= 14) m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0, 0, 2, &err); @@ -94,8 +94,8 @@ namespace transport isError = err != UPNPDISCOVER_SUCCESS; #else // MINIUPNPC_API_VERSION >= 8 - err = 0; - m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0); + err = 0; + m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0); isError = m_Devlist == NULL; #endif // MINIUPNPC_API_VERSION >= 8 { @@ -106,15 +106,15 @@ namespace transport if (isError) { - LogPrint (eLogError, "UPnP: unable to discover Internet Gateway Devices: error ", err); + LogPrint (eLogError, "UPnP: unable to discover Internet Gateway Devices: error ", err); return; } err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); - m_upnpUrlsInitialized=err!=0; + m_upnpUrlsInitialized=err!=0; if (err == UPNP_IGD_VALID_CONNECTED) { - err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); + err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); if(err != UPNPCOMMAND_SUCCESS) { LogPrint (eLogError, "UPnP: unable to get external address: error ", err); @@ -125,14 +125,14 @@ namespace transport LogPrint (eLogError, "UPnP: found Internet Gateway Device ", m_upnpUrls.controlURL); if (!m_externalIPAddress[0]) { - LogPrint (eLogError, "UPnP: found Internet Gateway Device doesn't know our external address"); + LogPrint (eLogError, "UPnP: found Internet Gateway Device doesn't know our external address"); return; } } } else { - LogPrint (eLogError, "UPnP: unable to find valid Internet Gateway Device: error ", err); + LogPrint (eLogError, "UPnP: unable to find valid Internet Gateway Device: error ", err); return; } @@ -183,7 +183,7 @@ namespace transport err = CheckMapping (strPort.c_str (), strType.c_str ()); if (err != UPNPCOMMAND_SUCCESS) // if mapping not found { - LogPrint (eLogDebug, "UPnP: possibly port ", strPort, " is not forwarded: return code ", err); + LogPrint (eLogDebug, "UPnP: possibly port ", strPort, " is not forwarded: return code ", err); #if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) err = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), NULL, NULL); @@ -203,7 +203,7 @@ namespace transport } else { - LogPrint (eLogDebug, "UPnP: external forward from ", m_NetworkAddr, ":", strPort, " exists on current Internet Gateway Device"); + LogPrint (eLogDebug, "UPnP: external forward from ", m_NetworkAddr, ":", strPort, " exists on current Internet Gateway Device"); return; } } @@ -220,14 +220,14 @@ namespace transport void UPnP::CloseMapping (std::shared_ptr address) { - if(!m_upnpUrlsInitialized) { - return; - } + if(!m_upnpUrlsInitialized) { + return; + } std::string strType (GetProto (address)), strPort (std::to_string (address->port)); int err = UPNPCOMMAND_SUCCESS; err = CheckMapping (strPort.c_str (), strType.c_str ()); - if (err == UPNPCOMMAND_SUCCESS) + if (err == UPNPCOMMAND_SUCCESS) { err = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), NULL); LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", err); @@ -238,11 +238,11 @@ namespace transport { freeUPNPDevlist (m_Devlist); m_Devlist = 0; - if(m_upnpUrlsInitialized){ - FreeUPNPUrls (&m_upnpUrls); - m_upnpUrlsInitialized=false; - } - } + if(m_upnpUrlsInitialized){ + FreeUPNPUrls (&m_upnpUrls); + m_upnpUrlsInitialized=false; + } + } std::string UPnP::GetProto (std::shared_ptr address) { diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 9da0bbe0..cc79c87c 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -50,7 +50,9 @@ namespace config { ("nat", bool_switch()->default_value(true), "Should we assume we are behind NAT? (default: enabled)") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv4", bool_switch()->default_value(true), "Enable communication through ipv4 (default: enabled)") + ("address4", value()->default_value(""), "Local address to bind ipv4 transport sockets to") ("ipv6", bool_switch()->default_value(false), "Enable communication through ipv6 (default: disabled)") + ("address6", value()->default_value(""), "Local address to bind ipv6 transport sockets to") ("reservedrange", bool_switch()->default_value(true), "Check remote RI for being in blacklist of reserved IP ranges (default: enabled)") ("netid", value()->default_value(I2PD_NET_ID), "Specify NetID. Main I2P is 2") ("daemon", bool_switch()->default_value(false), "Router will go to background after start (default: disabled)") @@ -247,7 +249,7 @@ namespace config { ("ntcp2.enabled", value()->default_value(true), "Enable NTCP2 (default: enabled)") ("ntcp2.published", value()->default_value(true), "Publish NTCP2 (default: enabled)") ("ntcp2.port", value()->default_value(0), "Port to listen for incoming NTCP2 connections (default: auto)") - ("ntcp2.addressv6", value()->default_value("::"), "Address to bind NTCP2 on") + ("ntcp2.addressv6", value()->default_value("::"), "Address to publish NTCP2 with") ("ntcp2.proxy", value()->default_value(""), "Proxy URL for NTCP2 transport") ; diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 68850a9d..14ef83ae 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -353,7 +353,7 @@ namespace crypto bool X25519Keys::Agree (const uint8_t * pub, uint8_t * shared) { - if (pub[31] & 0x80) return false; // not x25519 key + if (!pub || (pub[31] & 0x80)) return false; // not x25519 key #if OPENSSL_X25519 EVP_PKEY_derive_init (m_Ctx); auto pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_X25519, NULL, pub, 32); diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index dc485d5b..96a52b15 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -333,15 +333,14 @@ namespace transport if (in_RemoteRouter) // Alice { m_Establisher->m_RemoteIdentHash = GetRemoteIdentity ()->GetIdentHash (); - if (!addr) - addr = in_RemoteRouter->GetNTCP2Address (true); // we need a published address if (addr) { memcpy (m_Establisher->m_RemoteStaticKey, addr->ntcp2->staticKey, 32); memcpy (m_Establisher->m_IV, addr->ntcp2->iv, 16); + m_RemoteEndpoint = boost::asio::ip::tcp::endpoint (addr->host, addr->port); } else - LogPrint (eLogWarning, "NTCP2: Missing NTCP2 parameters"); + LogPrint (eLogWarning, "NTCP2: Missing NTCP2 address"); } m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL + rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; @@ -657,19 +656,13 @@ namespace transport SendTerminationAndTerminate (eNTCP2Message3Error); return; } - auto addr = ri.GetNTCP2Address (false); // any NTCP2 address + auto addr = ri.GetNTCP2AddressWithStaticKey (m_Establisher->m_RemoteStaticKey); if (!addr) { - LogPrint (eLogError, "NTCP2: No NTCP2 address found in SessionConfirmed"); + LogPrint (eLogError, "NTCP2: No NTCP2 address wth static key found in SessionConfirmed"); Terminate (); return; } - if (memcmp (addr->ntcp2->staticKey, m_Establisher->m_RemoteStaticKey, 32)) - { - LogPrint (eLogError, "NTCP2: Static key mismatch in SessionConfirmed"); - SendTerminationAndTerminate (eNTCP2IncorrectSParameter); - return; - } i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, buf.data () + 3, size)); // TODO: should insert ri and not parse it twice // TODO: process options @@ -1177,7 +1170,9 @@ namespace transport { try { - m_NTCP2Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port))); + auto ep = m_Address4 ? boost::asio::ip::tcp::endpoint (m_Address4->address(), address->port): + boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), address->port); + m_NTCP2Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), ep)); } catch ( std::exception & ex ) { @@ -1274,10 +1269,15 @@ namespace transport return nullptr; } - void NTCP2Server::Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn) + void NTCP2Server::Connect(std::shared_ptr conn) { - LogPrint (eLogDebug, "NTCP2: Connecting to ", address ,":", port); - GetService ().post([this, address, port, conn]() + if (!conn || conn->GetRemoteEndpoint ().address ().is_unspecified ()) + { + LogPrint (eLogError, "NTCP2: Can't connect to unspecified address"); + return; + } + LogPrint (eLogDebug, "NTCP2: Connecting to ", conn->GetRemoteEndpoint ()); + GetService ().post([this, conn]() { if (this->AddNTCP2Session (conn)) { @@ -1290,12 +1290,32 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogInfo, "NTCP2: Not connected in ", timeout, " seconds"); - if (conn->GetRemoteIdentity ()) - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); conn->Terminate (); } }); - conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port), std::bind (&NTCP2Server::HandleConnect, this, std::placeholders::_1, conn, timer)); + // bind to local address + std::shared_ptr localAddress; + if (conn->GetRemoteEndpoint ().address ().is_v6 ()) + { + if (i2p::util::net::IsYggdrasilAddress (conn->GetRemoteEndpoint ().address ())) + localAddress = m_YggdrasilAddress; + else + localAddress = m_Address6; + conn->GetSocket ().open (boost::asio::ip::tcp::v6 ()); + } + else + { + localAddress = m_Address4; + conn->GetSocket ().open (boost::asio::ip::tcp::v4 ()); + } + if (localAddress) + { + boost::system::error_code ec; + conn->GetSocket ().bind (*localAddress, ec); + if (ec) + LogPrint (eLogError, "NTCP2: can't bind to ", localAddress->address ().to_string (), ": ", ec.message ()); + } + conn->GetSocket ().async_connect (conn->GetRemoteEndpoint (), std::bind (&NTCP2Server::HandleConnect, this, std::placeholders::_1, conn, timer)); } else conn->Terminate (); @@ -1312,7 +1332,7 @@ namespace transport } else { - LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetSocket ().remote_endpoint ()); + LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetRemoteEndpoint ()); conn->ClientLogin (); } } @@ -1328,6 +1348,7 @@ namespace transport LogPrint (eLogDebug, "NTCP2: Connected from ", ep); if (conn) { + conn->SetRemoteEndpoint (ep); conn->ServerLogin (); m_PendingIncomingSessions.push_back (conn); conn = nullptr; @@ -1361,6 +1382,7 @@ namespace transport LogPrint (eLogDebug, "NTCP2: Connected from ", ep); if (conn) { + conn->SetRemoteEndpoint (ep); conn->ServerLogin (); m_PendingIncomingSessions.push_back (conn); } @@ -1415,13 +1437,18 @@ namespace transport } } - void NTCP2Server::ConnectWithProxy (const std::string& host, uint16_t port, RemoteAddressType addrtype, std::shared_ptr conn) + void NTCP2Server::ConnectWithProxy (std::shared_ptr conn) { if(!m_ProxyEndpoint) return; - GetService().post([this, host, port, addrtype, conn]() { + if (!conn || conn->GetRemoteEndpoint ().address ().is_unspecified ()) + { + LogPrint (eLogError, "NTCP2: Can't connect to unspecified address"); + return; + } + GetService().post([this, conn]() + { if (this->AddNTCP2Session (conn)) { - auto timer = std::make_shared(GetService()); auto timeout = NTCP2_CONNECT_TIMEOUT * 5; conn->SetTerminationTimeout(timeout * 2); @@ -1431,11 +1458,10 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogInfo, "NTCP2: Not connected in ", timeout, " seconds"); - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); conn->Terminate (); } }); - conn->GetSocket ().async_connect (*m_ProxyEndpoint, std::bind (&NTCP2Server::HandleProxyConnect, this, std::placeholders::_1, conn, timer, host, port, addrtype)); + conn->GetSocket ().async_connect (*m_ProxyEndpoint, std::bind (&NTCP2Server::HandleProxyConnect, this, std::placeholders::_1, conn, timer)); } }); } @@ -1447,7 +1473,7 @@ namespace transport m_ProxyPort = port; } - void NTCP2Server::HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype) + void NTCP2Server::HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer) { if (ecode) { @@ -1473,7 +1499,7 @@ namespace transport }); auto readbuff = std::make_shared >(2); boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 2), - [this, readbuff, timer, conn, host, port, addrtype](const boost::system::error_code & ec, std::size_t transferred) + [this, readbuff, timer, conn](const boost::system::error_code & ec, std::size_t transferred) { if(ec) { @@ -1486,7 +1512,7 @@ namespace transport { if((*readbuff)[1] == 0x00) { - AfterSocksHandshake(conn, timer, host, port, addrtype); + AfterSocksHandshake(conn, timer); return; } else if ((*readbuff)[1] == 0xff) @@ -1506,13 +1532,14 @@ namespace transport } case eHTTPProxy: { + auto& ep = conn->GetRemoteEndpoint (); i2p::http::HTTPReq req; req.method = "CONNECT"; req.version ="HTTP/1.1"; - if(addrtype == eIP6Address) - req.uri = "[" + host + "]:" + std::to_string(port); + if(ep.address ().is_v6 ()) + req.uri = "[" + ep.address ().to_string() + "]:" + std::to_string(ep.port ()); else - req.uri = host + ":" + std::to_string(port); + req.uri = ep.address ().to_string() + ":" + std::to_string(ep.port ()); boost::asio::streambuf writebuff; std::ostream out(&writebuff); @@ -1566,7 +1593,7 @@ namespace transport } } - void NTCP2Server::AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype) + void NTCP2Server::AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer) { // build request size_t sz = 6; // header + port @@ -1576,27 +1603,28 @@ namespace transport (*buff)[1] = 0x01; (*buff)[2] = 0x00; - if(addrtype == eIP4Address) + auto& ep = conn->GetRemoteEndpoint (); + if(ep.address ().is_v4 ()) { (*buff)[3] = 0x01; - auto addrbytes = boost::asio::ip::address::from_string(host).to_v4().to_bytes(); + auto addrbytes = ep.address ().to_v4().to_bytes(); sz += 4; memcpy(buff->data () + 4, addrbytes.data(), 4); } - else if (addrtype == eIP6Address) + else if (ep.address ().is_v6 ()) { (*buff)[3] = 0x04; - auto addrbytes = boost::asio::ip::address::from_string(host).to_v6().to_bytes(); + auto addrbytes = ep.address ().to_v6().to_bytes(); sz += 16; memcpy(buff->data () + 4, addrbytes.data(), 16); } - else if (addrtype == eHostname) + else { // We mustn't really fall here because all connections are made to IP addresses - LogPrint(eLogError, "NTCP2: Tried to connect to domain name via socks proxy"); + LogPrint(eLogError, "NTCP2: Tried to connect to unexpected address via proxy"); return; } - htobe16buf(buff->data () + sz - 2, port); + htobe16buf(buff->data () + sz - 2, ep.port ()); boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff->data (), sz), boost::asio::transfer_all(), [buff](const boost::system::error_code & ec, std::size_t written) { @@ -1623,11 +1651,23 @@ namespace transport return; } } - if(!e) - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); timer->cancel(); conn->Terminate(); }); } + + void NTCP2Server::SetLocalAddress (const boost::asio::ip::address& localAddress) + { + auto addr = std::make_shared(boost::asio::ip::tcp::endpoint(localAddress, 0)); + if (localAddress.is_v6 ()) + { + if (i2p::util::net::IsYggdrasilAddress (localAddress)) + m_YggdrasilAddress = addr; + else + m_Address6 = addr; + } + else + m_Address4 = addr; + } } } diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index a7708872..23b619e8 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -134,6 +134,8 @@ namespace transport void Close () { m_Socket.close (); }; // for accept boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; + const boost::asio::ip::tcp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; + void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; }; bool IsEstablished () const { return m_IsEstablished; }; bool IsTerminated () const { return m_IsTerminated; }; @@ -189,6 +191,7 @@ namespace transport NTCP2Server& m_Server; boost::asio::ip::tcp::socket m_Socket; + boost::asio::ip::tcp::endpoint m_RemoteEndpoint; bool m_IsEstablished, m_IsTerminated; std::unique_ptr m_Establisher; @@ -221,13 +224,6 @@ namespace transport { public: - enum RemoteAddressType - { - eIP4Address, - eIP6Address, - eHostname - }; - enum ProxyType { eNoProxy, @@ -246,23 +242,23 @@ namespace transport void RemoveNTCP2Session (std::shared_ptr session); std::shared_ptr FindNTCP2Session (const i2p::data::IdentHash& ident); - void ConnectWithProxy (const std::string& addr, uint16_t port, RemoteAddressType addrtype, std::shared_ptr conn); - void Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn); - - void AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype); - + void ConnectWithProxy (std::shared_ptr conn); + void Connect(std::shared_ptr conn); bool UsingProxy() const { return m_ProxyType != eNoProxy; }; void UseProxy(ProxyType proxy, const std::string & address, uint16_t port); + void SetLocalAddress (const boost::asio::ip::address& localAddress); + private: void HandleAccept (std::shared_ptr conn, const boost::system::error_code& error); void HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error); void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer); - void HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType adddrtype); - + void HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer); + void AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer); + // timer void ScheduleTermination (); void HandleTerminationTimer (const boost::system::error_code& ecode); @@ -279,7 +275,8 @@ namespace transport uint16_t m_ProxyPort; boost::asio::ip::tcp::resolver m_Resolver; std::unique_ptr m_ProxyEndpoint; - + std::shared_ptr m_Address4, m_Address6, m_YggdrasilAddress; + public: // for HTTP/I2PControl diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 0222ccdf..66d15cae 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1150,7 +1150,7 @@ namespace data return GetRandomRouter ( [v4only](std::shared_ptr router)->bool { - return !router->IsHidden () && router->IsPeerTesting () && router->IsSSU (v4only); + return !router->IsHidden () && router->IsPeerTesting (v4only); }); } diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 6183f2be..a7273376 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -41,6 +41,7 @@ namespace i2p if (!Load ()) CreateNewRouter (); m_Decryptor = m_Keys.CreateDecryptor (nullptr); + m_TunnelDecryptor = m_Keys.CreateDecryptor (nullptr); UpdateRouterInfo (); if (IsECIES ()) { @@ -77,6 +78,12 @@ namespace i2p std::string ifname; i2p::config::GetOption("ifname", ifname); std::string ifname4; i2p::config::GetOption("ifname4", ifname4); std::string ifname6; i2p::config::GetOption("ifname6", ifname6); + + if ((ntcp2 || ygg) && !m_NTCP2Keys) + NewNTCP2Keys (); + bool ntcp2Published = false; + if (ntcp2) + i2p::config::GetOption("ntcp2.published", ntcp2Published); uint8_t caps = 0; if (ipv4) { @@ -86,14 +93,20 @@ namespace i2p else if (!nat && !ifname.empty()) /* bind to interface, we have no NAT so set external address too */ host = i2p::util::net::GetInterfaceAddress(ifname, false).to_string(); // v4 - if(ifname4.size()) host = i2p::util::net::GetInterfaceAddress(ifname4, false).to_string(); + if (ntcp2) + { + if (ntcp2Published) + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v4::from_string (host), port); + else // add non-published NTCP2 address + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); + } if (ssu) { routerInfo.AddSSUAddress (host.c_str(), port, nullptr); - caps |= i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // R, BC + caps |= i2p::data::RouterInfo::eReachable; // R } } if (ipv6) @@ -103,16 +116,35 @@ namespace i2p i2p::config::GetOption("host", host); else if (!ifname.empty()) host = i2p::util::net::GetInterfaceAddress(ifname, true).to_string(); // v6 - if(ifname6.size()) host = i2p::util::net::GetInterfaceAddress(ifname6, true).to_string(); - + + if (ntcp2) + { + if (ntcp2Published) + { + std::string ntcp2Host; + if (!i2p::config::IsDefault ("ntcp2.addressv6")) + i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); + else + ntcp2Host = host; + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (ntcp2Host), port); + } + else if (!ipv4) // no other ntcp2 addresses yet + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); + } if (ssu) { routerInfo.AddSSUAddress (host.c_str(), port, nullptr); caps |= i2p::data::RouterInfo::eReachable; // R } } + if (ygg) + { + auto yggaddr = i2p::util::net::GetYggdrasilAddress (); + if (!yggaddr.is_unspecified ()) + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, yggaddr, port); + } routerInfo.SetCaps (caps); // caps + L routerInfo.SetProperty ("netId", std::to_string (m_NetID)); @@ -120,40 +152,6 @@ namespace i2p routerInfo.CreateBuffer (m_Keys); m_RouterInfo.SetRouterIdentity (GetIdentity ()); m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); - - if (ntcp2) // we don't store iv in the address if non published so we must update it from keys - { - if (!m_NTCP2Keys) NewNTCP2Keys (); - bool published; i2p::config::GetOption("ntcp2.published", published); - if (ipv4 || !published) UpdateNTCP2Address (true); // create not published NTCP2 address - if (published) - { - if (ipv4) - PublishNTCP2Address (port, true); - if (ipv6) - { - // add NTCP2 ipv6 address - std::string host = "::1"; - if (!i2p::config::IsDefault ("ntcp2.addressv6")) - i2p::config::GetOption ("ntcp2.addressv6", host); - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (host), port); - } - } - // enable added NTCP2 addresses - if (ipv4) m_RouterInfo.EnableV4 (); - if (ipv6) m_RouterInfo.EnableV6 (); - } - if (ygg) - { - auto yggaddr = i2p::util::net::GetYggdrasilAddress (); - if (!yggaddr.is_unspecified ()) - { - if (!m_NTCP2Keys) NewNTCP2Keys (); - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, yggaddr, port); - m_RouterInfo.EnableMesh (); - UpdateRouterInfo (); - } - } } void RouterContext::UpdateRouterInfo () @@ -226,7 +224,7 @@ namespace i2p if (port == 9150) port = 9151; // Tor browser } if (port) address->port = port; - address->cost = publish ? 3 : 14; + address->cost = publish ? i2p::data::COST_NTCP2_PUBLISHED : i2p::data::COST_NTCP2_NON_PUBLISHED; address->ntcp2->isPublished = publish; address->ntcp2->iv = m_NTCP2Keys->iv; updated = true; @@ -427,7 +425,6 @@ namespace i2p caps &= ~i2p::data::RouterInfo::eReachable; caps |= i2p::data::RouterInfo::eUnreachable; caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill - caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer m_RouterInfo.SetCaps (caps); uint16_t port = 0; // delete previous introducers @@ -435,6 +432,8 @@ namespace i2p for (auto& addr : addresses) if (addr->ssu) { + addr->cost = i2p::data::COST_SSU_THROUGH_INTRODUCERS; + addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer addr->ssu->introducers.clear (); port = addr->port; } @@ -452,7 +451,6 @@ namespace i2p uint8_t caps = m_RouterInfo.GetCaps (); caps &= ~i2p::data::RouterInfo::eUnreachable; caps |= i2p::data::RouterInfo::eReachable; - caps |= i2p::data::RouterInfo::eSSUIntroducer; if (m_IsFloodfill) caps |= i2p::data::RouterInfo::eFloodfill; m_RouterInfo.SetCaps (caps); @@ -462,6 +460,8 @@ namespace i2p for (auto& addr : addresses) if (addr->ssu) { + addr->cost = i2p::data::COST_SSU_DIRECT; + addr->caps |= i2p::data::RouterInfo::eSSUIntroducer; addr->ssu->introducers.clear (); port = addr->port; } @@ -774,7 +774,7 @@ namespace i2p bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) { - if (!m_Decryptor) return false; + if (!m_TunnelDecryptor) return false; if (IsECIES ()) { if (!m_InitialNoiseState) return false; @@ -782,7 +782,7 @@ namespace i2p m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState)); m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk) uint8_t sharedSecret[32]; - if (!m_Decryptor->Decrypt (encrypted, sharedSecret, ctx, false)) + if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, ctx, false)) { LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); return false; @@ -801,7 +801,7 @@ namespace i2p return true; } else - return m_Decryptor->Decrypt (encrypted, data, ctx, false); + return m_TunnelDecryptor->Decrypt (encrypted, data, ctx, false); } i2p::crypto::X25519Keys& RouterContext::GetStaticKeys () diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 12b77d65..228da788 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -39,7 +39,8 @@ namespace i2p { eRouterErrorNone = 0, eRouterErrorClockSkew = 1, - eRouterErrorOffline = 2 + eRouterErrorOffline = 2, + eRouterErrorSymmetricNAT = 3 }; class RouterContext: public i2p::garlic::GarlicDestination @@ -153,7 +154,7 @@ namespace i2p i2p::data::RouterInfo m_RouterInfo; i2p::data::PrivateKeys m_Keys; - std::shared_ptr m_Decryptor; + std::shared_ptr m_Decryptor, m_TunnelDecryptor; uint64_t m_LastUpdateTime; // in seconds bool m_AcceptsTunnels, m_IsFloodfill; std::chrono::time_point m_StartupTime; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 8da49fc6..4ea66add 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -186,6 +186,7 @@ namespace data void RouterInfo::ReadFromStream (std::istream& s) { + m_Caps = 0; s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); m_Timestamp = be64toh (m_Timestamp); // read addresses @@ -215,6 +216,7 @@ namespace data } else address->transportStyle = eTransportUnknown; + address->caps = 0; address->port = 0; uint16_t size, r = 0; s.read ((char *)&size, sizeof (size)); if (!s) return; @@ -250,7 +252,7 @@ namespace data LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP"); } else if (!strcmp (key, "caps")) - ExtractCaps (value); + address->caps = ExtractAddressCaps (value); else if (!strcmp (key, "s")) // ntcp2 static key { Base64ToByteStream (value, strlen (value), address->ntcp2->staticKey, 32); @@ -309,7 +311,15 @@ namespace data supportedTransports |= eNTCP2V4; } else if (!address->ntcp2->isPublished) - supportedTransports |= eNTCP2V4; // most likely, since we don't have host + { + if (address->caps) + { + if (address->caps | AddressCaps::eV4) supportedTransports |= eNTCP2V4; + if (address->caps | AddressCaps::eV6) supportedTransports |= eNTCP2V6; + } + else + supportedTransports |= eNTCP2V4; // most likely, since we don't have host + } } } else if (address->transportStyle == eTransportSSU) @@ -319,7 +329,7 @@ namespace data if (isHost) supportedTransports |= address->host.is_v4 () ? eSSUV4 : eSSUV6; else - if (introducers) supportedTransports |= eSSUV4; // in case if host is not presented + if (introducers) supportedTransports |= eSSUV4; // in case if host is not presented } } if (supportedTransports) @@ -430,18 +440,39 @@ namespace data case CAPS_FLAG_UNREACHABLE: m_Caps |= Caps::eUnreachable; break; - case CAPS_FLAG_SSU_TESTING: - m_Caps |= Caps::eSSUTesting; - break; - case CAPS_FLAG_SSU_INTRODUCER: - m_Caps |= Caps::eSSUIntroducer; - break; default: ; } cap++; } } + uint8_t RouterInfo::ExtractAddressCaps (const char * value) const + { + uint8_t caps = 0; + const char * cap = value; + while (*cap) + { + switch (*cap) + { + case CAPS_FLAG_V4: + caps |= AddressCaps::eV4; + break; + case CAPS_FLAG_V6: + caps |= AddressCaps::eV6; + break; + case CAPS_FLAG_SSU_TESTING: + caps |= AddressCaps::eSSUTesting; + break; + case CAPS_FLAG_SSU_INTRODUCER: + caps |= AddressCaps::eSSUIntroducer; + break; + default: ; + } + cap++; + } + return caps; + } + void RouterInfo::UpdateCapsProperty () { std::string caps; @@ -482,10 +513,26 @@ namespace data s.write ((const char *)&address.cost, sizeof (address.cost)); s.write ((const char *)&address.date, sizeof (address.date)); std::stringstream properties; + bool isPublished = false; if (address.transportStyle == eTransportNTCP) { if (address.IsNTCP2 ()) + { WriteString ("NTCP2", s); + if (address.IsPublishedNTCP2 ()) + isPublished = true; + else + { + WriteString ("caps", properties); + properties << '='; + std::string caps; + if (address.caps & AddressCaps::eV4) caps += CAPS_FLAG_V4; + if (address.caps & AddressCaps::eV6) caps += CAPS_FLAG_V6; + if (caps.empty ()) caps += CAPS_FLAG_V4; + WriteString (caps, properties); + properties << ';'; + } + } else continue; // don't write NTCP address } @@ -496,15 +543,19 @@ namespace data WriteString ("caps", properties); properties << '='; std::string caps; - if (IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; - if (IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; + if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; + if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; + if (IsReachable ()) + isPublished = true; + else + caps += CAPS_FLAG_V4; WriteString (caps, properties); properties << ';'; } else WriteString ("", s); - if (!address.IsNTCP2 () || address.IsPublishedNTCP2 ()) + if (isPublished) { WriteString ("host", properties); properties << '='; @@ -514,7 +565,7 @@ namespace data if (address.transportStyle == eTransportSSU) { // write introducers if any - if (address.ssu->introducers.size () > 0) + if (!address.ssu->introducers.empty()) { int i = 0; for (const auto& introducer: address.ssu->introducers) @@ -593,7 +644,7 @@ namespace data WriteString (address.ntcp2->iv.ToBase64 (), properties); properties << ';'; } - if (!address.IsNTCP2 () || address.IsPublishedNTCP2 ()) + if (isPublished || address.ssu) { WriteString ("port", properties); properties << '='; @@ -721,7 +772,8 @@ namespace data addr->host = boost::asio::ip::address::from_string (host); addr->port = port; addr->transportStyle = eTransportSSU; - addr->cost = 10; // NTCP should have priority over SSU + addr->cost = COST_SSU_DIRECT; // NTCP2 should have priority over SSU + addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC; addr->date = 0; addr->ssu.reset (new SSUExt ()); addr->ssu->mtu = mtu; @@ -744,7 +796,8 @@ namespace data addr->host = host; addr->port = port; addr->transportStyle = eTransportNTCP; - addr->cost = port ? 3 : 14; // override from RouterContext::PublishNTCP2Address + addr->cost = port ? COST_NTCP2_PUBLISHED : COST_NTCP2_NON_PUBLISHED; // override from RouterContext::PublishNTCP2Address + addr->caps = 0; addr->date = 0; addr->ntcp2.reset (new NTCP2Ext ()); if (port) addr->ntcp2->isPublished = true; @@ -962,12 +1015,13 @@ namespace data return nullptr; } - std::shared_ptr RouterInfo::GetNTCP2Address (bool publishedOnly) const + std::shared_ptr RouterInfo::GetNTCP2AddressWithStaticKey (const uint8_t * key) const { + if (!key) return nullptr; return GetAddress ( - [publishedOnly](std::shared_ptr address)->bool + [key](std::shared_ptr address)->bool { - return address->IsNTCP2 () && (!publishedOnly || address->IsPublishedNTCP2 ()); + return address->IsNTCP2 () && !memcmp (address->ntcp2->staticKey, key, 32); }); } @@ -1019,5 +1073,28 @@ namespace data return IsReachable () && m_Version >= NETDB_MIN_FLOODFILL_VERSION && GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; } + + bool RouterInfo::IsPeerTesting (bool v4only) const + { + auto supportedTransports = m_SupportedTransports & (eSSUV4 | eSSUV6); + if (!supportedTransports) return false; // no SSU + if (v4only && !(supportedTransports & eSSUV4)) return false; // no SSU v4 + return GetAddress ( + [](std::shared_ptr address)->bool + { + return (address->transportStyle == eTransportSSU) && address->IsPeerTesting (); + }) != nullptr; + } + + bool RouterInfo::IsIntroducer () const + { + // TODO: support ipv6 + if (!(m_SupportedTransports & eSSUV4)) return false; + return GetAddress ( + [](std::shared_ptr address)->bool + { + return (address->transportStyle == eTransportSSU) && address->IsIntroducer (); + }) != nullptr; + } } } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 45670ee5..9e9f00cd 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -44,9 +44,16 @@ namespace data const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2000 KBps */ const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2000 KBps */ + const char CAPS_FLAG_V4 = '4'; + const char CAPS_FLAG_V6 = '6'; const char CAPS_FLAG_SSU_TESTING = 'B'; const char CAPS_FLAG_SSU_INTRODUCER = 'C'; + const uint8_t COST_NTCP2_PUBLISHED = 3; + const uint8_t COST_NTCP2_NON_PUBLISHED = 14; + const uint8_t COST_SSU_DIRECT = 9; + const uint8_t COST_SSU_THROUGH_INTRODUCERS = 11; + const int MAX_RI_BUFFER_SIZE = 2048; // if RouterInfo exceeds 2048 we consider it as malformed, might be changed later class RouterInfo: public RoutingDestination { @@ -67,12 +74,18 @@ namespace data eHighBandwidth = 0x02, eExtraBandwidth = 0x04, eReachable = 0x08, - eSSUTesting = 0x10, - eSSUIntroducer = 0x20, - eHidden = 0x40, - eUnreachable = 0x80 + eHidden = 0x10, + eUnreachable = 0x20 }; + enum AddressCaps + { + eV4 = 0x01, + eV6 = 0x02, + eSSUTesting = 0x04, + eSSUIntroducer = 0x08 + }; + enum TransportStyle { eTransportUnknown = 0, @@ -111,7 +124,7 @@ namespace data boost::asio::ip::address host; int port; uint64_t date; - uint8_t cost; + uint8_t cost, caps; std::unique_ptr ssu; // not null for SSU std::unique_ptr ntcp2; // not null for NTCP2 @@ -134,6 +147,9 @@ namespace data bool IsNTCP2 () const { return (bool)ntcp2; }; bool IsPublishedNTCP2 () const { return IsNTCP2 () && ntcp2->isPublished; }; + + bool IsIntroducer () const { return caps & eSSUIntroducer; }; + bool IsPeerTesting () const { return caps & eSSUTesting; }; }; typedef std::list > Addresses; @@ -150,7 +166,7 @@ namespace data uint64_t GetTimestamp () const { return m_Timestamp; }; int GetVersion () const { return m_Version; }; Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr - std::shared_ptr GetNTCP2Address (bool publishedOnly) const; + std::shared_ptr GetNTCP2AddressWithStaticKey (const uint8_t * key) const; std::shared_ptr GetPublishedNTCP2V4Address () const; std::shared_ptr GetPublishedNTCP2V6Address () const; std::shared_ptr GetSSUAddress (bool v4only = true) const; @@ -183,12 +199,12 @@ namespace data bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; bool HasValidAddresses () const { return m_SupportedTransports; }; bool UsesIntroducer () const; - bool IsIntroducer () const { return m_Caps & eSSUIntroducer; }; - bool IsPeerTesting () const { return m_Caps & eSSUTesting; }; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; bool IsEligibleFloodfill () const; + bool IsPeerTesting (bool v4only) const; + bool IsIntroducer () const; uint8_t GetCaps () const { return m_Caps; }; void SetCaps (uint8_t caps); @@ -232,6 +248,7 @@ namespace data size_t ReadString (char* str, size_t len, std::istream& s) const; void WriteString (const std::string& str, std::ostream& s) const; void ExtractCaps (const char * value); + uint8_t ExtractAddressCaps (const char * value) const; template std::shared_ptr GetAddress (Filter filter) const; void UpdateCapsProperty (); diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index 79b4f35f..be5ac9c7 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -22,21 +22,8 @@ namespace i2p { namespace transport { - - SSUServer::SSUServer (const boost::asio::ip::address & addr, int port): - m_OnlyV6(true), m_IsRunning(false), m_Thread (nullptr), - m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), - m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), - m_EndpointV6 (addr, port), m_Socket (m_ReceiversService, m_Endpoint), - m_SocketV6 (m_ReceiversServiceV6), m_IntroducersUpdateTimer (m_Service), - m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), - m_TerminationTimerV6 (m_Service) - { - OpenSocketV6 (); - } - SSUServer::SSUServer (int port): - m_OnlyV6(false), m_IsRunning(false), m_Thread (nullptr), + m_IsRunning(false), m_Thread (nullptr), m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), @@ -44,9 +31,6 @@ namespace transport m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_Service) { - OpenSocket (); - if (context.SupportsV6 ()) - OpenSocketV6 (); } SSUServer::~SSUServer () @@ -91,18 +75,18 @@ namespace transport void SSUServer::Start () { m_IsRunning = true; - if (!m_OnlyV6) + m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); + if (context.SupportsV4 ()) { - m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); - m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); + OpenSocket (); + m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); ScheduleTermination (); } if (context.SupportsV6 ()) { + OpenSocketV6 (); m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); - if (!m_Thread) - m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); ScheduleTerminationV6 (); } @@ -205,6 +189,14 @@ namespace transport } } + void SSUServer::SetLocalAddress (const boost::asio::ip::address& localAddress) + { + if (localAddress.is_v6 ()) + m_EndpointV6.address (localAddress); + else if (localAddress.is_v4 ()) + m_Endpoint.address (localAddress); + } + void SSUServer::AddRelay (uint32_t tag, std::shared_ptr relay) { m_Relays[tag] = relay; @@ -442,21 +434,21 @@ namespace transport { auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ()); if (address) - CreateSession (router, address->host, address->port, peerTest); + CreateSession (router, address, peerTest); else LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); } void SSUServer::CreateSession (std::shared_ptr router, - const boost::asio::ip::address& addr, int port, bool peerTest) + std::shared_ptr address, bool peerTest) { - if (router) + if (router && address) { if (router->UsesIntroducer ()) - m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, peerTest)); // always V4 thread + m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, address, peerTest)); // always V4 thread else { - boost::asio::ip::udp::endpoint remoteEndpoint (addr, port); + boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); m_Service.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); } } @@ -485,13 +477,13 @@ namespace transport } } - void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest) + void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, + std::shared_ptr address, bool peerTest) { - if (router && router->UsesIntroducer ()) - { - auto address = router->GetSSUAddress (true); // v4 only for now - if (address) - { + if (router && router->UsesIntroducer () && address) + { + if (!address->host.is_unspecified () && address->port) + { boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); auto it = m_Sessions.find (remoteEndpoint); // check if session is presented already @@ -502,70 +494,67 @@ namespace transport session->SendPeerTest (); return; } - // create new session - int numIntroducers = address->ssu->introducers.size (); - if (numIntroducers > 0) + } + // create new session + int numIntroducers = address->ssu->introducers.size (); + if (numIntroducers > 0) + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + std::shared_ptr introducerSession; + const i2p::data::RouterInfo::Introducer * introducer = nullptr; + // we might have a session to introducer already + for (int i = 0; i < numIntroducers; i++) { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::shared_ptr introducerSession; - const i2p::data::RouterInfo::Introducer * introducer = nullptr; - // we might have a session to introducer already - for (int i = 0; i < numIntroducers; i++) + auto intr = &(address->ssu->introducers[i]); + if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer + boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort); + if (ep.address ().is_v4 ()) // ipv4 only { - auto intr = &(address->ssu->introducers[i]); - if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer - boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort); - if (ep.address ().is_v4 ()) // ipv4 only + if (!introducer) introducer = intr; // we pick first one for now + auto it = m_Sessions.find (ep); + if (it != m_Sessions.end ()) { - if (!introducer) introducer = intr; // we pick first one for now - it = m_Sessions.find (ep); - if (it != m_Sessions.end ()) - { - introducerSession = it->second; - break; - } + introducerSession = it->second; + break; } } - if (!introducer) - { - LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no ipv4 non-expired introducers presented"); - return; - } - - if (introducerSession) // session found - LogPrint (eLogWarning, "SSU: Session to introducer already exists"); - else // create new - { - LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost); - boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); - introducerSession = std::make_shared (*this, introducerEndpoint, router); - m_Sessions[introducerEndpoint] = introducerSession; - } -#if BOOST_VERSION >= 104900 - if (!address->host.is_unspecified () && address->port) -#endif - { - // create session - auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); - m_Sessions[remoteEndpoint] = session; - - // introduce - LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), - "] through introducer ", introducer->iHost, ":", introducer->iPort); - session->WaitForIntroduction (); - if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable - { - uint8_t buf[1]; - Send (buf, 0, remoteEndpoint); // send HolePunch - } - } - introducerSession->Introduce (*introducer, router); } - else - LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present"); + if (!introducer) + { + LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no ipv4 non-expired introducers presented"); + return; + } + + if (introducerSession) // session found + LogPrint (eLogWarning, "SSU: Session to introducer already exists"); + else // create new + { + LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost); + boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); + introducerSession = std::make_shared (*this, introducerEndpoint, router); + m_Sessions[introducerEndpoint] = introducerSession; + } + if (!address->host.is_unspecified () && address->port) + { + // create session + boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); + auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); + m_Sessions[remoteEndpoint] = session; + + // introduce + LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), + "] through introducer ", introducer->iHost, ":", introducer->iPort); + session->WaitForIntroduction (); + if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable + { + uint8_t buf[1]; + Send (buf, 0, remoteEndpoint); // send HolePunch + } + } + introducerSession->Introduce (*introducer, router); } else - LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); + LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present"); } } diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h index d20f1c9d..bfcfed4e 100644 --- a/libi2pd/SSU.h +++ b/libi2pd/SSU.h @@ -48,13 +48,12 @@ namespace transport public: SSUServer (int port); - SSUServer (const boost::asio::ip::address & addr, int port); // ipv6 only constructor ~SSUServer (); void Start (); void Stop (); void CreateSession (std::shared_ptr router, bool peerTest = false, bool v4only = false); void CreateSession (std::shared_ptr router, - const boost::asio::ip::address& addr, int port, bool peerTest = false); + std::shared_ptr address, bool peerTest = false); void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); std::shared_ptr FindSession (std::shared_ptr router) const; std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; @@ -64,7 +63,9 @@ namespace transport void DeleteAllSessions (); boost::asio::io_service& GetService () { return m_Service; }; - const boost::asio::ip::udp::endpoint& GetEndpoint () const { return m_Endpoint; }; + uint16_t GetPort () const { return m_Endpoint.port (); }; + void SetLocalAddress (const boost::asio::ip::address& localAddress); + void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to); void AddRelay (uint32_t tag, std::shared_ptr relay); void RemoveRelay (uint32_t tag); @@ -90,7 +91,8 @@ namespace transport void HandleReceivedPackets (std::vector packets, std::map >* sessions); - void CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest = false); + void CreateSessionThroughIntroducer (std::shared_ptr router, + std::shared_ptr address, bool peerTest = false); template std::shared_ptr GetRandomV4Session (Filter filter); template @@ -118,7 +120,6 @@ namespace transport std::shared_ptr session; // for Bob to Alice }; - bool m_OnlyV6; volatile bool m_IsRunning; std::thread * m_Thread, * m_ReceiversThread, * m_ReceiversThreadV6; boost::asio::io_service m_Service, m_ReceiversService, m_ReceiversServiceV6; diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index b4f2fcdb..e1e48b75 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -321,6 +321,7 @@ namespace transport void SSUSession::ProcessSessionConfirmed (const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SSU: Session confirmed received"); + m_ConnectTimer.cancel (); auto headerSize = GetSSUHeaderSize (buf); if (headerSize >= len) { @@ -683,6 +684,8 @@ namespace transport buf += 2; // our port LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); i2p::context.UpdateAddress (ourIP); + if (ourPort != m_Server.GetPort ()) + i2p::context.SetError (eRouterErrorSymmetricNAT); uint32_t nonce = bufbe32toh (buf); buf += 4; // nonce auto it = m_RelayRequests.find (nonce); diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 2eadda85..705754f0 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -189,7 +189,6 @@ namespace transport proxytype = NTCP2Server::eHTTPProxy; m_NTCP2Server->UseProxy(proxytype, proxyurl.host, proxyurl.port); - m_NTCP2Server->Start(); } else LogPrint(eLogError, "Transports: unsupported NTCP2 proxy URL ", ntcp2proxy); @@ -199,40 +198,91 @@ namespace transport return; } else - { m_NTCP2Server = new NTCP2Server (); - m_NTCP2Server->Start (); - } } - // create acceptors - auto& addresses = context.GetRouterInfo ().GetAddresses (); - for (const auto& address : addresses) - { - if (!address) continue; - if (address->transportStyle == RouterInfo::eTransportSSU) + // create SSU server + int ssuPort = 0; + if (enableSSU) + { + auto& addresses = context.GetRouterInfo ().GetAddresses (); + for (const auto& address: addresses) { - if (m_SSUServer == nullptr && enableSSU) + if (!address) continue; + if (address->transportStyle == RouterInfo::eTransportSSU) { - if (address->host.is_v4()) - m_SSUServer = new SSUServer (address->port); - else - m_SSUServer = new SSUServer (address->host, address->port); - LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port); - try { - m_SSUServer->Start (); - } catch ( std::exception & ex ) { - LogPrint(eLogError, "Transports: Failed to bind to UDP port", address->port); - delete m_SSUServer; - m_SSUServer = nullptr; - continue; - } - DetectExternalIP (); + ssuPort = address->port; + m_SSUServer = new SSUServer (address->port); + break; } - else - LogPrint (eLogError, "Transports: SSU server already exists"); } + } + + // bind to interfaces + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + if (ipv4) + { + std::string address; i2p::config::GetOption("address4", address); + if (!address.empty ()) + { + boost::system::error_code ec; + auto addr = boost::asio::ip::address::from_string (address, ec); + if (!ec) + { + if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr); + if (m_SSUServer) m_SSUServer->SetLocalAddress (addr); + } + } + } + + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + if (ipv6) + { + std::string address; i2p::config::GetOption("address6", address); + if (!address.empty ()) + { + boost::system::error_code ec; + auto addr = boost::asio::ip::address::from_string (address, ec); + if (!ec) + { + if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr); + if (m_SSUServer) m_SSUServer->SetLocalAddress (addr); + } + } } + + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); + if (ygg) + { + std::string address; i2p::config::GetOption("meshnets.yggaddress", address); + if (!address.empty ()) + { + boost::system::error_code ec; + auto addr = boost::asio::ip::address::from_string (address, ec); + if (!ec && m_NTCP2Server && i2p::util::net::IsYggdrasilAddress (addr)) + m_NTCP2Server->SetLocalAddress (addr); + } + } + + // start servers + if (m_NTCP2Server) m_NTCP2Server->Start (); + if (m_SSUServer) + { + LogPrint (eLogInfo, "Transports: Start listening UDP port ", ssuPort); + try + { + m_SSUServer->Start (); + } + catch (std::exception& ex ) + { + LogPrint(eLogError, "Transports: Failed to bind to UDP port", ssuPort); + m_SSUServer->Stop (); + delete m_SSUServer; + m_SSUServer = nullptr; + } + if (m_SSUServer) DetectExternalIP (); + } + m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); @@ -351,7 +401,7 @@ namespace transport try { auto r = netdb.FindRouter (ident); - if (!r || !r->IsCompatible (i2p::context.GetRouterInfo ())) return; + if (!r || r->IsUnreachable () || !r->IsCompatible (i2p::context.GetRouterInfo ())) return; { std::unique_lock l(m_PeersMutex); it = m_Peers.insert (std::pair(ident, { 0, r, {}, @@ -418,19 +468,10 @@ namespace transport if (address) { auto s = std::make_shared (*m_NTCP2Server, peer.router, address); - - if(m_NTCP2Server->UsingProxy()) - { - NTCP2Server::RemoteAddressType remote = NTCP2Server::eIP4Address; - std::string addr = address->host.to_string(); - - if(address->host.is_v6()) - remote = NTCP2Server::eIP6Address; - - m_NTCP2Server->ConnectWithProxy(addr, address->port, remote, s); - } + if( m_NTCP2Server->UsingProxy()) + m_NTCP2Server->ConnectWithProxy(s); else - m_NTCP2Server->Connect (address->host, address->port, s); + m_NTCP2Server->Connect (s); return true; } } @@ -464,7 +505,7 @@ namespace transport } if (address) { - m_SSUServer->CreateSession (peer.router, address->host, address->port); + m_SSUServer->CreateSession (peer.router, address); return true; } } @@ -480,7 +521,7 @@ namespace transport if (address) { auto s = std::make_shared (*m_NTCP2Server, peer.router, address); - m_NTCP2Server->Connect (address->host, address->port, s); + m_NTCP2Server->Connect (s); return true; } } diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index c4447808..69c516d2 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -645,6 +645,22 @@ namespace client localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, requstCallback); } + void BOBCommandSession::LookupLocalCommandHandler (const char * operand, size_t len) + { + LogPrint (eLogDebug, "BOB: lookup local ", operand); + auto addr = context.GetAddressBook ().GetAddress (operand); + if (!addr) + { + SendReplyError ("Address Not found"); + return; + } + auto ls = i2p::data::netdb.FindLeaseSet (addr->identHash); + if (ls) + SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); + else + SendReplyError ("Local LeaseSet Not found"); + } + void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: clear"); @@ -770,6 +786,7 @@ namespace client m_CommandHandlers[BOB_COMMAND_INPORT] = &BOBCommandSession::InportCommandHandler; m_CommandHandlers[BOB_COMMAND_QUIET] = &BOBCommandSession::QuietCommandHandler; m_CommandHandlers[BOB_COMMAND_LOOKUP] = &BOBCommandSession::LookupCommandHandler; + m_CommandHandlers[BOB_COMMAND_LOOKUP_LOCAL] = &BOBCommandSession::LookupLocalCommandHandler; m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler; m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler; m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler; diff --git a/libi2pd_client/BOB.h b/libi2pd_client/BOB.h index 74418011..f5f0c8ee 100644 --- a/libi2pd_client/BOB.h +++ b/libi2pd_client/BOB.h @@ -42,6 +42,7 @@ namespace client const char BOB_COMMAND_INPORT[] = "inport"; const char BOB_COMMAND_QUIET[] = "quiet"; const char BOB_COMMAND_LOOKUP[] = "lookup"; + const char BOB_COMMAND_LOOKUP_LOCAL[] = "lookuplocal"; const char BOB_COMMAND_CLEAR[] = "clear"; const char BOB_COMMAND_LIST[] = "list"; const char BOB_COMMAND_OPTION[] = "option"; @@ -206,6 +207,7 @@ namespace client void InportCommandHandler (const char * operand, size_t len); void QuietCommandHandler (const char * operand, size_t len); void LookupCommandHandler (const char * operand, size_t len); + void LookupLocalCommandHandler (const char * operand, size_t len); void ClearCommandHandler (const char * operand, size_t len); void ListCommandHandler (const char * operand, size_t len); void OptionCommandHandler (const char * operand, size_t len); diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index cac472f9..2f3d4d48 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -691,7 +691,7 @@ namespace client i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); - std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, "127.0.0.1"); + std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, ""); bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); // I2CP @@ -718,6 +718,7 @@ namespace client { // udp server tunnel // TODO: hostnames + if (address.empty ()) address = "127.0.0.1"; auto localAddress = boost::asio::ip::address::from_string(address); boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, port, gzip); @@ -750,12 +751,13 @@ namespace client else // regular server tunnel by default serverTunnel = std::make_shared (name, host, port, localDestination, inPort, gzip); + if (!address.empty ()) + serverTunnel->SetLocalAddress (address); if(!isUniqueLocal) { LogPrint(eLogInfo, "Clients: disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } - if (accessList.length () > 0) { std::set idents; diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 61c42b24..b15b47b0 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -107,6 +107,22 @@ namespace client } } + void I2PTunnelConnection::Connect (const boost::asio::ip::address& localAddress) + { + if (m_Socket) + { + if (m_RemoteEndpoint.address().is_v6 ()) + m_Socket->open (boost::asio::ip::tcp::v6 ()); + else + m_Socket->open (boost::asio::ip::tcp::v4 ()); + boost::system::error_code ec; + m_Socket->bind (boost::asio::ip::tcp::endpoint (localAddress, 0), ec); + if (ec) + LogPrint (eLogError, "I2PTunnel: can't bind to ", localAddress.to_string (), ": ", ec.message ()); + } + Connect (false); + } + void I2PTunnelConnection::Terminate () { if (Kill()) return; @@ -600,6 +616,16 @@ namespace client m_IsAccessList = true; } + void I2PServerTunnel::SetLocalAddress (const std::string& localAddress) + { + boost::system::error_code ec; + auto addr = boost::asio::ip::address::from_string(localAddress, ec); + if (!ec) + m_LocalAddress.reset (new boost::asio::ip::address (addr)); + else + LogPrint (eLogError, "I2PTunnel: can't set local address ", localAddress); + } + void I2PServerTunnel::Accept () { if (m_PortDestination) @@ -631,7 +657,10 @@ namespace client // new connection auto conn = CreateI2PConnection (stream); AddHandler (conn); - conn->Connect (m_IsUniqueLocal); + if (m_LocalAddress) + conn->Connect (*m_LocalAddress); + else + conn->Connect (m_IsUniqueLocal); } } diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index f7185dfd..3b52ea1a 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -48,7 +48,8 @@ namespace client ~I2PTunnelConnection (); void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); void Connect (bool isUniqueLocal = true); - + void Connect (const boost::asio::ip::address& localAddress); + protected: void Terminate (); @@ -314,6 +315,8 @@ namespace client void SetUniqueLocal (bool isUniqueLocal) { m_IsUniqueLocal = isUniqueLocal; } bool IsUniqueLocal () const { return m_IsUniqueLocal; } + void SetLocalAddress (const std::string& localAddress); + const std::string& GetAddress() const { return m_Address; } int GetPort () const { return m_Port; }; uint16_t GetLocalPort () const { return m_PortDestination->GetLocalPort (); }; @@ -339,6 +342,7 @@ namespace client std::shared_ptr m_PortDestination; std::set m_AccessList; bool m_IsAccessList; + std::unique_ptr m_LocalAddress; }; class I2PServerTunnelHTTP: public I2PServerTunnel