Chinaunix首页 | 论坛 | 博客
  • 博客访问: 674274
  • 博文数量: 404
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 1237
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-03 10:45
文章分类

全部博文(404)

文章存档

2017年(1)

2016年(27)

2015年(39)

2014年(55)

2013年(66)

2012年(216)

分类: C/C++

2014-04-23 21:47:27

原文地址:setsockopt 内核实现 作者:mrpre

遗憾的是我对linux socket编程基本不会,记得当初大三看unix网络编程,写一个最简单的基于客户端/服务器的程序,花了一下午,作为电子专业的学生,惭愧的说我真没学过 计算机网络 这门课;又讽刺的是,我现在从事或者说99%的可能将来会从事 网关设备 的开发(如果我毕业时,仍然选择我现在签的公司的话,那真的是讽刺了)。不说了,我就单独说说setsockopt 内核的大概流程吧,如果你想要增加自己的socket选项(类似SO_BINDTODEVICE ,SO_DONTROUTE之类的 ),那么本文章希望给你一定的启发。


用户态,当需要设置socket属性的时候,会调用setsockopt,



点击(此处)折叠或打开

  1. int setsockopt( int socket, int level, int option_name,const void *option_value, size_t option_len);
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:下发到内核的数据。



以下是setsockopt函数调用的内核接口:

点击(此处)折叠或打开

  1. int kernel_setsockopt(struct socket *sock, int level, int optname,
  2.             char *optval, unsigned int optlen)
  3. {
  4.     mm_segment_t oldfs = get_fs();
  5.     int err;

  6.     set_fs(KERNEL_DS);
  7.     if (level == SOL_SOCKET)
  8.         err = sock_setsockopt(sock, level, optname, optval, optlen);
  9.     else
  10.         err = sock->ops->setsockopt(sock, level, optname, optval,
  11.                      optlen);
  12.     set_fs(oldfs);
  13.     return err;
  14. }
如果用户态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。

点击(此处)折叠或打开

  1. int tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
  2.          unsigned int optlen)
  3. {
  4.     struct inet_connection_sock *icsk = inet_csk(sk);

  5.     if (level != SOL_TCP)
  6.         return icsk->icsk_af_ops->setsockopt(sk, level, optname,
  7.                          optval, optlen);
  8.     return do_tcp_setsockopt(sk, level, optname, optval, optlen);
  9. }
我们看到内核再次对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的时候,就被初始化了:


点击(此处)折叠或打开

  1. static int inet_create(struct net *net, struct socket *sock, int protocol)
  2. {
  3.     ...................
  4.     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;

  5. }
咦,还是没有对icsk->icsk_af_ops->setsockopt的初始化?别急。

我们先看到 sk->sk_prot->init(sk) 这个函数,init指针其实就是之前我们在tcp_proto 全局变量中,看到的 init字段,被初始化为:tcp_v4_init_sock()

点击(此处)折叠或打开

  1. /* NOTE: A lot of things set to zero explicitly by call to
  2.  * sk_alloc() so need not be done here.
  3.  */
  4. static int tcp_v4_init_sock(struct sock *sk)
  5. {
  6.     struct inet_connection_sock *icsk = inet_csk(sk);
  7.     struct tcp_sock *tp = tcp_sk(sk);

  8.     skb_queue_head_init(&tp->out_of_order_queue);
  9.     tcp_init_xmit_timers(sk);
  10.     tcp_prequeue_init(tp);

  11.     icsk->icsk_rto = TCP_TIMEOUT_INIT;
  12.     tp->mdev = TCP_TIMEOUT_INIT;

  13.     /* So many TCP implementations out there (incorrectly) count the
  14.      * initial SYN frame in their delayed-ACK and congestion control
  15.      * algorithms that we must have the following bandaid to talk
  16.      * efficiently to them. -DaveM
  17.      */
  18.     tp->snd_cwnd = 2;

  19.     /* See draft-stevens-tcpca-spec-01 for discussion of the
  20.      * initialization of these values.
  21.      */
  22.     tp->snd_ssthresh = TCP_INFINITE_SSTHRESH;
  23.     tp->snd_cwnd_clamp = ~0;
  24.     tp->mss_cache = 536;

  25.     tp->reordering = sysctl_tcp_reordering;
  26.     icsk->icsk_ca_ops = &tcp_init_congestion_ops;

  27.     sk->sk_state = TCP_CLOSE;

  28.     sk->sk_write_space = sk_stream_write_space;
  29.     sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);

  30.     icsk->icsk_af_ops = &ipv4_specific;
  31.     icsk->icsk_sync_mss = tcp_sync_mss;
  32. #ifdef CONFIG_TCP_MD5SIG
  33.     tp->af_specific = &tcp_sock_ipv4_specific;
  34. #endif

  35.     sk->sk_sndbuf = sysctl_tcp_wmem[1];
  36.     sk->sk_rcvbuf = sysctl_tcp_rmem[1];

  37.     local_bh_disable();
  38.     percpu_counter_inc(&tcp_sockets_allocated);
  39.     local_bh_enable();

  40.     return 0;
  41. }
