遗憾的是我对linux socket编程基本不会,记得当初大三看unix网络编程,写一个最简单的基于客户端/服务器的程序,花了一下午,作为电子专业的学生,惭愧的说我真没学过 计算机网络 这门课;又讽刺的是,我现在从事或者说99%的可能将来会从事 网关设备 的开发(如果我毕业时,仍然选择我现在签的公司的话,那真的是讽刺了)。不说了,我就单独说说setsockopt 内核的大概流程吧,如果你想要增加自己的socket选项(类似
SO_BINDTODEVICE ,SO_DONTROUTE之类的 ),那么本文章希望给你一定的启发。
用户态,当需要设置socket属性的时候,会调用
setsockopt,
-
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函数调用的内核接口:
-
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时,那么直接调用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。
-
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作出“筛选”,如果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的时候,就被初始化了:
-
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;
-
}
咦,还是没有对icsk->icsk_af_ops->setsockopt的初始化?别急。
我们先看到 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;
-
}
看到icsk->icsk_af_ops 了,离答案更加接近一步了!
-
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;
-
}
当然,前提是你在sock结构体中增加一个名字为my_opt 的字段,存放你用户态的数据,这样,只要有sock的地方,比如在查路由,协议发包等流程,你就能用你的my_opt,做你想做的事情。
阅读(589) | 评论(0) | 转发(0) |