From 8d0b735a75e9cd6ad6d675c20d6ef7a266413d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Sun, 8 Dec 2024 01:55:38 +0100 Subject: [PATCH] fruity: Improve userspace NCM driver to perform batching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As we observed packet loss in bursty situations, and implementing batching avoids it. Co-authored-by: Håvard Sørbø --- src/fruity/ncm.vala | 192 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 160 insertions(+), 32 deletions(-) diff --git a/src/fruity/ncm.vala b/src/fruity/ncm.vala index 4be074513..ab3852924 100644 --- a/src/fruity/ncm.vala +++ b/src/fruity/ncm.vala @@ -23,13 +23,29 @@ namespace Frida.Fruity { } } + private uint32 ntb_in_max_size; + private uint32 ntb_out_max_size; + private uint16 ndp_out_divisor; + private uint16 ndp_out_payload_remainder; + private uint16 ndp_out_alignment; + private uint16 ntb_out_max_datagrams; private VirtualNetworkStack? _netstack; + private Gee.Queue pending_output = new Gee.ArrayQueue (); + private bool writing = false; private uint16 next_outgoing_sequence = 1; private InetAddress? _remote_ipv6_address; private Cancellable io_cancellable = new Cancellable (); + private const uint16 TRANSFER_HEADER_SIZE = 4 + 2 + 2 + 2 + 2; + + private enum CdcRequest { + GET_NTB_PARAMETERS = 0x80, + } + + private const uint16 NTB_PARAMETERS_MIN_SIZE = 28; + private enum EtherType { IPV6 = 0x86dd, } @@ -70,11 +86,31 @@ namespace Frida.Fruity { unowned LibUSB.DeviceHandle handle = device.handle; try { - Usb.check (handle.claim_interface (config.data_iface), "Failed to claim USB interface"); + Usb.check (handle.claim_interface (config.ctrl_iface), "Failed to claim control interface"); + Usb.check (handle.claim_interface (config.data_iface), "Failed to claim data interface"); } catch (Error e) { throw new Error.PERMISSION_DENIED ("%s", make_user_error_message (@"Unable to claim USB CDC-NCM interface ($(e.message))")); } + + var raw_ntb_params = yield device.control_transfer ( + LibUSB.RequestRecipient.INTERFACE | LibUSB.RequestType.CLASS | LibUSB.EndpointDirection.IN, + CdcRequest.GET_NTB_PARAMETERS, + 0, + config.ctrl_iface, + NTB_PARAMETERS_MIN_SIZE, + 1000, + cancellable); + if (raw_ntb_params.get_size () < NTB_PARAMETERS_MIN_SIZE) + throw new Error.PROTOCOL ("Truncated NTB parameters response"); + var ntb_params = new Buffer (raw_ntb_params, LITTLE_ENDIAN); + ntb_in_max_size = ntb_params.read_uint32 (4); + ntb_out_max_size = ntb_params.read_uint32 (16); + ndp_out_divisor = ntb_params.read_uint16 (20); + ndp_out_payload_remainder = ntb_params.read_uint16 (22); + ndp_out_alignment = ntb_params.read_uint16 (24); + ntb_out_max_datagrams = ntb_params.read_uint16 (26); + Usb.check (handle.set_interface_alt_setting (config.data_iface, config.data_altsetting), "Failed to set USB interface alt setting"); @@ -92,7 +128,7 @@ namespace Frida.Fruity { } private async void process_incoming_datagrams () { - var data = new uint8[64 * 1024]; + var data = new uint8[ntb_in_max_size]; while (true) { try { @@ -153,45 +189,134 @@ namespace Frida.Fruity { } while (ndp_index != 0); } - private async void on_netif_outgoing_datagram (Bytes datagram) { - uint16 transfer_header_length = 12; - uint16 ndp_header_length = 16; - uint16 alignment_padding_length = 2; + private void on_netif_outgoing_datagram (Bytes datagram) { + pending_output.offer (datagram); + + if (!writing) { + writing = true; + var source = new IdleSource (); + source.set_callback (() => { + process_pending_output.begin (); + return false; + }); + source.attach (MainContext.get_thread_default ()); + } + } - uint16 datagram_start_index = transfer_header_length + ndp_header_length + alignment_padding_length; - uint16 datagram_length = (uint16) datagram.length; + private async void process_pending_output () { + while (!pending_output.is_empty) { + size_t num_datagrams = ntb_out_max_datagrams; + TransferLayout layout; + while ((layout = TransferLayout.compute (pending_output, num_datagrams, ndp_out_alignment, + ndp_out_divisor, ndp_out_payload_remainder)).size > ntb_out_max_size) { + num_datagrams--; + } - uint16 sentinel_start_index = 0; - uint16 sentinel_size = 0; + var batch = new Gee.ArrayList (); + for (var i = 0; i != layout.offsets.size; i++) + batch.add (pending_output.poll ()); - uint16 sequence = next_outgoing_sequence++; - uint16 block_length = datagram_start_index + datagram_length; - uint16 ndp_index = transfer_header_length; - uint16 next_ndp_index = 0; + var transfer = build_output_transfer (batch, layout, next_outgoing_sequence++); + + try { + yield device.bulk_transfer (config.tx_address, transfer.get_data (), uint.MAX, io_cancellable); + } catch (GLib.Error e) { + break; + } + } + + writing = false; + } + + private class TransferLayout { + public uint16 size; + public uint16 ndp_header_offset; + public uint16 ndp_header_size; + public Gee.List offsets; + + public static TransferLayout compute (Gee.Collection datagrams, size_t max_datagrams, size_t ndp_alignment, + size_t datagram_modulus, size_t datagram_remainder) { + size_t ndp_header_base_size = 4 + 2 + 2; + size_t ndp_entry_size = 2 + 2; + size_t ethernet_header_size = 14; + + size_t ndp_header_offset = align (TRANSFER_HEADER_SIZE, ndp_alignment, 0); + size_t num_datagram_slots = size_t.min (datagrams.size, max_datagrams); + size_t ndp_header_size = ndp_header_base_size + ((num_datagram_slots + 1) * ndp_entry_size); + + size_t current_transfer_size = ndp_header_offset + ndp_header_size; + var offsets = new Gee.ArrayList (); + + uint i = 0; + foreach (var datagram in datagrams) { + var size = (uint16) datagram.get_size (); + + size_t start_offset = + align (current_transfer_size + ethernet_header_size, datagram_modulus, datagram_remainder); + size_t end_offset = start_offset + size; + if (end_offset > uint16.MAX) + break; + + current_transfer_size = end_offset; + offsets.add ((uint16) start_offset); + + i++; + if (i == max_datagrams) + break; + } - uint16 alignment_padding_value = 0; + return new TransferLayout () { + size = (uint16) current_transfer_size, + ndp_header_offset = (uint16) ndp_header_offset, + ndp_header_size = (uint16) ndp_header_size, + offsets = offsets, + }; + } - var frame = new BufferBuilder (LITTLE_ENDIAN) + private static size_t align (size_t val, size_t modulus, size_t remainder) { + var delta = val % modulus; + if (delta != remainder) + return val + modulus - delta + remainder; + return val; + } + } + + private static Bytes build_output_transfer (Gee.List datagrams, TransferLayout layout, uint16 sequence_number) { + var builder = new BufferBuilder (LITTLE_ENDIAN) .append_string ("NCMH", StringTerminator.NONE) - .append_uint16 (transfer_header_length) - .append_uint16 (sequence) - .append_uint16 (block_length) - .append_uint16 (ndp_index) + .append_uint16 (TRANSFER_HEADER_SIZE) + .append_uint16 (sequence_number) + .append_uint16 (layout.size) + .append_uint16 (layout.ndp_header_offset); + + uint16 next_ndp_index = 0; + builder + .seek (layout.ndp_header_offset) .append_string ("NCM0", StringTerminator.NONE) - .append_uint16 (ndp_header_length) - .append_uint16 (next_ndp_index) - .append_uint16 (datagram_start_index) - .append_uint16 (datagram_length) - .append_uint16 (sentinel_start_index) - .append_uint16 (sentinel_size) - .append_uint16 (alignment_padding_value) - .append_bytes (datagram) - .build (); + .append_uint16 (layout.ndp_header_size) + .append_uint16 (next_ndp_index); - try { - yield device.bulk_transfer (config.tx_address, frame.get_data (), uint.MAX, io_cancellable); - } catch (GLib.Error e) { + int i; + + i = 0; + foreach (var datagram in datagrams) { + builder + .append_uint16 (layout.offsets[i]) + .append_uint16 ((uint16) datagram.get_size ()); + i++; } + + i = 0; + foreach (var datagram in datagrams) { + builder + .seek (layout.offsets[i]) + .append_bytes (datagram); + i++; + } + + builder.seek (layout.size); + + return builder.build (); } private static InetAddress? try_infer_remote_address_from_datagram (Bytes datagram) { @@ -213,6 +338,7 @@ namespace Frida.Fruity { } internal class UsbNcmConfig { + public uint8 ctrl_iface; public uint8 data_iface; public int data_altsetting; public uint8 rx_address; @@ -256,6 +382,8 @@ namespace Frida.Fruity { foreach (var setting in iface.altsetting) { if (setting.bInterfaceClass == LibUSB.ClassCode.COMM && setting.bInterfaceSubClass == UsbCommSubclass.NCM) { + config.ctrl_iface = setting.bInterfaceNumber; + try { parse_cdc_header (setting.extra, out config.mac_address_index); found_cdc_header = true;