Linux内核网络源码分析——相关初始化

目录:Linux内核网络源码分析

inet_init() <net/ipv4/af_inet.c>

//inet_create函数在inet_init函数中注册:
//ip_rcv函数在此注册。

static struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner = THIS_MODULE,
};

static struct packet_type ip_packet_type = {
.type = __constant_htons(ETH_P_IP),
.func = ip_rcv,
.gso_send_check = inet_gso_send_check,
.gso_segment = inet_gso_segment,
};

(void)sock_register(&inet_family_ops);//注册INET处理

dev_add_pack(&ip_packet_type);//添加IP包处理

 sock_create()
//其中sk_prot在sock_create中定义:
//首先根据protocol从inetsw列表中找到对应的inet_protosw(answer),然后将answer->prot作为参数
//调用sk_alloc为sock分配内存及初始化,sk->sk_prot就是在sk_alloc中定义为sk->sk_prot = answer->prot
//参见:
inet_create(…) <net/ipv4/af_inet.c:>
static struct inet_protosw inetsw_array[] <net/ipv4/af_inet.c:>

.prot = &tcp_prot,

.prot = &udp_prot,

.prot = &raw_prot,

struct proto tcp_prot={…} <net/ipv4/tcp_ipv4.c>

.recvmsg = tcp_recvmsg,
.backlog_rcv = tcp_v4_do_rcv,

struct proto udp_prot={…} <net/ipv4/udp.c>

.sendmsg = udp_sendmsg,
.recvmsg = udp_recvmsg,
 

NAPI

NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以 POLL 的方法来轮询数据,类似于底半方式(bottom-half 的处理模式)……
(摘自NAPI 技术在 Linux 网络驱动上的应用和完善

New API (also referred to as NAPI) is an interface to use interrupt mitigation techniques for networking devices in the Linux kernel. Such an approach is intended to reduce the overhead of packet receiving. The idea is to defer incoming message handling until there is a sufficient amount of them so that it is worth handling them all at once.
(摘自 http://en.wikipedia.org/wiki/NAPI

 

Linux的内核软中断(softirq)执行分析

  本文对 Linux 内核软中断的执行流程进行了分析,并尽可能的结合当前运行环境详细地写出我的理解,但这并不表明我的理解一定正确。这本是论坛里的一篇帖子,发出来是为了抛砖引玉,如果您在阅读本文时发现了我的错误,还望得到您的指正。

今天无意中看了眼 2.6 内核的软中断实现,发现和以前我看到的大不相同(以前也是走马观花,不大仔细),可以说改动很大。连 softirq 的调用点都不一样了,以前是三个调用点,今天搜索了一下源代码,发现在多出了ksoftirqd 后,softirq 在系统中的调用点仅是在 ISR 返回时和使用了local_bh_enable() 函数后被调用了。网卡部分的显示调用,我觉得应该不算是系统中的调用点。ksoftirqd 返回去调用 do_softirq() 函数应该也只能算是其中的一个分支,因为其本身从源头上来讲也还是在 ISR 返回时 irq_exit() 调用的。这样一来就和前些日子写的那份笔记(Windows/Linux

  /Solaris 软中断机制)里介绍的 Linux 内核部分的软中断有出处了,看来以后讨论 Linux kernel 代码一定要以内核版本为前题,要不非乱了不可。得买本 Linux 方面的书了,每次上来直接看相关代码也不是回事,时间也不允许。

 

//
// do_IRQ 函数执行完硬件 ISR 后退出时调用此函数。
//

void irq_exit(void)
{
        account_system_vtime(current);
        trace_hardirq_exit();
        sub_preempt_count(IRQ_EXIT_OFFSET);

        //
        // 判断当前是否有硬件中断嵌套,并且是否有软中断在
        // pending 状态,注意:这里只有两个条件同时满足
        // 时,才有可能调用 do_softirq() 进入软中断。也就是
        // 说确认当前所有硬件中断处理完成,且有硬件中断安装了
        // 软中断处理时理时才会进入。
        //
        if (!in_interrupt() && local_softirq_pending())

                //
                // 其实这里就是调用 do_softirq() 执行
                //
                invoke_softirq();
        preempt_enable_no_resched();
}

#ifndef __ARCH_HAS_DO_SOFTIRQ

asmlinkage void do_softirq(void)
{
        __u32 pending;
        unsigned long flags;

        //
        // 这个函数判断,如果当前有硬件中断嵌套,或者
        // 有软中断正在执行时候,则马上返回。在这个
        // 入口判断主要是为了与 ksoftirqd 互斥。
        //
        if (in_interrupt())
                return;

        //
        // 关中断执行以下代码
        //
        local_irq_save(flags);

        //
        // 判断是否有 pending 的软中断需要处理。
        //
        pending = local_softirq_pending();

        //
        // 如果有则调用 __do_softirq() 进行实际处理
        //
        if (pending)
                __do_softirq();

        //
        // 开中断继续执行
        //
        local_irq_restore(flags);
}

//
// 最大软中断调用次数为 10 次。
//

#define MAX_SOFTIRQ_RESTART 10

asmlinkage void __do_softirq(void)
{
        //
        // 软件中断处理结构,此结构中包括了 ISR 中
        // 注册的回调函数。
        //
        struct softirq_action *h;
        __u32 pending;
        int max_restart = MAX_SOFTIRQ_RESTART;
        int cpu;

        //
        // 得到当前所有 pending 的软中断。
        //
        pending = local_softirq_pending();
        account_system_vtime(current);

        //
        // 执行到这里要屏蔽其他软中断,这里也就证明了
        // 每个 CPU 上同时运行的软中断只能有一个。
        //
        __local_bh_disable((unsigned long)__builtin_return_address(0));
        trace_softirq_enter();

        //
        // 针对 SMP 得到当前正在处理的 CPU
        //
        cpu = smp_processor_id();
//
// 循环标志
//
restart:
        //
        // 每次循环在允许硬件 ISR 强占前,首先重置软中断
        // 的标志位。
        //
        /* Reset the pending bitmask before enabling irqs */
        set_softirq_pending(0);

        //
        // 到这里才开中断运行,注意:以前运行状态一直是关中断
        // 运行,这时当前处理软中断才可能被硬件中断抢占。也就
        // 是说在进入软中断时不是一开始就会被硬件中断抢占。只有
        // 在这里以后的代码才可能被硬件中断抢占。
        //
        local_irq_enable();

        //
        // 这里要注意,以下代码运行时可以被硬件中断抢占,但
        // 这个硬件 ISR 执行完成后,它的所注册的软中断无法马上运行,
        // 别忘了,现在虽是开硬件中断执行,但前面的 __local_bh_disable()
        // 函数屏蔽了软中断。所以这种环境下只能被硬件中断抢占,但这
        // 个硬中断注册的软中断回调函数无法运行。要问为什么,那是因为
        // __local_bh_disable() 函数设置了一个标志当作互斥量,而这个
        // 标志正是上面的 irq_exit() 和 do_softirq() 函数中的
        // in_interrupt() 函数判断的条件之一,也就是说 in_interrupt()
        // 函数不仅检测硬中断而且还判断了软中断。所以在这个环境下触发
        // 硬中断时注册的软中断,根本无法重新进入到这个函数中来,只能
        // 是做一个标志,等待下面的重复循环(最大 MAX_SOFTIRQ_RESTART)
        // 才可能处理到这个时候触发的硬件中断所注册的软中断。
        //

        //
        // 得到软中断向量表。
        //
        h = softirq_vec;

        //
        // 循环处理所有 softirq 软中断注册函数。
        //
        do {
                //
                // 如果对应的软中断设置 pending 标志则表明
                // 需要进一步处理它所注册的函数。
                //
                if (pending & 1) {
                        //
                        // 在这里执行了这个软中断所注册的回调函数。
                        //
                        h->action(h);
                        rcu_bh_qsctr_inc(cpu);
                }
        //
        // 继续找,直到把软中断向量表中所有 pending 的软
        // 中断处理完成。
        //
                h++;

                //
                // 从代码里可以看出按位操作,表明一次循环只
                // 处理 32 个软中断的回调函数。
                //
                pending >>= 1;
        } while (pending);

        //
        // 关中断执行以下代码。注意:这里又关中断了,下面的
        // 代码执行过程中硬件中断无法抢占。
        //
        local_irq_disable();

        //
        // 前面提到过,在刚才开硬件中断执行环境时只能被硬件中断
        // 抢占,在这个时候是无法处理软中断的,因为刚才开中
        // 断执行过程中可能多次被硬件中断抢占,每抢占一次就有可
        // 能注册一个软中断,所以要再重新取一次所有的软中断。
        // 以便下面的代码进行处理后跳回到 restart 处重复执行。
        //
        pending = local_softirq_pending();

        //
        // 如果在上面的开中断执行环境中触发了硬件中断,且每个都
        // 注册了一个软中断的话,这个软中断会设置 pending 位,
        // 但在当前一直屏蔽软中断的环境下无法得到执行,前面提
        // 到过,因为 irq_exit() 和 do_softirq() 根本无法进入到
        // 这个处理过程中来。这个在上面详细的记录过了。那么在
        // 这里又有了一个执行的机会。注意:虽然当前环境一直是
        // 处于屏蔽软中断执行的环境中,但在这里又给出了一个执行
        // 刚才在开中断环境过程中触发硬件中断时所注册的软中断的
        // 机会,其实只要理解了软中断机制就会知道,无非是在一些特
        // 定环境下调用 ISR 注册到软中断向量表里的函数而已。
        //

        //
        // 如果刚才触发的硬件中断注册了软中断,并且重复执行次数
        // 没有到 10 次的话,那么则跳转到 restart 标志处重复以上
        // 所介绍的所有步骤:设置软中断标志位,重新开中断执行…
        // 注意:这里是要两个条件都满足的情况下才可能重复以上步骤。
        //
        if (pending && –max_restart)
                goto restart;

        //
        // 如果以上步骤重复了 10 次后还有 pending 的软中断的话,
        // 那么系统在一定时间内可能达到了一个峰值,为了平衡这点。
        // 系统专门建立了一个 ksoftirqd 线程来处理,这样避免在一
        // 定时间内负荷太大。这个 ksoftirqd 线程本身是一个大循环,
        // 在某些条件下为了不负载过重,它是可以被其他进程抢占的,
        // 但注意,它是显示的调用了 preempt_xxx() 和 schedule()
        // 才会被抢占和切换的。这么做的原因是因为在它一旦调用
        // local_softirq_pending() 函数检测到有 pending 的软中断
        // 需要处理的时候,则会显示的调用 do_softirq() 来处理软中
        // 断。也就是说,下面代码唤醒的 ksoftirqd 线程有可能会回
        // 到这个函数当中来,尤其是在系统需要响应很多软中断的情况
        // 下,它的调用入口是 do_softirq(),这也就是为什么在 do_softirq()
        // 的入口处也会用 in_interrupt()  函数来判断是否有软中断
        // 正在处理的原因了,目的还是为了防止重入。ksoftirqd 实现
        // 看下面对 ksoftirqd() 函数的分析。
        //
        if (pending)

               //
               // 此函数实际是调用 wake_up_process() 来唤醒 ksoftirqd
               //
                wakeup_softirqd();

        trace_softirq_exit();

        account_system_vtime(current);

        //
        // 到最后才开软中断执行环境,允许软中断执行。注意:这里
        // 使用的不是 local_bh_enable(),不会再次触发 do_softirq()
        // 的调用。
        //
        _local_bh_enable();
}

 

static int ksoftirqd(void * __bind_cpu)
{

        //
        // 显示调用此函数设置当前进程的静态优先级。当然,
        // 这个优先级会随调度器策略而变化。
        //
        set_user_nice(current, 19);

        //
        // 设置当前进程不允许被挂启
        //
        current->flags |= PF_NOFREEZE;

        //
        // 设置当前进程状态为可中断的状态,这种睡眠状
        // 态可响应信号处理等。
        //
        set_current_state(TASK_INTERRUPTIBLE);

        //
        // 下面是一个大循环,循环判断当前进程是否会停止,
        // 不会则继续判断当前是否有 pending 的软中断需
        // 要处理。
        //
        while (!kthread_should_stop()) {

                //
                // 如果可以进行处理,那么在此处理期间内禁止
                // 当前进程被抢占。
                //
                preempt_disable();

                //
                // 首先判断系统当前没有需要处理的 pending 状态的
                // 软中断
                //
                if (!local_softirq_pending()) {

                        //
                        // 没有的话在主动放弃 CPU 前先要允许抢占,因为
                        // 一直是在不允许抢占状态下执行的代码。
                        //
                        preempt_enable_no_resched();

                        //
                        // 显示调用此函数主动放弃 CPU 将当前进程放入睡眠队列,
                        // 并切换新的进程执行(调度器相关不记录在此)
                        //
                        schedule();

                        //
                        // 注意:如果当前显示调用 schedule() 函数主动切换的进
                        // 程再次被调度执行的话,那么将从调用这个函数的下一条
                        // 语句开始执行。也就是说,在这里当前进程再次被执行的
                        // 话,将会执行下面的 preempt_disable() 函数。
                        //

                        //
                        // 当进程再度被调度时,在以下处理期间内禁止当前进程
                        // 被抢占。
                        //
                        preempt_disable();
                }

                //
                // 设置当前进程为运行状态。注意:已经设置了当前进程不可抢占
                // 在进入循环后,以上两个分支不论走哪个都会执行到这里。一是
                // 进入循环时就有 pending 的软中断需要执行时。二是进入循环时
                // 没有 pending 的软中断,当前进程再次被调度获得 CPU 时继续
                // 执行时。
                //
                __set_current_state(TASK_RUNNING);

                //
                // 循环判断是否有 pending 的软中断,如果有则调用 do_softirq()
                // 来做具体处理。注意:这里又是一个 do_softirq() 的入口点,
                // 那么在 __do_softirq() 当中循环处理 10 次软中断的回调函数
                // 后,如果还有 pending 的话,会又调用到这里。那么在这里则
                // 又会有可能去调用 __do_softirq() 来处理软中断回调函数。在前
                // 面介绍 __do_softirq() 时已经提到过,处理 10 次还处理不完的
                // 话说明系统正处于繁忙状态。根据以上分析,我们可以试想如果在
                // 系统非常繁忙时,这个进程将会与 do_softirq() 相互交替执行,
                // 这时此进程占用 CPU 应该会很高,虽然下面的 cond_resched()
                // 函数做了一些处理,它在处理完一轮软中断后当前处理进程可能会
                // 因被调度而减少 CPU 负荷,但是在非常繁忙时这个进程仍然有可
                // 能大量占用 CPU。
                //
                while (local_softirq_pending()) {
                        /* Preempt disable stops cpu going offline.
                           If already offline, we’ll be on wrong CPU:
                           don’t process */
                        if (cpu_is_offline((long)__bind_cpu))

                                //
                                // 如果当前被关联的 CPU 无法继续处理则跳转
                                // 到 wait_to_die 标记出,等待结束并退出。
                                //
                                goto wait_to_die;

                        //
                        // 执行 do_softirq() 来处理具体的软中断回调函数。注
                        // 意:如果此时有一个正在处理的软中断的话,则会马上
                        // 返回,还记得前面介绍的 in_interrupt() 函数么。
                        //
                        do_softirq();

                        //
                        // 允许当前进程被抢占。
                        //
                        preempt_enable_no_resched();
                       
                        //
                        // 这个函数有可能间接的调用 schedule() 来切换当前
                        // 进程,而且上面已经允许当前进程可被抢占。也就是
                        // 说在处理完一轮软中断回调函数时,有可能会切换到
                        // 其他进程。我认为这样做的目的一是为了在某些负载
                        // 超标的情况下不至于让这个进程长时间大量的占用 CPU,
                        // 二是让在有很多软中断需要处理时不至于让其他进程
                        // 得不到响应。
                        //
                        cond_resched();

                        //
                        // 禁止当前进程被抢占。
                        //
                        preempt_disable();

                        //
                        // 处理完所有软中断了吗?没有的话继续循环以上步骤
                        //
                }

                //
                // 待一切都处理完成后,允许当前进程被抢占,并设置
                // 当前进程状态为可中断状态,继续循环以上所有过程。
                //
                preempt_enable();
                set_current_state(TASK_INTERRUPTIBLE);
        }
  
        //
        // 如果将会停止则设置当前进程为运行状态后直接返回。
        // 调度器会根据优先级来使当前进程运行。
        //
        __set_current_state(TASK_RUNNING);
        return 0;

//
// 一直等待到当前进程被停止
//
wait_to_die:

        //
        // 允许当前进程被抢占。
        //
        preempt_enable();
        /* Wait for kthread_stop */

        //
        // 设置当前进程状态为可中断的状态,这种睡眠状
        // 态可响应信号处理等。
        //
        set_current_state(TASK_INTERRUPTIBLE);

        //
        // 判断当前进程是否会被停止,如果不是的话
        // 则设置进程状态为可中断状态并放弃当前 CPU
        // 主动切换。也就是说这里将一直等待当前进程
        // 将被停止时候才结束。
        //
        while (!kthread_should_stop()) {
                schedule();
                set_current_state(TASK_INTERRUPTIBLE);
        }

        //
        // 如果将会停止则设置当前进程为运行状态后直接返回。
        // 调度器会根据优先级来使当前进程运行。
        //
        __set_current_state(TASK_RUNNING);
        return 0;
}

Linux内核网络源码分析——接收数据

目录:Linux内核网络源码分析

概述
接收数据分为从上往下和从下往上的两个过程
从上往下:

1、用户空间中调用read接收数据,此系统调用在内核中的函数是sys_read(fs/read_write.c)。
2、sys_read最终会调用skb_recv_datagram:

§ 如果数据还没有到达,且设置socket设为阻塞模式时,进程挂起signal_pending(current)。
§ 如果数据已到达,或data_ready通知进程资源得到满足后继续处理 (wake_up_interruptible(sk->sleep);)进入第3步

3、INET Socket接收数据

从下往上:

1、数据到达网卡,产生一个硬件中断,由网卡驱动程序处理中断。
2、网卡驱动程序将收到的数据封装成struct sk_buff,然后把它放入接收队列,并置NET_TX_SOFTIRQ软中断标志位等待CPU调度。
3、CPU进程调度schedule()检测到软中断NET_RX_SOFTIRQ,调用相应处理函数net_tx_action(该函数在中net_dev_init注册)
4、net_tx_action开始处理sk_buff,经过网络层(IP)和传输层(TCP/UDP)处理,针对发往本机的数据包找到相应的INET Socket并放入其接收队列,设置data_ready唤醒上层接收进程。





接收数据处理流程

—————————————–硬件中断处理

rtl8139_interrup

…NAPIPoll机制

—————————————–软中断处理

net_rx_action

rtl8139_poll

rt18139_rx

netif_recive_skb

—————————————–网络层处理

ip_rcv

<NF_INET_PRE_ROUTING>

ip_rcv_finish

ip_local_deliver

<NF_INET_LOCAL_IN>

ip_local_deliver_finish

—————————————–传输层处理

UDP

udp_rcv

__udp4_lib_rcv

udp_queue_rcv_skb

__udp_queue_rcv_skb

sock_queue_rcv_skb

skb_queue_tail

__skb_queue_tail

sk->sk_data_ready

TCP

tcp_v4_rcv

tcp_v4_do_rcv

tcp_rcv_established

__skb_queue_tail

sk->sk_data_ready


详细流程分析

 

数据包到达网卡,产生硬件中断
———————————
硬件中断处理:

rtl8139_interrupt() <8139too.c>


/* Receive packets are processed by poll routine.
If not running start it now. */
if (status & RxAckBits){

if (netif_rx_schedule_prep(dev, &tp->napi)) {

RTL_W16_F (IntrMask, rtl8139_norx_intr_mask);
__netif_rx_schedule(dev, &tp->napi);

}

}

netif_rx_schedule() <include/linux/netdevice.h>

// 这 个函数被中断服务程序调用,将设备的POLL方法添加到网络层次的POLL处理队列中去,
// 排队并且准备接收数据包,在使用之前需要调用 netif_rx_reschedule_prep,并且返回的数为 1,
// 并且触发一个NET_RX_SOFTIRQ 的软中断通知网络层接收数据包。

/* Add interface to tail of rx poll list. This assumes that _prep has
* already been called and returned 1.
*/
static inline void __netif_rx_schedule(struct net_device *dev,
struct napi_struct *napi)
{

__napi_schedule(napi);

}

/* Try to reschedule poll. Called by irq handler. */
static inline void netif_rx_schedule(struct net_device *dev,
struct napi_struct *napi)
{

if (netif_rx_schedule_prep(dev, napi))
__netif_rx_schedule(dev, napi);

}

netif_rx_schedule_prep() <include/linux/netdevice.h>

// 确定设备处于运行,而且设备还没有被添加到网络层的POLL处理队列中。
// 在调用 netif_rx_schedule之前会调用这个函数。

/* Test if receive needs to be scheduled but only if up */
static inline int netif_rx_schedule_prep(struct net_device *dev,
struct napi_struct *napi)
{

return napi_schedule_prep(napi);

}

netif_rx_ni() <net/core/dev.c>

int netif_rx_ni(struct sk_buff *skb)

preempt_disable();// 禁止当前进程被抢占
err = netif_rx(skb);
if (local_softirq_pending()) // 检测本地处理器是否有需要处理的softirq
do_softirq();// 处理软中断
preempt_enable();

netif_rx() <net/core/dev.c>

netif_rx() <net/core/dev.c>
  1. int netif_rx(struct sk_buff *skb)   
  2. //该函数从设备驱动程序中接收数据包,并添加到上层协议的处理队列  
  3. //所有接收到的数据包都会在这里被处理发往上层  
  4. //在拥塞控制或协议层再中来决定是接收数据或将数据包丢弃  
  5. …  
  6. local_irq_save(flags);//在保存当前中断状态到 flags 之后禁止当前处理器的中断  
  7. queue = &__get_cpu_var(softnet_data);  
  8.   
  9. __get_cpu_var(netdev_rx_stat).total++;  
  10. if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {//判断接收队列是否已满  
  11.     if (queue->input_pkt_queue.qlen) {//接收队列未满,继续判断接收队列中是否为空  
  12. enqueue:  
  13.         //接收队列非空,即NET_RX_SOFTIRQ已设置  
  14.         //不必设置软中断,直接将数据包入队  
  15.         dev_hold(skb->dev);//Hold reference to device to keep it from being freed.  
  16.         __skb_queue_tail(&queue->input_pkt_queue, skb);  
  17.         local_irq_restore(flags);  
  18.         return NET_RX_SUCCESS;  
  19.     }  
  20.     //若接收队列为空,则使用NAPI设置NET_RX_SOFTIRQ软中断  
  21.     napi_schedule(&queue->backlog);  
  22.     goto enqueue;  
  23. }  
  24. //接收队列已满,数据包将被遗弃  
  25. __get_cpu_var(netdev_rx_stat).dropped++;//统计丢弃数据包数量  
  26. local_irq_restore(flags);//恢复中断  
  27. …  

__napi_schedule() <net/core/dev.c>

__napi_schedule() <net/core/dev.c>
  1. /** 
  2.  * __napi_schedule – schedule for receive 
  3.  * @n: entry to schedule 
  4.  * 
  5.  * The entry’s receive function will be scheduled to run 
  6.  */  
  7. void __napi_schedule(struct napi_struct *n)  
  8. {  
  9.     unsigned long flags;  
  10.   
  11.     local_irq_save(flags);  
  12.     list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);  
  13.     __raise_softirq_irqoff(NET_RX_SOFTIRQ);  
  14.     local_irq_restore(flags);  
  15. }  

—————————————–

软中断处理:
net_rx_action() <net/core/dev.c>

static void net_rx_action(struct softirq_action *h)

if (test_bit(NAPI_STATE_SCHED, &n->state))
work = n->poll(n, weight);
// 调用NIC设备注册的poll函数获取数据,
// 对于RTL8139网卡即rtl8139_poll(该函数在中rtl8139_init_onece注册)

rtl8139_poll() <drivers/net/8139too.c>

static int rtl8139_poll(struct napi_struct *napi, int budget)

if (likely(RTL_R16(IntrStatus) & RxAckBits))
work_done += rtl8139_rx(dev, tp, budget);

rt18139_rx() <drivers/net/8139too.c>

rt1839_rx() <drivers/net/8139too.c>
  1. static int rtl8139_rx(struct net_device *dev, 
  2.                 struct rtl8139_private *tp,int budget)  
  3. //从rx_ring中接收数据,构造sk_buff并发往上层  
  4. …  
  5. //为分配sk_buff空间,准备保存接收的数据包 
  6. skb = dev_alloc_skb (pkt_size + 2);
  7. if (likely(skb)) {  
  8.     skb_reserve (skb, 2);   /* 16 byte align the IP fields. */  
  9. #if RX_BUF_IDX == 3  
  10.     wrap_copy(skb, rx_ring, ring_offset+4, pkt_size);  
  11. #else
        //从rx_ring中复制数据
  12.     skb_copy_to_linear_data (skb, &rx_ring[ring_offset + 4], pkt_size); 
  13. #endif  
  14.     skb_put (skb, pkt_size);//调整skb->tial的位置  
  15.   
  16.     skb->protocol = eth_type_trans (skb, dev);  
  17.   
  18.     dev->last_rx = jiffies;  
  19.     tp->stats.rx_bytes += pkt_size;  
  20.     tp->stats.rx_packets++;  
  21.   
  22.     netif_receive_skb (skb);//将收到的数据包发往上层  
  23. else {  
  24.     if (net_ratelimit())  
  25.         printk (KERN_WARNING  
  26.             "%s: Memory squeeze, dropping packet.n",  
  27.             dev->name);  
  28.     tp->stats.rx_dropped++;  
  29. }  
  30. …  

netif_recive_skb() <net/core/dev.c>
// ptype_all 链用于监听从网络设备上接收的所有包,通常不使用它。
// ptype_base hash表是被协议标识符弄乱的,用于决定哪个协议将接收传入的网络包。

netif_recive_skb() <net/core/dev.c>
  1. int netif_receive_skb(struct sk_buff *skb)  
  2. …  
  3. skb_reset_network_header(skb);  
  4. skb_reset_transport_header(skb);  
  5. skb->mac_len = skb->network_header – skb->mac_header;  
  6. …  
  7. //遍历ptype_all中绑定的协议处理数据包  
  8. list_for_each_entry_rcu(ptype, &ptype_all, list) {  
  9.     if (!ptype->dev || ptype->dev == skb->dev) {  
  10.         if (pt_prev)  
  11.             ret = deliver_skb(skb, pt_prev, orig_dev);//处理数据包  
  12.         pt_prev = ptype;  
  13.     }  
  14. }  
  15. …  
  16. //遍历ptype_base[ntohs(type) & PTYPE_HASH_MASK]中绑定的协议处理数据包  
  17. type = skb->protocol;  
  18. list_for_each_entry_rcu(ptype,  
  19.         &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {  
  20.     if (ptype->type == type &&  
  21.         (!ptype->dev || ptype->dev == skb->dev)) {  
  22.         if (pt_prev)  
  23.             ret = deliver_skb(skb, pt_prev, orig_dev);//处理数据包  
  24.         pt_prev = ptype;  
  25.     }  
  26. }  
  27. if (pt_prev) {  
  28.     ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);//处理数据包  
  29. else {  
  30.     //没有找到任何上层协议处理数据包,则将数据包丢弃  
  31.     kfree_skb(skb);  
  32.     /* Jamal, now you will not able to escape explaining 
  33.      * me how you were going to use this. 🙂 
  34.      */  
  35.     ret = NET_RX_Drop;  
  36. }  

deliver_skb() <net/core/dev.c>

deliver_skb() <net/core/dev.c>
  1. static inline int deliver_skb(struct sk_buff *skb,  
  2.                   struct packet_type *pt_prev,  
  3.                   struct net_device *orig_dev)  
  4. {  
  5.     atomic_inc(&skb->users);//skb->user中登记一个用户  
  6.     return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);  
  7.     //调用上层协议的处理函数,对于IP协议数据包,则pt_prev->func注册为ip_rcv()  
  8. }  

ip_rcv() <net/ipv4/ip_input.c>

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
// 对包进行基本校验:IP目标、完整性、CSUM等
//  * 1. Length at least the size of an ip header
// * 2. Version of 4
// * 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
// * 4. Doesn’t have a bogus length
// 然后数据包穿过Netfilter传到达ip_rcv_finish


return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);

ip_rcv_finish() <net/ipv4/ip_input.c>


if (skb->dst == NULL) {
int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);
// 查找路由,将路由信息写到skb->dst中,
// 如果是发往本地的数据包则skb->dst->input函数注册为ip_local_deliver处理。


return dst_input(skb);// 根据路由信息处理数据包

dst_input() <include/net/dst.h>

static inline int dst_input(struct sk_buff *skb)
/* Input packet from network to transport. */

err = skb->dst->input(skb);

ip_local_deliver() <net/ipv4/ip_input.c>
// 将IP数据包发往上层协议,数据包穿过Netfilter传到达ip_rcv_finish

ip_local_deliver() <net/ipv4/ip_input.c>
  1.     /* 
  2.      *  Deliver IP Packets to the higher protocol layers. 
  3.      */  
  4.     int ip_local_deliver(struct sk_buff *skb)  
  5.     {  
  6.         /* 
  7.          *  Reassemble IP fragments. 
  8.          */  
  9.       
  10.         if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {  
  11.             if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))  
  12.                 return 0;  
  13.         }  
  14.       
  15.         return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL,  
  16.                    ip_local_deliver_finish);  
  17.   
  18. }  

