mirror of https://github.com/oxen-io/lokinet
remove old out of date documentation
parent
5890c99a81
commit
29df7bec74
@ -1,263 +0,0 @@
|
||||
|
||||
LLARP Traffic Routing Protocol (LTRP)
|
||||
|
||||
LRTP is a protocol that instructs how to route hidden service traffic on LLARP
|
||||
based networks.
|
||||
|
||||
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].
|
||||
|
||||
Overview:
|
||||
|
||||
LRTP is a message oriented data delivery and receival protocol for hidden
|
||||
service traffic. All structures are BitTorrent Encoded dictionaries sent
|
||||
over TCP.
|
||||
|
||||
all structures are bencoded when sent over the networks.
|
||||
In this document they are provided in JSON for ease of display.
|
||||
|
||||
message format:
|
||||
|
||||
<2 bytes length (N)>
|
||||
<N bytes of data>
|
||||
|
||||
|
||||
Nouns (data structures):
|
||||
|
||||
Path: information about a path that we have built
|
||||
|
||||
{
|
||||
H: [router_id_32_bytes, router_id_32_bytes, router_id_32_bytes, router_id_32_bytes],
|
||||
R: "<16 bytes local rxid>",
|
||||
T: "<16 bytes local txid>"
|
||||
}
|
||||
|
||||
Introduction: a hidden service introduction
|
||||
|
||||
{
|
||||
E: expiration_ms_since_epoch_uint64,
|
||||
L: advertised_latency_ms_uint64,
|
||||
P: "<16 bytes pathid>",
|
||||
R: "<32 bytes RouterID>"
|
||||
}
|
||||
|
||||
ServiceInfo: public key info for hidden service address
|
||||
|
||||
{
|
||||
A: "<32 bytes .loki address>",
|
||||
E: "<32 bytes public encryption key>",
|
||||
S: "<32 bytes public signing key>"
|
||||
}
|
||||
|
||||
IntroSet: information about an introduction set from the network
|
||||
|
||||
{
|
||||
E: expires_at_timestamp_ms_since_epoch_uint64,
|
||||
I: [Intro0, Intro1, ... IntroN],
|
||||
S: ServiceInfo
|
||||
}
|
||||
|
||||
Converstation: information about a loki network converstation
|
||||
|
||||
{
|
||||
L: "<32 bytes loki address provided if a loki address>",
|
||||
S: "<32 bytes snode address provided if a snode address>",
|
||||
T: "<16 bytes convo tag>"
|
||||
}
|
||||
|
||||
SessionInfo: information about our current session
|
||||
|
||||
{
|
||||
I: [inbound,convos,here],
|
||||
O: [outbound,covos,here],
|
||||
P: [Path0, Path1, .... PathN],
|
||||
S: Current IntroSet,
|
||||
}
|
||||
|
||||
Verbs (methods):
|
||||
|
||||
session requset (C->S)
|
||||
|
||||
the first message sent by the client
|
||||
|
||||
{
|
||||
A: "session",
|
||||
B: "<8 bytes random>",
|
||||
T: milliseconds_since_epoch_client_now_uint64,
|
||||
Y: 0,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
session accept (S->C)
|
||||
|
||||
sent in reply to a session message to indicate session accept and give
|
||||
a session cookie to the client.
|
||||
|
||||
{
|
||||
A: "session-reply",
|
||||
B: "<8 bytes random from session request>",
|
||||
C: "<16 bytes session cookie>",
|
||||
T: milliseconds_since_epoch_server_now_uint64,
|
||||
Y: 0,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
session reject (S->C)
|
||||
|
||||
sent in reply to a session message to indicate session rejection
|
||||
|
||||
{
|
||||
A: "session-reject",
|
||||
B: "<8 bytes random from session request>",
|
||||
R: "<variable length utf-8 encoded bytes human readable reason here>",
|
||||
T: milliseconds_since_epoch_server_now_uint64,
|
||||
Y: 0,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
spawn a hidden service (C->S)
|
||||
|
||||
only one hidden service can be made per session
|
||||
|
||||
{
|
||||
A: "spawn",
|
||||
C: "<16 bytes session cookie>",
|
||||
O: config_options_dict,
|
||||
Y: 1,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
inform that we have spawned a new hidden service endpoint (S->C)
|
||||
|
||||
{
|
||||
A: "spawn-reply",
|
||||
C: "<16 bytes session cookie>",
|
||||
S: ServiceInfo,
|
||||
Y: 1,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
inform that we have not spaned a new hidden service endpint (S->C)
|
||||
|
||||
after sending this message the server closes the connection
|
||||
|
||||
{
|
||||
A: "spawn-reject",
|
||||
C: "<16 bytes session cookie>",
|
||||
E: "<error message goes here>",
|
||||
Y: 1,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
create a new convseration on a loki/snode address (C->S)
|
||||
|
||||
{
|
||||
A: "start-convo",
|
||||
B: "<8 bytes random>",
|
||||
C: "<16 bytes session cookie>",
|
||||
R: "human readable remote address .snode/.loki",
|
||||
Y: sequence_num_uint64,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
sent in reply to a make-convo message to indicate rejection (S->C)
|
||||
|
||||
{
|
||||
A: "start-convo-reject",
|
||||
B: "<8 bytes random from start-convo message>",
|
||||
C: "<16 bytes session cookie>",
|
||||
S: status_bitmask_uint,
|
||||
Y: sequence_num_uint64,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
sent in reply to a make-convo message to indicate that we have accepted this
|
||||
new conversation and gives the convo tag it uses.
|
||||
|
||||
{
|
||||
A: "start-convo-accept",
|
||||
B: "<8 bytes random from start-convo message>",
|
||||
C: "<16 bytes session cookie>",
|
||||
Y: sequence_num_uint64,
|
||||
Z: "<32 bytes keyed hash>
|
||||
}
|
||||
|
||||
infrom the status of a converstation on a loki address (S->C)
|
||||
|
||||
for an outbund conversation it is sent every time the status bitmask changes.
|
||||
for inbound convos it is sent immediately when a new inbound conversation is made.
|
||||
|
||||
S bit 0 (LSB): we found the introset/endpoint for (set by outbound)
|
||||
S bit 1: we found the router to align on (set by outbound)
|
||||
S bit 2: we have a path right now (set by outbound)
|
||||
S bit 3: we have made the converstation (set by both)
|
||||
S bit 4: we are an inbound converstation (set by inbound)
|
||||
|
||||
{
|
||||
A: "convo-status",
|
||||
C: "<16 bytes session cookie>",
|
||||
R: "human readable address .snode/.loki",
|
||||
S: bitmask_status_uint64,
|
||||
T: "<16 bytes convotag>",
|
||||
Y: sequence_num_uint64,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
send or recieve authenticated data to or from the network (bidi)
|
||||
|
||||
protocol numbers are
|
||||
|
||||
1 for ipv4
|
||||
2 for ipv6
|
||||
|
||||
{
|
||||
A: "data",
|
||||
C: "<16 bytes session cookie>",
|
||||
T: "<16 bytes convotag>",
|
||||
W: protocol_number_uint,
|
||||
X: "<N bytes payload>",
|
||||
Y: sequence_num_uint64,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
get session information (C->S)
|
||||
|
||||
{
|
||||
A: "info",
|
||||
C: "<16 bytes session cookie>",
|
||||
Y: sequence_num_uint64,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
|
||||
session information update (S->C)
|
||||
|
||||
sent in reply to a get session information message
|
||||
|
||||
{
|
||||
A: "info-reply",
|
||||
C: "<16 bytes session cookie>",
|
||||
I: hiddenserviceinfo,
|
||||
Y: sequence_num_uint64,
|
||||
Z: "<32 bytes keyed hash>"
|
||||
}
|
||||
|
||||
Protocol Flow:
|
||||
|
||||
all messages have an A, C, Y and Z value
|
||||
|
||||
A is the function name being called
|
||||
|
||||
C is the session cookie indicating the current session
|
||||
|
||||
Y is the 64 bit message sequence number as an integer
|
||||
|
||||
Z is the keyed hash computed by MDS(BE(msg), K) where K is HS(api_password)
|
||||
with the msg.Z being set to 32 bytes of \x00
|
||||
|
||||
both client and server MUST know a variable length string api_password used to
|
||||
authenticate access to the api subsystem.
|
||||
|
||||
the Y value is incremented by 1 for each direction every time the sender sends
|
||||
a message in that direction.
|
@ -1,117 +0,0 @@
|
||||
|
||||
* lokinet components
|
||||
|
||||
** basic data structures
|
||||
*** AlignedBuffer
|
||||
*** RouterContact
|
||||
**** self signed router descriptor
|
||||
*** Crypto key types
|
||||
|
||||
|
||||
** network / threading / sync utilities
|
||||
*** threadpool
|
||||
*** logic (single thread threadpool)
|
||||
|
||||
** configuration
|
||||
*** ini parser
|
||||
*** llarp::Configuration
|
||||
|
||||
** cryptography
|
||||
*** llarp::Crypto interface
|
||||
**** libsodium / sntrup implementation
|
||||
*** llarp::CryptoManager
|
||||
**** crypto implementation signleton manager
|
||||
|
||||
** nodedb
|
||||
*** llarp_nodedb
|
||||
**** holds many RouterContacts loaded from disk in memory
|
||||
**** uses a filesystem skiplist for on disk storage
|
||||
**** stores in a std::unordered_map addressable via public identity key
|
||||
|
||||
** event loop
|
||||
*** llarp_event_loop
|
||||
**** udp socket read/write
|
||||
**** network interface packet read/write
|
||||
**** stream connection (outbound stream)
|
||||
**** stream acceptor (inbound stream)
|
||||
|
||||
** link layer message transport:
|
||||
*** ILinkSession
|
||||
**** the interface for an entity that is single session with relay
|
||||
**** responsible for delivery recieval of link layer messages in full
|
||||
*** ILinkLayer
|
||||
**** bound to an address / interface
|
||||
**** has a direction, inbound / outbound
|
||||
**** distinctly identified by the union of interface and direction
|
||||
**** Holds many ILinkSessions bound on the distinctly idenitfied direction/interface
|
||||
|
||||
|
||||
** link layer messages
|
||||
*** link layer message parser
|
||||
**** parses buffers as bencoded dicts
|
||||
*** link layer message handler
|
||||
**** handles each type of link layer message
|
||||
|
||||
|
||||
** IHopHandler
|
||||
*** llarp::PathHopConfig
|
||||
**** txid, rxid, shared secret at hop
|
||||
*** llarp::path::Path
|
||||
**** a built path or a path being built
|
||||
**** owns a std::vector<PathHopConfig> for each hop's info
|
||||
*** TransitHop
|
||||
**** a single hop on a built path
|
||||
**** has txid, rxid, shared secret, hash of shared secret
|
||||
|
||||
|
||||
** pathset
|
||||
*** path::Builder
|
||||
**** builds and maintains a set of paths for a common use
|
||||
|
||||
|
||||
** routing layer message router
|
||||
*** routing::IMessageHandler
|
||||
**** interface for routing layer message processing
|
||||
**** transit hops implement this if they are an endpoint
|
||||
**** path::Path implement this always
|
||||
|
||||
|
||||
** dht "layer" / rc gossiper
|
||||
*** TODO rewrite/refactor
|
||||
|
||||
** hidden service data structures
|
||||
*** IntroSet
|
||||
**** decrypted plaintext hidden service descriptor
|
||||
*** EncryptedIntroSet
|
||||
**** public encrpyted / signed version of IntroSet
|
||||
|
||||
|
||||
** service endpoint / outbound context connectivitybackend
|
||||
*** service::Endpoint
|
||||
**** backend for sending/recieving packets over the hidden service protocol layer
|
||||
**** kitchen sink
|
||||
*** service::SendContext
|
||||
**** interface type for sending to a resource on the network
|
||||
*** service::OutboundContext
|
||||
**** implements SendContext extends path::Builder and path::PathSet
|
||||
**** for maintaining a pathset that aligns on an introset's intros
|
||||
~
|
||||
|
||||
** snode / exit connectivity backend
|
||||
*** exit::BaseSession
|
||||
**** extends path::Builder
|
||||
**** obtains an exit/snode session from the router they are aligning to
|
||||
*** exit::Endpoint
|
||||
**** snode/exit side of an exit::Session
|
||||
|
||||
** snapp / exit / mobile / null frontend handlers
|
||||
*** handlers::TunEndpoint
|
||||
**** extends service::Endpoint
|
||||
**** provides tun interface frontend for hidden service backend
|
||||
*** handlers::ExitEndpoint
|
||||
**** provides tun interface frontend for exit/snode backend
|
||||
|
||||
|
||||
** outbound message dispatcher
|
||||
*** TODO tom please document these
|
||||
|
@ -1,39 +0,0 @@
|
||||
|
||||
|
||||
cryptography:
|
||||
|
||||
H(x) is 512 bit blake2b digest of x
|
||||
HS(x) is 256 bit blake2b digest of x
|
||||
MD(x, k) is 512 bit blake2b hmac of x with secret value k
|
||||
MDS(x, k) is 256 bit blake2b hmac of x with secret value k
|
||||
SE(k, n, x) is chacha20 encrypt data x using symettric key k and nounce n
|
||||
SD(k, n, x) is chacha20 dectypt data x using symettric key k and nounce n
|
||||
S(k, x) is sign x with ed25519 using secret key k
|
||||
EDKG() is generate ec keypair (p, s) public key p (32 bytes), secret key s (64 bytes)
|
||||
V(k, x, sig) is verify x data using signature sig using public key k
|
||||
EDDH(a, b) is curve25519 scalar multiplication of a and b
|
||||
HKE(a, b, x) is hashed key exchange between a and b using a secret key x HS(a + b + EDDH(x, b))
|
||||
TKE(a, b, x, n) is a transport shared secret kdf using MDS(n, HKE(a, b, x))
|
||||
|
||||
when A is client and B is server where n is a 32 bytes shared random
|
||||
|
||||
client computes TKE(A.pk, B.pk, A.sk, n)
|
||||
server computes TKE(A.pk, B.pk, B.sk, n)
|
||||
|
||||
PDH(a, b, x) is path shared secret generation HS(a + b + EDDH(x, b))
|
||||
|
||||
PKE(a, b, x, n) is a path shared secret kdf using MDS(n, PDH(a, b, x))
|
||||
|
||||
given A is the path creator and B is a hop in the path and n is 32 bytes shared random
|
||||
|
||||
A computes PKE(A.pk, B.pk, A.sk, n) as S_a
|
||||
B computes PKE(A.pk, B.pk, B.sk, n) as S_b
|
||||
|
||||
S_a is equal to S_b
|
||||
|
||||
RAND(n) is n random bytes
|
||||
|
||||
PQKG() is generate a sntrup4591761 key pair (sk, pk)
|
||||
PQKE_A(pk) is alice generating (x, k) where x is sntrup4591761 ciphertext block and k is the session key
|
||||
PQKE_B(x, sk) is bob calculating k where x is sntrup4591761 ciphertext block, sk is bob's sntrup4591761 secretkey and k is the session key
|
||||
|
@ -1,153 +0,0 @@
|
||||
DHT messages
|
||||
|
||||
DHT messages can be either wrapped in a LIDM message or sent anonymously over a path inside a DHT routing message
|
||||
|
||||
The distance function is A xor B (traditional kademlia)
|
||||
|
||||
The dht implements both iterative and recursive lookups.
|
||||
|
||||
Recursive lookups forward the request to a node closer if not found.
|
||||
Iterative lookups return the key and of the DHT node who is closer.
|
||||
|
||||
In the case of iterative FRCM the RC of the closer router is provided.
|
||||
In the case of iterative FIM the pubkey of a dht node who is closer is provided in the GIM.
|
||||
|
||||
find introduction message (FIM)
|
||||
|
||||
variant 1: find an IS by SA
|
||||
|
||||
{
|
||||
A: "F",
|
||||
R: 0 for iterative and 1 for recurisve,
|
||||
S: "<32 bytes SA>",
|
||||
T: transaction_id_uint64,
|
||||
V: 0
|
||||
}
|
||||
|
||||
variant 2: recursively find many IS in a tag
|
||||
|
||||
{
|
||||
A: "F",
|
||||
E: [list, of, excluded, SA],
|
||||
R: 0 for iterative and 1 for recurisve,
|
||||
N: "<16 bytes topic tag>",
|
||||
T: transaction_id_uint64,
|
||||
V: 0
|
||||
}
|
||||
|
||||
exclude adding service addresses in E if present
|
||||
|
||||
|
||||
got introduction message (GIM)
|
||||
|
||||
sent in reply to FIM containing the result of the search
|
||||
sent in reply to PIM to acknoledge the publishing of an IS
|
||||
|
||||
{
|
||||
A: "G",
|
||||
I: [IS, IS, IS, ...],
|
||||
K: "<32 bytes public key of router who is closer, provided ONLY if FIM.R is 0>",
|
||||
T: transaction_id_uint64,
|
||||
V: 0,
|
||||
}
|
||||
|
||||
The I value MUST NOT contain more than 4 IS.
|
||||
The I value MUST contain either 1 or 0 IS for PIM and FIM variant 1.
|
||||
|
||||
|
||||
publish introduction message (PIM)
|
||||
|
||||
publish one IS to the DHT.
|
||||
|
||||
version 0 uses the SA of the IS as the keyspace location.
|
||||
|
||||
in the future the location will be determined by the dht kdf
|
||||
which uses a shared random source to obfuscate keyspace location.
|
||||
|
||||
|
||||
R is currently set to 0 by the sender.
|
||||
|
||||
{
|
||||
A: "I",
|
||||
I: IS,
|
||||
R: random_walk_counter,
|
||||
S: optional member 0 for immediate store otherwise non zero,
|
||||
T: transaction_id_uint64,
|
||||
V: 0
|
||||
}
|
||||
|
||||
if R is greater than 0, decrement R and forward the request to a random DHT
|
||||
node without storing the IS.
|
||||
As of protocol version 0, R is always 0.
|
||||
|
||||
If S is provided store the IS for later lookup unconditionally, then
|
||||
decrement S by 1 and forward to dht peer who is next closest to
|
||||
the SA of the IS. If S is greater than 3, don't store the IS and
|
||||
discard this message.
|
||||
|
||||
acknoledge introduction message (AIM)
|
||||
|
||||
acknoledge the publishing of an introduction
|
||||
|
||||
{
|
||||
A: "A",
|
||||
P: published_to_counter,
|
||||
T: transaction_id_uint64,
|
||||
V: 0
|
||||
}
|
||||
|
||||
increment P by 1 and forward to requester
|
||||
|
||||
|
||||
find router contact message (FRCM)
|
||||
|
||||
find a router by long term RC.k public key
|
||||
|
||||
{
|
||||
A: "R",
|
||||
E: 0 or 1 if exploritory lookup,
|
||||
I: 0 or 1 if iterative lookup,
|
||||
K: "<32 byte public key of router>",
|
||||
T: transaction_id_uint64,
|
||||
V: 0
|
||||
}
|
||||
|
||||
if E is provided and non zero then return E dht nodes that are closest to K
|
||||
if I is provided and non zero then this request is considered an iterative lookup
|
||||
during an iterative lookup the response's GRCM.K is set to the pubkey of the router closer in key space.
|
||||
during a recursive lookup the request is forwarded to a router who is closer in
|
||||
keyspace to K.
|
||||
If we have no peers that are closer to K and we don't have the RC known locally
|
||||
we reply with a GRCM whose R value is emtpy.
|
||||
In any case if we have a locally known RC with pubkey equal to K reply with
|
||||
a GRCM with 1 RC in the R value corrisponding to that locally known RC.
|
||||
|
||||
got router contact message (GRCM)
|
||||
|
||||
R is a list containing a single RC if found or is an empty list if not found
|
||||
sent in reply to FRCM only
|
||||
|
||||
{
|
||||
A: "S",
|
||||
K: "<32 bytes public identity key of router closer, provided ONLY if FRCM.I is 1>",
|
||||
R: [RC],
|
||||
T: transaction_id_uint64,
|
||||
V: 0
|
||||
}
|
||||
|
||||
in response to an exploritory router lookup, where FRCM.E is provided and non zero.
|
||||
|
||||
{
|
||||
A: "S",
|
||||
N: [list, of, router, publickeys, near, K],
|
||||
T: transaction_id_uint64,
|
||||
V: 0
|
||||
}
|
||||
|
||||
sent in reply to a dht request to indicate transaction timeout
|
||||
|
||||
{
|
||||
A: "T",
|
||||
T: transaction_id_uint64,
|
||||
V: 0
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
llarp's dht is a recusrive kademlia dht with optional request proxying via paths for requester anonymization.
|
||||
|
||||
dht is separated into 2 different networks, one for router contacts, one for introsets.
|
||||
|
||||
|
||||
|
||||
|
||||
format for consesus propagation messages:
|
||||
|
||||
keys: A, H, K, N, O, T, U, V
|
||||
|
||||
concensus request messages
|
||||
|
||||
requester requests current table hash, H,N,O is set to zeros if not known
|
||||
|
||||
C -> S
|
||||
|
||||
{
|
||||
A: "C",
|
||||
H: "<32 byte last hash of consensus table>",
|
||||
N: uint64_number_of_entries_to_request,
|
||||
O: uint64_offset_in_table,
|
||||
T: uint64_txid,
|
||||
V: []
|
||||
}
|
||||
|
||||
|
||||
when H or N is set to zero from the requester, they are requesting the current consensus table's hash
|
||||
|
||||
consensus response message is as follows for a zero H or N value
|
||||
|
||||
S -> C
|
||||
|
||||
{
|
||||
A: "C",
|
||||
H: "<32 byte hash of current consensus table>",
|
||||
N: uint64_number_of_entries_in_table,
|
||||
T: uint64_txid,
|
||||
U: uint64_ms_next_update_required,
|
||||
V: [proto, major, minor, patch]
|
||||
}
|
||||
|
||||
requester requests a part of the current table for hash H
|
||||
|
||||
N must be less than or equal to 512
|
||||
|
||||
C -> S
|
||||
|
||||
{
|
||||
A: "C",
|
||||
H: "<32 bytes current consensus table hash>",
|
||||
N: 256,
|
||||
O: 512,
|
||||
T: uint64_txid,
|
||||
V: []
|
||||
}
|
||||
|
||||
consensus response message for routers 512 to 512 + 256
|
||||
|
||||
S -> C
|
||||
|
||||
{
|
||||
A: "C",
|
||||
H: "<32 bytes current concensus table hash>",
|
||||
K: [list, of, N, pubkeys, from, request, starting, at, position, O],
|
||||
T: uint64_txid,
|
||||
V: [proto, major, minor, patch]
|
||||
}
|
||||
|
||||
consensus table is a concatination of all public keys in lexigraphical order.
|
||||
|
||||
the hash function in use is 256 bit blake2
|
||||
|
||||
|
||||
|
||||
gossip RC message
|
||||
|
||||
broadcast style RC publish message. sent to all peers infrequently.
|
||||
|
||||
it is really an unwarrented GRCM, propagate to all peers.
|
||||
|
||||
{
|
||||
A: "S",
|
||||
R: [RC],
|
||||
T: 0,
|
||||
V: proto
|
||||
}
|
||||
|
||||
replays are dropped using a decaying hashset or decaying bloom filter.
|
||||
|
||||
|
||||
|
||||
the introset dht has 3 message: GetIntroSet Message (GIM), PutIntroSet Message (PIM), FoundIntroSet Message (FIM)
|
||||
|
@ -1,147 +0,0 @@
|
||||
THIS DOCUMENT IS 3 YEARS OUT OF DATE AND NEEDS TO BE REWRITTEN
|
||||
|
||||
|
||||
LLARP - Low Latency Anon Routing Protocol
|
||||
|
||||
TL;DR edition: an onion router with a tun interface for transporting ip packets
|
||||
anonymously between you and the internet and internally inside itself to other users.
|
||||
|
||||
|
||||
This document describes the high level outline of LLARP, specific all the
|
||||
project's goals, non-goals and network architecture from a bird's eye view.
|
||||
|
||||
|
||||
Preface:
|
||||
|
||||
|
||||
Working on I2P has been a really big learning experience for everyone involved.
|
||||
After much deliberation I have decided to start making a "next generation" onion
|
||||
routing protocol. Specifically LLARP is (currently) a research project
|
||||
to explore the question:
|
||||
|
||||
|
||||
"What if I2P was made in the current year (2018)? What would be different?"
|
||||
|
||||
|
||||
Project Non Goals:
|
||||
|
||||
|
||||
This project does not attempt to solve traffic shape correlation or active nation
|
||||
state sponsored network attacks. The former is an inherit property of low latency
|
||||
computer networks that I personally do not think is possible to properly fully
|
||||
"solve". The latter is a threat that lies outside the scope of what the current
|
||||
toolset that is available to me at the moment provides.
|
||||
|
||||
|
||||
This project does not attempt to be a magical application level cure-all for
|
||||
application or end user security. At the end of the day that is a problem that
|
||||
exists between chair and keyboard.
|
||||
|
||||
|
||||
The Single Project Goal:
|
||||
|
||||
|
||||
LLARP is a protocol suite meant to anonymize IP by providing an anonymous
|
||||
network level (IPv4/IPv6) tunnel broker for both "hidden services" and
|
||||
communication back to "the clearnet" (the normal internet). Both hidden service
|
||||
and clearnet communication MUST permit both outbound and inbound traffic on the
|
||||
network level without any NAT (except for IPv4 in which NAT is permitted due to
|
||||
lack of address availability).
|
||||
|
||||
|
||||
In short We want to permit both anonymous exit and entry network level traffic
|
||||
between LLARP enabled networks and the internet.
|
||||
|
||||
|
||||
Rationale for starting over:
|
||||
|
||||
|
||||
Despite Tor Project's best efforts to popularize Tor use, Tor2Web seems to be
|
||||
widely popular for people who do not wish to opt into the ecosystem. My proposed
|
||||
solution would be to permit inbound traffic from "exit nodes" in addition to
|
||||
allowing outbound exit traffic. I have no ideas on how this could be done with
|
||||
the existing protocols in Tor or if it is possible or advisable to attempt such
|
||||
as I am not familiar with their ecosystem.
|
||||
|
||||
|
||||
I2P could have been used as a medium for encrypted anonymous IP transit but the
|
||||
current network has issues with latency and throughput. Rebasing I2P atop more
|
||||
modern cryptography has been going on internally inside I2P for at least 5 years
|
||||
with less progress than desired. Like some before me, I have concluded that it
|
||||
would be faster to redo the whole stack "the right way" than to wait for I2P to
|
||||
finish rebasing. That being said, nothing is preventing I2P from be used for
|
||||
encrypted anonymous IP transit traffic in a future where I2P finishes their
|
||||
protocol migrations, I just don't want to wait.
|
||||
|
||||
|
||||
In short, I want to take the "best parts" from Tor and I2P and make a new
|
||||
protocol suite.
|
||||
|
||||
|
||||
For both Tor and I2P I have 2 categories for the attributes they have.
|
||||
|
||||
|
||||
the good
|
||||
the bad and the ugly
|
||||
|
||||
|
||||
The good (I2P):
|
||||
|
||||
|
||||
I2P aims to provide an anonymous unspoofable load balanced network layer.
|
||||
|
||||
|
||||
I want this feature.
|
||||
|
||||
|
||||
I2P is trust agile, it does not have any hard coded trusts in its network
|
||||
architecture. Even network boostrap can be done from a single router if the user
|
||||
desires to (albeit this is currently ill advised).
|
||||
|
||||
|
||||
I want this feature.
|
||||
|
||||
|
||||
The good (Tor):
|
||||
|
||||
|
||||
Tor embraces the reality of the current internet infrastructure by having a
|
||||
client/server architecture. This allows very low barriers of entry in using the
|
||||
Tor network and a higher barrier of entry for contributing routing
|
||||
infrastructure. This promotes a healthy network shape of high capacity servers
|
||||
serving low capacity clients that "dangle off of the side" of the network.
|
||||
|
||||
|
||||
I want this feature.
|
||||
|
||||
|
||||
The bad and the ugly (I2P):
|
||||
|
||||
|
||||
Bad: I2P uses old cryptography, specially 2048 bit ElGamal using non standard primes.
|
||||
The use of ElGamal is so pervasive throughout the I2P protocol stack that it
|
||||
exists at every level of it. Removing it is a massive task that is taking a long
|
||||
LONG time.
|
||||
|
||||
|
||||
I don't want this feature.
|
||||
|
||||
|
||||
Ugly: I2P cannot currently mitigate most sybil attacks with their current network
|
||||
architecture. Recently I2P has added some blocklist solutions signed by release
|
||||
signers but this probably won't scale in the event of a "big" attack. In
|
||||
addition I2P isn't staffed for such attacks either.
|
||||
|
||||
|
||||
This is a hard problem to solve that the Loki network may be able to help
|
||||
with by creating a financial barrier to running multiple a relays.
|
||||
|
||||
|
||||
|
||||
The bad and the ugly (Tor):
|
||||
|
||||
|
||||
Bad: Tor is strictly TCP oriented.
|
||||
I don't want this feature.
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
Lokinet needs certain capabilities to run to set up a virtual network interface and provide a DNS server. The preferred approach to using this is through the linux capabilities mechanism, which allows assigning limited capabilities without needing to run the entire process as root.
|
||||
|
||||
There are two main ways to do this:
|
||||
|
||||
1. If you are running lokinet via an init system such as systemd, you can specify the capabilities in the service file by adding:
|
||||
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
|
||||
into the [Service] section of the systemd service file. This will assign the necessary permissions when running the process and allow lokinet to work while running as a non-root user.
|
||||
|
||||
2. You can set the capabilities on the binary by using the setcap program (if not available you may need to install libcap2-bin on Debian/Ubuntu-based systems) and running:
|
||||
|
||||
setcap cap_net_admin,cap_net_bind_service=+eip lokinet
|
||||
|
||||
This grants the permissions whenever the lokinet binary is executed.
|
@ -1,102 +0,0 @@
|
||||
digraph {
|
||||
constants -> util;
|
||||
|
||||
crypto -> constants;
|
||||
crypto -> llarp;
|
||||
crypto -> util;
|
||||
|
||||
dht -> crypto;
|
||||
dht -> messages;
|
||||
dht -> llarp;
|
||||
dht -> path;
|
||||
dht -> routing;
|
||||
dht -> service;
|
||||
dht -> util;
|
||||
|
||||
dns -> crypto;
|
||||
dns -> ev;
|
||||
dns -> handlers;
|
||||
dns -> llarp;
|
||||
dns -> net;
|
||||
dns -> service;
|
||||
dns -> util;
|
||||
|
||||
ev -> net;
|
||||
ev -> util;
|
||||
|
||||
exit -> crypto;
|
||||
exit -> handlers;
|
||||
exit -> messages;
|
||||
exit -> net;
|
||||
exit -> path;
|
||||
exit -> routing;
|
||||
exit -> util;
|
||||
|
||||
handlers -> dns;
|
||||
handlers -> ev;
|
||||
handlers -> exit;
|
||||
handlers -> net;
|
||||
handlers -> service;
|
||||
handlers -> util;
|
||||
|
||||
link -> constants;
|
||||
link -> crypto;
|
||||
link -> ev;
|
||||
link -> messages;
|
||||
link -> net;
|
||||
link -> util;
|
||||
|
||||
messages -> crypto;
|
||||
messages -> dht;
|
||||
messages -> exit;
|
||||
messages -> link;
|
||||
messages -> llarp;
|
||||
messages -> path;
|
||||
messages -> routing;
|
||||
messages -> service;
|
||||
messages -> util;
|
||||
|
||||
net -> crypto;
|
||||
net -> util;
|
||||
|
||||
path -> crypto;
|
||||
path -> dht;
|
||||
path -> llarp;
|
||||
path -> messages;
|
||||
path -> routing;
|
||||
path -> service;
|
||||
path -> util;
|
||||
|
||||
routing -> llarp;
|
||||
routing -> messages;
|
||||
routing -> path;
|
||||
routing -> util;
|
||||
|
||||
service -> crypto;
|
||||
service -> dht;
|
||||
service -> ev;
|
||||
service -> exit;
|
||||
service -> handlers;
|
||||
service -> messages;
|
||||
service -> net;
|
||||
service -> path;
|
||||
service -> routing;
|
||||
service -> util;
|
||||
|
||||
util -> constants;
|
||||
|
||||
llarp -> constants;
|
||||
llarp -> crypto;
|
||||
llarp -> dht;
|
||||
llarp -> dns;
|
||||
llarp -> ev;
|
||||
llarp -> exit;
|
||||
llarp -> handlers;
|
||||
llarp -> link;
|
||||
llarp -> messages;
|
||||
llarp -> net;
|
||||
llarp -> path;
|
||||
llarp -> routing;
|
||||
llarp -> service;
|
||||
llarp -> util;
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1000 368" style="enable-background:new 0 0 1000 368;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:url(#SVGID_1_);}
|
||||
.st2{fill:#333333;}
|
||||
.st3{fill:url(#SVGID_2_);}
|
||||
.st4{fill:url(#SVGID_3_);}
|
||||
.st5{fill:#00263A;}
|
||||
.st6{fill:url(#SVGID_4_);}
|
||||
.st7{fill:url(#SVGID_5_);}
|
||||
.st8{fill:url(#SVGID_6_);}
|
||||
.st9{fill:url(#SVGID_7_);}
|
||||
.st10{fill:url(#SVGID_8_);}
|
||||
.st11{fill:url(#SVGID_9_);}
|
||||
.st12{fill:url(#SVGID_10_);}
|
||||
.st13{fill:url(#SVGID_11_);}
|
||||
.st14{fill:url(#SVGID_12_);}
|
||||
.st15{fill:url(#SVGID_13_);}
|
||||
.st16{fill:url(#SVGID_14_);}
|
||||
.st17{fill:url(#SVGID_15_);}
|
||||
.st18{fill:url(#SVGID_16_);}
|
||||
.st19{fill:url(#SVGID_17_);}
|
||||
.st20{opacity:6.000000e-02;}
|
||||
.st21{opacity:4.000000e-02;fill:#FFFFFF;}
|
||||
.st22{opacity:7.000000e-02;fill:#FFFFFF;}
|
||||
.st23{fill:#008522;}
|
||||
.st24{fill:#78BE20;}
|
||||
.st25{fill:#005F61;}
|
||||
.st26{fill:url(#SVGID_18_);}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M366.6,78h37.1v178.9H497v32.7H366.6V78z"/>
|
||||
<path class="st0" d="M619.8,74.5C683.3,74.5,728,120.8,728,184c0,63.1-44.7,109.5-108.2,109.5c-63.5,0-108.2-46.3-108.2-109.5
|
||||
C511.6,120.8,556.3,74.5,619.8,74.5z M619.8,107.5c-42.8,0-70.1,32.7-70.1,76.5c0,43.5,27.3,76.5,70.1,76.5
|
||||
c42.5,0,70.1-33,70.1-76.5C689.9,140.2,662.3,107.5,619.8,107.5z"/>
|
||||
<path class="st0" d="M819.4,200.5L801,222v67.6h-37.1V78H801v100.9L883.8,78h46l-86,99.9l92.3,111.7h-45.7L819.4,200.5z"/>
|
||||
<path class="st0" d="M960.9,78H998v211.6h-37.1V78z"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="86.8402" y1="268.7968" x2="86.8402" y2="0.426">
|
||||
<stop offset="0" style="stop-color:#78BE20"/>
|
||||
<stop offset="0.1197" style="stop-color:#58AF21"/>
|
||||
<stop offset="0.3682" style="stop-color:#199122"/>
|
||||
<stop offset="0.486" style="stop-color:#008522"/>
|
||||
<stop offset="0.6925" style="stop-color:#007242"/>
|
||||
<stop offset="0.8806" style="stop-color:#006459"/>
|
||||
<stop offset="1" style="stop-color:#005F61"/>
|
||||
</linearGradient>
|
||||
<polygon class="st1" points="132.1,268.8 0.3,137 136.9,0.4 173.3,36.8 73.1,137 168.5,232.4 "/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="212.9564" y1="367.5197" x2="212.9564" y2="99.1484">
|
||||
<stop offset="0" style="stop-color:#78BE20"/>
|
||||
<stop offset="0.1197" style="stop-color:#58AF21"/>
|
||||
<stop offset="0.3682" style="stop-color:#199122"/>
|
||||
<stop offset="0.486" style="stop-color:#008522"/>
|
||||
<stop offset="0.6925" style="stop-color:#007242"/>
|
||||
<stop offset="0.8806" style="stop-color:#006459"/>
|
||||
<stop offset="1" style="stop-color:#005F61"/>
|
||||
</linearGradient>
|
||||
<polygon class="st3" points="162.9,367.5 126.5,331.1 226.7,230.9 131.3,135.6 167.7,99.1 299.5,230.9 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.9 KiB |
@ -1,70 +0,0 @@
|
||||
LokiNET admin api
|
||||
|
||||
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].
|
||||
|
||||
------
|
||||
|
||||
the admin api currently uses jsonrpc 2.0 over http
|
||||
|
||||
the methods currently provided are:
|
||||
|
||||
llarp.nodedb.rc.getbykey
|
||||
|
||||
get rc by public identity key
|
||||
|
||||
required parameters:
|
||||
|
||||
key: 32 bytes public identity key
|
||||
|
||||
returns:
|
||||
|
||||
a list of RCs (see protocol v0 spec) that have this public identity key
|
||||
usually 0 or 1 RCs
|
||||
|
||||
|
||||
llarp.nodedb.rc.getbycidr
|
||||
|
||||
get a list of RCs in an address range
|
||||
|
||||
required parameters:
|
||||
|
||||
cidr: ipv6 network cidr string, i.e. "::ffff.21.0.0.0/8" or "fc00::/7"
|
||||
limit: integer max number of items to fetch, zero or positive integer,
|
||||
if zero no limit.
|
||||
|
||||
returns:
|
||||
|
||||
a list of 0 to limit RCs that advertise themselves as being reachble via an
|
||||
address in the given CIDR.
|
||||
|
||||
|
||||
llarp.admin.sys.uptime (authentication required)
|
||||
|
||||
required paramters:
|
||||
|
||||
(none)
|
||||
|
||||
returns:
|
||||
|
||||
an integer milliseconds since unix epoch we've been online
|
||||
|
||||
llarp.admin.link.neighboors
|
||||
|
||||
get a list of connected service nodes on all links
|
||||
|
||||
required parameters:
|
||||
|
||||
(none)
|
||||
|
||||
returns:
|
||||
|
||||
list of 0 to N dicts in the following format:
|
||||
|
||||
{
|
||||
"connected" : uint64_milliseconds_timestamp_connected_at
|
||||
"ident" : "<64 hex encoded public identity key>",
|
||||
"laddr" : "local address",
|
||||
"raddr" : "remote address"
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
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)
|
||||
|
||||
608 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 576 bytes (h)]
|
||||
[ 32 bytes random nonce (n)]
|
||||
[ 544 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,903 +0,0 @@
|
||||
LLARP v0
|
||||
|
||||
LLARP (Low Latency Anon Routing Protocol) is a protocol for anonymizing senders and
|
||||
recipiants of encrypted messages sent over the internet without a centralised
|
||||
trusted party.
|
||||
|
||||
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].
|
||||
|
||||
basic structures:
|
||||
|
||||
all structures are key, value dictionaries encoded with bittorrent encoding
|
||||
notation:
|
||||
|
||||
a + b is a concatanated with b
|
||||
|
||||
a ^ b is a bitwise XOR b
|
||||
|
||||
x[a:b] is a memory slice of x from index a to b
|
||||
|
||||
BE(x) is bittorrent encode x
|
||||
|
||||
BD(x) is bittorrent decode x
|
||||
|
||||
{ a: b, y: z } is a dictionary with two keys a and y
|
||||
whose values are b and z respectively
|
||||
|
||||
[ a, b, c ... ] is a list containing a b c and more items in that order
|
||||
|
||||
"<description>" is a bytestring whose contents and length is described by the
|
||||
quoted value <description>
|
||||
|
||||
"<value>" * N is a bytestring containing the <value> concatenated N times.
|
||||
|
||||
cryptography:
|
||||
|
||||
see crypto_v0.txt
|
||||
|
||||
---
|
||||
|
||||
wire protocol
|
||||
|
||||
see wire-protocol.txt
|
||||
|
||||
---
|
||||
|
||||
datastructures:
|
||||
|
||||
all datastructures are assumed version 0 if they lack a v value
|
||||
otherwise version is provided by the v value
|
||||
|
||||
all ip addresses can be ipv4 via hybrid dual stack ipv4 mapped ipv6 addresses,
|
||||
i.e ::ffff.8.8.8.8. The underlying implementation MAY implement ipv4 as native
|
||||
ipv4 instead of using a hybrid dual stack.
|
||||
|
||||
net address:
|
||||
|
||||
net addresses are a variable length byte string, if between 7 and 15 bytes it's
|
||||
treated as a dot notation ipv4 address (xxx.xxx.xxx.xxx)
|
||||
if it's exactly 16 bytes it's treated as a big endian encoding ipv6 address.
|
||||
|
||||
address info (AI)
|
||||
|
||||
An address info (AI) defines a publically reachable endpoint
|
||||
|
||||
{
|
||||
c: transport_rank_uint16,
|
||||
d: "<transport dialect name>",
|
||||
e: "<32 bytes public encryption key>",
|
||||
i: "<net address>",
|
||||
p: port_uint16,
|
||||
v: 0
|
||||
}
|
||||
|
||||
example wank address info:
|
||||
|
||||
{
|
||||
c: 1,
|
||||
d: "wank",
|
||||
e: "<32 bytes of 0x61>",
|
||||
i: "123.123.123.123",
|
||||
p: 1234,
|
||||
v: 0
|
||||
}
|
||||
|
||||
bencoded form:
|
||||
|
||||
d1:ci1e1:d4:wank1:e32:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1:d3:iwp1:i15:123.123.123.1231:pi1234e1:vi0ee
|
||||
|
||||
Traffic Policy (TP)
|
||||
|
||||
Traffic policy (TP) defines port, protocol and QoS/drop policy.
|
||||
|
||||
{
|
||||
a: protocol_integer,
|
||||
b: port_integeger,
|
||||
d: drop_optional_integer,
|
||||
v: 0
|
||||
}
|
||||
|
||||
drop d is set to 1 to indicate that packets of protocol a with source port b will be dropped.
|
||||
if d is 0 or not provided this traffic policy does nothing.
|
||||
|
||||
Exit Info (XI)
|
||||
|
||||
An exit info (XI) defines a exit address that can relay exit traffic to the
|
||||
internet.
|
||||
|
||||
{
|
||||
a: "<net address exit address>",
|
||||
b: "<net address exit netmask>",
|
||||
k: "<32 bytes public encryption/signing key>",
|
||||
p: [ list, of, traffic, policies],
|
||||
v: 0
|
||||
}
|
||||
|
||||
|
||||
Exit Route (XR)
|
||||
|
||||
An exit route (XR) define an allocated exit address and any additional
|
||||
information required to access the internet via that exit address.
|
||||
|
||||
{
|
||||
a: "<16 bytes big endian ipv6 gateway address>",
|
||||
b: "<16 bytes big endian ipv6 netmask>",
|
||||
c: "<16 bytes big endian ipv6 source address>",
|
||||
l: lifetime_in_milliseconds_uint64,
|
||||
v: 0
|
||||
}
|
||||
|
||||
router contact (RC)
|
||||
|
||||
router's full identity
|
||||
|
||||
{
|
||||
a: [ one, or, many, AI, here ... ],
|
||||
e: extensions_supported,
|
||||
i: "<max 8 bytes network identifier>",
|
||||
k: "<32 bytes public long term identity signing key>",
|
||||
n: "<optional max 32 bytes router nickname>",
|
||||
p: "<32 bytes public path encryption key>",
|
||||
s: [services, supported],
|
||||
u: time_signed_at_milliseconds_since_epoch_uint64,
|
||||
v: 0,
|
||||
x: [ Exit, Infos ],
|
||||
z: "<64 bytes signature using identity key>"
|
||||
}
|
||||
|
||||
e is a dict containing key/value of supported protocol extensions on this service node, keys are strings, values MUST be 0 or 1.
|
||||
|
||||
s is a list of services on this service node:
|
||||
|
||||
each service is a 6 item long, list of the following:
|
||||
|
||||
["_service", "_proto", ttl_uint, priority_uint, weight_uint, port_uint]
|
||||
|
||||
with the corrisponding SRV record:
|
||||
|
||||
<_service>.<_proto>.router_pubkey_goes_here.snode. <ttl_uint> IN SRV <priority_uint> <weight_uint> <port_uint> router_pubkey_goes_here.snode
|
||||
|
||||
|
||||
RC.t is the timestamp of when this RC was signed.
|
||||
RC is valid for a maximum of 1 hour after which it MUST be resigned with the new
|
||||
timestamp.
|
||||
|
||||
RC.i is set to the network identifier.
|
||||
|
||||
only routers with the same network identifier may connect to each other.
|
||||
|
||||
"testnet" for testnet.
|
||||
"lokinet" for the "official" lokinet mainline network.
|
||||
|
||||
other values of RC.i indicate the router belongs to a network fork.
|
||||
|
||||
|
||||
service info (SI)
|
||||
|
||||
public information blob for a hidden service
|
||||
|
||||
e is the long term public encryption key
|
||||
s is the long term public signing key
|
||||
v is the protocol version
|
||||
x is a nounce value for generating vanity addresses that can be omitted
|
||||
|
||||
if x is included it MUST be equal to 16 bytes
|
||||
|
||||
{
|
||||
e: "<32 bytes public encryption key>",
|
||||
s: "<32 bytes public signing key>",
|
||||
v: 0,
|
||||
x: "<optional 16 bytes nonce for vanity>"
|
||||
}
|
||||
|
||||
service address (SA)
|
||||
|
||||
the "network address" of a hidden service, which is computed as the blake2b
|
||||
256 bit hash of the public infomration blob.
|
||||
|
||||
HS(BE(SI))
|
||||
|
||||
when in string form it's encoded with z-base32 and uses the .loki tld
|
||||
|
||||
introduction (I)
|
||||
|
||||
a descriptor annoucing a path to a hidden service
|
||||
|
||||
k is the rc.k value of the router to contact
|
||||
p is the path id on the router that is owned by the service
|
||||
v is the protocol version
|
||||
x is the timestamp milliseconds since epoch that this introduction expires at
|
||||
|
||||
{
|
||||
k: "<32 bytes public identity key of router>",
|
||||
l: advertised_path_latency_ms_uint64, (optional)
|
||||
p: "<16 bytes path id>",
|
||||
v: 0,
|
||||
x: time_expires_milliseconds_since_epoch_uint64
|
||||
}
|
||||
|
||||
introduction set (IS)
|
||||
|
||||
and introset is a signed set of introductions for a hidden service
|
||||
a is the service info of the publisher
|
||||
h is a list of srv records in same format as in RCs
|
||||
i is the list of introductions that this service is advertising with
|
||||
k is the public key to use when doing encryption to this hidden service
|
||||
n is a 16 byte null padded utf-8 encoded string tagging the hidden service in
|
||||
a topic searchable via a lookup (optional)
|
||||
v is the protocol version
|
||||
w is an optinal proof of work for DoS protection (slot for future)
|
||||
z is the signature of the entire IS where z is set to zero signed by the hidden
|
||||
service's signing key.
|
||||
|
||||
{
|
||||
a: SI,
|
||||
h: [list, of, advertised, services],
|
||||
i: [ I, I, I, ... ],
|
||||
k: "<1218 bytes sntrup4591761 public key block>",
|
||||
n: "<16 bytes service topic (optional)>",
|
||||
t: timestamp_uint64_milliseconds_since_epoch_published_at,
|
||||
v: 0,
|
||||
w: optional proof of work,
|
||||
z: "<64 bytes signature using service info signing key>"
|
||||
}
|
||||
|
||||
h is a list of services on this endpoint
|
||||
|
||||
each service is a 7 item long, list of the following:
|
||||
|
||||
["_service", "_proto", ttl_uint, priority_uint, weight_uint, port_uint, "<32 bytes SA of the service>"]
|
||||
|
||||
with the corrisponding SRV record:
|
||||
|
||||
<_service>.<_proto>.<service_address>.loki. <ttl_uint> IN SRV <priority_uint> <weight_uint> <port_uint> <SA of sub service>.loki.
|
||||
|
||||
recursion on SRV records is NOT permitted.
|
||||
|
||||
---
|
||||
|
||||
Encrypted frames:
|
||||
|
||||
|
||||
Encrypted frames are encrypted containers for link message records like LRCR.
|
||||
|
||||
32 bytes hmac, h
|
||||
32 bytes nounce, n
|
||||
32 bytes ephmeral sender's public encryption key, k
|
||||
remaining bytes ciphertext, x
|
||||
|
||||
decryption:
|
||||
|
||||
0) verify hmac
|
||||
|
||||
S = PKE(n, k, our_RC.K)
|
||||
verify h == MDS(n + k + x, S)
|
||||
|
||||
If the hmac verification fails the entire parent message is discarded
|
||||
|
||||
1) decrypt and decode
|
||||
|
||||
new_x = SD(S, n[0:24], x)
|
||||
msg = BD(new_x)
|
||||
|
||||
If the decoding fails the entire parent message is discarded
|
||||
|
||||
encryption:
|
||||
|
||||
to encrypt a frame to a router with public key B.k
|
||||
|
||||
0) prepare nounce n, ephemeral keypair (A.k, s) and derive shared secret S
|
||||
|
||||
A.k, s = ECKG()
|
||||
n = RAND(32)
|
||||
S = PKE(p, A.k, B.k, n)
|
||||
|
||||
1) encode and encrypt
|
||||
|
||||
x = BE(msg)
|
||||
new_x = SE(S, n[0:24], x)
|
||||
|
||||
2) generate message authentication
|
||||
|
||||
h = MDS(n + A.k + new_x, S)
|
||||
|
||||
resulting frame is h + n + A.k + new_x
|
||||
|
||||
|
||||
---
|
||||
|
||||
link layer messages:
|
||||
|
||||
the link layer is responsible for anonymising the source and destination of
|
||||
routing layer messages.
|
||||
|
||||
any link layer message without a key v is assumed to be version 0 otherwise
|
||||
indicates the protocol version in use.
|
||||
|
||||
|
||||
|
||||
link introduce message (LIM)
|
||||
|
||||
This message MUST be the first link message sent before any others. This message
|
||||
identifies the sender as having the RC contained in r. The recipiant MUST
|
||||
validate the RC's signature and ensure that the public key in use is listed in
|
||||
the RC.a matching the ipv6 address it originated from.
|
||||
|
||||
{
|
||||
a: "i",
|
||||
e: "<32 bytes ephemeral public encryption key>",
|
||||
n: "<32 bytes nonce for key exhcange>",
|
||||
p: uint64_milliseconds_session_period,
|
||||
r: RC,
|
||||
v: 0,
|
||||
z: "<64 bytes signature of entire message by r.k>"
|
||||
}
|
||||
|
||||
the link session will be kept open for p milliseconds after which
|
||||
the session MUST be renegotiated.
|
||||
|
||||
link relay commit message (LRCM)
|
||||
|
||||
request a commit to relay traffic to another node.
|
||||
|
||||
{
|
||||
a: "c",
|
||||
c: [ list, of, encrypted, LRCR frames ],
|
||||
v: 0
|
||||
}
|
||||
|
||||
c MUST contain dummy records if the hop length is less than the maximum
|
||||
hop length.
|
||||
|
||||
link relay commit record (LRCR)
|
||||
|
||||
record requesting relaying messages for 600 seconds to router
|
||||
on network whose i is equal to RC.k and decrypt data any messages using
|
||||
PKE(n, rc.p, c) as symmetric key for encryption and decryption.
|
||||
|
||||
if l is provided and is less than 600 and greater than 10 then that lifespan
|
||||
is used (in seconds) instead of 600 seconds.
|
||||
|
||||
if w is provided and fits the required proof of work then the lifetime of
|
||||
the path is extended by w.y seconds
|
||||
|
||||
{
|
||||
c: "<32 byte public encryption key used for upstream>",
|
||||
d: uint_optional_ms_delay, // TODO
|
||||
i: "<32 byte RC.k of next hop>",
|
||||
l: uint_optional_lifespan,
|
||||
n: "<32 bytes nounce for key exchange>",
|
||||
r: "<16 bytes rx path id>",
|
||||
t: "<16 bytes tx path id>",
|
||||
u: "<optional RC of the next hop>",
|
||||
v: 0,
|
||||
w: proof of work
|
||||
}
|
||||
|
||||
w if provided is a dict with the following struct
|
||||
|
||||
{
|
||||
t: time_created_seconds_since_epoch,
|
||||
v: 0,
|
||||
y: uint32_seconds_extended_lifetime,
|
||||
z: "<32 bytes nonce>"
|
||||
}
|
||||
|
||||
the validity of the proof of work is that given
|
||||
|
||||
h = HS(BE(w))
|
||||
|
||||
h has log_e(y) prefixed bytes being 0x00
|
||||
|
||||
this proof of work requirement is subject to change
|
||||
|
||||
if i is equal to RC.k then any LRDM.x values are decrypted and interpreted as
|
||||
routing layer messages. This indicates that we are the farthest hop in the path.
|
||||
|
||||
link relay status message (LRSM)
|
||||
|
||||
response to path creator about build status
|
||||
|
||||
{
|
||||
a: "s",
|
||||
c: [ list, of, encrypted, LRSR frames],
|
||||
p: "<16 bytes rx path id>",
|
||||
s: uint_status_flags, // for now, only set (or don't) success bit
|
||||
v: 0
|
||||
}
|
||||
|
||||
the creator of the LRSM MUST include dummy LRSR records
|
||||
to pad out to the maximum path length
|
||||
|
||||
link relay status record (LRSR)
|
||||
|
||||
record indicating status of path build
|
||||
|
||||
{
|
||||
s: uint_status_flags,
|
||||
v: 0
|
||||
}
|
||||
|
||||
uint_status_flags (bitwise booleans):
|
||||
[0] = success
|
||||
[1] = fail, hop timeout
|
||||
[2] = fail, congestion
|
||||
[3] = fail, refusal, next hop is not known to be a snode
|
||||
[4] = fail, used by hop creator when decrypting frames if decryption fails
|
||||
[5] = fail, used by hop creator when record decode fails
|
||||
[4-63] reserved
|
||||
|
||||
link relay upstream message (LRUM)
|
||||
|
||||
sent to relay data via upstream direction of a previously created path.
|
||||
|
||||
{
|
||||
a: "u",
|
||||
p: "<16 bytes path id>",
|
||||
v: 0,
|
||||
x: "<N bytes encrypted x1 value>",
|
||||
y: "<32 bytes nonce>"
|
||||
}
|
||||
|
||||
x1 = SD(k, y, x)
|
||||
|
||||
if we are the farthest hop, process x1 as a routing message
|
||||
otherwise transmit a LRUM to the next hop
|
||||
|
||||
{
|
||||
a: "u",
|
||||
p: p,
|
||||
v: 0,
|
||||
x: x1,
|
||||
y: y ^ HS(k)
|
||||
}
|
||||
|
||||
link relay downstream message (LRDM)
|
||||
|
||||
sent to relay data via downstream direction of a previously created path.
|
||||
|
||||
{
|
||||
a: "d",
|
||||
p: "<16 bytes path id>",
|
||||
v: 0,
|
||||
x: "<N bytes encrypted x1 value>",
|
||||
y: "<32 bytes nonce>"
|
||||
}
|
||||
|
||||
if we are the creator of the path decrypt x for each hop key k
|
||||
|
||||
x = SD(k, y, x)
|
||||
|
||||
otherwise transmit LRDM to next hop
|
||||
|
||||
x1 = SE(k, y, x)
|
||||
|
||||
{
|
||||
a: "d",
|
||||
p: p,
|
||||
v: 0,
|
||||
x: x1,
|
||||
y: y ^ HS(k)
|
||||
}
|
||||
|
||||
link immediate dht message (LIDM):
|
||||
|
||||
transfer one or more dht messages directly without a previously made path.
|
||||
|
||||
{
|
||||
a: "m",
|
||||
m: [many, dht, messages],
|
||||
v: 0
|
||||
}
|
||||
|
||||
|
||||
link immediate state message (LISM)
|
||||
|
||||
transfer a state message between nodes
|
||||
|
||||
{
|
||||
a: "s",
|
||||
s: state_message,
|
||||
v: 0
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
state message:
|
||||
|
||||
NOTE: this message type is currently a documentation stub and remains unimplemented.
|
||||
|
||||
state messages propagate changes to the service nodes concensous to thin clients.
|
||||
|
||||
service node joined network
|
||||
|
||||
{
|
||||
A: "J",
|
||||
R: "<32 bytes public key>",
|
||||
V: 0
|
||||
}
|
||||
|
||||
service node parted network
|
||||
|
||||
{
|
||||
A: "P",
|
||||
R: "<32 bytes public key>",
|
||||
V: 0
|
||||
}
|
||||
|
||||
service node list request
|
||||
|
||||
request the service node list starting at index I containing R entries
|
||||
|
||||
{
|
||||
A: "R",
|
||||
I: starting_offset_int,
|
||||
R: number_of_entires_to_request_int,
|
||||
V: 0
|
||||
}
|
||||
|
||||
service node list response
|
||||
|
||||
response to service node list request
|
||||
|
||||
{
|
||||
A: "L",
|
||||
S: {
|
||||
"<32 bytes pubkey>" : 1,
|
||||
"<32 bytes pubkey>" : 1,
|
||||
....
|
||||
},
|
||||
V: 0
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
routing layer:
|
||||
|
||||
the routing layer provides inter network communication between the LLARP link
|
||||
layer and ip (internet protocol) for exit traffic or ap (anonymous protocol) for
|
||||
hidden services. replies to messages are sent back via the path they
|
||||
originated from inside a LRDM. all routing messages have an S value which
|
||||
provides the sequence number of the message so the messages can be ordered.
|
||||
|
||||
ipv4 addresses are allowed via ipv4 mapped ipv6 addresses, i.e. ::ffff.10.0.0.1
|
||||
|
||||
path confirm message (PCM)
|
||||
|
||||
sent as the first message down a path after it's built to confirm it was built
|
||||
|
||||
always the first message sent
|
||||
|
||||
{
|
||||
A: "P",
|
||||
L: uint64_milliseconds_path_lifetime,
|
||||
S: 0,
|
||||
T: uint64_milliseconds_sent_timestamp,
|
||||
V: 0
|
||||
}
|
||||
|
||||
path latency message (PLM)
|
||||
|
||||
a latency measurement message, reply with a PLM response if we are the far end
|
||||
of a path.
|
||||
|
||||
variant 1, request, generated by the path creator:
|
||||
|
||||
{
|
||||
A: "L",
|
||||
S: uint64_sequence_number,
|
||||
V: 0
|
||||
}
|
||||
|
||||
variant 2, response, generated by the endpoint that recieved the request.
|
||||
|
||||
{
|
||||
A: "L",
|
||||
S: uint64_sequence_number,
|
||||
T: uint64_timestamp_recieved,
|
||||
V: 0
|
||||
}
|
||||
|
||||
obtain exit message (OXM)
|
||||
|
||||
sent to an exit router to obtain ip exit traffic context.
|
||||
replies are sent down the path that messages originate from.
|
||||
|
||||
{
|
||||
A: "X",
|
||||
B: [list, of, permitted, blacklisted, traffic, policies],
|
||||
E: 0 for snode communication or 1 for internet access,
|
||||
I: "<32 bytes signing public key for future communication>",
|
||||
S: uint64_sequence_number,
|
||||
T: uint64_transaction_id,
|
||||
V: 0,
|
||||
W: [list, of, required, whitelisted, traffic, policies],
|
||||
X: lifetime_of_address_mapping_in_seconds_uint64,
|
||||
Z: "<64 bytes signature using I>"
|
||||
}
|
||||
|
||||
grant exit messsage (GXM)
|
||||
|
||||
sent in response to an OXM to grant an ip for exit traffic from an external
|
||||
ip address used for exit traffic.
|
||||
|
||||
{
|
||||
A: "G",
|
||||
S: uint64_sequence_number,
|
||||
T: transaction_id_uint64,
|
||||
Y: "<16 byte nonce>",
|
||||
V: 0,
|
||||
Z: "<64 bytes signature>"
|
||||
}
|
||||
|
||||
any TITM recieved on the same path will be forwarded out to the internet if
|
||||
OXAM.E is not 0, otherwise it is interpreted as service node traffic.
|
||||
|
||||
|
||||
reject exit message (RXM)
|
||||
|
||||
sent in response to an OXAM to indicate that exit traffic is not allowed or
|
||||
was denied.
|
||||
|
||||
{
|
||||
A: "J",
|
||||
B: backoff_milliseconds_uint64,
|
||||
R: [list, of, rejected, traffic, policies],
|
||||
S: uint64_sequence_number,
|
||||
T: transaction_id_uint64,
|
||||
V: 0,
|
||||
Y: "<16 byte nonce>",
|
||||
Z: "<64 bytes signature>"
|
||||
}
|
||||
|
||||
|
||||
discarded data fragment message (DDFM)
|
||||
|
||||
sent in reply to TDFM when we don't have a path locally or are doing network
|
||||
congestion control from a TITM.
|
||||
|
||||
{
|
||||
A: "D",
|
||||
P: "<16 bytes path id>",
|
||||
S: uint64_sequence_number_of_fragment_dropped,
|
||||
V: 0
|
||||
}
|
||||
|
||||
transfer data fragment message (TDFM)
|
||||
|
||||
transfer data between paths.
|
||||
|
||||
{
|
||||
A: "T",
|
||||
P: "<16 bytes path id>",
|
||||
S: uint64_sequence_number,
|
||||
T: hidden_service_frame,
|
||||
V: 0
|
||||
}
|
||||
|
||||
transfer data to another path with id P on the local router place a random 32
|
||||
byte and T values into y and z values into a LRDM message (respectively) and
|
||||
send it in the downstream direction. if this path does not exist on the router
|
||||
it is replied to with a DDFM.
|
||||
|
||||
|
||||
|
||||
hidden service data (HSD)
|
||||
|
||||
data sent anonymously over the network to a recipiant from a sender.
|
||||
sent inside a HSFM encrypted with a shared secret.
|
||||
|
||||
{
|
||||
a: protocol_number_uint,
|
||||
d: "<N bytes payload>",
|
||||
i: Introduction for reply,
|
||||
n: uint_message_sequence_number,
|
||||
o: N seconds until this converstation plans terminate,
|
||||
s: SI of sender,
|
||||
t: "<16 bytes converstation_tag>,
|
||||
v: 0
|
||||
}
|
||||
|
||||
|
||||
hidden service frame (HSF)
|
||||
|
||||
TODO: document this better
|
||||
|
||||
establish converstation tag message (variant 1)
|
||||
|
||||
generate a new convotag that is contained inside an encrypted HSD
|
||||
|
||||
{
|
||||
A: "H",
|
||||
C: "<1048 bytes ciphertext block>",
|
||||
D: "<N bytes encrypted HSD>",
|
||||
F: "<16 bytes source path_id>",
|
||||
N: "<32 bytes nonce for key exchange>",
|
||||
V: 0,
|
||||
Z: "<64 bytes signature of entire message using sender's signing key>"
|
||||
}
|
||||
|
||||
alice (A) wants to talk to bob (B) over the network, both have hidden services
|
||||
set up and are online on the network.
|
||||
|
||||
A and B are both referring to alice and bob's SI respectively.
|
||||
A_sk is alice's private signing key.
|
||||
|
||||
for alice (A) to send the string "beep" to bob (B), alice picks an introduction
|
||||
to use on one of her paths (I_A) such that I_A is aligning with one of bobs's
|
||||
paths (I_B)
|
||||
|
||||
alice generates:
|
||||
|
||||
T = RAND(16)
|
||||
|
||||
m = {
|
||||
a: 0,
|
||||
d: "beep",
|
||||
i: I_A,
|
||||
n: 0,
|
||||
s: A,
|
||||
t: T,
|
||||
v: 0
|
||||
}
|
||||
|
||||
X = BE(m)
|
||||
|
||||
C, K = PQKE_A(I_B.k)
|
||||
N = RAND(32)
|
||||
D = SE(X, K, N)
|
||||
|
||||
path = PickSendPath()
|
||||
|
||||
M = {
|
||||
A: "T",
|
||||
P: I_B.P,
|
||||
S: uint64_sequence_number,
|
||||
T: {
|
||||
A: "H",
|
||||
C: C,
|
||||
D: D,
|
||||
F: path.lastHop.txID,
|
||||
N: N,
|
||||
V: 0,
|
||||
Z: "\x00" * 64
|
||||
},
|
||||
V: 0
|
||||
}
|
||||
|
||||
Z = S(A_sk, BE(M))
|
||||
|
||||
alice transmits a TDFM to router with public key I_B.K via her path that ends
|
||||
with router with public key I_B.k
|
||||
|
||||
path = PickSendPath()
|
||||
|
||||
{
|
||||
A: "T",
|
||||
P: I_B.P,
|
||||
S: uint64_sequence_number,
|
||||
T: {
|
||||
A: "H",
|
||||
C: C,
|
||||
D: D,
|
||||
F: path.lastHop.txID,
|
||||
N: N,
|
||||
V: 0,
|
||||
Z: Z
|
||||
},
|
||||
V: 0
|
||||
}
|
||||
|
||||
the shared secret (S) for further message encryption is:
|
||||
|
||||
S = HS(K + PKE(A, B, sk, N))
|
||||
|
||||
given sk is the local secret encryption key used by the current hidden service
|
||||
|
||||
please note:
|
||||
signature verification of the outer message can only be done after decryption
|
||||
because the signing keys are inside the encrypted HSD.
|
||||
|
||||
data from a previously made session (variant 2)
|
||||
|
||||
transfer data on a converstation previously made
|
||||
|
||||
{
|
||||
A: "H",
|
||||
D: "<N bytes encrypted HSD>",
|
||||
F: "<16 bytes path id of soruce>",
|
||||
N: "<32 bytes nonce for symettric cipher>",
|
||||
T: "<16 bytes converstation tag>",
|
||||
V: 0,
|
||||
Z: "<64 bytes signature using sender's signing key>"
|
||||
}
|
||||
|
||||
reject a message sent on a convo tag, when a remote endpoint
|
||||
sends this message a new converstation SHOULD be established.
|
||||
|
||||
{
|
||||
A: "H",
|
||||
F: "<16 bytes path id of soruce>",
|
||||
R: 1,
|
||||
T: "<16 bytes converstation tag>",
|
||||
V: 0,
|
||||
Z: "<64 bytes signature using sender's signing key>"
|
||||
}
|
||||
|
||||
|
||||
transfer ip traffic message (TITM)
|
||||
|
||||
transfer ip traffic
|
||||
|
||||
{
|
||||
A: "I",
|
||||
S: uint64_sequence_number,
|
||||
V: 0,
|
||||
X: [list, of, ip, packet, buffers],
|
||||
}
|
||||
|
||||
an ip packet buffer is prefixed with a 64 bit big endian unsigned integer
|
||||
denoting the sequence number for re-ordering followed by the ip packet itself.
|
||||
|
||||
X is parsed as a list of IP packet buffers.
|
||||
for each ip packet the source addresss is extracted and sent on the
|
||||
appropriate network interface.
|
||||
|
||||
When we receive an ip packet from the internet to an exit address, we put it
|
||||
into a TITM, and send it downstream the corresponding path in an LRDM.
|
||||
|
||||
update exit path message (UXPM)
|
||||
|
||||
sent from a new path by client to indicate that a previously established exit
|
||||
should use the new path that this message came from.
|
||||
|
||||
{
|
||||
A: "U",
|
||||
P: "<16 bytes previous tx path id>",
|
||||
S: uint64_sequence_number,
|
||||
T: uint64_txid,
|
||||
V: 0,
|
||||
Y: "<16 bytes nonce>",
|
||||
Z: "<64 bytes signature using previously provided signing key>"
|
||||
}
|
||||
|
||||
close exit path message (CXPM)
|
||||
|
||||
client sends a CXPM when the exit is no longer needed or by the exit if the
|
||||
exit wants to close prematurely.
|
||||
also sent by exit in reply to a CXPM to confirm close.
|
||||
|
||||
{
|
||||
A: "C",
|
||||
S: uint64_sequence_number,
|
||||
V: 0,
|
||||
Y: "<16 bytes nonce>",
|
||||
Z: "<64 bytes signature>"
|
||||
}
|
||||
|
||||
update exit verify message (EXVM)
|
||||
|
||||
sent in reply to a UXPM to verify that the path handover was accepted
|
||||
|
||||
{
|
||||
A: "V",
|
||||
S: uint64_sequence_number,
|
||||
T: uint64_txid,
|
||||
V: 0,
|
||||
Y: "<16 bytes nonce>",
|
||||
Z: "<64 bytes signature>"
|
||||
}
|
||||
|
||||
|
||||
DHT message holder message:
|
||||
|
||||
wrapper message for sending many dht messages down a path.
|
||||
|
||||
{
|
||||
A: "M",
|
||||
M: [many, dht, messages, here],
|
||||
S: uint64_sequence_number,
|
||||
V: 0
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
What is a RouterEvent?
|
||||
|
||||
A RouterEvent is a way of representing a conceptual event that took place in a "router" (relay or client).
|
||||
|
||||
RouterEvents are used in order to collect information about a network all in one place and preserve causality.
|
||||
|
||||
How do I make a new RouterEvent?
|
||||
|
||||
Add your event following the structure in llarp/tooling/router_event.{hpp,cpp}
|
||||
|
||||
Add your event to pybind in pybind/llarp/tooling/router_event.cpp
|
||||
|
||||
What if a class my event uses is missing members in pybind?
|
||||
|
||||
Find the relevant file pybind/whatever/class.cpp and remedy that!
|
||||
|
||||
What if I need to refer to classes which aren't available already in pybind?
|
||||
|
||||
Add pybind/namespace/namespace/etc/class.cpp and pybind it!
|
||||
|
||||
You will need to edit the following files accordingly:
|
||||
pybind/common.hpp
|
||||
pybind/module.cpp
|
||||
pybind/CMakeLists.txt
|
||||
|
||||
How do I use a RouterEvent?
|
||||
|
||||
From the cpp side, find the place in the code where it makes the most logical sense
|
||||
that the conceptual event has taken place (and you have the relevant information to
|
||||
create the "event" instance) and create it there as follows:
|
||||
|
||||
#include <llarp/tooling/relevant_event_header.hpp>
|
||||
|
||||
where the event takes place, do the following:
|
||||
auto event = std::make_unique<event_type_here>(constructor_args...);
|
||||
somehow_get_a_router->NotifyRouterEvent(std::move(event));
|
||||
|
||||
From the Python side...it's a python object!
|
@ -1,309 +0,0 @@
|
||||
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's wire protocol is Internet Wire Protocol (IWP)
|
||||
|
||||
The main goal of iwp is to provide an authenticated encrypted
|
||||
reliable semi-ordered durable datagram transfer protocol supporting
|
||||
datagrams of larger size than link mtu.
|
||||
|
||||
in iwp there is an initiator who initiates a session to a recipiant.
|
||||
|
||||
iwp has 3 phases. the first phase is the proof of flow phase.
|
||||
the second is a session handshake phase, the third is data transmission.
|
||||
|
||||
proof of flow:
|
||||
|
||||
the purpose of the proof of flow phase is to verify the existence
|
||||
of the initiator's endpoint.
|
||||
|
||||
At any time before the data transfer phase a reject message
|
||||
is sent the session is reset.
|
||||
|
||||
Alice (A) is the sender and Bob (B) is the recipiant.
|
||||
|
||||
A asks for a flow id from B.
|
||||
|
||||
B MAY send a flow id to A or MAY reject the message from A.
|
||||
|
||||
session handshake:
|
||||
|
||||
an encrypted session is established using establish wire session messages
|
||||
using a newly created flow id.
|
||||
|
||||
message format:
|
||||
|
||||
there are 2 layers in this protocol, outer messages and inner messages.
|
||||
|
||||
outer messages are sent in plaintext and / or obfsucated with symettric
|
||||
encryption using a preshared key.
|
||||
|
||||
inner messages are inside an encrypted and authenticated envelope
|
||||
wrapped by an outer messages, which is always a data tranmssion message.
|
||||
|
||||
outer message format:
|
||||
|
||||
every outer message MAY be obfsucated via symettric encryption for dpi
|
||||
resistance reasons, this is not authenticated encryption.
|
||||
|
||||
the message is first assumed to be sent in clear first.
|
||||
if parsing of clear variant fails then the recipiant MUST fall back to assuming
|
||||
the protocol is in obfuscated mode.
|
||||
|
||||
|
||||
<16 bytes nounce, n>
|
||||
<remaining bytes obsfucated, m>
|
||||
|
||||
obfuscated via:
|
||||
|
||||
K = HS(B_k)
|
||||
N = HS(n + K)
|
||||
X = SD(K, m, N[0:24])
|
||||
|
||||
where
|
||||
B_k is the long term identity public key of the recipient.
|
||||
HS is blake2 256 bit non keyed hash
|
||||
SD is xchacha20 symettric stream cipher (decryption)
|
||||
|
||||
outer-header:
|
||||
|
||||
<1 byte command>
|
||||
<1 byte reserved set to 0x3d>
|
||||
|
||||
command 'O' - obtain flow id
|
||||
|
||||
obtain a flow id
|
||||
|
||||
<outer-header>
|
||||
<6 magic bytes "netid?">
|
||||
<8 bytes netid, I>
|
||||
<8 bytes timestamp milliseconds since epoch, T>
|
||||
<32 bytes public identity key of sender, A_k>
|
||||
<0-N bytes discarded>
|
||||
<last 64 bytes signature of unobfuscated packet, Z>
|
||||
|
||||
the if the network id differs from the current network's id a reject message
|
||||
MUST be sent
|
||||
|
||||
MUST be replied to with a message rejected or a give handshake cookie
|
||||
|
||||
command 'G' - give flow id
|
||||
|
||||
<outer-header>
|
||||
<6 magic bytes "netid!">
|
||||
<16 bytes new flow id>
|
||||
<32 bytes public identiy key of sender, A_k>
|
||||
<0-N bytes ignored but included in signature>
|
||||
<last 64 bytes signature of unobfsucated packet, Z>
|
||||
|
||||
after recieving a give flow id message a session negotiation can happen with that flow id.
|
||||
|
||||
command 'R' - flow rejected
|
||||
|
||||
reject new flow
|
||||
|
||||
<outer-header>
|
||||
<14 ascii bytes reason for rejection null padded>
|
||||
<8 bytes timestamp>
|
||||
<32 bytes public identity key of sender, A_k>
|
||||
<0-N bytes ignored but included in signature>
|
||||
<last 64 bytes signature of unobsfucated packet, Z>
|
||||
|
||||
command 'E' - establish wire session
|
||||
|
||||
establish an encrypted session using a flow id
|
||||
|
||||
<outer-header>
|
||||
<2 bytes 0x0a 0x0d>
|
||||
<4 bytes flags, F>
|
||||
<16 bytes flow id, B>
|
||||
<32 bytes ephemeral public encryption key, E>
|
||||
<8 bytes packet counter starting at 0>
|
||||
<optional 32 bytes authenticated credentials, A>
|
||||
<last 64 bytes signature of unobfuscated packet using identity key, Z>
|
||||
|
||||
|
||||
F is currently set to all zeros
|
||||
|
||||
every time we try establishing a wire session we increment the counter
|
||||
by 1 for the next message we send.
|
||||
|
||||
when we get an establish wire session message
|
||||
we reply with an establish wire session message with counter being counter + 1
|
||||
|
||||
if A is provided that is interpreted as being generated via:
|
||||
|
||||
h0 = HS('<insert some password here>')
|
||||
h1 = EDDH(us, them)
|
||||
A = HS(B + h0 + h1)
|
||||
|
||||
each side establishes their own rx key using this message.
|
||||
when each side has both established thier rx key data can be transmitted.
|
||||
|
||||
command 'D' - encrypted data transmission
|
||||
|
||||
transmit encrypted data on a wire session
|
||||
|
||||
<outer-header>
|
||||
<16 bytes flow-id, F>
|
||||
<24 bytes nonce, N>
|
||||
<N encrypted data, X>
|
||||
<last 32 bytes keyed hash of entire payload, Z>
|
||||
|
||||
|
||||
B is the flow id from the recipiant (from outer header)
|
||||
N is a random nounce
|
||||
X is encrypted data
|
||||
Z is keyed hash of entire message
|
||||
|
||||
Z is generated via:
|
||||
|
||||
msg.Z = MDS(outer-header + F + N + X, tx_K)
|
||||
|
||||
data tranmission:
|
||||
|
||||
inner message format of X (after decryption):
|
||||
|
||||
inner header:
|
||||
|
||||
<1 byte protocol version>
|
||||
<1 byte command>
|
||||
|
||||
|
||||
command: 'k' (keep alive)
|
||||
|
||||
tell other side to acknoledge they are alive
|
||||
|
||||
<inner header>
|
||||
<2 bytes resevered, set to 0>
|
||||
<2 bytes attempt counter, set to 0 and incremented every retransmit, reset when we get a keepalive ack>
|
||||
<2 bytes milliseconds ping timeout>
|
||||
<8 bytes current session TX limit in bytes per second>
|
||||
<8 bytes current session RX use in bytes per second>
|
||||
<8 bytes milliseconds since epoch our current time>
|
||||
<remaining bytes discarded>
|
||||
|
||||
command: 'l' (keep alive ack)
|
||||
|
||||
acknolege keep alive message
|
||||
|
||||
<inner header>
|
||||
<6 bytes reserved, set to 0>
|
||||
<8 bytes current session RX limit in bytes per second>
|
||||
<8 bytes current session TX use in bytes per second>
|
||||
<8 bytes milliseconds since epoch our current time>
|
||||
<remaining bytes discarded>
|
||||
|
||||
|
||||
command: 'n' (advertise neighboors)
|
||||
|
||||
tell peer about neighboors, only sent by non service nodes to other non service
|
||||
nodes.
|
||||
|
||||
<inner header>
|
||||
<route between us and them>
|
||||
<0 or more intermediate routes>
|
||||
<route from a service node>
|
||||
|
||||
route:
|
||||
|
||||
<1 byte route version (currently 0)>
|
||||
<1 byte flags, lsb set indicates src is a service node>
|
||||
<2 bytes latency in ms>
|
||||
<2 bytes backpressure>
|
||||
<2 bytes number of connected peers>
|
||||
<8 bytes publish timestamp ms since epoch>
|
||||
<32 bytes pubkey neighboor>
|
||||
<32 bytes pubkey src>
|
||||
<64 bytes signature of entire route signed by src>
|
||||
|
||||
command: 'c' (congestion)
|
||||
|
||||
tell other side to slow down
|
||||
|
||||
<inner header>
|
||||
<2 bytes reduce TX rate by this many 1024 bytes per second>
|
||||
<4 bytes milliseconds slowdown lifetime>
|
||||
<remaining bytes discarded>
|
||||
|
||||
command: 'd' (anti-congestion)
|
||||
|
||||
tell other side to speed up
|
||||
|
||||
<inner header>
|
||||
<2 bytes increase TX rate by this many 1024 bytes per second>
|
||||
<4 bytes milliseconds speedup lifetime>
|
||||
<remaining bytes discarded>
|
||||
|
||||
|
||||
command: 's' (start transmission)
|
||||
|
||||
initate the transmission of a message to the remote peer
|
||||
|
||||
<inner header>
|
||||
<1 byte flags F>
|
||||
<1 byte reserved R set to zero>
|
||||
<2 bytes total size of full message>
|
||||
<4 bytes sequence number S>
|
||||
<32 bytes blake2 hash of full message>
|
||||
<N remaining bytes first fragment of message>
|
||||
|
||||
if F lsb is set then there is no further fragments
|
||||
|
||||
command: 't' (continued transmission)
|
||||
|
||||
continue transmission of a bigger message
|
||||
|
||||
<inner header>
|
||||
<1 byte flags F>
|
||||
<1 bytes reserved R set to zero>
|
||||
<2 bytes 16 byte block offset in message>
|
||||
<4 bytes sequence number S>
|
||||
<N remaining bytes fragment of message aligned to 16 bytes>
|
||||
<remaining bytes not aligned to 16 bytes discarded>
|
||||
|
||||
command: 'q' (acknoledge transmission)
|
||||
|
||||
acknoledges a transmitted message
|
||||
|
||||
|
||||
|
||||
command: 'r' (rotate keys)
|
||||
|
||||
inform remote that their RX key should be rotated
|
||||
|
||||
given alice(A) sends this message to bob(B) the new keys are computed as such:
|
||||
|
||||
n_K = TKE(K, B_e, K_seed, N)
|
||||
|
||||
A.tx_K = n_K
|
||||
B.rx_K = n_K
|
||||
|
||||
<inner header>
|
||||
<2 bytes milliseconds lifetime of old keys, retain them for this long and then discard>
|
||||
<4 bytes reserved, set to 0>
|
||||
<32 bytes key exchange nounce, N>
|
||||
<32 bytes next public encryption key, K>
|
||||
<remaining bytes discarded>
|
||||
|
||||
command: 'u' (upgrade)
|
||||
|
||||
request protocol upgrade
|
||||
|
||||
<inner header>
|
||||
<1 byte protocol min version to upgrade to>
|
||||
<1 byte protocol max version to upgrade to>
|
||||
<remaining bytes discarded>
|
||||
|
||||
command: 'v' (version upgrade)
|
||||
|
||||
sent in response to upgrade message
|
||||
|
||||
<inner header>
|
||||
<1 byte protocol version selected>
|
||||
<1 byte protocol version highest we support>
|
||||
<remaining bytes discarded>
|
Loading…
Reference in New Issue