今天我们继续内核中的TCP 的socket 的学习,同样按照Unix 那节中的socket 地址绑定的路线我们来分析一下,我们看到在以前的练习中有这样的绑定代码
bind ( server_sockfd, ( struct sockaddr * ) & server_address, server_len) ;
上面的练习请参考http://blog.chinaunix.net/u2/64681/showart_1280050.html 2-socket 的实践到内核 - - socket 使用 IP 地址通讯的那节练习
首先也是进入系统调用的总入口处sys_socketcall()系统调用处
case SYS_BIND: err = sys_bind( a0, ( struct sockaddr __user * ) a1, a[ 2] ) ; break ;
很明显我们要进入sys_bind,朋友们请参考我的另一篇文章http://blog.chinaunix.net/u2/64681/showart_1308464.html ,在那里我们讲述了进入sys_bind函数的过程,我们直接跳过细节部分,首先在sys_bind中使用sockfd_lookup_light()找到我们在上一节创建的socket,接着要把我们练习中的地址结构变量通过move_addr_to_kernel()拷贝到局部数组变量char address[MAX_SOCK_ADDR]中。我们看一下练习中的声明的
struct sockaddr_in server_address; 我们以前说过 unix 的 socket 使用的地址结构是 struct sockaddr_un,而结构struct sockaddr_in 我们还没有看过
/* Structure describing an Internet (IP) socket address. */ # define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ struct sockaddr_in { sa_family_t sin_family; /* Address family */ __be16 sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ /* Pad to size of `struct sockaddr'. */ unsigned char __pad[ __SOCK_SIZE__ - sizeof ( short int ) - sizeof ( unsigned short int ) - sizeof ( struct in_addr ) ] ; } ;
这个结构正象注释中说明的那样用于 IP 的 socket 地址使用。而在练习中我们看到
server_address. sin_family = AF_INET ; server_address. sin_addr. s_addr = inet_addr( "127.0.0.1" ) ; server_address. sin_port = 9734;
对照上面的结构我们发现还有一个 __pad
数组我们没看到对他的赋值和操作,这个数组主要作用填充保障我们的地址数据结构与通用的
sockaddr
数据结构保持相同的大小。而
sockaddr
结构我们在
http://blog.chinaunix.net/u2/64681/showart_1308464.html
章节中看到过了,不了解的朋友可以回过头去看一下,此后我们在应用程序中设置的地址结构的数据都复制到了
sys_bind()
函数中的数组变量
address
中了,接着我们看到会执行
sock- > ops- > bind ( sock, ( struct sockaddr * ) address, addrlen) ;
在这里应该明白了上面__pad 的作用了,确实是为了将我们的数据能够正确对应到sockaddr结构的大小起填充作用的。我们回忆一下昨天讲到的socket的创建过程中对ops钩子结构的操作,在那里将socket的ops通过answer结构变量转接入了
inet_stream_ops
,
所以这里会跳入这个钩子结构去执行,我们先看一下这个结构
const struct proto_ops inet_stream_ops = { . family = PF_INET , . owner = THIS_MODULE, . release = inet_release, . bind = inet_bind, . connect = inet_stream_connect, . socketpair = sock_no_socketpair, . accept = inet_accept, . getname = inet_getname, . poll = tcp_poll, . ioctl = inet_ioctl, . listen = inet_listen, . shutdown = inet_shutdown, . setsockopt = sock_common_setsockopt, . getsockopt = sock_common_getsockopt, . sendmsg = tcp_sendmsg, . recvmsg = sock_common_recvmsg, . mmap = sock_no_mmap, . sendpage = tcp_sendpage, . splice_read = tcp_splice_read, # ifdef CONFIG_COMPAT . compat_setsockopt = compat_sock_common_setsockopt, . compat_getsockopt = compat_sock_common_getsockopt, # endif } ;
结构中其他的部分我们暂且不要关心,只注意
.bind= inet_bind 这一句,也就是说会执行钩子函数inet_bind 。我是无名小卒,请转载的朋友注明出处。这个函数在/net/ipv4/Af_inet.c中的449行处
int inet_bind( struct socket * sock, struct sockaddr * uaddr, int addr_len) { struct sockaddr_in * addr = ( struct sockaddr_in * ) uaddr; struct sock * sk = sock- > sk; struct inet_sock * inet = inet_sk( sk) ; unsigned short snum; int chk_addr_ret; int err; /* If the socket has its own bind function then use it. (RAW) */ if ( sk- > sk_prot- > bind ) { err = sk- > sk_prot- > bind ( sk, uaddr, addr_len) ; goto out; } err = - EINVAL; if ( addr_len < sizeof ( struct sockaddr_in ) ) goto out; chk_addr_ret = inet_addr_type( sock_net( sk) , addr- > sin_addr. s_addr) ; /* Not specified by any standard per-se, however it breaks too * many applications when removed. It is unfortunate since * allowing applications to make a non-local bind solves * several problems with systems using dynamic addressing. * (ie. your servers still start up even if your ISDN link * is temporarily down) */ err = - EADDRNOTAVAIL; if ( ! sysctl_ip_nonlocal_bind & & ! inet- > freebind & & addr- > sin_addr. s_addr ! = htonl ( INADDR_ANY ) & & chk_addr_ret ! = RTN_LOCAL & & chk_addr_ret ! = RTN_MULTICAST & & chk_addr_ret ! = RTN_BROADCAST) goto out; snum = ntohs ( addr- > sin_port) ; err = - EACCES; if ( snum & & snum < PROT_SOCK & & ! capable( CAP_NET_BIND_SERVICE) ) goto out; /* We keep a pair of addresses. rcv_saddr is the one * used by hash lookups, and saddr is used for transmit. * * In the BSD API these are the same except where it * would be illegal to use them (multicast/broadcast) in * which case the sending device address is used. */ lock_sock( sk) ; /* Check these errors (active socket, double bind). */ err = - EINVAL; if ( sk- > sk_state ! = TCP_CLOSE | | inet- > num) goto out_release_sock; inet- > rcv_saddr = inet- > saddr = addr- > sin_addr. s_addr; if ( chk_addr_ret = = RTN_MULTICAST | | chk_addr_ret = = RTN_BROADCAST) inet- > saddr = 0; /* Use device */ /* Make sure we are allowed to bind here. */ if ( sk- > sk_prot- > get_port( sk, snum) ) { inet- > saddr = inet- > rcv_saddr = 0; err = - EADDRINUSE; goto out_release_sock; } if ( inet- > rcv_saddr) sk- > sk_userlocks | = SOCK_BINDADDR_LOCK; if ( snum) sk- > sk_userlocks | = SOCK_BINDPORT_LOCK; inet- > sport = htons ( inet- > num) ; inet- > daddr = 0; inet- > dport = 0; sk_dst_reset( sk) ; err = 0; out_release_sock: release_sock( sk) ; out: return err; }
可能函数看起来比较长,贴出完整的是为了朋友们在下面阅读的随时对照,但是请朋友们看完了我的分析自己再单独阅读一下上面的代码,这样有利于提高朋友阅读代码的能力同时也加深对函数的理解。在这里我们首先看到再次将通用的数据结构sockaddr 转换成我们IP中作用的地址结构sockaddr_in,这个结构已经在上面看过了。并且从socket中取出sock结构转换成我们IP使用的sock专用的结构inet_sock。接着我们看到使用sock中的sk_prot钩子结构来调用bind,而sk_prot则在上一节创建socket时我们说到了他是如何挂钩的,这里直接进入挂入的
tcp_prot
结构变量,它是传输层的结构体,但是我们在那里没有看到他挂入的bind钩子函数。所以if (sk->sk_prot->bind) 语句就失效了。然而函数下面是检测一下地址长度是否正确。此后进入inet_addr_type来检查地址的类型
unsigned int inet_addr_type( struct net * net, __be32 addr) { return __inet_dev_addr_type( net, NULL , addr) ; } static inline unsigned __inet_dev_addr_type( struct net * net, const struct net_device * dev, __be32 addr) { struct flowi fl = { . nl_u = { . ip4_u = { . daddr = addr } } } ; struct fib_result res; unsigned ret = RTN_BROADCAST; struct fib_table * local_table; if ( ipv4_is_zeronet( addr) | | ipv4_is_lbcast( addr) ) return RTN_BROADCAST; if ( ipv4_is_multicast( addr) ) return RTN_MULTICAST; # ifdef CONFIG_IP_MULTIPLE_TABLES res. r = NULL ; # endif local_table = fib_get_table( net, RT_TABLE_LOCAL) ; if ( local_table) { ret = RTN_UNICAST; if ( ! local_table- > tb_lookup( local_table, & fl, & res) ) { if ( ! dev | | dev = = res. fi- > fib_dev) ret = res. type; fib_res_put( & res) ; } } return ret; }
在上面函数中有几个数据结构,首先是struct flowi结构用于路由的键值。我们看到他内部的nl_u是一个联合,联合内部有三个ip4_u、ip6_u以及dn_u结构体。所以上面的struct flowi fl = { .nl_u = { .ip4_u = { .daddr = addr } } };用我们在练习中的
inet_addr( "127.0.0.1" ) 地址初始了这个路由键值结构变量 fl 。 struct fib_result 是返回路由查询结果用的,而 struct fib_table 则是路由表的结构体。函数中首先是检查确定使用本地的广播地址或者是多播地址。接着我们要结合参数 net 来分析,但是 struct net 我们还没有看过,我们看到上面传递下来的是 sock_net(sk) 给 net 的
static inline struct net * sock_net( const struct sock * sk) { # ifdef CONFIG_NET_NS return sk- > sk_net; # else return & init_net; # endif }
Struct net
结构非常大,他是专门用于命名网络所使用的数据结构。我们这里不列出了,只不过根据上面sock_net取我们所使用的网络空间结构。我们在分配sock结构时在
sk_alloc
函数中曾经调用了
sock_net_set
对sk_net进行挂入操作
static inline void sock_net_set( struct sock * sk, struct net * net) { # ifdef CONFIG_NET_NS sk- > sk_net = net; # endif }
而层层传递下来的net 参数则是从在创建socket时
int sock_create( int family, int type, int protocol, struct socket * * res) { return __sock_create( current- > nsproxy- > net_ns, family, type, protocol, res, 0) ; }
这个current 则是当前进程的task_struct结构,其内部有一个系统的命名空间的结构变量
/* namespaces */
struct nsproxy *nsproxy;
这个命名空间结构中则封装着系统进程所有的共享的命名空间
struct nsproxy { atomic_t count ; struct uts_namespace * uts_ns; struct ipc_namespace * ipc_ns; struct mnt_namespace * mnt_ns; struct pid_namespace * pid_ns; struct user_namespace * user_ns; struct net * net_ns; } ;
在内核中CONFIG_NET_NS配置选项是为了让用户自定义自己的网络空间结构,即上面的net结构,可以看出2.6.26内核的灵活性,但是我们一般在内核中不会配置该项,所以这里应该是取得init_net,这个结构是什么时间被初始化的呢?明天继续本文。
转自:http://blog.chinaunix.net/uid-7960587-id-2035549.html