Linux内核源码-sys_connect()
来源:互联网 发布:caddy windows 编辑:程序博客网 时间:2024/05/17 23:45
(本文部分参考了《Linux内核源代码情景分析》)
操作SYS_CONNECT请求连接由sys_connect()完成。
有连接”模式的插口与“无连接”模式的插口都可以调用库函数 connect(),但是意义却不同。内核中的函数 sys_connect()是二者公用的。
sys_connect()代码如下:
/* connet系统调用 */asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen){ struct socket *sock; char address[MAX_SOCK_ADDR]; int err; sock = sockfd_lookup(fd, &err);/* 查找文件句柄对应的socket */ if (!sock) goto out; /* 从用户态复制地址参数到内核中 */ 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; /* 调用传输层的connet方法inet_stream_connect或inet_dgram_connect */ err = sock->ops->connect(sock, (struct sockaddr *) address, addrlen, sock->file->f_flags);out_put: sockfd_put(sock);out: return err;}
本文只看“有连接”模式的插口。如前所述,只有 client 插口才可以(并且一定要)通过 connect()向一个 server 插口提出连接请求,在请求被 server 插口接受而建立起连接之前是不能在两个插口之间传递数据报文的。
inet_stream_connect()代码如下:
/* connect系统调用的套接口层实现 */int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags){ struct sock *sk = sock->sk; int err; long timeo; lock_sock(sk);/* 获取套接口的锁 */ if (uaddr->sa_family == AF_UNSPEC) {/* 未指定地址类型,错误 */ err = sk->sk_prot->disconnect(sk, flags); sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED; goto out; } switch (sock->state) { default: err = -EINVAL; goto out; case SS_CONNECTED:/* 已经与对方端口连接*/ err = -EISCONN; goto out; case SS_CONNECTING:/*正在连接过程中*/ err = -EALREADY; /* Fall out of switch with err, set for this state */ break; case SS_UNCONNECTED:/* 只有此状态才能调用connect */ err = -EISCONN; if (sk->sk_state != TCP_CLOSE)/* 如果不是TCP_CLOSE状态,说明已经连接了 */ goto out;/* 调用传输层接口tcp_v4_connect建立与服务器连接,并发送SYN段 */ err = sk->sk_prot->connect(sk, uaddr, addr_len); if (err < 0) goto out; /* 发送SYN段后,设置状态为SS_CONNECTING */ sock->state = SS_CONNECTING; err = -EINPROGRESS;/* 如果是以非阻塞方式进行连接,则默认的返回值为EINPROGRESS,表示正在连接 */ break; } /* 获取连接超时时间,如果指定非阻塞方式,则不等待直接返回 */ timeo = sock_sndtimeo(sk, flags & O_NONBLOCK); if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {/* 发送完SYN后,连接状态一般为这两种状态,但是如果连接建立非常快,则可能越过这两种状态 */ if (!timeo || !inet_wait_for_connect(sk, timeo))/* 等待连接完成或超时 */ goto out; err = sock_intr_errno(timeo); if (signal_pending(current)) goto out; } if (sk->sk_state == TCP_CLOSE)/* 运行到这里说明连接建立失败 */ goto sock_error; sock->state = SS_CONNECTED;/* 连接建立成功,设置为已经连接状态 */ err = 0;out: release_sock(sk); return err;sock_error: err = sock_error(sk) ? : -ECONNABORTED; sock->state = SS_UNCONNECTED; if (sk->sk_prot->disconnect(sk, flags)) sock->state = SS_DISCONNECTING; goto out;}
inet_stream_connect()函数主要功能如下:
(1)调用tcp_v4_connect函数建立与服务器联系并发送SYN段;
(2)获取连接超时时间timeo,如果timeo不为0,则会调用inet_wait_for_connect一直等待到连接成功或超时;
tcp_v4_connect函数代码如下,比较长,分段来看:
/* 建立与服务器连接,发送SYN段 */int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len){ struct inet_sock *inet = inet_sk(sk); struct tcp_sock *tp = tcp_sk(sk); struct sockaddr_in *usin = (struct sockaddr_in *)uaddr; struct rtable *rt; u32 daddr, nexthop; int tmp; int err; /* 校验目的地址的长度及地址族的有效性 */ if (addr_len < sizeof(struct sockaddr_in)) return -EINVAL; if (usin->sin_family != AF_INET) return -EAFNOSUPPORT; /* 将下一跳和目的地址都设置为源地址 */ nexthop = daddr = usin->sin_addr.s_addr; if (inet->opt && inet->opt->srr) {/* 如果是源站路由,则下一跳设置为选项中的地址 */ if (!daddr) return -EINVAL; nexthop = inet->opt->faddr; } /* 根据下一跳地址等信息查找目的路由缓存项。 */ tmp = ip_route_connect(&rt, nexthop, inet->saddr, RT_CONN_FLAGS(sk), sk->sk_bound_dev_if, IPPROTO_TCP, inet->sport, usin->sin_port, sk); if (tmp < 0) return tmp; if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {/* 对TCP来说,不能使用多播和组播路由项 */ ip_rt_put(rt); return -ENETUNREACH; } if (!inet->opt || !inet->opt->srr)/* 如果没有启用源路由选项,则使用路由缓存项中的目的地址 */ daddr = rt->rt_dst; if (!inet->saddr)/* 如果未设置传输控制块中的源地址,则使用路由缓存项中的源地址 */ inet->saddr = rt->rt_src; inet->rcv_saddr = inet->saddr; /* 如果传输控制块中的时间戳和目的地址已经使用过,则说明传输控制块之前已建立连接并?型ㄐ?*/ if (tp->rx_opt.ts_recent_stamp && inet->daddr != daddr) { /* Reset inherited state */ /* 重新初始化相关成员 */ tp->rx_opt.ts_recent = 0; tp->rx_opt.ts_recent_stamp = 0; tp->write_seq = 0; } if (sysctl_tcp_tw_recycle &&/* 允许处于TIME-WAIT状态快速迁移到CLOSE状态 */ !tp->rx_opt.ts_recent_stamp && rt->rt_dst == daddr) {/* 接收过时间戳 */ struct inet_peer *peer = rt_get_peer(rt); /* VJ's idea. We save last timestamp seen from * the destination in peer table, when entering state TIME-WAIT * and initialize rx_opt.ts_recent from it, when trying new connection. */ /* 从对端信息块中获取值来初始化ts_recent_stamp和ts_recent */ if (peer && peer->tcp_ts_stamp + TCP_PAWS_MSL >= xtime.tv_sec) { tp->rx_opt.ts_recent_stamp = peer->tcp_ts_stamp; tp->rx_opt.ts_recent = peer->tcp_ts; } } /* 设置目的地址和目标端口 */ inet->dport = usin->sin_port; inet->daddr = daddr;}
到此为止。函数主要设置了目的地址和目标端口,接下来重要步骤有:设置状态;将传输控制添加到散列表并动态分布端口,此处涉及到一个函数tcp_v4_hash_connect(),代码如下:
/* * Bind a port for a connect operation and hash it. *//* 动态绑定端口,并将传输控制块加入哈希表 */static inline int tcp_v4_hash_connect(struct sock *sk){ unsigned short snum = inet_sk(sk)->num; struct tcp_bind_hashbucket *head; struct tcp_bind_bucket *tb; int ret; if (!snum) {/* 未绑定端口,自动选择端口并进行绑定 */ /* 动态端口的范围 */ int low = sysctl_local_port_range[0]; int high = sysctl_local_port_range[1]; int range = high - low; int i; int port; static u32 hint; u32 offset = hint + connect_port_offset(sk); struct hlist_node *node; struct tcp_tw_bucket *tw = NULL; local_bh_disable(); for (i = 1; i <= range; i++) {/* 遍历动态端口的范围 */ /* 通过源地址、目的地址和目的端口计算得到的值作为端口初始值 */ port = low + (i + offset) % range; head = &tcp_bhash[tcp_bhashfn(port)]; spin_lock(&head->lock); /* Does not bother with rcv_saddr checks, * because the established check is already * unique enough. */ tb_for_each(tb, node, &head->chain) {/* 检测临时端口是否可用 */ if (tb->port == port) { BUG_TRAP(!hlist_empty(&tb->owners)); if (tb->fastreuse >= 0)/* 不能重用 */ goto next_port; if (!__tcp_v4_check_established(sk, port, &tw))/* 能重用 */ goto ok; goto next_port; } } /* 端口未被绑定,为该端口创建一个信息块 */ tb = tcp_bucket_create(head, port); if (!tb) { spin_unlock(&head->lock); break; } tb->fastreuse = -1; goto ok; next_port: spin_unlock(&head->lock); } local_bh_enable(); /* 找不到可用端口 */ return -EADDRNOTAVAIL;ok: hint += i; /* Head lock still held and bh's disabled */ tcp_bind_hash(sk, tb, port);/* 将传输控制块与绑定端口信息关联,完成绑定 */ if (sk_unhashed(sk)) {/* 该传输控制块未添加到哈希表 */ inet_sk(sk)->sport = htons(port); __tcp_v4_hash(sk, 0);/* 将传输控制块加入到控制块 */ } spin_unlock(&head->lock); if (tw) {/* 与TIME_WAIT状态的套接口复用端口,则释放该套接口 */ tcp_tw_deschedule(tw); tcp_tw_put(tw); } ret = 0; goto out; } /* 运行到这里,说明是指定端口,需要对其进行确认 */ head = &tcp_bhash[tcp_bhashfn(snum)]; tb = tcp_sk(sk)->bind_hash; spin_lock_bh(&head->lock); if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) { __tcp_v4_hash(sk, 0); spin_unlock_bh(&head->lock); return 0; } else { spin_unlock(&head->lock); /* No definite answer... Walk to established hash table */ ret = __tcp_v4_check_established(sk, snum, NULL);out: local_bh_enable(); return ret; }}
接下来继续tcp_v4_connect()函数:
/* 设置IP首部选项长度 */ tp->ext_header_len = 0; if (inet->opt) tp->ext_header_len = inet->opt->optlen; /* 初始化MSS上限 */ tp->rx_opt.mss_clamp = 536; /* 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 = tcp_v4_hash_connect(sk);/* 将传输控制添加到ehash散列表中,并动态分配端口 */ if (err) goto failure; /* 如果源端口或者目的端口发生改变,则需要重新查找路由 */ err = ip_route_newports(&rt, inet->sport, inet->dport, sk); if (err) goto failure; /* OK, now commit destination to socket. */ __sk_dst_set(sk, &rt->u.dst);/* 设置路由,并根据路由更新网络设备的特性 */ tcp_v4_setup_caps(sk, &rt->u.dst); tp->ext2_header_len = rt->u.dst.header_len; if (!tp->write_seq)/* 还未计算初始序号 */ /* 根据双方地址、端口计算初始序号 */ tp->write_seq = secure_tcp_sequence_number(inet->saddr, inet->daddr, inet->sport, usin->sin_port); /* 根据初始序号和当前时间,随机算一个初始id */ inet->id = tp->write_seq ^ jiffies; /* 发送SYN段 */ err = tcp_connect(sk); rt = NULL; if (err) goto failure; return 0;failure: /* This unhashes the socket and releases the local port, if necessary. */ tcp_set_state(sk, TCP_CLOSE); ip_rt_put(rt); sk->sk_route_caps = 0; inet->dport = 0; return err;}
在一系列设置及必要计算后,重点在于发送SYN段 ,函数tcp_connect()如下:
/* 构造并发送SYN段 */int tcp_connect(struct sock *sk){ struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *buff; tcp_connect_init(sk);/* 初始化传输控制块中与连接相关的成员 */ /* 为SYN段分配报文并进行初始化 */ buff = alloc_skb(MAX_TCP_HEADER + 15, sk->sk_allocation); if (unlikely(buff == NULL)) return -ENOBUFS; /* Reserve space for headers. */ skb_reserve(buff, MAX_TCP_HEADER); TCP_SKB_CB(buff)->flags = TCPCB_FLAG_SYN; TCP_ECN_send_syn(sk, tp, buff); TCP_SKB_CB(buff)->sacked = 0; skb_shinfo(buff)->tso_segs = 1; skb_shinfo(buff)->tso_size = 0; buff->csum = 0; TCP_SKB_CB(buff)->seq = tp->write_seq++; TCP_SKB_CB(buff)->end_seq = tp->write_seq; tp->snd_nxt = tp->write_seq; tp->pushed_seq = tp->write_seq; tcp_ca_init(tp); /* Send it off. */ TCP_SKB_CB(buff)->when = tcp_time_stamp; tp->retrans_stamp = TCP_SKB_CB(buff)->when; /* 将报文添加到发送队列上 */ __skb_queue_tail(&sk->sk_write_queue, buff); sk_charge_skb(sk, buff); tp->packets_out += tcp_skb_pcount(buff); /* 发送SYN段 */ tcp_transmit_skb(sk, skb_clone(buff, GFP_KERNEL)); TCP_INC_STATS(TCP_MIB_ACTIVEOPENS); /* Timer for repeating the SYN until an answer. */ /* 启动重传定时器 */ tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto); return 0;}
tcp_connect()中又调用了tcp_transmit_skb函数:
/* 发送一个TCP报文 */static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb){ if (skb != NULL) { struct inet_sock *inet = inet_sk(sk); struct tcp_sock *tp = tcp_sk(sk); struct tcp_skb_cb *tcb = TCP_SKB_CB(skb); int tcp_header_size = tp->tcp_header_len; struct tcphdr *th; int sysctl_flags; int err; BUG_ON(!tcp_skb_pcount(skb));#define SYSCTL_FLAG_TSTAMPS 0x1#define SYSCTL_FLAG_WSCALE 0x2#define SYSCTL_FLAG_SACK 0x4 sysctl_flags = 0;/* 标识TCP选项 */ /* 根据TCP选项调整TCP首部长度 */ if (tcb->flags & TCPCB_FLAG_SYN) {/* 如果当前段是SYN段,需要特殊处理一下 */ /* SYN段必须通告MSS,因此报头加上MSS通告选项的长度 */ tcp_header_size = sizeof(struct tcphdr) + TCPOLEN_MSS; if(sysctl_tcp_timestamps) {/* 启用了时间戳 */ /* 报头加上时间戳标志 */ tcp_header_size += TCPOLEN_TSTAMP_ALIGNED; sysctl_flags |= SYSCTL_FLAG_TSTAMPS; } if(sysctl_tcp_window_scaling) {/* 处理窗口扩大因子选项 */ tcp_header_size += TCPOLEN_WSCALE_ALIGNED; sysctl_flags |= SYSCTL_FLAG_WSCALE; } if(sysctl_tcp_sack) {/* 处理SACK选项 */ sysctl_flags |= SYSCTL_FLAG_SACK; if(!(sysctl_flags & SYSCTL_FLAG_TSTAMPS)) tcp_header_size += TCPOLEN_SACKPERM_ALIGNED; } } else if (tp->rx_opt.eff_sacks) {/* 非SYN段,但是有SACK块 */ /* 根据SACK块数调整TCP首部长度 */ tcp_header_size += (TCPOLEN_SACK_BASE_ALIGNED + (tp->rx_opt.eff_sacks * TCPOLEN_SACK_PERBLOCK)); } if (tcp_is_vegas(tp) && tcp_packets_in_flight(tp) == 0) tcp_vegas_enable(tp); /* 在报文首部中加入TCP首部 */ th = (struct tcphdr *) skb_push(skb, tcp_header_size); /* 更新TCP首部指针 */ skb->h.th = th; /* 设置报文的传输控制块 */ skb_set_owner_w(skb, sk); /* Build TCP header and checksum it. */ /* 填充TCP首部中的数据 */ th->source = inet->sport; th->dest = inet->dport; th->seq = htonl(tcb->seq); th->ack_seq = htonl(tp->rcv_nxt); *(((__u16 *)th) + 6) = htons(((tcp_header_size >> 2) << 12) | tcb->flags); /* 设置TCP首部的接收窗口 */ if (tcb->flags & TCPCB_FLAG_SYN) { th->window = htons(tp->rcv_wnd);/* 对SYN段来说,接收窗口初始值为rcv_wnd */ } else { /* 对其他段来说,调用tcp_select_window计算当前接收窗口的大小 */ th->window = htons(tcp_select_window(sk)); } /* 初始化校验码和带外数据指针 */ th->check = 0; th->urg_ptr = 0; if (tp->urg_mode &&/* 发送时设置了紧急方式 */ between(tp->snd_up, tcb->seq+1, tcb->seq+0xFFFF)) {/* 紧急指针在报文序号开始的65535范围内 */ /* 设置紧急指针和带外数据标志位 */ th->urg_ptr = htons(tp->snd_up-tcb->seq); th->urg = 1; } /* 开始构建TCP首部选项 */ if (tcb->flags & TCPCB_FLAG_SYN) { /* 调用tcp_syn_build_options构建SYN段的首部 */ tcp_syn_build_options((__u32 *)(th + 1), tcp_advertise_mss(sk), (sysctl_flags & SYSCTL_FLAG_TSTAMPS), (sysctl_flags & SYSCTL_FLAG_SACK), (sysctl_flags & SYSCTL_FLAG_WSCALE), tp->rx_opt.rcv_wscale, tcb->when, tp->rx_opt.ts_recent); } else { /* 构建普通段的首部 */ tcp_build_and_update_options((__u32 *)(th + 1), tp, tcb->when); TCP_ECN_send(sk, tp, skb, tcp_header_size); } /* 计算传输层的校验和 */ tp->af_specific->send_check(sk, th, skb->len, skb); /* 如果发送的段有ACK标志,则通知延时确认模块,递减快速发送ACK段的数量,同时停止延时确认定时器 */ if (tcb->flags & TCPCB_FLAG_ACK) tcp_event_ack_sent(sk); if (skb->len != tcp_header_size)/* 发送的段有负载,则检测拥塞窗口闲置是否超时 */ tcp_event_data_sent(tp, skb, sk); TCP_INC_STATS(TCP_MIB_OUTSEGS); /* 调用IP层的发送函数发送报文 */ err = tp->af_specific->queue_xmit(skb, 0); if (err <= 0) return err; /* 如果发送失败,则类似于接收到显式拥塞通知的处理 */ tcp_enter_cwr(tp); return err == NET_XMIT_CN ? 0 : err; } return -ENOBUFS;#undef SYSCTL_FLAG_TSTAMPS#undef SYSCTL_FLAG_WSCALE#undef SYSCTL_FLAG_SACK}
在发送完成后回到inet_stream_connect()函数,有一个等待连接成功的状态,此时用到了函数inet_wait_for_connect(),代码如下:
static long inet_wait_for_connect(struct sock *sk, long timeo){ DEFINE_WAIT(wait); prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE); while ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) { release_sock(sk); timeo = schedule_timeout(timeo); lock_sock(sk); if (signal_pending(current) || !timeo) break; prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE); } finish_wait(sk->sk_sleep, &wait); return timeo;}
本函数主要功能如下:在当前进程的系统空间堆栈上分配一个 wait_queue_t 数据结构,将其挂入目标插口的 sk_sleep 队列,然后就通过 schedule_timeout()进入定时的睡眠,醒来时不能直接从该队列中脱链。在上节系统调用accept()的代码中我们看到,当 server方进程从队列中接收了一个连接请求,从而使队列长度有所下降时,就要唤醒一个(如果有的话)正等待着要将连接请求挂入该队列的进程。inet_wait_for_connect中返回时,要再检查是否因为接收到了信号(signal_pending(current))而被唤醒。如果是就直接返回,提前结束本次系统调用;否则就得timeo为0(也就是定时睡眠醒来)才能返回,此时从该队列中脱链(finish_wait(sk->sk_sleep, &wait))。
为什么在接收到了信号而被唤醒时要提前结束本次系统调用呢?是因为这里等待的是 server 方的报文队列中出现空间而使报文得以投递,可是那并不意味着connect操作的完成,本次系统调用要到连接请求被接受时才会完成。但是那并不是一个有限的、可以预测的短时间内能够完成的,所以只好使系统调用提前结束,先来处理已经到达的信号。
在连接建立成功,设置为已经连接状态,connect函数就功成身退了。
函数调用过程:
一些主要数据结构如下:
- Linux内核源码-sys_connect()
- 看Linux内核源码
- linux 内核源码结构
- linux内核源码组织
- linux内核源码阅读
- Linux内核源码
- linux内核源码结构
- linux内核源码学习
- linux内核源码结构
- Linux内核在线源码
- Linux内核源码分析
- linux内核源码编译
- 编译linux内核源码
- linux内核源码
- Linux内核源码目录
- linux 内核源码树
- Linux内核源码结构
- Linux内核源码目录
- 使用Jquery+bootstrap无限级菜单树
- window10 GPU版tensorflow安装(二)
- 定时器的输出比较模式产生的PWM波的频率计算
- 题目244-16进制的简单运算
- 【Java学习笔记】29:再谈多态性
- Linux内核源码-sys_connect()
- 2017 暑假艾教集训 day8 (补一道思维题,
- 排序算法面试题
- MapReduce 操作 HBase
- HBase HA 高可用集群搭建
- 工厂模式
- 微服务架构二三事:总论
- [BZOJ2815][ZJOI2012]灾难-灭绝树
- Web应用程序状态管理