ip_local_deliver_finish() <net/ipv4/ip_input.c>

ip_local_deliver_finish() <net/ipv4/ip_input.c>
  1. static int ip_local_deliver_finish(struct sk_buff *skb)  
  2. {  
  3.     struct net *net = dev_net(skb->dev);  
  4.   
  5.     __skb_pull(skb, ip_hdrlen(skb));  
  6.   
  7.     /* Point into the IP datagram, just past the header. */  
  8.     skb_reset_transport_header(skb);  
  9.   
  10.     rcu_read_lock();  
  11.     {  
  12.         int protocol = ip_hdr(skb)->protocol;  
  13.         int hash, raw;  
  14.         struct net_protocol *ipprot;  
  15.   
  16.     resubmit:  
  17.         raw = raw_local_deliver(skb, protocol);//原始套接字接收数据  
  18.   
  19.         hash = protocol & (MAX_INET_PROTOS – 1);  
  20.         ipprot = rcu_dereference(inet_protos[hash]);//获取协议处理结构  
  21.         if (ipprot != NULL && (net == &init_net || ipprot->netns_ok)) {  
  22.             int ret;  
  23.   
  24.             if (!ipprot->no_policy) {  
  25.                 // 对转发的数据包进行安全策略检查, 检查失败的话丢包  
  26.                 if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {  
  27.                     kfree_skb(skb);  
  28.                     goto out;  
  29.                 }  
  30.                 nf_reset(skb);  
  31.             }  
  32.             ret = ipprot->handler(skb);//调用相应的处理函数  
  33.             if (ret < 0) {  
  34.                 protocol = -ret;//?  
  35.                 goto resubmit;  
  36.             }  
  37.             IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);  
  38.         } else {  
  39.             if (!raw) {  
  40.                 if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {  
  41.                     IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);  
  42.                     icmp_send(skb, ICMP_DEST_UNREACH,  
  43.                           ICMP_PROT_UNREACH, 0);  
  44.                 }  
  45.             } else  
  46.                 IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);  
  47.             kfree_skb(skb);  
  48.         }  
  49.     }  
  50.  out:  
  51.     rcu_read_unlock();  
  52.   
  53.     return 0;  
  54. }  

 

 

