防火墙导致Linux发送网络报文出现errno等于1的错误码

本文探讨了在服务器启动过程中UDP Socket发送报文遇到errno 1错误的现象及解决方案。通过对网络配置和防火墙规则的分析,揭示了该问题的偶发性和背后的原因,并提供了具体的测试案例。

    服务启动过程中,偶然间看到udp socket发送报文,出现 errno == 1 错误码。

    错误码对应的描述:

  1.    zh_CN: 不允许的操作
  2.    en_US: Operation not permitted

  出现错误的代码,在失败后,下次定时尝试重作,无权限问题不出现了,具有偶发性!

分析


   在服务器启动过程中发送网络报文,出现errno == 1错误,最开始执拗于udp socket是无连接的协议,只要目的地址正确,而且有响应的路由,都应该可以发送的,觉得不应该出现此类错误?

   而且,在网上搜索,此类错误码等于‘1’的场景描述也较少。

   为了具体而微地解剖这个问题,特意从网上复制了一个udp socket的例子,进行发送接口不同的异常参数设置,发现udp socket的发送接口函数对于异常参数输入,虽然得到的errno的值是各不一样的,但就是不是为errno == 1的错误码!

   积累了两天的观察和对比分析,以及基于过去对于k8s  service IP是通过iptables进行NAT转换到达后端POD IP; 如果通过tcpdump抓取报文,就会发现访问service IP的报文,实际上被转换为POD IP投送过去,所以,k8s的很多网络问题,必须首先保证POD网络,或k8s节点网络的互通!

   猜测在启动节点,k8s对于iptables nat规则设置可能处于中间态,在NAT规则未设置完全的情况下,对于某些service IP网络服务Endpoint进行访问,发送网络报文,就会在防火墙层面拒绝了,进而,socket接口返回错误码,出现无权限提示!

  有了怀疑的方向和测试程序在手,就可以进一步验证是否如此!

  通过防火墙命令设置测试程序的目的port被不允许,则在测试程序中,就打印了‘permission not allowed’的错误提示:)

 知道的原因,就可以根据这个原因,采取一些规避的措施了!

  一切都得到了解释,是如此的欢快!!!

再次补充证明材料


# 如果在启动过程持续观察k8s节点,对于POD IP Endpoint的限制变化
# 就可以看出k8s在这个错误码场景的嫌疑

while true; do iptables -t filter -S | grep endpoint_keyword; echo 'sleep a while'; sleep 1; done

后来,觉得应该可以通过持续观察防火墙设置,发现k8s对于防火墙规则的修改过程,通过循环脚本执行了下,果然如此!

Linux UDP Socket 编程例子


#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char * argv[]) {

    int client_sockfd;
    int len;
    char buf[BUFSIZ]            = {}; //数据传送的缓冲区
   
    struct sockaddr_in remote_addr; //服务器端网络地址结构体
    remote_addr.sin_family      = AF_INET; //设置为IP通信
    remote_addr.sin_addr.s_addr = inet_addr("172.16.6.1"); //服务器IP地址
    remote_addr.sin_port        = htons(8000); //服务器端口号

    /*创建客户端套接字--IPv4协议,面向无连接通信,UDP协议*/
    if ((client_sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
        printf("\n create socket error");
        return 1;
    }

    strcpy(buf, "This is a test message"); // 发送的内容
    printf("sending: '%s'\n", buf);

    /*向服务器发送数据包;如果出现安全因素阻拦,例如,防火墙,则出现errno == 1 的错误码*/

    if ((len = sendto(client_sockfd, buf, strlen(buf), 0, (struct sockaddr * ) &remote_addr, sizeof(struct sockaddr))) < 0) {
        char szErrno[128];
        strerror_r(errno, szErrno, sizeof(szErrno));
        printf("\n sendto error, errno:%d, %s", errno, szErrno);
        return 1;
    }

    printf("\n close udp socket fd\n");
    /*关闭套接字*/
    close(client_sockfd);

    return 0;
}

Linux 中的 `errno` 是一个全局变量,用于保存最近一次系统调用或库函数调用失败时的错误代码。它在 POSIX 标准中被定义,并广泛用于诊断错误和调试程序。以下是关于 `errno` 设计的优缺点分析: ### 优点 1. **标准化与可移植性** `errno` 提供了一组标准化的错误码,如 `EINVAL`(无效参数)、`ENOMEM`(内存足)、`ENODEV`(设备存在)等。这些错误码同系统间具有良好的一致性,使得开发者可以编写可移植的错误处理代码[^1]。 2. **易于调试与诊断** 通过 `errno`,开发者可以快速定位系统调用失败的原因。例如,当 `open()` 返回 -1 时,检查 `errno` 可以得知是文件存在(`ENOENT`)还是权限足(`EACCES`)等问题。 3. **线程安全性设计(TLS)** 在现代 Linux 系统中,`errno` 被实现为线程本地存储(Thread Local Storage, TLS),每个线程拥有独立的 `errno` 值。这种设计避免了多线程环境下因共享全局变量而导致的竞态条件,提高了并发安全性[^2]。 4. **与系统调用紧密结合** `errno` 与系统调用紧密集成,几乎所有的系统调用和 C 库函数都支持 `errno` 错误报告机制,这使得错误处理机制统一且高效。 ### 缺点 1. **非线程安全的旧实现问题** 在早期的多线程实现中,`errno` 曾是一个全局变量,多个线程同时访问可能导致错误码被覆盖,从而无法准确反映错误原因。虽然现代系统已通过 TLS 解决此问题,但旧代码或非标准实现仍可能存在此类风险[^2]。 2. **错误码语义模糊或重复** 某些错误码的语义够明确,或在同上下文中被复用,导致开发者难以准确判断错误原因。例如,`EIO`(输入/输出错误)可能表示多种底层硬件或驱动问题,缺乏具体信息。 3. **缺乏结构化错误信息** `errno` 仅提供整数错误码,缺乏结构化信息(如错误发生的具体模块、堆栈跟踪等),在复杂系统中难以满足高级调试需求。现代系统通常需要结合日志、堆栈追踪等工具进行辅助诊断。 4. **错误码扩展性受限** `errno` 的错误码数量有限,且新增错误码需要修改系统头文件并保持兼容性,限制了其扩展性。在某些复杂场景下,可能需要自定义错误码体系。 5. **错误码与函数返回值耦合** 多数系统调用通过返回 -1 并设置 `errno` 来报告错误,这种方式虽然简洁,但容易导致错误处理代码与正常流程混杂,影响代码可读性和维护性。 ### 示例代码:使用 `errno` 进行错误处理 ```c #include <stdio.h> #include <errno.h> #include <fcntl.h> int main() { int fd = open("nonexistent_file.txt", O_RDONLY); if (fd == -1) { perror("Open failed"); // 打印 errno 对应的错误信息 printf("errno value: %d\n", errno); } return 0; } ``` 输出示例(文件存在): ``` Open failed: No such file or directory errno value: 2 ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值