mirror of
https://github.com/svpcom/wfb-ng.git
synced 2025-03-14 10:33:58 -03:00
Add FEC parameters to session packets
This commit is contained in:
parent
c00f4ca081
commit
beaa791d66
2
Makefile
2
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
|
||||
|
@ -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.
|
@ -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;
|
||||
}
|
||||
|
||||
|
44
src/rx.cpp
44
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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user