Linux内核网络源码分析——发送数据

目录:Linux内核网络源码分析

UDP发送:
|       sys_write               fs/read_write.c
|       sock_writev             net/socket.c                    
|       sock_sendmsg            net/socket.c
|       inet_sendmsg            net/ipv4/af_inet.c
|       udp_sendmsg             net/ipv4/udp.c
|       ip_build_xmit           net/ipv4/ip_output.c
|       output_maybe_reroute    net/ipv4/ip_output.c
|       ip_output               net/ipv4/ip_output.c
|       ip_finish_output        net/ipv4/ip_output.c
|       dev_queue_xmit          net/dev.c
|       ——————————————–
|       el3_start_xmit          driver/net/3c309.c
V

write()
e.g. write(sockfd,”Hello”,strlen(“Hello”));
user
————————————————–
kernel

sys_write() <fs/read_write.c>
asmlinkage ssize_t sys_write(unsigned int fd, const char __user * buf, size_t count)

ret = vfs_write(file, buf, count, &pos);

vfs_write()

if (file->f_op->write)
ret = file->f_op->write(file, buf, count, pos);

//在前面建立socket时sock_map_fd将sock对应file的f_op定义为socket_file_ops,参见:
static const struct file_operations socket_file_ops = {

.aio_write = sock_aio_write,

}
sock_aio_write()//与之前的版本不同了。。。
do_sock_write()
__sock_sendmsg()
static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size)

