当前位置:首页 » 《关注互联网》 » 正文

Linux错误(3)Linux里IP套接字sendmsg出现EPERM错误

28 人参与  2024年04月23日 13:04  分类 : 《关注互联网》  评论

点击全文阅读


Linux错误(3)之Linux里IP套接字sendmsg出现EPERM错误

Author: Once Day Date: 2024年2月21日

漫漫长路才刚刚开始…

全系列文章可参考专栏: Mermaid使用指南_Once_day的博客-CSDN博客

参考文档:

c - How to fix EPERM error when trying to use sendto() with Ethernet socket(AF_INET, ..., ...) (IP output packets) on Linux - Stack Overflowlinux ping 报错 sendmsg: Operation not permitted_ping: sendmsg: operation not permitted-CSDN博客

文章目录

Linux错误(3)之Linux里IP套接字sendmsg出现EPERM错误1. 问题分析1.1 现象介绍1.2 分析原因1.3 解决方法 2. 实例验证2.1 测试代码2.2 复现故障 3. Linux源码总结

1. 问题分析
1.1 现象介绍

当执行一段IP套接字的sendmsg函数(或者类似函数)时,出现EPERM错误,如下所示:

ubuntu->perf-ana:$ sudo python3 sendmsg-eperm.py Traceback (most recent call last):  File "/home/ubuntu/tdata/perf-ana/sendmsg-eperm.py", line 14, in <module>    so.sendmsg([icmp_bytes], [], socket.MSG_DONTWAIT, ("6.6.6.6", 0))PermissionError: [Errno 1] Operation not permitted

这类错误大部分是因为程序执行用户的权限不够,比如对比ICMP报文,需要root权限才能执行,例如下面这种:

ubuntu->perf-ana:$ python3 sendmsg-eperm.py Traceback (most recent call last):  File "/home/ubuntu/tdata/perf-ana/sendmsg-eperm.py", line 4, in <module>    so = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)  File "/usr/lib/python3.10/socket.py", line 232, in __init__    _socket.socket.__init__(self, family, type, proto, fileno)PermissionError: [Errno 1] Operation not permittedubuntu->perf-ana:$ sudo python3 sendmsg-eperm.py 

这类权限不够的问题,在创建套接字时,就可以发现。除了权限不够,更多的原因是被内核中过滤规则拦截。

1.2 分析原因

Linux中使用sendmsg系统调用通过IP socket发送数据时,如果出现EPERM错误,通常有以下几种可能原因:

进程没有足够的权限,发送原始IP数据包需要root或CAP_NET_RAW权限,普通用户进程没有该权限会导致EPERM错误。解决办法是以root权限运行进程,或者赋予进程CAP_NET_RAW权限。使用了无效的socket选项,有些socket选项只能由特权进程设置,如IP_HDRINCL。普通进程设置这些选项会引发EPERM错误。企图伪造数据包的源地址,普通进程构造IP头部时,不能随意指定源IP地址,否则内核会拒绝发送并返回EPERM错误,以防止IP欺骗,只有root用户可以任意指定源IP。发往无效的目的地址,例如目的IP不在本地路由表中,或者目的端口没有进程监听。AppArmor或SELinux等安全模块的限制,这些安全框架可以配置规则来阻止某些进程的网络访问。其他内核模块的限制,如iptables规则对发出的数据包进行过滤。
1.3 解决方法

遇到这类问题,一般是先检查执行程序的权限,尝试以root权限运行。

如果失败,再分析报文是否比较特殊,去看看设备上的防火墙、iptables等报文过滤规则(也有可能是其他软件)。

逐一关闭这些防火墙类规则验证,找到最终的拦截程序,然后修改相关配置。

2. 实例验证
2.1 测试代码
import socketfrom scapy.all import IP, ICMP, Raw, raw# 以AF_INET原始套接字发送ICMP数据包so = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)# 构造ICMP数据包# 例如:ICMP Time Exceeded message, type = 11# 使用scapy构造ICMP数据包icmp_packet = ICMP(type=11, code=0) / Raw(b"ICMP Time Exceeded")# 发送数据包, 配置iptables规则丢包, sudo iptables -A OUTPUT -p icmp --icmp-type 11 -j DROPicmp_bytes = raw(icmp_packet)# https://docs.python.org/3/library/socket.html#socket-objectsso.sendmsg([icmp_bytes], [], socket.MSG_DONTWAIT, ("6.6.6.6", 0))# 关闭套接字so.close()

这段Python代码的含义是使用原始套接字发送一个ICMP数据包:

socket库用于创建套接字,而scapy库用于构造和处理网络数据包。创建了一个原始套接字对象so,使用socket.socket()函数,并指定了协议类型为AF_INET(IPv4)和套接字类型为SOCK_RAW(原始套接字)。这意味着可以直接发送和接收原始的网络数据包。使用scapy库构造了一个ICMP数据包。在这个例子中,构造的ICMP数据包的类型为11(参数问题),代码中使用了ICMP(type=12, code=0)来创建ICMP数据包对象。此外,还添加了一个原始负载(payload)为"ICMP Time Exceeded"。使用scapy库的raw()函数将ICMP数据包转换为字节表示形式。raw()函数是一个辅助函数,用于构建数据包并返回其字节表示形式。使用原始套接字的sendmsg()方法发送数据包。sendmsg()方法接受一个参数列表,其中第一个参数是要发送的数据包的字节表示形式。这里将ICMP数据包的字节表示形式作为参数传递给sendmsg()方法。

