linux内核中socket有关的编程接口
内核中socket有关的编程接口及其对应的功能:
系统调用 | 描述 |
---|---|
socketcall | socket系统调用 |
socket | 建立socket |
bind | 绑定socket到端口 |
connect | 连接远程主机 |
accept | 响应socket连接请求 |
send | 通过socket发送信息 |
sendto | 发送UDP信息 |
sendmsg | 参见send |
recv | 通过socket接收信息 |
recvfrom | 接收UDP信息 |
recvmsg | 参见recv |
listen | 监听socket端口 |
select | 对多路同步I/O进行轮询 |
shutdown | 关闭socket上的连接 |
getsockname | 取得本地socket名字 |
getpeername | 获取通信对方的socket名字 |
getsockopt | 取端口设置 |
setsockopt | 设置端口参数 |
sendfile | 在文件或端口间传输数据 |
socketpair | 创建一对已联接的无名socket |
对应的接口可以在unistd.h【左图:中断向量表】与syscalls_64.S【右图:系统调用表】文件中找到:
系统调用机制:
系统调用是操作系统为用户态进程与硬件设备进行交互提供的一组接口,是一种特殊的中断,中断处理是从用户态进入内核态从而使用内核函数的主要方式。从用户态到和心态调用流程主要包括三个部分xyz(API)、system_ call(中断向量)、sys_xyz(中断向量对应的中断服务程序)。
其中在进行系统调用时,从用户态程序到以上述文件中的系统调用号_NR_xxxx
作为下标,可找出系统调用表sys_call_table
中对应表项的内容,该表中对应的内容就是系统调用的响应函数sys_xxxx
的入口地址。也就是说整个系统调用的过程就是从中断向量表 ->系统调用表 JUMP(EAX*4+基地址)根据系统调用号找到对应的系统调用代码并执行。【其中xxxx对应的是具体的调用函数名字】
从用户态程序到内核态程序的过程可描述为下图:
系统调用号怎么从用户函数传递到内核函数呢?
当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数,在Linux中是通过执行int $0x80来执行系统调用传参,由于内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,通过在eax寄存器存储系统调用号来准确的锁定本次的系统调用。
replyhi和hello的通信过程中系统调用过程分析:
从本文一开始的图中各个接口的作用可以得到,我们的replyhi/hello通信过程中,一定会涉及到sys_socket【socket系统调用】,sys_bind【绑定端口】,sys_listen【服务器端倾听socket端口】。
我们为这三个接口打上断点,看是否在通信过程中调用了这几个接口,方法是通过逐步执行到下个断点。
根据break打断点的响应可以判断在我们的socket接口中确实存在这几个接口。其次通过c逐步执行断点。可以看出在这个通信过程中确实三个接口都被调用了。且调用顺序如下。
使用list查看可得每次socket系统调用都要经过一次SYSCALL_DEFINE2这个函数,查看该函数:
1 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) 2 { 3 unsigned long a[AUDITSC_ARGS]; 4 unsigned long a0, a1; 5 int err; 6 unsigned int len; 7 8 if (call < 1 || call > SYS_SENDMMSG) 9 return -EINVAL; 10 call = array_index_nospec(call, SYS_SENDMMSG + 1); 11 12 len = nargs[call]; 13 if (len > sizeof(a)) 14 return -EINVAL; 15 16 /* copy_from_user should be SMP safe. */ 17 if (copy_from_user(a, args, len)) 18 return -EFAULT; 19 20 err = audit_socketcall(nargs[call] / sizeof(unsigned long), a); 21 if (err) 22 return err; 23 24 a0 = a[0]; 25 a1 = a[1]; 26 27 switch (call) { 28 case SYS_SOCKET: 29 err = __sys_socket(a0, a1, a[2]); 30 break; 31 case SYS_BIND: 32 err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]); 33 break; 34 case SYS_CONNECT: 35 err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]); 36 break; 37 case SYS_LISTEN: 38 err = __sys_listen(a0, a1); 39 break; 40 case SYS_ACCEPT: 41 err = __sys_accept4(a0, (struct sockaddr __user *)a1, 42 (int __user *)a[2], 0); 43 break; 44 case SYS_GETSOCKNAME: 45 err = 46 __sys_getsockname(a0, (struct sockaddr __user *)a1, 47 (int __user *)a[2]); 48 break; 49 case SYS_GETPEERNAME: 50 err = 51 __sys_getpeername(a0, (struct sockaddr __user *)a1, 52 (int __user *)a[2]); 53 break; 54 case SYS_SOCKETPAIR: 55 err = __sys_socketpair(a0, a1, a[2], (int __user *)a[3]); 56 break; 57 case SYS_SEND: 58 err = __sys_sendto(a0, (void __user *)a1, a[2], a[3], 59 NULL, 0); 60 break; 61 case SYS_SENDTO: 62 err = __sys_sendto(a0, (void __user *)a1, a[2], a[3], 63 (struct sockaddr __user *)a[4], a[5]); 64 break; 65 case SYS_RECV: 66 err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3], 67 NULL, NULL); 68 break; 69 case SYS_RECVFROM: 70 err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3], 71 (struct sockaddr __user *)a[4], 72 (int __user *)a[5]); 73 break; 74 case SYS_SHUTDOWN: 75 err = __sys_shutdown(a0, a1); 76 break; 77 case SYS_SETSOCKOPT: 78 err = __sys_setsockopt(a0, a1, a[2], (char __user *)a[3], 79 a[4]); 80 break; 81 case SYS_GETSOCKOPT: 82 err = 83 __sys_getsockopt(a0, a1, a[2], (char __user *)a[3], 84 (int __user *)a[4]); 85 break; 86 case SYS_SENDMSG: 87 err = __sys_sendmsg(a0, (struct user_msghdr __user *)a1, 88 a[2], true); 89 break; 90 case SYS_SENDMMSG: 91 err = __sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2], 92 a[3], true); 93 break; 94 case SYS_RECVMSG: 95 err = __sys_recvmsg(a0, (struct user_msghdr __user *)a1, 96 a[2], true); 97 break; 98 case SYS_RECVMMSG: 99 if (IS_ENABLED(CONFIG_64BIT) || !IS_ENABLED(CONFIG_64BIT_TIME)) 100 err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1, 101 a[2], a[3], 102 (struct __kernel_timespec __user *)a[4], 103 NULL); 104 else 105 err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1, 106 a[2], a[3], NULL, 107 (struct old_timespec32 __user *)a[4]); 108 break; 109 case SYS_ACCEPT4: 110 err = __sys_accept4(a0, (struct sockaddr __user *)a1, 111 (int __user *)a[2], a[3]); 112 break; 113 default: 114 err = -EINVAL; 115 break; 116 } 117 return err; 118 }
根据该函数可得,确实socket调用都要经过这个函数,通过一个switch语句来实现不同的真正的socket系统调用。
那么我们对该switch语句中的所有调用对应的方法都打上断点,来判断该通信过程到底使用了那些接口。
根据断点可以得到,确实都存在这些系统接口。接着使用c来逐步调到下个断点看使用了哪些端口。可以得到。
首先确实sys socket_call 是每个socket系统调用的入口函数,其次在这个通信过程中不仅使用到了之前预测的sys_bind,sys_listen,而且也用到了sys_sendto,sys_recvfrom,recvfrom等调用。与老师的给的通信代码对应。代码地址:replyhi/hello。