tcp/ip协议流程分析

一:tcp分析:应用层

1:tcp协议流程分析:

程序流程:Af_inet.c(/net/ipv4)文件中;用于tcp,ip协议接口的初始化。

1:inet_init:表示会话层初始化
1.1:协议层注册:
1.2:添加所有的基本控制
1.3:设置tcp,urp,icmp等初始化
1.4:ipv4初始化

struct proto tcp_prot = { //用于初始化tcp协议的接口
    .name            = "TCP",
    .owner            = THIS_MODULE,
    .close            = tcp_close,
    .connect        = tcp_v4_connect,
    .disconnect        = tcp_disconnect,
    .accept            = inet_csk_accept,
    .ioctl            = tcp_ioctl,
    .init            = tcp_v4_init_sock,
    .destroy        = tcp_v4_destroy_sock,
    .shutdown        = tcp_shutdown,
    .setsockopt        = tcp_setsockopt,
    .getsockopt        = tcp_getsockopt,
    .recvmsg        = tcp_recvmsg,
    .sendmsg        = tcp_sendmsg,
    .sendpage        = tcp_sendpage,
    .backlog_rcv        = tcp_v4_do_rcv,
    .release_cb        = tcp_release_cb,
    .mtu_reduced        = tcp_v4_mtu_reduced,
    .hash            = inet_hash,
    .unhash            = inet_unhash,
    .get_port        = inet_csk_get_port,
    .enter_memory_pressure    = tcp_enter_memory_pressure,
    .sockets_allocated    = &tcp_sockets_allocated,
    .orphan_count        = &tcp_orphan_count,
    .memory_allocated    = &tcp_memory_allocated,
    .memory_pressure    = &tcp_memory_pressure,
    .sysctl_wmem        = sysctl_tcp_wmem,
    .sysctl_rmem        = sysctl_tcp_rmem,
    .max_header        = MAX_TCP_HEADER,
    .obj_size        = sizeof(struct tcp_sock),
    .slab_flags        = SLAB_DESTROY_BY_RCU,
    .twsk_prot        = &tcp_timewait_sock_ops,
    .rsk_prot        = &tcp_request_sock_ops,
    .h.hashinfo        = &tcp_hashinfo,
    .no_autobind        = true,
#ifdef CONFIG_COMPAT
    .compat_setsockopt    = compat_tcp_setsockopt,
    .compat_getsockopt    = compat_tcp_getsockopt,
#endif
#ifdef CONFIG_MEMCG_KMEM
    .init_cgroup        = tcp_init_cgroup,
    .destroy_cgroup        = tcp_destroy_cgroup,
    .proto_cgroup        = tcp_proto_cgroup,
#endif
};

    if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)用来添加tcp的接收协议控制

static const struct net_protocol tcp_protocol = {
    .early_demux    =    tcp_v4_early_demux,
    .handler    =    tcp_v4_rcv,//,当验证是tcp协议,则调用该接口,用来完成tcp后功能
    .err_handler    =    tcp_v4_err,
    .no_policy    =    1,
    .netns_ok    =    1,
};

tcp协议层接口函数

rc = proto_register(&tcp_prot, 1);

ip包接收接口设置

static struct packet_type ip_packet_type __read_mostly = {
    .type = cpu_to_be16(ETH_P_IP),
    .func = ip_rcv,
};

二:接收:

网卡接收到数据后:stmmac_rx:netif_receive_skb(标准接收接口)->__netif_receive_skb_core

如果采用gro装包模式:napi_gro_receive

2.1:__netif_receive_skb_core:

https://www.cnblogs.com/wanpengcoder/p/7577088.html

net_timestamp_check(!netdev_tstamp_prequeue, skb);记录收包时间

orig_dev = skb->dev;记录网络设备

skb_reset_transport_header(skb);设置传输头,指向ip层,

skb->skb_iif = skb->dev->ifindex;:收包网络设备索引

