#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace llarp { namespace path { Path::Path(const std::vector< RouterContact >& h, PathSet* parent, PathRole startingRoles) : m_PathSet(parent), _role(startingRoles) { hops.resize(h.size()); size_t hsz = h.size(); for(size_t idx = 0; idx < hsz; ++idx) { hops[idx].rc = h[idx]; hops[idx].txID.Randomize(); hops[idx].rxID.Randomize(); } for(size_t idx = 0; idx < hsz - 1; ++idx) { hops[idx].txID = hops[idx + 1].rxID; } // initialize parts of the introduction intro.router = hops[hsz - 1].rc.pubkey; intro.pathID = hops[hsz - 1].txID; EnterState(ePathBuilding, parent->Now()); } void Path::SetBuildResultHook(BuildResultHookFunc func) { m_BuiltHook = func; } RouterID Path::Endpoint() const { return hops[hops.size() - 1].rc.pubkey; } PubKey Path::EndpointPubKey() const { return hops[hops.size() - 1].rc.pubkey; } PathID_t Path::TXID() const { return hops[0].txID; } PathID_t Path::RXID() const { return hops[0].rxID; } bool Path::IsReady() const { return intro.latency > 0 && _status == ePathEstablished; } bool Path::IsEndpoint(const RouterID& r, const PathID_t& id) const { return hops[hops.size() - 1].rc.pubkey == r && hops[hops.size() - 1].txID == id; } RouterID Path::Upstream() const { return hops[0].rc.pubkey; } std::string Path::HopsString() const { std::stringstream ss; for(const auto& hop : hops) ss << RouterID(hop.rc.pubkey) << " -> "; return ss.str(); } bool Path::HandleLRSM(uint64_t status, std::array< EncryptedFrame, 8 >& frames, AbstractRouter* r) { uint64_t currentStatus = status; size_t index = 0; while(index < hops.size()) { if(!frames[index].DoDecrypt(hops[index].shared)) { currentStatus = LR_StatusRecord::FAIL_DECRYPT_ERROR; break; } llarp::LogDebug("decrypted LRSM frame from ", hops[index].rc.pubkey); llarp_buffer_t* buf = frames[index].Buffer(); buf->cur = buf->base + EncryptedFrameOverheadSize; LR_StatusRecord record; // successful decrypt if(!record.BDecode(buf)) { llarp::LogWarn("malformed frame inside LRCM from ", hops[index].rc.pubkey); currentStatus = LR_StatusRecord::FAIL_MALFORMED_RECORD; break; } llarp::LogDebug("Decoded LR Status Record from ", hops[index].rc.pubkey); currentStatus = record.status; if((currentStatus & LR_StatusRecord::SUCCESS) == 0) { break; } ++index; } if(currentStatus & LR_StatusRecord::SUCCESS) { llarp::LogDebug("LR_Status message processed, path build successful"); auto self = shared_from_this(); r->logic()->queue_func([=]() { self->HandlePathConfirmMessage(r); }); } else { r->routerProfiling().MarkPathFail(this); llarp::LogDebug("LR_Status message processed, path build failed"); if(currentStatus & LR_StatusRecord::FAIL_TIMEOUT) { llarp::LogDebug("Path build failed due to timeout"); } else if(currentStatus & LR_StatusRecord::FAIL_CONGESTION) { llarp::LogDebug("Path build failed due to congestion"); } else if(currentStatus & LR_StatusRecord::FAIL_DEST_UNKNOWN) { llarp::LogDebug( "Path build failed due to one or more nodes giving destination " "unknown"); } else if(currentStatus & LR_StatusRecord::FAIL_DEST_INVALID) { llarp::LogDebug( "Path build failed due to one or more nodes considered an " "invalid destination"); } else if(currentStatus & LR_StatusRecord::FAIL_CANNOT_CONNECT) { llarp::LogDebug( "Path build failed due to a node being unable to connect to the " "next hop"); } else if(currentStatus & LR_StatusRecord::FAIL_MALFORMED_RECORD) { llarp::LogDebug( "Path build failed due to a malformed record in the build status " "message"); } else if(currentStatus & LR_StatusRecord::FAIL_DECRYPT_ERROR) { llarp::LogDebug( "Path build failed due to a decrypt error in the build status " "message"); } else { llarp::LogDebug("Path build failed for an unspecified reason"); } auto self = shared_from_this(); r->logic()->queue_func( [=]() { self->EnterState(ePathFailed, r->Now()); }); } // TODO: meaningful return value? return true; } void Path::EnterState(PathStatus st, llarp_time_t now) { if(st == ePathFailed) { _status = st; m_PathSet->HandlePathBuildFailed(shared_from_this()); return; } if(st == ePathExpired && _status == ePathBuilding) { _status = st; m_PathSet->HandlePathBuildTimeout(shared_from_this()); } else if(st == ePathBuilding) { LogInfo("path ", Name(), " is building"); buildStarted = now; } else if(st == ePathEstablished && _status == ePathBuilding) { LogInfo("path ", Name(), " is built, took ", now - buildStarted, " ms"); } else if(st == ePathTimeout && _status == ePathEstablished) { LogInfo("path ", Name(), " died"); _status = st; m_PathSet->HandlePathDied(shared_from_this()); } else if(st == ePathEstablished && _status == ePathTimeout) { LogInfo("path ", Name(), " reanimated"); } _status = st; } util::StatusObject PathHopConfig::ExtractStatus() const { util::StatusObject obj{{"lifetime", lifetime}, {"router", rc.pubkey.ToHex()}, {"txid", txID.ToHex()}, {"rxid", rxID.ToHex()}}; return obj; } util::StatusObject Path::ExtractStatus() const { auto now = llarp::time_now_ms(); util::StatusObject obj{{"intro", intro.ExtractStatus()}, {"lastRecvMsg", m_LastRecvMessage}, {"lastLatencyTest", m_LastLatencyTestTime}, {"buildStarted", buildStarted}, {"expired", Expired(now)}, {"expiresSoon", ExpiresSoon(now)}, {"expiresAt", ExpireTime()}, {"ready", IsReady()}, {"hasExit", SupportsAnyRoles(ePathRoleExit)}}; std::vector< util::StatusObject > hopsObj; std::transform(hops.begin(), hops.end(), std::back_inserter(hopsObj), [](const auto& hop) -> util::StatusObject { return hop.ExtractStatus(); }); obj["hops"] = hopsObj; switch(_status) { case ePathBuilding: obj["status"] = "building"; break; case ePathEstablished: obj["status"] = "established"; break; case ePathTimeout: obj["status"] = "timeout"; break; case ePathExpired: obj["status"] = "expired"; break; case ePathFailed: obj["status"] = "failed"; break; case ePathIgnore: obj["status"] = "ignored"; break; default: obj["status"] = "unknown"; break; } return obj; } void Path::Rebuild() { std::vector< RouterContact > newHops; for(const auto& hop : hops) newHops.emplace_back(hop.rc); LogInfo(Name(), " rebuilding on ", HopsString()); m_PathSet->Build(newHops); } void Path::Tick(llarp_time_t now, AbstractRouter* r) { if(Expired(now)) return; if(_status == ePathBuilding) { if(now >= buildStarted) { auto dlt = now - buildStarted; if(dlt >= path::build_timeout) { r->routerProfiling().MarkPathFail(this); EnterState(ePathExpired, now); return; } } } // check to see if this path is dead if(_status == ePathEstablished) { const auto dlt = now - m_LastLatencyTestTime; if(dlt > path::latency_interval && m_LastLatencyTestID == 0) { routing::PathLatencyMessage latency; latency.T = randint(); m_LastLatencyTestID = latency.T; m_LastLatencyTestTime = now; SendRoutingMessage(latency, r); FlushUpstream(r); return; } if(m_LastRecvMessage && now > m_LastRecvMessage) { const auto delay = now - m_LastRecvMessage; if(m_CheckForDead && m_CheckForDead(shared_from_this(), delay)) { r->routerProfiling().MarkPathFail(this); EnterState(ePathTimeout, now); } } else if(dlt >= path::alive_timeout && m_LastRecvMessage == 0) { if(m_CheckForDead && m_CheckForDead(shared_from_this(), dlt)) { r->routerProfiling().MarkPathFail(this); EnterState(ePathTimeout, now); } } } } void Path::HandleAllUpstream(std::vector< RelayUpstreamMessage > msgs, AbstractRouter* r) { for(const auto& msg : msgs) { if(!r->SendToOrQueue(Upstream(), &msg)) { LogDebug("failed to send upstream to ", Upstream()); } } r->linkManager().PumpLinks(); } void Path::UpstreamWork(TrafficQueue_ptr msgs, AbstractRouter* r) { std::vector< RelayUpstreamMessage > sendmsgs(msgs->size()); size_t idx = 0; for(auto& ev : *msgs) { const llarp_buffer_t buf(ev.first); TunnelNonce n = ev.second; for(const auto& hop : hops) { CryptoManager::instance()->xchacha20(buf, hop.shared, n); n ^= hop.nonceXOR; } auto& msg = sendmsgs[idx]; msg.X = buf; msg.Y = ev.second; msg.pathid = TXID(); ++idx; } r->logic()->queue_func(std::bind(&Path::HandleAllUpstream, shared_from_this(), std::move(sendmsgs), r)); } void Path::FlushUpstream(AbstractRouter* r) { if(m_UpstreamQueue && !m_UpstreamQueue->empty()) { r->threadpool()->addJob(std::bind(&Path::UpstreamWork, shared_from_this(), std::move(m_UpstreamQueue), r)); } m_UpstreamQueue = nullptr; } void Path::FlushDownstream(AbstractRouter* r) { if(m_DownstreamQueue && !m_DownstreamQueue->empty()) { r->threadpool()->addJob(std::bind(&Path::DownstreamWork, shared_from_this(), std::move(m_DownstreamQueue), r)); } m_DownstreamQueue = nullptr; } bool Path::Expired(llarp_time_t now) const { if(_status == ePathFailed) return true; if(_status == ePathEstablished || _status == ePathTimeout) return now >= ExpireTime(); if(_status == ePathBuilding) return false; return true; } std::string Path::Name() const { std::stringstream ss; ss << "TX=" << TXID() << " RX=" << RXID(); if(m_PathSet) ss << " on " << m_PathSet->Name(); return ss.str(); } void Path::DownstreamWork(TrafficQueue_ptr msgs, AbstractRouter* r) { std::vector< RelayDownstreamMessage > sendMsgs(msgs->size()); size_t idx = 0; for(auto& ev : *msgs) { const llarp_buffer_t buf(ev.first); sendMsgs[idx].Y = ev.second; for(const auto& hop : hops) { sendMsgs[idx].Y ^= hop.nonceXOR; CryptoManager::instance()->xchacha20(buf, hop.shared, sendMsgs[idx].Y); } sendMsgs[idx].X = buf; ++idx; } r->logic()->queue_func(std::bind(&Path::HandleAllDownstream, shared_from_this(), std::move(sendMsgs), r)); } void Path::HandleAllDownstream(std::vector< RelayDownstreamMessage > msgs, AbstractRouter* r) { for(const auto& msg : msgs) { const llarp_buffer_t buf(msg.X); if(!HandleRoutingMessage(buf, r)) { LogWarn("failed to handle downstream message"); continue; } m_LastRecvMessage = r->Now(); } FlushUpstream(r); } bool Path::HandleRoutingMessage(const llarp_buffer_t& buf, AbstractRouter* r) { if(!r->ParseRoutingMessageBuffer(buf, this, RXID())) { LogWarn("Failed to parse inbound routing message"); return false; } return true; } bool Path::HandleUpdateExitVerifyMessage( const routing::UpdateExitVerifyMessage& msg, AbstractRouter* r) { (void)r; if(m_UpdateExitTX && msg.T == m_UpdateExitTX) { if(m_ExitUpdated) return m_ExitUpdated(shared_from_this()); } if(m_CloseExitTX && msg.T == m_CloseExitTX) { if(m_ExitClosed) return m_ExitClosed(shared_from_this()); } return false; } bool Path::SendRoutingMessage(const routing::IMessage& msg, AbstractRouter* r) { std::array< byte_t, MAX_LINK_MSG_SIZE / 2 > tmp; llarp_buffer_t buf(tmp); // should help prevent bad paths with uninitialized members // FIXME: Why would we get uninitialized IMessages? if(msg.version != LLARP_PROTO_VERSION) return false; if(!msg.BEncode(&buf)) { LogError("Bencode failed"); DumpBuffer(buf); return false; } // make nonce TunnelNonce N; N.Randomize(); buf.sz = buf.cur - buf.base; // pad smaller messages if(buf.sz < pad_size) { // randomize padding CryptoManager::instance()->randbytes(buf.cur, pad_size - buf.sz); buf.sz = pad_size; } buf.cur = buf.base; return HandleUpstream(buf, N, r); } bool Path::HandlePathTransferMessage( ABSL_ATTRIBUTE_UNUSED const routing::PathTransferMessage& msg, ABSL_ATTRIBUTE_UNUSED AbstractRouter* r) { LogWarn("unwarranted path transfer message on tx=", TXID(), " rx=", RXID()); return false; } bool Path::HandleDataDiscardMessage(const routing::DataDiscardMessage& msg, AbstractRouter* r) { MarkActive(r->Now()); if(m_DropHandler) return m_DropHandler(shared_from_this(), msg.P, msg.S); return true; } bool Path::HandlePathConfirmMessage(AbstractRouter* r) { LogDebug("Path Build Confirm, path: ", HopsString()); auto now = r->Now(); if(_status == ePathBuilding) { // finish initializing introduction intro.expiresAt = buildStarted + hops[0].lifetime; r->routerProfiling().MarkPathSuccess(this); // persist session with upstream router until the path is done r->PersistSessionUntil(Upstream(), intro.expiresAt); EnterState(ePathEstablished, now); if(m_BuiltHook) m_BuiltHook(shared_from_this()); m_BuiltHook = nullptr; MarkActive(now); // send path latency test routing::PathLatencyMessage latency; latency.T = randint(); m_LastLatencyTestID = latency.T; m_LastLatencyTestTime = now; if(!SendRoutingMessage(latency, r)) return false; FlushUpstream(r); return true; } LogWarn("got unwarranted path confirm message on tx=", RXID(), " rx=", RXID()); return false; } bool Path::HandlePathConfirmMessage( ABSL_ATTRIBUTE_UNUSED const routing::PathConfirmMessage& msg, AbstractRouter* r) { return HandlePathConfirmMessage(r); } bool Path::HandleHiddenServiceFrame(const service::ProtocolFrame& frame) { MarkActive(m_PathSet->Now()); return m_DataHandler && m_DataHandler(shared_from_this(), frame); } bool Path::HandlePathLatencyMessage(const routing::PathLatencyMessage& msg, AbstractRouter* r) { const auto now = r->Now(); MarkActive(now); if(msg.L == m_LastLatencyTestID) { intro.latency = now - m_LastLatencyTestTime; m_LastLatencyTestID = 0; LogDebug("path latency is now ", intro.latency, " for ", Name()); return true; } LogWarn("unwarranted path latency message via ", Upstream()); return false; } bool Path::HandleDHTMessage(const dht::IMessage& msg, AbstractRouter* r) { MarkActive(r->Now()); routing::DHTMessage reply; if(!msg.HandleMessage(r->dht(), reply.M)) return false; if(reply.M.size()) return SendRoutingMessage(reply, r); return true; } bool Path::HandleCloseExitMessage(const routing::CloseExitMessage& msg, ABSL_ATTRIBUTE_UNUSED AbstractRouter* r) { /// allows exits to close from their end if(SupportsAnyRoles(ePathRoleExit | ePathRoleSVC)) { if(msg.Verify(EndpointPubKey())) { LogInfo(Name(), " had its exit closed"); _role &= ~ePathRoleExit; return true; } LogError(Name(), " CXM from exit with bad signature"); } else LogError(Name(), " unwarranted CXM"); return false; } bool Path::SendExitRequest(const routing::ObtainExitMessage& msg, AbstractRouter* r) { LogInfo(Name(), " sending exit request to ", Endpoint()); m_ExitObtainTX = msg.T; return SendRoutingMessage(msg, r); } bool Path::SendExitClose(const routing::CloseExitMessage& msg, AbstractRouter* r) { LogInfo(Name(), " closing exit to ", Endpoint()); // mark as not exit anymore _role &= ~ePathRoleExit; return SendRoutingMessage(msg, r); } bool Path::HandleObtainExitMessage(const routing::ObtainExitMessage& msg, AbstractRouter* r) { (void)msg; (void)r; LogError(Name(), " got unwarranted OXM"); return false; } bool Path::HandleUpdateExitMessage(const routing::UpdateExitMessage& msg, AbstractRouter* r) { (void)msg; (void)r; LogError(Name(), " got unwarranted UXM"); return false; } bool Path::HandleRejectExitMessage(const routing::RejectExitMessage& msg, AbstractRouter* r) { if(m_ExitObtainTX && msg.T == m_ExitObtainTX) { if(!msg.Verify(EndpointPubKey())) { LogError(Name(), "RXM invalid signature"); return false; } LogInfo(Name(), " ", Endpoint(), " Rejected exit"); MarkActive(r->Now()); return InformExitResult(msg.B); } LogError(Name(), " got unwarranted RXM"); return false; } bool Path::HandleGrantExitMessage(const routing::GrantExitMessage& msg, AbstractRouter* r) { if(m_ExitObtainTX && msg.T == m_ExitObtainTX) { if(!msg.Verify(EndpointPubKey())) { LogError(Name(), " GXM signature failed"); return false; } // we now can send exit traffic _role |= ePathRoleExit; LogInfo(Name(), " ", Endpoint(), " Granted exit"); MarkActive(r->Now()); return InformExitResult(0); } LogError(Name(), " got unwarranted GXM"); return false; } bool Path::InformExitResult(llarp_time_t B) { auto self = shared_from_this(); bool result = true; for(const auto& hook : m_ObtainedExitHooks) result &= hook(self, B); m_ObtainedExitHooks.clear(); return result; } bool Path::HandleTransferTrafficMessage( const routing::TransferTrafficMessage& msg, AbstractRouter* r) { // check if we can handle exit data if(!SupportsAnyRoles(ePathRoleExit | ePathRoleSVC)) return false; // handle traffic if we have a handler if(!m_ExitTrafficHandler) return false; bool sent = msg.X.size() > 0; auto self = shared_from_this(); for(const auto& pkt : msg.X) { if(pkt.size() <= 8) return false; uint64_t counter = bufbe64toh(pkt.data()); if(m_ExitTrafficHandler( self, llarp_buffer_t(pkt.data() + 8, pkt.size() - 8), counter)) { MarkActive(r->Now()); EnterState(ePathEstablished, r->Now()); } } return sent; } } // namespace path } // namespace llarp