Linux 传输层实现

来源:互联网 发布:林丹 知乎 编辑:程序博客网 时间:2024/05/16 05:24

====================================进入传输层(TCP)==============================

tcp_v4_rcv(skb)
{
  不是发给本机的报文则丢弃;
  取TCP头,检查头部长度,取出完整TCP头,校验;因为每层头部包含选项,所以长度不固定,需要这么多步骤来取完整头。
  根据TCP头来设置skb->cb[]控制块中的值;
  根据五元组查找struct sock *sk,没找到则goto no_tcp_socket;ehash/bhash
  process:
    如果套接口处于TIME_WAIT,则goto do_time_wait;
    防火墙过滤,失败则goto discard_and_relse;
    skb->dev = NULL;传输层不关心接收报文的dev;
    如果进程没有在访问struct sock *sk,则调用tcp_v4_do_rcv正常接收;否则,将报文添加到后备队列;?????????????
  no_tcp_socket:
    给对方发送RST报文;
  discard_it:
    kfree_skb(skb);
  do_time_wait:?????????
    调用tcp_timewait_state_process(),如果收到连接请求,重新处理报文;如果收到FIN,需要向对方发送ACK;如果收到无效段,需要向对方发送RST;
}


tcp_v4_do_rcv(sk,skb)
{
  如果sk已建立sk->sk_state==TCP_ESTABLISHED,调用tcp_rcv_established()快速路径处理报文,处理失败的话给对端发送RST;
  如果sk处于监听状态sk->sk_state==TCP_LISTEN,调用tcp_v4_hnd_req()处理半连接ACK报文;
    如果半连接已经建立(nsk!=sk),调用tcp_child_process(sk,nsk,skb)初始化子传输控制块,如果失败则向客户端发送rst段,成功则返回;??????????
  如果sk处于其他状态(SYN_RECV/SYN_SENT/FIN_WAIT1/FIN_WAIT2/LAST_ACK/CLOSING),调用tcp_rcv_state_process()处理状态(包括建立连接的函数tcp_v4_conn_request),失败的话向对方发送RST;?????????
  对于所有发送RST的情况,都要随后kfree_skb(skb)。
}


tcp_rcv_established(sk,skb,struct tcphdr *th,len) //establish状态,处理接收到的报文
{
  如果段满足条件(正好是预期的段序号)
  {//快速路径
    如果该段没有payload
    {//没有payload的段??????
 tcp_store_ts_recent()保存时间戳;?????????
     tcp_rcv_rtt_measure_ts(tb,skb)采样RTT;??????????
 tcp_ack(sk,skb,0) 处理ACK???????
 tcp_data_snd_check(sk);/* 检测是否有数据需要发送给对方,同时检测是否有必要增加发送缓冲区大小 */
 return 0;
}
否则
    {//有payload??
 如果该段满足一系列条件,可以直接复制到用户空间:
   调用tcp_copy_to_iovec()将段复制给用户空间;更新时间戳;更新往返时间;更新下一个预期的段序号;
 如果复制给用户失败,或者没有复制给用户:
   检验校验和;更新时间戳;将数据包添加到接收队列(sk->sk_receive_queue)缓存,等用户进程来读取;设置预期段序号;
 调用tcp_event_data_recv(),处理接收到段之后应该触发的事件,如设置发送确认状态,估算MSS,计算RTT,快速确认还是慢速确认?????
 调用tcp_send_ack()快速确认或者调用tcp_send_delayed_ack()慢速确认;
 如果数据已经传给用户空间,则释放skb,否则说明数据已经就绪,唤醒等待队列进程sk->sk_data_ready(sk, 0);通知他们读取数据;??????how?阻塞?非阻塞?
}
  }
slow_path://慢速路径,慢速接收
  解析TCP选项,失败的话,如果不是RST段,则需要向对方发送DACK tcp_send_dupack(),说明收到的段不在接收窗口内;
  如果有RST字段,调用tcp_reset()关闭sk;
  如果报文有效,且有SYN字段,说明对方发送了错误的段,同样调用tcp_reset()关闭sk;
step5:
  处理ack,更新采样RTT;处理带外数据;
  调用tcp_data_queue(sk,skb)慢速处理段中的数据;????????????????
  检查是否有数据需要发送;
  检查是否有ACK需要发送;
返回;
}


tcp_data_queue()
{//慢速处理段中的数据,此函数很长
  如果可以复制到用户空间,则复制到用户空间,更新用户空间缓存,更新接收缓存、接收窗口。如果复制到用户空间成功,则释放报文;
  如果没有复制到用户空间,则缓存到接收队列,如果接收队列缓存不足,则丢弃;
  如果接收成功,更新下一个预期段序号;
  如果没有复制到用户空间,则调用sk->sk_data_ready(sk,0);唤醒等待接收的用户进程;
  如果不是预期段,释放skb并退出;
  如果段序号太大,释放skb并退出;
  如果接收窗口为0,丢弃;
  对于乱序的段,加入tp->out_of_order_queue乱序队列,并处理乱序队列(SACK/DSACK)??????????
}
=========================进入传输层(UDP)===================================
udp_rcv(skb)
{
  检查UDP报头、校验和;
  根据四元组查找所属传输控制块struct sock sk;如果找到,调用udp_queue_rcv_skb(sk,skb)将UDP数据包添加到所属sk的接收队列;
  如果找不到sk,检查IPSEC、校验和,如果有问题,就把skb给释放了,否则向对方发送ICMP表示报文不可达;
}


udp_queue_rcv_skb(sk,skb)
{
  安全性检查;
  通过检查之后调用sock_queue_rcv_skb(sk,skb);将接收到的数据报添加到传输控制块的接收队列;
}


sock_queue_rcv_skb(sk,skb)
{
  检查接收缓存大小释放已经达到接收缓冲区上限;
  过滤器过滤报文;
  skb->dev=NULL;到达传输层,不关注设备
  skb_queue_tail(sk->sk_receive_queue,skb)将报文添加到接收队列尾部;
  如果套接口sk没关闭,则唤醒等待的进程sk->sk_data_ready(sk, skb_len);sk_data_ready = sock_def_readable
}
============================================================================

0 0
原创粉丝点击