Linux TCP/IP 协议栈之 Socket的实现分析(Connect客户端发起连接请求)

简介: sys_connect对于客户端来说,当创建了一个套接字后,就可以连接它了。                case SYS_CONNECT:                         err = sys_connect(a0, (struct sockaddr __user ...
sys_connect

对于客户端来说,当创建了一个套接字后,就可以连接它了。
               case SYS_CONNECT:
                        err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
                        break;
 
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);
        if (!sock)
                goto out;
        err = move_addr_to_kernel(uservaddr, addrlen, address);
        if (err                 goto out_put;
 
        err = security_socket_connect(sock, (struct sockaddr *)address, addrlen);
        if (err)                 goto out_put;
 
        err = sock->ops->connect(sock, (struct sockaddr *) address, addrlen,
                                 sock->file->f_flags);
out_put:
        sockfd_put(sock);
out:
        return err;
}[/code]
 
跟其它操作类似,sys_connect 接着调用 inet_connect:
 
/*
*        Connect to a remote host. There is regrettably still a little
*        TCP 'magic' in here.
*/
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;

        socket 处于不正确的连接状态,返回相应的错误值。
 
        case SS_UNCONNECTED:
                err = -EISCONN;
                if (sk->sk_state != TCP_CLOSE)
                        goto out;

                /*调用协议的连接函数*/
                err = sk->sk_prot->connect(sk, uaddr, addr_len);
                if (err                         goto out;

                /*协议方面的工作已经处理完成了,但是自己的一切工作还没有完成,所以切换至正在连接中*/
                 sock->state = SS_CONNECTING;
 
                /* Just entered SS_CONNECTING state; the only
                 * difference is that return value in non-blocking
                 * case is EINPROGRESS, rather than EALREADY.
                 */
                err = -EINPROGRESS;
                break;
        }
 
对于 TCP的实际的连接,是通过调用 tcp_v4_connect()函数来实现的。
 
tcp_v4_connect函数

对于 TCP 协议来说,其连接实际上就是发送一个 SYN 报文,在服务器的应答到来时,回答它一
个 ack 报文,也就是完成三次握手中的第一和第三次。
 
要发送 SYN 报文,也就是说,需要有完整的来源/目的地址,来源/目的端口,目的地址/端口由用户
态提交,但是问题是没有自己的地址和端口,因为并没有调  用过 bind(2),一台主机,对于端口,
可以像 sys_bind()那样,从本地未用端口中动态分配一个,那地址呢?因为一台主机可能会存在多
个 IP地  址,如果随机动态选择,那么有可能选择一个错误的来源地址,将不能正确地到达目的地
址。换句话说,来源地址的选择,是与路由相关的。
 
调用路由查找的核心函数 ip_route_output_slow(),在没有提供来源地址的情况下,会根据实际情况,
调用 inet_select_addr()函数来选择一个合适的。同时,如果路由查找命中,会生成一个相应的路由
缓存项,这个缓存项,不但对当前发送SYN报文有意义,对于后续的所有数据包,都可以起到一
个加速路由查找的作用。这一任务,是通过 ip_route_connect()函数完成的,它返回相应的路由缓存
项(也就是说,来源地址也在其中了):
 
static inline int ip_route_connect(struct rtable **rp, u32 dst,
                                   u32 src, u32 tos, int oif, u8 protocol,
                                   u16 sport, u16 dport, struct sock *sk)
{         struct flowi fl = { .oif = oif,
                            .nl_u = { .ip4_u = { .daddr = dst,
                                                 .saddr = src,
                                                 .tos   = tos } },
                            .proto = protocol,
                            .uli_u = { .ports =
                                       { .sport = sport,
                                         .dport = dport } } };
 
        int err;
        if (!dst || !src) {
                err = __ip_route_output_key(rp, &fl);
                if (err)
                        return err;
                fl.fl4_dst = (*rp)->rt_dst;
                fl.fl4_src = (*rp)->rt_src;
                ip_rt_put(*rp);
                *rp = NULL;
        }
        return ip_route_output_flow(rp, &fl, sk, 0);
}
 
首先,构建一个搜索 key fl,在搜索要素中,来源地址/端口是不存在的。所以,当通过__ip_route_output_key
进行查找时,第一次是不会命中缓存的。 __ip_route_output_key 将继续调用ip_route_output_slow()函数,
在路由表中搜索,并返回一个合适的来源地址,  并且生成一个路由缓存项。 路由查找的更多细节,我会在另一个贴子
中来分析。
 
/* This will initiate an outgoing connection. */
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                 return -EINVAL;
 
        if (usin->sin_family != AF_INET)
                return -EAFNOSUPPORT;[/code] 校验地址长度和协议簇。
 
        nexthop = daddr = usin->sin_addr.s_addr;[/code]
        将下一跳地址和目的地址的临时变量都暂时设为用户提交的地址。
 
        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                 return tmp;
 
        if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
                ip_rt_put(rt);
                return -ENETUNREACH;
        }[/code]
        进行路由查找,并校验返回的路由的类型,TCP是不被允许使用多播和广播的。
 
        if (!inet->opt || !inet->opt->srr)
                daddr = rt->rt_dst;[/code]
        更新目的地址临时变量——使用路由查找后返回的值。
 
        if (!inet->saddr)
                inet->saddr = rt->rt_src;
        inet->rcv_saddr = inet->saddr;[/code]
        如果还没有设置源地址,和本地发送地址,则使用路由中返回的值。
 
        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 &&
            !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.
                 */
 
                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;
        保存目的地址及端口。
 
        tp->ext_header_len = 0;
        if (inet->opt)
                tp->ext_header_len = inet->opt->optlen;
 
        tp->rx_opt.mss_clamp = 536;
        设置最小允许的mss值

        tcp_set_state(sk, TCP_SYN_SENT);
        套接字状态被置为 TCP_SYN_SENT,
 
        err = tcp_v4_hash_connect(sk);
        if (err)
                goto failure;
        动态选择一个本地端口,并加入 hash 表,与bind(2)选择端口类似。
 
        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);
        因为本地端口已经改变,使用新端口,重新查找路由,并用新的路由缓存项更新 sk 中保存的路由缓存项。
 
        if (!tp->write_seq)
                tp->write_seq = secure_tcp_sequence_number(inet->saddr,                                                            inet->daddr,
                                                           inet->sport,
                                                           usin->sin_port);[/code]
        为 TCP报文计算一个 seq值(实际使用的值是 tp->write_seq+1)。
 
        inet->id = tp->write_seq ^ jiffies;
 
        err = tcp_connect(sk);
        rt = NULL;
        if (err)
                goto failure;
 
        return 0;
        tp_connect()函数用来根据 sk 中的信息,构建一个完成的 syn 报文,并将它发送出去。
        在分析 tcp栈的实现时再来分析它。
 
