资源描述
Ipv6 初始化和处理流程分析一 .Ipv6 的初始化1. 网络子系统概述Linux内核中,与网络相关的代码是一个相对独立的子系统,称为网络子系统。网络子系统是一个层次化的结构,可分为以下几个层次:1)Socket 层Linux在发展过程中,采用BSD socket APIs作为自己的网络相关的API接口。同时, Linux 的目标又要能支持各种不同的协议族, 而且这些协议族都可以使用 BSD socket APIs 作为应用层的编程接口。因此,在 socket APIs 与协议族层之间抽象出一个 socket 层,用于将 user space 的 socket API 调用,转给具体的协议族做处理。2)协议族层( INET 协议族、 INET6 协议族等)Linux网络子系统功能上相当完备, 它不仅支持 INET 协议族(也就是通常所说的 TCP/IP stack),而且还支持其它很多种协议族,如DECnet, ROSE,NETBEUI等。 INET6 就是一种新增加的协议族。对于 INET 、INET6 协议族来说,又进一步划分为传输层和网络层。3)设备驱动层设备驱动层则主要将协议族层与物理的网络设备隔离开。它不在本文的讨论范围之内。下图是 Linux网络系统层次结构图。2. 网络子系统的初始化1) Socket 层的初始化 :Init()-do_basic_setup()-sock_init()Sock_init():对 sock和 skbuff结构进行 SLAB 内存的初始化工作2)各种网络协议族的初始化:.Do_initcalls():对于编译到内核中的功能模块 (而不是以模块的形式动态加载) ,它的初始化函数会在这个地方被调用到。内核映象中专门有一个初始化段,所有编译到内核中的功能模块的初始化函数都会加入到这个段中; 而 do_initcalls() 就是依次执行初始化段中的这些函数。INET 协议族通常是被编译进内核的;它的模块初始化函数是net/ipv4/af_inet.c中的 inet_init()而INET6是作为一个模块编译的。它的模块初始化函数是net/ipv6/af_inet6.c中的 inet6_init()3. 协议族Linux网络子系统可以支持不同的协议族,Linux所支持的协议族定义在include/linux/socket.h1)协议族数据结构协议族数据结构是struct net_proto_family。structnet_proto_familyintfamily;int(*create)(structsocket*sock,intprotocol);shortauthentication;shortencryption;shortencrypt_net;structmodule*owner;这个结构中,最重要的是 create 函数,一个新的协议族,必须提供此函数的实现。这是因为:不同的网络协议族,从user space的使用方法来说,都是一样的,都是先调用 socket() 来创建一个 socket fd,然后通过这个fd 发送 / 接收数据。在 user space 通过 socket()系统调用进入内核后,根据第一个参数协议族类型 ,来 调用相应 协议族 create() 函数 。 对 INET6 来 说,这个 函数 inet6_create() 。因此,要实现一个新的协议族,首先需要提供一个 create() 的实现。关于 create() 里面具体做了什么,后面再叙述。Linux系统通过这种方式, 可以很方便的支持新的网络协议族,而不用修改.已有的代码。这很好的符合了“开 - 闭原则”,对扩展开放,对修改封闭。2)协议族注册Linux维护一个 struct net_proto_family的数组 net_families如果 要 支 持 一个 新 的 网 络协 议 族 , 那 么 需 要 定 义 自 己 的 struct net_proto_family ,并且通过调用 sock_register 将它注册到 net_families 中。4.sock 层socket层又叫 “socket access protocol layer”。它处于 BSD socketAPIs 与底层具体的协议族之间。这是一个抽象层,它起着承上启下的作用。在这一层的数据结构也有着这种特点1)数据结构在 user space ,通过 socket() 创建的 socket fd ,在内核中对应的就是一个 struct socket。structsocketsocket_statestate;unsignedlongflags;structproto_ops*ops;structfasync_struct*fasync_list;structfile*file;structsock*sk;wait_queue_head_twait;shorttype;它定义于 include/linux/net.h中。Structsocket的ops 域指向一个structproto_ops结 构, structproto_ops定义于 include/linux/net.h中,它是 socket层提供给上层的接口,这个结构中,都是BSD socket API的具体实现的函数指针。一个 socket API通过系统调用进入内核后,首先由socket层处理。Socket 层找到对应的 structsocket ,通过它找到 structproto_ops ,然后由它所指向的函数进行进一步处理。以 sendmsg()这个函数为例,从user space通过系统调用进入kernel后,由 sys_sendmsg() 、sock_sendmsg() 依次处理,然后交给 structproto_ops.的 sendmsg() 处理。2)sock 层和传输层的关联INET 和 INET6 这两种协议族,可以支持多种传输层协议, 包括 TCP、UDP、RAW,在 2.6内核中,又增加了一种新的传输层协议:SCTP。从内核角度看,要实现INET6协议族的某种传输层协议,则必须既提供socket层的 struct proto_ops的实现,也提供struct proto的实现。除此之外,还需要提供一种手段, 把这两个结构关联起来, 也就是把 socket 层和传输层关联起来。Linux提供了一个 struct inet_protosw的结构,用于 socket层与传输层的关联。structinet_protoswstructlist_headlist;/*Thesetwofieldsformthelookupkey.*/unsignedshorttype;/*Thisisthe2ndargumenttosocket(2).*/intprotocol;/*ThisistheL4protocolnumber.*/structproto*prot;structproto_ops*ops;intcapability;/*Which(ifany)capabilitydo*weneedtousethissocket*interface?*/charno_check;/*checksumonrcv/xmit/none?*/unsignedcharflags;/*SeeINET_PROTOSW_*below.*/;这个结构定义于include/net/protocol.h中,从它的命名上可以看到它属于 INET 和 INET6 协议族,但是没有查到资料为什么叫做protosw 。这个结构中, ops 指向 socket层的 struct proto_ops, prot指向传输层的struct proto。因此,对 INET6 这种要支持多种传输层协议的协议族,从内核的角度来说,只需要为每一种传输层协议定义相应的struct proto_ops、struct proto,然.后再定义 struct inet_protosw,并将三者关联起来即可。以 INET6 所支持的 TCP 为例:staticstructproto_opsinet6_sockraw_ops=.family=PF_INET6,.owner=THIS_MODULE,.release=inet6_release,.bind=inet6_bind,.connect=inet_dgram_connect,/*ok*/.socketpair=sock_no_socketpair,/*adonothing*/.accept=sock_no_accept,/*adonothing*/.getname=inet6_getname,.poll=datagram_poll,/*ok*/.ioctl=inet6_ioctl,/*mustchange*/.listen=sock_no_listen,/*ok*/.shutdown=inet_shutdown,/*ok*/.setsockopt=sock_common_setsockopt,/*ok*/.getsockopt=sock_common_getsockopt,/*ok*/.sendmsg=inet_sendmsg,/* ok.*/.recvmsg=sock_common_recvmsg,/*ok*/.mmap=sock_no_mmap,.sendpage=sock_no_sendpage,;structprototcpv6_prot=.name= TCPv6,.owner= THIS_MODULE,.close= tcp_close,.connect= tcp_v6_connect,.disconnect= tcp_disconnect,.accept= inet_csk_accept,.ioctl= tcp_ioctl,.init= tcp_v6_init_sock,.destroy= tcp_v6_destroy_sock,.shutdown= tcp_shutdown,.setsockopt= tcp_setsockopt,.getsockopt=.tcp_getsockopt,.sendmsg=tcp_sendmsg,.recvmsg=tcp_recvmsg,.backlog_rcv=tcp_v6_do_rcv,.hash=tcp_v6_hash,.unhash=tcp_unhash,.get_port=tcp_v6_get_port,.enter_memory_pressure=tcp_enter_memory_pressure,.sockets_allocated=&tcp_sockets_allocated,.memory_allocated=&tcp_memory_allocated,.memory_pressure=&tcp_memory_pressure,.orphan_count=&tcp_orphan_count,.sysctl_mem=sysctl_tcp_mem,.sysctl_wmem=sysctl_tcp_wmem,.sysctl_rmem=sysctl_tcp_rmem,.max_header=MAX_TCP_HEADER,.obj_size.=sizeof ( structtcp6_sock),.twsk_obj_size=sizeof ( structtcp6_timewait_sock),.rsk_prot= &tcp6_request_sock_ops,;staticstructinet_protoswtcpv6_protosw=.type=SOCK_STREAM,.protocol=IPPROTO_TCP,.prot=&tcpv6_prot,.ops=&inet6_stream_ops,.capability=-1,.no_check=0,.flags=INET_PROTOSW_PERMANENT,;Linux为INET6 协议族 定义 一 个structinet_protosw的 链表数组inetsw6。要支持某种传输层协议, 首先实现相应的structproto_ops 、structproto ,然 后 实 现structinet_protosw, 将 两 者 关 联 , 最 后 , 通 过inet6_register_protosw(),将此 struct inet_protosw注册到 inet6_sw中。注 册 的 时 候 , 根 据structinet_protosw的type, 将 它 放 到inet6_swtype所在的链表中,相同的type,不同的 protocol,会在同一个链表上。3)数据结构之间的联系从 user space角度看,要使用INET6 协议族的某种传输层协议,首先需要通过 socket()调用创建一个相应的socketfd ,然后再通过这个socketfd ,接收和发送数据。socket()的原型是:int socket(int domain, int type, int protocol);.domain 指定了协议族 .type表明在网络中通信所遵循的模式。主要的值有:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等。SOCK_STREAM是面向流的通信方式, 而 SOCK_DGRAM是面向报文的通信方式。不同的通信方式,在接收数据和发送数据时,具有不同的处理方式。Protocol 则指明具体的传输层协议。不同的传输层协议,可能具有相同的 type ,例如 TCP 和 SCTP 都是 SOCK_STREAM类型。以 socket(PF_INET6, SOCK_STREAM, 0) 为例,在进入内核空间后,根据 domain ,找到 inet6_family_ops。创建 struct socket调用 inet6_family_opsdecreate(),也就是 inet6_create()inet6_create() 根据 type 和 protocol 在 inet6_sw 中找到对应的 struct inet_protosw ,也就是 tcpv6_protosw创建 struct sock,将 struct socket和 struct sock关联起来将 structsocket和 tcpv6_protosw的 ops,也就是 inet6_stream_ops关联起来将 struct sock和 tcpv6_protosw的 prot ,也就是tcpv6_prot关联起来。这样, socket层和传输层的数据结构之间的关系建立起来了,此后,应用层通过socketfd接 收 或 者 发 送 数 据 的 时 候 , 就 会 先 经 过socket层inet6_stream_ops处理,然后经过传输层的tcpv6_prot处理。二 . 网卡接收数据这部分是说明数据报文在在链路层的处理, 以及如何将报文送交给对应的网络层协议来处理。这些功能基本都是在驱动中实现的。1. 网络中接收数据报文的两个终端:硬中断和软中断(1). 硬中断的中断处理函数是在驱动中注册,一般在deviceopen() 函数或者 device init() 函数中注册,使用 request_irq() 来注册硬中断处理函数。当网卡接收到数据的时候,就会调用这个终端处理函数来处理。比如8139too.c函数就用retval= request_irq(dev-irq,rtl8139_interrupt,SA_SHIRQ,dev-name, dev) 来注册硬中断处理函数。(2).软中断是通过NET_RX_SOFTIRQ信号来触发的,处理函数是net_rx_action 。注册函数是 open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL)。触发这个中断信号( raise irq )一般是在硬中断处理流程中,当硬中断处理基本结束的时候, 通过调用 _raise_softirq_irqoff(NET_RX_SOFTIRQ) 来触发这个中断。2. 接收软中断接收软中断 (net_rx_action)主要还是通过调用驱动中的poll的方法进行接收。在 poll方法中,会提取接收包,根据它所在的设备和协议类型传递给各自的包处理器。以rtl8139_poll为例,它会调用rtl8139_rx()来把尽可能多的数据在一次中断处理中处理完,而不是一个软中断只处理一个数据包,这样可以提高效率。每个数据包都会通过netif_receive_skb()函数,根据报文的协议类.型,调用上层的包处理器。如果网卡本身驱动没有poll函数,将是调用bakclog_dev 的 process_backlog函数。3. 包处理器注册包处理器用dev_add_pack() 注册 , 如果注册的设备号是零则表明它接收所有设备的包 ,如果注册的包类型是(ETH_P_ALL),则表示它接收所有类型的包。netif_receive_skb()函数会根 据接 受数 据的协议类型,在ptype_all和ptype_base 列表中去查找对应的处理协议,再将数据包传递给对应的协议处理函数。对于ipv4, 就 是 在af_inet.c中 的inet_init()函数 中, 初始 化了ip_packet_type.func = ip_rcv,因此, ip_rcv()将接收ipv4的报文。在inet_init()中 调 用dev_add_pack(&ip_packet_type)去 在ptype_all和ptype_base 中注册协议处理函数。 ipv4 的 IP 头类型值是 ETH_P_IP:0x0800。对于ipv6, 则在af_inet6.c中的 inet6_init()函数 中完成 初始化,inet6_init()调用 ipv6_packet_init()来注册协议处理报文。 Ipv6 的 IP 头类型值是 ETH_P_IPV60x86DD。其注册的接收处理程序是ipv6_rcv()。三 . 网络层的处理这部分是说明数据报文在网络层的处理。 上面一部分已经说明了在链路层的处理。在链路层的处理,基本都是在驱动中已经实现了的。接着链路层的处理,对于 ipv6 协议,处理过程在 ipv6_recv() 中。ipv6_rcv()中,会做一些必要的检查和更新MIB 的一些信息,接着处理hopbyhop 报头。然后进入NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev,NULL, ip6_rcv_finish);对于 NF_HOOK的作用解释。 如果没有配置 netfilter,可以简单认为 NF_HOOK就等于直接调用ip6_rcv_finish (skb)。Ipv6_rcv()会处理 hopbyhop 报头,在 ipv6_parse_hopopts()函数中处理。注意 ip6_parse_tlv()的处理过程,它本身只处理PAD0和 PAD1的 type ,就是rfc2460 里面最早定义的两个选项,其它选项都是通过tlvprochopopt_lst中定义的回调函数来处理的。这样就能够根据将来协议的发展,灵活的添加新的 hopbyhop 类型,而不需要修改这个函数本身。对于除hopbyhop 以外的扩展报头的处理,是通过路由表来进行的。在ip6_rcv_finish()里面,会调用ip6_route_input(skb),这个函数返回的是路由表中对应的 fib6_node ,这个节点的 input函数,就会根据不同的目的地调用不同的函数来处理。具体说来:(1)If the destination address matches FE80: skb-dst-input=ip6_inputskb-dst-output=ip6_output(2)Else ifthe destinationaddress s first10 bitsmatches FE80:skb-dst-input=ip6_forwardskb-dst-output=ip6_output(3)Else if the destination addresss first 8 bits matches FF00:skb-dst-input=ip6_mc_inputskb-dst-output=ip6_output(4)Else ( no match).skb-dst-input= ip6_pkt_discardskb-dst-output=ip6_pkt_discard对于 ip6_route_input(skb)函数的分析,我们后面讲路由查找的时候再叙述,这里先跳过去。说明一下到本机的路由表项的初始化过程。到本机的路由表的初始化是在给网卡分配 ipv6 地址的时候初始化的, 代码在 addrconf.c中。比如当用户在给网卡手动赋ipv6地址的时候,会通过netlink接口,传递到内核以后,就由rtnetlink_rcv_msg()来处理。 Rtnetlink_recv_msg()会根据family的值,在rtnetlink_linksfamily表中进行查找,找到对应协议簇的处理表。对于ipv6而 言 是PF_INET6 协 议 簇 , 调 用 的 是inet6_rtnetlink_table。 在inet6_rtnetlink_table表中 ,对 应添 加网 卡ipv6地址 的处 理函 数是inet6_rtm_newaddr()函数,因此整个处理过程是inet6_rtm_newaddr()ipv6_add_addr()addrconf_dst_alloc(),从而转入 ip6_input()函数的处理。转发路由表项的初始化和到本机的路由表的初始化过程类似。从netlink再到 ip6_route_add(),添加 ip6_forward()的处理函数。接上面第 2 点,在 ip6_route_input(skb)函数调用中,返回的是路由表中对应的 fib6_node 结构,它会调用 skb-dst-input() 函数。如果数据报文是到本机,这个函数就是 ip6_input() 函数。扩展报头的处理就在 ip6_input() 函数中。然后调用 ip6_input_finish()ipprot-handler(&skb), 然后调用在inet6_protos里面注册了的 ipv6 各个扩展报头的处理函数。在 ipv6 协议簇中的函数调用流程如下图所示。在 tcpv6_init()函数中,通过 inet6_add_protocol()向 inet6_protos注.册了处理函数tcp_v6_rcv(),这样,协议就会交给tcp_v6_rcv ()处理了。这样就交给了传输层的协议栈来处理了。ICMPv6和 UPDv6协议的处理类似。这里注意各个处理函数的返回值,像 icmpv6_rcv ()返回 0,表示这个数据报不再处理了,已经处理完了。而 ipv6_destopt_rcv() 则返回 -1/1 ,-1 表示出错了,就不再处理; 1 表示当前的报头已经处理完了,要接着处理这个数据报的下一个报头。这样,就把报文传送到了传输层了。对于传输层,我们选择一个简单的 updv6 协议,它注册的处理函数是 udpv6_rcv(), 这部分会在传输层的处理中论述。四 . 路由模块的处理路由节点结构是 fib6_node 的结构,通过这个结构来组织成一棵路由树。这个结构主要是用来组织路由结构树的,具体的路由信息是存放在fib6_node-leaf结构中,这是一个rt6_info的结构体。每个fib6_node 伴随着一个 rt6_info。查找路由的时候,遍历整个路由树,根据每个fib6_node节点的 rt6_info信息,判断是否是自己需要的节点。如果是,则返回,然后根据这个节点的 rt6_info信息进行路由。路由表的组织结构如下图所示。这里多说两句,定义 CONFIG_IPV6_SUBTREES情况, fib6_lookup_1 会递归调用,但最多只能递归一次 ( 因为 subtree 里不会再有 subtree) 。递归的那次 fib6_lookup_1 调用只对 src 进行了匹配,因为 args1 里的 addr 是 src 。下图中的蓝色部分,表示每个 fib6_node 都伴随着一个 rt6_info 结构用来携带具体的路由信息。ipv6 的路由表是是一个 radix 树,根对应默认路由,结点的层次和路由 prefix_len 对应。在 fib6_lookup_1() 中下面的循环把 fn 设为叶子结点,然后从他开始匹配,如果不符就 fn = fn-parent 。这样就做到了最长匹配原则。for (;)structfib6_node *next;dir = addr_bit_set(args-addr, fn-fn_bit);next = dir ? fn-right : fn-left;if(next).fn = next;continue ;break ;关于 radix树的介绍,可以google ,这里简单介绍一下,参考了blog :。Radix tree 是一种搜索树,采用二进制数据进行查找,但对于路由表,采用的是二叉树的方式,只有一个 Left 和 right 两个子节点。(好像 fn_bit 表示的是 prefix_len ,就是路由前缀的长度,不确定?)五 . 数据包接收流程分析接收的流程为:ipv6_rcv-ipv6_rcv_finish-dst_input-ip6_input- ip6_input_finish或者 ipv6_rcv-ipv6_rcv_finish-ip6_route_input或者ipv6_rcv-ipv6_rcv_finish-dst_input-ip6_forward- ip6_forward_finishstatic struct packet_type ipv6_packet_type_read_mostly = .type= cpu_to_be16(ETH_P_IPV6),.func= ipv6_rcv,.gso_send_check= ipv6_gso_send_check,.gso_segment= ipv6_gso_segment,.gro_receive= ipv6_gro_receive,.gro_complete= ipv6_gro_complete,;/ 执行一些检查,判断数据包是否转发、有效性、正确性intipv6_rcv(structsk_buff*skb,structnet_device*dev,structpacket_type*pt,struct net_device *orig_dev).structipv6hdr *hdr;u32 pkt_len; structinet6_dev *idev;/ 获取数据包网卡structnet *net = dev_net(skb-dev);/ 丢弃发送给其他主机的数据包if(skb-pkt_type = PACKET_OTHERHOST) kfree_skb(skb);return0;rcu_read_lock();idev= _in6_dev_get(skb-dev);IP6_UPD_PO_STATS_BH(net,idev, IPSTATS_MIB_IN, skb-len);/if(skb = skb_share_check(skb, GFP_ATOMIC) = NULL | !idev |unlikely(idev-cnf.disable_ipv6) IP6_INC_STATS_BH(net,idev,IPSTATS_MIB_INDISCARDS);gotodrop;memset(IP6CB(skb),0, sizeof(struct inet6_skb_parm);/* Store incoming device index. When the packetwill* be queued, we cannot refer to skb-devanymore.* BTW, when we send a packet forour own localaddressona.* non-loopback interface (e.g. ethX), it isbeingdelivered* via the loopback interface (lo) here;skb-dev =loopback_dev.* It, however, should be considered as if itis being* arrivedvia the sending interface(ethX),becauseof the* nature of scoping architecture. -yoshfuji*/ 保存入口设备索引IP6CB(skb)-iif= skb_dst(skb) ?ip6_dst_idev(skb_dst(skb)-dev-ifindex : dev-ifindex;/ 检查数据包长度是否为 IP 报头的长度if(unlikely(!pskb_may_pull(skb, sizeof(*hdr)gotoerr;/ 获取 IPv6 报头位置hdr= ipv6_hdr(skb);/ 检查版本是否为 IPv6if(hdr-version != 6)gotoerr;/* A packet received on an interface with adestinationaddress* of loopback must be dropped.*/ 丢弃环路数据包if(!(dev-flags & IFF_LOOPBACK) &ipv6_addr_loopback(&hdr-daddr)gotoerr;.skb-transport_header= skb-network_header +sizeof(*hdr);IP6CB(skb)-nhoff= offsetof(struct ipv6hdr, nexthdr);pkt_len= ntohs(hdr-payload_len);/ 处理 Jumbo负载选项/*pkt_len may be zero if Jumbo payload option is present*/if(pkt_len | hdr-nexthdr != NEXTHDR_HOP) if(pkt_len+ sizeof(structipv6hdr)skb-len) IP6_INC_STATS_BH(net,idev, IPSTATS_MIB_INTRUNCATEDPKTS);gotodrop;if(pskb_trim_rcsum(skb, pkt_len +sizeof(struct ipv6hdr) IP6_INC_STATS_BH(net,idev, IPSTATS_MIB_INHDRERRORS);gotodrop;hdr= ipv6_hdr(skb);if(hdr-nexthdr = NEXTHDR_HOP) if(ipv6_parse_hopopts(skb) dst-input指向的函数;否则,调用ip6_rout_input函数查找路由表,返回skb-dst-input的具体内容inlineint ip6_rcv_finish( struct sk_buff *skb)if(skb_dst(skb) = NULL)ip6_route_input(skb);returndst_input(skb);/ 获取目的地址描述符static inlinestruct dst_entry *skb_dst(const struct sk_buff *skb).return (struct dst_entry*)skb-_skb_dst;/* Input packetfrom network to transport.将数据包从网络层送到传输层*/static inline int dst_input(struct sk_buff *skb)return skb_dst(skb)-input(skb);/ 交给路由模块void ip6_route_input(struct sk_buff *skb)struct ipv6hdr *iph = ipv6_hdr(skb);struct net *net = dev_net(skb-dev);int flags = RT6_LOOKUP_F_HAS_SADDR;struct flowi fl = .iif = skb-dev-ifindex,.nl_u = .ip6_u = .daddr =iph-daddr,.saddr =iph-saddr,.flowlabel= (* (_be32 *) iph)&IPV6_FLOWINFO_MASK,.mark = skb-mark,.proto = iph-nexthdr,;.if (rt6_need_strict(&iph-daddr)& skb-dev-type !=ARPHRD_PIMREG)flags |= RT6_LOOKUP_F_IFACE;skb_dst_set(skb, fib6_rule_lookup(net,&fl, flags, ip6_pol_route_input);intip6_input(struct sk_buff *skb)returnNF_HOOK(PF_INET6, NF_INET_LOCAL_IN,skb, skb-dev,NULL,ip6_input_finish);staticint ip6_input_finish(struct sk_buff *skb)structinet6_protocol *ipprot;unsignedint nhoff;intnexthdr, raw;u8hash;structinet6_dev *idev;structnet *net = dev_net(skb_dst(skb)-dev);/*Parseextension headers解析扩展头*/rcu_read_lock();resubmit:/ 指向目的设备idev= ip6_dst_idev(skb_dst(skb);./ 检查数据包长度是否是传输层头部if(!pskb_pull(skb, skb_transport_offset(skb)gotodiscard;/ #define IP6CB(skb)(struct inet6_skb_parm*)(skb)-cb)nhoff= IP6CB(skb)-nhoff;/ 获取下一个扩展头部nexthdr= skb_network_header(skb)nhoff;/*int raw6_local_deliver(struct sk_buff *skb,int nexthdr)structsock *raw_sk;/ 判断可否通过原始套接字接受数据,如果可以,则返回对应原始套接字,并通过ipv6_raw_deliver函数接受套接字缓冲区中的数据内容raw_sk= sk_head(&raw_v6_hashinfo.htnexthdr & (MAX_INET_PROTOS - 1);if(raw_sk & !ipv6_raw_deliver(skb, nexthdr)raw_sk= NULL;returnraw_sk != NULL;*/raw= raw6_local_deliver(skb, nexthdr);/ 查找 inet6_protos表,确定是否注册过第四层协议,如果有,则调用对应的函数来接受数据包hash= nexthdr & (MAX_INET_PROTOS - 1);if(ipprot= rcu_dereference(inet6_protoshash)!= NULL)intret;if(ipprot-flags& INET6_PROTO_FINAL).structipv6hdr*hdr;/*Free referenceearly: we dont need it any more,and it mayhold ip_conntrack module loadedindefinitely.*/nf_reset(skb);skb_postpull_rcsum(skb,skb_network_header(skb),skb_network_header_len(skb);hdr=ipv6_hdr(skb);if(ipv6_addr_is_multicast(&hdr-daddr) &!ipv6_chk_
展开阅读全文