# vpp-pv-plugin **Repository Path**: basilguo/vpp-pv-plugin ## Basic Information - **Project Name**: vpp-pv-plugin - **Description**: Data Plane Path Validation of FC-BGP - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-02-01 - **Last Updated**: 2026-05-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # README.cn > **Author** Basil Guo > > **Date** Jul. 10, 2024 > > **Description** 一些开发中的说明 [TOC] ## 环境 - Ubuntu 22.04 - OpenSSL 3.0.2 - VPP 23.06 ```sh sudo apt install -y libjson-c-dev pkg-config sudo apt install -y libreadline-dev # libreadline-dev/jammy,now 8.1.2-1 amd64 [installed] sudo apt install -y libglib2.0-dev # this will install libglib2.0-dev:amd64 (2.72.4-0ubuntu2.3) ``` gdb 调试,忽略某个 signal:`handle SIG34 nostop noprint`。 ## 测试注意事项 1. 发现 LPM 有一个 bug,如果一个前缀属于多个 AD,那么它可能找到的 ADID 会是错的,所以尽量不要这样划分前缀,而是要上级的 AD 包含下级的 AD。 ## 分析 虽然叫做 SAVAX,但其实是 PV+SAV。现在在做的是 PV,没做 SAV。 根据要求,需要使用测试仪进行测试,所以不能修改数据包格式,而是需要由第一跳添加上所有的包格式,最后一跳直接去掉。也就是说,终端不能修改报文格式,也不做最终验证。 按照最新要求,SAV 和 PV 最后要做到一个数据包上,所以需要既要支持 PV,也要支持 SAVA-X。这样要求,我们必须换一个 Option,同时要在有 SAV 时支持 SAV,没有 SAV 时自己插入 DstExt Hdr。 我们计划 SAVAX 使用 0x3B(0b00111011,59),路径验证使用 0x37(0b00110111,55)[Internet Protocol Version 6 (IPv6) Parameters](https://www.iana.org/assignments/ipv6-parameters/ipv6-parameters.xhtml)。 由于只是验证,需要使用下面的拓扑图,所以并不是完全的需要做完。这样就存在四种情况: ![topo](./docs/images/topo.png) 1. 从 H3C 发给 x86 的是普通的 IPv6 数据包,没有添加任何 Extension Header (ExtHdr)。这时候进入时需要路径验证来添加 ExtHdr 以及(Destination Option)DstOpt,在离开的时候移除(但是如果将下行方向看作是一个地址域的时候,就不移除了)。所以在这里即相当于 R2 连接的 302 是没有使能路径验证,但是仍然属于 20 范围。为了让中间的 10、20、30、60 能继续使用,规划拓扑和前缀的时候,需要将 302 规划为 20 的前缀。如果 302 和 20 使用不一样的前缀(没有包含关系),那么就需要 302 就添加路径验证标签(但华三不搞),否则 20 会检测为不需要路径验证的流量,就不添加路径验证标记了(前缀是必须的,并不会为所有的 ipv6 流量都进行路径验证,或者要不要修改?因为这里是按照 savax 的方式来搞得)。 - Next hdr 填充为下一部分标签头,比如下一个是 ICMP(ping),那就填写 ping,如果是 UDP,那就是 UDP - Hdr ext len:长度,单位(8-octet – 1)B - Opt type:现在 0x3B,后续 0x37 - Opt len:后续长度,单位 B - Flags:0x00 for probabilistic, 0x01 for full - Hops:总共跳数。表示的是路径共有多少跳,包括 src route node, 不包括 dst route node。 - LeftHops,表示路径还有多少跳,包括 src route node, 不包括 dst route node。 - Marks Num,表示路径上当前已经添加了多少标记,最多 Hops 个,最少一个。 - Marks List,路径上 ASN List 添加的标记。每个都是 64 位,其中前 32 位是 ASN,后 32 位是 Tag/Mark/Lable/Signature。包括 src route node, 不包括 dst route node。 2. 从 H3C 发给 x86 的是携带有 SAVA-X 标签的数据包,带有 DstOpt=0x3B。此时需要共存。这个时候不需要路径验证节点添加 ExtHdr 了,但是需要修改 hdr ext len,以及注意是否有 pad 填充(简单方式就是直接跳过现有的长度,保持 pad 填充,然后继续在 Ext 最后边(下一个协议 header 之前)添加路径验证 option 以及 pad 填充),同时需要在原来 savax opt 最后添加一个 option,表示路径验证 option,添加在后边是因为这个 option 的长度可能会变化(变化是体现在跨越层级的时候,同层会直接依据 hops 在第一跳都添加好,后续只需要添加 mark 就行)。我想这个时候也别在修改 hdr ext len 了,由于 IPv6 支持至多两个 dst ext hdr,不然就再添加一个 dst opt 好了,反正这个在离开 PV 部署区域之前都会去除。20240923,经和负责人沟通,可以先这么做,后续需要在 x86 上集成 SAVA-X、PV 和 FC-BGP。 3. _不会有的情况。_ 从 H3C 发给 x86 的是携带有路径验证标签的数据包,带有 DstOpt=0x3B 和 0x37。此时直接替换 0x3B,但是因为华三不做这个程度的路径验证,他们只是根据 ACL 过滤数据包,\_所以不会有这个需求。 4. _不会有的情况_。就是两者 option 按照情况二那种混合的方式发给 x86 路由器。 ### 路径验证 路径验证必须要数据包指定路径,才能进行验证。所以就是在第一跳 src route node 上在数据包上指定路径,然后根据数据包指定的路径上节点进行添加标记。最后的 dst route node 对所有的标记进行验证。概率标记就需要指定路径长度,根据概率进行添加。 那就选取方式一:由第一跳添加完整报文格式,比如有 4 跳要添加标记,那么就加上 4 个位置的{asn: uint32, mark: uint32},一次性将报文格式填充好,这样后面节点也不需要每次都调整报文格式了,同时对 mtu 也比较友好(虽然我们也不打算管 mtu 的事情)。只是这个 4 跳如何获取,同时如何确定哪些 asn 添加呢?测试的时候非常好说,我只需要在第一跳那个地方搞一个命令设置,设置为 4 就行。对概率添加的概率也比较好获取。 数据包格式设计: ```txt 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Next Hdr | Hdr Ext Len | Opt Type | Opt Len | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Flags | Hops | LeftHops | Marks Num | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ Marks List ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` - Next Hdr,就将 IPv6 中的移到这里来就行,IPv6 的就改成 dstopt。 - Hdr Ext Len,计算的是除了第一个 8-octet 之后有多少个 8-octet,注意 autopad 问题。我们这里使用的方式不存在 autopad 问题。 - Opt Type,0x37 - Opt Len,后续有多少个 octet,不包括 Opt Type 和 Opt Len - Flags,0x00 for probabilistic, 0x01 for full - Hops,表示的是路径共有多少跳,包括 src route node, 不包括 dst route node。 - LeftHops,表示路径还有多少跳,包括 src route node, 不包括 dst route node。 - Marks Num,表示路径上当前已经添加了多少标记,最多 Hops 个,最少一个。 - Marks List,路径上 ASN List 添加的标记。每个都是 64 位,其中前 32 位是 ASN,后 32 位是 Tag/Mark/Lable/Signature。包括 src route node, 不包括 dst route node。 ### 插件命令 ```sh DBGvpp# savax ? # 使能,必须先使能,使能除了这个,还要在接口上使能。现在去使能实现还有些问题。 savax enable-disable savax enable-disable [disable] # 接口使能,是必须的,不然没用 savax interface savax interface [disable] # 策略,由于不能使用自定义包头,还是需要指定的 savax mark-policy savax mark-policy # 记着要拼写全了 savax show savax show # savax ad ancestors' info savax ancestor add savax ancestor add [ [...]] savax ancestor del savax ancestor del [ [...]] # savax prefix add 10 10:: 64 savax prefix savax prefix
# savax tag renew 20 10 0xdeadbeef 0xdeadbeef savax tag savax tag renew # 可以指定多个,使用level进行区分,level从0-4,其实可以到7,不过不建议那么多层级 # 这里必须和level挂钩,所以就写在一起吧。 # 路径必须是完整的,从头到尾,一个都不能少 savax path-validation savax path-validation level adid hops ``` ### Keygen 在`acs`目录中,启动服务器。是单独的,和 VPP 没有关系,但是 VPP-savax 插件需要配置连接到这个,由其下发前缀和 mark/tag/key。 Key 生成规则是:每个 AD 具备一个 ACS 服务器,且每个 AD 只和自己同父的 AD 生成 Key 下发给 AER。 ### 标识 mark 分析 以下分析太麻烦,废弃。我决定修改,请参考[标识 mark 分析](docs/mark-analysis.cn.md)。 ```txt +--------------------------------+ +--------------+ +--------------+ | +----+ +----+ +----+ | | | | | | | 11 | --- | 12 | --- | 13 | | --- | | --- | | | +----+ +----+ +----+ | | | | | | 1 | | 2 | | 3 | +--------------------------------+ +--------------+ +--------------+ ``` | | VPP 11 | VPP 12 | VPP 13, VPP 1 | VPP 2 | VPP 3 | | ---------- | ------------ | --------------- | ------------------- | ------------- | ---------- | | <1, 2> | no | no | add(<1,2>) | delete | no | | <1, 3> | no | no | add(<1,3>) | append(<2,3>) | delete | | | | | | | | | <1, 11> | no | no | no | no | no | | <12, 1> | no | no | no | no | no | | <1, 13> | no | no | no | no | no | | | | | | | | | <11, 12> | add(<11,12>) | delete | no | no | no | | <11, 13> | add(<11,13>) | append(<12,13>) | delete | no | no | | | | | | | | | <3, 11> | delete | append(<12,11>) | delete,add(<13,11>) | append(<2,1>) | add(<3,1>) | | <11, 3> | add(<11,1>) | append(<12,1>) | delete,add(<1,3>) | append(<2,3>) | delete | 现在只分析 full-mark 的,one-mark 的需要路径配合,那个更加麻烦。另外,这个只能允许一个 VPP 最多处于两层 AD 之上,否则分析起来太麻烦了。源和目的节点或许会简单些,但中间节点分析特别麻烦。 - src 和 dst 相同,啥也不做。 - 如果自己是源 S,就看目的 D 在哪里。 - 如果同父(<1, 2>, <1, 3>, <11, 12>, <11, 13>),那么就添加(S, D); - 如果源父与目的同父(<11, 3>),那么就添加(S, S 父); - 如果源与目的父同父(<3, 11>),那么就添加(S, D 父); - 如果源是目的父(<1, 11>, <1, 13>),那么就啥也不做; - 如果目的是源父(<12, 1>),那么就啥也不做; - 如果自己是目的 D,就看源 S 在哪里。 - 如果同父(<1, 2>, <1, 3>, <11, 12>, <11, 13>),那么就验证删除所有 mark; - 如果源父与目的同父(<11, 3>),那么就验证删除所有 mark(<1, 3>); - 如果源与目的父同父(<3, 11>),那么就验证删除所有 mark(<12, 11>, <13, 11>);这里是因为下行目的 AD 是确定的; - 如果源是目的父(<1, 11>, <1, 13>),那么就啥也不做; - 如果目的是源父(<12, 1>),那么就啥也不做; - 如果是中间节点 M,那么需要同时看源 S 和目的 D 的关系。(也看更通用的吧) - 如果源目的同父(<1, 2>, <1, 3>, <11, 12>, <11, 13>),那么就: - 判断是否在路径上,就看自己 M 是否与 S、D 同父; - 如果在路径上(VPP2 对<1, 3>,VPP12 对<11, 13>),那么就添加(M, D); - 如果不在路径上,就啥也不做; - 如果源父与目的同父(<11, 3>),那么就: - 如果 M 与目的父同父(VPP2),使用(M,D); - 如果 M 与源同父(VPP12,VPP13),使用(M, S 父(M 父)); - 如果源与目的父同父(<3, 11>),那么就: - 如果 M 与目的同父(VPP12,VPP13),使用(M,D); - 如果 M 与源同父(VPP2),使用(M,D 父(M 父)) - 如果源是目的父(<1, 11>, <1, 13>),那么就啥也不做; - 如果目的是源父(<12, 1>),那么就啥也不做; - 更加通用一些,假设路径 AD 为(`{1[11(111-113-112)]-[13]-[12]}-{2}-{3[32]-[33]-[31(312-313-311)]}`,自己大致能对称还原吧),以下 VPP 只有最底层的 VPP,即 VPP111,VPP112,VPP113(VPP11),VPP13,VPP12(VPP1),VPP2,VPP32(VPP3),VPP33,VPP312(VPP31),VPP313,VPP311。 - 只看中间节点 M,如果高/低一级路径在此未尽,则需 append,如果高/低一级路径在此已尽,则需 delete; - 111=>3 通信(还不够通用) - 如果 M 与目的同父(VPP2),追加使用(M,D) - 如果 M 与源祖先更近(VPP113, VPP112, VPP13, VPP12) - 验证(VPP112, VPP12)使用(M 同阶,M 父) - 添加(VPP113, VPP13)使用(M,M 父) - 111=>311 通信(最通用的了): - 如果 M 与目的同父(VPP312, VPP313),追加使用(M,D) - 如果 M 与源祖先更近(VPP113, VPP112, VPP13, VPP12) - 验证(VPP112, VPP12)使用(M,M 父) - 添加(VPP113, VPP13)使用(M,M 父) - 如果 M 与目的祖先更近或相同(VPP1, VPP2, VPP32, VPP33, VPP312, VPP313) - 添加(VPP1, VPP2, VPP32, VPP33, VPP312, VPP313)使用(M, M 同阶 D 祖先) - 验证(VPP3, VPP31, VPP311)使用(M,M 同阶 D 祖先) - 更加通用一些,假设路径 AD 为(`{1[11(111-113-112)]-[13]-[12]}-{2}-{3[32]-[33]-[31(312-313-311)]}`,自己大致能对称还原吧),以下 VPP 只有最底层的 VPP,即 VPP111,VPP112,VPP113(VPP11),VPP13,VPP12(VPP1),VPP2,VPP32(VPP3),VPP33,VPP312(VPP31),VPP313,VPP311。 - 111=>311 通信(最通用的了): - node is src(S): - SD 同父,添加(S, D) - S 是 D 祖,啥也不做 - D 是 S 祖,啥也不做 - D 在 S 父外,添加(S, S 父) - D 在 S 父内,添加(S, S 同阶 D 祖) - node is dst(D): - SD 同父,验证删除(S, D) - S 是 D 祖,啥也不做 - D 是 S 祖,啥也不做 - D 在 S 父外,验证删除(S, S 父) - D 在 S 父内,验证删除(S, S 同阶 D 祖) - node is intermediate(M): 只看中间节点 M,如果高/低一级路径在此未尽,则需 append,如果高/低一级路径在此已尽,则需 delete: - M 与 S 祖更近: - 追加使用(M, M 父) - 验证使用(M, M 父) - 添加使用(M 父, M 父父),如无 M 父父(M 父即最高),则使用最高 D 祖 - M 与 D 祖更近: - 追加使用(M, M 同阶 D 祖) - 验证使用(M, M 同阶 D 祖) - 添加使用(M 子, M 同阶 D 祖子) - 这里分析仍然有缺憾,就是当穿越一个有小 AD 的 AD 时,可能会出岔子。 ## 测试 启动 VPP,然后配置路由。 路由配置完成后,除了使能全局`savax enable-disable`,还需要配置`savax address-domain`以及给各个接口使能:`savax interface eth1 role ingress`,`savax interface eth2 role ingress`,ingress 和 egress 应该都行,都可以让接口使能。 发包 sender.py 程序,在使用时,使用 FULL PATH 时,指定 aslist 不需要包括发包节点,即发包节点不会添加,也不包括目的节点,即目的节点也不会添加,只是计算中间节点。 在测试时,使用`sender.py`发送数据包,使用`sniffer.py`接收数据包,当然或许使用`wireshark`或者`tcpdump`更好用一些。 如果要在 VPP 上查看,使用以下命令: ```sh clear trace trace add dpdk-input 10 show trace ``` ### ACS - AER - VPP ```txt ACS1 --- ACS2 \ / \ / AER | | VPP ``` 一个典型的 topo 如上图所示,ACS 整理 Ancestor-info、Prefix-info 以及 Key-info,发送给 AER,然后由 AER 下发给 VPP。之所以提取 AER,是因为可能多个 ACS 都要给同一个 AD 内部的多个 VPP 下发,所以 ACS 应该不见得和 VPP 位于同一台机器上。与其如此,不如提取一个 AER,这是一个 AD 中只有一个的。ACS 虽然也是一个 AD 只有一个,但是一个 VPP 可能位于多个层次。为了后续的扩展,提取了一个 AER。但是目前还是一个 VPP 对应一个 AER。(这个描述也不见得多正确) 如果自定义类似 worker thread,可以使用如下配置以及`VLIB_REGISTER_THREAD`,但不知道为什么不太好用,编译报错。[VPP 创建自己的 work 线程\_vpp 线程个数-CSDN 博客](https://blog.csdn.net/sjin_1314/article/details/102953044) 我想还是先通过命令行来实现下发吧,后续可以通过 API 来做。添加命令行如下: - `savax prefix [add | del]
` - `savax tag renew ` 使用方式就是`sudo vppctl `。 > 错误的尝试:从 VPP 中,无法使用 Linux 的 connect 等 socket 函数,但是 VNET 应该有自己的,插件中可搜索`socket_init`,在``中。 这里 ACS 是正常的协商密钥、下发前缀、下发密钥的服务器,AER 则是接收 ACS 下发的服务器,VPP 和 AER 之间通过 fifo 进行通信。这是因为直接使用 Linux 的 SOCKET API 无法与 ACS 建立连接。 ACS 下发前缀和地址域信息时,只下发自己层级的 在启动时,需要的顺序是: 1. 启动 VPP,配置各个接口地址,使得 ACS 能够监听到地址; 2. 启动 ACS,同 peer 和 AER 建联; 3. 启动 AER,获取 ACS 下发的前缀和 tag; 4. 启用 VPP 的 fifo 功能; 5. 使能各个接口。 TODO: 后续需要将 AER 这个层级去掉,直接让 VPP 和 ACS 通过 fifo 通信,不过保留也行,这样 AER 就是路由器 VPP 的控制面,所有的都通过 AER 转发一下,当有多个 AER 的时候,基本不用怎么调整。 ## ACS - AER 信息只更新自己同级的,以及自己和上级的,不要更新与下级的,主要是对 key info。 ### header ```txt 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | version | type | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | action | entry size | reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` - type:1 for ancestor,2 for prefix,3 for key. - length:in octet, including version and type. - action:1 for add,2 for del. - entry size:有多少个 info 条目,最好最多不要超过 10 条,超过了最好分包发送 ### ancestor info ```txt 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | level | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ ancestor adid list ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ### prefix info ```txt # prefix list data 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+ | prefixlen | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | prefix | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # prefix list 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | info number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | adid | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ prefix list ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ### key info ```txt 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | src adid | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | dst adid | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | src to dst key | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | dst to src key | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## TODO - [ ] 解决每次断掉 aer 之后,acs 会失败。大概率 acs 关闭之后,其它 acs 也会收到影响。 - [ ] 【低优先级】添加使用 API 而不是直接使用命令行。 ## CHANGELOG - v0.2.0:更新 docs/flat-pv.cn.md 文档,以及修改代码为层次化方式。 - v0.1.3:更新 docs/flat-pv.cn.md 文档,同时更新一些命令行显示问题。 - v0.1.2:解决功能开启关闭,以及两个 tag 移动问题 - [x] `savax enable-disable disable`,执行之后,接口没有关闭能力。 - v0.1.1:正常向普通数据包中添加及验证标记,只是还不是两个 key 同时生效模式。测试可以参考 docs/flat-pv.cn.md 文档。 - [x] 全量包标记。对于所有的应该添加标记的数据包都要添加标记。 - [x] 概率包标记。对于一条路径上的每个数据包都只添加一个标记。 - [x] 不使用特殊的包,为了使用测试仪进行测试,要支持普通的数据包。这里需要设计的是,40-20-10-30-50 这个拓扑,需要 20 主动去添加 dstopt,10 也要添加 mark,而 30 要验证 marks 并移除 dstopt,实现起来类似 savax,所以这里只有两跳,而不是三跳。这里需要一个标记还有多少跳的命令,这个命令应该是将一个节点标记为源,只是这样就一定是测试用的了,感觉不是特别好。虽然可以通过源地址判断出来是否是源端,但最重要的是,中间会经过多少跳肯定拿不到了。 - v0.1.0:可以在数据包添加正确的标记,而且 keygen/keynegotiation 也正常。测试可以参考 docs/flat-pv.cn.md 文档。 - v0.0.2:在数据包上正确添加标记,而且 ping 功能应该也正常,sender.py 也可以发送正确的 option 长度以及较为明确的提示。 - v0.0.1: 这次表示对以前的发版说明。之前版本只是实现了对所有经过的数据包都添加一个 tag option ext 的功能。后续需要实现的