return sock->ops->sendmsg(iocb, sock, msg, size);

//sock->ops在inet_create函数中被初始化,参见:
inet_create() <net/ipv4/af_inet.c:>
static struct inet_protosw inetsw_array[] <net/ipv4/af_inet.c:>
<net/ipv4/tcp_ipv4.c>
const struct proto_ops inet_stream_ops ={

.sendmsg = tcp_sendmsg,

}
<net/ipv4/udp.c>
const struct proto_ops inet_dgram_ops ={

.sendmsg = inet_sendmsg,

}
————————————————–
UDP
inet_sendmsg(…)
int inet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size)

return sk->sk_prot->sendmsg(iocb, sk, msg, size);


udp_sendmsg(…)
ip_route_output_flow()
这里进行路由!参见5、路由和ARP

ip_append_data()
* ip_append_data() and ip_append_page() can make one large IP datagram
* from many pieces of data. Each pieces will be holded on the socket
* until ip_push_pending_frames() is called. Each piece can be a page
* or non-page data.
*
* Not only UDP, other transport protocols – e.g. raw sockets – can use
* this interface potentially.
*
* LATER: length must be adjusted by pad at tail, when it is required.
udp_push_pending_frames()

udp_push_pending_frames()
* Push out all pending data as one UDP datagram. Socket is locked.

