RNDIS 下行速度低

简介: RNDIS 下行速度低

现象

rndis 是微软定义的一套通讯方案。类似的协议还有 qmimbimecmncm 等。


rndis 协议足够简单,可靠。所以最近在使用一款 quectel 公司模块时采用的就是 rndis 模式。在 linux 下 对应驱动是 rndis_host 驱动。 windows 10 下自带 rndis 驱动! 拿到模块首先测速度! 发现模块下行速度 Windows 上速度比 Linux 高很多,而且上行速度则差不多! 单独对比 Linux ,发现上行又比下行高很多。。。问题很奇怪!


产生原因

分析下行收包逻辑:

/*
 * DATA -- host must not write zlps
 */
int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{
    int tm = 0;
    /* This check is no longer done by usbnet */
    if (skb->len < dev->net->hard_header_len)
        return 0;
    /* peripheral may have batched packets to us... */
    while (likely(skb->len)) {
        struct rndis_data_hdr    *hdr = (void *)skb->data;
        struct sk_buff        *skb2;
        u32            msg_type, msg_len, data_offset, data_len;
        msg_type = le32_to_cpu(hdr->msg_type);
        msg_len = le32_to_cpu(hdr->msg_len);
        data_offset = le32_to_cpu(hdr->data_offset);
        data_len = le32_to_cpu(hdr->data_len);
        /* don't choke if we see oob, per-packet data, etc */
        if (unlikely(msg_type != RNDIS_MSG_PACKET || skb->len < msg_len
                || (data_offset + data_len + 8) > msg_len)) {
            dev->net->stats.rx_frame_errors++;
            netdev_dbg(dev->net, "bad rndis message %d/%d/%d/%d, len %d\n",
                   le32_to_cpu(hdr->msg_type),
                   msg_len, data_offset, data_len, skb->len);
            return 0;
        }
        skb_pull(skb, 8 + data_offset);
        /* at most one packet left? */
        if (likely((data_len - skb->len) <= sizeof *hdr)) {
            skb_trim(skb, data_len);
            break;
        }
        /* try to return all the packets in the batch */
        skb2 = skb_clone(skb, GFP_ATOMIC);
        if (unlikely(!skb2))
            break;
        skb_pull(skb, msg_len - sizeof *hdr);
        skb_trim(skb2, data_len);
        usbnet_skb_return(dev, skb2);
    }
    /* caller will usbnet_skb_return the remaining packet */
    return 1;
}
EXPORT_SYMBOL_GPL(rndis_rx_fixup);点击复制复制失败已复制


收包代码稍微复杂点,因为收包需要考虑到聚合报文的情况!因此起了一个while循环判断。while 里面就是剥离rndis 报文头,并调用网卡收包函数的过程!


这里对 skb 有两次偏移操作:

  1. skb_pull(skb, 8 + data_offset); 这一步从skb 去除当前消息的 rndis 报文头!
  2. skb_pull(skb, msg_len - sizeof *hdr); 因为skb payload 部分已经在skb2 有了一份clone,那么skb 当前的payload 就不重要了。因此,这里实际要做的是继续从skb剥离当前rndis 报文的数据部分(报文头已经剥离掉了)。这一步操作后,skb 将指向下一个rndis 报文的 rndis 报文头!


但是这里第 2 步逻辑错了,这里直接减去 rndis 报文头是错的! 因为 rndis 报文的 payload 之前并不一定全是协议头, payload 的偏移是头部 offset 定义的。


解决方案

方案很简单,修改偏移计算逻辑!

/*
 * DATA -- host must not write zlps
 */
int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{
    int tm = 0;
    /* This check is no longer done by usbnet */
    if (skb->len < dev->net->hard_header_len)
        return 0;
    /* peripheral may have batched packets to us... */
    while (likely(skb->len)) {
        struct rndis_data_hdr    *hdr = (void *)skb->data;
        struct sk_buff        *skb2;
        u32            msg_type, msg_len, data_offset, data_len;
        msg_type = le32_to_cpu(hdr->msg_type);
        msg_len = le32_to_cpu(hdr->msg_len);
        data_offset = le32_to_cpu(hdr->data_offset);
        data_len = le32_to_cpu(hdr->data_len);
        /* don't choke if we see oob, per-packet data, etc */
        if (unlikely(msg_type != RNDIS_MSG_PACKET || skb->len < msg_len
                || (data_offset + data_len + 8) > msg_len)) {
            dev->net->stats.rx_frame_errors++;
            netdev_dbg(dev->net, "bad rndis message %d/%d/%d/%d, len %d\n",
                   le32_to_cpu(hdr->msg_type),
                   msg_len, data_offset, data_len, skb->len);
            return 0;
        }
        skb_pull(skb, 8 + data_offset);
        /* at most one packet left? */
        if (likely((data_len - skb->len) <= sizeof *hdr)) {
            skb_trim(skb, data_len);
            break;
        }
        /* try to return all the packets in the batch */
        skb2 = skb_clone(skb, GFP_ATOMIC);
        if (unlikely(!skb2))
            break;
        skb_pull(skb, msg_len - data_offset - 8); // here is what I fixed
        skb_trim(skb2, data_len);
        usbnet_skb_return(dev, skb2);
    }
    /* caller will usbnet_skb_return the remaining packet */
    return 1;
}
EXPORT_SYMBOL_GPL(rndis_rx_fixup);
点击复制复制失败已复制


