Skip to content

Commit

Permalink
fruity: Improve userspace NCM driver to perform batching
Browse files Browse the repository at this point in the history
As we observed packet loss in bursty situations, and implementing
batching avoids it.

Co-authored-by: Håvard Sørbø <[email protected]>
  • Loading branch information
oleavr and hsorbo committed Dec 8, 2024
1 parent 930776d commit 8d0b735
Showing 1 changed file with 160 additions and 32 deletions.
192 changes: 160 additions & 32 deletions src/fruity/ncm.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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<Bytes> pending_output = new Gee.ArrayQueue<Bytes> ();
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,
}
Expand Down Expand Up @@ -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");

Expand All @@ -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 {
Expand Down Expand Up @@ -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<Bytes> ();
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<uint16> offsets;

public static TransferLayout compute (Gee.Collection<Bytes> 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<uint16> ();

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<Bytes> 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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 8d0b735

Please sign in to comment.