Linux 网络相关系统调用按功能可分为套接字创建与管理、连接建立与终止、数据收发、套接字选项与信息、I/O 多路复用、网络接口控制六大类,是用户态网络编程的核心接口。
套接字创建与基础管理
socket():创建通信端点(套接字),返回文件描述符。
int socket(int domain, int type, int protocol);- domain:地址族(AF_INET/AF_INET6)
- type:套接字类型(SOCK_STREAM/SOCK_DGRAM)
- protocol:协议(0 默认)
socketpair():创建一对已连接的无名套接字(多用于进程间通信)。
close():关闭套接字,释放内核资源。
shutdown():单向关闭读写通道(SHUT_RD/SHUT_WR/SHUT_RDWR)。
连接建立与终止(TCP 为主)
bind():绑定套接字到本地地址 + 端口(服务器必用)。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);listen():将套接字设为被动监听模式,设置连接队列长度。
int listen(int sockfd, int backlog);accept():从监听队列接受连接,返回新通信套接字。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);connect():客户端发起连接请求(TCP 三次握手)。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
数据收发(TCP/UDP 通用)
1. 面向连接(TCP)
send()/recv():基础收发,支持 flags 控制(MSG_OOB/MSG_PEEK)。
ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags);sendmsg()/recvmsg():支持分散 / 聚集 IO、控制信息(如辅助数据),功能更强大。
2. 无连接(UDP)
- sendto()/recvfrom():收发时指定 / 获取对方地址,无需提前连接。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
套接字选项与信息查询
setsockopt()/getsockopt():设置 / 获取套接字选项(超时、缓冲区大小、端口复用等)。
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);getsockname():获取套接字本地地址。
getpeername():获取连接对方地址。
I/O 多路复用(高并发核心)
- select():跨平台多路复用,监听 fd 集合的读写异常事件。
- poll():改进 select,无 fd 数量限制,效率更高。
- epoll 系列(Linux 特有):
- epoll_create():创建 epoll 实例。
- epoll_ctl():注册 / 修改 / 删除监听 fd。
- epoll_wait():等待事件触发,返回就绪 fd 列表。
网络接口与底层控制
- ioctl():通用设备控制,常用于网卡配置(IP、子网掩码、MAC)、路由操作。
int ioctl(int fd, unsigned long request, ...); - fcntl():文件控制,可设置套接字非阻塞、获取 / 设置文件状态标志。
典型流程对比
TCP 服务器
socket() → bind() → listen() → accept() → send()/recv() → close()
TCP 客户端
socket() → connect() → send()/recv() → close()
UDP 服务器 / 客户端
socket() → bind()(可选客户端)→ recvfrom()/sendto() → close()
LSM 网络基础架构
LSM(Linux Security Module)是内核安全框架,通过CONFIG_SECURITY启用,提供200 + 钩子,其中 ** 网络钩子(socket/sk_buff)** 管控全链路网络行为。
核心网络钩子(Socket 层)
拦截所有 socket 系统调用,对应网络编程核心接口:
socket_create:创建套接字(控制类型 / 协议族)socket_bind:绑定地址 + 端口(防止特权端口滥用)socket_connect:发起连接(控制目标 IP / 端口)socket_listen:监听端口socket_accept:接受连接socket_sendmsg/socket_recvmsg:数据收发(过滤内容 / 方向)socket_getsockname/getpeername:获取地址信息socket_setsockopt/getsockopt:套接字选项控制
核心网络钩子(数据包层,sk_buff)
配合 Netfilter,对数据包进行标签化管控:
netif_receive_skb:网卡收包ip_local_out:IP 层发包skb_security_set/get:读写数据包安全标签(SELinux/Smack 用)secmark:基于 Netfilter 规则打标签(iptables 配合)
BPF‑LSM(5.7+,eBPF 动态策略)
- 网络模型:eBPF 程序挂载 LSM 钩子,动态自定义策略
- 核心特性:
- 无需内核模块 / 重启,
bpftool动态加载 - 支持所有网络钩子:socket、sk_buff、Netfilter
- 示例:阻断进程连接
192.168.1.100:22、限制bind端口范围 - 优势:可编程、动态更新、与 eBPF 生态融合
- 局限:需 5.7+ 内核、eBPF 编程门槛
- 无需内核模块 / 重启,
- 典型场景:云原生、微服务、零信任安全
典型调用流程(以connect()为例)
- 用户态:
connect(fd, addr, len)系统调用 - 内核态:
sys_connect()→security_socket_connect()- LSM 框架遍历所有已启用模块的
socket_connect钩子 - SELinux:检查进程域(如
user_t)是否允许连接目标端口类型(如ssh_port_t) - AppArmor:检查进程 Profile 是否允许
network inet tcp到目标 IP - BPF‑LSM:执行挂载的 eBPF 程序,动态判断放行 / 拒绝
- 结果:钩子返回 0 允许,
-EPERM拒绝,系统调用失败
绕过系统调用
普通标准 Socket:绝对跳不过系统调用
常规socket/connect/send/recv网络程序:
- TCP/UDP 必须走内核网络协议栈
- 只要用内核协议栈,收发、建连、绑定端口全都要陷入内核
- 必须触发syscall 陷入(x86
int 0x80/syscall指令,ARMsvc)
原因:
- 端口管理、路由、IP 分片、TCP 重传 / 拥塞控制全在内核
- 网卡硬件中断、DMA 收发只有内核能访问
- 资源隔离:用户态无权直接操作网卡、内核路由表
👉 标准 POSIX Socket无法跳过系统调用。
绕开 Socket 系统调用:用户态收发包(跳过内核协议栈)
可以完全不用 socket 系列系统调用,直接用户态读写网卡,自己实现二层 / 三层协议。
1. 实现方案(常用 3 种)
① RAW 裸套接字(还没完全跳系统调用,但绕开协议栈)
socket(AF_INET, SOCK_RAW, IPPROTO_TCP);- 仍要创建 socket 系统调用
- 但自己构造 IP/TCP 报文,内核不做协议解析、不做重组、不做重传
- 能自由发包、伪造源 IP / 端口
② PACKET 套接字(链路层,最常用)
socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));- 直接拿到以太网帧,到 MAC 层
- 完全绕开内核 TCP/UDP/IP 协议栈
- 自己在用户态实现:ARP、IP、TCP、UDP
③ DPDK / AF_XDP真正大规模跳过系统调用
企业级高性能用户态网络标配:
- 内核旁路、无系统调用、无中断、无拷贝
- 直接用户态映射网卡 DMA 缓冲区
- 轮询收发包,全程无 syscall 陷入
- 自己在用户态完整实现协议栈(librte_ip、用户态 TCP 栈)
关键技术:
- UIO / VFIO 把网卡设备直接透传给用户态
- 内存大页、零拷贝、轮询模式替代中断
- 彻底不调用 socket/send/recv 任何系统调用
最硬核:彻底无任何系统调用 网络通信
原理
- 通过mmap 映射网卡物理寄存器 / DMA 环形队列到用户态
- 用户态直接写网卡寄存器、填充 DMA 描述符
- 网卡 DMA 直接和用户态内存交互
- 全程一条系统调用都不执行
限制
- 需要网卡硬件支持、驱动支持用户态映射
- 需要 root 权限、设备权限
- 必须自己实现二层 + 三层 + 四层协议
- 不能和内核协议栈共存(端口冲突、路由冲突)
典型场景
- DPDK 网关、防火墙、负载均衡
- 金融低时延交易网络(微秒级,杜绝 syscall 陷入开销)
- 自研私有协议栈
RAW / PACKET 涉及系统调用清单
- RAW / AF_PACKET 不可能跳过系统调用,创建、收发、绑网卡、设选项 全依赖内核 syscall。
- 只是绕开了内核 TCP/UDP 协议栈,但没绕开系统调用。
必用
- socket
- sendto
- recvfrom
- close
高频常用
- bind
- setsockopt
- getsockopt
- ioctl
- fcntl
进阶 / 高性能
- sendmsg
- recvmsg
- mmap
- munmap
- bpf
RAW 套接字 / PACKET 套接字 都不需要 connect ()
TCP 是面向连接的协议:
connect()触发三次握手- 建立一条固定的<源 IP: 端口 → 目标 IP: 端口>链路
- 之后
send/recv不需要再传地址
但RAW/PACKET 是无连接模式,内核不维护连接状态。
RAW 调用connect()不会建立任何连接,不会握手,不会发包。它只做一件事:
把这个 RAW socket绑定到一个固定目标 IP
之后可以直接用send()发,不用每次填地址。
connect(raw_fd, (struct sockaddr*)&dest_addr, sizeof(dest_addr)); send(raw_fd, pkt, pkt_len, 0); // 不用目标地址了总结:RAW 的 connect 只是简化 API,无任何网络连接行为。
PACKET 工作在以太网帧级别,没有 “连接” 概念。
Linux 虽然允许你对AF_PACKET调用connect(),但:
- 没有任何实际网络行为
- 不能减少系统调用
- 不能加速发包
- 绝大多数工具(tcpdump、抓包、注入)从不使用
为什么普通程序不这么做?
- 协议栈工作量巨大,要自己实现:ARP、IP 分片、TCP 三次握手、滑动窗口、重传、拥塞控制、定时器。
- 不兼容标准 Socket 生态
- 需要 root、依赖硬件、开发复杂度极高
- 内核协议栈已经高度优化,普通业务没必要造轮子
关键要点
- 标准 TCP/UDP Socket 程序:必须走系统调用,无法跳过
- RAW/PACKET 裸包:绕开内核协议栈,但仍需少量 socket 系统调用
- DPDK/AF_XDP/VFIO 用户态网卡:完全跳过系统调用、内核协议栈,用户态直接收发网络包
- 能实现,但只适合高性能网关、低时延交易、安全抓包攻防场景,不适合业务开发
总结
- 基础通信:
socket/bind/listen/accept/connect/send/recv - 高效 IO:
epoll(Linux)/select/poll - 选项控制:
setsockopt/getsockopt/ioctl