Add FEC parameters to session packets

This commit is contained in:
Vasily Evseenko 2022-09-13 14:17:18 +03:00
parent c00f4ca081
commit beaa791d66
6 changed files with 72 additions and 26 deletions

View File

@ -35,7 +35,7 @@ wfb_tx: src/tx.o src/fec.o src/wifibroadcast.o
wfb_keygen: src/keygen.o
$(CC) -o $@ $^ $(_LDFLAGS)
test:
test: all_bin
PYTHONPATH=`pwd` trial3 wfb_ng.tests
rpm: all_bin env

View File

@ -1,6 +1,6 @@
% WFB-NG Data Transport Standard [Draft]
% Vasily Evseenko <<svpcom@p2ptech.org>>
% Aug 29, 2022
% Sep 13, 2022
## Introduction
@ -49,7 +49,7 @@ First address byte `'W'`(0x57) has two lower bits set which means that address i
- ...
2. Next, the packet stream is processed by the FEC codec (using [zfec](http://info.iet.unipi.it/~luigi/fec.html) -- Erasure codes based on Vandermonde matrices.)
3. FEC packets are encrypted with the aead_chacha20poly1305 stream cipher using the libsodium library
3. FEC packets are encrypted and authenticated with the aead_chacha20poly1305 stream cipher using the libsodium library
4. The result is transmitted to the air in the form of one WiFi packet.
@ -72,9 +72,18 @@ All other ranges reserved for future use
There are two packet types
1. Data packet (`packet_type = 1`, has encrypted and fec-encoded data)
2. Session packet (`packet_type = 2`, has encrypted session key)
1. Data packet (`packet_type = 1`, has encrypted and authenticated (using session key) FEC-encoded data)
2. Session packet (`packet_type = 2`, has encrypted and signed (using RX public key and TX secret key) session parameters and session key)
Currently only supported FEC type is Reed-Solomon on Vandermonde matrix, but new FEC algorithms can be added in future.
``` .c
// FEC types
#define WFB_FEC_VDM_RS 0x1 // Reed-Solomon on Vandermonde matrix
// packet flags
#define WFB_PACKET_FEC_ONLY 0x1 // Empty packet to close FEC block
```
``` .c
static uint8_t ieee80211_header[] = {
@ -96,7 +105,7 @@ There are two packet types
+-- encrypted and authenticated by session key
2. Session packet:
wsession_hdr_t { packet_type = 2, nonce = random() }
wsession_data_t { epoch, channel_id, session_key } # -- encrypted and signed using crypto_box_easy(rx_publickey, tx_secretkey)
wsession_data_t { epoch, channel_id, fec_type, fec_k, fec_n, session_key } # -- encrypted and signed using crypto_box_easy(rx_publickey, tx_secretkey)
data nonce: 56bit block_idx + 8bit fragment_idx
session nonce: crypto_box_NONCEBYTES of random bytes
@ -116,6 +125,9 @@ There are two packet types
typedef struct{
uint64_t epoch; // It allow to drop session packets from old epoch
uint32_t channel_id; // (link_id << 8) + port_number
uint8_t fec_type; // FEC type (WFB_FEC_VDM_RS or other)
uint8_t k; // FEC k
uint8_t n; // FEC n
uint8_t session_key[crypto_aead_chacha20poly1305_KEYBYTES];
} __attribute__ ((packed)) wsession_data_t;
@ -146,7 +158,7 @@ WFB-NG encrypts data stream using libsodium.
When TX starts, it generates new session key, encrypts it using public key authenticated encryption (cryptobox) and announce it every SESSION_KEY_ANNOUNCE_MSEC (default 1s).
Data packets encrypted by crypto_aead_chacha20poly1305_encrypt using session key and packet index as nonce.
TX can change FEC settings online, but it must generate a new session key to avoid invalid data on the RX side.
### RX-Ring
Due to multiple RX radios with own internal queues incoming packets can arrive out of order and you need a method to rearrange them.
@ -156,8 +168,8 @@ new fragments to block(s) in the tail and fetch them from the head.
When you receive a new packet it can belongs to:
1. New fec block - you need to allocate it in RX ring (do nothing if block was already processed)
2. Already existing fec block - you need to add it to them (do nothing if packet already processed)
1. New FEC block - you need to allocate it in RX ring (do nothing if block was already processed)
2. Already existing FEC block - you need to add it to them (do nothing if packet already processed)
If you successfully decode all fragments from the block then you should yield and remove ALL unfinished blocks before it.
@ -172,3 +184,9 @@ So you can support invariant that output UDP packets will be always ordered and
By default WFB-NG encapsulates one source UDP packet to one WiFi packet. But mavlink packets are very small (usually less than 100 bytes) and
send them in separate packets produces too much overhead. You can add optimized mavlink mode.
It will pack mavlink packets into one UDP packet while size < ``MAX_PAYLOAD_SIZE`` and ``mavlink_agg_in_ms`` is not expired.
### TX FEC timeout
By default WFB-NG doesn't close TX FEC block if less than ``K`` packets was sent and no new packets available.
This can be an issue for interactive protocols or for protocols with variable data stream speed such as mavlink or IP tunnel.
In such cases TX can issue empty packets with ``WFB_PACKET_FEC_ONLY`` flag to close non-empty FEC blocks if no new packets are available in some timeout.
As alternative you can use FEC with ``K=1`` for such streams.

View File

@ -51,7 +51,7 @@ int main(void)
if (sodium_init() < 0)
{
fprintf(stderr, "libsodium init failed\n");
fprintf(stderr, "Libsodium init failed\n");
return 1;
}

View File

@ -191,7 +191,7 @@ void Receiver::loop_iter(void)
{
agg->process_packet(pkt + sizeof(ieee80211_header), pktlen - sizeof(ieee80211_header), wlan_idx, antenna, rssi, NULL);
} else {
fprintf(stderr, "short packet (ieee header)\n");
fprintf(stderr, "Short packet (ieee header)\n");
continue;
}
}
@ -253,6 +253,8 @@ Aggregator::~Aggregator()
}
delete rx_ring[ring_idx].fragments;
}
fec_free(fec_p);
close(sockfd);
}
@ -310,7 +312,7 @@ int Aggregator::rx_ring_push(void)
*/
#if 0
fprintf(stderr, "override block 0x%" PRIx64 " flush %d fragments\n", rx_ring[rx_ring_front].block_idx, rx_ring[rx_ring_front].has_fragments);
fprintf(stderr, "Override block 0x%" PRIx64 " flush %d fragments\n", rx_ring[rx_ring_front].block_idx, rx_ring[rx_ring_front].has_fragments);
#endif
count_p_override += 1;
@ -422,7 +424,7 @@ void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_id
if (size > MAX_FORWARDER_PACKET_SIZE)
{
fprintf(stderr, "long packet (fec payload)\n");
fprintf(stderr, "Long packet (fec payload)\n");
count_p_bad += 1;
return;
}
@ -432,7 +434,7 @@ void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_id
case WFB_PACKET_DATA:
if(size < sizeof(wblock_hdr_t) + sizeof(wpacket_hdr_t))
{
fprintf(stderr, "short packet (fec header)\n");
fprintf(stderr, "Short packet (fec header)\n");
count_p_bad += 1;
return;
}
@ -441,7 +443,7 @@ void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_id
case WFB_PACKET_KEY:
if(size != sizeof(wsession_hdr_t) + sizeof(wsession_data_t) + crypto_box_MACBYTES)
{
fprintf(stderr, "invalid session key packet\n");
fprintf(stderr, "Invalid session key packet\n");
count_p_bad += 1;
return;
}
@ -452,14 +454,30 @@ void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_id
((wsession_hdr_t*)buf)->session_nonce,
tx_publickey, rx_secretkey) != 0)
{
fprintf(stderr, "unable to decrypt session key\n");
fprintf(stderr, "Unable to decrypt session key\n");
count_p_dec_err += 1;
return;
}
if (be64toh(new_session_data.epoch) < epoch || be32toh(new_session_data.channel_id) != channel_id)
if (be64toh(new_session_data.epoch) < epoch)
{
fprintf(stderr, "session channel_id or epoch doesn't match\n");
fprintf(stderr, "Session epoch doesn't match\n");
count_p_dec_err += 1;
return;
}
if (be32toh(new_session_data.channel_id) != channel_id)
{
fprintf(stderr, "Session channel_id doesn't match\n");
count_p_dec_err += 1;
return;
}
if (new_session_data.k != fec_k ||
new_session_data.n != fec_n ||
new_session_data.fec_type != WFB_FEC_VDM_RS)
{
fprintf(stderr, "Session FEC settings doesn't match\n");
count_p_dec_err += 1;
return;
}
@ -503,7 +521,7 @@ void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_id
sizeof(wblock_hdr_t),
(uint8_t*)(&(block_hdr->data_nonce)), session_key) != 0)
{
fprintf(stderr, "unable to decrypt packet #0x%" PRIx64 "\n", be64toh(block_hdr->data_nonce));
fprintf(stderr, "Unable to decrypt packet #0x%" PRIx64 "\n", be64toh(block_hdr->data_nonce));
count_p_dec_err += 1;
return;
}
@ -526,7 +544,7 @@ void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_id
if (fragment_idx >= fec_n)
{
fprintf(stderr, "invalid fragment_idx: %d\n", fragment_idx);
fprintf(stderr, "Invalid fragment_idx: %d\n", fragment_idx);
count_p_bad += 1;
return;
}
@ -643,7 +661,7 @@ void Aggregator::send_packet(int ring_idx, int fragment_idx)
if(packet_size > MAX_PAYLOAD_SIZE)
{
fprintf(stderr, "corrupted packet %u\n", seq);
fprintf(stderr, "Corrupted packet %u\n", seq);
count_p_bad += 1;
}else if(!(flags & WFB_PACKET_FEC_ONLY))
{
@ -805,7 +823,7 @@ void network_loop(int srv_port, Aggregator &agg, int log_interval)
if (rsize < (ssize_t)sizeof(wrxfwd_t))
{
fprintf(stderr, "short packet (rx fwd header)\n");
fprintf(stderr, "Short packet (rx fwd header)\n");
continue;
}
agg.process_packet(buf, rsize - sizeof(wrxfwd_t), fwd_hdr.wlan_idx, fwd_hdr.antenna, fwd_hdr.rssi, &sockaddr);
@ -893,7 +911,7 @@ int main(int argc, char* const *argv)
if (sodium_init() < 0)
{
fprintf(stderr, "libsodium init failed\n");
fprintf(stderr, "Libsodium init failed\n");
return 1;
}

View File

@ -105,7 +105,11 @@ void Transmitter::make_session_key(void)
// fill packet contents
wsession_data_t session_data = { .epoch = htobe64(epoch),
.channel_id = htobe32(channel_id) };
.channel_id = htobe32(channel_id),
.fec_type = WFB_FEC_VDM_RS,
.k = (uint8_t)fec_k,
.n = (uint8_t)fec_n,
};
memcpy(session_data.session_key, session_key, sizeof(session_key));
@ -470,7 +474,7 @@ int main(int argc, char * const *argv)
if (sodium_init() < 0)
{
fprintf(stderr, "libsodium init failed\n");
fprintf(stderr, "Libsodium init failed\n");
return 1;
}

View File

@ -110,7 +110,7 @@ static uint8_t ieee80211_header[] __attribute__((unused)) = {
+-- encrypted and authenticated by session key
2. Session packet:
wsession_hdr_t { packet_type = 2, nonce = random() }
wsession_data_t { epoch, channel_id, session_key } # -- encrypted and signed using rx and tx keys
wsession_data_t { epoch, channel_id, fec_type, fec_k, fec_n, session_key } # -- encrypted and signed using rx and tx keys
*/
// data nonce: 56bit block_idx + 8bit fragment_idx
@ -123,6 +123,9 @@ static uint8_t ieee80211_header[] __attribute__((unused)) = {
#define WFB_PACKET_DATA 0x1
#define WFB_PACKET_KEY 0x2
// FEC types
#define WFB_FEC_VDM_RS 0x1 //Reed-Solomon on Vandermonde matrix
// packet flags
#define WFB_PACKET_FEC_ONLY 0x1
@ -149,6 +152,9 @@ typedef struct {
typedef struct{
uint64_t epoch; // Drop session packets from old epoch
uint32_t channel_id; // (link_id << 8) + port_number
uint8_t fec_type; // Now only supported type is WFB_FEC_VDM_RS
uint8_t k; // FEC k
uint8_t n; // FEC n
uint8_t session_key[crypto_aead_chacha20poly1305_KEYBYTES];
} __attribute__ ((packed)) wsession_data_t;