这里的网关 ping 指的是对虚拟网络网关发包,确认网关的数据面能够符合预期地转发网络包。
在部署、升级网关的时候,需要对网关进行 ping 测试,以确认网关的数据面符合预期地转发网络包。目前,发的 ping 包是 tcp SYN 包。
然而,存在一个网关跟业务节点混部的场景,导致网关的数据面不知道该将 SYN 包转发出去,还是放行到内核协议栈。
近来,研究了一下解决办法:
网关判断 magic number 是不是网关 ping 包:
经过 POC 验证,方法 1 不可行;因为取决于内核协议栈的行为,可能直接丢包,也有可能回包。
经过 POC 验证,方法 2 可行。
因为需要存放 MAGIC,所以需要选择能支持值为 4 个字节长度的 option。
快速了解了一下 TCP options,看到 TCP timestamps option[1] 符合需求,就它了。
使用 gopacket[2] 发包时,将 MAGIC 写入该 TCP option 即可:
const MAGIC = 0xdeadbeef
optData := make([]byte, 8)
binary.BigEndian.PutUint32(optData[:4], MAGIC)
tcpLayer := layers.TCP{
// ...
Options: []layers.TCPOption{
{OptionType: layers.TCPOptionKindTimestamps, OptionLength: 10, OptionData: optData},
},
}
因为是 tcp SYN 包,所以在判断协议、SYN 等之后,就需要做如下处理:
#define MAGIC 0xdeadbeef
#define MAGIC_PASS 0xddadbeef
struct tcp_option_timestamps {
u8 type;
u8 len;
u32 magic;
u32 pad;
} __packed;
static __always_inline bool
pass_to_kernel(struct iphdr *iph) {
// ...
struct tcphdr *tcph = (typeof(tcph)) ((void *) iph + (iph->ihl << 2));
// ...
struct tcp_option_timestamps *topt = (typeof(topt)) (tcph + 1);
if (topt->magic == MAGIC_PASS)
return true;
if (topt->magic != MAGIC)
return false;
// Update tcp checksum.
u32 check = (u32) tcph->check;
check += (u32) bpf_htons(0x0100);
tcp->check = (u16) (check + (check>=0xFFFF));
topt->magic--;
return false;
}
如此,网关便可判断出一个 ping 包该放行到内核协议栈还是该转发走了。
通过将 MAGIC 保存到 TCP timestamps option 的方式,巧妙地让网关正确地判断出 ping 包、并正确地处理 ping 包。
以此,更好地支持网关跟业务节点混部的场景。毕竟,得降本增效嘛。
TCP timestamps option: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_timestamps
[2]gopacket: https://pkg.go.dev/github.com/google/gopacket@v1.1.19/layers#TCP