lokinet/docs/wire-protocol.txt

299 lines
7.4 KiB
Plaintext

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.
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])
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 ed25519 public 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 ed25519 public key of sender, A_k>
<0-N bytes discarded>
<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 ed25519 public key of sender, A_k>
<0-N bytes discarded>
<last 64 bytes signature of unobsfucated packet, Z>
command 'E' - establish wire session
establish an encrypted session using a flow id
<outer-header>
<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>
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 = '0x00' * 32
msg.Z = MDS(msg, tx_K)
data tranmission:
inner message format of X (after decryption):
header:
<1 byte protocol version>
<1 byte command>
command: 'k' (keep alive)
tell other side to acknoledge they are alive
<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
<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.
<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
<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
<header>
<2 bytes increase TX rate by this many 1024 bytes per second>
<4 bytes milliseconds speedup lifetime>
<remaining bytes discarded>
command: 't' (transmit data)
transmit a message to a peer
if this fragment is not addressed to us we route it to the neighboor
with the shortest route to the recipiant as advertised by all neighboors.
<header>
<32 bytes public identity key of recipiant>
<32 bytes public identity key of sender>
<24 bytes nounce, N>
<N bytes encrypted message, X>
<last 32 bytes keyed hash, Z>
encrypted via:
K = EDDH(recipiant, sender)
X = SE(msg, K, N)
Z = MDS(X, K)
encrypted message format:
<1 byte version, currently 0>
<1 byte number of acks following, aN>
<8 * aN bytes acks>
<4 byte sequence number of fragment or 0 if no fragment is included>
<2 byte 16 byte block offset in message of this fragment if it is included>
<remaining bytes fragment data aligned to 16 bytes>
<discard anything not aligned to 16 bytes>
ack format:
<4 byte message sequence number>
<1 byte reserved current set to 0>
<1 byte ack counter (number of acks sent for the corrisponding message)>
<1 byte bitmask fragments selective ack (msb is fragment 0, lsb is fragment 7)>
<1 byte bitmask fragments posative ack (msb is fragment 0, lsb is fragment 7)>
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
<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
<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
<header>
<1 byte protocol version selected>
<1 byte protocol version highest we support>
<remaining bytes discarded>