看到icsk->icsk_af_ops 了,离答案更加接近一步了!

点击(此处)折叠或打开

  1. const struct inet_connection_sock_af_ops ipv4_specific = {
  2.     .queue_xmit     = ip_queue_xmit,
  3.     .send_check     = tcp_v4_send_check,
  4.     .rebuild_header     = inet_sk_rebuild_header,
  5.     .conn_request     = tcp_v4_conn_request,
  6.     .syn_recv_sock     = tcp_v4_syn_recv_sock,
  7.     .remember_stamp     = tcp_v4_remember_stamp,
  8.     .net_header_len     = sizeof(struct iphdr),
  9.     .setsockopt     = ip_setsockopt,
  10.     .getsockopt     = ip_getsockopt,
  11.     .addr2sockaddr     = inet_csk_addr2sockaddr,
  12.     .sockaddr_len     = sizeof(struct sockaddr_in),
  13.     .bind_conflict     = inet_csk_bind_conflict,
  14. #ifdef CONFIG_COMPAT
  15.     .compat_setsockopt = compat_ip_setsockopt,
  16.     .compat_getsockopt = compat_ip_getsockopt,
  17. #endif
  18. };

ok,终于找到icsk->icsk_af_ops->setsockopt 对应的函数了 :ip_setsockopt !!!!大功告成!!

所以用户态的setsockopt函数。最终根据你给出的level以及相应的协议类型,会调用下面的其中一个函数:
sock_setsockopt
udp_setsockopt
tcp_setsockopt

ip_setsockopt 

我们来看看 ip_setsockopt函数吧,基本自己添加socket选项,都往这个上面添加,比较方面,你懂的,不需要每个协议都添加一遍,省事儿。


