I am trying to make a WiFi boardcast application in Python. The idea is to place two network interface cards (NICs) in monitor mode, and inject packets such that two devices can communicate. This has been done before, especially in the context of drone RC/telemetry/video links, some examples include OpenHD, ez-Wifibroadcast and WFB-NG. I specifically tested my hardware on OpenHD and was able to achieve a bitrate of ~4 MBits/s (2 x Raspberry Pi 3B+, 2 x TL-WN722N V2, 1 USB camera).
I put the two NICs into monitor mode, confirmed with iwconfig. They are also at the same frequency.
In making my Python application, I noticed a very poor bitrate and packet loss. I created a test script to demonstrate:
import socket
from time import perf_counter, sleep
import binascii
import sys
import threading
class PacketLoss:
def __init__(self, transmitter_receiver, size, iface1, iface2):
self.transmitter_receiver = transmitter_receiver
self.iface1, self.iface2 = iface1, iface2
self.send = True
self.counting = False
self.packet_recv_counter = 0
self.packet_send_counter = 0
self.t_target = 1
self.t_true = None
payload = (0).to_bytes(size, byteorder='little', signed=False)
self.payload_length = len(payload)
h = bytes((self.payload_length).to_bytes(2, byteorder='little', signed=False))
radiotap_header = b'\x00\x00\x0c\x00\x04\x80\x00\x00' + bytes([6 * 2]) + b'\x00\x18\x00'
frame_type = b'\xb4\x00\x00\x00'
self.msg = radiotap_header + frame_type + transmitter_receiver + h + payload
def inject_packets(self):
rawSocket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0004))
rawSocket.bind((self.iface1, 0))
t0 = perf_counter()
while (self.send):
rawSocket.send(self.msg)
if self.counting:
self.packet_send_counter += 1
self.t_send_true = perf_counter() - t0
rawSocket.close()
def sniff_packets(self):
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003))
s.bind((self.iface2, 0))
t0 = perf_counter()
self.counting = True
while perf_counter() - t0 < self.t_target:
packet = s.recv(200)
match = packet[22:34]
if match == self.transmitter_receiver:
self.packet_recv_counter += 1
self.t_true = perf_counter() - t0
self.send = False
self.counting = False
s.close()
def get_stats(self):
dr_send = self.packet_send_counter * self.payload_length / self.t_send_true
dr_recv = self.packet_recv_counter * self.payload_length / self.t_true
packet_loss = 1 - self.packet_recv_counter/self.packet_send_counter
return dr_send, dr_recv, packet_loss
def print_statistics(self):
print(f'In {self.t_true:.3f}s, sent {self.packet_send_counter} captured {self.packet_recv_counter} packets.')
dr_send, dr_recv, packet_loss = self.get_stats()
print(f'{dr_send=:.1f}B/s; {dr_recv=:.1f}B/s; Packet loss: {packet_loss*100:.1f}%')
I tested PacketLoss with the following:
if __name__ == '__main__':
# Get test parameters
iface1, iface2 = sys.argv[1], sys.argv[2] # eg 'wlan0', 'wlan1'
size = int(sys.argv[3]) # eg 60
# Recv/transmit mac addresses
MAC_r = '35:eb:9e:3b:75:33'
MAC_t = 'e5:26:be:89:65:27'
receiver_transmitter = binascii.unhexlify(MAC_r.replace(':', '')) + binascii.unhexlify(MAC_t.replace(':', ''))
# create testing object
pl = PacketLoss(receiver_transmitter, size, iface1, iface2)
# start injecting packets
t = threading.Thread(target=pl.inject_packets)
t.start()
# wait a bit
sleep(0.1)
# start sniffing
pl.sniff_packets()
# print statistics
pl.print_statistics()
I tested with packet sizes of 30, 60 and 120, getting the following results:
| Packet size (B) | Received data rate (kB/s) | Packet loss (%) |
|---|---|---|
| 30 | 32.3 | 47.3 |
| 60 | 51.3 | 57.2 |
| 120 | 63.9 | 73.0 |
Eventually I want to use my program to stream video (and RC/telemetry), and I would need at least 125 kB/s received data rate, and a much better packet loss. Am I overlooking something that would increase the data rate and reduce the packet loss?
Thank you