————————————————–
TCP
tcp_transmit_skb()
err = icsk->icsk_af_ops->queue_xmit(skb,0);

tcp_transmit_skb 引用表:
tcp_mtu_probe
tcp_write_xmit
tcp_push_one
tcp_retransmit_skb
tcp_send_active_reset
tcp_send_synack
tcp_connect
tcp_send_ack
tcp_xmit_probe_skb
tcp_write_wakeup
 

ip_queue_xmit() ip_send_reply() ip_build_and_send_pkt()
int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
这里有route过程
ip_route_output_flow(…)

*dccp int ip_build_and_send_pkt(struct sk_buff *skb, struct sock *sk, __be32 saddr, __be32 daddr, struct ip_options *opt)
Add an ip header to a skbuff and send it out.

void ip_send_reply(struct sock *sk, struct sk_buff *skb, struct ip_reply_arg *arg, unsigned int len) <net/ipv4/ip_output.c>
* Generic function to send a packet as reply to another packet.
* Used to send TCP resets so far. ICMP should use this function too.

* Should run single threaded per socket because it uses the sock
* structure to pass arguments.
这里有用到ip_route_output_key()进行路由。

int ip_push_pending_frames(struct sock *sk);
Combined all pending IP fragments on the socket as one IP datagram
and push them out.
ip_local_out();

————————————————–
IP

ip_push_pending_frames()

ip_local_out() <ip_output.c>
int ip_local_out(struct sk_buff *skb)

{
int err;

err = __ip_local_out(skb);
if (likely(err == 1))
err = dst_output(skb);

return err;
}
EXPORT_SYMBOL_GPL(ip_local_out);

__ip_local_out() <ip_output.c>
int __ip_local_out(struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);

iph->tot_len = htons(skb->len);
ip_send_check(iph);
return nf_hook(PF_INET, NF_INET_LOCAL_OUT, skb, NULL, skb->dst->dev,
dst_output);
}

dst_output() <net/core/dst.c>
/* Output packet to network from transport. */
static inline int dst_output(struct sk_buff *skb)
{
return skb->dst->output(skb);
}

其中dst->output() = ip_output();在__mkroute_output()和__mkroute_input()中注册。

ip_output() <net/ipv4/ip_output.c>

return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev,
ip_finish_output,
!(IPCB(skb)->flags & IPSKB_REROUTED));
 

ip_finish_output() <net/ipv4/ip_output.c>

static int ip_finish_output(struct sk_buff *skb)
{
#if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)
/* Policy lookup after SNAT yielded a new policy */
if (skb->dst->xfrm != NULL) {
IPCB(skb)->flags |= IPSKB_REROUTED;
return dst_output(skb);
}
#endif
if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))
return ip_fragment(skb, ip_finish_output2);
else
return ip_finish_output2(skb);
}
 

ip_finish_output2() <net/ipv4/ip_output.c>


if (dst->hh)
         return neigh_hh_output(dst->hh, skb);
else if (dst->neighbour)
         return dst->neighbour->output(skb);

// 此函数在neigh_alloc中注册为neigh_blackhole(), 但这个是默认的,一般会被替换掉

static struct neighbour *neigh_alloc(struct neigh_table *tbl)

n->output = neigh_blackhole;

// 此函数在arp_constructor中注册为ops->queue_xmit或ops->output或connected_output

static int arp_constructor(struct neighbour *neigh)

.output = neigh_resolve_output,
.connected_output = neigh_connected_output,
.queue_xmit = dev_queue_xmit()

// 故一般为neigh_resolve_output

neigh_resolve_output() <net/core/neighbour.c>


err = dev_hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
// 这里有Mac头填充的动作,参见路由和ARP
// 说明此前neigh->ha已获取。如果neigh->ha为空呢?

if (err >= 0)
        rc = neigh->ops->queue_xmit(skb); // 此函数注册为dev_queue_xmit()

dev_queue_xmit() <net/core/dev.c>

int dev_queue_xmit(struct sk_buff *skb)


if (!netif_queue_stopped(dev) &&
    !netif_subqueue_stopped(dev, skb)) {
    rc = 0;
    if (!dev_hard_start_xmit(skb, dev)) {
        HARD_TX_UNLOCK(dev);
        goto out;
    }
}

dev_hard_start_xmit() <net/core/dev.c>

int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)


return dev->hard_start_xmit(skb, dev);

xxx_start_xmit() <drivers/net/xxx.c>

Linux内核网络源码分析——建立套接字

目录:Linux内核网络源码分析

socket()
user
————————————————–
kernel

sys_socketcall() <net/socket.c>
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
    …
    case SYS_SOCKET:
    err = sys_socket(a0, a1, a[2]);
    break;
    …
sys_socket() <net/socket.c>
asmlinkage long sys_socket(int family, int type, int protocol)

retval = sock_create(family, type, protocol, &sock);

retval = sock_map_fd(sock);
//为套接字分配一个文件描述符并分配一个file文件,
//在应用层可象处理文件一样处理套接字了。

sock_create() <net/socket.c>
int sock_create(int family, int type, int protocol, struct socket **res)
{
return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
// current宏用于获取当前进程的task_struct
}

__sock_create()
static int __sock_create(struct net *net, int family, int type, int protocol,
struct socket **res, int kern)

pf = rcu_dereference(net_families[family]);//获取net_proto_family结构

err = pf->create(net, sock, protocol);
// 使用net_proto_family的create函数构建BSD Socket
// 当family为AF_INET时,调用的是inet_create

inet_create() <net/ipv4/af_inet.c> //Create an inet socket
static int inet_create(struct net *net, struct socket *sock, int protocol)

sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
// 根据指定协议为sock分配内存空间,并初始化