list_for_each_entry_rcu(ptype, &ptype_all, list) {  //tcpdump抓包接口
        if (!ptype->dev || ptype->dev == skb->dev) {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }

    if (vlan_tx_tag_present(skb))//判断是vlan接口

if (vlan_do_receive(&skb))vlan处理函数,

    type = skb->protocol;得到上册传输协议,然后调用配置好的接口ip_packet_type :(ip包的接口是ip_rcv)

2.2:ip_rcv

https://www.cnblogs.com/wanpengcoder/p/7577398.html

IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);获取net信息

iph = ip_hdr(skb);获取ip头if (!pskb_may_pull(skb, iph->ihl*4)) 测试实际使用的ip头

return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
               ip_rcv_finish);//钩子函数,选项处理,路由查询,并购根据路由决定数据是转发还是发往本机

ip_rcv_finish:函数主要做路由选择

    if (!skb_dst(skb))     //从外界接收到的数据包,不会包含路由信息,ip_route_input函数会根据路由表设置路由信息

    rt = skb_rtable(skb);//skb_rtable函数等同于skb_dst,获取skb->dst

    return dst_input(skb); //函数指针,如果是递交到本地的则为ip_local_deliver,若是转发则为ip_forward.

ip_local_deliver(struct sk_buff *skb)->ip_local_deliver_finish->ret = ipprot->handler(skb);//调用到tcp层接口。

2.3:tcp接收入口函数:tcp_v4_rcv->tcp_v4_do_rcv->tcp_rcv_established->

tcp_v4_rcv:设置tcp块,查找控制块。这只块的不同状态,包括等待状态,响应状态,接收状态,接收tcp包

https://www.cnblogs.com/wanpengcoder/p/11751763.html

{
    const struct iphdr *iph;
    const struct tcphdr *th;
    struct sock *sk;
    int ret;
    struct net *net = dev_net(skb->dev);

    if (skb->pkt_type != PACKET_HOST)
        goto discard_it;

    /* Count it even if it's bad */
    TCP_INC_STATS_BH(net, TCP_MIB_INSEGS);

//检查头部数据,若不满足,拷贝分片
    if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
        goto discard_it;
    //取tcp头
    th = tcp_hdr(skb);
//    printk(KERN_EMERG "th->doff: %d\n",th->doff); //7

    if (th->doff < sizeof(struct tcphdr) / 4)
        goto bad_packet;
        
    //检查tcp首部和tcp偏移是否越界
    if (!pskb_may_pull(skb, th->doff * 4))
        goto discard_it;
//    printk(KERN_EMERG "Here I am66: tcp_v4_rcv\n");
//     printk(KERN_EMERG "tcp_v4_rcv:skb->ip_summed: %d\n",skb->ip_summed);
    if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb))
    goto csum_error;
        
//    printk(KERN_EMERG "Here I am77: tcp_v4_rcv\n");

    th = tcp_hdr(skb);
    iph = ip_hdr(skb);
    //获取开始序列号,结束序列号(实际是期待tcp确认),确认序列号
    //获取ip头服务字段
    TCP_SKB_CB(skb)->seq = ntohl(th->seq);
    TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
                    skb->len - th->doff * 4);
    TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
    TCP_SKB_CB(skb)->when     = 0;
    TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph);
    TCP_SKB_CB(skb)->sacked     = 0;
//    printk(KERN_EMERG "Here I am2: tcp_v4_rcv\n");
    //查找对应的sock块,根据思源数组查找对应的sock结构
    //首先用__inet_lookup_established查找函数已经处于establish状态的链接
    //匹配处于listen状态的sock,这个时候实际是被动的接收来自其他主句的链接请求
    sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
    if (!sk)
        goto no_tcp_socket;
process:
    //检查sock是否处于半等待状态
    if (sk->sk_state == TCP_TIME_WAIT)
        goto do_time_wait;

    if (unlikely(iph->ttl < inet_sk(sk)->min_ttl)) {
        NET_INC_STATS_BH(net, LINUX_MIB_TCPMINTTLDROP);
        goto discard_and_relse;
    }
