前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >tcp紧急数据处理源码浅析

tcp紧急数据处理源码浅析

作者头像
theanarkh
发布2019-03-06 10:03:39
6650
发布2019-03-06 10:03:39
举报
文章被收录于专栏:原创分享原创分享

tcp紧急数据用于一端有紧急通知需要告之对端的时候,他传输的其实是一种命令或者说信号,而不算是数据,因为他只有一个字节。对端收到紧急数据后会给对应的进程发送一个信号,通知该进程有紧急的命令需要处理(前提是设置了进程或者进程组来处理紧急数据)。下面看一下紧急数据的发送。入口函数是tcp_write。关键代码如下。

  1. 之前缓存了小量数据,还没发送。则先发送出去。
代码语言:javascript
复制
if ((skb = tcp_dequeue_partial(sk)) != NULL) 
        {
                int hdrlen;

                 /* IP header + TCP header */
            // 所有协议头的长度
            hdrlen = ((unsigned long)skb->h.th - (unsigned long)skb->data)
                     + sizeof(struct tcphdr);

            ...

            // 数据部分大于等于mss或者是紧急数据或者还没有发出去一个数据包则直接发送
            if ((skb->len - hdrlen) >= sk->mss ||
                (flags & MSG_OOB) || !sk->packets_out)
                tcp_send_skb(sk, skb);
            else
                // 继续缓存,满足条件后一起发送
                tcp_enqueue_partial(skb, sk);
            continue;
        }

2 然后则构造一个新的skb发送。

代码语言:javascript
复制
// 可发送的序列化最大值 - 下一个可写的序列化值等于可以发送的字节数,如果当前可以发送的数据量太大,这里会导致紧急数据不在当前的tcp报文中,需要等下一个报文才会发送真正的紧急数据,但是该tcp报文还是会设置紧急指针和紧急标记位
        copy = sk->window_seq - sk->write_seq;
        if (copy <= 0 || copy < (sk->max_window >> 1) || copy > sk->mss)
            copy = sk->mss;
        // 能发送的比需要发送的大,则取需要发送的
        if (copy > len)
            copy = len;
        ...
// 构建ip头和mac头,返回ip头+mac头的长度的大小,查找路由项的时候会给dev赋值
        tmp = prot->build_header(skb, sk->saddr, sk->daddr, &dev,
                 IPPROTO_TCP, sk->opt, skb->mem_len,sk->ip_tos,sk->ip_ttl);
        if (tmp < 0 ) 
        {
            prot->wfree(sk, skb->mem_addr, skb->mem_len);
            release_sock(sk);
            if (copied) 
                return(copied);
            return(tmp);
        }
        // 更新data中的数据长度
        skb->len += tmp;
        skb->dev = dev;
        // 指向可写地址,准备写入tcp头
        buff += tmp;
        // skb的tcp头指向data字段的tcp头
        skb->h.th =(struct tcphdr *) buff;
        // 构建tcp头,len-copy表示是否已经传输完len字节的数据,用于设置push标记
        tmp = tcp_build_header((struct tcphdr *)buff, sk, len-copy);
        if (tmp < 0) 
        {
            prot->wfree(sk, skb->mem_addr, skb->mem_len);
            release_sock(sk);
            if (copied) 
                return(copied);
            return(tmp);
        }
        // 带外数据
        if (flags & MSG_OOB) 
        {   // 设置urg标记位,设置紧急指针指向紧急数据的后面一个字节,并且只有这个字节算紧急数据
            ((struct tcphdr *)buff)->urg = 1;
            ((struct tcphdr *)buff)->urg_ptr = ntohs(copy);
        }
        // 更新skb->data中的数据长度
        skb->len += tmp;
        // 复制copy个字节到tcp头后面成为tcp报文的负载
        memcpy_fromfs(buff+tmp, from, copy);
        // 更新需要复制的数据地址
        from += copy;
        // 复制字节数累加
        copied += copy;
        // 还有多少个字节需要复制
        len -= copy;
        // 更新skb->data的数据长度
        skb->len += copy;
        skb->free = 0;
        // 更新下一个tcp报文的序列化
        sk->write_seq += copy;
        // 数据量太少并且不是紧急数据,并且有待确认的包(nagle算法规则),则先缓存
        if (send_tmp != NULL && sk->packets_out) 
        {
            tcp_enqueue_partial(send_tmp, sk);
            continue;
        }
        // 否则直接发送
        tcp_send_skb(sk, skb);