根据 TCP协议,接下来的问题是,
1. 可能收到了服务器的应答,则要回送一个 ack 报文;
2. 如果超时还没有应答,则使用超时重发定时器;

目录
相关文章
|
5月前
|
安全 Linux iOS开发
Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
544 53
Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
|
5月前
|
Linux API iOS开发
Binary Ninja 4.2.6455 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
Binary Ninja 4.2.6455 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
383 14
Binary Ninja 4.2.6455 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
|
6月前
|
数据管理 Linux iOS开发
Splunk Enterprise 9.4.5 (macOS, Linux, Windows) - 机器数据管理和分析
Splunk Enterprise 9.4.5 (macOS, Linux, Windows) - 机器数据管理和分析
193 0
|
存储 Unix Linux
浅入分析Linux
Linux 操作系统必须完成的两个主要目的 与硬件部分交互, 为包含在硬件平台上的所有底层可编程部件提供服务 为运行在计算机系统上的应用程序(即所谓的用户空间)提供执行环境 一些操作系统运行所有的用户程序都直接与硬件部分进行交互, 比如典型的MS-DOS。
1124 0
|
6月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
724 1
二、Linux文本处理与文件操作核心命令
|
6月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
431 137
|
6月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
1100 58
|
9月前
|
JSON 自然语言处理 Linux
linux命令—tree
tree是一款强大的Linux命令行工具,用于以树状结构递归展示目录和文件,直观呈现层级关系。支持多种功能,如过滤、排序、权限显示及格式化输出等。安装方法因系统而异常用场景包括:基础用法(显示当前或指定目录结构)、核心参数应用(如层级控制-L、隐藏文件显示-a、完整路径输出-f)以及进阶操作(如磁盘空间分析--du、结合grep过滤内容、生成JSON格式列表-J等)。此外,还可生成网站目录结构图并导出为HTML文件。注意事项:使用Tab键补全路径避免错误;超大目录建议限制遍历层数;脚本中推荐禁用统计信息以优化性能。更多详情可查阅手册mantree。
798 143
linux命令—tree
|
5月前
|
存储 安全 Linux
Linux卡在emergency mode怎么办?xfs_repair 命令轻松解决
Linux虚拟机遇紧急模式?别慌!多因磁盘挂载失败。本文教你通过日志定位问题,用`xfs_repair`等工具修复文件系统,三步快速恢复。掌握查日志、修磁盘、验重启,轻松应对紧急模式,保障系统稳定运行。
1014 2
|
6月前
|
缓存 监控 Linux
Linux内存问题排查命令详解
Linux服务器卡顿?可能是内存问题。掌握free、vmstat、sar三大命令,快速排查内存使用情况。free查看实时内存,vmstat诊断系统整体性能瓶颈,sar实现长期监控,三者结合,高效定位并解决内存问题。
546 0
Linux内存问题排查命令详解