//检查ipsec规则
    if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
        goto discard_and_relse;
    nf_reset(skb);
//检查bpf规则
    if (sk_filter(sk, skb))
        goto discard_and_relse;
//    printk(KERN_EMERG "Here I am3: tcp_v4_rcv\n");
    skb->dev = NULL;
    //调用和socket是否互斥
    bh_lock_sock_nested(sk);
    ret = 0;
    //检查用户态是否对socket锁定,如果为真,socket不能修改
    if (!sock_owned_by_user(sk)) {
#ifdef CONFIG_NET_DMA
        struct tcp_sock *tp = tcp_sk(sk);
        if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)
            tp->ucopy.dma_chan = net_dma_find_channel();
        if (tp->ucopy.dma_chan)
            ret = tcp_v4_do_rcv(sk, skb);
        else
#endif
        {
            if (!tcp_prequeue(sk, skb))
                //进入预处理队列
                ret = tcp_v4_do_rcv(sk, skb);
        }
    } else if (unlikely(sk_add_backlog(sk, skb,
                       sk->sk_rcvbuf + sk->sk_sndbuf))) {
        //如果数据包被用户京城锁定,则数据包进入后备处理队列,并且该进程
        //进入套接字后备处理等待队列
        bh_unlock_sock(sk);
        NET_INC_STATS_BH(net, LINUX_MIB_TCPBACKLOGDROP);
        goto discard_and_relse;
    }
    bh_unlock_sock(sk);
//    printk(KERN_EMERG " tcp_v4_rcv_over\n");

    sock_put(sk);

    return ret;}

tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb):

if (sk->sk_state == TCP_ESTABLISHED)与sock已经建立了链接->tcp_rcv_established

    if (sk->sk_state == TCP_LISTEN)处于监听状态,则调用tcp_v4_hnd_req,函数生成一个新的sock用于传输

if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len))//处理sk的状态转换

tcp_rcv_established:表示已经在网络接收的状态下,,首先根据首部预测是快速路径还是慢速路径,在快速路径中,对有数据的负荷进行不同的处理,若无数据,处理输入ack,释放该skb,检查是否有数据发送,有则发送,若有数据局,检查当前处理上下文,并且是期望速去的数据,则将数据加入到接收队列中,加入的方式合并到已有的数据段,或者加入到数据段的尾部,并唤醒用户进程有数据刻度,在慢路中,进行详细的校验,处理ack,处理紧急数据,处理接收数据段,其中数据段可能包含乱序的情况,进行有效的数据和ack的发送检查,