由上代码知道,构造完新的数据包后,直接调用tcp_send_skb函数,下面我们看一下该函数的代码。实现里如果tcp因为某些原因导致不能发送数据包的时候,会先缓存该skb,但是紧急数据是不受拥塞控制影响的,还是应该直接发送。这里待研究。

代码语言:javascript
复制
/*
 *    This is the main buffer sending routine. We queue the buffer
 *    having checked it is sane seeming.
 */
// 发送数据包 
static void tcp_send_skb(struct sock *sk, struct sk_buff *skb)
{
    int size;
    // 指向skb->data字段了的tcp头地址
    struct tcphdr * th = skb->h.th;

    /*
     *  length of packet (not counting length of pre-tcp headers) 
     */
    // tcp头+数据的长度
    size = skb->len - ((unsigned char *) th - skb->data);

    /*
     *  Sanity check it.. 
     */

    if (size < sizeof(struct tcphdr) || size > skb->len) 
    {
        printk("tcp_send_skb: bad skb (skb = %p, data = %p, th = %p, len = %lu)\n",
            skb, skb->data, th, skb->len);
        kfree_skb(skb, FREE_WRITE);
        return;
    }

    /*
     *  If we have queued a header size packet.. (these crash a few
     *  tcp stacks if ack is not set)
     */
    // 相等说明待发送的数据长度0
    if (size == sizeof(struct tcphdr)) 
    {
        /* If it's got a syn or fin it's notionally included in the size..*/
        // 不是syn或fin包则报错,只有这两种包的负载可以为0
        if(!th->syn && !th->fin) 
        {
            printk("tcp_send_skb: attempt to queue a bogon.\n");
            kfree_skb(skb,FREE_WRITE);
            return;
        }
    }

    /*
     *  Actual processing.
     */

    tcp_statistics.TcpOutSegs++; 
    // size - 4*th->doff为数据负载的大小 
    skb->h.seq = ntohl(th->seq) + size - 4*th->doff;

    /*
     *  We must queue if
     *
     *  a) The right edge of this frame exceeds the window
     *  b) We are retransmitting (Nagle's rule)
     *  c) We have too many packets 'in flight'
     */
    // 包的序列号大于可以发送的最大序列号,正在进行超时重传(nagle算法规定只能有一个未收到确认的包,发出的包大于拥塞窗口了  
    if (after(skb->h.seq, sk->window_seq) ||
        (sk->retransmits && sk->ip_xmit_timeout == TIME_WRITE) ||
         sk->packets_out >= sk->cong_window) 
    {
        /* checksum will be supplied by tcp_write_xmit.  So
         * we shouldn't need to set it at all.  I'm being paranoid */
        th->check = 0;
        if (skb->next != NULL) 
        {
            printk("tcp_send_partial: next != NULL\n");
            skb_unlink(skb);
        }
        // 插入待发送队列
        skb_queue_tail(&sk->write_queue, skb);

        /*
         *  If we don't fit we have to start the zero window
         *  probes. This is broken - we really need to do a partial
         *  send _first_ (This is what causes the Cisco and PC/TCP
         *  grief).
         */
        // 可发送的最大序列号小于包的序列号,并且没有等待确认的包,则需要发送窗口探测包看能不能继续发送数据 
        if (before(sk->window_seq, sk->write_queue.next->h.seq) &&
            sk->send_head == NULL && sk->ack_backlog == 0)
            reset_xmit_timer(sk, TIME_PROBE0, sk->rto);
    } 
    else 
    {
        /*
         *  This is going straight out
         */
        // 希望对方传输的数据的序列化,即小于ack_seq的都收到了
        th->ack_seq = ntohl(sk->acked_seq);
        th->window = ntohs(tcp_select_window(sk));

        tcp_send_check(th, sk->saddr, sk->daddr, size, sk);
        // 将要发送的数据包第一个字节的序号 
        sk->sent_seq = sk->write_seq;

        /*
         *  This is mad. The tcp retransmit queue is put together
         *  by the ip layer. This causes half the problems with
         *  unroutable FIN's and other things.
         */
        // 使用ip_queue_xmit发送
        sk->prot->queue_xmit(sk, skb->dev, skb, 0);

        /*
         *  Set for next retransmit based on expected ACK time.
         *  FIXME: We set this every time which means our 
         *  retransmits are really about a window behind.
         */
        // 设置定时器用于超时重传
        reset_xmit_timer(sk, TIME_WRITE, sk->rto);
    }
}

至此,一个带有紧急数据的tcp报文就发送到对端了。下面看一下对端的接收实现代码。入口函数是tcp_rcv,但是真正处理的代码是tcp_urg。下面是该函数代码。

