网卡获取模组ip失败问题解析

现象

执行网关后端程序,发现ip不显示,ifconfig也不显示ip:

复制代码
ifconfig enx62fde47ffdbb
enx62fde47ffdbb: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        ether 62:fd:e4:7f:fd:bb  txqueuelen 1000  (Ethernet)
        RX packets 150  bytes 11956 (11.9 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 87  bytes 7754 (7.7 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

但是模组已经拿到ip了

复制代码
+SPCHMDATAINFO:1,1,sipa_eth0,1,172.170.11.114/16,8.8.8.8 8.8.4.4,,1400,1
notification:
^NDISDUN:sipa_usb0,1,enabled,IP,172.170.11.114,255.255.255.255,172.170.11.114,8.8.8.8,8.8.4.4

解决

用gdb检查后端程序,发现问题:mypopen执行失败。

复制代码
string UsbnameGet() {
    string res;
    // string cmd = "dmesg | grep 'CDC NCM' | grep 'register' | awk '{print $5}' | cut -d: -f1"; // 这个获取的是驱动刚注册时获取的名字
    // 获取到的就是实际在用的接口名:
    string cmd = "for i in /sys/class/net/*; do "
             "readlink $i/device/driver 2>/dev/null | grep -q cdc_ncm && basename $i; "
             "done";

    if(!myPopen(cmd, res)){
        hloge("check 5G net card failed");
        return "";
    }
    std::cout<<"5G网卡:"<<res<<std::endl;
    return res;
}

可能是指令没权限,可能mypopen有问题,先逐步排查从最有可能的地方入手:

1.修改mypopen为popen

ifconfig正常显示ip,说明确实是mypopen有问题

复制代码
std::string UsbnameGet()
{
    std::string res;
    const char* cmd =
        "for i in /sys/class/net/*; do "
        "drv=$(readlink $i/device/driver 2>/dev/null); "
        "echo $drv | grep -Eq 'cdc_ncm|cdc_ether|rndis_host' && basename $i; "
        "done";

    FILE* fp = popen(cmd, "r");
    if (!fp) {
        perror("popen failed");
        return "";
    }

    char buf[256];
    while (fgets(buf, sizeof(buf), fp)) {
        res += buf;
    }

    int rc = pclose(fp);
    std::cout << "pclose rc=" << rc << std::endl;
    std::cout << "RAW res=[" << res << "] len=" << res.size() << std::endl;

    return res;
}

ifconfig enx62fde47ffdbb
enx62fde47ffdbb: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.170.11.114  netmask 255.255.255.255  broadcast 0.0.0.0
        inet6 fe80::a66d:3382:f0a2:fd41  prefixlen 64  scopeid 0x20<link>
        ether 62:fd:e4:7f:fd:bb  txqueuelen 1000  (Ethernet)
        RX packets 159  bytes 13476 (13.4 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 119  bytes 12114 (12.1 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

mypopen代码如下:

cpp 复制代码
static bool executeChild(const string &cmd, string &res) {
    FILE *fp = popen(cmd.c_str(),"r");
    if(NULL == fp) {
        // if fork(2) or pipe(2) calls fail, or cannot callocate memory.
        // it does not set errno if memory allocation fails.
        // if the underlying fork(2) or pipe(2) fails, errno is set
        // appropriately.
        // if the type arguments is invalid, and this condition is detected,
        // errno is set to EINVAL.
        res += static_cast<string>("popen failed. ") + strerror(errno); 
        return false;
    }

    char buffer[512] = {0};
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        res += buffer;
    }
    int status = pclose(fp); //❗❗❗❗❗❗❗逻辑有严重问题❗❗❗❗❗❗❗❗❗❗
    if (status == 0) {
        return true;
    }

    if (status == -1) {
        res += static_cast<string>(" failed to get child status: ") + strerror(errno);
    } else {
        if (WIFEXITED(status)) {
            // 命令不存在,exit_status = 127; 命令不存在或无权限执行,仍然是执行成功,只是exit_status>0
            res += " normal termination, exit_status = " + to_string(WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            res += " termination by a signal, signal number = " + to_string(WTERMSIG(status));
        } else if (WIFSTOPPED(status)) {
            res += " the child is stopped, status = " + to_string(WSTOPSIG(status));
        } else {
            res += static_cast<string>(" unknown error: ") + strerror(errno);
        }
    }
    return false;
}
bool myPopen(string cmd, string &res, int maxTimes) {
    if (cmd.empty()) {
        hloge("myPopen cmd null");
        return false;
    }
    cmd += " 2>&1";
    for(int tryTimes = 0; tryTimes < maxTimes; tryTimes++) {
        bool success = executeChild(cmd, res); // ❗❗❗❗严重问题❗❗❗❗❗
        if (success) {
            res = hv::rtrim(res);
            return true;
        }
        hlogi("mySystem cmd: %s, err msg: %s, try times: %d", cmd.c_str(), res.c_str(), tryTimes+1);
        res.clear();
    }
    hloge("mySystem cmd: %s, failed", cmd.c_str());
    return false;
}

bool mySystem(string cmd) {
    string res;
    return myPopen(cmd, res);
}

我的指令是:

cpp 复制代码
for i in /sys/class/net/*; do
    drv=$(readlink $i/device/driver 2>/dev/null)
    echo $drv | grep -Eq 'cdc_ncm|cdc_ether|rndis_host' && basename $i
done

grep -q:匹配到,退出码0,没匹配到,退出码1.

for循环里,只要某次grep -q没匹配,最后一个退出码可能是1

但stdout里早就已经输出了正确的enx62fde47ffdbb\n

这在shell里是完全成功的,但在executeChild() 里被当成"失败"

还把stderr 合并进来了,雪上加霜:

cmd += " 2>&1";

stderr 的提示,shell 的调试信息,都被拼进 res

然后又在失败分支里继续拼:

复制代码
res += " normal termination, exit_status = X";

res 被污染

更不可能"等于期望的接口名"

修改:

cpp 复制代码
static bool executeChild(const string &cmd, string &res) {
    FILE *fp = popen(cmd.c_str(),"r");
    if (!fp) {
        res += string("popen failed: ") + strerror(errno);
        return false;
    }

    char buffer[512];
    while (fgets(buffer, sizeof(buffer), fp)) {
        res += buffer;
    }

    int status = pclose(fp);

    // 仅用于日志
    if (status != 0) {
        if (WIFEXITED(status)) {
            hlogi("cmd exit_status=%d", WEXITSTATUS(status));
        }
    }

    // ✅ 不用 exit code 决定成败
    return !res.empty();
}

问题

按照错误码本身没什么问题呀?

for 循环里最后一次命令的退出码 ≠ 你想表达的"整体成功/失败"

  • for 循环 + grep -q → 非常不适合用 exit code 判断成功

  • cmd 是"语义正确的",但"退出码语义不可靠"

这个是经典的shell坑,shell的语义本身就不是想象中的"整体成功或失败"

我的指令:

cpp 复制代码
for i in /sys/class/net/*; do
    drv=$(readlink $i/device/driver 2>/dev/null)
    echo $drv | grep -Eq 'cdc_ncm|cdc_ether|rndis_host' && basename $i
done

shell 的 退出码规则只有一句话

整个 for 循环的退出码 = 最后一次执行的那条"简单命令"的退出码

不是:

  • 有没有输出

  • 有没有"某一次成功"

  • 有没有 basename 打印

而是:

最后一个 grep -Eq 的返回值

系统网卡顺序是:

cpp 复制代码
lo
enP5p1s0f0
enP5p1s0f1
...
enx62fde47ffdbb   ← 这是 cdc_ncm
usb0
usb1             ← 最后一个

执行过程是:

1️⃣ 前面一堆接口

grep -Eq 不匹配

→ exit code = 1

2️⃣ 执行到 enx62fde47ffdbb

grep -Eq 匹配成功

basename 被执行

stdout 正确输出 enx62fde47ffdbb

3️⃣ 接着继续循环(for 不会停)

4️⃣ 最后一个接口(比如 usb1

grep -Eq 不匹配

→ exit code = 1

最终结果(致命点)

  • stdout:有我想要的内容

    enx62fde47ffdbb

  • shell 最终退出码:1

这在shell语义里是完全合理的,但是被executeChild当成失败

所以:

不要用 exit code 判命令

C++里已经有:

cpp 复制代码
res += buffer;

正确的工程判断是:

判断项 适合吗
popen 是否成功
stdout 是否非空
shell exit code ❌(在这个场景)

修改后崩溃!

由于把通用执行器改了,影响了全局:

「exit code == 0」改成了「stdout 非空」

命令实际上成功了,但被当成失败,后续流程被中断。

程序崩溃没有ip,所以只能改回原来的通用执行器,修改UsbnameGet(),使其忽略返回值或者用popen代替。如果没有匹配到,也是返回空。

复制代码
std::string UsbnameGet()
{
    std::string res;
    const char* cmd =
        "for i in /sys/class/net/*; do "
        "drv=$(readlink $i/device/driver 2>/dev/null); "
        "echo $drv | grep -Eq 'cdc_ncm|cdc_ether|rndis_host' && basename $i; "
        "done";
 
    FILE* fp = popen(cmd, "r");
    if (!fp) {
        perror("popen failed");
        return "";
    }
 
    char buf[256];
    while (fgets(buf, sizeof(buf), fp)) {
        res += buf;
    }
 
    int rc = pclose(fp);
    std::cout << "pclose rc=" << rc << std::endl;
    std::cout << "RAW res=[" << res << "] len=" << res.size() << std::endl;
 
    return res;
}
相关推荐
nnsix1 天前
Unity ReferenceFinder插件 多选资源查找bug解决
unity·游戏引擎·bug
中冕—霍格沃兹软件开发测试1 天前
边界值分析:功能测试中的精度利器
人工智能·功能测试·科技·测试工具·appium·bug
中冕—霍格沃兹软件开发测试2 天前
探索性测试:思维驱动下的高效缺陷狩猎
人工智能·科技·开源·appium·bug
中冕—霍格沃兹软件开发测试2 天前
Git版本控制在测试项目管理中的应用
人工智能·git·科技·开源·appium·bug
中冕—霍格沃兹软件开发测试2 天前
用户体验测试:功能与界面并重
人工智能·科技·开源·appium·bug·ux
中冕—霍格沃兹软件开发测试3 天前
测试工具链的构建与团队协作:从工具集成到价值流动
人工智能·科技·测试工具·开源·appium·bug
yuxuan66993 天前
【Docker】使用docker启动禅道出现mysql.sock 文件已经存在的bug
mysql·docker·centos·bug
zfxwasaboy3 天前
BUG: failure at drivers/pci/msi.c:376/free_msi_irqs()!
linux·c语言·bug
yscript3 天前
GPU分配BUG: Duplicate GPU detected : rank 1 and rank 0 both on CUDA device d5000
linux·运维·服务器·vscode·bug