基于socket的sockopt是Linux中一种简单易用的内核态与用户态的通信方式。
应用层-系统调用 Linux中有两个系统调用,setsockopt和getsockopt,用于设置、获取socket选项。它们的函数原型为:
int getsockopt (int s, int level, int optname, void *optval, socklen_t *optlen) ;
int setsockopt (int s, int level, int optname, const void *optval, socklen_t optlen) ;
其中s即socket_fd,套接字的文件描述符。
level指定在哪一层控制socket选项,常用的有以下三种:
SOL_SOCKET(= 1),通用套接字选项;
IPPROTO_IP(= 0),IP选项;
IPPROTO_TCG(= 6),TCP选项;
optname为选项代号。
对于getsockopt,optval,optlen为返回的选项值的指针及长度。比如,一个字符串或结构体指针和它的长度。对于setsockopt,类似,optval,optlen为要设置的数据指针和数据长度。
如果函数执行成功,返回值为0,否则返回-1。错误代号保存在errno中。
举个例子:
int tmp = 1
setsockopt(listen_fd , SOL_SOCKET, SO_REUSEADDR, &tmp , sizeof(tmp ) )
以上代码设置套接字地址和端口可重用。
内核层-netfilter Linux的netfilter提供了一组接口用于自定义socket选项。
数据结构:
struct nf_sockopt_ops
{
struct list_head list ;
int pf;
int set_optmin;
int set_optmax;
int (*set )(struct sock *sk, int optval, void __user *user, unsigned int len);
int (*compat_set)(struct sock *sk, int optval,
void __user *user, unsigned int len);
int get_optmin;
int get_optmax;
int (*get)(struct sock *sk, int optval, void __user *user, int *len);
int (*compat_get)(struct sock *sk, int optval,
void __user *user, int *len);
unsigned int use;
struct task_struct *cleanup_task;
};
我们只要新建一个nf_sockopt_ops结构,设置好选项取值范围,自定义set和get函数,然后注册到内核中。这样就可以在用户层调用setsockopt或getsockopt,和内核通信了。 注册卸载nf_sockopt_ops的接口为:
int nf_register_sockopt (struct nf_sockopt_ops *reg) ;
void nf_unregister_sockopt (struct nf_sockopt_ops *reg) ;
示例 示例一:互相发送字符串 内核模块代码 sockopt_kern.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include <linux/module.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/string.h> #include <linux/netfilter_ipv4.h> #include <linux/init.h> #include <asm/uaccess.h> #define SOCKET_OPS_BASE 128 #define SOCKET_OPS_SET (SOCKET_OPS_BASE) #define SOCKET_OPS_GET (SOCKET_OPS_BASE) #define SOCKET_OPS_MAX (SOCKET_OPS_BASE + 1 ) #define KMSG "a message from kernel" #define KMSG_LEN sizeof("a message from kernel" ) MODULE_LICENSE("GPL" ); static int recv_msg (struct sock *sk, int cmd, void *user, unsigned int len) { int ret = 0 ; printk(KERN_INFO "sockopt: recv_msg()\n" ); if (cmd == SOCKET_OPS_SET) { char umsg[64 ]; int len = sizeof (char )*64 ; memset (umsg, 0 , len); ret = copy_from_user(umsg, user, len); printk("recv_msg: umsg = %s. ret = %d\n" , umsg, ret); } return 0 ; } static int send_msg (struct sock *sk, int cmd, void *user, int *len) { int ret = 0 ; printk(KERN_INFO "sockopt: send_msg()\n" ); if (cmd == SOCKET_OPS_GET) { ret = copy_to_user(user, KMSG, KMSG_LEN); printk("send_msg: umsg = %s. ret = %d. success\n" , KMSG, ret); } return 0 ; } static struct nf_sockopt_ops test_sockops ={ .pf = PF_INET, .set_optmin = SOCKET_OPS_SET, .set_optmax = SOCKET_OPS_MAX, .set = recv_msg, .get_optmin = SOCKET_OPS_GET, .get_optmax = SOCKET_OPS_MAX, .get = send_msg, }; static int __init init_sockopt (void ) { printk(KERN_INFO "sockopt: init_sockopt()\n" ); return nf_register_sockopt(&test_sockops); } static void __exit fini_sockopt (void ) { printk(KERN_INFO "sockopt: fini_sockopt()\n" ); nf_unregister_sockopt(&test_sockops); } module_init(init_sockopt); module_exit(fini_sockopt);
应用层代码 sockopt_usr.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <unistd.h> #include <stdio.h> #include <sys/socket.h> #include <linux/in.h> #include <string.h> #include <errno.h> #define SOCKET_OPS_BASE 128 #define SOCKET_OPS_SET (SOCKET_OPS_BASE) #define SOCKET_OPS_GET (SOCKET_OPS_BASE) #define SOCKET_OPS_MAX (SOCKET_OPS_BASE + 1 ) #define UMSG "a message from userspace" #define UMSG_LEN sizeof("a message from userspace" ) char kmsg[64 ];int main () { int sockfd; int len; int ret; sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (sockfd < 0 ) { printf ("can not create a socket\n" ); return -1 ; } ret = setsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_SET, UMSG, UMSG_LEN); printf ("setsockopt: ret = %d. msg = %s\n" , ret, UMSG); len = sizeof (char )*64 ; ret = getsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_GET, kmsg, &len); printf ("getsockopt: ret = %d. msg = %s\n" , ret, kmsg); if (ret != 0 ) { printf ("getsockopt error: errno = %d, errstr = %s\n" , errno, strerror(errno)); } close(sockfd); return 0 ; }
示例二:用户层获取tcp连接对端窗口 内核模块代码 rcvwnd.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <linux/module.h> #include <linux/kernel.h> #include <linux/net.h> #include <linux/tcp.h> #include <linux/fs.h> #include <linux/version.h> #include <net/sock.h> #include <linux/netfilter_ipv4.h> #define SOCKET_OPS_RCVWND 128 int get_rcv_wnd(struct sock *sk, int cmd, void __user *user, int *len){ if (cmd == SOCKET_OPS_RCVWND) { struct tcp_sock *tsk; if (sk == NULL ) return -1 ; if ((tsk = tcp_sk(sk)) == NULL ) return -1 ; copy_to_user(user, &tsk->rcv_wnd, sizeof (tsk->rcv_wnd)); } return 0 ; } static struct nf_sockopt_ops rcvwnd_sockops = { .pf = PF_INET, .set_optmin = 0 , .set_optmax = 0 , .set = NULL , .get_optmin = SOCKET_OPS_RCVWND, .get_optmax = SOCKET_OPS_RCVWND + 1 , .get = get_rcv_wnd, }; int minit(void ){ return nf_register_sockopt(&rcvwnd_sockops); } void mexit(void ){ nf_unregister_sockopt(&rcvwnd_sockops); } module_init(minit); module_exit(mexit); MODULE_LICENSE ("GPL" );
应用层用法 建立连接后,对tcp连接的文件描述符sock_fd调用getsockopt。
u32 rcvwnd = 0
int len = 0
getsockopt(sock_fd , IPPROTO, SOCKET_OPS_RCVWND, &rcvwnd , &len )