在说connect调用之前,先简单看下
inet_aton,这个函数完成的是ip地址的转换,它是将一个IP地址转换为一个4字节的整数。ok,回到我们的connect函数,首先,connect是一个系统调用。如下:
kernel\net\Socket.c
SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
int, addrlen)
{
struct socket *sock;
struct sockaddr_storage address;
int err, fput_needed;
//之前在socket的创建的时候,内核生成了一个struct file *file结构,而且这个结构被关联到了fd描述符,这里要做的就是通过这个fd找到file指针,然后通过file->private_data找到sock指针,因为在socket创建的时候文件的私有数据里面存放的就是这个socket的指针
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
//这个就是将用户态的IP地址信息拷贝到内核态中,之后地址就存放到了address结构中
err = move_addr_to_kernel(uservaddr, addrlen, &address);
if (err < 0)
goto out_put;
//安全相关的检查,忽略
err =
security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
if (err)
goto out_put;
//在上一篇文章的socket创建过程中,我们讲到过sock->ops对于TCP而言被赋值为inet_stream_ops
err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
sock->file->f_flags);
//最后通过nlmsg_multicast将socket连接的消息发送出去
if (!err)
sockev_notify(SOCKEV_CONNECT, sock);
out_put:
fput_light(sock->file, fput_needed);
out:
return err;
}
继续分析
inet_stream_ops函数
kernel\net\ipv4\Af_inet.c
const struct proto_ops inet_stream_ops = {
.family = PF_INET,
...
.connect = inet_stream_connect,
...
}
inet_stream_connect
__inet_stream_connect(sock, uaddr, addr_len, flags);
switch (sock->state) {
...
之前我们在socket创建的时候是将当前socket的状态设置为未连接的状态,现在进行钻港台的改变
case
SS_UNCONNECTED:
err = sk->sk_prot->
connect(sk, uaddr, addr_len);
sock->state =
SS_CONNECTING;
break;
}
sock->state =
SS_CONNECTED;
针对我们目前的TCP协议而言,这里的sk->sk_prot指向是tcp_prot,所以其connect函数就是
tcp_v4_connect
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v4_connect,
...
}
对于 TCP 协议来说,其连接实际上就是发送一个 SYN 报文,在服务器的应答到来时,回答它一
个 ack 报文,也就是完成三次握手中的第一和第三次。
要发送 SYN 报文,也就是说,需要有完整的来源/目的地址,来源/目的端口,目的地址/端口由用户
态提交,但是问题是没有自己的地址和端口,因为并没有调用过 bind(2),一台主机,对于端口,
可以像 sys_bind()那样,从本地未用端口中动态分配一个,那地址呢?因为一台主机可能会存在多
个 IP地址,如果随机动态选择,那么有可能选择一个错误的来源地址,将不能正确地到达目的地
址。换句话说,来源地址的选择,是与路由相关的。
调用路由查找的核心函数 ip_route_output_flow(),在没有提供来源地址的情况下,会根据实际情况,
调用 inet_select_addr()函数来选择一个合适的。同时,如果路由查找命中,会生成一个相应的路由
缓存项,这个缓存项,不但对当前发送SYN报文有意义,对于后续的所有数据包,都可以起到一
个加速路由查找的作用。这一任务,是通过 ip_route_connect()函数完成的,它返回相应的路由缓存
项(也就是说,来源地址也在其中了):
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
//这里的inet_sock和tcp_sock需要特别的说明下:这个结构是在socket创建的时候分配的,其大小为.obj_size = sizeof(struct tcp_sock),如下:
/*
struct tcp_sock
struct inet_connection_sock
struct inet_sock
struct sock sk;
*/
//因此我们可以知道我们这儿的inet和tp指针是向下转型
struct inet_sock *inet = inet_sk(sk);
struct tcp_sock *tp = tcp_sk(sk);
__be16 orig_sport, orig_dport;
//目标地址和ip层的下一跳地址
__be32 daddr, nexthop;
struct flowi4 *fl4;
struct rtable *rt;
int err;
struct ip_options_rcu *inet_opt;
...
//先将下一跳地址和目标地址设置用户设置的目的地址
nexthop = daddr = usin->sin_addr.s_addr;
inet_opt = rcu_dereference_protected(inet->inet_opt,
sock_owned_by_user(sk));
/* 如果使用源地址路由,则需要改变下一跳的地址,我们应用的APP并没有设置源路由,因此上一步返回的inet_opt指针实际是空指针*/
if (inet_opt && inet_opt->opt.srr) {
if (!daddr)
return -EINVAL;
nexthop = inet_opt->opt.faddr;
}
/* 本端端口,目前可能已绑定,也可能没分配,针对我们的APP而言这个值没有设置为0*/
orig_sport = inet->inet_sport;
//用户设置的目的IP的端口
orig_dport = usin->sin_port;
fl4 = &inet->cork.fl.u.ip4;
inet->inet_saddr这个参数同样为0,也就是说当前源地址和源端口都是为0的
rt = ip_route_connect(fl4, nexthop, inet->inet_saddr,
RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
IPPROTO_TCP,
orig_sport, orig_dport, sk, true);
if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
ip_rt_put(rt);
return -ENETUNREACH;
}
if (!inet_opt || !inet_opt->opt.srr)
daddr = fl4->daddr;
if (!inet->inet_saddr)
inet->inet_saddr = fl4->saddr;
inet->inet_rcv_saddr = inet->inet_saddr;
if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) {
/* Reset inherited state */
tp->rx_opt.ts_recent = 0;
tp->rx_opt.ts_recent_stamp = 0;
if (likely(!tp->repair))
tp->write_seq = 0;
}
if (tcp_death_row.sysctl_tw_recycle &&
!tp->rx_opt.ts_recent_stamp && fl4->daddr == daddr)
tcp_fetch_timewait_stamp(sk, &rt->dst);
inet->inet_dport = usin->sin_port;
inet->inet_daddr = daddr;
inet_csk(sk)->icsk_ext_hdr_len = 0;
if (inet_opt)
inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen;
tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT;
/* Socket identity is still unknown (sport may be zero).
* However we set state to SYN-SENT and not releasing socket
* lock select source port, enter ourselves into the hash tables and
* complete initialization after this.
*/
tcp_set_state(sk, TCP_SYN_SENT);
err = inet_hash_connect(&tcp_death_row, sk);
if (err)
goto failure;
rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
inet->inet_sport, inet->inet_dport, sk);
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
rt = NULL;
goto failure;
}
/* OK, now commit destination to socket. */
sk->sk_gso_type = SKB_GSO_TCPV4;
sk_setup_caps(sk, &rt->dst);
if (!tp->write_seq && likely(!tp->repair))
tp->write_seq = secure_tcp_sequence_number(inet->inet_saddr,
inet->inet_daddr,
inet->inet_sport,
usin->sin_port);
inet->inet_id = tp->write_seq ^ jiffies;
err = tcp_connect(sk);
rt = NULL;
if (err)
goto failure;
return 0;
}