{
//    unsigned int a1,a2, b1,b2, c1 ,c2,d1,d2;
    struct tcp_sock *tp = tcp_sk(sk);
//    printk(KERN_EMERG "tcp_rcv_established1\n");

    if (unlikely(sk->sk_rx_dst == NULL))
        inet_csk(sk)->icsk_af_ops->sk_rx_dst_set(sk, skb);

    tp->rx_opt.saw_tstamp = 0;
     //快速检查,序号正确,ack正确
    if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
        TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&
        !after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) {
        //tcp头部长度
        int tcp_header_len = tp->tcp_header_len;
    //    printk(KERN_EMERG "len =%d,tcp_header_len=%d,tp->tcp_header_len =%d\n",len,tcp_header_len,tp->tcp_header_len);    

        /* Check timestamp
        时间戳选项
        */
        if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
            if (!tcp_parse_aligned_timestamp(tp, th))
                goto slow_path;
            if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0)
                goto slow_path;
        }
            if (len <= tcp_header_len) {
            /* Bulk data transfer: sender */
            if (len == tcp_header_len) {
                 //有时间戳选项,所有接收端都确认完毕,保存时间戳
                if (tcp_header_len ==
                    (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
                    tp->rcv_nxt == tp->rcv_wup)
                    tcp_store_ts_recent(tp);
  
                 //输入ack处理
                tcp_ack(sk, skb, 0);
                __kfree_skb(skb);
                tcp_data_snd_check(sk);
                return 0;
            } else { /* Header too small */
                TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
                goto discard;
            }
        } else {
            int eaten = 0;
            int copied_early = 0;
            bool fragstolen = false;
            //期待读取的期待接收的序列号一致,数据<=带读取数据长度
            if (tp->copied_seq == tp->rcv_nxt &&
                len - tcp_header_len <= tp->ucopy.len) {
#ifdef CONFIG_NET_DMA
                if (tp->ucopy.task == current &&
                    sock_owned_by_user(sk) &&
                    tcp_dma_try_early_copy(sk, skb, tcp_header_len)) {
                    copied_early = 1;
                    eaten = 1;
                }
#endif
            //读取当前进程上下文,控制块被用户锁定
                if (tp->ucopy.task == current &&
                    sock_owned_by_user(sk) && !copied_early) {
                    __set_current_state(TASK_RUNNING);
                    //拷贝数据到msghdr
                    if (!tcp_copy_to_iovec(sk, skb, tcp_header_len))
                        eaten = 1;
                }
                if (eaten) {
                    if (tcp_header_len ==
                        (sizeof(struct tcphdr) +
                         TCPOLEN_TSTAMP_ALIGNED) &&
                        tp->rcv_nxt == tp->rcv_wup)
                        tcp_store_ts_recent(tp);

                    tcp_rcv_rtt_measure_ts(sk, skb);

                    __skb_pull(skb, tcp_header_len);
                    tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
                    NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITSTOUSER);
                }
                if (copied_early)
                    tcp_cleanup_rbuf(sk, skb->len);
            }
            //未拷贝数据,或者拷贝数据是失败
            if (!eaten) {
        //        printk(KERN_EMERG "tcp_rcv_established:skb->ip_summed  =%d\n",skb->ip_summed );
                if (tcp_checksum_complete_user(sk, skb))
                    goto csum_error;
                    //skb长度>预分配长度
                if ((int)skb->truesize > sk->sk_forward_alloc)
                    goto step5;
                 //有时间戳选项,数据均以确认完毕,更新时间戳
                if (tcp_header_len ==
                    (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
                    tp->rcv_nxt == tp->rcv_wup)
                    tcp_store_ts_recent(tp);

                tcp_rcv_rtt_measure_ts(sk, skb);

                NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITS);

                /* Bulk data transfer: receiver */
                //数据加入接收队列
                eaten = tcp_queue_rcv(sk, skb, tcp_header_len,
                              &fragstolen);
            }

            tcp_event_data_recv(sk, skb);
            //确认序列号确认数据
            if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
                /* Well, only one small jumplet in fast path... */
                tcp_ack(sk, skb, FLAG_DATA);
                //检查是否有数据要发送,需要则发送
                tcp_data_snd_check(sk);
                if (!inet_csk_ack_scheduled(sk))
                    goto no_ack;
            }

            if (!copied_early || tp->rcv_nxt != tp->rcv_wup)
                __tcp_ack_snd_check(sk, 0);
no_ack:
//        printk(KERN_EMERG "tcp_rcv_established8\n");
#ifdef CONFIG_NET_DMA
            if (copied_early)
                __skb_queue_tail(&sk->sk_async_wait_queue, skb);
            else
#endif
            if (eaten)
                kfree_skb_partial(skb, fragstolen);
            sk->sk_data_ready(sk, 0);
            return 0;
        }
    }

}

if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags && TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&
        !after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) //快速检查,序号检查,ack检查

拷贝tcp数据到用户空间:

tcp_copy_to_iovec->skb_copy_and_csum_datagram_iovec->skb_copy_datagram_iovec->memcpy_toiovec->copy_to_user

至此tcp协议传输到用户空间。

