suricata的Decode协议解码流程源码分析
在《suricata中DPDK收发包源码分析1》和《suricata中DPDK收发包源码分析2》已经对DPDK收包线程第一个模块 -- ReceiveDPDK模块的源码进行了分析,今天我们一起来分析一下第二个模块 -- DecodeDPDK解码模块的源码。
Decode解码的源代码调用栈如下:
DecodeDPDK(解码模块入口)
-->DecodeLinkLayer
-->DecodeEthernet(以太网层)
-->DecodeNetworkLayer(网络层)
-->DecodeIPV4
-->DecodeIPV4Packet
-->Defrag(IPv4分片包做重组)
-->DecodeTCP
-->DecodeTCPPacket(TCP报文解析)
-->FlowSetupPacket(设置建流标记和哈希值)
-->DecodeUDP
-->DecodeUDPPacket(UDP报文解析)
-->DecodeTeredo (Teredo隧道解码)
-->PacketTunnelPktSetup(构造一个pseudo隧道伪装报文)
-->DecodeTunnel(隧道包解码)
-->DecodeGeneve(Geneve隧道解码)
-->PacketTunnelPktSetup(构造一个pseudo隧道伪装报文)
-->DecodeVXLAN(VXLAN隧道解码)
-->PacketTunnelPktSetup(构造一个pseudo隧道伪装报文)
-->FlowSetupPacket(设置建流标记和哈希值)
-->DecodeICMPV4
-->DecodePartialIPV4(ICMP差错报文解析,不可达报文、参数错误、超时报文等等)
-->FlowSetupPacket(设置建流标记和哈希值)
-->DecodeGRE
-->PacketTunnelPktSetup(构造一个pseudo隧道伪装报文)
-->DecodeSCTP
-->DecodeESP
-->DecodeIPV6
-->DecodePPPOESession
-->DecodePPPOEDiscovery
-->DecodeVLAN
-->DecodeIEEE8021ah
-->ETHERNET_TYPE_ARP
-->DecodeMPLS
一般报文的解码流程比较简单,先确定报文的起始位置和总长度,这两个值在ReceiveDPDKLoop中PacketSetData函数,将DPDK mbuf的报文起始地址和报文总长度赋值给了suricata报文Pakcet结构体的ext_pkt和pktlen。
inline int PacketSetData(Packet *p, const uint8_t *pktdata, uint32_t pktlen)
{
SET_PKT_LEN(p, (size_t)pktlen);
if (unlikely(!pktdata)) {
return -1;
}
// ext_pkt cannot be const (because we sometimes copy)
p->ext_pkt = (uint8_t *) pktdata;
p->flags |= PKT_ZERO_COPY;
return 0;
}
DPDK解码流程的函数入口是DecodeDPDK,解码流程按照以太网层、网络层、传输层以及负载一层层找到报文中指定的位置,并且记录相关字段,最后在FlowSetupPacket函数中根据建流的需要打上相应的标记以及计算出流表的哈希值。
void FlowSetupPacket(Packet *p)
{
p->flags |= PKT_WANTS_FLOW; //设置 要建流的标记
p->flow_hash = FlowGetHash(p); //计算流的哈希值
}
需要特别说明的是解码时对隧道报文的处理,在DecodeIPV4中有对gre报文的处理, 在DecodeUDP中有针对Teredo、Geneve和VXLAN隧道报文的处理,还有IPv4 in IPv6, IPv6 in IPv4以及IPv6 in IPv6等隧道, 都会针对隧道报文调用PacketTunnelPktSetup重新生成一个新的Packet,然后调用PacketEnqueueNoLock将这个报文放入队列中,在收包线程TmThreadsSlotVarRun函数调用Decode这个slot后,通过PacketDequeueNoLock取出队列中的报文再次进行slot的调用处理。在后续代码中,涉及到pseudo报文的处理流程,都是针对隧道或者重组这种特殊构造的报文的处理。
报文Decode协议解码后,相关字段都存在Packet结构体中,具体字段含义如下:
typedef struct Packet_
{
/* Addresses, Ports and protocol
* these are on top so we can use
* the Packet as a hash key */
Address src; //ipv4或ipv6源地址
Address dst; //ipv4或ipv6目的地址
union {
Port sp; //源端口
// icmp type and code of this packet
struct {
uint8_t type; //icmp type
uint8_t code; //icmp code
} icmp_s;
};
union {
Port dp; //目的端口
// icmp type and code of the expected counterpart (for flows)
struct {
uint8_t type; //icmp type
uint8_t code; //icmp code
} icmp_d;
};
uint8_t proto; //传输层协议号 tcp/udp/icmp等
/* make sure we can't be attacked on when the tunneled packet
* has the exact same tuple as the lower levels */
uint8_t recursion_level;
uint16_t vlan_id[2]; //内外层vlan
uint8_t vlan_idx; //vlan层数
/* flow */
uint8_t flowflags; //流相关标记
/* coccinelle: Packet:flowflags:FLOW_PKT_ */
/* Pkt Flags */
uint32_t flags; //包的处理标记
struct Flow_ *flow; //流指针
/* raw hash value for looking up the flow, will need to modulated to the
* hash size still */
uint32_t flow_hash; //流哈希值
SCTime_t ts; //包的时间戳
union {
/* nfq stuff */
#ifdef HAVE_NFLOG
NFLOGPacketVars nflog_v;
#endif /* HAVE_NFLOG */
#ifdef NFQ
NFQPacketVars nfq_v;
#endif /* NFQ */
#ifdef IPFW
IPFWPacketVars ipfw_v;
#endif /* IPFW */
#ifdef AF_PACKET
AFPPacketVars afp_v;
#endif
#ifdef HAVE_NETMAP
NetmapPacketVars netmap_v;
#endif
#ifdef HAVE_PFRING
#ifdef HAVE_PF_RING_FLOW_OFFLOAD
PfringPacketVars pfring_v;
#endif
#endif
#ifdef WINDIVERT
WinDivertPacketVars windivert_v;
#endif /* WINDIVERT */
#ifdef HAVE_DPDK
DPDKPacketVars dpdk_v; //DPDK的包变量,用于报文mbuf发送
#endif
#ifdef HAVE_NAPATECH
NapatechPacketVars ntpv;
#endif
#ifdef HAVE_AF_XDP
AFXDPPacketVars afxdp_v;
#endif
/* A chunk of memory that a plugin can use for its packet vars. */
uint8_t plugin_v[PLUGIN_VAR_SIZE];
/** libpcap vars: shared by Pcap Live mode and Pcap File mode */
PcapPacketVars pcap_v;
};
/** The release function for packet structure and data */
void (*ReleasePacket)(struct Packet_ *); //报文释放或发送的函数指针
/** The function triggering bypass the flow in the capture method.
* Return 1 for success and 0 on error */
int (*BypassPacketsFlow)(struct Packet_ *);
/* pkt vars */
PktVar *pktvar;
/* header pointers */
EthernetHdr *ethh; //以太网头指针
/* Checksum for IP packets. */
int32_t level3_comp_csum;
/* Check sum for TCP, UDP or ICMP packets */
int32_t level4_comp_csum;
IPV4Hdr *ip4h; //ip头指针
IPV6Hdr *ip6h; //ipv6头指针
/* IPv4 and IPv6 are mutually exclusive */
union {
IPV4Vars ip4vars; //ipv4 options
struct {
IPV6Vars ip6vars;
IPV6ExtHdrs ip6eh; //ipv6扩展头
};
};
/* Can only be one of TCP, UDP, ICMP at any given time */
union {
TCPVars tcpvars; //tcp options
ICMPV4Vars icmpv4vars; //icmp 差错报文字段(不可达等)
ICMPV6Vars icmpv6vars; //icmpv6 差错报文字段
} l4vars;
#define tcpvars l4vars.tcpvars
#define icmpv4vars l4vars.icmpv4vars
#define icmpv6vars l4vars.icmpv6vars
TCPHdr *tcph; //tcp头指针
UDPHdr *udph; //udp头指针
SCTPHdr *sctph; //sctp头指针
ESPHdr *esph; //esp头指针
ICMPV4Hdr *icmpv4h; //icmpv4头指针
ICMPV6Hdr *icmpv6h; //icmpv6头指针
PPPHdr *ppph; //ppp头指针
PPPOESessionHdr *pppoesh; //pppoe会话头指针
PPPOEDiscoveryHdr *pppoedh;//pppoe 链路发现头指针
GREHdr *greh; //gre头指针
/* ptr to the payload of the packet
* with it's length. */
uint8_t *payload; //报文负载位置指针(传输层之后)
uint16_t payload_len; //报文负载长度
/* IPS action to take */
uint8_t action;
uint8_t pkt_src;
/* storage: set to pointer to heap and extended via allocation if necessary */
uint32_t pktlen; //报文总长度
uint8_t *ext_pkt; //报文起始指针
/* Incoming interface */
struct LiveDevice_ *livedev; //进网口信息
PacketAlerts alerts; //告警信息
struct Host_ *host_src;
struct Host_ *host_dst;
/** packet number in the pcap file, matches wireshark */
uint64_t pcap_cnt;
/* engine events */
PacketEngineEvents events;
AppLayerDecoderEvents *app_layer_events;
/* double linked list ptrs */
struct Packet_ *next;
struct Packet_ *prev;
/** data linktype in host order */
int datalink;
/* count decoded layers of packet : too many layers
* cause issues with performance and stability (stack exhaustion)
*/
uint8_t nb_decoded_layers;
/* enum PacketDropReason::PKT_DROP_REASON_* as uint8_t for compactness */
uint8_t drop_reason;
/* tunnel/encapsulation handling */
struct Packet_ *root; /* in case of tunnel this is a ptr
* to the 'real' packet, the one we
* need to set the verdict on --
* It should always point to the lowest
* packet in a encapsulated packet */
/* ready to set verdict counter, only set in root */
uint16_t tunnel_rtv_cnt;
/* tunnel packet ref count */
uint16_t tunnel_tpr_cnt;
/** tenant id for this packet, if any. If 0 then no tenant was assigned. */
uint32_t tenant_id;
/* The Packet pool from which this packet was allocated. Used when returning
* the packet to its owner's stack. If NULL, then allocated with malloc.
*/
struct PktPool_ *pool;
#ifdef PROFILING
PktProfiling *profile;
#endif
/* things in the packet that live beyond a reinit */
struct {
/** lock to protect access to:
* - tunnel_rtv_cnt
* - tunnel_tpr_cnt
* - nfq_v.mark
* - flags
*/
SCSpinlock tunnel_lock;
} persistent;
} Packet;
好了,suricata协议解码的源码分析就到这里了。喜欢文章内容的朋友,记得加个关注哦~~