代码语言:javascript
复制
extern __inline__ int tcp_urg(struct sock *sk, struct tcphdr *th,
    unsigned long saddr, unsigned long len)
{
    unsigned long ptr;

    /*
     *  Check if we get a new urgent pointer - normally not 
     */
    // 报文设置了紧急标记位,说明有紧急数据需要处理 
    if (th->urg)
        tcp_check_urg(sk,th);

    /*
     *  Do we wait for any urgent data? - normally not
     */
    // 在tcp_check_urg里设置,说明紧急数据有效,紧急数据可被当做普通数据处理。
    if (sk->urg_data != URG_NOTYET)
        return 0;

    /*
     *  Is the urgent pointer pointing into this packet? 
     */
    // 指向紧急数据相对偏移
    ptr = sk->urg_seq - th->seq + th->doff*4;
    if (ptr >= len)
        return 0;

    /*
     *  Ok, got the correct packet, update info 
     */
    // urg_data是两个字节,一个保存标记,一个保存一个字节的紧急数据
    sk->urg_data = URG_VALID | *(ptr + (unsigned char *) th);
    if (!sk->dead)
        sk->data_ready(sk,0);
    return 0;
}

static void tcp_check_urg(struct sock * sk, struct tcphdr * th)
{    
    // 指向紧急数据最后一个字节的下一个字节
    unsigned long ptr = ntohs(th->urg_ptr);
    // ptr指向有效数据的最后一个字节,
    if (ptr)
        ptr--;
    // 数据的第一个字节的序列号+偏移,ptr指向紧急数据偏移
    ptr += th->seq;

    /* ignore urgent data that we've already seen and read */
    // ptr小于可以已读取的字节的序列号,说明ptr指向的数据被读取过了
    if (after(sk->copied_seq, ptr))
        return;

    /* do we already have a newer (or duplicate) urgent pointer? */
    // 之前已经收到过紧急数据,并且之前收到的紧急数据序列号比现在收到的大
    if (sk->urg_data && !after(ptr, sk->urg_seq))
        return;

    /* tell the world about our new urgent pointer */
    // 通知进程或组收到紧急数据
    if (sk->proc != 0) {
        if (sk->proc > 0) {
            // 给进程发送一个SIGURG信号
            kill_proc(sk->proc, SIGURG, 1);
        } else {
            // 给进程组发送一个SIGURG信号
            kill_pg(-sk->proc, SIGURG, 1);
        }
    }
    // 标记紧急数据有效
    sk->urg_data = URG_NOTYET;
    // 设置紧急数据的序列号
    sk->urg_seq = ptr;
}

从上面的代码中看到,tcp处理紧急数据的时候,最后把紧急数据的有效标记和数据存储在sk->urg_data字段里,所以紧急数据的大小只有一个字节,并且会发生覆盖。至此,处理收到的紧急数据已经完成。还有最后一步就是,收到紧急数据的时候会给进程或进程组发送一个信号,那进程在信号的处理函数里会调用tcp_read来读取紧急数据。实际处理逻辑在tcp_read_urg函数,下面我们看实现的代码。

代码语言:javascript
复制
tcp_read:
    if (flags & MSG_OOB)
        return tcp_read_urg(sk, nonblock, to, len, flags);

tcp_read_urg:
    static int tcp_read_urg(struct sock * sk, int nonblock,
         unsigned char *to, int len, unsigned flags)
{
    /*
     *  No URG data to read
     */
    // 没有紧急数据或者紧急数据被读取了又或者紧急数据当作普通数据处理了
    if (sk->urginline || !sk->urg_data || sk->urg_data == URG_READ)
        return -EINVAL; /* Yes this is right ! */

    ...

    sk->inuse = 1;
    // urg_data是两个字节,一个字节保存紧急数据有效标记,一个保存一个字节的紧急数据
    if (sk->urg_data & URG_VALID) 
    {
        char c = sk->urg_data;
        // 如果不是预读,则读完后设置已读,下次读的时候就直接返回错误
        if (!(flags & MSG_PEEK))
            sk->urg_data = URG_READ;
        // 只读一个字节
        put_fs_byte(c, to);
        release_sock(sk);
        return 1;
    }
    release_sock(sk);

    /*
     * Fixed the recv(..., MSG_OOB) behaviour.  BSD docs and
     * the available implementations agree in this case:
     * this call should never block, independent of the
     * blocking state of the socket.
     * Mike <pall@rz.uni-karlsruhe.de>
     */
    return -EAGAIN;
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-02-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档