三:tcp发包:socket建立连接以后,用户态使用send发送tcp包,send调用tcp_sendmsg来实现,这个函数在中的参数:    .sendmsg        = tcp_sendmsg,tcp_sendmsg(net/ipv4/tcp.c)

https://blog.csdn.net/xiaoyu_750516366/article/details/85228023

tcp_sendmsg->tcp_push->__tcp_push_pending_frames->tcp_write_xmit->tcp_transmit_skb->err = icsk->icsk_af_ops->queue_xmit(skb, &inet->cork.fl);(调用队列发送函数)

主要功能:发送队列尾部skb尚未发送的而且还有的剩余空间,将用户缓存中的数据copy进去,如果没有空间,则申请一个空间,再copy数据,如果一个空间不够,则在申请固定的skb,知道所有的数据copy完,或者skb的缓存空间无法申请,或者缓存达到限制,将申请的skb放到队列的尾部。在调用tcp_push,__tcp_push_pending_frames或者tcp_push_one函数队列。

iovlen = msg->msg_iovlen;    //应用层的数据保存在msg中,以数组方式组织,msg_iovlen为数组大小,
    iov = msg->msg_iov; //msg_iov为数组的第一个元素

copied = 0;//本次能够写入的tcp字节数,

size_t seglen = iov->iov_len; //每个元素包含的数据可以不同,每个元素记录在自己的iov_len字段中
        unsigned char __user *from = iov->iov_base;//from拷贝的字节起点

    if (!sk_stream_memory_free(sk))检车内存分配是否超限,如果会则先等待可用的内存。

数据拷贝分为内外两层循环,外层循环遍历数组,内存将一个数组拷贝完成,寻找skb,如果当前发送队列最后一个skb可以继续听填充,那么先往skb中填充数据,如果没有现成的skb可用,那么就分配一个,找到skb后,决定往哪个skb中拷贝数据,线性优先缓冲,拷贝过程中,调用不同接口,进行数据发送

3.1:tcp_push
 tcp_send_head :是否有数据未被发送,                            tcp_mark_urg:设置要发送的OOB数据,记录数据的下一个字节的序列号  __tcp_push_pending_frames:发送skb数据

3.2:tcp_write_xmit :根据拥塞窗口,发送窗口,nagle算法判断发送数据以及发送多少,即新放入的数据不会立即发送,,并且发包时只发送skb的副本,原来的skb会一直呆在发送队列中,如果数据丢失,则tcp会将数据重新发送一次,直到数据被确认

https://blog.csdn.net/Al_xin/article/details/52304732

tcp_mtu_probe:获取mtu数据,tcp_send_head获取最老的skb头,tcp_init_tso_segs:获取网卡将skb分割成段的个数  ,cwnd_quota = tcp_cwnd_test(tp, skb);计算拥有阻塞窗口容许的字节数,      if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))检测发送窗口是否容许发送数据

if (tso_segs == 1) { //网卡按一个包发送数据
            if (unlikely(!tcp_nagle_test(tp, skb, mss_now,  (tcp_skb_is_last(sk, skb) ?
                              nonagle : TCP_NAGLE_PUSH)))) // nagle算法是否发送当前包
                break;
        } else {
            if (!push_one && tcp_tso_should_defer(sk, skb))  //将skb分割成多个包发送
                break;
        }

limit = tcp_mss_split_point(sk, skb, mss_now, min_t(unsigned int,cwnd_quota,
                              sk->sk_gso_max_segs))//计算网卡一次性发送的字节数

if (skb->len > limit &&unlikely(tso_fragment(sk, skb, limit, mss_now, gfp))) 包过大,拆成两个包,最后减为与limit一致
  if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp))):发送数据的(重要。。。。。)

sent_pkts += tcp_skb_pcount(skb);计算已经发送的包的个数。

3.3:tcp_transmit_skb:发送skb队列

if (likely(clone_it)):clone一个副本发送出去,等待队列中的ack确认后在删除,