if (sk->sk_prot->init) {
err = sk->sk_prot->init(sk);//调用协议(struct proto)指定的初始化函数

// 如果类型是SOCK_STREAM的话会调用tcp_v4_init_sock,
// 而SOCK_DGRAM类型的socket没有额外的初始化了,到此socket调用结束
// 参见:

inet_init() <net/ipv4/af_inet.c:>
static struct inet_protosw inetsw_array[] <net/ipv4/af_inet.c:>
struct proto tcp_prot={…} <net/ipv4/tcp_ipv4.c>
struct proto udp_prot={…} <net/ipv4/udp.c>

 

sk_buff源码

Linux 2.6.26内核中sk_buff结构的定义如下:

struct sk_buff <include/linux/skbuff.h>
  1. /**  
  2.  *  struct sk_buff – socket buffer 
  3.  *  @next: Next buffer in list 
  4.  *  @prev: Previous buffer in list 
  5.  *  @sk: Socket we are owned by 
  6.  *  @tstamp: Time we arrived 
  7.  *  @dev: Device we arrived on/are leaving by 
  8.  *  @transport_header: Transport layer header 
  9.  *  @network_header: Network layer header 
  10.  *  @mac_header: Link layer header 
  11.  *  @dst: destination entry 
  12.  *  @sp: the security path, used for xfrm 
  13.  *  @cb: Control buffer. Free for use by every layer. Put private vars here 
  14.  *  @len: Length of actual data 
  15.  *  @data_len: Data length 
  16.  *  @mac_len: Length of link layer header 
  17.  *  @hdr_len: writable header length of cloned skb 
  18.  *  @csum: Checksum (must include start/offset pair) 
  19.  *  @csum_start: Offset from skb->head where checksumming should start 
  20.  *  @csum_offset: Offset from csum_start where checksum should be stored 
  21.  *  @local_df: allow local fragmentation 
  22.  *  @cloned: Head may be cloned (check refcnt to be sure) 
  23.  *  @nohdr: Payload reference only, must not modify header 
  24.  *  @pkt_type: Packet class 
  25.  *  @fclone: skbuff clone status 
  26.  *  @ip_summed: Driver fed us an IP checksum 
  27.  *  @priority: Packet queueing priority 
  28.  *  @users: User count – see {datagram,tcp}.c 
  29.  *  @protocol: Packet protocol from driver 
  30.  *  @truesize: Buffer size  
  31.  *  @head: Head of buffer 
  32.  *  @data: Data head pointer 
  33.  *  @tail: Tail pointer 
  34.  *  @end: End pointer 
  35.  *  @destructor: Destruct function 
  36.  *  @mark: Generic packet mark 
  37.  *  @nfct: Associated connection, if any 
  38.  *  @ipvs_property: skbuff is owned by ipvs 
  39.  *  @peeked: this packet has been seen already, so stats have been 
  40.  *      done for it, don’t do them again 
  41.  *  @nf_trace: netfilter packet trace flag 
  42.  *  @nfctinfo: Relationship of this skb to the connection 
  43.  *  @nfct_reasm: netfilter conntrack re-assembly pointer 
  44.  *  @nf_bridge: Saved data about a bridged frame – see br_netfilter.c 
  45.  *  @iif: ifindex of device we arrived on 
  46.  *  @queue_mapping: Queue mapping for multiqueue devices 
  47.  *  @tc_index: Traffic control index 
  48.  *  @tc_verd: traffic control verdict 
  49.  *  @ndisc_nodetype: router type (from link layer) 
  50.  *  @dma_cookie: a cookie to one of several possible DMA operations 
  51.  *      done by skb DMA functions 
  52.  *  @secmark: security marking 
  53.  */  
  54.   
  55. struct sk_buff {  
  56.     /* These two members must be first. */  
  57.     struct sk_buff      *next;  
  58.     struct sk_buff      *prev;  
  59.   
  60.     struct sock     *sk;  
  61.     ktime_t         tstamp;  
  62.     struct net_device   *dev;  
  63.   
  64.     union {  
  65.         struct  dst_entry   *dst;  
  66.         struct  rtable      *rtable;  
  67.     };  
  68.     struct  sec_path    *sp;  
  69.   
  70.     /* 
  71.      * This is the control buffer. It is free to use for every 
  72.      * layer. Please put your private variables there. If you 
  73.      * want to keep them across layers you have to do a skb_clone() 
  74.      * first. This is owned by whoever has the skb queued ATM. 
  75.      */  
  76.     char            cb[48];  
  77.   
  78.     unsigned int        len,  
  79.                 data_len;  
  80.     __u16           mac_len,  
  81.                 hdr_len;  
  82.     union {  
  83.         __wsum      csum;  
  84.         struct {  
  85.             __u16   csum_start;  
  86.             __u16   csum_offset;  
  87.         };  
  88.     };  
  89.     __u32           priority;  
  90.     __u8            local_df:1,  
  91.                 cloned:1,  
  92.                 ip_summed:2,  
  93.                 nohdr:1,  
  94.                 nfctinfo:3;  
  95.     __u8            pkt_type:3,  
  96.                 fclone:2,  
  97.                 ipvs_property:1,  
  98.                 peeked:1,  
  99.                 nf_trace:1;  
  100.     __be16          protocol;  
  101.   
  102.     void            (*destructor)(struct sk_buff *skb);  
  103. #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)  
  104.     struct nf_conntrack *nfct;  
  105.     struct sk_buff      *nfct_reasm;  
  106. #endif  
  107. #ifdef CONFIG_BRIDGE_NETFILTER  
  108.     struct nf_bridge_info   *nf_bridge;  
  109. #endif  
  110.   
  111.     int         iif;  
  112. #ifdef CONFIG_NETDEVICES_MULTIQUEUE  
  113.     __u16           queue_mapping;  
  114. #endif  
  115. #ifdef CONFIG_NET_SCHED  
  116.     __u16           tc_index;   /* traffic control index */  
  117. #ifdef CONFIG_NET_CLS_ACT  
  118.     __u16           tc_verd;    /* traffic control verdict */  
  119. #endif  
  120. #endif  
  121. #ifdef CONFIG_IPV6_NDISC_NODETYPE  
  122.     __u8            ndisc_nodetype:2;  
  123. #endif  
  124.     /* 14 bit hole */  
  125.   
  126. #ifdef CONFIG_NET_DMA  
  127.     dma_cookie_t        dma_cookie;  
  128. #endif  
  129. #ifdef CONFIG_NETWORK_SECMARK  
  130.     __u32           secmark;  
  131. #endif  
  132.   
  133.     __u32           mark;  
  134.   
  135.     sk_buff_data_t      transport_header;  
  136.     sk_buff_data_t      network_header;  
  137.     sk_buff_data_t      mac_header;  
  138.     /* These elements must be at the end, see alloc_skb() for details.  */  
  139.     sk_buff_data_t      tail;  
  140.     sk_buff_data_t      end;  
  141.     unsigned char       *head,  
  142.                 *data;  
  143.     unsigned int        truesize;  
  144.     atomic_t        users;  
  145. };  

 

tcp_v4_rcv()函数流程分析





1、判断是否发往本机的数据包,是则继续处理;否则跳转到discard_it,丢弃数据包。

=========================================================

if (skb->pkt_type != PACKET_HOST)

goto discard_it;

=========================================================                

skb的pkt_type属性表示数据包的类型,其参考值由宏定义给出

 

/* Packet types */

#define PACKET_HOST      0   /* To us                */发往本机的包

#define PACKET_BROADCAST 1   /* To all               */广播包

#define PACKET_MULTICAST 2   /* To group             */多播包

#define PACKET_OTHERHOST 3   /* To someone else      */发往其他主机的包

#define PACKET_OUTGOING  4   /* Outgoing of any type */由本机发出的包

/* These ones are invisible by user level */

#define PACKET_LOOPBACK  5   /* MC/BRD frame looped back */

#define PACKET_FASTROUTE 6   /* Fastrouted frame        */

 

备注:     skb生成时pkt_type默认为0!

为什么不处理广播包和多播包?

 

2、SNMP数据统计        

=========================================================                

/* Count it even if it’s bad */

TCP_INC_STATS_BH(TCP_MIB_INSEGS);

=========================================================                

TCP_INC_STATS_BH宏定义为:

 

#define TCP_INC_STATS_BH(field) SNMP_INC_STATS_BH(tcp_statistics, field)

#define SNMP_INC_STATS_BH(mib, field)         

(per_cpu_ptr(mib[0], raw_smp_processor_id())->mibs[field]++)

 

备注:详细参照SNMP相关知识。

 

3、 获取TCP头,校验数据包长度和TCP头       

============================================================

if (!pskb_may_pull(skb, sizeof(struct tcphdr)))

goto discard_it;

th = tcp_hdr(skb);

 if (th->doff < sizeof(struct tcphdr) / 4)

goto bad_packet;

if (!pskb_may_pull(skb, th->doff * 4))

goto discard_it;

============================================================





在调用 skb_pull() 去掉外层协议头之前,通常先调用pskb_may_pull判断一下是否有足够的数据用于“pull”。
(参考:Linux网络子系统sk_buffer详细分析 9.2小节)

 4、校验和

============================================================

/* An explanation is required here, I think.

 * Packet length and doff are validated by header prediction,

 * provided case of th->doff==0 is eliminated.

 * So, we defer the checks. */

if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb))

