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

这篇具有很好参考价值的文章主要介绍了Linux错误(3)Linux里IP套接字sendmsg出现EPERM错误。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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 Overflow
  • linux ping 报错 sendmsg: Operation not permitted_ping: sendmsg: operation not permitted-CSDN博客
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 permitted
ubuntu->perf-ana:$ sudo python3 sendmsg-eperm.py 

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

1.2 分析原因

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

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

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

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

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

2. 实例验证
2.1 测试代码
import socket
from 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 DROP
icmp_bytes = raw(icmp_packet)
# https://docs.python.org/3/library/socket.html#socket-objects
so.sendmsg([icmp_bytes], [], socket.MSG_DONTWAIT, ("6.6.6.6", 0))

# 关闭套接字
so.close()

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

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

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

onceday->~:# sudo tcpdump -xx -vv -i eth0 icmp
23: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 DROP
ubuntu->tdata:$ sudo iptables -L OUTPUT -n -v
Chain 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 DROP
ubuntu->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 -vn
Chain 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 11

Chain 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 ~1318
int 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基本是由过滤规则拦截的特征,并且以此构造报文验证测试。文章来源地址https://www.toymoban.com/news/detail-856940.html

到了这里,关于Linux错误(3)Linux里IP套接字sendmsg出现EPERM错误的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 五:优雅断连 & 域名<=>IP & 套接字多种选项

    TCP断开连接过程比建立连接过程更重要,因为连接过程一般不会出问题,但是断开连接过程有可能发生预想不到的情况,所以应该了解半关闭(Half-close)。 单方面断开带来的问题 Linux的close函数和Windows的closesocket函数意味着完全断开连接,既不能传输数据,也不能接收。因此,

    2024年02月19日
    浏览(38)
  • [Linux]套接字通信

    摘于https://subingwen.cn,作者:苏丙榅 侵删 局域网和广域网 局域网:局域网将一定区域内的各种计算机、外部设备和数据库连接起来形成计算机通信的私有网络。 广域网:又称 广域网 、 外网 、 公网 。 是连接不同地区局域网或城域网计算机通信的远程公共网络。 IP(Internet

    2024年02月09日
    浏览(70)
  • 【Linux】套接字编程

    目录 套接字 IP + PORT TCP和UDP的介绍 TCP UDP 网络字节序 转换接口 UDP服务器的编写 服务器的初始化  socket bind sockaddr 结构 服务器的运行 数据的收发 业务处理 客户端的编写 运行效果 拓展  🌸首先,我们先思考一个问题, 数据从 A 主机发送到 B 主机是网络通信的最终目的吗 ?

    2024年02月05日
    浏览(46)
  • Linux套接字编程

    在上一篇博客中我们对网络中一些基本概念进行了简单阐述,这一篇博客我们来对套接字编程的内容进行初步了解。 目录 1.引入 2.UDP协议 2.1通信两端流程 2.1.1服务端流程 2.1.2客户端流程 2.2套接字相关操作接口 2.2.1创建套接字 2.2.2为套接字绑定地址信息 2.2.3发送数据 2.2.4接收

    2024年02月02日
    浏览(43)
  • linux_网络通信-套接字通信socket-网络字节序-IP地址转换函数-inet_pton函数-htonl函数-htons函数-ntohl函数-ntohs函数

    接上一篇:linux_进程锁与文件锁-pthread_mutexattr_init函数-pthread_mutexattr_setpshared函数   今天开始分享网络通信了,主要是就是socket套接字通信,本篇先分享一些预备知识,有网络字节序以及一些IP地址转换函数,话不多说,开始上菜: 此博主在CSDN发布的文章目录:我的CSDN目录

    2024年02月03日
    浏览(58)
  • TCP/IP网络编程(一) 理解网络编程和套接字

    网络编程和套接字概要 网络编程就是编写程序使两台联网的计算机相互交换数据 为了与远程计算机进行数据传输,需要连接因特网,而编程种的套接字就是用来连接该网络的工具。 构建套接字 1.调用soecket函数创建套接字 2.调用bind函数给套接字分配地址 3.调用listen函数将套

    2024年02月11日
    浏览(175)
  • TCP/IP网络编程 第九章:套接字的多种可选项

    套接字的多种可选项 下列是针对SOL_SOCKET协议层的 可选项 描述 SO_REUSEADDR 允许重用本地地址和端口,即使之前的连接处于 TIME_WAIT 状态。 SO_KEEPALIVE 启用 TCP 连接的心跳检测功能,保持连接活动状态。 SO_LINGER 控制关闭连接时的行为。设置为 0 表示立即关闭连接,非零值则表示

    2024年02月16日
    浏览(47)
  • TCP/IP 网络编程 第七章:优雅地断开套接字连接

    在前面的章节中,我们都是通过close或者closesocket来断开套接字连接的,但是调用这两个函数导致我们套接字完全断开,套接字将无法接受数据,并且也只能传输完最后余留在缓冲区的数据内容。此时\\\"只关闭一部分数据交换中使用的流\\\"的方法应运而生。 针对优雅断开的shutd

    2024年02月17日
    浏览(56)
  • TCP/IP网络编程(二) 套接字协议及其数据传输特性

    关于协议 如果相隔比较远的两人进行通话,必须先决定通话方式,如果一方选择电话,另一方也必须选择电话,否则接受不到消息。 总之,协议就是为了完成数据交换而定好的约定。 创建套接字 协议族 通过socket函数的第一个参数传递套接字中使用的协议分类信息,此协议

    2024年02月10日
    浏览(62)
  • 【Linux】TCP套接字编程

    目录 前言 UDP服务器的完善 线程的封装 结构定义 接口实现 环形队列 结构定义 接口实现 加锁 信号量的申请与释放 入队与出队 整体组装  初始化与析构 信息接收线程 消息发送线程 TCP套接字 创建套接字 listen accept 收发操作 客户端的编写 进一步完善 多进程 多线程 总结 上篇

    2024年02月05日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包