lokinet/llarp/utp/session.cpp

736 lines
19 KiB
C++
Raw Normal View History

2019-03-29 14:23:19 +00:00
#include <utp/session.hpp>
2019-03-29 14:23:19 +00:00
#include <utp/linklayer.hpp>
#include <messages/discard.hpp>
#include <messages/link_intro.hpp>
2019-03-26 19:30:10 +00:00
#include <util/metrics.hpp>
2019-06-02 21:19:10 +00:00
#include <util/memfn.hpp>
2019-03-26 19:30:10 +00:00
namespace llarp
{
namespace utp
{
void
2019-04-02 09:03:53 +00:00
Session::OnLinkEstablished(ILinkLayer* p)
{
parent = p;
EnterState(eLinkEstablished);
LogDebug("link established with ", remoteAddr);
}
/// pump tx queue
void
Session::PumpWrite()
{
if(!sock)
return;
ssize_t expect = 0;
2019-05-08 15:42:38 +00:00
std::vector< utp_iovec > send;
for(const auto& vec : vecq)
{
if(vec.iov_len)
{
expect += vec.iov_len;
send.emplace_back(vec);
}
}
if(expect)
{
2019-05-08 15:42:38 +00:00
ssize_t s = utp_writev(sock, send.data(), send.size());
2019-02-25 12:46:29 +00:00
if(s < 0)
return;
2019-03-29 15:26:44 +00:00
if(s > 0)
lastSend = parent->Now();
2019-06-13 21:58:17 +00:00
metrics::integerTick("utp.session.tx", "writes", s, "id",
RouterID(remoteRC.pubkey).ToString());
m_TXRate += s;
2019-02-25 12:46:29 +00:00
size_t sz = s;
2019-02-25 12:51:01 +00:00
while(vecq.size() && sz >= vecq.front().iov_len)
2018-09-12 13:29:42 +00:00
{
2019-02-25 12:46:29 +00:00
sz -= vecq.front().iov_len;
vecq.pop_front();
sendq.pop_front();
2018-09-12 13:29:42 +00:00
}
if(vecq.size())
2018-09-12 13:29:42 +00:00
{
auto& front = vecq.front();
2019-02-25 12:46:29 +00:00
front.iov_len -= sz;
front.iov_base = ((byte_t*)front.iov_base) + sz;
2018-09-12 13:29:42 +00:00
}
}
}
/// prune expired inbound messages
void
Session::PruneInboundMessages(llarp_time_t now)
{
auto itr = m_RecvMsgs.begin();
while(itr != m_RecvMsgs.end())
2018-09-06 11:46:19 +00:00
{
if(itr->second.IsExpired(now))
{
itr = m_RecvMsgs.erase(itr);
}
else
++itr;
2018-09-06 11:46:19 +00:00
}
}
2018-09-06 11:46:19 +00:00
void
Session::OutboundLinkEstablished(LinkLayer* p)
{
OnLinkEstablished(p);
OutboundHandshake();
}
2018-09-06 11:46:19 +00:00
template < bool (Crypto::*dh_func)(SharedSecret&, const PubKey&,
const SecretKey&, const TunnelNonce&) >
bool
Session::DoKeyExchange(SharedSecret& K, const KeyExchangeNonce& n,
const PubKey& other, const SecretKey& secret)
{
ShortHash t_h;
static constexpr size_t TMP_SIZE = 64;
static_assert(SharedSecret::SIZE + KeyExchangeNonce::SIZE == TMP_SIZE,
"Invalid sizes");
AlignedBuffer< TMP_SIZE > tmp;
std::copy(K.begin(), K.end(), tmp.begin());
std::copy(n.begin(), n.end(), tmp.begin() + K.size());
// t_h = HS(K + L.n)
if(!CryptoManager::instance()->shorthash(t_h, llarp_buffer_t(tmp)))
{
LogError("failed to mix key to ", remoteAddr);
return false;
}
// K = TKE(a.p, B_a.e, sk, t_h)
if(!(CryptoManager::instance()->*dh_func)(K, other, secret, t_h))
2018-12-16 20:34:04 +00:00
{
LogError("key exchange with ", other, " failed");
return false;
2018-12-16 20:34:04 +00:00
}
LogDebug("keys mixed with session to ", remoteAddr);
return true;
}
2018-12-16 20:34:04 +00:00
bool
Session::MutateKey(SharedSecret& K, const AlignedBuffer< 24 >& A)
{
AlignedBuffer< 56 > tmp;
2019-02-02 23:12:42 +00:00
llarp_buffer_t buf{tmp};
std::copy(K.begin(), K.end(), buf.cur);
buf.cur += K.size();
std::copy(A.begin(), A.end(), buf.cur);
buf.cur = buf.base;
return CryptoManager::instance()->shorthash(K, buf);
}
void
2019-04-02 09:03:53 +00:00
Session::Tick(llarp_time_t now)
{
PruneInboundMessages(now);
m_TXRate = 0;
m_RXRate = 0;
2019-06-13 21:58:17 +00:00
metrics::integerTick("utp.session.sendq", "size", sendq.size(), "id",
RouterID(remoteRC.pubkey).ToString());
}
/// low level read
bool
Session::Recv(const byte_t* buf, size_t sz)
{
// mark we are alive
Alive();
m_RXRate += sz;
size_t s = sz;
2019-06-13 21:58:17 +00:00
metrics::integerTick("utp.session.rx", "size", s, "id",
RouterID(remoteRC.pubkey).ToString());
// process leftovers
if(recvBufOffset)
{
auto left = FragmentBufferSize - recvBufOffset;
if(s >= left)
2018-09-07 17:41:49 +00:00
{
// yes it fills it
LogDebug("process leftovers, offset=", recvBufOffset, " sz=", s,
" left=", left);
std::copy(buf, buf + left, recvBuf.begin() + recvBufOffset);
s -= left;
2018-09-07 20:36:06 +00:00
recvBufOffset = 0;
buf += left;
if(!VerifyThenDecrypt(recvBuf.data()))
2018-09-07 17:41:49 +00:00
return false;
}
}
// process full fragments
while(s >= FragmentBufferSize)
{
recvBufOffset = 0;
LogDebug("process full sz=", s);
if(!VerifyThenDecrypt(buf))
return false;
buf += FragmentBufferSize;
s -= FragmentBufferSize;
}
if(s)
{
// hold onto leftovers
LogDebug("leftovers sz=", s);
std::copy(buf, buf + s, recvBuf.begin() + recvBufOffset);
recvBufOffset += s;
}
return true;
}
bool
2019-04-02 09:03:53 +00:00
Session::TimedOut(llarp_time_t now) const
{
2019-05-25 16:27:42 +00:00
if(state == eConnecting)
return now - lastActive > 5000;
2019-03-22 18:23:33 +00:00
if(sendq.size() >= MaxSendQueueSize)
{
2019-05-25 16:27:42 +00:00
if(now <= lastSend)
return false;
2019-03-29 15:26:44 +00:00
return now - lastSend > 5000;
2019-03-22 18:23:33 +00:00
}
2019-03-22 11:44:28 +00:00
// let utp manage this
return state == eClose;
}
2019-04-02 09:03:53 +00:00
PubKey
Session::GetPubKey() const
{
return remoteRC.pubkey;
}
Addr
2019-04-02 09:03:53 +00:00
Session::GetRemoteEndpoint() const
{
return remoteAddr;
}
2018-12-16 20:34:04 +00:00
/// base constructor
Session::Session(LinkLayer* p)
{
2019-03-26 13:51:57 +00:00
state = eInitial;
m_NextTXMsgID = 0;
m_NextRXMsgID = 0;
parent = p;
2018-09-07 17:41:49 +00:00
remoteTransportPubKey.Zero();
2018-12-03 18:28:16 +00:00
gotLIM = false;
2018-09-06 11:46:19 +00:00
recvBufOffset = 0;
lastActive = parent->Now();
2018-09-06 11:46:19 +00:00
}
2019-04-02 09:03:53 +00:00
bool
Session::ShouldPing() const
2018-09-06 11:46:19 +00:00
{
if(state != eSessionReady)
return false;
const auto dlt = parent->Now() - lastActive;
2019-04-02 09:03:53 +00:00
return dlt >= 10000;
2019-04-25 23:21:19 +00:00
}
ILinkLayer*
2019-04-02 09:03:53 +00:00
Session::GetLinkLayer() const
{
return parent;
}
2019-01-04 12:43:41 +00:00
void
2019-04-02 09:03:53 +00:00
Session::Pump()
2019-01-04 12:43:41 +00:00
{
// pump write queue
PumpWrite();
// prune inbound messages
PruneInboundMessages(parent->Now());
}
2018-10-29 16:48:36 +00:00
bool
2019-04-02 09:03:53 +00:00
Session::SendMessageBuffer(const llarp_buffer_t& buf)
2018-10-29 16:48:36 +00:00
{
if(sendq.size() >= MaxSendQueueSize)
{
// pump write queue if we seem to be full
PumpWrite();
}
if(sendq.size() >= MaxSendQueueSize)
{
// we didn't pump anything wtf
// this means we're stalled
2018-10-29 16:48:36 +00:00
return false;
}
size_t sz = buf.sz;
byte_t* ptr = buf.base;
uint32_t msgid = m_NextTXMsgID++;
2018-10-29 16:48:36 +00:00
while(sz)
{
uint32_t s = std::min(FragmentBodyPayloadSize, sz);
if(!EncryptThenHash(ptr, msgid, s, sz - s))
{
LogError("EncryptThenHash failed?!");
return false;
}
LogDebug("encrypted ", s, " bytes");
2018-10-29 16:48:36 +00:00
ptr += s;
sz -= s;
}
return true;
}
bool
2019-04-02 09:03:53 +00:00
Session::SendKeepAlive()
{
if(ShouldPing())
{
2019-04-02 09:03:53 +00:00
DiscardMessage msg;
std::array< byte_t, 128 > tmp;
llarp_buffer_t buf(tmp);
if(!msg.BEncode(&buf))
return false;
buf.sz = buf.cur - buf.base;
buf.cur = buf.base;
return this->SendMessageBuffer(buf);
}
return true;
}
void
Session::OutboundHandshake()
{
2019-02-02 23:12:42 +00:00
std::array< byte_t, LinkIntroMessage::MaxSize > tmp;
llarp_buffer_t buf(tmp);
// build our RC
LinkIntroMessage msg;
msg.rc = parent->GetOurRC();
if(!msg.rc.Verify(parent->Now()))
{
LogError("our RC is invalid? closing session to", remoteAddr);
Close();
return;
}
msg.N.Randomize();
msg.P = DefaultLinkSessionLifetime;
if(!msg.Sign(parent->Sign))
{
LogError("failed to sign LIM for outbound handshake to ", remoteAddr);
Close();
return;
}
// encode
if(!msg.BEncode(&buf))
{
LogError("failed to encode LIM for handshake to ", remoteAddr);
Close();
return;
}
// rewind
2018-12-03 18:28:16 +00:00
buf.sz = buf.cur - buf.base;
buf.cur = buf.base;
// send
if(!SendMessageBuffer(buf))
{
LogError("failed to send handshake to ", remoteAddr);
Close();
return;
}
if(!DoClientKeyExchange(txKey, msg.N, remoteTransportPubKey,
parent->RouterEncryptionSecret()))
{
LogError("failed to mix keys for outbound session to ", remoteAddr);
Close();
return;
}
}
Session::~Session()
{
2018-09-06 13:16:24 +00:00
if(sock)
{
utp_set_userdata(sock, nullptr);
2019-03-07 22:53:36 +00:00
sock = nullptr;
2018-09-06 13:16:24 +00:00
}
}
bool
Session::EncryptThenHash(const byte_t* ptr, uint32_t msgid, uint16_t length,
uint16_t remaining)
2018-09-06 11:46:19 +00:00
{
sendq.emplace_back();
auto& buf = sendq.back();
2018-09-12 13:29:42 +00:00
vecq.emplace_back();
2018-12-03 18:28:16 +00:00
auto& vec = vecq.back();
vec.iov_base = buf.data();
2018-12-03 18:28:16 +00:00
vec.iov_len = FragmentBufferSize;
buf.Randomize();
byte_t* noncePtr = buf.data() + FragmentHashSize;
byte_t* body = noncePtr + FragmentNonceSize;
byte_t* base = body;
AlignedBuffer< 24 > A(base);
// skip inner nonce
body += A.size();
// put msgid
htobe32buf(body, msgid);
2018-09-06 11:46:19 +00:00
body += sizeof(uint32_t);
// put length
htobe16buf(body, length);
body += sizeof(uint16_t);
// put remaining
htobe16buf(body, remaining);
body += sizeof(uint16_t);
// put body
memcpy(body, ptr, length);
2018-09-07 20:36:06 +00:00
2019-02-02 23:12:42 +00:00
llarp_buffer_t payload(base, base,
FragmentBufferSize - FragmentOverheadSize);
2018-09-07 20:36:06 +00:00
TunnelNonce nonce(noncePtr);
2018-09-07 20:36:06 +00:00
// encrypt
if(!CryptoManager::instance()->xchacha20(payload, txKey, nonce))
return false;
2018-09-07 20:36:06 +00:00
payload.base = noncePtr;
2018-12-03 18:28:16 +00:00
payload.cur = payload.base;
payload.sz = FragmentBufferSize - FragmentHashSize;
2018-09-07 20:36:06 +00:00
// key'd hash
if(!CryptoManager::instance()->hmac(buf.data(), payload, txKey))
return false;
return MutateKey(txKey, A);
2018-09-06 11:46:19 +00:00
}
void
Session::EnterState(State st)
2018-09-06 11:46:19 +00:00
{
2018-09-06 13:16:24 +00:00
state = st;
2018-11-21 17:46:33 +00:00
Alive();
2018-09-06 11:46:19 +00:00
if(st == eSessionReady)
{
parent->MapAddr(remoteRC.pubkey.as_array(), this);
2019-02-27 12:55:26 +00:00
if(!parent->SessionEstablished(this))
Close();
2018-09-06 11:46:19 +00:00
}
}
util::StatusObject
Session::ExtractStatus() const
{
return {{"client", !remoteRC.IsPublicRouter()},
{"sendBacklog", uint64_t(SendQueueBacklog())},
{"tx", m_TXRate},
{"rx", m_RXRate},
{"remoteAddr", remoteAddr.ToString()},
{"pubkey", remoteRC.pubkey.ToHex()}};
}
bool
Session::GotSessionRenegotiate(const LinkIntroMessage* msg)
{
// check with parent and possibly process and store new rc
if(!parent->SessionRenegotiate(msg->rc, remoteRC))
{
// failed to renegotiate
Close();
return false;
}
// set remote rc
remoteRC = msg->rc;
2019-02-02 23:12:42 +00:00
// recalculate rx key
return DoServerKeyExchange(rxKey, msg->N, remoteRC.enckey,
parent->RouterEncryptionSecret());
}
bool
2019-04-02 09:03:53 +00:00
Session::RenegotiateSession()
{
LinkIntroMessage lim;
lim.rc = parent->GetOurRC();
lim.N.Randomize();
lim.P = 60 * 1000 * 10;
if(!lim.Sign(parent->Sign))
return false;
2019-02-02 23:12:42 +00:00
std::array< byte_t, LinkIntroMessage::MaxSize > tmp;
llarp_buffer_t buf(tmp);
if(!lim.BEncode(&buf))
return false;
// rewind and resize buffer
buf.sz = buf.cur - buf.base;
buf.cur = buf.base;
// send message
if(!SendMessageBuffer(buf))
return false;
// regen our tx Key
return DoClientKeyExchange(txKey, lim.N, remoteRC.enckey,
parent->RouterEncryptionSecret());
}
2018-09-06 11:46:19 +00:00
bool
2018-12-17 22:43:16 +00:00
Session::VerifyThenDecrypt(const byte_t* ptr)
2018-09-06 11:46:19 +00:00
{
LogDebug("verify then decrypt ", remoteAddr);
2018-09-06 11:46:19 +00:00
ShortHash digest;
2018-09-06 20:31:58 +00:00
2019-02-02 23:12:42 +00:00
llarp_buffer_t hbuf(ptr + FragmentHashSize,
FragmentBufferSize - FragmentHashSize);
if(!CryptoManager::instance()->hmac(digest.data(), hbuf, rxKey))
2018-09-06 11:46:19 +00:00
{
LogError("keyed hash failed");
2018-09-06 11:46:19 +00:00
return false;
}
const ShortHash expected(ptr);
2018-09-07 17:41:49 +00:00
if(expected != digest)
2018-09-06 11:46:19 +00:00
{
LogError("Message Integrity Failed: got ", digest, " from ", remoteAddr,
" instead of ", expected);
2018-12-19 17:48:29 +00:00
Close();
2018-09-06 11:46:19 +00:00
return false;
}
2019-02-02 23:12:42 +00:00
llarp_buffer_t in(ptr + FragmentOverheadSize,
FragmentBufferSize - FragmentOverheadSize);
2018-12-16 20:34:04 +00:00
2019-02-03 00:31:10 +00:00
llarp_buffer_t out(rxFragBody);
2018-12-16 20:34:04 +00:00
// decrypt
if(!CryptoManager::instance()->xchacha20_alt(out, in, rxKey,
ptr + FragmentHashSize))
{
LogError("failed to decrypt message from ", remoteAddr);
return false;
}
// get inner nonce
AlignedBuffer< 24 > A(out.base);
// advance buffer
out.cur += A.size();
// read msgid
2018-12-16 20:34:04 +00:00
uint32_t msgid;
if(!out.read_uint32(msgid))
{
LogError("failed to read msgid");
2018-12-16 20:34:04 +00:00
return false;
}
// read length and remaining
uint16_t length, remaining;
if(!(out.read_uint16(length) && out.read_uint16(remaining)))
{
LogError("failed to read the rest of the header");
2018-09-06 11:46:19 +00:00
return false;
}
2019-02-05 00:41:33 +00:00
if(length > (out.sz - (out.cur - out.base)))
2018-09-06 11:46:19 +00:00
{
2018-12-16 20:34:04 +00:00
// too big length
LogError("fragment body too big");
2018-09-06 11:46:19 +00:00
return false;
}
2018-12-17 22:43:16 +00:00
if(msgid < m_NextRXMsgID)
return false;
m_NextRXMsgID = msgid;
2018-12-16 20:34:04 +00:00
// get message
2018-12-17 22:43:16 +00:00
if(m_RecvMsgs.find(msgid) == m_RecvMsgs.end())
{
m_RecvMsgs.emplace(msgid, InboundMessage());
}
2018-12-17 22:43:16 +00:00
auto itr = m_RecvMsgs.find(msgid);
// add message activity
itr->second.lastActive = parent->Now();
// append data
if(!itr->second.AppendData(out.cur, length))
{
LogError("inbound buffer is full");
m_RecvMsgs.erase(itr);
return false; // not enough room
}
// mutate key
if(!MutateKey(rxKey, A))
{
LogError("failed to mutate rx key");
return false;
}
2018-12-16 20:34:04 +00:00
if(remaining == 0)
2018-09-06 11:46:19 +00:00
{
ManagedBuffer buf{itr->second.buffer};
// resize
2019-02-02 23:12:42 +00:00
buf.underlying.sz = buf.underlying.cur - buf.underlying.base;
2018-12-16 20:34:04 +00:00
// rewind
2019-02-02 23:12:42 +00:00
buf.underlying.cur = buf.underlying.base;
// process buffer
LogDebug("got message ", msgid, " from ", remoteAddr);
2019-02-23 17:54:35 +00:00
parent->HandleMessage(this, buf.underlying);
m_RecvMsgs.erase(itr);
}
2018-12-27 19:10:38 +00:00
return true;
2018-09-06 11:46:19 +00:00
}
2018-09-06 20:31:58 +00:00
void
Session::Close()
2018-09-06 20:31:58 +00:00
{
if(state != eClose)
{
2018-09-07 20:36:06 +00:00
if(sock)
{
2019-01-07 16:13:16 +00:00
if(state == eLinkEstablished || state == eSessionReady)
{
// only call shutdown when we are actually connected
2019-01-07 16:13:16 +00:00
utp_shutdown(sock, SHUT_RDWR);
}
utp_close(sock);
utp_set_userdata(sock, nullptr);
sock = nullptr;
LogDebug("utp_close ", remoteAddr);
2018-09-07 20:36:06 +00:00
}
2018-09-06 20:31:58 +00:00
}
EnterState(eClose);
}
void
Session::Alive()
2018-09-06 20:31:58 +00:00
{
lastActive = parent->Now();
2018-09-06 11:46:19 +00:00
}
2019-04-02 09:03:53 +00:00
InboundSession::InboundSession(LinkLayer* p, utp_socket* s,
const Addr& addr)
: Session(p)
{
sock = s;
remoteAddr = addr;
RouterID rid = p->GetOurRC().pubkey;
CryptoManager::instance()->shorthash(rxKey, llarp_buffer_t(rid));
2019-04-02 09:03:53 +00:00
remoteRC.Clear();
ABSL_ATTRIBUTE_UNUSED void* res = utp_set_userdata(sock, this);
assert(res == this);
2019-04-02 09:03:53 +00:00
assert(s == sock);
2019-06-02 21:19:10 +00:00
GotLIM = util::memFn(&InboundSession::InboundLIM, this);
2019-04-02 09:03:53 +00:00
}
bool
InboundSession::InboundLIM(const LinkIntroMessage* msg)
{
if(gotLIM && remoteRC.pubkey != msg->rc.pubkey)
{
Close();
return false;
}
if(!gotLIM)
{
remoteRC = msg->rc;
CryptoManager::instance()->shorthash(txKey,
llarp_buffer_t(remoteRC.pubkey));
2019-04-02 09:03:53 +00:00
if(!DoServerKeyExchange(rxKey, msg->N, remoteRC.enckey,
parent->TransportSecretKey()))
2019-04-02 09:03:53 +00:00
return false;
std::array< byte_t, LinkIntroMessage::MaxSize > tmp;
llarp_buffer_t buf(tmp);
LinkIntroMessage replymsg;
replymsg.rc = parent->GetOurRC();
if(!replymsg.rc.Verify(parent->Now()))
2019-04-02 09:03:53 +00:00
{
LogError("our RC is invalid? closing session to", remoteAddr);
Close();
return false;
}
replymsg.N.Randomize();
replymsg.P = DefaultLinkSessionLifetime;
if(!replymsg.Sign(parent->Sign))
{
LogError("failed to sign LIM for inbound handshake from ",
remoteAddr);
Close();
return false;
}
// encode
if(!replymsg.BEncode(&buf))
{
LogError("failed to encode LIM for handshake from ", remoteAddr);
Close();
return false;
}
// rewind
buf.sz = buf.cur - buf.base;
buf.cur = buf.base;
// send
if(!SendMessageBuffer(buf))
{
LogError("failed to repl to handshake from ", remoteAddr);
Close();
return false;
}
if(!DoClientKeyExchange(txKey, replymsg.N, remoteRC.enckey,
parent->RouterEncryptionSecret()))
2019-04-02 09:03:53 +00:00
return false;
LogDebug("Sent reply LIM");
gotLIM = true;
EnterState(eSessionReady);
/// future LIM are used for session renegotiation
2019-06-02 21:19:10 +00:00
GotLIM = util::memFn(&Session::GotSessionRenegotiate, this);
2019-04-02 09:03:53 +00:00
}
return true;
}
OutboundSession::OutboundSession(LinkLayer* p, utp_socket* s,
const RouterContact& rc,
const AddressInfo& addr)
: Session(p)
{
remoteTransportPubKey = addr.pubkey;
remoteRC = rc;
sock = s;
remoteAddr = addr;
RouterID rid = remoteRC.pubkey;
CryptoManager::instance()->shorthash(txKey, llarp_buffer_t(rid));
2019-04-02 09:03:53 +00:00
rid = p->GetOurRC().pubkey;
CryptoManager::instance()->shorthash(rxKey, llarp_buffer_t(rid));
2019-04-02 09:03:53 +00:00
ABSL_ATTRIBUTE_UNUSED void* res = utp_set_userdata(sock, this);
assert(res == this);
2019-04-02 09:03:53 +00:00
assert(s == sock);
2019-06-02 21:19:10 +00:00
GotLIM = util::memFn(&OutboundSession::OutboundLIM, this);
2019-04-02 09:03:53 +00:00
}
void
OutboundSession::Start()
{
utp_connect(sock, remoteAddr, remoteAddr.SockLen());
EnterState(eConnecting);
}
bool
OutboundSession::OutboundLIM(const LinkIntroMessage* msg)
{
if(gotLIM && remoteRC.pubkey != msg->rc.pubkey)
{
return false;
}
remoteRC = msg->rc;
gotLIM = true;
if(!DoServerKeyExchange(rxKey, msg->N, remoteRC.enckey,
parent->RouterEncryptionSecret()))
2019-04-02 09:03:53 +00:00
{
Close();
return false;
}
/// future LIM are used for session renegotiation
2019-06-02 21:19:10 +00:00
GotLIM = util::memFn(&Session::GotSessionRenegotiate, this);
2019-04-02 09:03:53 +00:00
EnterState(eSessionReady);
return true;
}
} // namespace utp
} // namespace llarp