注意

这里的修改仅是规避方案,根本原因还是模块侧封包逻辑的问题。

目录
相关文章
|
人工智能 自然语言处理 文字识别
DeepMind首发游戏AI智能体SIMA:开启虚拟世界的智能探索之旅
【4月更文挑战第3天】DeepMind推出了SIMA,一种能在多个3D环境中执行语言指令的智能体,标志着AI在理解和互动虚拟世界上的进步。SIMA通过多样化的训练数据学习导航、操作、决策等技能,并结合预训练模型处理高维度输入输出。尽管在复杂任务上仍有提升空间,SIMA展现了正向迁移能力和潜力,为AI研究和未来机器人技术铺平道路。然而,仍需解决鲁棒性、可控性、评估方法及道德安全问题。
666 4
DeepMind首发游戏AI智能体SIMA:开启虚拟世界的智能探索之旅
|
缓存 监控 数据可视化
linux查看内存信息
在Linux中检查内存使用:`free -h`或`-m`显示简洁内存统计;`cat /proc/meminfo`获取详细信息;`top`或`htop`(如果安装)实时监控进程内存占用;`vmstat`查看虚拟内存统计;`sar -r`(需要sysstat)报告系统内存活动。图形工具如Gnome System Monitor提供可视化界面。
1297 4
win11关闭快速启动
win11关闭快速启动
690 0
|
8月前
|
人工智能 API 开发者
Dify x AiOnly平台:手把手教你调用GPT-5从零构建AI工作流!
本文介绍如何通过Dify与AiOnly平台,快速构建基于GPT-5等顶尖大模型的AI应用。涵盖环境部署、模型接入、工作流编排及实战案例,助力开发者低门槛打造专属聊天机器人,轻松实现AI应用落地。(238字)
|
网络协议 Dubbo Java
一文搞懂NIO、AIO、BIO的核心区别(建议收藏)
本文详细解析了NIO、AIO、BIO的核心区别,NIO的三个核心概念,以及NIO在Java框架中的应用等。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
一文搞懂NIO、AIO、BIO的核心区别(建议收藏)
|
8月前
|
缓存 人工智能 测试技术
腾讯混元 3D 世界模型家族又添新成员Voyager:支持超长距离漫游
9 月 2 日,腾讯混元宣布,其3D世界模型系列最新成员——HunyuanWorld-Voyager(简称混元Voyager)发布并开源,这将推动AI在空间智能领域的应用扩展,为虚拟现实、物理仿真、游戏开发等领域提供高保真3D场景漫游能力,加速行业应用落地。
510 0
|
存储 安全 算法
Linux命令sha256sum详解
`sha256sum`是Linux中用于计算文件SHA-256哈希的命令,确保数据完整性。它基于安全哈希算法,产生不可逆的64字符哈希值,用于验证文件未被篡改。主要参数包括`-b`(二进制模式)、`-c`(检查校验和)、`-t`(文本模式)。应用示例包括计算单个文件哈希、校验文件哈希及批量处理多个文件。最佳实践包括定期验证文件、保存校验和文件和结合其他安全工具使用。
|
Ubuntu 安全 网络协议
|
运维 监控 安全
物联网卡:物联网卡为什么不能使用在手机上
物联网卡(IoT SIM卡)通常是为物联网设备设计的,这些设备包括但不限于智能家居设备、可穿戴设备、工业监控设备等。它们与用于智能手机的SIM卡有所不同,主要是因为设计目标、功能限制、资费结构以及网络接入策略上的差异。以下是物联网卡不能直接在手机上使用的主要原因:
|
监控 安全 Linux
docker 命令 --cap-add NET_ADMIN 什么作用
`--cap-add NET_ADMIN` 是 Docker 容器运行时的一个参数,用于添加网络管理员权限(NET_ADMIN capability)给容器。 在 Linux 系统中,每个进程都有特定的权限来执行特定的操作。网络管理员权限是一种较高级的权限,它允许容器内的进程执行一些需要网络配置和管理的操作,例如配置网络接口、修改路由表、设置防火墙规则等。 通过添加 `--cap-add NET_ADMIN` 参数,Docker 容器将获得网络管理员权限,使其能够执行更高级的网络操作。这对于某些特定的应用场景,如网络监控、网络测试等非常有用。 需要注意的是,使用网络管理员权限可能存在安全
3478 1