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函数就功成身退了。

函数调用过程:
这里写图片描述

一些主要数据结构如下:
这里写图片描述

原创粉丝点击