这个代码在root权限下,可以正常发送报文,并且可以用tcpdump抓包到其报文,如下:

onceday->~:# sudo tcpdump -xx -vv -i eth0 icmp23:32:51.738303 IP (tos 0x0, ttl 64, id 58934, offset 0, flags [DF], proto ICMP (1), length 46)    VM-4-17-ubuntu > 6.6.6.6: ICMP parameter problem - octet 0, length 26        IP  [|ip]        0x0000:  feee 8fbf 8699 5254 0085 f022 0800 4500        0x0010:  002e e636 4000 4001 3a7c 0a00 0411 0606        0x0020:  0606 0c00 fae3 0000 0000 4943 4d50 2054        0x0030:  696d 6520 4578 6365 6564 6564
2.2 复现故障

在Ubuntu设备上添加iptables规则,如下所示:

ubuntu->tdata:$ sudo iptables -A OUTPUT -p icmp --icmp-type 11 -j DROPubuntu->tdata:$ sudo iptables -L OUTPUT -n -vChain OUTPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target     prot opt in     out     source               destination             0     0 DROP       icmp --  *      *       0.0.0.0/0            0.0.0.0/0            icmptype 11

添加的iptables规则在出口OUTPUT拦截type类型为11的报文,所以构建的ICMP超时报文将无法发送出去,即使以root权限运行。

ubuntu->perf-ana:$ sudo iptables -A OUTPUT -p icmp --icmp-type 11 -j DROPubuntu->perf-ana:$ sudo python3 sendmsg-eperm.py Traceback (most recent call last):  File "/home/ubuntu/tdata/perf-ana/sendmsg-eperm.py", line 13, in <module>    so.sendmsg([icmp_bytes], [], socket.MSG_DONTWAIT, ("6.6.6.6", 0))PermissionError: [Errno 1] Operation not permitted

被iptables拦截之后,显示的错误也是EPERM,这非常具有疑惑性,iptables的统计计数也能验证这点

ubuntu->perf-ana:$ sudo iptables -L -vnChain INPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target     prot opt in     out     source               destination         Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target     prot opt in     out     source               destination         Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target     prot opt in     out     source               destination             1    46 DROP       icmp --  *      *       0.0.0.0/0            0.0.0.0/0            icmptype 11Chain YJ-FIREWALL-INPUT (0 references) pkts bytes target     prot opt in     out     source               destination  

注意,OUTPUT的过滤规则拦截了一个ICMP类型为11的报文,这正是上面尝试发送的报文。

3. Linux源码总结

一般这类问题,最好的解决方法就是查阅Linux源码,IP套接字sendmsg函数多用于数据报类,其函数位于net/ipv4/udp.c,如下这段是大概率返回EPERM的代码:

// net/ipv4/udp.c 1041 ~1318int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len){//......if (cgroup_bpf_enabled(CGROUP_UDP4_SENDMSG) && !connected) {err = BPF_CGROUP_RUN_PROG_UDP4_SENDMSG_LOCK(sk,    (struct sockaddr *)usin, &ipc.addr);if (err)goto out_free;if (usin) {if (usin->sin_port == 0) {/* BPF program set invalid port. Reject it. */err = -EINVAL;goto out_free;}daddr = usin->sin_addr.s_addr;dport = usin->sin_port;}}//......}

BPF_CGROUP_RUN_PROG_UDP4_SENDMSG_LOCK这个宏里面会跑很多验证程序,其最终执行到:

// kernel/bpf/cgroup.c 1081~1127/** * __cgroup_bpf_run_filter_sock_addr() - Run a program on a sock and *                                       provided by user sockaddr * @sk: sock struct that will use sockaddr * @uaddr: sockaddr struct provided by user * @type: The type of program to be exectuted * @t_ctx: Pointer to attach type specific context * @flags: Pointer to u32 which contains higher bits of BPF program *         return value (OR'ed together). * * socket is expected to be of type INET or INET6. * * This function will return %-EPERM if an attached program is found and * returned value != 1 during execution. In all other cases, 0 is returned. */int __cgroup_bpf_run_filter_sock_addr(struct sock *sk,      struct sockaddr *uaddr,      enum cgroup_bpf_attach_type atype,      void *t_ctx,      u32 *flags){struct bpf_sock_addr_kern ctx = {.sk = sk,.uaddr = uaddr,.t_ctx = t_ctx,};struct sockaddr_storage unspec;struct cgroup *cgrp;int ret;/* Check socket family since not all sockets represent network * endpoint (e.g. AF_UNIX). */if (sk->sk_family != AF_INET && sk->sk_family != AF_INET6)return 0;if (!ctx.uaddr) {memset(&unspec, 0, sizeof(unspec));ctx.uaddr = (struct sockaddr *)&unspec;}cgrp = sock_cgroup_ptr(&sk->sk_cgrp_data);ret = BPF_PROG_RUN_ARRAY_CG_FLAGS(cgrp->bpf.effective[atype], &ctx,          bpf_prog_run, flags);return ret == 1 ? 0 : -EPERM;}

可以看到,这个函数里会跑BPF指令码,并且根据结果返回0或者-EPERM,这正是遇到的错误码

这里调用的是动态的BPF执行程序,并且实际上内核的过滤位点也不只这些,比如iptables在IP层发送出口OUTPUT还有过滤函数回调位点。从这个特征出发,可以推断出EPERM基本是由过滤规则拦截的特征,并且以此构造报文验证测试。


点击全文阅读


本文链接:http://zhangshiyu.com/post/99068.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1