点击(此处)折叠或打开

  1. /*
  2.  *    Socket option code for IP. This is the end of the line after any
  3.  *    TCP,UDP etc options on an IP socket.
  4.  */

  5. static int do_ip_setsockopt(struct sock *sk, int level,
  6.              int optname, char __user *optval, unsigned int optlen)
  7. {
  8.     struct inet_sock *inet = inet_sk(sk);
  9.     int val = 0, err;
  10.     //对选项进行验证,如果你自己光顾着新增一个新的宏,当作optname,而不在这了进行修改,那么你用户态的数据传到内核永远会是0.
  11.     if (((1<<optname) & ((1<<IP_PKTINFO) | (1<<IP_RECVTTL) |
  12.              (1<<IP_RECVOPTS) | (1<<IP_RECVTOS) |
  13.              (1<<IP_RETOPTS) | (1<<IP_TOS) |
  14.              (1<<IP_TTL) | (1<<IP_HDRINCL) |
  15.              (1<<IP_MTU_DISCOVER) | (1<<IP_RECVERR) |
  16.              (1<<IP_ROUTER_ALERT) | (1<<IP_FREEBIND) |
  17.              (1<<IP_PASSSEC) | (1<<IP_TRANSPARENT))) ||
  18.      optname == IP_MULTICAST_TTL ||
  19.      optname == IP_MULTICAST_ALL ||
  20.      optname == IP_MULTICAST_LOOP ||
  21.      optname == IP_RECVORIGDSTADDR) {  //如果你新建了一个名字叫 IP_ROUTETO,那么务必在这里加上一句:|| optname == IP_ROUTETO
  22.         if (optlen >= sizeof(int)) {   //否则你的val永远会是被初始化时的0,而不是你用用户态传递的数据。
  23.             if (get_user(val, (int __user *) optval))
  24.                 return -EFAULT;
  25.         } else if (optlen >= sizeof(char)) {
  26.             unsigned char ucval;

  27.             if (get_user(ucval, (unsigned char __user *) optval))
  28.                 return -EFAULT;
  29.             val = (int) ucval;
  30.         }
  31.     }

  32.     /* If optlen==0, it is equivalent to val == 0 */

  33.     if (ip_mroute_opt(optname))
  34.         return ip_mroute_setsockopt(sk, optname, optval, optlen);

  35.     err = 0;
  36.     lock_sock(sk);

  37.     switch (optname) {
  38.     case IP_OPTIONS:
  39.     {
  40.         struct ip_options_rcu *old, *opt = NULL;

  41.         if (optlen > 40 || optlen < 0)
  42.             goto e_inval;
  43.         err = ip_options_get_from_user(sock_net(sk), &opt,
  44.                      optval, optlen);
  45.         if (err)
  46.             break;
  47.         old = inet->inet_opt;
  48.         if (inet->is_icsk) {
  49.             struct inet_connection_sock *icsk = inet_csk(sk);
  50. #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
  51.             if (sk->sk_family == PF_INET ||
  52.              (!((1 << sk->sk_state) &
  53.              (TCPF_LISTEN | TCPF_CLOSE)) &&
  54.              inet->daddr != LOOPBACK4_IPV6)) {
  55. #endif
  56.                 if (old)
  57.                     icsk->icsk_ext_hdr_len -= old->opt.optlen;
  58.                 if (opt)
  59.                     icsk->icsk_ext_hdr_len += opt->opt.optlen;
  60.                 icsk->icsk_sync_mss(sk, icsk->icsk_pmtu_cookie);
  61. #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
  62.             }
  63. #endif
  64.         }
  65.         rcu_assign_pointer(inet->inet_opt, opt);
  66.         if (old)
  67.             call_rcu(&old->rcu, opt_kfree_rcu);
  68.         break;
  69.     }
  70.     case IP_PKTINFO:
  71.         if (val)
  72.             inet->cmsg_flags |= IP_CMSG_PKTINFO;
  73.         else
  74.             inet->cmsg_flags &= ~IP_CMSG_PKTINFO;
  75.         break;
  76.     case IP_RECVTTL:
  77.         if (val)
  78.             inet->cmsg_flags |= IP_CMSG_TTL;
  79.         else
  80.             inet->cmsg_flags &= ~IP_CMSG_TTL;
  81.         break;
  82.     case IP_RECVTOS:
  83.         if (val)
  84.             inet->cmsg_flags |= IP_CMSG_TOS;
  85.         else
  86.             inet->cmsg_flags &= ~IP_CMSG_TOS;
  87.         break;
  88.     case IP_RECVOPTS:
  89.         if (val)
  90.             inet->cmsg_flags |= IP_CMSG_RECVOPTS;
  91.         else
  92.             inet->cmsg_flags &= ~IP_CMSG_RECVOPTS;
  93.         break;
  94.     case IP_RETOPTS:
  95.         if (val)
  96.             inet->cmsg_flags |= IP_CMSG_RETOPTS;
  97.         else
  98.             inet->cmsg_flags &= ~IP_CMSG_RETOPTS;
  99.         break;
  100.     case IP_PASSSEC:
  101.         if (val)
  102.             inet->cmsg_flags |= IP_CMSG_PASSSEC;
  103.         else

  104.     //一大堆case,我就不一一列出来了。你可以添加自己的case比如:
  105.     case IP_ROUTETO:
  106.         sk->my_opt = val;
  107.         break;

  108.     default:
  109.         err = -ENOPROTOOPT;
  110.         break;
  111.     }
  112.     release_sock(sk);
  113.     return err;

  114. e_inval:
  115.     release_sock(sk);
  116.     return -EINVAL;
  117. }
当然,前提是你在sock结构体中增加一个名字为my_opt 的字段,存放你用户态的数据,这样,只要有sock的地方,比如在查路由,协议发包等流程,你就能用你的my_opt,做你想做的事情。
阅读(589) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~