转载自我自己的chinaunix博客:
http://blog.chinaunix.net/uid-24857907-id-4217438.html
遗憾的是我对linux socket编程基本不会,记得当初大三看unix网络编程,写一个最简单的基于客户端/服务器的程序,花了一下午,作为电子专业的学生,惭愧的说我真没学过 计算机网络 这门课;又讽刺的是,我现在从事或者说99%的可能将来会从事 网关设备 的开发(如果我毕业时,仍然选择我现在签的公司的话,那真的是讽刺了)。不说了,我就单独说说setsockopt 内核的大概流程吧,如果你想要增加自己的socket选项(类似
SO_BINDTODEVICE ,SO_DONTROUTE之类的 ),那么本文章希望给你一定的启发。
用户态,当需要设置socket属性的时候,会调用 setsockopt
socket:套接字描述符
level:层次,决定了内核处理setsokopt系统调用时调用的函数,不同的level,选用的内核函数都不同。
一般来说,这么几种类型level:SOL_SOCKET,SOL_TCP,SOL_UDP,SOL_RAW,IPPROTO_IP。
如果是 SOL_TCP/SOL_UDP/SOL_RAW,那么内核将调用各自传输层协议的setsockopt函数;如果 IPPROTO_IP,那么无论哪种一些协议,都调用统一的内核函数,在ip层处理,如果是 SOL_SOCKET同样也是,在socket层处理,无论哪种协议,都调用统一的接口。
至于为什么有sock_setsockopt和ip_setsockopt这两个不同层次的处理,暂时不知道区别在哪里(区别是当然有区别的,不然为什么要保留这种机制呢。)
option_name:选项名字。
option_value:下发到内核的数据。
以下是set sockopt函数调用的内核接口:
如果用户态level ==
SOL_SOCKET时,那么直接调用socket层统一的接口:sock_setsockopt,在socket层处理了,就像我之前说的那样,无论哪种协议,都在一个函数里面处理。
如果level不是 SOL_SOCKET(也就是level是SOL_TCP/SOL_UDP,IPPROTO_IP),那么调用各自协议栈初始化时指向的setsockopt函数,11行的:sock->ops->setsockopt,
那么sock->ops->setsockopt是什么函数呢?其实就是我说的不同协议对应的setsockopt函数。
sock->ops->setsockopt 在inet_init函数中被初始化。
我们来看看tcp_proto全局变量:
在socket被创建时(即用户态使用socket系统调用时),内核就把就判断用户创建的socket类型,是tcp还是udp还是raw,然后把相应的指针函数,赋值成相应协议的函数。
当我们用户态level不是SOL_SOCKET,如果是tcp,那么sock->ops->setsockopt 就是tcp_setsockopt。
我们看到内核再次对level作出“筛选
”,如果level 是
SOL_TCP,那么ok,直接调用tcp 的 setsockopt函数。
如果level!=SOL_TCP,那么level 就是IPPROTO_IP,就会调用 icsk - > icsk_af_ops - > setsockopt。
那么, icsk - > icsk_af_ops - > setsockopt又他妈的是什么函数呢?
其实icsk - > icsk_af_ops - > setsockopt 是在inet_create 函数中被初始化的,也就是当你新建一个socket的时候,就被初始化了:
咦,还是没有对
icsk
-
>
icsk_af_ops
-
>
setsockopt的初始化?别急。
我们先看到 sk->sk_prot->init(sk) 这个函数,init指针其实就是之前我们在 tcp_proto 全局变量中,看到的 init字段,被初始化为:tcp_v4_init_sock()
看到
icsk
-
>
icsk_af_ops 了,离答案更加接近一步了!
ok,终于找到 icsk - > icsk_af_ops - > setsockopt 对应的函数了 :ip_setsockopt !!!!大功告成!!
所以用户态的setsockopt函数。最终根据你给出的level以及相应的协议类型,会调用下面的其中一个函数:
sock_setsockopt
udp_setsockopt
tcp_setsockopt
ip_setsockopt
我们来看看 ip_setsockopt函数吧,基本自己添加socket选项,都往这个上面添加,比较方面,你懂的,不需要每个协议都添加一遍,省事儿。
当然,前提是你在sock结构体中增加一个名字为my_opt 的字段,存放你用户态的数据,这样,只要有sock的地方,比如在查路由,协议发包等流程,你就能用你的my_opt,做你想做的事情。
用户态,当需要设置socket属性的时候,会调用 setsockopt
点击(此处)折叠或打开
- int setsockopt( int socket, int level, int option_name,const void *option_value, size_t option_len);
level:层次,决定了内核处理setsokopt系统调用时调用的函数,不同的level,选用的内核函数都不同。
一般来说,这么几种类型level:SOL_SOCKET,SOL_TCP,SOL_UDP,SOL_RAW,IPPROTO_IP。
如果是 SOL_TCP/SOL_UDP/SOL_RAW,那么内核将调用各自传输层协议的setsockopt函数;如果 IPPROTO_IP,那么无论哪种一些协议,都调用统一的内核函数,在ip层处理,如果是 SOL_SOCKET同样也是,在socket层处理,无论哪种协议,都调用统一的接口。
至于为什么有sock_setsockopt和ip_setsockopt这两个不同层次的处理,暂时不知道区别在哪里(区别是当然有区别的,不然为什么要保留这种机制呢。)
option_name:选项名字。
option_value:下发到内核的数据。
以下是set sockopt函数调用的内核接口:
点击(此处)折叠或打开
- int kernel_setsockopt(struct socket *sock, int level, int optname,
- char *optval, unsigned int optlen)
- {
- mm_segment_t oldfs = get_fs();
- int err;
-
- set_fs(KERNEL_DS);
- if (level == SOL_SOCKET)
- err = sock_setsockopt(sock, level, optname, optval, optlen);
- else
- err = sock->ops->setsockopt(sock, level, optname, optval,
- optlen);
- set_fs(oldfs);
- return err;
- }
如果level不是 SOL_SOCKET(也就是level是SOL_TCP/SOL_UDP,IPPROTO_IP),那么调用各自协议栈初始化时指向的setsockopt函数,11行的:sock->ops->setsockopt,
那么sock->ops->setsockopt是什么函数呢?其实就是我说的不同协议对应的setsockopt函数。
sock->ops->setsockopt 在inet_init函数中被初始化。
我们来看看tcp_proto全局变量:
在socket被创建时(即用户态使用socket系统调用时),内核就把就判断用户创建的socket类型,是tcp还是udp还是raw,然后把相应的指针函数,赋值成相应协议的函数。
当我们用户态level不是SOL_SOCKET,如果是tcp,那么sock->ops->setsockopt 就是tcp_setsockopt。
点击(此处)折叠或打开
- int tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
- unsigned int optlen)
- {
- struct inet_connection_sock *icsk = inet_csk(sk);
-
- if (level != SOL_TCP)
- return icsk->icsk_af_ops->setsockopt(sk, level, optname,
- optval, optlen);
- return do_tcp_setsockopt(sk, level, optname, optval, optlen);
- }
如果level!=SOL_TCP,那么level 就是IPPROTO_IP,就会调用 icsk - > icsk_af_ops - > setsockopt。
那么, icsk - > icsk_af_ops - > setsockopt又他妈的是什么函数呢?
其实icsk - > icsk_af_ops - > setsockopt 是在inet_create 函数中被初始化的,也就是当你新建一个socket的时候,就被初始化了:
点击(此处)折叠或打开
- static int inet_create(struct net *net, struct socket *sock, int protocol)
- {
- ...................
- if (sk->sk_prot->init) {
err = sk->sk_prot->init(sk);
if (err)
sk_common_release(sk);
}
out:
return err;
out_rcu_unlock:
rcu_read_unlock();
goto out;
- }
我们先看到 sk->sk_prot->init(sk) 这个函数,init指针其实就是之前我们在 tcp_proto 全局变量中,看到的 init字段,被初始化为:tcp_v4_init_sock()
点击(此处)折叠或打开
- /* NOTE: A lot of things set to zero explicitly by call to
- * sk_alloc() so need not be done here.
- */
- static int tcp_v4_init_sock(struct sock *sk)
- {
- struct inet_connection_sock *icsk = inet_csk(sk);
- struct tcp_sock *tp = tcp_sk(sk);
-
- skb_queue_head_init(&tp->out_of_order_queue);
- tcp_init_xmit_timers(sk);
- tcp_prequeue_init(tp);
-
- icsk->icsk_rto = TCP_TIMEOUT_INIT;
- tp->mdev = TCP_TIMEOUT_INIT;
-
- /* So many TCP implementations out there (incorrectly) count the
- * initial SYN frame in their delayed-ACK and congestion control
- * algorithms that we must have the following bandaid to talk
- * efficiently to them. -DaveM
- */
- tp->snd_cwnd = 2;
-
- /* See draft-stevens-tcpca-spec-01 for discussion of the
- * initialization of these values.
- */
- tp->snd_ssthresh = TCP_INFINITE_SSTHRESH;
- tp->snd_cwnd_clamp = ~0;
- tp->mss_cache = 536;
-
- tp->reordering = sysctl_tcp_reordering;
- icsk->icsk_ca_ops = &tcp_init_congestion_ops;
-
- sk->sk_state = TCP_CLOSE;
-
- sk->sk_write_space = sk_stream_write_space;
- sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
-
- icsk->icsk_af_ops = &ipv4_specific;
- icsk->icsk_sync_mss = tcp_sync_mss;
- #ifdef CONFIG_TCP_MD5SIG
- tp->af_specific = &tcp_sock_ipv4_specific;
- #endif
-
- sk->sk_sndbuf = sysctl_tcp_wmem[1];
- sk->sk_rcvbuf = sysctl_tcp_rmem[1];
-
- local_bh_disable();
- percpu_counter_inc(&tcp_sockets_allocated);
- local_bh_enable();
-
- return 0;
- }
点击(此处)折叠或打开
- const struct inet_connection_sock_af_ops ipv4_specific = {
- .queue_xmit = ip_queue_xmit,
- .send_check = tcp_v4_send_check,
- .rebuild_header = inet_sk_rebuild_header,
- .conn_request = tcp_v4_conn_request,
- .syn_recv_sock = tcp_v4_syn_recv_sock,
- .remember_stamp = tcp_v4_remember_stamp,
- .net_header_len = sizeof(struct iphdr),
- .setsockopt = ip_setsockopt,
- .getsockopt = ip_getsockopt,
- .addr2sockaddr = inet_csk_addr2sockaddr,
- .sockaddr_len = sizeof(struct sockaddr_in),
- .bind_conflict = inet_csk_bind_conflict,
- #ifdef CONFIG_COMPAT
- .compat_setsockopt = compat_ip_setsockopt,
- .compat_getsockopt = compat_ip_getsockopt,
- #endif
- };
ok,终于找到 icsk - > icsk_af_ops - > setsockopt 对应的函数了 :ip_setsockopt !!!!大功告成!!
所以用户态的setsockopt函数。最终根据你给出的level以及相应的协议类型,会调用下面的其中一个函数:
sock_setsockopt
udp_setsockopt
tcp_setsockopt
ip_setsockopt
我们来看看 ip_setsockopt函数吧,基本自己添加socket选项,都往这个上面添加,比较方面,你懂的,不需要每个协议都添加一遍,省事儿。
点击(此处)折叠或打开
- /*
- * Socket option code for IP. This is the end of the line after any
- * TCP,UDP etc options on an IP socket.
- */
-
- static int do_ip_setsockopt(struct sock *sk, int level,
- int optname, char __user *optval, unsigned int optlen)
- {
- struct inet_sock *inet = inet_sk(sk);
- int val = 0, err;
- //对选项进行验证,如果你自己光顾着新增一个新的宏,当作optname,而不在这了进行修改,那么你用户态的数据传到内核永远会是0.
- if (((1<<optname) & ((1<<IP_PKTINFO) | (1<<IP_RECVTTL) |
- (1<<IP_RECVOPTS) | (1<<IP_RECVTOS) |
- (1<<IP_RETOPTS) | (1<<IP_TOS) |
- (1<<IP_TTL) | (1<<IP_HDRINCL) |
- (1<<IP_MTU_DISCOVER) | (1<<IP_RECVERR) |
- (1<<IP_ROUTER_ALERT) | (1<<IP_FREEBIND) |
- (1<<IP_PASSSEC) | (1<<IP_TRANSPARENT))) ||
- optname == IP_MULTICAST_TTL ||
- optname == IP_MULTICAST_ALL ||
- optname == IP_MULTICAST_LOOP ||
- optname == IP_RECVORIGDSTADDR) { //如果你新建了一个名字叫 IP_ROUTETO,那么务必在这里加上一句:|| optname == IP_ROUTETO
- if (optlen >= sizeof(int)) { //否则你的val永远会是被初始化时的0,而不是你用用户态传递的数据。
- if (get_user(val, (int __user *) optval))
- return -EFAULT;
- } else if (optlen >= sizeof(char)) {
- unsigned char ucval;
-
- if (get_user(ucval, (unsigned char __user *) optval))
- return -EFAULT;
- val = (int) ucval;
- }
- }
-
- /* If optlen==0, it is equivalent to val == 0 */
-
- if (ip_mroute_opt(optname))
- return ip_mroute_setsockopt(sk, optname, optval, optlen);
-
- err = 0;
- lock_sock(sk);
-
- switch (optname) {
- case IP_OPTIONS:
- {
- struct ip_options_rcu *old, *opt = NULL;
-
- if (optlen > 40 || optlen < 0)
- goto e_inval;
- err = ip_options_get_from_user(sock_net(sk), &opt,
- optval, optlen);
- if (err)
- break;
- old = inet->inet_opt;
- if (inet->is_icsk) {
- struct inet_connection_sock *icsk = inet_csk(sk);
- #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
- if (sk->sk_family == PF_INET ||
- (!((1 << sk->sk_state) &
- (TCPF_LISTEN | TCPF_CLOSE)) &&
- inet->daddr != LOOPBACK4_IPV6)) {
- #endif
- if (old)
- icsk->icsk_ext_hdr_len -= old->opt.optlen;
- if (opt)
- icsk->icsk_ext_hdr_len += opt->opt.optlen;
- icsk->icsk_sync_mss(sk, icsk->icsk_pmtu_cookie);
- #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
- }
- #endif
- }
- rcu_assign_pointer(inet->inet_opt, opt);
- if (old)
- call_rcu(&old->rcu, opt_kfree_rcu);
- break;
- }
- case IP_PKTINFO:
- if (val)
- inet->cmsg_flags |= IP_CMSG_PKTINFO;
- else
- inet->cmsg_flags &= ~IP_CMSG_PKTINFO;
- break;
- case IP_RECVTTL:
- if (val)
- inet->cmsg_flags |= IP_CMSG_TTL;
- else
- inet->cmsg_flags &= ~IP_CMSG_TTL;
- break;
- case IP_RECVTOS:
- if (val)
- inet->cmsg_flags |= IP_CMSG_TOS;
- else
- inet->cmsg_flags &= ~IP_CMSG_TOS;
- break;
- case IP_RECVOPTS:
- if (val)
- inet->cmsg_flags |= IP_CMSG_RECVOPTS;
- else
- inet->cmsg_flags &= ~IP_CMSG_RECVOPTS;
- break;
- case IP_RETOPTS:
- if (val)
- inet->cmsg_flags |= IP_CMSG_RETOPTS;
- else
- inet->cmsg_flags &= ~IP_CMSG_RETOPTS;
- break;
- case IP_PASSSEC:
- if (val)
- inet->cmsg_flags |= IP_CMSG_PASSSEC;
- else
-
- //一大堆case,我就不一一列出来了。你可以添加自己的case比如:
- case IP_ROUTETO:
- sk->my_opt = val;
- break;
-
- default:
- err = -ENOPROTOOPT;
- break;
- }
- release_sock(sk);
- return err;
-
- e_inval:
- release_sock(sk);
- return -EINVAL;
- }