From beaa791d668b643b8763a915b2360f6cc7300b02 Mon Sep 17 00:00:00 2001 From: Vasily Evseenko Date: Tue, 13 Sep 2022 14:17:18 +0300 Subject: [PATCH] Add FEC parameters to session packets --- Makefile | 2 +- doc/wfb-ng-std-draft.md | 34 +++++++++++++++++++++++-------- src/keygen.c | 2 +- src/rx.cpp | 44 +++++++++++++++++++++++++++++------------ src/tx.cpp | 8 ++++++-- src/wifibroadcast.hpp | 8 +++++++- 6 files changed, 72 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 5e4408f..16ca679 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/doc/wfb-ng-std-draft.md b/doc/wfb-ng-std-draft.md index 0aa8c2c..0219b52 100644 --- a/doc/wfb-ng-std-draft.md +++ b/doc/wfb-ng-std-draft.md @@ -1,6 +1,6 @@ % WFB-NG Data Transport Standard [Draft] % Vasily Evseenko <> -% 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. \ No newline at end of file diff --git a/src/keygen.c b/src/keygen.c index d982c99..9954973 100644 --- a/src/keygen.c +++ b/src/keygen.c @@ -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; } diff --git a/src/rx.cpp b/src/rx.cpp index f3bccab..26436cf 100644 --- a/src/rx.cpp +++ b/src/rx.cpp @@ -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; } diff --git a/src/tx.cpp b/src/tx.cpp index 64b5a1b..c9dc5f7 100644 --- a/src/tx.cpp +++ b/src/tx.cpp @@ -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; } diff --git a/src/wifibroadcast.hpp b/src/wifibroadcast.hpp index a6ff155..c98e798 100644 --- a/src/wifibroadcast.hpp +++ b/src/wifibroadcast.hpp @@ -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;