suricata 4.0.3 收包解码
suricata中receive和decode两个模块总是在一个线程中,而且每种receive模块都对应一个自己的decode模块,因此这里记录在一起。这两个模块的主要目标是根据收取的数据包填充合适的Packet结构,交由flowworker进行后续处理。decode阶段同时做了分片数据包重组、隧道数据包解封装等处理。
Packet
整个数据包的处理流程都是围绕Packet结构体来操作的,先看一下这个结构体,其他关联的结构比较简单,不做更多记录。
1 | /* sizes of the members: |
- src
- dst
src和dst保存了三层地址的地址族类型和地址数据。 - sp、dp
sp和dp保存了四层端口号。 - type、code
type和code保存了icmpv4协议的type和code。其中type和sp共用一个联合体,code和dp共用一个联合体。 - proto
三层数据包载荷的协议类型。一般来说是一个四层协议类型,但如果是一个隧道包那这里会是隧道内层数据包的类型,就不是四层协议类型了。 - recursion_level
指示了当前Packet经历了几次隧道封装。普通数据包这个值是0. - vlan_id
长度为2的数组,存储了vlan的id,允许vlan嵌套,数组中的两项分别代表该嵌套层的vlan id。 - vlan_idx
标记了当前的vlan嵌套层数。最多2层。 - flowflags
标记了当前packet与flow相关的一些标识。- FLOW_PKT_TOSERVER
- FLOW_PKT_TOCLIENT
- FLOW_PKT_ESTABLISHED
- FLOW_PKT_TOSERVER_IPONLY_SET
- FLOW_PKT_TOCLIENT_IPONLY_SET
- FLOW_PKT_TOSERVER_FIRST
- FLOW_PKT_TOCLIENT_FIRST
- flags
标识了当前Packet的flag,后面列举了各个flag。 - flow
关联的flow。 - flow_hash
数据包匹配相应的flow时使用的hash值,tcp/udp/sctp/icmp会设定该项。使用源目的地址、源目的端口、协议号、隧道递归值、vlan_id数组、系统初始化随机数计算得来。 - ts
记录了数据包的时间。 - nflog_v
- nfq_v
- ipfw_v
- afp_v
- mpipe_v
- netmap_v
- pcap_v
上面几个用于记录收包阶段时各个收包模块特有的数据。 - ReleasePacket
函数指针,当数据包需要被释放时调用。可能是释放回原线程的PktPool中,也可能是直接释放内存。 - BypassPacketsFlow
TODO - pktvar
TODO - ethh
二层以太网包头指针。 - level3_comp_csum
- level4_comp_csum
TODO - ip4h
三层ipv4包头指针。 - ip6h
三层ipv6包头指针。 - ip4vars
保存了ipv4的可选项信息,如果存在可选项则写入该成员。- opt_cnt
保存了可选项的数量。 - opts_set
保存了有哪些可选项的标记。以IPV4_OPT_FLAG_
开头的一组枚举型标识。
- opt_cnt
- ip6vars
- ip6eh
这两个成员组成了一个结构体,这个结构体与ip4vars组成联合体,可以看出是ipv6的扩展选项,目前不关注。 - tcpvars
- icmpv4vars
- icmpv6vars
四层协议的扩展项。 - tcph
四层tcp协议头。 - udph
四层udp协议头。 - sctph
四层sctp协议头。 - icmpv4h
四层icmpV4协议头。 - icmpv6h
四层icmpV6协议头。 - ppph
二层ppp协议头。上层支持ipv4与ipv6协议。 - pppoesh
pppoe会话协议头。位于二层以太网之上,内部封装支持ipv4和ipv6。 - pppoedh
pppoe发现协议头。位于二层以太网之上。 - greh
gre封装协议头。位于三层ipv4之上,内部封装支持的比较多。 - vlanh
长度为2的数组,vlan头指针,与上面的vlan_idx组合,与vlan_id数组对应。 - payload
四层数据包的载荷,比如tcp或udp协议内的载荷。 - payload_len
四层数据包的载荷长度。 - action
IPS模式下,标识了要对这个数据包做的处理动作。动作定义在action-globals.h
中。 - pkt_src
标识数据包的来源。比如PKT_SRC_WIRE表示直接收到的包,PKT_SRC_DECODER_GRE表示是由GRE解封装得到的伪造数据包。 - pktlen
收取的数据包字节长度 - ext_pkt
这个成员有一点特殊。Packet结构体在创建并分配内存时预分配了更多的default_packet_size字节的内存,也就是说当收取的数据包长度不大于default_packet_size时可以使用Packet结构体后的内存空间用以拷贝存储数据包数据。但是当数据包长度超出了default_packet_size时,将分配一块MAX_PAYLOAD_SIZE大小的内存块,地址赋值给ext_pkt成员,用以拷贝存储数据包数据,当数据包内存释放或归还到池子后重用时ext_pkt指向的内存将释放。另外如果数据包是使用零拷贝映射的,那么ext_pkt成员用于指向数据包内存地址,这时该指向的内存不能由用户释放。 - livedev
指向数据包来源的一个LiveDevice结构。 - alerts
TODO - host_src
- host_dst
TODO - pcap_cnt
pcap文件中的数据包序号。 - events
记录解码、分片重组、和stream时的event。event类型记录在decode-events.h
中。- cnt
当前记录了的event的数量。 - events
PACKET_ENGINE_EVENT_MAX长度的数组,顺序记录event
- cnt
- app_layer_events
TODO - next
- prev
next和prev两个成员可以将Packet链接起来,PktPool和PacketQueue结构都有使用。 - datalink
标识了收包设备的二层类型。决定了收取数据包首部的header结构类型。 - root
如果一个数据包是隧道数据包或封装数据包,解封装后会生成一个新的伪造数据包进入后续模块处理,这时新的伪造数据包的root成员会设置为指向最原始的那个数据包,也就是说如果是多层封装,那么后续的解封装后的伪造数据包root成员们都会指向最底层的那一个原始数据包。 - tunnel_mutex
锁,用于保护另外两个成员tunnel_rtv_cnt和tunnel_tpr_cnt的并发访问。 - tunnel_rtv_cnt
标识这个数据包做为其他数据包的root时,生成的新伪造数据包处理完成被回收(retrieve)的个数。只增不减。 - tunnel_tpr_cnt
标识这个数据包作为其他数据包的root被引用的次数,与root和tunnel_rtv_cnt配置实用。只增不减。 - tenant_id
TODO - pool
这个数据包所属的PktPool,在释放时会返回挂载到这个池子中。 - profile
- cuda_pkt_vars
- ntpv
TODO
1 | /*Packet Flags*/ |
收包
以pcap和af-packet实时嗅探为例,收包过程对Packet的填充如下。
- 取得一个空的Packet实例。从线程专有的PktPool中取得,池子空时新分配内存,这个新分配的Packet内存在使用结束后将直接释放。
- 设置Packet的pkt_src成员为PKT_SRC_WIRE,表示由设备线路获取。
- 设置Packet的ts成员记录时间。
- 设置Packet的datalink成员记录二层链路层类型。
- 设置Packet的livedev成员,指向收包设备LiveDevice实例。
- 如果使用的收包设备接口在接收到的数据包头部支持提取vlan信息,则设置Packet的vlan_id、vlan_idx成员。
- 设置Packet的pktlen成员,记录收取数据包长度。
- 设置Packet的数据包原始数据。
- 如果是零拷贝方式,设置Packet的ext_pkt成员指向映射内存的数据包头地址,同时设置flags成员增加标识PKT_ZERO_COPY。
- 如果需要拷贝数据,根据情况选择是将数据拷贝到Packet内存尾部还是分配内存给ext_pkt成员用以拷贝数据。
- 根据情况选择是否为Packet的flags成员设定标识PKT_IGNORE_CHECKSUM。如果符合忽略数据包校验的条件,会置PKT_IGNORE_CHECKSUM标记。比如配置文件中的checksum-checks设置为no,或设置auto的同时统计了足够比例的校验失败数据包。
- 如果收包模块一段时间没有收到数据包,并且detect engine需要reload时,收包模块会调用TmThreadsCaptureInjectPacket函数,创建一个伪造数据包并将flasg成员增加PKT_PSEUDO_STREAM_END标记,数据包将交付给后续模块处理,这个数据包会流经整个引擎并使detect engine完成reload过程。
- 收包模块生成的Packet由TmThreadsSlotProcessPkt函数交由后续模块处理。参考之前的线程模型介绍
解码
以pcap和af-packet实时嗅探为例,解码过程对Packet的填充如下。
- 检查Packet中PKT_PSEUDO_STREAM_END标记,如果存在则直接完成decode过程进入下一阶段。
- 判断Packet中datalink,选择不同的解码函数,各个链路类型的解码函数对于不同的decode模块是通用的,也就是说各个receive模块对应了自己的decode模块,但是这个decode模块是很轻的,因为主要的解码函数是通用的。
pcap和af-packet支持的二层链路类型相同,以下。- LINKTYPE_LINUX_SLL
- LINKTYPE_ETHERNET
- LINKTYPE_PPP
- LINKTYPE_RAW
- LINKTYPE_NULL
下面记录部分常用协议的解码。
链路层解码
DecodeEthernet
- 设置Packet成员ethh,类型为EthernetHdr指针。
- 判断以太网头中的类型字段,选择上层数据解码函数,支持以下。
- ETHERNET_TYPE_IP
- ETHERNET_TYPE_IPV6
- ETHERNET_TYPE_PPPOE_SESS
- ETHERNET_TYPE_PPPOE_DISC
- ETHERNET_TYPE_VLAN
- ETHERNET_TYPE_8021QINQ
- ETHERNET_TYPE_MPLS_UNICAST
- ETHERNET_TYPE_MPLS_MULTICAST
- ETHERNET_TYPE_DCE
1 | typedef struct EthernetHdr_ { |
DecodeSll
- 通过头部内存SllHdr结构选择上层数据解码函数,支持以下。
- ETHERNET_TYPE_IP
- ETHERNET_TYPE_IPV6
- ETHERNET_TYPE_VLAN
1 | typedef struct SllHdr_ { |
DecodePPP
- 设置Packet成员ppph,类型为PPPHdr指针。
- 判断头中协议字段,选择上层解码函数,支持以下。
- PPP_VJ_UCOMP
ipv4解码 - PPP_IP
ipv4解码 - PPP_IPV6
ipv6解码
- PPP_VJ_UCOMP
1 | /** PPP Packet header */ |
DecodeRaw
这里数据包直接就是三层数据包,该函数只支持ipv4与ipv6,通过判断头部版本号区分。
DecodeNull
二层链路类型为LINKTYPE_NULL时使用此解码函数,数据包首部4字节代表了上层数据的协议族,比如PF_INET、PF_INET6,这里字节序使用的是主机序,比如x86为小端序。这里只支持ipv4与ipv6。
DecodeVLAN
- 设置Packet成员vlanh,根据vlan_idx选择vlanh数组中的项。
- 设置Packet成员vlan_id,根据vlan_idx选择vlan_id数组中的项。
- 加一Packet成员vlan_idx,标记当前vlan层数。suricata中只允许两层。
- 判断vlan头中协议类型字段,选择上层解码函数,支持以下。
- ETHERNET_TYPE_IP
- ETHERNET_TYPE_IPV6
- ETHERNET_TYPE_PPPOE_SESS
- ETHERNET_TYPE_PPPOE_DISC
- ETHERNET_TYPE_VLAN
- ETHERNET_TYPE_8021AD
- ETHERNET_TYPE_8021AH
网络层解码
DecodeIPV4
- 设置Packet成员ip4h。类型为IPV4Hdr指针。
- 设置Packet成员src、dst,写入地址族类型及地址。
- 设置Packet成员ip4vars(如果存在ipv4协议可选项的话)。
- 设置Packet成员proto,记录ipv4载荷协议类型。可能是tcp、udp这类传输层协议,也可能是GRE这类隧道协议。
- 如果这是一个IP分片,进入分片重组流程,增加flags标记PKT_IS_FRAGMENT,直接返回不再继续进行载荷解码。(如果这里重组完成了一个新的伪造数据包,则将数据包置入模块的slot_pre_pq队列中。)
- 如果不是一个IP分片,判断载荷协议类型,选择上层解码函数,支持以下。
- IPPROTO_TCP
- IPPROTO_UDP
- IPPROTO_ICMP
- IPPROTO_GRE
- IPPROTO_SCTP
- IPPROTO_IPV6
- IPPROTO_IP
看注释这里与ppp协议有关,载荷按照tcp解码。不了解ppp协议,不关注这里。
1 | typedef struct IPV4Hdr_ |
DecodeIPV6
IPv6协议不够熟悉,而且对这方面需求不大,暂时不做分析。
传输层解码
DecodeTCP
- 设置Packet成员tcph。类型为TCPHdr指针。
- 设置Packet成员tcpvars(如果存在tcp可选项的话)。
- 设置Packet成员sp、dp,tcp源端口目的端口。
- 再次设置Packet成员proto为IPPROTO_TCP。
- 设置Packet成员payload,指向tcp载荷数据。
- 设置Packet成员payload_len,tcp载荷数据长度。
- 设置Packet成员flags增加标记PKT_WANTS_FLOW。
- 设置Packet成员flow_hash。
1 | typedef struct TCPHdr_ |
DecodeUDP
- 设置Packet成员udph,类型为UDPHdr指针。
- 设置Packet成员sp、dp,udp源端口目的端口。
- 设置Packet成员payload,指向udp载荷数据。
- 设置Packet成员payload_len,udp载荷数据长度。
- 再次设置Packet成员proto为IPPROTO_UDP。
- 这里会判断是否是Teredo隧道包,我暂时不关注Teredo包的处理。
- 如果非Teredo协议数据包
- 设置Packet成员flags增加标记PKT_WANTS_FLOW。
- 设置Packet成员flow_hash。
1 | /* UDP header structure */ |
DecodeICMPV4
- 设置Packet成员icmpv4h。类型为ICMPV4Hdr指针。
- 再次设置Packet成员proto为IPPROTO_ICMP。
- 设置Packet成员type为icmpv4h中的type。
- 设置Packet成员code为icmpv4h中的code。
- 设置Packet成员payload,指向icmpv4载荷。
- 设置Packet成员payload_len,icmpv4载荷长度。
- 根据type类型设置Packet成员icmpv4vars。
1 | /* ICMPv4 header structure */ |
隧道数据包
隧道数据包处理的核心逻辑就是,对封装的载荷创建一个新的Packet结构并合理填充,将其放入模块的slot_pre_pq队列。原始数据包标记为不需要继续分析,然后结束原始数据包的解码。下面记录一些填充细节。
GRE
通用路由封装协议是一种隧道协议,可以用于将网络层数据报封装起来,使这些被封装的数据报能在IPv4网络上传输。
DecodeGRE
- 设置Packet成员greh,类型为GREHdr指针。
- 判断GRE版本号,做一系列校验。同时根据各个选项,确定gre头占用的确定长度。
- 判断GRE内部承载的协议类型,调用PacketTunnelPktSetup函数,这个函数将返回一个新的Packet结构,我们可以命名为tp(tunnel packet)。
- 如果返回的tp不为NULL,设置tp的成员pkt_src为PKT_SRC_DECODER_GRE,表示这个数据包是由gre隧道解封装得来的,是一个新建的伪造数据包。
- 将tp放入模块的slot_pre_pq队列,入队列后的处理逻辑参考之前的线程模型介绍
针对隧道数据包的处理都经由PacketTunnelPktSetup函数,这里记录该函数的主要逻辑。原数据包我们命名为parent,新数据包延续前文命名为tp。
- 取得一个新的空Packet,tp。
- 将parent中的载荷数据拷贝到tp中,同时设置tp的成员pktlen。
- 设置tp成员recursion_level为parent中的同名成员加一。
- 设置tp成员ts与parent中的值一致。
- 设置tp成员datalink为DLT_RAW,这表示载荷中没有链路层头,直接就是网络层头。
- 设置tp成员tenant_id与parent中的值一致。
- 设置tp成员root,设置为最底层的root。也就是说如果父数据包root不为NULL则设置为父的root,否则设置为父数据包。这里另外说一句,root指向的Packet的tunnel_tpr_cnt将加一,用以标识有多少个数据包引用了它。
- 设置tp成员flags增加标记PKT_TUNNEL。
- 对tp调用函数DecodeTunnel。
- 设置parent成员flags增加标记PKT_TUNNEL。
- 设置root中的tunnel_tpr_cnt加一,用以记录root数据包被引用的次数。
- 设置parent成员flags增加标记PKT_NOPAYLOAD_INSPECTION。
- 返回tp。
函数DecodeTunnel很简单,根据传入的DecodeTunnelProto类型的参数proto,选择具体的解码函数继续填充新建的伪造数据包tp。比如参数值DECODE_TUNNEL_IPV4对应的解码函数为DecodeIPV4。这里支持的参数如下。
- DECODE_TUNNEL_ETHERNET,
- DECODE_TUNNEL_ERSPAN
- DECODE_TUNNEL_VLAN
- DECODE_TUNNEL_IPV4
- DECODE_TUNNEL_IPV6
- DECODE_TUNNEL_PPP
1 | typedef struct GREHdr_ |
IPv6 over IPv4
IPv6 over IPv4的解码逻辑与gre解码逻辑几乎一样,同样都由调用PacketTunnelPktSetup生成解封装后的数据包然后放入模块的slot_pre_pq队列,区别只在于两层间少了解码gre头这一个步骤。
其他隧道模式同理。
分片数据包
这里仅关注IPv4的分片处理。IPv6不做关注。
分片数据包处理围绕tracker进行,一个tracker保存了该ip数据包的所有分片(frag),使用中的tracker保存在一个hash table中,hash冲突使用链表解决,在这里链表被称为行,空闲的tracker都保存在一个队列中。frag拷贝了Packet中的数据,空闲的frag保存在一个池子中,这个池子也是全局的。tracker和frag的操作都可能多线程并发,因此都有锁保护。多线程锁的使用和frag的数据拷贝可能影响suricata性能。
分片相关初始化
main -> PostConfLoadedSetup -> PreRunInit -> DefragInit。这里是分片相关的初始化运行位置。
- 依据配置文件中defrag.host-config段,以ip网段为key,以timeout为value,插入一个基数树种,树根为静态全局变量defrag_tree。
- defrag_context静态全局变量初始化。
- frag_pool。存储了空闲frag的池子,池子数量上限为配置文件项defrag.max-frags(默认65534),池子预先创建了数量上限一半的frag内存。
- frag_pool_lock。并发访问用的保护锁。
- timeout。tracker的过期时间,配置文件项defrag.timeout(默认60秒)。
- default_timeout静态环境变量值设置为上面的timeout,用于host的默认timeout。
- defrag_config静态全局变量初始化。
- hash_rand。随机数。
- hash_size。tracker使用的hash table的行数。配置文件defrag.hash-size,默认4096。
- memcap。tracker使用的内存上限。配置文件defrag.memcap,默认16M。
- prealloc。tracker预分配数量。配置文件defrag.trackers,默认1000。
- defragtracker_hash。tracker的全局hash table。分配内存并置0,初始化每行的锁。
- defragtracker_counter。使用中的tracker计数。
- defrag_memuse。tracker和其hash table所使用的内存总量。跟随hash table及tracker的预创建而变动。
- defragtracker_prune_idx。从使用中的tracker中选出一个清空并再次使用时,使用该值选择遍历开始的hash table行号。
- defragtracker_spare_q。空闲tracker队列。配置文件项defrag.prealloc为真时将预分配defrag_config.prealloc个数的tracker到这个空闲队列。
分片数据包处理
- 在DecodeIPV4解码中,会检查ipv4头的offset和mf(more fragment)标记
- 如果是分片数据包,调用函数Defrag,这个函数将返回一个新的Packet结构,我们可以命名为rp(reassembly packet)。
- 如果rp不为NULL,则放入模块的slot_pre_pq队列。
- 原始数据包增加标记PKT_IS_FRAGMENT。
分片数据包的处理逻辑集中在函数Defrag内,这个函数是ipv4与ipv6通用的。
- 调用DefragGetTracker获得数据包的tracker,如果tracker为NULL则返回NULL。
- 调用DefragInsertFrag插入数据包到tracker中,如果在加入此数据包后分片重组成功则函数返回rp,既重组完成的Packet。如果返回NULL则说明重组没有完成。
- 调用DefragTrackerRelease释放tracker。
DefragGetTracker
tracker有一个空闲链表,在使用的tracker放在一个hash table中,table中每一行对应一个hash值,Packet依次比较这一行中的tracker,匹配的tracker移动到队列头。行有锁,tracker有锁。
查找一个合适的tracker时,先从hash table中取得行,对该行上锁。hash key使用Packet的源目的地址、ip包id、vlan_id、系统初始化的随机数,这5项计算得到。
- 如果该行空,则尝试获取一个新tracker。
- 如果没能拿到新tracker,则释放行锁,返回NULL。
- 如果拿到新tracker,则将该tracker插入行,对tracker调用DefragTrackerInit初始化数据,释放行锁,返回tracker。
- 如果该行不空,则顺序比较该行的每个tracker
- 如果匹配到tracker,将该tracker移动到行首,锁定该tracker,对tracker成员use_cnt加一,释放行锁,返回tracker。
- 如果到末尾仍没有匹配到,则尝试获取新tracker。
- 如果没能拿到新tracker,则释放行锁,返回NULL。
- 如果拿到新tracker,将该tracker挂载到行尾部,对tracker调用DefragTrackerInit进行初始化数据,释放行锁,返回tracker。(这里很奇怪,匹配到的活跃tracker移动到头部,新的活跃tracker却挂载到尾部,我估计是忘改了)
尝试获取新tracker的流程如下:
- 从空闲链表中尝试获取tracker。
- 如果没取到,检查当前全局tracker内存用量是否达到配置限额。
- 内存使用未达到限额,尝试分配内存。
- 再次检查,如果仍未达到限额则分配内存,增加全局tracker内存用量,对tracker内存置零,初始化锁,返回tracker。如果检查达到限额则返回NULL(这里完全是吃饱了撑的)
- 尝试从在使用中的tracker中清理出一个。
- 从defragtracker_prune_idx所以开始遍历tracker的hash table的每一行。
- 尝试对行上锁,如果上锁失败则尝试下一行。
- 上锁成功后,检查是不是空行,如果是空行则释放行锁尝试下一行。
- 尝试上锁行尾的tracker,如果上锁失败则释放行锁尝试下一行。
- 上锁tracker成功后,检查track成员use_cnt,如果不大于零则将tracker从行上移除,释放行锁,将该tracker上的分片frag释放回池子,释放tracker锁,增加defragtracker_prune_idx值,增加的值为尝试的行数,返回tracker。
- 如果尝试了所有行仍未成功取到tracker,则返回NULL。
- 内存使用未达到限额,尝试分配内存。
- 如果前面的操作有取到tracker,增加全局的tracker使用计数,上锁并返回该tracker。
- 如果没取到,检查当前全局tracker内存用量是否达到配置限额。
DefragTrackerInit初始化tracker数据:
- 设置tracker成员源地址目的地址。
- 设置tracker成员id为ip的id。
- 设置tracker成员af为地址族。
- 设置tracker成员proto为ip数据包载荷的协议。
- 设置tracker成员vlan_id数组。
- 设置tracker成员policy,该tracker所匹配上的host-os,也就是认为这个tracker追踪的数据包归属于哪种操作系统。匹配规则来源于配置文件项host-os-policy。
- 设置tracker成员host_timeout,该tracker的timeout值,从前文提过的defrag_tree得到,有默认值。
- 设置tracker成员remove和seen_last都置0.
- 初始化tracker成员frags队列。
- 设置tracker成员use_cnt加一。
DefragInsertFrag
- 检查分片重组后长度如果超过允许的IP包最大长度,则返回NULL。
- 更新tracker成员timeout,每次有新分片到来都会更新此项。
- 根据tracker成员policy,检查该Packet与已经保存的前后分片的数据是否有相互覆盖,更新相关frag成员的skip项,如果本数据包数据有需要做ltrim(左端一定长度的数据无用)的,记录需要ltrim的数据。
- 从frag池子中取出一个新的frag,如果取出NULL,则返回NULL。
- 设置frag成员pkt,分配当前Packet数据长度的内存,如果失败返还frag并返回NULL。
- 将Packet中去掉ltrim部分的数据拷贝到frag成员pkt中。
- 设置frag成员len。
- 设置frag成员hlen、offset、data_offset、data_len、ip_hdr_offset、frag_hdr_offset、more_frags。
- 将该frag插入tracker成员frags上,这里根据frag的offset顺序排列。
- 如果more_frag为假,设置tracker成员seen_last为一。
- 如果tracker的seen_last是一,IPv4协议的话将调用Defrag4Reassemble尝试重组分片,这个函数返回Packet类型指针。
- 如果上一步返回非NULL,则调用DecodeIPV4对重组数据包做解码。解码成功后对原数据包调用函数PacketDefragPktSetupParent,增加Packet成员flags标记PKT_TUNNEL和PKT_NOPAYLOAD_INSPECTION,增加成员tunnel_tpr_cnt计数。
- 如果之前重组分片返回了Packet指针,则将其返回,否则返回的将是NULL。
分片重组工作在函数Defrag4Reassemble中进行。
- 检查有没有seen_last,既有没有接收到最后一个分片。
- 检查分片见有没有空洞。
- 取得一个新的Packet,这里命名为rp。以原始数据包为参考,设置rp成员,包括:
- root,这个参考隧道数据包的root,逻辑一致。
- recursion_level,与原始数据包一致,不做增加。
- ts,与原始数据包一致。
- datalink,设置为DLT_RAW。
- tenant_id,与原始数据包一致。
- flags,增加PKT_TUNNEL标记。
- vlan_id,vlan_idx,与原始数据包一致。
- 如果取得rp失败,进入清空tracker错误处理流程。
- 设置rp成员pkt_src为PKT_SRC_DEFRAG,记录其来源是分片重组。
- 设置rp成员flags,增加PKT_REBUILT_FRAGMENT标记。
- 按顺序将tracker上挂载的frag中的数据拷贝到rp成员ext_pkt合适的位置上。
- 设置rp成员ip4h。ip4h中的ip_len、ip_off、ip_csum需要重新计算。
- 设置rp成员pktlen。
- 设置tracker成员remove置1。
- 调用DefragTrackerFreeFrags释放tracker及其中的frag。
- 返回rp。
函数Defrag4Reassemble的清空tracker错误处理流程比较简单:
- tracker成员remove置1。
- 调用函数DefragTrackerFreeFrags清空tracker中的所有frag。顺序移除所有的frag,frag的成员pkt释放内存,frag置0,将frag放回池子。
- 如果之前已经取得了rp,将rp释放,具体是释放内存还是放回池子取决于rp的flags与释放函数。
DefragTrackerRelease
这里只做了两步操作。
- 对tracer的use_cnt项减一。
- 释放tracker的锁。