goto bad_packet;

============================================================

5、设置控制符

============================================================

th = tcp_hdr(skb);

iph = ip_hdr(skb);

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)->flags         = iph->tos;

TCP_SKB_CB(skb)->sacked         = 0;

============================================================

这里TCP_SKB_CB宏定义为:

#define TCP_SKB_CB(__skb)        ((struct tcp_skb_cb *)&((__skb)->cb[0]))

其中tcp_skb_cb定义如下

 

/* This is what the send packet queuing engine uses to pass

 * TCP per-packet control information to the transmission

 * code.  We also store the host-order sequence numbers in

 * here too.  This is 36 bytes on 32-bit architectures,

 * 40 bytes on 64-bit machines, if this grows please adjust

 * skbuff.h:skbuff->cb[xxx] size appropriately.

 */

struct tcp_skb_cb {

union {

struct inet_skb_parm        h4;

#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)

struct inet6_skb_parm        h6;

#endif

} header;        /* For incoming frames                */

__u32                seq;                /* Starting sequence number        */

__u32                end_seq;        /* SEQ + FIN + SYN + datalen        */

__u32                when;                /* used to compute rtt’s        */

__u8                flags;                /* TCP header flags.                */

 

/* NOTE: These must match up to the flags byte in a

 *       real TCP header.

 */

#define TCPCB_FLAG_FIN                0x01

#define TCPCB_FLAG_SYN                0x02

#define TCPCB_FLAG_RST                0x04

#define TCPCB_FLAG_PSH                0x08

#define TCPCB_FLAG_ACK                0x10

#define TCPCB_FLAG_URG                0x20

#define TCPCB_FLAG_ECE                0x40

#define TCPCB_FLAG_CWR                0x80

 

__u8                sacked;                /* State flags for SACK/FACK.        */

#define TCPCB_SACKED_ACKED        0x01        /* SKB ACK’d by a SACK block        */

#define TCPCB_SACKED_RETRANS        0x02        /* SKB retransmitted                */

#define TCPCB_LOST                0x04        /* SKB is lost                        */

#define TCPCB_TAGBITS                0x07        /* All tag bits                        */

 

#define TCPCB_EVER_RETRANS        0x80        /* Ever retransmitted frame        */

#define TCPCB_RETRANS                (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)

 

__u16                urg_ptr;        /* Valid w/URG flags is set.        */

__u32                ack_seq;        /* Sequence number ACK’d        */

};

 

6 查找对应的inet sock

====================================================================

sk = __inet_lookup(dev_net(skb->dev), &tcp_hashinfo, iph->saddr,

th->source, iph->daddr, th->dest, inet_iif(skb));

if (!sk)

goto no_tcp_socket;

====================================================================

7、处理数据包

====================================================================

process:

if (sk->sk_state == TCP_TIME_WAIT)

goto do_time_wait;

 

if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))

goto discard_and_relse;

nf_reset(skb);

 

if (sk_filter(sk, skb))

goto discard_and_relse;

====================================================================

8、接收数据

====================================================================

skb->dev = NULL;

 

bh_lock_sock_nested(sk);

ret = 0;

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 = get_softnet_dma();

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

sk_add_backlog(sk, skb);

bh_unlock_sock(sk);

 

sock_put(sk);

 

return ret;

====================================================================

9、没有匹配的socket处理

====================================================================

no_tcp_socket:

if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))

goto discard_it;

 

if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {

bad_packet:

TCP_INC_STATS_BH(TCP_MIB_INERRS);

} else {

tcp_v4_send_reset(NULL, skb);

}

====================================================================

10、抛弃数据

====================================================================

discard_it:

/* Discard frame. */

kfree_skb(skb);

return 0;

 

discard_and_relse:

sock_put(sk);

goto discard_it;

====================================================================

11time wait机制

====================================================================

do_time_wait:

if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {

inet_twsk_put(inet_twsk(sk));

goto discard_it;

}

 

if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {

TCP_INC_STATS_BH(TCP_MIB_INERRS);

inet_twsk_put(inet_twsk(sk));

goto discard_it;

}

switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {

case TCP_TW_SYN: {

struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),

&tcp_hashinfo,

iph->daddr, th->dest,

inet_iif(skb));

if (sk2) {

inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);

inet_twsk_put(inet_twsk(sk));

sk = sk2;

goto process;

}

/* Fall through to ACK */

}

case TCP_TW_ACK:

tcp_v4_timewait_ack(sk, skb);

break;

case TCP_TW_RST:

goto no_tcp_socket;

case TCP_TW_SUCCESS:;

}

goto discard_it;

====================================================================