用户自定义parser
在Hydra中,parser是处理网络数据包的必要步骤,parser用于将原始packet的比特流,根据自定义的解析规则转换为结构化的协议头部,便于后续进行表的匹配以及action的处理。
Hydra parser通过一组有状态的状态机来定义,起始状态为start,根据packet中的内容逐步跳转到不同的状态,提取特定的协议头部。解析器的核心主要是状态的定义以及状态之间的转换逻辑。
解析器定义
parser Parser<H>(packet_in buffer, out H parsed_hdr);
解析器的定义包含两个入参:packet_in(输入的数据包,用来从数据包中逐步提取字段)和hdr(存储输出的头部信息,保存解析后的协议头部)。其中hdr的out方向参数表示其在parser内部赋值后将值传递给调用方。
状态
Hydra解析器描述了包含一个起始状态和两个终止状态的状态机。
- start:起始状态。固定为进入parser功能块后的第一个状态。
- accept:终止状态。表示解析成功,网络数据包会进入后续control流程。
- reject:终止状态。表示解析失败,网络数据包会被上报。
除了固定的起始状态和终止状态,用户可自定义一系列中间状态,每个状态包含name和body。
- 用户自定义状态的name不可与固定状态重名,即不可为start、accept、reject。
- 通常一个状态负责提取一个协议头部,并根据条件,如协议的类型字段,来决定如何跳转到下一个状态。
解析方式
parser从第一个字节开始解析报文。它将维护报文的当前偏移量,这个偏移量是一个指向报文中的特定字节的指针。在初步extract的过程中,这个指针会持续移动并且将报文中的内容提取到hdr之中,并且将这些字段标记为有效。
以FullNAT中解析器的代码为例,示例代码如下所示,可查看API接口,了解具体函数作用。
struct headers {
ethernet_t ethernet;
l3_union_t l3;
l4_union_t l4;
}
parser MyParser(packet_in packet,
out headers hdr) {
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet, TYPE_ETH, layers_e.l2);
transition select(hdr.ethernet.eth_type) {
TYPE_IPV4 : parse_ipv4;
TYPE_IPV6 : parse_ipv6;
default : reject;
}
}
state parse_ipv6 {
packet.extract(hdr.l3.ipv6, TYPE_IPV6, layers_e.l3);
transition select(hdr.l3.ipv6.next_header) {
TYPE_TCP : parse_tcp;
TYPE_UDP : parse_udp;
default : reject;
}
}
state parse_ipv4 {
packet.extract(hdr.l3.ipv4, TYPE_IPV4, layers_e.l3);
transition select(hdr.l3.ipv4.protocol) {
TYPE_TCP : parse_tcp;
TYPE_UDP : parse_udp;
default : reject;
}
}
state parse_tcp {
packet.extract(hdr.l4.tcp, TYPE_TCP, layers_e.l4);
transition accept;
}
state parse_udp {
packet.extract(hdr.l4.udp, TYPE_UDP, layers_e.l4);
transition accept;
}
}
上述代码中,解析器的入口如下所示。
state start {
transition parse_ethernet;
}
整个解析过程从start状态开始,在parser中必须有一个start状态,如果没有start状态,在编译时会出现编译错误。在此处出于代码入口整洁的考虑将parse_ethernet作为一个新的状态,实际上start状态中也可以写解析逻辑。
其中,transition是parser状态机进行跳转的关键字,parse_ethernet是跳转到的状态。
在parse_ethernet状态里,可以看到以下内容。
state parse_ethernet {
packet.extract(hdr.ethernet, TYPE_ETH, layers_e.l2);
transition select(hdr.ethernet.eth_type) {
TYPE_IPV4 : parse_ipv4;
TYPE_IPV6 : parse_ipv6;
default : reject;
}
}
示例如下。
packet.extract(hdr.ethernet, TYPE_ETH, layers_e.l2);
调用了extract方法,从数据包中提取以太网的头部,并且将其存储到hdr.ethernet之中。这里的hdr.ethernet是header类型的变量,包含了以太网头部的各个字段。TYPE_ETH表示Ethernet的协议值,layers_e.l2表示Ethernet协议属于L2层。
transition select(hdr.ethernet.eth_type) { TYPE_IPV4 : parse_ipv4; TYPE_IPV6 : parse_ipv6; default : reject; }
transition select(hdr.ethernet.ether_type)会对hdr中的ethernet header中的eth_type进行检查。
- 如果eth_type==TYPE_IPV4 ,表示这是一个IPv4包,转移到parse_ipv4状态。
- 如果eth_type==TYPE_IPV6 ,表示这是一个IPv6包,转移到parse_ipv6状态。
- 否则会进入默认状态reject,上送该数据包。
用户自定义的报文解析header结构体内最多可设置14个报文头结构。