P4 08-ECMP

P4示例程序-08 ECMP

  • 功能

    ECMP即等价多路径路由,网络里如果存在多条到达同一目的地且代价相同的路径,就可以把流量分散到这些路径上,实现负载均衡,以提高带宽利用率。在P4交换机的实现中主要涉及哈希函数

  • 拓扑结构

拓扑结构
  • 代码

  1. p4app.json
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    {
    "p4_src": "p4src/ecmp.p4",
    "cli": true,
    "pcap_dump": true,
    "enable_log": true,
    "topology": {
    "assignment_strategy": "mixed",
    "links": [["h1", "s1"], ["h2", "s6"], ["s1", "s2"], ["s1", "s3"], ["s1", "s4"], ["s1", "s5"], ["s2", "s6"], ["s3", "s6"], ["s4", "s6"], ["s5", "s6"]],
    "hosts": {
    "h1": {
    },
    "h2": {
    }
    },
    "switches": {
    "s1": {
    "cli_input": "sw-commands/s1-commands.txt"
    },
    "s2": {
    "cli_input": "sw-commands/s2-commands.txt"
    },
    "s3": {
    "cli_input": "sw-commands/s3-commands.txt"
    },
    "s4": {
    "cli_input": "sw-commands/s4-commands.txt"
    },
    "s5": {
    "cli_input": "sw-commands/s5-commands.txt"
    },
    "s6": {
    "cli_input": "sw-commands/s6-commands.txt"
    }
    }
    }
    }
  2. Headers
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    const bit<16> TYPE_IPV4 = 0x800; // IPv4以太网类型

    typedef bit<9> egressSpec_t; // 定义出口端口类型
    typedef bit<48> macAddr_t; // 定义MAC地址类型
    typedef bit<32> ip4Addr_t; // 定义IPv4地址类型

    header ethernet_t {
    macAddr_t dstAddr; // 目的MAC地址
    macAddr_t srcAddr; // 源MAC地址
    bit<16> etherType; // 以太网类型字段
    }

    header ipv4_t {
    bit<4> version; // IP版本
    bit<4> ihl; // 首部长度
    bit<6> dscp; // 区分服务字段
    bit<2> ecn; // 显式拥塞通知
    bit<16> totalLen; // 总长度
    bit<16> identification; // 标识
    bit<3> flags; // 标志
    bit<13> fragOffset; // 分段偏移
    bit<8> ttl; // 生存时间
    bit<8> protocol; // 上层协议类型
    bit<16> hdrChecksum; // 头部校验和
    ip4Addr_t srcAddr; // 源IPv4地址
    ip4Addr_t dstAddr; // 目的IPv4地址
    }

    header tcp_t{
    bit<16> srcPort; // 源端口号
    bit<16> dstPort; // 目的端口号
    bit<32> seqNo; // 序列号
    bit<32> ackNo; // 确认号
    bit<4> dataOffset; // 数据偏移
    bit<4> res; // 保留字段
    bit<1> cwr; // CWR位
    bit<1> ece; // ECE位
    bit<1> urg; // 紧急指针有效
    bit<1> ack; // 确认位
    bit<1> psh; // 推送标志
    bit<1> rst; // 重置连接
    bit<1> syn; // 同步序列编号
    bit<1> fin; // 终止连接
    bit<16> window; // 滑动窗口大小
    bit<16> checksum; // TCP校验和
    bit<16> urgentPtr; // 紧急指针
    }

    struct metadata {
    bit<14> ecmp_hash; // ECMP哈希值
    bit<14> ecmp_group_id; // ECMP组ID
    }

    struct headers {
    ethernet_t ethernet; // 以太网头部
    ipv4_t ipv4; // IPv4头部
    tcp_t tcp; // TCP头部
    }
  3. Parser
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    parser MyParser(packet_in packet,
    out headers hdr,
    inout metadata meta,
    inout standard_metadata_t standard_metadata) {

    state start {
    transition parse_ethernet; // 初始状态转到以太网解析
    }

    state parse_ethernet {
    packet.extract(hdr.ethernet); // 提取以太网头部
    transition select(hdr.ethernet.etherType){ // 根据类型字段选择下一状态
    TYPE_IPV4: parse_ipv4; // 如果是IPv4,转到IPv4解析
    default: accept; // 否则接受包
    }
    }

    state parse_ipv4 {
    packet.extract(hdr.ipv4); // 提取IPv4头部
    transition select(hdr.ipv4.protocol){ // 根据协议字段判断下一状态
    6 : parse_tcp; // TCP协议则进入TCP解析
    default: accept; // 其他协议直接接受
    }
    }

    state parse_tcp {
    packet.extract(hdr.tcp); // 提取TCP头部
    transition accept; // 接受包
    }
    }
  4. Checksum Verification
    1
    2
    3
    4
    control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
    apply {
    }
    }
  5. Ingress Processing
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    control MyIngress(inout headers hdr,
    inout metadata meta,
    inout standard_metadata_t standard_metadata) {
    action drop() {
    mark_to_drop(standard_metadata); // 丢弃数据包
    }

    action ecmp_group(bit<14> ecmp_group_id, bit<16> num_nhops){
    hash(meta.ecmp_hash, // 对以下字段进行哈希计算
    HashAlgorithm.crc16, // 哈希算法为crc16
    (bit<1>)0, // 随机种子
    { hdr.ipv4.srcAddr,
    hdr.ipv4.dstAddr,
    hdr.tcp.srcPort,
    hdr.tcp.dstPort,
    hdr.ipv4.protocol},
    num_nhops); // 取模运算基数为下一跳数

    meta.ecmp_group_id = ecmp_group_id; // 存储ECMP组ID
    }

    action set_nhop(macAddr_t dstAddr, egressSpec_t port) {
    hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; // 源MAC地址设为原先的目的地址
    hdr.ethernet.dstAddr = dstAddr; // 设置新的目的MAC地址
    standard_metadata.egress_spec = port; // 设置转发端口
    hdr.ipv4.ttl = hdr.ipv4.ttl - 1; // TTL减1
    }

    table ecmp_group_to_nhop {
    key = {
    meta.ecmp_group_id: exact; // ECMP组ID精确匹配
    meta.ecmp_hash: exact; // 哈希值精确匹配
    }
    actions = {
    drop; // 丢弃
    set_nhop; // 设置下一跳
    }
    size = 1024; // 表大小为1024
    }

    table ipv4_lpm {
    key = {
    hdr.ipv4.dstAddr: lpm; // 目的IP地址最长前缀匹配
    }
    actions = {
    set_nhop; // 设置下一跳
    ecmp_group; // 使用ECMP
    drop; // 丢弃
    }
    size = 1024; // 表大小为1024
    default_action = drop; // 默认动作为丢弃
    }

    apply {
    if (hdr.ipv4.isValid()){ // 如果IPv4头部有效
    switch (ipv4_lpm.apply().action_run){ // 应用ipv4_lpm表并获取运行的action
    ecmp_group: {
    ecmp_group_to_nhop.apply(); // 若是ECMP则再应用映射表
    }
    }
    }
    }
    }
  6. Egress Processing
    1
    2
    3
    4
    5
    6
    control MyEgress(inout headers hdr,
    inout metadata meta,
    inout standard_metadata_t standard_metadata) {
    apply {
    }
    }
  7. Checksum Computation
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    control MyComputeChecksum(inout headers hdr, inout metadata meta) {
    apply {
    update_checksum(
    hdr.ipv4.isValid(), // 仅在IPv4有效时计算校验和
    { hdr.ipv4.version,
    hdr.ipv4.ihl,
    hdr.ipv4.dscp,
    hdr.ipv4.ecn,
    hdr.ipv4.totalLen,
    hdr.ipv4.identification,
    hdr.ipv4.flags,
    hdr.ipv4.fragOffset,
    hdr.ipv4.ttl,
    hdr.ipv4.protocol,
    hdr.ipv4.srcAddr,
    hdr.ipv4.dstAddr },
    hdr.ipv4.hdrChecksum, // 要更新的校验和字段
    HashAlgorithm.csum16); // 使用16位加法算法
    }
    }
  8. DE parser
    1
    2
    3
    4
    5
    6
    7
    control MyDeparser(packet_out packet, in headers hdr) {
    apply {
    packet.emit(hdr.ethernet); // 重新封装以太网头部
    packet.emit(hdr.ipv4); // 重新封装IPv4头部
    packet.emit(hdr.tcp); // 重新封装TCP头部
    }
    }
  9. Switch
    1
    2
    3
    4
    5
    6
    7
    8
    V1Switch(
    MyParser(),
    MyVerifyChecksum(),
    MyIngress(),
    MyEgress(),
    MyComputeChecksum(),
    MyDeparser()
    ) main;
  10. s1-commands.txt
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 交换机S1
    // 设置匹配表ipv4_lpm和匹配表ecmp_group_to_nhop的默认动作为丢包
    table_set_default ipv4_lpm drop
    table_set_default ecmp_group_to_nhop drop
    // 设置发完主机Host1的数据包直接通过1端口发出,不使用ECMP
    table_add ipv4_lpm set_nhop 10.0.1.1/32 => 00:00:0a:00:01:01 1
    // 设置发完Host2的数据包使用ECMP,ECMP的组号为1、共包含4条可选路径
    table_add ipv4_lpm ecmp_group 10.0.6.2/32 => 1 4
    // 在ECMP组1中,针对哈希结果0、1、2、3分布设置出端口为2、3、4、5
    table_add ecmp_group_to_nhop set_nhop 1 0 => 00:00:00:02:01:00 2
    table_add ecmp_group_to_nhop set_nhop 1 1 => 00:00:00:03:01:00 3
    table_add ecmp_group_to_nhop set_nhop 1 2 => 00:00:00:04:01:00 4
    table_add ecmp_group_to_nhop set_nhop 1 3 => 00:00:00:05:01:00 5
  11. s2-commands.txt
    1
    2
    3
    4
    5
    6
    7
    8
    // 交换机S2
    // 设置匹配表ipv4_lpm和匹配表ecmp_group_to_nhop的默认动作为丢包
    table_set_default ipv4_lpm drop
    table_set_default ecmp_group_to_nhop drop
    // 设置发完Host2的数据包直接通过1端口发送出去
    table_add ipv4_lpm set_nhop 10.0.1.1/32 => 00:00:00:01:02:00 1
    // 设置发完Host1的数据包直接通过2端口发送出去
    table_add ipv4_lpm set_nhop 10.0.6.2/32 => 00:00:00:06:02:00 2
  12. s3-commands.txt
    1
    2
    3
    4
    5
    6
    7
    8
    // 交换机S3
    // 设置匹配表ipv4_lpm和匹配表ecmp_group_to_nhop的默认动作为丢包
    table_set_default ipv4_lpm drop
    table_set_default ecmp_group_to_nhop drop
    // 设置发完Host2的数据包直接通过1端口发送出去
    table_add ipv4_lpm set_nhop 10.0.1.1/32 => 00:00:00:01:03:00 1
    // 设置发完Host1的数据包直接通过2端口发送出去
    table_add ipv4_lpm set_nhop 10.0.6.2/32 => 00:00:00:06:03:00 2
  13. s4-commands.txt
    1
    2
    3
    4
    5
    6
    7
    8
    // 交换机S4
    // 设置匹配表ipv4_lpm和匹配表ecmp_group_to_nhop的默认动作为丢包
    table_set_default ipv4_lpm drop
    table_set_default ecmp_group_to_nhop drop
    // 设置发完Host2的数据包直接通过1端口发送出去
    table_add ipv4_lpm set_nhop 10.0.1.1/32 => 00:00:00:01:04:00 1
    // 设置发完Host1的数据包直接通过2端口发送出去
    table_add ipv4_lpm set_nhop 10.0.6.2/32 => 00:00:00:06:04:00 2
  14. s5-commands.txt
    1
    2
    3
    4
    5
    6
    7
    8
    // 交换机S5
    // 设置匹配表ipv4_lpm和匹配表ecmp_group_to_nhop的默认动作为丢包
    table_set_default ipv4_lpm drop
    table_set_default ecmp_group_to_nhop drop
    // 设置发完Host2的数据包直接通过1端口发送出去
    table_add ipv4_lpm set_nhop 10.0.1.1/32 => 00:00:00:01:05:00 1
    // 设置发完Host1的数据包直接通过2端口发送出去
    table_add ipv4_lpm set_nhop 10.0.6.2/32 => 00:00:00:06:05:00 2
  15. s6-commands.txt
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 交换机S6
    // 设置匹配表ipv4_lpm和匹配表ecmp_group_to_nhop的默认动作为丢包
    table_set_default ipv4_lpm drop
    table_set_default ecmp_group_to_nhop drop
    // 设置发完主机Host2的数据包直接通过1端口发出,不使用ECMP
    table_add ipv4_lpm set_nhop 10.0.6.2/32 => 00:00:0a:00:06:02 1
    // 设置发完Host1的数据包使用ECMP,ECMP的组号为1、共包含4条可选路径
    table_add ipv4_lpm ecmp_group 10.0.1.0/24 => 1 4
    // 在ECMP组1中,针对哈希结果0、1、2、3分布设置出端口为2、3、4、5
    table_add ecmp_group_to_nhop set_nhop 1 0 => 00:00:00:02:06:00 2
    table_add ecmp_group_to_nhop set_nhop 1 1 => 00:00:00:03:06:00 3
    table_add ecmp_group_to_nhop set_nhop 1 2 => 00:00:00:04:06:00 4
    table_add ecmp_group_to_nhop set_nhop 1 3 => 00:00:00:05:06:00 5
  16. send.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    #!/usr/bin/env python3
    # 指定解释器为 Python 3

    import argparse # 可选模块,用于命令行参数解析(此脚本未使用)
    import sys # 用于访问命令行参数
    import socket # 用于主机名到 IP 的转换
    import random # 用于生成随机端口号
    import struct # 用于处理二进制数据(此脚本未使用)

    # 从 Scapy 导入必要的函数和协议层
    from scapy.all import sendp, get_if_list, get_if_hwaddr
    from scapy.all import Ether, IP, UDP, TCP

    def get_if():
    """
    自动获取名为 eth0 的网络接口名称。
    返回值:接口名称字符串,如 'h1-eth0'
    """
    ifs = get_if_list() # 获取所有网络接口列表
    iface = None # 默认未找到接口
    for i in ifs:
    if "eth0" in i: # 查找包含 'eth0' 的接口(适用于 Mininet 环境)
    iface = i
    break
    if not iface:
    print("Cannot find eth0 interface")
    exit(1)
    return iface

    def main():
    """
    主函数:向指定目标地址发送指定数量的随机 TCP 数据包。
    """
    if len(sys.argv) < 3:
    # 如果命令行参数数量不足,则提示用法并退出
    print('pass 2 arguments: <destination> <number_of_random_packets>')
    exit(1)

    # 获取目标地址(第一个参数)并解析为 IP 地址
    addr = socket.gethostbyname(sys.argv[1])

    # 获取网卡名称
    iface = get_if()

    print("sending on interface %s to %s" % (iface, str(addr)))

    # 发送指定数量的随机 TCP 包
    for _ in range(int(sys.argv[2])):
    # 构造以太网帧,目的地址为广播地址 ff:ff:ff:ff:ff:ff
    pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff')

    # 添加 IP 层,目标 IP 为用户输入的地址
    # 添加 TCP 层,目标端口和源端口随机生成
    pkt = pkt / IP(dst=addr) / TCP(
    dport=random.randint(5000, 60000), # 随机目的端口
    sport=random.randint(49152, 65535) # 随机源端口
    )

    # 发送数据包(通过指定接口),不显示详细信息
    sendp(pkt, iface=iface, verbose=False)

    if __name__ == '__main__':
    main()

  17. network.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    from p4utils.mininetlib.network_API import NetworkAPI

    net = NetworkAPI()

    # Network general options
    net.setLogLevel('info')

    # Network definition
    net.addP4Switch('s1', cli_input='sw-commands/s1-commands.txt')
    net.addP4Switch('s2', cli_input='sw-commands/s2-commands.txt')
    net.addP4Switch('s3', cli_input='sw-commands/s3-commands.txt')
    net.addP4Switch('s4', cli_input='sw-commands/s4-commands.txt')
    net.addP4Switch('s5', cli_input='sw-commands/s5-commands.txt')
    net.addP4Switch('s6', cli_input='sw-commands/s6-commands.txt')
    net.setP4SourceAll('p4src/ecmp.p4')

    net.addHost('h1')
    net.addHost('h2')

    net.addLink("h1", "s1")
    net.addLink("h2", "s6")
    net.addLink("s1", "s2")
    net.addLink("s1", "s3")
    net.addLink("s1", "s4")
    net.addLink("s1", "s5")
    net.addLink("s2", "s6")
    net.addLink("s3", "s6")
    net.addLink("s4", "s6")
    net.addLink("s5", "s6")

    # Assignment strategy
    net.mixed()

    # Nodes general options
    net.enablePcapDumpAll()
    net.enableLogAll()
    net.enableCli()
    net.startNetwork()
  • P4仿真

  1. 启动网络拓扑sudo p4run
启动网络拓扑
  1. 使用pingall命令测试h1,h2是否联通
使用ping命令
  1. 监控4条链路 (从s1-eth2s1-eth5) sudo tcpdump -enn -i s1-ethX
监控四条链路
  1. 再次pingall测试h1,h2之间的连接
再次pingall
  1. 在两个主机之间做 iperf 由于属于同一流的所有数据包都有相同的5元元组,因此哈希总是返回相同的索引
iperf测试

P4 08-ECMP
http://example.com/2025/08/06/P4 08-ECMP/
作者
Wsdbybyd
发布于
2025年8月6日
许可协议