From d2ef1edfeaea4103664cbe57b76ee4a5b5f5408e Mon Sep 17 00:00:00 2001 From: Vasily Evseenko Date: Thu, 1 Aug 2024 19:11:21 +0300 Subject: [PATCH] Add ability to add optional attributes to session packet without breaking protocol format --- doc/wfb-ng-std-draft.md | 21 ++++++++++-- src/rx.cpp | 67 +++++++++++++++++++++++++++----------- src/rx.hpp | 2 ++ src/tx.cpp | 71 ++++++++++++++++++++++++++++++----------- src/tx.hpp | 15 +++++---- src/wifibroadcast.hpp | 29 ++++++++++++++--- 6 files changed, 154 insertions(+), 51 deletions(-) diff --git a/doc/wfb-ng-std-draft.md b/doc/wfb-ng-std-draft.md index fb56a48..5b2f3d3 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 <> -% Sep 13, 2022 +% Aug 1, 2024 ## Introduction @@ -15,7 +15,7 @@ using ordinary wifi adapters that support the transmission of "raw" packets. At ## Areas of use: - Communication between robots and ground station -- Communication of amateur satellites (CUBESAT) with the earth +- Communication of amateur satellites (CUBESAT) with the Earth - Digital radio communication on the ground - ... @@ -76,6 +76,7 @@ There are two packet types 2. Session packet (`packet_type = 2`, has encrypted and authenticated session parameters and session key, see note below) Currently only supported FEC type is Reed-Solomon on Vandermonde matrix, but new FEC algorithms can be added in future. +Session packet can have any amount of optional tags. Receiver should ignore all unknown or unused tags. ``` .c // FEC types @@ -105,7 +106,13 @@ Currently only supported FEC type is Reed-Solomon on Vandermonde matrix, but new +-- encrypted and authenticated by session key 2. Session packet: wsession_hdr_t { packet_type = 2, nonce = random() } - wsession_data_t { epoch, channel_id, fec_type, fec_k, fec_n, session_key } # -- encrypted and authenticated using crypto_box_easy(rx_publickey, tx_secretkey) + wsession_data_t { epoch, channel_id, # + fec_type, fec_k, fec_n, # + session_key, # + optional TLV list } # -- encrypted and authenticated using crypto_box_easy(rx_publickey, tx_secretkey) + + Where TLV list is a list of optional tags with the following format: + [{tag_id : tag_size : }, ... ] data nonce: 56bit block_idx + 8bit fragment_idx session nonce: crypto_box_NONCEBYTES of random bytes @@ -129,8 +136,16 @@ Currently only supported FEC type is Reed-Solomon on Vandermonde matrix, but new uint8_t k; // FEC k uint8_t n; // FEC n uint8_t session_key[crypto_aead_chacha20poly1305_KEYBYTES]; + uint8_t tags[]; // Optional TLV attributes } __attribute__ ((packed)) wsession_data_t; + // TLV item header + typedef struct { + uint8_t id; + uint16_t len; + uint8_t value[]; + } __attribute__ ((packed)) tlv_hdr_t; + // Data packet. Embed FEC-encoded data typedef struct { diff --git a/src/rx.cpp b/src/rx.cpp index 3111121..1df4cb0 100644 --- a/src/rx.cpp +++ b/src/rx.cpp @@ -508,12 +508,35 @@ void Aggregator::log_rssi(const sockaddr_in *sockaddr, uint8_t wlan_idx, const u } } +int Aggregator::get_tag(const void *buf, size_t size, uint8_t tag_id, void *value, size_t value_size) +{ + tlv_hdr_t *p = (tlv_hdr_t*)buf; + void *end = (uint8_t*)buf + size; + + while((void*)(p + 1) <= end) + { + if(p->id != tag_id) + { + p = (tlv_hdr_t*)((uint8_t*)(p + 1) + p->len); + continue; + } + if(p->len > value_size) return -1; + if(p->value + p->len > end) return -1; + memcpy(value, p->value, p->len); + return p->len; + } + + return -1; +} void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_idx, const uint8_t *antenna, const int8_t *rssi, const int8_t *noise, uint16_t freq, uint8_t mcs_index, uint8_t bandwidth, sockaddr_in *sockaddr) { - wsession_data_t new_session_data; + uint8_t session_tmp[MAX_SESSION_PACKET_SIZE - crypto_box_MACBYTES - sizeof(wsession_hdr_t)]; + wsession_data_t* new_session_data = NULL; + //size_t new_session_tags_size = 0; + count_p_all += 1; count_b_all += size; @@ -537,17 +560,20 @@ void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_id } break; - case WFB_PACKET_KEY: - if(size != sizeof(wsession_hdr_t) + sizeof(wsession_data_t) + crypto_box_MACBYTES) + case WFB_PACKET_SESSION: + new_session_data = (wsession_data_t*)session_tmp; + + if(size < sizeof(wsession_hdr_t) + sizeof(wsession_data_t) + crypto_box_MACBYTES || \ + size > MAX_SESSION_PACKET_SIZE) { fprintf(stderr, "Invalid session key packet\n"); count_p_bad += 1; return; } - if(crypto_box_open_easy((uint8_t*)&new_session_data, + if(crypto_box_open_easy((uint8_t*)session_tmp, buf + sizeof(wsession_hdr_t), - sizeof(wsession_data_t) + crypto_box_MACBYTES, + size - sizeof(wsession_hdr_t), ((wsession_hdr_t*)buf)->session_nonce, tx_publickey, rx_secretkey) != 0) { @@ -556,37 +582,39 @@ void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_id return; } - if (be64toh(new_session_data.epoch) < epoch) + //new_session_tags_size = size - (sizeof(wsession_hdr_t) + sizeof(wsession_data_t) + crypto_box_MACBYTES); + + if (be64toh(new_session_data->epoch) < epoch) { - fprintf(stderr, "Session epoch doesn't match: %" PRIu64 " < %" PRIu64 "\n", be64toh(new_session_data.epoch), epoch); + fprintf(stderr, "Session epoch doesn't match: %" PRIu64 " < %" PRIu64 "\n", be64toh(new_session_data->epoch), epoch); count_p_dec_err += 1; return; } - if (be32toh(new_session_data.channel_id) != channel_id) + if (be32toh(new_session_data->channel_id) != channel_id) { - fprintf(stderr, "Session channel_id doesn't match: %u != %u\n", be32toh(new_session_data.channel_id), channel_id); + fprintf(stderr, "Session channel_id doesn't match: %u != %u\n", be32toh(new_session_data->channel_id), channel_id); count_p_dec_err += 1; return; } - if (new_session_data.fec_type != WFB_FEC_VDM_RS) + if (new_session_data->fec_type != WFB_FEC_VDM_RS) { - fprintf(stderr, "Unsupported FEC codec type: %d\n", new_session_data.fec_type); + fprintf(stderr, "Unsupported FEC codec type: %d\n", new_session_data->fec_type); count_p_dec_err += 1; return; } - if (new_session_data.n < 1) + if (new_session_data->n < 1) { - fprintf(stderr, "Invalid FEC N: %d\n", new_session_data.n); + fprintf(stderr, "Invalid FEC N: %d\n", new_session_data->n); count_p_dec_err += 1; return; } - if (new_session_data.k < 1 || new_session_data.k > new_session_data.n) + if (new_session_data->k < 1 || new_session_data->k > new_session_data->n) { - fprintf(stderr, "Invalid FEC K: %d\n", new_session_data.k); + fprintf(stderr, "Invalid FEC K: %d\n", new_session_data->k); count_p_dec_err += 1; return; } @@ -594,22 +622,23 @@ void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_id count_p_dec_ok += 1; log_rssi(sockaddr, wlan_idx, antenna, rssi, noise, freq, mcs_index, bandwidth); - if (memcmp(session_key, new_session_data.session_key, sizeof(session_key)) != 0) + if (memcmp(session_key, new_session_data->session_key, sizeof(session_key)) != 0) { - epoch = be64toh(new_session_data.epoch); - memcpy(session_key, new_session_data.session_key, sizeof(session_key)); + epoch = be64toh(new_session_data->epoch); + memcpy(session_key, new_session_data->session_key, sizeof(session_key)); if (fec_p != NULL) { deinit_fec(); } - init_fec(new_session_data.k, new_session_data.n); + init_fec(new_session_data->k, new_session_data->n); fprintf(stdout, "%" PRIu64 "\tSESSION\t%" PRIu64 ":%u:%d:%d\n", get_time_ms(), epoch, WFB_FEC_VDM_RS, fec_k, fec_n); fflush(stdout); } + return; default: diff --git a/src/rx.hpp b/src/rx.hpp index 250e63b..564470e 100644 --- a/src/rx.hpp +++ b/src/rx.hpp @@ -217,6 +217,8 @@ private: const int8_t *noise, uint16_t freq, uint8_t mcs_index, uint8_t bandwidth); int get_block_ring_idx(uint64_t block_idx); int rx_ring_push(void); + int get_tag(const void *buf, size_t size, uint8_t tag_id, void *value, size_t value_size); + fec_t* fec_p; int fec_k; // RS number of primary fragments in block int fec_n; // RS total number of fragments in block diff --git a/src/tx.cpp b/src/tx.cpp index 369ff04..ce32e9b 100644 --- a/src/tx.cpp +++ b/src/tx.cpp @@ -49,13 +49,19 @@ using namespace std; #include "tx.hpp" -Transmitter::Transmitter(int k, int n, const string &keypair, uint64_t epoch, uint32_t channel_id, uint32_t fec_delay) : \ +Transmitter::Transmitter(int k, int n, const string &keypair, uint64_t epoch, uint32_t channel_id, uint32_t fec_delay, std::vector &tags) : \ fec_k(k), fec_n(n), block_idx(0), fragment_idx(0), max_packet_size(0), epoch(epoch), channel_id(channel_id), - fec_delay(fec_delay) + fec_delay(fec_delay), + tx_secretkey{}, + rx_publickey{}, + session_key{}, + session_packet{}, + session_packet_size(0), + tags(tags) { fec_p = fec_new(fec_k, fec_n); @@ -97,33 +103,59 @@ Transmitter::~Transmitter() } -void Transmitter::make_session_key(void) +void Transmitter::make_session_key() { // init session key randombytes_buf(session_key, sizeof(session_key)); // fill packet header - wsession_hdr_t *session_hdr = (wsession_hdr_t *)session_key_packet; - session_hdr->packet_type = WFB_PACKET_KEY; + wsession_hdr_t *session_hdr = (wsession_hdr_t *)session_packet; + session_hdr->packet_type = WFB_PACKET_SESSION; randombytes_buf(session_hdr->session_nonce, sizeof(session_hdr->session_nonce)); // fill packet contents - wsession_data_t session_data = { .epoch = htobe64(epoch), - .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)); + uint8_t tmp[MAX_SESSION_PACKET_SIZE - crypto_box_MACBYTES - sizeof(wsession_hdr_t)]; - if (crypto_box_easy(session_key_packet + sizeof(wsession_hdr_t), - (uint8_t*)&session_data, sizeof(session_data), + // Fill fixed headers + { + wsession_data_t* session_data = (wsession_data_t*)tmp; + assert(sizeof(*session_data) <= sizeof(tmp)); + + session_data->epoch = htobe64(epoch); + session_data->channel_id = htobe32(channel_id); + session_data->fec_type = WFB_FEC_VDM_RS; + session_data->k = (uint8_t)fec_k; + session_data->n = (uint8_t)fec_n; + + assert(sizeof(session_data->session_key) == sizeof(session_key)); + memcpy(session_data->session_key, session_key, sizeof(session_key)); + } + + // Fill optional Tags + + uint32_t session_data_size = sizeof(wsession_data_t); + for(auto it=tags.begin(); it != tags.end(); it++) + { + tlv_hdr_t* tlv = (tlv_hdr_t*)((uint8_t*)tmp + session_data_size); + session_data_size += sizeof(tlv_hdr_t) + it->len; + assert(session_data_size <= sizeof(tmp)); + + tlv->id = it->id; + tlv->len = it->len; + memcpy(tlv->value, it->value, it->len); + } + + if (crypto_box_easy(session_packet + sizeof(wsession_hdr_t), + (uint8_t*)tmp, session_data_size, session_hdr->session_nonce, rx_publickey, tx_secretkey) != 0) { throw runtime_error("Unable to make session key!"); } + + session_packet_size = sizeof(wsession_hdr_t) + session_data_size + crypto_box_MACBYTES; + assert(session_packet_size <= MAX_SESSION_PACKET_SIZE); } void RawSocketTransmitter::set_mark(uint32_t idx) @@ -144,9 +176,9 @@ void RawSocketTransmitter::set_mark(uint32_t idx) RawSocketTransmitter::RawSocketTransmitter(int k, int n, const string &keypair, uint64_t epoch, uint32_t channel_id, uint32_t fec_delay, - const vector &wlans, shared_ptr radiotap_header, size_t radiotap_header_len, + std::vector &tags, const vector &wlans, shared_ptr radiotap_header, size_t radiotap_header_len, uint8_t frame_type, bool use_qdisc, uint32_t fwmark) : \ - Transmitter(k, n, keypair, epoch, channel_id, fec_delay), + Transmitter(k, n, keypair, epoch, channel_id, fec_delay, tags), channel_id(channel_id), current_output(0), ieee80211_seq(0), @@ -335,7 +367,7 @@ void Transmitter::send_block_fragment(size_t packet_size) void Transmitter::send_session_key(void) { //fprintf(stderr, "Announce session key\n"); - inject_packet((uint8_t*)session_key_packet, sizeof(session_key_packet)); + inject_packet((uint8_t*)session_packet, session_packet_size); } bool Transmitter::send_packet(const uint8_t *buf, size_t size, uint8_t flags) @@ -890,6 +922,7 @@ int main(int argc, char * const *argv) fflush(stdout); } + std::vector tags; shared_ptr t; uint32_t channel_id = (link_id << 8) + radio_port; @@ -898,9 +931,9 @@ int main(int argc, char * const *argv) { fprintf(stderr, "Using %zu ports from %d for wlan emulation\n", wlans.size(), debug_port); t = shared_ptr(new UdpTransmitter(k, n, keypair, "127.0.0.1", debug_port, epoch, channel_id, - fec_delay, use_qdisc, fwmark)); + fec_delay, tags, use_qdisc, fwmark)); } else { - t = shared_ptr(new RawSocketTransmitter(k, n, keypair, epoch, channel_id, fec_delay, + t = shared_ptr(new RawSocketTransmitter(k, n, keypair, epoch, channel_id, fec_delay, tags, wlans, radiotap_header, radiotap_header_len, frame_type, use_qdisc, fwmark)); } diff --git a/src/tx.hpp b/src/tx.hpp index 4c77218..dda93b5 100644 --- a/src/tx.hpp +++ b/src/tx.hpp @@ -33,7 +33,7 @@ class Transmitter { public: - Transmitter(int k, int m, const std::string &keypair, uint64_t epoch, uint32_t channel_id, uint32_t fec_delay); + Transmitter(int k, int m, const std::string &keypair, uint64_t epoch, uint32_t channel_id, uint32_t fec_delay, std::vector &tags); virtual ~Transmitter(); bool send_packet(const uint8_t *buf, size_t size, uint8_t flags); void send_session_key(void); @@ -62,7 +62,9 @@ private: uint8_t tx_secretkey[crypto_box_SECRETKEYBYTES]; uint8_t rx_publickey[crypto_box_PUBLICKEYBYTES]; uint8_t session_key[crypto_aead_chacha20poly1305_KEYBYTES]; - uint8_t session_key_packet[sizeof(wsession_hdr_t) + sizeof(wsession_data_t) + crypto_box_MACBYTES]; + uint8_t session_packet[MAX_SESSION_PACKET_SIZE]; + uint16_t session_packet_size; + std::vector &tags; }; class txAntennaItem @@ -108,8 +110,9 @@ typedef std::unordered_map tx_antenna_stat_t; class RawSocketTransmitter : public Transmitter { public: - RawSocketTransmitter(int k, int m, const std::string &keypair, uint64_t epoch, uint32_t channel_id, uint32_t fec_delay, const std::vector &wlans, - shared_ptr radiotap_header, size_t radiotap_header_len, uint8_t frame_type, bool use_qdisc, uint32_t fwmark); + RawSocketTransmitter(int k, int m, const std::string &keypair, uint64_t epoch, uint32_t channel_id, uint32_t fec_delay, std::vector &tags, + const std::vector &wlans, shared_ptr radiotap_header, size_t radiotap_header_len, + uint8_t frame_type, bool use_qdisc, uint32_t fwmark); virtual ~RawSocketTransmitter(); virtual void select_output(int idx) { @@ -143,8 +146,8 @@ class UdpTransmitter : public Transmitter { public: UdpTransmitter(int k, int m, const std::string &keypair, const std::string &client_addr, int base_port, uint64_t epoch, uint32_t channel_id, - uint32_t fec_delay, bool use_qdisc, uint32_t fwmark): \ - Transmitter(k, m, keypair, epoch, channel_id, fec_delay), base_port(base_port), use_qdisc(use_qdisc), fwmark(fwmark) + uint32_t fec_delay, std::vector &tags, bool use_qdisc, uint32_t fwmark): \ + Transmitter(k, m, keypair, epoch, channel_id, fec_delay, tags), base_port(base_port), use_qdisc(use_qdisc), fwmark(fwmark) { sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) throw std::runtime_error(string_format("Error opening socket: %s", strerror(errno))); diff --git a/src/wifibroadcast.hpp b/src/wifibroadcast.hpp index 81b7522..64e6b69 100644 --- a/src/wifibroadcast.hpp +++ b/src/wifibroadcast.hpp @@ -170,7 +170,10 @@ static const 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, fec_type, fec_k, fec_n, session_key } # -- encrypted and signed using rx and tx keys + wsession_data_t { epoch, channel_id, # + fec_type, fec_k, fec_n, # + session_key, # + optional TLV list } # -- encrypted and signed using rx and tx keys */ // data nonce: 56bit block_idx + 8bit fragment_idx @@ -180,8 +183,8 @@ static const uint8_t ieee80211_header[] __attribute__((unused)) = { #define MAX_BLOCK_IDX ((1LLU << 55) - 1) // packet types -#define WFB_PACKET_DATA 0x1 -#define WFB_PACKET_KEY 0x2 +#define WFB_PACKET_DATA 0x1 +#define WFB_PACKET_SESSION 0x2 // FEC types #define WFB_FEC_VDM_RS 0x1 //Reed-Solomon on Vandermonde matrix @@ -213,15 +216,32 @@ typedef struct { uint8_t session_nonce[crypto_box_NONCEBYTES]; // random data } __attribute__ ((packed)) wsession_hdr_t; -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]; + uint8_t tags[]; // Optional TLV attributes } __attribute__ ((packed)) wsession_data_t; + +// TLV attr header (in packet) +typedef struct { + uint8_t id; + uint16_t len; + uint8_t value[]; +} __attribute__ ((packed)) tlv_hdr_t; + + +// TLV item +typedef struct { + uint8_t id; + uint16_t len; + void* value; +} tags_item_t; + // Data packet. Embed FEC-encoded data typedef struct { @@ -239,6 +259,7 @@ typedef struct { #define MAX_PAYLOAD_SIZE (WIFI_MTU - sizeof(ieee80211_header) - sizeof(wblock_hdr_t) - crypto_aead_chacha20poly1305_ABYTES - sizeof(wpacket_hdr_t)) #define MAX_FEC_PAYLOAD (WIFI_MTU - sizeof(ieee80211_header) - sizeof(wblock_hdr_t) - crypto_aead_chacha20poly1305_ABYTES) #define MAX_FORWARDER_PACKET_SIZE (WIFI_MTU - sizeof(ieee80211_header)) +#define MAX_SESSION_PACKET_SIZE (WIFI_MTU - sizeof(ieee80211_header)) int open_udp_socket_for_rx(int port, int rcv_buf_size); uint64_t get_time_ms(void);