tcp_options_size = tcp_established_options(sk, skb, &opts, &md5);构建确认后的配置信息,

tcp_header_size = tcp_options_size + sizeof(struct tcphdr);:计算tcp头长度,

skb_push(skb, tcp_header_size);skb->data指向tcp的数据头,

    th->source        = inet->inet_sport; //设置源端口
    th->dest        = inet->inet_dport;  //设置目的端口
    th->seq            = htonl(tcb->seq); //设置序列号
    th->ack_seq        = htonl(tp->rcv_nxt);//设置确认号
   if (unlikely(tcp_urg_mode(tp) && before(tcb->seq, tp->snd_up))):有紧急包并且目前的序列号小于紧急数据的下一个字节的序列号。

icsk->icsk_af_ops->send_check(sk, skb);:调用tcp_v4_check检查tcp校验和

err = icsk->icsk_af_ops->queue_xmit(skb, &inet->cork.fl);调用ip_queue_xmit构建IP头并将数据发送出去。(net/ipv4/tcp_output.c文件中)(调用函数在net/dccp/ipv4.c中)

传输层和网络层联系的函数是tcp_transmit_skb(...),net/dccp/tcp_ipv4.c文件中,用于传输层和网络层的数据接口的传输

const struct inet_connection_sock_af_ops ipv4_specific = {
    .queue_xmit       = ip_queue_xmit,
    .send_check       = tcp_v4_send_check,
    .rebuild_header       = inet_sk_rebuild_header,
    .sk_rx_dst_set       = inet_sk_rx_dst_set,
    .conn_request       = tcp_v4_conn_request,
    .syn_recv_sock       = tcp_v4_syn_recv_sock,
    .net_header_len       = sizeof(struct iphdr),
    .setsockopt       = ip_setsockopt,
    .getsockopt       = ip_getsockopt,
    .addr2sockaddr       = inet_csk_addr2sockaddr,
    .sockaddr_len       = sizeof(struct sockaddr_in),
    .bind_conflict       = inet_csk_bind_conflict,
#ifdef CONFIG_COMPAT
    .compat_setsockopt = compat_ip_setsockopt,
    .compat_getsockopt = compat_ip_getsockopt,
#endif
};

四:ip层发包(net/ipv4/ip_output.c文件中)

4.1:int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)完成面向套接字的输出,当套接字处于连接状态,所有由套接字发出的包都具有确定的路由,无需为每个输出包查询它的目的入口,可将套接字绑定到路由入口上,这由套接字的目的缓冲指针来完成,,数显为输入包建立报头,经过本地过滤器,再将ip包分片输出,

rt = skb_rtable(skb);检查skb是否已经制定了路由功能,rt = (struct rtable *)__sk_dst_check(sk, 0);检查socket的合法性,

if (inet_opt && inet_opt->opt.is_strictroute && rt->rt_uses_gateway)增加路由缓存引用计数,查找的路由目标地址不等于网关地址前往no_route

    skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0));
    skb_reset_network_header(skb);
    iph = ip_hdr(skb);     在skb的数据中预留出ip首部包选项的空间给ip报头。

    *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));填入ip版本号,ip首部长度,后面根据选项增加长度,

   if (ip_dont_fragment(sk, &rt->dst) && !skb->local_df):如果使用路径mtu发现要求部分偏,容许分片参数等于0.

        ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0);opt从sock中获得,

ip_select_ident_more(iph, &rt->dst, sk,
                 (skb_shinfo(skb)->gso_segs ?: 1) - 1); 填入ip首部的id字段

res = ip_local_out(skb);:ip包发送 return skb_dst(skb)->output(skb);

最终实际调用函数对于单包:ip_output(net/ipv4/route.c __mkroute_input函数中设置,用于设置路由输入输出函数接口)

ip_output->ip_finish_output->ip_finish_output2->dst_neigh_output(调用邻居子系统(与网卡驱动挂钩)输出)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值