mirror of https://github.com/oxen-io/lokinet
Merge branch 'datagram' into staging
commit
6206fb2a41
@ -0,0 +1,90 @@
|
||||
Wire Protocol (version ½)
|
||||
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
||||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
|
||||
document are to be interpreted as described in RFC 2119 [RFC2119].
|
||||
|
||||
LLARP supports by default an authenticated and framed transport over UTP [1]
|
||||
|
||||
Handshake:
|
||||
|
||||
Alice establishes a UTP "connection" with Bob.
|
||||
|
||||
Alice sends a LIM a_L encrpyted with the initial b_K key
|
||||
|
||||
if Bob accepts Alice's router, Bob replies with a LIM b_L encrpyted with the
|
||||
b_K key.
|
||||
|
||||
next the session keys are generated via:
|
||||
|
||||
a_h = HS(a_K + a_L.n)
|
||||
b_h = HS(b_K + b_L.n)
|
||||
a_K = TKE(A.p, B_a.e, sk, a_h)
|
||||
b_K = TKE(A.p, B_a.e, sk, b_h)
|
||||
|
||||
A.tx_K = b_K
|
||||
A.rx_K = a_K
|
||||
B.tx_K = a_K
|
||||
B.rx_K = B_K
|
||||
|
||||
the initial value of a_K is HS(A.k) and b_K is HS(B.k)
|
||||
|
||||
1120 byte fragments are sent over UTP in an ordered fashion.
|
||||
|
||||
The each fragment F has the following structure:
|
||||
|
||||
[ 32 bytes blake2 keyed hash of the following 1088 bytes (h)]
|
||||
[ 32 bytes random nonce (n)]
|
||||
[ 1056 bytes encrypted payload (p)]
|
||||
|
||||
the recipiant verifies F.h == MDS(F.n + F.p, rx_K) and the UTP session
|
||||
is reset if verification fails.
|
||||
|
||||
the decrypted payload P has the following structure:
|
||||
|
||||
[ 24 bytes random (A) ]
|
||||
[ big endian unsigned 32 bit message id (I) ]
|
||||
[ big endian unsigned 16 bit fragment length (N) ]
|
||||
[ big endian unsigned 16 bit fragment remaining bytes (R) ]
|
||||
[ N bytes of plaintext payload (X) ]
|
||||
[ trailing bytes discarded ]
|
||||
|
||||
link layer messages fragmented and delievered in any order the sender chooses.
|
||||
|
||||
recipaint ensures a buffer for message number P.I exists, allocating one if it
|
||||
does not exist.
|
||||
|
||||
recipiant appends P.X to the end of the buffer for message P.I
|
||||
|
||||
if P.R is zero then message number P.I is completed and processed as a link
|
||||
layer messages. otherwise the recipiant expects P.R additional bytes.
|
||||
P.R's value MUST decrease by P.N in the next fragment sent.
|
||||
|
||||
message size MUST NOT exceed 8192 bytes.
|
||||
|
||||
if a message is not received in 2 seconds it is discarded and any further
|
||||
fragments for the message are also discarded.
|
||||
|
||||
P.I MUST have the initial value 0
|
||||
P.I MUST be incremeneted by 1 for each new messsage transmitted
|
||||
P.I MAY wrap around back to 0
|
||||
|
||||
|
||||
after every fragment F the session key K is mutated via:
|
||||
|
||||
K = HS(K + P.A)
|
||||
|
||||
Periodically the connection initiator MUST renegotiate the session key by
|
||||
sending a LIM after L.p milliseconds have elapsed.
|
||||
|
||||
If the local RC changes while a connection is established they MUST
|
||||
renegotioate the session keys by sending a LIM to ensure the new RC is sent.
|
||||
|
||||
|
||||
references:
|
||||
|
||||
[1] http://www.bittorrent.org/beps/bep_0029.html
|
||||
|
||||
|
||||
|
@ -1,90 +1,166 @@
|
||||
Wire Protocol (version ½)
|
||||
Wire Protocol (version 1)
|
||||
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
||||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
|
||||
document are to be interpreted as described in RFC 2119 [RFC2119].
|
||||
|
||||
LLARP supports by default an authenticated and framed transport over UTP [1]
|
||||
LLARP supports by default an authenticated message transport over a
|
||||
datagram based network layer.
|
||||
|
||||
Handshake:
|
||||
|
||||
Alice establishes a UTP "connection" with Bob.
|
||||
outer message format:
|
||||
|
||||
Alice sends a LIM a_L encrpyted with the initial b_K key
|
||||
{
|
||||
A: command,
|
||||
B: <16 bytes flow id>,
|
||||
C: <optional 32 bytes cookie>,
|
||||
X: <N bytes payload>
|
||||
}
|
||||
|
||||
if Bob accepts Alice's router, Bob replies with a LIM b_L encrpyted with the
|
||||
b_K key.
|
||||
comamnds:
|
||||
|
||||
next the session keys are generated via:
|
||||
A - get handshake cookie
|
||||
|
||||
a_h = HS(a_K + a_L.n)
|
||||
b_h = HS(b_K + b_L.n)
|
||||
a_K = TKE(A.p, B_a.e, sk, a_h)
|
||||
b_K = TKE(A.p, B_a.e, sk, b_h)
|
||||
obtain a handshake cookie
|
||||
|
||||
A.tx_K = b_K
|
||||
A.rx_K = a_K
|
||||
B.tx_K = a_K
|
||||
B.rx_K = B_K
|
||||
B is randomized
|
||||
X MUST contain the user agent string of the requester.
|
||||
|
||||
the initial value of a_K is HS(A.k) and b_K is HS(B.k)
|
||||
the if the network id differs from the current network's id a reject message is
|
||||
sent:
|
||||
|
||||
1120 byte fragments are sent over UTP in an ordered fashion.
|
||||
{
|
||||
A: R,
|
||||
B: msg.B,
|
||||
X: "<reply line>"
|
||||
}
|
||||
|
||||
The each fragment F has the following structure:
|
||||
MUST be replied to with a message rejected or a give handshake cookie
|
||||
|
||||
[ 32 bytes blake2 keyed hash of the following 1088 bytes (h)]
|
||||
[ 32 bytes random nonce (n)]
|
||||
[ 1056 bytes encrypted payload (p)]
|
||||
C - give handshake cookie
|
||||
|
||||
the recipiant verifies F.h == MDS(F.n + F.p, rx_K) and the UTP session
|
||||
is reset if verification fails.
|
||||
give a handshake cookie to a remote endpoint that asks for one
|
||||
|
||||
the decrypted payload P has the following structure:
|
||||
B is the B value from the get handshake cookie message
|
||||
X is a 32 byte handshake cookie, calcuated via:
|
||||
|
||||
[ 24 bytes random (A) ]
|
||||
[ big endian unsigned 32 bit message id (I) ]
|
||||
[ big endian unsigned 16 bit fragment length (N) ]
|
||||
[ big endian unsigned 16 bit fragment remaining bytes (R) ]
|
||||
[ N bytes of plaintext payload (X) ]
|
||||
[ trailing bytes discarded ]
|
||||
r = RAND(32)
|
||||
a = "<ascii representation of ip>" + " " + "<port number>"
|
||||
X = HS(a + B + r)
|
||||
|
||||
link layer messages fragmented and delievered in any order the sender chooses.
|
||||
R - message rejected
|
||||
|
||||
recipaint ensures a buffer for message number P.I exists, allocating one if it
|
||||
does not exist.
|
||||
B is the flow id from the recipiant
|
||||
X is a reply line
|
||||
|
||||
recipiant appends P.X to the end of the buffer for message P.I
|
||||
reject a message with flow id B
|
||||
|
||||
if P.R is zero then message number P.I is completed and processed as a link
|
||||
layer messages. otherwise the recipiant expects P.R additional bytes.
|
||||
P.R's value MUST decrease by P.N in the next fragment sent.
|
||||
S - session negotiation
|
||||
|
||||
message size MUST NOT exceed 8192 bytes.
|
||||
negotiate encrypted session
|
||||
|
||||
if a message is not received in 2 seconds it is discarded and any further
|
||||
fragments for the message are also discarded.
|
||||
B is the flow id from the recipiant
|
||||
C is the handshake cookie
|
||||
X is encrypted session negotiation data
|
||||
|
||||
P.I MUST have the initial value 0
|
||||
P.I MUST be incremeneted by 1 for each new messsage transmitted
|
||||
P.I MAY wrap around back to 0
|
||||
D - encrypted data transmission
|
||||
|
||||
transmit encrypted data on session
|
||||
|
||||
after every fragment F the session key K is mutated via:
|
||||
B is the flow id from the recipiant
|
||||
X is authenticated and encrypted data
|
||||
|
||||
K = HS(K + P.A)
|
||||
BNF:
|
||||
|
||||
Periodically the connection initiator MUST renegotiate the session key by
|
||||
sending a LIM after L.p milliseconds have elapsed.
|
||||
<reply-line> ::= <status-code> <space> <method> <space> <message>
|
||||
|
||||
If the local RC changes while a connection is established they MUST
|
||||
renegotioate the session keys by sending a LIM to ensure the new RC is sent.
|
||||
<status-code> ::= <integer> <digit> <digit>
|
||||
|
||||
<word> ::= <letter>+
|
||||
|
||||
references:
|
||||
<message> ::= <word> <space> <word>* | <word>
|
||||
|
||||
[1] http://www.bittorrent.org/beps/bep_0029.html
|
||||
<method> ::= "COOKIE" | "HANDSHAKE"
|
||||
|
||||
<user-agent> ::= <net-id> <space> <protocol-version> <space> <agent-version>
|
||||
|
||||
<net-id> ::= "lokinet" | "testnet"
|
||||
|
||||
<space> ::= " "
|
||||
|
||||
<zero> ::= "0"
|
||||
|
||||
<integer> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
||||
|
||||
<digit> ::= <zero> | <integer>
|
||||
|
||||
<number> ::= <zero> | <integer> <digit>*
|
||||
|
||||
<agent-version> ::= <number> "." <number> "." <number>
|
||||
|
||||
<protocol-version> ::= <number>
|
||||
|
||||
|
||||
session negotiation:
|
||||
|
||||
The session starts out with each side having 2 session keys rx_K and tx_K for
|
||||
decrypting inbound messages and encrypting outbound messages respectively.
|
||||
|
||||
The initiator (alice) and the recipiant (bob) start out with static session keys
|
||||
|
||||
k_a = HS(a.k)
|
||||
k_b = HS(b.k)
|
||||
|
||||
a.rx_K = k_a
|
||||
b.rx_K = k_b
|
||||
|
||||
a.tx_K = k_b
|
||||
b.tx_K = k_a
|
||||
|
||||
|
||||
inner message format:
|
||||
|
||||
<32 bytes blake2s keyed hash of following data>
|
||||
<24 bytes nounce>
|
||||
<remaining bytes encrypted payload>
|
||||
|
||||
decryption is done via:
|
||||
|
||||
SD(remaining, rx_K, nounce)
|
||||
|
||||
encrypted payload is bencoded LIM (see proto_v0.txt)
|
||||
|
||||
the initiator starts out by sending a LIM a_LIM to the recipiant.
|
||||
|
||||
the recipiant replies with a LIM b_LIM to the initiator.
|
||||
|
||||
when the initiator gets a valid LIM from the recipiant the session keys for data
|
||||
transmission are set to:
|
||||
|
||||
k_a = TKE(a.k, b.k, a.sk, a_LIM.n)
|
||||
k_b = TKE(b.k, a.k, b.sk, b_LIM.n)
|
||||
|
||||
a.rx_K = k_a
|
||||
b.rx_K = k_b
|
||||
|
||||
a.tx_K = k_b
|
||||
b.tx_K = k_a
|
||||
|
||||
afterwards data transmission may happen
|
||||
|
||||
data tranmission:
|
||||
|
||||
message format:
|
||||
|
||||
<10 byte header>
|
||||
<remaining data payload>
|
||||
|
||||
header format:
|
||||
|
||||
<1 byte proto version>
|
||||
<1 byte command>
|
||||
<1 byte flags>
|
||||
<1 byte fragno>
|
||||
<2 bytes fraglen>
|
||||
<4 bytes seqno>
|
||||
|
@ -1 +0,0 @@
|
||||
#include <iwp.hpp>
|
@ -1,23 +0,0 @@
|
||||
#ifndef LLARP_IWP_HPP
|
||||
#define LLARP_IWP_HPP
|
||||
|
||||
#include <crypto.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace llarp
|
||||
{
|
||||
class Logic;
|
||||
struct Router;
|
||||
} // namespace llarp
|
||||
|
||||
struct llarp_iwp_args
|
||||
{
|
||||
struct llarp::Crypto* crypto;
|
||||
llarp::Logic* logic;
|
||||
struct llarp_threadpool* cryptoworker;
|
||||
struct llarp::Router* router;
|
||||
bool permitInbound;
|
||||
};
|
||||
|
||||
#endif
|
@ -0,0 +1,24 @@
|
||||
#include <link/iwp_internal.hpp>
|
||||
#include <router.hpp>
|
||||
|
||||
namespace llarp
|
||||
{
|
||||
namespace iwp
|
||||
{
|
||||
std::unique_ptr< ILinkLayer >
|
||||
NewServerFromRouter(llarp::Router*)
|
||||
{
|
||||
// TODO: implement me
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr< ILinkLayer >
|
||||
NewServer(llarp::Crypto*, const SecretKey&, llarp::GetRCFunc,
|
||||
llarp::LinkMessageHandler, llarp::SessionEstablishedHandler,
|
||||
llarp::SessionRenegotiateHandler, llarp::SignBufferFunc,
|
||||
llarp::TimeoutHandler, llarp::SessionClosedHandler)
|
||||
{
|
||||
// TODO: implement me
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace iwp
|
||||
} // namespace llarp
|
@ -0,0 +1,25 @@
|
||||
#ifndef LLARP_LINK_IWP_HPP
|
||||
#define LLARP_LINK_IWP_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <link/server.hpp>
|
||||
|
||||
namespace llarp
|
||||
{
|
||||
namespace iwp
|
||||
{
|
||||
std::unique_ptr< ILinkLayer >
|
||||
NewServer(llarp::Crypto* crypto, const SecretKey& routerEncSecret,
|
||||
llarp::GetRCFunc getrc, llarp::LinkMessageHandler h,
|
||||
llarp::SessionEstablishedHandler est,
|
||||
llarp::SessionRenegotiateHandler reneg,
|
||||
llarp::SignBufferFunc sign, llarp::TimeoutHandler timeout,
|
||||
llarp::SessionClosedHandler closed);
|
||||
|
||||
std::unique_ptr< ILinkLayer >
|
||||
NewServerFromRouter(llarp::Router* r);
|
||||
|
||||
} // namespace iwp
|
||||
} // namespace llarp
|
||||
|
||||
#endif
|
@ -0,0 +1,365 @@
|
||||
#ifndef LLARP_LINK_IWP_INTERNAL_HPP
|
||||
#define LLARP_LINK_IWP_INTERNAL_HPP
|
||||
#include <link/server.hpp>
|
||||
#include <link/session.hpp>
|
||||
#include <link_layer.hpp>
|
||||
|
||||
#include <bitset>
|
||||
#include <deque>
|
||||
|
||||
namespace llarp
|
||||
{
|
||||
namespace iwp
|
||||
{
|
||||
struct LinkLayer;
|
||||
|
||||
struct Session final : public llarp::ILinkSession
|
||||
{
|
||||
/// base
|
||||
Session(LinkLayer *parent);
|
||||
/// inbound
|
||||
Session(LinkLayer *parent, const llarp::Addr &from);
|
||||
/// outbound
|
||||
Session(LinkLayer *parent, const RouterContact &rc,
|
||||
const AddressInfo &ai);
|
||||
~Session();
|
||||
|
||||
void
|
||||
PumpIO();
|
||||
|
||||
void
|
||||
TickIO(llarp_time_t now);
|
||||
|
||||
bool
|
||||
QueueMessageBuffer(llarp_buffer_t buf);
|
||||
|
||||
/// return true if the session is established and handshaked and all that
|
||||
/// jazz
|
||||
bool
|
||||
SessionIsEstablished();
|
||||
|
||||
/// inbound start
|
||||
void
|
||||
Accept();
|
||||
|
||||
/// sendclose
|
||||
void
|
||||
Close();
|
||||
|
||||
void
|
||||
Connect();
|
||||
|
||||
// set tls config
|
||||
void
|
||||
Configure();
|
||||
|
||||
/// low level recv
|
||||
void
|
||||
Recv_ll(const void *buf, size_t sz);
|
||||
|
||||
/// verify a lim
|
||||
bool
|
||||
VerfiyLIM(const llarp::LinkIntroMessage *msg);
|
||||
|
||||
SharedSecret m_TXKey;
|
||||
SharedSecret m_RXKey;
|
||||
LinkLayer *m_Parent;
|
||||
llarp::Crypto *const crypto;
|
||||
llarp::RouterContact remoteRC;
|
||||
llarp::Addr remoteAddr;
|
||||
|
||||
using MessageBuffer_t = llarp::AlignedBuffer< MAX_LINK_MSG_SIZE >;
|
||||
|
||||
using Seqno_t = uint32_t;
|
||||
using Proto_t = uint8_t;
|
||||
using FragLen_t = uint16_t;
|
||||
using Flags_t = uint8_t;
|
||||
using Fragno_t = uint8_t;
|
||||
using Cmd_t = uint8_t;
|
||||
|
||||
static constexpr size_t fragoverhead = sizeof(Proto_t) + sizeof(Cmd_t)
|
||||
+ sizeof(Flags_t) + sizeof(Fragno_t) + sizeof(FragLen_t)
|
||||
+ sizeof(Seqno_t);
|
||||
|
||||
/// keepalive command
|
||||
static constexpr Cmd_t PING = 0;
|
||||
/// transmit fragment command
|
||||
static constexpr Cmd_t XMIT = 1;
|
||||
/// fragment ack command
|
||||
static constexpr Cmd_t FACK = 2;
|
||||
|
||||
/// maximum number of fragments
|
||||
static constexpr uint8_t maxfrags = 8;
|
||||
|
||||
/// maximum fragment size
|
||||
static constexpr FragLen_t fragsize = MAX_LINK_MSG_SIZE / maxfrags;
|
||||
|
||||
struct FragmentHeader
|
||||
{
|
||||
/// protocol version, always LLARP_PROTO_VERSION
|
||||
Proto_t version = LLARP_PROTO_VERSION;
|
||||
/// fragment command type
|
||||
Cmd_t cmd = 0;
|
||||
/// if cmd is XMIT this is the number of additional fragments this
|
||||
/// message has
|
||||
/// if cmd is FACK this is the fragment bitfield of the
|
||||
/// messages acked otherwise 0
|
||||
Flags_t flags = 0;
|
||||
/// if cmd is XMIT this is the fragment index
|
||||
/// if cmd is FACK this is set to 0xff to indicate message drop
|
||||
/// otherwise set to 0
|
||||
/// any other cmd it is set to 0
|
||||
Fragno_t fragno = 0;
|
||||
/// if cmd is XMIT then this is the size of the current fragment
|
||||
/// if cmd is FACK then this MUST be set to 0
|
||||
FragLen_t fraglen = 0;
|
||||
/// if cmd is XMIT or FACK this is the sequence number of the message
|
||||
/// otherwise it's 0
|
||||
Seqno_t seqno = 0;
|
||||
|
||||
bool
|
||||
Decode(llarp_buffer_t *buf)
|
||||
{
|
||||
if(llarp_buffer_size_left(*buf) < fragoverhead)
|
||||
return false;
|
||||
version = *buf->cur;
|
||||
if(version != LLARP_PROTO_VERSION)
|
||||
return false;
|
||||
buf->cur++;
|
||||
cmd = *buf->cur;
|
||||
buf->cur++;
|
||||
flags = *buf->cur;
|
||||
buf->cur++;
|
||||
fragno = *buf->cur;
|
||||
buf->cur++;
|
||||
llarp_buffer_read_uint16(buf, &fraglen);
|
||||
llarp_buffer_read_uint32(buf, &seqno);
|
||||
return fraglen <= fragsize;
|
||||
}
|
||||
|
||||
bool
|
||||
Encode(llarp_buffer_t *buf, llarp_buffer_t body)
|
||||
{
|
||||
if(body.sz > fragsize)
|
||||
return false;
|
||||
fraglen = body.sz;
|
||||
if(llarp_buffer_size_left(*buf) < (fragoverhead + fraglen))
|
||||
return false;
|
||||
*buf->cur = LLARP_PROTO_VERSION;
|
||||
buf->cur++;
|
||||
*buf->cur = cmd;
|
||||
buf->cur++;
|
||||
*buf->cur = flags;
|
||||
buf->cur++;
|
||||
*buf->cur = fragno;
|
||||
buf->cur++;
|
||||
llarp_buffer_put_uint16(buf, fraglen);
|
||||
llarp_buffer_put_uint32(buf, seqno);
|
||||
if(fraglen)
|
||||
memcpy(buf->cur, body.base, fraglen);
|
||||
buf->cur += fraglen;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct MessageState
|
||||
{
|
||||
/// default
|
||||
MessageState(){};
|
||||
/// inbound
|
||||
MessageState(Flags_t numfrags)
|
||||
{
|
||||
acks.set();
|
||||
if(numfrags <= maxfrags)
|
||||
{
|
||||
while(numfrags)
|
||||
acks.reset(maxfrags - (numfrags--));
|
||||
}
|
||||
else // invalid value
|
||||
return;
|
||||
}
|
||||
|
||||
/// outbound
|
||||
MessageState(llarp_buffer_t buf)
|
||||
{
|
||||
sz = std::min(buf.sz, MAX_LINK_MSG_SIZE);
|
||||
memcpy(msg.data(), buf.base, sz);
|
||||
size_t idx = 0;
|
||||
acks.set();
|
||||
while(idx * fragsize < sz)
|
||||
acks.reset(idx++);
|
||||
};
|
||||
|
||||
/// which fragments have we got
|
||||
std::bitset< maxfrags > acks;
|
||||
/// the message buffer
|
||||
MessageBuffer_t msg;
|
||||
/// the message's size
|
||||
FragLen_t sz;
|
||||
/// the last activity we have had
|
||||
llarp_time_t lastActiveAt;
|
||||
|
||||
/// return true if this message is to be removed
|
||||
/// because of inactivity
|
||||
bool
|
||||
IsExpired(llarp_time_t now) const
|
||||
{
|
||||
return now > lastActiveAt && now - lastActiveAt > 2000;
|
||||
}
|
||||
|
||||
bool
|
||||
IsDone() const
|
||||
{
|
||||
return acks.all();
|
||||
}
|
||||
|
||||
bool
|
||||
ShouldRetransmit(llarp_time_t now) const
|
||||
{
|
||||
if(IsDone())
|
||||
return false;
|
||||
return now > lastActiveAt && now - lastActiveAt > 500;
|
||||
}
|
||||
|
||||
template < typename write_pkt_func >
|
||||
bool
|
||||
TransmitUnacked(write_pkt_func write_pkt, Seqno_t seqno) const
|
||||
{
|
||||
static FragLen_t maxfragsize = fragsize;
|
||||
FragmentHeader hdr;
|
||||
hdr.seqno = seqno;
|
||||
hdr.cmd = XMIT;
|
||||
AlignedBuffer< fragoverhead + fragsize > frag;
|
||||
auto buf = frag.as_buffer();
|
||||
const byte_t *ptr = msg.data();
|
||||
Fragno_t idx = 0;
|
||||
FragLen_t len = sz;
|
||||
while(idx < maxfrags)
|
||||
{
|
||||
const FragLen_t l = std::min(len, maxfragsize);
|
||||
if(!acks.test(idx))
|
||||
{
|
||||
hdr.fragno = idx;
|
||||
hdr.fraglen = l;
|
||||
if(!hdr.Encode(&buf, llarp::InitBuffer(ptr, l)))
|
||||
return false;
|
||||
buf.sz = buf.cur - buf.base;
|
||||
buf.cur = buf.base;
|
||||
len -= l;
|
||||
if(write_pkt(buf.base, buf.sz) != int(buf.sz))
|
||||
return false;
|
||||
}
|
||||
ptr += l;
|
||||
len -= l;
|
||||
if(l >= fragsize)
|
||||
++idx;
|
||||
else
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template < typename write_pkt_func >
|
||||
bool
|
||||
TransmitAcks(write_pkt_func write_pkt, Seqno_t seqno)
|
||||
{
|
||||
FragmentHeader hdr;
|
||||
hdr.seqno = seqno;
|
||||
hdr.cmd = FACK;
|
||||
hdr.flags = 0;
|
||||
byte_t idx = 0;
|
||||
while(idx < maxfrags)
|
||||
{
|
||||
if(acks.test(idx))
|
||||
hdr.flags |= 1 << idx;
|
||||
++idx;
|
||||
}
|
||||
hdr.fraglen = 0;
|
||||
hdr.fragno = 0;
|
||||
AlignedBuffer< fragoverhead > frag;
|
||||
auto buf = frag.as_buffer();
|
||||
if(!hdr.Encode(&buf, llarp::InitBuffer(nullptr, 0)))
|
||||
return false;
|
||||
return write_pkt(buf.base, buf.sz) == int(buf.sz);
|
||||
}
|
||||
};
|
||||
|
||||
using MessageHolder_t = std::unordered_map< Seqno_t, MessageState >;
|
||||
|
||||
MessageHolder_t m_Inbound;
|
||||
MessageHolder_t m_Outbound;
|
||||
|
||||
using Buf_t = std::vector< byte_t >;
|
||||
using IOQueue_t = std::deque< Buf_t >;
|
||||
|
||||
IOQueue_t ll_recv;
|
||||
IOQueue_t ll_send;
|
||||
};
|
||||
|
||||
struct LinkLayer final : public llarp::ILinkLayer
|
||||
{
|
||||
LinkLayer(llarp::Crypto *crypto, const SecretKey &encryptionSecretKey,
|
||||
const SecretKey &identitySecretKey, llarp::GetRCFunc getrc,
|
||||
llarp::LinkMessageHandler h, llarp::SignBufferFunc sign,
|
||||
llarp::SessionEstablishedHandler established,
|
||||
llarp::SessionRenegotiateHandler reneg,
|
||||
llarp::TimeoutHandler timeout,
|
||||
llarp::SessionClosedHandler closed);
|
||||
|
||||
~LinkLayer();
|
||||
llarp::Crypto *const crypto;
|
||||
|
||||
bool
|
||||
Start(llarp::Logic *l) override;
|
||||
|
||||
ILinkSession *
|
||||
NewOutboundSession(const llarp::RouterContact &rc,
|
||||
const llarp::AddressInfo &ai) override;
|
||||
|
||||
void
|
||||
Pump() override;
|
||||
|
||||
bool
|
||||
KeyGen(SecretKey &k) override;
|
||||
|
||||
const char *
|
||||
Name() const override;
|
||||
|
||||
uint16_t
|
||||
Rank() const override;
|
||||
|
||||
const byte_t *
|
||||
IndentityKey() const
|
||||
{
|
||||
return m_IdentityKey.data();
|
||||
}
|
||||
|
||||
const AlignedBuffer< 32 > &
|
||||
CookieSec() const
|
||||
{
|
||||
return m_CookieSec;
|
||||
}
|
||||
|
||||
RouterID
|
||||
GetRouterID() const
|
||||
{
|
||||
return m_IdentityKey.toPublic();
|
||||
}
|
||||
|
||||
private:
|
||||
bool
|
||||
SignBuffer(llarp::Signature &sig, llarp_buffer_t buf) const
|
||||
{
|
||||
return crypto->sign(sig, m_IdentityKey, buf);
|
||||
}
|
||||
const llarp::SecretKey m_IdentityKey;
|
||||
AlignedBuffer< 32 > m_CookieSec;
|
||||
|
||||
/// handle ll recv
|
||||
void
|
||||
RecvFrom(const llarp::Addr &from, const void *buf, size_t sz) override;
|
||||
};
|
||||
} // namespace iwp
|
||||
} // namespace llarp
|
||||
|
||||
#endif
|
@ -0,0 +1,426 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <link/utp.hpp>
|
||||
#include <link/iwp.hpp>
|
||||
#include <messages/link_intro.hpp>
|
||||
#include <messages/discard.hpp>
|
||||
#include <ev.h>
|
||||
|
||||
struct LinkLayerTest : public ::testing::Test
|
||||
{
|
||||
static constexpr uint16_t AlicePort = 5000;
|
||||
static constexpr uint16_t BobPort = 6000;
|
||||
|
||||
struct Context
|
||||
{
|
||||
Context(llarp::Crypto& c)
|
||||
{
|
||||
crypto = &c;
|
||||
crypto->identity_keygen(signingKey);
|
||||
crypto->encryption_keygen(encryptionKey);
|
||||
rc.pubkey = llarp::seckey_topublic(signingKey);
|
||||
rc.enckey = llarp::seckey_topublic(encryptionKey);
|
||||
}
|
||||
|
||||
llarp::SecretKey signingKey;
|
||||
llarp::SecretKey encryptionKey;
|
||||
|
||||
llarp::RouterContact rc;
|
||||
|
||||
llarp::Crypto* crypto;
|
||||
|
||||
bool gotLIM = false;
|
||||
|
||||
const llarp::RouterContact&
|
||||
GetRC() const
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
llarp::RouterID
|
||||
GetRouterID() const
|
||||
{
|
||||
return rc.pubkey;
|
||||
}
|
||||
|
||||
/// regenerate rc and rotate onion key
|
||||
bool
|
||||
Regen()
|
||||
{
|
||||
crypto->encryption_keygen(encryptionKey);
|
||||
rc.enckey = llarp::seckey_topublic(encryptionKey);
|
||||
return rc.Sign(crypto, signingKey);
|
||||
}
|
||||
|
||||
std::unique_ptr< llarp::ILinkLayer > link;
|
||||
|
||||
static std::string
|
||||
localLoopBack()
|
||||
{
|
||||
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \
|
||||
|| (__APPLE__ && __MACH__)
|
||||
return "lo0";
|
||||
#else
|
||||
return "lo";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
Start(llarp::Logic* logic, llarp_ev_loop* loop, uint16_t port)
|
||||
{
|
||||
if(!link)
|
||||
return false;
|
||||
if(!link->Configure(loop, localLoopBack(), AF_INET, port))
|
||||
return false;
|
||||
if(!link->GenEphemeralKeys())
|
||||
return false;
|
||||
rc.addrs.emplace_back();
|
||||
if(!link->GetOurAddressInfo(rc.addrs[0]))
|
||||
return false;
|
||||
if(!rc.Sign(crypto, signingKey))
|
||||
return false;
|
||||
return link->Start(logic);
|
||||
}
|
||||
|
||||
void
|
||||
Stop()
|
||||
{
|
||||
if(link)
|
||||
link->Stop();
|
||||
}
|
||||
|
||||
void
|
||||
TearDown()
|
||||
{
|
||||
Stop();
|
||||
link.reset();
|
||||
}
|
||||
};
|
||||
|
||||
llarp::Crypto crypto;
|
||||
|
||||
Context Alice;
|
||||
Context Bob;
|
||||
|
||||
bool success = false;
|
||||
|
||||
llarp_ev_loop* netLoop;
|
||||
std::unique_ptr< llarp::Logic > logic;
|
||||
|
||||
llarp_time_t oldRCLifetime;
|
||||
|
||||
LinkLayerTest()
|
||||
: crypto(llarp::Crypto::sodium{})
|
||||
, Alice(crypto)
|
||||
, Bob(crypto)
|
||||
, netLoop(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
SetUp()
|
||||
{
|
||||
oldRCLifetime = llarp::RouterContact::Lifetime;
|
||||
llarp::RouterContact::IgnoreBogons = true;
|
||||
llarp::RouterContact::Lifetime = 500;
|
||||
llarp_ev_loop_alloc(&netLoop);
|
||||
logic.reset(new llarp::Logic());
|
||||
}
|
||||
|
||||
void
|
||||
TearDown()
|
||||
{
|
||||
Alice.TearDown();
|
||||
Bob.TearDown();
|
||||
logic.reset();
|
||||
llarp_ev_loop_free(&netLoop);
|
||||
llarp::RouterContact::IgnoreBogons = false;
|
||||
llarp::RouterContact::Lifetime = oldRCLifetime;
|
||||
}
|
||||
|
||||
static void
|
||||
OnTimeout(void* u, uint64_t, uint64_t left)
|
||||
{
|
||||
if(left)
|
||||
return;
|
||||
static_cast< LinkLayerTest* >(u)->Stop();
|
||||
}
|
||||
|
||||
void
|
||||
RunMainloop()
|
||||
{
|
||||
logic->call_later({5000, this, &OnTimeout});
|
||||
llarp_ev_loop_run_single_process(netLoop, logic->thread, logic.get());
|
||||
}
|
||||
|
||||
void
|
||||
Stop()
|
||||
{
|
||||
llarp_ev_loop_stop(netLoop);
|
||||
}
|
||||
|
||||
bool AliceGotMessage(llarp_buffer_t)
|
||||
{
|
||||
success = true;
|
||||
Stop();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(LinkLayerTest, TestUTPAliceRenegWithBob)
|
||||
{
|
||||
Alice.link = llarp::utp::NewServer(
|
||||
&crypto, Alice.encryptionKey,
|
||||
[&]() -> const llarp::RouterContact& { return Alice.GetRC(); },
|
||||
[&](llarp::ILinkSession* s, llarp_buffer_t buf) -> bool {
|
||||
if(Alice.gotLIM)
|
||||
{
|
||||
Alice.Regen();
|
||||
return s->RenegotiateSession();
|
||||
}
|
||||
else
|
||||
{
|
||||
llarp::LinkIntroMessage msg;
|
||||
if(!msg.BDecode(&buf))
|
||||
return false;
|
||||
if(!s->GotLIM(&msg))
|
||||
return false;
|
||||
Alice.gotLIM = true;
|
||||
return true;
|
||||
}
|
||||
},
|
||||
[&](llarp::RouterContact rc) {
|
||||
ASSERT_EQ(rc, Bob.GetRC());
|
||||
llarp::LogInfo("alice established with bob");
|
||||
},
|
||||
[&](llarp::RouterContact, llarp::RouterContact) -> bool { return true; },
|
||||
[&](llarp::Signature& sig, llarp_buffer_t buf) -> bool {
|
||||
return crypto.sign(sig, Alice.signingKey, buf);
|
||||
},
|
||||
[&](llarp::ILinkSession* session) {
|
||||
ASSERT_FALSE(session->IsEstablished());
|
||||
Stop();
|
||||
},
|
||||
[&](llarp::RouterID router) { ASSERT_EQ(router, Bob.GetRouterID()); });
|
||||
|
||||
auto sendDiscardMessage = [](llarp::ILinkSession* s) -> bool {
|
||||
// send discard message in reply to complete unit test
|
||||
byte_t tmp[32] = {0};
|
||||
auto otherBuf = llarp::StackBuffer< decltype(tmp) >(tmp);
|
||||
llarp::DiscardMessage discard;
|
||||
if(!discard.BEncode(&otherBuf))
|
||||
return false;
|
||||
otherBuf.sz = otherBuf.cur - otherBuf.base;
|
||||
otherBuf.cur = otherBuf.base;
|
||||
return s->SendMessageBuffer(otherBuf);
|
||||
};
|
||||
|
||||
Bob.link = llarp::utp::NewServer(
|
||||
&crypto, Bob.encryptionKey,
|
||||
[&]() -> const llarp::RouterContact& { return Bob.GetRC(); },
|
||||
[&](llarp::ILinkSession* s, llarp_buffer_t buf) -> bool {
|
||||
llarp::LinkIntroMessage msg;
|
||||
if(!msg.BDecode(&buf))
|
||||
return false;
|
||||
if(!s->GotLIM(&msg))
|
||||
return false;
|
||||
Bob.gotLIM = true;
|
||||
return sendDiscardMessage(s);
|
||||
},
|
||||
[&](llarp::RouterContact rc) {
|
||||
ASSERT_EQ(rc, Alice.GetRC());
|
||||
llarp::LogInfo("bob established with alice");
|
||||
Bob.link->VisitSessionByPubkey(Alice.GetRC().pubkey.as_array(),
|
||||
sendDiscardMessage);
|
||||
},
|
||||
[&](llarp::RouterContact newrc, llarp::RouterContact oldrc) -> bool {
|
||||
success = newrc.pubkey == oldrc.pubkey;
|
||||
return true;
|
||||
},
|
||||
[&](llarp::Signature& sig, llarp_buffer_t buf) -> bool {
|
||||
return crypto.sign(sig, Bob.signingKey, buf);
|
||||
},
|
||||
[&](llarp::ILinkSession* session) {
|
||||
ASSERT_FALSE(session->IsEstablished());
|
||||
},
|
||||
[&](llarp::RouterID router) { ASSERT_EQ(router, Alice.GetRouterID()); });
|
||||
|
||||
ASSERT_TRUE(Alice.Start(logic.get(), netLoop, AlicePort));
|
||||
ASSERT_TRUE(Bob.Start(logic.get(), netLoop, BobPort));
|
||||
|
||||
ASSERT_TRUE(Alice.link->TryEstablishTo(Bob.GetRC()));
|
||||
|
||||
RunMainloop();
|
||||
ASSERT_TRUE(Alice.gotLIM);
|
||||
ASSERT_TRUE(Bob.gotLIM);
|
||||
ASSERT_TRUE(success);
|
||||
}
|
||||
|
||||
TEST_F(LinkLayerTest, TestUTPAliceConnectToBob)
|
||||
{
|
||||
Alice.link = llarp::utp::NewServer(
|
||||
&crypto, Alice.encryptionKey,
|
||||
[&]() -> const llarp::RouterContact& { return Alice.GetRC(); },
|
||||
[&](llarp::ILinkSession* s, llarp_buffer_t buf) -> bool {
|
||||
if(Alice.gotLIM)
|
||||
{
|
||||
return AliceGotMessage(buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
llarp::LinkIntroMessage msg;
|
||||
if(!msg.BDecode(&buf))
|
||||
return false;
|
||||
if(!s->GotLIM(&msg))
|
||||
return false;
|
||||
Alice.gotLIM = true;
|
||||
return true;
|
||||
}
|
||||
},
|
||||
[&](llarp::RouterContact rc) {
|
||||
ASSERT_EQ(rc, Bob.GetRC());
|
||||
llarp::LogInfo("alice established with bob");
|
||||
},
|
||||
[&](llarp::RouterContact, llarp::RouterContact) -> bool { return true; },
|
||||
[&](llarp::Signature& sig, llarp_buffer_t buf) -> bool {
|
||||
return crypto.sign(sig, Alice.signingKey, buf);
|
||||
},
|
||||
[&](llarp::ILinkSession* session) {
|
||||
ASSERT_FALSE(session->IsEstablished());
|
||||
Stop();
|
||||
},
|
||||
[&](llarp::RouterID router) { ASSERT_EQ(router, Bob.GetRouterID()); });
|
||||
|
||||
auto sendDiscardMessage = [](llarp::ILinkSession* s) -> bool {
|
||||
// send discard message in reply to complete unit test
|
||||
byte_t tmp[32] = {0};
|
||||
auto otherBuf = llarp::StackBuffer< decltype(tmp) >(tmp);
|
||||
llarp::DiscardMessage discard;
|
||||
if(!discard.BEncode(&otherBuf))
|
||||
return false;
|
||||
otherBuf.sz = otherBuf.cur - otherBuf.base;
|
||||
otherBuf.cur = otherBuf.base;
|
||||
return s->SendMessageBuffer(otherBuf);
|
||||
};
|
||||
|
||||
Bob.link = llarp::utp::NewServer(
|
||||
&crypto, Bob.encryptionKey,
|
||||
[&]() -> const llarp::RouterContact& { return Bob.GetRC(); },
|
||||
[&](llarp::ILinkSession* s, llarp_buffer_t buf) -> bool {
|
||||
llarp::LinkIntroMessage msg;
|
||||
if(!msg.BDecode(&buf))
|
||||
return false;
|
||||
if(!s->GotLIM(&msg))
|
||||
return false;
|
||||
Bob.gotLIM = true;
|
||||
return true;
|
||||
},
|
||||
[&](llarp::RouterContact rc) {
|
||||
ASSERT_EQ(rc, Alice.GetRC());
|
||||
llarp::LogInfo("bob established with alice");
|
||||
Bob.link->VisitSessionByPubkey(Alice.GetRC().pubkey.as_array(),
|
||||
sendDiscardMessage);
|
||||
},
|
||||
[&](llarp::RouterContact, llarp::RouterContact) -> bool { return true; },
|
||||
[&](llarp::Signature& sig, llarp_buffer_t buf) -> bool {
|
||||
return crypto.sign(sig, Bob.signingKey, buf);
|
||||
},
|
||||
[&](llarp::ILinkSession* session) {
|
||||
ASSERT_FALSE(session->IsEstablished());
|
||||
},
|
||||
[&](llarp::RouterID router) { ASSERT_EQ(router, Alice.GetRouterID()); });
|
||||
|
||||
ASSERT_TRUE(Alice.Start(logic.get(), netLoop, AlicePort));
|
||||
ASSERT_TRUE(Bob.Start(logic.get(), netLoop, BobPort));
|
||||
|
||||
ASSERT_TRUE(Alice.link->TryEstablishTo(Bob.GetRC()));
|
||||
|
||||
RunMainloop();
|
||||
ASSERT_TRUE(Alice.gotLIM);
|
||||
ASSERT_TRUE(Bob.gotLIM);
|
||||
ASSERT_TRUE(success);
|
||||
}
|
||||
|
||||
TEST_F(LinkLayerTest, TestIWPAliceConnectToBob)
|
||||
{
|
||||
Alice.link = llarp::iwp::NewServer(
|
||||
&crypto, Alice.encryptionKey,
|
||||
[&]() -> const llarp::RouterContact& { return Alice.GetRC(); },
|
||||
[&](llarp::ILinkSession* s, llarp_buffer_t buf) -> bool {
|
||||
if(Alice.gotLIM)
|
||||
{
|
||||
return AliceGotMessage(buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
llarp::LinkIntroMessage msg;
|
||||
if(!msg.BDecode(&buf))
|
||||
return false;
|
||||
if(!s->GotLIM(&msg))
|
||||
return false;
|
||||
Alice.gotLIM = true;
|
||||
return true;
|
||||
}
|
||||
},
|
||||
[&](llarp::RouterContact rc) {
|
||||
ASSERT_EQ(rc, Bob.GetRC());
|
||||
llarp::LogInfo("alice established with bob");
|
||||
},
|
||||
[&](llarp::RouterContact, llarp::RouterContact) -> bool { return true; },
|
||||
[&](llarp::Signature& sig, llarp_buffer_t buf) -> bool {
|
||||
return crypto.sign(sig, Alice.signingKey, buf);
|
||||
},
|
||||
[&](llarp::ILinkSession* session) {
|
||||
ASSERT_FALSE(session->IsEstablished());
|
||||
Stop();
|
||||
},
|
||||
[&](llarp::RouterID router) { ASSERT_EQ(router, Bob.GetRouterID()); });
|
||||
|
||||
auto sendDiscardMessage = [](llarp::ILinkSession* s) -> bool {
|
||||
// send discard message in reply to complete unit test
|
||||
byte_t tmp[32] = {0};
|
||||
auto otherBuf = llarp::StackBuffer< decltype(tmp) >(tmp);
|
||||
llarp::DiscardMessage discard;
|
||||
if(!discard.BEncode(&otherBuf))
|
||||
return false;
|
||||
otherBuf.sz = otherBuf.cur - otherBuf.base;
|
||||
otherBuf.cur = otherBuf.base;
|
||||
return s->SendMessageBuffer(otherBuf);
|
||||
};
|
||||
|
||||
Bob.link = llarp::iwp::NewServer(
|
||||
&crypto, Bob.encryptionKey,
|
||||
[&]() -> const llarp::RouterContact& { return Bob.GetRC(); },
|
||||
[&](llarp::ILinkSession* s, llarp_buffer_t buf) -> bool {
|
||||
llarp::LinkIntroMessage msg;
|
||||
if(!msg.BDecode(&buf))
|
||||
return false;
|
||||
if(!s->GotLIM(&msg))
|
||||
return false;
|
||||
Bob.gotLIM = true;
|
||||
return true;
|
||||
},
|
||||
[&](llarp::RouterContact rc) {
|
||||
ASSERT_EQ(rc, Alice.GetRC());
|
||||
llarp::LogInfo("bob established with alice");
|
||||
Bob.link->VisitSessionByPubkey(Alice.GetRC().pubkey.as_array(),
|
||||
sendDiscardMessage);
|
||||
},
|
||||
[&](llarp::RouterContact, llarp::RouterContact) -> bool { return true; },
|
||||
[&](llarp::Signature& sig, llarp_buffer_t buf) -> bool {
|
||||
return crypto.sign(sig, Bob.signingKey, buf);
|
||||
},
|
||||
[&](llarp::ILinkSession* session) {
|
||||
ASSERT_FALSE(session->IsEstablished());
|
||||
},
|
||||
[&](llarp::RouterID router) { ASSERT_EQ(router, Alice.GetRouterID()); });
|
||||
|
||||
ASSERT_TRUE(Alice.Start(logic.get(), netLoop, AlicePort));
|
||||
ASSERT_TRUE(Bob.Start(logic.get(), netLoop, BobPort));
|
||||
|
||||
ASSERT_TRUE(Alice.link->TryEstablishTo(Bob.GetRC()));
|
||||
|
||||
RunMainloop();
|
||||
ASSERT_TRUE(Alice.gotLIM);
|
||||
ASSERT_TRUE(Bob.gotLIM);
|
||||
ASSERT_TRUE(success);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
struct DTLSTest : public ::testing::Test
|
||||
{
|
||||
};
|
||||
|
||||
TEST_F(DTLSTest, TestAliceConnectToBob)
|
||||
{
|
||||
}
|
Loading…
Reference in New Issue